claudecode-omc 5.6.8 → 5.9.1

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 (121) hide show
  1. package/.local/skills/prompt-optimizer/SKILL.md +262 -19
  2. package/.omc-curation/ecc-selection.json +80 -0
  3. package/.omc-curation/governance.json +113 -0
  4. package/.omc-curation/sources.lock.json +25 -0
  5. package/README.md +69 -4
  6. package/bundled/manifest.json +5 -5
  7. package/bundled/upstream/anthropic-skills/.omc-source/bundle.json +18 -0
  8. package/bundled/upstream/anthropic-skills/.omc-source/provenance.json +399 -0
  9. package/bundled/upstream/anthropic-skills/skills/claude-api/SKILL.md +18 -17
  10. package/bundled/upstream/anthropic-skills/skills/claude-api/curl/examples.md +9 -9
  11. package/bundled/upstream/anthropic-skills/skills/claude-api/curl/managed-agents.md +4 -4
  12. package/bundled/upstream/anthropic-skills/skills/claude-api/go/managed-agents/README.md +2 -2
  13. package/bundled/upstream/anthropic-skills/skills/claude-api/java/claude-api.md +2 -2
  14. package/bundled/upstream/anthropic-skills/skills/claude-api/java/managed-agents/README.md +2 -2
  15. package/bundled/upstream/anthropic-skills/skills/claude-api/php/claude-api.md +10 -10
  16. package/bundled/upstream/anthropic-skills/skills/claude-api/php/managed-agents/README.md +2 -2
  17. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/README.md +16 -16
  18. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/batches.md +3 -3
  19. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/files-api.md +3 -3
  20. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/streaming.md +7 -7
  21. package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/tool-use.md +19 -19
  22. package/bundled/upstream/anthropic-skills/skills/claude-api/python/managed-agents/README.md +3 -3
  23. package/bundled/upstream/anthropic-skills/skills/claude-api/ruby/claude-api.md +4 -4
  24. package/bundled/upstream/anthropic-skills/skills/claude-api/ruby/managed-agents/README.md +2 -2
  25. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/error-codes.md +5 -5
  26. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/live-sources.md +3 -1
  27. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-api-reference.md +10 -4
  28. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-core.md +19 -1
  29. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-environments.md +6 -2
  30. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-multiagent.md +1 -1
  31. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-onboarding.md +3 -3
  32. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-overview.md +3 -2
  33. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +173 -0
  34. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-tools.md +10 -4
  35. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/model-migration.md +113 -13
  36. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/models.md +14 -11
  37. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/prompt-caching.md +2 -2
  38. package/bundled/upstream/anthropic-skills/skills/claude-api/shared/tool-use-concepts.md +4 -4
  39. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/README.md +15 -15
  40. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/batches.md +2 -2
  41. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/files-api.md +1 -1
  42. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/streaming.md +5 -5
  43. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/tool-use.md +15 -15
  44. package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/managed-agents/README.md +3 -3
  45. package/bundled/upstream/ecc/.omc-source/bundle.json +2 -1
  46. package/bundled/upstream/ecc/.omc-source/last-plan-apply.json +108 -24
  47. package/bundled/upstream/ecc/.omc-source/manifests/.claude-plugin/marketplace.json +3 -3
  48. package/bundled/upstream/ecc/.omc-source/provenance.json +563 -0
  49. package/bundled/upstream/ecc/agents/marketing-agent.md +159 -0
  50. package/bundled/upstream/ecc/agents/react-build-resolver.md +215 -0
  51. package/bundled/upstream/ecc/agents/react-reviewer.md +167 -0
  52. package/bundled/upstream/ecc/agents/typescript-reviewer.md +3 -0
  53. package/bundled/upstream/ecc/commands/harness-audit.md +17 -10
  54. package/bundled/upstream/ecc/commands/marketing-campaign.md +129 -0
  55. package/bundled/upstream/ecc/commands/react-build.md +187 -0
  56. package/bundled/upstream/ecc/commands/react-review.md +170 -0
  57. package/bundled/upstream/ecc/commands/react-test.md +265 -0
  58. package/bundled/upstream/ecc/skills/benchmark-optimization-loop/SKILL.md +69 -0
  59. package/bundled/upstream/ecc/skills/blender-motion-state-inspection/SKILL.md +164 -0
  60. package/bundled/upstream/ecc/skills/canary-watch/SKILL.md +9 -1
  61. package/bundled/upstream/ecc/skills/continuous-learning-v2/hooks/observe.sh +31 -9
  62. package/bundled/upstream/ecc/skills/continuous-learning-v2/scripts/detect-project.sh +38 -4
  63. package/bundled/upstream/ecc/skills/continuous-learning-v2/scripts/instinct-cli.py +319 -12
  64. package/bundled/upstream/ecc/skills/data-throughput-accelerator/SKILL.md +72 -0
  65. package/bundled/upstream/ecc/skills/dynamic-workflow-mode/SKILL.md +123 -0
  66. package/bundled/upstream/ecc/skills/frontend-a11y/SKILL.md +446 -0
  67. package/bundled/upstream/ecc/skills/ito-basket-compare/SKILL.md +63 -0
  68. package/bundled/upstream/ecc/skills/ito-data-atlas-agent/SKILL.md +63 -0
  69. package/bundled/upstream/ecc/skills/ito-market-intelligence/SKILL.md +60 -0
  70. package/bundled/upstream/ecc/skills/ito-trade-planner/SKILL.md +67 -0
  71. package/bundled/upstream/ecc/skills/latency-critical-systems/SKILL.md +73 -0
  72. package/bundled/upstream/ecc/skills/marketing-campaign/SKILL.md +113 -0
  73. package/bundled/upstream/ecc/skills/nextjs-turbopack/SKILL.md +13 -0
  74. package/bundled/upstream/ecc/skills/parallel-execution-optimizer/SKILL.md +72 -0
  75. package/bundled/upstream/ecc/skills/prediction-market-oracle-research/SKILL.md +63 -0
  76. package/bundled/upstream/ecc/skills/prediction-market-risk-review/SKILL.md +60 -0
  77. package/bundled/upstream/ecc/skills/react-patterns/SKILL.md +341 -0
  78. package/bundled/upstream/ecc/skills/react-performance/SKILL.md +574 -0
  79. package/bundled/upstream/ecc/skills/react-testing/SKILL.md +423 -0
  80. package/bundled/upstream/ecc/skills/recsys-pipeline-architect/SKILL.md +114 -0
  81. package/bundled/upstream/ecc/skills/recursive-decision-ledger/SKILL.md +79 -0
  82. package/bundled/upstream/ecc/skills/social-publisher/SKILL.md +115 -0
  83. package/bundled/upstream/ecc/skills/team-agent-orchestration/SKILL.md +110 -0
  84. package/bundled/upstream/ecc/skills/uncloud/SKILL.md +343 -0
  85. package/bundled/upstream/ecc/skills/windows-desktop-e2e/SKILL.md +99 -0
  86. package/bundled/upstream/oh-my-claudecode/.omc-source/bundle.json +2 -1
  87. package/bundled/upstream/oh-my-claudecode/.omc-source/provenance.json +116 -0
  88. package/bundled/upstream/oh-my-claudecode/skills/autopilot/SKILL.md +7 -0
  89. package/bundled/upstream/oh-my-claudecode/skills/cancel/SKILL.md +1 -0
  90. package/bundled/upstream/oh-my-claudecode/skills/deep-interview/SKILL.md +39 -5
  91. package/bundled/upstream/oh-my-claudecode/skills/hud/SKILL.md +1 -0
  92. package/bundled/upstream/oh-my-claudecode/skills/local-build-reminder/SKILL.md +78 -0
  93. package/bundled/upstream/oh-my-claudecode/skills/omc-doctor/SKILL.md +1 -1
  94. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/SKILL.md +26 -10
  95. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/01-install-claude-md.md +3 -3
  96. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/02-configure.md +6 -4
  97. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/03-integrations.md +1 -1
  98. package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/04-welcome.md +2 -2
  99. package/bundled/upstream/oh-my-claudecode/skills/omc-teams/SKILL.md +6 -6
  100. package/bundled/upstream/oh-my-claudecode/skills/plan/SKILL.md +44 -32
  101. package/bundled/upstream/oh-my-claudecode/skills/ralph/SKILL.md +45 -21
  102. package/bundled/upstream/oh-my-claudecode/skills/ralplan/SKILL.md +1 -1
  103. package/bundled/upstream/oh-my-claudecode/skills/self-improve/SKILL.md +7 -0
  104. package/bundled/upstream/oh-my-claudecode/skills/self-improve/scripts/resolve-paths.mjs +39 -15
  105. package/bundled/upstream/oh-my-claudecode/skills/team/SKILL.md +132 -90
  106. package/bundled/upstream/oh-my-claudecode/skills/ultragoal/SKILL.md +93 -0
  107. package/bundled/upstream/oh-my-claudecode/skills/ultraqa/SKILL.md +28 -13
  108. package/bundled/upstream/oh-my-claudecode/skills/ultrawork/SKILL.md +7 -0
  109. package/bundled/upstream/superpowers/.omc-source/bundle.json +2 -1
  110. package/bundled/upstream/superpowers/.omc-source/provenance.json +63 -0
  111. package/package.json +2 -1
  112. package/src/catalog/source-catalog.js +10 -4
  113. package/src/cli/index.js +4 -0
  114. package/src/cli/plan.js +14 -2
  115. package/src/cli/setup.js +52 -13
  116. package/src/cli/skill.js +1 -1
  117. package/src/cli/source.js +265 -14
  118. package/src/config/sources.js +67 -1
  119. package/src/merge/content-patch.js +84 -0
  120. package/templates/merge-config.json +1 -8
  121. 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,39 @@ 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
