oh-my-customcode 0.10.1 → 0.10.3
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 +4 -0
- package/dist/cli/index.js +264 -2
- package/dist/index.js +265 -2
- package/package.json +1 -1
- package/templates/.claude/agents/mgr-gitnerd.md +1 -1
- package/templates/.claude/agents/mgr-sauron.md +3 -3
- package/templates/.claude/rules/MUST-continuous-improvement.md +1 -1
- package/templates/.claude/rules/MUST-intent-transparency.md +1 -1
- package/templates/.claude/rules/MUST-orchestrator-coordination.md +1 -1
- package/templates/.claude/rules/MUST-sync-verification.md +1 -1
- package/templates/.claude/rules/SHOULD-agent-teams.md +1 -1
- package/templates/.claude/rules/index.yaml +4 -4
- package/templates/.claude/skills/de-lead-routing/SKILL.md +13 -0
- package/templates/.claude/skills/dev-lead-routing/SKILL.md +14 -0
- package/templates/.claude/skills/qa-lead-routing/SKILL.md +13 -0
- package/templates/.claude/skills/sauron-watch/SKILL.md +4 -4
- package/templates/.claude/skills/secretary-routing/SKILL.md +14 -1
- package/templates/.codex/agents/mgr-gitnerd.md +1 -1
- package/templates/.codex/agents/mgr-sauron.md +3 -3
- package/templates/.codex/rules/MUST-continuous-improvement.md +1 -1
- package/templates/.codex/rules/MUST-intent-transparency.md +1 -1
- package/templates/.codex/rules/MUST-orchestrator-coordination.md +1 -1
- package/templates/.codex/rules/MUST-sync-verification.md +1 -1
- package/templates/.codex/rules/SHOULD-agent-teams.md +1 -1
- package/templates/.codex/rules/index.yaml +4 -4
- package/templates/.codex/skills/de-lead-routing/SKILL.md +13 -0
- package/templates/.codex/skills/dev-lead-routing/SKILL.md +14 -0
- package/templates/.codex/skills/qa-lead-routing/SKILL.md +13 -0
- package/templates/.codex/skills/sauron-watch/SKILL.md +4 -4
- package/templates/.codex/skills/secretary-routing/SKILL.md +14 -1
- package/templates/CLAUDE.md.en +8 -29
- package/templates/CLAUDE.md.ko +8 -29
- package/templates/guides/index.yaml +74 -0
package/README.md
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -13518,8 +13518,260 @@ async function doctorCommand(options = {}) {
|
|
|
13518
13518
|
import { join as join5 } from "node:path";
|
|
13519
13519
|
|
|
13520
13520
|
// src/core/installer.ts
|
|
13521
|
-
import {
|
|
13521
|
+
import { readFile as fsReadFile, writeFile as fsWriteFile, rename } from "node:fs/promises";
|
|
13522
13522
|
import { basename, join as join4 } from "node:path";
|
|
13523
|
+
|
|
13524
|
+
// src/core/git-workflow.ts
|
|
13525
|
+
import { execFileSync } from "node:child_process";
|
|
13526
|
+
function execGit(args, cwd) {
|
|
13527
|
+
try {
|
|
13528
|
+
return execFileSync("git", args, {
|
|
13529
|
+
cwd,
|
|
13530
|
+
encoding: "utf-8",
|
|
13531
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
13532
|
+
env: { ...process.env, GIT_DIR: undefined, GIT_WORK_TREE: undefined }
|
|
13533
|
+
}).trim();
|
|
13534
|
+
} catch {
|
|
13535
|
+
return "";
|
|
13536
|
+
}
|
|
13537
|
+
}
|
|
13538
|
+
function isGitRepo(cwd) {
|
|
13539
|
+
return execGit(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
|
|
13540
|
+
}
|
|
13541
|
+
function detectDefaultBranch(cwd) {
|
|
13542
|
+
const remoteHead = execGit(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd);
|
|
13543
|
+
if (remoteHead) {
|
|
13544
|
+
return remoteHead.replace("refs/remotes/origin/", "");
|
|
13545
|
+
}
|
|
13546
|
+
const branches = getLocalBranches(cwd);
|
|
13547
|
+
for (const candidate of ["main", "master", "develop"]) {
|
|
13548
|
+
if (branches.includes(candidate)) {
|
|
13549
|
+
return candidate;
|
|
13550
|
+
}
|
|
13551
|
+
}
|
|
13552
|
+
const head = execGit(["symbolic-ref", "--short", "HEAD"], cwd);
|
|
13553
|
+
if (head) {
|
|
13554
|
+
return head;
|
|
13555
|
+
}
|
|
13556
|
+
return "main";
|
|
13557
|
+
}
|
|
13558
|
+
function getLocalBranches(cwd) {
|
|
13559
|
+
const output = execGit(["branch", "--format=%(refname:short)"], cwd);
|
|
13560
|
+
if (!output)
|
|
13561
|
+
return [];
|
|
13562
|
+
return output.split(`
|
|
13563
|
+
`).filter(Boolean);
|
|
13564
|
+
}
|
|
13565
|
+
function getRemoteBranches(cwd) {
|
|
13566
|
+
const output = execGit(["branch", "-r", "--format=%(refname:short)"], cwd);
|
|
13567
|
+
if (!output)
|
|
13568
|
+
return [];
|
|
13569
|
+
return output.split(`
|
|
13570
|
+
`).filter(Boolean).map((b) => b.replace(/^origin\//, "")).filter((b) => b !== "HEAD");
|
|
13571
|
+
}
|
|
13572
|
+
function detectBranchPatterns(branches) {
|
|
13573
|
+
const prefixes = new Set;
|
|
13574
|
+
const knownPrefixes = ["feature", "release", "hotfix", "bugfix", "fix", "chore", "docs"];
|
|
13575
|
+
for (const branch of branches) {
|
|
13576
|
+
const slashIdx = branch.indexOf("/");
|
|
13577
|
+
if (slashIdx > 0) {
|
|
13578
|
+
const prefix = branch.substring(0, slashIdx);
|
|
13579
|
+
if (knownPrefixes.includes(prefix)) {
|
|
13580
|
+
prefixes.add(`${prefix}/*`);
|
|
13581
|
+
}
|
|
13582
|
+
}
|
|
13583
|
+
}
|
|
13584
|
+
return [...prefixes].sort();
|
|
13585
|
+
}
|
|
13586
|
+
function determineWorkflowType(hasDevelop, branchPatterns, allBranches) {
|
|
13587
|
+
const hasFlowPatterns = branchPatterns.some((p) => p === "feature/*" || p === "release/*" || p === "hotfix/*");
|
|
13588
|
+
if (hasDevelop && hasFlowPatterns) {
|
|
13589
|
+
return "git-flow";
|
|
13590
|
+
}
|
|
13591
|
+
const hasFeatureBranches = allBranches.some((b) => b.includes("/"));
|
|
13592
|
+
if (!hasDevelop && hasFeatureBranches) {
|
|
13593
|
+
return "github-flow";
|
|
13594
|
+
}
|
|
13595
|
+
if (!hasDevelop && !hasFeatureBranches) {
|
|
13596
|
+
return "trunk-based";
|
|
13597
|
+
}
|
|
13598
|
+
if (hasDevelop) {
|
|
13599
|
+
return "git-flow";
|
|
13600
|
+
}
|
|
13601
|
+
return "github-flow";
|
|
13602
|
+
}
|
|
13603
|
+
function detectGitWorkflow(cwd) {
|
|
13604
|
+
if (!isGitRepo(cwd)) {
|
|
13605
|
+
return null;
|
|
13606
|
+
}
|
|
13607
|
+
const localBranches = getLocalBranches(cwd);
|
|
13608
|
+
const remoteBranches = getRemoteBranches(cwd);
|
|
13609
|
+
const allBranches = [...new Set([...localBranches, ...remoteBranches])];
|
|
13610
|
+
const defaultBranch = detectDefaultBranch(cwd);
|
|
13611
|
+
const hasDevelop = localBranches.includes("develop") || remoteBranches.includes("develop");
|
|
13612
|
+
const branchPatterns = detectBranchPatterns(allBranches);
|
|
13613
|
+
const type = determineWorkflowType(hasDevelop, branchPatterns, allBranches);
|
|
13614
|
+
return {
|
|
13615
|
+
type,
|
|
13616
|
+
defaultBranch,
|
|
13617
|
+
hasDevelop,
|
|
13618
|
+
branchPatterns
|
|
13619
|
+
};
|
|
13620
|
+
}
|
|
13621
|
+
function renderGitWorkflowEN(result) {
|
|
13622
|
+
switch (result.type) {
|
|
13623
|
+
case "git-flow":
|
|
13624
|
+
return renderGitFlowEN(result);
|
|
13625
|
+
case "github-flow":
|
|
13626
|
+
return renderGithubFlowEN(result);
|
|
13627
|
+
case "trunk-based":
|
|
13628
|
+
return renderTrunkBasedEN(result);
|
|
13629
|
+
}
|
|
13630
|
+
}
|
|
13631
|
+
function renderGitWorkflowKO(result) {
|
|
13632
|
+
switch (result.type) {
|
|
13633
|
+
case "git-flow":
|
|
13634
|
+
return renderGitFlowKO(result);
|
|
13635
|
+
case "github-flow":
|
|
13636
|
+
return renderGithubFlowKO(result);
|
|
13637
|
+
case "trunk-based":
|
|
13638
|
+
return renderTrunkBasedKO(result);
|
|
13639
|
+
}
|
|
13640
|
+
}
|
|
13641
|
+
function renderGitFlowEN(r) {
|
|
13642
|
+
const lines = [
|
|
13643
|
+
"## Git Workflow (MUST follow)",
|
|
13644
|
+
"",
|
|
13645
|
+
"| Branch | Purpose |",
|
|
13646
|
+
"|--------|---------|",
|
|
13647
|
+
`| \`${r.defaultBranch}\` | Main development branch (default) |`
|
|
13648
|
+
];
|
|
13649
|
+
if (r.branchPatterns.includes("feature/*")) {
|
|
13650
|
+
lines.push(`| \`feature/*\` | New features -> PR to ${r.defaultBranch} |`);
|
|
13651
|
+
}
|
|
13652
|
+
if (r.branchPatterns.includes("release/*")) {
|
|
13653
|
+
lines.push("| `release/*` | Release preparation -> **npm publish here only** |");
|
|
13654
|
+
}
|
|
13655
|
+
if (r.branchPatterns.includes("hotfix/*")) {
|
|
13656
|
+
lines.push(`| \`hotfix/*\` | Critical fixes -> tag -> publish -> merge to ${r.defaultBranch} |`);
|
|
13657
|
+
}
|
|
13658
|
+
if (r.branchPatterns.includes("bugfix/*")) {
|
|
13659
|
+
lines.push(`| \`bugfix/*\` | Bug fixes -> PR to ${r.defaultBranch} |`);
|
|
13660
|
+
}
|
|
13661
|
+
lines.push("");
|
|
13662
|
+
lines.push("**Key rules:**");
|
|
13663
|
+
lines.push(`- Create feature branches from \`${r.defaultBranch}\``);
|
|
13664
|
+
lines.push("- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`");
|
|
13665
|
+
lines.push('- Include "Closes #N" in commit message to auto-close issues');
|
|
13666
|
+
return lines.join(`
|
|
13667
|
+
`);
|
|
13668
|
+
}
|
|
13669
|
+
function renderGithubFlowEN(r) {
|
|
13670
|
+
const lines = [
|
|
13671
|
+
"## Git Workflow (MUST follow)",
|
|
13672
|
+
"",
|
|
13673
|
+
"| Branch | Purpose |",
|
|
13674
|
+
"|--------|---------|",
|
|
13675
|
+
`| \`${r.defaultBranch}\` | Production-ready code (default) |`,
|
|
13676
|
+
`| \`feature/*\` | New features -> PR to ${r.defaultBranch} |`,
|
|
13677
|
+
"",
|
|
13678
|
+
"**Key rules:**",
|
|
13679
|
+
`- Create feature branches from \`${r.defaultBranch}\``,
|
|
13680
|
+
`- All changes go through PR to \`${r.defaultBranch}\``,
|
|
13681
|
+
"- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`",
|
|
13682
|
+
'- Include "Closes #N" in commit message to auto-close issues'
|
|
13683
|
+
];
|
|
13684
|
+
return lines.join(`
|
|
13685
|
+
`);
|
|
13686
|
+
}
|
|
13687
|
+
function renderTrunkBasedEN(r) {
|
|
13688
|
+
const lines = [
|
|
13689
|
+
"## Git Workflow (MUST follow)",
|
|
13690
|
+
"",
|
|
13691
|
+
"| Branch | Purpose |",
|
|
13692
|
+
"|--------|---------|",
|
|
13693
|
+
`| \`${r.defaultBranch}\` | Main trunk (default) |`,
|
|
13694
|
+
"",
|
|
13695
|
+
"**Key rules:**",
|
|
13696
|
+
`- Commit directly to \`${r.defaultBranch}\` or use short-lived branches`,
|
|
13697
|
+
"- Keep branches short-lived (merge within 1-2 days)",
|
|
13698
|
+
"- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`"
|
|
13699
|
+
];
|
|
13700
|
+
return lines.join(`
|
|
13701
|
+
`);
|
|
13702
|
+
}
|
|
13703
|
+
function renderGitFlowKO(r) {
|
|
13704
|
+
const lines = [
|
|
13705
|
+
"## Git 워크플로우 (반드시 준수)",
|
|
13706
|
+
"",
|
|
13707
|
+
"| 브랜치 | 용도 |",
|
|
13708
|
+
"|--------|------|",
|
|
13709
|
+
`| \`${r.defaultBranch}\` | 메인 개발 브랜치 (기본) |`
|
|
13710
|
+
];
|
|
13711
|
+
if (r.branchPatterns.includes("feature/*")) {
|
|
13712
|
+
lines.push(`| \`feature/*\` | 새 기능 -> ${r.defaultBranch}으로 PR |`);
|
|
13713
|
+
}
|
|
13714
|
+
if (r.branchPatterns.includes("release/*")) {
|
|
13715
|
+
lines.push("| `release/*` | 릴리스 준비 -> **npm 배포는 여기서만** |");
|
|
13716
|
+
}
|
|
13717
|
+
if (r.branchPatterns.includes("hotfix/*")) {
|
|
13718
|
+
lines.push(`| \`hotfix/*\` | 긴급 수정 -> 태그 -> 배포 -> ${r.defaultBranch} 머지 |`);
|
|
13719
|
+
}
|
|
13720
|
+
if (r.branchPatterns.includes("bugfix/*")) {
|
|
13721
|
+
lines.push(`| \`bugfix/*\` | 버그 수정 -> ${r.defaultBranch}으로 PR |`);
|
|
13722
|
+
}
|
|
13723
|
+
lines.push("");
|
|
13724
|
+
lines.push("**핵심 규칙:**");
|
|
13725
|
+
lines.push(`- \`${r.defaultBranch}\`에서 feature 브랜치 생성`);
|
|
13726
|
+
lines.push("- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`");
|
|
13727
|
+
lines.push('- 커밋 메시지에 "Closes #N" 포함시 이슈 자동 종료');
|
|
13728
|
+
return lines.join(`
|
|
13729
|
+
`);
|
|
13730
|
+
}
|
|
13731
|
+
function renderGithubFlowKO(r) {
|
|
13732
|
+
const lines = [
|
|
13733
|
+
"## Git 워크플로우 (반드시 준수)",
|
|
13734
|
+
"",
|
|
13735
|
+
"| 브랜치 | 용도 |",
|
|
13736
|
+
"|--------|------|",
|
|
13737
|
+
`| \`${r.defaultBranch}\` | 프로덕션 준비 코드 (기본) |`,
|
|
13738
|
+
`| \`feature/*\` | 새 기능 -> ${r.defaultBranch}으로 PR |`,
|
|
13739
|
+
"",
|
|
13740
|
+
"**핵심 규칙:**",
|
|
13741
|
+
`- \`${r.defaultBranch}\`에서 feature 브랜치 생성`,
|
|
13742
|
+
`- 모든 변경은 \`${r.defaultBranch}\`으로 PR을 통해 진행`,
|
|
13743
|
+
"- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`",
|
|
13744
|
+
'- 커밋 메시지에 "Closes #N" 포함시 이슈 자동 종료'
|
|
13745
|
+
];
|
|
13746
|
+
return lines.join(`
|
|
13747
|
+
`);
|
|
13748
|
+
}
|
|
13749
|
+
function renderTrunkBasedKO(r) {
|
|
13750
|
+
const lines = [
|
|
13751
|
+
"## Git 워크플로우 (반드시 준수)",
|
|
13752
|
+
"",
|
|
13753
|
+
"| 브랜치 | 용도 |",
|
|
13754
|
+
"|--------|------|",
|
|
13755
|
+
`| \`${r.defaultBranch}\` | 메인 트렁크 (기본) |`,
|
|
13756
|
+
"",
|
|
13757
|
+
"**핵심 규칙:**",
|
|
13758
|
+
`- \`${r.defaultBranch}\`에 직접 커밋하거나 단기 브랜치 사용`,
|
|
13759
|
+
"- 브랜치는 단기 유지 (1-2일 내 머지)",
|
|
13760
|
+
"- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`"
|
|
13761
|
+
];
|
|
13762
|
+
return lines.join(`
|
|
13763
|
+
`);
|
|
13764
|
+
}
|
|
13765
|
+
function getDefaultWorkflow() {
|
|
13766
|
+
return {
|
|
13767
|
+
type: "github-flow",
|
|
13768
|
+
defaultBranch: "main",
|
|
13769
|
+
hasDevelop: false,
|
|
13770
|
+
branchPatterns: ["feature/*"]
|
|
13771
|
+
};
|
|
13772
|
+
}
|
|
13773
|
+
|
|
13774
|
+
// src/core/installer.ts
|
|
13523
13775
|
var DEFAULT_LANGUAGE2 = "en";
|
|
13524
13776
|
function getTemplateDir() {
|
|
13525
13777
|
const packageRoot = getPackageRoot();
|
|
@@ -13654,6 +13906,11 @@ async function installComponent(targetDir, provider, component, options) {
|
|
|
13654
13906
|
debug("install.component_installed", { component });
|
|
13655
13907
|
return true;
|
|
13656
13908
|
}
|
|
13909
|
+
var GIT_WORKFLOW_PLACEHOLDER = "<!-- omcustom:git-workflow -->";
|
|
13910
|
+
function renderGitWorkflowSection(targetDir, language) {
|
|
13911
|
+
const result = detectGitWorkflow(targetDir) ?? getDefaultWorkflow();
|
|
13912
|
+
return language === "ko" ? renderGitWorkflowKO(result) : renderGitWorkflowEN(result);
|
|
13913
|
+
}
|
|
13657
13914
|
async function installEntryDoc(targetDir, provider, language, overwrite = false) {
|
|
13658
13915
|
const layout = getProviderLayout(provider);
|
|
13659
13916
|
const templateFile = getEntryTemplateName(provider, language);
|
|
@@ -13668,7 +13925,12 @@ async function installEntryDoc(targetDir, provider, language, overwrite = false)
|
|
|
13668
13925
|
debug("install.entry_md_skipped", { reason: "exists", language, entry: layout.entryFile });
|
|
13669
13926
|
return false;
|
|
13670
13927
|
}
|
|
13671
|
-
await
|
|
13928
|
+
let content = await fsReadFile(srcPath, "utf-8");
|
|
13929
|
+
if (content.includes(GIT_WORKFLOW_PLACEHOLDER)) {
|
|
13930
|
+
const workflowSection = renderGitWorkflowSection(targetDir, language);
|
|
13931
|
+
content = content.replace(GIT_WORKFLOW_PLACEHOLDER, workflowSection);
|
|
13932
|
+
}
|
|
13933
|
+
await fsWriteFile(destPath, content, "utf-8");
|
|
13672
13934
|
debug("install.entry_md_installed", { language, entry: layout.entryFile });
|
|
13673
13935
|
return true;
|
|
13674
13936
|
}
|
package/dist/index.js
CHANGED
|
@@ -503,8 +503,257 @@ function migrateConfig(config) {
|
|
|
503
503
|
migrated.configVersion = CURRENT_CONFIG_VERSION;
|
|
504
504
|
return migrated;
|
|
505
505
|
}
|
|
506
|
+
// src/core/git-workflow.ts
|
|
507
|
+
import { execFileSync } from "node:child_process";
|
|
508
|
+
function execGit(args, cwd) {
|
|
509
|
+
try {
|
|
510
|
+
return execFileSync("git", args, {
|
|
511
|
+
cwd,
|
|
512
|
+
encoding: "utf-8",
|
|
513
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
514
|
+
env: { ...process.env, GIT_DIR: undefined, GIT_WORK_TREE: undefined }
|
|
515
|
+
}).trim();
|
|
516
|
+
} catch {
|
|
517
|
+
return "";
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
function isGitRepo(cwd) {
|
|
521
|
+
return execGit(["rev-parse", "--is-inside-work-tree"], cwd) === "true";
|
|
522
|
+
}
|
|
523
|
+
function detectDefaultBranch(cwd) {
|
|
524
|
+
const remoteHead = execGit(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd);
|
|
525
|
+
if (remoteHead) {
|
|
526
|
+
return remoteHead.replace("refs/remotes/origin/", "");
|
|
527
|
+
}
|
|
528
|
+
const branches = getLocalBranches(cwd);
|
|
529
|
+
for (const candidate of ["main", "master", "develop"]) {
|
|
530
|
+
if (branches.includes(candidate)) {
|
|
531
|
+
return candidate;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
const head = execGit(["symbolic-ref", "--short", "HEAD"], cwd);
|
|
535
|
+
if (head) {
|
|
536
|
+
return head;
|
|
537
|
+
}
|
|
538
|
+
return "main";
|
|
539
|
+
}
|
|
540
|
+
function getLocalBranches(cwd) {
|
|
541
|
+
const output = execGit(["branch", "--format=%(refname:short)"], cwd);
|
|
542
|
+
if (!output)
|
|
543
|
+
return [];
|
|
544
|
+
return output.split(`
|
|
545
|
+
`).filter(Boolean);
|
|
546
|
+
}
|
|
547
|
+
function getRemoteBranches(cwd) {
|
|
548
|
+
const output = execGit(["branch", "-r", "--format=%(refname:short)"], cwd);
|
|
549
|
+
if (!output)
|
|
550
|
+
return [];
|
|
551
|
+
return output.split(`
|
|
552
|
+
`).filter(Boolean).map((b) => b.replace(/^origin\//, "")).filter((b) => b !== "HEAD");
|
|
553
|
+
}
|
|
554
|
+
function detectBranchPatterns(branches) {
|
|
555
|
+
const prefixes = new Set;
|
|
556
|
+
const knownPrefixes = ["feature", "release", "hotfix", "bugfix", "fix", "chore", "docs"];
|
|
557
|
+
for (const branch of branches) {
|
|
558
|
+
const slashIdx = branch.indexOf("/");
|
|
559
|
+
if (slashIdx > 0) {
|
|
560
|
+
const prefix = branch.substring(0, slashIdx);
|
|
561
|
+
if (knownPrefixes.includes(prefix)) {
|
|
562
|
+
prefixes.add(`${prefix}/*`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return [...prefixes].sort();
|
|
567
|
+
}
|
|
568
|
+
function determineWorkflowType(hasDevelop, branchPatterns, allBranches) {
|
|
569
|
+
const hasFlowPatterns = branchPatterns.some((p) => p === "feature/*" || p === "release/*" || p === "hotfix/*");
|
|
570
|
+
if (hasDevelop && hasFlowPatterns) {
|
|
571
|
+
return "git-flow";
|
|
572
|
+
}
|
|
573
|
+
const hasFeatureBranches = allBranches.some((b) => b.includes("/"));
|
|
574
|
+
if (!hasDevelop && hasFeatureBranches) {
|
|
575
|
+
return "github-flow";
|
|
576
|
+
}
|
|
577
|
+
if (!hasDevelop && !hasFeatureBranches) {
|
|
578
|
+
return "trunk-based";
|
|
579
|
+
}
|
|
580
|
+
if (hasDevelop) {
|
|
581
|
+
return "git-flow";
|
|
582
|
+
}
|
|
583
|
+
return "github-flow";
|
|
584
|
+
}
|
|
585
|
+
function detectGitWorkflow(cwd) {
|
|
586
|
+
if (!isGitRepo(cwd)) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
const localBranches = getLocalBranches(cwd);
|
|
590
|
+
const remoteBranches = getRemoteBranches(cwd);
|
|
591
|
+
const allBranches = [...new Set([...localBranches, ...remoteBranches])];
|
|
592
|
+
const defaultBranch = detectDefaultBranch(cwd);
|
|
593
|
+
const hasDevelop = localBranches.includes("develop") || remoteBranches.includes("develop");
|
|
594
|
+
const branchPatterns = detectBranchPatterns(allBranches);
|
|
595
|
+
const type = determineWorkflowType(hasDevelop, branchPatterns, allBranches);
|
|
596
|
+
return {
|
|
597
|
+
type,
|
|
598
|
+
defaultBranch,
|
|
599
|
+
hasDevelop,
|
|
600
|
+
branchPatterns
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function renderGitWorkflowEN(result) {
|
|
604
|
+
switch (result.type) {
|
|
605
|
+
case "git-flow":
|
|
606
|
+
return renderGitFlowEN(result);
|
|
607
|
+
case "github-flow":
|
|
608
|
+
return renderGithubFlowEN(result);
|
|
609
|
+
case "trunk-based":
|
|
610
|
+
return renderTrunkBasedEN(result);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function renderGitWorkflowKO(result) {
|
|
614
|
+
switch (result.type) {
|
|
615
|
+
case "git-flow":
|
|
616
|
+
return renderGitFlowKO(result);
|
|
617
|
+
case "github-flow":
|
|
618
|
+
return renderGithubFlowKO(result);
|
|
619
|
+
case "trunk-based":
|
|
620
|
+
return renderTrunkBasedKO(result);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
function renderGitFlowEN(r) {
|
|
624
|
+
const lines = [
|
|
625
|
+
"## Git Workflow (MUST follow)",
|
|
626
|
+
"",
|
|
627
|
+
"| Branch | Purpose |",
|
|
628
|
+
"|--------|---------|",
|
|
629
|
+
`| \`${r.defaultBranch}\` | Main development branch (default) |`
|
|
630
|
+
];
|
|
631
|
+
if (r.branchPatterns.includes("feature/*")) {
|
|
632
|
+
lines.push(`| \`feature/*\` | New features -> PR to ${r.defaultBranch} |`);
|
|
633
|
+
}
|
|
634
|
+
if (r.branchPatterns.includes("release/*")) {
|
|
635
|
+
lines.push("| `release/*` | Release preparation -> **npm publish here only** |");
|
|
636
|
+
}
|
|
637
|
+
if (r.branchPatterns.includes("hotfix/*")) {
|
|
638
|
+
lines.push(`| \`hotfix/*\` | Critical fixes -> tag -> publish -> merge to ${r.defaultBranch} |`);
|
|
639
|
+
}
|
|
640
|
+
if (r.branchPatterns.includes("bugfix/*")) {
|
|
641
|
+
lines.push(`| \`bugfix/*\` | Bug fixes -> PR to ${r.defaultBranch} |`);
|
|
642
|
+
}
|
|
643
|
+
lines.push("");
|
|
644
|
+
lines.push("**Key rules:**");
|
|
645
|
+
lines.push(`- Create feature branches from \`${r.defaultBranch}\``);
|
|
646
|
+
lines.push("- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`");
|
|
647
|
+
lines.push('- Include "Closes #N" in commit message to auto-close issues');
|
|
648
|
+
return lines.join(`
|
|
649
|
+
`);
|
|
650
|
+
}
|
|
651
|
+
function renderGithubFlowEN(r) {
|
|
652
|
+
const lines = [
|
|
653
|
+
"## Git Workflow (MUST follow)",
|
|
654
|
+
"",
|
|
655
|
+
"| Branch | Purpose |",
|
|
656
|
+
"|--------|---------|",
|
|
657
|
+
`| \`${r.defaultBranch}\` | Production-ready code (default) |`,
|
|
658
|
+
`| \`feature/*\` | New features -> PR to ${r.defaultBranch} |`,
|
|
659
|
+
"",
|
|
660
|
+
"**Key rules:**",
|
|
661
|
+
`- Create feature branches from \`${r.defaultBranch}\``,
|
|
662
|
+
`- All changes go through PR to \`${r.defaultBranch}\``,
|
|
663
|
+
"- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`",
|
|
664
|
+
'- Include "Closes #N" in commit message to auto-close issues'
|
|
665
|
+
];
|
|
666
|
+
return lines.join(`
|
|
667
|
+
`);
|
|
668
|
+
}
|
|
669
|
+
function renderTrunkBasedEN(r) {
|
|
670
|
+
const lines = [
|
|
671
|
+
"## Git Workflow (MUST follow)",
|
|
672
|
+
"",
|
|
673
|
+
"| Branch | Purpose |",
|
|
674
|
+
"|--------|---------|",
|
|
675
|
+
`| \`${r.defaultBranch}\` | Main trunk (default) |`,
|
|
676
|
+
"",
|
|
677
|
+
"**Key rules:**",
|
|
678
|
+
`- Commit directly to \`${r.defaultBranch}\` or use short-lived branches`,
|
|
679
|
+
"- Keep branches short-lived (merge within 1-2 days)",
|
|
680
|
+
"- Use conventional commits: `feat:`, `fix:`, `docs:`, `chore:`"
|
|
681
|
+
];
|
|
682
|
+
return lines.join(`
|
|
683
|
+
`);
|
|
684
|
+
}
|
|
685
|
+
function renderGitFlowKO(r) {
|
|
686
|
+
const lines = [
|
|
687
|
+
"## Git 워크플로우 (반드시 준수)",
|
|
688
|
+
"",
|
|
689
|
+
"| 브랜치 | 용도 |",
|
|
690
|
+
"|--------|------|",
|
|
691
|
+
`| \`${r.defaultBranch}\` | 메인 개발 브랜치 (기본) |`
|
|
692
|
+
];
|
|
693
|
+
if (r.branchPatterns.includes("feature/*")) {
|
|
694
|
+
lines.push(`| \`feature/*\` | 새 기능 -> ${r.defaultBranch}으로 PR |`);
|
|
695
|
+
}
|
|
696
|
+
if (r.branchPatterns.includes("release/*")) {
|
|
697
|
+
lines.push("| `release/*` | 릴리스 준비 -> **npm 배포는 여기서만** |");
|
|
698
|
+
}
|
|
699
|
+
if (r.branchPatterns.includes("hotfix/*")) {
|
|
700
|
+
lines.push(`| \`hotfix/*\` | 긴급 수정 -> 태그 -> 배포 -> ${r.defaultBranch} 머지 |`);
|
|
701
|
+
}
|
|
702
|
+
if (r.branchPatterns.includes("bugfix/*")) {
|
|
703
|
+
lines.push(`| \`bugfix/*\` | 버그 수정 -> ${r.defaultBranch}으로 PR |`);
|
|
704
|
+
}
|
|
705
|
+
lines.push("");
|
|
706
|
+
lines.push("**핵심 규칙:**");
|
|
707
|
+
lines.push(`- \`${r.defaultBranch}\`에서 feature 브랜치 생성`);
|
|
708
|
+
lines.push("- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`");
|
|
709
|
+
lines.push('- 커밋 메시지에 "Closes #N" 포함시 이슈 자동 종료');
|
|
710
|
+
return lines.join(`
|
|
711
|
+
`);
|
|
712
|
+
}
|
|
713
|
+
function renderGithubFlowKO(r) {
|
|
714
|
+
const lines = [
|
|
715
|
+
"## Git 워크플로우 (반드시 준수)",
|
|
716
|
+
"",
|
|
717
|
+
"| 브랜치 | 용도 |",
|
|
718
|
+
"|--------|------|",
|
|
719
|
+
`| \`${r.defaultBranch}\` | 프로덕션 준비 코드 (기본) |`,
|
|
720
|
+
`| \`feature/*\` | 새 기능 -> ${r.defaultBranch}으로 PR |`,
|
|
721
|
+
"",
|
|
722
|
+
"**핵심 규칙:**",
|
|
723
|
+
`- \`${r.defaultBranch}\`에서 feature 브랜치 생성`,
|
|
724
|
+
`- 모든 변경은 \`${r.defaultBranch}\`으로 PR을 통해 진행`,
|
|
725
|
+
"- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`",
|
|
726
|
+
'- 커밋 메시지에 "Closes #N" 포함시 이슈 자동 종료'
|
|
727
|
+
];
|
|
728
|
+
return lines.join(`
|
|
729
|
+
`);
|
|
730
|
+
}
|
|
731
|
+
function renderTrunkBasedKO(r) {
|
|
732
|
+
const lines = [
|
|
733
|
+
"## Git 워크플로우 (반드시 준수)",
|
|
734
|
+
"",
|
|
735
|
+
"| 브랜치 | 용도 |",
|
|
736
|
+
"|--------|------|",
|
|
737
|
+
`| \`${r.defaultBranch}\` | 메인 트렁크 (기본) |`,
|
|
738
|
+
"",
|
|
739
|
+
"**핵심 규칙:**",
|
|
740
|
+
`- \`${r.defaultBranch}\`에 직접 커밋하거나 단기 브랜치 사용`,
|
|
741
|
+
"- 브랜치는 단기 유지 (1-2일 내 머지)",
|
|
742
|
+
"- Conventional commits 사용: `feat:`, `fix:`, `docs:`, `chore:`"
|
|
743
|
+
];
|
|
744
|
+
return lines.join(`
|
|
745
|
+
`);
|
|
746
|
+
}
|
|
747
|
+
function getDefaultWorkflow() {
|
|
748
|
+
return {
|
|
749
|
+
type: "github-flow",
|
|
750
|
+
defaultBranch: "main",
|
|
751
|
+
hasDevelop: false,
|
|
752
|
+
branchPatterns: ["feature/*"]
|
|
753
|
+
};
|
|
754
|
+
}
|
|
506
755
|
// src/core/installer.ts
|
|
507
|
-
import {
|
|
756
|
+
import { readFile as fsReadFile, writeFile as fsWriteFile, rename } from "node:fs/promises";
|
|
508
757
|
import { basename, join as join3 } from "node:path";
|
|
509
758
|
|
|
510
759
|
// src/core/layout.ts
|
|
@@ -735,6 +984,11 @@ async function installComponent(targetDir, provider, component, options) {
|
|
|
735
984
|
debug("install.component_installed", { component });
|
|
736
985
|
return true;
|
|
737
986
|
}
|
|
987
|
+
var GIT_WORKFLOW_PLACEHOLDER = "<!-- omcustom:git-workflow -->";
|
|
988
|
+
function renderGitWorkflowSection(targetDir, language) {
|
|
989
|
+
const result = detectGitWorkflow(targetDir) ?? getDefaultWorkflow();
|
|
990
|
+
return language === "ko" ? renderGitWorkflowKO(result) : renderGitWorkflowEN(result);
|
|
991
|
+
}
|
|
738
992
|
async function installEntryDoc(targetDir, provider, language, overwrite = false) {
|
|
739
993
|
const layout = getProviderLayout(provider);
|
|
740
994
|
const templateFile = getEntryTemplateName(provider, language);
|
|
@@ -749,7 +1003,12 @@ async function installEntryDoc(targetDir, provider, language, overwrite = false)
|
|
|
749
1003
|
debug("install.entry_md_skipped", { reason: "exists", language, entry: layout.entryFile });
|
|
750
1004
|
return false;
|
|
751
1005
|
}
|
|
752
|
-
await
|
|
1006
|
+
let content = await fsReadFile(srcPath, "utf-8");
|
|
1007
|
+
if (content.includes(GIT_WORKFLOW_PLACEHOLDER)) {
|
|
1008
|
+
const workflowSection = renderGitWorkflowSection(targetDir, language);
|
|
1009
|
+
content = content.replace(GIT_WORKFLOW_PLACEHOLDER, workflowSection);
|
|
1010
|
+
}
|
|
1011
|
+
await fsWriteFile(destPath, content, "utf-8");
|
|
753
1012
|
debug("install.entry_md_installed", { language, entry: layout.entryFile });
|
|
754
1013
|
return true;
|
|
755
1014
|
}
|
|
@@ -1399,6 +1658,8 @@ export {
|
|
|
1399
1658
|
setLocale,
|
|
1400
1659
|
saveConfig,
|
|
1401
1660
|
resolveTemplatePath,
|
|
1661
|
+
renderGitWorkflowKO,
|
|
1662
|
+
renderGitWorkflowEN,
|
|
1402
1663
|
readJsonFile,
|
|
1403
1664
|
preserveCustomizations,
|
|
1404
1665
|
mergeConfig,
|
|
@@ -1408,12 +1669,14 @@ export {
|
|
|
1408
1669
|
getTemplateManifest,
|
|
1409
1670
|
getProviderLayout,
|
|
1410
1671
|
getPackageRoot,
|
|
1672
|
+
getDefaultWorkflow,
|
|
1411
1673
|
getDefaultConfig,
|
|
1412
1674
|
getConfigPath,
|
|
1413
1675
|
fileExists,
|
|
1414
1676
|
error,
|
|
1415
1677
|
ensureDirectory,
|
|
1416
1678
|
detectProvider,
|
|
1679
|
+
detectGitWorkflow,
|
|
1417
1680
|
src_default as default,
|
|
1418
1681
|
debug,
|
|
1419
1682
|
createLogger,
|
package/package.json
CHANGED
|
@@ -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 (
|
|
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
|
|
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
|
|
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
|
|
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
|
|
|
@@ -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
|
|
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
|
|