mustard-claude 3.1.25 → 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.
Files changed (25) hide show
  1. package/package.json +1 -1
  2. package/templates/CLAUDE.md +4 -0
  3. package/templates/scripts/_fence-languages.json +57 -0
  4. package/templates/scripts/_skill-meta.json +0 -12
  5. package/templates/scripts/migrate-skill-paths.js +190 -0
  6. package/templates/scripts/skill-generator.js +553 -1441
  7. package/templates/scripts/skill-validate.js +199 -0
  8. package/templates/skill-templates/dto-conventions.examples.md.tmpl +0 -15
  9. package/templates/skill-templates/dto-conventions.skill.md.tmpl +0 -37
  10. package/templates/skill-templates/entity-creation.examples.md.tmpl +0 -19
  11. package/templates/skill-templates/entity-creation.skill.md.tmpl +0 -41
  12. package/templates/skill-templates/enum-placement.examples.md.tmpl +0 -15
  13. package/templates/skill-templates/enum-placement.skill.md.tmpl +0 -33
  14. package/templates/skill-templates/module-registration.examples.md.tmpl +0 -15
  15. package/templates/skill-templates/module-registration.skill.md.tmpl +0 -36
  16. package/templates/skill-templates/navigation-pattern.examples.md.tmpl +0 -9
  17. package/templates/skill-templates/navigation-pattern.skill.md.tmpl +0 -36
  18. package/templates/skill-templates/repository-pattern.examples.md.tmpl +0 -9
  19. package/templates/skill-templates/repository-pattern.skill.md.tmpl +0 -37
  20. package/templates/skill-templates/route-conventions.examples.md.tmpl +0 -15
  21. package/templates/skill-templates/route-conventions.skill.md.tmpl +0 -34
  22. package/templates/skill-templates/service-pattern.examples.md.tmpl +0 -15
  23. package/templates/skill-templates/service-pattern.skill.md.tmpl +0 -37
  24. package/templates/skill-templates/state-management.examples.md.tmpl +0 -9
  25. package/templates/skill-templates/state-management.skill.md.tmpl +0 -36
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mustard-claude",
3
- "version": "3.1.25",
3
+ "version": "3.1.26",
4
4
  "description": "Framework-agnostic CLI for Claude Code project setup",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
@@ -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
+ }