oh-my-customcode 0.10.1 → 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.
- package/dist/cli/index.js +264 -2
- package/dist/index.js +281 -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/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
|
@@ -1,4 +1,20 @@
|
|
|
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
|
|
@@ -503,8 +519,257 @@ function migrateConfig(config) {
|
|
|
503
519
|
migrated.configVersion = CURRENT_CONFIG_VERSION;
|
|
504
520
|
return migrated;
|
|
505
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
|
+
}
|
|
506
771
|
// src/core/installer.ts
|
|
507
|
-
import {
|
|
772
|
+
import { readFile as fsReadFile, writeFile as fsWriteFile, rename } from "node:fs/promises";
|
|
508
773
|
import { basename, join as join3 } from "node:path";
|
|
509
774
|
|
|
510
775
|
// src/core/layout.ts
|
|
@@ -735,6 +1000,11 @@ async function installComponent(targetDir, provider, component, options) {
|
|
|
735
1000
|
debug("install.component_installed", { component });
|
|
736
1001
|
return true;
|
|
737
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
|
+
}
|
|
738
1008
|
async function installEntryDoc(targetDir, provider, language, overwrite = false) {
|
|
739
1009
|
const layout = getProviderLayout(provider);
|
|
740
1010
|
const templateFile = getEntryTemplateName(provider, language);
|
|
@@ -749,7 +1019,12 @@ async function installEntryDoc(targetDir, provider, language, overwrite = false)
|
|
|
749
1019
|
debug("install.entry_md_skipped", { reason: "exists", language, entry: layout.entryFile });
|
|
750
1020
|
return false;
|
|
751
1021
|
}
|
|
752
|
-
await
|
|
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");
|
|
753
1028
|
debug("install.entry_md_installed", { language, entry: layout.entryFile });
|
|
754
1029
|
return true;
|
|
755
1030
|
}
|
|
@@ -1399,6 +1674,8 @@ export {
|
|
|
1399
1674
|
setLocale,
|
|
1400
1675
|
saveConfig,
|
|
1401
1676
|
resolveTemplatePath,
|
|
1677
|
+
renderGitWorkflowKO,
|
|
1678
|
+
renderGitWorkflowEN,
|
|
1402
1679
|
readJsonFile,
|
|
1403
1680
|
preserveCustomizations,
|
|
1404
1681
|
mergeConfig,
|
|
@@ -1408,12 +1685,14 @@ export {
|
|
|
1408
1685
|
getTemplateManifest,
|
|
1409
1686
|
getProviderLayout,
|
|
1410
1687
|
getPackageRoot,
|
|
1688
|
+
getDefaultWorkflow,
|
|
1411
1689
|
getDefaultConfig,
|
|
1412
1690
|
getConfigPath,
|
|
1413
1691
|
fileExists,
|
|
1414
1692
|
error,
|
|
1415
1693
|
ensureDirectory,
|
|
1416
1694
|
detectProvider,
|
|
1695
|
+
detectGitWorkflow,
|
|
1417
1696
|
src_default as default,
|
|
1418
1697
|
debug,
|
|
1419
1698
|
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
|
|