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.
- package/dist/cli/index.js +370 -19
- package/dist/index.js +387 -19
- 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/.claude/skills/springboot-best-practices/SKILL.md +7 -152
- package/templates/.claude/skills/springboot-best-practices/examples/config-properties-example.java +22 -0
- package/templates/.claude/skills/springboot-best-practices/examples/controller-example.java +28 -0
- package/templates/.claude/skills/springboot-best-practices/examples/controller-test-example.java +33 -0
- package/templates/.claude/skills/springboot-best-practices/examples/entity-example.java +22 -0
- package/templates/.claude/skills/springboot-best-practices/examples/exception-handler-example.java +30 -0
- package/templates/.claude/skills/springboot-best-practices/examples/repository-example.java +17 -0
- package/templates/.claude/skills/springboot-best-practices/examples/repository-test-example.java +23 -0
- package/templates/.claude/skills/springboot-best-practices/examples/security-config-example.java +27 -0
- package/templates/.claude/skills/springboot-best-practices/examples/service-example.java +33 -0
- 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/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:
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1082
|
-
|
|
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 (
|
|
1085
|
-
customizations.
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
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
|
-
|
|
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
|
@@ -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
|
|
|
@@ -62,7 +62,7 @@ rules:
|
|
|
62
62
|
scope: agents
|
|
63
63
|
|
|
64
64
|
# Intent Transparency - MUST
|
|
65
|
-
- id:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|