@nerviq/cli 1.2.6 → 1.2.7
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/bin/cli.js +27 -2
- package/package.json +1 -1
- package/src/harmony/add.js +68 -0
- package/src/harmony/cli.js +1 -0
- package/src/harmony/watch.js +35 -1
package/bin/cli.js
CHANGED
|
@@ -24,7 +24,7 @@ const COMMAND_ALIASES = {
|
|
|
24
24
|
gov: 'governance',
|
|
25
25
|
outcome: 'feedback',
|
|
26
26
|
};
|
|
27
|
-
const KNOWN_COMMANDS = ['audit', 'org', 'setup', 'augment', 'suggest-only', 'plan', 'apply', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'history', 'compare', 'trend', 'scan', 'feedback', 'doctor', 'convert', 'migrate', 'catalog', 'certify', 'serve', 'harmony-audit', 'harmony-sync', 'harmony-drift', 'harmony-advise', 'harmony-watch', 'harmony-governance', 'synergy-report', 'help', 'version'];
|
|
27
|
+
const KNOWN_COMMANDS = ['audit', 'org', 'setup', 'augment', 'suggest-only', 'plan', 'apply', 'governance', 'benchmark', 'deep-review', 'interactive', 'watch', 'badge', 'insights', 'history', 'compare', 'trend', 'scan', 'feedback', 'doctor', 'convert', 'migrate', 'catalog', 'certify', 'serve', 'harmony-audit', 'harmony-sync', 'harmony-drift', 'harmony-advise', 'harmony-watch', 'harmony-governance', 'harmony-add', 'synergy-report', 'help', 'version'];
|
|
28
28
|
|
|
29
29
|
function levenshtein(a, b) {
|
|
30
30
|
const matrix = Array.from({ length: a.length + 1 }, () => Array(b.length + 1).fill(0));
|
|
@@ -315,6 +315,7 @@ const HELP = `
|
|
|
315
315
|
nerviq harmony-sync Preview cross-platform sync (dry run)
|
|
316
316
|
nerviq harmony-sync --fix Apply cross-platform sync (write files)
|
|
317
317
|
nerviq harmony-sync --json JSON output for CI/automation
|
|
318
|
+
nerviq harmony-add <platform> Add a new platform to the project
|
|
318
319
|
nerviq synergy-report Multi-agent amplification opportunities
|
|
319
320
|
nerviq convert --from X --to Y Convert configs between platforms
|
|
320
321
|
nerviq migrate --platform X Platform version migration helper
|
|
@@ -412,6 +413,7 @@ async function main() {
|
|
|
412
413
|
snapshot: flags.includes('--snapshot'),
|
|
413
414
|
feedback: flags.includes('--feedback'),
|
|
414
415
|
fix: flags.includes('--fix'),
|
|
416
|
+
autoSync: flags.includes('--auto-sync'),
|
|
415
417
|
dryRun: flags.includes('--dry-run'),
|
|
416
418
|
threshold: parsed.threshold !== null ? Number(parsed.threshold) : null,
|
|
417
419
|
out: parsed.out,
|
|
@@ -505,7 +507,7 @@ async function main() {
|
|
|
505
507
|
'history', 'compare', 'trend', 'feedback', 'catalog', 'certify', 'serve', 'help', 'version',
|
|
506
508
|
// Harmony + Synergy (cross-platform)
|
|
507
509
|
'harmony-audit', 'harmony-sync', 'harmony-drift', 'harmony-advise',
|
|
508
|
-
'harmony-watch', 'harmony-governance', 'synergy-report',
|
|
510
|
+
'harmony-watch', 'harmony-governance', 'harmony-add', 'synergy-report',
|
|
509
511
|
]);
|
|
510
512
|
|
|
511
513
|
if (options.platform === 'codex') {
|
|
@@ -962,6 +964,29 @@ async function main() {
|
|
|
962
964
|
const { runHarmonyGovernance } = require('../src/harmony/cli');
|
|
963
965
|
await runHarmonyGovernance(options);
|
|
964
966
|
process.exit(0);
|
|
967
|
+
} else if (normalizedCommand === 'harmony-add') {
|
|
968
|
+
const { addPlatform } = require('../src/harmony/add');
|
|
969
|
+
const platformArg = parsed.extraArgs[0];
|
|
970
|
+
if (!platformArg) {
|
|
971
|
+
console.log('\n Usage: nerviq harmony-add <platform>');
|
|
972
|
+
console.log(' Available: claude, codex, gemini, copilot, cursor, windsurf, aider, opencode\n');
|
|
973
|
+
process.exit(1);
|
|
974
|
+
}
|
|
975
|
+
const dir = options.dir || process.cwd();
|
|
976
|
+
const result = addPlatform(dir, platformArg.toLowerCase());
|
|
977
|
+
if (options.json) {
|
|
978
|
+
console.log(JSON.stringify(result, null, 2));
|
|
979
|
+
} else if (result.success) {
|
|
980
|
+
console.log(`\n \x1b[32m\u2713\x1b[0m Added ${result.platform} to project`);
|
|
981
|
+
result.created.forEach(f => console.log(` Created: ${f}`));
|
|
982
|
+
console.log(` Platforms: ${result.beforeCount} \u2192 ${result.afterCount}`);
|
|
983
|
+
if (result.syncApplied > 0) console.log(` Harmony sync: ${result.syncApplied} file(s) updated`);
|
|
984
|
+
console.log('');
|
|
985
|
+
} else {
|
|
986
|
+
console.log(`\n \x1b[31m\u2717\x1b[0m ${result.error}\n`);
|
|
987
|
+
process.exit(1);
|
|
988
|
+
}
|
|
989
|
+
process.exit(0);
|
|
965
990
|
} else if (normalizedCommand === 'synergy-report') {
|
|
966
991
|
// Placeholder — synergy report is referenced but may not be implemented yet
|
|
967
992
|
console.log('\n Synergy report: coming soon.\n');
|
package/package.json
CHANGED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform Addition Wizard
|
|
3
|
+
* Helps users add a new platform config to their project.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { detectActivePlatforms, PLATFORM_SIGNATURES } = require('./canon');
|
|
9
|
+
const { applyHarmonySync } = require('./sync');
|
|
10
|
+
|
|
11
|
+
const PLATFORM_BOOTSTRAPS = {
|
|
12
|
+
claude: { files: [{ path: 'CLAUDE.md', content: '# Project Instructions\n\nAdd your Claude Code instructions here.\n' }] },
|
|
13
|
+
codex: { files: [{ path: 'AGENTS.md', content: '# Agents Instructions\n\nAdd your Codex instructions here.\n' }] },
|
|
14
|
+
gemini: { files: [{ path: 'GEMINI.md', content: '# Gemini Instructions\n\nAdd your Gemini CLI instructions here.\n' }] },
|
|
15
|
+
copilot: { files: [{ path: '.github/copilot-instructions.md', content: '# Copilot Instructions\n\nAdd your GitHub Copilot instructions here.\n' }] },
|
|
16
|
+
cursor: { files: [{ path: '.cursorrules', content: '# Cursor Rules\n\nAdd your Cursor rules here.\n' }] },
|
|
17
|
+
windsurf: { files: [{ path: '.windsurfrules', content: '# Windsurf Rules\n\nAdd your Windsurf rules here.\n' }] },
|
|
18
|
+
aider: { files: [{ path: '.aider.conf.yml', content: '# Aider Configuration\n# See: https://aider.chat/docs/config/aider_conf.html\n' }] },
|
|
19
|
+
opencode: { files: [{ path: 'opencode.json', content: '{\n "instructions": "Add your OpenCode instructions here."\n}\n' }] },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function addPlatform(dir, platformKey) {
|
|
23
|
+
// Validate platform
|
|
24
|
+
if (!PLATFORM_SIGNATURES[platformKey]) {
|
|
25
|
+
return { success: false, error: `Unknown platform: ${platformKey}. Available: ${Object.keys(PLATFORM_SIGNATURES).join(', ')}` };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check if already active
|
|
29
|
+
const active = detectActivePlatforms(dir);
|
|
30
|
+
const alreadyActive = active.find(p => p.platform === platformKey);
|
|
31
|
+
if (alreadyActive) {
|
|
32
|
+
return { success: false, error: `${platformKey} is already active in this project.` };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const beforeCount = active.length;
|
|
36
|
+
const bootstrap = PLATFORM_BOOTSTRAPS[platformKey];
|
|
37
|
+
const created = [];
|
|
38
|
+
|
|
39
|
+
// Create bootstrap files
|
|
40
|
+
for (const file of bootstrap.files) {
|
|
41
|
+
const fullPath = path.join(dir, file.path);
|
|
42
|
+
if (fs.existsSync(fullPath)) continue;
|
|
43
|
+
const dirName = path.dirname(fullPath);
|
|
44
|
+
fs.mkdirSync(dirName, { recursive: true });
|
|
45
|
+
fs.writeFileSync(fullPath, file.content, 'utf8');
|
|
46
|
+
created.push(file.path);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Run harmony sync to populate managed blocks
|
|
50
|
+
let syncResult = null;
|
|
51
|
+
try {
|
|
52
|
+
syncResult = applyHarmonySync(dir);
|
|
53
|
+
} catch { /* sync is optional */ }
|
|
54
|
+
|
|
55
|
+
const afterActive = detectActivePlatforms(dir);
|
|
56
|
+
const afterCount = afterActive.length;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
platform: platformKey,
|
|
61
|
+
created,
|
|
62
|
+
beforeCount,
|
|
63
|
+
afterCount,
|
|
64
|
+
syncApplied: syncResult ? syncResult.applied.length : 0,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { addPlatform, PLATFORM_BOOTSTRAPS };
|
package/src/harmony/cli.js
CHANGED
|
@@ -300,6 +300,7 @@ async function runHarmonyWatch(options) {
|
|
|
300
300
|
|
|
301
301
|
await startHarmonyWatch({
|
|
302
302
|
dir,
|
|
303
|
+
autoSync: !!options.autoSync,
|
|
303
304
|
debounceMs: options.debounce || 800,
|
|
304
305
|
onDriftDetected: (platform, details) => {
|
|
305
306
|
console.log(c(` DRIFT ALERT: ${platform} score dropped by ${Math.abs(details.delta)}`, 'red'));
|
package/src/harmony/watch.js
CHANGED
|
@@ -36,6 +36,13 @@ const PLATFORM_WATCH_FILES = [
|
|
|
36
36
|
'.github/copilot-review-instructions.md',
|
|
37
37
|
// Cursor
|
|
38
38
|
'.cursorrules',
|
|
39
|
+
// Windsurf
|
|
40
|
+
'.windsurfrules',
|
|
41
|
+
// Aider
|
|
42
|
+
'.aider.conf.yml',
|
|
43
|
+
'.aiderignore',
|
|
44
|
+
// OpenCode
|
|
45
|
+
'opencode.json',
|
|
39
46
|
// Shared
|
|
40
47
|
'.gitignore',
|
|
41
48
|
'package.json',
|
|
@@ -53,6 +60,9 @@ const PLATFORM_WATCH_DIRS = [
|
|
|
53
60
|
'.github',
|
|
54
61
|
'.cursor',
|
|
55
62
|
'.cursor/rules',
|
|
63
|
+
'.windsurf',
|
|
64
|
+
'.windsurf/rules',
|
|
65
|
+
'.opencode',
|
|
56
66
|
];
|
|
57
67
|
|
|
58
68
|
// ─── fs.watch helpers (mirror pattern from src/watch.js) ──────────────────────
|
|
@@ -174,6 +184,9 @@ function identifyPlatform(filePath) {
|
|
|
174
184
|
if (normalized.includes('.gemini') || normalized.includes('gemini.md')) return 'gemini';
|
|
175
185
|
if (normalized.includes('copilot') || normalized.includes('.github')) return 'copilot';
|
|
176
186
|
if (normalized.includes('.cursor') || normalized.includes('cursorrules')) return 'cursor';
|
|
187
|
+
if (normalized.includes('.windsurf') || normalized.includes('windsurfrules')) return 'windsurf';
|
|
188
|
+
if (normalized.includes('.aider') || normalized.includes('aiderignore')) return 'aider';
|
|
189
|
+
if (normalized.includes('.opencode') || normalized.includes('opencode.json')) return 'opencode';
|
|
177
190
|
return 'unknown';
|
|
178
191
|
}
|
|
179
192
|
|
|
@@ -187,6 +200,7 @@ function identifyPlatform(filePath) {
|
|
|
187
200
|
* @param {Function} [options.onDriftDetected] - Callback when drift increases: (platform, details) => void
|
|
188
201
|
* @param {Function} [options.onPlatformChange] - Callback on any platform config change: (platform, file) => void
|
|
189
202
|
* @param {Function} [options.runAudit] - Optional audit function to re-run on changes
|
|
203
|
+
* @param {boolean} [options.autoSync=false] - Auto-apply harmony sync when drift is detected
|
|
190
204
|
* @param {number} [options.debounceMs=800] - Debounce interval in ms
|
|
191
205
|
*/
|
|
192
206
|
async function startHarmonyWatch(options) {
|
|
@@ -195,6 +209,7 @@ async function startHarmonyWatch(options) {
|
|
|
195
209
|
onDriftDetected,
|
|
196
210
|
onPlatformChange,
|
|
197
211
|
runAudit,
|
|
212
|
+
autoSync = false,
|
|
198
213
|
debounceMs = 800,
|
|
199
214
|
} = options;
|
|
200
215
|
|
|
@@ -204,7 +219,10 @@ async function startHarmonyWatch(options) {
|
|
|
204
219
|
console.log(c(' nerviq harmony watch', 'bold'));
|
|
205
220
|
console.log(c(' ═══════════════════════════════════════', 'dim'));
|
|
206
221
|
console.log(c(` Watching: ${dir}`, 'dim'));
|
|
207
|
-
console.log(c(` Platforms: Claude, Codex, Gemini, Copilot, Cursor`, 'dim'));
|
|
222
|
+
console.log(c(` Platforms: Claude, Codex, Gemini, Copilot, Cursor, Windsurf, Aider, OpenCode`, 'dim'));
|
|
223
|
+
if (autoSync) {
|
|
224
|
+
console.log(c(` Auto-sync: ON — drift will be auto-corrected`, 'green'));
|
|
225
|
+
}
|
|
208
226
|
console.log(c(` Mode: ${recursiveSupported ? 'native recursive' : 'expanded directory fallback'}`, 'dim'));
|
|
209
227
|
console.log(c(' Press Ctrl+C to stop', 'dim'));
|
|
210
228
|
console.log('');
|
|
@@ -293,6 +311,22 @@ async function startHarmonyWatch(options) {
|
|
|
293
311
|
}
|
|
294
312
|
}
|
|
295
313
|
|
|
314
|
+
// Auto-sync on drift
|
|
315
|
+
if (delta < 0 && autoSync) {
|
|
316
|
+
try {
|
|
317
|
+
const { applyHarmonySync } = require('./sync');
|
|
318
|
+
const syncResult = applyHarmonySync(dir);
|
|
319
|
+
if (syncResult.applied.length > 0) {
|
|
320
|
+
console.log(c(` Auto-sync: applied ${syncResult.applied.length} fix(es)`, 'green'));
|
|
321
|
+
for (const item of syncResult.applied) {
|
|
322
|
+
console.log(c(` ✓ ${item.action} ${item.path}`, 'dim'));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} catch (_e) {
|
|
326
|
+
console.log(c(` Auto-sync failed: ${_e.message}`, 'yellow'));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
296
330
|
lastScores[p] = newScore;
|
|
297
331
|
}
|
|
298
332
|
}
|