@nerviq/cli 0.9.2 → 0.9.4
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 +64 -3
- package/package.json +3 -2
- package/src/aider/techniques.js +85 -11
- package/src/audit.js +3 -2
- package/src/codex/techniques.js +3 -0
- package/src/convert.js +336 -0
- package/src/copilot/techniques.js +125 -11
- package/src/cursor/techniques.js +93 -10
- package/src/doctor.js +253 -0
- package/src/feedback.js +173 -0
- package/src/freshness.js +177 -0
- package/src/gemini/techniques.js +177 -23
- package/src/mcp-server.js +373 -0
- package/src/migrate.js +354 -0
- package/src/opencode/techniques.js +73 -99
- package/src/source-urls.js +219 -0
- package/src/techniques.js +3 -0
- package/src/windsurf/techniques.js +214 -138
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenCode Techniques —
|
|
2
|
+
* OpenCode Techniques — 73 checks (OC-A01 through OC-P03)
|
|
3
3
|
*
|
|
4
4
|
* Categories:
|
|
5
5
|
* A. Instructions (7 checks)
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
const os = require('os');
|
|
24
24
|
const path = require('path');
|
|
25
25
|
const { EMBEDDED_SECRET_PATTERNS, containsEmbeddedSecret } = require('../secret-patterns');
|
|
26
|
+
const { attachSourceUrls } = require('../source-urls');
|
|
26
27
|
|
|
27
28
|
const DEFAULT_PROJECT_DOC_MAX_BYTES = 32768;
|
|
28
29
|
|
|
@@ -62,18 +63,14 @@ const VALID_PLUGIN_EVENTS = new Set([
|
|
|
62
63
|
'error', 'warning',
|
|
63
64
|
]);
|
|
64
65
|
|
|
65
|
-
const DEPRECATED_CONFIG_KEYS = [
|
|
66
|
-
{ key: 'mode', replacement: 'agent', note: 'Use `agent` field instead of deprecated `mode`.' },
|
|
67
|
-
];
|
|
68
|
-
|
|
69
66
|
// --- Helpers ---
|
|
70
67
|
|
|
71
68
|
function agentsPath(ctx) {
|
|
72
|
-
return ctx.
|
|
69
|
+
return ctx.fileContent('AGENTS.md') ? 'AGENTS.md' : null;
|
|
73
70
|
}
|
|
74
71
|
|
|
75
72
|
function agentsContent(ctx) {
|
|
76
|
-
return ctx.
|
|
73
|
+
return ctx.fileContent('AGENTS.md') || '';
|
|
77
74
|
}
|
|
78
75
|
|
|
79
76
|
function configFileName(ctx) {
|
|
@@ -262,17 +259,16 @@ const OPENCODE_TECHNIQUES = {
|
|
|
262
259
|
|
|
263
260
|
opencodeNoCoexistenceConflict: {
|
|
264
261
|
id: 'OC-A05',
|
|
265
|
-
name: '
|
|
262
|
+
name: 'Mixed AGENTS.md + CLAUDE.md repos keep OpenCode guidance in AGENTS.md',
|
|
266
263
|
check: (ctx) => {
|
|
267
264
|
if (!ctx.hasAgentsMdAndClaudeMd || !ctx.hasAgentsMdAndClaudeMd()) return true;
|
|
268
|
-
// Both exist: check that AGENTS.md is primary, warn about potential confusion
|
|
269
265
|
const agentsMd = ctx.fileContent('AGENTS.md') || '';
|
|
270
266
|
return agentsMd.length > 0;
|
|
271
267
|
},
|
|
272
268
|
impact: 'high',
|
|
273
269
|
rating: 4,
|
|
274
270
|
category: 'instructions',
|
|
275
|
-
fix: '
|
|
271
|
+
fix: 'Keep OpenCode instructions in `AGENTS.md` when both files exist. Current runtime evidence did not validate a clean `CLAUDE.md` fallback, so do not rely on `CLAUDE.md` as the primary OpenCode instruction surface.',
|
|
276
272
|
template: 'opencode-agents-md',
|
|
277
273
|
file: () => 'AGENTS.md',
|
|
278
274
|
line: () => null,
|
|
@@ -291,7 +287,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
291
287
|
category: 'instructions',
|
|
292
288
|
fix: 'Remove generic filler ("Be helpful", "Write clean code") and replace with specific, actionable project instructions.',
|
|
293
289
|
template: 'opencode-agents-md',
|
|
294
|
-
file: () => agentsPath,
|
|
290
|
+
file: (ctx) => agentsPath(ctx),
|
|
295
291
|
line: (ctx) => findFillerLine(agentsContent(ctx)),
|
|
296
292
|
},
|
|
297
293
|
|
|
@@ -571,7 +567,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
571
567
|
|
|
572
568
|
opencodeAllToolsCovered: {
|
|
573
569
|
id: 'OC-C08',
|
|
574
|
-
name: '
|
|
570
|
+
name: 'Critical tool permissions are explicit',
|
|
575
571
|
check: (ctx) => {
|
|
576
572
|
const perms = ctx.toolPermissions();
|
|
577
573
|
if (!perms || Object.keys(perms).length === 0) return null;
|
|
@@ -582,7 +578,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
582
578
|
impact: 'high',
|
|
583
579
|
rating: 4,
|
|
584
580
|
category: 'permissions',
|
|
585
|
-
fix: 'Set explicit permissions for at least the critical tools: bash, edit, read, task.',
|
|
581
|
+
fix: 'Set explicit permissions for at least the critical tools: bash, edit, read, and task. The old fixed "15 tools" framing no longer matches current CLI/runtime surfaces.',
|
|
586
582
|
template: 'opencode-permissions',
|
|
587
583
|
file: (ctx) => configFileName(ctx),
|
|
588
584
|
line: () => null,
|
|
@@ -670,21 +666,17 @@ const OPENCODE_TECHNIQUES = {
|
|
|
670
666
|
|
|
671
667
|
opencodePluginHookGapAware: {
|
|
672
668
|
id: 'OC-D05',
|
|
673
|
-
name: '
|
|
669
|
+
name: 'Plugin docs do not rely on stale hook-gap claims',
|
|
674
670
|
check: (ctx) => {
|
|
675
671
|
const pluginFiles = ctx.pluginFiles();
|
|
676
672
|
if (pluginFiles.length === 0) return null;
|
|
677
673
|
const docs = docsBundle(ctx);
|
|
678
|
-
|
|
679
|
-
if (/\btool\.execute\.before\b/i.test(docs) && !/\b(bypass|gap|limitation|bug|5894|2319)\b/i.test(docs)) {
|
|
680
|
-
return false;
|
|
681
|
-
}
|
|
682
|
-
return true;
|
|
674
|
+
return !/\btool\.execute\.before\b[\s\S]{0,80}\b(subagent|mcp)\b[\s\S]{0,80}\b(bypass|gap|broken|2319|5894)\b/i.test(docs);
|
|
683
675
|
},
|
|
684
676
|
impact: 'high',
|
|
685
677
|
rating: 4,
|
|
686
678
|
category: 'plugins',
|
|
687
|
-
fix: '
|
|
679
|
+
fix: 'Remove blanket claims that subagent or MCP calls bypass plugin visibility. On current runtime, hook coverage was observed for direct, subagent, and MCP paths, so any caveat should be version-specific and evidence-backed.',
|
|
688
680
|
template: 'opencode-agents-md',
|
|
689
681
|
file: () => 'AGENTS.md',
|
|
690
682
|
line: () => null,
|
|
@@ -713,20 +705,17 @@ const OPENCODE_TECHNIQUES = {
|
|
|
713
705
|
|
|
714
706
|
opencodeToolInterceptionGap: {
|
|
715
707
|
id: 'OC-E02',
|
|
716
|
-
name: '
|
|
708
|
+
name: 'Security docs do not overstate plugin hook bypass gaps',
|
|
717
709
|
check: (ctx) => {
|
|
718
710
|
const pluginFiles = ctx.pluginFiles();
|
|
719
711
|
if (pluginFiles.length === 0) return null;
|
|
720
|
-
// Check if the project uses plugins and has documented the gap
|
|
721
712
|
const docs = docsBundle(ctx);
|
|
722
|
-
|
|
723
|
-
if (!usesToolHooks) return true;
|
|
724
|
-
return /\b(bypass|gap|limitation|5894|2319)\b/i.test(docs);
|
|
713
|
+
return !/\b(subagent|mcp)\b[\s\S]{0,80}\b(bypass|gap|broken|2319|5894)\b/i.test(docs);
|
|
725
714
|
},
|
|
726
715
|
impact: 'high',
|
|
727
716
|
rating: 4,
|
|
728
717
|
category: 'security',
|
|
729
|
-
fix: '
|
|
718
|
+
fix: 'Do not treat historical hook-gap bug reports as a current security guarantee. If you mention plugin coverage limits, mark them as version-sensitive and pair them with fresh runtime evidence.',
|
|
730
719
|
template: 'opencode-agents-md',
|
|
731
720
|
file: () => 'AGENTS.md',
|
|
732
721
|
line: () => null,
|
|
@@ -734,19 +723,19 @@ const OPENCODE_TECHNIQUES = {
|
|
|
734
723
|
|
|
735
724
|
opencodeAgentDenyNotBypassable: {
|
|
736
725
|
id: 'OC-E03',
|
|
737
|
-
name: 'Agent
|
|
726
|
+
name: 'Agent-permission docs do not rely on stale SDK bypass claims',
|
|
738
727
|
check: (ctx) => {
|
|
739
728
|
const agents = ctx.customAgents();
|
|
740
729
|
if (!agents || Object.keys(agents).length === 0) return null;
|
|
741
730
|
const docs = docsBundle(ctx);
|
|
742
731
|
const usesAgentPerms = Object.values(agents).some(a => a && a.permissions);
|
|
743
732
|
if (!usesAgentPerms) return true;
|
|
744
|
-
return
|
|
733
|
+
return !/\b6396\b|\bagent\b[\s\S]{0,80}\b(bypass|gap|broken)\b/i.test(docs);
|
|
745
734
|
},
|
|
746
735
|
impact: 'high',
|
|
747
736
|
rating: 4,
|
|
748
737
|
category: 'security',
|
|
749
|
-
fix: '
|
|
738
|
+
fix: 'Remove blanket claims that agent deny permissions are bypassed via SDK unless you have fresh version-specific proof. The older `#6396` framing did not reproduce in the current CLI harness.',
|
|
750
739
|
template: 'opencode-agents-md',
|
|
751
740
|
file: () => 'AGENTS.md',
|
|
752
741
|
line: () => null,
|
|
@@ -781,7 +770,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
781
770
|
impact: 'critical',
|
|
782
771
|
rating: 5,
|
|
783
772
|
category: 'security',
|
|
784
|
-
fix: '
|
|
773
|
+
fix: 'Do not hardcode secrets in `opencode.json`, and do not assume `{env:VAR}` keeps values invisible. Current runtime exposed resolved env substitutions in `debug config`, so treat that surface as sensitive too.',
|
|
785
774
|
template: 'opencode-config',
|
|
786
775
|
file: (ctx) => configFileName(ctx),
|
|
787
776
|
line: (ctx) => {
|
|
@@ -821,9 +810,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
821
810
|
if (!mcp || Object.keys(mcp).length === 0) return null;
|
|
822
811
|
for (const [id, server] of Object.entries(mcp)) {
|
|
823
812
|
if (!server) continue;
|
|
824
|
-
|
|
825
|
-
if (server.command && !Array.isArray(server.command) && typeof server.command !== 'string') return false;
|
|
826
|
-
// env is the wrong key — should be environment
|
|
813
|
+
if (server.command && !Array.isArray(server.command)) return false;
|
|
827
814
|
if (server.env && !server.environment) return false;
|
|
828
815
|
}
|
|
829
816
|
return true;
|
|
@@ -831,7 +818,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
831
818
|
impact: 'critical',
|
|
832
819
|
rating: 5,
|
|
833
820
|
category: 'mcp',
|
|
834
|
-
fix: 'Fix MCP config schema: use
|
|
821
|
+
fix: 'Fix MCP config schema: use `command` as a string array and `environment` as the env-var object. Current runtime rejected string commands and the legacy `env` key.',
|
|
835
822
|
template: 'opencode-config',
|
|
836
823
|
file: (ctx) => configFileName(ctx),
|
|
837
824
|
line: () => null,
|
|
@@ -843,17 +830,13 @@ const OPENCODE_TECHNIQUES = {
|
|
|
843
830
|
check: (ctx) => {
|
|
844
831
|
const mcp = ctx.mcpServers();
|
|
845
832
|
if (!mcp || Object.keys(mcp).length === 0) return null;
|
|
846
|
-
|
|
847
|
-
const perms = ctx.toolPermissions();
|
|
848
|
-
if (!perms) return null;
|
|
849
|
-
// Look for MCP-related tool permission patterns
|
|
850
|
-
const hasMcpToolRestrictions = Object.keys(perms).some(key => key.includes('mcp') || key.includes('*'));
|
|
833
|
+
const hasMcpToolRestrictions = Object.values(mcp).some((server) => server && server.tools && Object.keys(server.tools).length > 0);
|
|
851
834
|
return hasMcpToolRestrictions || Object.keys(mcp).length <= 2;
|
|
852
835
|
},
|
|
853
836
|
impact: 'high',
|
|
854
837
|
rating: 4,
|
|
855
838
|
category: 'mcp',
|
|
856
|
-
fix: 'Add tool
|
|
839
|
+
fix: 'Add MCP tool restrictions with per-tool globs such as `{ "tools": { "my-mcp*": false } }`. This limits only those MCP tools; other available tools like `webfetch` may still satisfy the same intent unless you restrict them too.',
|
|
857
840
|
template: 'opencode-config',
|
|
858
841
|
file: (ctx) => configFileName(ctx),
|
|
859
842
|
line: () => null,
|
|
@@ -886,19 +869,19 @@ const OPENCODE_TECHNIQUES = {
|
|
|
886
869
|
|
|
887
870
|
opencodeMcpHookLimitation: {
|
|
888
871
|
id: 'OC-F04',
|
|
889
|
-
name: 'MCP
|
|
872
|
+
name: 'MCP hook caveats are treated as version-sensitive',
|
|
890
873
|
check: (ctx) => {
|
|
891
874
|
const mcp = ctx.mcpServers();
|
|
892
875
|
const pluginFiles = ctx.pluginFiles();
|
|
893
876
|
if (!mcp || Object.keys(mcp).length === 0) return null;
|
|
894
877
|
if (pluginFiles.length === 0) return null;
|
|
895
878
|
const docs = docsBundle(ctx);
|
|
896
|
-
return
|
|
879
|
+
return !/\bmcp\b[\s\S]{0,80}\b(hook|plugin)\b[\s\S]{0,80}\b(bypass|gap|broken|2319)\b/i.test(docs);
|
|
897
880
|
},
|
|
898
881
|
impact: 'medium',
|
|
899
882
|
rating: 3,
|
|
900
883
|
category: 'mcp',
|
|
901
|
-
fix: '
|
|
884
|
+
fix: 'Do not hard-code an MCP hook-bypass warning as if it were universal. Current runtime showed MCP plugin events firing, so keep any caveat version-sensitive and backed by fresh evidence.',
|
|
902
885
|
template: 'opencode-agents-md',
|
|
903
886
|
file: () => 'AGENTS.md',
|
|
904
887
|
line: () => null,
|
|
@@ -937,18 +920,17 @@ const OPENCODE_TECHNIQUES = {
|
|
|
937
920
|
|
|
938
921
|
opencodeCiPermissionsPreset: {
|
|
939
922
|
id: 'OC-G01',
|
|
940
|
-
name: 'opencode run usage pre-configures
|
|
923
|
+
name: 'opencode run usage pre-configures permissions to avoid silent auto-rejects',
|
|
941
924
|
check: (ctx) => {
|
|
942
925
|
const workflows = workflowArtifacts(ctx);
|
|
943
926
|
const hasOpencodeRun = workflows.some(w => /\bopencode\s+run\b/i.test(w.content));
|
|
944
927
|
if (!hasOpencodeRun) return null;
|
|
945
|
-
// Check that permissions are pre-configured
|
|
946
928
|
return workflows.some(w => /\bpermissions?\b.*\ballow\b|\b--yes\b|\b--no-prompt\b/i.test(w.content));
|
|
947
929
|
},
|
|
948
930
|
impact: 'critical',
|
|
949
931
|
rating: 5,
|
|
950
932
|
category: 'ci',
|
|
951
|
-
fix: 'Pre-configure
|
|
933
|
+
fix: 'Pre-configure permissions when using `opencode run` in CI. In the current harness, permission requests auto-rejected instead of hanging, which still breaks tasks that expected tool access.',
|
|
952
934
|
template: 'opencode-ci',
|
|
953
935
|
file: () => '.github/workflows/',
|
|
954
936
|
line: () => null,
|
|
@@ -984,7 +966,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
984
966
|
impact: 'medium',
|
|
985
967
|
rating: 3,
|
|
986
968
|
category: 'ci',
|
|
987
|
-
fix: 'Use `--format json` when running OpenCode in CI
|
|
969
|
+
fix: 'Use `--format json` when running OpenCode in CI, and parse it as JSONL/event frames rather than expecting one monolithic JSON document.',
|
|
988
970
|
template: 'opencode-ci',
|
|
989
971
|
file: () => '.github/workflows/',
|
|
990
972
|
line: () => null,
|
|
@@ -1036,19 +1018,16 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1036
1018
|
|
|
1037
1019
|
opencodeNoDeprecatedPatterns: {
|
|
1038
1020
|
id: 'OC-H02',
|
|
1039
|
-
name: '
|
|
1021
|
+
name: 'Repo docs do not push the stale mode -> agent migration claim',
|
|
1040
1022
|
check: (ctx) => {
|
|
1041
|
-
const
|
|
1042
|
-
if (!
|
|
1043
|
-
|
|
1044
|
-
if (config.data[key] !== undefined) return false;
|
|
1045
|
-
}
|
|
1046
|
-
return true;
|
|
1023
|
+
const docs = docsBundle(ctx);
|
|
1024
|
+
if (!docs.trim()) return null;
|
|
1025
|
+
return !/\bmode\b[\s\S]{0,60}\bdeprecated\b|\buse\b[\s\S]{0,40}\bagent\b[\s\S]{0,40}\binstead of\b[\s\S]{0,20}\bmode\b/i.test(docs);
|
|
1047
1026
|
},
|
|
1048
1027
|
impact: 'medium',
|
|
1049
1028
|
rating: 3,
|
|
1050
1029
|
category: 'quality-deep',
|
|
1051
|
-
fix: '
|
|
1030
|
+
fix: 'Do not tell users that `mode` has been replaced by `agent` across the board. Current runtime still validated `mode` for markdown custom agents, so any migration guidance should be explicitly version-scoped.',
|
|
1052
1031
|
template: 'opencode-config',
|
|
1053
1032
|
file: (ctx) => configFileName(ctx),
|
|
1054
1033
|
line: () => null,
|
|
@@ -1162,16 +1141,16 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1162
1141
|
|
|
1163
1142
|
opencodeSkillKebabCase: {
|
|
1164
1143
|
id: 'OC-I03',
|
|
1165
|
-
name: 'Skill names use kebab-case
|
|
1144
|
+
name: 'Skill names preferably use kebab-case',
|
|
1166
1145
|
check: (ctx) => {
|
|
1167
1146
|
const skillDirs = ctx.skillDirs();
|
|
1168
1147
|
if (skillDirs.length === 0) return null;
|
|
1169
1148
|
return skillDirs.every(name => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name));
|
|
1170
1149
|
},
|
|
1171
|
-
impact: '
|
|
1172
|
-
rating:
|
|
1150
|
+
impact: 'medium',
|
|
1151
|
+
rating: 2,
|
|
1173
1152
|
category: 'skills',
|
|
1174
|
-
fix: '
|
|
1153
|
+
fix: 'Prefer kebab-case for skill names, but treat it as a style recommendation rather than a hard runtime requirement. Current runtime still discovered underscore-based names.',
|
|
1175
1154
|
template: 'opencode-skills',
|
|
1176
1155
|
file: () => '.opencode/commands/',
|
|
1177
1156
|
line: () => null,
|
|
@@ -1201,20 +1180,17 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1201
1180
|
|
|
1202
1181
|
opencodeSkillCompatPaths: {
|
|
1203
1182
|
id: 'OC-I05',
|
|
1204
|
-
name: '
|
|
1183
|
+
name: 'OpenCode skill discovery accepts either .opencode/commands or .claude/skills',
|
|
1205
1184
|
check: (ctx) => {
|
|
1206
1185
|
const hasClaudeSkills = ctx.hasDir('.claude/skills');
|
|
1207
|
-
if (!hasClaudeSkills) return null;
|
|
1208
1186
|
const hasOpencodeCommands = ctx.hasDir('.opencode/commands');
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
// Claude skills exist but no OpenCode commands — warn
|
|
1212
|
-
return false;
|
|
1187
|
+
if (!hasClaudeSkills && !hasOpencodeCommands) return null;
|
|
1188
|
+
return hasClaudeSkills || hasOpencodeCommands;
|
|
1213
1189
|
},
|
|
1214
1190
|
impact: 'medium',
|
|
1215
1191
|
rating: 3,
|
|
1216
1192
|
category: 'skills',
|
|
1217
|
-
fix: '
|
|
1193
|
+
fix: 'Use `.opencode/commands/` for native OpenCode skills when you need them, but do not require a duplicate tree just to mirror `.claude/skills/`. Current runtime discovered `.claude/skills/` compatibility successfully.',
|
|
1218
1194
|
template: 'opencode-skills',
|
|
1219
1195
|
file: () => '.opencode/commands/',
|
|
1220
1196
|
line: () => null,
|
|
@@ -1247,21 +1223,22 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1247
1223
|
|
|
1248
1224
|
opencodeAgentModeValid: {
|
|
1249
1225
|
id: 'OC-J02',
|
|
1250
|
-
name: '
|
|
1226
|
+
name: 'Custom agent mode is valid when declared',
|
|
1251
1227
|
check: (ctx) => {
|
|
1252
1228
|
const agents = ctx.customAgents();
|
|
1253
1229
|
if (!agents || Object.keys(agents).length === 0) return null;
|
|
1254
1230
|
const validModes = new Set(['primary', 'subagent', 'all']);
|
|
1255
1231
|
for (const [name, agent] of Object.entries(agents)) {
|
|
1256
1232
|
if (!agent) continue;
|
|
1257
|
-
|
|
1233
|
+
const mode = agent.mode || agent.agent;
|
|
1234
|
+
if (mode && !validModes.has(mode)) return false;
|
|
1258
1235
|
}
|
|
1259
1236
|
return true;
|
|
1260
1237
|
},
|
|
1261
1238
|
impact: 'medium',
|
|
1262
1239
|
rating: 3,
|
|
1263
1240
|
category: 'agents',
|
|
1264
|
-
fix: '
|
|
1241
|
+
fix: 'Use a valid mode value (`primary`, `subagent`, or `all`) when declaring custom agents. Current runtime still validated `mode` for markdown agents, so do not rename to `agent` solely because of stale docs.',
|
|
1265
1242
|
template: 'opencode-config',
|
|
1266
1243
|
file: (ctx) => configFileName(ctx),
|
|
1267
1244
|
line: () => null,
|
|
@@ -1269,20 +1246,20 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1269
1246
|
|
|
1270
1247
|
opencodeBuiltinAgentsProtected: {
|
|
1271
1248
|
id: 'OC-J03',
|
|
1272
|
-
name: 'Built-in
|
|
1249
|
+
name: 'Built-in agent overrides are intentional and documented',
|
|
1273
1250
|
check: (ctx) => {
|
|
1274
1251
|
const agents = ctx.customAgents();
|
|
1275
1252
|
if (!agents || Object.keys(agents).length === 0) return null;
|
|
1276
1253
|
const builtins = new Set(['build', 'plan', 'default']);
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
return
|
|
1254
|
+
const overriding = Object.keys(agents).filter((name) => builtins.has(name.toLowerCase()));
|
|
1255
|
+
if (overriding.length === 0) return true;
|
|
1256
|
+
const docs = docsBundle(ctx);
|
|
1257
|
+
return /override|intentional|customized|replace/i.test(docs);
|
|
1281
1258
|
},
|
|
1282
1259
|
impact: 'medium',
|
|
1283
1260
|
rating: 3,
|
|
1284
1261
|
category: 'agents',
|
|
1285
|
-
fix: '
|
|
1262
|
+
fix: 'Built-in agents appear overrideable in current runtime. If you intentionally override `build`, `plan`, or `default`, document why; otherwise rename the custom agent to avoid surprising behavior.',
|
|
1286
1263
|
template: 'opencode-config',
|
|
1287
1264
|
file: (ctx) => configFileName(ctx),
|
|
1288
1265
|
line: () => null,
|
|
@@ -1389,7 +1366,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1389
1366
|
impact: 'medium',
|
|
1390
1367
|
rating: 3,
|
|
1391
1368
|
category: 'tui',
|
|
1392
|
-
fix: 'Fix JSONC syntax in tui.json.
|
|
1369
|
+
fix: 'Fix JSONC syntax in `tui.json`, then validate the behavioral effect in the real TUI/UI. Headless CLI surfaces did not provide enough evidence for TUI behavior on their own.',
|
|
1393
1370
|
template: 'opencode-config',
|
|
1394
1371
|
file: () => 'tui.json',
|
|
1395
1372
|
line: () => 1,
|
|
@@ -1415,7 +1392,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1415
1392
|
impact: 'low',
|
|
1416
1393
|
rating: 2,
|
|
1417
1394
|
category: 'tui',
|
|
1418
|
-
fix: 'Fix JSON syntax errors in theme
|
|
1395
|
+
fix: 'Fix JSON syntax errors in `.opencode/themes/`, then verify the theme in an actual UI/TUI session. Headless `run` did not give reliable theme evidence.',
|
|
1419
1396
|
template: 'opencode-config',
|
|
1420
1397
|
file: () => '.opencode/themes/',
|
|
1421
1398
|
line: () => null,
|
|
@@ -1432,7 +1409,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1432
1409
|
impact: 'medium',
|
|
1433
1410
|
rating: 3,
|
|
1434
1411
|
category: 'tui',
|
|
1435
|
-
fix: 'Remove any sensitive data from tui.json.',
|
|
1412
|
+
fix: 'Remove any sensitive data from `tui.json`, and remember that `tui.json` was not meaningfully observable through headless `run` alone.',
|
|
1436
1413
|
template: 'opencode-config',
|
|
1437
1414
|
file: () => 'tui.json',
|
|
1438
1415
|
line: (ctx) => {
|
|
@@ -1520,17 +1497,16 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1520
1497
|
id: 'OC-N02',
|
|
1521
1498
|
name: 'Config references current OpenCode features (no removed or renamed keys)',
|
|
1522
1499
|
check: (ctx) => {
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
return true;
|
|
1500
|
+
const docs = docsBundle(ctx);
|
|
1501
|
+
const config = ctx.configContent();
|
|
1502
|
+
if (!docs.trim() && !config) return null;
|
|
1503
|
+
const combined = `${docs}\n${config || ''}`;
|
|
1504
|
+
return !/\bconfig\.json\b|\.well-known\/opencode|mode\s*->\s*agent|CLAUDE\.md fallback/i.test(combined);
|
|
1529
1505
|
},
|
|
1530
1506
|
impact: 'medium',
|
|
1531
1507
|
rating: 3,
|
|
1532
1508
|
category: 'release-freshness',
|
|
1533
|
-
fix: 'Update
|
|
1509
|
+
fix: 'Update stale OpenCode references. Use `opencode.json`/`opencode.jsonc`, keep `mode` guidance version-scoped, and treat `.well-known/opencode` plus `CLAUDE.md` fallback claims as unvalidated until you have fresh runtime proof.',
|
|
1534
1510
|
template: 'opencode-config',
|
|
1535
1511
|
file: (ctx) => configFileName(ctx),
|
|
1536
1512
|
line: () => null,
|
|
@@ -1596,15 +1572,13 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1596
1572
|
|
|
1597
1573
|
opencodeInstructionsArrayResolvable: {
|
|
1598
1574
|
id: 'OC-O02',
|
|
1599
|
-
name: 'instructions array
|
|
1575
|
+
name: 'instructions array uses validated local file paths',
|
|
1600
1576
|
check: (ctx) => {
|
|
1601
1577
|
const instructions = ctx.instructionsArray();
|
|
1602
1578
|
if (!Array.isArray(instructions) || instructions.length === 0) return null;
|
|
1603
1579
|
for (const instruction of instructions) {
|
|
1604
1580
|
if (typeof instruction !== 'string') continue;
|
|
1605
|
-
|
|
1606
|
-
if (instruction.startsWith('http') || instruction.includes('*')) continue;
|
|
1607
|
-
// Check local file references
|
|
1581
|
+
if (instruction.startsWith('http') || instruction.includes('*')) return false;
|
|
1608
1582
|
if (!ctx.fileContent(instruction)) return false;
|
|
1609
1583
|
}
|
|
1610
1584
|
return true;
|
|
@@ -1612,7 +1586,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1612
1586
|
impact: 'high',
|
|
1613
1587
|
rating: 4,
|
|
1614
1588
|
category: 'mixed-agent',
|
|
1615
|
-
fix: '
|
|
1589
|
+
fix: 'Prefer direct local file paths in the `instructions` array. Current runtime clearly validated direct files, but glob and URL sources were not visibly applied in `run`, so treat them as experimental until reproduced.',
|
|
1616
1590
|
template: 'opencode-config',
|
|
1617
1591
|
file: (ctx) => configFileName(ctx),
|
|
1618
1592
|
line: () => null,
|
|
@@ -1620,18 +1594,16 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1620
1594
|
|
|
1621
1595
|
opencodeGlobalAgentsNoConflict: {
|
|
1622
1596
|
id: 'OC-O03',
|
|
1623
|
-
name: '
|
|
1597
|
+
name: 'Project docs do not depend on global AGENTS.md behavior',
|
|
1624
1598
|
check: (ctx) => {
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
// Basic conflict check: same heading structure with different content
|
|
1629
|
-
return true; // Soft pass; deep-review does thorough analysis
|
|
1599
|
+
const docs = `${ctx.fileContent('AGENTS.md') || ''}\n${ctx.fileContent('README.md') || ''}`;
|
|
1600
|
+
if (!docs.trim()) return null;
|
|
1601
|
+
return !/~\/\.config\/opencode\/AGENTS\.md|global AGENTS/i.test(docs);
|
|
1630
1602
|
},
|
|
1631
1603
|
impact: 'medium',
|
|
1632
1604
|
rating: 3,
|
|
1633
1605
|
category: 'mixed-agent',
|
|
1634
|
-
fix: '
|
|
1606
|
+
fix: 'Do not rely on `~/.config/opencode/AGENTS.md` as a guaranteed project behavior. Current Windows runtime did not show global AGENTS loading in `run`, so keep project-critical guidance in repo files.',
|
|
1635
1607
|
template: 'opencode-agents-md',
|
|
1636
1608
|
file: () => 'AGENTS.md',
|
|
1637
1609
|
line: () => null,
|
|
@@ -1643,7 +1615,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1643
1615
|
|
|
1644
1616
|
opencodeConfigMergeConsistent: {
|
|
1645
1617
|
id: 'OC-P01',
|
|
1646
|
-
name: '
|
|
1618
|
+
name: 'Observed config merge hierarchy does not produce conflicting values',
|
|
1647
1619
|
check: (ctx) => {
|
|
1648
1620
|
const projectConfig = ctx.configJson();
|
|
1649
1621
|
const globalConfig = ctx.globalConfigJson();
|
|
@@ -1658,7 +1630,7 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1658
1630
|
impact: 'high',
|
|
1659
1631
|
rating: 4,
|
|
1660
1632
|
category: 'propagation',
|
|
1661
|
-
fix: 'Review
|
|
1633
|
+
fix: 'Review the observed precedence chain: global `opencode.json` < `OPENCODE_CONFIG` < project `opencode.json` < `.opencode/opencode.json` < `OPENCODE_CONFIG_CONTENT`. Treat `.well-known/opencode` as remote-only until you have runtime proof.',
|
|
1662
1634
|
template: 'opencode-config',
|
|
1663
1635
|
file: (ctx) => configFileName(ctx),
|
|
1664
1636
|
line: () => null,
|
|
@@ -1708,6 +1680,8 @@ const OPENCODE_TECHNIQUES = {
|
|
|
1708
1680
|
},
|
|
1709
1681
|
};
|
|
1710
1682
|
|
|
1683
|
+
attachSourceUrls('opencode', OPENCODE_TECHNIQUES);
|
|
1684
|
+
|
|
1711
1685
|
module.exports = {
|
|
1712
1686
|
OPENCODE_TECHNIQUES,
|
|
1713
1687
|
};
|