@malamute/ai-rules 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/bin/cli.js +1 -1
- package/configs/_shared/rules/conventions/npm.md +80 -0
- package/configs/angular/{.claude/settings.json → settings.json} +2 -0
- package/configs/dotnet/{.claude/settings.json → settings.json} +2 -0
- package/configs/fastapi/{.claude/settings.json → settings.json} +2 -0
- package/configs/flask/{.claude/settings.json → settings.json} +2 -0
- package/configs/nestjs/{.claude/settings.json → settings.json} +2 -0
- package/configs/nextjs/{.claude/settings.json → settings.json} +2 -0
- package/package.json +2 -1
- package/src/cli.js +5 -7
- package/src/config.js +52 -18
- package/src/index.js +4 -13
- package/src/installer.js +118 -65
- package/src/merge.js +8 -15
- package/src/tech-config.json +29 -13
- package/src/utils.js +7 -15
- package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +0 -362
- package/configs/angular/.claude/skills/signal-store/SKILL.md +0 -445
- /package/configs/_shared/{.claude/rules → rules}/conventions/documentation.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/conventions/git.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/conventions/performance.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/conventions/principles.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/devops/ci-cd.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/devops/docker.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/devops/nx.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/domain/backend/api-design.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/domain/frontend/accessibility.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/csharp/async.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/csharp/csharp.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/csharp/linq.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/async.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/celery.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/config.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/database/sqlalchemy.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/deployment.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/error-handling.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/migrations.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/python.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/repository.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/python/testing.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/typescript/async.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/typescript/generics.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/lang/typescript/typescript.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/quality/error-handling.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/quality/logging.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/quality/observability.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/quality/testing-patterns.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/security/secrets-management.md +0 -0
- /package/configs/_shared/{.claude/rules → rules}/security/security.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/analysis/explore/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/analysis/security-audit/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/dev/api-endpoint/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/dev/debug/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/dev/generate-tests/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/dev/learning/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/dev/spec/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/git/fix-issue/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/git/review/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/git/review-pr/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/infra/deploy/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/infra/docker/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/infra/migration/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/nx/nx-affected/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills}/nx/nx-lib/SKILL.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/core/components.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/core/resource.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/core/signals.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/http.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/routing.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/ssr.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/state/signal-store.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/state/state.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/ui/aria.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/ui/forms.md +0 -0
- /package/configs/angular/{.claude/rules → rules}/ui/pipes-directives.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/background-services.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/configuration.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/ddd.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/dependency-injection.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/mediatr.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/middleware.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/result-pattern.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/validation.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/background-tasks.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/dependencies.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/fastapi.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/lifespan.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/middleware.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/pydantic.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/responses.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/routers.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/security.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/fastapi/{.claude/rules → rules}/websockets.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/blueprints.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/cli.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/configuration.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/context.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/error-handlers.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/extensions.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/flask.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/marshmallow.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/security.md +0 -0
- /package/configs/flask/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/common-patterns.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/filters.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/interceptors.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/middleware.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/modules.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/pipes.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/websockets.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/api-routes.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/authentication.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/components.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/data-fetching.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/database.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/middleware.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/routing.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/seo.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/server-actions.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
package/README.md
CHANGED
|
@@ -313,11 +313,13 @@ mkdir configs/your-tech
|
|
|
313
313
|
### Adding a Technology
|
|
314
314
|
|
|
315
315
|
1. Create `configs/[tech]/CLAUDE.md` — start with `@../_shared/CLAUDE.md`
|
|
316
|
-
2. Add rules in `configs/[tech]
|
|
317
|
-
3. Add `configs/[tech]
|
|
316
|
+
2. Add rules in `configs/[tech]/rules/`
|
|
317
|
+
3. Add `configs/[tech]/settings.json` for permissions
|
|
318
318
|
4. Add tests
|
|
319
319
|
5. Submit a PR
|
|
320
320
|
|
|
321
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details on skills structure.
|
|
322
|
+
|
|
321
323
|
## License
|
|
322
324
|
|
|
323
325
|
MIT © Mehdi Chaabi
|
package/bin/cli.js
CHANGED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/package.json"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# npm Conventions
|
|
7
|
+
|
|
8
|
+
## Version Pinning
|
|
9
|
+
|
|
10
|
+
**Always use exact versions** - no `^` or `~` prefixes.
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
// GOOD
|
|
14
|
+
{
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"express": "4.18.2",
|
|
17
|
+
"lodash": "4.17.21"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// BAD
|
|
22
|
+
{
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"express": "^4.18.2",
|
|
25
|
+
"lodash": "~4.17.21"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Why?
|
|
31
|
+
|
|
32
|
+
- **Reproducible builds** across environments
|
|
33
|
+
- **No surprise breaking changes** from minor/patch updates
|
|
34
|
+
- **Lock file is source of truth** but pinning adds defense in depth
|
|
35
|
+
- **Explicit upgrades** via `npm update` or renovate/dependabot
|
|
36
|
+
|
|
37
|
+
### Commands
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Install with exact version
|
|
41
|
+
npm install express --save-exact
|
|
42
|
+
|
|
43
|
+
# Configure npm to always save exact
|
|
44
|
+
npm config set save-exact true
|
|
45
|
+
|
|
46
|
+
# Or in .npmrc
|
|
47
|
+
save-exact=true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Scripts
|
|
51
|
+
|
|
52
|
+
Use consistent script names:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"scripts": {
|
|
57
|
+
"dev": "...",
|
|
58
|
+
"build": "...",
|
|
59
|
+
"start": "...",
|
|
60
|
+
"test": "...",
|
|
61
|
+
"test:watch": "...",
|
|
62
|
+
"test:cov": "...",
|
|
63
|
+
"lint": "...",
|
|
64
|
+
"lint:fix": "...",
|
|
65
|
+
"format": "..."
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Engine Requirements
|
|
71
|
+
|
|
72
|
+
Specify Node.js version:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=20.0.0"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@malamute/ai-rules",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Claude Code configuration boilerplates for Angular, Next.js, NestJS, .NET, Python and more",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"main": "src/index.js",
|
|
6
7
|
"bin": "./bin/cli.js",
|
|
7
8
|
"scripts": {
|
package/src/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import { colors, log } from './utils.js';
|
|
3
|
+
import { VERSION, AVAILABLE_TECHS } from './config.js';
|
|
4
|
+
import { init, update, status, listTechnologies } from './installer.js';
|
|
5
5
|
|
|
6
6
|
function printUsage() {
|
|
7
7
|
console.log(`
|
|
@@ -123,7 +123,7 @@ async function interactiveInit() {
|
|
|
123
123
|
return { techs, options };
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
async function run(args) {
|
|
126
|
+
export async function run(args) {
|
|
127
127
|
if (args.includes('--help') || args.includes('-h')) {
|
|
128
128
|
printUsage();
|
|
129
129
|
return;
|
|
@@ -216,5 +216,3 @@ async function run(args) {
|
|
|
216
216
|
printUsage();
|
|
217
217
|
process.exit(1);
|
|
218
218
|
}
|
|
219
|
-
|
|
220
|
-
module.exports = { run };
|
package/src/config.js
CHANGED
|
@@ -1,29 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { createRequire } from 'module';
|
|
3
5
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const VERSION = require('../package.json').version;
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
export const CONFIGS_DIR = path.join(__dirname, '..', 'configs');
|
|
12
|
+
export const AVAILABLE_TECHS = ['angular', 'nextjs', 'nestjs', 'dotnet', 'fastapi', 'flask'];
|
|
13
|
+
export const VERSION = require('../package.json').version;
|
|
14
|
+
|
|
15
|
+
export const TECH_CONFIG = JSON.parse(
|
|
9
16
|
fs.readFileSync(path.join(__dirname, 'tech-config.json'), 'utf8')
|
|
10
17
|
);
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Get rule directories to include based on selected technologies.
|
|
21
|
+
* Returns paths relative to the rules directory (e.g., "lang/typescript", "domain/frontend").
|
|
22
|
+
*/
|
|
23
|
+
export function getRulePathsToInclude(techs) {
|
|
24
|
+
const paths = new Set();
|
|
25
|
+
const { ruleMapping, alwaysInclude } = TECH_CONFIG;
|
|
26
|
+
|
|
27
|
+
// Always include common rules
|
|
28
|
+
alwaysInclude.forEach((dir) => paths.add(dir));
|
|
29
|
+
|
|
30
|
+
// Add language and domain-specific rules based on selected techs
|
|
14
31
|
for (const tech of techs) {
|
|
15
32
|
const config = TECH_CONFIG.technologies[tech];
|
|
16
|
-
if (config
|
|
17
|
-
|
|
33
|
+
if (!config) continue;
|
|
34
|
+
|
|
35
|
+
// Add language-specific rules (e.g., "lang/typescript")
|
|
36
|
+
if (config.language && ruleMapping.language[config.language]) {
|
|
37
|
+
paths.add(ruleMapping.language[config.language]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Add domain-specific rules (e.g., "domain/frontend")
|
|
41
|
+
if (config.type && ruleMapping.type[config.type]) {
|
|
42
|
+
paths.add(ruleMapping.type[config.type]);
|
|
18
43
|
}
|
|
19
44
|
}
|
|
20
|
-
|
|
45
|
+
|
|
46
|
+
return paths;
|
|
21
47
|
}
|
|
22
48
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Check if a rule path should be included for the given technologies.
|
|
51
|
+
* @param {string} rulePath - Path relative to rules dir (e.g., "lang/python/async.md")
|
|
52
|
+
* @param {Set<string>} includedPaths - Set of paths to include
|
|
53
|
+
* @returns {boolean}
|
|
54
|
+
*/
|
|
55
|
+
export function shouldIncludeRule(rulePath, includedPaths) {
|
|
56
|
+
// Check if the path starts with any of the included paths
|
|
57
|
+
for (const includedPath of includedPaths) {
|
|
58
|
+
if (rulePath === includedPath || rulePath.startsWith(includedPath + '/')) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
module.exports = {
|
|
7
|
-
run,
|
|
8
|
-
init,
|
|
9
|
-
update,
|
|
10
|
-
status,
|
|
11
|
-
readManifest,
|
|
12
|
-
VERSION,
|
|
13
|
-
};
|
|
1
|
+
export { run } from './cli.js';
|
|
2
|
+
export { init, update, status } from './installer.js';
|
|
3
|
+
export { readManifest } from './merge.js';
|
|
4
|
+
export { VERSION } from './config.js';
|
package/src/installer.js
CHANGED
|
@@ -1,10 +1,95 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { colors, log, getFilesRecursive, copyDirRecursive, backupFile } from './utils.js';
|
|
4
|
+
import { CONFIGS_DIR, AVAILABLE_TECHS, VERSION, getRulePathsToInclude, shouldIncludeRule } from './config.js';
|
|
5
|
+
import { mergeClaudeMd, mergeSettingsJson, readManifest, writeManifest } from './merge.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Copy skills to target directory with flat structure.
|
|
9
|
+
* Source: skills/<category>/<skill-name>/SKILL.md
|
|
10
|
+
* Target: .claude/skills/<skill-name>/SKILL.md
|
|
11
|
+
*/
|
|
12
|
+
function copySkillsToTarget(srcDir, destDir, options = {}) {
|
|
13
|
+
const { dryRun, backup, targetDir } = options;
|
|
14
|
+
const operations = [];
|
|
15
|
+
|
|
16
|
+
// getFilesRecursive returns paths relative to srcDir
|
|
17
|
+
const relativeFiles = getFilesRecursive(srcDir).filter((f) => f.endsWith('SKILL.md'));
|
|
18
|
+
|
|
19
|
+
for (const relativePath of relativeFiles) {
|
|
20
|
+
const srcFile = path.join(srcDir, relativePath);
|
|
21
|
+
const parts = relativePath.split(path.sep);
|
|
22
|
+
|
|
23
|
+
// Extract skill name from parent directory
|
|
24
|
+
// e.g., dev/debug/SKILL.md → debug
|
|
25
|
+
const skillName = parts[parts.length - 2];
|
|
26
|
+
|
|
27
|
+
// Create .claude/skills/<skill-name>/SKILL.md
|
|
28
|
+
const destSkillDir = path.join(destDir, skillName);
|
|
29
|
+
const destFile = path.join(destSkillDir, 'SKILL.md');
|
|
30
|
+
const exists = fs.existsSync(destFile);
|
|
31
|
+
const relativeDestPath = path.relative(targetDir, destFile);
|
|
32
|
+
|
|
33
|
+
if (dryRun) {
|
|
34
|
+
operations.push({ type: exists ? 'overwrite' : 'create', path: relativeDestPath });
|
|
35
|
+
} else {
|
|
36
|
+
if (exists && backup) {
|
|
37
|
+
backupFile(destFile, targetDir);
|
|
38
|
+
}
|
|
39
|
+
fs.mkdirSync(destSkillDir, { recursive: true });
|
|
40
|
+
fs.copyFileSync(srcFile, destFile);
|
|
41
|
+
operations.push({ type: exists ? 'overwrite' : 'create', path: relativeDestPath });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return operations;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Copy rules selectively based on technology configuration.
|
|
50
|
+
* Only copies rules that match the included paths.
|
|
51
|
+
*/
|
|
52
|
+
function copyRulesSelectively(srcDir, destDir, includedPaths, skippedPaths, options = {}) {
|
|
53
|
+
const { dryRun, backup, targetDir } = options;
|
|
54
|
+
const operations = [];
|
|
55
|
+
|
|
56
|
+
// getFilesRecursive returns paths relative to srcDir
|
|
57
|
+
const relativeFiles = getFilesRecursive(srcDir);
|
|
58
|
+
|
|
59
|
+
for (const relativePath of relativeFiles) {
|
|
60
|
+
const relativeDir = path.dirname(relativePath);
|
|
61
|
+
|
|
62
|
+
// Check if this file should be included
|
|
63
|
+
if (!shouldIncludeRule(relativeDir, includedPaths)) {
|
|
64
|
+
// Track skipped top-level directories for logging
|
|
65
|
+
const topLevel = relativePath.split(path.sep).slice(0, 2).join('/');
|
|
66
|
+
if (!skippedPaths.includes(topLevel)) {
|
|
67
|
+
skippedPaths.push(topLevel);
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const srcFile = path.join(srcDir, relativePath);
|
|
73
|
+
const destFile = path.join(destDir, relativePath);
|
|
74
|
+
const exists = fs.existsSync(destFile);
|
|
75
|
+
const relativeDestPath = path.relative(targetDir, destFile);
|
|
6
76
|
|
|
7
|
-
|
|
77
|
+
if (dryRun) {
|
|
78
|
+
operations.push({ type: exists ? 'overwrite' : 'create', path: relativeDestPath });
|
|
79
|
+
} else {
|
|
80
|
+
if (exists && backup) {
|
|
81
|
+
backupFile(destFile, targetDir);
|
|
82
|
+
}
|
|
83
|
+
fs.mkdirSync(path.dirname(destFile), { recursive: true });
|
|
84
|
+
fs.copyFileSync(srcFile, destFile);
|
|
85
|
+
operations.push({ type: exists ? 'overwrite' : 'create', path: relativeDestPath });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return operations;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function listTechnologies() {
|
|
8
93
|
console.log(`\n${colors.bold('Available technologies:')}\n`);
|
|
9
94
|
|
|
10
95
|
const techInfo = {
|
|
@@ -26,15 +111,15 @@ function listTechnologies() {
|
|
|
26
111
|
console.log(`\n${colors.bold('Shared resources:')}\n`);
|
|
27
112
|
|
|
28
113
|
const sharedPath = path.join(CONFIGS_DIR, '_shared');
|
|
29
|
-
const skills = fs.existsSync(path.join(sharedPath, '
|
|
30
|
-
const rules = fs.existsSync(path.join(sharedPath, '
|
|
114
|
+
const skills = fs.existsSync(path.join(sharedPath, 'skills'));
|
|
115
|
+
const rules = fs.existsSync(path.join(sharedPath, 'rules'));
|
|
31
116
|
|
|
32
117
|
console.log(` ${skills ? colors.green('✓') : colors.red('✗')} skills /learning, /review, /spec, /debug, and more`);
|
|
33
118
|
console.log(` ${rules ? colors.green('✓') : colors.red('✗')} rules security, performance, accessibility`);
|
|
34
119
|
console.log('');
|
|
35
120
|
}
|
|
36
121
|
|
|
37
|
-
function status(targetDir) {
|
|
122
|
+
export function status(targetDir) {
|
|
38
123
|
const manifest = readManifest(targetDir);
|
|
39
124
|
|
|
40
125
|
console.log(`\n${colors.bold('AI Rules Status')}\n`);
|
|
@@ -84,7 +169,7 @@ function status(targetDir) {
|
|
|
84
169
|
}
|
|
85
170
|
}
|
|
86
171
|
|
|
87
|
-
function init(techs, options) {
|
|
172
|
+
export function init(techs, options) {
|
|
88
173
|
const targetDir = path.resolve(options.target || process.cwd());
|
|
89
174
|
const { dryRun, force } = options;
|
|
90
175
|
const backup = !force;
|
|
@@ -132,7 +217,7 @@ function init(techs, options) {
|
|
|
132
217
|
}
|
|
133
218
|
}
|
|
134
219
|
|
|
135
|
-
const settingsPath = path.join(techDir, '
|
|
220
|
+
const settingsPath = path.join(techDir, 'settings.json');
|
|
136
221
|
if (fs.existsSync(settingsPath)) {
|
|
137
222
|
const op = mergeSettingsJson(
|
|
138
223
|
path.join(targetDir, '.claude', 'settings.json'),
|
|
@@ -148,7 +233,7 @@ function init(techs, options) {
|
|
|
148
233
|
}
|
|
149
234
|
}
|
|
150
235
|
|
|
151
|
-
const rulesDir = path.join(techDir, '
|
|
236
|
+
const rulesDir = path.join(techDir, 'rules');
|
|
152
237
|
if (fs.existsSync(rulesDir)) {
|
|
153
238
|
const ops = copyDirRecursive(
|
|
154
239
|
rulesDir,
|
|
@@ -165,9 +250,9 @@ function init(techs, options) {
|
|
|
165
250
|
}
|
|
166
251
|
|
|
167
252
|
if (options.withSkills) {
|
|
168
|
-
const techSkillsDir = path.join(techDir, '
|
|
253
|
+
const techSkillsDir = path.join(techDir, 'skills');
|
|
169
254
|
if (fs.existsSync(techSkillsDir)) {
|
|
170
|
-
const ops =
|
|
255
|
+
const ops = copySkillsToTarget(
|
|
171
256
|
techSkillsDir,
|
|
172
257
|
path.join(targetDir, '.claude', 'skills'),
|
|
173
258
|
{ dryRun, backup, targetDir }
|
|
@@ -181,9 +266,9 @@ function init(techs, options) {
|
|
|
181
266
|
|
|
182
267
|
if (options.withSkills) {
|
|
183
268
|
log.info(`${dryRun ? 'Would install' : 'Installing'} skills...`);
|
|
184
|
-
const skillsDir = path.join(sharedDir, '
|
|
269
|
+
const skillsDir = path.join(sharedDir, 'skills');
|
|
185
270
|
if (fs.existsSync(skillsDir)) {
|
|
186
|
-
const ops =
|
|
271
|
+
const ops = copySkillsToTarget(
|
|
187
272
|
skillsDir,
|
|
188
273
|
path.join(targetDir, '.claude', 'skills'),
|
|
189
274
|
{ dryRun, backup, targetDir }
|
|
@@ -200,54 +285,29 @@ function init(techs, options) {
|
|
|
200
285
|
|
|
201
286
|
if (options.withRules) {
|
|
202
287
|
log.info(`${dryRun ? 'Would install' : 'Installing'} shared rules...`);
|
|
203
|
-
const rulesDir = path.join(sharedDir, '
|
|
288
|
+
const rulesDir = path.join(sharedDir, 'rules');
|
|
204
289
|
if (fs.existsSync(rulesDir)) {
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const srcPath = path.join(rulesDir, entry.name);
|
|
219
|
-
const destPath = path.join(targetDir, '.claude', 'rules', entry.name);
|
|
220
|
-
|
|
221
|
-
if (entry.isDirectory()) {
|
|
222
|
-
const ops = copyDirRecursive(srcPath, destPath, { dryRun, backup, targetDir });
|
|
223
|
-
operations.push(...ops);
|
|
224
|
-
totalOps += ops.length;
|
|
225
|
-
} else {
|
|
226
|
-
const exists = fs.existsSync(destPath);
|
|
227
|
-
const relativePath = path.relative(targetDir, destPath);
|
|
228
|
-
|
|
229
|
-
if (dryRun) {
|
|
230
|
-
operations.push({ type: exists ? 'overwrite' : 'create', path: relativePath });
|
|
231
|
-
} else {
|
|
232
|
-
if (exists && backup) {
|
|
233
|
-
backupFile(destPath, targetDir);
|
|
234
|
-
}
|
|
235
|
-
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
236
|
-
fs.copyFileSync(srcPath, destPath);
|
|
237
|
-
operations.push({ type: exists ? 'overwrite' : 'create', path: relativePath });
|
|
238
|
-
}
|
|
239
|
-
totalOps++;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
290
|
+
const includedPaths = getRulePathsToInclude(techs);
|
|
291
|
+
const skippedPaths = [];
|
|
292
|
+
|
|
293
|
+
const ops = copyRulesSelectively(
|
|
294
|
+
rulesDir,
|
|
295
|
+
path.join(targetDir, '.claude', 'rules'),
|
|
296
|
+
includedPaths,
|
|
297
|
+
skippedPaths,
|
|
298
|
+
{ dryRun, backup, targetDir }
|
|
299
|
+
);
|
|
300
|
+
operations.push(...ops);
|
|
242
301
|
|
|
243
302
|
if (dryRun) {
|
|
244
|
-
log.dry(` shared rules/ (${
|
|
303
|
+
log.dry(` shared rules/ (${ops.length} files)`);
|
|
245
304
|
} else {
|
|
246
305
|
log.success(` shared rules/`);
|
|
247
306
|
}
|
|
248
307
|
|
|
249
|
-
if (
|
|
250
|
-
|
|
308
|
+
if (skippedPaths.length > 0) {
|
|
309
|
+
const uniqueSkipped = [...new Set(skippedPaths)];
|
|
310
|
+
log.info(` (skipped: ${uniqueSkipped.join(', ')} - not applicable)`);
|
|
251
311
|
}
|
|
252
312
|
}
|
|
253
313
|
}
|
|
@@ -317,7 +377,7 @@ function init(techs, options) {
|
|
|
317
377
|
console.log('');
|
|
318
378
|
}
|
|
319
379
|
|
|
320
|
-
async function update(options) {
|
|
380
|
+
export async function update(options) {
|
|
321
381
|
const targetDir = path.resolve(options.target || process.cwd());
|
|
322
382
|
const { dryRun, force } = options;
|
|
323
383
|
|
|
@@ -352,10 +412,3 @@ async function update(options) {
|
|
|
352
412
|
|
|
353
413
|
init(manifest.technologies, initOptions);
|
|
354
414
|
}
|
|
355
|
-
|
|
356
|
-
module.exports = {
|
|
357
|
-
init,
|
|
358
|
-
update,
|
|
359
|
-
status,
|
|
360
|
-
listTechnologies,
|
|
361
|
-
};
|
package/src/merge.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { log, backupFile } from './utils.js';
|
|
4
|
+
import { VERSION } from './config.js';
|
|
5
5
|
|
|
6
|
-
function mergeClaudeMd(targetPath, sourcePath, isFirst, options = {}) {
|
|
6
|
+
export function mergeClaudeMd(targetPath, sourcePath, isFirst, options = {}) {
|
|
7
7
|
const { dryRun = false, backup = false, targetDir } = options;
|
|
8
8
|
const content = fs.readFileSync(sourcePath, 'utf8');
|
|
9
9
|
const exists = fs.existsSync(targetPath);
|
|
@@ -26,7 +26,7 @@ function mergeClaudeMd(targetPath, sourcePath, isFirst, options = {}) {
|
|
|
26
26
|
return { type: exists ? 'merge' : 'create', path: 'CLAUDE.md' };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
function mergeSettingsJson(targetPath, sourcePath, options = {}) {
|
|
29
|
+
export function mergeSettingsJson(targetPath, sourcePath, options = {}) {
|
|
30
30
|
const { dryRun = false, backup = false, targetDir } = options;
|
|
31
31
|
const exists = fs.existsSync(targetPath);
|
|
32
32
|
|
|
@@ -83,7 +83,7 @@ function getManifestPath(targetDir) {
|
|
|
83
83
|
return path.join(targetDir, '.claude', '.ai-rules.json');
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
function readManifest(targetDir) {
|
|
86
|
+
export function readManifest(targetDir) {
|
|
87
87
|
const manifestPath = getManifestPath(targetDir);
|
|
88
88
|
if (!fs.existsSync(manifestPath)) return null;
|
|
89
89
|
|
|
@@ -94,7 +94,7 @@ function readManifest(targetDir) {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
function writeManifest(targetDir, data, dryRun = false) {
|
|
97
|
+
export function writeManifest(targetDir, data, dryRun = false) {
|
|
98
98
|
if (dryRun) return;
|
|
99
99
|
|
|
100
100
|
const manifestPath = getManifestPath(targetDir);
|
|
@@ -107,10 +107,3 @@ function writeManifest(targetDir, data, dryRun = false) {
|
|
|
107
107
|
fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
|
|
108
108
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
module.exports = {
|
|
112
|
-
mergeClaudeMd,
|
|
113
|
-
mergeSettingsJson,
|
|
114
|
-
readManifest,
|
|
115
|
-
writeManifest,
|
|
116
|
-
};
|