oh-my-customcode 0.56.0 → 0.57.0

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 CHANGED
@@ -13,7 +13,7 @@
13
13
 
14
14
  **[한국어 문서 (Korean)](./README_ko.md)**
15
15
 
16
- 45 agents. 92 skills. 21 rules. One command.
16
+ 45 agents. 93 skills. 21 rules. One command.
17
17
 
18
18
  ```bash
19
19
  npm install -g oh-my-customcode && cd your-project && omcustom init
@@ -146,7 +146,7 @@ Each agent declares its tools, model, memory scope, and limitations in YAML fron
146
146
 
147
147
  ---
148
148
 
149
- ### Skills (92)
149
+ ### Skills (93)
150
150
 
151
151
  | Category | Count | Includes |
152
152
  |----------|-------|----------|
package/dist/cli/index.js CHANGED
@@ -9325,7 +9325,7 @@ var init_package = __esm(() => {
9325
9325
  workspaces: [
9326
9326
  "packages/*"
9327
9327
  ],
9328
- version: "0.56.0",
9328
+ version: "0.57.0",
9329
9329
  description: "Batteries-included agent harness for Claude Code",
9330
9330
  type: "module",
9331
9331
  bin: {
@@ -24791,6 +24791,8 @@ var en_default = {
24791
24791
  backupCreated: "Backup created at {{path}}",
24792
24792
  summary: "Update complete: {{updated}} updated, {{skipped}} skipped",
24793
24793
  summaryFailed: "Update failed: {{error}}",
24794
+ hardOption: "Sync namespace (name: field) in unmodified files from upstream",
24795
+ namespaceSynced: "↻ Namespace synced: {{count}} file(s)",
24794
24796
  allOption: "Batch update all outdated projects found by project discovery",
24795
24797
  allScanning: "Scanning for oh-my-customcode projects...",
24796
24798
  allNoneFound: "No oh-my-customcode projects found.",
@@ -25173,6 +25175,8 @@ var ko_default = {
25173
25175
  backupCreated: "백업 생성됨: {{path}}",
25174
25176
  summary: "업데이트 완료: {{updated}}개 업데이트, {{skipped}}개 건너뜀",
25175
25177
  summaryFailed: "업데이트 실패: {{error}}",
25178
+ hardOption: "미수정 파일의 네임스페이스(name: 필드)를 upstream에서 동기화",
25179
+ namespaceSynced: "↻ 네임스페이스 동기화: {{count}}개 파일",
25176
25180
  allOption: "프로젝트 탐색으로 발견된 모든 outdated 프로젝트 일괄 업데이트",
25177
25181
  allScanning: "oh-my-customcode 프로젝트 검색 중...",
25178
25182
  allNoneFound: "oh-my-customcode 프로젝트를 찾을 수 없습니다.",
@@ -25841,6 +25845,7 @@ var MESSAGES = {
25841
25845
  "update.lockfile_regenerated": "Lockfile regenerated ({{files}} files tracked)",
25842
25846
  "update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
25843
25847
  "update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} updated: {{hint}}",
25848
+ "update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
25844
25849
  "config.load_failed": "Failed to load config: {{error}}",
25845
25850
  "config.not_found": "Config not found at {{path}}, using defaults",
25846
25851
  "config.saved": "Config saved to {{path}}",
@@ -25886,6 +25891,7 @@ var MESSAGES = {
25886
25891
  "update.lockfile_regenerated": "잠금 파일 재생성 완료 ({{files}}개 파일 추적)",
25887
25892
  "update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
25888
25893
  "update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트됨: {{hint}}",
25894
+ "update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
25889
25895
  "config.load_failed": "설정 로드 실패: {{error}}",
25890
25896
  "config.not_found": "{{path}}에 설정 없음, 기본값 사용",
25891
25897
  "config.saved": "설정 저장: {{path}}",
@@ -29927,7 +29933,8 @@ function createUpdateResult() {
29927
29933
  newVersion: "",
29928
29934
  warnings: [],
29929
29935
  syncedRootFiles: [],
29930
- removedDeprecatedFiles: []
29936
+ removedDeprecatedFiles: [],
29937
+ namespaceSynced: []
29931
29938
  };
29932
29939
  }
29933
29940
  async function handleBackupIfRequested(targetDir, backup, result) {
@@ -29952,6 +29959,10 @@ async function processComponentUpdate(targetDir, component, updateCheck, customi
29952
29959
  const preserved = await updateComponent(targetDir, component, customizations, options, config, lockfile);
29953
29960
  result.updatedComponents.push(component);
29954
29961
  result.preservedFiles.push(...preserved);
29962
+ if (options.hard) {
29963
+ const synced = await applyNamespaceSync(targetDir, component, lockfile);
29964
+ result.namespaceSynced.push(...synced);
29965
+ }
29955
29966
  } catch (err) {
29956
29967
  const message = err instanceof Error ? err.message : String(err);
29957
29968
  result.warnings.push(`Failed to update ${component}: ${message}`);
@@ -30403,6 +30414,75 @@ async function removeDeprecatedFiles(targetDir, options) {
30403
30414
  }
30404
30415
  return removed;
30405
30416
  }
30417
+ function extractFrontmatterName(content) {
30418
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
30419
+ if (!match)
30420
+ return null;
30421
+ const nameMatch = match[1].match(/^name:\s*(.+)$/m);
30422
+ if (!nameMatch)
30423
+ return null;
30424
+ return nameMatch[1].trim().replace(/^["']|["']$/g, "");
30425
+ }
30426
+ async function syncNamespaceInFile(targetFilePath, upstreamFilePath) {
30427
+ const targetContent = await readTextFile(targetFilePath);
30428
+ const upstreamContent = await readTextFile(upstreamFilePath);
30429
+ const upstreamName = extractFrontmatterName(upstreamContent);
30430
+ const targetName = extractFrontmatterName(targetContent);
30431
+ if (!upstreamName || !targetName || upstreamName === targetName)
30432
+ return false;
30433
+ const safeUpstreamName = upstreamName.replace(/\$/g, "$$$$");
30434
+ const updated = targetContent.replace(/^(name:\s*).+$/m, `$1${safeUpstreamName}`);
30435
+ if (updated === targetContent)
30436
+ return false;
30437
+ await writeTextFile(targetFilePath, updated);
30438
+ return true;
30439
+ }
30440
+ async function processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile) {
30441
+ if (!entry.isFile() || !entry.name.endsWith(".md"))
30442
+ return null;
30443
+ const targetFilePath = join14(destPath, relPath);
30444
+ const lockfileKey = `${componentPath}/${relPath}`.replace(/\\/g, "/");
30445
+ const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
30446
+ if (shouldSkip)
30447
+ return null;
30448
+ if (!await fileExists(targetFilePath))
30449
+ return null;
30450
+ const didSync = await syncNamespaceInFile(targetFilePath, fullSrcPath);
30451
+ return didSync ? `${componentPath}/${relPath}` : null;
30452
+ }
30453
+ async function applyNamespaceSync(targetDir, component, lockfile) {
30454
+ if (!lockfile)
30455
+ return [];
30456
+ const componentPath = getComponentPath2(component);
30457
+ const srcPath = resolveTemplatePath(componentPath);
30458
+ const destPath = join14(targetDir, componentPath);
30459
+ const fs3 = await import("node:fs/promises");
30460
+ const synced = [];
30461
+ const queue = [{ dir: srcPath, relDir: "" }];
30462
+ while (queue.length > 0) {
30463
+ const { dir: dir2, relDir } = queue.shift();
30464
+ let entries;
30465
+ try {
30466
+ entries = await fs3.readdir(dir2, { withFileTypes: true });
30467
+ } catch {
30468
+ continue;
30469
+ }
30470
+ for (const entry of entries) {
30471
+ const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
30472
+ const fullSrcPath = join14(dir2, entry.name);
30473
+ if (entry.isDirectory()) {
30474
+ queue.push({ dir: fullSrcPath, relDir: relPath });
30475
+ continue;
30476
+ }
30477
+ const syncedPath = await processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile);
30478
+ if (syncedPath) {
30479
+ synced.push(syncedPath);
30480
+ info("update.namespace_synced", { file: relPath, component });
30481
+ }
30482
+ }
30483
+ }
30484
+ return synced;
30485
+ }
30406
30486
  function getComponentPath2(component) {
30407
30487
  const layout = getProviderLayout();
30408
30488
  if (component === "guides") {
@@ -30469,7 +30549,8 @@ async function updateSingleProject(targetDir, options) {
30469
30549
  preserveCustomizations: true,
30470
30550
  forceOverwriteAll: options.forceOverwriteAll,
30471
30551
  dryRun: options.dryRun,
30472
- backup: options.backup
30552
+ backup: options.backup,
30553
+ hard: options.hard
30473
30554
  };
30474
30555
  const result = await update(updateOptions);
30475
30556
  printUpdateResults(result);
@@ -30509,7 +30590,8 @@ async function updateAllProjects(options) {
30509
30590
  preserveCustomizations: true,
30510
30591
  forceOverwriteAll: options.forceOverwriteAll,
30511
30592
  dryRun: options.dryRun,
30512
- backup: options.backup
30593
+ backup: options.backup,
30594
+ hard: options.hard
30513
30595
  };
30514
30596
  const result = await update(updateOptions);
30515
30597
  if (result.success) {
@@ -30600,6 +30682,12 @@ function printUpdateResults(result) {
30600
30682
  if (result.preservedFiles.length > 0) {
30601
30683
  console.log(i18n.t("cli.update.preservedFiles", { count: String(result.preservedFiles.length) }));
30602
30684
  }
30685
+ if ((result.namespaceSynced?.length ?? 0) > 0) {
30686
+ console.log(i18n.t("cli.update.namespaceSynced", { count: String(result.namespaceSynced.length) }));
30687
+ for (const file of result.namespaceSynced) {
30688
+ console.log(` ↻ ${file}`);
30689
+ }
30690
+ }
30603
30691
  if (result.backedUpPaths.length > 0) {
30604
30692
  for (const path4 of result.backedUpPaths) {
30605
30693
  console.log(i18n.t("cli.update.backupCreated", { path: path4 }));
@@ -30656,7 +30744,7 @@ function createProgram() {
30656
30744
  program2.command("init").description(i18n.t("cli.init.description")).option("-l, --lang <language>", i18n.t("cli.init.langOption")).option("--domain <domain>", "Install only agents/skills for specific domain (backend, frontend, data-engineering, devops)").option("--yes", "Skip interactive wizard, use defaults").action(async (options) => {
30657
30745
  await initCommand(options);
30658
30746
  });
30659
- program2.command("update").description(i18n.t("cli.update.description")).option("--dry-run", i18n.t("cli.update.dryRunOption")).option("--force", i18n.t("cli.update.forceOption")).option("--force-overwrite-all", i18n.t("cli.update.forceOverwriteAllOption")).option("--backup", i18n.t("cli.update.backupOption")).option("--agents", i18n.t("cli.update.agentsOption")).option("--skills", i18n.t("cli.update.skillsOption")).option("--rules", i18n.t("cli.update.rulesOption")).option("--guides", i18n.t("cli.update.guidesOption")).option("--hooks", i18n.t("cli.update.hooksOption")).option("--contexts", i18n.t("cli.update.contextsOption")).option("--all", i18n.t("cli.update.allOption")).action(async (options) => {
30747
+ program2.command("update").description(i18n.t("cli.update.description")).option("--dry-run", i18n.t("cli.update.dryRunOption")).option("--force", i18n.t("cli.update.forceOption")).option("--force-overwrite-all", i18n.t("cli.update.forceOverwriteAllOption")).option("--hard", i18n.t("cli.update.hardOption")).option("--backup", i18n.t("cli.update.backupOption")).option("--agents", i18n.t("cli.update.agentsOption")).option("--skills", i18n.t("cli.update.skillsOption")).option("--rules", i18n.t("cli.update.rulesOption")).option("--guides", i18n.t("cli.update.guidesOption")).option("--hooks", i18n.t("cli.update.hooksOption")).option("--contexts", i18n.t("cli.update.contextsOption")).option("--all", i18n.t("cli.update.allOption")).action(async (options) => {
30660
30748
  await updateCommand(options);
30661
30749
  });
30662
30750
  program2.command("list").description(i18n.t("cli.list.description")).argument("[type]", i18n.t("cli.list.typeArgument"), "all").option("-f, --format <format>", "Output format: table, json, or simple", "table").option("--verbose", "Show detailed information").action(async (type, options) => {
package/dist/index.js CHANGED
@@ -377,6 +377,7 @@ var MESSAGES = {
377
377
  "update.lockfile_regenerated": "Lockfile regenerated ({{files}} files tracked)",
378
378
  "update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
379
379
  "update.protected_file_updated": "⟳ Protected file {{file}} in {{component}} updated: {{hint}}",
380
+ "update.namespace_synced": "Namespace synced: {{file}} ({{component}})",
380
381
  "config.load_failed": "Failed to load config: {{error}}",
381
382
  "config.not_found": "Config not found at {{path}}, using defaults",
382
383
  "config.saved": "Config saved to {{path}}",
@@ -422,6 +423,7 @@ var MESSAGES = {
422
423
  "update.lockfile_regenerated": "잠금 파일 재생성 완료 ({{files}}개 파일 추적)",
423
424
  "update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
424
425
  "update.protected_file_updated": "⟳ 보호 파일 {{file}} ({{component}}) 업데이트됨: {{hint}}",
426
+ "update.namespace_synced": "네임스페이스 동기화: {{file}} ({{component}})",
425
427
  "config.load_failed": "설정 로드 실패: {{error}}",
426
428
  "config.not_found": "{{path}}에 설정 없음, 기본값 사용",
427
429
  "config.saved": "설정 저장: {{path}}",
@@ -1670,7 +1672,7 @@ var package_default = {
1670
1672
  workspaces: [
1671
1673
  "packages/*"
1672
1674
  ],
1673
- version: "0.56.0",
1675
+ version: "0.57.0",
1674
1676
  description: "Batteries-included agent harness for Claude Code",
1675
1677
  type: "module",
1676
1678
  bin: {
@@ -1893,7 +1895,8 @@ function createUpdateResult() {
1893
1895
  newVersion: "",
1894
1896
  warnings: [],
1895
1897
  syncedRootFiles: [],
1896
- removedDeprecatedFiles: []
1898
+ removedDeprecatedFiles: [],
1899
+ namespaceSynced: []
1897
1900
  };
1898
1901
  }
1899
1902
  async function handleBackupIfRequested(targetDir, backup, result) {
@@ -1918,6 +1921,10 @@ async function processComponentUpdate(targetDir, component, updateCheck, customi
1918
1921
  const preserved = await updateComponent(targetDir, component, customizations, options, config, lockfile);
1919
1922
  result.updatedComponents.push(component);
1920
1923
  result.preservedFiles.push(...preserved);
1924
+ if (options.hard) {
1925
+ const synced = await applyNamespaceSync(targetDir, component, lockfile);
1926
+ result.namespaceSynced.push(...synced);
1927
+ }
1921
1928
  } catch (err) {
1922
1929
  const message = err instanceof Error ? err.message : String(err);
1923
1930
  result.warnings.push(`Failed to update ${component}: ${message}`);
@@ -2390,6 +2397,75 @@ async function removeDeprecatedFiles(targetDir, options) {
2390
2397
  }
2391
2398
  return removed;
2392
2399
  }
2400
+ function extractFrontmatterName(content) {
2401
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
2402
+ if (!match)
2403
+ return null;
2404
+ const nameMatch = match[1].match(/^name:\s*(.+)$/m);
2405
+ if (!nameMatch)
2406
+ return null;
2407
+ return nameMatch[1].trim().replace(/^["']|["']$/g, "");
2408
+ }
2409
+ async function syncNamespaceInFile(targetFilePath, upstreamFilePath) {
2410
+ const targetContent = await readTextFile(targetFilePath);
2411
+ const upstreamContent = await readTextFile(upstreamFilePath);
2412
+ const upstreamName = extractFrontmatterName(upstreamContent);
2413
+ const targetName = extractFrontmatterName(targetContent);
2414
+ if (!upstreamName || !targetName || upstreamName === targetName)
2415
+ return false;
2416
+ const safeUpstreamName = upstreamName.replace(/\$/g, "$$$$");
2417
+ const updated = targetContent.replace(/^(name:\s*).+$/m, `$1${safeUpstreamName}`);
2418
+ if (updated === targetContent)
2419
+ return false;
2420
+ await writeTextFile(targetFilePath, updated);
2421
+ return true;
2422
+ }
2423
+ async function processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile) {
2424
+ if (!entry.isFile() || !entry.name.endsWith(".md"))
2425
+ return null;
2426
+ const targetFilePath = join6(destPath, relPath);
2427
+ const lockfileKey = `${componentPath}/${relPath}`.replace(/\\/g, "/");
2428
+ const shouldSkip = await shouldSkipProtectedFile(targetFilePath, lockfileKey, lockfile);
2429
+ if (shouldSkip)
2430
+ return null;
2431
+ if (!await fileExists(targetFilePath))
2432
+ return null;
2433
+ const didSync = await syncNamespaceInFile(targetFilePath, fullSrcPath);
2434
+ return didSync ? `${componentPath}/${relPath}` : null;
2435
+ }
2436
+ async function applyNamespaceSync(targetDir, component, lockfile) {
2437
+ if (!lockfile)
2438
+ return [];
2439
+ const componentPath = getComponentPath2(component);
2440
+ const srcPath = resolveTemplatePath(componentPath);
2441
+ const destPath = join6(targetDir, componentPath);
2442
+ const fs = await import("node:fs/promises");
2443
+ const synced = [];
2444
+ const queue = [{ dir: srcPath, relDir: "" }];
2445
+ while (queue.length > 0) {
2446
+ const { dir, relDir } = queue.shift();
2447
+ let entries;
2448
+ try {
2449
+ entries = await fs.readdir(dir, { withFileTypes: true });
2450
+ } catch {
2451
+ continue;
2452
+ }
2453
+ for (const entry of entries) {
2454
+ const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
2455
+ const fullSrcPath = join6(dir, entry.name);
2456
+ if (entry.isDirectory()) {
2457
+ queue.push({ dir: fullSrcPath, relDir: relPath });
2458
+ continue;
2459
+ }
2460
+ const syncedPath = await processNamespaceSyncEntry(entry, relPath, fullSrcPath, destPath, componentPath, lockfile);
2461
+ if (syncedPath) {
2462
+ synced.push(syncedPath);
2463
+ info("update.namespace_synced", { file: relPath, component });
2464
+ }
2465
+ }
2466
+ }
2467
+ return synced;
2468
+ }
2393
2469
  function getComponentPath2(component) {
2394
2470
  const layout = getProviderLayout();
2395
2471
  if (component === "guides") {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "workspaces": [
4
4
  "packages/*"
5
5
  ],
6
- "version": "0.56.0",
6
+ "version": "0.57.0",
7
7
  "description": "Batteries-included agent harness for Claude Code",
8
8
  "type": "module",
9
9
  "bin": {
@@ -12,10 +12,33 @@ set -euo pipefail
12
12
  input=$(cat)
13
13
  PPID_FILE="/tmp/.claude-task-outcomes-${PPID}"
14
14
 
15
- # Only attempt collection if outcome file exists and eval-core is available
16
- if [ -f "$PPID_FILE" ] && command -v eval-core >/dev/null 2>&1; then
15
+ # Only attempt collection if outcome file exists
16
+ if [ ! -f "$PPID_FILE" ]; then
17
+ echo "$input"
18
+ exit 0
19
+ fi
20
+
21
+ # Discover eval-core CLI using multiple strategies
22
+ EVAL_CORE=""
23
+
24
+ # Strategy 1: Global CLI installation
25
+ if command -v eval-core >/dev/null 2>&1; then
26
+ EVAL_CORE="eval-core"
27
+ fi
28
+
29
+ # Strategy 2: Workspace package (oh-my-customcode development)
30
+ if [ -z "$EVAL_CORE" ]; then
31
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
32
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
33
+ WORKSPACE_CLI="$PROJECT_ROOT/packages/eval-core/src/cli/index.ts"
34
+ if [ -f "$WORKSPACE_CLI" ] && command -v bun >/dev/null 2>&1; then
35
+ EVAL_CORE="bun run $WORKSPACE_CLI"
36
+ fi
37
+ fi
38
+
39
+ if [ -n "$EVAL_CORE" ]; then
17
40
  echo "[Hook] Collecting eval metrics via eval-core..." >&2
18
- eval-core collect --ppid "$PPID" 2>/dev/null || true
41
+ $EVAL_CORE collect --ppid "$PPID" 2>/dev/null || true
19
42
  fi
20
43
 
21
44
  # Always pass through input and exit 0 (advisory only)
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: omcustom:auto-improve
3
+ description: Apply verified improvement suggestions from eval-core analysis to omcustom configuration
4
+ scope: harness
5
+ user-invocable: true
6
+ effort: high
7
+ ---
8
+
9
+ # /omcustom:auto-improve — Automated Improvement Workflow
10
+
11
+ ## Purpose
12
+
13
+ Reads improvement suggestions from eval-core analysis, lets the user select which to apply, applies changes in an isolated worktree with sauron verification, and creates a PR for review.
14
+
15
+ ## Usage
16
+
17
+ ```
18
+ /omcustom:auto-improve # Interactive selection from pending suggestions
19
+ ```
20
+
21
+ ## Prerequisites
22
+
23
+ - eval-core analysis data exists (run `/omcustom:improve-report` first if empty)
24
+ - Pending improvement suggestions in `proposed` status
25
+
26
+ ## Workflow
27
+
28
+ ### Step 1: Read Suggestions
29
+
30
+ 1. Run `bun run packages/eval-core/src/cli/index.ts analyze --format json --save` via Bash
31
+ 2. Parse JSON output for improvement suggestions
32
+ 3. If no suggestions: display "No improvement suggestions available" and exit
33
+
34
+ ### Step 2: Display & Select
35
+
36
+ Display numbered list:
37
+ ```
38
+ [Auto-Improve] Available suggestions:
39
+ 1. [HIGH] agent:lang-golang-expert — Escalate model sonnet→opus (3 failures in 5 uses)
40
+ 2. [MED] routing:dev-lead-routing — Add Flutter keyword mapping (2 routing misses)
41
+ 3. [LOW] skill:systematic-debugging — Add timeout guard (1 timeout in 10 uses)
42
+
43
+ Select items: [1,2,3] / "all" / "cancel"
44
+ ```
45
+
46
+ **Self-reference filter**: Exclude items where targetName matches:
47
+ - `omcustom-auto-improve`, `auto-improve`
48
+ - `pipeline-guards`, `evaluator-optimizer`
49
+ - Any item targeting this skill itself
50
+
51
+ ### Step 3: Approve (State Transition)
52
+
53
+ For each selected item:
54
+ 1. Call eval-core API: transition `proposed` → `approved`
55
+ 2. Display: `[Approved] {N} items selected for application`
56
+
57
+ ### Step 4: Worktree Isolation
58
+
59
+ - Use `EnterWorktree` tool with name `auto-improve-{YYYYMMDD}`
60
+ - Creates isolated branch from HEAD
61
+
62
+ ### Step 5: Apply Changes
63
+
64
+ Map each approved item to the appropriate subagent by `targetType`:
65
+
66
+ | targetType | Agent | Action |
67
+ |------------|-------|--------|
68
+ | agent | mgr-creator | Modify agent frontmatter/body |
69
+ | skill | Matching domain expert | Revise skill SKILL.md |
70
+ | routing | general-purpose | Update routing patterns |
71
+ | model-escalation | general-purpose | Update model field in agent frontmatter |
72
+
73
+ Spawn agents in parallel (max 4 per R009). Each agent receives:
74
+ - Action description and evidence data
75
+ - Target file path
76
+ - Specific modification instructions
77
+
78
+ ### Step 6: Verification
79
+
80
+ 1. Delegate to mgr-sauron: full R017 verification
81
+ 2. If **PASS**: proceed to Step 7
82
+ 3. If **FAIL**: display failures, offer options:
83
+ - `fix` → re-apply with sauron feedback (max 2 cycles)
84
+ - `reject` → transition all to `rejected`, ExitWorktree(remove)
85
+ - `manual` → keep worktree for user inspection
86
+
87
+ ### Step 7: PR & Finalize
88
+
89
+ 1. Delegate to mgr-gitnerd: commit + create PR
90
+ - Title: `chore(auto-improve): apply {N} improvement suggestions`
91
+ - Body: table of applied items with evidence
92
+ 2. Transition all items to `applied` with `appliedAt` timestamp and PR URL
93
+ 3. `ExitWorktree(action: "keep")` — keep branch for PR
94
+ 4. Display PR URL to user
95
+
96
+ ## Safety Guards
97
+
98
+ | Guard | Implementation |
99
+ |-------|---------------|
100
+ | Self-reference prevention | Blocklist filter in Step 2 |
101
+ | User approval gate | Step 2 interactive selection |
102
+ | Worktree isolation | Step 4 EnterWorktree |
103
+ | Sauron verification | Step 6 mandatory pass |
104
+ | PR-based merge | Step 7 — no direct push to develop |
105
+ | Max items per run | 20 default, 50 hard cap |
106
+ | Max fix cycles | 2 retries before rejection |
107
+ | Rollback | `git revert` via mgr-gitnerd post-merge |
108
+
109
+ ## Error Handling
110
+
111
+ | Scenario | Action |
112
+ |----------|--------|
113
+ | No suggestions available | Display message, exit |
114
+ | User cancels selection | Exit, no state changes |
115
+ | Sauron verification fails 2x | Reject all, cleanup worktree |
116
+ | Agent application error | Mark individual item as rejected, continue others |
117
+ | EnterWorktree fails | Report error, exit |
118
+
119
+ ## Display Format
120
+
121
+ ```
122
+ [Auto-Improve] Starting improvement workflow
123
+ ├── Suggestions: {N} available ({high}H/{medium}M/{low}L confidence)
124
+ ├── Self-reference filtered: {count} items excluded
125
+ └── Select items to apply: [1,2,3] or "all" or "cancel"
126
+
127
+ [Auto-Improve] Applying {N} improvements in worktree
128
+ ├── Worktree: auto-improve-{date}
129
+ ├── Agents: {count} parallel
130
+ └── Pipeline guards: max 20 items, 2 retry cycles
131
+
132
+ [Auto-Improve] Verification
133
+ ├── Sauron: {PASS|FAIL}
134
+ ├── PR: #{number} created
135
+ └── Status: {N} items → applied
136
+ ```
@@ -22,6 +22,7 @@ Defines mandatory safety constraints for all pipeline, workflow, and iterative e
22
22
  | Timeout per pipeline | 900s | 1800s | worker-reviewer-pipeline |
23
23
  | Max retry count | 2 | 3 | Failure retry strategies |
24
24
  | Max PR improvement items | 20 | 50 | pr-auto-improve |
25
+ | Max auto-improve items | 20 | 50 | omcustom-auto-improve |
25
26
 
26
27
  ## Enforcement
27
28
 
@@ -152,6 +153,7 @@ Guard warnings appear inline:
152
153
  | dag-orchestration | Node count and timeout limits |
153
154
  | worker-reviewer-pipeline | Iteration and pipeline timeout limits |
154
155
  | pr-auto-improve | Improvement item count limits |
156
+ | omcustom-auto-improve | Auto-improve item count limits |
155
157
  | stuck-recovery | Guard triggers feed into stuck detection |
156
158
  | model-escalation | Repeated failures trigger escalation advisory |
157
159
 
@@ -50,10 +50,16 @@ git → mgr-gitnerd
50
50
  verify → mgr-sauron
51
51
  spec → mgr-claude-code-bible
52
52
  memory → sys-memory-keeper
53
- todo → sys-naggy
54
- batch multiple (parallel)
53
+ todo → sys-naggy
54
+ improve-report omcustom-improve-report (skill invocation)
55
+ auto-improve → omcustom-auto-improve (skill invocation)
56
+ batch → multiple (parallel)
55
57
  ```
