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.
Files changed (3) hide show
  1. package/README.md +19 -10
  2. package/package.json +5 -5
  3. package/src/index.js +205 -9
package/README.md CHANGED
@@ -1,9 +1,18 @@
1
- # cursor-templates
1
+ # agentic-team-templates
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/cursor-templates.svg)](https://www.npmjs.com/package/cursor-templates)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
5
 
6
- AI coding assistant templates for Cursor IDE. Pre-configured rules and guidelines that help AI assistants write better code in your projects.
6
+ **Compatible with:**
7
+
8
+ ![Cursor](https://img.shields.io/badge/Cursor_IDE-black?style=flat&logo=cursor)
9
+ ![Claude Code](https://img.shields.io/badge/Claude_Code-cc785c?style=flat&logo=anthropic)
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 two things in your project:
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 # AI development guide
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 in Cursor.
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
- - **Cursor IDE**: Any version with rules support
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/desiratech)
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.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 <desiratech@gmail.com>",
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/cursor-templates.git"
21
+ "url": "git+https://github.com/djm204/agentic-team-templates.git"
22
22
  },
23
- "homepage": "https://github.com/djm204/cursor-templates#readme",
23
+ "homepage": "https://github.com/djm204/agentic-team-templates#readme",
24
24
  "bugs": {
25
- "url": "https://github.com/djm204/cursor-templates/issues"
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
- console.log(` ${colors.blue('[rename]')} CLAUDE.md CLAUDE-1.md`);
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
- generateClaudeMd(targetDir, templates);
586
+ fs.writeFileSync(claudePath, templateContent);
401
587
  console.log(` ${colors.dim('[copied]')} CLAUDE.md`);
402
588
  stats.copied++;
403
589
  } else if (force) {
404
- generateClaudeMd(targetDir, templates);
590
+ fs.writeFileSync(claudePath, templateContent);
405
591
  console.log(` ${colors.dim('[updated]')} CLAUDE.md`);
406
592
  stats.updated++;
407
593
  } else {
408
- // Save ours as CLAUDE-1.md
409
- const altClaudePath = path.join(targetDir, 'CLAUDE-1.md');
410
- generateClaudeMdToPath(targetDir, templates, altClaudePath);
411
- renamedFiles.push({ original: 'CLAUDE.md', renamed: 'CLAUDE-1.md' });
412
- console.log(` ${colors.blue('[rename]')} CLAUDE.md CLAUDE-1.md`);
413
- stats.renamed++;
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