@nerviq/cli 0.9.0-beta.2 → 0.9.1
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/README.md +35 -423
- package/package.json +1 -1
- package/src/codex/setup.js +2 -14
- package/src/codex/techniques.js +630 -25
- package/src/techniques.js +127 -35
package/src/codex/techniques.js
CHANGED
|
@@ -1012,6 +1012,345 @@ function pluginConfigIssue(ctx) {
|
|
|
1012
1012
|
}
|
|
1013
1013
|
}
|
|
1014
1014
|
|
|
1015
|
+
function primaryDocsPath(ctx) {
|
|
1016
|
+
return agentsPath(ctx) || (ctx.fileContent('README.md') ? 'README.md' : null);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function webSearchModeRelevant(ctx) {
|
|
1020
|
+
const config = ctx.configToml();
|
|
1021
|
+
if (!config.ok || !config.data) return false;
|
|
1022
|
+
|
|
1023
|
+
const profiles = config.data.profiles && typeof config.data.profiles === 'object'
|
|
1024
|
+
? Object.values(config.data.profiles)
|
|
1025
|
+
: [];
|
|
1026
|
+
if (config.data.web_search !== undefined || profiles.some((profile) => profile && typeof profile === 'object' && profile.web_search !== undefined)) {
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
const docs = docsBundle(ctx);
|
|
1031
|
+
const workflowUsesSearch = workflowArtifacts(ctx).some((workflow) => /\s--search\b|\bweb_search\b/i.test(workflow.content));
|
|
1032
|
+
return workflowUsesSearch || /\s--search\b|\bweb_search\b|\blive search\b|\bcached search\b/i.test(docs);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function webSearchModeIssue(ctx) {
|
|
1036
|
+
const config = ctx.configToml();
|
|
1037
|
+
if (!config.ok || !config.data) return null;
|
|
1038
|
+
|
|
1039
|
+
const validModes = new Set(['cached', 'live', 'disabled']);
|
|
1040
|
+
const docs = docsBundle(ctx);
|
|
1041
|
+
const searchHintPresent = workflowArtifacts(ctx).some((workflow) => /\s--search\b|\bweb_search\b/i.test(workflow.content)) ||
|
|
1042
|
+
/\s--search\b|\bweb_search\b|\blive search\b|\bcached search\b/i.test(docs);
|
|
1043
|
+
const rootSearch = config.data.web_search;
|
|
1044
|
+
const rootEffort = config.data.model_reasoning_effort;
|
|
1045
|
+
const profiles = config.data.profiles && typeof config.data.profiles === 'object' ? config.data.profiles : {};
|
|
1046
|
+
|
|
1047
|
+
if (rootSearch !== undefined && !validModes.has(`${rootSearch}`)) {
|
|
1048
|
+
return { filePath: '.codex/config.toml', line: configKeyLine(ctx, 'web_search') || 1 };
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (rootEffort === 'minimal' && ((typeof rootSearch === 'string' && rootSearch !== 'disabled') || (rootSearch === undefined && searchHintPresent))) {
|
|
1052
|
+
return {
|
|
1053
|
+
filePath: '.codex/config.toml',
|
|
1054
|
+
line: configKeyLine(ctx, 'model_reasoning_effort') || configKeyLine(ctx, 'web_search') || 1,
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
for (const [name, profile] of Object.entries(profiles)) {
|
|
1059
|
+
if (!profile || typeof profile !== 'object') continue;
|
|
1060
|
+
if (profile.web_search !== undefined && !validModes.has(`${profile.web_search}`)) {
|
|
1061
|
+
return {
|
|
1062
|
+
filePath: '.codex/config.toml',
|
|
1063
|
+
line: configSectionKeyLine(ctx, `profiles.${name}`, 'web_search') ||
|
|
1064
|
+
(configSections(ctx).find((section) => section.section === `profiles.${name}`) || {}).line ||
|
|
1065
|
+
1,
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const effectiveSearch = profile.web_search !== undefined ? profile.web_search : rootSearch;
|
|
1070
|
+
if (profile.model_reasoning_effort === 'minimal' &&
|
|
1071
|
+
((typeof effectiveSearch === 'string' && effectiveSearch !== 'disabled') || (effectiveSearch === undefined && searchHintPresent))) {
|
|
1072
|
+
return {
|
|
1073
|
+
filePath: '.codex/config.toml',
|
|
1074
|
+
line: configSectionKeyLine(ctx, `profiles.${name}`, 'model_reasoning_effort') ||
|
|
1075
|
+
configSectionKeyLine(ctx, `profiles.${name}`, 'web_search') ||
|
|
1076
|
+
configKeyLine(ctx, 'web_search') ||
|
|
1077
|
+
(configSections(ctx).find((section) => section.section === `profiles.${name}`) || {}).line ||
|
|
1078
|
+
1,
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
return null;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
function requirementsTomlIssue(ctx) {
|
|
1087
|
+
const content = ctx.fileContent('requirements.toml');
|
|
1088
|
+
if (!content) return null;
|
|
1089
|
+
|
|
1090
|
+
const hasMeaningfulContent = content.split(/\r?\n/).some((line) => {
|
|
1091
|
+
const trimmed = line.trim();
|
|
1092
|
+
return trimmed && !trimmed.startsWith('#');
|
|
1093
|
+
});
|
|
1094
|
+
if (!hasMeaningfulContent) {
|
|
1095
|
+
return { filePath: 'requirements.toml', line: 1 };
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
const docs = docsBundle(ctx);
|
|
1099
|
+
const acknowledged = /\brequirements\.toml\b|\badmin[- ]enforced\b|\bmanaged configuration\b|\bmanaged\b/i.test(docs);
|
|
1100
|
+
return acknowledged ? null : { filePath: primaryDocsPath(ctx) || 'requirements.toml', line: 1 };
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
function sharedOrManagedMachineSignals(ctx) {
|
|
1104
|
+
const docs = docsBundle(ctx);
|
|
1105
|
+
return Boolean(ctx.fileContent('requirements.toml')) ||
|
|
1106
|
+
/\bshared\b|\bmanaged\b|\badmin[- ]enforced\b|\bmulti-user\b|\benterprise\b|\bkiosk\b|\bvdi\b/i.test(docs);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function authCredentialsStoreIssue(ctx) {
|
|
1110
|
+
if (!ctx.fileContent('.codex/config.toml')) return null;
|
|
1111
|
+
if (!sharedOrManagedMachineSignals(ctx)) return null;
|
|
1112
|
+
|
|
1113
|
+
const value = ctx.configValue('cli_auth_credentials_store');
|
|
1114
|
+
if (value === undefined) {
|
|
1115
|
+
return { filePath: '.codex/config.toml', line: configKeyLine(ctx, 'cli_auth_credentials_store') || 1 };
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
return ['auto', 'file', 'keyring'].includes(`${value}`)
|
|
1119
|
+
? null
|
|
1120
|
+
: { filePath: '.codex/config.toml', line: configKeyLine(ctx, 'cli_auth_credentials_store') || 1 };
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function protectedPathAssumptionRelevant(ctx) {
|
|
1124
|
+
return ctx.configValue('sandbox_mode') === 'workspace-write' && /\.(git|codex|agents)\b/i.test(docsBundle(ctx));
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
function protectedPathAssumptionIssue(ctx) {
|
|
1128
|
+
if (!protectedPathAssumptionRelevant(ctx)) return null;
|
|
1129
|
+
|
|
1130
|
+
const docs = docsBundle(ctx);
|
|
1131
|
+
const riskyPattern = /\.(git|codex|agents)\b[\s\S]{0,120}\b(edit|modify|write|delete|remove|patch|update|commit)\b/i;
|
|
1132
|
+
const safePattern = /\.(git|codex|agents)\b[\s\S]{0,120}\b(read-only|read only|protected|not writable|cannot (?:be )?(?:edited|modified|written)|blocked)\b/i;
|
|
1133
|
+
if (safePattern.test(docs)) return null;
|
|
1134
|
+
if (!riskyPattern.test(docs)) return null;
|
|
1135
|
+
|
|
1136
|
+
return {
|
|
1137
|
+
filePath: primaryDocsPath(ctx) || '.codex/config.toml',
|
|
1138
|
+
line: firstLineMatching(docs, /\.(git|codex|agents)\b/i) || 1,
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
function mcpHttpAuthAndCallbackRelevant(ctx) {
|
|
1143
|
+
if (!projectScopedMcpPresent(ctx)) return false;
|
|
1144
|
+
const servers = projectMcpServers(ctx);
|
|
1145
|
+
const hasRemoteHeaderAuth = Object.values(servers || {}).some((server) => server && server.url && (server.env_http_headers || server.http_headers));
|
|
1146
|
+
return hasRemoteHeaderAuth ||
|
|
1147
|
+
typeof ctx.configValue('mcp_oauth_callback_port') === 'number' ||
|
|
1148
|
+
Boolean(ctx.configValue('mcp_oauth_callback_url'));
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
function mcpHttpAuthAndCallbackIssue(ctx) {
|
|
1152
|
+
const docs = docsBundle(ctx);
|
|
1153
|
+
const callbackPort = ctx.configValue('mcp_oauth_callback_port');
|
|
1154
|
+
const callbackUrl = ctx.configValue('mcp_oauth_callback_url');
|
|
1155
|
+
|
|
1156
|
+
if ((typeof callbackPort === 'number' || callbackUrl) && !/\boauth\b|\bcallback\b|\bredirect\b|\bloopback\b/i.test(docs)) {
|
|
1157
|
+
return {
|
|
1158
|
+
filePath: '.codex/config.toml',
|
|
1159
|
+
line: configKeyLine(ctx, 'mcp_oauth_callback_url') || configKeyLine(ctx, 'mcp_oauth_callback_port') || 1,
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
for (const [id, server] of Object.entries(projectMcpServers(ctx))) {
|
|
1164
|
+
if (!server || !server.url) continue;
|
|
1165
|
+
if (!(server.env_http_headers || server.http_headers)) continue;
|
|
1166
|
+
|
|
1167
|
+
const hasDocNote = new RegExp(`\\b${escapeRegex(id)}\\b[\\s\\S]{0,180}\\b(header|oauth|callback|auth|token)\\b`, 'i').test(docs);
|
|
1168
|
+
if (!hasDocNote) {
|
|
1169
|
+
return {
|
|
1170
|
+
filePath: '.codex/config.toml',
|
|
1171
|
+
line: configSectionKeyLine(ctx, `mcp_servers.${id}`, 'env_http_headers') ||
|
|
1172
|
+
configSectionKeyLine(ctx, `mcp_servers.${id}`, 'http_headers') ||
|
|
1173
|
+
(configSections(ctx).find((section) => section.section === `mcp_servers.${id}`) || {}).line ||
|
|
1174
|
+
1,
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function batchStyleSubagentFlowPresent(ctx) {
|
|
1183
|
+
const files = ctx.customAgentFiles ? ctx.customAgentFiles() : [];
|
|
1184
|
+
const csvPattern = /\bspawn_agents_on_csv\b|\breport_agent_job_result\b|\boutput_csv_path\b|\boutput_schema\b|\bmax_concurrency\b|\bmax_runtime_seconds\b/i;
|
|
1185
|
+
return files.some((fileName) => {
|
|
1186
|
+
const content = ctx.fileContent(path.join('.codex', 'agents', fileName)) || '';
|
|
1187
|
+
return csvPattern.test(content);
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
function csvBatchAgentIssue(ctx) {
|
|
1192
|
+
const files = ctx.customAgentFiles ? ctx.customAgentFiles() : [];
|
|
1193
|
+
const csvPattern = /\bspawn_agents_on_csv\b|\breport_agent_job_result\b|\boutput_csv_path\b|\boutput_schema\b|\bmax_concurrency\b|\bmax_runtime_seconds\b/i;
|
|
1194
|
+
|
|
1195
|
+
for (const fileName of files) {
|
|
1196
|
+
const content = ctx.fileContent(path.join('.codex', 'agents', fileName)) || '';
|
|
1197
|
+
if (!csvPattern.test(content)) continue;
|
|
1198
|
+
if (typeof ctx.configValue('agents.job_max_runtime_seconds') === 'number') return null;
|
|
1199
|
+
return {
|
|
1200
|
+
filePath: '.codex/config.toml',
|
|
1201
|
+
line: configSectionKeyLine(ctx, 'agents', 'job_max_runtime_seconds') || 1,
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
function nicknameCandidatesIssue(ctx) {
|
|
1209
|
+
const files = ctx.customAgentFiles ? ctx.customAgentFiles() : [];
|
|
1210
|
+
const seen = new Map();
|
|
1211
|
+
|
|
1212
|
+
for (const fileName of files) {
|
|
1213
|
+
const parsed = ctx.customAgentConfig(fileName);
|
|
1214
|
+
if (!parsed.ok || !parsed.data) {
|
|
1215
|
+
return { filePath: `.codex/agents/${fileName}`, line: 1 };
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
const candidates = parsed.data.nickname_candidates;
|
|
1219
|
+
if (candidates === undefined) continue;
|
|
1220
|
+
if (!Array.isArray(candidates) || candidates.length === 0) {
|
|
1221
|
+
const content = ctx.fileContent(path.join('.codex', 'agents', fileName)) || '';
|
|
1222
|
+
return {
|
|
1223
|
+
filePath: `.codex/agents/${fileName}`,
|
|
1224
|
+
line: firstLineMatching(content, /^\s*nickname_candidates\s*=/i) || 1,
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
const localSeen = new Set();
|
|
1229
|
+
for (const candidate of candidates) {
|
|
1230
|
+
const normalized = typeof candidate === 'string' ? candidate.trim() : '';
|
|
1231
|
+
const canonical = normalized.toLowerCase();
|
|
1232
|
+
if (!normalized || !/^[A-Za-z0-9 _-]+$/.test(normalized) || localSeen.has(canonical) || seen.has(canonical)) {
|
|
1233
|
+
const content = ctx.fileContent(path.join('.codex', 'agents', fileName)) || '';
|
|
1234
|
+
return {
|
|
1235
|
+
filePath: `.codex/agents/${fileName}`,
|
|
1236
|
+
line: firstLineMatching(content, /^\s*nickname_candidates\s*=/i) || 1,
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
localSeen.add(canonical);
|
|
1240
|
+
seen.set(canonical, fileName);
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
function nativeWindowsConfigRelevant(ctx) {
|
|
1248
|
+
return configSections(ctx).some((section) => section.section === 'windows') ||
|
|
1249
|
+
/\bnative windows\b|\bwindows sandbox\b|\bprivate desktop\b/i.test(docsBundle(ctx));
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
function windowsSandboxModeIssue(ctx) {
|
|
1253
|
+
if (!nativeWindowsConfigRelevant(ctx)) return null;
|
|
1254
|
+
|
|
1255
|
+
const value = ctx.configValue('windows.sandbox');
|
|
1256
|
+
if (value === undefined) {
|
|
1257
|
+
return {
|
|
1258
|
+
filePath: '.codex/config.toml',
|
|
1259
|
+
line: configSectionKeyLine(ctx, 'windows', 'sandbox') ||
|
|
1260
|
+
(configSections(ctx).find((section) => section.section === 'windows') || {}).line ||
|
|
1261
|
+
1,
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
return ['elevated', 'unelevated'].includes(`${value}`)
|
|
1266
|
+
? null
|
|
1267
|
+
: {
|
|
1268
|
+
filePath: '.codex/config.toml',
|
|
1269
|
+
line: configSectionKeyLine(ctx, 'windows', 'sandbox') ||
|
|
1270
|
+
(configSections(ctx).find((section) => section.section === 'windows') || {}).line ||
|
|
1271
|
+
1,
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function appAutomationRelevant(ctx) {
|
|
1276
|
+
return /\bautomations?\b|\btriage inbox\b|\bbackground tasks?\b/i.test(docsBundle(ctx));
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
function automationAppRunningIssue(ctx) {
|
|
1280
|
+
if (!appAutomationRelevant(ctx)) return null;
|
|
1281
|
+
|
|
1282
|
+
const docs = docsBundle(ctx);
|
|
1283
|
+
const acknowledged = /\bapp needs to be running\b|\bkeep the app running\b|\bCodex app\b[\s\S]{0,80}\brunning\b|\bselected project\b[\s\S]{0,80}\bon disk\b/i.test(docs);
|
|
1284
|
+
return acknowledged
|
|
1285
|
+
? null
|
|
1286
|
+
: {
|
|
1287
|
+
filePath: primaryDocsPath(ctx) || 'README.md',
|
|
1288
|
+
line: firstLineMatching(docs, /\bautomations?\b|\btriage inbox\b|\bbackground tasks?\b/i) || 1,
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function codexActionPromptSourceIssue(ctx) {
|
|
1293
|
+
for (const workflow of workflowArtifacts(ctx)) {
|
|
1294
|
+
if (!/uses:\s*openai\/codex-action@/i.test(workflow.content)) continue;
|
|
1295
|
+
|
|
1296
|
+
const hasPrompt = /^\s*prompt\s*:/im.test(workflow.content);
|
|
1297
|
+
const hasPromptFile = /^\s*prompt-file\s*:/im.test(workflow.content);
|
|
1298
|
+
if (hasPrompt && hasPromptFile) {
|
|
1299
|
+
return {
|
|
1300
|
+
filePath: workflow.filePath,
|
|
1301
|
+
line: firstLineMatching(workflow.content, /^\s*prompt(?:-file)?\s*:/im) || 1,
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
if (!hasPrompt && !hasPromptFile) {
|
|
1306
|
+
return {
|
|
1307
|
+
filePath: workflow.filePath,
|
|
1308
|
+
line: firstLineMatching(workflow.content, /uses:\s*openai\/codex-action@/i) || 1,
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
return null;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
function codexActionTriggerAllowlistIssue(ctx) {
|
|
1317
|
+
for (const workflow of workflowArtifacts(ctx)) {
|
|
1318
|
+
if (!/uses:\s*openai\/codex-action@/i.test(workflow.content)) continue;
|
|
1319
|
+
|
|
1320
|
+
const triggerLine = firstLineMatching(workflow.content, /\bissue_comment\b|\bpull_request_target\b|\bpull_request_review_comment\b|\bdiscussion_comment\b/i);
|
|
1321
|
+
if (!triggerLine) continue;
|
|
1322
|
+
|
|
1323
|
+
const hasAllowUsers = /^\s*allow-users\s*:/im.test(workflow.content);
|
|
1324
|
+
const hasAllowBots = /^\s*allow-bots\s*:/im.test(workflow.content);
|
|
1325
|
+
if (!hasAllowUsers && !hasAllowBots) {
|
|
1326
|
+
return { filePath: workflow.filePath, line: triggerLine };
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
return null;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
function codexActionExternalTriggersPresent(ctx) {
|
|
1334
|
+
return workflowArtifacts(ctx).some((workflow) =>
|
|
1335
|
+
/uses:\s*openai\/codex-action@/i.test(workflow.content) &&
|
|
1336
|
+
/\bissue_comment\b|\bpull_request_target\b|\bpull_request_review_comment\b|\bdiscussion_comment\b/i.test(workflow.content));
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function desktopProjectMcpCaveatIssue(ctx) {
|
|
1340
|
+
if (!projectScopedMcpPresent(ctx)) return null;
|
|
1341
|
+
|
|
1342
|
+
const docs = docsBundle(ctx);
|
|
1343
|
+
if (!/\bdesktop\b|\bide\b|\bextension\b/i.test(docs)) return null;
|
|
1344
|
+
|
|
1345
|
+
const caveated = /\btrusted project\b|\btrust\b|\brepo-local\b|\bproject-scoped\b|\bglobal config\b|\buser-global\b|\bmay be ignored\b/i.test(docs);
|
|
1346
|
+
return caveated
|
|
1347
|
+
? null
|
|
1348
|
+
: {
|
|
1349
|
+
filePath: primaryDocsPath(ctx) || '.codex/config.toml',
|
|
1350
|
+
line: firstLineMatching(docs, /\bdesktop\b|\bide\b|\bextension\b/i) || 1,
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1015
1354
|
const CODEX_TECHNIQUES = {
|
|
1016
1355
|
codexAgentsMd: {
|
|
1017
1356
|
id: 'CX-A01',
|
|
@@ -1203,7 +1542,7 @@ const CODEX_TECHNIQUES = {
|
|
|
1203
1542
|
impact: 'low',
|
|
1204
1543
|
rating: 3,
|
|
1205
1544
|
category: 'config',
|
|
1206
|
-
fix: 'Set `model_reasoning_effort` explicitly
|
|
1545
|
+
fix: 'Set `model_reasoning_effort` explicitly only when the repo needs a non-default reasoning posture; this setting is optional, and minimal effort should stay compatible with any `web_search` usage.',
|
|
1207
1546
|
template: 'codex-config',
|
|
1208
1547
|
file: () => '.codex/config.toml',
|
|
1209
1548
|
line: (ctx) => configKeyLine(ctx, 'model_reasoning_effort'),
|
|
@@ -1211,14 +1550,17 @@ const CODEX_TECHNIQUES = {
|
|
|
1211
1550
|
codexWeakModelExplicit: {
|
|
1212
1551
|
id: 'CX-B04',
|
|
1213
1552
|
name: 'Weak-task delegation model is explicit',
|
|
1214
|
-
check: (
|
|
1553
|
+
check: () => {
|
|
1554
|
+
// Retired: config key removed from official schema as of 2026-04-05
|
|
1555
|
+
return null;
|
|
1556
|
+
},
|
|
1215
1557
|
impact: 'medium',
|
|
1216
1558
|
rating: 4,
|
|
1217
1559
|
category: 'config',
|
|
1218
|
-
fix: '
|
|
1219
|
-
template:
|
|
1220
|
-
file: () =>
|
|
1221
|
-
line: (
|
|
1560
|
+
fix: '`model_for_weak_tasks` was removed from the official Codex config schema as of 2026-04-05. This check is retired and no repo change is required.',
|
|
1561
|
+
template: null,
|
|
1562
|
+
file: () => null,
|
|
1563
|
+
line: () => null,
|
|
1222
1564
|
},
|
|
1223
1565
|
codexConfigSectionPlacement: {
|
|
1224
1566
|
id: 'CX-B05',
|
|
@@ -1285,7 +1627,7 @@ const CODEX_TECHNIQUES = {
|
|
|
1285
1627
|
impact: 'low',
|
|
1286
1628
|
rating: 3,
|
|
1287
1629
|
category: 'config',
|
|
1288
|
-
fix: 'If you
|
|
1630
|
+
fix: 'Profiles are an advanced feature, not a baseline requirement. If you use them, make sure each profile contains real settings and any selected `profile` points to an existing profile section.',
|
|
1289
1631
|
template: null,
|
|
1290
1632
|
file: () => '.codex/config.toml',
|
|
1291
1633
|
line: (ctx) => configKeyLine(ctx, 'profile') || (profileSections(ctx)[0] || {}).line || null,
|
|
@@ -1293,14 +1635,80 @@ const CODEX_TECHNIQUES = {
|
|
|
1293
1635
|
codexFullAutoErrorModeExplicit: {
|
|
1294
1636
|
id: 'CX-B09',
|
|
1295
1637
|
name: 'full_auto_error_mode is explicit',
|
|
1296
|
-
check: (
|
|
1638
|
+
check: () => {
|
|
1639
|
+
// Retired: config key removed from official schema as of 2026-04-05
|
|
1640
|
+
return null;
|
|
1641
|
+
},
|
|
1297
1642
|
impact: 'medium',
|
|
1298
1643
|
rating: 4,
|
|
1299
1644
|
category: 'config',
|
|
1300
|
-
fix: '
|
|
1645
|
+
fix: '`full_auto_error_mode` was removed from the official Codex config schema as of 2026-04-05. This check is retired and no repo change is required.',
|
|
1646
|
+
template: null,
|
|
1647
|
+
file: () => null,
|
|
1648
|
+
line: () => null,
|
|
1649
|
+
},
|
|
1650
|
+
codexWebSearchModeCompatible: {
|
|
1651
|
+
id: 'CX-B10',
|
|
1652
|
+
name: 'web_search mode is explicit and compatible when search is part of the workflow',
|
|
1653
|
+
check: (ctx) => {
|
|
1654
|
+
if (!webSearchModeRelevant(ctx)) return null;
|
|
1655
|
+
return !webSearchModeIssue(ctx);
|
|
1656
|
+
},
|
|
1657
|
+
impact: 'medium',
|
|
1658
|
+
rating: 4,
|
|
1659
|
+
category: 'config',
|
|
1660
|
+
fix: 'When the repo uses search-aware Codex flows, set `web_search = "cached" | "live" | "disabled"` intentionally and avoid pairing search with `model_reasoning_effort = "minimal"` in the same effective profile.',
|
|
1301
1661
|
template: 'codex-config',
|
|
1302
|
-
file: () =>
|
|
1303
|
-
|
|
1662
|
+
file: (ctx) => {
|
|
1663
|
+
const issue = webSearchModeIssue(ctx);
|
|
1664
|
+
return issue ? issue.filePath : '.codex/config.toml';
|
|
1665
|
+
},
|
|
1666
|
+
line: (ctx) => {
|
|
1667
|
+
const issue = webSearchModeIssue(ctx);
|
|
1668
|
+
return issue ? issue.line : configKeyLine(ctx, 'web_search');
|
|
1669
|
+
},
|
|
1670
|
+
},
|
|
1671
|
+
codexRequirementsTomlRecognized: {
|
|
1672
|
+
id: 'CX-B11',
|
|
1673
|
+
name: 'requirements.toml posture is recognized when a managed layer exists',
|
|
1674
|
+
check: (ctx) => {
|
|
1675
|
+
if (!ctx.fileContent('requirements.toml')) return null;
|
|
1676
|
+
return !requirementsTomlIssue(ctx);
|
|
1677
|
+
},
|
|
1678
|
+
impact: 'medium',
|
|
1679
|
+
rating: 3,
|
|
1680
|
+
category: 'config',
|
|
1681
|
+
fix: 'If the repo uses `requirements.toml`, keep it non-empty and acknowledge that it is a managed/admin layer rather than an ordinary project preference file.',
|
|
1682
|
+
template: null,
|
|
1683
|
+
file: (ctx) => {
|
|
1684
|
+
const issue = requirementsTomlIssue(ctx);
|
|
1685
|
+
return issue ? issue.filePath : 'requirements.toml';
|
|
1686
|
+
},
|
|
1687
|
+
line: (ctx) => {
|
|
1688
|
+
const issue = requirementsTomlIssue(ctx);
|
|
1689
|
+
return issue ? issue.line : 1;
|
|
1690
|
+
},
|
|
1691
|
+
},
|
|
1692
|
+
codexCliAuthCredentialsStoreExplicit: {
|
|
1693
|
+
id: 'CX-B12',
|
|
1694
|
+
name: 'cli_auth_credentials_store is explicit on shared or managed setups',
|
|
1695
|
+
check: (ctx) => {
|
|
1696
|
+
const issue = authCredentialsStoreIssue(ctx);
|
|
1697
|
+
return issue ? false : (sharedOrManagedMachineSignals(ctx) ? true : null);
|
|
1698
|
+
},
|
|
1699
|
+
impact: 'high',
|
|
1700
|
+
rating: 4,
|
|
1701
|
+
category: 'config',
|
|
1702
|
+
fix: 'On shared or managed machines, set `cli_auth_credentials_store = "auto" | "keyring" | "file"` explicitly so Codex auth-cache handling is reviewable.',
|
|
1703
|
+
template: 'codex-config',
|
|
1704
|
+
file: (ctx) => {
|
|
1705
|
+
const issue = authCredentialsStoreIssue(ctx);
|
|
1706
|
+
return issue ? issue.filePath : '.codex/config.toml';
|
|
1707
|
+
},
|
|
1708
|
+
line: (ctx) => {
|
|
1709
|
+
const issue = authCredentialsStoreIssue(ctx);
|
|
1710
|
+
return issue ? issue.line : configKeyLine(ctx, 'cli_auth_credentials_store');
|
|
1711
|
+
},
|
|
1304
1712
|
},
|
|
1305
1713
|
codexApprovalPolicyExplicit: {
|
|
1306
1714
|
id: 'CX-C02',
|
|
@@ -1364,32 +1772,32 @@ const CODEX_TECHNIQUES = {
|
|
|
1364
1772
|
codexDisableResponseStorageForRegulatedRepos: {
|
|
1365
1773
|
id: 'CX-C05',
|
|
1366
1774
|
name: 'disable_response_storage is explicit for regulated repos',
|
|
1367
|
-
check: (
|
|
1368
|
-
|
|
1369
|
-
return
|
|
1775
|
+
check: () => {
|
|
1776
|
+
// Retired: config key removed from official schema as of 2026-04-05
|
|
1777
|
+
return null;
|
|
1370
1778
|
},
|
|
1371
1779
|
impact: 'medium',
|
|
1372
1780
|
rating: 4,
|
|
1373
1781
|
category: 'trust',
|
|
1374
|
-
fix: '
|
|
1375
|
-
template:
|
|
1376
|
-
file: () =>
|
|
1377
|
-
line: (
|
|
1782
|
+
fix: '`disable_response_storage` was removed from the official Codex config schema as of 2026-04-05. This check is retired and no repo change is required.',
|
|
1783
|
+
template: null,
|
|
1784
|
+
file: () => null,
|
|
1785
|
+
line: () => null,
|
|
1378
1786
|
},
|
|
1379
1787
|
codexHistorySendToServerExplicit: {
|
|
1380
1788
|
id: 'CX-C06',
|
|
1381
1789
|
name: 'history.send_to_server is explicit',
|
|
1382
|
-
check: (
|
|
1383
|
-
|
|
1384
|
-
return
|
|
1790
|
+
check: () => {
|
|
1791
|
+
// Retired: config key removed from official schema as of 2026-04-05
|
|
1792
|
+
return null;
|
|
1385
1793
|
},
|
|
1386
1794
|
impact: 'medium',
|
|
1387
1795
|
rating: 4,
|
|
1388
1796
|
category: 'trust',
|
|
1389
|
-
fix: '
|
|
1390
|
-
template:
|
|
1391
|
-
file: () =>
|
|
1392
|
-
line: (
|
|
1797
|
+
fix: '`history.send_to_server` was removed from the official Codex config schema as of 2026-04-05. This check is retired and no repo change is required.',
|
|
1798
|
+
template: null,
|
|
1799
|
+
file: () => null,
|
|
1800
|
+
line: () => null,
|
|
1393
1801
|
},
|
|
1394
1802
|
codexGitHubActionUnsafeJustified: {
|
|
1395
1803
|
id: 'CX-C07',
|
|
@@ -1448,6 +1856,48 @@ const CODEX_TECHNIQUES = {
|
|
|
1448
1856
|
return content ? findSecretLine(content) : null;
|
|
1449
1857
|
},
|
|
1450
1858
|
},
|
|
1859
|
+
codexProtectedPathsRespectedInWorkspaceWriteDocs: {
|
|
1860
|
+
id: 'CX-C10',
|
|
1861
|
+
name: 'Workspace-write docs do not imply protected paths are writable',
|
|
1862
|
+
check: (ctx) => {
|
|
1863
|
+
if (!protectedPathAssumptionRelevant(ctx)) return null;
|
|
1864
|
+
return !protectedPathAssumptionIssue(ctx);
|
|
1865
|
+
},
|
|
1866
|
+
impact: 'high',
|
|
1867
|
+
rating: 4,
|
|
1868
|
+
category: 'trust',
|
|
1869
|
+
fix: 'If repo docs mention `.git`, `.codex`, or `.agents` under workspace-write, describe them as protected/read-only rather than writable runtime surfaces.',
|
|
1870
|
+
template: 'codex-agents-md',
|
|
1871
|
+
file: (ctx) => {
|
|
1872
|
+
const issue = protectedPathAssumptionIssue(ctx);
|
|
1873
|
+
return issue ? issue.filePath : primaryDocsPath(ctx);
|
|
1874
|
+
},
|
|
1875
|
+
line: (ctx) => {
|
|
1876
|
+
const issue = protectedPathAssumptionIssue(ctx);
|
|
1877
|
+
return issue ? issue.line : null;
|
|
1878
|
+
},
|
|
1879
|
+
},
|
|
1880
|
+
codexWindowsSandboxModeExplicit: {
|
|
1881
|
+
id: 'CX-C11',
|
|
1882
|
+
name: 'Native Windows sandbox mode is explicit when Windows config is used',
|
|
1883
|
+
check: (ctx) => {
|
|
1884
|
+
const issue = windowsSandboxModeIssue(ctx);
|
|
1885
|
+
return issue ? false : (nativeWindowsConfigRelevant(ctx) ? true : null);
|
|
1886
|
+
},
|
|
1887
|
+
impact: 'high',
|
|
1888
|
+
rating: 4,
|
|
1889
|
+
category: 'trust',
|
|
1890
|
+
fix: 'If the repo relies on native Windows Codex settings, set `[windows] sandbox = "elevated" | "unelevated"` explicitly so the trust boundary is reviewable.',
|
|
1891
|
+
template: 'codex-config',
|
|
1892
|
+
file: (ctx) => {
|
|
1893
|
+
const issue = windowsSandboxModeIssue(ctx);
|
|
1894
|
+
return issue ? issue.filePath : '.codex/config.toml';
|
|
1895
|
+
},
|
|
1896
|
+
line: (ctx) => {
|
|
1897
|
+
const issue = windowsSandboxModeIssue(ctx);
|
|
1898
|
+
return issue ? issue.line : configSectionKeyLine(ctx, 'windows', 'sandbox');
|
|
1899
|
+
},
|
|
1900
|
+
},
|
|
1451
1901
|
codexRulesExistForRiskyCommands: {
|
|
1452
1902
|
id: 'CX-D01',
|
|
1453
1903
|
name: 'Rules exist for risky or out-of-sandbox command classes',
|
|
@@ -1769,6 +2219,48 @@ const CODEX_TECHNIQUES = {
|
|
|
1769
2219
|
return issue ? issue.line : null;
|
|
1770
2220
|
},
|
|
1771
2221
|
},
|
|
2222
|
+
codexMcpHttpAuthAndCallbacksDocumented: {
|
|
2223
|
+
id: 'CX-F07',
|
|
2224
|
+
name: 'MCP HTTP auth and callback fields are documented when used',
|
|
2225
|
+
check: (ctx) => {
|
|
2226
|
+
if (!mcpHttpAuthAndCallbackRelevant(ctx)) return null;
|
|
2227
|
+
return !mcpHttpAuthAndCallbackIssue(ctx);
|
|
2228
|
+
},
|
|
2229
|
+
impact: 'medium',
|
|
2230
|
+
rating: 4,
|
|
2231
|
+
category: 'mcp',
|
|
2232
|
+
fix: 'If remote MCP uses header-based auth or custom OAuth callback settings, document the header/callback posture in repo docs so setup stays reviewable.',
|
|
2233
|
+
template: null,
|
|
2234
|
+
file: (ctx) => {
|
|
2235
|
+
const issue = mcpHttpAuthAndCallbackIssue(ctx);
|
|
2236
|
+
return issue ? issue.filePath : '.codex/config.toml';
|
|
2237
|
+
},
|
|
2238
|
+
line: (ctx) => {
|
|
2239
|
+
const issue = mcpHttpAuthAndCallbackIssue(ctx);
|
|
2240
|
+
return issue ? issue.line : null;
|
|
2241
|
+
},
|
|
2242
|
+
},
|
|
2243
|
+
codexDesktopProjectMcpCaveatDocumented: {
|
|
2244
|
+
id: 'CX-F08',
|
|
2245
|
+
name: 'Project-scoped MCP docs caveat desktop or IDE behavior when relevant',
|
|
2246
|
+
check: (ctx) => {
|
|
2247
|
+
const issue = desktopProjectMcpCaveatIssue(ctx);
|
|
2248
|
+
return issue ? false : (projectScopedMcpPresent(ctx) && /\bdesktop\b|\bide\b|\bextension\b/i.test(docsBundle(ctx)) ? true : null);
|
|
2249
|
+
},
|
|
2250
|
+
impact: 'medium',
|
|
2251
|
+
rating: 3,
|
|
2252
|
+
category: 'mcp',
|
|
2253
|
+
fix: 'If repo-local MCP config is discussed for desktop or IDE use, note the trusted-project boundary and the possibility that user-global config may still matter on some surfaces.',
|
|
2254
|
+
template: 'codex-agents-md',
|
|
2255
|
+
file: (ctx) => {
|
|
2256
|
+
const issue = desktopProjectMcpCaveatIssue(ctx);
|
|
2257
|
+
return issue ? issue.filePath : primaryDocsPath(ctx);
|
|
2258
|
+
},
|
|
2259
|
+
line: (ctx) => {
|
|
2260
|
+
const issue = desktopProjectMcpCaveatIssue(ctx);
|
|
2261
|
+
return issue ? issue.line : null;
|
|
2262
|
+
},
|
|
2263
|
+
},
|
|
1772
2264
|
codexSkillsDirPresentWhenUsed: {
|
|
1773
2265
|
id: 'CX-G01',
|
|
1774
2266
|
name: '.agents/skills exists when Codex skills are used',
|
|
@@ -1941,6 +2433,54 @@ const CODEX_TECHNIQUES = {
|
|
|
1941
2433
|
return issue ? issue.line : null;
|
|
1942
2434
|
},
|
|
1943
2435
|
},
|
|
2436
|
+
codexJobMaxRuntimeExplicitForBatchAgents: {
|
|
2437
|
+
id: 'CX-H05',
|
|
2438
|
+
name: 'agents.job_max_runtime_seconds is explicit for batch-style subagent flows',
|
|
2439
|
+
check: (ctx) => {
|
|
2440
|
+
if (!batchStyleSubagentFlowPresent(ctx)) return null;
|
|
2441
|
+
const issue = csvBatchAgentIssue(ctx);
|
|
2442
|
+
return !issue;
|
|
2443
|
+
},
|
|
2444
|
+
impact: 'medium',
|
|
2445
|
+
rating: 3,
|
|
2446
|
+
category: 'agents',
|
|
2447
|
+
fix: 'If custom agents use CSV or batch-style fanout fields, set `[agents] job_max_runtime_seconds` explicitly so worker runtime is bounded and reviewable.',
|
|
2448
|
+
template: 'codex-config',
|
|
2449
|
+
file: (ctx) => {
|
|
2450
|
+
const issue = csvBatchAgentIssue(ctx);
|
|
2451
|
+
return issue ? issue.filePath : '.codex/config.toml';
|
|
2452
|
+
},
|
|
2453
|
+
line: (ctx) => {
|
|
2454
|
+
const issue = csvBatchAgentIssue(ctx);
|
|
2455
|
+
return issue ? issue.line : configSectionKeyLine(ctx, 'agents', 'job_max_runtime_seconds');
|
|
2456
|
+
},
|
|
2457
|
+
},
|
|
2458
|
+
codexNicknameCandidatesValid: {
|
|
2459
|
+
id: 'CX-H06',
|
|
2460
|
+
name: 'nickname_candidates are valid and unique when used',
|
|
2461
|
+
check: (ctx) => {
|
|
2462
|
+
const issue = nicknameCandidatesIssue(ctx);
|
|
2463
|
+
const hasNicknameCandidates = (ctx.customAgentFiles ? ctx.customAgentFiles() : []).some((fileName) => {
|
|
2464
|
+
const parsed = ctx.customAgentConfig(fileName);
|
|
2465
|
+
return parsed.ok && parsed.data && parsed.data.nickname_candidates !== undefined;
|
|
2466
|
+
});
|
|
2467
|
+
if (!hasNicknameCandidates) return null;
|
|
2468
|
+
return !issue;
|
|
2469
|
+
},
|
|
2470
|
+
impact: 'medium',
|
|
2471
|
+
rating: 3,
|
|
2472
|
+
category: 'agents',
|
|
2473
|
+
fix: 'When custom agents use `nickname_candidates`, keep them non-empty, ASCII-safe, and unique across the repo so display names stay deterministic.',
|
|
2474
|
+
template: null,
|
|
2475
|
+
file: (ctx) => {
|
|
2476
|
+
const issue = nicknameCandidatesIssue(ctx);
|
|
2477
|
+
return issue ? issue.filePath : '.codex/agents';
|
|
2478
|
+
},
|
|
2479
|
+
line: (ctx) => {
|
|
2480
|
+
const issue = nicknameCandidatesIssue(ctx);
|
|
2481
|
+
return issue ? issue.line : null;
|
|
2482
|
+
},
|
|
2483
|
+
},
|
|
1944
2484
|
codexExecUsageSafe: {
|
|
1945
2485
|
id: 'CX-I01',
|
|
1946
2486
|
name: 'codex exec usage avoids unsafe automation defaults',
|
|
@@ -2026,6 +2566,71 @@ const CODEX_TECHNIQUES = {
|
|
|
2026
2566
|
return issue ? issue.line : null;
|
|
2027
2567
|
},
|
|
2028
2568
|
},
|
|
2569
|
+
codexAutomationAppRunningAcknowledged: {
|
|
2570
|
+
id: 'CX-I05',
|
|
2571
|
+
name: 'App-running prerequisite is acknowledged for Codex app automations',
|
|
2572
|
+
check: (ctx) => {
|
|
2573
|
+
const issue = automationAppRunningIssue(ctx);
|
|
2574
|
+
return issue ? false : (appAutomationRelevant(ctx) ? true : null);
|
|
2575
|
+
},
|
|
2576
|
+
impact: 'medium',
|
|
2577
|
+
rating: 3,
|
|
2578
|
+
category: 'automation',
|
|
2579
|
+
fix: 'If the repo documents Codex app automations, note that the app must be running and the selected project must be available on disk.',
|
|
2580
|
+
template: 'codex-agents-md',
|
|
2581
|
+
file: (ctx) => {
|
|
2582
|
+
const issue = automationAppRunningIssue(ctx);
|
|
2583
|
+
return issue ? issue.filePath : primaryDocsPath(ctx);
|
|
2584
|
+
},
|
|
2585
|
+
line: (ctx) => {
|
|
2586
|
+
const issue = automationAppRunningIssue(ctx);
|
|
2587
|
+
return issue ? issue.line : null;
|
|
2588
|
+
},
|
|
2589
|
+
},
|
|
2590
|
+
codexGitHubActionSinglePromptSource: {
|
|
2591
|
+
id: 'CX-I06',
|
|
2592
|
+
name: 'Codex GitHub Action uses exactly one prompt source',
|
|
2593
|
+
check: (ctx) => {
|
|
2594
|
+
const hasAction = workflowArtifacts(ctx).some((workflow) => /uses:\s*openai\/codex-action@/i.test(workflow.content));
|
|
2595
|
+
if (!hasAction) return null;
|
|
2596
|
+
return !codexActionPromptSourceIssue(ctx);
|
|
2597
|
+
},
|
|
2598
|
+
impact: 'high',
|
|
2599
|
+
rating: 4,
|
|
2600
|
+
category: 'automation',
|
|
2601
|
+
fix: 'For each `openai/codex-action` workflow, choose exactly one prompt input: `prompt` or `prompt-file`.',
|
|
2602
|
+
template: null,
|
|
2603
|
+
file: (ctx) => {
|
|
2604
|
+
const issue = codexActionPromptSourceIssue(ctx);
|
|
2605
|
+
return issue ? issue.filePath : null;
|
|
2606
|
+
},
|
|
2607
|
+
line: (ctx) => {
|
|
2608
|
+
const issue = codexActionPromptSourceIssue(ctx);
|
|
2609
|
+
return issue ? issue.line : null;
|
|
2610
|
+
},
|
|
2611
|
+
},
|
|
2612
|
+
codexGitHubActionTriggerAllowlistsExplicit: {
|
|
2613
|
+
id: 'CX-I07',
|
|
2614
|
+
name: 'Codex GitHub Action uses trigger allowlists on externally triggered workflows',
|
|
2615
|
+
check: (ctx) => {
|
|
2616
|
+
if (!codexActionExternalTriggersPresent(ctx)) return null;
|
|
2617
|
+
const issue = codexActionTriggerAllowlistIssue(ctx);
|
|
2618
|
+
return !issue;
|
|
2619
|
+
},
|
|
2620
|
+
impact: 'high',
|
|
2621
|
+
rating: 4,
|
|
2622
|
+
category: 'automation',
|
|
2623
|
+
fix: 'If a Codex Action workflow is triggered by comments or `pull_request_target`, set `allow-users` or `allow-bots` explicitly to constrain who can invoke it.',
|
|
2624
|
+
template: null,
|
|
2625
|
+
file: (ctx) => {
|
|
2626
|
+
const issue = codexActionTriggerAllowlistIssue(ctx);
|
|
2627
|
+
return issue ? issue.filePath : null;
|
|
2628
|
+
},
|
|
2629
|
+
line: (ctx) => {
|
|
2630
|
+
const issue = codexActionTriggerAllowlistIssue(ctx);
|
|
2631
|
+
return issue ? issue.line : null;
|
|
2632
|
+
},
|
|
2633
|
+
},
|
|
2029
2634
|
codexReviewWorkflowDocumented: {
|
|
2030
2635
|
id: 'CX-J01',
|
|
2031
2636
|
name: 'Review workflow is available and documented',
|