@nerviq/cli 0.9.2 → 0.9.3

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * OpenCode Techniques — 68+ checks (OC-A01 through OC-P03)
2
+ * OpenCode Techniques — 73 checks (OC-A01 through OC-P03)
3
3
  *
4
4
  * Categories:
5
5
  * A. Instructions (7 checks)
@@ -62,18 +62,14 @@ const VALID_PLUGIN_EVENTS = new Set([
62
62
  'error', 'warning',
63
63
  ]);
64
64
 
65
- const DEPRECATED_CONFIG_KEYS = [
66
- { key: 'mode', replacement: 'agent', note: 'Use `agent` field instead of deprecated `mode`.' },
67
- ];
68
-
69
65
  // --- Helpers ---
70
66
 
71
67
  function agentsPath(ctx) {
72
- return ctx.agentsMdPath ? ctx.agentsMdPath() : null;
68
+ return ctx.fileContent('AGENTS.md') ? 'AGENTS.md' : null;
73
69
  }
74
70
 
75
71
  function agentsContent(ctx) {
76
- return ctx.agentsMdContent ? (ctx.agentsMdContent() || '') : '';
72
+ return ctx.fileContent('AGENTS.md') || '';
77
73
  }
78
74
 
79
75
  function configFileName(ctx) {
@@ -262,17 +258,16 @@ const OPENCODE_TECHNIQUES = {
262
258
 
263
259
  opencodeNoCoexistenceConflict: {
264
260
  id: 'OC-A05',
265
- name: 'No CLAUDE.md coexistence conflict (AGENTS.md wins if both exist in same dir)',
261
+ name: 'Mixed AGENTS.md + CLAUDE.md repos keep OpenCode guidance in AGENTS.md',
266
262
  check: (ctx) => {
267
263
  if (!ctx.hasAgentsMdAndClaudeMd || !ctx.hasAgentsMdAndClaudeMd()) return true;
268
- // Both exist: check that AGENTS.md is primary, warn about potential confusion
269
264
  const agentsMd = ctx.fileContent('AGENTS.md') || '';
270
265
  return agentsMd.length > 0;
271
266
  },
272
267
  impact: 'high',
273
268
  rating: 4,
274
269
  category: 'instructions',
275
- fix: 'When both AGENTS.md and CLAUDE.md exist, AGENTS.md takes precedence in OpenCode. Keep CLAUDE.md for Claude Code and use AGENTS.md for OpenCode instructions.',
270
+ 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
271
  template: 'opencode-agents-md',
277
272
  file: () => 'AGENTS.md',
278
273
  line: () => null,
@@ -291,7 +286,7 @@ const OPENCODE_TECHNIQUES = {
291
286
  category: 'instructions',
292
287
  fix: 'Remove generic filler ("Be helpful", "Write clean code") and replace with specific, actionable project instructions.',
293
288
  template: 'opencode-agents-md',
294
- file: () => agentsPath,
289
+ file: (ctx) => agentsPath(ctx),
295
290
  line: (ctx) => findFillerLine(agentsContent(ctx)),
296
291
  },
297
292
 
@@ -571,7 +566,7 @@ const OPENCODE_TECHNIQUES = {
571
566
 
572
567
  opencodeAllToolsCovered: {
573
568
  id: 'OC-C08',
574
- name: 'All 15 permissioned tools have explicit state',
569
+ name: 'Critical tool permissions are explicit',
575
570
  check: (ctx) => {
576
571
  const perms = ctx.toolPermissions();
577
572
  if (!perms || Object.keys(perms).length === 0) return null;
@@ -582,7 +577,7 @@ const OPENCODE_TECHNIQUES = {
582
577
  impact: 'high',
583
578
  rating: 4,
584
579
  category: 'permissions',
585
- fix: 'Set explicit permissions for at least the critical tools: bash, edit, read, task.',
580
+ 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
581
  template: 'opencode-permissions',
587
582
  file: (ctx) => configFileName(ctx),
588
583
  line: () => null,
@@ -670,21 +665,17 @@ const OPENCODE_TECHNIQUES = {
670
665
 
671
666
  opencodePluginHookGapAware: {
672
667
  id: 'OC-D05',
673
- name: 'No plugins relying on tool.execute.before for subagent/MCP coverage (broken: #5894, #2319)',
668
+ name: 'Plugin docs do not rely on stale hook-gap claims',
674
669
  check: (ctx) => {
675
670
  const pluginFiles = ctx.pluginFiles();
676
671
  if (pluginFiles.length === 0) return null;
677
672
  const docs = docsBundle(ctx);
678
- // If plugins reference tool interception, check for gap awareness
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;
673
+ 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
674
  },
684
675
  impact: 'high',
685
676
  rating: 4,
686
677
  category: 'plugins',
687
- fix: 'Document that tool.execute.before hooks do not intercept subagent/MCP calls (known bugs #5894, #2319).',
678
+ 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
679
  template: 'opencode-agents-md',
689
680
  file: () => 'AGENTS.md',
690
681
  line: () => null,
@@ -713,20 +704,17 @@ const OPENCODE_TECHNIQUES = {
713
704
 
714
705
  opencodeToolInterceptionGap: {
715
706
  id: 'OC-E02',
716
- name: 'tool.execute.before hook gap documented (#5894, #2319)',
707
+ name: 'Security docs do not overstate plugin hook bypass gaps',
717
708
  check: (ctx) => {
718
709
  const pluginFiles = ctx.pluginFiles();
719
710
  if (pluginFiles.length === 0) return null;
720
- // Check if the project uses plugins and has documented the gap
721
711
  const docs = docsBundle(ctx);
722
- const usesToolHooks = /\btool\.execute/i.test(docs);
723
- if (!usesToolHooks) return true;
724
- return /\b(bypass|gap|limitation|5894|2319)\b/i.test(docs);
712
+ return !/\b(subagent|mcp)\b[\s\S]{0,80}\b(bypass|gap|broken|2319|5894)\b/i.test(docs);
725
713
  },
726
714
  impact: 'high',
727
715
  rating: 4,
728
716
  category: 'security',
729
- fix: 'Document that subagent and MCP tool calls bypass tool.execute.before hooks (bugs #5894, #2319).',
717
+ 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
718
  template: 'opencode-agents-md',
731
719
  file: () => 'AGENTS.md',
732
720
  line: () => null,
@@ -734,19 +722,19 @@ const OPENCODE_TECHNIQUES = {
734
722
 
735
723
  opencodeAgentDenyNotBypassable: {
736
724
  id: 'OC-E03',
737
- name: 'Agent deny permissions not bypassable via SDK (#6396)',
725
+ name: 'Agent-permission docs do not rely on stale SDK bypass claims',
738
726
  check: (ctx) => {
739
727
  const agents = ctx.customAgents();
740
728
  if (!agents || Object.keys(agents).length === 0) return null;
741
729
  const docs = docsBundle(ctx);
742
730
  const usesAgentPerms = Object.values(agents).some(a => a && a.permissions);
743
731
  if (!usesAgentPerms) return true;
744
- return /\b(bypass|gap|limitation|6396)\b/i.test(docs);
732
+ return !/\b6396\b|\bagent\b[\s\S]{0,80}\b(bypass|gap|broken)\b/i.test(docs);
745
733
  },
746
734
  impact: 'high',
747
735
  rating: 4,
748
736
  category: 'security',
749
- fix: 'Document that agent deny permissions can be bypassed via SDK (bug #6396). Add compensating controls.',
737
+ 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
738
  template: 'opencode-agents-md',
751
739
  file: () => 'AGENTS.md',
752
740
  line: () => null,
@@ -781,7 +769,7 @@ const OPENCODE_TECHNIQUES = {
781
769
  impact: 'critical',
782
770
  rating: 5,
783
771
  category: 'security',
784
- fix: 'Use {env:VAR_NAME} substitution for secrets in opencode.json instead of hardcoded values.',
772
+ 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
773
  template: 'opencode-config',
786
774
  file: (ctx) => configFileName(ctx),
787
775
  line: (ctx) => {
@@ -821,9 +809,7 @@ const OPENCODE_TECHNIQUES = {
821
809
  if (!mcp || Object.keys(mcp).length === 0) return null;
822
810
  for (const [id, server] of Object.entries(mcp)) {
823
811
  if (!server) continue;
824
- // command should be an array in OpenCode MCP config
825
- if (server.command && !Array.isArray(server.command) && typeof server.command !== 'string') return false;
826
- // env is the wrong key — should be environment
812
+ if (server.command && !Array.isArray(server.command)) return false;
827
813
  if (server.env && !server.environment) return false;
828
814
  }
829
815
  return true;
@@ -831,7 +817,7 @@ const OPENCODE_TECHNIQUES = {
831
817
  impact: 'critical',
832
818
  rating: 5,
833
819
  category: 'mcp',
834
- fix: 'Fix MCP config schema: use "command": ["npx", ...] (array) and "environment": {} (not "env").',
820
+ 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
821
  template: 'opencode-config',
836
822
  file: (ctx) => configFileName(ctx),
837
823
  line: () => null,
@@ -843,17 +829,13 @@ const OPENCODE_TECHNIQUES = {
843
829
  check: (ctx) => {
844
830
  const mcp = ctx.mcpServers();
845
831
  if (!mcp || Object.keys(mcp).length === 0) return null;
846
- // Check if any MCP servers have tool restrictions configured
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('*'));
832
+ const hasMcpToolRestrictions = Object.values(mcp).some((server) => server && server.tools && Object.keys(server.tools).length > 0);
851
833
  return hasMcpToolRestrictions || Object.keys(mcp).length <= 2;
852
834
  },
853
835
  impact: 'high',
854
836
  rating: 4,
855
837
  category: 'mcp',
856
- fix: 'Add tool whitelisting for MCP servers: { "tools": { "my-mcp*": false } } to limit tool access.',
838
+ 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
839
  template: 'opencode-config',
858
840
  file: (ctx) => configFileName(ctx),
859
841
  line: () => null,
@@ -886,19 +868,19 @@ const OPENCODE_TECHNIQUES = {
886
868
 
887
869
  opencodeMcpHookLimitation: {
888
870
  id: 'OC-F04',
889
- name: 'MCP tool calls do not trigger plugin hooks — documented limitation (#2319)',
871
+ name: 'MCP hook caveats are treated as version-sensitive',
890
872
  check: (ctx) => {
891
873
  const mcp = ctx.mcpServers();
892
874
  const pluginFiles = ctx.pluginFiles();
893
875
  if (!mcp || Object.keys(mcp).length === 0) return null;
894
876
  if (pluginFiles.length === 0) return null;
895
877
  const docs = docsBundle(ctx);
896
- return /\b(mcp|2319|hook.*mcp|mcp.*hook)\b/i.test(docs);
878
+ 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
879
  },
898
880
  impact: 'medium',
899
881
  rating: 3,
900
882
  category: 'mcp',
901
- fix: 'Document that MCP tool calls bypass plugin hooks (#2319). Ensure security does not rely on hook interception for MCP.',
883
+ 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
884
  template: 'opencode-agents-md',
903
885
  file: () => 'AGENTS.md',
904
886
  line: () => null,
@@ -937,18 +919,17 @@ const OPENCODE_TECHNIQUES = {
937
919
 
938
920
  opencodeCiPermissionsPreset: {
939
921
  id: 'OC-G01',
940
- name: 'opencode run usage pre-configures all permissions to avoid hang (#10411)',
922
+ name: 'opencode run usage pre-configures permissions to avoid silent auto-rejects',
941
923
  check: (ctx) => {
942
924
  const workflows = workflowArtifacts(ctx);
943
925
  const hasOpencodeRun = workflows.some(w => /\bopencode\s+run\b/i.test(w.content));
944
926
  if (!hasOpencodeRun) return null;
945
- // Check that permissions are pre-configured
946
927
  return workflows.some(w => /\bpermissions?\b.*\ballow\b|\b--yes\b|\b--no-prompt\b/i.test(w.content));
947
928
  },
948
929
  impact: 'critical',
949
930
  rating: 5,
950
931
  category: 'ci',
951
- fix: 'Pre-configure all permissions when using `opencode run` in CI. Without this, the process hangs on permission prompts.',
932
+ 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
933
  template: 'opencode-ci',
953
934
  file: () => '.github/workflows/',
954
935
  line: () => null,
@@ -984,7 +965,7 @@ const OPENCODE_TECHNIQUES = {
984
965
  impact: 'medium',
985
966
  rating: 3,
986
967
  category: 'ci',
987
- fix: 'Use `--format json` when running OpenCode in CI for machine-readable output parsing.',
968
+ fix: 'Use `--format json` when running OpenCode in CI, and parse it as JSONL/event frames rather than expecting one monolithic JSON document.',
988
969
  template: 'opencode-ci',
989
970
  file: () => '.github/workflows/',
990
971
  line: () => null,
@@ -1036,19 +1017,16 @@ const OPENCODE_TECHNIQUES = {
1036
1017
 
1037
1018
  opencodeNoDeprecatedPatterns: {
1038
1019
  id: 'OC-H02',
1039
- name: 'No deprecated OpenCode patterns (mode -> agent migration)',
1020
+ name: 'Repo docs do not push the stale mode -> agent migration claim',
1040
1021
  check: (ctx) => {
1041
- const config = ctx.configJson();
1042
- if (!config.ok || !config.data) return null;
1043
- for (const { key } of DEPRECATED_CONFIG_KEYS) {
1044
- if (config.data[key] !== undefined) return false;
1045
- }
1046
- return true;
1022
+ const docs = docsBundle(ctx);
1023
+ if (!docs.trim()) return null;
1024
+ 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
1025
  },
1048
1026
  impact: 'medium',
1049
1027
  rating: 3,
1050
1028
  category: 'quality-deep',
1051
- fix: 'Replace deprecated config keys: use "agent" instead of "mode".',
1029
+ 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
1030
  template: 'opencode-config',
1053
1031
  file: (ctx) => configFileName(ctx),
1054
1032
  line: () => null,
@@ -1162,16 +1140,16 @@ const OPENCODE_TECHNIQUES = {
1162
1140
 
1163
1141
  opencodeSkillKebabCase: {
1164
1142
  id: 'OC-I03',
1165
- name: 'Skill names use kebab-case (not PascalCase — 0% invocation rate)',
1143
+ name: 'Skill names preferably use kebab-case',
1166
1144
  check: (ctx) => {
1167
1145
  const skillDirs = ctx.skillDirs();
1168
1146
  if (skillDirs.length === 0) return null;
1169
1147
  return skillDirs.every(name => /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name));
1170
1148
  },
1171
- impact: 'high',
1172
- rating: 4,
1149
+ impact: 'medium',
1150
+ rating: 2,
1173
1151
  category: 'skills',
1174
- fix: 'Rename skill directories to kebab-case (e.g., code-review, not CodeReview). PascalCase has 0% implicit invocation.',
1152
+ 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
1153
  template: 'opencode-skills',
1176
1154
  file: () => '.opencode/commands/',
1177
1155
  line: () => null,
@@ -1201,20 +1179,17 @@ const OPENCODE_TECHNIQUES = {
1201
1179
 
1202
1180
  opencodeSkillCompatPaths: {
1203
1181
  id: 'OC-I05',
1204
- name: '.claude/skills/ compatibility paths resolve correctly in OpenCode',
1182
+ name: 'OpenCode skill discovery accepts either .opencode/commands or .claude/skills',
1205
1183
  check: (ctx) => {
1206
1184
  const hasClaudeSkills = ctx.hasDir('.claude/skills');
1207
- if (!hasClaudeSkills) return null;
1208
1185
  const hasOpencodeCommands = ctx.hasDir('.opencode/commands');
1209
- // If both exist, they should be consistent
1210
- if (hasOpencodeCommands) return true;
1211
- // Claude skills exist but no OpenCode commands — warn
1212
- return false;
1186
+ if (!hasClaudeSkills && !hasOpencodeCommands) return null;
1187
+ return hasClaudeSkills || hasOpencodeCommands;
1213
1188
  },
1214
1189
  impact: 'medium',
1215
1190
  rating: 3,
1216
1191
  category: 'skills',
1217
- fix: 'If using .claude/skills/, also create .opencode/commands/ for OpenCode compatibility.',
1192
+ 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
1193
  template: 'opencode-skills',
1219
1194
  file: () => '.opencode/commands/',
1220
1195
  line: () => null,
@@ -1247,21 +1222,22 @@ const OPENCODE_TECHNIQUES = {
1247
1222
 
1248
1223
  opencodeAgentModeValid: {
1249
1224
  id: 'OC-J02',
1250
- name: 'Agent mode field is valid: "primary", "subagent", or "all"',
1225
+ name: 'Custom agent mode is valid when declared',
1251
1226
  check: (ctx) => {
1252
1227
  const agents = ctx.customAgents();
1253
1228
  if (!agents || Object.keys(agents).length === 0) return null;
1254
1229
  const validModes = new Set(['primary', 'subagent', 'all']);
1255
1230
  for (const [name, agent] of Object.entries(agents)) {
1256
1231
  if (!agent) continue;
1257
- if (agent.agent && !validModes.has(agent.agent)) return false;
1232
+ const mode = agent.mode || agent.agent;
1233
+ if (mode && !validModes.has(mode)) return false;
1258
1234
  }
1259
1235
  return true;
1260
1236
  },
1261
1237
  impact: 'medium',
1262
1238
  rating: 3,
1263
1239
  category: 'agents',
1264
- fix: 'Set agent "agent" field to one of: "primary", "subagent", or "all".',
1240
+ 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
1241
  template: 'opencode-config',
1266
1242
  file: (ctx) => configFileName(ctx),
1267
1243
  line: () => null,
@@ -1269,20 +1245,20 @@ const OPENCODE_TECHNIQUES = {
1269
1245
 
1270
1246
  opencodeBuiltinAgentsProtected: {
1271
1247
  id: 'OC-J03',
1272
- name: 'Built-in agents (build, plan) not accidentally overridden',
1248
+ name: 'Built-in agent overrides are intentional and documented',
1273
1249
  check: (ctx) => {
1274
1250
  const agents = ctx.customAgents();
1275
1251
  if (!agents || Object.keys(agents).length === 0) return null;
1276
1252
  const builtins = new Set(['build', 'plan', 'default']);
1277
- for (const name of Object.keys(agents)) {
1278
- if (builtins.has(name.toLowerCase())) return false;
1279
- }
1280
- return true;
1253
+ const overriding = Object.keys(agents).filter((name) => builtins.has(name.toLowerCase()));
1254
+ if (overriding.length === 0) return true;
1255
+ const docs = docsBundle(ctx);
1256
+ return /override|intentional|customized|replace/i.test(docs);
1281
1257
  },
1282
1258
  impact: 'medium',
1283
1259
  rating: 3,
1284
1260
  category: 'agents',
1285
- fix: 'Do not name custom agents "build", "plan", or "default" to avoid overriding built-in agents.',
1261
+ 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
1262
  template: 'opencode-config',
1287
1263
  file: (ctx) => configFileName(ctx),
1288
1264
  line: () => null,
@@ -1389,7 +1365,7 @@ const OPENCODE_TECHNIQUES = {
1389
1365
  impact: 'medium',
1390
1366
  rating: 3,
1391
1367
  category: 'tui',
1392
- fix: 'Fix JSONC syntax in tui.json. Ensure valid JSON with optional comments.',
1368
+ 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
1369
  template: 'opencode-config',
1394
1370
  file: () => 'tui.json',
1395
1371
  line: () => 1,
@@ -1415,7 +1391,7 @@ const OPENCODE_TECHNIQUES = {
1415
1391
  impact: 'low',
1416
1392
  rating: 2,
1417
1393
  category: 'tui',
1418
- fix: 'Fix JSON syntax errors in theme files under .opencode/themes/.',
1394
+ 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
1395
  template: 'opencode-config',
1420
1396
  file: () => '.opencode/themes/',
1421
1397
  line: () => null,
@@ -1432,7 +1408,7 @@ const OPENCODE_TECHNIQUES = {
1432
1408
  impact: 'medium',
1433
1409
  rating: 3,
1434
1410
  category: 'tui',
1435
- fix: 'Remove any sensitive data from tui.json.',
1411
+ fix: 'Remove any sensitive data from `tui.json`, and remember that `tui.json` was not meaningfully observable through headless `run` alone.',
1436
1412
  template: 'opencode-config',
1437
1413
  file: () => 'tui.json',
1438
1414
  line: (ctx) => {
@@ -1520,17 +1496,16 @@ const OPENCODE_TECHNIQUES = {
1520
1496
  id: 'OC-N02',
1521
1497
  name: 'Config references current OpenCode features (no removed or renamed keys)',
1522
1498
  check: (ctx) => {
1523
- const config = ctx.configJson();
1524
- if (!config.ok || !config.data) return null;
1525
- for (const { key } of DEPRECATED_CONFIG_KEYS) {
1526
- if (config.data[key] !== undefined) return false;
1527
- }
1528
- return true;
1499
+ const docs = docsBundle(ctx);
1500
+ const config = ctx.configContent();
1501
+ if (!docs.trim() && !config) return null;
1502
+ const combined = `${docs}\n${config || ''}`;
1503
+ return !/\bconfig\.json\b|\.well-known\/opencode|mode\s*->\s*agent|CLAUDE\.md fallback/i.test(combined);
1529
1504
  },
1530
1505
  impact: 'medium',
1531
1506
  rating: 3,
1532
1507
  category: 'release-freshness',
1533
- fix: 'Update deprecated config keys to their current equivalents.',
1508
+ 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
1509
  template: 'opencode-config',
1535
1510
  file: (ctx) => configFileName(ctx),
1536
1511
  line: () => null,
@@ -1596,15 +1571,13 @@ const OPENCODE_TECHNIQUES = {
1596
1571
 
1597
1572
  opencodeInstructionsArrayResolvable: {
1598
1573
  id: 'OC-O02',
1599
- name: 'instructions array in opencode.json references valid paths',
1574
+ name: 'instructions array uses validated local file paths',
1600
1575
  check: (ctx) => {
1601
1576
  const instructions = ctx.instructionsArray();
1602
1577
  if (!Array.isArray(instructions) || instructions.length === 0) return null;
1603
1578
  for (const instruction of instructions) {
1604
1579
  if (typeof instruction !== 'string') continue;
1605
- // Skip URLs and globs
1606
- if (instruction.startsWith('http') || instruction.includes('*')) continue;
1607
- // Check local file references
1580
+ if (instruction.startsWith('http') || instruction.includes('*')) return false;
1608
1581
  if (!ctx.fileContent(instruction)) return false;
1609
1582
  }
1610
1583
  return true;
@@ -1612,7 +1585,7 @@ const OPENCODE_TECHNIQUES = {
1612
1585
  impact: 'high',
1613
1586
  rating: 4,
1614
1587
  category: 'mixed-agent',
1615
- fix: 'Ensure all paths in the "instructions" array of opencode.json point to existing files.',
1588
+ 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
1589
  template: 'opencode-config',
1617
1590
  file: (ctx) => configFileName(ctx),
1618
1591
  line: () => null,
@@ -1620,18 +1593,16 @@ const OPENCODE_TECHNIQUES = {
1620
1593
 
1621
1594
  opencodeGlobalAgentsNoConflict: {
1622
1595
  id: 'OC-O03',
1623
- name: 'Global AGENTS.md does not conflict with project AGENTS.md',
1596
+ name: 'Project docs do not depend on global AGENTS.md behavior',
1624
1597
  check: (ctx) => {
1625
- const globalContent = ctx.globalAgentsMdContent ? ctx.globalAgentsMdContent() : null;
1626
- const projectContent = ctx.fileContent('AGENTS.md');
1627
- if (!globalContent || !projectContent) return null;
1628
- // Basic conflict check: same heading structure with different content
1629
- return true; // Soft pass; deep-review does thorough analysis
1598
+ const docs = `${ctx.fileContent('AGENTS.md') || ''}\n${ctx.fileContent('README.md') || ''}`;
1599
+ if (!docs.trim()) return null;
1600
+ return !/~\/\.config\/opencode\/AGENTS\.md|global AGENTS/i.test(docs);
1630
1601
  },
1631
1602
  impact: 'medium',
1632
1603
  rating: 3,
1633
1604
  category: 'mixed-agent',
1634
- fix: 'Review global AGENTS.md (~/.config/opencode/AGENTS.md) for conflicts with project AGENTS.md.',
1605
+ 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
1606
  template: 'opencode-agents-md',
1636
1607
  file: () => 'AGENTS.md',
1637
1608
  line: () => null,
@@ -1643,7 +1614,7 @@ const OPENCODE_TECHNIQUES = {
1643
1614
 
1644
1615
  opencodeConfigMergeConsistent: {
1645
1616
  id: 'OC-P01',
1646
- name: '6-level config merge hierarchy does not produce conflicting values',
1617
+ name: 'Observed config merge hierarchy does not produce conflicting values',
1647
1618
  check: (ctx) => {
1648
1619
  const projectConfig = ctx.configJson();
1649
1620
  const globalConfig = ctx.globalConfigJson();
@@ -1658,7 +1629,7 @@ const OPENCODE_TECHNIQUES = {
1658
1629
  impact: 'high',
1659
1630
  rating: 4,
1660
1631
  category: 'propagation',
1661
- fix: 'Review 6-level config merge: .well-known > global > env > project > .opencode/ > env content.',
1632
+ 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
1633
  template: 'opencode-config',
1663
1634
  file: (ctx) => configFileName(ctx),
1664
1635
  line: () => null,