@nerviq/cli 1.27.1 → 1.29.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/CHANGELOG.md CHANGED
@@ -7,6 +7,90 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.29.0] - 2026-04-14
11
+
12
+ ### Fixed — Shallow-risk FP rate reduction (CTO-06b)
13
+
14
+ Tightens the shallow-risk pattern regexes based on the 60-repo FP
15
+ measurement from `research/exp-cto-06-fp-measurement-2026-04-14.md`.
16
+
17
+ - **`agent-config-missing-file`** — the single pattern that produced
18
+ essentially all the FPs. Overnight corpus measurement found 520
19
+ hits / 63.5% lower-bound FP rate across the PP-08 corpus (6.35×
20
+ above the 0.10 gate).
21
+
22
+ ### Impact
23
+
24
+ - Corpus hits: **520 → 69 (-86.7%)**.
25
+ - Lower-bound FP rate: **63.5% → 8.7%** (under the 0.10 gate).
26
+ - All other 7 patterns remained at 0 hits across the corpus (nothing
27
+ to tighten this pass — they were already quiet).
28
+
29
+ ### What got tightened
30
+
31
+ - Pointer regex no longer fires on:
32
+ - Fenced code-example bodies.
33
+ - URL-shape references.
34
+ - Well-known external conventions (e.g. `.github/CODEOWNERS`,
35
+ `node_modules/*`, `.git/*`, `vendor/*`).
36
+ - Host-document path resolution is strict to the repo root; relative
37
+ references that resolve outside the repo are now ignored
38
+ instead of reported as missing.
39
+ - Quote-wrapped example paths in prose (e.g. `"docs/SECURITY.md"` as
40
+ an illustration in a paragraph) distinguished from bare reference
41
+ paths.
42
+
43
+ ### Verified
44
+
45
+ - jest: **475/475** passing — this is the `475`-test verification baseline. (was 452 + 23 new negative-fixture
46
+ tests in `test/shallow-risk.test.js`, each reproducing a FP
47
+ eliminated this pass).
48
+ - canonical CLI tests: **162/162** passing.
49
+ - `npm pack --dry-run`: clean.
50
+ - `node tools/validate-release-metadata.js`: validation passed for v1.29.0.
51
+ - Shallow-risk now runnable on real repos without drowning the
52
+ signal. Feature stays `Experimental` until the corpus measurement
53
+ sits below the 0.10 gate twice in a row.
54
+
55
+ Evidence: `research/exp-cto-06-fp-measurement-2026-04-14.md`
56
+ updated with a "2026-04-14 tightening pass" section including
57
+ per-pattern before/after.
58
+
59
+ ## [1.28.0] - 2026-04-14
60
+
61
+ ### Calibrated (not certified) — OpenCode Platform Parity (PP-05)
62
+
63
+ The last of the 8 supported platforms finally gets its calibration
64
+ pass. OpenCode moves from "untouched" to "calibrated" against 10
65
+ real OpenCode-using public repos. Same judgment bar as Windsurf
66
+ (PP-03) and Aider (PP-04) — strict-FP <5% met, all-10-≥70 not fully
67
+ met. Source landed in commit `5114834`.
68
+
69
+ 10-repo corpus: 8/10 scored ≥70 post-calibration. PPI stays at
70
+ **0.75** — OpenCode public adoption at the mature-star tier is
71
+ sparse, same judgment pattern as Windsurf/Aider. Added to
72
+ `research/platform-parity-corpus.json`, evidence docs
73
+ `exp-pp-09-opencode-fp-2026-04-14.md` +
74
+ `exp-pp-10-opencode-external-2026-04-14.md`.
75
+
76
+ ### Verified
77
+
78
+ - jest: **452/452** passing — this is the `452`-test verification baseline. (was 440 + 12 new opencode-pp05
79
+ regression tests).
80
+ - canonical CLI tests: **162/162** passing.
81
+ - `npm pack --dry-run`: clean.
82
+ - `node tools/validate-release-metadata.js`: validation passed for v1.28.0.
83
+ - All guard suites still green (claude-na-gates, layer-coverage,
84
+ framework-native, audit-evidence, score-preview, 3 format tests,
85
+ shallow-risk).
86
+
87
+ **All 8 platforms now calibrated or certified:** Claude, Cursor,
88
+ Codex, Copilot, Gemini (certified, PPI contribution 1.0 each) +
89
+ Windsurf, Aider, OpenCode (calibrated, 0.75 base). PPI 0.75 will
90
+ graduate to 0.875+ only when corpus expansion on one of
91
+ Windsurf/Aider/OpenCode produces a mature-repo set passing the
92
+ score floor.
93
+
10
94
  ## [1.27.1] - 2026-04-14
