@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.
- package/AGENTS.md +262 -0
- package/DATABASE.md +147 -0
- package/LICENSE +21 -0
- package/README.md +310 -0
- package/bin/init.mjs +224 -0
- package/dist/ai/actions.d.ts +23 -0
- package/dist/ai/actions.d.ts.map +1 -0
- package/dist/ai/actions.js +58 -0
- package/dist/ai/actions.js.map +1 -0
- package/dist/ai/digest.d.ts +9 -0
- package/dist/ai/digest.d.ts.map +1 -0
- package/dist/ai/digest.js +108 -0
- package/dist/ai/digest.js.map +1 -0
- package/dist/ai/github.d.ts +51 -0
- package/dist/ai/github.d.ts.map +1 -0
- package/dist/ai/github.js +175 -0
- package/dist/ai/github.js.map +1 -0
- package/dist/ai/guard.d.ts +43 -0
- package/dist/ai/guard.d.ts.map +1 -0
- package/dist/ai/guard.js +57 -0
- package/dist/ai/guard.js.map +1 -0
- package/dist/ai/issues.d.ts +21 -0
- package/dist/ai/issues.d.ts.map +1 -0
- package/dist/ai/issues.js +291 -0
- package/dist/ai/issues.js.map +1 -0
- package/dist/ai/profile.d.ts +26 -0
- package/dist/ai/profile.d.ts.map +1 -0
- package/dist/ai/profile.js +160 -0
- package/dist/ai/profile.js.map +1 -0
- package/dist/ai/providers.d.ts +40 -0
- package/dist/ai/providers.d.ts.map +1 -0
- package/dist/ai/providers.js +142 -0
- package/dist/ai/providers.js.map +1 -0
- package/dist/ai/scan.d.ts +38 -0
- package/dist/ai/scan.d.ts.map +1 -0
- package/dist/ai/scan.js +274 -0
- package/dist/ai/scan.js.map +1 -0
- package/dist/ai/settings.d.ts +31 -0
- package/dist/ai/settings.d.ts.map +1 -0
- package/dist/ai/settings.js +182 -0
- package/dist/ai/settings.js.map +1 -0
- package/dist/ai/summary.d.ts +26 -0
- package/dist/ai/summary.d.ts.map +1 -0
- package/dist/ai/summary.js +260 -0
- package/dist/ai/summary.js.map +1 -0
- package/dist/ai/types.d.ts +188 -0
- package/dist/ai/types.d.ts.map +1 -0
- package/dist/ai/types.js +13 -0
- package/dist/ai/types.js.map +1 -0
- package/dist/client/init.d.ts +14 -0
- package/dist/client/init.d.ts.map +1 -0
- package/dist/client/init.js +274 -0
- package/dist/client/init.js.map +1 -0
- package/dist/client/monitor.d.ts +23 -0
- package/dist/client/monitor.d.ts.map +1 -0
- package/dist/client/monitor.js +176 -0
- package/dist/client/monitor.js.map +1 -0
- package/dist/client.d.ts +11 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +10 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +26 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/elements.d.ts +6 -0
- package/dist/dashboard/elements.d.ts.map +1 -0
- package/dist/dashboard/elements.js +35 -0
- package/dist/dashboard/elements.js.map +1 -0
- package/dist/dashboard/features.d.ts +6 -0
- package/dist/dashboard/features.d.ts.map +1 -0
- package/dist/dashboard/features.js +28 -0
- package/dist/dashboard/features.js.map +1 -0
- package/dist/dashboard/index.d.ts +54 -0
- package/dist/dashboard/index.d.ts.map +1 -0
- package/dist/dashboard/index.js +140 -0
- package/dist/dashboard/index.js.map +1 -0
- package/dist/dashboard/journeys.d.ts +5 -0
- package/dist/dashboard/journeys.d.ts.map +1 -0
- package/dist/dashboard/journeys.js +158 -0
- package/dist/dashboard/journeys.js.map +1 -0
- package/dist/dashboard/pages.d.ts +5 -0
- package/dist/dashboard/pages.d.ts.map +1 -0
- package/dist/dashboard/pages.js +104 -0
- package/dist/dashboard/pages.js.map +1 -0
- package/dist/dashboard/summary.d.ts +13 -0
- package/dist/dashboard/summary.d.ts.map +1 -0
- package/dist/dashboard/summary.js +333 -0
- package/dist/dashboard/summary.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/proxy.d.ts +40 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +29 -0
- package/dist/proxy.js.map +1 -0
- package/dist/route.d.ts +24 -0
- package/dist/route.d.ts.map +1 -0
- package/dist/route.js +37 -0
- package/dist/route.js.map +1 -0
- package/dist/server/ingest.d.ts +14 -0
- package/dist/server/ingest.d.ts.map +1 -0
- package/dist/server/ingest.js +73 -0
- package/dist/server/ingest.js.map +1 -0
- package/dist/server/queries.d.ts +245 -0
- package/dist/server/queries.d.ts.map +1 -0
- package/dist/server/queries.js +547 -0
- package/dist/server/queries.js.map +1 -0
- package/dist/server/store.d.ts +29 -0
- package/dist/server/store.d.ts.map +1 -0
- package/dist/server/store.js +359 -0
- package/dist/server/store.js.map +1 -0
- package/dist/server.d.ts +15 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +16 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +29 -0
- package/dist/session.js.map +1 -0
- package/dist/shared/types.d.ts +56 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +27 -0
- package/dist/shared/types.js.map +1 -0
- package/docs/SETUP.md +315 -0
- package/package.json +92 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — analytics digest (server only).
|
|
3
|
+
*
|
|
4
|
+
* This is the token-saving heart of the AI layer. Instead of shipping raw
|
|
5
|
+
* events (thousands of rows) to a model, we pre-aggregate everything on the
|
|
6
|
+
* server — the same numbers the dashboard already computes — and render them as
|
|
7
|
+
* a terse, structured text block. A model can read the whole picture of a site
|
|
8
|
+
* in a few hundred tokens.
|
|
9
|
+
*
|
|
10
|
+
* `fingerprintDigest` hashes the digest so the orchestrator can skip the model
|
|
11
|
+
* call entirely when nothing has changed since the last summary (see summary.ts).
|
|
12
|
+
*/
|
|
13
|
+
import { createHash } from "node:crypto";
|
|
14
|
+
/** Round to keep digests stable across tiny float jitter. */
|
|
15
|
+
const r = (n) => (n === null ? "—" : String(Math.round(n)));
|
|
16
|
+
/** Trim and cap a free-text label so one outlier can't bloat the prompt. */
|
|
17
|
+
const clip = (s, max = 40) => !s ? "" : s.replace(/\s+/g, " ").trim().slice(0, max);
|
|
18
|
+
/**
|
|
19
|
+
* Render the captured analytics as a compact, model-readable brief. Sections
|
|
20
|
+
* are omitted when empty so the prompt only ever contains real signal.
|
|
21
|
+
*/
|
|
22
|
+
export function buildDigest(data, context) {
|
|
23
|
+
const { overview, pages, elements, devices, topUsed, errors, monitored } = data;
|
|
24
|
+
const lines = [];
|
|
25
|
+
if (context && context.trim()) {
|
|
26
|
+
lines.push(`SITE: ${context.trim()}`);
|
|
27
|
+
}
|
|
28
|
+
lines.push(`TOTALS: ${overview.sessions} sessions, ${overview.pageViews} page views, ` +
|
|
29
|
+
`${overview.rageClicks} rage clicks, ${overview.deadClicks} dead clicks, ` +
|
|
30
|
+
`${overview.jsErrors} JS errors (${overview.totalEvents} events total).`);
|
|
31
|
+
if (devices.length) {
|
|
32
|
+
lines.push("DEVICES: " +
|
|
33
|
+
devices.map((d) => `${d.category} ${d.pct}%`).join(", ") +
|
|
34
|
+
".");
|
|
35
|
+
}
|
|
36
|
+
if (topUsed.length) {
|
|
37
|
+
lines.push("MOST-USED ELEMENTS (by clicks):");
|
|
38
|
+
for (const t of topUsed.slice(0, 8)) {
|
|
39
|
+
const label = clip(t.label) || clip(t.sampleText) || t.selector || "(unknown)";
|
|
40
|
+
lines.push(` - ${label}: ${t.clicks} clicks, ${t.pages} pages`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Pages ranked worst-first by issue score (rage/dead/errors/shallow scroll).
|
|
44
|
+
const worst = [...pages]
|
|
45
|
+
.map((p) => ({
|
|
46
|
+
...p,
|
|
47
|
+
score: p.rageClicks * 3 +
|
|
48
|
+
p.deadClicks * 2 +
|
|
49
|
+
p.jsErrors * 2.5 +
|
|
50
|
+
(p.avgScrollDepth !== null && p.avgScrollDepth < 50
|
|
51
|
+
? (50 - p.avgScrollDepth) / 25
|
|
52
|
+
: 0),
|
|
53
|
+
}))
|
|
54
|
+
.filter((p) => p.score > 0)
|
|
55
|
+
.sort((a, b) => b.score - a.score)
|
|
56
|
+
.slice(0, 6);
|
|
57
|
+
if (worst.length) {
|
|
58
|
+
lines.push("PAGES WITH FRICTION (worst first):");
|
|
59
|
+
for (const p of worst) {
|
|
60
|
+
lines.push(` - ${p.path}: ${p.sessions} sessions, ${p.rageClicks} rage, ` +
|
|
61
|
+
`${p.deadClicks} dead, ${p.jsErrors} err, scroll ${r(p.avgScrollDepth)}%`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const brokenEls = elements.filter((e) => e.rageClicks > 0 || e.deadClicks > 0).slice(0, 8);
|
|
65
|
+
if (brokenEls.length) {
|
|
66
|
+
lines.push("ELEMENTS USERS STRUGGLE WITH:");
|
|
67
|
+
for (const e of brokenEls) {
|
|
68
|
+
const label = clip(e.sampleText) || e.label;
|
|
69
|
+
lines.push(` - ${label}: ${e.rageClicks} rage, ${e.deadClicks} dead, ${e.pages} pages`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (monitored.length) {
|
|
73
|
+
lines.push("MONITORED COMPONENTS (engagement):");
|
|
74
|
+
for (const m of monitored.slice(0, 6)) {
|
|
75
|
+
const parts = [`${m.componentViews} views`];
|
|
76
|
+
if (m.avgViewMs !== null)
|
|
77
|
+
parts.push(`avg ${Math.round(m.avgViewMs / 100) / 10}s visible`);
|
|
78
|
+
if (m.avgScrollDepth !== null)
|
|
79
|
+
parts.push(`${m.avgScrollDepth}% scrolled`);
|
|
80
|
+
if (m.rageClicks > 0)
|
|
81
|
+
parts.push(`${m.rageClicks} rage`);
|
|
82
|
+
lines.push(` - ${m.name}: ${parts.join(", ")}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (errors.length) {
|
|
86
|
+
// Dedupe by message so a single recurring error doesn't fill the section.
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const uniq = [];
|
|
89
|
+
for (const e of errors) {
|
|
90
|
+
if (seen.has(e.message))
|
|
91
|
+
continue;
|
|
92
|
+
seen.add(e.message);
|
|
93
|
+
uniq.push(e);
|
|
94
|
+
if (uniq.length >= 5)
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
lines.push("RECENT ERRORS:");
|
|
98
|
+
for (const e of uniq) {
|
|
99
|
+
lines.push(` - "${clip(e.message, 80)}" on ${e.path}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
/** Stable fingerprint of a digest — drives the "data unchanged" short-circuit. */
|
|
105
|
+
export function fingerprintDigest(digest) {
|
|
106
|
+
return createHash("sha256").update(digest).digest("hex").slice(0, 16);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=digest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../../src/ai/digest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { PolishdGithubStatus, VerifyGithubResult } from "./types";
|
|
2
|
+
/** True when both a repo and a token are configured. */
|
|
3
|
+
export declare function isGithubConnected(): Promise<boolean>;
|
|
4
|
+
/**
|
|
5
|
+
* Check the saved token against the saved repo: does it exist, can we read it,
|
|
6
|
+
* can we push (branches/PRs), are issues enabled? Called right after the owner
|
|
7
|
+
* saves the connection so bad tokens fail at setup, not at first use.
|
|
8
|
+
*/
|
|
9
|
+
export declare function verifyGithubConnection(): Promise<VerifyGithubResult>;
|
|
10
|
+
/** The verified connection status, or null when not connected / unreachable. */
|
|
11
|
+
export declare function getGithubStatus(): Promise<PolishdGithubStatus | null>;
|
|
12
|
+
/**
|
|
13
|
+
* Read one file from the connected repo (default branch unless `ref` given).
|
|
14
|
+
* Returns null when the path doesn't exist or nothing is connected — callers
|
|
15
|
+
* treat GitHub as a fallback source tree, not a hard dependency.
|
|
16
|
+
*/
|
|
17
|
+
export declare function readGithubFile(path: string, ref?: string): Promise<string | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Find files in the connected repo whose content matches the given terms
|
|
20
|
+
* (ANDed), via GitHub's code search. Returns file paths, best matches first,
|
|
21
|
+
* or [] on any failure — search is an enrichment, never a hard dependency.
|
|
22
|
+
*/
|
|
23
|
+
export declare function searchGithubCode(terms: string[], max?: number): Promise<string[]>;
|
|
24
|
+
/**
|
|
25
|
+
* Create a branch off the repo's default branch (or `from`). Returns the new
|
|
26
|
+
* branch name. Throws on failure — callers surface GitHub's message.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createGithubBranch(name: string, from?: string): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* Open a pull request from `head` into `base` (default branch when omitted).
|
|
31
|
+
* Returns the PR's URL and number. Throws on failure.
|
|
32
|
+
*/
|
|
33
|
+
export declare function createGithubPullRequest(input: {
|
|
34
|
+
title: string;
|
|
35
|
+
body?: string;
|
|
36
|
+
head: string;
|
|
37
|
+
base?: string;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
url: string;
|
|
40
|
+
number: number;
|
|
41
|
+
}>;
|
|
42
|
+
/** File an issue in the connected repo. Throws on failure. */
|
|
43
|
+
export declare function createGithubIssue(input: {
|
|
44
|
+
title: string;
|
|
45
|
+
body?: string;
|
|
46
|
+
labels?: string[];
|
|
47
|
+
}): Promise<{
|
|
48
|
+
url: string;
|
|
49
|
+
number: number;
|
|
50
|
+
}>;
|
|
51
|
+
//# sourceMappingURL=github.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../src/ai/github.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAavE,wDAAwD;AACxD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AA0CD;;;;GAIG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CA2B1E;AAED,gFAAgF;AAChF,wBAAsB,eAAe,IAAI,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAG3E;AAID;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAcxB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,SAAI,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAclF;AAID;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CAgBjB;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAa3C;AAID,8DAA8D;AAC9D,wBAAsB,iBAAiB,CAAC,KAAK,EAAE;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAU3C"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — GitHub API client (server only).
|
|
3
|
+
*
|
|
4
|
+
* A thin client over GitHub's REST API for the repo the owner connected during
|
|
5
|
+
* onboarding. Capabilities, all scoped to that one repo: read and search source
|
|
6
|
+
* (Contents + code-search APIs — lets the AI layer ground its findings in real
|
|
7
|
+
* code even where the source tree isn't on disk, e.g. Vercel), create branches,
|
|
8
|
+
* open pull requests, and file issues. The "loss → verified bug" orchestration
|
|
9
|
+
* that uses this client lives in issues.ts.
|
|
10
|
+
*
|
|
11
|
+
* The token lives in the same server-side settings blob as the model API key
|
|
12
|
+
* and never reaches the browser; the client only sees a "connected" flag. A
|
|
13
|
+
* fine-grained PAT with Contents (read), Issues (write), and Pull requests
|
|
14
|
+
* (write) on the one repo is all it needs.
|
|
15
|
+
*/
|
|
16
|
+
import { resolveSettings } from "./settings";
|
|
17
|
+
const API = "https://api.github.com";
|
|
18
|
+
/** The connected repo + token, or null when either half is missing. */
|
|
19
|
+
async function connection() {
|
|
20
|
+
const { settings } = await resolveSettings();
|
|
21
|
+
const repo = settings.githubRepo?.trim();
|
|
22
|
+
const token = settings.githubToken?.trim();
|
|
23
|
+
if (!repo || !token)
|
|
24
|
+
return null;
|
|
25
|
+
return { repo, token };
|
|
26
|
+
}
|
|
27
|
+
/** True when both a repo and a token are configured. */
|
|
28
|
+
export async function isGithubConnected() {
|
|
29
|
+
return (await connection()) !== null;
|
|
30
|
+
}
|
|
31
|
+
/** One GitHub API call. Throws with GitHub's own message on non-2xx. */
|
|
32
|
+
async function gh(token, method, path, body) {
|
|
33
|
+
const res = await fetch(`${API}${path}`, {
|
|
34
|
+
method,
|
|
35
|
+
headers: {
|
|
36
|
+
Authorization: `Bearer ${token}`,
|
|
37
|
+
Accept: "application/vnd.github+json",
|
|
38
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
39
|
+
...(body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
40
|
+
},
|
|
41
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
42
|
+
cache: "no-store",
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
let message = `GitHub responded ${res.status}`;
|
|
46
|
+
try {
|
|
47
|
+
const err = (await res.json());
|
|
48
|
+
if (err.message)
|
|
49
|
+
message = `GitHub: ${err.message} (${res.status})`;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
/* keep the status-only message */
|
|
53
|
+
}
|
|
54
|
+
throw new Error(message);
|
|
55
|
+
}
|
|
56
|
+
return (await res.json());
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Check the saved token against the saved repo: does it exist, can we read it,
|
|
60
|
+
* can we push (branches/PRs), are issues enabled? Called right after the owner
|
|
61
|
+
* saves the connection so bad tokens fail at setup, not at first use.
|
|
62
|
+
*/
|
|
63
|
+
export async function verifyGithubConnection() {
|
|
64
|
+
const conn = await connection();
|
|
65
|
+
if (!conn) {
|
|
66
|
+
return {
|
|
67
|
+
ok: false,
|
|
68
|
+
error: "not-connected",
|
|
69
|
+
message: "Add a repository and access token first.",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const repo = await gh(conn.token, "GET", `/repos/${conn.repo}`);
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
status: {
|
|
77
|
+
repo: repo.full_name,
|
|
78
|
+
defaultBranch: repo.default_branch,
|
|
79
|
+
canPush: repo.permissions?.push === true,
|
|
80
|
+
issuesEnabled: repo.has_issues,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
error: "github-error",
|
|
88
|
+
message: err instanceof Error ? err.message : "Could not reach GitHub.",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/** The verified connection status, or null when not connected / unreachable. */
|
|
93
|
+
export async function getGithubStatus() {
|
|
94
|
+
const res = await verifyGithubConnection();
|
|
95
|
+
return res.ok ? res.status : null;
|
|
96
|
+
}
|
|
97
|
+
// ── Read source ──────────────────────────────────────────────────────────────
|
|
98
|
+
/**
|
|
99
|
+
* Read one file from the connected repo (default branch unless `ref` given).
|
|
100
|
+
* Returns null when the path doesn't exist or nothing is connected — callers
|
|
101
|
+
* treat GitHub as a fallback source tree, not a hard dependency.
|
|
102
|
+
*/
|
|
103
|
+
export async function readGithubFile(path, ref) {
|
|
104
|
+
const conn = await connection();
|
|
105
|
+
if (!conn)
|
|
106
|
+
return null;
|
|
107
|
+
try {
|
|
108
|
+
const file = await gh(conn.token, "GET", `/repos/${conn.repo}/contents/${encodeURI(path)}${ref ? `?ref=${encodeURIComponent(ref)}` : ""}`);
|
|
109
|
+
if (file.encoding !== "base64" || typeof file.content !== "string")
|
|
110
|
+
return null;
|
|
111
|
+
return Buffer.from(file.content, "base64").toString("utf8");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Find files in the connected repo whose content matches the given terms
|
|
119
|
+
* (ANDed), via GitHub's code search. Returns file paths, best matches first,
|
|
120
|
+
* or [] on any failure — search is an enrichment, never a hard dependency.
|
|
121
|
+
*/
|
|
122
|
+
export async function searchGithubCode(terms, max = 3) {
|
|
123
|
+
const conn = await connection();
|
|
124
|
+
if (!conn || terms.length === 0)
|
|
125
|
+
return [];
|
|
126
|
+
try {
|
|
127
|
+
const q = encodeURIComponent(`${terms.join(" ")} repo:${conn.repo}`);
|
|
128
|
+
const res = await gh(conn.token, "GET", `/search/code?q=${q}&per_page=${max}`);
|
|
129
|
+
return (res.items ?? []).map((i) => i.path).slice(0, max);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// ── Branches & pull requests ─────────────────────────────────────────────────
|
|
136
|
+
/**
|
|
137
|
+
* Create a branch off the repo's default branch (or `from`). Returns the new
|
|
138
|
+
* branch name. Throws on failure — callers surface GitHub's message.
|
|
139
|
+
*/
|
|
140
|
+
export async function createGithubBranch(name, from) {
|
|
141
|
+
const conn = await connection();
|
|
142
|
+
if (!conn)
|
|
143
|
+
throw new Error("GitHub is not connected.");
|
|
144
|
+
const base = from ??
|
|
145
|
+
(await gh(conn.token, "GET", `/repos/${conn.repo}`)).default_branch;
|
|
146
|
+
const ref = await gh(conn.token, "GET", `/repos/${conn.repo}/git/ref/heads/${encodeURIComponent(base)}`);
|
|
147
|
+
await gh(conn.token, "POST", `/repos/${conn.repo}/git/refs`, {
|
|
148
|
+
ref: `refs/heads/${name}`,
|
|
149
|
+
sha: ref.object.sha,
|
|
150
|
+
});
|
|
151
|
+
return name;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Open a pull request from `head` into `base` (default branch when omitted).
|
|
155
|
+
* Returns the PR's URL and number. Throws on failure.
|
|
156
|
+
*/
|
|
157
|
+
export async function createGithubPullRequest(input) {
|
|
158
|
+
const conn = await connection();
|
|
159
|
+
if (!conn)
|
|
160
|
+
throw new Error("GitHub is not connected.");
|
|
161
|
+
const base = input.base ??
|
|
162
|
+
(await gh(conn.token, "GET", `/repos/${conn.repo}`)).default_branch;
|
|
163
|
+
const pr = await gh(conn.token, "POST", `/repos/${conn.repo}/pulls`, { title: input.title, body: input.body ?? "", head: input.head, base });
|
|
164
|
+
return { url: pr.html_url, number: pr.number };
|
|
165
|
+
}
|
|
166
|
+
// ── Issues ───────────────────────────────────────────────────────────────────
|
|
167
|
+
/** File an issue in the connected repo. Throws on failure. */
|
|
168
|
+
export async function createGithubIssue(input) {
|
|
169
|
+
const conn = await connection();
|
|
170
|
+
if (!conn)
|
|
171
|
+
throw new Error("GitHub is not connected.");
|
|
172
|
+
const issue = await gh(conn.token, "POST", `/repos/${conn.repo}/issues`, { title: input.title, body: input.body ?? "", labels: input.labels });
|
|
173
|
+
return { url: issue.html_url, number: issue.number };
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=github.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github.js","sourceRoot":"","sources":["../../src/ai/github.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — server-action authorization (server only).
|
|
3
|
+
*
|
|
4
|
+
* Next.js server actions are independently addressable POST endpoints: their
|
|
5
|
+
* ids ship inside the public client bundle, so anyone can invoke one directly
|
|
6
|
+
* with a `Next-Action` header. Gating the dashboard *page* therefore protects
|
|
7
|
+
* only what renders — it does nothing for the actions behind it. Every action
|
|
8
|
+
* this package exports must `await requirePolishdAuth()` before doing any work.
|
|
9
|
+
*
|
|
10
|
+
* `createPolishdPage()` records the host app's `authenticate` callback here when
|
|
11
|
+
* the page module is evaluated, and the actions re-run it on each request. The
|
|
12
|
+
* check is therefore made against the caller's own cookies every time; nothing
|
|
13
|
+
* about the decision is trusted from the client.
|
|
14
|
+
*
|
|
15
|
+
* Fails closed: an action reached before any dashboard page has registered a
|
|
16
|
+
* policy is denied.
|
|
17
|
+
*/
|
|
18
|
+
export type PolishdAuthPolicy =
|
|
19
|
+
/** `createPolishdPage()` with no `authenticate` — deliberately public. */
|
|
20
|
+
{
|
|
21
|
+
mode: "open";
|
|
22
|
+
}
|
|
23
|
+
/** `createPolishdPage({ authenticate })` — re-checked per action call. */
|
|
24
|
+
| {
|
|
25
|
+
mode: "guarded";
|
|
26
|
+
authenticate: () => boolean | Promise<boolean>;
|
|
27
|
+
};
|
|
28
|
+
/** Record the dashboard's access policy. Called by `createPolishdPage()`. */
|
|
29
|
+
export declare function registerPolishdAuth(next: PolishdAuthPolicy): void;
|
|
30
|
+
/**
|
|
31
|
+
* Thrown when an action is invoked without authorization. Next replaces the
|
|
32
|
+
* message with an opaque digest in production, so nothing leaks to the caller.
|
|
33
|
+
*/
|
|
34
|
+
export declare class PolishdUnauthorizedError extends Error {
|
|
35
|
+
constructor();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Authorize the current server-action request, or throw. A callback that
|
|
39
|
+
* itself throws is treated as a denial — an auth helper blowing up must never
|
|
40
|
+
* open the gate.
|
|
41
|
+
*/
|
|
42
|
+
export declare function requirePolishdAuth(): Promise<void>;
|
|
43
|
+
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/ai/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,MAAM,iBAAiB;AAC3B,0EAA0E;AACxE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,0EAA0E;GACxE;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,YAAY,EAAE,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC;AAIxE,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,IAAI,CAEjE;AAED;;;GAGG;AACH,qBAAa,wBAAyB,SAAQ,KAAK;;CAKlD;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBxD"}
|
package/dist/ai/guard.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Polishd — server-action authorization (server only).
|
|
3
|
+
*
|
|
4
|
+
* Next.js server actions are independently addressable POST endpoints: their
|
|
5
|
+
* ids ship inside the public client bundle, so anyone can invoke one directly
|
|
6
|
+
* with a `Next-Action` header. Gating the dashboard *page* therefore protects
|
|
7
|
+
* only what renders — it does nothing for the actions behind it. Every action
|
|
8
|
+
* this package exports must `await requirePolishdAuth()` before doing any work.
|
|
9
|
+
*
|
|
10
|
+
* `createPolishdPage()` records the host app's `authenticate` callback here when
|
|
11
|
+
* the page module is evaluated, and the actions re-run it on each request. The
|
|
12
|
+
* check is therefore made against the caller's own cookies every time; nothing
|
|
13
|
+
* about the decision is trusted from the client.
|
|
14
|
+
*
|
|
15
|
+
* Fails closed: an action reached before any dashboard page has registered a
|
|
16
|
+
* policy is denied.
|
|
17
|
+
*/
|
|
18
|
+
let policy = null;
|
|
19
|
+
/** Record the dashboard's access policy. Called by `createPolishdPage()`. */
|
|
20
|
+
export function registerPolishdAuth(next) {
|
|
21
|
+
policy = next;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when an action is invoked without authorization. Next replaces the
|
|
25
|
+
* message with an opaque digest in production, so nothing leaks to the caller.
|
|
26
|
+
*/
|
|
27
|
+
export class PolishdUnauthorizedError extends Error {
|
|
28
|
+
constructor() {
|
|
29
|
+
super("[polishd] unauthorized");
|
|
30
|
+
this.name = "PolishdUnauthorizedError";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Authorize the current server-action request, or throw. A callback that
|
|
35
|
+
* itself throws is treated as a denial — an auth helper blowing up must never
|
|
36
|
+
* open the gate.
|
|
37
|
+
*/
|
|
38
|
+
export async function requirePolishdAuth() {
|
|
39
|
+
// No dashboard page registered a policy in this runtime: deny rather than
|
|
40
|
+
// guess. Reaching an action without the page module loaded is not a flow the
|
|
41
|
+
// dashboard produces.
|
|
42
|
+
if (!policy)
|
|
43
|
+
throw new PolishdUnauthorizedError();
|
|
44
|
+
if (policy.mode === "open")
|
|
45
|
+
return;
|
|
46
|
+
let ok = false;
|
|
47
|
+
try {
|
|
48
|
+
ok = await policy.authenticate();
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.warn("[polishd] authenticate() threw during an action call, denying:", err instanceof Error ? err.message : err);
|
|
52
|
+
ok = false;
|
|
53
|
+
}
|
|
54
|
+
if (!ok)
|
|
55
|
+
throw new PolishdUnauthorizedError();
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/ai/guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,IAAI,MAAM,GAA6B,IAAI,CAAC;AAE5C,6EAA6E;AAC7E,MAAM,UAAU,mBAAmB,CAAC,IAAuB;IACzD,MAAM,GAAG,IAAI,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,wBAAyB,SAAQ,KAAK;IACjD;QACE,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,0BAA0B,CAAC;IACzC,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,0EAA0E;IAC1E,6EAA6E;IAC7E,sBAAsB;IACtB,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,wBAAwB,EAAE,CAAC;IAClD,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO;IAEnC,IAAI,EAAE,GAAG,KAAK,CAAC;IACf,IAAI,CAAC;QACH,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,gEAAgE,EAChE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CACzC,CAAC;QACF,EAAE,GAAG,KAAK,CAAC;IACb,CAAC;IACD,IAAI,CAAC,EAAE;QAAE,MAAM,IAAI,wBAAwB,EAAE,CAAC;AAChD,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PolishdLossItem, CreateIssueResult } from "./types";
|
|
2
|
+
/** The slice of a loss the pipeline needs. */
|
|
3
|
+
type Loss = Pick<PolishdLossItem, "issue" | "evidence" | "location">;
|
|
4
|
+
/**
|
|
5
|
+
* File a bug from one loss — after verifying it against the source (see the
|
|
6
|
+
* module doc for the pipeline). Returns the issue on success, the stored issue
|
|
7
|
+
* when this evidence was already filed, or `not-a-bug` with the verdict's
|
|
8
|
+
* reasoning when the code disproves the report.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createIssueFromLoss(loss: Loss): Promise<CreateIssueResult>;
|
|
11
|
+
/**
|
|
12
|
+
* Decorate a fresh summary's losses with their GitHub issues: attach the link
|
|
13
|
+
* for anything already filed and — when the owner enabled auto-filing — run
|
|
14
|
+
* the verify-and-file pipeline on the new ones. Losses whose report the code
|
|
15
|
+
* disproved are skipped (and remembered, so they aren't re-investigated).
|
|
16
|
+
* Never throws; a hiccup just leaves a loss undecorated, with the manual
|
|
17
|
+
* "file bug" button as the fallback.
|
|
18
|
+
*/
|
|
19
|
+
export declare function attachGithubIssues(losses: PolishdLossItem[]): Promise<PolishdLossItem[]>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=issues.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../src/ai/issues.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAElE,8CAA8C;AAC9C,KAAK,IAAI,GAAG,IAAI,CAAC,eAAe,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC,CAAC;AAyPrE;;;;;GAKG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA4ChF;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAoB9F"}
|