@dzhechkov/harness-core 0.3.106 → 0.3.107

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.
@@ -21,11 +21,15 @@ export interface HookDecision {
21
21
  */
22
22
  export declare const ESCAPE_TEACHING: string;
23
23
  /**
24
- * Is the 1-based `line` inside a fenced ``` ``` ``` code block within `text`?
25
- * Counts fence toggles strictly BEFORE the line; an odd count ⇒ the line sits inside a fence.
26
- * Never throws a non-string / out-of-range line returns `false` (treated as not-fenced).
24
+ * Is the 1-based `line` inside a fenced code block within `text`?
25
+ *
26
+ * MOVED to `claim-check.ts` and re-exported here. The engine skips fenced lines and this hook exempts
27
+ * them from its deny path — if the two ever computed "inside a fence" differently, the hook would deny
28
+ * a line the engine had already dismissed, or vice versa. Two implementations WILL drift; one cannot.
29
+ * The engine owns it because the engine is the pure module with no dependents.
27
30
  */
28
- export declare function isFenced(text: string, line: number): boolean;
31
+ import { isFenced } from './claim-check.js';
32
+ export { isFenced };
29
33
  /**
30
34
  * Is `excerpt` a NEW claim line (guard d)? True when there is no pre-image (`oldString` absent/empty —
31
35
  * e.g. a `Write`) or the pre-image does not already contain the excerpt. A trailing clip ellipsis
@@ -1 +1 @@
1
- {"version":3,"file":"claim-check-hook-policy.d.ts","sourceRoot":"","sources":["../src/claim-check-hook-policy.ts"],"names":[],"mappings":"AA2BA,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,mGAAmG;IACnG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IAClC,sGAAsG;IACtG,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,4GAA4G;IAC5G,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAIiF,CAAC;AAuB9G;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAgB5D;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAOtE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,YAAY,CAoDhF"}
1
+ {"version":3,"file":"claim-check-hook-policy.d.ts","sourceRoot":"","sources":["../src/claim-check-hook-policy.ts"],"names":[],"mappings":"AA2BA,wCAAwC;AACxC,MAAM,WAAW,gBAAgB;IAC/B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,mGAAmG;IACnG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,oGAAoG;IACpG,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC;IAClC,sGAAsG;IACtG,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,4GAA4G;IAC5G,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED;;;;GAIG;AACH,eAAO,MAAM,eAAe,QAIiF,CAAC;AAuB9G;;;;;;;GAOG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,CAAC;AAEpB;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAOtE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,YAAY,CAoDhF"}
@@ -53,31 +53,15 @@ function formatFinding(f) {
53
53
  return ' - L' + f.line + ': "' + f.excerpt + '" — ' + f.reason + ' -> ' + f.suggestion;
54
54
  }
55
55
  /**
56
- * Is the 1-based `line` inside a fenced ``` ``` ``` code block within `text`?
57
- * Counts fence toggles strictly BEFORE the line; an odd count ⇒ the line sits inside a fence.
58
- * Never throws a non-string / out-of-range line returns `false` (treated as not-fenced).
56
+ * Is the 1-based `line` inside a fenced code block within `text`?
57
+ *
58
+ * MOVED to `claim-check.ts` and re-exported here. The engine skips fenced lines and this hook exempts
59
+ * them from its deny path — if the two ever computed "inside a fence" differently, the hook would deny
60
+ * a line the engine had already dismissed, or vice versa. Two implementations WILL drift; one cannot.
61
+ * The engine owns it because the engine is the pure module with no dependents.
59
62
  */
60
- export function isFenced(text, line) {
61
- if (typeof text !== 'string' || typeof line !== 'number' || !isFinite(line) || line < 1)
62
- return false;
63
- const lines = text.split(/\r?\n/);
64
- const upTo = Math.min(line - 1, lines.length);
65
- // CommonMark allows BOTH ``` and ~~~ fences, and a fence is closed only by its OWN marker — a
66
- // ``` inside a ~~~ block is literal content, not a toggle. A naive toggle counter that accepts
67
- // either marker therefore mis-tracks nesting; track the open marker instead.
68
- let open = null;
69
- for (let i = 0; i < upTo; i++) {
70
- const m = /^\s*(`{3,}|~{3,})/.exec(lines[i] || '');
71
- if (!m)
72
- continue;
73
- const marker = m[1][0];
74
- if (open === null)
75
- open = marker;
76
- else if (open === marker)
77
- open = null;
78
- }
79
- return open !== null;
80
- }
63
+ import { isFenced } from './claim-check.js';
64
+ export { isFenced };
81
65
  /**
82
66
  * Is `excerpt` a NEW claim line (guard d)? True when there is no pre-image (`oldString` absent/empty —
83
67
  * e.g. a `Write`) or the pre-image does not already contain the excerpt. A trailing clip ellipsis
@@ -1 +1 @@
1
- {"version":3,"file":"claim-check-hook-policy.js","sourceRoot":"","sources":["../src/claim-check-hook-policy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claim-check-hook-policy.js","sourceRoot":"","sources":["../src/claim-check-hook-policy.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,17 @@ export interface ClaimCheckResult {
11
11
  readonly ok: boolean;
12
12
  readonly findings: ClaimFinding[];
13
13
  }
14
+ /**
15
+ * Is the 1-based `line` inside a fenced code block within `text`?
16
+ *
17
+ * OWNED HERE, re-exported by `claim-check-hook-policy.ts`. The engine skips fenced lines; the hook
18
+ * exempts them from its deny path. Two implementations of "inside a fence" would drift; one cannot.
19
+ *
20
+ * CommonMark allows BOTH ``` and ~~~ fences, and a fence closes only on its OWN marker — a ``` inside
21
+ * a ~~~ block is literal content, not a toggle. A naive toggle counter that accepts either marker
22
+ * mis-tracks nesting, so track the open marker instead. Never throws.
23
+ */
24
+ export declare function isFenced(text: string, line: number): boolean;
14
25
  /**
15
26
  * Lint a block of text for untagged or overstated accuracy claims.
16
27
  * Pure and never-throws: a non-string or empty input returns `{ ok: true, findings: [] }`.
@@ -1 +1 @@
1
- {"version":3,"file":"claim-check.d.ts","sourceRoot":"","sources":["../src/claim-check.ts"],"names":[],"mappings":"AAkBA,oGAAoG;AACpG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,2DAA2D;AAC3D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;CACnC;AAuFD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CA6EzD;AAMD,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAI1D"}
1
+ {"version":3,"file":"claim-check.d.ts","sourceRoot":"","sources":["../src/claim-check.ts"],"names":[],"mappings":"AAkBA,oGAAoG;AACpG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,CAAC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,2DAA2D;AAC3D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,QAAQ,EAAE,YAAY,EAAE,CAAC;CACnC;AA4FD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAa5D;AA4CD;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,CA6FzD;AAMD,4DAA4D;AAC5D,wBAAgB,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAI1D"}
@@ -18,7 +18,9 @@
18
18
  /** Phrases that signal a quantitative accuracy claim (safe as substrings). */
19
19
  const METRIC_TERMS = [
20
20
  // Generic accuracy vocabulary kept from ruview.
21
- 'accuracy', 'precision', 'recall',
21
+ // `recall` is NOT here — it is this repo's command name. It re-enters via RECALL_METRIC_RE, which
22
+ // demands a scoring context. `precision` stays: it has no command collision.
23
+ 'accuracy', 'precision',
22
24
  'error rate', 'detection rate', 'true positive',
23
25
  // dz claim vocabulary — a harness monorepo claims coverage, test counts, catalogue sizes,
24
26
  // and performance numbers, not WiFi-sensing PCK/MPJPE.
@@ -68,18 +70,89 @@ function scrubLine(lower) {
68
70
  function mentionsMetricTerm(lower, scrubbed) {
69
71
  if (METRIC_TERMS.some((t) => lower.includes(t)))
70
72
  return true;
73
+ // `recall` only in a scoring context (see RECALL_METRIC_RE). `precision` on the line is enough:
74
+ // "precision 0.9 / recall 0.8" is the canonical ML pair.
75
+ if (RECALL_METRIC_RE.test(scrubbed))
76
+ return true;
71
77
  if (!HAS_NUMBER_RE.test(scrubbed))
72
78
  return false;
73
79
  return METRIC_TERMS_SHORT.some((re) => re.test(scrubbed));
74
80
  }
81
+ /**
82
+ * The PARAGRAPH containing 1-based `line`: the maximal run of contiguous non-blank lines, bounded by
83
+ * a blank line, a heading, or a fence marker. The claim is DETECTED per line; its honesty tag and its
84
+ * reproducer may live anywhere in this run — prose wrapped at 100 columns routinely splits a number
85
+ * from its `MEASURED`, and flagging the author who tagged correctly is how a gate teaches its users to
86
+ * ignore it.
87
+ *
88
+ * LAUNDERING EXPOSURE, stated: one tag covers every claim in its paragraph. It cannot cross a blank
89
+ * line, a heading, or a fence.
90
+ */
91
+ function paragraphAround(lines, i) {
92
+ const bounds = (k) => k.trim() === '' || HEADING_RE.test(k) || FENCE_RE.test(k);
93
+ let a = i;
94
+ let b = i;
95
+ while (a > 0 && !bounds(lines[a - 1] ?? ''))
96
+ a--;
97
+ while (b < lines.length - 1 && !bounds(lines[b + 1] ?? ''))
98
+ b++;
99
+ return lines.slice(a, b + 1).join('\n');
100
+ }
75
101
  /** Tags that make a claim honest (case-insensitive). */
76
102
  // `estimated` agrees with the `estimated: true` honest-uncertainty marker `dz usage` already
77
103
  // emits — the two honesty systems must not contradict each other.
78
104
  const HONEST_TAGS = ['measured', 'claimed', 'synthetic', 'unvalidated', 'baseline', 'estimated'];
105
+ /**
106
+ * Is the 1-based `line` inside a fenced code block within `text`?
107
+ *
108
+ * OWNED HERE, re-exported by `claim-check-hook-policy.ts`. The engine skips fenced lines; the hook
109
+ * exempts them from its deny path. Two implementations of "inside a fence" would drift; one cannot.
110
+ *
111
+ * CommonMark allows BOTH ``` and ~~~ fences, and a fence closes only on its OWN marker — a ``` inside
112
+ * a ~~~ block is literal content, not a toggle. A naive toggle counter that accepts either marker
113
+ * mis-tracks nesting, so track the open marker instead. Never throws.
114
+ */
115
+ export function isFenced(text, line) {
116
+ if (typeof text !== 'string' || typeof line !== 'number' || !isFinite(line) || line < 1)
117
+ return false;
118
+ const lines = text.split(/\r?\n/);
119
+ const upTo = Math.min(line - 1, lines.length);
120
+ let open = null;
121
+ for (let i = 0; i < upTo; i++) {
122
+ const m = FENCE_RE.exec(lines[i] || '');
123
+ if (!m)
124
+ continue;
125
+ const marker = m[1][0];
126
+ if (open === null)
127
+ open = marker;
128
+ else if (open === marker)
129
+ open = null;
130
+ }
131
+ return open !== null;
132
+ }
133
+ const FENCE_RE = /^\s*(`{3,}|~{3,})/;
134
+ const HEADING_RE = /^\s{0,3}#{1,6}\s/;
135
+ /**
136
+ * `recall` is ruview's completeness metric AND this repo's command name (`dz recall`). Matching it as
137
+ * a bare term produced 31 false positives (MEASURED — reproducer `dz claim-check --json`), every one
138
+ * of them prose about the command. It counts as a metric only in a scoring context: paired with
139
+ * `precision`, or immediately followed by a score (`recall@5`, `recall rate`, `recall of 0.9`,
140
+ * `recall = 0.9`, `recall score`).
141
+ */
142
+ const RECALL_METRIC_RE = /\brecall\s*(?:@\s*\d|rate\b|score\b|of\s+[\d.]|[:=]\s*[\d.])/i;
143
+ /**
144
+ * A shell reproducer is STRUCTURAL, never a word. `(MEASURED — reproducer)` is self-certifying and
145
+ * must not pass; a backticked span whose first token is a command this repo actually measures with is
146
+ * evidence. The allowlist boundary is exactly that: an unknown binary is a claim ABOUT evidence.
147
+ */
148
+ const SHELL_REPRO_RE = /`\s*\$?\s*(?:ps|stat|lsof|time|git|npm|npx|node|pnpm|yarn|dz|curl|wc|grep|find|cargo|make|docker|kubectl|awk|sed|du|df|vitest|pytest)\b[^`]*`/i;
79
149
  /** Reproducer references that count as evidence backing a MEASURED claim. */
80
150
  const REPRODUCER_HINTS = [
81
151
  // Generic evidence hints kept from ruview.
82
- 'baseline', 'reproduce', 'sha256', 'tarball', 'cargo test',
152
+ // 'reproduce' was inherited from ruview and is SELF-CERTIFYING: it makes the bare word "reproducer"
153
+ // count as its own evidence, so `(MEASURED — reproducer)` passed. Removed (D5). Evidence must be a
154
+ // named command or artifact — see SHELL_REPRO_RE for the structural form.
155
+ 'baseline', 'sha256', 'tarball', 'cargo test',
83
156
  // Packaging-claim reproducers (npm reviews): the tarball itself.
84
157
  'npm pack', 'npm view', 'npm i ', 'npm install',
85
158
  // dz reproducers — the actual commands/artifacts that back a dz claim.
@@ -107,14 +180,26 @@ export function claimCheck(text) {
107
180
  const original = raw.trim();
108
181
  if (!original)
109
182
  return;
183
+ // A fenced block is CODE, not prose. The hook already exempts fenced lines from its deny path
184
+ // (it re-exports `isFenced` from here); the engine flagging them was not a policy, it was a bug —
185
+ // 127 of this repo's 488 findings lived inside fences, and NONE of them was `high`
186
+ // (MEASURED — reproducer `dz claim-check --json`).
187
+ // EXPOSURE, accepted: a claim written inside a fence is now unreachable. That is exactly the right
188
+ // the inline backtick escape already grants; a fence is its block-level form.
189
+ if (isFenced(text, i + 1))
190
+ return;
110
191
  // Analyse the URL-stripped line, but report the ORIGINAL so the excerpt stays recognisable.
111
192
  const line = stripUrls(original);
112
193
  if (!line.trim())
113
194
  return;
114
195
  const lower = line.toLowerCase();
115
- const hasPercent = PERCENT_RE.test(line);
116
- PERCENT_RE.lastIndex = 0; // reset stateful global regex
117
196
  const scrubbed = scrubLine(lower);
197
+ // The percent TRIGGER reads the code-span-scrubbed line. Before this, `hasPercent` was computed on
198
+ // the UN-scrubbed line while metric terms were matched on the scrubbed one, so a backticked `99%`
199
+ // still opened the scan: the backtick escape downgraded a quoted claim from `high` to `medium`
200
+ // instead of silencing it. Documentation that quotes a forbidden claim was punished for doing so.
201
+ const hasPercent = PERCENT_RE.test(scrubbed);
202
+ PERCENT_RE.lastIndex = 0; // reset stateful global regex
118
203
  // DELIBERATE DIVERGENCE from ruview, which matched METRIC_TERMS against the UNSCRUBBED line.
119
204
  // dz's vocabulary contains path-like words ('packages', 'commands', 'skills'), so a code span
120
205
  // such as `packages/x/usage.test.ts:42` registered as a metric mention and produced a false
@@ -124,8 +209,12 @@ export function claimCheck(text) {
124
209
  const mentionsMetric = mentionsMetricTerm(scrubbed, scrubbed);
125
210
  if (!hasPercent && !mentionsMetric)
126
211
  return;
127
- const tagged = HONEST_TAGS.some((t) => lower.includes(t));
128
- const hasReproducer = REPRODUCER_HINTS.some((h) => lower.includes(h));
212
+ // The claim is detected on its own line; its honesty tag and reproducer may live anywhere in the
213
+ // same paragraph. Wrapped prose routinely puts `MEASURED` on the next line.
214
+ const para = stripUrls(paragraphAround(lines, i));
215
+ const paraLower = para.toLowerCase();
216
+ const tagged = HONEST_TAGS.some((t) => paraLower.includes(t));
217
+ const hasReproducer = REPRODUCER_HINTS.some((h) => paraLower.includes(h)) || SHELL_REPRO_RE.test(para);
129
218
  const perfect = PERFECT_WORD_RE.test(line) || (mentionsMetric && PERFECT_PCT_RE.test(line));
130
219
  if (perfect && !lower.includes('retract')) {
131
220
  findings.push({
@@ -1 +1 @@
1
- {"version":3,"file":"claim-check.js","sourceRoot":"","sources":["../src/claim-check.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"claim-check.js","sourceRoot":"","sources":["../src/claim-check.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dzhechkov/harness-core",
3
- "version": "0.3.106",
3
+ "version": "0.3.107",
4
4
  "description": "Shared harness logic - skill loading, additive apply, and the init/sync/verify/doctor operations.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -31,11 +31,11 @@
31
31
  "@dzhechkov/adapter-opencode": "^0.2.0",
32
32
  "yaml": "^2.0.0",
33
33
  "@dzhechkov/adapter-agents-md": "0.1.1",
34
- "@dzhechkov/adapter-cursor": "0.1.1",
35
34
  "@dzhechkov/adapter-copilot": "0.1.1",
35
+ "@dzhechkov/adapter-cursor": "0.1.1",
36
36
  "@dzhechkov/adapter-gemini": "0.1.1",
37
- "@dzhechkov/core": "0.2.14",
38
37
  "@dzhechkov/adapter-windsurf": "0.1.1",
38
+ "@dzhechkov/core": "0.2.14",
39
39
  "@dzhechkov/memory": "0.2.9"
40
40
  },
41
41
  "peerDependenciesMeta": {
@@ -76,27 +76,15 @@ function formatFinding(f: ClaimFinding): string {
76
76
  }
77
77
 
78
78
  /**
79
- * Is the 1-based `line` inside a fenced ``` ``` ``` code block within `text`?
80
- * Counts fence toggles strictly BEFORE the line; an odd count ⇒ the line sits inside a fence.
81
- * Never throws a non-string / out-of-range line returns `false` (treated as not-fenced).
79
+ * Is the 1-based `line` inside a fenced code block within `text`?
80
+ *
81
+ * MOVED to `claim-check.ts` and re-exported here. The engine skips fenced lines and this hook exempts
82
+ * them from its deny path — if the two ever computed "inside a fence" differently, the hook would deny
83
+ * a line the engine had already dismissed, or vice versa. Two implementations WILL drift; one cannot.
84
+ * The engine owns it because the engine is the pure module with no dependents.
82
85
  */
83
- export function isFenced(text: string, line: number): boolean {
84
- if (typeof text !== 'string' || typeof line !== 'number' || !isFinite(line) || line < 1) return false;
85
- const lines = text.split(/\r?\n/);
86
- const upTo = Math.min(line - 1, lines.length);
87
- // CommonMark allows BOTH ``` and ~~~ fences, and a fence is closed only by its OWN marker — a
88
- // ``` inside a ~~~ block is literal content, not a toggle. A naive toggle counter that accepts
89
- // either marker therefore mis-tracks nesting; track the open marker instead.
90
- let open: '`' | '~' | null = null;
91
- for (let i = 0; i < upTo; i++) {
92
- const m = /^\s*(`{3,}|~{3,})/.exec(lines[i] || '');
93
- if (!m) continue;
94
- const marker = m[1]![0] as '`' | '~';
95
- if (open === null) open = marker;
96
- else if (open === marker) open = null;
97
- }
98
- return open !== null;
99
- }
86
+ import { isFenced } from './claim-check.js';
87
+ export { isFenced };
100
88
 
101
89
  /**
102
90
  * Is `excerpt` a NEW claim line (guard d)? True when there is no pre-image (`oldString` absent/empty —
@@ -34,7 +34,9 @@ export interface ClaimCheckResult {
34
34
  /** Phrases that signal a quantitative accuracy claim (safe as substrings). */
35
35
  const METRIC_TERMS = [
36
36
  // Generic accuracy vocabulary kept from ruview.
37
- 'accuracy', 'precision', 'recall',
37
+ // `recall` is NOT here — it is this repo's command name. It re-enters via RECALL_METRIC_RE, which
38
+ // demands a scoring context. `precision` stays: it has no command collision.
39
+ 'accuracy', 'precision',
38
40
  'error rate', 'detection rate', 'true positive',
39
41
  // dz claim vocabulary — a harness monorepo claims coverage, test counts, catalogue sizes,
40
42
  // and performance numbers, not WiFi-sensing PCK/MPJPE.
@@ -88,19 +90,89 @@ function scrubLine(lower: string): string {
88
90
 
89
91
  function mentionsMetricTerm(lower: string, scrubbed: string): boolean {
90
92
  if (METRIC_TERMS.some((t) => lower.includes(t))) return true;
93
+ // `recall` only in a scoring context (see RECALL_METRIC_RE). `precision` on the line is enough:
94
+ // "precision 0.9 / recall 0.8" is the canonical ML pair.
95
+ if (RECALL_METRIC_RE.test(scrubbed)) return true;
91
96
  if (!HAS_NUMBER_RE.test(scrubbed)) return false;
92
97
  return METRIC_TERMS_SHORT.some((re) => re.test(scrubbed));
93
98
  }
94
99
 
100
+ /**
101
+ * The PARAGRAPH containing 1-based `line`: the maximal run of contiguous non-blank lines, bounded by
102
+ * a blank line, a heading, or a fence marker. The claim is DETECTED per line; its honesty tag and its
103
+ * reproducer may live anywhere in this run — prose wrapped at 100 columns routinely splits a number
104
+ * from its `MEASURED`, and flagging the author who tagged correctly is how a gate teaches its users to
105
+ * ignore it.
106
+ *
107
+ * LAUNDERING EXPOSURE, stated: one tag covers every claim in its paragraph. It cannot cross a blank
108
+ * line, a heading, or a fence.
109
+ */
110
+ function paragraphAround(lines: readonly string[], i: number): string {
111
+ const bounds = (k: string): boolean => k.trim() === '' || HEADING_RE.test(k) || FENCE_RE.test(k);
112
+ let a = i;
113
+ let b = i;
114
+ while (a > 0 && !bounds(lines[a - 1] ?? '')) a--;
115
+ while (b < lines.length - 1 && !bounds(lines[b + 1] ?? '')) b++;
116
+ return lines.slice(a, b + 1).join('\n');
117
+ }
118
+
95
119
  /** Tags that make a claim honest (case-insensitive). */
96
120
  // `estimated` agrees with the `estimated: true` honest-uncertainty marker `dz usage` already
97
121
  // emits — the two honesty systems must not contradict each other.
98
122
  const HONEST_TAGS = ['measured', 'claimed', 'synthetic', 'unvalidated', 'baseline', 'estimated'];
99
123
 
124
+ /**
125
+ * Is the 1-based `line` inside a fenced code block within `text`?
126
+ *
127
+ * OWNED HERE, re-exported by `claim-check-hook-policy.ts`. The engine skips fenced lines; the hook
128
+ * exempts them from its deny path. Two implementations of "inside a fence" would drift; one cannot.
129
+ *
130
+ * CommonMark allows BOTH ``` and ~~~ fences, and a fence closes only on its OWN marker — a ``` inside
131
+ * a ~~~ block is literal content, not a toggle. A naive toggle counter that accepts either marker
132
+ * mis-tracks nesting, so track the open marker instead. Never throws.
133
+ */
134
+ export function isFenced(text: string, line: number): boolean {
135
+ if (typeof text !== 'string' || typeof line !== 'number' || !isFinite(line) || line < 1) return false;
136
+ const lines = text.split(/\r?\n/);
137
+ const upTo = Math.min(line - 1, lines.length);
138
+ let open: '`' | '~' | null = null;
139
+ for (let i = 0; i < upTo; i++) {
140
+ const m = FENCE_RE.exec(lines[i] || '');
141
+ if (!m) continue;
142
+ const marker = m[1]![0] as '`' | '~';
143
+ if (open === null) open = marker;
144
+ else if (open === marker) open = null;
145
+ }
146
+ return open !== null;
147
+ }
148
+
149
+ const FENCE_RE = /^\s*(`{3,}|~{3,})/;
150
+ const HEADING_RE = /^\s{0,3}#{1,6}\s/;
151
+
152
+ /**
153
+ * `recall` is ruview's completeness metric AND this repo's command name (`dz recall`). Matching it as
154
+ * a bare term produced 31 false positives (MEASURED — reproducer `dz claim-check --json`), every one
155
+ * of them prose about the command. It counts as a metric only in a scoring context: paired with
156
+ * `precision`, or immediately followed by a score (`recall@5`, `recall rate`, `recall of 0.9`,
157
+ * `recall = 0.9`, `recall score`).
158
+ */
159
+ const RECALL_METRIC_RE = /\brecall\s*(?:@\s*\d|rate\b|score\b|of\s+[\d.]|[:=]\s*[\d.])/i;
160
+
161
+ /**
162
+ * A shell reproducer is STRUCTURAL, never a word. `(MEASURED — reproducer)` is self-certifying and
163
+ * must not pass; a backticked span whose first token is a command this repo actually measures with is
164
+ * evidence. The allowlist boundary is exactly that: an unknown binary is a claim ABOUT evidence.
165
+ */
166
+ const SHELL_REPRO_RE =
167
+ /`\s*\$?\s*(?:ps|stat|lsof|time|git|npm|npx|node|pnpm|yarn|dz|curl|wc|grep|find|cargo|make|docker|kubectl|awk|sed|du|df|vitest|pytest)\b[^`]*`/i;
168
+
100
169
  /** Reproducer references that count as evidence backing a MEASURED claim. */
101
170
  const REPRODUCER_HINTS = [
102
171
  // Generic evidence hints kept from ruview.
103
- 'baseline', 'reproduce', 'sha256', 'tarball', 'cargo test',
172
+ // 'reproduce' was inherited from ruview and is SELF-CERTIFYING: it makes the bare word "reproducer"
173
+ // count as its own evidence, so `(MEASURED — reproducer)` passed. Removed (D5). Evidence must be a
174
+ // named command or artifact — see SHELL_REPRO_RE for the structural form.
175
+ 'baseline', 'sha256', 'tarball', 'cargo test',
104
176
  // Packaging-claim reproducers (npm reviews): the tarball itself.
105
177
  'npm pack', 'npm view', 'npm i ', 'npm install',
106
178
  // dz reproducers — the actual commands/artifacts that back a dz claim.
@@ -130,14 +202,25 @@ export function claimCheck(text: string): ClaimCheckResult {
130
202
  lines.forEach((raw, i) => {
131
203
  const original = raw.trim();
132
204
  if (!original) return;
205
+ // A fenced block is CODE, not prose. The hook already exempts fenced lines from its deny path
206
+ // (it re-exports `isFenced` from here); the engine flagging them was not a policy, it was a bug —
207
+ // 127 of this repo's 488 findings lived inside fences, and NONE of them was `high`
208
+ // (MEASURED — reproducer `dz claim-check --json`).
209
+ // EXPOSURE, accepted: a claim written inside a fence is now unreachable. That is exactly the right
210
+ // the inline backtick escape already grants; a fence is its block-level form.
211
+ if (isFenced(text, i + 1)) return;
133
212
  // Analyse the URL-stripped line, but report the ORIGINAL so the excerpt stays recognisable.
134
213
  const line = stripUrls(original);
135
214
  if (!line.trim()) return;
136
215
  const lower = line.toLowerCase();
137
216
 
138
- const hasPercent = PERCENT_RE.test(line);
139
- PERCENT_RE.lastIndex = 0; // reset stateful global regex
140
217
  const scrubbed = scrubLine(lower);
218
+ // The percent TRIGGER reads the code-span-scrubbed line. Before this, `hasPercent` was computed on
219
+ // the UN-scrubbed line while metric terms were matched on the scrubbed one, so a backticked `99%`
220
+ // still opened the scan: the backtick escape downgraded a quoted claim from `high` to `medium`
221
+ // instead of silencing it. Documentation that quotes a forbidden claim was punished for doing so.
222
+ const hasPercent = PERCENT_RE.test(scrubbed);
223
+ PERCENT_RE.lastIndex = 0; // reset stateful global regex
141
224
  // DELIBERATE DIVERGENCE from ruview, which matched METRIC_TERMS against the UNSCRUBBED line.
142
225
  // dz's vocabulary contains path-like words ('packages', 'commands', 'skills'), so a code span
143
226
  // such as `packages/x/usage.test.ts:42` registered as a metric mention and produced a false
@@ -147,8 +230,13 @@ export function claimCheck(text: string): ClaimCheckResult {
147
230
  const mentionsMetric = mentionsMetricTerm(scrubbed, scrubbed);
148
231
  if (!hasPercent && !mentionsMetric) return;
149
232
 
150
- const tagged = HONEST_TAGS.some((t) => lower.includes(t));
151
- const hasReproducer = REPRODUCER_HINTS.some((h) => lower.includes(h));
233
+ // The claim is detected on its own line; its honesty tag and reproducer may live anywhere in the
234
+ // same paragraph. Wrapped prose routinely puts `MEASURED` on the next line.
235
+ const para = stripUrls(paragraphAround(lines, i));
236
+ const paraLower = para.toLowerCase();
237
+ const tagged = HONEST_TAGS.some((t) => paraLower.includes(t));
238
+ const hasReproducer =
239
+ REPRODUCER_HINTS.some((h) => paraLower.includes(h)) || SHELL_REPRO_RE.test(para);
152
240
  const perfect = PERFECT_WORD_RE.test(line) || (mentionsMetric && PERFECT_PCT_RE.test(line));
153
241
 
154
242
  if (perfect && !lower.includes('retract')) {