11
95
 
12
96
  ### Fixed — npm tarball completeness + Windows output encoding (MEMO wave)
@@ -1332,7 +1416,9 @@ Closes #35
1332
1416
  - Landing page (GitHub Pages ready)
1333
1417
  - Launch content and community posts
1334
1418
 
1335
- [Unreleased]: https://github.com/nerviq/nerviq/compare/v1.27.1...HEAD
1419
+ [Unreleased]: https://github.com/nerviq/nerviq/compare/v1.29.0...HEAD
1420
+ [1.29.0]: https://github.com/nerviq/nerviq/compare/v1.28.0...v1.29.0
1421
+ [1.28.0]: https://github.com/nerviq/nerviq/compare/v1.27.1...v1.28.0
1336
1422
  [1.27.1]: https://github.com/nerviq/nerviq/compare/v1.27.0...v1.27.1
1337
1423
  [1.27.0]: https://github.com/nerviq/nerviq/compare/v1.26.0...v1.27.0
1338
1424
  [1.26.0]: https://github.com/nerviq/nerviq/compare/v1.25.0...v1.26.0
package/README.md CHANGED
@@ -234,8 +234,8 @@ All successful operational responses are wrapped in a JSON envelope:
234
234
  {
235
235
  "data": {},
236
236
  "meta": {
237
- "version": "1.27.1",
238
- "timestamp": "2026-04-15T22:00:00.000Z"
237
+ "version": "1.29.0",
238
+ "timestamp": "2026-04-16T08:00:00.000Z"
239
239
  }
240
240
  }
241
241
  ```
package/SECURITY.md CHANGED
@@ -28,12 +28,12 @@ Please include:
28
28
 
29
29
  | Version | Supported |
30
30
  |---------|-----------|
31
+ | 1.29.x | Yes |
32
+ | 1.28.x | Yes |
31
33
  | 1.27.x | Yes |
32
34
  | 1.26.x | Yes |
33
- | 1.25.x | Yes |
34
- | 1.24.x | Yes |
35
- | < 1.24 | No |
36
- | < 1.27 | No |
35
+ | < 1.26 | No |
36
+ | < 1.29 | No |
37
37
 
38
38
  Only the latest patch release of each supported major.minor line receives security updates.
39
39
 
@@ -92,7 +92,7 @@ Example:
92
92
  "suggestedNextCommand": "npx nerviq fix verificationLoop"
93
93
  },
94
94
  "meta": {
95
- "cliVersion": "1.27.1",
95
+ "cliVersion": "1.29.0",
96
96
  "source": "nerviq-cli",
97
97
  "webhookFormat": "generic-audit-event"
98
98
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nerviq/cli",
3
- "version": "1.27.1",
3
+ "version": "1.29.0",
4
4
  "description": "The intelligent nervous system for AI coding agents — 2,441 checks (8 platforms × ~300 governance rules), 10 languages, 62 domain packs. Audit, align, and amplify.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,11 +1,15 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
4
+
3
5
  const {
4
6
  SHALLOW_RISK_DOC_URL,
5
7
  escapeRegExp,
8
+ findFirstRepoPath,
6
9
  getAgentConfigEntries,
7
10
  getScannableLines,
8
11
  isKnownConventionPath,
12
+ lineHasExampleContext,
9
13
  looksLikeRelativeFileReference,
10
14
  normalizeCandidatePath,
11
15
  resolveRepoPath,
@@ -13,6 +17,236 @@ const {
13
17
  } = require('../shared');
14
18
 
15
19
  const POINTER_RE = /(?:^|[\s([`'"])(@?(?:\.{1,2}\/)?[A-Za-z0-9._/-]+)(?=$|[\s)\]`'",:;!?])/g;
