agents-templated 1.2.9 → 1.2.11

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 CHANGED
@@ -120,11 +120,11 @@ Agents Templated automatically configures 4 major AI coding assistants:
120
120
  | AI Agent | Config File | Auto-Discovery |
121
121
  |----------|-------------|----------------|
122
122
  | **Cursor** | `.cursorrules` | ✅ Auto-loads in Cursor IDE |
123
- | **GitHub Copilot** | `.github/copilot-instructions.md` | ✅ Auto-loads in VS Code |
124
- | **Claude** | `CLAUDE.md` | ✅ Auto-loads in Claude IDE/API |
125
- | **Gemini** | `GEMINI.md` | ✅ Auto-loads in Gemini IDE/API |
123
+ | **GitHub Copilot** | `.github/instructions/copilot-instructions.md` (+ shim `.github/copilot-instructions.md`) | ✅ Auto-loads in VS Code |
124
+ | **Claude** | `.github/instructions/CLAUDE.md` (+ shim `CLAUDE.md`) | ✅ Compatible |
125
+ | **Gemini** | `.github/instructions/GEMINI.md` (+ shim `GEMINI.md`) | ✅ Compatible |
126
126
 
127
- **All agents follow the same standards:** `agents/rules/` contains behavior rules, and `agents/commands/` contains deterministic slash-command contracts.
127
+ **Single source of truth:** `instructions/source/core.md` drives generated tool-compatible instruction files.
128
128
 
129
129
  ---
130
130
 
@@ -134,13 +134,21 @@ When you run `agents-templated init`, you get:
134
134
 
