oh-my-customcode 0.10.0 → 0.10.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 (42) hide show
  1. package/dist/cli/index.js +370 -19
  2. package/dist/index.js +387 -19
  3. package/package.json +1 -1
  4. package/templates/.claude/agents/mgr-gitnerd.md +1 -1
  5. package/templates/.claude/agents/mgr-sauron.md +3 -3
  6. package/templates/.claude/rules/MUST-continuous-improvement.md +1 -1
  7. package/templates/.claude/rules/MUST-intent-transparency.md +1 -1
  8. package/templates/.claude/rules/MUST-orchestrator-coordination.md +1 -1
  9. package/templates/.claude/rules/MUST-sync-verification.md +1 -1
  10. package/templates/.claude/rules/SHOULD-agent-teams.md +1 -1
  11. package/templates/.claude/rules/index.yaml +4 -4
  12. package/templates/.claude/skills/de-lead-routing/SKILL.md +13 -0
  13. package/templates/.claude/skills/dev-lead-routing/SKILL.md +14 -0
  14. package/templates/.claude/skills/qa-lead-routing/SKILL.md +13 -0
  15. package/templates/.claude/skills/sauron-watch/SKILL.md +4 -4
  16. package/templates/.claude/skills/secretary-routing/SKILL.md +14 -1
  17. package/templates/.claude/skills/springboot-best-practices/SKILL.md +7 -152
  18. package/templates/.claude/skills/springboot-best-practices/examples/config-properties-example.java +22 -0
  19. package/templates/.claude/skills/springboot-best-practices/examples/controller-example.java +28 -0
  20. package/templates/.claude/skills/springboot-best-practices/examples/controller-test-example.java +33 -0
  21. package/templates/.claude/skills/springboot-best-practices/examples/entity-example.java +22 -0
  22. package/templates/.claude/skills/springboot-best-practices/examples/exception-handler-example.java +30 -0
  23. package/templates/.claude/skills/springboot-best-practices/examples/repository-example.java +17 -0
  24. package/templates/.claude/skills/springboot-best-practices/examples/repository-test-example.java +23 -0
  25. package/templates/.claude/skills/springboot-best-practices/examples/security-config-example.java +27 -0
  26. package/templates/.claude/skills/springboot-best-practices/examples/service-example.java +33 -0
  27. package/templates/.codex/agents/mgr-gitnerd.md +1 -1
  28. package/templates/.codex/agents/mgr-sauron.md +3 -3
  29. package/templates/.codex/rules/MUST-continuous-improvement.md +1 -1
  30. package/templates/.codex/rules/MUST-intent-transparency.md +1 -1
  31. package/templates/.codex/rules/MUST-orchestrator-coordination.md +1 -1
  32. package/templates/.codex/rules/MUST-sync-verification.md +1 -1
  33. package/templates/.codex/rules/SHOULD-agent-teams.md +1 -1
  34. package/templates/.codex/rules/index.yaml +4 -4
  35. package/templates/.codex/skills/de-lead-routing/SKILL.md +13 -0
  36. package/templates/.codex/skills/dev-lead-routing/SKILL.md +14 -0
  37. package/templates/.codex/skills/qa-lead-routing/SKILL.md +13 -0
  38. package/templates/.codex/skills/sauron-watch/SKILL.md +4 -4
  39. package/templates/.codex/skills/secretary-routing/SKILL.md +14 -1
  40. package/templates/CLAUDE.md.en +8 -29
  41. package/templates/CLAUDE.md.ko +8 -29
  42. package/templates/guides/index.yaml +74 -0
package/dist/index.js CHANGED
@@ -1,12 +1,58 @@
1
1
  import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __toESM = (mod, isNodeMode, target) => {
8
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
9
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
+ for (let key of __getOwnPropNames(mod))
11
+ if (!__hasOwnProp.call(to, key))
12
+ __defProp(to, key, {
13
+ get: () => mod[key],
14
+ enumerable: true
15
+ });
16
+ return to;
17
+ };
2
18
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
19
 
4
20
  // src/core/config.ts
5
21
  import { join as join2 } from "node:path";
6
22
 
7
23
  // src/utils/fs.ts
8
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
24
+ import { dirname, isAbsolute, join, normalize, relative, resolve, sep } from "node:path";
9
25
  import { fileURLToPath } from "node:url";