56
58
 
59
+ **improve-report keywords**: "improve-report", "improvement", "개선", "개선 리포트", "improve" → invoke `omcustom-improve-report` skill (read-only, no agent delegation needed)
60
+
61
+ **auto-improve keywords**: "auto-improve", "자동 개선", "개선 적용", "apply improvements", "improvement suggestions" → invoke `omcustom-auto-improve` skill (worktree isolation, sauron verification, PR creation)
62
+
57
63
  ### Ontology-RAG Enrichment (R019)
58
64
 
59
65
  If `get_agent_for_task` MCP tool is available, call it with the original query and inject `suggested_skills` into the agent prompt. Skip silently on failure.
@@ -101,6 +101,7 @@ oh-my-customcode로 구동됩니다.
101
101
  | `/omcustom:update-external` | 외부 소스에서 에이전트 업데이트 |
102
102
  | `/omcustom:audit-agents` | 에이전트 의존성 감사 |
103
103
  | `/omcustom:fix-refs` | 깨진 참조 수정 |
104
+ | `/omcustom:auto-improve` | 개선 사항 자동 적용 워크플로우 |
104
105
  | `/omcustom:improve-report` | eval-core 기반 개선 현황 리포트 |
105
106
  | `/omcustom-takeover` | 기존 에이전트/스킬에서 canonical spec 추출 |
106
107
  | `/adversarial-review` | 공격자 관점 보안 코드 리뷰 |
@@ -137,7 +138,7 @@ project/
137
138
  +-- CLAUDE.md # 진입점
138
139
  +-- .claude/
139
140
  | +-- agents/ # 서브에이전트 정의 (45 파일)
140
- | +-- skills/ # 스킬 (91 디렉토리)
141
+ | +-- skills/ # 스킬 (93 디렉토리)
141
142
  | +-- rules/ # 전역 규칙 (R000-R021)
142
143
  | +-- hooks/ # 훅 스크립트 (보안, 검증, HUD)
143
144
  | +-- contexts/ # 컨텍스트 파일 (ecomode)
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.56.0",
2
+ "version": "0.57.0",
3
3
  "lastUpdated": "2026-03-16T00:00:00.000Z",
4
4
  "components": [
5
5
  {
@@ -18,7 +18,7 @@
18
18
  "name": "skills",
19
19
  "path": ".claude/skills",
20
20
  "description": "Reusable skill modules (includes slash commands)",
21
- "files": 92
21
+ "files": 93
22
22
  },
23
23
  {
24
24
  "name": "guides",