contentrain 0.2.3 → 0.3.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.
package/dist/index.mjs CHANGED
@@ -5,11 +5,11 @@ import { defineCommand, runMain } from "citty";
5
5
  runMain(defineCommand({
6
6
  meta: {
7
7
  name: "contentrain",
8
- version: "0.2.3",
8
+ version: "0.3.1",
9
9
  description: "Contentrain CLI — AI content governance infrastructure"
10
10
  },
11
11
  subCommands: {
12
- init: () => import("./init-BbtT9_sl.mjs").then((m) => m.default),
12
+ init: () => import("./init-BARgfYUV.mjs").then((m) => m.default),
13
13
  status: () => import("./status-kF5miVty.mjs").then((m) => m.default),
14
14
  doctor: () => import("./doctor-DyKjAIKH.mjs").then((m) => m.default),
15
15
  validate: () => import("./validate-DnVamp3p.mjs").then((m) => m.default),
@@ -350,33 +350,64 @@ async function executeInit(projectRoot, opts) {
350
350
  await tx.cleanup();
351
351
  }
352
352
  }
353
- const RULE_BASE_NAMES = [
354
- "content-quality",
355
- "seo-rules",
356
- "i18n-quality",
357
- "accessibility-rules",
358
- "security-rules",
359
- "media-rules",
360
- "content-conventions",
361
- "schema-rules",
362
- "mcp-usage",
363
- "workflow-rules",
364
- "normalize-rules"
353
+ const AGENT_SKILL_NAMES = [
354
+ "contentrain",
355
+ "contentrain-normalize",
356
+ "contentrain-quality",
357
+ "contentrain-sdk",
358
+ "contentrain-content",
359
+ "contentrain-model",
360
+ "contentrain-init",
361
+ "contentrain-bulk",
362
+ "contentrain-validate-fix",
363
+ "contentrain-review",
364
+ "contentrain-translate",
365
+ "contentrain-generate",
366
+ "contentrain-serve",
367
+ "contentrain-diff",
368
+ "contentrain-doctor"
365
369
  ];
366
- const SKILL_FILE_NAMES = [
367
- "contentrain-normalize.md",
368
- "contentrain-content.md",
369
- "contentrain-model.md",
370
- "contentrain-bulk.md",
371
- "contentrain-validate-fix.md",
372
- "contentrain-review.md",
373
- "contentrain-translate.md",
374
- "contentrain-generate.md",
375
- "contentrain-serve.md",
376
- "contentrain-diff.md",
377
- "contentrain-doctor.md",
378
- "contentrain-init.md"
370
+ const OLD_RULE_FILES = [
371
+ "contentrain-content-quality.md",
372
+ "contentrain-seo-rules.md",
373
+ "contentrain-i18n-quality.md",
374
+ "contentrain-accessibility-rules.md",
375
+ "contentrain-security-rules.md",
376
+ "contentrain-media-rules.md",
377
+ "contentrain-content-conventions.md",
378
+ "contentrain-schema-rules.md",
379
+ "contentrain-mcp-usage.md",
380
+ "contentrain-workflow-rules.md",
381
+ "contentrain-normalize-rules.md"
379
382
  ];
383
+ const IDE_CONFIGS = {
384
+ "claude-code": {
385
+ name: "Claude Code",
386
+ rulesDir: ".claude/rules",
387
+ skillsDir: ".claude/skills",
388
+ guardrailsFileName: "contentrain-essentials.md"
389
+ },
390
+ cursor: {
391
+ name: "Cursor",
392
+ rulesDir: ".cursor/rules",
393
+ skillsDir: ".cursor/skills",
394
+ guardrailsFileName: "contentrain-essentials.mdc",
395
+ guardrailsFrontmatter: "---\ndescription: Contentrain essential content governance rules\nalwaysApply: true\n---\n\n"
396
+ },
397
+ windsurf: {
398
+ name: "Windsurf",
399
+ rulesDir: ".windsurf/rules",
400
+ skillsDir: ".windsurf/skills",
401
+ guardrailsFileName: "contentrain-essentials.md",
402
+ guardrailsFrontmatter: "---\ndescription: Contentrain essential content governance rules\ntrigger: always_on\n---\n\n"
403
+ },
404
+ copilot: {
405
+ name: "GitHub Copilot",
406
+ rulesDir: ".github",
407
+ skillsDir: ".agents/skills",
408
+ guardrailsFileName: "copilot-instructions.md"
409
+ }
410
+ };
380
411
  async function installRules(projectRoot) {
381
412
  try {
382
413
  const { createRequire } = await import("node:module");
@@ -388,27 +419,24 @@ async function installRules(projectRoot) {
388
419
  resolveSkillFile = (p) => requireSkills.resolve(`@contentrain/skills/${p}`);
389
420
  } catch {}
390
421
  const hasClaudeCode = await pathExists(join(projectRoot, "CLAUDE.md")) || await pathExists(join(projectRoot, ".claude"));
391
- const hasCursorDir = await pathExists(join(projectRoot, ".cursor"));
392
- const hasCursorRules = await pathExists(join(projectRoot, ".cursorrules"));
393
- const hasWindsurfDir = await pathExists(join(projectRoot, ".windsurf"));
394
- const noIDEDetected = !hasClaudeCode && !hasCursorDir && !hasCursorRules && !hasWindsurfDir;
395
- if (hasClaudeCode) await installClaudeCodeRules(projectRoot, resolveRuleFile, resolveSkillFile);
396
- if (hasCursorDir) await installCursorRules(projectRoot, resolveRuleFile);
397
- else if (hasCursorRules) {
398
- const source = resolveRuleFile("ide/cursor/contentrain.cursorrules");
399
- const dest = join(projectRoot, ".cursorrules");
400
- await writeFile(dest, await readFile(source, "utf-8"), "utf-8");
401
- const git = simpleGit(projectRoot);
402
- await git.add(dest);
403
- try {
404
- await git.commit("[contentrain] install Cursor rules");
405
- } catch {}
422
+ const hasCursor = await pathExists(join(projectRoot, ".cursor"));
423
+ const hasWindsurf = await pathExists(join(projectRoot, ".windsurf"));
424
+ const hasCopilot = await pathExists(join(projectRoot, ".github"));
425
+ const noIDEDetected = !hasClaudeCode && !hasCursor && !hasWindsurf && !hasCopilot;
426
+ const detectedIdes = [];
427
+ if (hasClaudeCode) detectedIdes.push("claude-code");
428
+ if (hasCursor) detectedIdes.push("cursor");
429
+ if (hasWindsurf) detectedIdes.push("windsurf");
430
+ if (hasCopilot) detectedIdes.push("copilot");
431
+ for (const ideKey of detectedIdes) {
432
+ const ide = IDE_CONFIGS[ideKey];
433
+ await installIdeRulesAndSkills(projectRoot, ide, resolveRuleFile, resolveSkillFile);
406
434
  }
407
- if (hasWindsurfDir) await installWindsurfRules(projectRoot, resolveRuleFile);
435
+ if (hasClaudeCode) await addClaudeMdReference(projectRoot);
408
436
  if (noIDEDetected) {
409
- const source = resolveRuleFile("ide/generic/contentrain.md");
437
+ const essentials = await readFile(resolveRuleFile("essential/contentrain-essentials.md"), "utf-8");
410
438
  const dest = join(projectRoot, "CLAUDE.md");
411
- await writeFile(dest, await readFile(source, "utf-8"), "utf-8");
439
+ await writeFile(dest, essentials, "utf-8");
412
440
  const git = simpleGit(projectRoot);
413
441
  await git.add(dest);
414
442
  try {
@@ -417,32 +445,65 @@ async function installRules(projectRoot) {
417
445
  }
418
446
  } catch {}
419
447
  }
420
- async function installClaudeCodeRules(projectRoot, resolveRuleFile, resolveSkillFile) {
421
- const rulesDir = join(projectRoot, ".claude", "rules");
422
- const skillsDir = join(projectRoot, ".claude", "skills");
448
+ async function installIdeRulesAndSkills(projectRoot, ide, resolveRuleFile, resolveSkillFile) {
449
+ const rulesDir = join(projectRoot, ide.rulesDir);
450
+ const skillsDir = join(projectRoot, ide.skillsDir);
423
451
  const git = simpleGit(projectRoot);
424
452
  const filesToAdd = [];
425
453
  await ensureDir(rulesDir);
426
- for (const baseName of RULE_BASE_NAMES) {
427
- const fileName = `contentrain-${baseName}.md`;
428
- const dest = join(rulesDir, fileName);
429
- if (await pathExists(dest)) continue;
430
- try {
431
- await writeFile(dest, await readFile(resolveRuleFile(`ide/claude-code/rules/${fileName}`), "utf-8"), "utf-8");
432
- filesToAdd.push(dest);
433
- } catch {}
454
+ const guardrailsDest = join(rulesDir, ide.guardrailsFileName);
455
+ if (!await pathExists(guardrailsDest)) try {
456
+ let content = await readFile(resolveRuleFile("essential/contentrain-essentials.md"), "utf-8");
457
+ if (ide.guardrailsFrontmatter) content = ide.guardrailsFrontmatter + content;
458
+ if (ide.name === "GitHub Copilot" && await pathExists(guardrailsDest)) {
459
+ if (!(await readFile(guardrailsDest, "utf-8")).includes("# Contentrain")) await appendFile(guardrailsDest, `\n\n${content}`);
460
+ } else await writeFile(guardrailsDest, content, "utf-8");
461
+ filesToAdd.push(guardrailsDest);
462
+ } catch {}
463
+ for (const oldFile of OLD_RULE_FILES) {
464
+ const oldPath = join(rulesDir, oldFile);
465
+ if (await pathExists(oldPath)) {
466
+ const { unlink } = await import("node:fs/promises");
467
+ await unlink(oldPath);
468
+ }
434
469
  }
435
470
  if (resolveSkillFile) {
436
471
  await ensureDir(skillsDir);
437
- for (const fileName of SKILL_FILE_NAMES) {
438
- const dest = join(skillsDir, fileName);
439
- if (await pathExists(dest)) continue;
472
+ for (const skillName of AGENT_SKILL_NAMES) {
473
+ const skillDir = join(skillsDir, skillName);
474
+ const skillMd = join(skillDir, "SKILL.md");
475
+ if (await pathExists(skillMd)) continue;
440
476
  try {
441
- await writeFile(dest, await readFile(resolveSkillFile(`workflows/${fileName}`), "utf-8"), "utf-8");
442
- filesToAdd.push(dest);
477
+ const src = resolveSkillFile(`skills/${skillName}/SKILL.md`);
478
+ await ensureDir(skillDir);
479
+ await writeFile(skillMd, await readFile(src, "utf-8"), "utf-8");
480
+ filesToAdd.push(skillMd);
481
+ const { readdirSync } = await import("node:fs");
482
+ try {
483
+ const refsDir = join(resolveSkillFile(`skills/${skillName}/SKILL.md`), "..", "references");
484
+ const refs = readdirSync(refsDir);
485
+ if (refs.length > 0) {
486
+ const destRefsDir = join(skillDir, "references");
487
+ await ensureDir(destRefsDir);
488
+ for (const ref of refs) {
489
+ const refSrc = join(refsDir, ref);
490
+ const refDest = join(destRefsDir, ref);
491
+ await writeFile(refDest, await readFile(refSrc, "utf-8"), "utf-8");
492
+ filesToAdd.push(refDest);
493
+ }
494
+ }
495
+ } catch {}
443
496
  } catch {}
444
497
  }
445
498
  }
499
+ if (filesToAdd.length > 0) {
500
+ await git.add(filesToAdd);
501
+ try {
502
+ await git.commit(`[contentrain] install ${ide.name} rules and skills`);
503
+ } catch {}
504
+ }
505
+ }
506
+ async function addClaudeMdReference(projectRoot) {
446
507
  const claudeMdPath = join(projectRoot, "CLAUDE.md");
447
508
  const marker = "# Contentrain AI Rules";
448
509
  const reference = [
@@ -450,65 +511,24 @@ async function installClaudeCodeRules(projectRoot, resolveRuleFile, resolveSkill
450
511
  "",
451
512
  "This project uses [Contentrain](https://ai.contentrain.io) for AI-driven content management.",
452
513
  "",
453
- "- **Rules** are in `.claude/rules/` — auto-loaded each conversation",
454
- "- **Skills** are in `.claude/skills/` — invoke with `/contentrain-normalize` etc.",
514
+ "- **Rules** are in `.claude/rules/contentrain-essentials.md` — auto-loaded each conversation",
515
+ "- **Skills** are in `.claude/skills/` — loaded on demand by the agent",
455
516
  "- **Docs:** https://ai.contentrain.io"
456
517
  ].join("\n");
518
+ const git = simpleGit(projectRoot);
457
519
  if (await pathExists(claudeMdPath)) {
458
520
  if (!(await readFile(claudeMdPath, "utf-8")).includes(marker)) {
459
521
  await appendFile(claudeMdPath, `\n\n${reference}\n`);
460
- filesToAdd.push(claudeMdPath);
522
+ await git.add(claudeMdPath);
523
+ try {
524
+ await git.commit("[contentrain] add CLAUDE.md reference");
525
+ } catch {}
461
526
  }
462
527
  } else {
463
528
  await writeFile(claudeMdPath, `${reference}\n`, "utf-8");
464
- filesToAdd.push(claudeMdPath);
465
- }
466
- if (filesToAdd.length > 0) {
467
- await git.add(filesToAdd);
468
- try {
469
- await git.commit("[contentrain] install Claude Code rules and skills");
470
- } catch {}
471
- }
472
- }
473
- async function installCursorRules(projectRoot, resolveRuleFile) {
474
- const rulesDir = join(projectRoot, ".cursor", "rules");
475
- await ensureDir(rulesDir);
476
- const git = simpleGit(projectRoot);
477
- const filesToAdd = [];
478
- for (const baseName of RULE_BASE_NAMES) {
479
- const srcFile = `contentrain-${baseName}.mdc`;
480
- const dest = join(rulesDir, srcFile);
481
- if (await pathExists(dest)) continue;
482
- try {
483
- await writeFile(dest, await readFile(resolveRuleFile(`ide/cursor/rules/${srcFile}`), "utf-8"), "utf-8");
484
- filesToAdd.push(dest);
485
- } catch {}
486
- }
487
- if (filesToAdd.length > 0) {
488
- await git.add(filesToAdd);
489
- try {
490
- await git.commit("[contentrain] install Cursor rules");
491
- } catch {}
492
- }
493
- }
494
- async function installWindsurfRules(projectRoot, resolveRuleFile) {
495
- const rulesDir = join(projectRoot, ".windsurf", "rules");
496
- await ensureDir(rulesDir);
497
- const git = simpleGit(projectRoot);
498
- const filesToAdd = [];
499
- for (const baseName of RULE_BASE_NAMES) {
500
- const fileName = `contentrain-${baseName}.md`;
501
- const dest = join(rulesDir, fileName);
502
- if (await pathExists(dest)) continue;
503
- try {
504
- await writeFile(dest, await readFile(resolveRuleFile(`ide/windsurf/rules/${fileName}`), "utf-8"), "utf-8");
505
- filesToAdd.push(dest);
506
- } catch {}
507
- }
508
- if (filesToAdd.length > 0) {
509
- await git.add(filesToAdd);
529
+ await git.add(claudeMdPath);
510
530
  try {
511
- await git.commit("[contentrain] install Windsurf rules");
531
+ await git.commit("[contentrain] add CLAUDE.md reference");
512
532
  } catch {}
513
533
  }
514
534
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contentrain",
3
- "version": "0.2.3",
3
+ "version": "0.3.1",
4
4
  "license": "MIT",
5
5
  "description": "CLI for Contentrain — AI content governance infrastructure",
6
6
  "type": "module",
@@ -46,9 +46,9 @@
46
46
  "picocolors": "^1.1.0",
47
47
  "simple-git": "^3.27.0",
48
48
  "ws": "^8.18.0",
49
- "@contentrain/mcp": "1.0.6",
50
- "@contentrain/rules": "0.2.0",
51
- "@contentrain/query": "5.0.2",
49
+ "@contentrain/rules": "0.3.0",
50
+ "@contentrain/mcp": "1.0.7",
51
+ "@contentrain/query": "5.1.0",
52
52
  "@contentrain/types": "0.2.0"
53
53
  },
54
54
  "devDependencies": {