@farming-labs/docs 0.2.103 → 0.2.105

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.
Files changed (77) hide show
  1. package/dist/{agent-BLHMoVId.mjs → agent-CaCWThBY.mjs} +4 -4
  2. package/dist/{agent-CFqECAG0.mjs → agent-DCTGj6J7.mjs} +212 -19
  3. package/dist/{agent-evals-GpcaCbRb.mjs → agent-evals-D-sPJfXz.mjs} +5 -5
  4. package/dist/{agent-export-oiWxB5aX.mjs → agent-export-GcTZPLfB.mjs} +30 -17
  5. package/dist/agent-feedback-evaluations-DQCTFegz.mjs +240 -0
  6. package/dist/agent-feedback-loop-vaC-M1sx.mjs +196 -0
  7. package/dist/agent-maintenance-CuZ7iAKq.mjs +289 -0
  8. package/dist/agent-skills-bundle.d.mts +1 -1
  9. package/dist/{agent-skills-progressive-disclosure-Da9LKEKv.mjs → agent-skills-progressive-disclosure-DL-q4JZo.mjs} +1 -1
  10. package/dist/{agent-skills-server-CICMh0zv.d.mts → agent-skills-server-D34054x5.d.mts} +2 -2
  11. package/dist/{agent-skills-server-DFqQyO_V.mjs → agent-skills-server-Ddbf15pY.mjs} +1 -1
  12. package/dist/agent-skills-vite.d.mts +3 -3
  13. package/dist/agent-skills-vite.mjs +2 -2
  14. package/dist/{agents-CtqIUW5I.mjs → agents-e1SYT64z.mjs} +11 -10
  15. package/dist/{analytics-Bx44lg6d.mjs → analytics-CqfcIBdg.mjs} +1 -1
  16. package/dist/{analytics-C75BnTEW.d.mts → analytics-aeXwEvQX.d.mts} +1 -1
  17. package/dist/authoring-Do87QzBd.mjs +41 -0
  18. package/dist/authoring-mcp-Be7quE5R.mjs +380 -0
  19. package/dist/browser.d.mts +3 -3
  20. package/dist/browser.mjs +2 -2
  21. package/dist/cli/index.mjs +103 -35
  22. package/dist/client/react.d.mts +1 -1
  23. package/dist/client/react.mjs +1 -1
  24. package/dist/{cloud-FNfcbfli.mjs → cloud-Bvof6mET.mjs} +3 -3
  25. package/dist/{cloud-ask-ai-BmbZVAT5.d.mts → cloud-ask-ai-BqfiQaNZ.d.mts} +1 -1
  26. package/dist/{cloud-ask-ai-CkCYjf2N.mjs → cloud-ask-ai-CK2qlNns.mjs} +1 -1
  27. package/dist/{code-blocks-Bel6vDqI.mjs → code-blocks-D90CCJFs.mjs} +1 -1
  28. package/dist/{codeblocks-jbWuvtJQ.mjs → codeblocks-C911CdyJ.mjs} +2 -2
  29. package/dist/{agent-lWTq6pgr.d.mts → content-change-hydration-CoYRs3pC.d.mts} +707 -675
  30. package/dist/{dev-BvbYmTLz.mjs → dev-Bx0JNglW.mjs} +3 -3
  31. package/dist/docs-cloud-server.d.mts +2 -2
  32. package/dist/docs-cloud-server.mjs +2 -2
  33. package/dist/{doctor-Bmm0uY98.mjs → doctor-CtK1CRPj.mjs} +20 -16
  34. package/dist/{downgrade-Dqm0rJMy.mjs → downgrade-BXmQMkMy.mjs} +2 -2
  35. package/dist/feedback-DWVUMrxy.mjs +72 -0
  36. package/dist/feedback-evals-k0sFDdms.mjs +146 -0
  37. package/dist/{golden-evaluations-K45fcWGW.mjs → golden-evaluations-De4KwoYr.mjs} +3 -3
  38. package/dist/index.d.mts +236 -9
  39. package/dist/index.mjs +14 -11
  40. package/dist/{init-jDQSo-Or.mjs → init-CCV2eRNB.mjs} +3 -3
  41. package/dist/markdown-rendering.mjs +1 -1
  42. package/dist/{markdown-sections-7OoA7ylx.mjs → markdown-sections-BKy4labo.mjs} +110 -2
  43. package/dist/{mcp-CwgKlbIz.mjs → mcp-DsjWxF7g.mjs} +11 -10
  44. package/dist/mcp.d.mts +20 -5
  45. package/dist/mcp.mjs +538 -56
  46. package/dist/{metadata-D4v_R1t5.mjs → metadata-7htr-Xj_.mjs} +1 -1
  47. package/dist/{content-change-hydration-BsbBXAUi.mjs → openapi-mcp-DQ3xptKo.mjs} +182 -4
  48. package/dist/openapi-mcp-node-vO3IXVGI.mjs +43 -0
  49. package/dist/{package-version--oQiHwZj.mjs → package-version-Ci99L0yo.mjs} +1 -1
  50. package/dist/{prompt-references-DW5vG5zs.mjs → prompt-references-1Wce71c7.mjs} +3 -3
  51. package/dist/propose-DJOiHu9Y.mjs +73 -0
  52. package/dist/retrieval-digest-B-SgtXKK.mjs +177 -0
  53. package/dist/retrieval-digest-DyNRD-mt.d.mts +467 -0
  54. package/dist/{review-C_5wB0rR.mjs → review-XI9FXeos.mjs} +12 -12
  55. package/dist/{robots-NQesPX5L.mjs → robots-BOjWB1ky.mjs} +2 -2
  56. package/dist/{robots-BQ42Y84a.mjs → robots-CzKesbdX.mjs} +6 -6
  57. package/dist/{search-B5etKU-v.mjs → search-DwAoC9X7.mjs} +12 -11
  58. package/dist/server.d.mts +47 -264
  59. package/dist/server.mjs +14 -11
  60. package/dist/{sidebar-CwZlgyAz.d.mts → sidebar-CJze8SBi.d.mts} +1 -1
  61. package/dist/{sitemap-DjhxvQP1.mjs → sitemap-DOaJIKJf.mjs} +11 -10
  62. package/dist/{sitemap-server-B0SiREN0.mjs → sitemap-server-NeVu8W38.mjs} +11 -1
  63. package/dist/{skills-BFang8vF.mjs → skills-BILSESek.mjs} +11 -10
  64. package/dist/{standards-discovery-Ckx0tN7B.mjs → standards-discovery-Bpz90VYq.mjs} +2 -177
  65. package/dist/{standards-discovery-D1Ys4J3h.d.mts → standards-discovery-Xm84bS4M.d.mts} +1 -1
  66. package/dist/{types-DLLefoU5.d.mts → types-QhYMnKvb.d.mts} +148 -2
  67. package/dist/{upgrade-kj2crJSk.mjs → upgrade-B92U83xo.mjs} +2 -2
  68. package/package.json +1 -1
  69. package/dist/retrieval-digest-DYEsIeCE.d.mts +0 -164
  70. /package/dist/{audience-exposure-Cw3SA4WE.d.mts → audience-exposure-CkNZNeVX.d.mts} +0 -0
  71. /package/dist/{cloud-analytics-CSyFE6SS.mjs → cloud-analytics-Ci1yiwD8.mjs} +0 -0
  72. /package/dist/{config-BaTmjDqD.mjs → config-DrZ3fXgf.mjs} +0 -0
  73. /package/dist/{errors-DZiNXv5c.mjs → errors-BDmra6mz.mjs} +0 -0
  74. /package/dist/{i18n-B3BIxShC.mjs → i18n-B1gJ9EW0.mjs} +0 -0
  75. /package/dist/{sidebar-IcpElOVH.mjs → sidebar-3W51s2sF.mjs} +0 -0
  76. /package/dist/{templates-kc7jxspR.mjs → templates-DdB4BBGq.mjs} +0 -0
  77. /package/dist/{utils-DOMeZW9V.mjs → utils-Cc1SrVza.mjs} +0 -0
