co-maintainer 0.5.1 → 0.5.3

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 (36) hide show
  1. package/dist/package.json +2 -4
  2. package/dist/src/ai/anthropic.js +6 -1
  3. package/dist/src/ai/openai.js +18 -19
  4. package/dist/src/ai/openrouter.js +13 -19
  5. package/dist/src/ai/provider.d.ts +9 -0
  6. package/dist/src/ai/provider.js +29 -1
  7. package/dist/src/cli/commands/clear.d.ts +1 -0
  8. package/dist/src/cli/commands/clear.js +152 -0
  9. package/dist/src/cli/commands/registry.d.ts +4 -0
  10. package/dist/src/cli/commands/registry.js +48 -0
  11. package/dist/src/cli/commands/uninstall.d.ts +1 -0
  12. package/dist/src/cli/commands/uninstall.js +43 -0
  13. package/dist/src/cli/main.js +10 -0
  14. package/dist/src/config.d.ts +14 -0
  15. package/dist/src/config.js +38 -1
  16. package/dist/src/knowledge/synthesis.js +3 -0
  17. package/dist/src/local/review_lock.js +2 -2
  18. package/dist/src/server/api/knowledge.d.ts +7 -0
  19. package/dist/src/server/api/knowledge.js +68 -0
  20. package/dist/src/server/api/repos.js +5 -0
  21. package/dist/src/server/app.js +4 -0
  22. package/dist/src/server/pages/client.d.ts +1 -1
  23. package/dist/src/server/pages/client.js +1 -1
  24. package/dist/src/server/pages/knowledge.js +20 -1
  25. package/dist/src/server/pages/settings.js +18 -0
  26. package/dist/src/services/clear.d.ts +70 -0
  27. package/dist/src/services/clear.js +310 -0
  28. package/dist/src/store/app_db.d.ts +3 -0
  29. package/dist/src/store/app_db.js +5 -0
  30. package/dist/src/store/cache_db.d.ts +11 -0
  31. package/dist/src/store/cache_db.js +60 -1
  32. package/dist/src/store/repos.d.ts +3 -0
  33. package/dist/src/store/repos.js +8 -0
  34. package/dist/src/util/runtime.d.ts +3 -0
  35. package/dist/src/util/runtime.js +6 -1
  36. package/package.json +2 -4
