@nerviq/cli 1.18.0 → 1.20.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/LICENSE +23 -23
- package/README.md +2 -2
- package/bin/cli.js +131 -130
- package/package.json +2 -1
- package/src/activity.js +1039 -1039
- package/src/adoption-advisor.js +299 -299
- package/src/aider/config-parser.js +166 -166
- package/src/aider/context.js +158 -158
- package/src/aider/deep-review.js +316 -316
- package/src/aider/domain-packs.js +303 -303
- package/src/aider/freshness.js +93 -93
- package/src/aider/governance.js +253 -253
- package/src/aider/interactive.js +334 -334
- package/src/aider/mcp-packs.js +329 -329
- package/src/aider/patch.js +214 -214
- package/src/aider/plans.js +186 -186
- package/src/aider/premium.js +360 -360
- package/src/aider/setup.js +404 -404
- package/src/aider/techniques.js +16 -16
- package/src/analyze.js +951 -951
- package/src/anti-patterns.js +485 -485
- package/src/audit/instruction-files.js +180 -180
- package/src/audit/recommendations.js +577 -577
- package/src/auto-suggest.js +154 -154
- package/src/badge.js +13 -13
- package/src/behavioral-drift.js +801 -801
- package/src/benchmark.js +67 -67
- package/src/catalog.js +103 -103
- package/src/certification.js +128 -128
- package/src/codex/config-parser.js +183 -183
- package/src/codex/context.js +223 -223
- package/src/codex/deep-review.js +493 -493
- package/src/codex/domain-packs.js +394 -394
- package/src/codex/freshness.js +84 -84
- package/src/codex/governance.js +192 -192
- package/src/codex/interactive.js +618 -618
- package/src/codex/mcp-packs.js +914 -914
- package/src/codex/patch.js +209 -209
- package/src/codex/plans.js +251 -251
- package/src/codex/premium.js +614 -614
- package/src/codex/setup.js +591 -591
- package/src/context.js +320 -320
- package/src/continuous-ops.js +681 -681
- package/src/copilot/activity.js +309 -309
- package/src/copilot/deep-review.js +346 -346
- package/src/copilot/domain-packs.js +372 -372
- package/src/copilot/freshness.js +57 -57
- package/src/copilot/governance.js +222 -222
- package/src/copilot/interactive.js +406 -406
- package/src/copilot/mcp-packs.js +826 -826
- package/src/copilot/plans.js +253 -253
- package/src/copilot/premium.js +451 -451
- package/src/copilot/setup.js +488 -488
- package/src/cost-tracking.js +61 -61
- package/src/cursor/activity.js +301 -301
- package/src/cursor/config-parser.js +265 -265
- package/src/cursor/context.js +256 -256
- package/src/cursor/deep-review.js +334 -334
- package/src/cursor/domain-packs.js +368 -368
- package/src/cursor/freshness.js +65 -65
- package/src/cursor/governance.js +229 -229
- package/src/cursor/interactive.js +391 -391
- package/src/cursor/mcp-packs.js +828 -828
- package/src/cursor/plans.js +254 -254
- package/src/cursor/premium.js +469 -469
- package/src/cursor/setup.js +488 -488
- package/src/dashboard.js +493 -493
- package/src/deep-review.js +428 -428
- package/src/deprecation.js +98 -98
- package/src/diff-only.js +280 -280
- package/src/doctor.js +119 -119
- package/src/domain-pack-expansion.js +1033 -1033
- package/src/domain-packs.js +387 -387
- package/src/feedback.js +178 -178
- package/src/fix-engine.js +783 -783
- package/src/fix-prompts.js +122 -122
- package/src/formatters/sarif.js +115 -115
- package/src/freshness.js +74 -74
- package/src/gemini/config-parser.js +275 -275
- package/src/gemini/context.js +290 -221
- package/src/gemini/deep-review.js +559 -559
- package/src/gemini/domain-packs.js +393 -393
- package/src/gemini/freshness.js +66 -66
- package/src/gemini/governance.js +201 -201
- package/src/gemini/interactive.js +860 -860
- package/src/gemini/mcp-packs.js +915 -915
- package/src/gemini/plans.js +269 -269
- package/src/gemini/premium.js +760 -760
- package/src/gemini/setup.js +692 -692
- package/src/gemini/techniques.js +105 -33
- package/src/governance.js +72 -72
- package/src/harmony/add.js +68 -68
- package/src/harmony/advisor.js +333 -333
- package/src/harmony/canon.js +565 -565
- package/src/harmony/cli.js +591 -591
- package/src/harmony/drift.js +401 -401
- package/src/harmony/governance.js +313 -313
- package/src/harmony/memory.js +239 -239
- package/src/harmony/sync.js +475 -475
- package/src/harmony/watch.js +370 -370
- package/src/hook-validation.js +342 -342
- package/src/index.js +271 -271
- package/src/init.js +184 -184
- package/src/instruction-surfaces.js +185 -185
- package/src/integrations.js +144 -144
- package/src/interactive.js +118 -118
- package/src/locales/en.json +1 -1
- package/src/locales/es.json +1 -1
- package/src/mcp-packs.js +830 -830
- package/src/mcp-server.js +726 -726
- package/src/mcp-validation.js +337 -337
- package/src/nerviq-sync.json +7 -7
- package/src/opencode/config-parser.js +109 -109
- package/src/opencode/context.js +247 -247
- package/src/opencode/deep-review.js +313 -313
- package/src/opencode/domain-packs.js +262 -262
- package/src/opencode/freshness.js +66 -66
- package/src/opencode/governance.js +159 -159
- package/src/opencode/interactive.js +392 -392
- package/src/opencode/mcp-packs.js +705 -705
- package/src/opencode/patch.js +184 -184
- package/src/opencode/plans.js +231 -231
- package/src/opencode/premium.js +413 -413
- package/src/opencode/setup.js +449 -449
- package/src/opencode/techniques.js +27 -27
- package/src/operating-profile.js +574 -574
- package/src/org.js +152 -152
- package/src/permission-rules.js +218 -218
- package/src/plans.js +839 -839
- package/src/platform-change-manifest.js +86 -86
- package/src/plugins.js +110 -110
- package/src/policy-layers.js +210 -210
- package/src/profiles.js +124 -124
- package/src/prompt-injection.js +74 -74
- package/src/public-api.js +173 -173
- package/src/recommendation-rules.js +84 -84
- package/src/repo-archetype.js +386 -386
- package/src/secret-patterns.js +39 -39
- package/src/server.js +527 -527
- package/src/setup/analysis.js +607 -607
- package/src/setup/runtime.js +172 -172
- package/src/setup.js +677 -677
- package/src/shared/capabilities.js +194 -194
- package/src/source-urls.js +132 -132
- package/src/stack-checks.js +565 -565
- package/src/supplemental-checks.js +13 -13
- package/src/synergy/adaptive.js +261 -261
- package/src/synergy/compensation.js +137 -137
- package/src/synergy/evidence.js +193 -193
- package/src/synergy/learning.js +199 -199
- package/src/synergy/patterns.js +227 -227
- package/src/synergy/ranking.js +83 -83
- package/src/synergy/report.js +165 -165
- package/src/synergy/routing.js +146 -146
- package/src/techniques/api.js +407 -407
- package/src/techniques/automation.js +316 -316
- package/src/techniques/compliance.js +257 -257
- package/src/techniques/hygiene.js +294 -294
- package/src/techniques/instructions.js +243 -243
- package/src/techniques/observability.js +226 -226
- package/src/techniques/optimization.js +142 -142
- package/src/techniques/quality.js +318 -318
- package/src/techniques/security.js +237 -237
- package/src/techniques/shared.js +443 -443
- package/src/techniques/stacks.js +2294 -2294
- package/src/techniques/tools.js +106 -106
- package/src/techniques/workflow.js +413 -413
- package/src/techniques.js +81 -81
- package/src/terminology.js +73 -73
- package/src/token-estimate.js +35 -35
- package/src/usage-patterns.js +99 -99
- package/src/verification-metadata.js +145 -145
- package/src/watch.js +247 -247
- package/src/windsurf/activity.js +302 -302
- package/src/windsurf/config-parser.js +267 -267
- package/src/windsurf/context.js +249 -249
- package/src/windsurf/deep-review.js +337 -337
- package/src/windsurf/domain-packs.js +370 -370
- package/src/windsurf/freshness.js +36 -36
- package/src/windsurf/governance.js +231 -231
- package/src/windsurf/interactive.js +388 -388
- package/src/windsurf/mcp-packs.js +792 -792
- package/src/windsurf/plans.js +247 -247
- package/src/windsurf/premium.js +468 -468
- package/src/windsurf/setup.js +471 -471
- package/src/windsurf/techniques.js +17 -17
- package/src/workspace.js +375 -375
package/src/opencode/patch.js
CHANGED
|
@@ -1,184 +1,184 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenCode Patch Intelligence
|
|
3
|
-
*
|
|
4
|
-
* Safe patching of existing OpenCode files using managed blocks.
|
|
5
|
-
* Supports AGENTS.md (HTML comment blocks) and opencode.json (JSONC managed sections).
|
|
6
|
-
*
|
|
7
|
-
* Managed blocks are sections that nerviq controls.
|
|
8
|
-
* Hand-authored content outside managed blocks is preserved.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const { writeRollbackArtifact, writeActivityArtifact } = require('../activity');
|
|
14
|
-
const { tryParseJsonc } = require('./config-parser');
|
|
15
|
-
|
|
16
|
-
// Managed block markers
|
|
17
|
-
const MANAGED_START_MD = '<!-- nerviq:managed:start -->';
|
|
18
|
-
const MANAGED_END_MD = '<!-- nerviq:managed:end -->';
|
|
19
|
-
const MANAGED_START_JSONC = '// nerviq:managed:start';
|
|
20
|
-
const MANAGED_END_JSONC = '// nerviq:managed:end';
|
|
21
|
-
|
|
22
|
-
function extractManagedBlock(content, startMarker, endMarker) {
|
|
23
|
-
const startIdx = content.indexOf(startMarker);
|
|
24
|
-
const endIdx = content.indexOf(endMarker);
|
|
25
|
-
|
|
26
|
-
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
27
|
-
return { before: content, managed: null, after: '' };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return {
|
|
31
|
-
before: content.substring(0, startIdx),
|
|
32
|
-
managed: content.substring(startIdx + startMarker.length, endIdx).trim(),
|
|
33
|
-
after: content.substring(endIdx + endMarker.length),
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function upsertManagedBlock(content, newManaged, startMarker, endMarker) {
|
|
38
|
-
const { before, managed, after } = extractManagedBlock(content, startMarker, endMarker);
|
|
39
|
-
|
|
40
|
-
if (managed !== null) {
|
|
41
|
-
return `${before}${startMarker}\n${newManaged}\n${endMarker}${after}`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const separator = content.endsWith('\n') ? '\n' : '\n\n';
|
|
45
|
-
return `${content}${separator}${startMarker}\n${newManaged}\n${endMarker}\n`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function patchAgentsMd(existingContent, managedSections) {
|
|
49
|
-
const newManaged = Object.entries(managedSections)
|
|
50
|
-
.map(([section, content]) => `## ${section}\n${content}`)
|
|
51
|
-
.join('\n\n');
|
|
52
|
-
|
|
53
|
-
return upsertManagedBlock(existingContent, newManaged, MANAGED_START_MD, MANAGED_END_MD);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Patch opencode.json by safely merging new keys.
|
|
58
|
-
* Never weakens existing permission posture.
|
|
59
|
-
* Only adds new keys that don't already exist.
|
|
60
|
-
*/
|
|
61
|
-
function patchConfigJsonc(existingContent, newKeys) {
|
|
62
|
-
const parsed = tryParseJsonc(existingContent);
|
|
63
|
-
if (!parsed.ok) return existingContent;
|
|
64
|
-
|
|
65
|
-
const existing = parsed.data;
|
|
66
|
-
let changed = false;
|
|
67
|
-
|
|
68
|
-
for (const [key, value] of Object.entries(newKeys)) {
|
|
69
|
-
if (existing[key] === undefined) {
|
|
70
|
-
existing[key] = value;
|
|
71
|
-
changed = true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (!changed) return existingContent;
|
|
76
|
-
|
|
77
|
-
return JSON.stringify(existing, null, 2) + '\n';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function detectMixedAgentRepo(dir) {
|
|
81
|
-
const hasClaude = fs.existsSync(path.join(dir, 'CLAUDE.md')) ||
|
|
82
|
-
fs.existsSync(path.join(dir, '.claude'));
|
|
83
|
-
const hasOpenCode = fs.existsSync(path.join(dir, 'opencode.json')) ||
|
|
84
|
-
fs.existsSync(path.join(dir, 'opencode.jsonc')) ||
|
|
85
|
-
fs.existsSync(path.join(dir, '.opencode'));
|
|
86
|
-
const hasAgentsMd = fs.existsSync(path.join(dir, 'AGENTS.md'));
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
isMixed: hasClaude && (hasOpenCode || hasAgentsMd),
|
|
90
|
-
hasClaude,
|
|
91
|
-
hasOpenCode,
|
|
92
|
-
hasAgentsMd,
|
|
93
|
-
guidance: hasClaude && hasOpenCode
|
|
94
|
-
? 'This is a mixed-agent repo. Keep Claude instructions in CLAUDE.md and OpenCode instructions in AGENTS.md. AGENTS.md takes precedence in OpenCode.'
|
|
95
|
-
: null,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function generatePatchPreview(originalContent, patchedContent, filePath) {
|
|
100
|
-
const origLines = originalContent.split('\n');
|
|
101
|
-
const patchLines = patchedContent.split('\n');
|
|
102
|
-
|
|
103
|
-
const lines = [`--- ${filePath} (original)`, `+++ ${filePath} (patched)`];
|
|
104
|
-
|
|
105
|
-
let inChange = false;
|
|
106
|
-
for (let i = 0; i < Math.max(origLines.length, patchLines.length); i++) {
|
|
107
|
-
const orig = origLines[i] || '';
|
|
108
|
-
const patched = patchLines[i] || '';
|
|
109
|
-
if (orig !== patched) {
|
|
110
|
-
if (!inChange) {
|
|
111
|
-
lines.push(`@@ line ${i + 1} @@`);
|
|
112
|
-
inChange = true;
|
|
113
|
-
}
|
|
114
|
-
if (i < origLines.length) lines.push(`-${orig}`);
|
|
115
|
-
if (i < patchLines.length) lines.push(`+${patched}`);
|
|
116
|
-
} else {
|
|
117
|
-
inChange = false;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return lines.join('\n');
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function applyPatch(dir, filePath, patchFn, options = {}) {
|
|
125
|
-
const fullPath = path.join(dir, filePath);
|
|
126
|
-
const dryRun = options.dryRun === true;
|
|
127
|
-
|
|
128
|
-
if (!fs.existsSync(fullPath)) {
|
|
129
|
-
return { success: false, reason: `${filePath} does not exist`, preview: null };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const original = fs.readFileSync(fullPath, 'utf8');
|
|
133
|
-
const patched = patchFn(original);
|
|
134
|
-
|
|
135
|
-
if (patched === original) {
|
|
136
|
-
return { success: true, reason: 'no changes needed', preview: null, unchanged: true };
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const preview = generatePatchPreview(original, patched, filePath);
|
|
140
|
-
|
|
141
|
-
if (dryRun) {
|
|
142
|
-
return { success: true, reason: 'dry run', preview, unchanged: false };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const backupPath = fullPath + '.nerviq-backup';
|
|
146
|
-
fs.writeFileSync(backupPath, original, 'utf8');
|
|
147
|
-
fs.writeFileSync(fullPath, patched, 'utf8');
|
|
148
|
-
|
|
149
|
-
const rollback = writeRollbackArtifact(dir, {
|
|
150
|
-
sourcePlan: 'opencode-patch',
|
|
151
|
-
patchedFiles: [filePath],
|
|
152
|
-
backupFiles: [{ original: filePath, backup: path.relative(dir, backupPath) }],
|
|
153
|
-
rollbackInstructions: [`Restore ${filePath} from ${path.relative(dir, backupPath)}`],
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
const activity = writeActivityArtifact(dir, 'opencode-patch', {
|
|
157
|
-
platform: 'opencode',
|
|
158
|
-
patchedFiles: [filePath],
|
|
159
|
-
rollbackArtifact: rollback.relativePath,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
return {
|
|
163
|
-
success: true,
|
|
164
|
-
reason: 'patched',
|
|
165
|
-
preview,
|
|
166
|
-
unchanged: false,
|
|
167
|
-
rollbackArtifact: rollback.relativePath,
|
|
168
|
-
activityArtifact: activity.relativePath,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
module.exports = {
|
|
173
|
-
MANAGED_START_MD,
|
|
174
|
-
MANAGED_END_MD,
|
|
175
|
-
MANAGED_START_JSONC,
|
|
176
|
-
MANAGED_END_JSONC,
|
|
177
|
-
extractManagedBlock,
|
|
178
|
-
upsertManagedBlock,
|
|
179
|
-
patchAgentsMd,
|
|
180
|
-
patchConfigJsonc,
|
|
181
|
-
detectMixedAgentRepo,
|
|
182
|
-
generatePatchPreview,
|
|
183
|
-
applyPatch,
|
|
184
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Patch Intelligence
|
|
3
|
+
*
|
|
4
|
+
* Safe patching of existing OpenCode files using managed blocks.
|
|
5
|
+
* Supports AGENTS.md (HTML comment blocks) and opencode.json (JSONC managed sections).
|
|
6
|
+
*
|
|
7
|
+
* Managed blocks are sections that nerviq controls.
|
|
8
|
+
* Hand-authored content outside managed blocks is preserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { writeRollbackArtifact, writeActivityArtifact } = require('../activity');
|
|
14
|
+
const { tryParseJsonc } = require('./config-parser');
|
|
15
|
+
|
|
16
|
+
// Managed block markers
|
|
17
|
+
const MANAGED_START_MD = '<!-- nerviq:managed:start -->';
|
|
18
|
+
const MANAGED_END_MD = '<!-- nerviq:managed:end -->';
|
|
19
|
+
const MANAGED_START_JSONC = '// nerviq:managed:start';
|
|
20
|
+
const MANAGED_END_JSONC = '// nerviq:managed:end';
|
|
21
|
+
|
|
22
|
+
function extractManagedBlock(content, startMarker, endMarker) {
|
|
23
|
+
const startIdx = content.indexOf(startMarker);
|
|
24
|
+
const endIdx = content.indexOf(endMarker);
|
|
25
|
+
|
|
26
|
+
if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx) {
|
|
27
|
+
return { before: content, managed: null, after: '' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
before: content.substring(0, startIdx),
|
|
32
|
+
managed: content.substring(startIdx + startMarker.length, endIdx).trim(),
|
|
33
|
+
after: content.substring(endIdx + endMarker.length),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function upsertManagedBlock(content, newManaged, startMarker, endMarker) {
|
|
38
|
+
const { before, managed, after } = extractManagedBlock(content, startMarker, endMarker);
|
|
39
|
+
|
|
40
|
+
if (managed !== null) {
|
|
41
|
+
return `${before}${startMarker}\n${newManaged}\n${endMarker}${after}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const separator = content.endsWith('\n') ? '\n' : '\n\n';
|
|
45
|
+
return `${content}${separator}${startMarker}\n${newManaged}\n${endMarker}\n`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function patchAgentsMd(existingContent, managedSections) {
|
|
49
|
+
const newManaged = Object.entries(managedSections)
|
|
50
|
+
.map(([section, content]) => `## ${section}\n${content}`)
|
|
51
|
+
.join('\n\n');
|
|
52
|
+
|
|
53
|
+
return upsertManagedBlock(existingContent, newManaged, MANAGED_START_MD, MANAGED_END_MD);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Patch opencode.json by safely merging new keys.
|
|
58
|
+
* Never weakens existing permission posture.
|
|
59
|
+
* Only adds new keys that don't already exist.
|
|
60
|
+
*/
|
|
61
|
+
function patchConfigJsonc(existingContent, newKeys) {
|
|
62
|
+
const parsed = tryParseJsonc(existingContent);
|
|
63
|
+
if (!parsed.ok) return existingContent;
|
|
64
|
+
|
|
65
|
+
const existing = parsed.data;
|
|
66
|
+
let changed = false;
|
|
67
|
+
|
|
68
|
+
for (const [key, value] of Object.entries(newKeys)) {
|
|
69
|
+
if (existing[key] === undefined) {
|
|
70
|
+
existing[key] = value;
|
|
71
|
+
changed = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!changed) return existingContent;
|
|
76
|
+
|
|
77
|
+
return JSON.stringify(existing, null, 2) + '\n';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function detectMixedAgentRepo(dir) {
|
|
81
|
+
const hasClaude = fs.existsSync(path.join(dir, 'CLAUDE.md')) ||
|
|
82
|
+
fs.existsSync(path.join(dir, '.claude'));
|
|
83
|
+
const hasOpenCode = fs.existsSync(path.join(dir, 'opencode.json')) ||
|
|
84
|
+
fs.existsSync(path.join(dir, 'opencode.jsonc')) ||
|
|
85
|
+
fs.existsSync(path.join(dir, '.opencode'));
|
|
86
|
+
const hasAgentsMd = fs.existsSync(path.join(dir, 'AGENTS.md'));
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
isMixed: hasClaude && (hasOpenCode || hasAgentsMd),
|
|
90
|
+
hasClaude,
|
|
91
|
+
hasOpenCode,
|
|
92
|
+
hasAgentsMd,
|
|
93
|
+
guidance: hasClaude && hasOpenCode
|
|
94
|
+
? 'This is a mixed-agent repo. Keep Claude instructions in CLAUDE.md and OpenCode instructions in AGENTS.md. AGENTS.md takes precedence in OpenCode.'
|
|
95
|
+
: null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function generatePatchPreview(originalContent, patchedContent, filePath) {
|
|
100
|
+
const origLines = originalContent.split('\n');
|
|
101
|
+
const patchLines = patchedContent.split('\n');
|
|
102
|
+
|
|
103
|
+
const lines = [`--- ${filePath} (original)`, `+++ ${filePath} (patched)`];
|
|
104
|
+
|
|
105
|
+
let inChange = false;
|
|
106
|
+
for (let i = 0; i < Math.max(origLines.length, patchLines.length); i++) {
|
|
107
|
+
const orig = origLines[i] || '';
|
|
108
|
+
const patched = patchLines[i] || '';
|
|
109
|
+
if (orig !== patched) {
|
|
110
|
+
if (!inChange) {
|
|
111
|
+
lines.push(`@@ line ${i + 1} @@`);
|
|
112
|
+
inChange = true;
|
|
113
|
+
}
|
|
114
|
+
if (i < origLines.length) lines.push(`-${orig}`);
|
|
115
|
+
if (i < patchLines.length) lines.push(`+${patched}`);
|
|
116
|
+
} else {
|
|
117
|
+
inChange = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return lines.join('\n');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function applyPatch(dir, filePath, patchFn, options = {}) {
|
|
125
|
+
const fullPath = path.join(dir, filePath);
|
|
126
|
+
const dryRun = options.dryRun === true;
|
|
127
|
+
|
|
128
|
+
if (!fs.existsSync(fullPath)) {
|
|
129
|
+
return { success: false, reason: `${filePath} does not exist`, preview: null };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const original = fs.readFileSync(fullPath, 'utf8');
|
|
133
|
+
const patched = patchFn(original);
|
|
134
|
+
|
|
135
|
+
if (patched === original) {
|
|
136
|
+
return { success: true, reason: 'no changes needed', preview: null, unchanged: true };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const preview = generatePatchPreview(original, patched, filePath);
|
|
140
|
+
|
|
141
|
+
if (dryRun) {
|
|
142
|
+
return { success: true, reason: 'dry run', preview, unchanged: false };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const backupPath = fullPath + '.nerviq-backup';
|
|
146
|
+
fs.writeFileSync(backupPath, original, 'utf8');
|
|
147
|
+
fs.writeFileSync(fullPath, patched, 'utf8');
|
|
148
|
+
|
|
149
|
+
const rollback = writeRollbackArtifact(dir, {
|
|
150
|
+
sourcePlan: 'opencode-patch',
|
|
151
|
+
patchedFiles: [filePath],
|
|
152
|
+
backupFiles: [{ original: filePath, backup: path.relative(dir, backupPath) }],
|
|
153
|
+
rollbackInstructions: [`Restore ${filePath} from ${path.relative(dir, backupPath)}`],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const activity = writeActivityArtifact(dir, 'opencode-patch', {
|
|
157
|
+
platform: 'opencode',
|
|
158
|
+
patchedFiles: [filePath],
|
|
159
|
+
rollbackArtifact: rollback.relativePath,
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
success: true,
|
|
164
|
+
reason: 'patched',
|
|
165
|
+
preview,
|
|
166
|
+
unchanged: false,
|
|
167
|
+
rollbackArtifact: rollback.relativePath,
|
|
168
|
+
activityArtifact: activity.relativePath,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = {
|
|
173
|
+
MANAGED_START_MD,
|
|
174
|
+
MANAGED_END_MD,
|
|
175
|
+
MANAGED_START_JSONC,
|
|
176
|
+
MANAGED_END_JSONC,
|
|
177
|
+
extractManagedBlock,
|
|
178
|
+
upsertManagedBlock,
|
|
179
|
+
patchAgentsMd,
|
|
180
|
+
patchConfigJsonc,
|
|
181
|
+
detectMixedAgentRepo,
|
|
182
|
+
generatePatchPreview,
|
|
183
|
+
applyPatch,
|
|
184
|
+
};
|