claudecode-omc 5.6.8 → 5.11.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.
Files changed (216) hide show
  1. package/.local/settings/settings.json +8 -0
  2. package/.local/skills/prompt-optimizer/SKILL.md +262 -19
  3. package/.omc-curation/ecc-selection.json +80 -0
  4. package/.omc-curation/governance.json +116 -0
  5. package/.omc-curation/sources.lock.json +30 -0
  6. package/README.md +78 -4
  7. package/bundled/manifest.json +6 -5
  8. package/bundled/upstream/anthropic-skills/.omc-source/bundle.json +18 -0
  9. package/bundled/upstream/anthropic-skills/.omc-source/provenance.json +399 -0
  10. package/bundled/upstream/anthropic-skills/skills/claude-api/SKILL.md +18 -17
  11. package/bundled/upstream/anthropic-skills/skills/claude-api/curl/examples.md +9 -9
  12. package/bundled/upstream/anthropic-skills/skills/claude-api/curl/managed-agents.md +4 -4
  13. package/bundled/upstream/anthropic-skills/skills/claude-api/go/managed-agents/README.md +2 -2
  14. package/bundled/upstream/anthropic-skills/skills/claude-api/java/claude-api.md +2 -2
  15. package/bundled/upstream/anthropic-skills/skills/claude-api/java/managed-agents/README.md +2 -2
  16. package/bundled/upstream/anthropic-skills/skills/claude-api/php/claude-api.md +10 -10
  17. package/bundled/upstream/anthropic-skills/skills/claude-api/php/managed-agents/README.md +2 -2
  18. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/README.md +16 -16
  19. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/batches.md +3 -3
  20. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/files-api.md +3 -3
  21. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/streaming.md +7 -7
  22. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/tool-use.md +19 -19
  23. package/bundled/upstream/anthropic-skills/skills/claude-api/python/managed-agents/README.md +3 -3
  24. package/bundled/upstream/anthropic-skills/skills/claude-api/ruby/claude-api.md +4 -4
  25. package/bundled/upstream/anthropic-skills/skills/claude-api/ruby/managed-agents/README.md +2 -2
  26. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/error-codes.md +5 -5
  27. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/live-sources.md +3 -1
  28. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-api-reference.md +10 -4
  29. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-core.md +19 -1
  30. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-environments.md +6 -2
  31. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-multiagent.md +1 -1
  32. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-onboarding.md +3 -3
  33. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-overview.md +3 -2
  34. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +173 -0
  35. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-tools.md +10 -4
  36. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/model-migration.md +113 -13
  37. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/models.md +14 -11
  38. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/prompt-caching.md +2 -2
  39. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/tool-use-concepts.md +4 -4
  40. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/README.md +15 -15
  41. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/batches.md +2 -2
  42. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/files-api.md +1 -1
  43. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/streaming.md +5 -5
  44. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/tool-use.md +15 -15
  45. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/managed-agents/README.md +3 -3
  46. package/bundled/upstream/ecc/.omc-source/bundle.json +2 -1
  47. package/bundled/upstream/ecc/.omc-source/last-plan-apply.json +108 -24
  48. package/bundled/upstream/ecc/.omc-source/manifests/.claude-plugin/marketplace.json +3 -3
  49. package/bundled/upstream/ecc/.omc-source/provenance.json +563 -0
  50. package/bundled/upstream/ecc/agents/marketing-agent.md +159 -0
  51. package/bundled/upstream/ecc/agents/react-build-resolver.md +215 -0
  52. package/bundled/upstream/ecc/agents/react-reviewer.md +167 -0
  53. package/bundled/upstream/ecc/agents/typescript-reviewer.md +3 -0
  54. package/bundled/upstream/ecc/commands/harness-audit.md +17 -10
  55. package/bundled/upstream/ecc/commands/marketing-campaign.md +129 -0
  56. package/bundled/upstream/ecc/commands/react-build.md +187 -0
  57. package/bundled/upstream/ecc/commands/react-review.md +170 -0
  58. package/bundled/upstream/ecc/commands/react-test.md +265 -0
  59. package/bundled/upstream/ecc/skills/benchmark-optimization-loop/SKILL.md +69 -0
  60. package/bundled/upstream/ecc/skills/blender-motion-state-inspection/SKILL.md +164 -0
  61. package/bundled/upstream/ecc/skills/canary-watch/SKILL.md +9 -1
  62. package/bundled/upstream/ecc/skills/continuous-learning-v2/hooks/observe.sh +31 -9
  63. package/bundled/upstream/ecc/skills/continuous-learning-v2/scripts/detect-project.sh +38 -4
  64. package/bundled/upstream/ecc/skills/continuous-learning-v2/scripts/instinct-cli.py +319 -12
  65. package/bundled/upstream/ecc/skills/data-throughput-accelerator/SKILL.md +72 -0
  66. package/bundled/upstream/ecc/skills/dynamic-workflow-mode/SKILL.md +123 -0
  67. package/bundled/upstream/ecc/skills/frontend-a11y/SKILL.md +446 -0
  68. package/bundled/upstream/ecc/skills/ito-basket-compare/SKILL.md +63 -0
  69. package/bundled/upstream/ecc/skills/ito-data-atlas-agent/SKILL.md +63 -0
  70. package/bundled/upstream/ecc/skills/ito-market-intelligence/SKILL.md +60 -0
  71. package/bundled/upstream/ecc/skills/ito-trade-planner/SKILL.md +67 -0
  72. package/bundled/upstream/ecc/skills/latency-critical-systems/SKILL.md +73 -0
  73. package/bundled/upstream/ecc/skills/marketing-campaign/SKILL.md +113 -0
  74. package/bundled/upstream/ecc/skills/nextjs-turbopack/SKILL.md +13 -0
  75. package/bundled/upstream/ecc/skills/parallel-execution-optimizer/SKILL.md +72 -0
  76. package/bundled/upstream/ecc/skills/prediction-market-oracle-research/SKILL.md +63 -0
  77. package/bundled/upstream/ecc/skills/prediction-market-risk-review/SKILL.md +60 -0
  78. package/bundled/upstream/ecc/skills/react-patterns/SKILL.md +341 -0
  79. package/bundled/upstream/ecc/skills/react-performance/SKILL.md +574 -0
  80. package/bundled/upstream/ecc/skills/react-testing/SKILL.md +423 -0
  81. package/bundled/upstream/ecc/skills/recsys-pipeline-architect/SKILL.md +114 -0
  82. package/bundled/upstream/ecc/skills/recursive-decision-ledger/SKILL.md +79 -0
  83. package/bundled/upstream/ecc/skills/social-publisher/SKILL.md +115 -0
  84. package/bundled/upstream/ecc/skills/team-agent-orchestration/SKILL.md +110 -0
  85. package/bundled/upstream/ecc/skills/uncloud/SKILL.md +343 -0
  86. package/bundled/upstream/ecc/skills/windows-desktop-e2e/SKILL.md +99 -0
  87. package/bundled/upstream/impeccable/.omc-source/bundle.json +20 -0
  88. package/bundled/upstream/impeccable/.omc-source/provenance.json +105 -0
  89. package/bundled/upstream/impeccable/agents/impeccable-manual-edit-applier.md +97 -0
  90. package/bundled/upstream/impeccable/skills/impeccable/SKILL.md +168 -0
  91. package/bundled/upstream/impeccable/skills/impeccable/reference/adapt.md +311 -0
  92. package/bundled/upstream/impeccable/skills/impeccable/reference/animate.md +201 -0
  93. package/bundled/upstream/impeccable/skills/impeccable/reference/audit.md +133 -0
  94. package/bundled/upstream/impeccable/skills/impeccable/reference/bolder.md +113 -0
  95. package/bundled/upstream/impeccable/skills/impeccable/reference/brand.md +108 -0
  96. package/bundled/upstream/impeccable/skills/impeccable/reference/clarify.md +288 -0
  97. package/bundled/upstream/impeccable/skills/impeccable/reference/codex.md +105 -0
  98. package/bundled/upstream/impeccable/skills/impeccable/reference/colorize.md +257 -0
  99. package/bundled/upstream/impeccable/skills/impeccable/reference/craft.md +123 -0
  100. package/bundled/upstream/impeccable/skills/impeccable/reference/critique.md +767 -0
  101. package/bundled/upstream/impeccable/skills/impeccable/reference/delight.md +302 -0
  102. package/bundled/upstream/impeccable/skills/impeccable/reference/distill.md +111 -0
  103. package/bundled/upstream/impeccable/skills/impeccable/reference/document.md +429 -0
  104. package/bundled/upstream/impeccable/skills/impeccable/reference/extract.md +69 -0
  105. package/bundled/upstream/impeccable/skills/impeccable/reference/harden.md +347 -0
  106. package/bundled/upstream/impeccable/skills/impeccable/reference/hooks.md +88 -0
  107. package/bundled/upstream/impeccable/skills/impeccable/reference/init.md +172 -0
  108. package/bundled/upstream/impeccable/skills/impeccable/reference/interaction-design.md +189 -0
  109. package/bundled/upstream/impeccable/skills/impeccable/reference/layout.md +161 -0
  110. package/bundled/upstream/impeccable/skills/impeccable/reference/live.md +718 -0
  111. package/bundled/upstream/impeccable/skills/impeccable/reference/onboard.md +234 -0
  112. package/bundled/upstream/impeccable/skills/impeccable/reference/optimize.md +258 -0
  113. package/bundled/upstream/impeccable/skills/impeccable/reference/overdrive.md +130 -0
  114. package/bundled/upstream/impeccable/skills/impeccable/reference/polish.md +241 -0
  115. package/bundled/upstream/impeccable/skills/impeccable/reference/product.md +60 -0
  116. package/bundled/upstream/impeccable/skills/impeccable/reference/quieter.md +99 -0
  117. package/bundled/upstream/impeccable/skills/impeccable/reference/shape.md +165 -0
  118. package/bundled/upstream/impeccable/skills/impeccable/reference/typeset.md +279 -0
  119. package/bundled/upstream/impeccable/skills/impeccable/scripts/command-metadata.json +94 -0
  120. package/bundled/upstream/impeccable/skills/impeccable/scripts/context-signals.mjs +225 -0
  121. package/bundled/upstream/impeccable/skills/impeccable/scripts/context.mjs +280 -0
  122. package/bundled/upstream/impeccable/skills/impeccable/scripts/critique-storage.mjs +242 -0
  123. package/bundled/upstream/impeccable/skills/impeccable/scripts/detect-csp.mjs +198 -0
  124. package/bundled/upstream/impeccable/skills/impeccable/scripts/detect.mjs +21 -0
  125. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/browser/injected/index.mjs +1735 -0
  126. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/cli/main.mjs +244 -0
  127. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +4907 -0
  128. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/detect-antipatterns.mjs +43 -0
  129. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +252 -0
  130. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +552 -0
  131. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +1013 -0
  132. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +208 -0
  133. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
  134. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/findings.mjs +12 -0
  135. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/node/file-system.mjs +198 -0
  136. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
  137. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/registry/antipatterns.mjs +419 -0
  138. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/rules/checks.mjs +2671 -0
  139. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
  140. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/shared/constants.mjs +101 -0
  141. package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
  142. package/bundled/upstream/impeccable/skills/impeccable/scripts/hook-admin.mjs +574 -0
  143. package/bundled/upstream/impeccable/skills/impeccable/scripts/hook-before-edit.mjs +473 -0
  144. package/bundled/upstream/impeccable/skills/impeccable/scripts/hook-lib.mjs +1286 -0
  145. package/bundled/upstream/impeccable/skills/impeccable/scripts/hook.mjs +61 -0
  146. package/bundled/upstream/impeccable/skills/impeccable/scripts/lib/design-parser.mjs +835 -0
  147. package/bundled/upstream/impeccable/skills/impeccable/scripts/lib/impeccable-paths.mjs +126 -0
  148. package/bundled/upstream/impeccable/skills/impeccable/scripts/lib/is-generated.mjs +69 -0
  149. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  150. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/completion.mjs +19 -0
  151. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/event-validation.mjs +137 -0
  152. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/insert-ui.mjs +458 -0
  153. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
  154. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  155. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/manual-edits-buffer.mjs +152 -0
  156. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/session-store.mjs +289 -0
  157. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/svelte-component.mjs +826 -0
  158. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
  159. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/ui-core.mjs +180 -0
  160. package/bundled/upstream/impeccable/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
  161. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-accept.mjs +812 -0
  162. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-browser-dom.js +146 -0
  163. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-browser-session.js +123 -0
  164. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-browser.js +11086 -0
  165. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
  166. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-complete.mjs +75 -0
  167. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
  168. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
  169. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-inject.mjs +583 -0
  170. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-insert.mjs +272 -0
  171. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
  172. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-poll.mjs +379 -0
  173. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-resume.mjs +94 -0
  174. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-server.mjs +1134 -0
  175. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-status.mjs +61 -0
  176. package/bundled/upstream/impeccable/skills/impeccable/scripts/live-wrap.mjs +894 -0
  177. package/bundled/upstream/impeccable/skills/impeccable/scripts/live.mjs +246 -0
  178. package/bundled/upstream/impeccable/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  179. package/bundled/upstream/impeccable/skills/impeccable/scripts/palette.mjs +633 -0
  180. package/bundled/upstream/impeccable/skills/impeccable/scripts/pin.mjs +214 -0
  181. package/bundled/upstream/oh-my-claudecode/.omc-source/bundle.json +2 -1
  182. package/bundled/upstream/oh-my-claudecode/.omc-source/provenance.json +116 -0
  183. package/bundled/upstream/oh-my-claudecode/skills/autopilot/SKILL.md +7 -0
  184. package/bundled/upstream/oh-my-claudecode/skills/cancel/SKILL.md +1 -0
  185. package/bundled/upstream/oh-my-claudecode/skills/deep-interview/SKILL.md +39 -5
  186. package/bundled/upstream/oh-my-claudecode/skills/hud/SKILL.md +1 -0
  187. package/bundled/upstream/oh-my-claudecode/skills/local-build-reminder/SKILL.md +78 -0
  188. package/bundled/upstream/oh-my-claudecode/skills/omc-doctor/SKILL.md +1 -1
  189. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/SKILL.md +26 -10
  190. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/01-install-claude-md.md +3 -3
  191. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/02-configure.md +6 -4
  192. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/03-integrations.md +1 -1
  193. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/04-welcome.md +2 -2
  194. package/bundled/upstream/oh-my-claudecode/skills/omc-teams/SKILL.md +6 -6
  195. package/bundled/upstream/oh-my-claudecode/skills/plan/SKILL.md +44 -32
  196. package/bundled/upstream/oh-my-claudecode/skills/ralph/SKILL.md +45 -21
  197. package/bundled/upstream/oh-my-claudecode/skills/ralplan/SKILL.md +1 -1
  198. package/bundled/upstream/oh-my-claudecode/skills/self-improve/SKILL.md +7 -0
  199. package/bundled/upstream/oh-my-claudecode/skills/self-improve/scripts/resolve-paths.mjs +39 -15
  200. package/bundled/upstream/oh-my-claudecode/skills/team/SKILL.md +132 -90
  201. package/bundled/upstream/oh-my-claudecode/skills/ultragoal/SKILL.md +93 -0
  202. package/bundled/upstream/oh-my-claudecode/skills/ultraqa/SKILL.md +28 -13
  203. package/bundled/upstream/oh-my-claudecode/skills/ultrawork/SKILL.md +7 -0
  204. package/bundled/upstream/superpowers/.omc-source/bundle.json +2 -1
  205. package/bundled/upstream/superpowers/.omc-source/provenance.json +63 -0
  206. package/package.json +2 -1
  207. package/src/catalog/source-catalog.js +10 -4
  208. package/src/cli/index.js +4 -0
  209. package/src/cli/plan.js +14 -2
  210. package/src/cli/setup.js +52 -13
  211. package/src/cli/skill.js +1 -1
  212. package/src/cli/source.js +271 -14
  213. package/src/config/sources.js +82 -1
  214. package/src/merge/content-patch.js +88 -0
  215. package/templates/merge-config.json +1 -8
  216. package/bundled/upstream/ecc/skills/strategic-compact/suggest-compact.sh +0 -54