@@ -17,6 +17,11 @@ export function appDbPath() {
17
17
  function lockPath() {
18
18
  return `${appDbPath()}.lock`;
19
19
  }
20
+ /** The lock file on its own, so `uninstall` deletes it with the database
21
+ * instead of leaving a pid file that names a process that no longer exists. */
22
+ export function appDbLockPath() {
23
+ return lockPath();
24
+ }
20
25
  /** Where the backup taken before a migration lives, and what it covers. */
21
26
  export function backupPaths() {
22
27
  return {
@@ -2,6 +2,17 @@ export declare function cacheGet(namespace: string, key: string): Promise<string
2
2
  export declare function cacheSet(namespace: string, key: string, value: string): Promise<void>;
3
3
  export declare function cacheDelete(namespace: string, key: string): Promise<void>;
4
4
  export declare function cacheDeletePrefix(namespace: string, prefix: string): Promise<void>;
5
+ /** Whether cache.db exists, so a `clear` that has nothing to delete does not
6
+ * create one just to close it again. */
7
+ export declare function cacheDbExists(): Promise<boolean>;
8
+ /** The skill state is the repository's own knowledge, so `clear` always drops
9
+ * it: without it the next build starts from `init` rather than `sync`. */
10
+ export declare function cacheDeleteRepoKnowledge(repo: string): Promise<number>;
11
+ /** Everything `init` fetched or paid for, kept unless the caller asked for a
12
+ * cold next build. The global `pricing` cache stays either way. Keys live in
13
+ * three shapes: an exact repository key, `${repo}:...`, and the local review
14
+ * carry-over's NUL separated `${repo}\0...`. */
15
+ export declare function cacheDeleteRepoEvidence(repo: string): Promise<number>;
5
16
  /** Every value under `namespace` whose key starts with `prefix`, newest first.
6
17
  * `recordAiCost` keys each job `${repo}:${uuid}`, so the probe estimate reads
7
18
  * a repository's own job history without a second index to keep in sync. */
@@ -1,6 +1,6 @@
1
1
  import { Database } from "./sqlite.js";
2
2
  import { cacheDbPath, getCacheDir } from "../config.js";
3
- import { mkdirSync } from "../util/runtime.js";
3
+ import { isNotFound, mkdirSync, stat } from "../util/runtime.js";
4
4
  /** Paths whose schema this process has already created. Keyed by path rather
5
5
  * than a single boolean: `cacheDbPath()` reads the environment at call time
6
6
  * and tests point it at a temp directory, so a process-level flag would skip
@@ -67,6 +67,65 @@ export async function cacheDeletePrefix(namespace, prefix) {
67
67
  db.close();
68
68
  }
69
69
  }
70
+ /** Whether cache.db exists, so a `clear` that has nothing to delete does not
71
+ * create one just to close it again. */
72
+ export async function cacheDbExists() {
73
+ try {
74
+ await stat(cacheDbPath());
75
+ return true;
76
+ }
77
+ catch (error) {
78
+ if (isNotFound(error))
79
+ return false;
80
+ throw error;
81
+ }
82
+ }
83
+ /** Escapes LIKE's own wildcards and the escape character. A repository name
84
+ * may contain `_`, which LIKE would otherwise read as "any character". */
85
+ function likePrefix(prefix) {
86
+ return `${prefix.replace(/[\\%_]/g, "\\$&")}%`;
87
+ }
88
+ function deleteRows(db, namespace, pattern) {
89
+ if ("exact" in pattern) {
90
+ db.prepare("DELETE FROM cache WHERE namespace = ? AND cache_key = ?").run(namespace, pattern.exact);
91
+ }
92
+ else {
93
+ db.prepare("DELETE FROM cache WHERE namespace = ? AND cache_key LIKE ? ESCAPE '\\'").run(namespace, likePrefix(pattern.prefix));
94
+ }
95
+ return db.changes;
96
+ }
97
+ /** The skill state is the repository's own knowledge, so `clear` always drops
98
+ * it: without it the next build starts from `init` rather than `sync`. */
99
+ export async function cacheDeleteRepoKnowledge(repo) {
100
+ const db = openDatabase();
101
+ try {
102
+ return deleteRows(db, "state", { exact: repo });
103
+ }
104
+ finally {
105
+ db.close();
106
+ }
107
+ }
108
+ /** Everything `init` fetched or paid for, kept unless the caller asked for a
109
+ * cold next build. The global `pricing` cache stays either way. Keys live in
110
+ * three shapes: an exact repository key, `${repo}:...`, and the local review
111
+ * carry-over's NUL separated `${repo}\0...`. */
112
+ export async function cacheDeleteRepoEvidence(repo) {
113
+ const db = openDatabase();
114
+ try {
115
+ let removed = 0;
116
+ for (const namespace of ["pr-listing", "probe"]) {
117
+ removed += deleteRows(db, namespace, { exact: repo });
118
+ }
119
+ for (const namespace of ["ai-jobs", "cost"]) {
120
+ removed += deleteRows(db, namespace, { prefix: `${repo}:` });
121
+ }
122
+ removed += deleteRows(db, "local-review", { prefix: `${repo}\0` });
123
+ return removed;
124
+ }
125
+ finally {
126
+ db.close();
127
+ }
128
+ }
70
129
  /** Every value under `namespace` whose key starts with `prefix`, newest first.
71
130
  * `recordAiCost` keys each job `${repo}:${uuid}`, so the probe estimate reads
72
131
  * a repository's own job history without a second index to keep in sync. */
@@ -4,6 +4,9 @@ export declare function deactivateRepo(fullName: string): void;
4
4
  export declare function deactivateReposForInstallation(installationId: number): void;
5
5
  export declare function updateRepoSettings(fullName: string, patch: Partial<Pick<RepoRow, "auto_review" | "review_scope" | "skip_drafts" | "skip_bots" | "use_codegraph" | "review_policy_json">>): void;
6
6
  export declare function markKnowledgeBuilt(fullName: string, baseSha: string): void;
7
+ /** Forgets the built knowledge: the stamp every review and the dashboard read
8
+ * as "built", and the drift baseline that only meant anything against it. */
9
+ export declare function clearRepoKnowledge(fullName: string): void;
7
10
  export declare function setInstallationId(fullName: string, installationId: number): void;
8
11
  export declare function getRepo(fullName: string): RepoRow | undefined;
9
12
  /** Case-insensitive match on `repos.full_name`. */
@@ -36,6 +36,14 @@ export function markKnowledgeBuilt(fullName, baseSha) {
36
36
  // baseline, so a rebuild makes them meaningless rather than merely stale.
37
37
  deleteDrift(fullName);
38
38
  }
39
+ /** Forgets the built knowledge: the stamp every review and the dashboard read
40
+ * as "built", and the drift baseline that only meant anything against it. */
41
+ export function clearRepoKnowledge(fullName) {
42
+ getAppDb()
43
+ .prepare(`UPDATE repos SET knowledge_built_at = NULL, knowledge_base_sha = NULL WHERE full_name = ?`)
44
+ .run(fullName);
45
+ deleteDrift(fullName);
46
+ }
39
47
  export function setInstallationId(fullName, installationId) {
40
48
  getAppDb()
41
49
  .prepare(`UPDATE repos SET installation_id = ? WHERE full_name = ?`)
@@ -29,6 +29,9 @@ export declare function mkdir(path: string | URL, options?: {
29
29
  export declare function remove(path: string | URL, options?: {
30
30
  recursive?: boolean;
31
31
  }): Promise<void>;
32
+ /** Removes an empty directory only. Unlike `remove`, a non-empty directory
33
+ * fails, which is what callers that prune parents after deleting files want. */
34
+ export declare function rmdir(path: string | URL): Promise<void>;
32
35
  export declare function stat(path: string | URL): Promise<Stats>;
33
36
  export declare function lstat(path: string | URL): Promise<Stats>;
34
37
  export declare function readLink(path: string | URL): Promise<string>;
@@ -6,7 +6,7 @@
6
6
  * two silent-break classes too: `"windows"` vs `"win32"`, and
7
7
  * `Deno.errors.NotFound` vs `ENOENT`.
8
8
  */
9
- import { chmod as fsChmod, lstat as fsLstat, mkdir as fsMkdir, mkdtemp as fsMkdtemp, readFile as fsReadFile, readdir, readlink as fsReadLink, rename as fsRename, rm as fsRm, stat as fsStat, writeFile as fsWriteFile, } from "node:fs/promises";
9
+ import { chmod as fsChmod, lstat as fsLstat, mkdir as fsMkdir, mkdtemp as fsMkdtemp, readFile as fsReadFile, readdir, readlink as fsReadLink, rename as fsRename, rmdir as fsRmdir, rm as fsRm, stat as fsStat, writeFile as fsWriteFile, } from "node:fs/promises";
10
10
  import { mkdirSync as fsMkdirSync, mkdtempSync as fsMkdtempSync, readFileSync as fsReadFileSync, rmSync as fsRmSync, statSync as fsStatSync, writeFileSync as fsWriteFileSync, } from "node:fs";
11
11
  import { spawn, spawnSync } from "node:child_process";
12
12
  import { tmpdir } from "node:os";
@@ -81,6 +81,11 @@ export function mkdir(path, options) {
81
81
  export function remove(path, options) {
82
82
  return fsRm(path, { recursive: options?.recursive ?? false, force: false });
83
83
  }
84
+ /** Removes an empty directory only. Unlike `remove`, a non-empty directory
85
+ * fails, which is what callers that prune parents after deleting files want. */
86
+ export function rmdir(path) {
87
+ return fsRmdir(path);
88
+ }
84
89
  export function stat(path) {
85
90
  return fsStat(path);
86
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "co-maintainer",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "An imitator of yours, A PR review tool like co-maintainer.",
5
5
  "keywords": [
6
6
  "ai",
@@ -59,11 +59,9 @@
59
59
  "dx:scenario": "node scripts/dx_scenario.ts",
60
60
  "dashboard:smoke": "node scripts/dashboard_smoke.ts"
61
61
  },
62
- "dependencies": {
63
- "marked": "^15.0.7"
64
- },
65
62
  "devDependencies": {
66
63
  "@types/node": "^24.0.0",
64
+ "marked": "^15.0.7",
67
65
  "oxfmt": "^0.68.0",
68
66
  "oxlint": "^1.83.0",
69
67
  "playwright": "^1.63.0",