+ // Fetch the source into tmpDir. With pinnedCommit, fetch that exact SHA for a
129
+ // reproducible (frozen) checkout; otherwise shallow-clone the ref tip.
130
+ function fetchSource(tmpDir, remote, ref, pinnedCommit) {
131
+ const opts = { encoding: 'utf8', timeout: 300000, stdio: ['ignore', 'pipe', 'pipe'] };
132
+ if (pinnedCommit) {
133
+ // Defense-in-depth: a pinned commit comes from the lockfile JSON. Require a
134
+ // bare hex SHA so a crafted value can't be read as a git option, and pass it
135
+ // after `--` so it's unambiguously a ref, never a flag.
136
+ if (!/^[0-9a-f]{7,40}$/i.test(pinnedCommit)) {
137
+ return { status: 1, stderr: `Invalid pinned commit (not a hex SHA): ${JSON.stringify(pinnedCommit)}` };
138
+ }
139
+ fs.mkdirSync(tmpDir, { recursive: true });
140
+ const steps = [
141
+ ['init', '-q', tmpDir],
142
+ ['-C', tmpDir, 'remote', 'add', 'origin', remote],
143
+ ['-C', tmpDir, 'fetch', '--depth', '1', 'origin', '--', pinnedCommit],
144
+ ['-C', tmpDir, 'checkout', '-q', 'FETCH_HEAD'],
145
+ ];
146
+ let last;
147
+ for (const args of steps) {
148
+ last = spawnSync('git', args, opts);
149
+ if (last.status !== 0) return last;
150
+ }
151
+ return last;
152
+ }
153
+ return spawnSync('git', [
154
+ 'clone', '--depth', '1', '--branch', ref, '--single-branch', remote, tmpDir,
155
+ ], opts);
156
+ }
157
+
158
+ async function syncRemoteSource(sourceName, sourceConfig, root, pinnedCommit = null) {
159
+ const refLabel = pinnedCommit ? `${sourceConfig.ref} @ ${pinnedCommit.slice(0, 12)} (frozen)` : sourceConfig.ref;
160
+ console.log(` Syncing ${sourceName} from ${redactRemote(sourceConfig.remote)} (${refLabel})...`);
51
161
 
52
162
  const tmpDir = getSyncTempDir(sourceName, root);
53
163
  try {
@@ -55,19 +165,21 @@ async function syncRemoteSource(sourceName, sourceConfig, root) {
55
165
  await fsp.rm(tmpDir, { recursive: true, force: true });
56
166
  }
57
167
 
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'] });
168
+ const cloneResult = fetchSource(tmpDir, sourceConfig.remote, sourceConfig.ref, pinnedCommit);
62
169
 
63
170
  if (cloneResult.status !== 0) {
64
171
  console.error(` Clone failed: ${cloneResult.stderr || cloneResult.error}`);
65
172
  return false;
66
173
  }
67
174
 
175
+ // Resolve the exact upstream commit this sync pulled (provenance + lock).
176
+ const headResult = spawnSync('git', ['-C', tmpDir, 'rev-parse', 'HEAD'], { encoding: 'utf8' });
177
+ const commit = headResult.status === 0 ? headResult.stdout.trim() : null;
178
+
68
179
  // Copy each declared artifact type
69
180
  const artifacts = sourceConfig.artifacts || ['skills'];
70
181
  const mapping = sourceConfig.mapping || {};
182
+ const provenance = {};
71
183
 
72
184
  for (const artifactType of artifacts) {
73
185
  const srcSubdir = mapping[artifactType] || artifactType;
@@ -85,6 +197,7 @@ async function syncRemoteSource(sourceName, sourceConfig, root) {
85
197
  const count = fs.statSync(destPath).isDirectory()
86
198
  ? fs.readdirSync(destPath).length : 1;
87
199
  console.log(` ${artifactType}: ${count} items`);
200
+ provenance[artifactType] = hashTree(destPath);
88
201
  }
89
202
  }
90
203
 
@@ -100,11 +213,13 @@ async function syncRemoteSource(sourceName, sourceConfig, root) {
100
213
  }
101
214
 
102
215
  await fsp.mkdir(metadataDir, { recursive: true });
216
+ const syncedAt = new Date().toISOString();
103
217
  await fsp.writeFile(path.join(metadataDir, 'bundle.json'), JSON.stringify({
104
- syncedAt: new Date().toISOString(),
218
+ syncedAt,
105
219
  sourceName,
106
- remote: sourceConfig.remote,
220
+ remote: redactRemote(sourceConfig.remote),
107
221
  ref: sourceConfig.ref,
222
+ commit,
108
223
  kind: sourceConfig.kind || 'content-repo',
109
224
  harnesses: sourceConfig.harnesses || ['claude'],
110
225
  artifacts: sourceConfig.artifacts || [],
@@ -112,6 +227,19 @@ async function syncRemoteSource(sourceName, sourceConfig, root) {
112
227
  profiles: sourceConfig.profiles || [],
113
228
  }, null, 2) + '\n', 'utf8');
114
229
 
230
+ // Provenance: per-file content hashes + the resolved commit, so `source drift`
231
+ // can later distinguish local edits from upstream changes.
232
+ await fsp.writeFile(path.join(metadataDir, 'provenance.json'), JSON.stringify({
233
+ syncedAt,
234
+ sourceName,
235
+ remote: redactRemote(sourceConfig.remote),
236
+ ref: sourceConfig.ref,
237
+ commit,
238
+ artifacts: provenance,
239
+ }, null, 2) + '\n', 'utf8');
240
+
241
+ if (commit) console.log(` commit: ${commit.slice(0, 12)}`);
242
+
115
243
  return true;
116
244
  } finally {
117
245
  if (fs.existsSync(tmpDir)) {
@@ -219,17 +347,21 @@ async function source(args, flags = {}) {
219
347
 
220
348
  const root = getProjectRoot();
221
349
  const targetName = args[1]; // optional: specific source name
350
+ const lock = flags.frozen ? readLock(root) : { sources: {} };
351
+ if (flags.frozen) console.log('(frozen: syncing to locked commits)\n');
222
352
  let success = true;
223
353
 
224
354
  for (const [name, src] of Object.entries(config.sources)) {
225
355
  if (name === 'local') continue; // local is in-repo, no sync needed
226
356
  if (targetName && name !== targetName) continue;
227
- if (!flags.all && !targetName && !flags[name]) {
228
- // Default: sync all remote sources
229
- }
230
-
231
357
  if (!src.remote) continue;
232
- const ok = await syncRemoteSource(name, src, root);
358
+
359
+ let pinnedCommit = null;
360
+ if (flags.frozen) {
361
+ pinnedCommit = (lock.sources[name] || {}).commit || null;
362
+ if (!pinnedCommit) console.log(` ${name}: no lock entry, syncing ref tip`);
363
+ }
364
+ const ok = await syncRemoteSource(name, src, root, pinnedCommit);
233
365
  if (!ok) success = false;
234
366
  console.log('');
235
367
  }
@@ -251,6 +383,13 @@ async function source(args, flags = {}) {
251
383
  console.log(`[${name}] (priority ${src.priority})`);
252
384
  console.log(` kind: ${src.kind || 'content-repo'}`);
253
385
  console.log(` installMode: ${src.installMode || 'auto'}`);
386
+ const bundlePath = path.join(getSourceMetadataDir(name, root), 'bundle.json');
387
+ if (fs.existsSync(bundlePath)) {
388
+ try {
389
+ const bundle = JSON.parse(fs.readFileSync(bundlePath, 'utf8'));
390
+ if (bundle.commit) console.log(` commit: ${bundle.commit.slice(0, 12)}`);
391
+ } catch { /* ignore unreadable bundle metadata */ }
392
+ }
254
393
  if (src.appliedProfile) {
255
394
  console.log(` appliedProfile: ${src.appliedProfile}`);
256
395
  }
@@ -286,6 +425,118 @@ async function source(args, flags = {}) {
286
425
  break;
287
426
  }
288
427
 
428
+ case 'lock': {
429
+ const root = getProjectRoot();
430
+ const targetName = args[1];
431
+ const lock = readLock(root);
432
+ let locked = 0;
433
+ const missing = [];
434
+
435
+ for (const [name, src] of Object.entries(config.sources)) {
436
+ if (targetName && name !== targetName) continue;
437
+ if (!src.remote) continue;
438
+ const bundlePath = path.join(getSourceMetadataDir(name, root), 'bundle.json');
439
+ let commit = null;
440
+ if (fs.existsSync(bundlePath)) {
441
+ try { commit = JSON.parse(fs.readFileSync(bundlePath, 'utf8')).commit || null; } catch { /* ignore */ }
442
+ }
443
+ if (!commit) { missing.push(name); continue; }
444
+ // Preserve lockedAt when the commit hasn't changed, to avoid timestamp
445
+ // churn in the version-controlled lockfile.
446
+ const prev = lock.sources[name];
447
+ const lockedAt = (prev && prev.commit === commit && prev.lockedAt) ? prev.lockedAt : new Date().toISOString();
448
+ lock.sources[name] = { commit, ref: src.ref || 'main', lockedAt };
449
+ locked += 1;
450
+ }
451
+
452
+ if (targetName && !config.sources[targetName]) {
453
+ const validNames = Object.keys(config.sources).join(', ');
454
+ throw new Error(`Invalid source. Use: ${validNames}`);
455
+ }
456
+
457
+ await writeLock(root, { sources: lock.sources });
458
+ console.log(`Locked ${locked} source(s) → ${path.relative(root, getLockPath(root))}`);
459
+ for (const [name, entry] of Object.entries(lock.sources)) {
460
+ if (!targetName || name === targetName) console.log(` ${name}: ${entry.commit.slice(0, 12)} (${entry.ref})`);
461
+ }
462
+ if (missing.length > 0) {
463
+ console.log(` unlocked (no synced commit): ${missing.join(', ')} — run "omc-manage source sync" first`);
464
+ }
465
+ break;
466
+ }
467
+
468
+ case 'drift': {
469
+ const root = getProjectRoot();
470
+ const targetName = args[1];
471
+ const report = {};
472
+ let anyDrift = false;
473
+ let anyChecked = false;
474
+
475
+ for (const [name, src] of Object.entries(config.sources)) {
476
+ if (targetName && name !== targetName) continue;
477
+ if (name === 'local') continue; // in-repo, nothing to compare against
478
+ const provPath = path.join(getSourceMetadataDir(name, root), 'provenance.json');
479
+ if (!fs.existsSync(provPath)) {
480
+ report[name] = { status: 'no-provenance' };
481
+ continue;
482
+ }
483
+ let prov;
484
+ try {
485
+ prov = JSON.parse(fs.readFileSync(provPath, 'utf8'));
486
+ } catch {
487
+ report[name] = { status: 'corrupt-provenance' };
488
+ anyDrift = true; // integrity problem — fail CI like drift
489
+ continue;
490
+ }
491
+ anyChecked = true;
492
+ const perType = {};
493
+ let sourceDrift = false;
494
+ for (const artifactType of Object.keys(prov.artifacts || {})) {
495
+ const dir = getSourceArtifactDir(name, artifactType, root);
496
+ const delta = diffHashTrees(prov.artifacts[artifactType], hashTree(dir));
497
+ if (delta.added.length || delta.removed.length || delta.changed.length) {
498
+ perType[artifactType] = delta;
499
+ sourceDrift = true;
500
+ }
501
+ }
502
+ report[name] = { status: sourceDrift ? 'drift' : 'clean', commit: prov.commit, syncedAt: prov.syncedAt, drift: perType };
503
+ if (sourceDrift) anyDrift = true;
504
+ }
505
+
506
+ if (targetName && !(targetName in report)) {
507
+ const validNames = Object.keys(config.sources).join(', ');
508
+ throw new Error(`Invalid source. Use: ${validNames}`);
509
+ }
510
+
511
+ if (flags.json) {
512
+ console.log(JSON.stringify(report, null, 2));
513
+ } else {
514
+ console.log('Drift Report');
515
+ console.log('============');
516
+ for (const [name, r] of Object.entries(report)) {
517
+ if (r.status === 'no-provenance') {
518
+ console.log(`○ ${name}: no provenance (run "omc-manage source sync ${name}")`);
519
+ continue;
520
+ }
521
+ if (r.status === 'corrupt-provenance') {
522
+ console.log(`✗ ${name}: corrupt provenance (re-sync to repair)`);
523
+ continue;
524
+ }
525
+ const marker = r.status === 'drift' ? '✗' : '✓';
526
+ console.log(`${marker} ${name}: ${r.status}${r.commit ? ` @ ${r.commit.slice(0, 12)}` : ''}`);
527
+ for (const [type, delta] of Object.entries(r.drift || {})) {
528
+ for (const f of delta.changed) console.log(` ~ ${type}/${f}`);
529
+ for (const f of delta.added) console.log(` + ${type}/${f}`);
530
+ for (const f of delta.removed) console.log(` - ${type}/${f}`);
531
+ }
532
+ }
533
+ if (!anyChecked) console.log('(no synced sources with provenance)');
534
+ }
535
+
536
+ if (anyDrift) process.exitCode = 1;
537
+ break;
538
+ }
539
+
289
540
  case 'inspect': {
290
541
  const name = args[1];
291
542
  if (!name || !config.sources[name]) {
@@ -332,7 +583,7 @@ async function source(args, flags = {}) {
332
583
  }
333
584
 
334
585
  default:
335
- throw new Error(`Unknown subcommand: ${cmd}. Use: list, add, remove, set, sync, status, or inspect`);
586
+ throw new Error(`Unknown subcommand: ${cmd}. Use: list, add, remove, set, sync, lock, status, drift, or inspect`);
336
587
  }
337
588
  }
338
589
 
@@ -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,21 @@ 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
+ },
58
113
  'anthropic-skills': {
59
114
  remote: 'https://github.com/anthropics/skills.git',
60
115
  ref: 'main',
@@ -114,7 +169,17 @@ function normalizeSourceConfig(name, source) {
114
169
  ? DEFAULT_DISTRIBUTION_MANIFESTS
115
170
  : []));
116
171
  source.profiles = dedupeStrings(source.profiles || DEFAULT_INSTALL_PROFILES);
117
- source.allowlist = normalizeAllowlist(source.allowlist);
172
+
173
+ // Unified governance.json is the authoritative cross-source policy.
174
+ const govSource = (loadGovernance().sources || {})[name] || {};
175
+ // Priority: governance wins when it declares one (single source of truth).
176
+ if (typeof govSource.priority === 'number') source.priority = govSource.priority;
177
+ // Allowlist authority: explicit config (e.g. `plan apply`) > governance.json
178
+ // inline allowlist > per-source .omc-curation/<name>-selection.json > none
179
+ // (no allowlist → install everything).
180
+ source.allowlist = normalizeAllowlist(source.allowlist)
181
+ || normalizeAllowlist(govSource.allowlist)
182
+ || normalizeAllowlist(loadCurationAllowlist(name));
118
183
 
119
184
  if (source.role === 'reference' && !source.profiles.includes('reference-only')) {
120
185
  source.profiles.push('reference-only');
@@ -249,6 +314,7 @@ module.exports = {
249
314
  updateSource,
250
315
  getSourceAllowlist,
251
316
  filterItemsByAllowlist,
317
+ loadGovernance,
252
318
  normalizeConfig,
253
319
  normalizeSourceConfig,
254
320
  DEFAULT_DISTRIBUTION_MANIFESTS,
@@ -0,0 +1,84 @@
1
+ 'use strict';
2
+
3
+ // Content-level patching for installed artifacts. A patch is declared inline in
4
+ // governance.json under sources.<name>.patches["<type>/<artifact>"] and is
5
+ // applied at install time, after the winning source is chosen and before the
6
+ // file is written to ~/.claude. Markdown + YAML-frontmatter aware.
7
+ //
8
+ // Patch ops (all optional, applied in this order):
9
+ // frontmatter: { key: value } merge/override scalar frontmatter keys
10
+ // replace: [{ find, with }] literal string replacements in the body
11
+ // prepend: "text" prepended to the body
12
+ // append: "text" appended to the body
13
+
14
+ function escapeRegExp(s) {
15
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
16
+ }
17
+
18
+ function splitFrontmatter(content) {
19
+ // CRLF-tolerant: match \r?\n so Windows-authored artifacts are parsed as
20
+ // frontmatter rather than slipping through as body (which would then get a
21
+ // second, duplicate frontmatter block prepended by a frontmatter patch).
22
+ const m = content.match(/^---\r?\n([\s\S]*?)\r?\n---[ \t]*\r?\n?/);
23
+ if (!m) return { frontmatter: null, body: content };
24
+ // Normalize CRs in the frontmatter block (we re-emit it as \n-joined lines);
25
+ // the body is left byte-for-byte so user content keeps its own line endings.
26
+ return { frontmatter: m[1].replace(/\r/g, ''), body: content.slice(m[0].length) };
27
+ }
28
+
29
+ // Serialize a scalar for a simple `key: value` frontmatter line, quoting only
30
+ // when the value would otherwise break a bare YAML scalar.
31
+ function serializeScalar(value) {
32
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
33
+ const s = String(value);
34
+ if (s === '' || s.trim() !== s || /[:#"'\n[\]{}&*!|>%@`]/.test(s)) {
35
+ return JSON.stringify(s); // valid double-quoted YAML, escapes as needed
36
+ }
37
+ return s;
38
+ }
39
+
40
+ // Line-based override of scalar frontmatter keys: replace the key's line if
41
+ // present, else append it. Deliberately scalar-only — nested/list overrides
42
+ // are out of scope and should use `replace`.
43
+ function mergeFrontmatter(block, overrides) {
44
+ let next = block || '';
45
+ for (const [key, value] of Object.entries(overrides)) {
46
+ const line = `${key}: ${serializeScalar(value)}`;
47
+ const re = new RegExp(`^${escapeRegExp(key)}\\s*:.*$`, 'm');
48
+ if (re.test(next)) next = next.replace(re, line);
49
+ else next = next ? `${next}\n${line}` : line;
50
+ }
51
+ return next;
52
+ }
53
+
54
+ // Apply a patch spec to file content. Returns { content, warnings }.
55
+ // Unknown/empty patch → content unchanged.
56
+ function applyContentPatch(content, patch) {
57
+ const warnings = [];
58
+ if (!patch || typeof patch !== 'object') return { content, warnings };
59
+
60
+ let { frontmatter, body } = splitFrontmatter(content);
61
+
62
+ if (patch.frontmatter && typeof patch.frontmatter === 'object') {
63
+ frontmatter = mergeFrontmatter(frontmatter, patch.frontmatter);
64
+ }
65
+
66
+ if (Array.isArray(patch.replace)) {
67
+ for (const rule of patch.replace) {
68
+ if (!rule || typeof rule.find !== 'string' || rule.find === '') continue;
69
+ if (!body.includes(rule.find)) {
70
+ warnings.push(`replace target not found: ${JSON.stringify(rule.find.slice(0, 40))}`);
71
+ continue;
72
+ }
73
+ body = body.split(rule.find).join(rule.with == null ? '' : String(rule.with));
74
+ }
75
+ }
76
+
77
+ if (typeof patch.prepend === 'string') body = patch.prepend + body;
78
+ if (typeof patch.append === 'string') body = body + patch.append;
79
+
80
+ const out = frontmatter != null ? `---\n${frontmatter}\n---\n${body}` : body;
81
+ return { content: out, warnings };
82
+ }
83
+
84
+ 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