Skip to content

Commit fb4002f

Browse files
Merge pull request #16 from Modsofthenation/cursor/ci-fix-year-round-slice-e5b1
Fix desktop CI + concern AI opt-in, faithfulness actions, cycle stats, 1:1 themes
2 parents 6568e8f + 63bd646 commit fb4002f

15 files changed

Lines changed: 598 additions & 51 deletions

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ apps/ui/dist
99
apps/api/dist
1010
apps/desktop/src-tauri/target
1111
apps/desktop/src-tauri/sidecars/
12+
*.tsbuildinfo

‎apps/api/src/faithfulness.test.ts‎

Lines changed: 95 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,117 @@
11
import assert from "node:assert/strict";
22
import { describe, it } from "node:test";
3-
import { mergeLintFindings, runFaithfulnessLint } from "../src/faithfulness.js";
3+
import {
4+
applyFaithfulnessFix,
5+
mergeLintFindings,
6+
runFaithfulnessLint,
7+
} from "../src/faithfulness.js";
8+
9+
/** Golden drafts with known invention patterns — regression set for faithfulness depth. */
10+
const GOLDEN = {
11+
clean: {
12+
text: "Led the checkout migration [ach_1] and coached two engineers through the rollout [fb_2].",
13+
allowlist: ["ach_1", "fb_2"],
14+
expectInvention: false,
15+
},
16+
inventedCite: {
17+
text: "Shipped the migration [ach_real] and invented support [ach_fake].",
18+
allowlist: ["ach_real"],
19+
expectIds: ["unknown_cite_ach_fake"],
20+
},
21+
ungroundedAbsolutes: {
22+
text: [
23+
"They completely transformed the platform architecture across every squad this half.",
24+
"Customer satisfaction improved dramatically while latency dropped for all regions.",
25+
"See [ach_1] for the migration they led.",
26+
].join(" "),
27+
allowlist: ["ach_1"],
28+
expectIds: ["ungrounded_claims"],
29+
},
30+
bareMetric: {
31+
text: "Reduced p99 latency by 40% across checkout.",
32+
allowlist: [] as string[],
33+
expectMetric: "40%",
34+
},
35+
metricWithCite: {
36+
text: "Reduced p99 latency by 40% across checkout [ach_1].",
37+
allowlist: ["ach_1"],
38+
expectInvention: false,
39+
},
40+
};
441

542
describe("faithfulness lint", () => {
643
it("flags unknown citations", () => {
7-
const findings = runFaithfulnessLint("Shipped [ach_real] and [ach_fake].", ["ach_real"]);
44+
const findings = runFaithfulnessLint(GOLDEN.inventedCite.text, GOLDEN.inventedCite.allowlist);
845
assert.ok(findings.some((f) => f.category === "invention" && /ach_fake/.test(f.message)));
46+
assert.equal(findings.find((f) => f.id.startsWith("unknown_cite_"))?.suggestedFix?.action, "strip_cite");
947
});
1048

1149
it("flags ungrounded long sentences", () => {
12-
const text = [
13-
"They completely transformed the platform architecture across every squad this half.",
14-
"Customer satisfaction improved dramatically while latency dropped for all regions.",
15-
"See [ach_1] for the migration they led.",
16-
].join(" ");
17-
const findings = runFaithfulnessLint(text, ["ach_1"]);
50+
const findings = runFaithfulnessLint(
51+
GOLDEN.ungroundedAbsolutes.text,
52+
GOLDEN.ungroundedAbsolutes.allowlist,
53+
);
1854
assert.ok(findings.some((f) => f.id === "ungrounded_claims"));
55+
assert.ok(findings.find((f) => f.id === "ungrounded_claims")?.suggestedFix);
1956
});
2057

2158
it("flags bare metrics", () => {
22-
const findings = runFaithfulnessLint("Reduced p99 latency by 40% across checkout.", []);
59+
const findings = runFaithfulnessLint(GOLDEN.bareMetric.text, GOLDEN.bareMetric.allowlist);
2360
assert.ok(findings.some((f) => f.category === "invention" && /40%/.test(f.message)));
2461
});
2562

63+
it("accepts clean cited prose", () => {
64+
const findings = runFaithfulnessLint(GOLDEN.clean.text, GOLDEN.clean.allowlist);
65+
assert.equal(
66+
findings.filter((f) => f.category === "invention" && f.severity === "warn").length,
67+
0,
68+
);
69+
});
70+
71+
it("does not flag metrics next to a citation", () => {
72+
const findings = runFaithfulnessLint(GOLDEN.metricWithCite.text, GOLDEN.metricWithCite.allowlist);
73+
assert.ok(!findings.some((f) => f.id.startsWith("metric_")));
74+
});
75+
2676
it("merges findings without dupes", () => {
2777
const a = runFaithfulnessLint("Reduced cost by 25% somehow.", []);
2878
const merged = mergeLintFindings(a, a);
2979
assert.equal(merged.length, a.length);
3080
});
81+
82+
it("applies soften and cite fixes", () => {
83+
const findings = runFaithfulnessLint(GOLDEN.bareMetric.text, ["ach_1"]);
84+
const metric = findings.find((f) => f.id.startsWith("metric_"));
85+
assert.ok(metric?.suggestedFix);
86+
const next = applyFaithfulnessFix(GOLDEN.bareMetric.text, metric!);
87+
assert.ok(next);
88+
assert.match(next!, /\[ach_1\]/);
89+
});
90+
91+
it("strips unknown cites via suggested fix", () => {
92+
const findings = runFaithfulnessLint(GOLDEN.inventedCite.text, GOLDEN.inventedCite.allowlist);
93+
const bad = findings.find((f) => f.id === "unknown_cite_ach_fake");
94+
assert.ok(bad);
95+
const next = applyFaithfulnessFix(GOLDEN.inventedCite.text, bad!);
96+
assert.ok(next);
97+
assert.ok(!next!.includes("[ach_fake]"));
98+
assert.ok(next!.includes("[ach_real]"));
99+
});
100+
});
101+
102+
describe("faithfulness golden eval set", () => {
103+
it("scores known invention drafts", () => {
104+
const cases = [
105+
GOLDEN.inventedCite,
106+
GOLDEN.ungroundedAbsolutes,
107+
GOLDEN.bareMetric,
108+
];
109+
for (const c of cases) {
110+
const findings = runFaithfulnessLint(c.text, c.allowlist);
111+
assert.ok(
112+
findings.some((f) => f.category === "invention"),
113+
`expected invention in: ${c.text.slice(0, 60)}`,
114+
);
115+
}
116+
});
31117
});

