@phren/cli 0.0.57 → 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 (167) hide show
  1. package/LICENSE +21 -0
  2. package/dist/capabilities/cli.d.ts +2 -0
  3. package/dist/capabilities/index.d.ts +7 -0
  4. package/dist/capabilities/mcp.d.ts +2 -0
  5. package/dist/capabilities/types.d.ts +12 -0
  6. package/dist/capabilities/vscode.d.ts +2 -0
  7. package/dist/capabilities/web-ui.d.ts +2 -0
  8. package/dist/cli/actions.d.ts +16 -0
  9. package/dist/cli/cli.d.ts +3 -0
  10. package/dist/cli/config.d.ts +24 -0
  11. package/dist/cli/extract.d.ts +22 -0
  12. package/dist/cli/govern.d.ts +11 -0
  13. package/dist/cli/graph.d.ts +15 -0
  14. package/dist/cli/hooks-citations.d.ts +8 -0
  15. package/dist/cli/hooks-context.d.ts +34 -0
  16. package/dist/cli/hooks-globs.d.ts +2 -0
  17. package/dist/cli/hooks-output.d.ts +2 -0
  18. package/dist/cli/hooks-session.d.ts +38 -0
  19. package/dist/cli/hooks.d.ts +12 -0
  20. package/dist/cli/namespaces.d.ts +11 -0
  21. package/dist/cli/ops.d.ts +5 -0
  22. package/dist/cli/search.d.ts +38 -0
  23. package/dist/cli/team.d.ts +1 -0
  24. package/dist/cli-hooks-git.d.ts +35 -0
  25. package/dist/cli-hooks-prompt.d.ts +18 -0
  26. package/dist/cli-hooks-session-handlers.d.ts +3 -0
  27. package/dist/cli-hooks-stop.d.ts +11 -0
  28. package/dist/content/archive.d.ts +13 -0
  29. package/dist/content/citation.d.ts +50 -0
  30. package/dist/content/dedup.d.ts +55 -0
  31. package/dist/content/learning.d.ts +29 -0
  32. package/dist/content/metadata.d.ts +107 -0
  33. package/dist/content/validate.d.ts +70 -0
  34. package/dist/core/finding.d.ts +25 -0
  35. package/dist/core/project.d.ts +16 -0
  36. package/dist/core/search.d.ts +13 -0
  37. package/dist/data/access.d.ts +83 -0
  38. package/dist/data/tasks.d.ts +89 -0
  39. package/dist/embedding.d.ts +54 -0
  40. package/dist/entrypoint.d.ts +1 -0
  41. package/dist/finding/context.d.ts +8 -0
  42. package/dist/finding/impact.d.ts +11 -0
  43. package/dist/finding/journal.d.ts +40 -0
  44. package/dist/finding/lifecycle.d.ts +40 -0
  45. package/dist/governance/audit.d.ts +1 -0
  46. package/dist/governance/locks.d.ts +3 -0
  47. package/dist/governance/policy.d.ts +109 -0
  48. package/dist/governance/rbac.d.ts +25 -0
  49. package/dist/governance/scores.d.ts +12 -0
  50. package/dist/hooks.d.ts +59 -0
  51. package/dist/index-query.d.ts +33 -0
  52. package/dist/index.d.ts +2 -0
  53. package/dist/init/config.d.ts +43 -0
  54. package/dist/init/init-configure.d.ts +21 -0
  55. package/dist/init/init-hooks-mode.d.ts +1 -0
  56. package/dist/init/init-mcp-mode.d.ts +1 -0
  57. package/dist/init/init-uninstall.d.ts +3 -0
  58. package/dist/init/init-walkthrough.d.ts +61 -0
  59. package/dist/init/init.d.ts +84 -0
  60. package/dist/init/preferences.d.ts +28 -0
  61. package/dist/init/setup.d.ts +86 -0
  62. package/dist/init/shared.d.ts +13 -0
  63. package/dist/init-bootstrap.d.ts +5 -0
  64. package/dist/init-detect.d.ts +8 -0
  65. package/dist/init-env.d.ts +6 -0
  66. package/dist/init-fresh.d.ts +10 -0
  67. package/dist/init-hooks.d.ts +5 -0
  68. package/dist/init-mcp.d.ts +8 -0
  69. package/dist/init-modes.d.ts +4 -0
  70. package/dist/init-npm.d.ts +10 -0
  71. package/dist/init-project-local.d.ts +2 -0
  72. package/dist/init-semantic.d.ts +4 -0
  73. package/dist/init-types.d.ts +59 -0
  74. package/dist/init-uninstall.d.ts +3 -0
  75. package/dist/init-update.d.ts +10 -0
  76. package/dist/init-walkthrough.d.ts +55 -0
  77. package/dist/link/checksums.d.ts +8 -0
  78. package/dist/link/context.d.ts +7 -0
  79. package/dist/link/doctor.d.ts +2 -0
  80. package/dist/link/link.d.ts +29 -0
  81. package/dist/link/skills.d.ts +47 -0
  82. package/dist/logger.d.ts +9 -0
  83. package/dist/machine-identity.d.ts +4 -0
  84. package/dist/package-metadata.d.ts +4 -0
  85. package/dist/phren-art.d.ts +26 -0
  86. package/dist/phren-core.d.ts +64 -0
  87. package/dist/phren-dotenv.d.ts +2 -0
  88. package/dist/phren-paths.d.ts +60 -0
  89. package/dist/proactivity.d.ts +13 -0
  90. package/dist/profile-store.d.ts +34 -0
  91. package/dist/project-config.d.ts +60 -0
  92. package/dist/project-locator.d.ts +1 -0
  93. package/dist/project-topics.d.ts +122 -0
  94. package/dist/provider-adapters.d.ts +34 -0
  95. package/dist/query-correlation.d.ts +31 -0
  96. package/dist/runtime-profile.d.ts +6 -0
  97. package/dist/session/checkpoints.d.ts +25 -0
  98. package/dist/session/utils.d.ts +43 -0
  99. package/dist/shared/content.d.ts +7 -0
  100. package/dist/shared/data-utils.d.ts +8 -0
  101. package/dist/shared/embedding-cache.d.ts +30 -0
  102. package/dist/shared/fragment-graph.d.ts +60 -0
  103. package/dist/shared/governance.d.ts +4 -0
  104. package/dist/shared/index.d.ts +29 -0
  105. package/dist/shared/ollama.d.ts +28 -0
  106. package/dist/shared/process.d.ts +17 -0
  107. package/dist/shared/retrieval.d.ts +84 -0
  108. package/dist/shared/search-fallback.d.ts +23 -0
  109. package/dist/shared/sqljs.d.ts +5 -0
  110. package/dist/shared/stemmer.d.ts +5 -0
  111. package/dist/shared/vector-index.d.ts +18 -0
  112. package/dist/shared.d.ts +9 -0
  113. package/dist/shell/entry.d.ts +26 -0
  114. package/dist/shell/input.d.ts +57 -0
  115. package/dist/shell/palette.d.ts +13 -0
  116. package/dist/shell/render-api.d.ts +29 -0
  117. package/dist/shell/render.d.ts +50 -0
  118. package/dist/shell/shell.d.ts +61 -0
  119. package/dist/shell/state-store.d.ts +14 -0
  120. package/dist/shell/types.d.ts +29 -0
  121. package/dist/shell/view-list.d.ts +5 -0
  122. package/dist/shell/view.d.ts +34 -0
  123. package/dist/skill/files.d.ts +5 -0
  124. package/dist/skill/registry.d.ts +55 -0
  125. package/dist/skill/state.d.ts +3 -0
  126. package/dist/startup-embedding.d.ts +15 -0
  127. package/dist/status.d.ts +1 -0
  128. package/dist/store-registry.d.ts +60 -0
  129. package/dist/store-routing.d.ts +37 -0
  130. package/dist/task/github.d.ts +22 -0
  131. package/dist/task/hygiene.d.ts +13 -0
  132. package/dist/task/lifecycle.d.ts +26 -0
  133. package/dist/telemetry.d.ts +10 -0
  134. package/dist/test-global-setup.d.ts +14 -0
  135. package/dist/tool-registry.d.ts +14 -0
  136. package/dist/tools/config.d.ts +3 -0
  137. package/dist/tools/data.d.ts +3 -0
  138. package/dist/tools/extract-facts.d.ts +17 -0
  139. package/dist/tools/extract.d.ts +3 -0
  140. package/dist/tools/finding.d.ts +3 -0
  141. package/dist/tools/graph.d.ts +3 -0
  142. package/dist/tools/hooks.d.ts +3 -0
  143. package/dist/tools/memory.d.ts +3 -0
  144. package/dist/tools/ops.d.ts +3 -0
  145. package/dist/tools/search.d.ts +8 -0
  146. package/dist/tools/session.d.ts +44 -0
  147. package/dist/tools/skills.d.ts +3 -0
  148. package/dist/tools/tasks.d.ts +3 -0
  149. package/dist/tools/types.d.ts +50 -0
  150. package/dist/ui/assets.d.ts +2 -0
  151. package/dist/ui/data.d.ts +113 -0
  152. package/dist/ui/graph.d.ts +1 -0
  153. package/dist/ui/memory-ui.d.ts +4 -0
  154. package/dist/ui/page.d.ts +2 -0
  155. package/dist/ui/scripts.d.ts +17 -0
  156. package/dist/ui/server.d.ts +17 -0
  157. package/dist/ui/styles.d.ts +4 -0
  158. package/dist/update.d.ts +9 -0
  159. package/dist/utils-fts.d.ts +12 -0
  160. package/dist/utils-fts.js +450 -0
  161. package/dist/utils-helpers.d.ts +12 -0
  162. package/dist/utils-helpers.js +80 -0
  163. package/dist/utils-paths.d.ts +3 -0
  164. package/dist/utils-paths.js +61 -0
  165. package/dist/utils.d.ts +3 -0
  166. package/dist/utils.js +8 -587
  167. package/package.json +53 -19
