mustard-claude 3.1.24 → 3.1.26
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/bin/mustard.js +0 -0
- package/package.json +59 -59
- package/templates/CLAUDE.md +4 -0
- package/templates/commands/mustard/scan/SKILL.md +21 -3
- package/templates/scripts/_fence-languages.json +57 -0
- package/templates/scripts/_skill-meta.json +0 -12
- package/templates/scripts/migrate-skill-paths.js +190 -0
- package/templates/scripts/skill-generator.js +553 -1441
- package/templates/scripts/skill-validate.js +199 -0
- package/templates/skill-templates/dto-conventions.examples.md.tmpl +0 -15
- package/templates/skill-templates/dto-conventions.skill.md.tmpl +0 -37
- package/templates/skill-templates/entity-creation.examples.md.tmpl +0 -19
- package/templates/skill-templates/entity-creation.skill.md.tmpl +0 -41
- package/templates/skill-templates/enum-placement.examples.md.tmpl +0 -15
- package/templates/skill-templates/enum-placement.skill.md.tmpl +0 -33
- package/templates/skill-templates/module-registration.examples.md.tmpl +0 -15
- package/templates/skill-templates/module-registration.skill.md.tmpl +0 -36
- package/templates/skill-templates/navigation-pattern.examples.md.tmpl +0 -9
- package/templates/skill-templates/navigation-pattern.skill.md.tmpl +0 -36
- package/templates/skill-templates/repository-pattern.examples.md.tmpl +0 -9
- package/templates/skill-templates/repository-pattern.skill.md.tmpl +0 -37
- package/templates/skill-templates/route-conventions.examples.md.tmpl +0 -15
- package/templates/skill-templates/route-conventions.skill.md.tmpl +0 -34
- package/templates/skill-templates/service-pattern.examples.md.tmpl +0 -15
- package/templates/skill-templates/service-pattern.skill.md.tmpl +0 -37
- package/templates/skill-templates/state-management.examples.md.tmpl +0 -9
- package/templates/skill-templates/state-management.skill.md.tmpl +0 -36
package/bin/mustard.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mustard-claude",
|
|
3
|
-
"version": "3.1.
|
|
4
|
-
"description": "Framework-agnostic CLI for Claude Code project setup",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"mustard": "./bin/mustard.js"
|
|
8
|
-
},
|
|
9
|
-
"main": "dist/cli.js",
|
|
10
|
-
"types": "dist/cli.d.ts",
|
|
11
|
-
"files": [
|
|
12
|
-
"bin/",
|
|
13
|
-
"dist/",
|
|
14
|
-
"templates/"
|
|
15
|
-
],
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
}
|
|
59
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "mustard-claude",
|
|
3
|
+
"version": "3.1.26",
|
|
4
|
+
"description": "Framework-agnostic CLI for Claude Code project setup",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mustard": "./bin/mustard.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "dist/cli.js",
|
|
10
|
+
"types": "dist/cli.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"dist/",
|
|
14
|
+
"templates/"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node bin/mustard.js",
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"clean": "rimraf dist",
|
|
20
|
+
"typecheck": "tsc --noEmit",
|
|
21
|
+
"test": "node --test",
|
|
22
|
+
"release": "npm version patch && npm run build && npm publish"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"claude",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"ai",
|
|
28
|
+
"cli",
|
|
29
|
+
"scaffold"
|
|
30
|
+
],
|
|
31
|
+
"author": "rubensrpj",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/rubensrpj/mustard.git"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/rubensrpj/mustard#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/rubensrpj/mustard/issues"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"chalk": "^5.3.0",
|
|
46
|
+
"commander": "^12.1.0",
|
|
47
|
+
"inquirer": "^9.2.15",
|
|
48
|
+
"ora": "^8.0.1"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/inquirer": "^9.0.9",
|
|
55
|
+
"@types/node": "^25.2.1",
|
|
56
|
+
"rimraf": "^6.1.2",
|
|
57
|
+
"typescript": "^5.9.3"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/templates/CLAUDE.md
CHANGED
|
@@ -46,6 +46,10 @@ node scripts/sync-detect.js --no-cache
|
|
|
46
46
|
# Entity registry generation
|
|
47
47
|
node scripts/sync-registry.js
|
|
48
48
|
node scripts/sync-registry.js --force
|
|
49
|
+
|
|
50
|
+
# Skill validation (runs automatically at end of skill-generator.js; also callable standalone)
|
|
51
|
+
node scripts/skill-validate.js
|
|
52
|
+
node scripts/skill-validate.js --json
|
|
49
53
|
```
|
|
50
54
|
|
|
51
55
|
## Guards
|
|
@@ -4,7 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
## Trigger
|
|
6
6
|
|
|
7
|
-
`/scan
|
|
7
|
+
`/scan`, `/scan <subproject>`, `/scan --force`, `/scan <subproject> --force`
|
|
8
|
+
|
|
9
|
+
## Flags
|
|
10
|
+
|
|
11
|
+
- `--force` — descarta tudo o que é `<!-- mustard:generated -->` e regera do zero. Semântica:
|
|
12
|
+
- Ignora o incremental skip do Step 1/C: **todos** os subprojetos são reprocessados, independente de hash match ou `gitDirty`.
|
|
13
|
+
- Bypassa o fast-path de §2.6 (Bootstrap): sempre regenera `.claude/CLAUDE.md` e afins.
|
|
14
|
+
- Repassa "FORCE MODE" aos Task agents do Step 3 (eles apagam `{subproject}/.claude/skills/*/` com header `mustard:generated` antes de regerar).
|
|
15
|
+
- Roda sempre os dois comandos de §4.7 (`sync-registry.js --force` + `skill-generator.js --force`), mesmo com registry já v4.0.
|
|
16
|
+
- Skills sem o header `mustard:generated` (user-authored) são **preservadas**.
|
|
8
17
|
|
|
9
18
|
## Execution Model
|
|
10
19
|
|
|
@@ -49,6 +58,7 @@ Parse JSON output → list of `{ name, path, role, agent, stackSummary, gitDirty
|
|
|
49
58
|
4. **Hash mismatch OR gitDirty** → include in agent launch list (dirty files indicate changes the previous scan may not have captured)
|
|
50
59
|
5. If ALL subprojects can be skipped → skip to step 4 (Update CLAUDE.md) + step 5 (Compile) directly
|
|
51
60
|
6. **No old cache** → scan ALL subprojects (first run)
|
|
61
|
+
7. **`--force` ativo** → ignore tudo acima: marque **todos** os subprojetos como `needs-rescan`, não compare hashes, não pule nada.
|
|
52
62
|
|
|
53
63
|
**Module-level incremental** (when subproject hash changed):
|
|
54
64
|
- Compare `moduleHashes[subproject][module]` with cached values
|
|
@@ -98,7 +108,7 @@ If `.claude/docs/` exists and contains `.md` files:
|
|
|
98
108
|
|
|
99
109
|
### 2.6. Bootstrap (if needed)
|
|
100
110
|
|
|
101
|
-
**Fast-path**: If root `CLAUDE.md` exists AND `.claude/entity-registry.json` exists → skip to step 3 (Launch Agents).
|
|
111
|
+
**Fast-path**: If root `CLAUDE.md` exists AND `.claude/entity-registry.json` exists AND `--force` is **not** active → skip to step 3 (Launch Agents).
|
|
102
112
|
Bootstrap only runs on first scan or when foundational files are missing.
|
|
103
113
|
|
|
104
114
|
Otherwise create foundational files:
|
|
@@ -202,6 +212,12 @@ Path: {path}
|
|
|
202
212
|
Role: {role}
|
|
203
213
|
Stack: {stackSummary}
|
|
204
214
|
|
|
215
|
+
FORCE MODE (only when /scan was invoked with --force):
|
|
216
|
+
- Before generating skills, scan {path}/.claude/skills/ and delete every subdirectory
|
|
217
|
+
whose SKILL.md contains "<!-- mustard:generated" (preserve user-authored skills that
|
|
218
|
+
lack that marker).
|
|
219
|
+
- Also delete any pre-existing _backup/ under {path}/.claude/commands/ to avoid stacking stale backups.
|
|
220
|
+
|
|
205
221
|
Tasks:
|
|
206
222
|
1. Read existing knowledge from {path}/.claude/commands/ and {path}/CLAUDE.md
|
|
207
223
|
2. Backup generated files to {path}/.claude/commands/_backup/
|
|
@@ -330,6 +346,8 @@ Mark all with `<!-- mustard:generated -->`. Overwrite on next scan.
|
|
|
330
346
|
|
|
331
347
|
After agent-generated skills (4.6), run the registry-based skill generator to create structural pattern skills from `_patterns`:
|
|
332
348
|
|
|
349
|
+
> With `--force`, **always** run both commands — even if `entity-registry.json` already exists at v4.0.
|
|
350
|
+
|
|
333
351
|
```bash
|
|
334
352
|
node .claude/scripts/sync-registry.js --force
|
|
335
353
|
node .claude/scripts/skill-generator.js --force
|
|
@@ -349,7 +367,7 @@ These skills are derived from **detected patterns** (not hardcoded). They comple
|
|
|
349
367
|
**Skip conditions:**
|
|
350
368
|
- `entity-registry.json` version < 4.0 → skip (registry not populated)
|
|
351
369
|
- `skill-generator.js` not present → skip
|
|
352
|
-
- Pattern skill already exists and was NOT generated by mustard → skip (user-edited)
|
|
370
|
+
- Pattern skill already exists and was NOT generated by mustard → skip (user-edited, unless `--force` — but `skill-generator.js` already preserves user-authored skills by checking the `mustard:generated` header)
|
|
353
371
|
|
|
354
372
|
### 4. Update CLAUDE.md files
|
|
355
373
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
".cs": "csharp",
|
|
3
|
+
".ts": "typescript",
|
|
4
|
+
".tsx": "typescript",
|
|
5
|
+
".js": "javascript",
|
|
6
|
+
".jsx": "javascript",
|
|
7
|
+
".mjs": "javascript",
|
|
8
|
+
".dart": "dart",
|
|
9
|
+
".py": "python",
|
|
10
|
+
".java": "java",
|
|
11
|
+
".go": "go",
|
|
12
|
+
".rs": "rust",
|
|
13
|
+
".rb": "ruby",
|
|
14
|
+
".php": "php",
|
|
15
|
+
".kt": "kotlin",
|
|
16
|
+
".kts": "kotlin",
|
|
17
|
+
".ex": "elixir",
|
|
18
|
+
".exs": "elixir",
|
|
19
|
+
".swift": "swift",
|
|
20
|
+
".elm": "elm",
|
|
21
|
+
".zig": "zig",
|
|
22
|
+
".cr": "crystal",
|
|
23
|
+
".gleam": "gleam",
|
|
24
|
+
".erl": "erlang",
|
|
25
|
+
".hrl": "erlang",
|
|
26
|
+
".scala": "scala",
|
|
27
|
+
".clj": "clojure",
|
|
28
|
+
".cljs": "clojure",
|
|
29
|
+
".hs": "haskell",
|
|
30
|
+
".ml": "ocaml",
|
|
31
|
+
".mli": "ocaml",
|
|
32
|
+
".lua": "lua",
|
|
33
|
+
".nim": "nim",
|
|
34
|
+
".v": "v",
|
|
35
|
+
".rkt": "racket",
|
|
36
|
+
".jl": "julia",
|
|
37
|
+
".r": "r",
|
|
38
|
+
".R": "r",
|
|
39
|
+
".sh": "bash",
|
|
40
|
+
".bash": "bash",
|
|
41
|
+
".zsh": "bash",
|
|
42
|
+
".ps1": "powershell",
|
|
43
|
+
".sql": "sql",
|
|
44
|
+
".proto": "protobuf",
|
|
45
|
+
".tf": "terraform",
|
|
46
|
+
".yaml": "yaml",
|
|
47
|
+
".yml": "yaml",
|
|
48
|
+
".toml": "toml",
|
|
49
|
+
".json": "json",
|
|
50
|
+
".xml": "xml",
|
|
51
|
+
".html": "html",
|
|
52
|
+
".css": "css",
|
|
53
|
+
".scss": "scss",
|
|
54
|
+
".sass": "sass",
|
|
55
|
+
".vue": "vue",
|
|
56
|
+
".svelte":"svelte"
|
|
57
|
+
}
|
|
@@ -1,16 +1,4 @@
|
|
|
1
1
|
{
|
|
2
|
-
"stacks": {
|
|
3
|
-
"dotnet": { "lang": "csharp", "label": ".NET", "signals": { "exts": [".csproj", ".sln"], "files": [] } },
|
|
4
|
-
"typescript": { "lang": "typescript", "label": "TypeScript/Node.js", "signals": { "exts": [], "files": ["package.json", "tsconfig.json"] } },
|
|
5
|
-
"dart": { "lang": "dart", "label": "Flutter/Dart", "signals": { "exts": [], "files": ["pubspec.yaml"] } },
|
|
6
|
-
"php": { "lang": "php", "label": "Laravel/PHP", "signals": { "exts": [], "files": ["composer.json", "artisan"] } },
|
|
7
|
-
"python": { "lang": "python", "label": "Python", "signals": { "exts": [], "files": ["pyproject.toml", "manage.py", "setup.py", "requirements.txt"] } },
|
|
8
|
-
"java": { "lang": "java", "label": "Spring/Java", "signals": { "exts": [], "files": ["pom.xml", "build.gradle", "build.gradle.kts"] } },
|
|
9
|
-
"go": { "lang": "go", "label": "Go", "signals": { "exts": [], "files": ["go.mod"] } },
|
|
10
|
-
"rust": { "lang": "rust", "label": "Rust", "signals": { "exts": [], "files": ["Cargo.toml"] } },
|
|
11
|
-
"kotlin": { "lang": "kotlin", "label": "Kotlin/JVM", "signals": { "exts": [".kt"], "files": ["build.gradle.kts"] } },
|
|
12
|
-
"elixir": { "lang": "elixir", "label": "Elixir/Phoenix", "signals": { "exts": [".ex", ".exs"], "files": ["mix.exs"] } }
|
|
13
|
-
},
|
|
14
2
|
"roles": {
|
|
15
3
|
"api": "backend",
|
|
16
4
|
"ui": "frontend",
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* migrate-skill-paths.js
|
|
6
|
+
*
|
|
7
|
+
* One-shot migration helper for target projects that still have role-prefixed
|
|
8
|
+
* pattern skills (`frontend-*`, `backend-*`, `general-*`, plus any other agent
|
|
9
|
+
* prefix from `.claude/.detect-cache.json`) duplicated inside each
|
|
10
|
+
* `{subproject}/.claude/skills/`. Moves them to ROOT `{project}/.claude/skills/`
|
|
11
|
+
* (the current output location of `skill-generator.js`) and deletes the
|
|
12
|
+
* now-empty copies.
|
|
13
|
+
*
|
|
14
|
+
* SAFE BY DEFAULT: runs in dry-run unless `--apply` is passed.
|
|
15
|
+
*
|
|
16
|
+
* Usage (inside a Mustard-initialized project, NOT Mustard itself):
|
|
17
|
+
* node .claude/scripts/migrate-skill-paths.js # dry-run, prints plan
|
|
18
|
+
* node .claude/scripts/migrate-skill-paths.js --apply # actually move files
|
|
19
|
+
*
|
|
20
|
+
* Algorithm:
|
|
21
|
+
* 1. Read `.claude/.detect-cache.json` → list of subprojects & their agent prefixes.
|
|
22
|
+
* 2. For each subproject, scan `{sub}/.claude/skills/` for folders whose name
|
|
23
|
+
* begins with one of the known agent prefixes (frontend-, backend-, general-,
|
|
24
|
+
* api-, app-, database-, mobile-, …) — those are role skills in the wrong place.
|
|
25
|
+
* 3. For each such folder:
|
|
26
|
+
* a. If ROOT/.claude/skills/{folder} already exists with identical content
|
|
27
|
+
* → just delete the duplicate.
|
|
28
|
+
* b. If ROOT missing or different → move the folder to ROOT (prefer the
|
|
29
|
+
* freshest mtime if there is already one at ROOT).
|
|
30
|
+
* 4. Log every action. On --apply, perform the FS changes; otherwise just log.
|
|
31
|
+
*
|
|
32
|
+
* Non-goals:
|
|
33
|
+
* - Does NOT touch subproject-short prefixed skills (e.g. `admin-auth-guard`,
|
|
34
|
+
* `api-endpoint-wiring`) — those belong in their subproject.
|
|
35
|
+
* - Does NOT run skill-generator.js — user does that separately post-migration.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const path = require('path');
|
|
40
|
+
|
|
41
|
+
const args = process.argv.slice(2);
|
|
42
|
+
const APPLY = args.includes('--apply');
|
|
43
|
+
const TARGET_ROOT = (() => {
|
|
44
|
+
const idx = args.indexOf('--root');
|
|
45
|
+
if (idx !== -1 && args[idx + 1]) return path.resolve(args[idx + 1]);
|
|
46
|
+
return process.cwd();
|
|
47
|
+
})();
|
|
48
|
+
|
|
49
|
+
const DETECT_CACHE = path.join(TARGET_ROOT, '.claude', '.detect-cache.json');
|
|
50
|
+
const ROOT_SKILLS = path.join(TARGET_ROOT, '.claude', 'skills');
|
|
51
|
+
|
|
52
|
+
function readJsonSafe(p) {
|
|
53
|
+
try { return JSON.parse(fs.readFileSync(p, 'utf-8')); } catch { return null; }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function hasSameContent(a, b) {
|
|
57
|
+
try {
|
|
58
|
+
return fs.readFileSync(a).equals(fs.readFileSync(b));
|
|
59
|
+
} catch { return false; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function folderChecksum(dir) {
|
|
63
|
+
// Cheap signature: sorted list of relative paths + SKILL.md first-300 bytes.
|
|
64
|
+
try {
|
|
65
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
66
|
+
.flatMap(e => e.isDirectory()
|
|
67
|
+
? fs.readdirSync(path.join(dir, e.name), { withFileTypes: true }).map(f => `${e.name}/${f.name}`)
|
|
68
|
+
: [e.name]
|
|
69
|
+
).sort().join('|');
|
|
70
|
+
const skillMd = path.join(dir, 'SKILL.md');
|
|
71
|
+
const head = fs.existsSync(skillMd) ? fs.readFileSync(skillMd).slice(0, 300).toString('utf-8') : '';
|
|
72
|
+
return `${entries}\n${head}`;
|
|
73
|
+
} catch { return ''; }
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function main() {
|
|
77
|
+
const cache = readJsonSafe(DETECT_CACHE);
|
|
78
|
+
if (!cache) {
|
|
79
|
+
console.error(`No .detect-cache.json at ${DETECT_CACHE}. Run: node .claude/scripts/sync-detect.js`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const subs = cache.subprojects || [];
|
|
84
|
+
|
|
85
|
+
// A folder is "role-shared" (belongs at ROOT) only if its name prefix matches
|
|
86
|
+
// THIS subproject's agent and the same agent occurs in 2+ subprojects. Skills
|
|
87
|
+
// whose prefix is the subproject's unique short-name stay per-subproject.
|
|
88
|
+
const agentCount = new Map();
|
|
89
|
+
for (const s of subs) {
|
|
90
|
+
const a = s.agent || s.role || 'general';
|
|
91
|
+
agentCount.set(a, (agentCount.get(a) || 0) + 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const actions = []; // { type, from, to? }
|
|
95
|
+
|
|
96
|
+
for (const sub of subs) {
|
|
97
|
+
const subSkills = path.join(TARGET_ROOT, sub.path, '.claude', 'skills');
|
|
98
|
+
if (!fs.existsSync(subSkills)) continue;
|
|
99
|
+
let entries;
|
|
100
|
+
try { entries = fs.readdirSync(subSkills, { withFileTypes: true }); } catch { continue; }
|
|
101
|
+
|
|
102
|
+
const agent = sub.agent || sub.role || 'general';
|
|
103
|
+
// Only migrate if this agent is shared across subs, OR if the folder name
|
|
104
|
+
// prefix is a well-known role keyword (frontend/backend/general). A unique
|
|
105
|
+
// subproject-short prefix (e.g. `admin-auth-guard` when only sialia-admin
|
|
106
|
+
// exists) stays with the subproject.
|
|
107
|
+
const wellKnownRole = ['frontend', 'backend', 'general', 'database', 'mobile'].includes(agent);
|
|
108
|
+
const sharedAgent = (agentCount.get(agent) || 0) > 1;
|
|
109
|
+
if (!wellKnownRole && !sharedAgent) continue;
|
|
110
|
+
|
|
111
|
+
for (const e of entries) {
|
|
112
|
+
if (!e.isDirectory()) continue;
|
|
113
|
+
const folderName = e.name;
|
|
114
|
+
// Only match THIS subproject's own agent prefix.
|
|
115
|
+
if (!folderName.startsWith(agent + '-')) continue;
|
|
116
|
+
|
|
117
|
+
const fromDir = path.join(subSkills, folderName);
|
|
118
|
+
const toDir = path.join(ROOT_SKILLS, folderName);
|
|
119
|
+
|
|
120
|
+
if (!fs.existsSync(toDir)) {
|
|
121
|
+
actions.push({ type: 'move', from: fromDir, to: toDir, sub: sub.name });
|
|
122
|
+
} else if (folderChecksum(fromDir) === folderChecksum(toDir)) {
|
|
123
|
+
actions.push({ type: 'delete-dup', from: fromDir, sub: sub.name });
|
|
124
|
+
} else {
|
|
125
|
+
actions.push({ type: 'conflict', from: fromDir, to: toDir, sub: sub.name });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!actions.length) {
|
|
131
|
+
console.log('No role-prefixed skills found in any subproject. Nothing to migrate.');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(`\nPlanned actions${APPLY ? ' (APPLYING)' : ' (dry-run — pass --apply to execute)'}:\n`);
|
|
136
|
+
for (const a of actions) {
|
|
137
|
+
const relFrom = path.relative(TARGET_ROOT, a.from).replace(/\\/g, '/');
|
|
138
|
+
if (a.type === 'move') {
|
|
139
|
+
const relTo = path.relative(TARGET_ROOT, a.to).replace(/\\/g, '/');
|
|
140
|
+
console.log(` [move] ${relFrom} → ${relTo}`);
|
|
141
|
+
} else if (a.type === 'delete-dup') {
|
|
142
|
+
console.log(` [delete-dup] ${relFrom} (identical copy already at ROOT)`);
|
|
143
|
+
} else {
|
|
144
|
+
const relTo = path.relative(TARGET_ROOT, a.to).replace(/\\/g, '/');
|
|
145
|
+
console.log(` [conflict] ${relFrom} (ROOT has different content at ${relTo}) — manual review needed`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!APPLY) {
|
|
150
|
+
console.log('\n(dry-run — no changes made. Re-run with --apply to execute.)');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fs.mkdirSync(ROOT_SKILLS, { recursive: true });
|
|
155
|
+
let ok = 0, failed = 0, skipped = 0;
|
|
156
|
+
for (const a of actions) {
|
|
157
|
+
try {
|
|
158
|
+
if (a.type === 'move') {
|
|
159
|
+
// Re-check: an earlier action this run may have already placed a
|
|
160
|
+
// folder at `a.to` (common when 3 subs share the same agent prefix).
|
|
161
|
+
if (fs.existsSync(a.to)) {
|
|
162
|
+
if (folderChecksum(a.from) === folderChecksum(a.to)) {
|
|
163
|
+
fs.rmSync(a.from, { recursive: true, force: true });
|
|
164
|
+
ok++;
|
|
165
|
+
} else {
|
|
166
|
+
console.error(` CONFLICT at ${a.to} — content differs, leaving ${a.from} in place`);
|
|
167
|
+
skipped++;
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
fs.renameSync(a.from, a.to);
|
|
171
|
+
ok++;
|
|
172
|
+
}
|
|
173
|
+
} else if (a.type === 'delete-dup') {
|
|
174
|
+
fs.rmSync(a.from, { recursive: true, force: true });
|
|
175
|
+
ok++;
|
|
176
|
+
} else {
|
|
177
|
+
skipped++;
|
|
178
|
+
}
|
|
179
|
+
} catch (err) {
|
|
180
|
+
console.error(` FAILED on ${a.from}: ${err.message}`);
|
|
181
|
+
failed++;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
console.log(`\nMigration done: ${ok} applied, ${skipped} skipped (conflict), ${failed} failed.`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
try { main(); } catch (err) {
|
|
188
|
+
console.error(`Fatal: ${err.message}`);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|