pierre-review 0.1.14 → 0.1.16
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/dist/api/routes/me.js +9 -1
- package/dist/api/routes/repos.js +16 -0
- package/dist/app.js +33 -0
- package/dist/config.js +5 -0
- package/dist/db/migrations/0011_pr_diff_size.sql +10 -0
- package/dist/db/migrations/0012_findings_included_default.sql +7 -0
- package/dist/db/migrations/meta/_journal.json +14 -0
- package/dist/db/migrations-pg/0002_glorious_ikaris.sql +4 -0
- package/dist/db/migrations-pg/meta/0002_snapshot.json +2170 -0
- package/dist/db/migrations-pg/meta/_journal.json +7 -0
- package/dist/db/queries.js +150 -0
- package/dist/db/schema.pg.js +9 -0
- package/dist/db/schema.sqlite.js +9 -0
- package/dist/github/queries.js +20 -0
- package/dist/review/persist.js +4 -0
- package/dist/sync/hydrate-detail.js +25 -0
- package/dist/sync/upsert.js +19 -0
- package/package.json +1 -1
- package/public/assets/{index-Bfko6Dwb.js → index-CuAtfSS-.js} +83 -83
- package/public/assets/index-D6yIjMh5.css +10 -0
- package/public/index.html +2 -2
- package/public/assets/index-DqMYNa1t.css +0 -10
package/dist/api/routes/me.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { config } from '../../config.js';
|
|
2
2
|
import { accountToLocalUser } from '../../auth/account.js';
|
|
3
3
|
import { accountIdOf } from '../plugins/auth.js';
|
|
4
|
-
import { dismissMyTurn, getMyTurn } from '../../db/queries.js';
|
|
4
|
+
import { dismissMyTurn, getCompletedDismissals, getMyTurn, undismissMyTurn, } from '../../db/queries.js';
|
|
5
5
|
const dismissSchema = {
|
|
6
6
|
body: {
|
|
7
7
|
type: 'object',
|
|
@@ -34,5 +34,13 @@ export async function meRoutes(app) {
|
|
|
34
34
|
await dismissMyTurn(accountIdOf(req), kind, refId);
|
|
35
35
|
return { status: 'ok' };
|
|
36
36
|
});
|
|
37
|
+
// The "Done" tab: entries dismissed in the past 90 days (review_request + thread).
|
|
38
|
+
app.get('/api/my-turn/done', async (req) => getCompletedDismissals(accountIdOf(req), 90));
|
|
39
|
+
// Un-dismiss: move a completed entry back to the inbox.
|
|
40
|
+
app.post('/api/my-turn/undismiss', { schema: dismissSchema }, async (req) => {
|
|
41
|
+
const { kind, refId } = req.body;
|
|
42
|
+
await undismissMyTurn(accountIdOf(req), kind, refId);
|
|
43
|
+
return { status: 'ok' };
|
|
44
|
+
});
|
|
37
45
|
}
|
|
38
46
|
//# sourceMappingURL=me.js.map
|
package/dist/api/routes/repos.js
CHANGED
|
@@ -5,6 +5,10 @@ import { upsertRepo } from '../../sync/upsert.js';
|
|
|
5
5
|
import { getSyncStatus, isSyncRunning, requestSyncCancel, runSyncForRepo, waitForSyncToStop, } from '../../sync/sync-manager.js';
|
|
6
6
|
import { deleteRepo, getRepo, getWatchedRepoNodeIds, listRepos, } from '../../db/queries.js';
|
|
7
7
|
import { accountIdOf } from '../plugins/auth.js';
|
|
8
|
+
// Local copy of the shared MAX_REPOS_PER_ACCOUNT value. `@pierre-review/shared` is
|
|
9
|
+
// a types-only package (not shipped in the published tarball), so the backend must
|
|
10
|
+
// only `import type` from it — runtime constants are duplicated here. Keep in sync.
|
|
11
|
+
const MAX_REPOS_PER_ACCOUNT = 15;
|
|
8
12
|
const createRepoSchema = {
|
|
9
13
|
body: {
|
|
10
14
|
type: 'object',
|
|
@@ -131,6 +135,18 @@ export async function repoRoutes(app) {
|
|
|
131
135
|
message: `Repository ${owner}/${name} not found or inaccessible. Check the name and your gh auth / SSO.`,
|
|
132
136
|
};
|
|
133
137
|
}
|
|
138
|
+
// Enforce the per-account repo cap. Re-adding an already-watched repo is an
|
|
139
|
+
// idempotent no-op (it doesn't grow the count), so only genuinely NEW repos
|
|
140
|
+
// are blocked once at the limit.
|
|
141
|
+
const watched = await getWatchedRepoNodeIds(accountId);
|
|
142
|
+
if (watched.size >= MAX_REPOS_PER_ACCOUNT &&
|
|
143
|
+
!watched.has(resp.repository.id)) {
|
|
144
|
+
reply.status(409);
|
|
145
|
+
return {
|
|
146
|
+
error: 'RepoLimitExceeded',
|
|
147
|
+
message: `You can watch at most ${MAX_REPOS_PER_ACCOUNT} repositories. Remove one to add another.`,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
134
150
|
const canonOwner = resp.repository.owner.login;
|
|
135
151
|
const canonName = resp.repository.name;
|
|
136
152
|
const repoId = await upsertRepo(canonOwner, canonName, resp.repository.id, null, accountId);
|
package/dist/app.js
CHANGED
|
@@ -30,6 +30,39 @@ export async function buildApp() {
|
|
|
30
30
|
: { target: 'pino-pretty', options: { translateTime: 'HH:MM:ss' } },
|
|
31
31
|
},
|
|
32
32
|
});
|
|
33
|
+
// Cloud only: canonicalise the host and pin HTTPS. Local mode runs on
|
|
34
|
+
// http://127.0.0.1, where HSTS would wrongly pin localhost to HTTPS and a www
|
|
35
|
+
// redirect is meaningless — so both are cloud-gated. Registered first so it runs
|
|
36
|
+
// before everything else (the redirect short-circuits before CORS/routing).
|
|
37
|
+
if (config.isCloud) {
|
|
38
|
+
let canonicalHost = '';
|
|
39
|
+
try {
|
|
40
|
+
// hostname (not host) so a configured port never enters the comparison.
|
|
41
|
+
canonicalHost = new URL(config.appBaseUrl).hostname.toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
canonicalHost = '';
|
|
45
|
+
}
|
|
46
|
+
app.addHook('onRequest', async (req, reply) => {
|
|
47
|
+
// HSTS: keep browsers on HTTPS for this domain. Honored only over HTTPS
|
|
48
|
+
// (Railway terminates TLS); ignored on plain HTTP. `includeSubDomains` also
|
|
49
|
+
// covers www. No `preload` — it's hard to undo. HSTS_MAX_AGE=0 disables it.
|
|
50
|
+
if (config.hstsMaxAge > 0) {
|
|
51
|
+
reply.header('Strict-Transport-Security', `max-age=${config.hstsMaxAge}; includeSubDomains`);
|
|
52
|
+
}
|
|
53
|
+
// Canonical host: 301 www.<apex> → <apex> so the OAuth round-trip and the
|
|
54
|
+
// session cookie stay on a single origin (and crawlers see one canonical
|
|
55
|
+
// URL). The HSTS header set above still rides on the redirect response.
|
|
56
|
+
if (canonicalHost) {
|
|
57
|
+
// Strip any :port from the Host header before comparing hostnames.
|
|
58
|
+
const host = (req.headers.host ?? '').toLowerCase().split(':')[0] ?? '';
|
|
59
|
+
if (host !== canonicalHost &&
|
|
60
|
+
host.replace(/^www\./, '') === canonicalHost) {
|
|
61
|
+
return reply.redirect(`${config.appBaseUrl}${req.url}`, 301);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
33
66
|
// CORS: in cloud mode lock to the app's own origin and allow credentials so the
|
|
34
67
|
// session cookie rides along; local mode reflects any origin (dev convenience).
|
|
35
68
|
await app.register(cors, {
|
package/dist/config.js
CHANGED
|
@@ -96,6 +96,11 @@ export const config = {
|
|
|
96
96
|
sessionSecret: process.env.SESSION_SECRET ?? '',
|
|
97
97
|
// 32-byte (64-hex) key for AES-256-GCM encryption of stored access tokens.
|
|
98
98
|
encryptionKey: process.env.ENCRYPTION_KEY ?? '',
|
|
99
|
+
// HSTS max-age (seconds) for the cloud public origin. Sent only in cloud mode
|
|
100
|
+
// and honored only over HTTPS (Railway terminates TLS). Default 1 year. Set
|
|
101
|
+
// HSTS_MAX_AGE=0 as a kill switch. `preload` is intentionally NOT sent — it is
|
|
102
|
+
// hard to undo; opt in manually once the domain is proven.
|
|
103
|
+
hstsMaxAge: intFromEnv('HSTS_MAX_AGE', 31536000),
|
|
99
104
|
// ---- Claude Review (agentic PR review; opt-in, LOCAL-ONLY) ----
|
|
100
105
|
// OFF by default: the feature spends real money / Agent-SDK credits per run.
|
|
101
106
|
// Enable with ENABLE_CLAUDE_REVIEW=true. FORCE-DISABLED in cloud mode (it
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
-- PR diff-size metadata (additive). Adds the GraphQL additions/deletions/
|
|
2
|
+
-- changedFiles totals and the per-file breakdown (files JSON) to pull_requests,
|
|
3
|
+
-- powering the PR-detail LOC summary label and the new "Changes" tab. This is
|
|
4
|
+
-- small metadata (not bulky user text), so it's stored in BOTH modes regardless
|
|
5
|
+
-- of lean storage. Existing rows get the 0 defaults / NULL files until the next
|
|
6
|
+
-- sync backfills real values.
|
|
7
|
+
ALTER TABLE `pull_requests` ADD `additions` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
|
8
|
+
ALTER TABLE `pull_requests` ADD `deletions` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
|
9
|
+
ALTER TABLE `pull_requests` ADD `changed_files` integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
|
10
|
+
ALTER TABLE `pull_requests` ADD `files` text;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Claude Review findings are now INCLUDED by default (the UI is opt-OUT via an
|
|
2
|
+
-- "Ignore" button, replacing the old opt-IN "include" checkbox). New findings are
|
|
3
|
+
-- inserted with included=1 (see review/persist.ts); this backfills existing rows so
|
|
4
|
+
-- already-generated reviews show their findings as included rather than ignored.
|
|
5
|
+
-- SQLite-only: Claude Review is force-disabled in cloud, so the Postgres
|
|
6
|
+
-- claude_review_findings table is never populated.
|
|
7
|
+
UPDATE `claude_review_findings` SET `included` = 1 WHERE `included` = 0;
|
|
@@ -78,6 +78,20 @@
|
|
|
78
78
|
"when": 1780800000001,
|
|
79
79
|
"tag": "0010_lean_bodies_nullable",
|
|
80
80
|
"breakpoints": true
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"idx": 11,
|
|
84
|
+
"version": "6",
|
|
85
|
+
"when": 1780800000002,
|
|
86
|
+
"tag": "0011_pr_diff_size",
|
|
87
|
+
"breakpoints": true
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"idx": 12,
|
|
91
|
+
"version": "6",
|
|
92
|
+
"when": 1780800000003,
|
|
93
|
+
"tag": "0012_findings_included_default",
|
|
94
|
+
"breakpoints": true
|
|
81
95
|
}
|
|
82
96
|
]
|
|
83
97
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
ALTER TABLE "pull_requests" ADD COLUMN "additions" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
|
2
|
+
ALTER TABLE "pull_requests" ADD COLUMN "deletions" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
|
3
|
+
ALTER TABLE "pull_requests" ADD COLUMN "changed_files" integer DEFAULT 0 NOT NULL;--> statement-breakpoint
|
|
4
|
+
ALTER TABLE "pull_requests" ADD COLUMN "files" jsonb;
|