mandrel 2.19.0 → 2.20.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.
@@ -1,129 +0,0 @@
1
- /**
2
- * spec-freshness.js — path-reference helpers for plan authoring.
3
- *
4
- * Stage 5 retired the epic-era `validateSpecFreshness` / structured-comment
5
- * reporter (no production callers remained after the planning collapse).
6
- * Survivors: path-cue extraction used by `planning/authoring-context.js`
7
- * and the plan-authoring grounding tests.
8
- */
9
-
10
- /**
11
- * Path-shape regexes. Three forms the Architect persona emits today:
12
- *
13
- * 1. Backticked reference → `` `src/auth.ts` ``
14
- * 2. Code-block file header → `// src/auth.ts` or `# lib/foo.py`
15
- * 3. Inline prose mention → bare `src/auth.ts` between word boundaries
16
- *
17
- * All three pull the same captured-path group. We anchor the path on a
18
- * known repo root (`.agents`, `src`, `lib`, `app`, `tests`, `packages`,
19
- * `scripts`, `docs`) so we don't false-positive on `library`, `testimonial`,
20
- * versioned semver fragments, etc.
21
- */
22
- const PATH_ROOTS = [
23
- '\\.agents',
24
- 'src',
25
- 'lib',
26
- 'app',
27
- 'tests',
28
- 'packages',
29
- 'scripts',
30
- 'docs',
31
- ];
32
-
33
- const PATH_BODY = '[\\w./-]+\\.[a-zA-Z0-9]{1,8}';
34
-
35
- const BACKTICK_PATH_RE = new RegExp(
36
- `\`((?:${PATH_ROOTS.join('|')})/${PATH_BODY})\``,
37
- 'g',
38
- );
39
-
40
- const COMMENT_HEADER_PATH_RE = new RegExp(
41
- `(?:^|\\n)\\s*(?://|#)\\s*((?:${PATH_ROOTS.join('|')})/${PATH_BODY})\\b`,
42
- 'g',
43
- );
44
-
45
- const BARE_PATH_RE = new RegExp(
46
- `(?:^|[\\s([<>])((?:${PATH_ROOTS.join('|')})/${PATH_BODY})(?=[\\s)\\].,;:]|$)`,
47
- 'g',
48
- );
49
-
50
- /**
51
- * Words in surrounding prose that signal a reference is intentionally
52
- * net-new — the planner is *proposing* the path, not asserting it exists.
53
- * When any of these appear within `AMBIGUITY_WINDOW` characters of the
54
- * match, we treat the citation as intentional rather than drift.
55
- */
56
- const NEW_FILE_CUES = [
57
- 'introduce',
58
- 'introduces',
59
- 'introducing',
60
- 'add',
61
- 'adds',
62
- 'adding',
63
- 'create',
64
- 'creates',
65
- 'creating',
66
- 'new file',
67
- 'new module',
68
- 'new helper',
69
- 'to be created',
70
- 'will be created',
71
- 'net-new',
72
- 'scaffold',
73
- 'scaffolds',
74
- 'scaffolding',
75
- ];
76
-
77
- const AMBIGUITY_WINDOW = 80;
78
-
79
- /**
80
- * Check whether the prose surrounding `index` carries one of the
81
- * net-new cue phrases. Looks both before and after the match within
82
- * `AMBIGUITY_WINDOW` characters because authors phrase the cue either
83
- * way: "introduce src/x.ts" *or* "src/x.ts (new helper)".
84
- *
85
- * Case-insensitive substring match — deliberately not regex/word-boundary
86
- * so future cue variants don't silently slip the gate.
87
- *
88
- * @param {string} body
89
- * @param {number} index
90
- * @param {number} matchLength
91
- * @returns {boolean}
92
- */
93
- export function hasNewFileCue(body, index, matchLength) {
94
- const start = Math.max(0, index - AMBIGUITY_WINDOW);
95
- const end = Math.min(body.length, index + matchLength + AMBIGUITY_WINDOW);
96
- const window = body.slice(start, end).toLowerCase();
97
- for (const cue of NEW_FILE_CUES) {
98
- if (window.includes(cue)) return true;
99
- }
100
- return false;
101
- }
102
-
103
- /**
104
- * Collect every `(path, index, matchLength)` triple from `body` using the
105
- * three path-shape regexes. The same path can surface at multiple indices —
106
- * each callsite is preserved so the ambiguity check runs against the cue at
107
- * *that* index, not a different one.
108
- *
109
- * @param {string} body
110
- * @returns {Array<{ path: string, index: number, matchLength: number }>}
111
- */
112
- export function collectReferences(body) {
113
- const refs = [];
114
- for (const re of [BACKTICK_PATH_RE, COMMENT_HEADER_PATH_RE, BARE_PATH_RE]) {
115
- re.lastIndex = 0;
116
- let match = re.exec(body);
117
- while (match !== null) {
118
- const captured = match[1];
119
- const captureIndex = match.index + match[0].indexOf(captured);
120
- refs.push({
121
- path: captured,
122
- index: captureIndex,
123
- matchLength: captured.length,
124
- });
125
- match = re.exec(body);
126
- }
127
- }
128
- return refs;
129
- }