@ncoderz/awa 1.8.2 → 1.8.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.
@@ -224,11 +224,38 @@ function buildPropertyEntries(code, designFiles, entries) {
224
224
 
225
225
  // src/core/renumber/propagator.ts
226
226
  import { readFile, writeFile as writeFile2 } from "fs/promises";
227
- async function propagate(map, specs, markers, dryRun) {
227
+
228
+ // src/core/check/glob.ts
229
+ import { glob } from "fs/promises";
230
+ function matchSimpleGlob(path, pattern) {
231
+ const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<GLOBSTAR>>").replace(/\*/g, "[^/]*").replace(/<<GLOBSTAR>>/g, ".*");
232
+ return new RegExp(`(^|/)${regex}($|/)`).test(path);
233
+ }
234
+ async function collectFiles(globs, ignore) {
235
+ const files = [];
236
+ const dirPrefixes = ignore.filter((ig) => ig.endsWith("/**")).map((ig) => ig.slice(0, -3));
237
+ const compiledIgnore = ignore.map((ig) => {
238
+ const regex = ig.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<GLOBSTAR>>").replace(/\*/g, "[^/]*").replace(/<<GLOBSTAR>>/g, ".*");
239
+ return new RegExp(`(^|/)${regex}($|/)`);
240
+ });
241
+ for (const pattern of globs) {
242
+ for await (const filePath of glob(pattern, {
243
+ exclude: (p) => dirPrefixes.includes(p) || compiledIgnore.some((re) => re.test(p))
244
+ })) {
245
+ if (!compiledIgnore.some((re) => re.test(filePath))) {
246
+ files.push(filePath);
247
+ }
248
+ }
249
+ }
250
+ return [...new Set(files)];
251
+ }
252
+
253
+ // src/core/renumber/propagator.ts
254
+ async function propagate(map, specs, markers, config, dryRun) {
228
255
  if (map.entries.size === 0) {
229
256
  return { affectedFiles: [], totalReplacements: 0 };
230
257
  }
231
- const filePaths = collectFilePaths(map, specs, markers);
258
+ const filePaths = await collectFilePaths(map, specs, markers, config);
232
259
  const affectedFiles = [];
233
260
  let totalReplacements = 0;
234
261
  for (const filePath of filePaths) {
@@ -253,12 +280,17 @@ async function propagate(map, specs, markers, dryRun) {
253
280
  }
254
281
  return { affectedFiles, totalReplacements };
255
282
  }
256
- function collectFilePaths(map, specs, markers) {
283
+ async function collectFilePaths(map, specs, markers, config) {
257
284
  const paths = /* @__PURE__ */ new Set();
258
285
  const code = map.code;
259
286
  for (const specFile of specs.specFiles) {
260
287
  paths.add(specFile.filePath);
261
288
  }
289
+ const combinedIgnore = [...config.specIgnore, ...config.extraSpecIgnore];
290
+ const extraFiles = await collectFiles(config.extraSpecGlobs, combinedIgnore);
291
+ for (const filePath of extraFiles) {
292
+ paths.add(filePath);
293
+ }
262
294
  const affectedIds = new Set(map.entries.keys());
263
295
  for (const marker of markers.markers) {
264
296
  if (affectedIds.has(marker.id) || hasCodePrefix(marker.id, code)) {
@@ -388,33 +420,6 @@ function formatJson(result) {
388
420
 
389
421
  // src/core/check/marker-scanner.ts
390
422
  import { readFile as readFile2 } from "fs/promises";
391
-
392
- // src/core/check/glob.ts
393
- import { glob } from "fs/promises";
394
- function matchSimpleGlob(path, pattern) {
395
- const regex = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<GLOBSTAR>>").replace(/\*/g, "[^/]*").replace(/<<GLOBSTAR>>/g, ".*");
396
- return new RegExp(`(^|/)${regex}($|/)`).test(path);
397
- }
398
- async function collectFiles(globs, ignore) {
399
- const files = [];
400
- const dirPrefixes = ignore.filter((ig) => ig.endsWith("/**")).map((ig) => ig.slice(0, -3));
401
- const compiledIgnore = ignore.map((ig) => {
402
- const regex = ig.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "<<GLOBSTAR>>").replace(/\*/g, "[^/]*").replace(/<<GLOBSTAR>>/g, ".*");
403
- return new RegExp(`(^|/)${regex}($|/)`);
404
- });
405
- for (const pattern of globs) {
406
- for await (const filePath of glob(pattern, {
407
- exclude: (p) => dirPrefixes.includes(p) || compiledIgnore.some((re) => re.test(p))
408
- })) {
409
- if (!compiledIgnore.some((re) => re.test(filePath))) {
410
- files.push(filePath);
411
- }
412
- }
413
- }
414
- return [...new Set(files)];
415
- }
416
-
417
- // src/core/check/marker-scanner.ts
418
423
  var MARKER_TYPE_MAP = {
419
424
  "@awa-impl": "impl",
420
425
  "@awa-test": "test",
@@ -677,6 +682,8 @@ var DEFAULT_CHECK_CONFIG = {
677
682
  "out/**",
678
683
  ".awa/**"
679
684
  ],
685
+ extraSpecGlobs: [".awa/**/*"],
686
+ extraSpecIgnore: [".awa/.agent/**"],
680
687
  ignoreMarkers: [],
681
688
  markers: ["@awa-impl", "@awa-test", "@awa-component"],
682
689
  idPattern: "([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?(?:_AC-\\d+)?|[A-Z][A-Z0-9]*_P-\\d+)",
@@ -697,6 +704,12 @@ function buildScanConfig(fileConfig, overrides) {
697
704
  codeGlobs: toStringArray(section?.["code-globs"]) ?? [...DEFAULT_CHECK_CONFIG.codeGlobs],
698
705
  specIgnore: toStringArray(section?.["spec-ignore"]) ?? [...DEFAULT_CHECK_CONFIG.specIgnore],
699
706
  codeIgnore: toStringArray(section?.["code-ignore"]) ?? [...DEFAULT_CHECK_CONFIG.codeIgnore],
707
+ extraSpecGlobs: toStringArray(section?.["extra-spec-globs"]) ?? [
708
+ ...DEFAULT_CHECK_CONFIG.extraSpecGlobs
709
+ ],
710
+ extraSpecIgnore: toStringArray(section?.["extra-spec-ignore"]) ?? [
711
+ ...DEFAULT_CHECK_CONFIG.extraSpecIgnore
712
+ ],
700
713
  ignoreMarkers: toStringArray(section?.["ignore-markers"]) ?? [
701
714
  ...DEFAULT_CHECK_CONFIG.ignoreMarkers
702
715
  ],
@@ -734,7 +747,7 @@ async function renumberCommand(options) {
734
747
  logger.error("No feature code or --all specified");
735
748
  return 2;
736
749
  }
737
- const { markers, specs } = await scan(options.config);
750
+ const { markers, specs, config } = await scan(options.config);
738
751
  let codes;
739
752
  if (options.all) {
740
753
  codes = discoverFeatureCodes(specs.specFiles);
@@ -751,7 +764,14 @@ async function renumberCommand(options) {
751
764
  let hasChanges = false;
752
765
  for (const code of codes) {
753
766
  try {
754
- const result = await runRenumberPipeline(code, specs, markers, dryRun, fixMalformed);
767
+ const result = await runRenumberPipeline(
768
+ code,
769
+ specs,
770
+ markers,
771
+ config,
772
+ dryRun,
773
+ fixMalformed
774
+ );
755
775
  results.push(result);
756
776
  if (!result.noChange) {
757
777
  hasChanges = true;
@@ -794,7 +814,7 @@ async function renumberCommand(options) {
794
814
  return 2;
795
815
  }
796
816
  }
797
- async function runRenumberPipeline(code, specs, markers, dryRun, fixMalformed) {
817
+ async function runRenumberPipeline(code, specs, markers, config, dryRun, fixMalformed) {
798
818
  const { map, noChange } = buildRenumberMap(code, specs);
799
819
  if (noChange) {
800
820
  return {
@@ -819,7 +839,7 @@ async function runRenumberPipeline(code, specs, markers, dryRun, fixMalformed) {
819
839
  malformedWarnings = allWarnings;
820
840
  malformedCorrections = [];
821
841
  }
822
- const { affectedFiles, totalReplacements } = await propagate(map, specs, markers, dryRun);
842
+ const { affectedFiles, totalReplacements } = await propagate(map, specs, markers, config, dryRun);
823
843
  return {
824
844
  code,
825
845
  map,
@@ -872,4 +892,4 @@ export {
872
892
  propagate,
873
893
  renumberCommand
874
894
  };
875
- //# sourceMappingURL=chunk-FGJMXOYA.js.map
895
+ //# sourceMappingURL=chunk-2VUVSW6T.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/commands/renumber.ts","../src/core/renumber/malformed-detector.ts","../src/core/spec-file-utils.ts","../src/core/renumber/types.ts","../src/core/renumber/map-builder.ts","../src/core/renumber/propagator.ts","../src/core/check/glob.ts","../src/core/renumber/reporter.ts","../src/core/check/marker-scanner.ts","../src/core/check/spec-parser.ts","../src/core/check/types.ts","../src/core/trace/scanner.ts"],"sourcesContent":["// @awa-component: RENUM-RenumberCommand\n// @awa-impl: RENUM-8_AC-1, RENUM-8_AC-2\n// @awa-impl: RENUM-9_AC-1, RENUM-9_AC-2, RENUM-9_AC-3, RENUM-9_AC-4\n// @awa-impl: RENUM-10_AC-1\n\nimport { readFile } from 'node:fs/promises';\n\nimport { correctMalformed, detectMalformed } from '../core/renumber/malformed-detector.js';\nimport { buildRenumberMap } from '../core/renumber/map-builder.js';\nimport { propagate } from '../core/renumber/propagator.js';\nimport { formatJson, formatText } from '../core/renumber/reporter.js';\nimport type { RenumberCommandOptions, RenumberResult } from '../core/renumber/types.js';\nimport { RenumberError } from '../core/renumber/types.js';\nimport { scan } from '../core/trace/scanner.js';\nimport { logger } from '../utils/logger.js';\n\n/**\n * Execute the `awa renumber` command.\n * Returns exit code: 0 = no changes needed, 1 = changes applied/previewed, 2 = error.\n */\n// @awa-impl: RENUM-10_AC-1\nexport async function renumberCommand(options: RenumberCommandOptions): Promise<number> {\n try {\n // @awa-impl: RENUM-9_AC-3\n if (!options.code && !options.all) {\n logger.error('No feature code or --all specified');\n return 2;\n }\n\n const { markers, specs, config } = await scan(options.config);\n\n // Determine which codes to renumber\n // @awa-impl: RENUM-8_AC-1\n let codes: string[];\n if (options.all) {\n codes = discoverFeatureCodes(specs.specFiles);\n if (codes.length === 0) {\n logger.warn('No feature codes discovered from REQ files');\n return 0;\n }\n } else {\n codes = [options.code as string];\n }\n\n const dryRun = options.dryRun === true;\n const fixMalformed = options.expandUnambiguousIds === true;\n const results: RenumberResult[] = [];\n let hasChanges = false;\n\n // @awa-impl: RENUM-8_AC-2\n for (const code of codes) {\n try {\n const result = await runRenumberPipeline(\n code,\n specs,\n markers,\n config,\n dryRun,\n fixMalformed,\n );\n results.push(result);\n if (!result.noChange) {\n hasChanges = true;\n }\n } catch (err) {\n if (err instanceof RenumberError && err.errorCode === 'CODE_NOT_FOUND') {\n // @awa-impl: RENUM-9_AC-4\n logger.error(err.message);\n return 2;\n }\n throw err;\n }\n }\n\n // Output results\n if (options.json) {\n // JSON mode: output array if --all, single object otherwise\n if (results.length === 1) {\n const first = results[0] as RenumberResult;\n process.stdout.write(`${formatJson(first)}\\n`);\n } else {\n const jsonArray = results.map((r) => JSON.parse(formatJson(r)));\n process.stdout.write(`${JSON.stringify(jsonArray, null, 2)}\\n`);\n }\n } else {\n for (const result of results) {\n const text = formatText(result, dryRun);\n logger.info(text);\n }\n }\n\n // @awa-impl: RENUM-10_AC-1\n return hasChanges ? 1 : 0;\n } catch (err) {\n if (err instanceof RenumberError) {\n logger.error(err.message);\n return 2;\n }\n if (err instanceof Error) {\n logger.error(err.message);\n } else {\n logger.error(String(err));\n }\n return 2;\n }\n}\n\n/**\n * Run the renumber pipeline for a single feature code.\n */\n// @awa-impl: RENUM-9_AC-1\nasync function runRenumberPipeline(\n code: string,\n specs: import('../core/check/types.js').SpecParseResult,\n markers: import('../core/check/types.js').MarkerScanResult,\n config: import('../core/check/types.js').CheckConfig,\n dryRun: boolean,\n fixMalformed: boolean,\n): Promise<RenumberResult> {\n // Build renumber map\n const { map, noChange } = buildRenumberMap(code, specs);\n\n if (noChange) {\n return {\n code,\n map,\n affectedFiles: [],\n totalReplacements: 0,\n malformedWarnings: [],\n malformedCorrections: [],\n noChange: true,\n };\n }\n\n // Detect malformed IDs from spec and code files\n const fileContents = await collectFileContents(specs, markers, code);\n const allWarnings = detectMalformed(code, fileContents);\n\n // Optionally correct unambiguous malformed IDs before propagation\n let malformedWarnings: readonly import('../core/renumber/types.js').MalformedWarning[];\n let malformedCorrections: readonly import('../core/renumber/types.js').MalformedCorrection[];\n\n if (fixMalformed && allWarnings.length > 0) {\n const result = await correctMalformed(code, allWarnings, fileContents, dryRun);\n malformedCorrections = result.corrections;\n malformedWarnings = result.remainingWarnings;\n } else {\n malformedWarnings = allWarnings;\n malformedCorrections = [];\n }\n\n // Propagate changes (after corrections so corrected IDs get renumbered)\n const { affectedFiles, totalReplacements } = await propagate(map, specs, markers, config, dryRun);\n\n return {\n code,\n map,\n affectedFiles,\n totalReplacements,\n malformedWarnings,\n malformedCorrections,\n noChange: false,\n };\n}\n\n/**\n * Discover all feature codes from REQ files.\n */\nfunction discoverFeatureCodes(\n specFiles: readonly import('../core/check/types.js').SpecFile[],\n): string[] {\n const codes = new Set<string>();\n for (const sf of specFiles) {\n if (sf.code && /^REQ-/.test(sf.filePath.split('/').pop() ?? '')) {\n codes.add(sf.code);\n }\n }\n return [...codes].sort();\n}\n\n/**\n * Collect file contents for malformed detection.\n * Reads spec files and code files that have markers for the given code.\n */\nasync function collectFileContents(\n specs: import('../core/check/types.js').SpecParseResult,\n markers: import('../core/check/types.js').MarkerScanResult,\n code: string,\n): Promise<Map<string, string>> {\n const paths = new Set<string>();\n\n // Add spec files\n for (const sf of specs.specFiles) {\n paths.add(sf.filePath);\n }\n\n // Add code files with markers starting with the code prefix\n for (const m of markers.markers) {\n if (m.id.startsWith(`${code}-`) || m.id.startsWith(`${code}_`)) {\n paths.add(m.filePath);\n }\n }\n\n const contents = new Map<string, string>();\n for (const p of paths) {\n try {\n const content = await readFile(p, 'utf-8');\n contents.set(p, content);\n } catch {\n // Skip unreadable files\n }\n }\n\n return contents;\n}\n","// @awa-component: RENUM-MalformedDetector\n// @awa-impl: RENUM-12_AC-1, RENUM-12_AC-2, RENUM-12_AC-3\n\nimport { writeFile } from 'node:fs/promises';\n\nimport type { MalformedCorrection, MalformedWarning } from './types.js';\n\n/**\n * Standard ID patterns that are considered valid.\n * - CODE-N (requirement)\n * - CODE-N.P (subrequirement)\n * - CODE-N_AC-M or CODE-N.P_AC-M (acceptance criterion)\n * - CODE_P-N (property)\n * - CODE-ComponentName (component — PascalCase name after code)\n */\nconst VALID_ID_RE = /^[A-Z][A-Z0-9]*(?:-\\d+(?:\\.\\d+)?(?:_AC-\\d+)?|_P-\\d+|-[A-Z][a-zA-Z0-9]*)$/;\n\n/**\n * Patterns that can be unambiguously expanded into valid IDs:\n *\n * - Slash ranges on ACs: CODE-N_AC-M/P or CODE-N.S_AC-M/P\n * e.g. ARC-36_AC-8/9 → ARC-36_AC-8, ARC-36_AC-9\n *\n * - Dot-dot ranges on ACs: CODE-N_AC-M..P or CODE-N.S_AC-M..P\n * e.g. ARC-18_AC-14..16 → ARC-18_AC-14, ARC-18_AC-15, ARC-18_AC-16\n */\n\n/** Slash range on ACs: e.g. ARC-36_AC-8/9 */\nconst SLASH_RANGE_RE = /^([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?_AC-)(\\d+)\\/(\\d+)$/;\n\n/** Dot-dot range on ACs: e.g. ARC-18_AC-14..16 */\nconst DOT_DOT_AC_RANGE_RE = /^([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?_AC-)(\\d+)\\.\\.(\\d+)$/;\n\n/**\n * Detect tokens that start with the feature code prefix but do not conform\n * to standard ID formats. Reports each as a warning with location.\n */\n// @awa-impl: RENUM-12_AC-1, RENUM-12_AC-2\nexport function detectMalformed(\n code: string,\n fileContents: ReadonlyMap<string, string>,\n): readonly MalformedWarning[] {\n const warnings: MalformedWarning[] = [];\n\n // Match tokens that structurally resemble IDs:\n // CODE-<Uppercase-or-digit>... (requirement-like or component-like)\n // CODE_P-... (property-like)\n // Tokens like CODE-lowercase (e.g. CLI-provided) are natural language, not IDs.\n const esc = escapeRegex(code);\n const tokenRegex = new RegExp(\n `(?<![A-Za-z0-9_.-])(?:${esc}-[A-Z0-9][A-Za-z0-9_./-]*|${esc}_P-[A-Za-z0-9_./-]+)`,\n 'g',\n );\n\n for (const [filePath, content] of fileContents) {\n const lines = content.split('\\n');\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i] as string;\n let match: RegExpExecArray | null;\n while ((match = tokenRegex.exec(line)) !== null) {\n const token = match[0];\n // @awa-impl: RENUM-12_AC-3\n if (!VALID_ID_RE.test(token)) {\n warnings.push({ filePath, line: i + 1, token });\n }\n }\n }\n }\n\n return warnings;\n}\n\n/**\n * Attempt to expand a single malformed token into its corrected replacement string.\n * Returns the replacement string if unambiguous, or `undefined` if the pattern\n * is ambiguous and should remain as a warning only.\n *\n * Correctable patterns:\n * - Slash ranges: CODE-N_AC-M/P → \"CODE-N_AC-M, CODE-N_AC-P\"\n * - Dot-dot AC ranges: CODE-N_AC-M..P → \"CODE-N_AC-M, CODE-N_AC-M+1, ..., CODE-N_AC-P\"\n *\n * Ambiguous / ignored patterns (return undefined):\n * - Trailing periods (e.g. ARC_P-206.) — likely end of sentence\n * - Letter suffixes (e.g. ARC-18_AC-7a)\n * - Full-ID ranges (e.g. ARC-20..ARC-25)\n * - Component + period (e.g. ARC-ChunkedTransferManager.)\n */\nexport function expandMalformedToken(token: string): string | undefined {\n // Try slash range: CODE-N_AC-M/P\n const slashMatch = SLASH_RANGE_RE.exec(token);\n if (slashMatch) {\n const [, prefix, startStr, endStr] = slashMatch as RegExpExecArray &\n [string, string, string, string];\n return `${prefix}${startStr}, ${prefix}${endStr}`;\n }\n\n // Try dot-dot AC range: CODE-N_AC-M..P\n const dotDotMatch = DOT_DOT_AC_RANGE_RE.exec(token);\n if (dotDotMatch) {\n const [, prefix, startStr, endStr] = dotDotMatch as RegExpExecArray &\n [string, string, string, string];\n const start = Number(startStr);\n const end = Number(endStr);\n if (end <= start) return undefined; // Invalid range\n const ids: string[] = [];\n for (let n = start; n <= end; n++) {\n ids.push(`${prefix}${n}`);\n }\n return ids.join(', ');\n }\n\n // All other patterns are ambiguous — do not correct\n return undefined;\n}\n\n/**\n * Apply malformed ID corrections to file contents.\n * Only corrects tokens for which `expandMalformedToken` returns a value.\n * Returns the list of corrections applied and the updated file contents map.\n *\n * When `dryRun` is true, files are not written to disk but corrections are\n * still computed and returned for preview.\n */\nexport async function correctMalformed(\n _code: string,\n warnings: readonly MalformedWarning[],\n fileContents: ReadonlyMap<string, string>,\n dryRun: boolean,\n): Promise<{\n corrections: readonly MalformedCorrection[];\n remainingWarnings: readonly MalformedWarning[];\n}> {\n const corrections: MalformedCorrection[] = [];\n const remainingWarnings: MalformedWarning[] = [];\n\n // Group warnings by file for efficient batch replacement\n const warningsByFile = new Map<string, MalformedWarning[]>();\n for (const w of warnings) {\n const existing = warningsByFile.get(w.filePath) ?? [];\n existing.push(w);\n warningsByFile.set(w.filePath, existing);\n }\n\n // Track files that need writing\n const modifiedFiles = new Map<string, string>();\n\n for (const [filePath, fileWarnings] of warningsByFile) {\n const original = fileContents.get(filePath);\n if (original === undefined) {\n // Cannot correct — keep as warnings\n remainingWarnings.push(...fileWarnings);\n continue;\n }\n\n let content: string = original;\n\n for (const w of fileWarnings) {\n const replacement = expandMalformedToken(w.token);\n if (replacement === undefined) {\n remainingWarnings.push(w);\n continue;\n }\n\n // Replace the token in content (whole-token match to avoid partial substitution)\n const escaped = escapeRegex(w.token);\n const regex = new RegExp(`(?<![A-Za-z0-9_.-])${escaped}(?![A-Za-z0-9_.-])`, 'g');\n const updated: string = content.replace(regex, replacement);\n if (updated !== content) {\n content = updated;\n corrections.push({\n filePath: w.filePath,\n line: w.line,\n token: w.token,\n replacement,\n });\n } else {\n // Token not found in content (unexpected) — keep as warning\n remainingWarnings.push(w);\n }\n }\n\n if (content !== original) {\n modifiedFiles.set(filePath, content);\n }\n }\n\n // Write modified files unless dry-run\n if (!dryRun) {\n for (const [filePath, content] of modifiedFiles) {\n await writeFile(filePath, content, 'utf-8');\n }\n }\n\n return { corrections, remainingWarnings };\n}\n\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","// @awa-impl: RENUM-1_AC-1\n// @awa-impl: RCOD-1_AC-1\n\nimport { basename } from 'node:path';\n\nimport type { SpecFile } from './check/types.js';\n\n/**\n * Find all spec files matching a feature code and file type prefix.\n * Returns files sorted alphabetically by basename for deterministic ordering.\n */\nexport function findSpecFiles(\n specFiles: readonly SpecFile[],\n code: string,\n prefix: string,\n): SpecFile[] {\n return specFiles\n .filter((sf) => {\n const name = basename(sf.filePath);\n return name.startsWith(`${prefix}-${code}-`);\n })\n .sort((a, b) => basename(a.filePath).localeCompare(basename(b.filePath)));\n}\n\n/**\n * Find the first spec file matching a feature code and file type prefix.\n * Convenience wrapper over findSpecFiles for cases needing a single match.\n */\nexport function findSpecFile(\n specFiles: readonly SpecFile[],\n code: string,\n prefix: string,\n): SpecFile | undefined {\n return findSpecFiles(specFiles, code, prefix)[0];\n}\n\n/** Known spec file prefixes that carry a feature code. */\nexport const SPEC_PREFIXES = ['FEAT', 'REQ', 'DESIGN', 'EXAMPLE', 'API', 'TASK'] as const;\n\n/**\n * Check whether at least one spec file exists for the given feature code.\n */\nexport function hasAnySpecFile(specFiles: readonly SpecFile[], code: string): boolean {\n return specFiles.some((sf) => {\n const name = basename(sf.filePath);\n return SPEC_PREFIXES.some((prefix) => name.startsWith(`${prefix}-${code}-`));\n });\n}\n","// Types shared across the renumber module.\n// Components are declared in their respective implementation files.\n\n/**\n * Mapping from old ID strings to new ID strings for one feature code.\n */\nexport interface RenumberMap {\n readonly code: string;\n readonly entries: ReadonlyMap<string, string>;\n}\n\n/**\n * A file touched by propagation with per-line replacement details.\n */\nexport interface AffectedFile {\n readonly filePath: string;\n readonly replacements: readonly Replacement[];\n}\n\n/**\n * A single ID replacement within a file.\n */\nexport interface Replacement {\n readonly line: number;\n readonly oldId: string;\n readonly newId: string;\n}\n\n/**\n * Aggregated output of the renumber pipeline for one feature code.\n */\nexport interface RenumberResult {\n readonly code: string;\n readonly map: RenumberMap;\n readonly affectedFiles: readonly AffectedFile[];\n readonly totalReplacements: number;\n readonly malformedWarnings: readonly MalformedWarning[];\n readonly malformedCorrections: readonly MalformedCorrection[];\n readonly noChange: boolean;\n}\n\n/**\n * CLI options passed to the renumber command.\n */\nexport interface RenumberCommandOptions {\n readonly code?: string;\n readonly all?: boolean;\n readonly dryRun?: boolean;\n readonly json?: boolean;\n readonly config?: string;\n readonly expandUnambiguousIds?: boolean;\n}\n\n/**\n * Warning about a malformed ID token.\n */\nexport interface MalformedWarning {\n readonly filePath: string;\n readonly line: number;\n readonly token: string;\n}\n\n/**\n * A correction applied to a malformed ID token.\n */\nexport interface MalformedCorrection {\n readonly filePath: string;\n readonly line: number;\n readonly token: string;\n readonly replacement: string;\n}\n\n/**\n * Result of map building.\n */\nexport interface MapBuildResult {\n readonly map: RenumberMap;\n readonly noChange: boolean;\n}\n\n/**\n * Result of propagation.\n */\nexport interface PropagationResult {\n readonly affectedFiles: readonly AffectedFile[];\n readonly totalReplacements: number;\n}\n\n// --- Error types ---\n\nexport type RenumberErrorCode = 'CODE_NOT_FOUND' | 'NO_ARGS' | 'WRITE_FAILED';\n\nexport class RenumberError extends Error {\n readonly errorCode: RenumberErrorCode;\n\n constructor(errorCode: RenumberErrorCode, message: string) {\n super(message);\n this.name = 'RenumberError';\n this.errorCode = errorCode;\n }\n}\n","// @awa-component: RENUM-MapBuilder\n// @awa-impl: RENUM-1_AC-1, RENUM-1_AC-2, RENUM-1_AC-3\n// @awa-impl: RENUM-2_AC-1, RENUM-2_AC-2\n// @awa-impl: RENUM-3_AC-1, RENUM-3_AC-2\n// @awa-impl: RENUM-4_AC-1, RENUM-4_AC-2\n\nimport type { SpecFile, SpecParseResult } from '../check/types.js';\nimport { findSpecFiles } from '../spec-file-utils.js';\nimport type { MapBuildResult, RenumberMap } from './types.js';\nimport { RenumberError } from './types.js';\n\n/**\n * Build a renumber map by walking ALL REQ and DESIGN files in document order.\n * Files are sorted alphabetically by basename. Requirements and properties are\n * numbered globally across all files for the feature code.\n */\n// @awa-impl: RENUM-1_AC-1\nexport function buildRenumberMap(code: string, specs: SpecParseResult): MapBuildResult {\n const reqFiles = findSpecFiles(specs.specFiles, code, 'REQ');\n if (reqFiles.length === 0) {\n throw new RenumberError('CODE_NOT_FOUND', `No REQ file found for feature code: ${code}`);\n }\n\n const entries = new Map<string, string>();\n\n // Walk ALL REQ files in alphabetical order for requirements, subrequirements, and ACs\n buildRequirementEntries(code, reqFiles, entries);\n\n // Walk ALL DESIGN files in alphabetical order for properties\n // @awa-impl: RENUM-4_AC-1, RENUM-4_AC-2\n const designFiles = findSpecFiles(specs.specFiles, code, 'DESIGN');\n if (designFiles.length > 0) {\n buildPropertyEntries(code, designFiles, entries);\n }\n // If no DESIGN file exists, skip property renumbering without error (RENUM-4_AC-2)\n\n // Remove identity mappings (old === new)\n for (const [oldId, newId] of entries) {\n if (oldId === newId) {\n entries.delete(oldId);\n }\n }\n\n // @awa-impl: RENUM-1_AC-3\n const noChange = entries.size === 0;\n\n const map: RenumberMap = { code, entries };\n return { map, noChange };\n}\n\n/**\n * Build renumber entries for requirements, subrequirements, and ACs from all REQ files.\n * Files are processed in the order given (already sorted alphabetically).\n * IDs are numbered globally across all files.\n */\n// @awa-impl: RENUM-1_AC-2, RENUM-2_AC-1, RENUM-2_AC-2, RENUM-3_AC-1, RENUM-3_AC-2\nfunction buildRequirementEntries(\n code: string,\n reqFiles: readonly SpecFile[],\n entries: Map<string, string>,\n): void {\n // Collect all requirements and subrequirements across files in order\n const topLevelReqs: string[] = [];\n const subReqsByParent = new Map<string, string[]>();\n const allAcIds: string[] = [];\n\n for (const reqFile of reqFiles) {\n for (const id of reqFile.requirementIds) {\n if (id.includes('.')) {\n const dotIdx = id.lastIndexOf('.');\n const parent = id.slice(0, dotIdx);\n const subs = subReqsByParent.get(parent) ?? [];\n subs.push(id);\n subReqsByParent.set(parent, subs);\n } else {\n topLevelReqs.push(id);\n }\n }\n for (const acId of reqFile.acIds) {\n allAcIds.push(acId);\n }\n }\n\n // Build old→new mapping for top-level requirements\n const reqNumberMap = new Map<string, number>(); // old req ID → new number\n for (let i = 0; i < topLevelReqs.length; i++) {\n const oldId = topLevelReqs[i] as string;\n const newNum = i + 1;\n const newId = `${code}-${newNum}`;\n entries.set(oldId, newId);\n reqNumberMap.set(oldId, newNum);\n }\n\n // Build old→new mapping for subrequirements within each parent\n for (const oldParentId of topLevelReqs) {\n const subs = subReqsByParent.get(oldParentId);\n if (!subs) continue;\n\n const newParentNum = reqNumberMap.get(oldParentId);\n if (newParentNum === undefined) continue;\n\n for (let j = 0; j < subs.length; j++) {\n const oldSubId = subs[j] as string;\n const newSubNum = j + 1;\n const newSubId = `${code}-${newParentNum}.${newSubNum}`;\n entries.set(oldSubId, newSubId);\n }\n }\n\n // Build AC mapping: group ACs by their parent (req or subreq)\n const acsByParent = new Map<string, string[]>();\n for (const acId of allAcIds) {\n const parent = acId.split('_AC-')[0] as string;\n const acs = acsByParent.get(parent) ?? [];\n acs.push(acId);\n acsByParent.set(parent, acs);\n }\n\n // Renumber ACs within each parent\n for (const [oldParentId, acs] of acsByParent) {\n // Determine the new parent ID\n const newParentId = entries.get(oldParentId) ?? oldParentId;\n\n for (let k = 0; k < acs.length; k++) {\n const oldAcId = acs[k] as string;\n const newAcNum = k + 1;\n const newAcId = `${newParentId}_AC-${newAcNum}`;\n entries.set(oldAcId, newAcId);\n }\n }\n}\n\n/**\n * Build renumber entries for properties from all DESIGN files.\n * Files are processed in the order given (already sorted alphabetically).\n * Properties are numbered globally across all files.\n */\nfunction buildPropertyEntries(\n code: string,\n designFiles: readonly SpecFile[],\n entries: Map<string, string>,\n): void {\n let counter = 0;\n for (const designFile of designFiles) {\n for (const oldId of designFile.propertyIds) {\n counter++;\n const newId = `${code}_P-${counter}`;\n entries.set(oldId, newId);\n }\n }\n}\n","// @awa-component: RENUM-Propagator\n// @awa-impl: RENUM-5_AC-1, RENUM-5_AC-2, RENUM-5_AC-3\n// @awa-impl: RENUM-6_AC-1, RENUM-6_AC-2\n\nimport { readFile, writeFile } from 'node:fs/promises';\n\nimport { collectFiles } from '../check/glob.js';\nimport type { CheckConfig, MarkerScanResult, SpecParseResult } from '../check/types.js';\nimport type { AffectedFile, PropagationResult, RenumberMap, Replacement } from './types.js';\nimport { RenumberError } from './types.js';\n\n/**\n * Apply the renumber map across all spec files and code files.\n * Uses two-pass placeholder replacement to avoid swap collisions.\n */\nexport async function propagate(\n map: RenumberMap,\n specs: SpecParseResult,\n markers: MarkerScanResult,\n config: CheckConfig,\n dryRun: boolean,\n): Promise<PropagationResult> {\n if (map.entries.size === 0) {\n return { affectedFiles: [], totalReplacements: 0 };\n }\n\n // Collect all file paths that need scanning\n const filePaths = await collectFilePaths(map, specs, markers, config);\n\n const affectedFiles: AffectedFile[] = [];\n let totalReplacements = 0;\n\n for (const filePath of filePaths) {\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch {\n continue; // Skip unreadable files\n }\n\n const result = applyReplacements(content, map);\n if (result.replacements.length === 0) continue;\n\n affectedFiles.push({ filePath, replacements: result.replacements });\n totalReplacements += result.replacements.length;\n\n if (!dryRun) {\n try {\n await writeFile(filePath, result.newContent, 'utf-8');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n throw new RenumberError('WRITE_FAILED', `Failed to write ${filePath}: ${msg}`);\n }\n }\n }\n\n return { affectedFiles, totalReplacements };\n}\n\n/**\n * Collect unique file paths that may contain IDs needing replacement.\n * Includes all spec files (any may reference the code's IDs in matrices,\n * prose, or cross-references), extra spec files from extraSpecGlobs,\n * and code files with relevant markers.\n */\n// @awa-impl: RENUM-5_AC-1, RENUM-5_AC-2, RENUM-5_AC-3\nasync function collectFilePaths(\n map: RenumberMap,\n specs: SpecParseResult,\n markers: MarkerScanResult,\n config: CheckConfig,\n): Promise<string[]> {\n const paths = new Set<string>();\n const code = map.code;\n\n // Include all spec files — any may contain IDs in matrices, prose, or cross-refs.\n // The replacement pass efficiently skips files with no matches.\n // @awa-impl: RENUM-5_AC-1, RENUM-5_AC-3\n for (const specFile of specs.specFiles) {\n paths.add(specFile.filePath);\n }\n\n // Include extra spec files from extraSpecGlobs (custom files in .awa/ not matched by specGlobs)\n const combinedIgnore = [...config.specIgnore, ...config.extraSpecIgnore];\n const extraFiles = await collectFiles(config.extraSpecGlobs, combinedIgnore);\n for (const filePath of extraFiles) {\n paths.add(filePath);\n }\n\n // Add code/test files that have markers referencing affected IDs\n // @awa-impl: RENUM-5_AC-2\n const affectedIds = new Set(map.entries.keys());\n for (const marker of markers.markers) {\n if (affectedIds.has(marker.id) || hasCodePrefix(marker.id, code)) {\n paths.add(marker.filePath);\n }\n }\n\n return [...paths];\n}\n\n/**\n * Apply two-pass placeholder replacement to file content.\n * Pass 1: Replace old IDs with unique placeholders.\n * Pass 2: Replace placeholders with new IDs.\n * Uses whole-ID boundary matching to avoid partial replacements.\n */\n// @awa-impl: RENUM-6_AC-1, RENUM-6_AC-2\nfunction applyReplacements(\n content: string,\n map: RenumberMap,\n): { newContent: string; replacements: Replacement[] } {\n const replacements: Replacement[] = [];\n const lines = content.split('\\n');\n\n // Build sorted entries: replace longer IDs first to avoid partial matches\n const sortedEntries = [...map.entries].sort(([a], [b]) => b.length - a.length);\n\n // Build placeholder map: old ID → placeholder → new ID\n const placeholders = new Map<string, string>();\n const placeholderToNew = new Map<string, string>();\n for (const [oldId, newId] of sortedEntries) {\n const placeholder = `__RENUM_${placeholders.size}__`;\n placeholders.set(oldId, placeholder);\n placeholderToNew.set(placeholder, newId);\n }\n\n // Single pass per line: replace old IDs with placeholders, then placeholders with new IDs.\n // This avoids holding separate pass1Lines and pass2Lines arrays (reduces from 4× to 2× memory).\n for (let idx = 0; idx < lines.length; idx++) {\n let modified = lines[idx] as string;\n const origLine = modified;\n\n // Phase 1: Replace old IDs with placeholders and track replacements\n for (const [oldId, placeholder] of placeholders) {\n const regex = buildWholeIdRegex(oldId);\n // Count matches on original line for tracking\n const trackRegex = buildWholeIdRegex(oldId);\n while (trackRegex.exec(origLine) !== null) {\n replacements.push({\n line: idx + 1,\n oldId,\n newId: map.entries.get(oldId) ?? oldId,\n });\n }\n modified = modified.replace(regex, placeholder);\n }\n\n // Phase 2: Replace placeholders with new IDs\n for (const [placeholder, newId] of placeholderToNew) {\n modified = modified.replaceAll(placeholder, newId);\n }\n\n lines[idx] = modified;\n }\n\n // Deduplicate replacements per line (same oldId on same line counted once)\n const seen = new Set<string>();\n const dedupedReplacements = replacements.filter((r) => {\n const key = `${r.line}:${r.oldId}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n\n return { newContent: lines.join('\\n'), replacements: dedupedReplacements };\n}\n\n/**\n * Build a regex that matches a whole ID with boundary assertions.\n * Prevents partial matches within unrelated tokens.\n */\nfunction buildWholeIdRegex(id: string): RegExp {\n const escaped = id.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n // Word boundary won't work across hyphens/underscores, so use lookahead/lookbehind\n // Match the ID when not preceded or followed by alphanumeric, hyphen, underscore, or dot\n return new RegExp(`(?<![A-Za-z0-9_.-])${escaped}(?![A-Za-z0-9_.-])`, 'g');\n}\n\n/**\n * Check if an ID starts with the given feature code prefix.\n */\nfunction hasCodePrefix(id: string, code: string): boolean {\n return id.startsWith(`${code}-`) || id.startsWith(`${code}_`);\n}\n","// @awa-component: CLI-MarkerScanner\n// @awa-impl: CLI-28_AC-1\n\nimport { glob } from 'node:fs/promises';\n\n/**\n * Matches a path against a simple glob pattern supporting * and **.\n * Also matches a directory prefix against `dir/**` patterns (e.g. \"src\" matches \"src/**\").\n */\nexport function matchSimpleGlob(path: string, pattern: string): boolean {\n const regex = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*\\*/g, '<<GLOBSTAR>>')\n .replace(/\\*/g, '[^/]*')\n .replace(/<<GLOBSTAR>>/g, '.*');\n return new RegExp(`(^|/)${regex}($|/)`).test(path);\n}\n\n/**\n * Collect files matching globs while applying ignore patterns.\n * Uses fs.glob exclude callback for directory-level skipping (perf optimization)\n * and post-filters results for file-level ignore matching.\n */\nexport async function collectFiles(\n globs: readonly string[],\n ignore: readonly string[],\n): Promise<string[]> {\n const files: string[] = [];\n\n // For exclude callback, also match directory prefixes (e.g. \"src\" for \"src/**\")\n const dirPrefixes = ignore.filter((ig) => ig.endsWith('/**')).map((ig) => ig.slice(0, -3));\n\n // Pre-compile ignore patterns to RegExp objects to avoid re-creating them on every callback invocation\n const compiledIgnore = ignore.map((ig) => {\n const regex = ig\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\*\\*/g, '<<GLOBSTAR>>')\n .replace(/\\*/g, '[^/]*')\n .replace(/<<GLOBSTAR>>/g, '.*');\n return new RegExp(`(^|/)${regex}($|/)`);\n });\n\n for (const pattern of globs) {\n for await (const filePath of glob(pattern, {\n exclude: (p) => dirPrefixes.includes(p) || compiledIgnore.some((re) => re.test(p)),\n })) {\n // Post-filter: fs.glob exclude only receives directory names,\n // so we must also filter the yielded file paths\n if (!compiledIgnore.some((re) => re.test(filePath))) {\n files.push(filePath);\n }\n }\n }\n\n return [...new Set(files)];\n}\n","// @awa-component: RENUM-Reporter\n// @awa-impl: RENUM-7_AC-1\n// @awa-impl: RENUM-11_AC-1\n\nimport type { RenumberResult } from './types.js';\n\n/**\n * Format renumber results as human-readable text.\n * In dry-run mode, prefixes output with a banner.\n */\n// @awa-impl: RENUM-7_AC-1\nexport function formatText(result: RenumberResult, dryRun: boolean): string {\n const lines: string[] = [];\n\n if (dryRun) {\n lines.push('DRY RUN — no files were modified\\n');\n }\n\n if (result.noChange) {\n lines.push(`${result.code}: no changes needed (IDs already sequential)`);\n return lines.join('\\n');\n }\n\n // Renumber map table\n lines.push(`${result.code}: ${result.map.entries.size} ID(s) renumbered\\n`);\n lines.push(' Old ID → New ID');\n lines.push(` ${'─'.repeat(40)}`);\n for (const [oldId, newId] of result.map.entries) {\n lines.push(` ${oldId} → ${newId}`);\n }\n\n // Affected files\n if (result.affectedFiles.length > 0) {\n lines.push('');\n lines.push(\n ` ${result.totalReplacements} replacement(s) in ${result.affectedFiles.length} file(s):`,\n );\n for (const file of result.affectedFiles) {\n lines.push(` ${file.filePath} (${file.replacements.length})`);\n }\n }\n\n // Malformed corrections\n if (result.malformedCorrections.length > 0) {\n lines.push('');\n lines.push(' Malformed ID corrections:');\n for (const c of result.malformedCorrections) {\n lines.push(` ${c.filePath}:${c.line} — ${c.token} → ${c.replacement}`);\n }\n }\n\n // Malformed warnings\n if (result.malformedWarnings.length > 0) {\n lines.push('');\n lines.push(' Malformed ID warnings:');\n for (const w of result.malformedWarnings) {\n lines.push(` ${w.filePath}:${w.line} — ${w.token}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Format renumber results as JSON for CI consumption.\n */\n// @awa-impl: RENUM-11_AC-1\nexport function formatJson(result: RenumberResult): string {\n const output = {\n code: result.code,\n noChange: result.noChange,\n map: Object.fromEntries(result.map.entries),\n affectedFiles: result.affectedFiles.map((f) => ({\n filePath: f.filePath,\n replacements: f.replacements.map((r) => ({\n line: r.line,\n oldId: r.oldId,\n newId: r.newId,\n })),\n })),\n totalReplacements: result.totalReplacements,\n malformedCorrections: result.malformedCorrections.map((c) => ({\n filePath: c.filePath,\n line: c.line,\n token: c.token,\n replacement: c.replacement,\n })),\n malformedWarnings: result.malformedWarnings.map((w) => ({\n filePath: w.filePath,\n line: w.line,\n token: w.token,\n })),\n };\n\n return JSON.stringify(output, null, 2);\n}\n","// @awa-component: CLI-MarkerScanner\n// @awa-impl: CLI-16_AC-1\n// @awa-impl: CLI-26_AC-1\n// @awa-impl: CLI-28_AC-1\n\nimport { readFile } from 'node:fs/promises';\n\nimport { collectFiles, matchSimpleGlob } from './glob.js';\nimport type { CheckConfig, CodeMarker, Finding, MarkerScanResult, MarkerType } from './types.js';\n\nconst MARKER_TYPE_MAP: Record<string, MarkerType> = {\n '@awa-impl': 'impl',\n '@awa-test': 'test',\n '@awa-component': 'component',\n};\n\n/** Ignore directive patterns (similar to eslint-disable comments). */\n// Use RegExp constructor so the literal token does not appear in this source file,\n// preventing the marker scanner from self-ignoring when it scans its own code.\nconst IGNORE_FILE_RE = new RegExp('@awa-ignore' + '-file\\\\b');\nconst IGNORE_NEXT_LINE_RE = /@awa-ignore-next-line\\b/;\nconst IGNORE_LINE_RE = /@awa-ignore\\b/;\nconst IGNORE_START_RE = /@awa-ignore-start\\b/;\nconst IGNORE_END_RE = /@awa-ignore-end\\b/;\n\n// @awa-impl: CLI-16_AC-1\nexport async function scanMarkers(config: CheckConfig): Promise<MarkerScanResult> {\n const files = await collectCodeFiles(config.codeGlobs, config.codeIgnore);\n const markers: CodeMarker[] = [];\n const findings: Finding[] = [];\n\n for (const filePath of files) {\n // Skip files matching ignoreMarkers globs\n if (config.ignoreMarkers.some((ig) => matchSimpleGlob(filePath, ig))) {\n continue;\n }\n\n const result = await scanFile(filePath, config.markers);\n markers.push(...result.markers);\n findings.push(...result.findings);\n }\n\n return { markers, findings };\n}\n\n// @awa-impl: CLI-26_AC-1\nfunction buildMarkerRegex(markerNames: readonly string[]): RegExp {\n const escaped = markerNames.map((m) => m.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&'));\n // Match marker followed by colon, then capture the rest of the line (IDs)\n return new RegExp(`(${escaped.join('|')}):\\\\s*(.+)`, 'g');\n}\n\n/** Pattern matching valid impl/test IDs and component names. */\nconst ID_TOKEN_RE = /^([A-Z][A-Z0-9]*(?:[-_][A-Za-z0-9]+)*(?:\\.\\d+)?(?:[-_][A-Za-z0-9]+)*)/;\n\nasync function scanFile(\n filePath: string,\n markerNames: readonly string[],\n): Promise<{ markers: CodeMarker[]; findings: Finding[] }> {\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch {\n return { markers: [], findings: [] };\n }\n\n // Check for file-level ignore directive anywhere in the file\n if (IGNORE_FILE_RE.test(content)) {\n return { markers: [], findings: [] };\n }\n\n const regex = buildMarkerRegex(markerNames);\n const lines = content.split('\\n');\n const markers: CodeMarker[] = [];\n const findings: Finding[] = [];\n let ignoreNextLine = false;\n let ignoreBlock = false;\n\n for (const [i, line] of lines.entries()) {\n // Check for block ignore boundaries\n if (IGNORE_START_RE.test(line)) {\n ignoreBlock = true;\n continue;\n }\n if (IGNORE_END_RE.test(line)) {\n ignoreBlock = false;\n continue;\n }\n if (ignoreBlock) {\n continue;\n }\n\n // Check if previous line had @awa-ignore-next-line\n if (ignoreNextLine) {\n ignoreNextLine = false;\n continue;\n }\n\n // Check if this line is an @awa-ignore-next-line directive\n if (IGNORE_NEXT_LINE_RE.test(line)) {\n ignoreNextLine = true;\n continue;\n }\n\n // Check for inline @awa-ignore on this line (ignores markers on this line)\n if (IGNORE_LINE_RE.test(line)) {\n continue;\n }\n\n regex.lastIndex = 0;\n let match = regex.exec(line);\n\n while (match !== null) {\n const markerName = match[1] ?? '';\n const idsRaw = match[2] ?? '';\n const type = resolveMarkerType(markerName, markerNames);\n\n // Split by comma to support multiple IDs per marker line\n const ids = idsRaw\n .split(',')\n .map((id) => id.trim())\n .filter(Boolean);\n\n for (const id of ids) {\n // Extract only the leading ID token\n const tokenMatch = ID_TOKEN_RE.exec(id);\n const cleanId = tokenMatch?.[1]?.trim() ?? '';\n if (cleanId && tokenMatch) {\n // Check for trailing text after the ID (only whitespace allowed)\n const remainder = id.slice(tokenMatch[0].length).trim();\n if (remainder) {\n findings.push({\n severity: 'error',\n code: 'marker-trailing-text',\n message: `Marker has trailing text after ID '${cleanId}': '${remainder}' — use comma-separated IDs only`,\n filePath,\n line: i + 1,\n id: cleanId,\n });\n }\n markers.push({ type, id: cleanId, filePath, line: i + 1 });\n }\n }\n\n match = regex.exec(line);\n }\n }\n\n return { markers, findings };\n}\n\nfunction resolveMarkerType(markerName: string, configuredMarkers: readonly string[]): MarkerType {\n // Check default mapping first\n const mapped = MARKER_TYPE_MAP[markerName];\n if (mapped) return mapped;\n // For custom markers, infer type by position in configured array\n // Default order: [impl, test, component]\n const idx = configuredMarkers.indexOf(markerName);\n if (idx === 1) return 'test';\n if (idx === 2) return 'component';\n return 'impl';\n}\n\n// @awa-impl: CLI-28_AC-1\nasync function collectCodeFiles(\n codeGlobs: readonly string[],\n ignore: readonly string[],\n): Promise<string[]> {\n return collectFiles(codeGlobs, ignore);\n}\n","// @awa-component: CLI-SpecParser\n// @awa-impl: CLI-17_AC-1\n// @awa-impl: CLI-27_AC-1\n\nimport { readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\n\nimport { collectFiles } from './glob.js';\nimport type { CheckConfig, CrossReference, SpecFile, SpecParseResult } from './types.js';\n\n// @awa-impl: CLI-17_AC-1\nexport async function parseSpecs(config: CheckConfig): Promise<SpecParseResult> {\n const files = await collectSpecFiles(config.specGlobs, config.specIgnore);\n const specFiles: SpecFile[] = [];\n\n const requirementIds = new Set<string>();\n const acIds = new Set<string>();\n const propertyIds = new Set<string>();\n const componentNames = new Set<string>();\n const idLocations = new Map<string, { filePath: string; line: number }>();\n\n for (const filePath of files) {\n const specFile = await parseSpecFile(filePath, config.crossRefPatterns);\n if (specFile) {\n specFiles.push(specFile);\n for (const id of specFile.requirementIds) requirementIds.add(id);\n for (const id of specFile.acIds) acIds.add(id);\n for (const id of specFile.propertyIds) propertyIds.add(id);\n for (const name of specFile.componentNames) componentNames.add(name);\n // Merge id locations from parsed spec file\n for (const [id, loc] of specFile.idLocations ?? []) {\n idLocations.set(id, loc);\n }\n }\n }\n\n const allIds = new Set<string>([...requirementIds, ...acIds, ...propertyIds, ...componentNames]);\n\n return { requirementIds, acIds, propertyIds, componentNames, allIds, specFiles, idLocations };\n}\n\nasync function parseSpecFile(\n filePath: string,\n crossRefPatterns: readonly string[],\n): Promise<SpecFile | null> {\n let content: string;\n try {\n content = await readFile(filePath, 'utf-8');\n } catch {\n return null;\n }\n\n const code = extractCodePrefix(filePath);\n const lines = content.split('\\n');\n\n const requirementIds: string[] = [];\n const acIds: string[] = [];\n const propertyIds: string[] = [];\n const componentNames: string[] = [];\n const crossRefs: CrossReference[] = [];\n const idLocations = new Map<string, { filePath: string; line: number }>();\n const componentImplements = new Map<string, string[]>();\n\n // Requirement ID: ### CODE-N: Title or ### CODE-N.P: Title\n const reqIdRegex = /^###\\s+([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?)\\s*:/;\n // AC ID: - CODE-N_AC-M or - [ ] CODE-N_AC-M or - [x] CODE-N.P_AC-M\n const acIdRegex = /^-\\s+(?:\\[[ x]\\]\\s+)?([A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?_AC-\\d+)\\s/;\n // Property ID: - CODE_P-N [Name]\n const propIdRegex = /^-\\s+([A-Z][A-Z0-9]*_P-\\d+)\\s/;\n // Component name: ### CODE-ComponentName\n const componentRegex = /^###\\s+([A-Z][A-Z0-9]*-[A-Za-z][A-Za-z0-9]*(?:[A-Z][a-z0-9]*)*)\\s*$/;\n\n // Track current component for building componentImplements map\n let currentComponent: string | null = null;\n\n for (const [i, line] of lines.entries()) {\n const lineNum = i + 1;\n\n // Requirement IDs\n const reqMatch = reqIdRegex.exec(line);\n if (reqMatch?.[1]) {\n requirementIds.push(reqMatch[1]);\n idLocations.set(reqMatch[1], { filePath, line: lineNum });\n }\n\n // AC IDs\n const acMatch = acIdRegex.exec(line);\n if (acMatch?.[1]) {\n acIds.push(acMatch[1]);\n idLocations.set(acMatch[1], { filePath, line: lineNum });\n }\n\n // Property IDs\n const propMatch = propIdRegex.exec(line);\n if (propMatch?.[1]) {\n propertyIds.push(propMatch[1]);\n idLocations.set(propMatch[1], { filePath, line: lineNum });\n }\n\n // Component names (from DESIGN files)\n const compMatch = componentRegex.exec(line);\n if (compMatch?.[1]) {\n // Only count as component if it doesn't match requirement pattern\n if (!reqIdRegex.test(line)) {\n componentNames.push(compMatch[1]);\n idLocations.set(compMatch[1], { filePath, line: lineNum });\n currentComponent = compMatch[1];\n }\n }\n\n // Any H2 or H1 heading resets the current component context\n if (/^#{1,2}\\s/.test(line) && !compMatch) {\n currentComponent = null;\n }\n\n // Cross-references (IMPLEMENTS:, VALIDATES:)\n for (const pattern of crossRefPatterns) {\n const patIdx = line.indexOf(pattern);\n if (patIdx !== -1) {\n const afterPattern = line.slice(patIdx + pattern.length);\n const ids = extractIdsFromText(afterPattern);\n if (ids.length > 0) {\n const type = pattern.toLowerCase().includes('implements') ? 'implements' : 'validates';\n crossRefs.push({ type, ids, filePath, line: i + 1 });\n\n // Build componentImplements map for IMPLEMENTS lines\n if (type === 'implements' && currentComponent) {\n const existing = componentImplements.get(currentComponent) ?? [];\n existing.push(...ids);\n componentImplements.set(currentComponent, existing);\n }\n }\n }\n }\n }\n\n return {\n filePath,\n code,\n requirementIds,\n acIds,\n propertyIds,\n componentNames,\n crossRefs,\n idLocations,\n componentImplements,\n content,\n };\n}\n\nfunction extractCodePrefix(filePath: string): string {\n const name = basename(filePath, '.md');\n // Extract CODE from patterns like REQ-CODE-feature, DESIGN-CODE-feature, FEAT-CODE-feature\n const match = /^(?:REQ|DESIGN|FEAT|EXAMPLE|API)-([A-Z][A-Z0-9]*)-/.exec(name);\n if (match?.[1]) return match[1];\n // Fallback: ARCHITECTURE.md has no code prefix\n return '';\n}\n\nfunction extractIdsFromText(text: string): string[] {\n const idRegex = /[A-Z][A-Z0-9]*-\\d+(?:\\.\\d+)?(?:_AC-\\d+)?|[A-Z][A-Z0-9]*_P-\\d+/g;\n const ids: string[] = [];\n let match = idRegex.exec(text);\n while (match !== null) {\n ids.push(match[0]);\n match = idRegex.exec(text);\n }\n return ids;\n}\n\n// @awa-impl: CLI-27_AC-1\nasync function collectSpecFiles(\n specGlobs: readonly string[],\n ignore: readonly string[],\n): Promise<string[]> {\n return collectFiles(specGlobs, ignore);\n}\n","// @awa-component: CLI-CheckCommand\n\n// @awa-impl: CLI-31_AC-1\nexport interface CheckConfig {\n readonly specGlobs: readonly string[];\n readonly codeGlobs: readonly string[];\n readonly specIgnore: readonly string[];\n readonly codeIgnore: readonly string[];\n readonly extraSpecGlobs: readonly string[];\n readonly extraSpecIgnore: readonly string[];\n readonly ignoreMarkers: readonly string[];\n readonly markers: readonly string[];\n readonly idPattern: string;\n readonly crossRefPatterns: readonly string[];\n readonly format: 'text' | 'json';\n readonly schemaDir: string;\n readonly schemaEnabled: boolean;\n readonly allowWarnings: boolean;\n readonly specOnly: boolean;\n readonly fix: boolean;\n}\n\nexport const DEFAULT_CHECK_CONFIG: CheckConfig = {\n specGlobs: [\n '.awa/specs/ARCHITECTURE.md',\n '.awa/specs/FEAT-*.md',\n '.awa/specs/REQ-*.md',\n '.awa/specs/DESIGN-*.md',\n '.awa/specs/EXAMPLE-*.md',\n '.awa/specs/API-*.tsp',\n '.awa/tasks/TASK-*.md',\n '.awa/plans/PLAN-*.md',\n '.awa/align/ALIGN-*.md',\n ],\n codeGlobs: [\n '**/*.{ts,js,tsx,jsx,mts,mjs,cjs,py,go,rs,java,kt,kts,cs,c,h,cpp,cc,cxx,hpp,hxx,swift,rb,php,scala,ex,exs,dart,lua,zig}',\n ],\n specIgnore: [],\n codeIgnore: [\n 'node_modules/**',\n 'dist/**',\n 'vendor/**',\n 'target/**',\n 'build/**',\n 'out/**',\n '.awa/**',\n ],\n extraSpecGlobs: ['.awa/**/*'],\n extraSpecIgnore: ['.awa/.agent/**'],\n ignoreMarkers: [],\n markers: ['@awa-impl', '@awa-test', '@awa-component'],\n idPattern: '([A-Z][A-Z0-9]*-\\\\d+(?:\\\\.\\\\d+)?(?:_AC-\\\\d+)?|[A-Z][A-Z0-9]*_P-\\\\d+)',\n crossRefPatterns: ['IMPLEMENTS:', 'VALIDATES:'],\n format: 'text',\n schemaDir: '.awa/.agent/schemas',\n schemaEnabled: true,\n allowWarnings: false,\n specOnly: false,\n fix: true,\n};\n\nexport type FindingSeverity = 'error' | 'warning';\n\nexport type FindingCode =\n | 'orphaned-marker'\n | 'uncovered-ac'\n | 'uncovered-component'\n | 'uncovered-property'\n | 'unimplemented-ac'\n | 'unlinked-ac'\n | 'impl-not-in-implements'\n | 'implements-not-in-impl'\n | 'broken-cross-ref'\n | 'invalid-id-format'\n | 'marker-trailing-text'\n | 'orphaned-spec'\n | 'schema-missing-section'\n | 'schema-wrong-level'\n | 'schema-missing-content'\n | 'schema-table-columns'\n | 'schema-prohibited'\n | 'schema-no-rule'\n | 'schema-line-limit';\n\nexport interface Finding {\n readonly severity: FindingSeverity;\n readonly code: FindingCode;\n readonly message: string;\n readonly filePath?: string;\n readonly line?: number;\n readonly id?: string;\n /** Path to the .schema.yaml file that defines the violated rule. */\n readonly ruleSource?: string;\n /** Concise representation of the violated rule. */\n readonly rule?: string;\n}\n\nexport type MarkerType = 'impl' | 'test' | 'component';\n\nexport interface CodeMarker {\n readonly type: MarkerType;\n readonly id: string;\n readonly filePath: string;\n readonly line: number;\n}\n\nexport interface MarkerScanResult {\n readonly markers: readonly CodeMarker[];\n readonly findings: readonly Finding[];\n}\n\nexport interface CrossReference {\n readonly type: 'implements' | 'validates';\n readonly ids: readonly string[];\n readonly filePath: string;\n readonly line: number;\n}\n\nexport interface SpecFile {\n readonly filePath: string;\n readonly code: string;\n readonly requirementIds: readonly string[];\n readonly acIds: readonly string[];\n readonly propertyIds: readonly string[];\n readonly componentNames: readonly string[];\n readonly crossRefs: readonly CrossReference[];\n /** Maps IDs parsed from this file to their line number. Populated by spec-parser. */\n readonly idLocations?: ReadonlyMap<string, { filePath: string; line: number }>;\n /** Maps component names to their IMPLEMENTS AC IDs. Populated for DESIGN files. */\n readonly componentImplements?: ReadonlyMap<string, readonly string[]>;\n /** Raw file content, cached by spec-parser to avoid re-reads in downstream phases. */\n readonly content?: string;\n}\n\nexport interface SpecParseResult {\n readonly requirementIds: Set<string>;\n readonly acIds: Set<string>;\n readonly propertyIds: Set<string>;\n readonly componentNames: Set<string>;\n readonly allIds: Set<string>;\n readonly specFiles: readonly SpecFile[];\n /** Maps spec IDs (requirements, ACs, properties, components) to their source location. */\n readonly idLocations: ReadonlyMap<string, { filePath: string; line: number }>;\n}\n\nexport interface CheckResult {\n readonly findings: readonly Finding[];\n}\n\nexport interface RawCheckOptions {\n readonly specIgnore?: string[];\n readonly codeIgnore?: string[];\n readonly format?: string;\n readonly json?: boolean;\n readonly summary?: boolean;\n readonly config?: string;\n readonly allowWarnings?: boolean;\n readonly specOnly?: boolean;\n readonly fix?: boolean;\n}\n","// @awa-component: TRC-SharedScanner\n// @awa-impl: TRC-1_AC-1\n\nimport type { FileConfig } from '../../types/index.js';\nimport { scanMarkers } from '../check/marker-scanner.js';\nimport { parseSpecs } from '../check/spec-parser.js';\nimport type { CheckConfig, MarkerScanResult, SpecParseResult } from '../check/types.js';\nimport { DEFAULT_CHECK_CONFIG } from '../check/types.js';\nimport { configLoader } from '../config.js';\n\n/** Results from scanning markers and specs in parallel. */\nexport interface ScanResults {\n readonly markers: MarkerScanResult;\n readonly specs: SpecParseResult;\n readonly config: CheckConfig;\n}\n\n/**\n * Build a CheckConfig from file config + optional overrides.\n * Shared by trace, coverage, impact, and graph commands.\n */\nexport function buildScanConfig(\n fileConfig: FileConfig | null,\n overrides?: Partial<CheckConfig>,\n): CheckConfig {\n const section = fileConfig?.check;\n\n return {\n specGlobs: toStringArray(section?.['spec-globs']) ?? [...DEFAULT_CHECK_CONFIG.specGlobs],\n codeGlobs: toStringArray(section?.['code-globs']) ?? [...DEFAULT_CHECK_CONFIG.codeGlobs],\n specIgnore: toStringArray(section?.['spec-ignore']) ?? [...DEFAULT_CHECK_CONFIG.specIgnore],\n codeIgnore: toStringArray(section?.['code-ignore']) ?? [...DEFAULT_CHECK_CONFIG.codeIgnore],\n extraSpecGlobs: toStringArray(section?.['extra-spec-globs']) ?? [\n ...DEFAULT_CHECK_CONFIG.extraSpecGlobs,\n ],\n extraSpecIgnore: toStringArray(section?.['extra-spec-ignore']) ?? [\n ...DEFAULT_CHECK_CONFIG.extraSpecIgnore,\n ],\n ignoreMarkers: toStringArray(section?.['ignore-markers']) ?? [\n ...DEFAULT_CHECK_CONFIG.ignoreMarkers,\n ],\n markers: toStringArray(section?.markers) ?? [...DEFAULT_CHECK_CONFIG.markers],\n idPattern:\n typeof section?.['id-pattern'] === 'string'\n ? section['id-pattern']\n : DEFAULT_CHECK_CONFIG.idPattern,\n crossRefPatterns: toStringArray(section?.['cross-ref-patterns']) ?? [\n ...DEFAULT_CHECK_CONFIG.crossRefPatterns,\n ],\n format: DEFAULT_CHECK_CONFIG.format,\n schemaDir: DEFAULT_CHECK_CONFIG.schemaDir,\n schemaEnabled: false,\n allowWarnings: true,\n specOnly: false,\n fix: true,\n ...overrides,\n };\n}\n\n/**\n * Load config, scan markers and parse specs in parallel.\n * Shared by trace, coverage, impact, and graph commands.\n */\nexport async function scan(configPath?: string): Promise<ScanResults> {\n const fileConfig = await configLoader.load(configPath ?? null);\n const config = buildScanConfig(fileConfig);\n\n const [markers, specs] = await Promise.all([scanMarkers(config), parseSpecs(config)]);\n\n return { markers, specs, config };\n}\n\nfunction toStringArray(value: unknown): string[] | null {\n if (Array.isArray(value) && value.every((v) => typeof v === 'string')) {\n return value as string[];\n }\n return null;\n}\n"],"mappings":"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","names":["readFile","writeFile","writeFile","readFile","readFile","readFile","basename","readFile","basename","readFile"]}
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  renumberCommand,
11
11
  scan,
12
12
  scanMarkers
13
- } from "./chunk-FGJMXOYA.js";
13
+ } from "./chunk-2VUVSW6T.js";
14
14
  import {
15
15
  ConfigError,
16
16
  DiffError,
@@ -36,7 +36,7 @@ import { Command, Option } from "commander";
36
36
  // src/_generated/package_info.ts
37
37
  var PACKAGE_INFO = {
38
38
  "name": "@ncoderz/awa",
39
- "version": "1.8.2",
39
+ "version": "1.8.4",
40
40
  "author": "Richard Sewell <richard.sewell@ncoderz.com>",
41
41
  "license": "BSD-3-Clause",
42
42
  "description": "awa is an Agent Workflow for AIs. It is also a CLI tool to powerfully manage agent workflow files using templates."
@@ -1820,11 +1820,19 @@ function buildCheckConfig(fileConfig, cliOptions) {
1820
1820
  const allowWarnings = cliOptions.allowWarnings === true ? true : typeof section?.["allow-warnings"] === "boolean" ? section["allow-warnings"] : DEFAULT_CHECK_CONFIG.allowWarnings;
1821
1821
  const specOnly = cliOptions.specOnly === true ? true : typeof section?.["spec-only"] === "boolean" ? section["spec-only"] : DEFAULT_CHECK_CONFIG.specOnly;
1822
1822
  const fix = cliOptions.fix === false ? false : DEFAULT_CHECK_CONFIG.fix;
1823
+ const extraSpecGlobs = toStringArray(section?.["extra-spec-globs"]) ?? [
1824
+ ...DEFAULT_CHECK_CONFIG.extraSpecGlobs
1825
+ ];
1826
+ const extraSpecIgnore = toStringArray(section?.["extra-spec-ignore"]) ?? [
1827
+ ...DEFAULT_CHECK_CONFIG.extraSpecIgnore
1828
+ ];
1823
1829
  return {
1824
1830
  specGlobs,
1825
1831
  codeGlobs,
1826
1832
  specIgnore,
1827
1833
  codeIgnore,
1834
+ extraSpecGlobs,
1835
+ extraSpecIgnore,
1828
1836
  ignoreMarkers,
1829
1837
  markers,
1830
1838
  idPattern,
@@ -3548,18 +3556,18 @@ async function executeRenames(renames, sourceCode, targetCode, dryRun) {
3548
3556
  }
3549
3557
  return renames;
3550
3558
  }
3551
- async function findStaleRefs(sourceCode, specFiles) {
3559
+ async function findStaleRefs(sourceCode, filePaths) {
3552
3560
  const stale = [];
3553
3561
  const pattern = new RegExp(`\\b${sourceCode}-\\d`, "g");
3554
- for (const sf of specFiles) {
3562
+ for (const filePath of filePaths) {
3555
3563
  let content;
3556
3564
  try {
3557
- content = await readFile8(sf.filePath, "utf-8");
3565
+ content = await readFile8(filePath, "utf-8");
3558
3566
  } catch {
3559
3567
  continue;
3560
3568
  }
3561
3569
  if (pattern.test(content)) {
3562
- stale.push(sf.filePath);
3570
+ stale.push(filePath);
3563
3571
  }
3564
3572
  pattern.lastIndex = 0;
3565
3573
  }
@@ -3716,7 +3724,7 @@ function findHighestPropertyNumber(designFiles) {
3716
3724
  async function mergeCommand(options) {
3717
3725
  try {
3718
3726
  validateMerge(options.sourceCode, options.targetCode);
3719
- const { markers, specs } = await scan(options.config);
3727
+ const { markers, specs, config } = await scan(options.config);
3720
3728
  const dryRun = options.dryRun === true;
3721
3729
  if (!hasAnySpecFile(specs.specFiles, options.targetCode)) {
3722
3730
  throw new MergeError(
@@ -3732,7 +3740,7 @@ async function mergeCommand(options) {
3732
3740
  let affectedFiles = [];
3733
3741
  let totalReplacements = 0;
3734
3742
  if (!recodeNoChange) {
3735
- const result2 = await propagate(map, specs, markers, dryRun);
3743
+ const result2 = await propagate(map, specs, markers, config, dryRun);
3736
3744
  affectedFiles = result2.affectedFiles;
3737
3745
  totalReplacements = result2.totalReplacements;
3738
3746
  }
@@ -3744,10 +3752,13 @@ async function mergeCommand(options) {
3744
3752
  );
3745
3753
  const movedSourcePaths = new Set(moves.map((m) => m.sourceFile));
3746
3754
  const propagatedPaths = new Set(affectedFiles.map((af) => af.filePath));
3747
- const nonSourceFiles = specs.specFiles.filter(
3748
- (sf) => !movedSourcePaths.has(sf.filePath) && !propagatedPaths.has(sf.filePath)
3755
+ const specFilePaths = specs.specFiles.map((sf) => sf.filePath);
3756
+ const combinedIgnore = [...config.specIgnore, ...config.extraSpecIgnore];
3757
+ const extraFiles = await collectFiles(config.extraSpecGlobs, combinedIgnore);
3758
+ const allFiles = [.../* @__PURE__ */ new Set([...specFilePaths, ...extraFiles])].filter(
3759
+ (p) => !movedSourcePaths.has(p) && !propagatedPaths.has(p)
3749
3760
  );
3750
- const staleRefs = await findStaleRefs(options.sourceCode, nonSourceFiles);
3761
+ const staleRefs = await findStaleRefs(options.sourceCode, allFiles);
3751
3762
  const noChange = recodeNoChange && moves.length === 0;
3752
3763
  const result = {
3753
3764
  sourceCode: options.sourceCode,
@@ -3764,7 +3775,7 @@ async function mergeCommand(options) {
3764
3775
  return 2;
3765
3776
  }
3766
3777
  if (options.renumber && !dryRun && !noChange) {
3767
- const { renumberCommand: renumberCommand2 } = await import("./renumber-OJS767K3.js");
3778
+ const { renumberCommand: renumberCommand2 } = await import("./renumber-TGDI47IS.js");
3768
3779
  await renumberCommand2({
3769
3780
  code: options.targetCode,
3770
3781
  dryRun: false,
@@ -3872,7 +3883,7 @@ function formatJson3(result) {
3872
3883
  // src/commands/recode.ts
3873
3884
  async function recodeCommand(options) {
3874
3885
  try {
3875
- const { markers, specs } = await scan(options.config);
3886
+ const { markers, specs, config } = await scan(options.config);
3876
3887
  const dryRun = options.dryRun === true;
3877
3888
  const { map, noChange: recodeNoChange } = buildRecodeMap(
3878
3889
  options.sourceCode,
@@ -3882,7 +3893,7 @@ async function recodeCommand(options) {
3882
3893
  let affectedFiles = [];
3883
3894
  let totalReplacements = 0;
3884
3895
  if (!recodeNoChange) {
3885
- const result2 = await propagate(map, specs, markers, dryRun);
3896
+ const result2 = await propagate(map, specs, markers, config, dryRun);
3886
3897
  affectedFiles = result2.affectedFiles;
3887
3898
  totalReplacements = result2.totalReplacements;
3888
3899
  }
@@ -3895,11 +3906,15 @@ async function recodeCommand(options) {
3895
3906
  );
3896
3907
  }
3897
3908
  await executeRenames(renames, options.sourceCode, options.targetCode, dryRun);
3909
+ const renamedPaths = new Set(renames.map((r) => r.oldPath));
3898
3910
  const propagatedPaths = new Set(affectedFiles.map((af) => af.filePath));
3899
- const nonSourceFiles = specs.specFiles.filter(
3900
- (sf) => !renames.some((r) => r.oldPath === sf.filePath) && !propagatedPaths.has(sf.filePath)
3911
+ const specFilePaths = specs.specFiles.map((sf) => sf.filePath);
3912
+ const combinedIgnore = [...config.specIgnore, ...config.extraSpecIgnore];
3913
+ const extraFiles = await collectFiles(config.extraSpecGlobs, combinedIgnore);
3914
+ const allFiles = [.../* @__PURE__ */ new Set([...specFilePaths, ...extraFiles])].filter(
3915
+ (p) => !renamedPaths.has(p) && !propagatedPaths.has(p)
3901
3916
  );
3902
- const staleRefs = await findStaleRefs(options.sourceCode, nonSourceFiles);
3917
+ const staleRefs = await findStaleRefs(options.sourceCode, allFiles);
3903
3918
  const noChange = recodeNoChange && renames.length === 0;
3904
3919
  const result = {
3905
3920
  sourceCode: options.sourceCode,