@@ -0,0 +1,8 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type McpContext } from "./types.js";
3
+ /**
4
+ * Q30: Log zero-result queries to .runtime/search-misses.jsonl.
5
+ * Strips PII-like tokens (emails, UUIDs, numbers) and keeps only query terms.
6
+ */
7
+ export declare function logSearchMiss(phrenPath: string, query: string, project?: string): void;
8
+ export declare function register(server: McpServer, ctx: McpContext): void;
@@ -0,0 +1,44 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type McpContext } from "./types.js";
3
+ export declare function resolveActiveSessionScope(phrenPath: string, project?: string): string | undefined;
4
+ /** Find the most recent session with a summary (including ended sessions).
5
+ * @internal Exported for tests. */
6
+ export declare function findMostRecentSummary(phrenPath: string): string | null;
7
+ /** Increment the findingsAdded counter for a session. Falls back to the most relevant active session for the project. */
8
+ export declare function incrementSessionFindings(phrenPath: string, count?: number, sessionId?: string, project?: string): void;
9
+ export declare function incrementSessionTasksCompleted(phrenPath: string, count?: number, sessionId?: string, project?: string): void;
10
+ /** Summary of a session for history listing. */
11
+ interface SessionHistoryEntry {
12
+ sessionId: string;
13
+ project?: string;
14
+ agentScope?: string;
15
+ startedAt: string;
16
+ endedAt?: string;
17
+ durationMins?: number;
18
+ summary?: string;
19
+ findingsAdded: number;
20
+ tasksCompleted: number;
21
+ status: "active" | "ended";
22
+ }
23
+ /** List all sessions (both active and ended) from the sessions directory, sorted newest first. */
24
+ export declare function listAllSessions(phrenPath: string, limit?: number): SessionHistoryEntry[];
25
+ /** Get findings and tasks that belong to a specific session. */
26
+ interface SessionArtifactFinding {
27
+ project: string;
28
+ id: string;
29
+ date: string;
30
+ text: string;
31
+ }
32
+ interface SessionArtifactTask {
33
+ project: string;
34
+ id: string;
35
+ text: string;
36
+ section: string;
37
+ checked: boolean;
38
+ }
39
+ export declare function getSessionArtifacts(phrenPath: string, sessionId: string, project?: string): Promise<{
40
+ findings: SessionArtifactFinding[];
41
+ tasks: SessionArtifactTask[];
42
+ }>;
43
+ export declare function register(server: McpServer, ctx: McpContext): void;
44
+ export {};
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type McpContext } from "./types.js";
3
+ export declare function register(server: McpServer, ctx: McpContext): void;
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type McpContext } from "./types.js";
3
+ export declare function register(server: McpServer, ctx: McpContext): void;
@@ -0,0 +1,50 @@
1
+ import type { SqlJsDatabase } from "../shared/index.js";
2
+ export interface McpContext {
3
+ phrenPath: string;
4
+ profile: string;
5
+ db: () => SqlJsDatabase;
6
+ rebuildIndex: () => Promise<void>;
7
+ updateFileInIndex: (filePath: string) => void;
8
+ withWriteQueue: <T>(fn: () => Promise<T>) => Promise<T | {
9
+ content: {
10
+ type: "text";
11
+ text: string;
12
+ }[];
13
+ }>;
14
+ }
15
+ /**
16
+ * Resolve the effective phrenPath and bare project name for a project input.
17
+ * Handles store-qualified names ("store/project") by routing to the correct store.
18
+ * Returns the primary store path for bare names.
19
+ */
20
+ export declare function resolveStoreForProject(ctx: McpContext, projectInput: string): {
21
+ phrenPath: string;
22
+ project: string;
23
+ storeRole: string;
24
+ };
25
+ /**
26
+ * Standardized MCP tool response payload, based on PhrenResult conventions.
27
+ * - ok: true → data is present, message is optional display text
28
+ * - ok: false → error is present, data may carry diagnostic info
29
+ *
30
+ * Accepts `boolean` for ok (not just literals) to support computed expressions
31
+ * like `ok: added.length > 0`. All MCP tool handlers use this type.
32
+ */
33
+ interface McpToolResult {
34
+ ok: boolean;
35
+ data?: unknown;
36
+ error?: string;
37
+ errorCode?: string;
38
+ message?: string;
39
+ }
40
+ /**
41
+ * Convert an McpToolResult into the MCP SDK response format.
42
+ * Single shared implementation — replaces the per-file jsonResponse() duplicates.
43
+ */
44
+ export declare function mcpResponse(payload: McpToolResult): {
45
+ content: {
46
+ type: "text";
47
+ text: string;
48
+ }[];
49
+ };
50
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const WEB_UI_STYLES = " /*\n * Typography scale \u2014 modular 1.25 ratio, base = 14px\n * --text-xs (caption): ~11px (base / 1.25^2, rounded)\n * --text-sm (small): 12px (base / 1.25, rounded)\n * --text-base (body): 14px\n * --text-md (h3): 18px (base * 1.25, rounded)\n * --text-lg (h2): 22px (base * 1.25^2, rounded)\n * --text-xl (h1): 28px (base * 1.25^3, rounded)\n */\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n :root {\n --text-xs: 11px;\n --text-sm: 12px;\n --text-base: 14px;\n --text-md: 18px;\n --text-lg: 22px;\n --text-xl: 28px;\n --bg: #f9f8f6;\n --surface: #ffffff;\n --surface-raised: #fafbfc;\n --surface-sunken: #f1f3f6;\n --ink: #1a1a18;\n --ink-secondary: #3d3d3a;\n --muted: #7a7872;\n --accent: #b8860b;\n --accent-hover: #9a7209;\n --accent-dim: rgba(184,134,11,.08);\n --accent-glow: rgba(184,134,11,.15);\n --cyan: #2b5f8e;\n --cyan-dim: rgba(43,95,142,.08);\n --border: #e5e7eb;\n --border-light: #f3f4f6;\n --danger: #c45a4a;\n --danger-dim: rgba(239,68,68,.08);\n --warning: #d4892e;\n --success: #10b981;\n --success-dim: rgba(16,185,129,.08);\n --purple: #b8860b;\n --purple-dim: rgba(184,134,11,.08);\n --blue: #3b82f6;\n --red: #c45a4a;\n --green: #10b981;\n --radius: 10px;\n --radius-sm: 6px;\n --shadow-sm: 0 1px 2px rgba(0,0,0,.04), 0 1px 1px rgba(0,0,0,.02);\n --shadow: 0 2px 8px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);\n --shadow-lg: 0 8px 24px rgba(0,0,0,.08), 0 2px 8px rgba(0,0,0,.04);\n --font: \"Inter\", system-ui, -apple-system, sans-serif;\n --mono: \"JetBrains Mono\", \"Fira Code\", \"Cascadia Code\", monospace;\n }\n\n [data-theme=\"dark\"] {\n --bg: #0d0e0c;\n --surface: #15160f;\n --surface-raised: #1c1d16;\n --surface-sunken: #0a0b08;\n --ink: #e8e4d9;\n --ink-secondary: #b5b0a3;\n --muted: #6b6860;\n --accent: #d4892e;\n --accent-hover: #e09a3a;\n --accent-dim: rgba(212,137,46,.10);\n --accent-glow: rgba(212,137,46,.18);\n --cyan: #3a7bae;\n --cyan-dim: rgba(58,123,174,.12);\n --border: #2a2820;\n --border-light: #1e1d17;\n --danger-dim: rgba(239,68,68,.1);\n --success-dim: rgba(16,185,129,.1);\n --purple-dim: rgba(212,137,46,.12);\n --shadow-sm: 0 1px 3px rgba(0,0,0,.4);\n --shadow: 0 2px 8px rgba(0,0,0,.5), 0 0 1px rgba(0,0,0,.3);\n --shadow-lg: 0 8px 32px rgba(0,0,0,.6), 0 0 1px rgba(0,0,0,.4);\n }\n [data-theme=\"dark\"] .split-item.selected { background: rgba(212,137,46,.08); border-left-color: var(--accent); }\n [data-theme=\"dark\"] .split-item:hover { background: var(--surface-raised); }\n [data-theme=\"dark\"] .hook-item.selected { background: rgba(212,137,46,.08); border-left-color: var(--accent); }\n [data-theme=\"dark\"] .hook-item:hover { background: var(--surface-raised); }\n [data-theme=\"dark\"] .projects-search { background: var(--surface); color: var(--ink); }\n [data-theme=\"dark\"] .review-filters select { background: var(--surface); color: var(--ink); }\n [data-theme=\"dark\"] .review-edit-textarea { background: var(--surface-sunken); color: var(--ink); border-color: var(--border); }\n [data-theme=\"dark\"] .reader-content textarea { background: var(--surface-sunken); color: var(--ink); }\n [data-theme=\"dark\"] .graph-container { background: #0a0b08; }\n [data-theme=\"dark\"] .reader-toolbar { background: var(--surface-sunken); }\n [data-theme=\"dark\"] .card { border-color: var(--border); }\n [data-theme=\"dark\"] .badge-project { background: rgba(212,137,46,.15); color: #d4892e; }\n [data-theme=\"dark\"] .badge { background: var(--surface-sunken); }\n [data-theme=\"dark\"] .review-card { border-color: var(--border); background: var(--surface-raised); }\n [data-theme=\"dark\"] .review-card:hover { border-color: var(--accent); }\n\n body {\n font-family: var(--font);\n background: var(--bg);\n color: var(--ink);\n line-height: 1.5;\n min-height: 100vh;\n }\n\n /* \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .header {\n background: rgba(255,255,255,.82);\n backdrop-filter: blur(16px) saturate(1.8);\n -webkit-backdrop-filter: blur(16px) saturate(1.8);\n border-bottom: 1px solid var(--border);\n padding: 0 24px;\n display: flex;\n align-items: center;\n gap: 32px;\n height: 56px;\n position: sticky;\n top: 0;\n z-index: 100;\n }\n [data-theme=\"dark\"] .header {\n background: rgba(13,14,12,.82);\n border-bottom-color: var(--border);\n box-shadow: 0 1px 0 rgba(212,137,46,.08);\n }\n .header-brand {\n font-size: var(--text-md);\n font-weight: 700;\n color: var(--accent);\n display: flex;\n align-items: center;\n gap: 8px;\n letter-spacing: -0.03em;\n }\n .header-brand svg {\n width: 28px;\n height: 28px;\n opacity: .9;\n flex-shrink: 0;\n filter: drop-shadow(0 0 4px rgba(155,139,196,.5));\n cursor: pointer;\n animation: phren-breathe 3.6s ease-in-out infinite;\n transform-origin: center;\n transition: filter .2s;\n }\n .header-brand svg:hover {\n animation: phren-wobble .5s ease-in-out;\n filter: drop-shadow(0 0 8px rgba(155,139,196,.7));\n }\n .header-brand svg.popped {\n animation: phren-pop .45s ease-out forwards;\n }\n @keyframes phren-breathe {\n 0%, 100% { transform: scale(1); filter: drop-shadow(0 0 4px rgba(155,139,196,.5)); }\n 50% { transform: scale(1.06); filter: drop-shadow(0 0 6px rgba(155,139,196,.65)); }\n }\n @keyframes phren-wobble {\n 0% { transform: rotate(0deg) scale(1.1); }\n 20% { transform: rotate(-12deg) scale(1.1); }\n 40% { transform: rotate(10deg) scale(1.1); }\n 60% { transform: rotate(-6deg) scale(1.1); }\n 80% { transform: rotate(4deg) scale(1.1); }\n 100% { transform: rotate(0deg) scale(1.1); }\n }\n @keyframes phren-pop {\n 0% { transform: scale(1); }\n 30% { transform: scale(1.35); filter: drop-shadow(0 0 8px rgba(155,139,196,.8)); }\n 60% { transform: scale(0.92); }\n 100% { transform: scale(1); filter: drop-shadow(0 0 4px rgba(155,139,196,.5)); }\n }\n .nav { display: flex; gap: 0; height: 100%; }\n .nav-item {\n padding: 0 16px;\n font-size: var(--text-base);\n font-weight: 500;\n color: var(--muted);\n cursor: pointer;\n border: none;\n background: none;\n height: 100%;\n display: flex;\n align-items: center;\n border-bottom: 2px solid transparent;\n transition: color .15s, border-color .15s;\n font-family: var(--font);\n letter-spacing: -0.01em;\n }\n .nav-item:hover { color: var(--ink); }\n .nav-item.active {\n color: var(--accent);\n border-bottom-color: var(--accent);\n font-weight: 600;\n }\n .nav-item .count {\n background: var(--accent-dim);\n color: var(--accent);\n font-size: var(--text-xs);\n padding: 0 8px;\n border-radius: 10px;\n margin-left: 8px;\n font-weight: 700;\n letter-spacing: .02em;\n }\n\n /* \u2500\u2500 Main \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .main { padding: 24px; max-width: 1400px; margin: 0 auto; }\n .tab-content { display: none; }\n .tab-content.active { display: block; }\n\n /* \u2500\u2500 Cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .card {\n background: var(--surface-raised);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n box-shadow: var(--shadow-sm);\n overflow: hidden;\n }\n .card-header {\n padding: 12px 20px;\n border-bottom: 1px solid var(--border-light);\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--surface-raised);\n }\n .card-header h2 {\n font-size: var(--text-sm);\n font-weight: 650;\n text-transform: uppercase;\n letter-spacing: .05em;\n color: var(--muted);\n }\n .card-body { padding: 20px; }\n\n /* \u2500\u2500 Projects Tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .projects-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n gap: 16px;\n }\n .project-card {\n background: var(--surface-raised);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 20px;\n cursor: pointer;\n transition: box-shadow .2s, border-color .2s, transform .15s;\n position: relative;\n }\n .project-card:hover {\n box-shadow: var(--shadow-lg);\n border-color: color-mix(in srgb, var(--accent) 40%, var(--border));\n transform: translateY(-1px);\n }\n .project-card.selected {\n border-color: var(--accent);\n box-shadow: 0 0 0 1px var(--accent), var(--shadow);\n }\n .project-card-name {\n font-size: var(--text-md);\n font-weight: 600;\n margin-bottom: 8px;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .project-card-summary {\n font-size: var(--text-base);\n color: var(--muted);\n line-height: 1.5;\n margin-bottom: 12px;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n }\n .project-card-stats {\n display: flex;\n gap: 16px;\n font-size: var(--text-sm);\n color: var(--muted);\n }\n .project-card-stat {\n display: flex;\n align-items: center;\n gap: 4px;\n }\n .project-card-stat strong { color: var(--ink); font-weight: 600; }\n\n /* Project detail panel */\n .project-detail {\n margin-top: 20px;\n }\n .project-detail-header {\n display: flex;\n align-items: center;\n gap: 16px;\n margin-bottom: 16px;\n }\n .project-detail-header h2 { font-size: var(--text-lg); font-weight: 700; }\n .project-detail-header .btn { font-size: var(--text-sm); }\n .project-detail-tabs {\n display: flex;\n gap: 0;\n border-bottom: 1px solid var(--border);\n margin-bottom: 0;\n }\n .project-detail-tab {\n padding: 12px 20px;\n font-size: var(--text-base);\n font-weight: 500;\n color: var(--muted);\n cursor: pointer;\n border: none;\n background: none;\n border-bottom: 2px solid transparent;\n font-family: var(--font);\n transition: color .15s;\n }\n .project-detail-tab:hover { color: var(--ink); }\n .project-detail-tab.active { color: var(--accent); border-bottom-color: var(--accent); }\n .project-detail-content {\n background: var(--surface);\n border: 1px solid var(--border);\n border-top: none;\n border-radius: 0 0 var(--radius) var(--radius);\n min-height: 400px;\n }\n .project-detail-content pre {\n margin: 0;\n padding: 20px;\n font-family: var(--mono);\n font-size: var(--text-sm);\n line-height: 1.7;\n white-space: pre-wrap;\n word-break: break-word;\n overflow: auto;\n max-height: 600px;\n }\n .project-detail-empty {\n padding: 60px 20px;\n text-align: center;\n color: var(--muted);\n font-size: var(--text-base);\n }\n\n /* \u2500\u2500 Finding detail cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .finding-cards { display: flex; flex-direction: column; gap: 10px; padding: 14px; }\n .finding-detail-card {\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--surface-raised);\n overflow: hidden;\n }\n .finding-detail-card summary {\n list-style: none;\n cursor: pointer;\n padding: 12px 14px;\n color: var(--ink-secondary);\n font-size: var(--text-sm);\n line-height: 1.5;\n font-weight: 500;\n }\n .finding-detail-card summary::-webkit-details-marker { display: none; }\n .finding-detail-card[open] summary {\n border-bottom: 1px solid var(--border-light);\n background: color-mix(in srgb, var(--accent) 4%, var(--surface-raised));\n }\n .finding-detail-meta {\n padding: 10px 14px 12px;\n display: flex;\n gap: 12px;\n flex-wrap: wrap;\n align-items: center;\n font-size: var(--text-xs);\n color: var(--muted);\n }\n .finding-score-indicator {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n display: inline-block;\n margin-right: 6px;\n vertical-align: middle;\n }\n .finding-score-indicator.healthy { background: #10b981; }\n .finding-score-indicator.decaying { background: #f59e0b; }\n .finding-score-indicator.stale { background: #ef4444; }\n\n\n /* \u2500\u2500 Review Tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .review-filters {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 16px;\n flex-wrap: wrap;\n }\n .review-filters select {\n padding: 8px 28px 8px 12px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n font-size: var(--text-sm);\n font-family: var(--font);\n font-weight: 500;\n background: var(--surface);\n color: var(--ink-secondary);\n outline: none;\n cursor: pointer;\n transition: border-color .15s;\n appearance: none;\n -webkit-appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg width='10' height='6' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%236b7280' fill='none' stroke-width='1.5'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n }\n .review-filters select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }\n .review-cards { display: flex; flex-direction: column; gap: 12px; }\n .review-card {\n background: var(--surface-raised);\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 0;\n box-shadow: var(--shadow-sm);\n transition: border-color .2s, box-shadow .2s;\n overflow: hidden;\n }\n .review-card:hover {\n border-color: color-mix(in srgb, var(--accent) 30%, var(--border));\n box-shadow: var(--shadow), 0 0 0 1px var(--accent-dim);\n }\n .review-card-inner {\n padding: 16px 18px;\n }\n .review-card-header {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 10px;\n flex-wrap: wrap;\n }\n .review-card-text {\n font-size: var(--text-base);\n line-height: 1.65;\n margin-bottom: 12px;\n color: var(--ink);\n }\n .review-card-text code {\n background: var(--surface-sunken);\n border: 1px solid var(--border);\n padding: 0 4px;\n border-radius: 4px;\n font-size: var(--text-sm);\n font-family: var(--mono);\n }\n .review-card-text p { margin: 0 0 8px; }\n .review-card-text p:last-child { margin-bottom: 0; }\n .review-card-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n padding-top: 10px;\n border-top: 1px solid var(--border-light);\n }\n .review-card-edit {\n margin-top: 12px;\n padding-top: 12px;\n border-top: 1px solid var(--border-light);\n }\n .review-edit-textarea {\n width: 100%;\n min-height: 80px;\n padding: 12px 16px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n font-size: var(--text-base);\n font-family: var(--font);\n line-height: 1.6;\n resize: vertical;\n background: var(--surface-sunken);\n color: var(--ink);\n transition: border-color .15s;\n }\n .review-edit-textarea:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n .review-help {\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: var(--surface);\n overflow: hidden;\n }\n .review-help summary {\n cursor: pointer;\n font-size: var(--text-sm);\n font-weight: 600;\n color: var(--muted);\n padding: 12px 16px;\n letter-spacing: .02em;\n text-transform: uppercase;\n }\n .review-help dl { margin: 0; padding: 0 16px 16px; font-size: var(--text-base); }\n .review-help dt { font-weight: 600; margin-top: 12px; color: var(--ink-secondary); font-size: var(--text-base); }\n .review-help dd { margin: 4px 0 0 0; color: var(--muted); line-height: 1.55; font-size: var(--text-base); }\n .review-help dd code {\n background: var(--surface-sunken);\n border: 1px solid var(--border);\n padding: 0 4px;\n border-radius: 4px;\n font-size: var(--text-sm);\n font-family: var(--mono);\n }\n .panes { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 16px; }\n .card ul { margin: 0; padding-left: 0; list-style: none; max-height: 220px; overflow: auto; font-size: var(--text-sm); }\n .card li { padding: 8px 0; color: var(--muted); border-bottom: 1px solid var(--border-light); line-height: 1.5; font-size: var(--text-sm); }\n .card li:last-child { border-bottom: none; }\n\n /* \u2500\u2500 Star button \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .star-btn {\n position: absolute;\n top: 12px;\n right: 12px;\n background: none;\n border: none;\n font-size: var(--text-md);\n cursor: pointer;\n color: var(--border);\n transition: color .15s;\n padding: 4px;\n line-height: 1;\n }\n .star-btn:hover { color: var(--warning); }\n .star-btn.starred { color: var(--warning); }\n\n /* \u2500\u2500 Project search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .projects-search {\n width: 100%;\n padding: 12px 16px;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n font-size: var(--text-base);\n font-family: var(--font);\n margin-bottom: 16px;\n background: var(--surface);\n outline: none;\n transition: border-color .15s;\n }\n .projects-search:focus { border-color: var(--accent); }\n\n /* \u2500\u2500 GitHub link \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .github-link {\n font-size: var(--text-sm);\n color: var(--muted);\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n gap: 4px;\n }\n .github-link:hover { color: var(--ink); }\n\n /* \u2500\u2500 Graph Tab \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .graph-container {\n background: #0d0e0c;\n border-radius: var(--radius);\n border: 1px solid var(--border);\n overflow: hidden;\n position: relative;\n }\n #graph-canvas {\n width: 100%;\n height: calc(100vh - 160px);\n min-height: 800px;\n display: block;\n cursor: grab;\n }\n #graph-canvas:active { cursor: grabbing; }\n .graph-controls {\n position: absolute;\n top: 12px;\n right: 12px;\n display: flex;\n flex-direction: column;\n gap: 6px;\n z-index: 2;\n }\n .graph-controls button {\n width: 36px;\n height: 36px;\n background: rgba(255,255,255,.1);\n border: 1px solid rgba(255,255,255,.15);\n border-radius: 6px;\n color: #e2e8f0;\n font-size: var(--text-md);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n backdrop-filter: blur(8px);\n transition: background .15s;\n }\n .graph-controls button:hover { background: rgba(255,255,255,.2); }\n .graph-legend {\n display: flex;\n gap: 24px;\n padding: 14px 20px;\n background: rgba(10,11,8,.85);\n border-top: 1px solid rgba(255,255,255,.1);\n backdrop-filter: blur(8px);\n }\n .graph-legend-item {\n display: flex;\n align-items: center;\n gap: 8px;\n color: #94a3b8;\n font-size: var(--text-sm);\n font-weight: 500;\n }\n .graph-legend-dot {\n width: 12px;\n height: 12px;\n border-radius: 50%;\n display: inline-block;\n }\n .graph-tooltip {\n position: absolute;\n background: rgba(10,11,8,.95);\n color: #e2e8f0;\n padding: 8px 12px;\n border-radius: 6px;\n font-size: var(--text-sm);\n max-width: 300px;\n pointer-events: none;\n opacity: 0;\n transition: opacity .15s;\n border: 1px solid rgba(255,255,255,.1);\n line-height: 1.4;\n z-index: 10;\n }\n .graph-tooltip.visible { opacity: 1; }\n .graph-filters {\n position: absolute;\n top: 12px;\n left: 12px;\n right: 52px;\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 10px 14px;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 8px;\n backdrop-filter: blur(8px);\n }\n .graph-filter {\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n align-items: center;\n }\n .graph-filter-btn {\n padding: 4px 12px;\n background: rgba(255,255,255,.08);\n border: 1px solid rgba(255,255,255,.12);\n border-radius: 20px;\n color: #94a3b8;\n font-size: var(--text-xs);\n font-weight: 500;\n cursor: pointer;\n transition: all .15s;\n font-family: var(--font);\n }\n .graph-filter-btn:hover, .graph-filter-btn.active {\n background: rgba(255,255,255,.15);\n color: #e2e8f0;\n }\n\n /* \u2500\u2500 Two-pane (Skills/Hooks) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .split-view {\n display: grid;\n grid-template-columns: 280px 1fr;\n gap: 0;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: var(--surface);\n height: calc(100vh - 140px);\n min-height: 520px;\n overflow: hidden;\n }\n .split-sidebar {\n border-right: 1px solid var(--border);\n overflow-y: auto;\n background: var(--surface-sunken);\n }\n .split-group-label {\n padding: 8px 16px;\n font-size: var(--text-xs);\n text-transform: uppercase;\n letter-spacing: .06em;\n color: var(--muted);\n font-weight: 600;\n background: var(--surface-sunken);\n border-bottom: 1px solid var(--border);\n position: sticky;\n top: 0;\n }\n .split-item {\n padding: 12px 16px;\n cursor: pointer;\n border-bottom: 1px solid var(--border-light);\n font-size: var(--text-base);\n transition: background .1s;\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n .split-item:hover { background: var(--surface-raised); }\n .split-item.selected { background: var(--accent-dim); border-left: 3px solid var(--accent); padding-left: 12px; }\n .split-reader {\n display: flex;\n flex-direction: column;\n }\n .reader-toolbar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border);\n background: var(--surface-sunken);\n }\n .reader-title { font-weight: 650; font-size: var(--text-base); flex-shrink: 0; }\n .reader-path {\n font-size: var(--text-xs);\n color: var(--muted);\n flex: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-family: var(--mono);\n }\n .reader-content {\n flex: 1;\n overflow: auto;\n display: flex;\n flex-direction: column;\n min-height: 0;\n }\n .reader-content pre {\n margin: 0;\n padding: 20px;\n font-size: var(--text-sm);\n line-height: 1.7;\n font-family: var(--mono);\n white-space: pre-wrap;\n word-break: break-word;\n }\n .reader-content textarea {\n flex: 1;\n width: 100%;\n min-height: 300px;\n padding: 20px;\n font-size: var(--text-sm);\n line-height: 1.7;\n font-family: var(--mono);\n border: none;\n outline: none;\n resize: none;\n }\n .reader-empty {\n padding: 60px 20px;\n text-align: center;\n color: var(--muted);\n font-size: var(--text-base);\n }\n\n /* Hook items */\n .hook-item {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border-light);\n cursor: pointer;\n transition: background .1s;\n }\n .hook-item:hover { background: var(--surface-raised); }\n .hook-item.selected { background: var(--accent-dim); border-left: 3px solid var(--accent); padding-left: 12px; }\n .hook-name { flex: 1; font-size: var(--text-base); font-weight: 500; }\n .hook-custom-event { font-size: var(--text-sm); font-weight: 600; color: var(--ink); }\n .hook-custom-cmd { font-size: var(--text-xs); color: var(--muted); word-break: break-all; margin-top: 4px; }\n\n /* \u2500\u2500 Badges & Buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .badge {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: 6px;\n font-size: var(--text-xs);\n font-weight: 600;\n background: var(--surface-sunken);\n color: var(--muted);\n letter-spacing: .02em;\n text-transform: uppercase;\n }\n .badge-project { background: var(--purple-dim); color: var(--purple); }\n .badge-on { background: var(--success-dim); color: var(--success); }\n .badge-off { background: var(--danger-dim); color: var(--danger); }\n .badge-count { background: var(--accent); color: white; min-width: 20px; text-align: center; border-radius: 10px; }\n .badge-machine { background: var(--accent-dim); color: var(--accent); }\n .badge-model { background: rgba(245,158,11,.1); color: #d97706; }\n\n .btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px 16px;\n border-radius: var(--radius-sm);\n font-size: var(--text-sm);\n font-weight: 550;\n font-family: var(--font);\n cursor: pointer;\n transition: all .15s ease;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--ink-secondary);\n letter-spacing: -0.01em;\n }\n .btn:hover { background: var(--surface-sunken); color: var(--ink); }\n .btn:active { transform: scale(.97); }\n .btn-primary { background: var(--accent); color: white; border-color: transparent; }\n .btn-primary:hover { background: var(--accent-hover); }\n .btn-approve {\n background: var(--success-dim);\n color: var(--success);\n border-color: transparent;\n font-weight: 600;\n }\n .btn-approve:hover { background: var(--success); color: white; }\n .btn-reject {\n background: transparent;\n color: var(--muted);\n border-color: var(--border);\n }\n .btn-reject:hover { background: var(--danger-dim); color: var(--danger); border-color: var(--danger); }\n .btn-sm { padding: 4px 12px; font-size: var(--text-xs); }\n\n kbd {\n background: var(--surface-sunken);\n border: 1px solid var(--border);\n border-radius: 4px;\n padding: 0 8px;\n font-size: var(--text-xs);\n font-family: var(--mono);\n font-weight: 550;\n color: var(--muted);\n line-height: 1;\n }\n\n .text-muted { color: var(--muted); }\n .status-msg { font-size: var(--text-sm); padding: 4px 8px; border-radius: var(--radius-sm); }\n .status-msg.ok { background: var(--success-dim); color: var(--green); }\n .status-msg.err { background: var(--danger-dim); color: var(--red); }\n\n /* \u2500\u2500 Status LED \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .status-led {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n display: inline-block;\n vertical-align: middle;\n margin-right: 8px;\n flex-shrink: 0;\n }\n .status-led-ok {\n background: #22c55e;\n color: #22c55e;\n animation: ledPulse 2.5s infinite;\n }\n .status-led-warn {\n background: #f59e0b;\n color: #f59e0b;\n animation: ledPulse 1.2s infinite;\n }\n .status-led-err {\n background: #ef4444;\n color: #ef4444;\n animation: ledPulse 0.6s infinite;\n }\n @keyframes ledPulse {\n 0% { opacity: 1; box-shadow: 0 0 0 0 currentColor; }\n 70% { opacity: 0.8; box-shadow: 0 0 0 5px transparent; }\n 100% { opacity: 1; }\n }\n\n @media (max-width: 900px) {\n .projects-grid { grid-template-columns: 1fr; }\n .split-view { grid-template-columns: 1fr; }\n .panes { grid-template-columns: 1fr; }\n .header { padding: 0 12px; gap: 12px; }\n .main { padding: 16px; }\n }\n\n ::view-transition-old(root), ::view-transition-new(root) {\n animation-duration: 0.18s;\n }\n\n @keyframes cardIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n }\n .review-card {\n animation: cardIn 0.3s cubic-bezier(.21,1.02,.73,1) backwards;\n }\n .review-card.removing {\n animation: cardOut 0.25s ease forwards;\n pointer-events: none;\n }\n @keyframes cardOut {\n to { opacity: 0; transform: translateY(-6px) scale(.98); margin-top: -4px; padding: 0; }\n }\n\n /* \u2500\u2500 Batch actions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .review-card-check {\n position: absolute;\n top: 16px;\n right: 16px;\n width: 18px;\n height: 18px;\n border: 2px solid var(--border);\n border-radius: 4px;\n cursor: pointer;\n background: var(--surface);\n transition: all .15s;\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 2;\n }\n .review-card-check:hover { border-color: var(--accent); }\n .review-card-check.checked {\n background: var(--accent);\n border-color: var(--accent);\n }\n .review-card-check.checked::after {\n content: '';\n width: 6px;\n height: 10px;\n border: solid white;\n border-width: 0 2px 2px 0;\n transform: rotate(45deg);\n margin-top: -2px;\n }\n .review-card { position: relative; }\n .batch-bar {\n position: fixed;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%) translateY(80px);\n background: var(--surface-raised, #1a2233);\n border: 1px solid var(--border);\n border-radius: 12px;\n padding: 12px 16px;\n display: flex;\n align-items: center;\n gap: 12px;\n box-shadow: 0 8px 32px rgba(0,0,0,.2);\n z-index: 500;\n transition: transform .3s cubic-bezier(.21,1.02,.73,1);\n pointer-events: all;\n }\n .batch-bar.visible {\n transform: translateX(-50%) translateY(0);\n }\n .batch-bar-count {\n font-size: var(--text-base);\n font-weight: 600;\n color: var(--ink);\n min-width: 80px;\n }\n\n .review-banner-pill {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 10px;\n border-radius: 99px;\n font-size: var(--text-xs);\n font-weight: 600;\n background: var(--surface-raised);\n border: 1px solid var(--border);\n color: var(--ink);\n }\n .review-banner-pill.rp-danger {\n background: rgba(239,68,68,.12);\n border-color: rgba(239,68,68,.3);\n color: #f87171;\n }\n .review-banner-pill.rp-warn {\n background: rgba(251,191,36,.10);\n border-color: rgba(251,191,36,.3);\n color: #fbbf24;\n }\n\n /* \u2500\u2500 Diff view \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .review-diff {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 1px;\n background: var(--border);\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n overflow: hidden;\n margin-bottom: 8px;\n font-size: var(--text-sm);\n font-family: var(--mono);\n line-height: 1.6;\n }\n .review-diff-pane {\n padding: 12px;\n background: var(--surface-sunken);\n white-space: pre-wrap;\n word-break: break-word;\n min-height: 60px;\n }\n .review-diff-pane-label {\n font-size: var(--text-xs);\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: .05em;\n color: var(--muted);\n margin-bottom: 8px;\n font-family: var(--font);\n }\n .diff-del { background: var(--danger-dim); color: var(--danger); text-decoration: line-through; }\n .diff-ins { background: var(--success-dim); color: var(--success); }\n\n /* \u2500\u2500 Drag reorder \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .review-card.dragging {\n opacity: .5;\n transform: scale(.98);\n z-index: 10;\n }\n .review-card.drag-over {\n border-top: 2px solid var(--accent);\n margin-top: -1px;\n }\n .review-card-drag-handle {\n width: 16px;\n height: 16px;\n cursor: grab;\n color: var(--border);\n transition: color .15s;\n flex-shrink: 0;\n margin-right: 4px;\n }\n .review-card-drag-handle:hover { color: var(--muted); }\n .review-card-drag-handle:active { cursor: grabbing; }\n\n\n /* \u2500\u2500 Similarity groups \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .review-group {\n border: 1px solid var(--border);\n border-radius: var(--radius);\n padding: 8px;\n margin-bottom: 12px;\n background: color-mix(in srgb, var(--surface) 95%, var(--accent) 5%);\n }\n .review-group .review-card { margin-bottom: 8px; }\n .review-group .review-card:last-child { margin-bottom: 0; }\n .review-group-header {\n font-size: var(--text-xs);\n color: var(--muted);\n margin-bottom: 8px;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 0 4px;\n }\n .review-group-toggle {\n background: none;\n border: none;\n color: var(--muted);\n cursor: pointer;\n font-size: var(--text-xs);\n padding: 0 4px;\n }\n .review-group-toggle:hover { color: var(--ink-secondary); }\n .review-group.collapsed .review-card:not(:first-child) { display: none; }\n\n /* \u2500\u2500 Select all \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .review-select-all {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: var(--text-sm);\n color: var(--muted);\n cursor: pointer;\n user-select: none;\n margin-bottom: 8px;\n }\n .review-select-all input[type=\"checkbox\"] {\n width: 14px;\n height: 14px;\n cursor: pointer;\n accent-color: var(--accent);\n }\n /* \u2500\u2500 Toast \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .toast-container {\n position: fixed;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 999;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n pointer-events: none;\n }\n .toast {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 16px;\n background: var(--surface-raised, #1a2233);\n color: var(--ink, #e8ecf4);\n border: 1px solid var(--border, #1e2a3e);\n border-radius: var(--radius);\n font-size: var(--text-base);\n font-weight: 500;\n box-shadow: 0 8px 32px rgba(0,0,0,.2);\n pointer-events: all;\n animation: toastIn 0.25s cubic-bezier(.21,1.02,.73,1);\n max-width: 420px;\n backdrop-filter: blur(12px);\n }\n .toast.ok { border-color: var(--success); background: var(--success-dim); color: var(--success); }\n .toast.err { border-color: var(--danger); background: var(--danger-dim); color: var(--danger); }\n @keyframes toastIn {\n from { opacity: 0; transform: translateY(12px) scale(.96); }\n to { opacity: 1; transform: translateY(0) scale(1); }\n }\n .toast-undo {\n background: none;\n border: 1px solid currentColor;\n color: inherit;\n border-radius: var(--radius-sm);\n padding: 4px 12px;\n font-size: var(--text-xs);\n font-weight: 600;\n cursor: pointer;\n font-family: var(--font);\n opacity: .8;\n transition: opacity .15s;\n }\n .toast-undo:hover { opacity: 1; }\n\n /* \u2500\u2500 Command Palette \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .cmdpal-overlay {\n position: fixed;\n inset: 0;\n background: rgba(0,0,0,.4);\n backdrop-filter: blur(4px);\n z-index: 900;\n display: none;\n align-items: flex-start;\n justify-content: center;\n padding-top: 14vh;\n }\n .cmdpal-overlay.open { display: flex; }\n .cmdpal-box {\n background: var(--surface-raised);\n border: 1px solid var(--border);\n border-radius: 12px;\n width: 100%;\n max-width: 520px;\n box-shadow: 0 24px 80px rgba(0,0,0,.25);\n overflow: hidden;\n }\n .cmdpal-input {\n width: 100%;\n padding: 16px 20px;\n font-size: var(--text-md);\n font-family: var(--font);\n border: none;\n outline: none;\n background: transparent;\n color: var(--ink);\n border-bottom: 1px solid var(--border);\n font-weight: 450;\n }\n .cmdpal-input::placeholder { color: var(--muted); }\n .cmdpal-results { max-height: 340px; overflow-y: auto; }\n .cmdpal-item {\n padding: 12px 20px;\n cursor: pointer;\n font-size: var(--text-base);\n display: flex;\n align-items: center;\n gap: 8px;\n transition: background .1s;\n }\n .cmdpal-item:hover, .cmdpal-item.selected { background: var(--accent-dim); }\n .cmdpal-item-name { font-weight: 550; color: var(--ink); }\n .cmdpal-item-meta { font-size: var(--text-xs); color: var(--muted); margin-left: auto; font-weight: 500; }\n .cmdpal-empty { padding: 32px 20px; text-align: center; color: var(--muted); font-size: var(--text-base); }\n @keyframes countPop {\n 0% { transform: scale(1); }\n 50% { transform: scale(1.3); }\n 100% { transform: scale(1); }\n }\n @keyframes countFlip {\n 0% { transform: scale(1) rotateX(0); }\n 40% { transform: scale(1.3) rotateX(90deg); }\n 60% { transform: scale(1.3) rotateX(-10deg); }\n 100% { transform: scale(1) rotateX(0); }\n }\n .count-animating {\n animation: countPop 0.3s ease-out;\n }\n .count-flipping {\n animation: countFlip 0.3s ease-out;\n }";
2
+ export declare function renderWebUiScript(authToken: string): string;
@@ -0,0 +1,113 @@
1
+ import type { SqlJsDatabase } from "../shared/index.js";
2
+ interface EntryScore {
3
+ impressions: number;
4
+ helpful: number;
5
+ repromptPenalty: number;
6
+ regressionPenalty: number;
7
+ lastUsedAt: string;
8
+ }
9
+ interface GraphNode {
10
+ id: string;
11
+ label: string;
12
+ fullLabel: string;
13
+ group: string;
14
+ refCount: number;
15
+ project: string;
16
+ store?: string;
17
+ tagged: boolean;
18
+ scoreKey?: string;
19
+ priority?: string;
20
+ section?: string;
21
+ entityType?: string;
22
+ refDocs?: GraphDocRef[];
23
+ scoreKeys?: string[];
24
+ topicSlug?: string;
25
+ topicLabel?: string;
26
+ }
27
+ interface GraphDocRef {
28
+ doc: string;
29
+ project: string;
30
+ scoreKey?: string;
31
+ }
32
+ interface GraphTopicMeta {
33
+ slug: string;
34
+ label: string;
35
+ }
36
+ interface GraphLink {
37
+ source: string;
38
+ target: string;
39
+ }
40
+ interface ProjectInfo {
41
+ name: string;
42
+ storePath: string;
43
+ store?: string;
44
+ findingCount: number;
45
+ taskCount: number;
46
+ hasClaudeMd: boolean;
47
+ hasSummary: boolean;
48
+ hasReference: boolean;
49
+ summaryText: string;
50
+ githubUrl?: string;
51
+ sparkline: number[];
52
+ }
53
+ export declare function readSyncSnapshot(phrenPath: string): {
54
+ autoSaveStatus?: undefined;
55
+ autoSaveDetail?: undefined;
56
+ lastPullAt?: undefined;
57
+ lastPullStatus?: undefined;
58
+ lastPushAt?: undefined;
59
+ lastPushStatus?: undefined;
60
+ unsyncedCommits?: undefined;
61
+ lastPushDetail?: undefined;
62
+ } | {
63
+ autoSaveStatus: string;
64
+ autoSaveDetail: string;
65
+ lastPullAt: string;
66
+ lastPullStatus: string;
67
+ lastPushAt: string;
68
+ lastPushStatus: string;
69
+ unsyncedCommits: number;
70
+ lastPushDetail: string;
71
+ };
72
+ export declare function isAllowedFilePath(filePath: string, phrenPath: string): boolean;
73
+ /**
74
+ * Stricter path check for skill endpoints — only allows files under skills/ directories,
75
+ * not the entire phren store.
76
+ */
77
+ export declare function isAllowedSkillPath(filePath: string, phrenPath: string): boolean;
78
+ export declare function collectSkillsForUI(phrenPath: string, profile?: string): Array<{
79
+ name: string;
80
+ source: string;
81
+ path: string;
82
+ enabled: boolean;
83
+ }>;
84
+ export declare function getHooksData(phrenPath: string, profile?: string): {
85
+ globalEnabled: boolean;
86
+ tools: {
87
+ tool: "claude" | "copilot" | "cursor" | "codex";
88
+ enabled: boolean;
89
+ configPath: string;
90
+ exists: boolean;
91
+ }[];
92
+ customHooks: import("../hooks.js").CustomHookEntry[];
93
+ projectOverrides: {
94
+ project: string;
95
+ baseEnabled: boolean | null;
96
+ events: Array<{
97
+ event: string;
98
+ configured: boolean | null;
99
+ enabled: boolean;
100
+ }>;
101
+ }[];
102
+ };
103
+ export declare function buildGraph(phrenPath: string, profile?: string, focusProject?: string, existingDb?: SqlJsDatabase | null): Promise<{
104
+ nodes: GraphNode[];
105
+ links: GraphLink[];
106
+ total: number;
107
+ scores: Record<string, EntryScore>;
108
+ topics: GraphTopicMeta[];
109
+ }>;
110
+ export declare function recentUsage(phrenPath: string): string[];
111
+ export declare function recentAccepted(phrenPath: string): string[];
112
+ export declare function collectProjectsForUI(phrenPath: string, profile?: string): ProjectInfo[];
113
+ export {};
@@ -0,0 +1 @@
1
+ export declare function renderGraphScript(): string;
@@ -0,0 +1,4 @@
1
+ import { type WebUiOptions, type WebUiStartOptions } from "./server.js";
2
+ export { renderPageForTests } from "./page.js";
3
+ export declare function createWebUiServer(phrenPath: string, opts?: WebUiOptions, profile?: string): import("node:http").Server<typeof import("node:http").IncomingMessage, typeof import("node:http").ServerResponse>;
4
+ export declare function startWebUi(phrenPath: string, port: number, profile?: string, opts?: WebUiStartOptions): Promise<void>;
@@ -0,0 +1,2 @@
1
+ export declare function renderWebUiPage(_phrenPath: string, authToken?: string, nonce?: string): string;
2
+ export declare function renderPageForTests(phrenPath: string, _csrfToken?: string, authToken?: string): string;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Returns a <script> block with shared browser helpers used across all UI IIFEs:
3
+ * window._phrenEsc(s) — HTML-escape a value
4
+ * window._phrenAuthToken — the current auth token
5
+ * window._phrenAuthUrl(base) — append _auth param to a URL
6
+ * window._phrenAuthBody(body) — append _auth param to a form body
7
+ * window._phrenFetchCsrfToken(cb) — fetch the CSRF token and call cb(token)
8
+ */
9
+ export declare function renderSharedWebUiHelpers(authToken: string): string;
10
+ export declare function renderProfileSwitcherScript(_authToken: string): string;
11
+ export declare function renderSkillUiEnhancementScript(_authToken: string): string;
12
+ export declare function renderProjectReferenceEnhancementScript(_authToken: string): string;
13
+ export declare function renderTasksAndSettingsScript(authToken: string): string;
14
+ export declare function renderSearchScript(authToken: string): string;
15
+ export declare function renderEventWiringScript(): string;
16
+ export declare function renderGraphHostScript(): string;
17
+ export declare function renderReviewQueueKeyboardScript(_authToken: string): string;
@@ -0,0 +1,17 @@
1
+ import * as http from "http";
2
+ export interface WebUiOptions {
3
+ authToken?: string;
4
+ csrfTokens?: Map<string, number>;
5
+ }
6
+ export interface WebUiStartOptions {
7
+ autoOpen?: boolean;
8
+ allowPortFallback?: boolean;
9
+ browserLauncher?: (url: string) => Promise<void> | void;
10
+ }
11
+ export declare function getWebUiBrowserCommand(url: string, platform?: NodeJS.Platform): {
12
+ command: string;
13
+ args: string[];
14
+ };
15
+ export declare function waitForWebUiReady(url: string, attempts?: number, delayMs?: number): Promise<boolean>;
16
+ export declare function createWebUiHttpServer(phrenPath: string, renderPage: (phrenPath: string, authToken?: string, nonce?: string) => string, profile?: string, opts?: WebUiOptions): http.Server;
17
+ export declare function startWebUiServer(phrenPath: string, port: number, renderPage: (phrenPath: string, authToken?: string, nonce?: string) => string, profile?: string, opts?: WebUiStartOptions): Promise<void>;
@@ -0,0 +1,4 @@
1
+ export declare const PROJECT_REFERENCE_UI_STYLES = "\n .project-reference-shell {\n height: calc(100vh - 260px);\n min-height: 520px;\n }\n .reference-sidebar-toolbar {\n display: flex;\n gap: 8px;\n padding: 12px 16px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n position: sticky;\n top: 0;\n z-index: 1;\n }\n .reference-banner {\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: color-mix(in srgb, var(--accent) 6%, var(--surface));\n color: var(--ink-secondary);\n padding: 14px 16px;\n margin-bottom: 12px;\n font-size: var(--text-sm);\n line-height: 1.55;\n }\n .reference-doc-meta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 10px;\n }\n .reference-hint {\n padding: 20px;\n color: var(--muted);\n font-size: var(--text-sm);\n line-height: 1.6;\n }\n .reference-status {\n margin-left: auto;\n font-size: var(--text-xs);\n color: var(--muted);\n }\n .reference-status.ok { color: var(--success); }\n .reference-status.err { color: var(--danger); }\n .reference-sidebar-note {\n padding: 12px 16px;\n color: var(--muted);\n font-size: var(--text-sm);\n border-bottom: 1px solid var(--border-light);\n }\n .reference-item-main {\n display: flex;\n flex-direction: column;\n gap: 4px;\n min-width: 0;\n }\n .reference-item-title {\n font-size: var(--text-base);\n color: var(--ink);\n font-weight: 500;\n }\n .reference-item-meta {\n font-size: var(--text-xs);\n color: var(--muted);\n line-height: 1.4;\n }\n .reference-item-action {\n margin-left: 8px;\n flex-shrink: 0;\n }\n .topic-editor {\n padding: 20px;\n display: flex;\n flex-direction: column;\n gap: 14px;\n max-width: 720px;\n }\n .topic-editor label {\n display: flex;\n flex-direction: column;\n gap: 6px;\n color: var(--ink-secondary);\n font-size: var(--text-sm);\n font-weight: 600;\n }\n .topic-editor input,\n .topic-editor textarea {\n width: 100%;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n padding: 10px 12px;\n font-size: var(--text-base);\n font-family: var(--font);\n background: var(--surface);\n color: var(--ink);\n }\n .topic-editor textarea {\n min-height: 90px;\n resize: vertical;\n }\n .topic-editor-actions {\n display: flex;\n gap: 8px;\n align-items: center;\n }\n .topic-empty {\n padding: 24px 20px;\n color: var(--muted);\n line-height: 1.6;\n }\n .topic-keywords {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n margin-top: 12px;\n }\n .topic-keyword {\n display: inline-flex;\n align-items: center;\n padding: 4px 8px;\n border-radius: 999px;\n background: var(--surface-sunken);\n color: var(--ink-secondary);\n font-size: var(--text-xs);\n font-weight: 600;\n }\n";
2
+ export declare const SETTINGS_TAB_UI_STYLES = "\n .settings-shell {\n display: flex;\n flex-direction: column;\n gap: 18px;\n }\n .settings-section {\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: var(--surface);\n box-shadow: var(--shadow);\n overflow: hidden;\n }\n .settings-section-findings { border-top: 3px solid color-mix(in srgb, var(--accent) 60%, var(--border)); }\n .settings-section-behavior { border-top: 3px solid color-mix(in srgb, var(--blue) 45%, var(--border)); }\n .settings-section-integrations { border-top: 3px solid color-mix(in srgb, var(--purple) 45%, var(--border)); }\n .settings-section-header {\n padding: 16px 18px 12px;\n border-bottom: 1px solid var(--border-light);\n background: var(--surface);\n }\n .settings-section-header h3 {\n margin: 0;\n font-size: var(--text-md);\n font-weight: 700;\n letter-spacing: -0.02em;\n color: var(--ink);\n }\n .settings-section-header p {\n margin: 6px 0 0;\n color: var(--muted);\n font-size: var(--text-sm);\n line-height: 1.5;\n }\n .settings-section-body {\n padding: 18px;\n display: flex;\n flex-direction: column;\n gap: 16px;\n }\n .settings-control {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px;\n border: 1px solid var(--border-light);\n border-radius: var(--radius-sm);\n background: var(--surface-raised);\n }\n .settings-control-primary {\n background: color-mix(in srgb, var(--accent) 4%, var(--surface-raised));\n border-color: color-mix(in srgb, var(--accent) 18%, var(--border));\n }\n .settings-control-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n .settings-control-label {\n font-size: var(--text-base);\n font-weight: 600;\n color: var(--ink-secondary);\n }\n .settings-control-note {\n color: var(--muted);\n font-size: var(--text-sm);\n line-height: 1.5;\n }\n .settings-control-value {\n color: var(--ink-secondary);\n font-size: var(--text-base);\n font-weight: 600;\n }\n .settings-chip-row {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n }\n .settings-chip {\n border: 1px solid var(--border);\n background: transparent;\n color: var(--ink-secondary);\n border-radius: 999px;\n padding: 6px 12px;\n cursor: pointer;\n font-size: var(--text-sm);\n font-family: var(--font);\n font-weight: 600;\n transition: background .15s, border-color .15s, color .15s;\n }\n .settings-chip:hover {\n border-color: var(--accent);\n color: var(--ink);\n }\n .settings-chip.active {\n border-color: var(--accent);\n background: var(--accent);\n color: #fff;\n }\n .settings-chip.readonly {\n cursor: default;\n opacity: 0.92;\n }\n .settings-chip.readonly:hover {\n border-color: var(--border);\n color: var(--ink-secondary);\n }\n .settings-chip.active.readonly:hover {\n border-color: var(--accent);\n color: #fff;\n }\n .settings-status-inline {\n min-height: 18px;\n font-size: var(--text-sm);\n color: var(--muted);\n }\n .settings-status-inline.ok { color: var(--success); }\n .settings-status-inline.err { color: var(--danger); }\n .settings-integrations-table {\n width: 100%;\n border-collapse: collapse;\n }\n .settings-integrations-table th,\n .settings-integrations-table td {\n text-align: left;\n padding: 10px 8px;\n border-bottom: 1px solid var(--border-light);\n font-size: var(--text-sm);\n vertical-align: middle;\n }\n .settings-integrations-table th {\n color: var(--muted);\n font-weight: 650;\n letter-spacing: 0.02em;\n text-transform: uppercase;\n font-size: var(--text-xs);\n }\n .settings-integrations-table tr:last-child td { border-bottom: none; }\n .settings-tool {\n display: flex;\n align-items: center;\n gap: 8px;\n font-weight: 600;\n color: var(--ink-secondary);\n text-transform: capitalize;\n }\n .settings-indicator {\n width: 9px;\n height: 9px;\n border-radius: 50%;\n display: inline-block;\n margin-right: 6px;\n }\n .settings-indicator.on { background: var(--success); }\n .settings-indicator.off { background: var(--danger); }\n";
3
+ export declare const TASK_UI_STYLES = "\n /* \u2500\u2500 Task Manager Styles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .task-toolbar {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 20px;\n flex-wrap: wrap;\n }\n .task-filter-select {\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n padding: 8px 12px;\n background: var(--surface);\n color: var(--ink);\n font-size: var(--text-sm);\n font-family: var(--font);\n cursor: pointer;\n transition: border-color 0.15s;\n }\n .task-filter-select:hover { border-color: var(--accent); }\n .task-filter-select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 2px var(--accent-dim); }\n .task-count-label {\n font-size: var(--text-sm);\n color: var(--muted);\n margin-left: auto;\n font-weight: 500;\n }\n\n .task-empty-state {\n padding: 60px 40px;\n text-align: center;\n color: var(--muted);\n }\n\n .task-add-bar {\n display: flex;\n gap: 10px;\n margin-bottom: 20px;\n align-items: center;\n }\n .task-add-input {\n flex: 1;\n padding: 10px 14px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--surface);\n color: var(--ink);\n font-size: var(--text-base);\n font-family: var(--font);\n transition: border-color 0.15s, box-shadow 0.15s;\n }\n .task-add-input:focus {\n outline: none;\n border-color: var(--accent);\n box-shadow: 0 0 0 3px var(--accent-dim);\n }\n .task-add-input::placeholder { color: var(--muted); opacity: 0.6; }\n .task-add-btn {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 10px 18px;\n border: none;\n border-radius: var(--radius-sm);\n background: var(--accent);\n color: #fff;\n cursor: pointer;\n font-size: var(--text-sm);\n font-weight: 600;\n font-family: var(--font);\n transition: background 0.15s, transform 0.1s;\n white-space: nowrap;\n }\n .task-add-btn:hover { background: var(--accent-hover); }\n .task-add-btn:active { transform: scale(0.97); }\n\n .task-section-group { margin-bottom: 24px; }\n .task-section-header {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: var(--text-xs);\n font-weight: 700;\n color: var(--muted);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n margin-bottom: 8px;\n padding: 0 4px;\n }\n .task-section-count {\n font-weight: 500;\n color: var(--muted);\n font-size: var(--text-xs);\n opacity: 0.7;\n }\n\n .task-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n border: 1px solid var(--border);\n border-radius: var(--radius);\n background: var(--surface);\n overflow: hidden;\n }\n\n .task-row {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--border-light);\n transition: background 0.1s;\n cursor: default;\n position: relative;\n }\n .task-row:last-child { border-bottom: none; }\n .task-row:hover { background: var(--surface-raised); }\n .task-row:hover .task-row-actions { opacity: 1; pointer-events: auto; }\n\n .task-row-priority {\n width: 4px;\n height: 24px;\n border-radius: 2px;\n flex-shrink: 0;\n }\n .task-row-priority-high { background: #ef4444; }\n .task-row-priority-medium { background: #f59e0b; }\n .task-row-priority-low { background: #9ca3af; }\n .task-row-priority-none { background: transparent; }\n\n .task-row-content {\n flex: 1;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n }\n .task-row-text {\n font-size: var(--text-base);\n color: var(--ink);\n line-height: 1.4;\n word-break: break-word;\n }\n .task-row-done .task-row-text {\n text-decoration: line-through;\n opacity: 0.5;\n }\n .task-row-context {\n font-size: var(--text-xs);\n color: var(--muted);\n font-style: italic;\n margin-top: 2px;\n }\n\n .task-row-meta {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n }\n\n .task-priority-badge {\n display: inline-block;\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 600;\n text-transform: capitalize;\n }\n .task-priority-high { background: #ef444418; color: #ef4444; }\n .task-priority-medium { background: #f59e0b18; color: #f59e0b; }\n .task-priority-low { background: #6b728018; color: #6b7280; }\n\n .task-project-badge {\n display: inline-block;\n padding: 2px 8px;\n border-radius: var(--radius-sm);\n font-size: 10px;\n font-weight: 500;\n background: var(--accent-dim);\n color: var(--accent);\n }\n\n .task-pin-indicator {\n color: var(--accent);\n display: inline-flex;\n align-items: center;\n }\n\n .task-github-badge {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 8px;\n border-radius: var(--radius-sm);\n font-size: 10px;\n font-weight: 500;\n background: var(--surface-sunken);\n color: var(--ink-secondary);\n text-decoration: none;\n transition: background 0.15s;\n }\n .task-github-badge:hover { background: var(--accent-dim); color: var(--accent); }\n\n .task-row-actions {\n display: flex;\n align-items: center;\n gap: 4px;\n opacity: 0;\n pointer-events: none;\n transition: opacity 0.15s;\n flex-shrink: 0;\n }\n .task-action-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n background: var(--surface);\n color: var(--muted);\n cursor: pointer;\n font-size: 12px;\n font-family: var(--font);\n transition: all 0.15s;\n padding: 0;\n }\n .task-action-btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n background: var(--accent-dim);\n }\n .task-action-btn.task-action-complete:hover {\n border-color: var(--success);\n color: var(--success);\n background: var(--success-dim);\n }\n .task-action-btn.task-action-delete:hover {\n border-color: var(--danger);\n color: var(--danger);\n background: var(--danger-dim);\n }\n\n .task-done-section { margin-top: 24px; }\n .task-done-toggle {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 4px;\n background: none;\n border: none;\n color: var(--muted);\n font-size: var(--text-xs);\n font-weight: 700;\n font-family: var(--font);\n cursor: pointer;\n text-transform: uppercase;\n letter-spacing: 0.06em;\n transition: color 0.15s;\n }\n .task-done-toggle:hover { color: var(--ink-secondary); }\n .task-toggle-arrow {\n font-size: 10px;\n transition: transform 0.2s;\n }\n .task-done-list { padding-top: 4px; }\n\n /* \u2500\u2500 Task Summary Bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .task-summary-bar {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 14px;\n background: var(--surface-sunken, var(--surface));\n border: 1px solid var(--border);\n border-radius: var(--radius);\n margin-bottom: 16px;\n flex-wrap: wrap;\n font-size: var(--text-sm);\n }\n .task-summary-total {\n font-weight: 600;\n color: var(--ink);\n font-size: var(--text-base);\n }\n .task-summary-pill {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n }\n .task-summary-high { background: #ef444422; color: #ef4444; }\n .task-summary-medium { background: #f59e0b22; color: #f59e0b; }\n .task-summary-low { background: #6b728022; color: #6b7280; }\n .task-summary-projects {\n display: flex;\n gap: 6px;\n align-items: center;\n }\n .task-summary-project {\n font-size: 11px;\n color: var(--muted);\n padding: 1px 6px;\n border: 1px solid var(--border);\n border-radius: var(--radius-sm);\n }\n\n /* \u2500\u2500 Task Session Badge \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .task-session-badge {\n display: inline-block;\n padding: 1px 6px;\n border-radius: var(--radius-sm);\n font-size: 10px;\n font-family: var(--mono, monospace);\n background: var(--surface-sunken, var(--surface));\n color: var(--muted);\n border: 1px solid var(--border);\n }\n";
4
+ export declare const REVIEW_UI_STYLES = "\n /* \u2500\u2500 Review Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */\n .review-toolbar {\n display: flex;\n align-items: center;\n gap: 10px;\n margin-bottom: 16px;\n flex-wrap: wrap;\n }\n .review-flagged-toggle {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: var(--text-sm);\n font-weight: 500;\n color: var(--ink-secondary);\n cursor: pointer;\n user-select: none;\n }\n .review-flagged-toggle input[type=\"checkbox\"] {\n width: 14px;\n height: 14px;\n cursor: pointer;\n accent-color: var(--accent);\n }\n .review-sync-dot {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n flex-shrink: 0;\n }\n .review-sync-indicator {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--success);\n display: inline-block;\n transition: background 0.3s;\n }\n .review-sync-indicator.syncing {\n background: var(--warning);\n animation: ledPulse 1.2s infinite;\n }\n .review-sync-indicator.error {\n background: var(--danger);\n }\n /* \u2500\u2500 Review Card Highlight (keyboard shortcuts) \u2500\u2500\u2500\u2500 */\n .review-card-highlight {\n border: 2px solid var(--blue) !important;\n box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.08), var(--shadow) !important;\n background: color-mix(in srgb, var(--surface) 98%, var(--blue)) !important;\n }\n";
@@ -0,0 +1,9 @@
1
+ interface UpdateResult {
2
+ ok: boolean;
3
+ message: string;
4
+ }
5
+ interface RunPhrenUpdateOptions {
6
+ refreshStarter?: boolean;
7
+ }
8
+ export declare function runPhrenUpdate(opts?: RunPhrenUpdateOptions): Promise<UpdateResult>;
9
+ export {};
@@ -0,0 +1,12 @@
1
+ export declare const STOP_WORDS: Set<string>;
2
+ export declare function extractKeywordEntries(text: string): string[];
3
+ export declare function extractKeywords(text: string): string;
4
+ export declare function learnedSynonymsPath(phrenPath: string, project: string): string | null;
5
+ export declare function loadLearnedSynonyms(project?: string | null, phrenPath?: string | null): Record<string, string[]>;
6
+ export declare function loadSynonymMap(project?: string | null, phrenPath?: string | null): Record<string, string[]>;
7
+ export declare function learnSynonym(phrenPath: string, project: string, term: string, synonyms: string[]): Record<string, string[]>;
8
+ export declare function removeLearnedSynonym(phrenPath: string, project: string, term: string, synonyms?: string[]): Record<string, string[]>;
9
+ export declare function sanitizeFts5Query(raw: string): string;
10
+ export declare function buildRobustFtsQuery(raw: string, project?: string | null, phrenPath?: string): string;
11
+ export declare function buildRelaxedFtsQuery(raw: string, project?: string | null, phrenPath?: string): string;
12
+ export declare function buildFtsQueryVariants(raw: string, project?: string | null, phrenPath?: string): string[];