agentic-team-templates 0.3.0 → 0.4.2
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 +19 -10
- package/package.json +5 -5
- package/src/index.js +205 -9
package/README.md
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
|
-
#
|
|
1
|
+
# agentic-team-templates
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/cursor-templates)
|
|
4
4
|
[](https://opensource.org/licenses/MIT)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
**Compatible with:**
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
AI coding assistant templates for Cursor IDE and Claude Code. Pre-configured rules and guidelines that help AI assistants write better code in your projects.
|
|
12
|
+
|
|
13
|
+
**Installs:**
|
|
14
|
+
- **`CLAUDE.md`** - Development guide for Claude-based assistants (Claude Code, Cursor with Claude)
|
|
15
|
+
- **`.cursorrules/`** - Rule files for Cursor IDE
|
|
7
16
|
|
|
8
17
|
> **Disclaimer:** This project is provided for **educational and experimental purposes only**. The author takes no responsibility for any actions, outputs, or consequences resulting from an LLM or AI assistant following these rules. Use at your own risk. Always review AI-generated code before deploying to production.
|
|
9
18
|
|
|
@@ -32,9 +41,7 @@ Navigate to your project directory and run:
|
|
|
32
41
|
npx cursor-templates web-frontend
|
|
33
42
|
```
|
|
34
43
|
|
|
35
|
-
This installs
|
|
36
|
-
- **`CLAUDE.md`** - Main development guide for the AI assistant
|
|
37
|
-
- **`.cursorrules/`** - Directory containing domain-specific coding rules
|
|
44
|
+
This installs the template rules in your project directory.
|
|
38
45
|
|
|
39
46
|
### Install Multiple Templates
|
|
40
47
|
|
|
@@ -123,8 +130,8 @@ After running `npx cursor-templates web-frontend`:
|
|
|
123
130
|
|
|
124
131
|
```
|
|
125
132
|
your-project/
|
|
126
|
-
├── CLAUDE.md #
|
|
127
|
-
└── .cursorrules/ # Rule files
|
|
133
|
+
├── CLAUDE.md # Development guide (Claude Code, Cursor)
|
|
134
|
+
└── .cursorrules/ # Rule files (Cursor IDE)
|
|
128
135
|
├── core-principles.md # Shared
|
|
129
136
|
├── code-quality.md # Shared
|
|
130
137
|
├── security-fundamentals.md # Shared
|
|
@@ -155,7 +162,7 @@ All API calls go through `/lib/api.ts`...
|
|
|
155
162
|
|
|
156
163
|
### Modify Existing Rules
|
|
157
164
|
|
|
158
|
-
Edit any file in `.cursorrules/` directly. Changes take effect immediately
|
|
165
|
+
Edit any file in `.cursorrules/` or `CLAUDE.md` directly. Changes take effect immediately.
|
|
159
166
|
|
|
160
167
|
### Combine with Existing Rules
|
|
161
168
|
|
|
@@ -196,7 +203,9 @@ npx cursor-templates ml-ai data-engineering
|
|
|
196
203
|
## Requirements
|
|
197
204
|
|
|
198
205
|
- **Node.js**: 18.0.0 or higher
|
|
199
|
-
- **
|
|
206
|
+
- **Supported IDEs/Tools**:
|
|
207
|
+
- Cursor IDE (any version with `.cursorrules/` support)
|
|
208
|
+
- Claude Code (reads `CLAUDE.md` automatically)
|
|
200
209
|
|
|
201
210
|
## How to Contribute
|
|
202
211
|
|
|
@@ -277,4 +286,4 @@ Changes to shared rules affect all templates, so be thoughtful with modification
|
|
|
277
286
|
|
|
278
287
|
## License
|
|
279
288
|
|
|
280
|
-
MIT © [David Mendez](https://github.com/
|
|
289
|
+
MIT © [David Mendez](https://github.com/djm204)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-team-templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "AI coding assistant templates for Cursor IDE. Pre-configured rules and guidelines that help AI assistants write better code. - use at your own risk",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cursor",
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
"developer-tools",
|
|
15
15
|
"code-quality"
|
|
16
16
|
],
|
|
17
|
-
"author": "David Josef Mendez <
|
|
17
|
+
"author": "David Josef Mendez <me@davidmendez.dev>",
|
|
18
18
|
"license": "MIT",
|
|
19
19
|
"repository": {
|
|
20
20
|
"type": "git",
|
|
21
|
-
"url": "git+https://github.com/djm204/
|
|
21
|
+
"url": "git+https://github.com/djm204/agentic-team-templates.git"
|
|
22
22
|
},
|
|
23
|
-
"homepage": "https://github.com/djm204/
|
|
23
|
+
"homepage": "https://github.com/djm204/agentic-team-templates#readme",
|
|
24
24
|
"bugs": {
|
|
25
|
-
"url": "https://github.com/djm204/
|
|
25
|
+
"url": "https://github.com/djm204/agentic-team-templates/issues"
|
|
26
26
|
},
|
|
27
27
|
"bin": {
|
|
28
28
|
"cursor-templates": "./bin/cli.js"
|
package/src/index.js
CHANGED
|
@@ -86,6 +86,7 @@ ${colors.yellow('Examples:')}
|
|
|
86
86
|
|
|
87
87
|
${colors.dim('Shared rules (code-quality, security, git-workflow, etc.) are always included.')}
|
|
88
88
|
${colors.dim('Identical files are skipped. Modified files are preserved; ours saved as *-1.md.')}
|
|
89
|
+
${colors.dim('CLAUDE.md: missing sections are intelligently merged (not overwritten).')}
|
|
89
90
|
`);
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -117,6 +118,179 @@ function filesMatch(file1, file2) {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Parse markdown content into sections by ## headings
|
|
123
|
+
* @param {string} content - Markdown content
|
|
124
|
+
* @returns {Array<{heading: string, content: string, signature: string}>}
|
|
125
|
+
*/
|
|
126
|
+
function parseMarkdownSections(content) {
|
|
127
|
+
const lines = content.split('\n');
|
|
128
|
+
const sections = [];
|
|
129
|
+
let currentSection = null;
|
|
130
|
+
let preamble = [];
|
|
131
|
+
|
|
132
|
+
for (const line of lines) {
|
|
133
|
+
if (line.startsWith('## ')) {
|
|
134
|
+
// Save previous section
|
|
135
|
+
if (currentSection) {
|
|
136
|
+
currentSection.content = currentSection.lines.join('\n');
|
|
137
|
+
currentSection.signature = generateSectionSignature(currentSection.heading, currentSection.lines);
|
|
138
|
+
delete currentSection.lines;
|
|
139
|
+
sections.push(currentSection);
|
|
140
|
+
}
|
|
141
|
+
// Start new section
|
|
142
|
+
currentSection = {
|
|
143
|
+
heading: line.slice(3).trim(),
|
|
144
|
+
lines: []
|
|
145
|
+
};
|
|
146
|
+
} else if (currentSection) {
|
|
147
|
+
currentSection.lines.push(line);
|
|
148
|
+
} else {
|
|
149
|
+
// Content before first ## heading (preamble)
|
|
150
|
+
preamble.push(line);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Don't forget the last section
|
|
155
|
+
if (currentSection) {
|
|
156
|
+
currentSection.content = currentSection.lines.join('\n');
|
|
157
|
+
currentSection.signature = generateSectionSignature(currentSection.heading, currentSection.lines);
|
|
158
|
+
delete currentSection.lines;
|
|
159
|
+
sections.push(currentSection);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return { preamble: preamble.join('\n'), sections };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Generate a signature for a section based on heading + first meaningful lines
|
|
167
|
+
* Used for matching sections even if heading text differs slightly
|
|
168
|
+
* @param {string} heading
|
|
169
|
+
* @param {string[]} lines
|
|
170
|
+
* @returns {string}
|
|
171
|
+
*/
|
|
172
|
+
function generateSectionSignature(heading, lines) {
|
|
173
|
+
// Normalize heading: lowercase, remove special chars, collapse whitespace
|
|
174
|
+
const normalizedHeading = heading.toLowerCase()
|
|
175
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
176
|
+
.replace(/\s+/g, ' ')
|
|
177
|
+
.trim();
|
|
178
|
+
|
|
179
|
+
// Get first 3 non-empty, non-heading lines for content signature
|
|
180
|
+
const meaningfulLines = lines
|
|
181
|
+
.filter(l => l.trim() && !l.startsWith('#') && !l.startsWith('|') && !l.startsWith('-'))
|
|
182
|
+
.slice(0, 3)
|
|
183
|
+
.map(l => l.toLowerCase().replace(/[^a-z0-9\s]/g, '').replace(/\s+/g, ' ').trim())
|
|
184
|
+
.join(' ');
|
|
185
|
+
|
|
186
|
+
return `${normalizedHeading}::${meaningfulLines.slice(0, 100)}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Find sections from template that are missing in existing content
|
|
191
|
+
* @param {string} existingContent
|
|
192
|
+
* @param {string} templateContent
|
|
193
|
+
* @returns {{missing: Array<{heading: string, content: string}>, matchedCount: number}}
|
|
194
|
+
*/
|
|
195
|
+
function findMissingSections(existingContent, templateContent) {
|
|
196
|
+
const existing = parseMarkdownSections(existingContent);
|
|
197
|
+
const template = parseMarkdownSections(templateContent);
|
|
198
|
+
|
|
199
|
+
const existingSignatures = new Set(existing.sections.map(s => s.signature));
|
|
200
|
+
const existingHeadings = new Set(existing.sections.map(s => s.heading.toLowerCase()));
|
|
201
|
+
|
|
202
|
+
const missing = [];
|
|
203
|
+
let matchedCount = 0;
|
|
204
|
+
|
|
205
|
+
for (const section of template.sections) {
|
|
206
|
+
// Check by signature first (heading + content), then by heading alone
|
|
207
|
+
const signatureMatch = existingSignatures.has(section.signature);
|
|
208
|
+
const headingMatch = existingHeadings.has(section.heading.toLowerCase());
|
|
209
|
+
|
|
210
|
+
if (signatureMatch || headingMatch) {
|
|
211
|
+
matchedCount++;
|
|
212
|
+
} else {
|
|
213
|
+
missing.push(section);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { missing, matchedCount };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Merge template sections into existing content, inserting missing sections in template order
|
|
222
|
+
* @param {string} existingContent
|
|
223
|
+
* @param {string} templateContent
|
|
224
|
+
* @returns {{merged: string, addedSections: string[]}}
|
|
225
|
+
*/
|
|
226
|
+
function mergeClaudeContent(existingContent, templateContent) {
|
|
227
|
+
const existing = parseMarkdownSections(existingContent);
|
|
228
|
+
const template = parseMarkdownSections(templateContent);
|
|
229
|
+
const { missing } = findMissingSections(existingContent, templateContent);
|
|
230
|
+
|
|
231
|
+
if (missing.length === 0) {
|
|
232
|
+
return { merged: existingContent, addedSections: [] };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Build a map of existing sections by normalized heading for insertion point lookup
|
|
236
|
+
const existingByHeading = new Map();
|
|
237
|
+
existing.sections.forEach((s, i) => {
|
|
238
|
+
existingByHeading.set(s.heading.toLowerCase(), i);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Find template section order and determine where to insert missing sections
|
|
242
|
+
const templateOrder = template.sections.map(s => s.heading.toLowerCase());
|
|
243
|
+
|
|
244
|
+
// For each missing section, find the best insertion point based on template order
|
|
245
|
+
const insertions = []; // { afterIndex: number, section: section }
|
|
246
|
+
|
|
247
|
+
for (const missingSection of missing) {
|
|
248
|
+
const missingIndex = templateOrder.indexOf(missingSection.heading.toLowerCase());
|
|
249
|
+
|
|
250
|
+
// Find the closest preceding section that exists in the existing content
|
|
251
|
+
let insertAfterIndex = -1; // -1 means insert at beginning (after preamble)
|
|
252
|
+
|
|
253
|
+
for (let i = missingIndex - 1; i >= 0; i--) {
|
|
254
|
+
const precedingHeading = templateOrder[i];
|
|
255
|
+
if (existingByHeading.has(precedingHeading)) {
|
|
256
|
+
insertAfterIndex = existingByHeading.get(precedingHeading);
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
insertions.push({ afterIndex: insertAfterIndex, section: missingSection });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Sort insertions by afterIndex (descending) so we insert from bottom to top
|
|
265
|
+
// This preserves indices as we insert
|
|
266
|
+
insertions.sort((a, b) => b.afterIndex - a.afterIndex);
|
|
267
|
+
|
|
268
|
+
// Build the merged content
|
|
269
|
+
const mergedSections = [...existing.sections];
|
|
270
|
+
const addedSections = [];
|
|
271
|
+
|
|
272
|
+
for (const { afterIndex, section } of insertions) {
|
|
273
|
+
const insertAt = afterIndex + 1;
|
|
274
|
+
mergedSections.splice(insertAt, 0, section);
|
|
275
|
+
addedSections.push(section.heading);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Reconstruct the markdown
|
|
279
|
+
let merged = existing.preamble;
|
|
280
|
+
if (merged && !merged.endsWith('\n\n')) {
|
|
281
|
+
merged = merged.trimEnd() + '\n\n';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
for (const section of mergedSections) {
|
|
285
|
+
merged += `## ${section.heading}\n${section.content}\n`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// addedSections is in reverse order due to sorting, reverse it back
|
|
289
|
+
addedSections.reverse();
|
|
290
|
+
|
|
291
|
+
return { merged: merged.trimEnd() + '\n', addedSections };
|
|
292
|
+
}
|
|
293
|
+
|
|
120
294
|
/**
|
|
121
295
|
* Get alternate filename with -1 suffix (e.g., code-quality.md -> code-quality-1.md)
|
|
122
296
|
*/
|
|
@@ -305,6 +479,7 @@ function install(targetDir, templates, dryRun = false, force = false) {
|
|
|
305
479
|
console.log(`${colors.blue('Installing to:')} ${targetDir}`);
|
|
306
480
|
if (!force) {
|
|
307
481
|
console.log(colors.dim('(identical files skipped, modified files preserved with ours saved as *-1.md)'));
|
|
482
|
+
console.log(colors.dim('(CLAUDE.md: missing sections merged intelligently)'));
|
|
308
483
|
}
|
|
309
484
|
console.log();
|
|
310
485
|
|
|
@@ -386,6 +561,7 @@ function install(targetDir, templates, dryRun = false, force = false) {
|
|
|
386
561
|
// 3. Generate CLAUDE.md
|
|
387
562
|
const claudePath = path.join(targetDir, 'CLAUDE.md');
|
|
388
563
|
const claudeExists = fs.existsSync(claudePath);
|
|
564
|
+
const templateContent = generateClaudeMdContent(templates);
|
|
389
565
|
|
|
390
566
|
console.log(colors.green('► Generating CLAUDE.md...'));
|
|
391
567
|
if (dryRun) {
|
|
@@ -394,23 +570,43 @@ function install(targetDir, templates, dryRun = false, force = false) {
|
|
|
394
570
|
} else if (force) {
|
|
395
571
|
console.log(` ${colors.dim('[update]')} CLAUDE.md`);
|
|
396
572
|
} else {
|
|
397
|
-
|
|
573
|
+
// Check what would be merged
|
|
574
|
+
const existingContent = fs.readFileSync(claudePath, 'utf8');
|
|
575
|
+
const { missing } = findMissingSections(existingContent, templateContent);
|
|
576
|
+
if (missing.length === 0) {
|
|
577
|
+
console.log(` ${colors.yellow('[skip]')} CLAUDE.md (all sections present)`);
|
|
578
|
+
} else {
|
|
579
|
+
console.log(` ${colors.blue('[merge]')} CLAUDE.md (would add ${missing.length} section(s))`);
|
|
580
|
+
for (const section of missing) {
|
|
581
|
+
console.log(` ${colors.dim('+')} ${section.heading}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
398
584
|
}
|
|
399
585
|
} else if (!claudeExists) {
|
|
400
|
-
|
|
586
|
+
fs.writeFileSync(claudePath, templateContent);
|
|
401
587
|
console.log(` ${colors.dim('[copied]')} CLAUDE.md`);
|
|
402
588
|
stats.copied++;
|
|
403
589
|
} else if (force) {
|
|
404
|
-
|
|
590
|
+
fs.writeFileSync(claudePath, templateContent);
|
|
405
591
|
console.log(` ${colors.dim('[updated]')} CLAUDE.md`);
|
|
406
592
|
stats.updated++;
|
|
407
593
|
} else {
|
|
408
|
-
//
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
594
|
+
// Intelligent merge: append only missing sections
|
|
595
|
+
const existingContent = fs.readFileSync(claudePath, 'utf8');
|
|
596
|
+
const { merged, addedSections } = mergeClaudeContent(existingContent, templateContent);
|
|
597
|
+
|
|
598
|
+
if (addedSections.length === 0) {
|
|
599
|
+
console.log(` ${colors.yellow('[skip]')} CLAUDE.md (all sections present)`);
|
|
600
|
+
stats.skipped++;
|
|
601
|
+
} else {
|
|
602
|
+
fs.writeFileSync(claudePath, merged);
|
|
603
|
+
console.log(` ${colors.blue('[merged]')} CLAUDE.md`);
|
|
604
|
+
console.log(` ${colors.green('Added sections:')}`);
|
|
605
|
+
for (const heading of addedSections) {
|
|
606
|
+
console.log(` ${colors.dim('+')} ${heading}`);
|
|
607
|
+
}
|
|
608
|
+
stats.updated++;
|
|
609
|
+
}
|
|
414
610
|
}
|
|
415
611
|
console.log();
|
|
416
612
|
|