omgkit 2.24.3 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -747,6 +747,8 @@ Generated in `.omgkit/stdrules/` when you run `omgkit init`:
747
747
 
748
748
  ## CLI Commands
749
749
 
750
+ ### Global Commands
751
+
750
752
  ```bash
751
753
  omgkit install # Install plugin to Claude Code
752
754
  omgkit init # Initialize .omgkit/ in project
@@ -757,6 +759,41 @@ omgkit uninstall # Remove plugin
757
759
  omgkit help # Show help
758
760
  ```
759
761
 
762
+ ### Project Upgrade Commands (New)
763
+
764
+ Keep your project up-to-date with the latest OMGKIT features:
765
+
766
+ ```bash
767
+ omgkit project:upgrade # Upgrade project to latest OMGKIT version
768
+ omgkit project:upgrade --dry # Preview changes without applying
769
+ omgkit project:rollback # Rollback to previous backup
770
+ omgkit project:backups # List available backups
771
+ omgkit project:version # Show project's OMGKIT version
772
+ ```
773
+
774
+ #### Safe Upgrade System
775
+
776
+ OMGKIT's upgrade system is designed with safety first:
777
+
778
+ | Feature | Description |
779
+ |---------|-------------|
780
+ | **Version Tracking** | Each project tracks its OMGKIT version in settings.json |
781
+ | **Smart Merge** | workflow.yaml uses add-only merge (never overwrites your values) |
782
+ | **Protected Files** | config.yaml, sprints/*, artifacts/*, devlogs/* are NEVER modified |
783
+ | **Auto-Backup** | Creates timestamped backup before any changes |
784
+ | **Dry Run** | Preview all changes with `--dry` flag before applying |
785
+ | **Rollback** | One command to restore previous state if needed |
786
+
787
+ #### What Gets Upgraded
788
+
789
+ | File Type | Upgrade Behavior |
790
+ |-----------|-----------------|
791
+ | **stdrules/** | New standards are added, modified ones offer 3-way merge |
792
+ | **workflow.yaml** | Smart merge adds new sections, preserves your customizations |
793
+ | **CLAUDE.md** | Updated with new instructions |
794
+ | **settings.json** | Version updated, structure preserved |
795
+ | **Your files** | NEVER touched (config.yaml, sprints, artifacts, devlogs) |
796
+
760
797
  ---
761
798
 
762
799
  ## Documentation Sync Automation
package/bin/omgkit.js CHANGED
@@ -21,6 +21,10 @@ import {
21
21
  doctor,
22
22
  uninstallPlugin,
23
23
  listComponents,
24
+ upgradeProject,
25
+ rollbackProject,
26
+ listProjectBackups,
27
+ getProjectVersion,
24
28
  COLORS,
25
29
  BANNER,
26
30
  log
@@ -37,21 +41,33 @@ function showHelp() {
37
41
  ${COLORS.bright}USAGE${COLORS.reset}
38
42
  omgkit <command> [options]
39
43
 
40
- ${COLORS.bright}COMMANDS${COLORS.reset}
41
- ${COLORS.cyan}install${COLORS.reset} Install OMGKIT plugin to Claude Code
42
- ${COLORS.cyan}init${COLORS.reset} Initialize .omgkit/ in current project
43
- ${COLORS.cyan}update${COLORS.reset} Update OMGKIT plugin
44
- ${COLORS.cyan}uninstall${COLORS.reset} Remove OMGKIT plugin
45
- ${COLORS.cyan}doctor${COLORS.reset} Check installation status
46
- ${COLORS.cyan}list${COLORS.reset} List all commands/agents/skills
47
- ${COLORS.cyan}version${COLORS.reset} Show version
48
- ${COLORS.cyan}help${COLORS.reset} Show this help
44
+ ${COLORS.bright}GLOBAL COMMANDS${COLORS.reset}
45
+ ${COLORS.cyan}install${COLORS.reset} Install OMGKIT plugin to Claude Code
46
+ ${COLORS.cyan}update${COLORS.reset} Update OMGKIT plugin (same as install)
47
+ ${COLORS.cyan}uninstall${COLORS.reset} Remove OMGKIT plugin
48
+ ${COLORS.cyan}doctor${COLORS.reset} Check installation status
49
+ ${COLORS.cyan}list${COLORS.reset} List all commands/agents/skills
50
+ ${COLORS.cyan}version${COLORS.reset} Show version
51
+ ${COLORS.cyan}help${COLORS.reset} Show this help
52
+
53
+ ${COLORS.bright}PROJECT COMMANDS${COLORS.reset}
54
+ ${COLORS.cyan}init${COLORS.reset} Initialize .omgkit/ in current project
55
+ ${COLORS.cyan}project:upgrade${COLORS.reset} Upgrade project to latest OMGKIT version
56
+ ${COLORS.cyan}project:rollback${COLORS.reset} Rollback project to previous backup
57
+ ${COLORS.cyan}project:backups${COLORS.reset} List available project backups
58
+ ${COLORS.cyan}project:version${COLORS.reset} Show project's OMGKIT version
59
+
60
+ ${COLORS.bright}UPGRADE OPTIONS${COLORS.reset}
61
+ --dry Show what would change without applying
62
+ --force Skip confirmation prompts
49
63
 
50
64
  ${COLORS.bright}EXAMPLES${COLORS.reset}
51
- omgkit install # Install plugin globally
52
- omgkit init # Initialize project
53
- omgkit doctor # Check status
54
- omgkit list commands # List all commands
65
+ omgkit install # Install plugin globally
66
+ omgkit init # Initialize project
67
+ omgkit project:upgrade # Upgrade project config
68
+ omgkit project:upgrade --dry # Preview upgrade changes
69
+ omgkit project:rollback # Rollback to last backup
70
+ omgkit doctor # Check status
55
71
 
56
72
  ${COLORS.bright}AFTER INSTALLATION${COLORS.reset}
57
73
  In Claude Code, type / to see all OMGKIT commands:
@@ -59,7 +75,7 @@ ${COLORS.bright}AFTER INSTALLATION${COLORS.reset}
59
75
  Core: /dev:feature, /dev:fix, /planning:plan, /quality:test, /dev:review
60
76
  Omega: /omega:10x, /omega:100x, /omega:1000x, /omega:principles
61
77
  Sprint: /sprint:vision-set, /sprint:sprint-new, /sprint:team-run
62
- Workflow: /workflow:init, /workflow:status, /hooks:setup
78
+ Testing: /quality:verify-done, /quality:coverage-check, /quality:test-plan
63
79
 
64
80
  ${COLORS.bright}DOCUMENTATION${COLORS.reset}
65
81
  https://omg.mintlify.app
@@ -102,6 +118,44 @@ switch (command) {
102
118
  if (!result.success) process.exit(1);
103
119
  break;
104
120
  }
121
+ case 'project:upgrade': {
122
+ const dryRun = args.includes('--dry');
123
+ const force = args.includes('--force');
124
+ const result = upgradeProject({ dryRun, force });
125
+ if (!result.success && !result.upToDate) process.exit(1);
126
+ break;
127
+ }
128
+ case 'project:rollback': {
129
+ const result = rollbackProject();
130
+ if (!result.success) process.exit(1);
131
+ break;
132
+ }
133
+ case 'project:backups': {
134
+ console.log(BANNER);
135
+ const backups = listProjectBackups();
136
+ if (backups.length === 0) {
137
+ log.info('No backups found');
138
+ } else {
139
+ console.log(`${COLORS.bright}Available Backups${COLORS.reset}\n`);
140
+ backups.forEach((backup, i) => {
141
+ const age = Math.round((Date.now() - backup.created) / 1000 / 60);
142
+ const ageStr = age < 60 ? `${age} minutes ago` : `${Math.round(age / 60)} hours ago`;
143
+ console.log(` ${i + 1}. ${backup.name} (${ageStr})`);
144
+ });
145
+ console.log(`\nTo restore: omgkit project:rollback`);
146
+ }
147
+ break;
148
+ }
149
+ case 'project:version': {
150
+ const projectVersion = getProjectVersion();
151
+ const omgkitVersion = getVersion();
152
+ console.log(`Project OMGKIT version: ${projectVersion || 'not tracked (older project)'}`);
153
+ console.log(`Current OMGKIT version: ${omgkitVersion}`);
154
+ if (projectVersion && projectVersion !== omgkitVersion) {
155
+ log.info('Run: omgkit project:upgrade');
156
+ }
157
+ break;
158
+ }
105
159
  case 'version':
106
160
  case '-v':
107
161
  case '--version': {
package/lib/cli.js CHANGED
@@ -393,21 +393,33 @@ export function initProject(options = {}) {
393
393
  { src: 'CLAUDE.md', dest: 'CLAUDE.md' },
394
394
  { src: 'vision.yaml', dest: '.omgkit/sprints/vision.yaml' },
395
395
  { src: 'backlog.yaml', dest: '.omgkit/sprints/backlog.yaml' },
396
- { src: 'settings.json', dest: '.omgkit/settings.json' },
396
+ { src: 'settings.json', dest: '.omgkit/settings.json', process: true },
397
397
  { src: 'devlogs/README.md', dest: '.omgkit/devlogs/README.md' },
398
398
  { src: 'stdrules/README.md', dest: '.omgkit/stdrules/README.md' },
399
399
  { src: 'stdrules/SKILL_STANDARDS.md', dest: '.omgkit/stdrules/SKILL_STANDARDS.md' },
400
400
  { src: 'stdrules/BEFORE_COMMIT.md', dest: '.omgkit/stdrules/BEFORE_COMMIT.md' },
401
401
  { src: 'stdrules/TESTING_STANDARDS.md', dest: '.omgkit/stdrules/TESTING_STANDARDS.md' },
402
- { src: 'artifacts/README.md', dest: '.omgkit/artifacts/README.md' }
402
+ { src: 'artifacts/README.md', dest: '.omgkit/artifacts/README.md' },
403
+ { src: 'omgkit/workflow.yaml', dest: '.omgkit/workflow.yaml' }
403
404
  ];
404
405
 
405
- templates.forEach(({ src, dest }) => {
406
+ const version = getVersion();
407
+ const initDate = new Date().toISOString().split('T')[0];
408
+
409
+ templates.forEach(({ src, dest, process: shouldProcess }) => {
406
410
  const srcPath = join(templatesDir, src);
407
411
  const destPath = join(cwd, dest);
408
412
 
409
413
  if (existsSync(srcPath) && !existsSync(destPath)) {
410
- cpSync(srcPath, destPath);
414
+ if (shouldProcess) {
415
+ // Process template placeholders
416
+ let content = readFileSync(srcPath, 'utf8');
417
+ content = content.replace(/\{\{OMGKIT_VERSION\}\}/g, version);
418
+ content = content.replace(/\{\{INIT_DATE\}\}/g, initDate);
419
+ writeFileSync(destPath, content);
420
+ } else {
421
+ cpSync(srcPath, destPath);
422
+ }
411
423
  createdFiles.push(dest);
412
424
  if (!silent) log.success(`Created ${dest}`);
413
425
  }
@@ -934,3 +946,690 @@ export function validatePluginFile(filePath, requiredFields = []) {
934
946
  result.frontmatter = frontmatter;
935
947
  return result;
936
948
  }
949
+
950
+ // ============================================================================
951
+ // PROJECT UPGRADE SYSTEM
952
+ // ============================================================================
953
+
954
+ /**
955
+ * Create a hash of file content for change detection
956
+ * @param {string} content - File content
957
+ * @returns {string} Hash string
958
+ */
959
+ function hashContent(content) {
960
+ let hash = 0;
961
+ for (let i = 0; i < content.length; i++) {
962
+ const char = content.charCodeAt(i);
963
+ hash = ((hash << 5) - hash) + char;
964
+ hash = hash & hash;
965
+ }
966
+ return hash.toString(16);
967
+ }
968
+
969
+ /**
970
+ * Deep merge two objects (for YAML config merging)
971
+ * Only adds new keys, never overwrites existing values
972
+ * @param {Object} target - Target object (user's config)
973
+ * @param {Object} source - Source object (new template)
974
+ * @returns {Object} Merged object
975
+ */
976
+ function deepMergeAddOnly(target, source) {
977
+ const result = { ...target };
978
+
979
+ for (const key of Object.keys(source)) {
980
+ if (!(key in result)) {
981
+ // Key doesn't exist in target, add it
982
+ result[key] = source[key];
983
+ } else if (
984
+ typeof result[key] === 'object' &&
985
+ result[key] !== null &&
986
+ !Array.isArray(result[key]) &&
987
+ typeof source[key] === 'object' &&
988
+ source[key] !== null &&
989
+ !Array.isArray(source[key])
990
+ ) {
991
+ // Both are objects, recurse
992
+ result[key] = deepMergeAddOnly(result[key], source[key]);
993
+ }
994
+ // If key exists and is not an object, keep user's value
995
+ }
996
+
997
+ return result;
998
+ }
999
+
1000
+ /**
1001
+ * Simple YAML parser for config files
1002
+ * @param {string} content - YAML content
1003
+ * @returns {Object} Parsed object
1004
+ */
1005
+ function parseSimpleYaml(content) {
1006
+ const result = {};
1007
+ const lines = content.split('\n');
1008
+ const stack = [{ obj: result, indent: -1 }];
1009
+
1010
+ for (const line of lines) {
1011
+ // Skip comments and empty lines
1012
+ if (line.trim().startsWith('#') || line.trim() === '') continue;
1013
+
1014
+ const indent = line.search(/\S/);
1015
+ if (indent === -1) continue;
1016
+
1017
+ const trimmed = line.trim();
1018
+
1019
+ // Pop stack to find parent
1020
+ while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
1021
+ stack.pop();
1022
+ }
1023
+
1024
+ const parent = stack[stack.length - 1].obj;
1025
+
1026
+ if (trimmed.startsWith('- ')) {
1027
+ // Array item
1028
+ const value = trimmed.substring(2).trim();
1029
+ if (!Array.isArray(parent)) {
1030
+ // Find the key that should be an array
1031
+ const keys = Object.keys(parent);
1032
+ const lastKey = keys[keys.length - 1];
1033
+ if (lastKey && parent[lastKey] === null) {
1034
+ parent[lastKey] = [value];
1035
+ }
1036
+ } else {
1037
+ parent.push(value);
1038
+ }
1039
+ } else if (trimmed.includes(':')) {
1040
+ const colonIndex = trimmed.indexOf(':');
1041
+ const key = trimmed.substring(0, colonIndex).trim();
1042
+ let value = trimmed.substring(colonIndex + 1).trim();
1043
+
1044
+ // Remove quotes if present
1045
+ if ((value.startsWith('"') && value.endsWith('"')) ||
1046
+ (value.startsWith("'") && value.endsWith("'"))) {
1047
+ value = value.slice(1, -1);
1048
+ }
1049
+
1050
+ if (value === '' || value === null) {
1051
+ // Object or array follows
1052
+ parent[key] = {};
1053
+ stack.push({ obj: parent[key], indent });
1054
+ } else if (value === 'true') {
1055
+ parent[key] = true;
1056
+ } else if (value === 'false') {
1057
+ parent[key] = false;
1058
+ } else if (!isNaN(Number(value)) && value !== '') {
1059
+ parent[key] = Number(value);
1060
+ } else {
1061
+ parent[key] = value;
1062
+ }
1063
+ }
1064
+ }
1065
+
1066
+ return result;
1067
+ }
1068
+
1069
+ /**
1070
+ * Serialize object to simple YAML
1071
+ * @param {Object} obj - Object to serialize
1072
+ * @param {number} indent - Current indentation level
1073
+ * @returns {string} YAML string
1074
+ */
1075
+ function toSimpleYaml(obj, indent = 0) {
1076
+ const spaces = ' '.repeat(indent);
1077
+ let result = '';
1078
+
1079
+ for (const [key, value] of Object.entries(obj)) {
1080
+ if (value === null || value === undefined) {
1081
+ result += `${spaces}${key}:\n`;
1082
+ } else if (Array.isArray(value)) {
1083
+ result += `${spaces}${key}:\n`;
1084
+ for (const item of value) {
1085
+ if (typeof item === 'object') {
1086
+ result += `${spaces} -\n${toSimpleYaml(item, indent + 2)}`;
1087
+ } else {
1088
+ result += `${spaces} - ${item}\n`;
1089
+ }
1090
+ }
1091
+ } else if (typeof value === 'object') {
1092
+ result += `${spaces}${key}:\n${toSimpleYaml(value, indent + 1)}`;
1093
+ } else if (typeof value === 'string' && (value.includes(':') || value.includes('#'))) {
1094
+ result += `${spaces}${key}: "${value}"\n`;
1095
+ } else {
1096
+ result += `${spaces}${key}: ${value}\n`;
1097
+ }
1098
+ }
1099
+
1100
+ return result;
1101
+ }
1102
+
1103
+ /**
1104
+ * Get project settings including version info
1105
+ * @param {string} cwd - Project directory
1106
+ * @returns {Object|null} Settings object or null
1107
+ */
1108
+ export function getProjectSettings(cwd = process.cwd()) {
1109
+ const settingsPath = join(cwd, '.omgkit', 'settings.json');
1110
+ if (!existsSync(settingsPath)) return null;
1111
+
1112
+ try {
1113
+ return JSON.parse(readFileSync(settingsPath, 'utf8'));
1114
+ } catch (e) {
1115
+ return null;
1116
+ }
1117
+ }
1118
+
1119
+ /**
1120
+ * Get project version
1121
+ * @param {string} cwd - Project directory
1122
+ * @returns {string|null} Version string or null
1123
+ */
1124
+ export function getProjectVersion(cwd = process.cwd()) {
1125
+ const settings = getProjectSettings(cwd);
1126
+ return settings?.omgkit?.version || null;
1127
+ }
1128
+
1129
+ /**
1130
+ * Compare semantic versions
1131
+ * @param {string} v1 - First version
1132
+ * @param {string} v2 - Second version
1133
+ * @returns {number} -1 if v1 < v2, 0 if equal, 1 if v1 > v2
1134
+ */
1135
+ function compareVersions(v1, v2) {
1136
+ const parts1 = v1.split('.').map(Number);
1137
+ const parts2 = v2.split('.').map(Number);
1138
+
1139
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1140
+ const p1 = parts1[i] || 0;
1141
+ const p2 = parts2[i] || 0;
1142
+ if (p1 < p2) return -1;
1143
+ if (p1 > p2) return 1;
1144
+ }
1145
+ return 0;
1146
+ }
1147
+
1148
+ /**
1149
+ * Create backup of .omgkit directory
1150
+ * @param {string} cwd - Project directory
1151
+ * @returns {Object} Backup result with path
1152
+ */
1153
+ export function createProjectBackup(cwd = process.cwd()) {
1154
+ const omgkitDir = join(cwd, '.omgkit');
1155
+ if (!existsSync(omgkitDir)) {
1156
+ return { success: false, error: 'NOT_INITIALIZED' };
1157
+ }
1158
+
1159
+ const now = new Date();
1160
+ const timestamp = now.toISOString().replace(/[:.]/g, '-').split('T').join('-').substring(0, 19);
1161
+ const ms = String(now.getMilliseconds()).padStart(3, '0');
1162
+ const backupDir = join(cwd, `.omgkit-backup-${timestamp}-${ms}`);
1163
+
1164
+ try {
1165
+ cpSync(omgkitDir, backupDir, { recursive: true });
1166
+ return { success: true, path: backupDir, timestamp };
1167
+ } catch (e) {
1168
+ return { success: false, error: e.message };
1169
+ }
1170
+ }
1171
+
1172
+ /**
1173
+ * List available backups
1174
+ * @param {string} cwd - Project directory
1175
+ * @returns {Object[]} Array of backup info
1176
+ */
1177
+ export function listProjectBackups(cwd = process.cwd()) {
1178
+ const backups = [];
1179
+
1180
+ try {
1181
+ const items = readdirSync(cwd);
1182
+ for (const item of items) {
1183
+ if (item.startsWith('.omgkit-backup-')) {
1184
+ const fullPath = join(cwd, item);
1185
+ const stat = statSync(fullPath);
1186
+ if (stat.isDirectory()) {
1187
+ backups.push({
1188
+ name: item,
1189
+ path: fullPath,
1190
+ created: stat.mtime
1191
+ });
1192
+ }
1193
+ }
1194
+ }
1195
+ } catch (e) {
1196
+ // Directory read error
1197
+ }
1198
+
1199
+ return backups.sort((a, b) => b.created - a.created);
1200
+ }
1201
+
1202
+ /**
1203
+ * Rollback project to a backup
1204
+ * @param {Object} options - Options
1205
+ * @param {string} [options.cwd] - Project directory
1206
+ * @param {string} [options.backupPath] - Specific backup to restore
1207
+ * @param {boolean} [options.silent] - Suppress output
1208
+ * @returns {Object} Result
1209
+ */
1210
+ export function rollbackProject(options = {}) {
1211
+ const { cwd = process.cwd(), backupPath, silent = false } = options;
1212
+
1213
+ if (!silent) {
1214
+ console.log(BANNER);
1215
+ log.omega('Rolling back project...');
1216
+ }
1217
+
1218
+ const omgkitDir = join(cwd, '.omgkit');
1219
+ let targetBackup = backupPath;
1220
+
1221
+ if (!targetBackup) {
1222
+ // Find most recent backup
1223
+ const backups = listProjectBackups(cwd);
1224
+ if (backups.length === 0) {
1225
+ if (!silent) log.error('No backups found');
1226
+ return { success: false, error: 'NO_BACKUPS' };
1227
+ }
1228
+ targetBackup = backups[0].path;
1229
+ }
1230
+
1231
+ if (!existsSync(targetBackup)) {
1232
+ if (!silent) log.error(`Backup not found: ${targetBackup}`);
1233
+ return { success: false, error: 'BACKUP_NOT_FOUND' };
1234
+ }
1235
+
1236
+ try {
1237
+ // Remove current .omgkit
1238
+ if (existsSync(omgkitDir)) {
1239
+ rmSync(omgkitDir, { recursive: true, force: true });
1240
+ }
1241
+
1242
+ // Restore from backup
1243
+ cpSync(targetBackup, omgkitDir, { recursive: true });
1244
+
1245
+ if (!silent) {
1246
+ log.success(`Rolled back to: ${targetBackup}`);
1247
+ }
1248
+
1249
+ return { success: true, restoredFrom: targetBackup };
1250
+ } catch (e) {
1251
+ if (!silent) log.error(`Rollback failed: ${e.message}`);
1252
+ return { success: false, error: e.message };
1253
+ }
1254
+ }
1255
+
1256
+ /**
1257
+ * Calculate upgrade changes (dry run)
1258
+ * @param {string} cwd - Project directory
1259
+ * @returns {Object} Changes to be applied
1260
+ */
1261
+ export function calculateUpgradeChanges(cwd = process.cwd()) {
1262
+ const changes = {
1263
+ settings: [],
1264
+ workflow: [],
1265
+ stdrules: [],
1266
+ newFiles: [],
1267
+ protected: [
1268
+ 'config.yaml',
1269
+ 'sprints/vision.yaml',
1270
+ 'sprints/backlog.yaml',
1271
+ 'artifacts/*',
1272
+ 'devlogs/*'
1273
+ ]
1274
+ };
1275
+
1276
+ const templatesDir = join(getPackageRoot(), 'templates');
1277
+ const omgkitDir = join(cwd, '.omgkit');
1278
+ const currentVersion = getProjectVersion(cwd);
1279
+ const targetVersion = getVersion();
1280
+
1281
+ // Check workflow.yaml changes
1282
+ const workflowTemplatePath = join(templatesDir, 'omgkit', 'workflow.yaml');
1283
+ const workflowProjectPath = join(omgkitDir, 'workflow.yaml');
1284
+
1285
+ if (existsSync(workflowTemplatePath)) {
1286
+ const templateContent = readFileSync(workflowTemplatePath, 'utf8');
1287
+ const templateConfig = parseSimpleYaml(templateContent);
1288
+
1289
+ if (existsSync(workflowProjectPath)) {
1290
+ const projectContent = readFileSync(workflowProjectPath, 'utf8');
1291
+ const projectConfig = parseSimpleYaml(projectContent);
1292
+
1293
+ // Find new top-level sections
1294
+ for (const key of Object.keys(templateConfig)) {
1295
+ if (!(key in projectConfig)) {
1296
+ changes.workflow.push({
1297
+ type: 'add_section',
1298
+ key,
1299
+ value: templateConfig[key]
1300
+ });
1301
+ }
1302
+ }
1303
+ } else {
1304
+ changes.newFiles.push('workflow.yaml');
1305
+ }
1306
+ }
1307
+
1308
+ // Check stdrules updates
1309
+ const stdrulesTemplate = join(templatesDir, 'stdrules');
1310
+ const stdrulesProject = join(omgkitDir, 'stdrules');
1311
+
1312
+ if (existsSync(stdrulesTemplate)) {
1313
+ const templateFiles = readdirSync(stdrulesTemplate).filter(f => f.endsWith('.md'));
1314
+ const settings = getProjectSettings(cwd) || {};
1315
+ const checksums = settings.file_checksums || {};
1316
+
1317
+ for (const file of templateFiles) {
1318
+ const templatePath = join(stdrulesTemplate, file);
1319
+ const projectPath = join(stdrulesProject, file);
1320
+ const templateContent = readFileSync(templatePath, 'utf8');
1321
+ const templateHash = hashContent(templateContent);
1322
+
1323
+ if (!existsSync(projectPath)) {
1324
+ changes.stdrules.push({
1325
+ type: 'new',
1326
+ file,
1327
+ reason: 'File does not exist'
1328
+ });
1329
+ } else {
1330
+ const projectContent = readFileSync(projectPath, 'utf8');
1331
+ const projectHash = hashContent(projectContent);
1332
+ const originalHash = checksums[`stdrules/${file}`];
1333
+
1334
+ // Update only if user hasn't modified the file
1335
+ if (originalHash && projectHash === originalHash && templateHash !== projectHash) {
1336
+ changes.stdrules.push({
1337
+ type: 'update',
1338
+ file,
1339
+ reason: 'Template updated, user has not modified'
1340
+ });
1341
+ } else if (!originalHash && templateHash !== projectHash) {
1342
+ changes.stdrules.push({
1343
+ type: 'skip',
1344
+ file,
1345
+ reason: 'User may have modified (no checksum recorded)'
1346
+ });
1347
+ }
1348
+ }
1349
+ }
1350
+ }
1351
+
1352
+ // Settings.json updates
1353
+ const settingsPath = join(omgkitDir, 'settings.json');
1354
+ if (existsSync(settingsPath)) {
1355
+ const settings = getProjectSettings(cwd);
1356
+ if (!settings?.omgkit) {
1357
+ changes.settings.push({
1358
+ type: 'add',
1359
+ key: 'omgkit',
1360
+ value: { version: targetVersion, initialized_at: null, last_upgraded: null }
1361
+ });
1362
+ }
1363
+ if (!settings?.file_checksums) {
1364
+ changes.settings.push({
1365
+ type: 'add',
1366
+ key: 'file_checksums',
1367
+ value: {}
1368
+ });
1369
+ }
1370
+ }
1371
+
1372
+ return {
1373
+ currentVersion,
1374
+ targetVersion,
1375
+ needsUpgrade: compareVersions(targetVersion, currentVersion || '0.0.0') > 0,
1376
+ changes
1377
+ };
1378
+ }
1379
+
1380
+ /**
1381
+ * Upgrade project to latest OMGKIT version
1382
+ * @param {Object} options - Options
1383
+ * @param {string} [options.cwd] - Project directory
1384
+ * @param {boolean} [options.dryRun] - Only show what would change
1385
+ * @param {boolean} [options.force] - Skip confirmation
1386
+ * @param {boolean} [options.silent] - Suppress output
1387
+ * @returns {Object} Upgrade result
1388
+ */
1389
+ export function upgradeProject(options = {}) {
1390
+ const { cwd = process.cwd(), dryRun = false, force = false, silent = false } = options;
1391
+
1392
+ if (!silent) {
1393
+ console.log(BANNER);
1394
+ log.omega('Checking for project upgrades...');
1395
+ }
1396
+
1397
+ const omgkitDir = join(cwd, '.omgkit');
1398
+
1399
+ // Check if project is initialized
1400
+ if (!existsSync(omgkitDir)) {
1401
+ if (!silent) {
1402
+ log.error('Project not initialized. Run: omgkit init');
1403
+ }
1404
+ return { success: false, error: 'NOT_INITIALIZED' };
1405
+ }
1406
+
1407
+ // Calculate changes
1408
+ const upgradeInfo = calculateUpgradeChanges(cwd);
1409
+
1410
+ if (!upgradeInfo.needsUpgrade &&
1411
+ upgradeInfo.changes.workflow.length === 0 &&
1412
+ upgradeInfo.changes.stdrules.filter(s => s.type !== 'skip').length === 0 &&
1413
+ upgradeInfo.changes.newFiles.length === 0) {
1414
+ if (!silent) {
1415
+ log.success('Project is up to date!');
1416
+ console.log(` Current version: ${upgradeInfo.currentVersion || 'unknown'}`);
1417
+ console.log(` OMGKIT version: ${upgradeInfo.targetVersion}`);
1418
+ }
1419
+ return { success: true, upToDate: true };
1420
+ }
1421
+
1422
+ // Display changes
1423
+ if (!silent) {
1424
+ console.log(`
1425
+ ${COLORS.bright}OMGKIT Project Upgrade${COLORS.reset}
1426
+ ${'='.repeat(50)}
1427
+ Current version: ${upgradeInfo.currentVersion || 'unknown'}
1428
+ Target version: ${upgradeInfo.targetVersion}
1429
+
1430
+ ${COLORS.bright}Changes to be applied:${COLORS.reset}
1431
+ `);
1432
+
1433
+ if (upgradeInfo.changes.workflow.length > 0) {
1434
+ console.log(`${COLORS.cyan}📁 workflow.yaml${COLORS.reset}`);
1435
+ for (const change of upgradeInfo.changes.workflow) {
1436
+ if (change.type === 'add_section') {
1437
+ console.log(` ${COLORS.green}+ ${change.key}: {...}${COLORS.reset}`);
1438
+ }
1439
+ }
1440
+ }
1441
+
1442
+ if (upgradeInfo.changes.stdrules.length > 0) {
1443
+ console.log(`\n${COLORS.cyan}📁 stdrules/${COLORS.reset}`);
1444
+ for (const change of upgradeInfo.changes.stdrules) {
1445
+ if (change.type === 'new') {
1446
+ console.log(` ${COLORS.green}+ ${change.file} (new)${COLORS.reset}`);
1447
+ } else if (change.type === 'update') {
1448
+ console.log(` ${COLORS.yellow}~ ${change.file} (updated)${COLORS.reset}`);
1449
+ } else if (change.type === 'skip') {
1450
+ console.log(` ${COLORS.blue}○ ${change.file} (skipped - may be modified)${COLORS.reset}`);
1451
+ }
1452
+ }
1453
+ }
1454
+
1455
+ if (upgradeInfo.changes.newFiles.length > 0) {
1456
+ console.log(`\n${COLORS.cyan}📁 New files${COLORS.reset}`);
1457
+ for (const file of upgradeInfo.changes.newFiles) {
1458
+ console.log(` ${COLORS.green}+ ${file}${COLORS.reset}`);
1459
+ }
1460
+ }
1461
+
1462
+ console.log(`
1463
+ ${COLORS.bright}🔒 Protected (no changes):${COLORS.reset}`);
1464
+ for (const protected_ of upgradeInfo.changes.protected) {
1465
+ console.log(` - ${protected_}`);
1466
+ }
1467
+ }
1468
+
1469
+ if (dryRun) {
1470
+ if (!silent) {
1471
+ console.log(`\n${COLORS.yellow}Dry run - no changes made.${COLORS.reset}`);
1472
+ console.log('Run without --dry to apply changes.');
1473
+ }
1474
+ return { success: true, dryRun: true, changes: upgradeInfo.changes };
1475
+ }
1476
+
1477
+ // Create backup before upgrade
1478
+ if (!silent) log.info('\nCreating backup...');
1479
+ const backup = createProjectBackup(cwd);
1480
+ if (!backup.success) {
1481
+ if (!silent) log.error(`Backup failed: ${backup.error}`);
1482
+ return { success: false, error: 'BACKUP_FAILED' };
1483
+ }
1484
+ if (!silent) log.success(`Backup created: ${backup.path}`);
1485
+
1486
+ const templatesDir = join(getPackageRoot(), 'templates');
1487
+ const applied = { workflow: [], stdrules: [], newFiles: [], settings: [] };
1488
+
1489
+ try {
1490
+ // Apply workflow.yaml changes
1491
+ if (upgradeInfo.changes.workflow.length > 0) {
1492
+ const workflowPath = join(omgkitDir, 'workflow.yaml');
1493
+ const workflowTemplatePath = join(templatesDir, 'omgkit', 'workflow.yaml');
1494
+
1495
+ if (existsSync(workflowPath) && existsSync(workflowTemplatePath)) {
1496
+ const projectContent = readFileSync(workflowPath, 'utf8');
1497
+ const templateContent = readFileSync(workflowTemplatePath, 'utf8');
1498
+
1499
+ const projectConfig = parseSimpleYaml(projectContent);
1500
+ const templateConfig = parseSimpleYaml(templateContent);
1501
+
1502
+ const merged = deepMergeAddOnly(projectConfig, templateConfig);
1503
+
1504
+ // Preserve comments from original file by appending new sections
1505
+ let newContent = projectContent;
1506
+
1507
+ for (const change of upgradeInfo.changes.workflow) {
1508
+ if (change.type === 'add_section') {
1509
+ // Find section in template and append
1510
+ const sectionRegex = new RegExp(`^${change.key}:`, 'm');
1511
+ const templateLines = templateContent.split('\n');
1512
+ let inSection = false;
1513
+ let sectionContent = '';
1514
+
1515
+ for (const line of templateLines) {
1516
+ if (line.match(sectionRegex)) {
1517
+ inSection = true;
1518
+ } else if (inSection && line.match(/^\w+:/) && !line.startsWith(' ')) {
1519
+ break;
1520
+ }
1521
+
1522
+ if (inSection) {
1523
+ sectionContent += line + '\n';
1524
+ }
1525
+ }
1526
+
1527
+ if (sectionContent) {
1528
+ newContent = newContent.trimEnd() + '\n\n' + sectionContent;
1529
+ applied.workflow.push(change.key);
1530
+ }
1531
+ }
1532
+ }
1533
+
1534
+ writeFileSync(workflowPath, newContent);
1535
+ }
1536
+ }
1537
+
1538
+ // Apply stdrules updates
1539
+ for (const change of upgradeInfo.changes.stdrules) {
1540
+ if (change.type === 'new' || change.type === 'update') {
1541
+ const templatePath = join(templatesDir, 'stdrules', change.file);
1542
+ const projectPath = join(omgkitDir, 'stdrules', change.file);
1543
+
1544
+ if (existsSync(templatePath)) {
1545
+ cpSync(templatePath, projectPath);
1546
+ applied.stdrules.push(change.file);
1547
+ }
1548
+ }
1549
+ }
1550
+
1551
+ // Apply new files
1552
+ for (const file of upgradeInfo.changes.newFiles) {
1553
+ if (file === 'workflow.yaml') {
1554
+ const templatePath = join(templatesDir, 'omgkit', 'workflow.yaml');
1555
+ const projectPath = join(omgkitDir, 'workflow.yaml');
1556
+ if (existsSync(templatePath)) {
1557
+ cpSync(templatePath, projectPath);
1558
+ applied.newFiles.push(file);
1559
+ }
1560
+ }
1561
+ }
1562
+
1563
+ // Update settings.json
1564
+ const settingsPath = join(omgkitDir, 'settings.json');
1565
+ let settings = getProjectSettings(cwd) || {};
1566
+
1567
+ // Add omgkit version tracking
1568
+ if (!settings.omgkit) {
1569
+ settings.omgkit = {};
1570
+ }
1571
+ settings.omgkit.version = upgradeInfo.targetVersion;
1572
+ settings.omgkit.last_upgraded = new Date().toISOString().split('T')[0];
1573
+
1574
+ // Initialize checksums if not present
1575
+ if (!settings.file_checksums) {
1576
+ settings.file_checksums = {};
1577
+ }
1578
+
1579
+ // Update checksums for stdrules files
1580
+ const stdrulesDir = join(omgkitDir, 'stdrules');
1581
+ if (existsSync(stdrulesDir)) {
1582
+ const files = readdirSync(stdrulesDir).filter(f => f.endsWith('.md'));
1583
+ for (const file of files) {
1584
+ const content = readFileSync(join(stdrulesDir, file), 'utf8');
1585
+ settings.file_checksums[`stdrules/${file}`] = hashContent(content);
1586
+ }
1587
+ }
1588
+
1589
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
1590
+ applied.settings.push('version', 'checksums');
1591
+
1592
+ } catch (e) {
1593
+ // Rollback on error
1594
+ if (!silent) {
1595
+ log.error(`Upgrade failed: ${e.message}`);
1596
+ log.info('Rolling back...');
1597
+ }
1598
+ rollbackProject({ cwd, backupPath: backup.path, silent: true });
1599
+ if (!silent) log.success('Rolled back to previous state');
1600
+ return { success: false, error: e.message, rolledBack: true };
1601
+ }
1602
+
1603
+ if (!silent) {
1604
+ console.log(`
1605
+ ${COLORS.green}✓ Project upgraded successfully!${COLORS.reset}
1606
+
1607
+ ${COLORS.bright}Applied changes:${COLORS.reset}`);
1608
+
1609
+ if (applied.workflow.length > 0) {
1610
+ console.log(` workflow.yaml: Added ${applied.workflow.join(', ')}`);
1611
+ }
1612
+ if (applied.stdrules.length > 0) {
1613
+ console.log(` stdrules/: Updated ${applied.stdrules.join(', ')}`);
1614
+ }
1615
+ if (applied.newFiles.length > 0) {
1616
+ console.log(` New files: ${applied.newFiles.join(', ')}`);
1617
+ }
1618
+
1619
+ console.log(`
1620
+ ${COLORS.bright}Backup location:${COLORS.reset} ${backup.path}
1621
+
1622
+ To rollback: omgkit project:rollback
1623
+
1624
+ ${COLORS.magenta}🔮 Think Omega. Build Omega. Be Omega.${COLORS.reset}
1625
+ `);
1626
+ }
1627
+
1628
+ return {
1629
+ success: true,
1630
+ previousVersion: upgradeInfo.currentVersion,
1631
+ newVersion: upgradeInfo.targetVersion,
1632
+ applied,
1633
+ backupPath: backup.path
1634
+ };
1635
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omgkit",
3
- "version": "2.24.3",
3
+ "version": "2.25.0",
4
4
  "description": "Omega-Level Development Kit - AI Team System for Claude Code. 41 agents, 160 commands, 161 skills, 69 workflows.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -1,9 +1,9 @@
1
1
  # OMGKIT Component Registry
2
2
  # Single Source of Truth for Agents, Skills, Commands, Workflows, and MCPs
3
- # Version: 2.24.3
3
+ # Version: 2.25.0
4
4
  # Updated: 2026-01-06
5
5
 
6
- version: "2.24.3"
6
+ version: "2.25.0"
7
7
 
8
8
  # =============================================================================
9
9
  # OPTIMIZED ALIGNMENT PRINCIPLE (OAP)
@@ -1,4 +1,10 @@
1
1
  {
2
+ "omgkit": {
3
+ "version": "{{OMGKIT_VERSION}}",
4
+ "initialized_at": "{{INIT_DATE}}",
5
+ "last_upgraded": null
6
+ },
7
+ "file_checksums": {},
2
8
  "permissions": {
3
9
  "allow": [
4
10
  "Bash(npm run *)",