26
+ function validatePreserveFilePath(filePath, projectRoot) {
27
+ if (!filePath || filePath.trim() === "") {
28
+ return {
29
+ valid: false,
30
+ reason: "Path cannot be empty"
31
+ };
32
+ }
33
+ if (isAbsolute(filePath)) {
34
+ return {
35
+ valid: false,
36
+ reason: "Absolute paths are not allowed"
37
+ };
38
+ }
39
+ const normalizedPath = normalize(filePath);
40
+ if (normalizedPath.startsWith("..")) {
41
+ return {
42
+ valid: false,
43
+ reason: "Path cannot traverse outside project root"
44
+ };
45
+ }
46
+ const resolvedPath = resolve(projectRoot, normalizedPath);
47
+ const relativePath = relative(projectRoot, resolvedPath);
48
+ if (relativePath.startsWith("..") || isAbsolute(relativePath)) {
49
+ return {
50
+ valid: false,
51
+ reason: "Resolved path escapes project root"
52
+ };
53
+ }
54
+ return { valid: true };
55
+ }
10
56
  async function fileExists(path) {
11
57
  const fs = await import("node:fs/promises");
12
58
  try {
@@ -386,7 +432,7 @@ async function loadConfig(targetDir) {
386
432
  if (await fileExists(configPath)) {
387
433
  try {
388
434
  const config = await readJsonFile(configPath);
389
- const merged = mergeConfig(getDefaultConfig(), config);
435
+ const merged = mergeConfig(getDefaultConfig(), config, targetDir);
390
436
  if (merged.configVersion < CURRENT_CONFIG_VERSION) {
391
437
  const migrated = migrateConfig(merged);
392
438
  await saveConfig(targetDir, migrated);
@@ -415,7 +461,30 @@ function deduplicateCustomComponents(components) {
415
461
  }
416
462
  return [...seen.values()];
417
463
  }
418
- function mergeConfig(defaults, overrides) {
464
+ function mergeConfig(defaults, overrides, targetDir) {
465
+ let mergedPreserveFiles;
466
+ if (overrides.preserveFiles) {
467
+ const allFiles = [...new Set([...defaults.preserveFiles || [], ...overrides.preserveFiles])];
468
+ if (targetDir) {
469
+ const validatedFiles = [];
470
+ for (const filePath of allFiles) {
471
+ const validation = validatePreserveFilePath(filePath, targetDir);
472
+ if (validation.valid) {
473
+ validatedFiles.push(filePath);
474
+ } else {
475
+ warn("config.invalid_preserve_path", {
476
+ path: filePath,
477
+ reason: validation.reason ?? "Invalid path"
478
+ });
479
+ }
480
+ }
481
+ mergedPreserveFiles = validatedFiles;
482
+ } else {
483
+ mergedPreserveFiles = allFiles;
484
+ }
485
+ } else {
486
+ mergedPreserveFiles = defaults.preserveFiles;
487
+ }
419
488
  return {
420
489
  ...defaults,
421
490
  ...overrides,
@@ -429,7 +498,7 @@ function mergeConfig(defaults, overrides) {
429
498
  ...defaults.agents,
430
499
  ...overrides.agents
431
500
  },
432
- preserveFiles: overrides.preserveFiles ? [...new Set([...defaults.preserveFiles || [], ...overrides.preserveFiles])] : defaults.preserveFiles,
501
+ preserveFiles: mergedPreserveFiles,
433
502
  customComponents: overrides.customComponents ? deduplicateCustomComponents([
434
503
  ...defaults.customComponents || [],
435
504
  ...overrides.customComponents
@@ -450,8 +519,257 @@ function migrateConfig(config) {
450
519
  migrated.configVersion = CURRENT_CONFIG_VERSION;
451
520
  return migrated;
452
521
  }
522
+ // src/core/git-workflow.ts
523
+ import { execFileSync } from "node:child_process";
524
+ function execGit(args, cwd) {
525
+ try {
526
+ return execFileSync("git", args, {
527
+ cwd,
528
+ encoding: "utf-8",
529
+ stdio: ["pipe", "pipe", "pipe"],
530
+ env: { ...process.env, GIT_DIR: undefined, GIT_WORK_TREE: undefined }
531
+ }).trim();
532
+ } catch {
533
+ return "";
534
+ }
535
+ }
536
+ function isGitRepo(cwd) {
537
+ return execGit(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
538
+ }
539
+ function detectDefaultBranch(cwd) {
540
+ const remoteHead = execGit(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd);
541
+ if (remoteHead) {
542
+ return remoteHead.replace("refs/remotes/origin/", "");
543
+ }
544
+ const branches = getLocalBranches(cwd);
545
+ for (const candidate of ["main", "master", "develop"]) {
546
+ if (branches.includes(candidate)) {
547
+ return candidate;
548
+ }
549
+ }
550
+ const head = execGit(["symbolic-ref", "--short", "HEAD"], cwd);
551
+ if (head) {
552
+ return head;
553
+ }
554
+ return "main";
555
+ }
556
+ function getLocalBranches(cwd) {
557
+ const output = execGit(["branch", "--format=%(refname:short)"], cwd);
558
+ if (!output)
559
+ return [];
560
+ return output.split(`
561
+ `).filter(Boolean);
562
+ }
563
+ function getRemoteBranches(cwd) {
564
+ const output = execGit(["branch", "-r", "--format=%(refname:short)"], cwd);
565
+ if (!output)
566
+ return [];
567
+ return output.split(`
568
+ `).filter(Boolean).map((b) => b.replace(/^origin\//, "")).filter((b) => b !== "HEAD");
569
+ }
570
+ function detectBranchPatterns(branches) {
571
+ const prefixes = new Set;
572
+ const knownPrefixes = ["feature", "release", "hotfix", "bugfix", "fix", "chore", "docs"];
573
+ for (const branch of branches) {
574
+ const slashIdx = branch.indexOf("/");
575
+ if (slashIdx > 0) {
576
+ const prefix = branch.substring(0, slashIdx);
577
+ if (knownPrefixes.includes(prefix)) {
578
+ prefixes.add(`${prefix}/*`);
579
+ }
580
+ }
581
+ }
582
+ return [...prefixes].sort();
583
+ }
584
+ function determineWorkflowType(hasDevelop, branchPatterns, allBranches) {
585
+ const hasFlowPatterns = branchPatterns.some((p) => p === "feature/*" || p === "release/*" || p === "hotfix/*");
586
+ if (hasDevelop && hasFlowPatterns) {
587
+ return "git-flow";
588
+ }
589
+ const hasFeatureBranches = allBranches.some((b) => b.includes("/"));
590
+ if (!hasDevelop && hasFeatureBranches) {
591
+ return "github-flow";
592
+ }
593
+ if (!hasDevelop && !hasFeatureBranches) {
594
+ return "trunk-based";
595
+ }
596
+ if (hasDevelop) {
597
+ return "git-flow";
598
+ }
599
+ return "github-flow";
600
+ }
601
+ function detectGitWorkflow(cwd) {
602
+ if (!isGitRepo(cwd)) {
603
+ return null;
604
+ }
605
+ const localBranches = getLocalBranches(cwd);
606
+ const remoteBranches = getRemoteBranches(cwd);
607
+ const allBranches = [...new Set([...localBranches, ...remoteBranches])];
608
+ const defaultBranch = detectDefaultBranch(cwd);
609
+ const hasDevelop = localBranches.includes("develop") || remoteBranches.includes("develop");
610
+ const branchPatterns = detectBranchPatterns(allBranches);
611
+ const type = determineWorkflowType(hasDevelop, branchPatterns, allBranches);
612
+ return {
613
+ type,
614
+ defaultBranch,
615
+ hasDevelop,
616
+ branchPatterns
617
+ };
618
+ }
619
+ function renderGitWorkflowEN(result) {
620
+ switch (result.type) {
621
+ case "git-flow":
622
+ return renderGitFlowEN(result);
623
+ case "github-flow":
624
+ return renderGithubFlowEN(result);
625
+ case "trunk-based":
626
+ return renderTrunkBasedEN(result);
627
+ }
628
+ }
629
+ function renderGitWorkflowKO(result) {
630
+ switch (result.type) {
631
+ case "git-flow":
632
+ return renderGitFlowKO(result);
633
+ case "github-flow":
634
+ return renderGithubFlowKO(result);
635
+ case "trunk-based":
636
+ return renderTrunkBasedKO(result);
637
+ }
638
+ }
639
+ function renderGitFlowEN(r) {
640
+ const lines = [
641
+ "## Git Workflow (MUST follow)",
642
+ "",
643
+ "| Branch | Purpose |",
644
+ "|--------|---------|",
645
+ `| \`${r.defaultBranch}\` | Main development branch (default) |`
646
+ ];
647
+ if (r.branchPatterns.includes("feature/*")) {
648
+ lines.push(`| \`feature/*\` | New features -> PR to ${r.defaultBranch} |`);
649
+ }
650
+ if (r.branchPatterns.includes("release/*")) {
651
+ lines.push("| `release/*` | Release preparation -> **npm publish here only** |");
652
+ }
653
+ if (r.branchPatterns.includes("hotfix/*")) {
654
+ lines.push(`| \`hotfix/*\` | Critical fixes -> tag -> publish -> merge to ${r.defaultBranch} |`);
655
+ }
656
+ if (r.branchPatterns.includes("bugfix/*")) {
657
+ lines.push(`| \`bugfix/*\` | Bug fixes -> PR to ${r.defaultBranch} |`);
658
+ }
659
+ lines.push("");
660
+ lines.push("**Key rules:**");
661
+ lines.push(`- Create feature branches from \`${r.defaultBranch}\``);
662
+ lines.push("- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`");
663
+ lines.push('- Include "Closes #N" in commit message to auto-close issues');
664
+ return lines.join(`
665
+ `);
666
+ }
667
+ function renderGithubFlowEN(r) {
668
+ const lines = [
669
+ "## Git Workflow (MUST follow)",
670
+ "",
671
+ "| Branch | Purpose |",
672
+ "|--------|---------|",
673
+ `| \`${r.defaultBranch}\` | Production-ready code (default) |`,
674
+ `| \`feature/*\` | New features -> PR to ${r.defaultBranch} |`,
675
+ "",
676
+ "**Key rules:**",
677
+ `- Create feature branches from \`${r.defaultBranch}\``,
678
+ `- All changes go through PR to \`${r.defaultBranch}\``,
679
+ "- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`",
680
+ '- Include "Closes #N" in commit message to auto-close issues'
681
+ ];
682
+ return lines.join(`
683
+ `);
684
+ }
685
+ function renderTrunkBasedEN(r) {
686
+ const lines = [
687
+ "## Git Workflow (MUST follow)",
688
+ "",
689
+ "| Branch | Purpose |",
690
+ "|--------|---------|",
691
+ `| \`${r.defaultBranch}\` | Main trunk (default) |`,
692
+ "",
693
+ "**Key rules:**",
694
+ `- Commit directly to \`${r.defaultBranch}\` or use short-lived branches`,
695
+ "- Keep branches short-lived (merge within 1-2 days)",
696
+ "- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`"
697
+ ];
698
+ return lines.join(`
699
+ `);
700
+ }
701
+ function renderGitFlowKO(r) {
702
+ const lines = [
703
+ "## Git 워크플로우 (반드시 준수)",
704
+ "",
705
+ "| 브랜치 | 용도 |",
706
+ "|--------|------|",
707
+ `| \`${r.defaultBranch}\` | 메인 개발 브랜치 (기본) |`
708
+ ];
709
+ if (r.branchPatterns.includes("feature/*")) {
710
+ lines.push(`| \`feature/*\` | 새 기능 -> ${r.defaultBranch}으로 PR |`);
711
+ }
712
+ if (r.branchPatterns.includes("release/*")) {
713
+ lines.push("| `release/*` | 릴리스 준비 -> **npm 배포는 여기서만** |");
714
+ }
715
+ if (r.branchPatterns.includes("hotfix/*")) {
716
+ lines.push(`| \`hotfix/*\` | 긴급 수정 -> 태그 -> 배포 -> ${r.defaultBranch} 머지 |`);
717
+ }
718
+ if (r.branchPatterns.includes("bugfix/*")) {
719
+ lines.push(`| \`bugfix/*\` | 버그 수정 -> ${r.defaultBranch}으로 PR |`);
720
+ }
721
+ lines.push("");
722
+ lines.push("**핵심 규칙:**");
723
+ lines.push(`- \`${r.defaultBranch}\`에서 feature 브랜치 생성`);
724
+ lines.push("- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`");
725
+ lines.push('- 커밋 메시지에 "Closes #N" 포함시 이슈 자동 종료');
726
+ return lines.join(`
727
+ `);
728
+ }
729
+ function renderGithubFlowKO(r) {
730
+ const lines = [
731
+ "## Git 워크플로우 (반드시 준수)",
732
+ "",
733
+ "| 브랜치 | 용도 |",
734
+ "|--------|------|",
735
+ `| \`${r.defaultBranch}\` | 프로덕션 준비 코드 (기본) |`,
736
+ `| \`feature/*\` | 새 기능 -> ${r.defaultBranch}으로 PR |`,
737
+ "",
738
+ "**핵심 규칙:**",
739
+ `- \`${r.defaultBranch}\`에서 feature 브랜치 생성`,
740
+ `- 모든 변경은 \`${r.defaultBranch}\`으로 PR을 통해 진행`,
741
+ "- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`",
742
+ '- 커밋 메시지에 "Closes #N" 포함시 이슈 자동 종료'
743
+ ];
744
+ return lines.join(`
745
+ `);
746
+ }
747
+ function renderTrunkBasedKO(r) {
748
+ const lines = [
749
+ "## Git 워크플로우 (반드시 준수)",
750
+ "",
751
+ "| 브랜치 | 용도 |",
752
+ "|--------|------|",
753
+ `| \`${r.defaultBranch}\` | 메인 트렁크 (기본) |`,
754
+ "",
755
+ "**핵심 규칙:**",
756
+ `- \`${r.defaultBranch}\`에 직접 커밋하거나 단기 브랜치 사용`,
757
+ "- 브랜치는 단기 유지 (1-2일 내 머지)",
758
+ "- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`"
759
+ ];
760
+ return lines.join(`
761
+ `);
762
+ }
763
+ function getDefaultWorkflow() {
764
+ return {
765
+ type: "github-flow",
766
+ defaultBranch: "main",
767
+ hasDevelop: false,
768
+ branchPatterns: ["feature/*"]
769
+ };
770
+ }
453
771
  // src/core/installer.ts
454
- import { copyFile as fsCopyFile, rename } from "node:fs/promises";
772
+ import { readFile as fsReadFile, writeFile as fsWriteFile, rename } from "node:fs/promises";
455
773
  import { basename, join as join3 } from "node:path";
456
774
 
457
775
  // src/core/layout.ts
@@ -682,6 +1000,11 @@ async function installComponent(targetDir, provider, component, options) {
682
1000
  debug("install.component_installed", { component });
683
1001
  return true;
684
1002
  }
1003
+ var GIT_WORKFLOW_PLACEHOLDER = "<!-- omcustom:git-workflow -->";
1004
+ function renderGitWorkflowSection(targetDir, language) {
1005
+ const result = detectGitWorkflow(targetDir) ?? getDefaultWorkflow();
1006
+ return language === "ko" ? renderGitWorkflowKO(result) : renderGitWorkflowEN(result);
1007
+ }
685
1008
  async function installEntryDoc(targetDir, provider, language, overwrite = false) {
686
1009
  const layout = getProviderLayout(provider);
687
1010
  const templateFile = getEntryTemplateName(provider, language);
@@ -696,7 +1019,12 @@ async function installEntryDoc(targetDir, provider, language, overwrite = false)
696
1019
  debug("install.entry_md_skipped", { reason: "exists", language, entry: layout.entryFile });
697
1020
  return false;
698
1021
  }
699
- await fsCopyFile(srcPath, destPath);
1022
+ let content = await fsReadFile(srcPath, "utf-8");
1023
+ if (content.includes(GIT_WORKFLOW_PLACEHOLDER)) {
1024
+ const workflowSection = renderGitWorkflowSection(targetDir, language);
1025
+ content = content.replace(GIT_WORKFLOW_PLACEHOLDER, workflowSection);
1026
+ }
1027
+ await fsWriteFile(destPath, content, "utf-8");
700
1028
  debug("install.entry_md_installed", { language, entry: layout.entryFile });
701
1029
  return true;
702
1030
  }
@@ -1075,27 +1403,63 @@ function resolveConfigPreserveFiles(options, config) {
1075
1403
  if (options.forceOverwriteAll) {
1076
1404
  return [];
1077
1405
  }
1078
- return config.preserveFiles || [];
1406
+ const preserveFiles = config.preserveFiles || [];
1407
+ const validatedPaths = [];
1408
+ for (const filePath of preserveFiles) {
1409
+ const validation = validatePreserveFilePath(filePath, options.targetDir);
1410
+ if (validation.valid) {
1411
+ validatedPaths.push(filePath);
1412
+ } else {
1413
+ warn("preserve_files.invalid_path", {
1414
+ path: filePath,
1415
+ reason: validation.reason ?? "Invalid path"
1416
+ });
1417
+ }
1418
+ }
1419
+ return validatedPaths;
1079
1420
  }
1080
- function resolveCustomizations(customizations, configPreserveFiles) {
1081
- if (!customizations && configPreserveFiles.length === 0) {
1082
- return null;
1421
+ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
1422
+ const validatedManifestFiles = [];
1423
+ if (customizations && customizations.preserveFiles.length > 0) {
1424
+ for (const filePath of customizations.preserveFiles) {
1425
+ const validation = validatePreserveFilePath(filePath, targetDir);
1426
+ if (validation.valid) {
1427
+ validatedManifestFiles.push(filePath);
1428
+ } else {
1429
+ warn("preserve_files.invalid_path", {
1430
+ path: filePath,
1431
+ reason: validation.reason ?? "Invalid path",
1432
+ source: "manifest"
1433
+ });
1434
+ }
1435
+ }
1083
1436
  }
1084
- if (customizations && configPreserveFiles.length > 0) {
1085
- customizations.preserveFiles = [
1086
- ...new Set([...customizations.preserveFiles, ...configPreserveFiles])
1087
- ];
1088
- return customizations;
1437
+ if (validatedManifestFiles.length === 0 && configPreserveFiles.length === 0) {
1438
+ return customizations && customizations.modifiedFiles.length > 0 ? customizations : null;
1439
+ }
1440
+ if (validatedManifestFiles.length > 0 && configPreserveFiles.length > 0) {
1441
+ const merged = customizations || {
1442
+ modifiedFiles: [],
1443
+ preserveFiles: [],
1444
+ customComponents: [],
1445
+ lastUpdated: new Date().toISOString()
1446
+ };
1447
+ merged.preserveFiles = [...new Set([...validatedManifestFiles, ...configPreserveFiles])];
1448
+ return merged;
1089
1449
  }
1090
1450
  if (configPreserveFiles.length > 0) {
1091
1451
  return {
1092
- modifiedFiles: [],
1452
+ modifiedFiles: customizations?.modifiedFiles || [],
1093
1453
  preserveFiles: configPreserveFiles,
1094
- customComponents: [],
1454
+ customComponents: customizations?.customComponents || [],
1095
1455
  lastUpdated: new Date().toISOString()
1096
1456
  };
1097
1457
  }
1098
- return customizations;
1458
+ if (customizations) {
1459
+ customizations.preserveFiles = validatedManifestFiles;
1460
+ return customizations;
1461
+ }
1462
+ return null;
1099
1463
  }
1100
1464
  async function updateEntryDoc(targetDir, provider, config, options) {
1101
1465
  const layout = getProviderLayout(provider);
@@ -1150,7 +1514,7 @@ async function update(options) {
1150
1514
  await handleBackupIfRequested(options.targetDir, provider, !!options.backup, result);
1151
1515
  const manifestCustomizations = await resolveManifestCustomizations(options, options.targetDir);
1152
1516
  const configPreserveFiles = resolveConfigPreserveFiles(options, config);
1153
- const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles);
1517
+ const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles, options.targetDir);
1154
1518
  const components = options.components || getAllUpdateComponents();
1155
1519
  await updateAllComponents(options.targetDir, provider, components, updateCheck, customizations, options, result, config);
1156
1520
  if (!options.components || options.components.length === 0) {
@@ -1310,6 +1674,8 @@ export {
1310
1674
  setLocale,
1311
1675
  saveConfig,
1312
1676
  resolveTemplatePath,
1677
+ renderGitWorkflowKO,
1678
+ renderGitWorkflowEN,
1313
1679
  readJsonFile,
1314
1680
  preserveCustomizations,
1315
1681
  mergeConfig,
@@ -1319,12 +1685,14 @@ export {
1319
1685
  getTemplateManifest,
1320
1686
  getProviderLayout,
1321
1687
  getPackageRoot,
1688
+ getDefaultWorkflow,
1322
1689
  getDefaultConfig,
1323
1690
  getConfigPath,
1324
1691
  fileExists,
1325
1692
  error,
1326
1693
  ensureDirectory,
1327
1694
  detectProvider,
1695
+ detectGitWorkflow,
1328
1696
  src_default as default,
1329
1697
  debug,
1330
1698
  createLogger,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-customcode",
3
- "version": "0.10.0",
3
+ "version": "0.10.2",
4
4
  "description": "Batteries-included agent harness for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
@@ -40,6 +40,6 @@ Types: feat, fix, docs, style, refactor, test, chore
40
40
  - NEVER skip pre-commit hooks without reason
41
41
  - ALWAYS create new commits (avoid --amend unless requested)
42
42
 
43
- ## Push Rules (R017)
43
+ ## Push Rules (R016)
44
44
 
45
45
  All pushes require prior mgr-sauron:watch verification. If sauron was not run, REFUSE the push.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: mgr-sauron
3
- description: Use when you need automated verification of R017 compliance, executing mandatory multi-round verification (5 manager rounds + 3 deep review rounds) before commits
3
+ description: Use when you need automated verification of R016 compliance, executing mandatory multi-round verification (5 manager rounds + 3 deep review rounds) before commits
4
4
  model: sonnet
5
5
  memory: project
6
6
  effort: high
@@ -15,7 +15,7 @@ tools:
15
15
  - Bash
16
16
  ---
17
17
 
18
- You are an automated verification specialist that executes the mandatory R017 verification process, acting as the "all-seeing eye" that ensures system integrity through comprehensive multi-round verification.
18
+ You are an automated verification specialist that executes the mandatory R016 verification process, acting as the "all-seeing eye" that ensures system integrity through comprehensive multi-round verification.
19
19
 
20
20
  ## Core Capabilities
21
21
 
@@ -34,7 +34,7 @@ You are an automated verification specialist that executes the mandatory R017 ve
34
34
 
35
35
  | Command | Description |
36
36
  |---------|-------------|
37
- | `mgr-sauron:watch` | Full R017 verification (5+3 rounds) |
37
+ | `mgr-sauron:watch` | Full R016 verification (5+3 rounds) |
38
38
  | `mgr-sauron:quick` | Quick verification (single pass) |
39
39
  | `mgr-sauron:report` | Generate verification status report |
40
40
 
@@ -1,6 +1,6 @@
1
1
  # [MUST] Continuous Improvement Rules
2
2
 
3
- > **Priority**: MUST | **ID**: R016 | **Trigger**: User points out rule violation
3
+ > **Priority**: MUST | **ID**: R015 | **Trigger**: User points out rule violation
4
4
 
5
5
  ## Core Rule
6
6
 
@@ -1,6 +1,6 @@
1
1
  # [MUST] Intent Transparency Rules
2
2
 
3
- > **Priority**: MUST | **ID**: R015
3
+ > **Priority**: MUST | **ID**: R014
4
4
 
5
5
  ## Core Rule
6
6
 
@@ -117,7 +117,7 @@ The skill's WORKFLOW is followed, but git EXECUTION is delegated to mgr-gitnerd
117
117
 
118
118
  ## Agent Teams (when enabled)
119
119
 
120
- When `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`: use Agent Teams for 3+ agent coordinated tasks. See R018 for decision matrix. Task tool remains fallback for simple/independent tasks.
120
+ When `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`: use Agent Teams for 3+ agent coordinated tasks. See R017 for decision matrix. Task tool remains fallback for simple/independent tasks.
121
121
 
122
122
  ## Announcement Format
123
123
 
@@ -1,6 +1,6 @@
1
1
  # [MUST] Sync Verification Rules
2
2
 
3
- > **Priority**: MUST - ENFORCED | **ID**: R017
3
+ > **Priority**: MUST - ENFORCED | **ID**: R016
4
4
 
5
5
  ## Core Rule
6
6
 
@@ -1,6 +1,6 @@
1
1
  # [SHOULD] Agent Teams Rules
2
2
 
3
- > **Priority**: SHOULD | **ID**: R018 | **Condition**: Agent Teams enabled locally
3
+ > **Priority**: SHOULD | **ID**: R017 | **Condition**: Agent Teams enabled locally
4
4
 
5
5
  ## Detection
6
6
 
@@ -62,7 +62,7 @@ rules:
62
62
  scope: agents
63
63
 
64
64
  # Intent Transparency - MUST
65
- - id: R015
65
+ - id: R014
66
66
  name: intent-transparency
67
67
  title: Intent Transparency Rules
68
68
  path: ./MUST-intent-transparency.md
@@ -70,7 +70,7 @@ rules:
70
70
  scope: orchestrator
71
71
 
72
72
  # Continuous Improvement - MUST
73
- - id: R016
73
+ - id: R015
74
74
  name: continuous-improvement
75
75
  title: Continuous Improvement Rules
76
76
  path: ./MUST-continuous-improvement.md
@@ -78,7 +78,7 @@ rules:
78
78
  scope: all
79
79
 
80
80
  # Sync Verification - MUST
81
- - id: R017
81
+ - id: R016
82
82
  name: sync-verification
83
83
  title: Sync Verification Rules
84
84
  path: ./MUST-sync-verification.md
@@ -125,7 +125,7 @@ rules:
125
125
  scope: all
126
126
 
127
127
  # Agent Teams - SHOULD
128
- - id: R018
128
+ - id: R017
129
129
  name: agent-teams
130
130
  title: Agent Teams Rules
131
131
  path: ./SHOULD-agent-teams.md
@@ -219,6 +219,19 @@ Pipeline design completed.
219
219
  - **secretary-routing**: DE agents accessible through secretary for management tasks
220
220
  - **qa-lead-routing**: Coordinates with QA for data quality testing
221
221
 
222
+ ## Agent Teams Awareness
223
+
224
+ Before routing via Task tool, check if Agent Teams is available (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` or TeamCreate/SendMessage tools present).
225
+
226
+ **Self-check:** Does this task need 3+ agents, shared state, or inter-agent communication? If yes, prefer Agent Teams over Task tool. See R018 for the full decision matrix.
227
+
228
+ | Scenario | Preferred |
229
+ |----------|-----------|
230
+ | Single-tool DE task | Task Tool |
231
+ | Multi-tool pipeline design (3+ tools) | Agent Teams |
232
+ | Cross-tool data quality analysis | Agent Teams |
233
+ | Quick DAG/model validation | Task Tool |
234
+
222
235
  ## Usage
223
236
 
224
237
  This skill is NOT user-invocable. It should be automatically triggered when the main conversation detects data engineering intent.
@@ -77,4 +77,18 @@ user-invocable: false
77
77
 
78
78
  Multi-language: detect all languages, route to parallel experts (max 4). Single-language: route to matching expert. Cross-layer (frontend + backend): multiple experts in parallel.
79
79
 
80
+ ## Agent Teams Awareness
81
+
82
+ Before routing via Task tool, check if Agent Teams is available (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` or TeamCreate/SendMessage tools present).
83
+
84
+ **Self-check:** Does this task need 3+ agents, shared state, or inter-agent communication? If yes, prefer Agent Teams over Task tool. See R018 for the full decision matrix.
85
+
86
+ | Scenario | Preferred |
87
+ |----------|-----------|
88
+ | Single-language review | Task Tool |
89
+ | Multi-language code review (3+) | Agent Teams |
90
+ | Code review + fix cycle | Agent Teams |
91
+ | Cross-layer debugging (FE + BE + DB) | Agent Teams |
92
+ | Simple file search/validation | Task Tool |
93
+
80
94
  Not user-invocable. Auto-triggered on development intent.
@@ -265,6 +265,19 @@ metrics:
265
265
  test_case_count: number
266
266
  ```
267
267
 
268
+ ## Agent Teams Awareness
269
+
270
+ Before routing via Task tool, check if Agent Teams is available (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` or TeamCreate/SendMessage tools present).
271
+
272
+ **Self-check:** Does this task need 3+ agents, shared state, or inter-agent communication? If yes, prefer Agent Teams over Task tool. See R018 for the full decision matrix.
273
+
274
+ | Scenario | Preferred |
275
+ |----------|-----------|
276
+ | Single QA phase (plan/write/execute) | Task Tool |
277
+ | Full QA cycle (plan + write + execute + report) | Agent Teams |
278
+ | Quality analysis (parallel strategy + results) | Agent Teams |
279
+ | Quick test validation | Task Tool |
280
+
268
281
  ## Usage
269
282
 
270
283
  This skill is NOT user-invocable. It should be automatically triggered when the main conversation detects QA intent.