‎apps/api/src/faithfulness.ts‎

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,28 @@ import { extractCitationIds } from "./citations.js";
33

44
const SENTENCE_SPLIT = /(?<=[.!?])\s+(?=[A-Z("[])|(?:\n+)/;
55

6+
const ABSOLUTE_WORDS =
7+
/\b(always|never|completely|entirely|dramatically|everyone|no one|all regions|every squad)\b/gi;
8+
9+
function softenText(excerpt: string): string {
10+
return excerpt
11+
.replace(/\balways\b/gi, "often")
12+
.replace(/\bnever\b/gi, "rarely")
13+
.replace(/\bcompletely\b/gi, "substantially")
14+
.replace(/\bentirely\b/gi, "largely")
15+
.replace(/\bdramatically\b/gi, "notably")
16+
.replace(/\beveryone\b/gi, "many teammates")
17+
.replace(/\bno one\b/gi, "few people")
18+
.replace(/\ball regions\b/gi, "several regions")
19+
.replace(/\bevery squad\b/gi, "multiple squads")
20+
.replace(/(\d+%)/g, "about $1 (verify)")
21+
.replace(/(\$\d[\d,]*(?:\.\d+)?)/g, "about $1 (verify)");
22+
}
23+
24+
function pickCiteId(allowlist: string[]): string | undefined {
25+
return allowlist.find((id) => /^(ach|fb|goal|doc|rexp)_/.test(id));
26+
}
27+
628
/** Claims that look substantive but lack any [id] citation nearby. */
729
export function runFaithfulnessLint(text: string, allowlist: string[] = []): BiasFinding[] {
830
const findings: BiasFinding[] = [];
@@ -19,6 +41,11 @@ export function runFaithfulnessLint(text: string, allowlist: string[] = []): Bia
1941
category: "invention",
2042
message: `Citation [${id}] is not in the evidence allowlist — remove or replace with a real evidence id.`,
2143
excerpt: `[${id}]`,
44+
suggestedFix: {
45+
action: "strip_cite",
46+
label: "Remove unknown cite",
47+
newText: "",
48+
},
2249
});
2350
}
2451
}
@@ -33,9 +60,7 @@ export function runFaithfulnessLint(text: string, allowlist: string[] = []): Bia
3360
for (const sentence of sentences) {
3461
if (/^(summary|strengths|growth|overall|note|private)\b/i.test(sentence)) continue;
3562
if (!/\[[a-z]+_[A-Za-z0-9_-]+\]/.test(sentence)) {
36-
// Skip questions / instructions
3763
if (/\?$/.test(sentence) || /^(please|consider|remember)\b/i.test(sentence)) continue;
38-
// Skip name-ish lists (mostly Title Case tokens, almost no verbs/stopwords)
3964
const words = sentence.split(/\s+/).filter(Boolean);
4065
const titleish = words.filter((w) => /^[A-Z][a-z]+/.test(w.replace(/[.,]/g, ""))).length;
4166
if (words.length >= 3 && titleish / words.length >= 0.7) continue;
@@ -45,33 +70,83 @@ export function runFaithfulnessLint(text: string, allowlist: string[] = []): Bia
4570
}
4671

4772
if (ungrounded >= 2) {
73+
const sample = samples[0];
74+
const citeId = pickCiteId(allowlist);
75+
const softened = sample ? softenText(sample) : undefined;
4876
findings.push({
4977
id: "ungrounded_claims",
5078
severity: "warn",
5179
category: "invention",
5280
message: `${ungrounded} long sentence(s) lack evidence citations. Prefer claims tied to [ach_…]/[fb_…]/[goal_…] ids.`,
53-
excerpt: samples[0],
81+
excerpt: sample,
82+
suggestedFix: citeId
83+
? {
84+
action: "cite",
85+
label: `Cite [${citeId}]`,
86+
citeId,
87+
newText: sample ? `${sample.replace(/\.*$/, "")} [${citeId}].` : undefined,
88+
}
89+
: softened && sample
90+
? {
91+
action: "soften",
92+
label: "Soften absolute language",
93+
newText: softened,
94+
}
95+
: undefined,
5496
});
5597
}
5698

57-
// Numeric claims without citations are high invention risk (% is non-word — no trailing \b)
5899
const metricClaims = [
59100
...body.matchAll(/(\d+%|\$\d[\d,]*(?:\.\d+)?|\b\d{2,}\s*(?:users|PRs|tickets|customers)\b)/gi),
60101
];
61102
for (const m of metricClaims.slice(0, 5)) {
62103
const idx = m.index ?? 0;
63104
const window = body.slice(Math.max(0, idx - 80), idx + 80);
64105
if (!/\[[a-z]+_[A-Za-z0-9_-]+\]/.test(window)) {
106+
const citeId = pickCiteId(allowlist);
107+
const excerpt = m[0];
65108
findings.push({
66109
id: `metric_${idx}`,
67110
severity: "warn",
68111
category: "invention",
69-
message: `Metric “${m[0]}” appears without a nearby evidence citation — verify or cite the source.`,
70-
excerpt: m[0],
112+
message: `Metric “${excerpt}” appears without a nearby evidence citation — verify or cite the source.`,
113+
excerpt,
114+
suggestedFix: citeId
115+
? {
116+
action: "cite",
117+
label: `Cite [${citeId}] near metric`,
118+
citeId,
119+
newText: `${excerpt} [${citeId}]`,
120+
}
121+
: {
122+
action: "soften",
123+
label: "Soften / qualify metric",
124+
newText: softenText(excerpt),
125+
},
71126
});
72127
}
73128
}
74129

130+
// Absolute phrasing without cites
131+
for (const m of body.matchAll(ABSOLUTE_WORDS)) {
132+
const idx = m.index ?? 0;
133+
const window = body.slice(Math.max(0, idx - 40), idx + (m[0]?.length ?? 0) + 40);
134+
if (/\[[a-z]+_[A-Za-z0-9_-]+\]/.test(window)) continue;
135+
const word = m[0];
136+
findings.push({
137+
id: `absolute_${idx}`,
138+
severity: "info",
139+
category: "invention",
140+
message: `Absolute wording “${word}” is easy to overclaim without evidence.`,
141+
excerpt: word,
142+
suggestedFix: {
143+
action: "soften",
144+
label: "Soften wording",
145+
newText: softenText(word),
146+
},
147+
});
148+
}
149+
75150
return findings;
76151
}
77152

@@ -88,3 +163,20 @@ export function mergeLintFindings(...groups: BiasFinding[][]): BiasFinding[] {
88163
}
89164
return out;
90165
}
166+
167+
/** Apply a suggested faithfulness fix to draft text. Returns null if excerpt not found. */
168+
export function applyFaithfulnessFix(
169+
text: string,
170+
finding: Pick<BiasFinding, "excerpt" | "suggestedFix">,
171+
): string | null {
172+
const fix = finding.suggestedFix;
173+
const excerpt = finding.excerpt;
174+
if (!fix || !excerpt) return null;
175+
if (fix.action === "strip_cite") {
176+
if (!text.includes(excerpt)) return null;
177+
return text.replace(excerpt, "").replace(/ +/g, " ").replace(/ \./g, ".");
178+
}
179+
if (!fix.newText) return null;
180+
if (!text.includes(excerpt)) return null;
181+
return text.replace(excerpt, fix.newText);
182+
}

0 commit comments

Comments
 (0)