package/src/cli/source.js CHANGED
@@ -3,6 +3,7 @@ const { spawnSync } = require('child_process');
3
3
  const path = require('path');
4
4
  const fs = require('fs');
5
5
  const fsp = require('fs/promises');
6
+ const crypto = require('crypto');
6
7
  const { readConfig, setActiveSource, recordSync, addSource, removeSource } = require('../config/sources');
7
8
  const { getProjectRoot, getSourceArtifactDir, getSyncTargetDir, getSyncTempDir, getSourceMetadataDir } = require('../config/paths');
8
9
  const { buildSourceCatalog } = require('../catalog/source-catalog');
@@ -26,6 +27,84 @@ function copyFileRecursive(src, dest) {
26
27
  .then(() => fsp.copyFile(src, dest));
27
28
  }
28
29
 
30
+ // --- Provenance: content hashes + upstream commit, recorded per sync so drift
31
+ // (local edits, upstream changes pulled in) can be detected later. This is what
32
+ // separates a governed source from a blind copy.
33
+ function hashFile(filePath) {
34
+ return 'sha256:' + crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
35
+ }
36
+
37
+ // Map of POSIX relative path → content hash for every file under dir (recursive,
38
+ // so skill directories and flat .md files hash uniformly).
39
+ function hashTree(dir) {
40
+ const out = {};
41
+ if (!fs.existsSync(dir)) return out;
42
+ const stat = fs.statSync(dir);
43
+ if (stat.isFile()) {
44
+ out[path.basename(dir)] = hashFile(dir);
45
+ return out;
46
+ }
47
+ const walk = (current, prefix) => {
48
+ for (const entry of fs.readdirSync(current, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
49
+ const abs = path.join(current, entry.name);
50
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
51
+ if (entry.isDirectory()) walk(abs, rel);
52
+ else if (entry.isFile()) out[rel] = hashFile(abs);
53
+ }
54
+ };
55
+ walk(dir, '');
56
+ return out;
57
+ }
58
+
59
+ // Strip any inline credentials (https://user:token@host/…) before a remote URL
60
+ // is logged to stdout or persisted into bundle.json/provenance.json.
61
+ function redactRemote(remote) {
62
+ try {
63
+ const u = new URL(remote);
64
+ if (u.username || u.password) { u.username = ''; u.password = ''; return u.toString(); }
65
+ } catch { /* non-URL (e.g. scp-style or local path) — return as-is */ }
66
+ return remote;
67
+ }
68
+
69
+ function diffHashTrees(recorded = {}, current = {}) {
70
+ const added = [];
71
+ const removed = [];
72
+ const changed = [];
73
+ for (const key of Object.keys(current)) {
74
+ if (!(key in recorded)) added.push(key);
75
+ else if (recorded[key] !== current[key]) changed.push(key);
76
+ }
77
+ for (const key of Object.keys(recorded)) {
78
+ if (!(key in current)) removed.push(key);
79
+ }
80
+ return { added: added.sort(), removed: removed.sort(), changed: changed.sort() };
81
+ }
82
+
83
+ // --- Lockfile: pins each source to an exact upstream commit for reproducible
84
+ // syncs. Lives in-repo at .omc-curation/sources.lock.json so it ships and is
85
+ // version-controlled alongside the curation it locks.
86
+ function getLockPath(root) {
87
+ return path.join(root, '.omc-curation', 'sources.lock.json');
88
+ }
89
+
90
+ function readLock(root) {
91
+ try {
92
+ const data = JSON.parse(fs.readFileSync(getLockPath(root), 'utf8'));
93
+ return data && typeof data.sources === 'object' ? data : { sources: {} };
94
+ } catch {
95
+ return { sources: {} };
96
+ }
97
+ }
98
+
99
+ async function writeLock(root, lock) {
100
+ const lockPath = getLockPath(root);
101
+ await fsp.mkdir(path.dirname(lockPath), { recursive: true });
102
+ await fsp.writeFile(lockPath, JSON.stringify({
103
+ _comment: 'Pinned upstream commits for reproducible `source sync --frozen`. Update with `omc-manage source lock`.',
104
+ ...lock,
105
+ }, null, 2) + '\n', 'utf8');
106
+ }
107
+
29
108
  function parseMappingFlag(mappingFlag) {
30
109
  if (!mappingFlag) return {};
31
110
 
@@ -46,8 +125,43 @@ function parseMappingFlag(mappingFlag) {
46
125
  return mapping;
47
126
  }
48
127
 
49
- async function syncRemoteSource(sourceName, sourceConfig, root) {
50
- console.log(` Syncing ${sourceName} from ${sourceConfig.remote} (${sourceConfig.ref})...`);
128
+ // EXPERIMENTAL (not load-bearing): pinned/frozen fetch + provenance/drift below
129
+ // address potential, not currently validated, needs — the bundled snapshot
130
+ // already pins content for reproducibility. Keep them isolated/opt-in.
131
+ //
132
+ // Fetch the source into tmpDir. With pinnedCommit, fetch that exact SHA for a
133
+ // reproducible (frozen) checkout; otherwise shallow-clone the ref tip.
134
+ function fetchSource(tmpDir, remote, ref, pinnedCommit) {
135
+ const opts = { encoding: 'utf8', timeout: 300000, stdio: ['ignore', 'pipe', 'pipe'] };
136
+ if (pinnedCommit) {
137
+ // Defense-in-depth: a pinned commit comes from the lockfile JSON. Require a
138
+ // bare hex SHA so a crafted value can't be read as a git option, and pass it
139
+ // after `--` so it's unambiguously a ref, never a flag.
140
+ if (!/^[0-9a-f]{7,40}$/i.test(pinnedCommit)) {
141
+ return { status: 1, stderr: `Invalid pinned commit (not a hex SHA): ${JSON.stringify(pinnedCommit)}` };
142
+ }
143
+ fs.mkdirSync(tmpDir, { recursive: true });
144
+ const steps = [
145
+ ['init', '-q', tmpDir],
146
+ ['-C', tmpDir, 'remote', 'add', 'origin', remote],
147
+ ['-C', tmpDir, 'fetch', '--depth', '1', 'origin', '--', pinnedCommit],
148
+ ['-C', tmpDir, 'checkout', '-q', 'FETCH_HEAD'],
149
+ ];
150
+ let last;
151
+ for (const args of steps) {
152
+ last = spawnSync('git', args, opts);
153
+ if (last.status !== 0) return last;
154
+ }
155
+ return last;
156
+ }
157
+ return spawnSync('git', [
158
+ 'clone', '--depth', '1', '--branch', ref, '--single-branch', remote, tmpDir,
159
+ ], opts);
160
+ }
161
+
162
+ async function syncRemoteSource(sourceName, sourceConfig, root, pinnedCommit = null) {
163
+ const refLabel = pinnedCommit ? `${sourceConfig.ref} @ ${pinnedCommit.slice(0, 12)} (frozen)` : sourceConfig.ref;
164
+ console.log(` Syncing ${sourceName} from ${redactRemote(sourceConfig.remote)} (${refLabel})...`);
51
165
 
52
166
  const tmpDir = getSyncTempDir(sourceName, root);
53
167
  try {
@@ -55,19 +169,21 @@ async function syncRemoteSource(sourceName, sourceConfig, root) {
55
169
  await fsp.rm(tmpDir, { recursive: true, force: true });
56
170
  }
57
171
 
58
- const cloneResult = spawnSync('git', [
59
- 'clone', '--depth', '1', '--branch', sourceConfig.ref,
60
- '--single-branch', sourceConfig.remote, tmpDir,
61
- ], { encoding: 'utf8', timeout: 300000, stdio: ['ignore', 'pipe', 'pipe'] });
172
+ const cloneResult = fetchSource(tmpDir, sourceConfig.remote, sourceConfig.ref, pinnedCommit);
62
173
 
63
174
  if (cloneResult.status !== 0) {
64
175
  console.error(` Clone failed: ${cloneResult.stderr || cloneResult.error}`);
65
176
  return false;
66
177
  }
67
178
 
179
+ // Resolve the exact upstream commit this sync pulled (provenance + lock).
180
+ const headResult = spawnSync('git', ['-C', tmpDir, 'rev-parse', 'HEAD'], { encoding: 'utf8' });
181
+ const commit = headResult.status === 0 ? headResult.stdout.trim() : null;
182
+
68
183
  // Copy each declared artifact type
69
184
  const artifacts = sourceConfig.artifacts || ['skills'];
70
185
  const mapping = sourceConfig.mapping || {};
186
+ const provenance = {};
71
187
 
72
188
  for (const artifactType of artifacts) {
73
189
  const srcSubdir = mapping[artifactType] || artifactType;
@@ -85,6 +201,7 @@ async function syncRemoteSource(sourceName, sourceConfig, root) {
85
201
  const count = fs.statSync(destPath).isDirectory()
86
202
  ? fs.readdirSync(destPath).length : 1;
87
203
  console.log(` ${artifactType}: ${count} items`);
204
+ provenance[artifactType] = hashTree(destPath);
88
205
  }
89
206
  }
90
207
 
@@ -100,11 +217,13 @@ async function syncRemoteSource(sourceName, sourceConfig, root) {
100
217
  }
101
218
 
102
219
  await fsp.mkdir(metadataDir, { recursive: true });
220
+ const syncedAt = new Date().toISOString();
103
221
  await fsp.writeFile(path.join(metadataDir, 'bundle.json'), JSON.stringify({
104
- syncedAt: new Date().toISOString(),
222
+ syncedAt,
105
223
  sourceName,
106
- remote: sourceConfig.remote,
224
+ remote: redactRemote(sourceConfig.remote),
107
225
  ref: sourceConfig.ref,
226
+ commit,
108
227
  kind: sourceConfig.kind || 'content-repo',
109
228
  harnesses: sourceConfig.harnesses || ['claude'],
110
229
  artifacts: sourceConfig.artifacts || [],
@@ -112,6 +231,19 @@ async function syncRemoteSource(sourceName, sourceConfig, root) {
112
231
  profiles: sourceConfig.profiles || [],
113
232
  }, null, 2) + '\n', 'utf8');
114
233
 
234
+ // Provenance: per-file content hashes + the resolved commit, so `source drift`
235
+ // can later distinguish local edits from upstream changes.
236
+ await fsp.writeFile(path.join(metadataDir, 'provenance.json'), JSON.stringify({
237
+ syncedAt,
238
+ sourceName,
239
+ remote: redactRemote(sourceConfig.remote),
240
+ ref: sourceConfig.ref,
241
+ commit,
242
+ artifacts: provenance,
243
+ }, null, 2) + '\n', 'utf8');
244
+
245
+ if (commit) console.log(` commit: ${commit.slice(0, 12)}`);
246
+
115
247
  return true;
116
248
  } finally {
117
249
  if (fs.existsSync(tmpDir)) {
@@ -219,17 +351,21 @@ async function source(args, flags = {}) {
219
351
 
220
352
  const root = getProjectRoot();
221
353
  const targetName = args[1]; // optional: specific source name
354
+ const lock = flags.frozen ? readLock(root) : { sources: {} };
355
+ if (flags.frozen) console.log('(frozen: syncing to locked commits)\n');
222
356
  let success = true;
223
357
 
224
358
  for (const [name, src] of Object.entries(config.sources)) {
225
359
  if (name === 'local') continue; // local is in-repo, no sync needed
226
360
  if (targetName && name !== targetName) continue;
227
- if (!flags.all && !targetName && !flags[name]) {
228
- // Default: sync all remote sources
229
- }
230
-
231
361
  if (!src.remote) continue;
232
- const ok = await syncRemoteSource(name, src, root);
362
+
363
+ let pinnedCommit = null;
364
+ if (flags.frozen) {
365
+ pinnedCommit = (lock.sources[name] || {}).commit || null;
366
+ if (!pinnedCommit) console.log(` ${name}: no lock entry, syncing ref tip`);
367
+ }
368
+ const ok = await syncRemoteSource(name, src, root, pinnedCommit);
233
369
  if (!ok) success = false;
234
370
  console.log('');
235
371
  }
@@ -251,6 +387,13 @@ async function source(args, flags = {}) {
251
387
  console.log(`[${name}] (priority ${src.priority})`);
252
388
  console.log(` kind: ${src.kind || 'content-repo'}`);
253
389
  console.log(` installMode: ${src.installMode || 'auto'}`);
390
+ const bundlePath = path.join(getSourceMetadataDir(name, root), 'bundle.json');
391
+ if (fs.existsSync(bundlePath)) {
392
+ try {
393
+ const bundle = JSON.parse(fs.readFileSync(bundlePath, 'utf8'));
394
+ if (bundle.commit) console.log(` commit: ${bundle.commit.slice(0, 12)}`);
395
+ } catch { /* ignore unreadable bundle metadata */ }
396
+ }
254
397
  if (src.appliedProfile) {
255
398
  console.log(` appliedProfile: ${src.appliedProfile}`);
256
399
  }
@@ -286,6 +429,120 @@ async function source(args, flags = {}) {
286
429
  break;
287
430
  }
288
431
 
432
+ case 'lock': {
433
+ const root = getProjectRoot();
434
+ const targetName = args[1];
435
+ const lock = readLock(root);
436
+ let locked = 0;
437
+ const missing = [];
438
+
439
+ for (const [name, src] of Object.entries(config.sources)) {
440
+ if (targetName && name !== targetName) continue;
441
+ if (!src.remote) continue;
442
+ const bundlePath = path.join(getSourceMetadataDir(name, root), 'bundle.json');
443
+ let commit = null;
444
+ if (fs.existsSync(bundlePath)) {
445
+ try { commit = JSON.parse(fs.readFileSync(bundlePath, 'utf8')).commit || null; } catch { /* ignore */ }
446
+ }
447
+ if (!commit) { missing.push(name); continue; }
448
+ // Preserve lockedAt when the commit hasn't changed, to avoid timestamp
449
+ // churn in the version-controlled lockfile.
450
+ const prev = lock.sources[name];
451
+ const lockedAt = (prev && prev.commit === commit && prev.lockedAt) ? prev.lockedAt : new Date().toISOString();
452
+ lock.sources[name] = { commit, ref: src.ref || 'main', lockedAt };
453
+ locked += 1;
454
+ }
455
+
456
+ if (targetName && !config.sources[targetName]) {
457
+ const validNames = Object.keys(config.sources).join(', ');
458
+ throw new Error(`Invalid source. Use: ${validNames}`);
459
+ }
460
+
461
+ await writeLock(root, { sources: lock.sources });
462
+ console.log(`Locked ${locked} source(s) → ${path.relative(root, getLockPath(root))}`);
463
+ for (const [name, entry] of Object.entries(lock.sources)) {
464
+ if (!targetName || name === targetName) console.log(` ${name}: ${entry.commit.slice(0, 12)} (${entry.ref})`);
465
+ }
466
+ if (missing.length > 0) {
467
+ console.log(` unlocked (no synced commit): ${missing.join(', ')} — run "omc-manage source sync" first`);
468
+ }
469
+ break;
470
+ }
471
+
472
+ case 'drift': {
473
+ // EXPERIMENTAL: advisory only. `sync` overwrites local edits regardless,
474
+ // so this is not a strong integrity gate — don't rely on it in CI as one.
475
+ const root = getProjectRoot();
476
+ const targetName = args[1];
477
+ const report = {};
478
+ let anyDrift = false;
479
+ let anyChecked = false;
480
+
481
+ for (const [name, src] of Object.entries(config.sources)) {
482
+ if (targetName && name !== targetName) continue;
483
+ if (name === 'local') continue; // in-repo, nothing to compare against
484
+ const provPath = path.join(getSourceMetadataDir(name, root), 'provenance.json');
485
+ if (!fs.existsSync(provPath)) {
486
+ report[name] = { status: 'no-provenance' };
487
+ continue;
488
+ }
489
+ let prov;
490
+ try {
491
+ prov = JSON.parse(fs.readFileSync(provPath, 'utf8'));
492
+ } catch {
493
+ report[name] = { status: 'corrupt-provenance' };
494
+ anyDrift = true; // integrity problem — fail CI like drift
495
+ continue;
496
+ }
497
+ anyChecked = true;
498
+ const perType = {};
499
+ let sourceDrift = false;
500
+ for (const artifactType of Object.keys(prov.artifacts || {})) {
501
+ const dir = getSourceArtifactDir(name, artifactType, root);
502
+ const delta = diffHashTrees(prov.artifacts[artifactType], hashTree(dir));
503
+ if (delta.added.length || delta.removed.length || delta.changed.length) {
504
+ perType[artifactType] = delta;
505
+ sourceDrift = true;
506
+ }
507
+ }
508
+ report[name] = { status: sourceDrift ? 'drift' : 'clean', commit: prov.commit, syncedAt: prov.syncedAt, drift: perType };
509
+ if (sourceDrift) anyDrift = true;
510
+ }
511
+
512
+ if (targetName && !(targetName in report)) {
513
+ const validNames = Object.keys(config.sources).join(', ');
514
+ throw new Error(`Invalid source. Use: ${validNames}`);
515
+ }
516
+
517
+ if (flags.json) {
518
+ console.log(JSON.stringify(report, null, 2));
519
+ } else {
520
+ console.log('Drift Report');
521
+ console.log('============');
522
+ for (const [name, r] of Object.entries(report)) {
523
+ if (r.status === 'no-provenance') {
524
+ console.log(`○ ${name}: no provenance (run "omc-manage source sync ${name}")`);
525
+ continue;
526
+ }
527
+ if (r.status === 'corrupt-provenance') {
528
+ console.log(`✗ ${name}: corrupt provenance (re-sync to repair)`);
529
+ continue;
530
+ }
531
+ const marker = r.status === 'drift' ? '✗' : '✓';
532
+ console.log(`${marker} ${name}: ${r.status}${r.commit ? ` @ ${r.commit.slice(0, 12)}` : ''}`);
533
+ for (const [type, delta] of Object.entries(r.drift || {})) {
534
+ for (const f of delta.changed) console.log(` ~ ${type}/${f}`);
535
+ for (const f of delta.added) console.log(` + ${type}/${f}`);
536
+ for (const f of delta.removed) console.log(` - ${type}/${f}`);
537
+ }
538
+ }
539
+ if (!anyChecked) console.log('(no synced sources with provenance)');
540
+ }
541
+
542
+ if (anyDrift) process.exitCode = 1;
543
+ break;
544
+ }
545
+
289
546
  case 'inspect': {
290
547
  const name = args[1];
291
548
  if (!name || !config.sources[name]) {
@@ -332,7 +589,7 @@ async function source(args, flags = {}) {
332
589
  }
333
590
 
334
591
  default:
335
- throw new Error(`Unknown subcommand: ${cmd}. Use: list, add, remove, set, sync, status, or inspect`);
592
+ throw new Error(`Unknown subcommand: ${cmd}. Use: list, add, remove, set, sync, lock, status, drift, or inspect`);
336
593
  }
337
594
  }
338
595
 
@@ -6,6 +6,46 @@ const os = require('os');
6
6
  const CONFIG_DIR = path.join(os.homedir(), '.omc-manage');
7
7
  const CONFIG_PATH = path.join(CONFIG_DIR, 'sources.json');
8
8
 
9
+ // Package root (src/config/sources.js → ../../). Holds the shipped .omc-curation
10
+ // selection files that drive distribution-repo allowlists.
11
+ const PKG_ROOT = path.resolve(__dirname, '..', '..');
12
+
13
+ // The unified governance manifest: single authoritative source for cross-source
14
+ // policy (per-source priority + allowlist, and conflict resolution). Cached per
15
+ // process. Returns {} when absent so callers can fall back to legacy config.
16
+ let _governanceCache;
17
+ function loadGovernance() {
18
+ if (_governanceCache !== undefined) return _governanceCache;
19
+ try {
20
+ const data = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, '.omc-curation', 'governance.json'), 'utf8'));
21
+ if (data && typeof data === 'object') {
22
+ _governanceCache = data;
23
+ return data;
24
+ }
25
+ } catch { /* fall through */ }
26
+ // Don't cache a missing/unreadable result — a transient failure shouldn't
27
+ // poison the manifest for the rest of the process.
28
+ return {};
29
+ }
30
+
31
+ // Load a source's curated allowlist from the in-repo .omc-curation/<name>-selection.json.
32
+ // This is the single source of truth shared by `plan apply` (maintainer) and the
33
+ // end-user default config below — no hardcoded duplicate that could drift.
34
+ function loadCurationAllowlist(sourceName) {
35
+ const selectionPath = path.join(PKG_ROOT, '.omc-curation', `${sourceName}-selection.json`);
36
+ try {
37
+ const data = JSON.parse(fs.readFileSync(selectionPath, 'utf8'));
38
+ if (!data || typeof data !== 'object') return undefined;
39
+ const allowlist = {};
40
+ for (const type of ['skills', 'agents', 'commands', 'hooks']) {
41
+ if (Array.isArray(data[type]) && data[type].length > 0) allowlist[type] = data[type];
42
+ }
43
+ return Object.keys(allowlist).length > 0 ? allowlist : undefined;
44
+ } catch {
45
+ return undefined;
46
+ }
47
+ }
48
+
9
49
  const DEFAULT_DISTRIBUTION_MANIFESTS = [
10
50
  'package.json',
11
51
  '.claude-plugin/plugin.json',
@@ -55,6 +95,36 @@ function getDefaultConfig() {
55
95
  harnesses: ['claude'],
56
96
  profiles: DEFAULT_INSTALL_PROFILES,
57
97
  },
98
+ ecc: {
99
+ remote: 'https://github.com/affaan-m/everything-claude-code.git',
100
+ ref: 'main',
101
+ priority: 4,
102
+ artifacts: ['agents', 'commands', 'skills'],
103
+ kind: 'distribution-repo',
104
+ installMode: 'auto',
105
+ harnesses: ['claude'],
106
+ manifests: ['plugin.json', 'marketplace.json', '.claude-plugin/marketplace.json'],
107
+ // Allowlist is applied generically in normalizeSourceConfig from
108
+ // .omc-curation/ecc-selection.json (a fresh install gets the vetted
109
+ // slice, not all 251 skills / 63 agents) — same path as every source.
110
+ appliedProfile: 'claude-runtime',
111
+ profiles: DEFAULT_INSTALL_PROFILES,
112
+ },
113
+ impeccable: {
114
+ remote: 'https://github.com/pbakaus/impeccable.git',
115
+ ref: 'main',
116
+ priority: 5,
117
+ artifacts: ['skills', 'agents'],
118
+ // Skill + companion agent live under the repo's .claude/ tree, not at
119
+ // top-level skills/ — map artifact types to their real subdirs.
120
+ mapping: {
121
+ skills: '.claude/skills',
122
+ agents: '.claude/agents',
123
+ },
124
+ kind: 'content-repo',
125
+ harnesses: ['claude'],
126
+ profiles: DEFAULT_INSTALL_PROFILES,
127
+ },
58
128
  'anthropic-skills': {
59
129
  remote: 'https://github.com/anthropics/skills.git',
60
130
  ref: 'main',
@@ -114,7 +184,17 @@ function normalizeSourceConfig(name, source) {
114
184
  ? DEFAULT_DISTRIBUTION_MANIFESTS
115
185
  : []));
116
186
  source.profiles = dedupeStrings(source.profiles || DEFAULT_INSTALL_PROFILES);
117
- source.allowlist = normalizeAllowlist(source.allowlist);
187
+
188
+ // Unified governance.json is the authoritative cross-source policy.
189
+ const govSource = (loadGovernance().sources || {})[name] || {};
190
+ // Priority: governance wins when it declares one (single source of truth).
191
+ if (typeof govSource.priority === 'number') source.priority = govSource.priority;
192
+ // Allowlist authority: explicit config (e.g. `plan apply`) > governance.json
193
+ // inline allowlist > per-source .omc-curation/<name>-selection.json > none
194
+ // (no allowlist → install everything).
195
+ source.allowlist = normalizeAllowlist(source.allowlist)
196
+ || normalizeAllowlist(govSource.allowlist)
197
+ || normalizeAllowlist(loadCurationAllowlist(name));
118
198
 
119
199
  if (source.role === 'reference' && !source.profiles.includes('reference-only')) {
120
200
  source.profiles.push('reference-only');
@@ -249,6 +329,7 @@ module.exports = {
249
329
  updateSource,
250
330
  getSourceAllowlist,
251
331
  filterItemsByAllowlist,
332
+ loadGovernance,
252
333
  normalizeConfig,
253
334
  normalizeSourceConfig,
254
335
  DEFAULT_DISTRIBUTION_MANIFESTS,
@@ -0,0 +1,88 @@
1
+ 'use strict';
2
+
3
+ // EXPERIMENTAL (not load-bearing): for surgical edits, `.local` whole-file
4
+ // override usually suffices. This solves a thinner, potential need — keep it
5
+ // opt-in and don't treat it as a core guarantee.
6
+ //
7
+ // Content-level patching for installed artifacts. A patch is declared inline in
8
+ // governance.json under sources.<name>.patches["<type>/<artifact>"] and is
9
+ // applied at install time, after the winning source is chosen and before the
10
+ // file is written to ~/.claude. Markdown + YAML-frontmatter aware.
11
+ //
12
+ // Patch ops (all optional, applied in this order):
13
+ // frontmatter: { key: value } merge/override scalar frontmatter keys
14
+ // replace: [{ find, with }] literal string replacements in the body
15
+ // prepend: "text" prepended to the body
16
+ // append: "text" appended to the body
17
+
18
+ function escapeRegExp(s) {
19
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
20
+ }
21
+
22
+ function splitFrontmatter(content) {
23
+ // CRLF-tolerant: match \r?\n so Windows-authored artifacts are parsed as
24
+ // frontmatter rather than slipping through as body (which would then get a
25
+ // second, duplicate frontmatter block prepended by a frontmatter patch).
26
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?/);
27
+ if (!m) return { frontmatter: null, body: content };
28
+ // Normalize CRs in the frontmatter block (we re-emit it as \n-joined lines);
29
+ // the body is left byte-for-byte so user content keeps its own line endings.
30
+ return { frontmatter: m[1].replace(/\r/g, ''), body: content.slice(m[0].length) };
31
+ }
32
+
33
+ // Serialize a scalar for a simple `key: value` frontmatter line, quoting only
34
+ // when the value would otherwise break a bare YAML scalar.
35
+ function serializeScalar(value) {
36
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
37
+ const s = String(value);
38
+ if (s === '' || s.trim() !== s || /[:#"'\n[\]{}&*!|>%@`]/.test(s)) {
39
+ return JSON.stringify(s); // valid double-quoted YAML, escapes as needed
40
+ }
41
+ return s;
42
+ }
43
+
44
+ // Line-based override of scalar frontmatter keys: replace the key's line if
45
+ // present, else append it. Deliberately scalar-only — nested/list overrides
46
+ // are out of scope and should use `replace`.
47
+ function mergeFrontmatter(block, overrides) {
48
+ let next = block || '';
49
+ for (const [key, value] of Object.entries(overrides)) {
50
+ const line = `${key}: ${serializeScalar(value)}`;
51
+ const re = new RegExp(`^${escapeRegExp(key)}\\s*:.*$`, 'm');
52
+ if (re.test(next)) next = next.replace(re, line);
53
+ else next = next ? `${next}\n${line}` : line;
54
+ }
55
+ return next;
56
+ }
57
+
58
+ // Apply a patch spec to file content. Returns { content, warnings }.
59
+ // Unknown/empty patch → content unchanged.
60
+ function applyContentPatch(content, patch) {
61
+ const warnings = [];
62
+ if (!patch || typeof patch !== 'object') return { content, warnings };
63
+
64
+ let { frontmatter, body } = splitFrontmatter(content);
65
+
66
+ if (patch.frontmatter && typeof patch.frontmatter === 'object') {
67
+ frontmatter = mergeFrontmatter(frontmatter, patch.frontmatter);
68
+ }
69
+
70
+ if (Array.isArray(patch.replace)) {
71
+ for (const rule of patch.replace) {
72
+ if (!rule || typeof rule.find !== 'string' || rule.find === '') continue;
73
+ if (!body.includes(rule.find)) {
74
+ warnings.push(`replace target not found: ${JSON.stringify(rule.find.slice(0, 40))}`);
75
+ continue;
76
+ }
77
+ body = body.split(rule.find).join(rule.with == null ? '' : String(rule.with));
78
+ }
79
+ }
80
+
81
+ if (typeof patch.prepend === 'string') body = patch.prepend + body;
82
+ if (typeof patch.append === 'string') body = body + patch.append;
83
+
84
+ const out = frontmatter != null ? `---\n${frontmatter}\n---\n${body}` : body;
85
+ return { content: out, warnings };
86
+ }
87
+
88
+ module.exports = { applyContentPatch };
@@ -1,12 +1,5 @@
1
1
  {
2
- "merge_strategy": "version-priority",
3
- "auto_merge": true,
4
- "allow_namespacing": false,
5
- "sources": [
6
- { "name": "local", "priority": 1 },
7
- { "name": "oh-my-claudecode", "priority": 2 },
8
- { "name": "superpowers", "priority": 3 }
9
- ],
2
+ "_comment": "DEPRECATED — superseded by .omc-curation/governance.json (the unified authority for priority + allowlist + conflict). Kept only as a fallback for the conflict block (preferences/exclude) when governance.json declares none. Priority now lives solely in governance.json / the source config, not here.",
10
3
  "preferences": {},
11
4
  "exclude": {
12
5
  "skills": [
@@ -1,54 +0,0 @@
1
- #!/bin/bash
2
- # Strategic Compact Suggester
3
- # Runs on PreToolUse or periodically to suggest manual compaction at logical intervals
4
- #
5
- # Why manual over auto-compact:
6
- # - Auto-compact happens at arbitrary points, often mid-task
7
- # - Strategic compacting preserves context through logical phases
8
- # - Compact after exploration, before execution
9
- # - Compact after completing a milestone, before starting next
10
- #
11
- # Hook config (in ~/.claude/settings.json):
12
- # {
13
- # "hooks": {
14
- # "PreToolUse": [{
15
- # "matcher": "Edit|Write",
16
- # "hooks": [{
17
- # "type": "command",
18
- # "command": "~/.claude/skills/strategic-compact/suggest-compact.sh"
19
- # }]
20
- # }]
21
- # }
22
- # }
23
- #
24
- # Criteria for suggesting compact:
25
- # - Session has been running for extended period
26
- # - Large number of tool calls made
27
- # - Transitioning from research/exploration to implementation
28
- # - Plan has been finalized
29
-
30
- # Track tool call count (increment in a temp file)
31
- # Use CLAUDE_SESSION_ID for session-specific counter (not $$ which changes per invocation)
32
- SESSION_ID="${CLAUDE_SESSION_ID:-${PPID:-default}}"
33
- COUNTER_FILE="/tmp/claude-tool-count-${SESSION_ID}"
34
- THRESHOLD=${COMPACT_THRESHOLD:-50}
35
-
36
- # Initialize or increment counter
37
- if [ -f "$COUNTER_FILE" ]; then
38
- count=$(cat "$COUNTER_FILE")
39
- count=$((count + 1))
40
- echo "$count" > "$COUNTER_FILE"
41
- else
42
- echo "1" > "$COUNTER_FILE"
43
- count=1
44
- fi
45
-
46
- # Suggest compact after threshold tool calls
47
- if [ "$count" -eq "$THRESHOLD" ]; then
48
- echo "[StrategicCompact] $THRESHOLD tool calls reached - consider /compact if transitioning phases" >&2
49
- fi
50
-
51
- # Suggest at regular intervals after threshold
52
- if [ "$count" -gt "$THRESHOLD" ] && [ $((count % 25)) -eq 0 ]; then
53
- echo "[StrategicCompact] $count tool calls - good checkpoint for /compact if context is stale" >&2
54
- fi