agentic-team-templates 0.3.0 → 0.4.1

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 +2 -2
  2. package/package.json +5 -5
  3. package/src/index.js +205 -9
package/README.md CHANGED
@@ -1,4 +1,4 @@
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)
@@ -277,4 +277,4 @@ Changes to shared rules affect all templates, so be thoughtful with modification
277
277
 
278
278
  ## License
279
279
 
280
- MIT © [David Mendez](https://github.com/desiratech)
280
+ 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.1",
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