oh-my-customcode 0.9.4 → 0.10.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
@@ -5,6 +5,7 @@
5
5
  [![npm version](https://img.shields.io/npm/v/oh-my-customcode.svg)](https://www.npmjs.com/package/oh-my-customcode)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![CI](https://github.com/baekenough/oh-my-customcode/actions/workflows/ci.yml/badge.svg)](https://github.com/baekenough/oh-my-customcode/actions/workflows/ci.yml)
8
+ [![Security Audit](https://github.com/baekenough/oh-my-customcode/actions/workflows/security-audit.yml/badge.svg)](https://github.com/baekenough/oh-my-customcode/actions/workflows/security-audit.yml)
8
9
 
9
10
  **[한국어 문서 (Korean)](./README_ko.md)**
10
11
 
@@ -16,7 +17,7 @@ Like oh-my-zsh transformed shell customization, oh-my-customcode makes personali
16
17
 
17
18
  | Feature | Description |
18
19
  |---------|-------------|
19
- | **Batteries Included** | 42 agents, 52 skills, 22 guides, 18 rules, 1 hook, 1 context - ready to use out of the box |
20
+ | **Batteries Included** | 42 agents, 51 skills, 22 guides, 18 rules, 1 hook, 4 contexts - ready to use out of the box |
20
21
  | **Sub-Agent Model** | Supports hierarchical agent orchestration with specialized roles |
21
22
  | **Dead Simple Customization** | Create a folder + markdown file = new agent or skill |
22
23
  | **Mix and Match** | Use built-in components, create your own, or combine both |
@@ -121,7 +122,7 @@ Claude Code selects the appropriate model and parallelizes independent tasks (up
121
122
  | **QA** | 3 | qa-planner, qa-writer, qa-engineer |
122
123
  | **Total** | **42** | |
123
124
 
124
- ### Skills (52)
125
+ ### Skills (51)
125
126
 
126
127
  Includes slash commands and capabilities:
127
128
 
@@ -157,9 +158,9 @@ Comprehensive reference documentation covering:
157
158
 
158
159
  Event-driven automation for Claude Code lifecycle events (PreToolUse, PostToolUse, etc.).
159
160
 
160
- ### Contexts (1)
161
+ ### Contexts (4)
161
162
 
162
- Shared context file for cross-agent knowledge and mode configurations.
163
+ Shared context files for cross-agent knowledge and mode configurations.
163
164
 
164
165
  ---
165
166
 
@@ -194,13 +195,13 @@ your-project/
194
195
  └── .claude/ # (or .codex/)
195
196
  ├── rules/ # Behavior rules (18 total)
196
197
  ├── hooks/ # Event hooks (1 total)
197
- ├── contexts/ # Context files (1 total)
198
+ ├── contexts/ # Context files (4 total)
198
199
  ├── agents/ # Agent definitions (42 flat .md files)
199
200
  │ ├── lang-golang-expert.md
200
201
  │ ├── be-fastapi-expert.md
201
202
  │ ├── mgr-creator.md
202
203
  │ └── ...
203
- ├── skills/ # Skill modules (52 directories, each with SKILL.md)
204
+ ├── skills/ # Skill modules (51 directories, each with SKILL.md)
204
205
  │ ├── go-best-practices/
205
206
  │ ├── react-best-practices/
206
207
  │ ├── secretary-routing/
@@ -219,6 +220,17 @@ bun test # Run tests
219
220
  bun run build # Build for production
220
221
  ```
221
222
 
223
+ ### Quality Gates
224
+
225
+ | Gate | Tool | Threshold |
226
+ |------|------|-----------|
227
+ | Lint | Biome | Zero errors (complexity enforced) |
228
+ | Test Coverage | Bun test | 95% (pre-commit), 97% (CI) |
229
+ | Security Audit | bun pm audit | No high/critical vulnerabilities |
230
+ | Dependabot | GitHub | Weekly scans, auto-PR for updates |
231
+
232
+ Pre-commit hooks automatically enforce lint, test, and coverage gates before each commit.
233
+
222
234
  ### Requirements
223
235
 
224
236
  - Node.js >= 18.0.0
package/dist/cli/index.js CHANGED
@@ -11725,6 +11725,7 @@ var en_default = {
11725
11725
  skipped: "Update skipped",
11726
11726
  dryRunOption: "Show what would be updated without making changes",
11727
11727
  forceOption: "Force update even if already at latest version",
11728
+ forceOverwriteAllOption: "Bypass all file preservation (manifest and config)",
11728
11729
  backupOption: "Create backup before updating",
11729
11730
  agentsOption: "Update only agents",
11730
11731
  skillsOption: "Update only skills",
@@ -12008,6 +12009,7 @@ var ko_default = {
12008
12009
  skipped: "업데이트 건너뜀",
12009
12010
  dryRunOption: "변경 없이 업데이트할 내용 표시",
12010
12011
  forceOption: "이미 최신 버전이어도 강제 업데이트",
12012
+ forceOverwriteAllOption: "모든 파일 보존 무시 (manifest 및 config)",
12011
12013
  backupOption: "업데이트 전 백업 생성",
12012
12014
  agentsOption: "에이전트만 업데이트",
12013
12015
  skillsOption: "스킬만 업데이트",
@@ -13908,13 +13910,13 @@ async function tryExtractMarkdownDescription(mdPath, options = {}) {
13908
13910
  return;
13909
13911
  }
13910
13912
  }
13911
- async function getAgents(targetDir, rootDir = ".claude") {
13913
+ async function getAgents(targetDir, rootDir = ".claude", config) {
13912
13914
  const agentsDir = join6(targetDir, rootDir, "agents");
13913
13915
  if (!await fileExists(agentsDir))
13914
13916
  return [];
13915
13917
  try {
13916
- const config = await loadConfig(targetDir);
13917
- const customComponents = config.customComponents || [];
13918
+ const resolvedConfig = config ?? await loadConfig(targetDir);
13919
+ const customComponents = resolvedConfig.customComponents || [];
13918
13920
  const customAgentPaths = new Set(customComponents.filter((c) => c.type === "agent").map((c) => c.path));
13919
13921
  const agentMdFiles = await listFiles(agentsDir, { recursive: false, pattern: "*.md" });
13920
13922
  const agents = await Promise.all(agentMdFiles.map(async (agentMdPath) => {
@@ -13936,13 +13938,13 @@ async function getAgents(targetDir, rootDir = ".claude") {
13936
13938
  return [];
13937
13939
  }
13938
13940
  }
13939
- async function getSkills(targetDir, rootDir = ".claude") {
13941
+ async function getSkills(targetDir, rootDir = ".claude", config) {
13940
13942
  const skillsDir = join6(targetDir, rootDir, "skills");
13941
13943
  if (!await fileExists(skillsDir))
13942
13944
  return [];
13943
13945
  try {
13944
- const config = await loadConfig(targetDir);
13945
- const customComponents = config.customComponents || [];
13946
+ const resolvedConfig = config ?? await loadConfig(targetDir);
13947
+ const customComponents = resolvedConfig.customComponents || [];
13946
13948
  const customSkillPaths = new Set(customComponents.filter((c) => c.type === "skill").map((c) => c.path));
13947
13949
  const skillMdFiles = await listFiles(skillsDir, { recursive: true, pattern: "SKILL.md" });
13948
13950
  const skills = await Promise.all(skillMdFiles.map(async (skillMdPath) => {
@@ -13965,13 +13967,13 @@ async function getSkills(targetDir, rootDir = ".claude") {
13965
13967
  return [];
13966
13968
  }
13967
13969
  }
13968
- async function getGuides(targetDir) {
13970
+ async function getGuides(targetDir, config) {
13969
13971
  const guidesDir = join6(targetDir, "guides");
13970
13972
  if (!await fileExists(guidesDir))
13971
13973
  return [];
13972
13974
  try {
13973
- const config = await loadConfig(targetDir);
13974
- const customComponents = config.customComponents || [];
13975
+ const resolvedConfig = config ?? await loadConfig(targetDir);
13976
+ const customComponents = resolvedConfig.customComponents || [];
13975
13977
  const customGuidePaths = new Set(customComponents.filter((c) => c.type === "guide").map((c) => c.path));
13976
13978
  const guideMdFiles = await listFiles(guidesDir, { recursive: true, pattern: "*.md" });
13977
13979
  const guides = await Promise.all(guideMdFiles.map(async (guideMdPath) => {
@@ -13992,13 +13994,13 @@ async function getGuides(targetDir) {
13992
13994
  }
13993
13995
  }
13994
13996
  var RULE_PRIORITY_ORDER = { MUST: 0, SHOULD: 1, MAY: 2 };
13995
- async function getRules(targetDir, rootDir = ".claude") {
13997
+ async function getRules(targetDir, rootDir = ".claude", config) {
13996
13998
  const rulesDir = join6(targetDir, rootDir, "rules");
13997
13999
  if (!await fileExists(rulesDir))
13998
14000
  return [];
13999
14001
  try {
14000
- const config = await loadConfig(targetDir);
14001
- const customComponents = config.customComponents || [];
14002
+ const resolvedConfig = config ?? await loadConfig(targetDir);
14003
+ const customComponents = resolvedConfig.customComponents || [];
14002
14004
  const customRulePaths = new Set(customComponents.filter((c) => c.type === "rule").map((c) => c.path));
14003
14005
  const ruleMdFiles = await listFiles(rulesDir, { recursive: false, pattern: "*.md" });
14004
14006
  const rules = await Promise.all(ruleMdFiles.map(async (ruleMdPath) => {
@@ -14108,7 +14110,7 @@ async function getContexts(targetDir, rootDir = ".claude") {
14108
14110
  var COMPONENT_GETTERS = {
14109
14111
  agents: getAgents,
14110
14112
  skills: getSkills,
14111
- guides: async (dir2) => getGuides(dir2),
14113
+ guides: async (dir2, _rootDir, config) => getGuides(dir2, config),
14112
14114
  rules: getRules,
14113
14115
  hooks: getHooks,
14114
14116
  contexts: getContexts
@@ -14122,12 +14124,12 @@ function displayComponents(components, type, format) {
14122
14124
  formatAsTable(components, type);
14123
14125
  }
14124
14126
  }
14125
- async function handleListAll(targetDir, rootDir, format) {
14127
+ async function handleListAll(targetDir, rootDir, format, config) {
14126
14128
  const [agents, skills, guides, rules, hooks, contexts] = await Promise.all([
14127
- getAgents(targetDir, rootDir),
14128
- getSkills(targetDir, rootDir),
14129
- getGuides(targetDir),
14130
- getRules(targetDir, rootDir),
14129
+ getAgents(targetDir, rootDir, config),
14130
+ getSkills(targetDir, rootDir, config),
14131
+ getGuides(targetDir, config),
14132
+ getRules(targetDir, rootDir, config),
14131
14133
  getHooks(targetDir, rootDir),
14132
14134
  getContexts(targetDir, rootDir)
14133
14135
  ]);
@@ -14152,7 +14154,8 @@ async function listCommand(type = "all", options = {}) {
14152
14154
  preferProject: true
14153
14155
  });
14154
14156
  const layout = getProviderLayout(detection.provider);
14155
- const components = type === "all" ? await handleListAll(targetDir, layout.rootDir, format) : await COMPONENT_GETTERS[type](targetDir, layout.rootDir);
14157
+ const config = await loadConfig(targetDir);
14158
+ const components = type === "all" ? await handleListAll(targetDir, layout.rootDir, format, config) : await COMPONENT_GETTERS[type](targetDir, layout.rootDir, config);
14156
14159
  if (type === "all" && format === "json") {
14157
14160
  formatAsJson(components);
14158
14161
  } else if (type !== "all") {
@@ -14172,34 +14175,60 @@ import { join as join7 } from "node:path";
14172
14175
  // src/core/entry-merger.ts
14173
14176
  var MANAGED_START = "<!-- omcustom:start -->";
14174
14177
  var MANAGED_END = "<!-- omcustom:end -->";
14178
+ function isCodeBlockDelimiter(line) {
14179
+ const trimmed = line.trim();
14180
+ return trimmed.startsWith("```") || trimmed.startsWith("~~~");
14181
+ }
14182
+ function handleManagedStart(currentLines, sections) {
14183
+ if (currentLines.length > 0) {
14184
+ sections.push({
14185
+ type: "custom",
14186
+ content: currentLines.join(`
14187
+ `)
14188
+ });
14189
+ }
14190
+ return {
14191
+ currentSection: { type: "managed", content: "" },
14192
+ currentLines: []
14193
+ };
14194
+ }
14195
+ function handleManagedEnd(currentSection, currentLines, sections) {
14196
+ if (currentSection && currentSection.type === "managed") {
14197
+ currentSection.content = currentLines.join(`
14198
+ `);
14199
+ sections.push(currentSection);
14200
+ return {
14201
+ currentSection: null,
14202
+ currentLines: []
14203
+ };
14204
+ }
14205
+ return { currentSection, currentLines };
14206
+ }
14175
14207
  function parseEntryDoc(content) {
14176
14208
  const sections = [];
14177
14209
  const lines = content.split(`
14178
14210
  `);
14179
14211
  let currentSection = null;
14180
14212
  let currentLines = [];
14213
+ let insideCodeBlock = false;
14181
14214
  for (const line of lines) {
14182
- if (line.trim() === MANAGED_START) {
14183
- if (currentLines.length > 0) {
14184
- sections.push({
14185
- type: "custom",
14186
- content: currentLines.join(`
14187
- `)
14188
- });
14189
- currentLines = [];
14215
+ if (isCodeBlockDelimiter(line)) {
14216
+ insideCodeBlock = !insideCodeBlock;
14217
+ }
14218
+ if (!insideCodeBlock) {
14219
+ const trimmed = line.trim();
14220
+ if (trimmed === MANAGED_START) {
14221
+ const result = handleManagedStart(currentLines, sections);
14222
+ currentSection = result.currentSection;
14223
+ currentLines = result.currentLines;
14224
+ continue;
14190
14225
  }
14191
- currentSection = { type: "managed", content: "" };
14192
- continue;
14193
- }
14194
- if (line.trim() === MANAGED_END) {
14195
- if (currentSection && currentSection.type === "managed") {
14196
- currentSection.content = currentLines.join(`
14197
- `);
14198
- sections.push(currentSection);
14199
- currentSection = null;
14200
- currentLines = [];
14226
+ if (trimmed === MANAGED_END) {
14227
+ const result = handleManagedEnd(currentSection, currentLines, sections);
14228
+ currentSection = result.currentSection;
14229
+ currentLines = result.currentLines;
14230
+ continue;
14201
14231
  }
14202
- continue;
14203
14232
  }
14204
14233
  currentLines.push(line);
14205
14234
  }
@@ -14280,7 +14309,7 @@ async function handleBackupIfRequested(targetDir, provider, backup, result) {
14280
14309
  result.backedUpPaths.push(backupPath);
14281
14310
  info("update.backup_created", { path: backupPath });
14282
14311
  }
14283
- async function processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result) {
14312
+ async function processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result, config) {
14284
14313
  const componentUpdate = updateCheck.updatableComponents.find((c) => c.name === component);
14285
14314
  if (!componentUpdate && !options.force) {
14286
14315
  result.skippedComponents.push(component);
@@ -14292,7 +14321,7 @@ async function processComponentUpdate(targetDir, provider, component, updateChec
14292
14321
  return;
14293
14322
  }
14294
14323
  try {
14295
- const preserved = await updateComponent(targetDir, provider, component, customizations, options);
14324
+ const preserved = await updateComponent(targetDir, provider, component, customizations, options, config);
14296
14325
  result.updatedComponents.push(component);
14297
14326
  result.preservedFiles.push(...preserved);
14298
14327
  } catch (err) {
@@ -14301,9 +14330,9 @@ async function processComponentUpdate(targetDir, provider, component, updateChec
14301
14330
  result.skippedComponents.push(component);
14302
14331
  }
14303
14332
  }
14304
- async function updateAllComponents(targetDir, provider, components, updateCheck, customizations, options, result) {
14333
+ async function updateAllComponents(targetDir, provider, components, updateCheck, customizations, options, result, config) {
14305
14334
  for (const component of components) {
14306
- await processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result);
14335
+ await processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result, config);
14307
14336
  }
14308
14337
  }
14309
14338
  function getEntryTemplateName2(provider, language) {
@@ -14320,6 +14349,21 @@ async function backupFile(filePath) {
14320
14349
  debug("update.file_backed_up", { path: filePath, backup: backupPath });
14321
14350
  }
14322
14351
  }
14352
+ async function resolveManifestCustomizations(options, targetDir) {
14353
+ if (options.forceOverwriteAll) {
14354
+ return null;
14355
+ }
14356
+ if (options.preserveCustomizations === false) {
14357
+ return null;
14358
+ }
14359
+ return loadCustomizationManifest(targetDir);
14360
+ }
14361
+ function resolveConfigPreserveFiles(options, config) {
14362
+ if (options.forceOverwriteAll) {
14363
+ return [];
14364
+ }
14365
+ return config.preserveFiles || [];
14366
+ }
14323
14367
  function resolveCustomizations(customizations, configPreserveFiles) {
14324
14368
  if (!customizations && configPreserveFiles.length === 0) {
14325
14369
  return null;
@@ -14391,11 +14435,11 @@ async function update(options) {
14391
14435
  return result;
14392
14436
  }
14393
14437
  await handleBackupIfRequested(options.targetDir, provider, !!options.backup, result);
14394
- const manifestCustomizations = options.preserveCustomizations !== false ? await loadCustomizationManifest(options.targetDir) : null;
14395
- const configPreserveFiles = config.preserveFiles || [];
14438
+ const manifestCustomizations = await resolveManifestCustomizations(options, options.targetDir);
14439
+ const configPreserveFiles = resolveConfigPreserveFiles(options, config);
14396
14440
  const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles);
14397
14441
  const components = options.components || getAllUpdateComponents();
14398
- await updateAllComponents(options.targetDir, provider, components, updateCheck, customizations, options, result);
14442
+ await updateAllComponents(options.targetDir, provider, components, updateCheck, customizations, options, result, config);
14399
14443
  if (!options.components || options.components.length === 0) {
14400
14444
  await updateEntryDoc(options.targetDir, provider, config, options);
14401
14445
  }
@@ -14454,15 +14498,14 @@ async function componentHasUpdate(_targetDir, provider, component, config) {
14454
14498
  const latestVersion = await getLatestVersion(provider);
14455
14499
  return installedVersion !== latestVersion;
14456
14500
  }
14457
- async function updateComponent(targetDir, provider, component, customizations, options) {
14501
+ async function updateComponent(targetDir, provider, component, customizations, options, config) {
14458
14502
  const preservedFiles = [];
14459
14503
  const componentPath = getComponentPath2(provider, component);
14460
14504
  const srcPath = resolveTemplatePath(componentPath);
14461
14505
  const destPath = join7(targetDir, componentPath);
14462
- const config = await loadConfig(targetDir);
14463
14506
  const customComponents = config.customComponents || [];
14464
14507
  const skipPaths = [];
14465
- if (customizations && options.preserveCustomizations !== false) {
14508
+ if (customizations && !options.forceOverwriteAll) {
14466
14509
  const toPreserve = customizations.preserveFiles.filter((f) => f.startsWith(componentPath));
14467
14510
  preservedFiles.push(...toPreserve);
14468
14511
  skipPaths.push(...toPreserve);
@@ -14538,6 +14581,7 @@ async function updateCommand(options = {}) {
14538
14581
  components,
14539
14582
  force: options.force,
14540
14583
  preserveCustomizations: true,
14584
+ forceOverwriteAll: options.forceOverwriteAll,
14541
14585
  dryRun: options.dryRun,
14542
14586
  backup: options.backup
14543
14587
  };
@@ -14611,7 +14655,7 @@ function createProgram() {
14611
14655
  program2.command("init").description(i18n.t("cli.init.description")).option("-l, --lang <language>", i18n.t("cli.init.langOption"), "en").option("-p, --provider <provider>", i18n.t("cli.init.providerOption"), "auto").action(async (options) => {
14612
14656
  await initCommand(options);
14613
14657
  });
14614
- 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("--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("-p, --provider <provider>", i18n.t("cli.update.providerOption"), "auto").action(async (options) => {
14658
+ 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("-p, --provider <provider>", i18n.t("cli.update.providerOption"), "auto").action(async (options) => {
14615
14659
  await updateCommand(options);
14616
14660
  });
14617
14661
  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").option("-p, --provider <provider>", i18n.t("cli.list.providerOption"), "auto").action(async (type, options) => {
package/dist/index.js CHANGED
@@ -1,20 +1,4 @@
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
- };
18
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
19
3
 
20
4
  // src/core/config.ts
@@ -904,34 +888,60 @@ import { join as join5 } from "node:path";
904
888
  // src/core/entry-merger.ts
905
889
  var MANAGED_START = "<!-- omcustom:start -->";
906
890
  var MANAGED_END = "<!-- omcustom:end -->";
891
+ function isCodeBlockDelimiter(line) {
892
+ const trimmed = line.trim();
893
+ return trimmed.startsWith("```") || trimmed.startsWith("~~~");
894
+ }
895
+ function handleManagedStart(currentLines, sections) {
896
+ if (currentLines.length > 0) {
897
+ sections.push({
898
+ type: "custom",
899
+ content: currentLines.join(`
900
+ `)
901
+ });
902
+ }
903
+ return {
904
+ currentSection: { type: "managed", content: "" },
905
+ currentLines: []
906
+ };
907
+ }
908
+ function handleManagedEnd(currentSection, currentLines, sections) {
909
+ if (currentSection && currentSection.type === "managed") {
910
+ currentSection.content = currentLines.join(`
911
+ `);
912
+ sections.push(currentSection);
913
+ return {
914
+ currentSection: null,
915
+ currentLines: []
916
+ };
917
+ }
918
+ return { currentSection, currentLines };
919
+ }
907
920
  function parseEntryDoc(content) {
908
921
  const sections = [];
909
922
  const lines = content.split(`
910
923
  `);
911
924
  let currentSection = null;
912
925
  let currentLines = [];
926
+ let insideCodeBlock = false;
913
927
  for (const line of lines) {
914
- if (line.trim() === MANAGED_START) {
915
- if (currentLines.length > 0) {
916
- sections.push({
917
- type: "custom",
918
- content: currentLines.join(`
919
- `)
920
- });
921
- currentLines = [];
922
- }
923
- currentSection = { type: "managed", content: "" };
924
- continue;
928
+ if (isCodeBlockDelimiter(line)) {
929
+ insideCodeBlock = !insideCodeBlock;
925
930
  }
926
- if (line.trim() === MANAGED_END) {
927
- if (currentSection && currentSection.type === "managed") {
928
- currentSection.content = currentLines.join(`
929
- `);
930
- sections.push(currentSection);
931
- currentSection = null;
932
- currentLines = [];
931
+ if (!insideCodeBlock) {
932
+ const trimmed = line.trim();
933
+ if (trimmed === MANAGED_START) {
934
+ const result = handleManagedStart(currentLines, sections);
935
+ currentSection = result.currentSection;
936
+ currentLines = result.currentLines;
937
+ continue;
938
+ }
939
+ if (trimmed === MANAGED_END) {
940
+ const result = handleManagedEnd(currentSection, currentLines, sections);
941
+ currentSection = result.currentSection;
942
+ currentLines = result.currentLines;
943
+ continue;
933
944
  }
934
- continue;
935
945
  }
936
946
  currentLines.push(line);
937
947
  }
@@ -1012,7 +1022,7 @@ async function handleBackupIfRequested(targetDir, provider, backup, result) {
1012
1022
  result.backedUpPaths.push(backupPath);
1013
1023
  info("update.backup_created", { path: backupPath });
1014
1024
  }
1015
- async function processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result) {
1025
+ async function processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result, config) {
1016
1026
  const componentUpdate = updateCheck.updatableComponents.find((c) => c.name === component);
1017
1027
  if (!componentUpdate && !options.force) {
1018
1028
  result.skippedComponents.push(component);
@@ -1024,7 +1034,7 @@ async function processComponentUpdate(targetDir, provider, component, updateChec
1024
1034
  return;
1025
1035
  }
1026
1036
  try {
1027
- const preserved = await updateComponent(targetDir, provider, component, customizations, options);
1037
+ const preserved = await updateComponent(targetDir, provider, component, customizations, options, config);
1028
1038
  result.updatedComponents.push(component);
1029
1039
  result.preservedFiles.push(...preserved);
1030
1040
  } catch (err) {
@@ -1033,9 +1043,9 @@ async function processComponentUpdate(targetDir, provider, component, updateChec
1033
1043
  result.skippedComponents.push(component);
1034
1044
  }
1035
1045
  }
1036
- async function updateAllComponents(targetDir, provider, components, updateCheck, customizations, options, result) {
1046
+ async function updateAllComponents(targetDir, provider, components, updateCheck, customizations, options, result, config) {
1037
1047
  for (const component of components) {
1038
- await processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result);
1048
+ await processComponentUpdate(targetDir, provider, component, updateCheck, customizations, options, result, config);
1039
1049
  }
1040
1050
  }
1041
1051
  function getEntryTemplateName2(provider, language) {
@@ -1052,6 +1062,21 @@ async function backupFile(filePath) {
1052
1062
  debug("update.file_backed_up", { path: filePath, backup: backupPath });
1053
1063
  }
1054
1064
  }
1065
+ async function resolveManifestCustomizations(options, targetDir) {
1066
+ if (options.forceOverwriteAll) {
1067
+ return null;
1068
+ }
1069
+ if (options.preserveCustomizations === false) {
1070
+ return null;
1071
+ }
1072
+ return loadCustomizationManifest(targetDir);
1073
+ }
1074
+ function resolveConfigPreserveFiles(options, config) {
1075
+ if (options.forceOverwriteAll) {
1076
+ return [];
1077
+ }
1078
+ return config.preserveFiles || [];
1079
+ }
1055
1080
  function resolveCustomizations(customizations, configPreserveFiles) {
1056
1081
  if (!customizations && configPreserveFiles.length === 0) {
1057
1082
  return null;
@@ -1123,11 +1148,11 @@ async function update(options) {
1123
1148
  return result;
1124
1149
  }
1125
1150
  await handleBackupIfRequested(options.targetDir, provider, !!options.backup, result);
1126
- const manifestCustomizations = options.preserveCustomizations !== false ? await loadCustomizationManifest(options.targetDir) : null;
1127
- const configPreserveFiles = config.preserveFiles || [];
1151
+ const manifestCustomizations = await resolveManifestCustomizations(options, options.targetDir);
1152
+ const configPreserveFiles = resolveConfigPreserveFiles(options, config);
1128
1153
  const customizations = resolveCustomizations(manifestCustomizations, configPreserveFiles);
1129
1154
  const components = options.components || getAllUpdateComponents();
1130
- await updateAllComponents(options.targetDir, provider, components, updateCheck, customizations, options, result);
1155
+ await updateAllComponents(options.targetDir, provider, components, updateCheck, customizations, options, result, config);
1131
1156
  if (!options.components || options.components.length === 0) {
1132
1157
  await updateEntryDoc(options.targetDir, provider, config, options);
1133
1158
  }
@@ -1207,15 +1232,14 @@ async function componentHasUpdate(_targetDir, provider, component, config) {
1207
1232
  const latestVersion = await getLatestVersion(provider);
1208
1233
  return installedVersion !== latestVersion;
1209
1234
  }
1210
- async function updateComponent(targetDir, provider, component, customizations, options) {
1235
+ async function updateComponent(targetDir, provider, component, customizations, options, config) {
1211
1236
  const preservedFiles = [];
1212
1237
  const componentPath = getComponentPath2(provider, component);
1213
1238
  const srcPath = resolveTemplatePath(componentPath);
1214
1239
  const destPath = join5(targetDir, componentPath);
1215
- const config = await loadConfig(targetDir);
1216
1240
  const customComponents = config.customComponents || [];
1217
1241
  const skipPaths = [];
1218
- if (customizations && options.preserveCustomizations !== false) {
1242
+ if (customizations && !options.forceOverwriteAll) {
1219
1243
  const toPreserve = customizations.preserveFiles.filter((f) => f.startsWith(componentPath));
1220
1244
  preservedFiles.push(...toPreserve);
1221
1245
  skipPaths.push(...toPreserve);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-customcode",
3
- "version": "0.9.4",
3
+ "version": "0.10.0",
4
4
  "description": "Batteries-included agent harness for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {