zencefyl 0.2.5 → 0.2.7

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.
@@ -10,7 +10,7 @@ import {
10
10
  OLLAMA_MODELS_DEFAULT,
11
11
  OPENAI_SUBSCRIPTION_MODELS,
12
12
  PROVIDER_LABELS
13
- } from "./chunk-VGMBOH7B.js";
13
+ } from "./chunk-CZ4NCT3D.js";
14
14
  export {
15
15
  DEFAULT_MODELS,
16
16
  GEMINI_SUBSCRIPTION_MODELS,
@@ -23,4 +23,4 @@ export {
23
23
  OPENAI_SUBSCRIPTION_MODELS,
24
24
  PROVIDER_LABELS
25
25
  };
26
- //# sourceMappingURL=models-PR5MVFOC.js.map
26
+ //# sourceMappingURL=models-BADSTECA.js.map
@@ -0,0 +1,13 @@
1
+ -- Migration 005: compact session summaries for startup continuity.
2
+ --
3
+ -- Stores one lightweight recap per session. This is intentionally separate from
4
+ -- raw transcripts so startup UI can load a short project memory signal without
5
+ -- reading or embedding the full conversation history.
6
+
7
+ CREATE TABLE IF NOT EXISTS session_summaries (
8
+ session_id TEXT PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
9
+ summary TEXT NOT NULL,
10
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
11
+ );
12
+
13
+ CREATE INDEX IF NOT EXISTS idx_session_summaries_updated_at ON session_summaries(updated_at);
@@ -0,0 +1,15 @@
1
+ -- Migration 006: zero out scaffold topic retrievability
2
+ --
3
+ -- Older builds created hierarchy/scaffold topics with retrievability that looked
4
+ -- like demonstrated knowledge. These rows have no direct evidence and no review
5
+ -- history, so reset them to zero.
6
+
7
+ UPDATE topics
8
+ SET retrievability = 0.0,
9
+ updated_at = datetime('now')
10
+ WHERE review_count = 0
11
+ AND NOT EXISTS (
12
+ SELECT 1
13
+ FROM evidence e
14
+ WHERE e.topic_id = topics.id
15
+ );
@@ -0,0 +1,38 @@
1
+ -- Migration 007: repo-map metadata and scoped memory storage
2
+ --
3
+ -- Rebuild projects so path becomes the durable unique identity. Older schema
4
+ -- enforced unique(name), which is too weak for repos named app/api/frontend.
5
+
6
+ CREATE TABLE IF NOT EXISTS projects_new (
7
+ id INTEGER PRIMARY KEY,
8
+ name TEXT NOT NULL,
9
+ path TEXT NOT NULL UNIQUE,
10
+ git_remote TEXT,
11
+ language TEXT,
12
+ repo_map TEXT,
13
+ repo_map_built_at TEXT,
14
+ repo_map_version INTEGER DEFAULT 1,
15
+ last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
16
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
17
+ );
18
+
19
+ INSERT INTO projects_new (id, name, path, git_remote, language, last_seen_at, created_at)
20
+ SELECT id, name, path, git_remote, language, last_seen_at, created_at
21
+ FROM projects;
22
+
23
+ DROP TABLE projects;
24
+ ALTER TABLE projects_new RENAME TO projects;
25
+
26
+ CREATE INDEX IF NOT EXISTS idx_projects_name ON projects(name);
27
+
28
+ ALTER TABLE memories ADD COLUMN project_id INTEGER REFERENCES projects(id);
29
+ ALTER TABLE memories ADD COLUMN scope TEXT CHECK(scope IN ('global', 'project')) NOT NULL DEFAULT 'global';
30
+ ALTER TABLE memories ADD COLUMN kind TEXT CHECK(kind IN ('observation', 'gap', 'curiosity', 'summary'));
31
+ ALTER TABLE memories ADD COLUMN is_compacted INTEGER NOT NULL DEFAULT 0;
32
+ ALTER TABLE memories ADD COLUMN compacted_from_count INTEGER;
33
+ ALTER TABLE memories ADD COLUMN compacted_at TEXT;
34
+ ALTER TABLE memories ADD COLUMN source_cluster_key TEXT;
35
+ ALTER TABLE memories ADD COLUMN latest_source_at TEXT;
36
+
37
+ CREATE INDEX IF NOT EXISTS idx_memories_project ON memories(project_id);
38
+ CREATE INDEX IF NOT EXISTS idx_memories_scope ON memories(scope);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zencefyl",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Personal AI engineering companion",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,8 @@
16
16
  "postinstall": "node -e \"process.stdout.write('\\n zencefyl — by @bartugundogdu\\n\\n')\"",
17
17
  "dev": "tsx src/cli/index.tsx",
18
18
  "build": "tsup",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
19
21
  "typecheck": "tsc --noEmit",
20
22
  "start": "node dist/index.js",
21
23
  "prepublishOnly": "pnpm build"
@@ -43,7 +45,8 @@
43
45
  "@types/react": "^18.3.0",
44
46
  "tsup": "^8.3.0",
45
47
  "tsx": "^4.19.0",
46
- "typescript": "^5.6.0"
48
+ "typescript": "^5.6.0",
49
+ "vitest": "^3.2.4"
47
50
  },
