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.
- package/.local/settings/settings.json +8 -0
- package/.local/skills/prompt-optimizer/SKILL.md +262 -19
- package/.omc-curation/ecc-selection.json +80 -0
- package/.omc-curation/governance.json +116 -0
- package/.omc-curation/sources.lock.json +30 -0
- package/README.md +78 -4
- package/bundled/manifest.json +6 -5
- package/bundled/upstream/anthropic-skills/.omc-source/bundle.json +18 -0
- package/bundled/upstream/anthropic-skills/.omc-source/provenance.json +399 -0
- package/bundled/upstream/anthropic-skills/skills/claude-api/SKILL.md +18 -17
- package/bundled/upstream/anthropic-skills/skills/claude-api/curl/examples.md +9 -9
- package/bundled/upstream/anthropic-skills/skills/claude-api/curl/managed-agents.md +4 -4
- package/bundled/upstream/anthropic-skills/skills/claude-api/go/managed-agents/README.md +2 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/java/claude-api.md +2 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/java/managed-agents/README.md +2 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/php/claude-api.md +10 -10
- package/bundled/upstream/anthropic-skills/skills/claude-api/php/managed-agents/README.md +2 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/README.md +16 -16
- package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/batches.md +3 -3
- package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/files-api.md +3 -3
- package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/streaming.md +7 -7
- package/bundled/upstream/anthropic-skills/skills/claude-api/python/claude-api/tool-use.md +19 -19
- package/bundled/upstream/anthropic-skills/skills/claude-api/python/managed-agents/README.md +3 -3
- package/bundled/upstream/anthropic-skills/skills/claude-api/ruby/claude-api.md +4 -4
- package/bundled/upstream/anthropic-skills/skills/claude-api/ruby/managed-agents/README.md +2 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/error-codes.md +5 -5
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/live-sources.md +3 -1
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-api-reference.md +10 -4
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-core.md +19 -1
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-environments.md +6 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-multiagent.md +1 -1
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-onboarding.md +3 -3
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-overview.md +3 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +173 -0
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/managed-agents-tools.md +10 -4
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/model-migration.md +113 -13
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/models.md +14 -11
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/prompt-caching.md +2 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/shared/tool-use-concepts.md +4 -4
- package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/README.md +15 -15
- package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/batches.md +2 -2
- package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/files-api.md +1 -1
- package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/streaming.md +5 -5
- package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/claude-api/tool-use.md +15 -15
- package/bundled/upstream/anthropic-skills/skills/claude-api/typescript/managed-agents/README.md +3 -3
- package/bundled/upstream/ecc/.omc-source/bundle.json +2 -1
- package/bundled/upstream/ecc/.omc-source/last-plan-apply.json +108 -24
- package/bundled/upstream/ecc/.omc-source/manifests/.claude-plugin/marketplace.json +3 -3
- package/bundled/upstream/ecc/.omc-source/provenance.json +563 -0
- package/bundled/upstream/ecc/agents/marketing-agent.md +159 -0
- package/bundled/upstream/ecc/agents/react-build-resolver.md +215 -0
- package/bundled/upstream/ecc/agents/react-reviewer.md +167 -0
- package/bundled/upstream/ecc/agents/typescript-reviewer.md +3 -0
- package/bundled/upstream/ecc/commands/harness-audit.md +17 -10
- package/bundled/upstream/ecc/commands/marketing-campaign.md +129 -0
- package/bundled/upstream/ecc/commands/react-build.md +187 -0
- package/bundled/upstream/ecc/commands/react-review.md +170 -0
- package/bundled/upstream/ecc/commands/react-test.md +265 -0
- package/bundled/upstream/ecc/skills/benchmark-optimization-loop/SKILL.md +69 -0
- package/bundled/upstream/ecc/skills/blender-motion-state-inspection/SKILL.md +164 -0
- package/bundled/upstream/ecc/skills/canary-watch/SKILL.md +9 -1
- package/bundled/upstream/ecc/skills/continuous-learning-v2/hooks/observe.sh +31 -9
- package/bundled/upstream/ecc/skills/continuous-learning-v2/scripts/detect-project.sh +38 -4
- package/bundled/upstream/ecc/skills/continuous-learning-v2/scripts/instinct-cli.py +319 -12
- package/bundled/upstream/ecc/skills/data-throughput-accelerator/SKILL.md +72 -0
- package/bundled/upstream/ecc/skills/dynamic-workflow-mode/SKILL.md +123 -0
- package/bundled/upstream/ecc/skills/frontend-a11y/SKILL.md +446 -0
- package/bundled/upstream/ecc/skills/ito-basket-compare/SKILL.md +63 -0
- package/bundled/upstream/ecc/skills/ito-data-atlas-agent/SKILL.md +63 -0
- package/bundled/upstream/ecc/skills/ito-market-intelligence/SKILL.md +60 -0
- package/bundled/upstream/ecc/skills/ito-trade-planner/SKILL.md +67 -0
- package/bundled/upstream/ecc/skills/latency-critical-systems/SKILL.md +73 -0
- package/bundled/upstream/ecc/skills/marketing-campaign/SKILL.md +113 -0
- package/bundled/upstream/ecc/skills/nextjs-turbopack/SKILL.md +13 -0
- package/bundled/upstream/ecc/skills/parallel-execution-optimizer/SKILL.md +72 -0
- package/bundled/upstream/ecc/skills/prediction-market-oracle-research/SKILL.md +63 -0
- package/bundled/upstream/ecc/skills/prediction-market-risk-review/SKILL.md +60 -0
- package/bundled/upstream/ecc/skills/react-patterns/SKILL.md +341 -0
- package/bundled/upstream/ecc/skills/react-performance/SKILL.md +574 -0
- package/bundled/upstream/ecc/skills/react-testing/SKILL.md +423 -0
- package/bundled/upstream/ecc/skills/recsys-pipeline-architect/SKILL.md +114 -0
- package/bundled/upstream/ecc/skills/recursive-decision-ledger/SKILL.md +79 -0
- package/bundled/upstream/ecc/skills/social-publisher/SKILL.md +115 -0
- package/bundled/upstream/ecc/skills/team-agent-orchestration/SKILL.md +110 -0
- package/bundled/upstream/ecc/skills/uncloud/SKILL.md +343 -0
- package/bundled/upstream/ecc/skills/windows-desktop-e2e/SKILL.md +99 -0
- package/bundled/upstream/impeccable/.omc-source/bundle.json +20 -0
- package/bundled/upstream/impeccable/.omc-source/provenance.json +105 -0
- package/bundled/upstream/impeccable/agents/impeccable-manual-edit-applier.md +97 -0
- package/bundled/upstream/impeccable/skills/impeccable/SKILL.md +168 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/adapt.md +311 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/animate.md +201 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/audit.md +133 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/bolder.md +113 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/brand.md +108 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/clarify.md +288 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/codex.md +105 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/colorize.md +257 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/craft.md +123 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/critique.md +767 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/delight.md +302 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/distill.md +111 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/document.md +429 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/extract.md +69 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/harden.md +347 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/hooks.md +88 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/init.md +172 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/interaction-design.md +189 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/layout.md +161 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/live.md +718 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/onboard.md +234 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/optimize.md +258 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/overdrive.md +130 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/polish.md +241 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/product.md +60 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/quieter.md +99 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/shape.md +165 -0
- package/bundled/upstream/impeccable/skills/impeccable/reference/typeset.md +279 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/command-metadata.json +94 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/context-signals.mjs +225 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/context.mjs +280 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/critique-storage.mjs +242 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detect-csp.mjs +198 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detect.mjs +21 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/browser/injected/index.mjs +1735 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/cli/main.mjs +244 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +4907 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/detect-antipatterns.mjs +43 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +252 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +552 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +1013 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +208 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/findings.mjs +12 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/node/file-system.mjs +198 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/registry/antipatterns.mjs +419 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/rules/checks.mjs +2671 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/shared/constants.mjs +101 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/hook-admin.mjs +574 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/hook-before-edit.mjs +473 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/hook-lib.mjs +1286 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/hook.mjs +61 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/lib/design-parser.mjs +835 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/lib/impeccable-paths.mjs +126 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/lib/is-generated.mjs +69 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/completion.mjs +19 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/event-validation.mjs +137 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/insert-ui.mjs +458 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/manual-edits-buffer.mjs +152 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/session-store.mjs +289 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/svelte-component.mjs +826 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/ui-core.mjs +180 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-accept.mjs +812 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-browser-dom.js +146 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-browser-session.js +123 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-browser.js +11086 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-complete.mjs +75 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-inject.mjs +583 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-insert.mjs +272 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-poll.mjs +379 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-resume.mjs +94 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-server.mjs +1134 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-status.mjs +61 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live-wrap.mjs +894 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/live.mjs +246 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/palette.mjs +633 -0
- package/bundled/upstream/impeccable/skills/impeccable/scripts/pin.mjs +214 -0
- package/bundled/upstream/oh-my-claudecode/.omc-source/bundle.json +2 -1
- package/bundled/upstream/oh-my-claudecode/.omc-source/provenance.json +116 -0
- package/bundled/upstream/oh-my-claudecode/skills/autopilot/SKILL.md +7 -0
- package/bundled/upstream/oh-my-claudecode/skills/cancel/SKILL.md +1 -0
- package/bundled/upstream/oh-my-claudecode/skills/deep-interview/SKILL.md +39 -5
- package/bundled/upstream/oh-my-claudecode/skills/hud/SKILL.md +1 -0
- package/bundled/upstream/oh-my-claudecode/skills/local-build-reminder/SKILL.md +78 -0
- package/bundled/upstream/oh-my-claudecode/skills/omc-doctor/SKILL.md +1 -1
- package/bundled/upstream/oh-my-claudecode/skills/omc-setup/SKILL.md +26 -10
- package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/01-install-claude-md.md +3 -3
- package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/02-configure.md +6 -4
- package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/03-integrations.md +1 -1
- package/bundled/upstream/oh-my-claudecode/skills/omc-setup/phases/04-welcome.md +2 -2
- package/bundled/upstream/oh-my-claudecode/skills/omc-teams/SKILL.md +6 -6
- package/bundled/upstream/oh-my-claudecode/skills/plan/SKILL.md +44 -32
- package/bundled/upstream/oh-my-claudecode/skills/ralph/SKILL.md +45 -21
- package/bundled/upstream/oh-my-claudecode/skills/ralplan/SKILL.md +1 -1
- package/bundled/upstream/oh-my-claudecode/skills/self-improve/SKILL.md +7 -0
- package/bundled/upstream/oh-my-claudecode/skills/self-improve/scripts/resolve-paths.mjs +39 -15
- package/bundled/upstream/oh-my-claudecode/skills/team/SKILL.md +132 -90
- package/bundled/upstream/oh-my-claudecode/skills/ultragoal/SKILL.md +93 -0
- package/bundled/upstream/oh-my-claudecode/skills/ultraqa/SKILL.md +28 -13
- package/bundled/upstream/oh-my-claudecode/skills/ultrawork/SKILL.md +7 -0
- package/bundled/upstream/superpowers/.omc-source/bundle.json +2 -1
- package/bundled/upstream/superpowers/.omc-source/provenance.json +63 -0
- package/package.json +2 -1
- package/src/catalog/source-catalog.js +10 -4
- package/src/cli/index.js +4 -0
- package/src/cli/plan.js +14 -2
- package/src/cli/setup.js +52 -13
- package/src/cli/skill.js +1 -1
- package/src/cli/source.js +271 -14
- package/src/config/sources.js +82 -1
- package/src/merge/content-patch.js +88 -0
- package/templates/merge-config.json +1 -8
- 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
|
-
|
|
50
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
package/src/config/sources.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
"
|
|
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
|