@polishd/next 0.1.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.
Files changed (127) hide show
  1. package/AGENTS.md +262 -0
  2. package/DATABASE.md +147 -0
  3. package/LICENSE +21 -0
  4. package/README.md +310 -0
  5. package/bin/init.mjs +224 -0
  6. package/dist/ai/actions.d.ts +23 -0
  7. package/dist/ai/actions.d.ts.map +1 -0
  8. package/dist/ai/actions.js +58 -0
  9. package/dist/ai/actions.js.map +1 -0
  10. package/dist/ai/digest.d.ts +9 -0
  11. package/dist/ai/digest.d.ts.map +1 -0
  12. package/dist/ai/digest.js +108 -0
  13. package/dist/ai/digest.js.map +1 -0
  14. package/dist/ai/github.d.ts +51 -0
  15. package/dist/ai/github.d.ts.map +1 -0
  16. package/dist/ai/github.js +175 -0
  17. package/dist/ai/github.js.map +1 -0
  18. package/dist/ai/guard.d.ts +43 -0
  19. package/dist/ai/guard.d.ts.map +1 -0
  20. package/dist/ai/guard.js +57 -0
  21. package/dist/ai/guard.js.map +1 -0
  22. package/dist/ai/issues.d.ts +21 -0
  23. package/dist/ai/issues.d.ts.map +1 -0
  24. package/dist/ai/issues.js +291 -0
  25. package/dist/ai/issues.js.map +1 -0
  26. package/dist/ai/profile.d.ts +26 -0
  27. package/dist/ai/profile.d.ts.map +1 -0
  28. package/dist/ai/profile.js +160 -0
  29. package/dist/ai/profile.js.map +1 -0
  30. package/dist/ai/providers.d.ts +40 -0
  31. package/dist/ai/providers.d.ts.map +1 -0
  32. package/dist/ai/providers.js +142 -0
  33. package/dist/ai/providers.js.map +1 -0
  34. package/dist/ai/scan.d.ts +38 -0
  35. package/dist/ai/scan.d.ts.map +1 -0
  36. package/dist/ai/scan.js +274 -0
  37. package/dist/ai/scan.js.map +1 -0
  38. package/dist/ai/settings.d.ts +31 -0
  39. package/dist/ai/settings.d.ts.map +1 -0
  40. package/dist/ai/settings.js +182 -0
  41. package/dist/ai/settings.js.map +1 -0
  42. package/dist/ai/summary.d.ts +26 -0
  43. package/dist/ai/summary.d.ts.map +1 -0
  44. package/dist/ai/summary.js +260 -0
  45. package/dist/ai/summary.js.map +1 -0
  46. package/dist/ai/types.d.ts +188 -0
  47. package/dist/ai/types.d.ts.map +1 -0
  48. package/dist/ai/types.js +13 -0
  49. package/dist/ai/types.js.map +1 -0
  50. package/dist/client/init.d.ts +14 -0
  51. package/dist/client/init.d.ts.map +1 -0
  52. package/dist/client/init.js +274 -0
  53. package/dist/client/init.js.map +1 -0
  54. package/dist/client/monitor.d.ts +23 -0
  55. package/dist/client/monitor.d.ts.map +1 -0
  56. package/dist/client/monitor.js +176 -0
  57. package/dist/client/monitor.js.map +1 -0
  58. package/dist/client.d.ts +11 -0
  59. package/dist/client.d.ts.map +1 -0
  60. package/dist/client.js +10 -0
  61. package/dist/client.js.map +1 -0
  62. package/dist/config.d.ts +36 -0
  63. package/dist/config.d.ts.map +1 -0
  64. package/dist/config.js +26 -0
  65. package/dist/config.js.map +1 -0
  66. package/dist/dashboard/elements.d.ts +6 -0
  67. package/dist/dashboard/elements.d.ts.map +1 -0
  68. package/dist/dashboard/elements.js +35 -0
  69. package/dist/dashboard/elements.js.map +1 -0
  70. package/dist/dashboard/features.d.ts +6 -0
  71. package/dist/dashboard/features.d.ts.map +1 -0
  72. package/dist/dashboard/features.js +28 -0
  73. package/dist/dashboard/features.js.map +1 -0
  74. package/dist/dashboard/index.d.ts +54 -0
  75. package/dist/dashboard/index.d.ts.map +1 -0
  76. package/dist/dashboard/index.js +140 -0
  77. package/dist/dashboard/index.js.map +1 -0
  78. package/dist/dashboard/journeys.d.ts +5 -0
  79. package/dist/dashboard/journeys.d.ts.map +1 -0
  80. package/dist/dashboard/journeys.js +158 -0
  81. package/dist/dashboard/journeys.js.map +1 -0
  82. package/dist/dashboard/pages.d.ts +5 -0
  83. package/dist/dashboard/pages.d.ts.map +1 -0
  84. package/dist/dashboard/pages.js +104 -0
  85. package/dist/dashboard/pages.js.map +1 -0
  86. package/dist/dashboard/summary.d.ts +13 -0
  87. package/dist/dashboard/summary.d.ts.map +1 -0
  88. package/dist/dashboard/summary.js +333 -0
  89. package/dist/dashboard/summary.js.map +1 -0
  90. package/dist/index.d.ts +10 -0
  91. package/dist/index.d.ts.map +1 -0
  92. package/dist/index.js +10 -0
  93. package/dist/index.js.map +1 -0
  94. package/dist/proxy.d.ts +40 -0
  95. package/dist/proxy.d.ts.map +1 -0
  96. package/dist/proxy.js +29 -0
  97. package/dist/proxy.js.map +1 -0
  98. package/dist/route.d.ts +24 -0
  99. package/dist/route.d.ts.map +1 -0
  100. package/dist/route.js +37 -0
  101. package/dist/route.js.map +1 -0
  102. package/dist/server/ingest.d.ts +14 -0
  103. package/dist/server/ingest.d.ts.map +1 -0
  104. package/dist/server/ingest.js +73 -0
  105. package/dist/server/ingest.js.map +1 -0
  106. package/dist/server/queries.d.ts +245 -0
  107. package/dist/server/queries.d.ts.map +1 -0
  108. package/dist/server/queries.js +547 -0
  109. package/dist/server/queries.js.map +1 -0
  110. package/dist/server/store.d.ts +29 -0
  111. package/dist/server/store.d.ts.map +1 -0
  112. package/dist/server/store.js +359 -0
  113. package/dist/server/store.js.map +1 -0
  114. package/dist/server.d.ts +15 -0
  115. package/dist/server.d.ts.map +1 -0
  116. package/dist/server.js +15 -0
  117. package/dist/server.js.map +1 -0
  118. package/dist/session.d.ts +16 -0
  119. package/dist/session.d.ts.map +1 -0
  120. package/dist/session.js +29 -0
  121. package/dist/session.js.map +1 -0
  122. package/dist/shared/types.d.ts +56 -0
  123. package/dist/shared/types.d.ts.map +1 -0
  124. package/dist/shared/types.js +27 -0
  125. package/dist/shared/types.js.map +1 -0
  126. package/docs/SETUP.md +315 -0
  127. package/package.json +92 -0
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Polishd — AI settings resolution (server only).
3
+ *
4
+ * One place that decides "which model, with which key, and what the owner told
5
+ * us about the site". Saved dashboard settings win over `POLISHD_AI_*` env
6
+ * defaults. Lives in its own module so both the summary and the project-profile
7
+ * orchestrators can share it without importing each other.
8
+ *
9
+ * The API key never leaves the server: `getAISettingsPublic` maps it to a
10
+ * boolean before anything reaches the client.
11
+ */
12
+ import { getMeta, setMeta } from "../server/store";
13
+ import { DEFAULT_MODEL } from "./providers";
14
+ const SETTINGS_KEY = "ai_settings";
15
+ const VALID_PROVIDERS = [
16
+ "anthropic",
17
+ "openai",
18
+ "openai-compatible",
19
+ "google",
20
+ ];
21
+ function coerceProvider(v) {
22
+ return typeof v === "string" && VALID_PROVIDERS.includes(v)
23
+ ? v
24
+ : undefined;
25
+ }
26
+ const VALID_CADENCES = ["manual", "daily", "weekly"];
27
+ function coerceCadence(v) {
28
+ return typeof v === "string" && VALID_CADENCES.includes(v)
29
+ ? v
30
+ : undefined;
31
+ }
32
+ async function readSaved() {
33
+ const raw = await getMeta(SETTINGS_KEY);
34
+ if (!raw)
35
+ return {};
36
+ try {
37
+ return JSON.parse(raw);
38
+ }
39
+ catch {
40
+ return {};
41
+ }
42
+ }
43
+ /**
44
+ * Non-secret env defaults — handy for CI / shared deploys.
45
+ *
46
+ * Each key is read as `POLISHD_*` first, then the pre-rename `BUFFD_*` name, so
47
+ * deployments configured before the rename keep working without anyone having
48
+ * to touch their host's environment settings.
49
+ */
50
+ function envSettings() {
51
+ const e = process.env;
52
+ /** POLISHD_<name>, falling back to the legacy BUFFD_<name>. */
53
+ const read = (name) => e[`POLISHD_${name}`] || e[`BUFFD_${name}`];
54
+ const out = {};
55
+ const provider = coerceProvider(read("AI_PROVIDER"));
56
+ if (provider)
57
+ out.provider = provider;
58
+ const cadence = coerceCadence(read("AI_REFRESH_CADENCE"));
59
+ if (cadence)
60
+ out.refreshCadence = cadence;
61
+ const model = read("AI_MODEL");
62
+ if (model)
63
+ out.model = model;
64
+ const apiKey = read("AI_API_KEY");
65
+ if (apiKey)
66
+ out.apiKey = apiKey;
67
+ const baseUrl = read("AI_BASE_URL");
68
+ if (baseUrl)
69
+ out.baseUrl = baseUrl;
70
+ const instructions = read("AI_INSTRUCTIONS");
71
+ if (instructions)
72
+ out.instructions = instructions;
73
+ const context = read("AI_CONTEXT");
74
+ if (context)
75
+ out.context = context;
76
+ const audience = read("AI_AUDIENCE");
77
+ if (audience)
78
+ out.audience = audience;
79
+ const ideology = read("AI_IDEOLOGY");
80
+ if (ideology)
81
+ out.ideology = ideology;
82
+ const sourceDirs = read("AI_SOURCE_DIRS");
83
+ if (sourceDirs)
84
+ out.sourceDirs = sourceDirs;
85
+ const githubRepo = read("GITHUB_REPO");
86
+ if (githubRepo)
87
+ out.githubRepo = githubRepo;
88
+ const githubToken = read("GITHUB_TOKEN");
89
+ if (githubToken)
90
+ out.githubToken = githubToken;
91
+ const autoIssues = read("GITHUB_AUTO_ISSUES");
92
+ if (autoIssues)
93
+ out.githubAutoIssues = /^(1|true|yes)$/i.test(autoIssues);
94
+ return out;
95
+ }
96
+ /**
97
+ * Effective settings = saved (dashboard) over env defaults. Returns the full
98
+ * settings (with key) for server use, plus whether the *provider config* came
99
+ * purely from env (so the UI can show it as a read-only default).
100
+ */
101
+ export async function resolveSettings() {
102
+ const env = envSettings();
103
+ const saved = await readSaved();
104
+ const merged = { ...env, ...saved };
105
+ const provider = merged.provider ?? "anthropic";
106
+ return {
107
+ settings: {
108
+ provider,
109
+ model: merged.model || DEFAULT_MODEL[provider],
110
+ apiKey: merged.apiKey ?? "",
111
+ baseUrl: merged.baseUrl,
112
+ instructions: merged.instructions,
113
+ context: merged.context,
114
+ audience: merged.audience,
115
+ ideology: merged.ideology,
116
+ sourceDirs: merged.sourceDirs,
117
+ refreshCadence: merged.refreshCadence ?? "manual",
118
+ githubRepo: merged.githubRepo,
119
+ githubToken: merged.githubToken,
120
+ githubAutoIssues: merged.githubAutoIssues ?? false,
121
+ },
122
+ fromEnv: Object.keys(saved).length === 0 && Object.keys(env).length > 0,
123
+ };
124
+ }
125
+ /** Public (no-secret) settings for the dashboard. */
126
+ export async function getAISettingsPublic() {
127
+ const { settings, fromEnv } = await resolveSettings();
128
+ return {
129
+ provider: settings.provider,
130
+ model: settings.model,
131
+ hasApiKey: settings.apiKey.length > 0,
132
+ baseUrl: settings.baseUrl,
133
+ instructions: settings.instructions,
134
+ context: settings.context,
135
+ audience: settings.audience,
136
+ ideology: settings.ideology,
137
+ sourceDirs: settings.sourceDirs,
138
+ refreshCadence: settings.refreshCadence,
139
+ githubRepo: settings.githubRepo,
140
+ hasGithubToken: (settings.githubToken ?? "").length > 0,
141
+ githubAutoIssues: settings.githubAutoIssues,
142
+ fromEnv,
143
+ };
144
+ }
145
+ /** Persist owner settings. Returns the refreshed public view. */
146
+ export async function saveAISettings(input) {
147
+ const prev = await readSaved();
148
+ const next = { ...prev };
149
+ const provider = coerceProvider(input.provider);
150
+ if (provider)
151
+ next.provider = provider;
152
+ if (input.model !== undefined)
153
+ next.model = input.model.trim();
154
+ // Empty apiKey = "keep what's stored"; a non-empty value replaces it.
155
+ if (input.apiKey)
156
+ next.apiKey = input.apiKey.trim();
157
+ if (input.baseUrl !== undefined)
158
+ next.baseUrl = input.baseUrl.trim() || undefined;
159
+ if (input.instructions !== undefined)
160
+ next.instructions = input.instructions.trim() || undefined;
161
+ if (input.context !== undefined)
162
+ next.context = input.context.trim() || undefined;
163
+ if (input.audience !== undefined)
164
+ next.audience = input.audience.trim() || undefined;
165
+ if (input.ideology !== undefined)
166
+ next.ideology = input.ideology.trim() || undefined;
167
+ if (input.sourceDirs !== undefined)
168
+ next.sourceDirs = input.sourceDirs.trim() || undefined;
169
+ const cadence = coerceCadence(input.refreshCadence);
170
+ if (cadence)
171
+ next.refreshCadence = cadence;
172
+ if (input.githubRepo !== undefined)
173
+ next.githubRepo = input.githubRepo.trim() || undefined;
174
+ // Like apiKey: blank keeps the stored token, a value replaces it.
175
+ if (input.githubToken)
176
+ next.githubToken = input.githubToken.trim();
177
+ if (input.githubAutoIssues !== undefined)
178
+ next.githubAutoIssues = input.githubAutoIssues === true;
179
+ await setMeta(SETTINGS_KEY, JSON.stringify(next));
180
+ return getAISettingsPublic();
181
+ }
182
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../../src/ai/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import { type PolishdDashboardData } from "../server/queries";
2
+ import type { PolishdSummary, GenerateSummaryResult } from "./types";
3
+ export { getAISettingsPublic, saveAISettings, type SaveAISettingsInput, } from "./settings";
4
+ /** The last generated summary, or null. Read-only — never calls a model. */
5
+ export declare function loadSummary(): Promise<PolishdSummary | null>;
6
+ /**
7
+ * Generate (or reuse) the narrative summary.
8
+ *
9
+ * @param force When false (default), a summary whose fingerprint matches the
10
+ * current data is returned as-is — no model call, no tokens spent.
11
+ * When true, always re-asks the model.
12
+ */
13
+ export declare function generateSummary(opts?: {
14
+ force?: boolean;
15
+ }): Promise<GenerateSummaryResult>;
16
+ /**
17
+ * For the dashboard's first render: the cached summary plus whether the live
18
+ * data has drifted from it (so the UI can show a "new data — regenerate" hint).
19
+ * Computing this only reads the store; it never calls a model.
20
+ */
21
+ export declare function loadSummaryState(preloaded?: PolishdDashboardData): Promise<{
22
+ summary: PolishdSummary | null;
23
+ stale: boolean;
24
+ currentFingerprint: string | null;
25
+ }>;
26
+ //# sourceMappingURL=summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/ai/summary.ts"],"names":[],"mappings":"AAmBA,OAAO,EAA4B,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAOxF,OAAO,KAAK,EAGV,cAAc,EACd,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,KAAK,mBAAmB,GACzB,MAAM,YAAY,CAAC;AAIpB,4EAA4E;AAC5E,wBAAsB,WAAW,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAQlE;AA2ID;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GAC7B,OAAO,CAAC,qBAAqB,CAAC,CAgFhC;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,CAAC,EAAE,oBAAoB,GAC/B,OAAO,CAAC;IACT,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC,CAAC,CAuBD"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * Polishd — AI summary orchestration (server only).
3
+ *
4
+ * Ties the pieces together: resolve the owner's model settings, build a compact
5
+ * digest of the analytics, fold in the cached project profile, and — only when
6
+ * needed — ask the model for a one-paragraph story of how the site is used.
7
+ *
8
+ * Token thrift, in order of impact:
9
+ * 1. We send a pre-aggregated digest, never raw events.
10
+ * 2. Code understanding comes from the cached project profile (see
11
+ * profile.ts), generated once — the summary call never re-reads source.
12
+ * Only when analytics mention a component the profile doesn't cover do we
13
+ * do a tiny targeted read of the files naming it.
14
+ * 3. We fingerprint the inputs (digest + profile) and cache the summary. A
15
+ * regenerate with no new data returns the cached text for free
16
+ * (`regenerated: false`).
17
+ * 4. The system prompt is tight and the output is capped to a paragraph.
18
+ */
19
+ import { getMeta, setMeta } from "../server/store";
20
+ import { loadPolishdDashboardData } from "../server/queries";
21
+ import { buildDigest, fingerprintDigest } from "./digest";
22
+ import { attachGithubIssues } from "./issues";
23
+ import { callModel } from "./providers";
24
+ import { coverageGaps, loadProjectProfile } from "./profile";
25
+ import { collectTargeted, findInSource } from "./scan";
26
+ import { resolveSettings } from "./settings";
27
+ // Settings live in ./settings; re-exported here so existing imports keep working.
28
+ export { getAISettingsPublic, saveAISettings, } from "./settings";
29
+ const SUMMARY_KEY = "ai_summary";
30
+ /** The last generated summary, or null. Read-only — never calls a model. */
31
+ export async function loadSummary() {
32
+ const raw = await getMeta(SUMMARY_KEY);
33
+ if (!raw)
34
+ return null;
35
+ try {
36
+ return JSON.parse(raw);
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
42
+ const SYSTEM_PROMPT = "You are a product analyst embedded in a website's analytics dashboard. " +
43
+ "You receive a compact digest of real user-behavior signals (clicks, rage " +
44
+ "clicks, dead clicks, scroll depth, errors, per-page and per-element stats), " +
45
+ "usually preceded by a project profile describing the site's purpose, pages, " +
46
+ "and components — treat the profile as authoritative context for what every " +
47
+ "identifier means.\n\n" +
48
+ "Respond with ONLY a JSON object (no markdown fences, no preamble):\n" +
49
+ '{ "story": string, "wins": string[], "losses": [{ "issue": string, "evidence": string }] }\n\n' +
50
+ "story: a single tight paragraph (3-5 sentences) telling the site owner how " +
51
+ "people are actually using their site — what's working best and what's " +
52
+ "frustrating or broken. Name specific pages and elements, in the site's own " +
53
+ "terms from the profile. Lead with the strongest finding. If the data is too " +
54
+ "sparse to be confident, say so plainly.\n" +
55
+ "wins: 2-4 things that are working. These may be general observations.\n" +
56
+ "losses: 0-4 real, specific problems visible in the data — a broken or " +
57
+ "frustrating element, an underperforming page, a recurring error. Each issue " +
58
+ "must be concrete (what is wrong, where, and what the numbers show), and " +
59
+ "evidence must be the exact page path, CSS selector, or component name " +
60
+ "copied verbatim from the digest that demonstrates it. Never invent a loss: " +
61
+ "if the data shows no real problems, return an empty array.";
62
+ /**
63
+ * Parse the model's JSON reply, tolerating stray fences or prose around it.
64
+ * Falls back to treating the whole text as the story so a model that ignores
65
+ * the format still produces a usable card.
66
+ */
67
+ function parseStructured(text) {
68
+ let t = text.trim();
69
+ const fence = t.match(/```(?:json)?\s*([\s\S]*?)```/);
70
+ if (fence)
71
+ t = fence[1].trim();
72
+ const start = t.indexOf("{");
73
+ const end = t.lastIndexOf("}");
74
+ if (start >= 0 && end > start) {
75
+ try {
76
+ const obj = JSON.parse(t.slice(start, end + 1));
77
+ const story = typeof obj.story === "string" ? obj.story.trim() : "";
78
+ if (story) {
79
+ const wins = Array.isArray(obj.wins)
80
+ ? obj.wins.filter((w) => typeof w === "string" && w.trim().length > 0).slice(0, 4)
81
+ : [];
82
+ const losses = Array.isArray(obj.losses) ? obj.losses.slice(0, 6) : [];
83
+ return { story, wins, losses };
84
+ }
85
+ }
86
+ catch {
87
+ /* fall through to plain-text handling */
88
+ }
89
+ }
90
+ return { story: text.trim(), wins: [], losses: [] };
91
+ }
92
+ /** Tokens specific enough to ground a citation (drop generic words). */
93
+ function citationTokens(s) {
94
+ return s
95
+ .toLowerCase()
96
+ .split(/[^\w/-]+/)
97
+ .filter((t) => t.length >= 4 && !/^(button|input|click|clicks|page|pages|http|https|with|from|that)$/.test(t));
98
+ }
99
+ /**
100
+ * Hold losses to their evidence bar. A loss survives only if its citation
101
+ * actually appears in the prompt we sent (so it can't be invented), and is
102
+ * then matched to a source file when the codebase is on disk.
103
+ */
104
+ function verifyLosses(raw, promptText, sourceDirs) {
105
+ const hay = promptText.toLowerCase();
106
+ const out = [];
107
+ for (const l of raw) {
108
+ if (typeof l.issue !== "string" || !l.issue.trim())
109
+ continue;
110
+ const evidence = typeof l.evidence === "string" ? l.evidence.trim() : "";
111
+ if (!evidence)
112
+ continue; // no citation, no loss
113
+ const tokens = citationTokens(evidence);
114
+ if (!tokens.length || !tokens.some((t) => hay.includes(t)))
115
+ continue; // not in the data we sent
116
+ const location = findInSource(tokens, sourceDirs);
117
+ out.push({
118
+ issue: l.issue.trim(),
119
+ evidence,
120
+ location: location ?? undefined,
121
+ verified: location !== null,
122
+ });
123
+ if (out.length >= 4)
124
+ break;
125
+ }
126
+ return out;
127
+ }
128
+ /**
129
+ * The exact user message for a summary call, plus its cache fingerprint.
130
+ * Fingerprint covers the digest AND the profile, so a re-scan (new profile)
131
+ * marks the cached summary stale too.
132
+ */
133
+ function composePrompt(data, profile, context, sourceDirs) {
134
+ const digest = buildDigest(data, context);
135
+ const sections = [];
136
+ if (profile) {
137
+ sections.push(`PROJECT PROFILE (authoritative — what this site and its components are):\n${profile.text}`);
138
+ // The "only touch source again when something isn't covered" path: a tiny
139
+ // targeted read for identifiers the profile doesn't know about.
140
+ const gaps = coverageGaps(data, profile);
141
+ if (gaps.length) {
142
+ const excerpts = collectTargeted(gaps, sourceDirs);
143
+ if (excerpts.length) {
144
+ sections.push("NEW COMPONENTS NOT IN THE PROFILE (source excerpts):\n" +
145
+ excerpts.map((f) => `--- ${f.path} ---\n${f.content}`).join("\n\n"));
146
+ }
147
+ else {
148
+ sections.push(`COMPONENTS NOT COVERED BY THE PROFILE (no source found): ${gaps.join(", ")}`);
149
+ }
150
+ }
151
+ }
152
+ sections.push(digest);
153
+ const user = sections.join("\n\n");
154
+ return {
155
+ user,
156
+ fingerprint: fingerprintDigest(profile ? `${digest}\nPROFILE:${profile.fingerprint}` : digest),
157
+ };
158
+ }
159
+ /**
160
+ * Generate (or reuse) the narrative summary.
161
+ *
162
+ * @param force When false (default), a summary whose fingerprint matches the
163
+ * current data is returned as-is — no model call, no tokens spent.
164
+ * When true, always re-asks the model.
165
+ */
166
+ export async function generateSummary(opts = {}) {
167
+ const { settings } = await resolveSettings();
168
+ if (!settings.apiKey) {
169
+ return { ok: false, error: "no-key", message: "No API key configured." };
170
+ }
171
+ const data = await loadPolishdDashboardData();
172
+ if (!data.overview.ready || data.overview.totalEvents === 0) {
173
+ return {
174
+ ok: false,
175
+ error: "no-data",
176
+ message: "No analytics captured yet — browse the site, then generate.",
177
+ };
178
+ }
179
+ const profile = await loadProjectProfile();
180
+ const { user, fingerprint } = composePrompt(data, profile, settings.context, settings.sourceDirs);
181
+ if (!opts.force) {
182
+ const cached = await loadSummary();
183
+ if (cached && cached.fingerprint === fingerprint) {
184
+ return { ok: true, summary: cached, regenerated: false };
185
+ }
186
+ }
187
+ const system = settings.instructions
188
+ ? `${SYSTEM_PROMPT}\n\nAdditional instructions from the site owner: ${settings.instructions}`
189
+ : SYSTEM_PROMPT;
190
+ let reply;
191
+ try {
192
+ reply = await callModel(settings, system, user);
193
+ }
194
+ catch (err) {
195
+ return {
196
+ ok: false,
197
+ error: "provider-error",
198
+ message: err instanceof Error ? err.message : "The model request failed.",
199
+ };
200
+ }
201
+ if (!reply.text) {
202
+ return {
203
+ ok: false,
204
+ error: "provider-error",
205
+ message: "The model returned an empty response.",
206
+ };
207
+ }
208
+ if (reply.truncated) {
209
+ // Even the retry hit the cap — don't cache a mid-sentence stump.
210
+ return {
211
+ ok: false,
212
+ error: "provider-error",
213
+ message: "The model's response was cut off at the output limit, twice. Try a model that reasons less, or regenerate.",
214
+ };
215
+ }
216
+ const parsed = parseStructured(reply.text);
217
+ // Link losses to their GitHub issues — filing new ones when the owner
218
+ // enabled auto-filing, attaching already-filed ones either way.
219
+ const losses = await attachGithubIssues(verifyLosses(parsed.losses, user, settings.sourceDirs));
220
+ const summary = {
221
+ text: parsed.story,
222
+ wins: parsed.wins,
223
+ losses,
224
+ provider: settings.provider,
225
+ model: settings.model,
226
+ generatedAt: Date.now(),
227
+ fingerprint,
228
+ usage: reply.usage,
229
+ };
230
+ await setMeta(SUMMARY_KEY, JSON.stringify(summary));
231
+ return { ok: true, summary, regenerated: true };
232
+ }
233
+ /**
234
+ * For the dashboard's first render: the cached summary plus whether the live
235
+ * data has drifted from it (so the UI can show a "new data — regenerate" hint).
236
+ * Computing this only reads the store; it never calls a model.
237
+ */
238
+ export async function loadSummaryState(preloaded) {
239
+ const [summary, data] = await Promise.all([
240
+ loadSummary(),
241
+ preloaded ? Promise.resolve(preloaded) : loadPolishdDashboardData(),
242
+ ]);
243
+ if (!data.overview.ready || data.overview.totalEvents === 0) {
244
+ return { summary, stale: false, currentFingerprint: null };
245
+ }
246
+ const [{ settings }, profile] = await Promise.all([
247
+ resolveSettings(),
248
+ loadProjectProfile(),
249
+ ]);
250
+ // Staleness only needs the fingerprint — skip the targeted source read by
251
+ // computing it the same way composePrompt does, without collecting excerpts.
252
+ const digest = buildDigest(data, settings.context);
253
+ const currentFingerprint = fingerprintDigest(profile ? `${digest}\nPROFILE:${profile.fingerprint}` : digest);
254
+ return {
255
+ summary,
256
+ stale: summary !== null && summary.fingerprint !== currentFingerprint,
257
+ currentFingerprint,
258
+ };
259
+ }
260
+ //# sourceMappingURL=summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/ai/summary.ts"],"names":[],"mappings":"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"}