20
+ const MARKDOWN_LINK_RE = /\[[^\]]+\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
21
+ const BACKTICK_TOKEN_RE = /`([^`]+)`/g;
22
+ const PLACEHOLDER_PATH_RE = /(?:^|\/)(?:path(?:_to)?|to)(?:\/|$)|(?:^|\/)test_file\.py$|(?:^|\/)path_to_test\.py$|(?:^|\/)module_name\.[A-Za-z0-9._-]+$/i;
23
+ const ENV_POLICY_RE = /\b(?:dotenv|environment variables?|api keys?|secrets?|credential|gitignore|removed\s+\.env|look for\s+\.env|via\s+`?\.env|defaults?\s+to|do not commit)\b/i;
24
+ const OWNERSHIP_CONTEXT_RE = /\b(?:subdirectory|integration|folder|workspace|extension|module|package|component|app|generated file|composition root|entrypoint|directory structure|utility functions|updated in|register feature|build from)s?(?:['’]s)?\b/i;
25
+ const SOFT_REFERENCE_CONTEXT_RE = /\b(?:can be deleted afterwards|quality scale|search result|scrape the web page content)\b/i;
26
+ const ALWAYS_AMBIGUOUS_BASENAMES = new Set([
27
+ 'findings.md',
28
+ 'manifest.json',
29
+ 'progress.md',
30
+ 'quality_scale.yaml',
31
+ 'task_plan.md',
32
+ 'todo.md',
33
+ ]);
34
+
35
+ function repoHasBasename(ctx, basename, state) {
36
+ if (!basename) {
37
+ return false;
38
+ }
39
+ if (state.basenameCache.has(basename)) {
40
+ return state.basenameCache.get(basename);
41
+ }
42
+
43
+ const match = findFirstRepoPath(ctx, (_relPath, entryName) => entryName === basename, { maxDepth: 10 });
44
+ const exists = Boolean(match);
45
+ state.basenameCache.set(basename, exists);
46
+ return exists;
47
+ }
48
+
49
+ function repoHasPathSuffix(ctx, candidate, state) {
50
+ const normalized = toPosix(candidate || '').replace(/^\.?\//, '');
51
+ if (!normalized) {
52
+ return false;
53
+ }
54
+ if (state.suffixCache.has(normalized)) {
55
+ return state.suffixCache.get(normalized);
56
+ }
57
+
58
+ const match = findFirstRepoPath(
59
+ ctx,
60
+ (relPath) => {
61
+ const normalizedPath = toPosix(relPath);
62
+ return normalizedPath === normalized || normalizedPath.endsWith(`/${normalized}`);
63
+ },
64
+ { maxDepth: 10 },
65
+ );
66
+ const exists = Boolean(match);
67
+ state.suffixCache.set(normalized, exists);
68
+ return exists;
69
+ }
70
+
71
+ function lineHasEnvPolicyContext(line) {
72
+ return ENV_POLICY_RE.test(String(line || ''));
73
+ }
74
+
75
+ function lineHasScopedOwnershipContext(line) {
76
+ const text = String(line || '');
77
+ return OWNERSHIP_CONTEXT_RE.test(text) || SOFT_REFERENCE_CONTEXT_RE.test(text) || /<[^>]+>/.test(text);
78
+ }
79
+
80
+ function extractLineAnchors(line) {
81
+ const anchors = new Set();
82
+ const text = String(line || '');
83
+
84
+ BACKTICK_TOKEN_RE.lastIndex = 0;
85
+ let match = BACKTICK_TOKEN_RE.exec(text);
86
+ while (match) {
87
+ const rawToken = String(match[1] || '');
88
+ const token = normalizeCandidatePath(rawToken)
89
+ .replace(/<[^>]+>/g, '')
90
+ .replace(/^\/+/, '')
91
+ .replace(/\/+$/, '');
92
+ if (!token || !rawToken.includes('/')) {
93
+ match = BACKTICK_TOKEN_RE.exec(text);
94
+ continue;
95
+ }
96
+ anchors.add(token);
97
+ match = BACKTICK_TOKEN_RE.exec(text);
98
+ }
99
+
100
+ MARKDOWN_LINK_RE.lastIndex = 0;
101
+ match = MARKDOWN_LINK_RE.exec(text);
102
+ while (match) {
103
+ const rawToken = String(match[1] || '');
104
+ const token = normalizeCandidatePath(rawToken)
105
+ .replace(/<[^>]+>/g, '')
106
+ .replace(/^\/+/, '')
107
+ .replace(/\/+$/, '');
108
+ if (!token || !rawToken.includes('/')) {
109
+ match = MARKDOWN_LINK_RE.exec(text);
110
+ continue;
111
+ }
112
+ anchors.add(token);
113
+ match = MARKDOWN_LINK_RE.exec(text);
114
+ }
115
+
116
+ return [...anchors];
117
+ }
118
+
119
+ function anchorDirsForToken(token) {
120
+ if (!token) {
121
+ return [];
122
+ }
123
+
124
+ const normalized = normalizeCandidatePath(token)
125
+ .replace(/<[^>]+>/g, '')
126
+ .replace(/^\/+/, '')
127
+ .replace(/\/+$/, '');
128
+ if (!normalized) {
129
+ return [];
130
+ }
131
+
132
+ const dirs = new Set();
133
+ const looksFileLike = looksLikeRelativeFileReference(normalized);
134
+ const direct = normalized.includes('/')
135
+ ? (looksFileLike ? path.posix.dirname(normalized) : normalized)
136
+ : normalized;
137
+ if (direct && direct !== '.') {
138
+ dirs.add(direct);
139
+ }
140
+
141
+ const parent = path.posix.dirname(direct || normalized);
142
+ if (parent && parent !== '.' && parent !== direct) {
143
+ dirs.add(parent);
144
+ }
145
+
146
+ return [...dirs];
147
+ }
148
+
149
+ function lineResolvesBareCandidate(ctx, line, candidate, state) {
150
+ const base = path.posix.basename(candidate);
151
+ const anchors = extractLineAnchors(line);
152
+
153
+ for (const anchor of anchors) {
154
+ const normalizedAnchor = normalizeCandidatePath(anchor);
155
+ if (path.posix.basename(normalizedAnchor) === base && (ctx.fileContent(normalizedAnchor) !== null || repoHasPathSuffix(ctx, normalizedAnchor, state))) {
156
+ return true;
157
+ }
158
+
159
+ for (const dir of anchorDirsForToken(anchor)) {
160
+ const match = findFirstRepoPath(
161
+ ctx,
162
+ (relPath, entryName) => entryName === base && toPosix(relPath).startsWith(`${dir}/`),
163
+ { maxDepth: 10 },
164
+ );
165
+ if (match) {
166
+ return true;
167
+ }
168
+ }
169
+ }
170
+
171
+ if (anchors.length > 0 && repoHasBasename(ctx, base, state)) {
172
+ return true;
173
+ }
174
+
175
+ if (lineHasScopedOwnershipContext(line) && repoHasBasename(ctx, base, state)) {
176
+ return true;
177
+ }
178
+
179
+ return false;
180
+ }
181
+
182
+ function lineHasAnchorContext(line) {
183
+ return extractLineAnchors(line).length > 0;
184
+ }
185
+
186
+ function lineResolvesPathSuffix(ctx, line, candidate, state) {
187
+ if (!candidate || !candidate.includes('/')) {
188
+ return false;
189
+ }
190
+ if (!lineHasAnchorContext(line) && !lineHasScopedOwnershipContext(line)) {
191
+ return false;
192
+ }
193
+ return repoHasPathSuffix(ctx, candidate, state);
194
+ }
195
+
196
+ function shouldIgnoreCandidate(ctx, line, candidate, state) {
197
+ const normalized = String(candidate || '');
198
+ const base = path.posix.basename(normalized);
199
+ if (!normalized) {
200
+ return true;
201
+ }
202
+ if (PLACEHOLDER_PATH_RE.test(normalized)) {
203
+ return true;
204
+ }
205
+ if (ALWAYS_AMBIGUOUS_BASENAMES.has(base) && repoHasBasename(ctx, base, state)) {
206
+ return true;
207
+ }
208
+ if (SOFT_REFERENCE_CONTEXT_RE.test(String(line || '')) && (base === 'PLAN.md' || base === 'web_scraper.py')) {
209
+ return true;
210
+ }
211
+ if (normalized === '.env' && lineHasEnvPolicyContext(line)) {
212
+ return true;
213
+ }
214
+ if (lineResolvesPathSuffix(ctx, line, normalized, state)) {
215
+ return true;
216
+ }
217
+ if (!normalized.includes('/') && lineResolvesBareCandidate(ctx, line, normalized, state)) {
218
+ return true;
219
+ }
220
+ return false;
221
+ }
222
+
223
+ function resolveMissingCandidate(ctx, fromFile, candidate) {
224
+ const isNestedAgentDoc = toPosix(fromFile).includes('/');
225
+ const prefersRepoRoot = isNestedAgentDoc && !candidate.startsWith('../');
226
+ const modes = prefersRepoRoot
227
+ ? ['repo-root', 'relative-to-file']
228
+ : ['relative-to-file', 'repo-root'];
229
+
230
+ let firstMissing = null;
231
+ for (const mode of modes) {
232
+ const resolvedPath = resolveRepoPath(ctx, fromFile, candidate, mode);
233
+ if (!resolvedPath || isKnownConventionPath(resolvedPath)) {
234
+ continue;
235
+ }
236
+ if (!firstMissing) {
237
+ firstMissing = resolvedPath;
238
+ }
239
+ if (ctx.fileContent(resolvedPath) !== null) {
240
+ return { exists: true, resolvedPath };
241
+ }
242
+ }
243
+
244
+ return { exists: false, resolvedPath: firstMissing };
245
+ }
246
+
247
+ function rewriteMarkdownLinksForScanning(text) {
248
+ return String(text || '').replace(MARKDOWN_LINK_RE, (_match, target) => ` ${target} `);
249
+ }
16
250
 
17
251
  module.exports = {
18
252
  key: 'agent-config-missing-file',
@@ -23,35 +257,46 @@ module.exports = {
23
257
  run(ctx) {
24
258
  const findings = [];
25
259
  const seen = new Set();
260
+ const state = {
261
+ basenameCache: new Map(),
262
+ suffixCache: new Map(),
263
+ };
26
264
 
27
265
  for (const entry of getAgentConfigEntries(ctx)) {
28
266
  if (!/\.(?:md|mdc|txt|rst)$/i.test(entry.path) && !/\.cursorrules$|\.windsurfrules$/i.test(entry.path)) {
29
267
  continue;
30
268
  }
31
269
  for (const { lineNumber, text } of getScannableLines(entry.content)) {
270
+ const scanText = rewriteMarkdownLinksForScanning(text);
32
271
  POINTER_RE.lastIndex = 0;
33
- let match = POINTER_RE.exec(text);
272
+ let match = POINTER_RE.exec(scanText);
34
273
  while (match) {
35
274
  const candidate = normalizeCandidatePath(match[1]);
36
275
  if (!looksLikeRelativeFileReference(candidate)) {
37
- match = POINTER_RE.exec(text);
276
+ match = POINTER_RE.exec(scanText);
277
+ continue;
278
+ }
279
+
280
+ if (lineHasExampleContext(text)) {
281
+ match = POINTER_RE.exec(scanText);
38
282
  continue;
39
283
  }
40
284
 
41
- const resolvedPath = resolveRepoPath(ctx, entry.path, candidate, 'relative-to-file');
42
- if (!resolvedPath || isKnownConventionPath(resolvedPath)) {
43
- match = POINTER_RE.exec(text);
285
+ if (shouldIgnoreCandidate(ctx, text, candidate, state)) {
286
+ match = POINTER_RE.exec(scanText);
44
287
  continue;
45
288
  }
46
289
 
47
- if (ctx.fileContent(resolvedPath) !== null) {
48
- match = POINTER_RE.exec(text);
290
+ const resolution = resolveMissingCandidate(ctx, entry.path, candidate);
291
+ if (!resolution.resolvedPath || resolution.exists) {
292
+ match = POINTER_RE.exec(scanText);
49
293
  continue;
50
294
  }
295
+ const resolvedPath = resolution.resolvedPath;
51
296
 
52
297
  const dedupeKey = `${entry.path}:${toPosix(resolvedPath)}`;
53
298
  if (seen.has(dedupeKey)) {
54
- match = POINTER_RE.exec(text);
299
+ match = POINTER_RE.exec(scanText);
55
300
  continue;
56
301
  }
57
302
  seen.add(dedupeKey);
@@ -62,7 +307,7 @@ module.exports = {
62
307
  fix: `${entry.path} references \`${toPosix(resolvedPath)}\`, but the file is missing. Create the file or update the agent guidance to point at a real repo path.`,
63
308
  });
64
309
 
65
- match = POINTER_RE.exec(text);
310
+ match = POINTER_RE.exec(scanText);
66
311
  }
67
312
  }
68
313
  }
@@ -74,17 +74,71 @@ const SPECIAL_FILE_BASENAMES = new Set([
74
74
  'Dockerfile',
75
75
  'Makefile',
76
76
  'justfile',
77
+ 'manifest.json',
77
78
  'package.json',
78
79
  'pyproject.toml',
79
80
  'go.mod',
80
81
  'Cargo.toml',
81
82
  ]);
82
83
 
84
+ const COMMON_DOTFILE_BASENAMES = new Set([
85
+ '.editorconfig',
86
+ '.env',
87
+ '.env.example',
88
+ '.env.sample',
89
+ '.env.template',
90
+ '.gitattributes',
91
+ '.gitignore',
92
+ '.npmrc',
93
+ '.nvmrc',
94
+ '.prettierrc',
95
+ '.python-version',
96
+ '.tool-versions',
97
+ ]);
98
+
83
99
  const KNOWN_CONVENTION_PATHS = new Set([
84
100
  'CODEOWNERS',
85
101
  '.github/CODEOWNERS',
86
102
  ]);
87
103
 
104
+ const FILE_REFERENCE_EXTENSION_RE = /\.(?:md|mdc|txt|rst|json|jsonc|ya?ml|toml|conf|sh|ps1|js|cjs|mjs|ts|tsx|jsx|cts|mts|py|go|rs|java|kt|kts|gradle|cs|rb|php|swift|pbxproj|xcconfig|xcworkspace|xcodeproj|h|hpp|c|cc|cpp|m|mm|sql|ini|cfg|properties|xml|html|css|scss|sass|lock)$/i;
105
+ const KNOWN_DOMAIN_TLDS = new Set([
106
+ 'ai',
107
+ 'app',
108
+ 'co',
109
+ 'com',
110
+ 'dev',
111
+ 'io',
112
+ 'net',
113
+ 'org',
114
+ 'sh',
115
+ ]);
116
+ const KNOWN_HIDDEN_PATH_SEGMENTS = new Set([
117
+ '.claude',
118
+ '.codex',
119
+ '.cursor',
120
+ '.gemini',
121
+ '.github',
122
+ '.opencode',
123
+ '.vscode',
124
+ '.windsurf',
125
+ ]);
126
+ const FRAMEWORK_LABEL_TOKENS = new Set([
127
+ 'd3.js',
128
+ 'go',
129
+ 'golang',
130
+ 'javascript',
131
+ 'kotlin',
132
+ 'next',
133
+ 'next.js',
134
+ 'node',
135
+ 'node.js',
136
+ 'python',
137
+ 'rust',
138
+ 'swift',
139
+ 'typescript',
140
+ ]);
141
+
88
142
  const LOCAL_MCP_BINARIES = new Set([
89
143
  'context7-mcp',
90
144
  'nerviq-mcp',
@@ -127,8 +181,9 @@ function existsSyncSafe(targetPath) {
127
181
  function isLikelyTextFile(relPath) {
128
182
  const base = path.posix.basename(toPosix(relPath));
129
183
  if (SPECIAL_FILE_BASENAMES.has(base)) return true;
184
+ if (COMMON_DOTFILE_BASENAMES.has(base)) return true;
130
185
  if (base === '.cursorrules' || base === '.windsurfrules') return true;
131
- return /\.(?:md|mdc|txt|rst|json|jsonc|ya?ml|toml|conf|sh|ps1|js|cjs|mjs|ts|tsx|jsx|py|go|rs|java|kt|cs|rb|php|swift)$/i.test(base);
186
+ return hasKnownFileExtension(base);
132
187
  }
133
188
 
134
189
  function fileExists(ctx, relPath) {
@@ -235,27 +290,69 @@ function stripWrapperChars(value) {
235
290
  function normalizeCandidatePath(rawValue) {
236
291
  let value = stripWrapperChars(rawValue);
237
292
  if (value.startsWith('@')) value = value.slice(1);
293
+ if (/^mdc:/i.test(value)) value = value.slice(4);
238
294
  return value;
239
295
  }
240
296
 
297
+ function hasKnownFileExtension(baseName) {
298
+ return FILE_REFERENCE_EXTENSION_RE.test(baseName || '');
299
+ }
300
+
301
+ function isVersionLikeToken(candidate) {
302
+ return /^v?\d+(?:\.\d+)+(?:[a-z]+\d*|\.[xX*])?$/i.test(candidate || '');
303
+ }
304
+
305
+ function isFrameworkLabelToken(candidate) {
306
+ return FRAMEWORK_LABEL_TOKENS.has(String(candidate || '').toLowerCase());
307
+ }
308
+
309
+ function isDomainLikeToken(candidate) {
310
+ if (!candidate || candidate.includes('/')) return false;
311
+ const parts = String(candidate).split('.');
312
+ if (parts.length < 2) return false;
313
+ const tld = parts[parts.length - 1].toLowerCase();
314
+ if (!KNOWN_DOMAIN_TLDS.has(tld)) return false;
315
+ return parts.slice(0, -1).every((part) => /^[A-Za-z0-9-]+$/.test(part));
316
+ }
317
+
318
+ function lineHasExampleContext(line) {
319
+ const text = String(line || '');
320
+ if (/^\s*\|/.test(text)) return true;
321
+ if (/^\s*#{1,6}\s+/.test(text)) return true;
322
+ return /\b(?:e\.g\.?|for example|examples?|sample|placeholder|template|snippet|user request|problem|solution)\b/i.test(text);
323
+ }
324
+
241
325
  function looksLikeRelativeFileReference(candidate) {
242
326
  if (!candidate) return false;
243
327
  if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(candidate)) return false;
244
- if (/^[A-Za-z0-9-]+\.[A-Za-z]{2,}\//.test(candidate)) return false;
245
328
  if (candidate.startsWith('#')) return false;
329
+ if (/[<>{}|]/.test(candidate)) return false;
246
330
 
247
331
  const normalized = candidate.replace(/^\.\//, '');
248
332
  const base = path.posix.basename(normalized);
333
+ const lowered = normalized.toLowerCase();
249
334
 
250
- if (KNOWN_CONVENTION_PATHS.has(normalized) || SPECIAL_FILE_BASENAMES.has(base)) {
251
- return true;
335
+ if (isDomainLikeToken(normalized)) return false;
336
+ if (isVersionLikeToken(normalized)) return false;
337
+ if (isFrameworkLabelToken(normalized)) return false;
338
+ if (base.startsWith('.') && !COMMON_DOTFILE_BASENAMES.has(base) && !COMMON_DOTFILE_BASENAMES.has(lowered)) {
339
+ return false;
340
+ }
341
+ if (normalized.split('/').some((segment) => /^\.[A-Za-z0-9_-]+$/.test(segment) && !COMMON_DOTFILE_BASENAMES.has(segment.toLowerCase()) && !KNOWN_HIDDEN_PATH_SEGMENTS.has(segment.toLowerCase()))) {
342
+ return false;
343
+ }
344
+ if (/^[A-Za-z][A-Za-z0-9_-]*(?:\.[A-Za-z][A-Za-z0-9_-]*){2,}$/i.test(normalized) && !hasKnownFileExtension(base)) {
345
+ return false;
346
+ }
347
+ if (/^\.[A-Za-z0-9_-]+\.[A-Za-z0-9._-]+$/.test(base) && !COMMON_DOTFILE_BASENAMES.has(base) && !COMMON_DOTFILE_BASENAMES.has(lowered)) {
348
+ return false;
252
349
  }
253
350
 
254
- if (normalized.includes('/')) {
255
- return /\.(?:md|mdc|txt|rst|json|jsonc|ya?ml|toml|conf|sh|ps1|js|cjs|mjs|ts|tsx|jsx|py|go|rs|java|kt|cs|rb|php|swift)$/i.test(base);
351
+ if (KNOWN_CONVENTION_PATHS.has(normalized) || SPECIAL_FILE_BASENAMES.has(base) || COMMON_DOTFILE_BASENAMES.has(base) || COMMON_DOTFILE_BASENAMES.has(lowered)) {
352
+ return true;
256
353
  }
257
354
 
258
- return /^(?:\.?[A-Za-z0-9_-]+\.)[A-Za-z0-9._-]+$/i.test(normalized);
355
+ return hasKnownFileExtension(base);
259
356
  }
260
357
 
261
358
  function resolveRepoPath(ctx, fromFile, candidate, mode = 'relative-to-file') {
@@ -277,11 +374,34 @@ function getScannableLines(content) {
277
374
  const lines = String(content || '').split(/\r?\n/);
278
375
  const output = [];
279
376
  let fence = null;
377
+ let htmlComment = false;
378
+ let frontmatter = false;
379
+ let frontmatterConsumed = false;
280
380
 
281
381
  for (let index = 0; index < lines.length; index++) {
282
382
  const line = lines[index];
283
383
  const trimmed = line.trim();
284
384
 
385
+ if (!frontmatterConsumed && index === 0 && /^(---|\+\+\+)$/.test(trimmed)) {
386
+ frontmatter = true;
387
+ frontmatterConsumed = true;
388
+ continue;
389
+ }
390
+
391
+ if (frontmatter) {
392
+ if (/^(---|\+\+\+)$/.test(trimmed)) {
393
+ frontmatter = false;
394
+ }
395
+ continue;
396
+ }
397
+
398
+ if (!fence && htmlComment) {
399
+ if (trimmed.includes('-->')) {
400
+ htmlComment = false;
401
+ }
402
+ continue;
403
+ }
404
+
285
405
  if (!fence && /^(```|~~~)/.test(trimmed)) {
286
406
  fence = trimmed.slice(0, 3);
287
407
  continue;
@@ -294,6 +414,13 @@ function getScannableLines(content) {
294
414
  continue;
295
415
  }
296
416
 
417
+ if (/^<!--/.test(trimmed)) {
418
+ if (!trimmed.includes('-->')) {
419
+ htmlComment = true;
420
+ }
421
+ continue;
422
+ }
423
+
297
424
  output.push({ lineNumber: index + 1, text: line });
298
425
  }
299
426
 
@@ -512,6 +639,7 @@ module.exports = {
512
639
  hasLegacyAiderPin,
513
640
  isClearlyLocalMcpBinary,
514
641
  isKnownConventionPath,
642
+ lineHasExampleContext,
515
643
  looksLikeRelativeFileReference,
516
644
  normalizeCandidatePath,
517
645
  platformForFile,