48
51
  "repository": {
49
52
  "type": "git",
@@ -1,62 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/auth/credentials.ts
4
- import fs from "fs";
5
- import path from "path";
6
- function credentialsPath(dataDir) {
7
- return path.join(dataDir, "credentials.json");
8
- }
9
- function loadCredentials(dataDir) {
10
- const p = credentialsPath(dataDir);
11
- if (!fs.existsSync(p)) return {};
12
- try {
13
- return JSON.parse(fs.readFileSync(p, "utf8"));
14
- } catch {
15
- return {};
16
- }
17
- }
18
- function saveCredentials(dataDir, store) {
19
- fs.writeFileSync(credentialsPath(dataDir), JSON.stringify(store, null, 2), "utf8");
20
- }
21
- function saveProviderCredentials(dataDir, providerId, creds) {
22
- const store = loadCredentials(dataDir);
23
- store[providerId] = creds;
24
- saveCredentials(dataDir, store);
25
- }
26
- var REFRESH_BUFFER_MS = 6e4;
27
- function isExpired(creds) {
28
- return Date.now() >= creds.expires - REFRESH_BUFFER_MS;
29
- }
30
- async function getAccessToken(dataDir, providerId) {
31
- const store = loadCredentials(dataDir);
32
- const creds = store[providerId];
33
- if (!creds) {
34
- throw new Error(
35
- `No credentials for ${providerId}. Run zencefyl setup and choose the subscription option.`
36
- );
37
- }
38
- if (!isExpired(creds)) {
39
- return creds.access;
40
- }
41
- let refreshed;
42
- if (providerId === "openai-subscription") {
43
- const { refreshOpenAICodexToken } = await import("@mariozechner/pi-ai/oauth");
44
- refreshed = await refreshOpenAICodexToken(creds.refresh);
45
- } else {
46
- const { refreshGoogleCloudToken } = await import("@mariozechner/pi-ai/oauth");
47
- const projectId = creds.projectId ?? "";
48
- refreshed = await refreshGoogleCloudToken(creds.refresh, projectId);
49
- refreshed.projectId = projectId;
50
- }
51
- saveProviderCredentials(dataDir, providerId, refreshed);
52
- return refreshed.access;
53
- }
54
-
55
- export {
56
- credentialsPath,
57
- loadCredentials,
58
- saveCredentials,
59
- saveProviderCredentials,
60
- getAccessToken
61
- };
62
- //# sourceMappingURL=chunk-OBDSCGMH.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/auth/credentials.ts"],"sourcesContent":["// Credential storage and refresh for OAuth providers.\n//\n// Credentials are stored in ~/.zencefyl/credentials.json — separate from\n// config.json so they are never accidentally committed to git.\n//\n// Structure:\n// {\n// \"openai-subscription\": { access: \"...\", refresh: \"...\", expires: 1234567890 },\n// \"gemini-subscription\": { access: \"...\", refresh: \"...\", expires: 1234567890, projectId: \"...\" }\n// }\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { OAuthCredentials } from '@mariozechner/pi-ai/oauth'\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport type ProviderId = 'openai-subscription' | 'gemini-subscription'\n\n// Gemini credentials carry a projectId needed by refreshGoogleCloudToken.\nexport type GeminiCredentials = OAuthCredentials & { projectId?: string }\n\ntype CredentialsStore = {\n 'openai-subscription'?: OAuthCredentials\n 'gemini-subscription'?: GeminiCredentials\n}\n\n// ── File path ─────────────────────────────────────────────────────────────────\n\nexport function credentialsPath(dataDir: string): string {\n return path.join(dataDir, 'credentials.json')\n}\n\n// ── Load / Save ───────────────────────────────────────────────────────────────\n\nexport function loadCredentials(dataDir: string): CredentialsStore {\n const p = credentialsPath(dataDir)\n if (!fs.existsSync(p)) return {}\n try {\n return JSON.parse(fs.readFileSync(p, 'utf8')) as CredentialsStore\n } catch {\n return {}\n }\n}\n\nexport function saveCredentials(dataDir: string, store: CredentialsStore): void {\n fs.writeFileSync(credentialsPath(dataDir), JSON.stringify(store, null, 2), 'utf8')\n}\n\nexport function saveProviderCredentials(\n dataDir: string,\n providerId: ProviderId,\n creds: OAuthCredentials | GeminiCredentials,\n): void {\n const store = loadCredentials(dataDir)\n store[providerId] = creds as GeminiCredentials\n saveCredentials(dataDir, store)\n}\n\n// ── Token refresh ─────────────────────────────────────────────────────────────\n\n// Buffer in ms — refresh 60s before actual expiry so requests never hit an\n// expired token mid-flight.\nconst REFRESH_BUFFER_MS = 60_000\n\nfunction isExpired(creds: OAuthCredentials): boolean {\n return Date.now() >= creds.expires - REFRESH_BUFFER_MS\n}\n\n// Returns a valid access token, refreshing first if needed.\n// Persists updated credentials back to disk on refresh.\nexport async function getAccessToken(\n dataDir: string,\n providerId: 'openai-subscription',\n): Promise<string>\nexport async function getAccessToken(\n dataDir: string,\n providerId: 'gemini-subscription',\n): Promise<string>\nexport async function getAccessToken(dataDir: string, providerId: ProviderId): Promise<string> {\n const store = loadCredentials(dataDir)\n const creds = store[providerId]\n\n if (!creds) {\n throw new Error(\n `No credentials for ${providerId}. ` +\n `Run zencefyl setup and choose the subscription option.`\n )\n }\n\n if (!isExpired(creds)) {\n return creds.access\n }\n\n // Token expired — refresh it\n let refreshed: OAuthCredentials\n\n if (providerId === 'openai-subscription') {\n const { refreshOpenAICodexToken } = await import('@mariozechner/pi-ai/oauth')\n refreshed = await refreshOpenAICodexToken(creds.refresh)\n } else {\n const { refreshGoogleCloudToken } = await import('@mariozechner/pi-ai/oauth')\n const projectId = (creds as GeminiCredentials).projectId ?? ''\n refreshed = await refreshGoogleCloudToken(creds.refresh, projectId)\n // Preserve projectId through refresh\n ;(refreshed as GeminiCredentials).projectId = projectId\n }\n\n saveProviderCredentials(dataDir, providerId, refreshed)\n return refreshed.access\n}\n"],"mappings":";;;AAWA,OAAO,QAAU;AACjB,OAAO,UAAU;AAiBV,SAAS,gBAAgB,SAAyB;AACvD,SAAO,KAAK,KAAK,SAAS,kBAAkB;AAC9C;AAIO,SAAS,gBAAgB,SAAmC;AACjE,QAAM,IAAI,gBAAgB,OAAO;AACjC,MAAI,CAAC,GAAG,WAAW,CAAC,EAAG,QAAO,CAAC;AAC/B,MAAI;AACF,WAAO,KAAK,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,gBAAgB,SAAiB,OAA+B;AAC9E,KAAG,cAAc,gBAAgB,OAAO,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,MAAM;AACnF;AAEO,SAAS,wBACd,SACA,YACA,OACM;AACN,QAAM,QAAQ,gBAAgB,OAAO;AACrC,QAAM,UAAU,IAAI;AACpB,kBAAgB,SAAS,KAAK;AAChC;AAMA,IAAM,oBAAoB;AAE1B,SAAS,UAAU,OAAkC;AACnD,SAAO,KAAK,IAAI,KAAK,MAAM,UAAU;AACvC;AAYA,eAAsB,eAAe,SAAiB,YAAyC;AAC7F,QAAM,QAAQ,gBAAgB,OAAO;AACrC,QAAM,QAAQ,MAAM,UAAU;AAE9B,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;AAAA,MACR,sBAAsB,UAAU;AAAA,IAElC;AAAA,EACF;AAEA,MAAI,CAAC,UAAU,KAAK,GAAG;AACrB,WAAO,MAAM;AAAA,EACf;AAGA,MAAI;AAEJ,MAAI,eAAe,uBAAuB;AACxC,UAAM,EAAE,wBAAwB,IAAI,MAAM,OAAO,2BAA2B;AAC5E,gBAAY,MAAM,wBAAwB,MAAM,OAAO;AAAA,EACzD,OAAO;AACL,UAAM,EAAE,wBAAwB,IAAI,MAAM,OAAO,2BAA2B;AAC5E,UAAM,YAAa,MAA4B,aAAa;AAC5D,gBAAmB,MAAM,wBAAwB,MAAM,SAAS,SAAS;AAExE,IAAC,UAAgC,YAAY;AAAA,EAChD;AAEA,0BAAwB,SAAS,YAAY,SAAS;AACtD,SAAO,UAAU;AACnB;","names":[]}