@@ -0,0 +1,240 @@
1
+ import { i as sha256DocsContent } from "./retrieval-digest-B-SgtXKK.mjs";
2
+
3
+ //#region src/agent-feedback-evaluations.ts
4
+ const DOCS_AGENT_FEEDBACK_EVALUATION_CANDIDATES_FORMAT = "farming-labs-agent-feedback-evaluation-candidates.v1";
5
+ const DOCS_AGENT_FEEDBACK_EVALUATION_BASELINE_FORMAT = "farming-labs-agent-feedback-evaluation-baseline.v1";
6
+ const DOCS_AGENT_FEEDBACK_EVALUATION_REGRESSION_FORMAT = "farming-labs-agent-feedback-evaluation-regression.v1";
7
+ const SECRET_PATTERNS = [
8
+ {
9
+ pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/gu,
10
+ replacement: "[redacted-private-key]"
11
+ },
12
+ {
13
+ pattern: /\b(?:sk-[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,}|xox[baprs]-[A-Za-z0-9-]{12,})\b/gu,
14
+ replacement: "[redacted-token]"
15
+ },
16
+ {
17
+ pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{12,}\b/giu,
18
+ replacement: "Bearer [redacted-token]"
19
+ },
20
+ {
21
+ pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu,
22
+ replacement: "[redacted-email]"
23
+ },
24
+ {
25
+ pattern: /([?&](?:api[_-]?key|password|secret|signature|token)=)[^&#\s]+/giu,
26
+ replacement: (match) => `${match.slice(0, match.indexOf("=") + 1)}[redacted]`
27
+ }
28
+ ];
29
+ function sanitizeText(value, maxLength) {
30
+ if (typeof value !== "string") return { redactions: 0 };
31
+ let output = value.split("").map((character) => {
32
+ const code = character.charCodeAt(0);
33
+ return code <= 31 || code === 127 ? " " : character;
34
+ }).join("").trim().replace(/\s+/g, " ");
35
+ let redactions = 0;
36
+ for (const { pattern, replacement } of SECRET_PATTERNS) {
37
+ pattern.lastIndex = 0;
38
+ output = output.replace(pattern, (match) => {
39
+ redactions += 1;
40
+ return typeof replacement === "function" ? replacement(match) : replacement;
41
+ });
42
+ }
43
+ return output ? {
44
+ value: output.slice(0, maxLength),
45
+ redactions
46
+ } : { redactions };
47
+ }
48
+ function sanitizeStringList(value, maxItems, maxLength) {
49
+ if (!Array.isArray(value)) return {
50
+ values: [],
51
+ redactions: 0
52
+ };
53
+ const values = [];
54
+ let redactions = 0;
55
+ for (const item of value.slice(0, maxItems)) {
56
+ const sanitized = sanitizeText(item, maxLength);
57
+ redactions += sanitized.redactions;
58
+ if (sanitized.value && !values.includes(sanitized.value)) values.push(sanitized.value);
59
+ }
60
+ return {
61
+ values,
62
+ redactions
63
+ };
64
+ }
65
+ function slugify(value) {
66
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 96) || "feedback-evaluation";
67
+ }
68
+ function fingerprintTask(task) {
69
+ const sources = [...task.expect.relevantSources ?? []].sort();
70
+ return `sha256:${sha256DocsContent(JSON.stringify({
71
+ query: task.query.trim().toLowerCase(),
72
+ sources,
73
+ filters: task.filters ?? null
74
+ }))}`;
75
+ }
76
+ function sanitizeClusterTask(cluster) {
77
+ const rawTask = cluster.goldenTask;
78
+ if (!rawTask) return { redactions: 0 };
79
+ const query = sanitizeText(rawTask.query, 500);
80
+ const relevant = sanitizeStringList(rawTask.expect?.relevantSources, 20, 500);
81
+ const citations = sanitizeStringList(rawTask.expect?.requiredCitations, 20, 500);
82
+ const id = sanitizeText(rawTask.id, 120);
83
+ const redactions = query.redactions + relevant.redactions + citations.redactions + id.redactions;
84
+ if (!query.value || relevant.values.length === 0) return { redactions };
85
+ const taskId = slugify(id.value ?? `feedback-${cluster.id}`);
86
+ const topK = typeof rawTask.topK === "number" && Number.isFinite(rawTask.topK) ? Math.min(100, Math.max(1, Math.floor(rawTask.topK))) : 3;
87
+ return {
88
+ redactions,
89
+ task: {
90
+ id: taskId,
91
+ query: query.value,
92
+ tokenBudget: typeof rawTask.tokenBudget === "number" && Number.isFinite(rawTask.tokenBudget) ? Math.min(1e6, Math.max(1, Math.floor(rawTask.tokenBudget))) : 4e3,
93
+ topK,
94
+ expect: {
95
+ relevantSources: relevant.values,
96
+ requiredCitations: citations.values.length > 0 ? citations.values : relevant.values,
97
+ minRecallAtK: 1,
98
+ maxFirstRelevantRank: topK
99
+ }
100
+ }
101
+ };
102
+ }
103
+ function buildDocsAgentFeedbackEvaluationCandidates(report, options = {}) {
104
+ if (report.format !== "farming-labs-agent-feedback-improvements.v1") throw new Error("Input is not a supported agent feedback improvement report.");
105
+ const existingTasks = options.existingTasks ?? [];
106
+ const existingIds = new Set(existingTasks.map((task) => task.id));
107
+ const existingFingerprints = new Set(existingTasks.map(fingerprintTask));
108
+ const candidateFingerprints = /* @__PURE__ */ new Set();
109
+ const candidates = [];
110
+ const duplicates = [];
111
+ let redactionCount = 0;
112
+ for (const cluster of report.clusters) {
113
+ const clusterId = sanitizeText(cluster.id, 200);
114
+ const sanitized = sanitizeClusterTask(cluster);
115
+ redactionCount += clusterId.redactions + sanitized.redactions;
116
+ if (!sanitized.task) continue;
117
+ const fingerprint = fingerprintTask(sanitized.task);
118
+ const reason = existingIds.has(sanitized.task.id) ? "existing-id" : existingFingerprints.has(fingerprint) ? "existing-fingerprint" : candidateFingerprints.has(fingerprint) ? "duplicate-candidate" : void 0;
119
+ if (reason) {
120
+ duplicates.push({
121
+ clusterId: slugify(clusterId.value ?? "feedback"),
122
+ taskId: sanitized.task.id,
123
+ fingerprint,
124
+ reason
125
+ });
126
+ continue;
127
+ }
128
+ candidateFingerprints.add(fingerprint);
129
+ const page = sanitizeText(cluster.page, 500);
130
+ const outcomes = sanitizeStringList(cluster.outcomes, 20, 200);
131
+ redactionCount += page.redactions + outcomes.redactions;
132
+ candidates.push({
133
+ id: `candidate-${slugify(clusterId.value ?? "feedback")}`,
134
+ fingerprint,
135
+ state: "candidate",
136
+ task: sanitized.task,
137
+ evidence: {
138
+ clusterId: slugify(clusterId.value ?? "feedback"),
139
+ page: page.value ?? "(unknown page)",
140
+ occurrences: Math.max(1, Math.floor(cluster.occurrences)),
141
+ outcomes: outcomes.values
142
+ },
143
+ sanitization: { redactionCount: sanitized.redactions + clusterId.redactions + page.redactions + outcomes.redactions },
144
+ promotion: {
145
+ target: "agent.evaluations.tasks",
146
+ requiresHumanReview: true
147
+ }
148
+ });
149
+ }
150
+ candidates.sort((left, right) => left.id.localeCompare(right.id));
151
+ return {
152
+ format: DOCS_AGENT_FEEDBACK_EVALUATION_CANDIDATES_FORMAT,
153
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
154
+ sourceFeedbackCount: report.feedbackCount,
155
+ sourceClusterCount: report.recurringClusterCount,
156
+ candidateCount: candidates.length,
157
+ duplicateCount: duplicates.length,
158
+ redactionCount,
159
+ candidates,
160
+ duplicates,
161
+ promotion: {
162
+ mode: "review-required",
163
+ automaticallyUpdatesConfig: false,
164
+ automaticallyPublishes: false
165
+ }
166
+ };
167
+ }
168
+ function createDocsAgentFeedbackEvaluationBaseline(report, generatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
169
+ if (report.score === null || report.taskCount === 0) throw new Error("Cannot create a baseline from an unmeasured evaluation report.");
170
+ return {
171
+ format: DOCS_AGENT_FEEDBACK_EVALUATION_BASELINE_FORMAT,
172
+ generatedAt,
173
+ score: report.score,
174
+ taskCount: report.taskCount,
175
+ passedTaskCount: report.passedTaskCount,
176
+ tasks: report.tasks.map((task) => ({
177
+ id: task.id,
178
+ passed: task.passed,
179
+ score: task.score
180
+ })).sort((left, right) => left.id.localeCompare(right.id))
181
+ };
182
+ }
183
+ function round(value) {
184
+ return Math.round(value * 100) / 100;
185
+ }
186
+ function compareDocsAgentFeedbackEvaluationBaseline(current, baseline, options = {}) {
187
+ if (current.score === null) throw new Error("Current evaluation report is unmeasured.");
188
+ if (baseline.format !== DOCS_AGENT_FEEDBACK_EVALUATION_BASELINE_FORMAT) throw new Error("Evaluation baseline format is not supported.");
189
+ const requestedScoreDrop = options.maxScoreDrop ?? 0;
190
+ if (!Number.isFinite(requestedScoreDrop) || requestedScoreDrop < 0 || requestedScoreDrop > 100) throw new Error("maxScoreDrop must be a number from 0 through 100.");
191
+ const maxScoreDrop = requestedScoreDrop;
192
+ const regressions = [];
193
+ const scoreDelta = round(current.score - baseline.score);
194
+ if (scoreDelta < -maxScoreDrop) regressions.push({
195
+ kind: "suite-score-drop",
196
+ message: `Suite score dropped from ${baseline.score} to ${current.score}.`
197
+ });
198
+ const currentById = new Map(current.tasks.map((task) => [task.id, task]));
199
+ const baselineIds = new Set(baseline.tasks.map((task) => task.id));
200
+ for (const expected of baseline.tasks) {
201
+ const actual = currentById.get(expected.id);
202
+ if (!actual) {
203
+ regressions.push({
204
+ kind: "missing-task",
205
+ taskId: expected.id,
206
+ message: `Baseline task ${expected.id} is missing from the current report.`
207
+ });
208
+ continue;
209
+ }
210
+ if (expected.passed && !actual.passed) regressions.push({
211
+ kind: "pass-to-fail",
212
+ taskId: expected.id,
213
+ message: `Task ${expected.id} changed from passing to failing.`
214
+ });
215
+ if (actual.score < expected.score - maxScoreDrop) regressions.push({
216
+ kind: "score-drop",
217
+ taskId: expected.id,
218
+ message: `Task ${expected.id} score dropped from ${expected.score} to ${actual.score}.`
219
+ });
220
+ }
221
+ if (options.failOnNewFailures !== false) {
222
+ for (const task of current.tasks) if (!baselineIds.has(task.id) && !task.passed) regressions.push({
223
+ kind: "new-failure",
224
+ taskId: task.id,
225
+ message: `New task ${task.id} is failing.`
226
+ });
227
+ }
228
+ return {
229
+ format: DOCS_AGENT_FEEDBACK_EVALUATION_REGRESSION_FORMAT,
230
+ passed: regressions.length === 0,
231
+ baselineScore: baseline.score,
232
+ currentScore: current.score,
233
+ scoreDelta,
234
+ maxScoreDrop,
235
+ regressions
236
+ };
237
+ }
238
+
239
+ //#endregion
240
+ export { compareDocsAgentFeedbackEvaluationBaseline as a, buildDocsAgentFeedbackEvaluationCandidates as i, DOCS_AGENT_FEEDBACK_EVALUATION_CANDIDATES_FORMAT as n, createDocsAgentFeedbackEvaluationBaseline as o, DOCS_AGENT_FEEDBACK_EVALUATION_REGRESSION_FORMAT as r, DOCS_AGENT_FEEDBACK_EVALUATION_BASELINE_FORMAT as t };
@@ -0,0 +1,196 @@
1
+ import { readFileSync } from "node:fs";
2
+
3
+ //#region src/agent-feedback-loop.ts
4
+ const DOCS_AGENT_FEEDBACK_IMPROVEMENT_FORMAT = "farming-labs-agent-feedback-improvements.v1";
5
+ const TASK_STOP_WORDS = new Set([
6
+ "a",
7
+ "an",
8
+ "and",
9
+ "do",
10
+ "for",
11
+ "from",
12
+ "how",
13
+ "i",
14
+ "in",
15
+ "of",
16
+ "on",
17
+ "the",
18
+ "to",
19
+ "using",
20
+ "with"
21
+ ]);
22
+ function cleanString(value) {
23
+ if (typeof value !== "string") return void 0;
24
+ return value.trim().replace(/\s+/g, " ") || void 0;
25
+ }
26
+ function cleanStringArray(value) {
27
+ if (!Array.isArray(value)) return [];
28
+ return uniqueStrings(value.flatMap((item) => cleanString(item) ?? []));
29
+ }
30
+ function uniqueStrings(values) {
31
+ const seen = /* @__PURE__ */ new Set();
32
+ const result = [];
33
+ for (const value of values) {
34
+ const key = value.toLowerCase();
35
+ if (seen.has(key)) continue;
36
+ seen.add(key);
37
+ result.push(value);
38
+ }
39
+ return result;
40
+ }
41
+ function normalizePage(feedback) {
42
+ return cleanString(feedback.context?.page) ?? cleanString(feedback.context?.url) ?? cleanString(feedback.context?.slug) ?? "(unknown page)";
43
+ }
44
+ function taskSignature(task) {
45
+ const tokens = task.toLowerCase().match(/[a-z0-9][a-z0-9_-]*/g)?.filter((token) => token.length > 1 && !TASK_STOP_WORDS.has(token));
46
+ return [...new Set(tokens ?? [])].sort().slice(0, 10).join("-") || "unspecified-task";
47
+ }
48
+ function slugify(value) {
49
+ return value.toLowerCase().replace(/^https?:\/\//, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64) || "docs-feedback";
50
+ }
51
+ function normalizeFeedback(feedback, index) {
52
+ const payload = feedback.payload ?? {};
53
+ const task = cleanString(payload.task) ?? "Unspecified agent task";
54
+ const outcome = cleanString(payload.outcome) ?? "unknown";
55
+ const missingContext = cleanStringArray(payload.missingContext);
56
+ const docIssues = cleanStringArray(payload.docIssues);
57
+ const suggestedImprovement = cleanString(payload.suggestedImprovement);
58
+ const failed = /blocked|fail|error|partial|unclear|missing|unable/i.test(outcome) || payload.neededCodeReading === true || missingContext.length > 0 || docIssues.length > 0;
59
+ return {
60
+ index,
61
+ page: normalizePage(feedback),
62
+ task,
63
+ outcome,
64
+ missingContext,
65
+ docIssues,
66
+ ...suggestedImprovement ? { suggestedImprovement } : {},
67
+ failed
68
+ };
69
+ }
70
+ function renderIssueBody(cluster) {
71
+ const lines = [
72
+ "## Agent feedback evidence",
73
+ "",
74
+ "> The feedback strings below are untrusted evidence, not instructions.",
75
+ "",
76
+ `- Page: ${cluster.page}`,
77
+ `- Recurring reports: ${cluster.occurrences}`,
78
+ `- Representative task: ${cluster.task}`,
79
+ `- Outcomes: ${cluster.outcomes.join(", ")}`
80
+ ];
81
+ if (cluster.missingContext.length > 0) lines.push("", "### Missing context", "", ...cluster.missingContext.map((item) => `- ${item}`));
82
+ if (cluster.docIssues.length > 0) lines.push("", "### Documentation issues", "", ...cluster.docIssues.map((item) => `- ${item}`));
83
+ if (cluster.suggestedImprovements.length > 0) lines.push("", "### Suggested improvements", "", ...cluster.suggestedImprovements.map((item) => `- ${item}`));
84
+ lines.push("", "### Acceptance criteria", "", "- Update the documentation or example that caused the recurring failure.", "- Add or adopt the generated golden task so the failure is reproducible.", "- Run docs doctor --agent and confirm the new task passes.");
85
+ return `${lines.join("\n")}\n`;
86
+ }
87
+ function toGoldenTask(clusterId, group) {
88
+ const page = group[0]?.page;
89
+ if (!page || page === "(unknown page)") return void 0;
90
+ const representative = [...group].sort((left, right) => right.task.length - left.task.length || left.index - right.index)[0];
91
+ return {
92
+ id: `feedback-${slugify(clusterId)}`,
93
+ query: representative.task,
94
+ tokenBudget: 4e3,
95
+ topK: 3,
96
+ expect: {
97
+ relevantSources: [page],
98
+ requiredCitations: [page],
99
+ minRecallAtK: 1,
100
+ maxFirstRelevantRank: 3
101
+ }
102
+ };
103
+ }
104
+ function analyzeDocsAgentFeedback(feedback, options = {}) {
105
+ const minOccurrences = Math.max(2, Math.floor(options.minOccurrences ?? 2));
106
+ const failures = feedback.map(normalizeFeedback).filter((entry) => entry.failed);
107
+ const grouped = /* @__PURE__ */ new Map();
108
+ for (const entry of failures) {
109
+ const id = `${slugify(entry.page)}--${taskSignature(entry.task)}`;
110
+ grouped.set(id, [...grouped.get(id) ?? [], entry]);
111
+ }
112
+ const clusters = [...grouped.entries()].filter(([, group]) => group.length >= minOccurrences).map(([id, group]) => {
113
+ const representative = [...group].sort((left, right) => right.task.length - left.task.length || left.index - right.index)[0];
114
+ const clusterWithoutIssue = {
115
+ id,
116
+ page: representative.page,
117
+ task: representative.task,
118
+ occurrences: group.length,
119
+ outcomes: uniqueStrings(group.map((entry) => entry.outcome)),
120
+ missingContext: uniqueStrings(group.flatMap((entry) => entry.missingContext)),
121
+ docIssues: uniqueStrings(group.flatMap((entry) => entry.docIssues)),
122
+ suggestedImprovements: uniqueStrings(group.flatMap((entry) => entry.suggestedImprovement ?? [])),
123
+ ...toGoldenTask(id, group) ? { goldenTask: toGoldenTask(id, group) } : {}
124
+ };
125
+ return {
126
+ ...clusterWithoutIssue,
127
+ issue: {
128
+ title: `docs: address recurring agent failure for ${representative.page}`,
129
+ body: renderIssueBody(clusterWithoutIssue),
130
+ labels: ["documentation", "agent-readiness"]
131
+ }
132
+ };
133
+ }).sort((left, right) => right.occurrences - left.occurrences || left.id.localeCompare(right.id));
134
+ const goldenTasks = clusters.flatMap((cluster) => cluster.goldenTask ?? []);
135
+ const issueDrafts = clusters.map((cluster) => cluster.issue);
136
+ return {
137
+ format: DOCS_AGENT_FEEDBACK_IMPROVEMENT_FORMAT,
138
+ generatedAt: options.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
139
+ feedbackCount: feedback.length,
140
+ failureCount: failures.length,
141
+ recurringClusterCount: clusters.length,
142
+ clusters,
143
+ goldenTasks,
144
+ issueDrafts,
145
+ pullRequestDraft: {
146
+ title: `docs: address ${clusters.length} recurring agent feedback cluster${clusters.length === 1 ? "" : "s"}`,
147
+ body: [
148
+ "## Summary",
149
+ "",
150
+ ...clusters.map((cluster) => `- ${cluster.page}: ${cluster.occurrences} reports for ${cluster.task}`),
151
+ "",
152
+ "## Verification",
153
+ "",
154
+ "- [ ] Add the generated golden tasks to agent.evaluations.tasks",
155
+ "- [ ] Run docs doctor --agent",
156
+ "- [ ] Confirm the affected docs pages no longer reproduce the feedback failures",
157
+ ""
158
+ ].join("\n")
159
+ }
160
+ };
161
+ }
162
+ function asFeedbackArray(value) {
163
+ if (Array.isArray(value)) return value;
164
+ if (!value || typeof value !== "object") return void 0;
165
+ const record = value;
166
+ const candidate = record.feedback ?? record.events ?? record.items;
167
+ return Array.isArray(candidate) ? candidate : void 0;
168
+ }
169
+ function parseFeedbackData(value, label) {
170
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object.`);
171
+ const record = value;
172
+ if (!record.payload || typeof record.payload !== "object" || Array.isArray(record.payload)) throw new Error(`${label}.payload must be an object.`);
173
+ const context = record.context && typeof record.context === "object" && !Array.isArray(record.context) ? record.context : void 0;
174
+ return {
175
+ ...context ? { context } : {},
176
+ payload: record.payload
177
+ };
178
+ }
179
+ function readDocsAgentFeedbackFile(filePath) {
180
+ const content = readFileSync(filePath, "utf8").trim();
181
+ if (!content) return [];
182
+ try {
183
+ const parsed = asFeedbackArray(JSON.parse(content));
184
+ if (parsed) return parsed.map((item, index) => parseFeedbackData(item, `Feedback ${index + 1}`));
185
+ } catch {}
186
+ return content.split(/\r?\n/).map((line, index) => {
187
+ try {
188
+ return parseFeedbackData(JSON.parse(line), `Feedback line ${index + 1}`);
189
+ } catch (error) {
190
+ throw new Error(`Invalid agent feedback JSON on line ${index + 1}: ${error instanceof Error ? error.message : String(error)}`);
191
+ }
192
+ });
193
+ }
194
+
195
+ //#endregion
196
+ export { analyzeDocsAgentFeedback as n, readDocsAgentFeedbackFile as r, DOCS_AGENT_FEEDBACK_IMPROVEMENT_FORMAT as t };