@nerviq/cli 1.2.3 → 1.2.6

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 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', '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', '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));
@@ -312,6 +312,9 @@ const HELP = `
312
312
 
313
313
  CROSS-PLATFORM
314
314
  nerviq harmony-audit Drift detection across all active platforms
315
+ nerviq harmony-sync Preview cross-platform sync (dry run)
316
+ nerviq harmony-sync --fix Apply cross-platform sync (write files)
317
+ nerviq harmony-sync --json JSON output for CI/automation
315
318
  nerviq synergy-report Multi-agent amplification opportunities
316
319
  nerviq convert --from X --to Y Convert configs between platforms
317
320
  nerviq migrate --platform X Platform version migration helper
@@ -408,6 +411,7 @@ async function main() {
408
411
  lite: flags.includes('--lite'),
409
412
  snapshot: flags.includes('--snapshot'),
410
413
  feedback: flags.includes('--feedback'),
414
+ fix: flags.includes('--fix'),
411
415
  dryRun: flags.includes('--dry-run'),
412
416
  threshold: parsed.threshold !== null ? Number(parsed.threshold) : null,
413
417
  out: parsed.out,
@@ -862,6 +866,106 @@ async function main() {
862
866
  process.on('SIGINT', closeServer);
863
867
  process.on('SIGTERM', closeServer);
864
868
  return;
869
+ } else if (normalizedCommand === 'harmony-audit') {
870
+ const { runHarmonyAudit } = require('../src/harmony/cli');
871
+ await runHarmonyAudit(options);
872
+ process.exit(0);
873
+ } else if (normalizedCommand === 'harmony-sync') {
874
+ const { previewHarmonySync, applyHarmonySync } = require('../src/harmony/sync');
875
+ const dir = options.dir || process.cwd();
876
+
877
+ if (options.fix) {
878
+ // Apply mode: write files
879
+ const result = applyHarmonySync(dir);
880
+ if (options.json) {
881
+ console.log(JSON.stringify(result, null, 2));
882
+ } else {
883
+ console.log('');
884
+ console.log('\x1b[1m Harmony Sync — Apply\x1b[0m');
885
+ console.log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
886
+ console.log('');
887
+ if (result.applied.length === 0 && result.skipped.length === 0) {
888
+ console.log(' \x1b[32mAll platforms are already in sync. Nothing to apply.\x1b[0m');
889
+ } else {
890
+ for (const item of result.applied) {
891
+ console.log(` \x1b[32m✓\x1b[0m ${item.action.padEnd(8)} ${item.platform.padEnd(12)} ${item.path}`);
892
+ }
893
+ for (const item of result.skipped) {
894
+ const reason = typeof item === 'string' ? item : (item.reason || item.path);
895
+ console.log(` \x1b[33m⚠\x1b[0m skipped ${reason}`);
896
+ }
897
+ console.log('');
898
+ if (result.summary) {
899
+ console.log(` Files: ${result.summary.totalFiles} (${result.summary.creates} created, ${result.summary.patches} patched)`);
900
+ console.log(` Platforms: ${result.summary.platforms.join(', ')}`);
901
+ }
902
+ }
903
+ if (result.warnings && result.warnings.length > 0) {
904
+ console.log('');
905
+ for (const w of result.warnings) {
906
+ console.log(` \x1b[33m⚠\x1b[0m ${w}`);
907
+ }
908
+ }
909
+ console.log('');
910
+ }
911
+ } else {
912
+ // Preview mode (dry run)
913
+ const plan = previewHarmonySync(dir);
914
+ if (options.json) {
915
+ console.log(JSON.stringify(plan, null, 2));
916
+ } else {
917
+ console.log('');
918
+ console.log('\x1b[1m Harmony Sync — Preview\x1b[0m');
919
+ console.log('\x1b[2m ═══════════════════════════════════════\x1b[0m');
920
+ console.log('');
921
+ if (plan.files.length === 0) {
922
+ console.log(' \x1b[32mAll platforms are already in sync. No changes needed.\x1b[0m');
923
+ } else {
924
+ for (const file of plan.files) {
925
+ const actionColor = file.action === 'create' ? '\x1b[32m' : '\x1b[36m';
926
+ console.log(` ${actionColor}${file.action.padEnd(8)}\x1b[0m ${file.platform.padEnd(12)} ${file.path}`);
927
+ if (file.preview) {
928
+ console.log(` \x1b[2m${file.preview}\x1b[0m`);
929
+ }
930
+ }
931
+ console.log('');
932
+ console.log(` Total: ${plan.summary.totalFiles} file(s) — ${plan.summary.creates} create, ${plan.summary.patches} patch`);
933
+ console.log(` Platforms: ${plan.summary.platforms.join(', ')}`);
934
+ if (plan.summary.recommendedTrust) {
935
+ console.log(` Recommended trust: ${plan.summary.recommendedTrust}`);
936
+ }
937
+ console.log('');
938
+ console.log(' Run \x1b[1mnerviq harmony-sync --fix\x1b[0m to apply these changes.');
939
+ }
940
+ if (plan.warnings && plan.warnings.length > 0) {
941
+ console.log('');
942
+ for (const w of plan.warnings) {
943
+ console.log(` \x1b[33m⚠\x1b[0m ${w}`);
944
+ }
945
+ }
946
+ console.log('');
947
+ }
948
+ }
949
+ process.exit(0);
950
+ } else if (normalizedCommand === 'harmony-drift') {
951
+ const { runHarmonyDrift } = require('../src/harmony/cli');
952
+ await runHarmonyDrift(options);
953
+ process.exit(0);
954
+ } else if (normalizedCommand === 'harmony-advise') {
955
+ const { runHarmonyAdvise } = require('../src/harmony/cli');
956
+ await runHarmonyAdvise(options);
957
+ process.exit(0);
958
+ } else if (normalizedCommand === 'harmony-watch') {
959
+ const { runHarmonyWatch } = require('../src/harmony/cli');
960
+ await runHarmonyWatch(options);
961
+ } else if (normalizedCommand === 'harmony-governance') {
962
+ const { runHarmonyGovernance } = require('../src/harmony/cli');
963
+ await runHarmonyGovernance(options);
964
+ process.exit(0);
965
+ } else if (normalizedCommand === 'synergy-report') {
966
+ // Placeholder — synergy report is referenced but may not be implemented yet
967
+ console.log('\n Synergy report: coming soon.\n');
968
+ process.exit(0);
865
969
  } else if (normalizedCommand === 'doctor') {
866
970
  const { runDoctor } = require('../src/doctor');
867
971
  const output = await runDoctor({ dir: options.dir, json: options.json, verbose: options.verbose });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerviq/cli",
3
- "version": "1.2.3",
3
+ "version": "1.2.6",
4
4
  "description": "The intelligent nervous system for AI coding agents — 2,306 checks across 8 platforms and 10 languages. Audit, align, and amplify.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  "test": "node test/run.js",
19
19
  "test:jest": "jest",
20
20
  "test:coverage": "jest --coverage",
21
- "test:all": "npm test && npx jest && node test/check-matrix.js && node test/codex-check-matrix.js && node test/gemini-check-matrix.js && node test/copilot-check-matrix.js && node test/cursor-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/gemini-golden-matrix.js && node test/copilot-golden-matrix.js && node test/cursor-golden-matrix.js",
21
+ "test:all": "npm test && npx jest && node test/check-matrix.js && node test/codex-check-matrix.js && node test/gemini-check-matrix.js && node test/copilot-check-matrix.js && node test/cursor-check-matrix.js && node test/windsurf-check-matrix.js && node test/aider-check-matrix.js && node test/opencode-check-matrix.js && node test/golden-matrix.js && node test/codex-golden-matrix.js && node test/gemini-golden-matrix.js && node test/copilot-golden-matrix.js && node test/cursor-golden-matrix.js && node test/windsurf-golden-matrix.js && node test/aider-golden-matrix.js && node test/opencode-golden-matrix.js",
22
22
  "benchmark:perf": "node tools/benchmark.js",
23
23
  "catalog": "node -e \"const {generateCatalog}=require('./src/catalog');console.log(JSON.stringify(generateCatalog(),null,2))\""
24
24
  },
package/src/activity.js CHANGED
@@ -2,6 +2,10 @@ const fs = require('fs');
2
2
  const os = require('os');
3
3
  const path = require('path');
4
4
  const { version } = require('../package.json');
5
+ const {
6
+ resolveProjectStateReadPath,
7
+ ensureProjectStateDir,
8
+ } = require('./state-paths');
5
9
 
6
10
  /**
7
11
  * Generate a machine-level user identity for audit tracking.
@@ -33,15 +37,11 @@ function timestampId() {
33
37
  }
34
38
 
35
39
  function ensureArtifactDirs(dir) {
36
- const root = path.join(dir, '.claude', 'claudex-setup');
37
- const activityDir = path.join(root, 'activity');
38
- const rollbackDir = path.join(root, 'rollbacks');
39
- const snapshotDir = path.join(root, 'snapshots');
40
- const outcomesDir = path.join(root, 'outcomes');
41
- fs.mkdirSync(activityDir, { recursive: true });
42
- fs.mkdirSync(rollbackDir, { recursive: true });
43
- fs.mkdirSync(snapshotDir, { recursive: true });
44
- fs.mkdirSync(outcomesDir, { recursive: true });
40
+ const root = ensureProjectStateDir(dir);
41
+ const activityDir = ensureProjectStateDir(dir, 'activity');
42
+ const rollbackDir = ensureProjectStateDir(dir, 'rollbacks');
43
+ const snapshotDir = ensureProjectStateDir(dir, 'snapshots');
44
+ const outcomesDir = ensureProjectStateDir(dir, 'outcomes');
45
45
  return { root, activityDir, rollbackDir, snapshotDir, outcomesDir };
46
46
  }
47
47
 
@@ -161,7 +161,7 @@ function updateSnapshotIndex(snapshotDir, record) {
161
161
  }
162
162
 
163
163
  /**
164
- * Write a normalized snapshot artifact to .claude/claudex-setup/snapshots/ and update the index.
164
+ * Write a normalized snapshot artifact to .nerviq/snapshots/ and update the index.
165
165
  * @param {string} dir - Project root directory.
166
166
  * @param {string} snapshotKind - Snapshot type ('audit', 'benchmark', 'governance', 'augment', 'suggest-only').
167
167
  * @param {Object} payload - Full result payload to persist.
@@ -208,7 +208,7 @@ function writeSnapshotArtifact(dir, snapshotKind, payload, meta = {}) {
208
208
  }
209
209
 
210
210
  function readSnapshotIndex(dir) {
211
- const indexPath = path.join(dir, '.claude', 'claudex-setup', 'snapshots', 'index.json');
211
+ const indexPath = resolveProjectStateReadPath(dir, 'snapshots', 'index.json');
212
212
  if (!fs.existsSync(indexPath)) return [];
213
213
  try {
214
214
  const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
@@ -228,7 +228,11 @@ function getHistory(dir, limit = 20) {
228
228
  const entries = readSnapshotIndex(dir);
229
229
  return entries
230
230
  .filter(e => e.snapshotKind === 'audit')
231
- .sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
231
+ .sort((a, b) => {
232
+ const dateDiff = new Date(b.createdAt) - new Date(a.createdAt);
233
+ if (dateDiff !== 0) return dateDiff;
234
+ return (b.id || '').localeCompare(a.id || '');
235
+ })
232
236
  .slice(0, limit);
233
237
  }
234
238
 
@@ -344,7 +348,7 @@ function exportTrendReport(dir) {
344
348
  }
345
349
 
346
350
  function readOutcomeIndex(dir) {
347
- const indexPath = path.join(dir, '.claude', 'claudex-setup', 'outcomes', 'index.json');
351
+ const indexPath = resolveProjectStateReadPath(dir, 'outcomes', 'index.json');
348
352
  if (!fs.existsSync(indexPath)) return [];
349
353
  try {
350
354
  const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
@@ -4,7 +4,7 @@
4
4
  * Adapts the shared activity/snapshot backend for Aider platform.
5
5
  * Provides: history, compare, trend, watch, feedback, insights.
6
6
  *
7
- * Aider snapshots stored in .claude/claudex-setup/snapshots/ filtered by platform='aider'.
7
+ * Aider snapshots stored in .nerviq/snapshots/ (legacy: .claude/claudex-setup/snapshots/) filtered by platform='aider'.
8
8
  */
9
9
 
10
10
  const path = require('path');
package/src/audit.js CHANGED
@@ -1279,7 +1279,25 @@ async function audit(options) {
1279
1279
  console.log('');
1280
1280
  }
1281
1281
 
1282
- console.log(colorize(` Backed by CLAUDEX research and evidence for ${spec.platformLabel}`, 'dim'));
1282
+ // Cross-platform synergy hint
1283
+ try {
1284
+ const { detectActivePlatforms } = require('./harmony/canon');
1285
+ const { analyzeCompensation } = require('./synergy/compensation');
1286
+ const { calculateSynergyScore } = require('./synergy/ranking');
1287
+ const detected = detectActivePlatforms(options.dir);
1288
+ const activePlatforms = (detected || []).filter(p => p.detected).map(p => p.platform);
1289
+ if (activePlatforms.length >= 2) {
1290
+ const comp = analyzeCompensation(activePlatforms);
1291
+ const synergyScore = calculateSynergyScore(activePlatforms);
1292
+ console.log(colorize(` Cross-platform synergy: ${activePlatforms.length} platforms detected`, 'blue'));
1293
+ console.log(colorize(` Platforms: ${activePlatforms.join(', ')}`, 'dim'));
1294
+ console.log(colorize(` Compensations: ${comp.compensations.length} | Gaps: ${comp.uncoveredGaps.length}`, 'dim'));
1295
+ console.log(colorize(` Run: npx nerviq harmony-audit for full cross-platform analysis`, 'dim'));
1296
+ console.log('');
1297
+ }
1298
+ } catch { /* synergy display is optional */ }
1299
+
1300
+ console.log(colorize(` Backed by NERVIQ research and evidence for ${spec.platformLabel}`, 'dim'));
1283
1301
  console.log(colorize(' https://github.com/nerviq/nerviq', 'dim'));
1284
1302
  console.log('');
1285
1303
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "synced_from": "claudex",
3
- "synced_at": "2026-04-02T15:12:04Z",
4
- "total_items": 1107,
3
+ "synced_at": "2026-04-05T17:08:58Z",
4
+ "total_items": 1118,
5
5
  "tested": 948,
6
- "last_id": 1157
6
+ "last_id": 1137
7
7
  }
@@ -5,7 +5,7 @@
5
5
  * Provides: history, compare, trend, watch, feedback, insights.
6
6
  *
7
7
  * Codex snapshots are stored alongside Claude snapshots in
8
- * .claude/claudex-setup/snapshots/ but filtered by platform='codex'.
8
+ * .nerviq/snapshots/ (legacy: .claude/claudex-setup/snapshots/) but filtered by platform='codex'.
9
9
  */
10
10
 
11
11
  const path = require('path');
@@ -166,7 +166,7 @@ function applyPatch(dir, filePath, patchFn, options = {}) {
166
166
  }
167
167
 
168
168
  // Backup + write
169
- const backupPath = fullPath + '.claudex-backup';
169
+ const backupPath = fullPath + '.nerviq-backup';
170
170
  fs.writeFileSync(backupPath, original, 'utf8');
171
171
  fs.writeFileSync(fullPath, patched, 'utf8');
172
172
 
@@ -1,8 +1,9 @@
1
1
  const os = require('os');
2
- const path = require('path');
3
- const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
4
- const { attachSourceUrls } = require('../source-urls');
5
- const { buildSupplementalChecks } = require('../supplemental-checks');
2
+ const path = require('path');
3
+ const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
4
+ const { attachSourceUrls } = require('../source-urls');
5
+ const { buildSupplementalChecks } = require('../supplemental-checks');
6
+ const { resolveProjectStateReadPath } = require('../state-paths');
6
7
 
7
8
  const CODEX_SUPPLEMENTAL_SOURCE_URLS = {
8
9
  'testing-strategy': 'https://developers.openai.com/codex/cli',
@@ -3097,16 +3098,15 @@ const CODEX_TECHNIQUES = {
3097
3098
  // CP-08: New checks (O. Repeat-Usage Hygiene)
3098
3099
  // =============================================
3099
3100
 
3100
- codexSnapshotRetention: {
3101
- id: 'CX-O01',
3102
- name: 'At least one prior audit snapshot exists for repeat-usage',
3103
- check: (ctx) => {
3104
- const snapshotDir = path.join(ctx.dir, '.claude', 'claudex-setup', 'snapshots');
3105
- try {
3106
- const indexPath = path.join(snapshotDir, 'index.json');
3107
- const fs = require('fs');
3108
- if (!fs.existsSync(indexPath)) return null; // No snapshots yet, not a failure
3109
- const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
3101
+ codexSnapshotRetention: {
3102
+ id: 'CX-O01',
3103
+ name: 'At least one prior audit snapshot exists for repeat-usage',
3104
+ check: (ctx) => {
3105
+ try {
3106
+ const indexPath = resolveProjectStateReadPath(ctx.dir, 'snapshots', 'index.json');
3107
+ const fs = require('fs');
3108
+ if (!fs.existsSync(indexPath)) return null; // No snapshots yet, not a failure
3109
+ const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
3110
3110
  return Array.isArray(entries) && entries.length > 0;
3111
3111
  } catch {
3112
3112
  return null;
@@ -3121,16 +3121,15 @@ const CODEX_TECHNIQUES = {
3121
3121
  line: () => null,
3122
3122
  },
3123
3123
 
3124
- codexFeedbackLoopHealth: {
3125
- id: 'CX-O02',
3126
- name: 'Feedback loop is functional when feedback has been submitted',
3127
- check: (ctx) => {
3128
- const outcomesDir = path.join(ctx.dir, '.claude', 'claudex-setup', 'outcomes');
3129
- try {
3130
- const indexPath = path.join(outcomesDir, 'index.json');
3131
- const fs = require('fs');
3132
- if (!fs.existsSync(indexPath)) return null; // No feedback yet, not a failure
3133
- const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
3124
+ codexFeedbackLoopHealth: {
3125
+ id: 'CX-O02',
3126
+ name: 'Feedback loop is functional when feedback has been submitted',
3127
+ check: (ctx) => {
3128
+ try {
3129
+ const indexPath = resolveProjectStateReadPath(ctx.dir, 'outcomes', 'index.json');
3130
+ const fs = require('fs');
3131
+ if (!fs.existsSync(indexPath)) return null; // No feedback yet, not a failure
3132
+ const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
3134
3133
  return Array.isArray(entries) && entries.length > 0;
3135
3134
  } catch {
3136
3135
  return null;
@@ -3145,16 +3144,15 @@ const CODEX_TECHNIQUES = {
3145
3144
  line: () => null,
3146
3145
  },
3147
3146
 
3148
- codexTrendDataAvailability: {
3149
- id: 'CX-O03',
3150
- name: 'Trend data is computable (2+ snapshots with compatible schemas)',
3151
- check: (ctx) => {
3152
- const snapshotDir = path.join(ctx.dir, '.claude', 'claudex-setup', 'snapshots');
3153
- try {
3154
- const indexPath = path.join(snapshotDir, 'index.json');
3155
- const fs = require('fs');
3156
- if (!fs.existsSync(indexPath)) return null;
3157
- const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
3147
+ codexTrendDataAvailability: {
3148
+ id: 'CX-O03',
3149
+ name: 'Trend data is computable (2+ snapshots with compatible schemas)',
3150
+ check: (ctx) => {
3151
+ try {
3152
+ const indexPath = resolveProjectStateReadPath(ctx.dir, 'snapshots', 'index.json');
3153
+ const fs = require('fs');
3154
+ if (!fs.existsSync(indexPath)) return null;
3155
+ const entries = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
3158
3156
  const audits = (Array.isArray(entries) ? entries : []).filter(e => e.snapshotKind === 'audit');
3159
3157
  return audits.length >= 2;
3160
3158
  } catch {
@@ -196,7 +196,7 @@ function applyPatch(dir, filePath, patchFn, options = {}) {
196
196
  return { success: true, reason: 'dry run', preview, unchanged: false };
197
197
  }
198
198
 
199
- const backupPath = fullPath + '.claudex-backup';
199
+ const backupPath = fullPath + '.nerviq-backup';
200
200
  fs.writeFileSync(backupPath, original, 'utf8');
201
201
  fs.writeFileSync(fullPath, patched, 'utf8');
202
202
 
@@ -10,6 +10,7 @@
10
10
  const path = require('path');
11
11
  const { COPILOT_DOMAIN_PACKS } = require('./domain-packs');
12
12
  const { COPILOT_MCP_PACKS } = require('./mcp-packs');
13
+ const { resolveProjectStateReadPath } = require('../state-paths');
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // 1. Multi-Pack Composition Engine
@@ -388,7 +389,7 @@ const GATE_THRESHOLDS = {
388
389
 
389
390
  function getCopilotHistory(dir, limit = 20) {
390
391
  const fs = require('fs');
391
- const snapshotDir = path.join(dir, '.claude', 'claudex-setup', 'snapshots');
392
+ const snapshotDir = resolveProjectStateReadPath(dir, 'snapshots');
392
393
  try {
393
394
  const files = fs.readdirSync(snapshotDir)
394
395
  .filter(f => f.endsWith('.json'))
@@ -201,7 +201,7 @@ function applyPatch(dir, filePath, patchFn, options = {}) {
201
201
  return { success: true, reason: 'dry run', preview, unchanged: false };
202
202
  }
203
203
 
204
- const backupPath = fullPath + '.claudex-backup';
204
+ const backupPath = fullPath + '.nerviq-backup';
205
205
  fs.writeFileSync(backupPath, original, 'utf8');
206
206
  fs.writeFileSync(fullPath, patched, 'utf8');
207
207
 
@@ -10,6 +10,7 @@
10
10
  const path = require('path');
11
11
  const { CURSOR_DOMAIN_PACKS } = require('./domain-packs');
12
12
  const { CURSOR_MCP_PACKS } = require('./mcp-packs');
13
+ const { resolveProjectStateReadPath } = require('../state-paths');
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // 1. Multi-Pack Composition Engine (with MDC awareness)
@@ -406,7 +407,7 @@ const GATE_THRESHOLDS = {
406
407
 
407
408
  function getCursorHistory(dir, limit = 20) {
408
409
  const fs = require('fs');
409
- const snapshotDir = path.join(dir, '.claude', 'claudex-setup', 'snapshots');
410
+ const snapshotDir = resolveProjectStateReadPath(dir, 'snapshots');
410
411
  try {
411
412
  const files = fs.readdirSync(snapshotDir)
412
413
  .filter(f => f.endsWith('.json'))
package/src/feedback.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const readline = require('readline');
4
+ const { ensureProjectStateDir, resolveProjectStateReadPath } = require('./state-paths');
4
5
 
5
6
  let lastTimestamp = '';
6
7
  let counter = 0;
@@ -17,9 +18,11 @@ function timestampId() {
17
18
  }
18
19
 
19
20
  function ensureFeedbackDir(dir) {
20
- const feedbackDir = path.join(dir, '.claude', 'claudex-setup', 'feedback');
21
- fs.mkdirSync(feedbackDir, { recursive: true });
22
- return feedbackDir;
21
+ return ensureProjectStateDir(dir, 'feedback');
22
+ }
23
+
24
+ function resolveFeedbackDir(dir) {
25
+ return resolveProjectStateReadPath(dir, 'feedback');
23
26
  }
24
27
 
25
28
  function writeJson(filePath, payload) {
@@ -47,8 +50,10 @@ function saveFeedback(dir, payload) {
47
50
  }
48
51
 
49
52
  function getFeedbackSummary(dir) {
50
- const feedbackDir = ensureFeedbackDir(dir);
51
- const files = fs.readdirSync(feedbackDir).filter((name) => name.endsWith('.json'));
53
+ const feedbackDir = resolveFeedbackDir(dir);
54
+ const files = fs.existsSync(feedbackDir)
55
+ ? fs.readdirSync(feedbackDir).filter((name) => name.endsWith('.json'))
56
+ : [];
52
57
  const entries = [];
53
58
 
54
59
  for (const file of files) {
@@ -107,7 +112,7 @@ async function collectFeedback(dir, options = {}) {
107
112
  helpful: 0,
108
113
  unhelpful: 0,
109
114
  entries: [],
110
- relativeDir: path.relative(dir, ensureFeedbackDir(dir)),
115
+ relativeDir: path.relative(dir, resolveFeedbackDir(dir)),
111
116
  };
112
117
  }
113
118
 
@@ -119,7 +124,7 @@ async function collectFeedback(dir, options = {}) {
119
124
  helpful: 0,
120
125
  unhelpful: 0,
121
126
  entries: [],
122
- relativeDir: path.relative(dir, ensureFeedbackDir(dir)),
127
+ relativeDir: path.relative(dir, resolveFeedbackDir(dir)),
123
128
  };
124
129
  }
125
130
 
@@ -161,7 +166,7 @@ async function collectFeedback(dir, options = {}) {
161
166
  helpful,
162
167
  unhelpful,
163
168
  entries,
164
- relativeDir: path.relative(dir, ensureFeedbackDir(dir)),
169
+ relativeDir: path.relative(dir, resolveFeedbackDir(dir)),
165
170
  summary: getFeedbackSummary(dir),
166
171
  };
167
172
  }
@@ -5,7 +5,7 @@
5
5
  * Provides: history, compare, trend, feedback, insights.
6
6
  *
7
7
  * Gemini snapshots are stored alongside Claude snapshots in
8
- * .claude/claudex-setup/snapshots/ but filtered by platform='gemini'.
8
+ * .nerviq/snapshots/ (legacy: .claude/claudex-setup/snapshots/) but filtered by platform='gemini'.
9
9
  */
10
10
 
11
11
  const path = require('path');
@@ -187,7 +187,7 @@ function applyPatch(dir, filePath, patchFn, options = {}) {
187
187
  }
188
188
 
189
189
  // Backup + write
190
- const backupPath = fullPath + '.claudex-backup';
190
+ const backupPath = fullPath + '.nerviq-backup';
191
191
  fs.writeFileSync(backupPath, original, 'utf8');
192
192
  fs.writeFileSync(fullPath, patched, 'utf8');
193
193
 
@@ -10,6 +10,7 @@
10
10
  const path = require('path');
11
11
  const { GEMINI_DOMAIN_PACKS } = require('./domain-packs');
12
12
  const { GEMINI_MCP_PACKS } = require('./mcp-packs');
13
+ const { resolveGeminiStateReadPath } = require('../state-paths');
13
14
 
14
15
  // ---------------------------------------------------------------------------
15
16
  // 1. Multi-Pack Composition Engine
@@ -666,14 +667,14 @@ const GATE_THRESHOLDS = {
666
667
  };
667
668
 
668
669
  /**
669
- * Read Gemini audit snapshot history from the local .gemini/.claudex/ directory.
670
+ * Read Gemini audit snapshot history from the local .gemini/.nerviq/ directory.
670
671
  * @param {string} dir - Project directory
671
672
  * @param {number} limit - Max snapshots to read
672
673
  * @returns {object[]} Array of snapshot objects
673
674
  */
674
675
  function getGeminiHistory(dir, limit = 20) {
675
676
  const fs = require('fs');
676
- const snapshotDir = path.join(dir, '.gemini', '.claudex', 'snapshots');
677
+ const snapshotDir = resolveGeminiStateReadPath(dir, 'snapshots');
677
678
  try {
678
679
  const files = fs.readdirSync(snapshotDir)
679
680
  .filter(f => f.endsWith('.json'))
@@ -14,10 +14,11 @@
14
14
  const os = require('os');
15
15
  const path = require('path');
16
16
  const { GeminiProjectContext } = require('./context');
17
- const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
18
- const { attachSourceUrls } = require('../source-urls');
19
- const { buildSupplementalChecks } = require('../supplemental-checks');
20
- const { buildStackChecks } = require('../stack-checks');
17
+ const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
18
+ const { attachSourceUrls } = require('../source-urls');
19
+ const { buildSupplementalChecks } = require('../supplemental-checks');
20
+ const { buildStackChecks } = require('../stack-checks');
21
+ const { resolveProjectStateReadPath } = require('../state-paths');
21
22
 
22
23
  const GEMINI_SUPPLEMENTAL_SOURCE_URLS = {
23
24
  'testing-strategy': 'https://geminicli.com/docs/get-started/',
@@ -2062,23 +2063,23 @@ const GEMINI_TECHNIQUES = {
2062
2063
  },
2063
2064
 
2064
2065
  // CP-08: O. Repeat-Usage Hygiene (3 checks)
2065
- geminiSnapshotRetention: {
2066
- id: 'GM-O01', name: 'At least one prior audit snapshot exists',
2067
- check: (ctx) => { try { const fs = require('fs'); const p = require('path').join(ctx.dir, '.claude', 'claudex-setup', 'snapshots', 'index.json'); if (!fs.existsSync(p)) return null; const e = JSON.parse(fs.readFileSync(p, 'utf8')); return Array.isArray(e) && e.length > 0; } catch { return null; } },
2066
+ geminiSnapshotRetention: {
2067
+ id: 'GM-O01', name: 'At least one prior audit snapshot exists',
2068
+ check: (ctx) => { try { const fs = require('fs'); const p = resolveProjectStateReadPath(ctx.dir, 'snapshots', 'index.json'); if (!fs.existsSync(p)) return null; const e = JSON.parse(fs.readFileSync(p, 'utf8')); return Array.isArray(e) && e.length > 0; } catch { return null; } },
2068
2069
  impact: 'medium', rating: 3, category: 'repeat-usage',
2069
2070
  fix: 'Run `npx nerviq --platform gemini --snapshot` to save your first snapshot.',
2070
2071
  template: null, file: () => null, line: () => null,
2071
2072
  },
2072
- geminiFeedbackLoopHealth: {
2073
- id: 'GM-O02', name: 'Feedback loop functional when feedback submitted',
2074
- check: (ctx) => { try { const fs = require('fs'); const p = require('path').join(ctx.dir, '.claude', 'claudex-setup', 'outcomes', 'index.json'); if (!fs.existsSync(p)) return null; const e = JSON.parse(fs.readFileSync(p, 'utf8')); return Array.isArray(e) && e.length > 0; } catch { return null; } },
2073
+ geminiFeedbackLoopHealth: {
2074
+ id: 'GM-O02', name: 'Feedback loop functional when feedback submitted',
2075
+ check: (ctx) => { try { const fs = require('fs'); const p = resolveProjectStateReadPath(ctx.dir, 'outcomes', 'index.json'); if (!fs.existsSync(p)) return null; const e = JSON.parse(fs.readFileSync(p, 'utf8')); return Array.isArray(e) && e.length > 0; } catch { return null; } },
2075
2076
  impact: 'medium', rating: 3, category: 'repeat-usage',
2076
2077
  fix: 'Submit feedback using `npx nerviq --platform gemini feedback`.',
2077
2078
  template: null, file: () => null, line: () => null,
2078
2079
  },
2079
- geminiTrendDataAvailability: {
2080
- id: 'GM-O03', name: 'Trend data computable (2+ snapshots)',
2081
- check: (ctx) => { try { const fs = require('fs'); const p = require('path').join(ctx.dir, '.claude', 'claudex-setup', 'snapshots', 'index.json'); if (!fs.existsSync(p)) return null; const e = JSON.parse(fs.readFileSync(p, 'utf8')); return (Array.isArray(e) ? e : []).filter(x => x.snapshotKind === 'audit').length >= 2; } catch { return null; } },
2080
+ geminiTrendDataAvailability: {
2081
+ id: 'GM-O03', name: 'Trend data computable (2+ snapshots)',
2082
+ check: (ctx) => { try { const fs = require('fs'); const p = resolveProjectStateReadPath(ctx.dir, 'snapshots', 'index.json'); if (!fs.existsSync(p)) return null; const e = JSON.parse(fs.readFileSync(p, 'utf8')); return (Array.isArray(e) ? e : []).filter(x => x.snapshotKind === 'audit').length >= 2; } catch { return null; } },
2082
2083
  impact: 'low', rating: 2, category: 'repeat-usage',
2083
2084
  fix: 'Run at least 2 audits with --snapshot for trend tracking.',
2084
2085
  template: null, file: () => null, line: () => null,