@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 +105 -1
- package/package.json +2 -2
- package/src/activity.js +17 -13
- package/src/aider/activity.js +1 -1
- package/src/audit.js +19 -1
- package/src/claudex-sync.json +3 -3
- package/src/codex/activity.js +1 -1
- package/src/codex/patch.js +1 -1
- package/src/codex/techniques.js +32 -34
- package/src/copilot/patch.js +1 -1
- package/src/copilot/premium.js +2 -1
- package/src/cursor/patch.js +1 -1
- package/src/cursor/premium.js +2 -1
- package/src/feedback.js +13 -8
- package/src/gemini/activity.js +1 -1
- package/src/gemini/patch.js +1 -1
- package/src/gemini/premium.js +3 -2
- package/src/gemini/techniques.js +14 -13
- package/src/harmony/advisor.js +15 -65
- package/src/harmony/audit.js +3 -0
- package/src/harmony/canon.js +122 -1
- package/src/harmony/cli.js +21 -0
- package/src/harmony/drift.js +2 -2
- package/src/harmony/memory.js +9 -8
- package/src/harmony/sync.js +18 -1
- package/src/insights.js +1 -1
- package/src/opencode/activity.js +1 -1
- package/src/opencode/patch.js +1 -1
- package/src/opencode/techniques.js +15 -13
- package/src/shared/capabilities.js +194 -0
- package/src/state-paths.js +85 -0
- package/src/synergy/compensation.js +1 -20
- package/src/synergy/learning.js +20 -5
- package/src/synergy/routing.js +8 -14
- package/src/techniques.js +3231 -2929
- package/src/windsurf/patch.js +1 -1
- package/src/windsurf/premium.js +2 -1
package/src/harmony/advisor.js
CHANGED
|
@@ -8,69 +8,9 @@
|
|
|
8
8
|
* Zero external dependencies - imports only from sibling/parent modules.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
// ─── Platform Strength Matrix (
|
|
12
|
-
|
|
13
|
-
const PLATFORM_STRENGTHS =
|
|
14
|
-
claude: {
|
|
15
|
-
label: 'Claude Code',
|
|
16
|
-
reasoning: 5,
|
|
17
|
-
refactoring: 5,
|
|
18
|
-
ci: 2,
|
|
19
|
-
ide: 2,
|
|
20
|
-
sandbox: 3,
|
|
21
|
-
inline: 3,
|
|
22
|
-
context: 4,
|
|
23
|
-
automation: 4,
|
|
24
|
-
},
|
|
25
|
-
codex: {
|
|
26
|
-
label: 'Codex',
|
|
27
|
-
reasoning: 4,
|
|
28
|
-
ci: 5,
|
|
29
|
-
cloud: 5,
|
|
30
|
-
ide: 3,
|
|
31
|
-
sandbox: 4,
|
|
32
|
-
refactoring: 4,
|
|
33
|
-
inline: 2,
|
|
34
|
-
context: 3,
|
|
35
|
-
automation: 4,
|
|
36
|
-
},
|
|
37
|
-
gemini: {
|
|
38
|
-
label: 'Gemini CLI',
|
|
39
|
-
reasoning: 4,
|
|
40
|
-
context: 5,
|
|
41
|
-
sandbox: 5,
|
|
42
|
-
ci: 3,
|
|
43
|
-
ide: 3,
|
|
44
|
-
refactoring: 3,
|
|
45
|
-
inline: 2,
|
|
46
|
-
cloud: 4,
|
|
47
|
-
automation: 3,
|
|
48
|
-
},
|
|
49
|
-
copilot: {
|
|
50
|
-
label: 'GitHub Copilot',
|
|
51
|
-
inline: 5,
|
|
52
|
-
'cloud-agent': 4,
|
|
53
|
-
ide: 4,
|
|
54
|
-
ci: 4,
|
|
55
|
-
governance: 3,
|
|
56
|
-
reasoning: 3,
|
|
57
|
-
refactoring: 3,
|
|
58
|
-
context: 3,
|
|
59
|
-
automation: 3,
|
|
60
|
-
},
|
|
61
|
-
cursor: {
|
|
62
|
-
label: 'Cursor',
|
|
63
|
-
ide: 5,
|
|
64
|
-
ui: 5,
|
|
65
|
-
background: 4,
|
|
66
|
-
automation: 4,
|
|
67
|
-
reasoning: 3,
|
|
68
|
-
refactoring: 3,
|
|
69
|
-
inline: 4,
|
|
70
|
-
context: 3,
|
|
71
|
-
ci: 2,
|
|
72
|
-
},
|
|
73
|
-
};
|
|
11
|
+
// ─── Platform Strength Matrix (canonical source: shared/capabilities.js) ─────
|
|
12
|
+
|
|
13
|
+
const { PLATFORM_CAPABILITIES: PLATFORM_STRENGTHS } = require('../shared/capabilities');
|
|
74
14
|
|
|
75
15
|
// ─── Task-type to platform-strength mapping ───────────────────────────────────
|
|
76
16
|
|
|
@@ -82,7 +22,7 @@ const TASK_TYPE_PROFILES = {
|
|
|
82
22
|
},
|
|
83
23
|
'ci-review': {
|
|
84
24
|
label: 'CI / Async Review',
|
|
85
|
-
requiredStrengths: { ci: 0.
|
|
25
|
+
requiredStrengths: { ci: 0.4, cloudTasks: 0.3, async: 0.2, automation: 0.1 },
|
|
86
26
|
description: 'Asynchronous code review and CI-integrated workflows.',
|
|
87
27
|
},
|
|
88
28
|
'ui-work': {
|
|
@@ -97,7 +37,7 @@ const TASK_TYPE_PROFILES = {
|
|
|
97
37
|
},
|
|
98
38
|
'infrastructure': {
|
|
99
39
|
label: 'Infrastructure',
|
|
100
|
-
requiredStrengths: { sandbox: 0.4,
|
|
40
|
+
requiredStrengths: { sandbox: 0.4, cloudTasks: 0.3, reasoning: 0.3 },
|
|
101
41
|
description: 'Infrastructure, DevOps, and sandbox-heavy workflows.',
|
|
102
42
|
},
|
|
103
43
|
'refactoring': {
|
|
@@ -115,6 +55,16 @@ const TASK_TYPE_PROFILES = {
|
|
|
115
55
|
requiredStrengths: { reasoning: 0.3, automation: 0.3, ide: 0.2, refactoring: 0.2 },
|
|
116
56
|
description: 'Starting new projects or major new features from scratch.',
|
|
117
57
|
},
|
|
58
|
+
'harness-optimization': {
|
|
59
|
+
label: 'Config Optimization',
|
|
60
|
+
requiredStrengths: { governance: 0.4, automation: 0.3, reasoning: 0.3 },
|
|
61
|
+
description: 'Optimizing AI coding agent configuration and harness settings.',
|
|
62
|
+
},
|
|
63
|
+
'phased-migration': {
|
|
64
|
+
label: 'Phased Migration',
|
|
65
|
+
requiredStrengths: { reasoning: 0.4, refactoring: 0.3, architecture: 0.3 },
|
|
66
|
+
description: 'Multi-phase migrations requiring sequential execution with validation gates.',
|
|
67
|
+
},
|
|
118
68
|
};
|
|
119
69
|
|
|
120
70
|
// ─── Scoring ──────────────────────────────────────────────────────────────────
|
package/src/harmony/audit.js
CHANGED
package/src/harmony/canon.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Reads ALL platform config files from a single project and builds a unified
|
|
5
5
|
* understanding of instructions, MCP servers, trust posture, and governance
|
|
6
|
-
* across Claude, Codex, Gemini, Copilot, and
|
|
6
|
+
* across Claude, Codex, Gemini, Copilot, Cursor, Windsurf, Aider, and OpenCode.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const fs = require('fs');
|
|
@@ -13,10 +13,17 @@ const { CodexProjectContext } = require('../codex/context');
|
|
|
13
13
|
const { GeminiProjectContext } = require('../gemini/context');
|
|
14
14
|
const { CopilotProjectContext } = require('../copilot/context');
|
|
15
15
|
const { CursorProjectContext } = require('../cursor/context');
|
|
16
|
+
const { WindsurfProjectContext } = require('../windsurf/context');
|
|
17
|
+
const { AiderProjectContext } = require('../aider/context');
|
|
18
|
+
const { OpenCodeProjectContext } = require('../opencode/context');
|
|
16
19
|
const { getCodexGovernanceSummary } = require('../codex/governance');
|
|
17
20
|
const { getGeminiGovernanceSummary } = require('../gemini/governance');
|
|
18
21
|
const { getCopilotGovernanceSummary } = require('../copilot/governance');
|
|
19
22
|
const { getCursorGovernanceSummary } = require('../cursor/governance');
|
|
23
|
+
const { getWindsurfGovernanceSummary } = require('../windsurf/governance');
|
|
24
|
+
const { getAiderGovernanceSummary } = require('../aider/governance');
|
|
25
|
+
const { getOpenCodeGovernanceSummary } = require('../opencode/governance');
|
|
26
|
+
const { tryParseJsonc } = require('../opencode/config-parser');
|
|
20
27
|
|
|
21
28
|
// ─── Platform detection signatures ──────────────────────────────────────────
|
|
22
29
|
|
|
@@ -96,6 +103,52 @@ const PLATFORM_SIGNATURES = {
|
|
|
96
103
|
rulesDir: '.cursor/rules',
|
|
97
104
|
hooksDir: null,
|
|
98
105
|
},
|
|
106
|
+
windsurf: {
|
|
107
|
+
label: 'Windsurf',
|
|
108
|
+
detect: (dir) => {
|
|
109
|
+
try {
|
|
110
|
+
if (fs.existsSync(path.join(dir, '.windsurfrules'))) return true;
|
|
111
|
+
if (fs.existsSync(path.join(dir, '.windsurf'))) return true;
|
|
112
|
+
return false;
|
|
113
|
+
} catch { return false; }
|
|
114
|
+
},
|
|
115
|
+
instructionFiles: ['.windsurfrules'],
|
|
116
|
+
configFiles: ['.windsurfrules', '.windsurf/mcp.json', '.cascadeignore'],
|
|
117
|
+
mcpFiles: ['.windsurf/mcp.json'],
|
|
118
|
+
rulesDir: '.windsurf/rules',
|
|
119
|
+
hooksDir: '.windsurf/workflows',
|
|
120
|
+
},
|
|
121
|
+
aider: {
|
|
122
|
+
label: 'Aider',
|
|
123
|
+
detect: (dir) => {
|
|
124
|
+
try {
|
|
125
|
+
if (fs.existsSync(path.join(dir, '.aider.conf.yml'))) return true;
|
|
126
|
+
if (fs.existsSync(path.join(dir, '.aiderignore'))) return true;
|
|
127
|
+
return false;
|
|
128
|
+
} catch { return false; }
|
|
129
|
+
},
|
|
130
|
+
instructionFiles: ['CONVENTIONS.md', '.aider.conventions.md'],
|
|
131
|
+
configFiles: ['.aider.conf.yml', '.aider.model.settings.yml', '.aiderignore'],
|
|
132
|
+
mcpFiles: [],
|
|
133
|
+
rulesDir: null,
|
|
134
|
+
hooksDir: null,
|
|
135
|
+
},
|
|
136
|
+
opencode: {
|
|
137
|
+
label: 'OpenCode',
|
|
138
|
+
detect: (dir) => {
|
|
139
|
+
try {
|
|
140
|
+
if (fs.existsSync(path.join(dir, 'opencode.json'))) return true;
|
|
141
|
+
if (fs.existsSync(path.join(dir, 'opencode.jsonc'))) return true;
|
|
142
|
+
if (fs.existsSync(path.join(dir, '.opencode'))) return true;
|
|
143
|
+
return false;
|
|
144
|
+
} catch { return false; }
|
|
145
|
+
},
|
|
146
|
+
instructionFiles: ['AGENTS.md', 'CLAUDE.md'],
|
|
147
|
+
configFiles: ['opencode.json', 'opencode.jsonc'],
|
|
148
|
+
mcpFiles: ['opencode.json', 'opencode.jsonc'],
|
|
149
|
+
rulesDir: null,
|
|
150
|
+
hooksDir: null,
|
|
151
|
+
},
|
|
99
152
|
};
|
|
100
153
|
|
|
101
154
|
// ─── Context builders per platform ──────────────────────────────────────────
|
|
@@ -106,6 +159,9 @@ const CONTEXT_BUILDERS = {
|
|
|
106
159
|
gemini: (dir) => new GeminiProjectContext(dir),
|
|
107
160
|
copilot: (dir) => new CopilotProjectContext(dir),
|
|
108
161
|
cursor: (dir) => new CursorProjectContext(dir),
|
|
162
|
+
windsurf: (dir) => new WindsurfProjectContext(dir),
|
|
163
|
+
aider: (dir) => new AiderProjectContext(dir),
|
|
164
|
+
opencode: (dir) => new OpenCodeProjectContext(dir),
|
|
109
165
|
};
|
|
110
166
|
|
|
111
167
|
const GOVERNANCE_GETTERS = {
|
|
@@ -113,6 +169,9 @@ const GOVERNANCE_GETTERS = {
|
|
|
113
169
|
gemini: getGeminiGovernanceSummary,
|
|
114
170
|
copilot: getCopilotGovernanceSummary,
|
|
115
171
|
cursor: getCursorGovernanceSummary,
|
|
172
|
+
windsurf: getWindsurfGovernanceSummary,
|
|
173
|
+
aider: getAiderGovernanceSummary,
|
|
174
|
+
opencode: getOpenCodeGovernanceSummary,
|
|
116
175
|
};
|
|
117
176
|
|
|
118
177
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
@@ -133,6 +192,11 @@ function safeParseJson(content) {
|
|
|
133
192
|
}
|
|
134
193
|
}
|
|
135
194
|
|
|
195
|
+
function safeParseJsonc(content) {
|
|
196
|
+
const parsed = tryParseJsonc(content);
|
|
197
|
+
return parsed.ok ? parsed.data : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
136
200
|
/**
|
|
137
201
|
* Extract instruction text from a platform's instruction files.
|
|
138
202
|
* Returns array of { file, content } for each found file.
|
|
@@ -177,6 +241,17 @@ function extractMcpFromMcpJson(content) {
|
|
|
177
241
|
}));
|
|
178
242
|
}
|
|
179
243
|
|
|
244
|
+
function extractMcpFromOpenCodeConfig(content) {
|
|
245
|
+
const json = safeParseJsonc(content);
|
|
246
|
+
if (!json) return [];
|
|
247
|
+
const servers = json.mcpServers || json.servers || {};
|
|
248
|
+
return Object.keys(servers).map(name => ({
|
|
249
|
+
name,
|
|
250
|
+
command: servers[name].command || null,
|
|
251
|
+
args: servers[name].args || [],
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
|
|
180
255
|
/**
|
|
181
256
|
* Extract MCP server names from Gemini settings.json.
|
|
182
257
|
*/
|
|
@@ -205,6 +280,8 @@ function readMcpServers(dir, platform, mcpFiles) {
|
|
|
205
280
|
extracted = extractMcpFromClaudeSettings(content);
|
|
206
281
|
} else if (platform === 'gemini') {
|
|
207
282
|
extracted = extractMcpFromGeminiSettings(content);
|
|
283
|
+
} else if (platform === 'opencode') {
|
|
284
|
+
extracted = extractMcpFromOpenCodeConfig(content);
|
|
208
285
|
} else {
|
|
209
286
|
extracted = extractMcpFromMcpJson(content);
|
|
210
287
|
}
|
|
@@ -268,6 +345,27 @@ function detectTrustPosture(platform, ctx) {
|
|
|
268
345
|
return 'no-sandbox';
|
|
269
346
|
}
|
|
270
347
|
|
|
348
|
+
if (platform === 'windsurf') {
|
|
349
|
+
if (ctx.fileContent('.cascadeignore')) return 'guarded';
|
|
350
|
+
if (ctx.fileContent('.windsurf/mcp.json')) return 'team-managed';
|
|
351
|
+
return 'foreground';
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (platform === 'aider') {
|
|
355
|
+
const config = ctx.configContent ? (ctx.configContent() || '') : '';
|
|
356
|
+
if (/^\s*(yes|yes-always)\s*:\s*true\b/m.test(config)) return 'full-auto';
|
|
357
|
+
if (/^\s*auto-commits\s*:\s*true\b/m.test(config)) return 'git-guarded';
|
|
358
|
+
return 'manual-review';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (platform === 'opencode') {
|
|
362
|
+
const config = ctx.configContent ? (ctx.configContent() || '') : '';
|
|
363
|
+
if (/"\*"\s*:\s*"allow"/.test(config)) return 'unrestricted';
|
|
364
|
+
if (/"(?:bash|edit|task|external_directory)"\s*:\s*"deny"/.test(config)) return 'locked-down';
|
|
365
|
+
if (/"(?:bash|edit|task|external_directory)"\s*:\s*"ask"/.test(config)) return 'prompted';
|
|
366
|
+
return 'standard';
|
|
367
|
+
}
|
|
368
|
+
|
|
271
369
|
return 'unknown';
|
|
272
370
|
}
|
|
273
371
|
|
|
@@ -415,10 +513,33 @@ function buildCanonicalModel(dir) {
|
|
|
415
513
|
governanceSummary[key] = platformDetails[key].governance;
|
|
416
514
|
}
|
|
417
515
|
|
|
516
|
+
// SD2: Adaptive project signals — infrastructure & tooling detection
|
|
517
|
+
const projectSignals = {};
|
|
518
|
+
const signalChecks = [
|
|
519
|
+
{ key: 'docker', label: 'Docker', files: ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', '.dockerignore'] },
|
|
520
|
+
{ key: 'terraform', label: 'Terraform', files: ['main.tf', 'terraform.tf', '.terraform.lock.hcl'] },
|
|
521
|
+
{ key: 'kubernetes', label: 'Kubernetes', files: ['k8s/', 'kubernetes/', 'helm/', 'Chart.yaml'] },
|
|
522
|
+
{ key: 'ci-github', label: 'GitHub Actions', files: ['.github/workflows/'] },
|
|
523
|
+
{ key: 'ci-gitlab', label: 'GitLab CI', files: ['.gitlab-ci.yml'] },
|
|
524
|
+
{ key: 'pytest', label: 'pytest', files: ['pytest.ini', 'conftest.py', 'pyproject.toml'] },
|
|
525
|
+
{ key: 'jest', label: 'Jest', files: ['jest.config.js', 'jest.config.ts', 'jest.config.mjs'] },
|
|
526
|
+
{ key: 'migrations', label: 'DB Migrations', files: ['migrations/', 'alembic/', 'prisma/migrations/', 'db/migrate/'] },
|
|
527
|
+
{ key: 'monorepo', label: 'Monorepo', files: ['pnpm-workspace.yaml', 'lerna.json', 'nx.json', 'turbo.json'] },
|
|
528
|
+
{ key: 'openapi', label: 'OpenAPI', files: ['openapi.yaml', 'openapi.json', 'swagger.yaml', 'swagger.json'] },
|
|
529
|
+
];
|
|
530
|
+
for (const signal of signalChecks) {
|
|
531
|
+
const detected = signal.files.some(f => {
|
|
532
|
+
const full = path.join(dir, f);
|
|
533
|
+
try { return fs.existsSync(full); } catch { return false; }
|
|
534
|
+
});
|
|
535
|
+
if (detected) projectSignals[signal.key] = signal.label;
|
|
536
|
+
}
|
|
537
|
+
|
|
418
538
|
return {
|
|
419
539
|
projectName,
|
|
420
540
|
dir,
|
|
421
541
|
stacks: stacks.map(s => s.key),
|
|
542
|
+
projectSignals,
|
|
422
543
|
activePlatforms: platformKeys.map(key => ({
|
|
423
544
|
platform: key,
|
|
424
545
|
label: platformDetails[key].label,
|
package/src/harmony/cli.js
CHANGED
|
@@ -68,6 +68,27 @@ function collectPlatformAudits(dir) {
|
|
|
68
68
|
if (result) results.push({ platform: 'cursor', ...result });
|
|
69
69
|
} catch (_e) { /* platform not available */ }
|
|
70
70
|
|
|
71
|
+
// Try Windsurf audit
|
|
72
|
+
try {
|
|
73
|
+
const { audit } = require('../audit');
|
|
74
|
+
const result = audit({ dir, silent: true, platform: 'windsurf' });
|
|
75
|
+
if (result) results.push({ platform: 'windsurf', ...result });
|
|
76
|
+
} catch (_e) { /* platform not available */ }
|
|
77
|
+
|
|
78
|
+
// Try Aider audit
|
|
79
|
+
try {
|
|
80
|
+
const { audit } = require('../audit');
|
|
81
|
+
const result = audit({ dir, silent: true, platform: 'aider' });
|
|
82
|
+
if (result) results.push({ platform: 'aider', ...result });
|
|
83
|
+
} catch (_e) { /* platform not available */ }
|
|
84
|
+
|
|
85
|
+
// Try OpenCode audit
|
|
86
|
+
try {
|
|
87
|
+
const { audit } = require('../audit');
|
|
88
|
+
const result = audit({ dir, silent: true, platform: 'opencode' });
|
|
89
|
+
if (result) results.push({ platform: 'opencode', ...result });
|
|
90
|
+
} catch (_e) { /* platform not available */ }
|
|
91
|
+
|
|
71
92
|
return results;
|
|
72
93
|
}
|
|
73
94
|
|
package/src/harmony/drift.js
CHANGED
|
@@ -97,7 +97,7 @@ function detectMcpDrift(model) {
|
|
|
97
97
|
const drifts = [];
|
|
98
98
|
const allPlatforms = model.activePlatforms.map(p => p.platform);
|
|
99
99
|
const mcpCapablePlatforms = allPlatforms.filter(p =>
|
|
100
|
-
p === 'claude' || p === 'gemini' || p === 'copilot' || p === 'cursor'
|
|
100
|
+
p === 'claude' || p === 'gemini' || p === 'copilot' || p === 'cursor' || p === 'windsurf' || p === 'opencode'
|
|
101
101
|
);
|
|
102
102
|
|
|
103
103
|
if (mcpCapablePlatforms.length < 2) return drifts;
|
|
@@ -150,7 +150,7 @@ function detectRuleDrift(model) {
|
|
|
150
150
|
|
|
151
151
|
// Platforms that support rules: claude (.claude/rules), copilot (.github/instructions), cursor (.cursor/rules)
|
|
152
152
|
const ruleCapable = allPlatforms.filter(p =>
|
|
153
|
-
p === 'claude' || p === 'copilot' || p === 'cursor'
|
|
153
|
+
p === 'claude' || p === 'copilot' || p === 'cursor' || p === 'windsurf'
|
|
154
154
|
);
|
|
155
155
|
|
|
156
156
|
if (ruleCapable.length < 2) return drifts;
|
package/src/harmony/memory.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* H7. Shared Memory / Knowledge Layer
|
|
3
3
|
*
|
|
4
|
-
* Cross-platform knowledge storage in .
|
|
4
|
+
* Cross-platform knowledge storage in .nerviq/harmony/ directory.
|
|
5
5
|
* Persists canonical models, drift history, platform scores,
|
|
6
6
|
* recommendation outcomes, and routing history.
|
|
7
7
|
*
|
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
+
const {
|
|
14
|
+
resolveHarmonyStateReadPath,
|
|
15
|
+
ensureHarmonyStateDir,
|
|
16
|
+
} = require('../state-paths');
|
|
13
17
|
|
|
14
|
-
const HARMONY_DIR = '.
|
|
18
|
+
const HARMONY_DIR = '.nerviq/harmony';
|
|
15
19
|
|
|
16
20
|
const STATE_FILES = {
|
|
17
21
|
canon: 'canon.json',
|
|
@@ -24,9 +28,7 @@ const STATE_FILES = {
|
|
|
24
28
|
// ─── File I/O helpers ─────────────────────────────────────────────────────────
|
|
25
29
|
|
|
26
30
|
function ensureHarmonyDir(dir) {
|
|
27
|
-
|
|
28
|
-
fs.mkdirSync(harmonyPath, { recursive: true });
|
|
29
|
-
return harmonyPath;
|
|
31
|
+
return ensureHarmonyStateDir(dir);
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
function readJsonSafe(filePath) {
|
|
@@ -78,11 +80,10 @@ function saveHarmonyState(dir, state) {
|
|
|
78
80
|
* @returns {Object} State object with all available keys populated
|
|
79
81
|
*/
|
|
80
82
|
function loadHarmonyState(dir) {
|
|
81
|
-
const harmonyPath = path.join(dir, HARMONY_DIR);
|
|
82
83
|
const state = {};
|
|
83
84
|
|
|
84
85
|
for (const [key, filename] of Object.entries(STATE_FILES)) {
|
|
85
|
-
const filePath =
|
|
86
|
+
const filePath = resolveHarmonyStateReadPath(dir, filename);
|
|
86
87
|
const data = readJsonSafe(filePath);
|
|
87
88
|
if (data !== null) {
|
|
88
89
|
state[key] = data;
|
|
@@ -90,7 +91,7 @@ function loadHarmonyState(dir) {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
// Load manifest for metadata
|
|
93
|
-
const manifest = readJsonSafe(
|
|
94
|
+
const manifest = readJsonSafe(resolveHarmonyStateReadPath(dir, 'manifest.json'));
|
|
94
95
|
if (manifest) {
|
|
95
96
|
state._manifest = manifest;
|
|
96
97
|
}
|
package/src/harmony/sync.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generates aligned configs for ALL active platforms from a shared canonical
|
|
5
5
|
* understanding. Ensures instructions, MCP servers, and trust posture are
|
|
6
|
-
* consistent across Claude, Codex, Gemini, Copilot,
|
|
6
|
+
* consistent across Claude, Codex, Gemini, Copilot, Cursor, Windsurf,
|
|
7
|
+
* Aider, and OpenCode.
|
|
7
8
|
*
|
|
8
9
|
* Uses managed blocks from each platform's patch module so hand-authored
|
|
9
10
|
* content is always preserved.
|
|
@@ -37,6 +38,12 @@ const MANAGED_MARKERS = {
|
|
|
37
38
|
start: '<!-- nerviq:managed:start -->',
|
|
38
39
|
end: '<!-- nerviq:managed:end -->',
|
|
39
40
|
},
|
|
41
|
+
windsurf: {
|
|
42
|
+
start: '<!-- nerviq:managed:start -->',
|
|
43
|
+
end: '<!-- nerviq:managed:end -->',
|
|
44
|
+
},
|
|
45
|
+
// aider: uses .aider.conf.yml (YAML) — managed HTML-comment blocks not supported
|
|
46
|
+
// opencode: uses opencode.json (JSON) — managed HTML-comment blocks not supported
|
|
40
47
|
};
|
|
41
48
|
|
|
42
49
|
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
@@ -144,6 +151,9 @@ function getInstructionPath(platform) {
|
|
|
144
151
|
case 'gemini': return 'GEMINI.md';
|
|
145
152
|
case 'copilot': return '.github/copilot-instructions.md';
|
|
146
153
|
case 'cursor': return '.cursorrules';
|
|
154
|
+
case 'windsurf': return '.windsurfrules';
|
|
155
|
+
// aider: .aider.conf.yml is YAML config — no managed block support, skip instruction sync
|
|
156
|
+
// opencode: opencode.json is JSON config — no managed block support, skip instruction sync
|
|
147
157
|
default: return null;
|
|
148
158
|
}
|
|
149
159
|
}
|
|
@@ -178,6 +188,11 @@ function buildMcpConfig(platform, mcpServers) {
|
|
|
178
188
|
return { mcpServers: servers };
|
|
179
189
|
}
|
|
180
190
|
|
|
191
|
+
if (platform === 'windsurf') {
|
|
192
|
+
// Windsurf mcp.json format
|
|
193
|
+
return { mcpServers: servers };
|
|
194
|
+
}
|
|
195
|
+
|
|
181
196
|
return null;
|
|
182
197
|
}
|
|
183
198
|
|
|
@@ -190,6 +205,8 @@ function getMcpConfigPath(platform) {
|
|
|
190
205
|
case 'gemini': return '.gemini/settings.json';
|
|
191
206
|
case 'copilot': return '.vscode/mcp.json';
|
|
192
207
|
case 'cursor': return '.cursor/mcp.json';
|
|
208
|
+
case 'windsurf': return '.windsurf/mcp.json';
|
|
209
|
+
// aider & opencode: no MCP config support
|
|
193
210
|
default: return null;
|
|
194
211
|
}
|
|
195
212
|
}
|
package/src/insights.js
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
const https = require('https');
|
|
22
22
|
const os = require('os');
|
|
23
23
|
|
|
24
|
-
const INSIGHTS_ENDPOINT = 'https://
|
|
24
|
+
const INSIGHTS_ENDPOINT = 'https://insights.nerviq.net/v1/report';
|
|
25
25
|
const TIMEOUT_MS = 3000;
|
|
26
26
|
|
|
27
27
|
function shouldCollect() {
|
package/src/opencode/activity.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Provides: history, compare, trend, watch, feedback, insights.
|
|
6
6
|
*
|
|
7
7
|
* OpenCode snapshots are stored alongside Claude snapshots in
|
|
8
|
-
* .claude/claudex-setup/snapshots/ but filtered by platform='opencode'.
|
|
8
|
+
* .nerviq/snapshots/ (legacy: .claude/claudex-setup/snapshots/) but filtered by platform='opencode'.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
const path = require('path');
|
package/src/opencode/patch.js
CHANGED
|
@@ -142,7 +142,7 @@ function applyPatch(dir, filePath, patchFn, options = {}) {
|
|
|
142
142
|
return { success: true, reason: 'dry run', preview, unchanged: false };
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
const backupPath = fullPath + '.
|
|
145
|
+
const backupPath = fullPath + '.nerviq-backup';
|
|
146
146
|
fs.writeFileSync(backupPath, original, 'utf8');
|
|
147
147
|
fs.writeFileSync(fullPath, patched, 'utf8');
|
|
148
148
|
|
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
const os = require('os');
|
|
24
|
-
const path = require('path');
|
|
25
|
-
const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
|
|
26
|
-
const { attachSourceUrls } = require('../source-urls');
|
|
27
|
-
const { buildStackChecks } = require('../stack-checks');
|
|
28
|
-
const { isApiProject, isDatabaseProject, isAuthProject, isMonitoringRelevant } = require('../supplemental-checks');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
|
|
26
|
+
const { attachSourceUrls } = require('../source-urls');
|
|
27
|
+
const { buildStackChecks } = require('../stack-checks');
|
|
28
|
+
const { isApiProject, isDatabaseProject, isAuthProject, isMonitoringRelevant } = require('../supplemental-checks');
|
|
29
|
+
const { resolveProjectStateReadPath } = require('../state-paths');
|
|
29
30
|
|
|
30
31
|
const DEFAULT_PROJECT_DOC_MAX_BYTES = 32768;
|
|
31
32
|
|
|
@@ -1458,14 +1459,15 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1458
1459
|
line: () => null,
|
|
1459
1460
|
},
|
|
1460
1461
|
|
|
1461
|
-
opencodePilotEvidence: {
|
|
1462
|
-
id: 'OC-M03',
|
|
1463
|
-
name: 'OpenCode setup has been audited at least once',
|
|
1464
|
-
check: (ctx) => {
|
|
1465
|
-
// Check for nerviq activity artifacts
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1468
|
-
|
|
1462
|
+
opencodePilotEvidence: {
|
|
1463
|
+
id: 'OC-M03',
|
|
1464
|
+
name: 'OpenCode setup has been audited at least once',
|
|
1465
|
+
check: (ctx) => {
|
|
1466
|
+
// Check for nerviq activity artifacts
|
|
1467
|
+
const fs = require('fs');
|
|
1468
|
+
const hasArtifacts = fs.existsSync(resolveProjectStateReadPath(ctx.dir));
|
|
1469
|
+
return hasArtifacts ? true : null;
|
|
1470
|
+
},
|
|
1469
1471
|
impact: 'low',
|
|
1470
1472
|
rating: 2,
|
|
1471
1473
|
category: 'governance',
|