135
135
  ```
136
136
  your-project/
137
+ ├── instructions/
138
+ │ └── source/
139
+ │ └── core.md # Canonical instruction source of truth
140
+
137
141
  ├── agent-docs/ # 📚 Comprehensive documentation
138
- │ ├── AGENTS.MD # AI assistant guide
139
142
  │ ├── ARCHITECTURE.md # Project architecture & tech stack
140
143
  │ └── README.md # Human-readable setup guide
141
144
 
142
- ├── agents/ # 🤖 AI Agent rules and skills
143
- │ ├── rules/
145
+ ├── .github/
146
+ │ ├── instructions/ # Canonical generated instructions
147
+ │ │ ├── AGENTS.md
148
+ │ │ ├── copilot-instructions.md
149
+ │ │ ├── CLAUDE.md
150
+ │ │ ├── GEMINI.md
151
+ │ │ └── rules/
144
152
  │ │ ├── core.mdc # Core development principles
145
153
  │ │ ├── security.mdc # Security patterns (CRITICAL)
146
154
  │ │ ├── testing.mdc # Testing strategy
@@ -151,7 +159,18 @@ your-project/
151
159
  │ │ ├── intent-routing.mdc # Intent-to-command routing policy
152
160
  │ │ ├── system-workflow.mdc # End-to-end delivery lifecycle gates
153
161
  │ │ └── hardening.mdc # Hardening and obfuscation guidance
154
- │ ├── commands/
162
+ │ ├── skills/
163
+ │ │ ├── find-skills/ # Skill discovery helper
164
+ │ │ ├── feature-delivery/ # Scoped feature delivery workflow
165
+ │ │ ├── bug-triage/ # Reproduction-first defect workflow
166
+ │ │ ├── app-hardening/ # Hardening and release-evidence workflow
167
+ │ │ ├── ui-ux-pro-max/ # Advanced UI/UX design implementation skill
168
+ │ │ ├── README.md # Guide for creating custom skills
169
+ │ │ └── [your-custom-skills]/ # Your project-specific skills
170
+ │ └── copilot-instructions.md # Compatibility shim for Copilot
171
+
172
+ ├── agents/ # 🤖 Deterministic command contracts
173
+ │ └── commands/
155
174
  │ │ ├── SCHEMA.md # Global slash-command response schema
156
175
  │ │ ├── plan.md # /plan contract
157
176
  │ │ ├── fix.md # /fix contract
@@ -159,20 +178,10 @@ your-project/
159
178
  │ │ ├── release.md # /release contract
160
179
  │ │ ├── ... # Other command contracts
161
180
  │ │ └── README.md # Commands directory guide
162
- │ └── skills/
163
- │ ├── find-skills/ # Skill discovery helper
164
- │ ├── feature-delivery/ # Scoped feature delivery workflow
165
- │ ├── bug-triage/ # Reproduction-first defect workflow
166
- │ ├── app-hardening/ # Hardening and release-evidence workflow
167
- │ ├── ui-ux-pro-max/ # Advanced UI/UX design implementation skill
168
- │ ├── README.md # Guide for creating custom skills
169
- │ └── [your-custom-skills]/ # Your project-specific skills
170
-
171
- ├── .github/
172
- │ └── copilot-instructions.md # GitHub Copilot config
173
181
 
174
- ├── CLAUDE.md # Claude AI config
175
- ├── GEMINI.md # Gemini AI config
182
+ ├── AGENTS.MD # Compatibility shim for generic agents
183
+ ├── CLAUDE.md # Compatibility shim for Claude tooling
184
+ ├── GEMINI.md # Compatibility shim for Gemini tooling
176
185
  ├── .cursorrules # Cursor IDE config
177
186
  ├── .gitignore # Pre-configured Git ignore
178
187
  └── README.md # Project documentation
package/bin/cli.js CHANGED
@@ -6,6 +6,19 @@ const fs = require('fs-extra');
6
6
  const path = require('path');
7
7
  const chalk = require('chalk');
8
8
  const { version } = require('../package.json');
9
+ const {
10
+ LAYOUT,
11
+ resolveRulesDir,
12
+ resolveSkillsDir,
13
+ hasAnyLayout,
14
+ getLegacyMigrationPlan
15
+ } = require('../lib/layout');
16
+ const {
17
+ CORE_SOURCE_REL_PATH,
18
+ GENERATED_INSTRUCTION_PATHS,
19
+ writeGeneratedInstructions,
20
+ validateInstructionDrift
21
+ } = require('../lib/instructions');
9
22
 
10
23
  // Resolve the templates directory - works in both dev and installed contexts
11
24
  const getTemplatesDir = () => {
@@ -104,8 +117,8 @@ program
104
117
  choices: [
105
118
  { name: 'All components', value: 'all' },
106
119
  { name: 'Documentation files (agent-docs/)', value: 'docs' },
107
- { name: 'Agent rules (agents/rules/*.mdc)', value: 'rules' },
108
- { name: 'Skills (agents/skills/*)', value: 'skills' },
120
+ { name: 'Agent rules (.github/instructions/rules/*.mdc)', value: 'rules' },
121
+ { name: 'Skills (.github/skills/*)', value: 'skills' },
109
122
  { name: 'AI Agent instructions (Cursor, Copilot, VSCode, Gemini)', value: 'github' }
110
123
  ],
111
124
  default: ['all']
@@ -136,18 +149,19 @@ program
136
149
  if (installAll || choices.includes('docs')) {
137
150
  console.log(chalk.yellow('Installing documentation files...'));
138
151
  const sourceDir = path.join(templateDir, 'agent-docs');
139
- const targetDir = path.join(targetDir, 'agent-docs');
140
- await fs.ensureDir(targetDir);
141
- await copyDirectory(sourceDir, targetDir, options.force);
152
+ const targetDocsDir = path.join(targetDir, 'agent-docs');
153
+ await fs.ensureDir(targetDocsDir);
154
+ await copyDirectory(sourceDir, targetDocsDir, options.force);
155
+ await copyFiles(templateDir, targetDir, [CORE_SOURCE_REL_PATH], options.force);
142
156
  }
143
157
 
144
158
  // Install agent rules
145
159
  if (installAll || choices.includes('rules')) {
146
160
  console.log(chalk.yellow('Installing agent rules...'));
147
- await fs.ensureDir(path.join(targetDir, 'agents', 'rules'));
161
+ await fs.ensureDir(path.join(targetDir, LAYOUT.canonical.rulesDir));
148
162
  await copyDirectory(
149
163
  path.join(templateDir, 'agents', 'rules'),
150
- path.join(targetDir, 'agents', 'rules'),
164
+ path.join(targetDir, LAYOUT.canonical.rulesDir),
151
165
  options.force
152
166
  );
153
167
  }
@@ -155,10 +169,10 @@ program
155
169
  // Install skills
156
170
  if (installAll || choices.includes('skills')) {
157
171
  console.log(chalk.yellow('Installing skills...'));
158
- await fs.ensureDir(path.join(targetDir, 'agents', 'skills'));
172
+ await fs.ensureDir(path.join(targetDir, LAYOUT.canonical.skillsDir));
159
173
  await copyDirectory(
160
174
  path.join(templateDir, 'agents', 'skills'),
161
- path.join(targetDir, 'agents', 'skills'),
175
+ path.join(targetDir, LAYOUT.canonical.skillsDir),
162
176
  options.force
163
177
  );
164
178
  }
@@ -166,24 +180,20 @@ program
166
180
  // Install AI Agent instructions (Cursor, Copilot, Claude, Gemini)
167
181
  if (installAll || choices.includes('github')) {
168
182
  console.log(chalk.yellow('Installing AI agent instructions...'));
169
- await fs.ensureDir(path.join(targetDir, '.github'));
170
- await copyFiles(templateDir, targetDir, [
171
- '.cursorrules',
172
- '.github/copilot-instructions.md',
173
- 'CLAUDE.md',
174
- 'GEMINI.md'
175
- ], options.force);
183
+ await fs.ensureDir(path.join(targetDir, '.github', 'instructions'));
184
+ await copyFiles(templateDir, targetDir, ['.cursorrules'], options.force);
185
+ await writeGeneratedInstructions(targetDir, templateDir, options.force);
176
186
  console.log(chalk.gray(' ✓ Cursor (.cursorrules)'));
177
- console.log(chalk.gray(' ✓ GitHub Copilot (.github/copilot-instructions.md)'));
178
- console.log(chalk.gray(' ✓ Claude (CLAUDE.md)'));
179
- console.log(chalk.gray(' ✓ Google Gemini (GEMINI.md)'));
187
+ console.log(chalk.gray(' ✓ GitHub Copilot (.github/instructions/copilot-instructions.md + compat shim)'));
188
+ console.log(chalk.gray(' ✓ Claude (.github/instructions/CLAUDE.md + compat shim)'));
189
+ console.log(chalk.gray(' ✓ Google Gemini (.github/instructions/GEMINI.md + compat shim)'));
180
190
  }
181
191
 
182
192
  console.log(chalk.green.bold('\nInstallation complete!\n'));
183
193
  console.log(chalk.cyan('Next steps:'));
184
- console.log(chalk.white(' 1. Review AGENTS.md for generic AI assistant guide'));
194
+ console.log(chalk.white(' 1. Review instructions/source/core.md (canonical AI guide)'));
185
195
  console.log(chalk.white(' 2. Review agent-docs/ARCHITECTURE.md for project guidelines'));
186
- console.log(chalk.white(' 3. Review AGENTS.md for AI assistant guide'));
196
+ console.log(chalk.white(' 3. Review .github/instructions/ for generated tool-compatible files'));
187
197
  console.log(chalk.white(' 4. Configure your AI assistant (Cursor, Copilot, etc.)'));
188
198
  console.log(chalk.white(' 5. Adapt the rules to your technology stack\n'));
189
199
 
@@ -195,89 +205,69 @@ program
195
205
 
196
206
  program
197
207
  .command('wizard')
198
- .description('Interactive setup wizard with tech stack recommendations')
208
+ .description('Interactive setup wizard')
199
209
  .action(async () => {
200
210
  try {
201
211
  const templateDir = getTemplatesDir();
212
+ const targetDir = process.cwd();
202
213
  console.log(chalk.blue.bold('\n🧙 Agents Templated Setup Wizard\n'));
203
214
  console.log(chalk.gray('Let\'s set up your project with the right patterns and guidelines.\n'));
204
215
 
205
- // Step 1: Tech stack selection
206
- const stackAnswers = await inquirer.prompt([
207
- {
208
- type: 'list',
209
- name: 'category',
210
- message: 'What type of project is this?',
211
- choices: [
212
- { name: '🌐 Full-stack Web Application', value: 'fullstack' },
213
- { name: '⚛️ Frontend Only', value: 'frontend' },
214
- { name: '🔧 Backend API', value: 'backend' },
215
- { name: '📦 NPM Package/Library', value: 'package' },
216
- { name: '🤖 CLI Tool', value: 'cli' },
217
- { name: '🎯 Custom/Other', value: 'custom' }
218
- ]
219
- }
220
- ]);
216
+ const hasExistingSetup = await hasInstalledTemplates(targetDir);
217
+ if (hasExistingSetup) {
218
+ console.log(chalk.cyan('Detected an existing agents-templated setup in this project.\n'));
221
219
 
222
- let techStack = {};
223
-
224
- if (stackAnswers.category === 'fullstack' || stackAnswers.category === 'frontend') {
225
- const frontendAnswers = await inquirer.prompt([
220
+ const modeAnswer = await inquirer.prompt([
226
221
  {
227
222
  type: 'list',
228
- name: 'framework',
229
- message: 'Which frontend framework?',
223
+ name: 'mode',
224
+ message: 'How would you like to proceed?',
230
225
  choices: [
231
- 'React / Next.js',
232
- 'Vue / Nuxt',
233
- 'Angular',
234
- 'Svelte / SvelteKit',
235
- 'Vanilla JS / HTML',
236
- 'Other'
237
- ]
226
+ { name: 'Update existing setup (recommended)', value: 'update' },
227
+ { name: 'Run full setup flow', value: 'setup' }
228
+ ],
229
+ default: 'update'
238
230
  }
239
231
  ]);
240
- techStack.frontend = frontendAnswers.framework;
241
- }
242
232
 
243
- if (stackAnswers.category === 'fullstack' || stackAnswers.category === 'backend') {
244
- const backendAnswers = await inquirer.prompt([
245
- {
246
- type: 'list',
247
- name: 'framework',
248
- message: 'Which backend framework?',
249
- choices: [
250
- 'Node.js (Express/Fastify)',
251
- 'Python (Django/FastAPI)',
252
- 'Go',
253
- 'Rust',
254
- 'Java / Spring Boot',
255
- 'Ruby on Rails',
256
- 'PHP (Laravel)',
257
- 'Other'
258
- ]
259
- },
260
- {
261
- type: 'list',
262
- name: 'database',
263
- message: 'Which database?',
264
- choices: [
265
- 'PostgreSQL',
266
- 'MySQL / MariaDB',
267
- 'MongoDB',
268
- 'SQLite',
269
- 'Supabase',
270
- 'Firebase',
271
- 'None / Not sure yet',
272
- 'Other'
273
- ]
274
- }
275
- ]);
276
- techStack.backend = backendAnswers.framework;
277
- techStack.database = backendAnswers.database;
233
+ if (modeAnswer.mode === 'update') {
234
+ const updateAnswers = await inquirer.prompt([
235
+ {
236
+ type: 'checkbox',
237
+ name: 'components',
238
+ message: 'Select components to update:',
239
+ choices: [
240
+ { name: 'All components', value: 'all', checked: true },
241
+ { name: 'Documentation (agent-docs/)', value: 'docs' },
242
+ { name: 'Agent Rules (security, testing, database, etc.)', value: 'rules' },
243
+ { name: 'Skills (reusable agent capabilities)', value: 'skills' },
244
+ { name: 'AI Agent instructions (Cursor, Copilot, VSCode, Gemini)', value: 'github' }
245
+ ],
246
+ validate: (answer) => {
247
+ if (answer.length === 0) {
248
+ return 'You must choose at least one component.';
249
+ }
250
+ return true;
251
+ }
252
+ },
253
+ {
254
+ type: 'confirm',
255
+ name: 'overwrite',
256
+ message: 'Overwrite existing files while updating?',
257
+ default: true
258
+ }
259
+ ]);
260
+
261
+ console.log(chalk.blue('\n📦 Updating selected components...\n'));
262
+ await updateSelectedComponents(targetDir, templateDir, updateAnswers.components, updateAnswers.overwrite);
263
+
264
+ console.log(chalk.green.bold('\n✅ Update complete!\n'));
265
+ console.log(chalk.gray('Tip: run "agents-templated validate" to verify your setup.\n'));
266
+ return;
267
+ }
278
268
  }
279
269
 
280
- // Step 2: Component selection
270
+ // Component selection
281
271
  const componentAnswers = await inquirer.prompt([
282
272
  {
283
273
  type: 'checkbox',
@@ -307,7 +297,6 @@ program
307
297
  // Step 3: Install components
308
298
  console.log(chalk.blue('\n📦 Installing components...\n'));
309
299
 
310
- const targetDir = process.cwd();
311
300
  const options = {
312
301
  force: componentAnswers.overwrite,
313
302
  docs: componentAnswers.components.includes('docs'),
@@ -323,15 +312,16 @@ program
323
312
  const targetDocsDir = path.join(targetDir, 'agent-docs');
324
313
  await fs.ensureDir(targetDocsDir);
325
314
  await copyDirectory(sourceDocsDir, targetDocsDir, options.force);
315
+ await copyFiles(templateDir, targetDir, [CORE_SOURCE_REL_PATH], options.force);
326
316
  }
327
317
 
328
318
  // Install agent rules
329
319
  if (options.rules) {
330
320
  console.log(chalk.yellow('Installing agent rules...'));
331
- await fs.ensureDir(path.join(targetDir, 'agents', 'rules'));
321
+ await fs.ensureDir(path.join(targetDir, LAYOUT.canonical.rulesDir));
332
322
  await copyDirectory(
333
323
  path.join(templateDir, 'agents', 'rules'),
334
- path.join(targetDir, 'agents', 'rules'),
324
+ path.join(targetDir, LAYOUT.canonical.rulesDir),
335
325
  options.force
336
326
  );
337
327
  }
@@ -339,10 +329,10 @@ program
339
329
  // Install skills
340
330
  if (options.skills) {
341
331
  console.log(chalk.yellow('Installing skills...'));
342
- await fs.ensureDir(path.join(targetDir, 'agents', 'skills'));
332
+ await fs.ensureDir(path.join(targetDir, LAYOUT.canonical.skillsDir));
343
333
  await copyDirectory(
344
334
  path.join(templateDir, 'agents', 'skills'),
345
- path.join(targetDir, 'agents', 'skills'),
335
+ path.join(targetDir, LAYOUT.canonical.skillsDir),
346
336
  options.force
347
337
  );
348
338
  }
@@ -350,64 +340,24 @@ program
350
340
  // Install AI Agent instructions (Cursor, Copilot, VSCode, Gemini)
351
341
  if (options.github) {
352
342
  console.log(chalk.yellow('Installing AI agent instructions...'));
353
- await fs.ensureDir(path.join(targetDir, '.github'));
354
- await copyFiles(templateDir, targetDir, [
355
- '.cursorrules',
356
- '.github/copilot-instructions.md',
357
- 'AGENTS.md',
358
- 'CLAUDE.md',
359
- 'GEMINI.md'
360
- ], options.force);
343
+ await fs.ensureDir(path.join(targetDir, '.github', 'instructions'));
344
+ await copyFiles(templateDir, targetDir, ['.cursorrules'], options.force);
345
+ await writeGeneratedInstructions(targetDir, templateDir, options.force);
361
346
  console.log(chalk.gray(' ✓ Cursor (.cursorrules)'));
362
- console.log(chalk.gray(' ✓ GitHub Copilot (.github/copilot-instructions.md)'));
363
- console.log(chalk.gray(' ✓ Generic AI (AGENTS.md)'));
364
- console.log(chalk.gray(' ✓ Claude (CLAUDE.md)'));
365
- console.log(chalk.gray(' ✓ Google Gemini (GEMINI.md)'));
347
+ console.log(chalk.gray(' ✓ GitHub Copilot (.github/instructions/copilot-instructions.md + compat shim)'));
348
+ console.log(chalk.gray(' ✓ Generic AI (.github/instructions/AGENTS.md + compat shim)'));
349
+ console.log(chalk.gray(' ✓ Claude (.github/instructions/CLAUDE.md + compat shim)'));
350
+ console.log(chalk.gray(' ✓ Google Gemini (.github/instructions/GEMINI.md + compat shim)'));
366
351
  }
367
352
 
368
- // Step 4: Show recommendations
353
+ // Show summary and next steps
369
354
  console.log(chalk.green.bold('\n✅ Installation complete!\n'));
370
355
 
371
- console.log(chalk.blue('📋 Your Project Setup:\n'));
372
- if (techStack.frontend) console.log(chalk.white(` Frontend: ${techStack.frontend}`));
373
- if (techStack.backend) console.log(chalk.white(` Backend: ${techStack.backend}`));
374
- if (techStack.database) console.log(chalk.white(` Database: ${techStack.database}`));
375
-
376
356
  console.log(chalk.cyan('\n📚 Next Steps:\n'));
377
- console.log(chalk.white(' 1. Review AGENTS.md for AI assistant guide'));
357
+ console.log(chalk.white(' 1. Review instructions/source/core.md (canonical AI guide)'));
378
358
  console.log(chalk.white(' 2. Review agent-docs/ARCHITECTURE.md for project guidelines'));
379
- console.log(chalk.white(' 3. Review AGENTS.md for AI assistant guide'));
380
- console.log(chalk.white(' 3. Customize agents/rules/*.mdc for your tech stack'));
381
-
382
- if (techStack.frontend || techStack.backend) {
383
- console.log(chalk.cyan('\n📦 Recommended Packages:\n'));
384
-
385
- if (techStack.frontend?.includes('React')) {
386
- console.log(chalk.white(' npm install react react-dom next typescript'));
387
- console.log(chalk.white(' npm install -D @types/react @types/node'));
388
- } else if (techStack.frontend?.includes('Vue')) {
389
- console.log(chalk.white(' npm install vue nuxt typescript'));
390
- } else if (techStack.frontend?.includes('Svelte')) {
391
- console.log(chalk.white(' npm install svelte @sveltejs/kit typescript'));
392
- }
393
-
394
- if (techStack.backend?.includes('Node.js')) {
395
- console.log(chalk.white(' npm install express zod dotenv'));
396
- console.log(chalk.white(' npm install -D @types/express'));
397
- } else if (techStack.backend?.includes('Python')) {
398
- console.log(chalk.white(' pip install django djangorestframework pydantic'));
399
- } else if (techStack.backend?.includes('Go')) {
400
- console.log(chalk.white(' go get github.com/gin-gonic/gin'));
401
- }
402
-
403
- if (techStack.database?.includes('PostgreSQL')) {
404
- console.log(chalk.white(' npm install @prisma/client (or) npm install pg'));
405
- } else if (techStack.database?.includes('MongoDB')) {
406
- console.log(chalk.white(' npm install mongoose'));
407
- } else if (techStack.database?.includes('Supabase')) {
408
- console.log(chalk.white(' npm install @supabase/supabase-js'));
409
- }
410
- }
359
+ console.log(chalk.white(' 3. Review .github/instructions/ for generated tool-compatible files'));
360
+ console.log(chalk.white(' 4. Customize .github/instructions/rules/*.mdc for your tech stack'));
411
361
 
412
362
  console.log(chalk.cyan('\n🔒 Security Reminder:\n'));
413
363
  console.log(chalk.white(' • Review agents/rules/security.mdc'));
@@ -427,8 +377,8 @@ program
427
377
  .action(() => {
428
378
  console.log(chalk.blue.bold('\nAvailable Components:\n'));
429
379
  console.log(chalk.yellow('docs') + ' - Documentation files (agent-docs/ directory)');
430
- console.log(chalk.yellow('rules') + ' - Agent rules (core, database, frontend, security, testing, style)');
431
- console.log(chalk.yellow('skills') + ' - Agent skills (find-skills, ui-ux-pro-max)');
380
+ console.log(chalk.yellow('rules') + ' - Agent rules (.github/instructions/rules/*.mdc)');
381
+ console.log(chalk.yellow('skills') + ' - Agent skills (.github/skills/*)');
432
382
  console.log(chalk.yellow('github') + ' - AI Agent instructions (Cursor, Copilot, VSCode, Gemini)');
433
383
  console.log(chalk.yellow('all') + ' - All components');
434
384
 
@@ -454,11 +404,12 @@ program
454
404
  let warnings = [];
455
405
  let passed = [];
456
406
 
457
- // Check documentation files
458
- if (await fs.pathExists(path.join(targetDir, 'AGENTS.md'))) {
459
- passed.push(`✓ AGENTS.md found`);
407
+ // Check canonical source file
408
+ const coreSourcePath = path.join(targetDir, CORE_SOURCE_REL_PATH);
409
+ if (await fs.pathExists(coreSourcePath)) {
410
+ passed.push(`✓ ${CORE_SOURCE_REL_PATH} found`);
460
411
  } else {
461
- warnings.push(`⚠ AGENTS.md missing - run 'agents-templated init --docs'`);
412
+ warnings.push(`⚠ ${CORE_SOURCE_REL_PATH} missing - run 'agents-templated init --docs'`);
462
413
  }
463
414
 
464
415
  const docFiles = ['ARCHITECTURE.md'];
@@ -478,32 +429,67 @@ program
478
429
 
479
430
  // Check agent rules
480
431
  const ruleFiles = ['core.mdc', 'security.mdc', 'testing.mdc', 'frontend.mdc', 'database.mdc', 'style.mdc'];
481
- const rulesDir = path.join(targetDir, 'agents', 'rules');
482
-
432
+ const canonicalRulesDir = path.join(targetDir, LAYOUT.canonical.rulesDir);
433
+ const legacyRulesDir = path.join(targetDir, LAYOUT.legacy.rulesDirs[0]);
434
+ const rulesDir = path.join(targetDir, resolveRulesDir(targetDir));
435
+
436
+ if (!(await fs.pathExists(canonicalRulesDir)) && await fs.pathExists(legacyRulesDir)) {
437
+ issues.push(`✗ Legacy rules layout detected at ${LAYOUT.legacy.rulesDirs[0]} - run 'agents-templated update --all' to migrate`);
438
+ }
439
+
483
440
  if (await fs.pathExists(rulesDir)) {
441
+ const relativeRulesDir = path.relative(targetDir, rulesDir) || LAYOUT.canonical.rulesDir;
484
442
  for (const file of ruleFiles) {
485
443
  if (await fs.pathExists(path.join(rulesDir, file))) {
486
- passed.push(`✓ agents/rules/${file} found`);
444
+ passed.push(`✓ ${relativeRulesDir}/${file} found`);
487
445
  } else {
488
- warnings.push(`⚠ agents/rules/${file} missing`);
446
+ warnings.push(`⚠ ${relativeRulesDir}/${file} missing`);
489
447
  }
490
448
  }
491
449
  } else {
492
- warnings.push(`⚠ agents/rules directory missing - run 'agents-templated init --rules'`);
450
+ warnings.push(`⚠ ${LAYOUT.canonical.rulesDir} directory missing - run 'agents-templated init --rules'`);
493
451
  }
494
452
 
495
453
  // Check skills
496
- const skillsDir = path.join(targetDir, 'agents', 'skills');
454
+ const canonicalSkillsDir = path.join(targetDir, LAYOUT.canonical.skillsDir);
455
+ const legacySkillsDir = path.join(targetDir, LAYOUT.legacy.skillsDirs[0]);
456
+ const skillsDir = path.join(targetDir, resolveSkillsDir(targetDir));
457
+
458
+ if (!(await fs.pathExists(canonicalSkillsDir)) && await fs.pathExists(legacySkillsDir)) {
459
+ issues.push(`✗ Legacy skills layout detected at ${LAYOUT.legacy.skillsDirs[0]} - run 'agents-templated update --all' to migrate`);
460
+ }
461
+
497
462
  if (await fs.pathExists(skillsDir)) {
498
463
  const skills = await fs.readdir(skillsDir);
499
- passed.push(`✓ ${skills.length} skills installed`);
464
+ const relativeSkillsDir = path.relative(targetDir, skillsDir) || LAYOUT.canonical.skillsDir;
465
+ passed.push(`✓ ${skills.length} skills installed in ${relativeSkillsDir}`);
500
466
  } else {
501
- warnings.push(`⚠ agents/skills directory missing - run 'agents-templated init --skills'`);
467
+ warnings.push(`⚠ ${LAYOUT.canonical.skillsDir} directory missing - run 'agents-templated init --skills'`);
468
+ }
469
+
470
+ // Check generated instruction files and drift
471
+ const hasInstructionFootprint =
472
+ await fs.pathExists(path.join(targetDir, '.github', 'instructions')) ||
473
+ await fs.pathExists(path.join(targetDir, '.claude', 'rules')) ||
474
+ await fs.pathExists(path.join(targetDir, GENERATED_INSTRUCTION_PATHS.compatibility.copilot)) ||
475
+ await fs.pathExists(path.join(targetDir, GENERATED_INSTRUCTION_PATHS.compatibility.claude)) ||
476
+ await fs.pathExists(path.join(targetDir, GENERATED_INSTRUCTION_PATHS.compatibility.gemini)) ||
477
+ await fs.pathExists(path.join(targetDir, GENERATED_INSTRUCTION_PATHS.compatibility.generic));
478
+
479
+ if (hasInstructionFootprint) {
480
+ const instructionDrift = await validateInstructionDrift(targetDir);
481
+ if (instructionDrift.missingCore) {
482
+ issues.push(`✗ Canonical instruction source missing - run 'agents-templated init --docs --github'`);
483
+ } else if (!instructionDrift.ok) {
484
+ issues.push(`✗ Generated instruction files are out of sync: ${instructionDrift.driftFiles.join(', ')}`);
485
+ } else {
486
+ passed.push('✓ Generated instruction files are in sync with canonical source');
487
+ }
502
488
  }
503
489
 
504
- // Check GitHub Copilot config
505
- const copilotFile = path.join(targetDir, '.github', 'copilot-instructions.md');
506
- if (await fs.pathExists(copilotFile)) {
490
+ const canonicalCopilotFile = path.join(targetDir, GENERATED_INSTRUCTION_PATHS.canonical.copilot);
491
+ const compatCopilotFile = path.join(targetDir, GENERATED_INSTRUCTION_PATHS.compatibility.copilot);
492
+ if (await fs.pathExists(canonicalCopilotFile) || await fs.pathExists(compatCopilotFile)) {
507
493
  passed.push(`✓ GitHub Copilot configuration found`);
508
494
  } else {
509
495
  warnings.push(`⚠ GitHub Copilot configuration missing - run 'agents-templated init --github'`);
@@ -639,6 +625,81 @@ async function copyDirectory(sourceDir, targetDir, force = false) {
639
625
  }
640
626
  }
641
627
 
628
+ async function hasInstalledTemplates(targetDir) {
629
+ return await hasAnyLayout(targetDir) ||
630
+ await fs.pathExists(path.join(targetDir, CORE_SOURCE_REL_PATH)) ||
631
+ await fs.pathExists(path.join(targetDir, GENERATED_INSTRUCTION_PATHS.compatibility.generic));
632
+ }
633
+
634
+ async function updateSelectedComponents(targetDir, templateDir, selectedComponents, overwrite = true) {
635
+ const components = selectedComponents.includes('all')
636
+ ? ['docs', 'rules', 'skills', 'github']
637
+ : selectedComponents;
638
+
639
+ if (components.includes('docs')) {
640
+ console.log(chalk.yellow('Updating documentation files...'));
641
+ await fs.ensureDir(path.join(targetDir, 'agent-docs'));
642
+ await copyDirectory(
643
+ path.join(templateDir, 'agent-docs'),
644
+ path.join(targetDir, 'agent-docs'),
645
+ overwrite
646
+ );
647
+ await copyFiles(templateDir, targetDir, [CORE_SOURCE_REL_PATH], overwrite);
648
+ }
649
+
650
+ if (components.includes('rules')) {
651
+ console.log(chalk.yellow('Updating agent rules...'));
652
+ await fs.ensureDir(path.join(targetDir, LAYOUT.canonical.rulesDir));
653
+ await copyDirectory(
654
+ path.join(templateDir, 'agents', 'rules'),
655
+ path.join(targetDir, LAYOUT.canonical.rulesDir),
656
+ overwrite
657
+ );
658
+ }
659
+
660
+ if (components.includes('skills')) {
661
+ console.log(chalk.yellow('Updating skills...'));
662
+ await fs.ensureDir(path.join(targetDir, LAYOUT.canonical.skillsDir));
663
+ await copyDirectory(
664
+ path.join(templateDir, 'agents', 'skills'),
665
+ path.join(targetDir, LAYOUT.canonical.skillsDir),
666
+ overwrite
667
+ );
668
+ }
669
+
670
+ if (components.includes('github')) {
671
+ console.log(chalk.yellow('Updating AI agent instructions...'));
672
+ await fs.ensureDir(path.join(targetDir, '.github', 'instructions'));
673
+ await copyFiles(templateDir, targetDir, ['.cursorrules'], overwrite);
674
+ await writeGeneratedInstructions(targetDir, templateDir, overwrite);
675
+ }
676
+
677
+ if ((components.includes('docs') || components.includes('github')) && !components.includes('github')) {
678
+ await writeGeneratedInstructions(targetDir, templateDir, overwrite);
679
+ }
680
+ }
681
+
682
+ async function applyLegacyMigrationPlan(targetDir, migrationPlan, overwrite = true) {
683
+ for (const move of migrationPlan) {
684
+ const sourcePath = path.join(targetDir, move.source);
685
+ const targetPath = path.join(targetDir, move.target);
686
+
687
+ if (!(await fs.pathExists(sourcePath))) {
688
+ continue;
689
+ }
690
+
691
+ await fs.ensureDir(path.dirname(targetPath));
692
+
693
+ if (await fs.pathExists(targetPath)) {
694
+ await copyDirectory(sourcePath, targetPath, overwrite);
695
+ await fs.remove(sourcePath);
696
+ continue;
697
+ }
698
+
699
+ await fs.move(sourcePath, targetPath, { overwrite });
700
+ }
701
+ }
702
+
642
703
  program
643
704
  .command('update')
644
705
  .description('Check for and apply template updates')
@@ -657,8 +718,7 @@ program
657
718
  console.log(chalk.blue.bold('\n🔄 Checking for template updates...\n'));
658
719
 
659
720
  // Check if templates are installed
660
- const hasTemplates = await fs.pathExists(path.join(targetDir, 'AGENTS.md')) ||
661
- await fs.pathExists(path.join(targetDir, 'agents'));
721
+ const hasTemplates = await hasInstalledTemplates(targetDir);
662
722
 
663
723
  if (!hasTemplates) {
664
724
  console.log(chalk.yellow('No templates detected in this directory.'));
@@ -666,6 +726,38 @@ program
666
726
  process.exit(0);
667
727
  }
668
728
 
729
+ const migrationPlan = await getLegacyMigrationPlan(targetDir);
730
+ if (migrationPlan.length > 0) {
731
+ console.log(chalk.yellow('Legacy layout detected. Migration to canonical Copilot-style paths is required.\n'));
732
+ migrationPlan.forEach(({ source, target }) => {
733
+ console.log(chalk.white(` ${source} -> ${target}`));
734
+ });
735
+ console.log('');
736
+
737
+ if (options.checkOnly) {
738
+ console.log(chalk.red('Migration required before validation can pass.'));
739
+ console.log(chalk.gray('Run "agents-templated update" and confirm migration.\n'));
740
+ process.exit(1);
741
+ }
742
+
743
+ const migrationAnswer = await inquirer.prompt([
744
+ {
745
+ type: 'confirm',
746
+ name: 'proceed',
747
+ message: 'Migrate legacy directories now?',
748
+ default: true
749
+ }
750
+ ]);
751
+
752
+ if (!migrationAnswer.proceed) {
753
+ console.log(chalk.red('\nMigration skipped. Setup remains non-canonical.\n'));
754
+ process.exit(1);
755
+ }
756
+
757
+ await applyLegacyMigrationPlan(targetDir, migrationPlan, true);
758
+ console.log(chalk.green('✓ Legacy layout migration completed.\n'));
759
+ }
760
+
669
761
  const hasComponentSelection = options.all || options.docs || options.rules || options.skills || options.github;
670
762
 
671
763
  // Component refresh mode: update selected parts directly without stack/wizard prompts
@@ -678,46 +770,7 @@ program
678
770
 
679
771
  console.log(chalk.blue('📦 Updating selected components...\n'));
680
772
 
681
- if (selectedComponents.includes('docs')) {
682
- console.log(chalk.yellow('Updating documentation files...'));
683
- await fs.ensureDir(path.join(targetDir, 'agent-docs'));
684
- await copyDirectory(
685
- path.join(templateDir, 'agent-docs'),
686
- path.join(targetDir, 'agent-docs'),
687
- true
688
- );
689
- }
690
-
691
- if (selectedComponents.includes('rules')) {
692
- console.log(chalk.yellow('Updating agent rules...'));
693
- await fs.ensureDir(path.join(targetDir, 'agents', 'rules'));
694
- await copyDirectory(
695
- path.join(templateDir, 'agents', 'rules'),
696
- path.join(targetDir, 'agents', 'rules'),
697
- true
698
- );
699
- }
700
-
701
- if (selectedComponents.includes('skills')) {
702
- console.log(chalk.yellow('Updating skills...'));
703
- await fs.ensureDir(path.join(targetDir, 'agents', 'skills'));
704
- await copyDirectory(
705
- path.join(templateDir, 'agents', 'skills'),
706
- path.join(targetDir, 'agents', 'skills'),
707
- true
708
- );
709
- }
710
-
711
- if (selectedComponents.includes('github')) {
712
- console.log(chalk.yellow('Updating AI agent instructions...'));
713
- await fs.ensureDir(path.join(targetDir, '.github'));
714
- await copyFiles(templateDir, targetDir, [
715
- '.cursorrules',
716
- '.github/copilot-instructions.md',
717
- 'CLAUDE.md',
718
- 'GEMINI.md'
719
- ], true);
720
- }
773
+ await updateSelectedComponents(targetDir, templateDir, selectedComponents, true);
721
774
 
722
775
  console.log(chalk.green.bold('\n✅ Selected component updates applied successfully!\n'));
723
776
  process.exit(0);
@@ -726,27 +779,27 @@ program
726
779
  // List potential updates
727
780
  const updates = [];
728
781
  const checkFiles = [
729
- { file: 'AGENTS.md', component: 'root' },
730
- { file: 'agent-docs/ARCHITECTURE.md', component: 'docs' },
731
- { file: 'agents/rules/security.mdc', component: 'rules' },
732
- { file: 'agents/rules/testing.mdc', component: 'rules' },
733
- { file: 'agents/rules/core.mdc', component: 'rules' },
734
- { file: 'agents/skills/README.md', component: 'skills' },
735
- { file: 'agents/skills/find-skills/SKILL.md', component: 'skills' },
736
- { file: 'agents/skills/ui-ux-pro-max/SKILL.md', component: 'skills' },
737
- { file: '.github/copilot-instructions.md', component: 'github' }
782
+ { targetFile: CORE_SOURCE_REL_PATH, templateFile: CORE_SOURCE_REL_PATH, component: 'root' },
783
+ { targetFile: 'agent-docs/ARCHITECTURE.md', templateFile: 'agent-docs/ARCHITECTURE.md', component: 'docs' },
784
+ { targetFile: `${LAYOUT.canonical.rulesDir}/security.mdc`, templateFile: 'agents/rules/security.mdc', component: 'rules' },
785
+ { targetFile: `${LAYOUT.canonical.rulesDir}/testing.mdc`, templateFile: 'agents/rules/testing.mdc', component: 'rules' },
786
+ { targetFile: `${LAYOUT.canonical.rulesDir}/core.mdc`, templateFile: 'agents/rules/core.mdc', component: 'rules' },
787
+ { targetFile: `${LAYOUT.canonical.skillsDir}/README.md`, templateFile: 'agents/skills/README.md', component: 'skills' },
788
+ { targetFile: `${LAYOUT.canonical.skillsDir}/find-skills/SKILL.md`, templateFile: 'agents/skills/find-skills/SKILL.md', component: 'skills' },
789
+ { targetFile: `${LAYOUT.canonical.skillsDir}/ui-ux-pro-max/SKILL.md`, templateFile: 'agents/skills/ui-ux-pro-max/SKILL.md', component: 'skills' },
790
+ { targetFile: '.cursorrules', templateFile: '.cursorrules', component: 'github' }
738
791
  ];
739
792
 
740
- for (const {file, component} of checkFiles) {
741
- const targetPath = path.join(targetDir, file);
742
- const templatePath = path.join(templateDir, file);
793
+ for (const {targetFile, templateFile, component} of checkFiles) {
794
+ const targetPath = path.join(targetDir, targetFile);
795
+ const templatePath = path.join(templateDir, templateFile);
743
796
 
744
797
  if (await fs.pathExists(targetPath) && await fs.pathExists(templatePath)) {
745
798
  const targetContent = await fs.readFile(targetPath, 'utf8');
746
799
  const templateContent = await fs.readFile(templatePath, 'utf8');
747
800
 
748
801
  if (targetContent !== templateContent) {
749
- updates.push({ file, component });
802
+ updates.push({ targetFile, templateFile, component });
750
803
  }
751
804
  }
752
805
  }
@@ -757,8 +810,8 @@ program
757
810
  }
758
811
 
759
812
  console.log(chalk.yellow(`Found ${updates.length} file(s) with updates available:\n`));
760
- updates.forEach(({ file }) => {
761
- console.log(chalk.white(` 📄 ${file}`));
813
+ updates.forEach(({ targetFile }) => {
814
+ console.log(chalk.white(` 📄 ${targetFile}`));
762
815
  });
763
816
  console.log('');
764
817
 
@@ -785,20 +838,23 @@ program
785
838
  // Apply updates
786
839
  console.log(chalk.blue('\n📦 Applying updates...\n'));
787
840
 
788
- for (const { file } of updates) {
789
- const targetPath = path.join(targetDir, file);
790
- const templatePath = path.join(templateDir, file);
841
+ for (const { targetFile, templateFile } of updates) {
842
+ const targetPath = path.join(targetDir, targetFile);
843
+ const templatePath = path.join(templateDir, templateFile);
791
844
 
792
845
  // Backup original file
793
846
  const backupPath = `${targetPath}.backup`;
794
847
  await fs.copy(targetPath, backupPath);
795
- console.log(chalk.gray(` Backed up: ${file}.backup`));
848
+ console.log(chalk.gray(` Backed up: ${targetFile}.backup`));
796
849
 
797
850
  // Copy new version
798
851
  await fs.copy(templatePath, targetPath, { overwrite: true });
799
- console.log(chalk.green(` ✓ Updated: ${file}`));
852
+ console.log(chalk.green(` ✓ Updated: ${targetFile}`));
800
853
  }
801
854
 
855
+ await writeGeneratedInstructions(targetDir, templateDir, true);
856
+ console.log(chalk.green(' ✓ Regenerated instruction compatibility files'));
857
+
802
858
  console.log(chalk.green.bold('\n✅ Updates applied successfully!\n'));
803
859
  console.log(chalk.gray('Backup files created with .backup extension\n'));
804
860
 
@@ -886,7 +942,7 @@ program
886
942
  console.log(chalk.blue('\n📚 Quick Tips:\n'));
887
943
  console.log(chalk.white(' • Run "agents-templated validate" to check setup'));
888
944
  console.log(chalk.white(' • Run "agents-templated wizard" for guided setup'));
889
- console.log(chalk.white(' • Review agents/rules/security.mdc for security patterns\n'));
945
+ console.log(chalk.white(' • Review .github/instructions/rules/security.mdc for security patterns\n'));
890
946
 
891
947
  } catch (error) {
892
948
  console.error(chalk.red('Error:'), error.message);
package/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
+ const { LAYOUT } = require('./lib/layout');
4
+ const { CORE_SOURCE_REL_PATH, writeGeneratedInstructions } = require('./lib/instructions');
3
5
 
4
6
  /**
5
7
  * Programmatic API for agents-templated
@@ -26,9 +28,9 @@ async function install(targetDir, options = {}) {
26
28
  // Documentation files
27
29
  if (installAll || options.docs) {
28
30
  files.push(
29
- 'AGENTS.md',
30
31
  'agent-docs/ARCHITECTURE.md',
31
- 'agent-docs/README.md'
32
+ 'agent-docs/README.md',
33
+ CORE_SOURCE_REL_PATH
32
34
  );
33
35
  }
34
36
 
@@ -49,51 +51,35 @@ async function install(targetDir, options = {}) {
49
51
 
50
52
  // Agent rules
51
53
  if (installAll || options.rules) {
52
- await fs.ensureDir(path.join(targetDir, 'agents', 'rules'));
54
+ await fs.ensureDir(path.join(targetDir, LAYOUT.canonical.rulesDir));
53
55
  await copyDirectory(
54
56
  path.join(templateDir, 'agents', 'rules'),
55
- path.join(targetDir, 'agents', 'rules'),
57
+ path.join(targetDir, LAYOUT.canonical.rulesDir),
56
58
  options.force
57
59
  );
58
60
  }
59
61
 
60
62
  // Skills
61
63
  if (installAll || options.skills) {
62
- await fs.ensureDir(path.join(targetDir, 'agents', 'skills'));
64
+ await fs.ensureDir(path.join(targetDir, LAYOUT.canonical.skillsDir));
63
65
  await copyDirectory(
64
66
  path.join(templateDir, 'agents', 'skills'),
65
- path.join(targetDir, 'agents', 'skills'),
67
+ path.join(targetDir, LAYOUT.canonical.skillsDir),
66
68
  options.force
67
69
  );
68
70
  }
69
71
 
70
72
  // AI Agent instructions (Cursor, Copilot, Claude, Gemini)
71
73
  if (installAll || options.github) {
72
- await fs.ensureDir(path.join(targetDir, '.github'));
73
-
74
- // Copy all AI agent config files
75
- const agentConfigs = [
76
- '.cursorrules',
77
- 'CLAUDE.md',
78
- 'GEMINI.md'
79
- ];
80
-
81
- for (const config of agentConfigs) {
82
- const sourcePath = path.join(templateDir, config);
83
- const targetPath = path.join(targetDir, config);
84
-
85
- if (await fs.pathExists(sourcePath)) {
86
- await fs.copy(sourcePath, targetPath, { overwrite: options.force });
87
- }
88
- }
89
-
90
- // Copy GitHub Copilot instructions
91
- const sourceGithubPath = path.join(templateDir, '.github', 'copilot-instructions.md');
92
- const targetGithubPath = path.join(targetDir, '.github', 'copilot-instructions.md');
93
-
94
- if (await fs.pathExists(sourceGithubPath)) {
95
- await fs.copy(sourceGithubPath, targetGithubPath, { overwrite: options.force });
74
+ await fs.ensureDir(path.join(targetDir, '.github', 'instructions'));
75
+
76
+ const cursorSource = path.join(templateDir, '.cursorrules');
77
+ const cursorTarget = path.join(targetDir, '.cursorrules');
78
+ if (await fs.pathExists(cursorSource)) {
79
+ await fs.copy(cursorSource, cursorTarget, { overwrite: options.force });
96
80
  }
81
+
82
+ await writeGeneratedInstructions(targetDir, templateDir, options.force);
97
83
  }
98
84
  }
99
85
 
@@ -0,0 +1,167 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+
4
+ const CORE_SOURCE_REL_PATH = 'instructions/source/core.md';
5
+
6
+ const GENERATED_INSTRUCTION_PATHS = {
7
+ canonical: {
8
+ generic: '.github/instructions/AGENTS.md',
9
+ copilot: '.github/instructions/copilot-instructions.md',
10
+ claude: '.github/instructions/CLAUDE.md',
11
+ gemini: '.github/instructions/GEMINI.md'
12
+ },
13
+ styleCompat: {
14
+ githubInstructions: '.github/instructions/agents.instructions.md',
15
+ claudeRules: '.claude/rules/claude.instructions.md'
16
+ },
17
+ compatibility: {
18
+ generic: 'AGENTS.MD',
19
+ copilot: '.github/copilot-instructions.md',
20
+ claude: 'CLAUDE.md',
21
+ gemini: 'GEMINI.md'
22
+ }
23
+ };
24
+
25
+ function getLegacyCoreCandidates() {
26
+ return ['AGENTS.MD', 'AGENTS.md'];
27
+ }
28
+
29
+ function buildHeaders(toolName) {
30
+ return [
31
+ '<!-- GENERATED FILE - DO NOT EDIT DIRECTLY -->',
32
+ `<!-- Source of truth: ${CORE_SOURCE_REL_PATH} -->`,
33
+ `<!-- Tool profile: ${toolName} -->`,
34
+ ''
35
+ ].join('\n');
36
+ }
37
+
38
+ function buildCompatInstruction(toolName, corePath) {
39
+ const titles = {
40
+ generic: '# AGENTS Instructions',
41
+ copilot: '# GitHub Copilot Instructions',
42
+ claude: '# Claude Instructions',
43
+ gemini: '# Gemini Instructions'
44
+ };
45
+
46
+ return [
47
+ `${titles[toolName]}`,
48
+ '',
49
+ `Primary policy source: \`${corePath}\`.`,
50
+ '',
51
+ 'Always apply these mandatory rules:',
52
+ '- Security-first implementation (validate inputs, authz/authn, rate limiting).',
53
+ '- Testing-first delivery with unit + integration coverage for changed logic.',
54
+ '- Strong typing and runtime boundary validation.',
55
+ '- Do not expose secrets or sensitive data in logs/errors.',
56
+ '',
57
+ 'Use the canonical source for full policy and architecture guidance.',
58
+ ''
59
+ ].join('\n');
60
+ }
61
+
62
+ function buildGeneratedArtifacts(coreContent) {
63
+ const corePath = CORE_SOURCE_REL_PATH;
64
+ const files = {};
65
+
66
+ files[GENERATED_INSTRUCTION_PATHS.canonical.generic] = `${buildHeaders('generic')}${coreContent.trim()}\n`;
67
+ files[GENERATED_INSTRUCTION_PATHS.canonical.copilot] = `${buildHeaders('copilot')}${buildCompatInstruction('copilot', corePath)}`;
68
+ files[GENERATED_INSTRUCTION_PATHS.canonical.claude] = `${buildHeaders('claude')}${buildCompatInstruction('claude', corePath)}`;
69
+ files[GENERATED_INSTRUCTION_PATHS.canonical.gemini] = `${buildHeaders('gemini')}${buildCompatInstruction('gemini', corePath)}`;
70
+ files[GENERATED_INSTRUCTION_PATHS.styleCompat.githubInstructions] = `${buildHeaders('github-instructions-style')}${buildCompatInstruction('generic', corePath)}`;
71
+ files[GENERATED_INSTRUCTION_PATHS.styleCompat.claudeRules] = `${buildHeaders('claude-rules-style')}${buildCompatInstruction('claude', corePath)}`;
72
+
73
+ files[GENERATED_INSTRUCTION_PATHS.compatibility.generic] = `${buildHeaders('generic-compat')}${buildCompatInstruction('generic', corePath)}`;
74
+ files[GENERATED_INSTRUCTION_PATHS.compatibility.copilot] = `${buildHeaders('copilot-compat')}${buildCompatInstruction('copilot', corePath)}`;
75
+ files[GENERATED_INSTRUCTION_PATHS.compatibility.claude] = `${buildHeaders('claude-compat')}${buildCompatInstruction('claude', corePath)}`;
76
+ files[GENERATED_INSTRUCTION_PATHS.compatibility.gemini] = `${buildHeaders('gemini-compat')}${buildCompatInstruction('gemini', corePath)}`;
77
+
78
+ return files;
79
+ }
80
+
81
+ async function resolveCoreContent(targetDir, templateDir) {
82
+ const canonicalPath = path.join(targetDir, CORE_SOURCE_REL_PATH);
83
+ if (await fs.pathExists(canonicalPath)) {
84
+ return fs.readFile(canonicalPath, 'utf8');
85
+ }
86
+
87
+ for (const legacyFile of getLegacyCoreCandidates()) {
88
+ const legacyPath = path.join(targetDir, legacyFile);
89
+ if (await fs.pathExists(legacyPath)) {
90
+ return fs.readFile(legacyPath, 'utf8');
91
+ }
92
+ }
93
+
94
+ const templateCorePath = path.join(templateDir, CORE_SOURCE_REL_PATH);
95
+ return fs.readFile(templateCorePath, 'utf8');
96
+ }
97
+
98
+ async function ensureCoreSource(targetDir, templateDir, force = false) {
99
+ const targetCorePath = path.join(targetDir, CORE_SOURCE_REL_PATH);
100
+
101
+ if (await fs.pathExists(targetCorePath) && !force) {
102
+ return;
103
+ }
104
+
105
+ const coreContent = await resolveCoreContent(targetDir, templateDir);
106
+ await fs.ensureDir(path.dirname(targetCorePath));
107
+ await fs.writeFile(targetCorePath, coreContent, 'utf8');
108
+ }
109
+
110
+ async function writeGeneratedInstructions(targetDir, templateDir, force = false) {
111
+ await ensureCoreSource(targetDir, templateDir, force);
112
+ const corePath = path.join(targetDir, CORE_SOURCE_REL_PATH);
113
+ const coreContent = await fs.readFile(corePath, 'utf8');
114
+ const artifacts = buildGeneratedArtifacts(coreContent);
115
+
116
+ for (const [relPath, content] of Object.entries(artifacts)) {
117
+ const targetPath = path.join(targetDir, relPath);
118
+
119
+ if (await fs.pathExists(targetPath) && !force) {
120
+ continue;
121
+ }
122
+
123
+ await fs.ensureDir(path.dirname(targetPath));
124
+ await fs.writeFile(targetPath, content, 'utf8');
125
+ }
126
+ }
127
+
128
+ async function validateInstructionDrift(targetDir) {
129
+ const corePath = path.join(targetDir, CORE_SOURCE_REL_PATH);
130
+ if (!(await fs.pathExists(corePath))) {
131
+ return {
132
+ ok: false,
133
+ missingCore: true,
134
+ driftFiles: []
135
+ };
136
+ }
137
+
138
+ const coreContent = await fs.readFile(corePath, 'utf8');
139
+ const expected = buildGeneratedArtifacts(coreContent);
140
+ const driftFiles = [];
141
+
142
+ for (const [relPath, expectedContent] of Object.entries(expected)) {
143
+ const filePath = path.join(targetDir, relPath);
144
+ if (!(await fs.pathExists(filePath))) {
145
+ driftFiles.push(relPath);
146
+ continue;
147
+ }
148
+
149
+ const actual = await fs.readFile(filePath, 'utf8');
150
+ if (actual !== expectedContent) {
151
+ driftFiles.push(relPath);
152
+ }
153
+ }
154
+
155
+ return {
156
+ ok: driftFiles.length === 0,
157
+ missingCore: false,
158
+ driftFiles
159
+ };
160
+ }
161
+
162
+ module.exports = {
163
+ CORE_SOURCE_REL_PATH,
164
+ GENERATED_INSTRUCTION_PATHS,
165
+ writeGeneratedInstructions,
166
+ validateInstructionDrift
167
+ };
package/lib/layout.js ADDED
@@ -0,0 +1,95 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+
4
+ const LAYOUT = {
5
+ canonical: {
6
+ docsDir: 'agent-docs',
7
+ rulesDir: '.github/instructions/rules',
8
+ skillsDir: '.github/skills'
9
+ },
10
+ legacy: {
11
+ rulesDirs: ['agents/rules'],
12
+ skillsDirs: ['agents/skills']
13
+ },
14
+ compatible: {
15
+ rulesDirs: ['.claude/rules', '.github/instructions'],
16
+ skillsDirs: ['.claude/skills', '.agents/skills']
17
+ }
18
+ };
19
+
20
+ function firstExistingPath(baseDir, relativePaths) {
21
+ return relativePaths.find((relPath) => fs.existsSync(path.join(baseDir, relPath))) || null;
22
+ }
23
+
24
+ function resolveRulesDir(baseDir) {
25
+ const candidates = [
26
+ LAYOUT.canonical.rulesDir,
27
+ ...LAYOUT.compatible.rulesDirs,
28
+ ...LAYOUT.legacy.rulesDirs
29
+ ];
30
+ return firstExistingPath(baseDir, candidates) || LAYOUT.canonical.rulesDir;
31
+ }
32
+
33
+ function resolveSkillsDir(baseDir) {
34
+ const candidates = [
35
+ LAYOUT.canonical.skillsDir,
36
+ ...LAYOUT.compatible.skillsDirs,
37
+ ...LAYOUT.legacy.skillsDirs
38
+ ];
39
+ return firstExistingPath(baseDir, candidates) || LAYOUT.canonical.skillsDir;
40
+ }
41
+
42
+ async function hasAnyLayout(baseDir) {
43
+ const checks = [
44
+ path.join(baseDir, LAYOUT.canonical.rulesDir),
45
+ path.join(baseDir, LAYOUT.canonical.skillsDir),
46
+ ...LAYOUT.compatible.rulesDirs.map((relPath) => path.join(baseDir, relPath)),
47
+ ...LAYOUT.compatible.skillsDirs.map((relPath) => path.join(baseDir, relPath)),
48
+ ...LAYOUT.legacy.rulesDirs.map((relPath) => path.join(baseDir, relPath)),
49
+ ...LAYOUT.legacy.skillsDirs.map((relPath) => path.join(baseDir, relPath))
50
+ ];
51
+
52
+ for (const checkPath of checks) {
53
+ if (await fs.pathExists(checkPath)) {
54
+ return true;
55
+ }
56
+ }
57
+
58
+ return false;
59
+ }
60
+
61
+ async function getLegacyMigrationPlan(baseDir) {
62
+ const plan = [];
63
+
64
+ for (const relPath of LAYOUT.legacy.rulesDirs) {
65
+ const from = path.join(baseDir, relPath);
66
+ if (await fs.pathExists(from)) {
67
+ plan.push({
68
+ type: 'directory',
69
+ source: relPath,
70
+ target: LAYOUT.canonical.rulesDir
71
+ });
72
+ }
73
+ }
74
+
75
+ for (const relPath of LAYOUT.legacy.skillsDirs) {
76
+ const from = path.join(baseDir, relPath);
77
+ if (await fs.pathExists(from)) {
78
+ plan.push({
79
+ type: 'directory',
80
+ source: relPath,
81
+ target: LAYOUT.canonical.skillsDir
82
+ });
83
+ }
84
+ }
85
+
86
+ return plan;
87
+ }
88
+
89
+ module.exports = {
90
+ LAYOUT,
91
+ resolveRulesDir,
92
+ resolveSkillsDir,
93
+ hasAnyLayout,
94
+ getLegacyMigrationPlan
95
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agents-templated",
3
- "version": "1.2.9",
3
+ "version": "1.2.11",
4
4
  "description": "Technology-agnostic development template with multi-AI agent support (Cursor, Copilot, VSCode, Gemini), security-first patterns, and comprehensive testing guidelines",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -36,6 +36,7 @@
36
36
  },
37
37
  "files": [
38
38
  "bin",
39
+ "lib",
39
40
  "templates",
40
41
  "templates/presets",
41
42
  "index.js",
@@ -101,11 +101,11 @@ Agents Templated automatically configures 4 major AI coding assistants:
101
101
  | AI Agent | Config File | Auto-Discovery |
102
102
  |----------|-------------|----------------|
103
103
  | **Cursor** | `.cursorrules` | ✅ Auto-loads in Cursor IDE |
104
- | **GitHub Copilot** | `.github/copilot-instructions.md` | ✅ Auto-loads in VS Code |
105
- | **Claude** | `CLAUDE.md` | ✅ Auto-loads in Claude IDE/API |
106
- | **Gemini** | `GEMINI.md` | ✅ Auto-loads in Gemini IDE/API |
104
+ | **GitHub Copilot** | `.github/instructions/copilot-instructions.md` (+ shim `.github/copilot-instructions.md`) | ✅ Auto-loads in VS Code |
105
+ | **Claude** | `.github/instructions/CLAUDE.md` (+ shim `CLAUDE.md`) | ✅ Compatible |
106
+ | **Gemini** | `.github/instructions/GEMINI.md` (+ shim `GEMINI.md`) | ✅ Compatible |
107
107
 
108
- **All agents follow the same standards:** `agents/rules/` contains behavior rules, and `agents/commands/` contains deterministic slash-command contracts.
108
+ **Single source of truth:** `instructions/source/core.md` drives generated tool-compatible instruction files.
109
109
 
110
110
  ---
111
111
 
@@ -115,20 +115,36 @@ When you run `agents-templated init`, you get:
115
115
 
116
116
  ```
117
117
  your-project/
118
+ ├── instructions/
119
+ │ └── source/
120
+ │ └── core.md # Canonical instruction source of truth
121
+
118
122
  ├── agent-docs/ # 📚 Comprehensive documentation
119
- │ ├── AGENTS.MD # AI assistant guide
120
123
  │ ├── ARCHITECTURE.md # Project architecture & tech stack
121
124
  │ └── README.md # Human-readable setup guide
122
125
 
123
- ├── agents/ # 🤖 AI Agent rules and skills
124
- │ ├── rules/
126
+ ├── .github/
127
+ │ ├── instructions/ # Canonical generated instructions
128
+ │ │ ├── AGENTS.md
129
+ │ │ ├── copilot-instructions.md
130
+ │ │ ├── CLAUDE.md
131
+ │ │ ├── GEMINI.md
132
+ │ │ └── rules/
125
133
  │ │ ├── core.mdc # Core development principles
126
134
  │ │ ├── security.mdc # Security patterns (CRITICAL)
127
135
  │ │ ├── testing.mdc # Testing strategy
128
136
  │ │ ├── frontend.mdc # Frontend patterns
129
137
  │ │ ├── database.mdc # Database patterns
130
138
  │ │ └── style.mdc # Code style guidelines
131
- │ ├── commands/
139
+ │ ├── skills/
140
+ │ │ ├── find-skills/ # Skill discovery helper
141
+ │ │ ├── ui-ux-pro-max/ # Advanced UI/UX design implementation skill
142
+ │ │ ├── README.md # Guide for creating custom skills
143
+ │ │ └── [your-custom-skills]/ # Your project-specific skills
144
+ │ └── copilot-instructions.md # Compatibility shim for Copilot
145
+
146
+ ├── agents/ # 🤖 Deterministic command contracts
147
+ │ └── commands/
132
148
  │ │ ├── SCHEMA.md # Global slash-command response schema
133
149
  │ │ ├── plan.md # /plan contract
134
150
  │ │ ├── fix.md # /fix contract
@@ -136,17 +152,10 @@ your-project/
136
152
  │ │ ├── release.md # /release contract
137
153
  │ │ ├── ... # Other command contracts
138
154
  │ │ └── README.md # Commands directory guide
139
- │ └── skills/
140
- │ ├── find-skills/ # Skill discovery helper
141
- │ ├── ui-ux-pro-max/ # Advanced UI/UX design implementation skill
142
- │ ├── README.md # Guide for creating custom skills
143
- │ └── [your-custom-skills]/ # Your project-specific skills
144
-
145
- ├── .github/
146
- │ └── copilot-instructions.md # GitHub Copilot config
147
155
 
148
- ├── CLAUDE.md # Claude AI config
149
- ├── GEMINI.md # Gemini AI config
156
+ ├── AGENTS.MD # Compatibility shim for generic agents
157
+ ├── CLAUDE.md # Compatibility shim for Claude tooling
158
+ ├── GEMINI.md # Compatibility shim for Gemini tooling
150
159
  ├── .cursorrules # Cursor IDE config
151
160
  ├── .gitignore # Pre-configured Git ignore
152
161
  └── README.md # Project documentation
@@ -0,0 +1,36 @@
1
+ # Canonical AI Instructions
2
+
3
+ This file is the single source of truth for project AI guidance.
4
+
5
+ ## Always Apply
6
+
7
+ 1. Security-first implementation:
8
+ - Validate all external inputs at boundaries.
9
+ - Enforce authentication and authorization on protected operations.
10
+ - Rate limit public endpoints.
11
+ - Never expose sensitive values in logs, errors, or responses.
12
+
13
+ 2. Testing discipline:
14
+ - Add tests for changed business logic.
15
+ - Prefer focused unit tests first, then integration tests for boundaries.
16
+ - Keep critical workflows covered before release.
17
+
18
+ 3. Type safety and reliability:
19
+ - Use strong typing where available.
20
+ - Add runtime validation at trust boundaries.
21
+ - Keep changes minimal, deterministic, and scoped to the requested task.
22
+
23
+ ## Project References
24
+
25
+ - `agent-docs/ARCHITECTURE.md`
26
+ - `agents/rules/security.mdc`
27
+ - `agents/rules/testing.mdc`
28
+ - `agents/rules/core.mdc`
29
+
30
+ ## Operational Rules
31
+
32
+ - Prefer smallest safe change set.
33
+ - Preserve existing APIs unless the task requires breaking changes.
34
+ - Do not commit secrets or credentials.
35
+ - Do not disable security controls to make tests pass.
36
+