@balpal4495/quorum 3.4.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -380,6 +380,43 @@ Writes to `.chronicle/committed/`, updates `SUMMARY.md`, removes the proposal. A
380
380
 
381
381
  ---
382
382
 
383
+ ### `quorum serve` — governance UI + MCP server
384
+
385
+ ```bash
386
+ quorum serve # starts on http://localhost:4242
387
+ quorum serve --port 8080 # custom port
388
+ quorum serve --no-llm # disable LLM auto-detection
389
+ ```
390
+
391
+ Starts a single HTTP server that provides:
392
+
393
+ - **Governance UI** at `/` — browse committed entries, review and approve pending proposals (with inline evidence, Jury breakdown, and Council conditions), track Chronicle health, and run Compass product-direction analysis.
394
+ - **MCP server** at `POST /mcp` — JSON-RPC 2.0 endpoint exposing 10 tools and 6 resources to any MCP-compatible agent.
395
+ - **REST API** — used by the UI and accessible directly:
396
+
397
+ | Route | Description |
398
+ |---|---|
399
+ | `GET /api/entries?q=<query>` | Search committed Chronicle entries |
400
+ | `GET /api/proposals` | List pending proposals |
401
+ | `GET /api/coverage` | Chronicle coverage map |
402
+ | `GET /api/growth` | Chronicle health score |
403
+ | `GET /api/compass?subcommand=map\|brief\|opportunities\|bets` | Compass product direction |
404
+ | `PATCH /api/proposals/:id` | Edit a pending proposal before committing |
405
+
406
+ **MCP resources** (readable by agents via `resources/read`):
407
+
408
+ ```
409
+ chronicle://summary chronicle://proposals
410
+ chronicle://coverage chronicle://growth
411
+ chronicle://compass chronicle://entry/{id}
412
+ ```
413
+
414
+ **MCP tools**: `quorum_query`, `quorum_brief`, `quorum_stage`, `quorum_pending`, `quorum_coverage`, `quorum_growth`, `quorum_help`, `quorum_advisor`\*, `quorum_check`, `quorum_compass`\*
415
+
416
+ \* LLM-powered tools auto-activate when `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `GEMINI_API_KEY` is set. Without a key they return a `no-llm` status with CLI fallback instructions.
417
+
418
+ ---
419
+
383
420
  ### `quorum growth` — is Chronicle actually learning?
384
421
 
385
422
  ```bash
@@ -474,7 +474,7 @@ describe("POST /mcp — tools/call", () => {
474
474
  expect(typeof result.content).toBe("string")
475
475
  })
476
476
 
477
- it("quorum_advisor returns todo status", async () => {
477
+ it("quorum_advisor returns no-llm status when no provider is configured", async () => {
478
478
  const { dir } = await makeChronicle(tmpDir)
479
479
  srv = await startServer(tmpDir, dir)
480
480
 
@@ -485,7 +485,7 @@ describe("POST /mcp — tools/call", () => {
485
485
 
486
486
  expect(body.error).toBeUndefined()
487
487
  const result = JSON.parse(body.result.content[0].text)
488
- expect(result.status).toBe("todo")
488
+ expect(result.status).toBe("no-llm")
489
489
  })
490
490
 
491
491
  it("returns -32601 for unknown tool name", async () => {
@@ -555,7 +555,7 @@ describe("unknown route", () => {
555
555
  // ── MCP Resources ─────────────────────────────────────────────────────────────
556
556
 
557
557
  describe("POST /mcp — resources/list", () => {
558
- it("returns all five chronicle:// resources", async () => {
558
+ it("returns all six chronicle:// resources", async () => {
559
559
  const { dir } = await makeChronicle(tmpDir)
560
560
  srv = await startServer(tmpDir, dir)
561
561
 
@@ -570,7 +570,8 @@ describe("POST /mcp — resources/list", () => {
570
570
  expect(uris).toContain("chronicle://coverage")
571
571
  expect(uris).toContain("chronicle://growth")
572
572
  expect(uris).toContain("chronicle://entry/{id}")
573
- expect(uris).toHaveLength(5)
573
+ expect(uris).toContain("chronicle://compass")
574
+ expect(uris).toHaveLength(6)
574
575
  })
575
576
 
576
577
  it("each resource has a name, description, and mimeType", async () => {
@@ -489,37 +489,37 @@ describe("toolHelp", () => {
489
489
  })
490
490
  })
491
491
 
492
- // ── TODO placeholder tools ────────────────────────────────────────────────────
492
+ // ── LLM-powered tools (no-llm fallback when no provider configured) ───────────
493
493
 
494
- describe("toolAdvisor (TODO placeholder)", () => {
494
+ describe("toolAdvisor", () => {
495
495
  it("throws when question is missing", async () => {
496
496
  await expect(toolAdvisor({})).rejects.toThrow("question is required")
497
497
  })
498
498
 
499
- it("returns status=todo with a message", async () => {
499
+ it("returns status=no-llm with a message when no provider is set", async () => {
500
500
  const result = await toolAdvisor({ question: "what was decided about retries?" })
501
- expect(result.status).toBe("todo")
502
- expect(result.message).toMatch(/todo|quorum advisor/i)
501
+ expect(result.status).toBe("no-llm")
502
+ expect(result.message).toMatch(/quorum advisor/i)
503
503
  })
504
504
  })
505
505
 
506
- describe("toolCheck (TODO placeholder)", () => {
506
+ describe("toolCheck", () => {
507
507
  it("throws when neither outcome nor design is provided", async () => {
508
508
  await expect(toolCheck({})).rejects.toThrow("outcome or design is required")
509
509
  })
510
510
 
511
- it("returns status=todo with a message", async () => {
511
+ it("returns preflight and risk when called with outcome and design", async () => {
512
512
  const result = await toolCheck({ outcome: "ship safely", design: "use feature flags" })
513
- expect(result.status).toBe("todo")
514
- expect(result.message).toMatch(/todo|quorum check/i)
513
+ expect(result).toHaveProperty("preflight")
514
+ expect(result).toHaveProperty("risk")
515
515
  })
516
516
  })
517
517
 
518
- describe("toolCompass (TODO placeholder)", () => {
519
- it("returns status=todo with a message", async () => {
518
+ describe("toolCompass", () => {
519
+ it("returns status=no-llm with a message when no provider is set", async () => {
520
520
  const result = await toolCompass({ subcommand: "brief" })
521
- expect(result.status).toBe("todo")
522
- expect(result.message).toMatch(/todo|quorum compass/i)
521
+ expect(result.status).toBe("no-llm")
522
+ expect(result.message).toMatch(/quorum compass/i)
523
523
  })
524
524
  })
525
525
 
@@ -1,6 +1,7 @@
1
1
  import { c } from "../shared/colors.js"
2
2
  import { findChronicleDir } from "../shared/chronicle.js"
3
3
  import { createServer } from "../mcp/server.js"
4
+ import { detectProvider } from "../shared/llm.js"
4
5
 
5
6
  function parseArgs(argv) {
6
7
  const portIdx = argv.indexOf("--port")
@@ -9,11 +10,12 @@ function parseArgs(argv) {
9
10
  : Number(argv.find(a => /^\d{2,5}$/.test(a)) ?? 3000)
10
11
  const hostIdx = argv.indexOf("--host")
11
12
  const host = hostIdx !== -1 ? argv[hostIdx + 1] : "localhost"
12
- return { port: isNaN(port) ? 3000 : port, host }
13
+ const noLlm = argv.includes("--no-llm")
14
+ return { port: isNaN(port) ? 3000 : port, host, noLlm }
13
15
  }
14
16
 
15
17
  export async function run(argv) {
16
- const { port, host } = parseArgs(argv)
18
+ const { port, host, noLlm } = parseArgs(argv)
17
19
 
18
20
  const projectRoot = process.cwd()
19
21
  const chronicleDir = await findChronicleDir(projectRoot)
@@ -23,14 +25,31 @@ export async function run(argv) {
23
25
  process.exit(1)
24
26
  }
25
27
 
26
- const server = await createServer({ projectRoot, chronicleDir })
28
+ // Auto-detect LLM provider unless --no-llm is passed
29
+ let llmProvider = null
30
+ let llmName = null
31
+ if (!noLlm) {
32
+ const detected = await detectProvider()
33
+ if (detected) {
34
+ llmProvider = detected.llm
35
+ llmName = detected.name
36
+ }
37
+ }
38
+
39
+ const server = await createServer({ projectRoot, chronicleDir, llm: llmProvider })
27
40
 
28
41
  server.listen(port, host, () => {
29
42
  const base = `http://${host}:${port}`
30
43
  console.log(`\n${c.bold("Quorum")} ${c.dim(`serving ${projectRoot}`)}\n`)
31
44
  console.log(` ${c.cyan("UI")} ${c.dim(base + "/")}`)
32
45
  console.log(` ${c.cyan("MCP")} ${c.dim(base + "/mcp")}`)
33
- console.log(` ${c.cyan("Chronicle")} ${c.dim(chronicleDir)}\n`)
46
+ console.log(` ${c.cyan("Chronicle")} ${c.dim(chronicleDir)}`)
47
+ if (llmName) {
48
+ console.log(` ${c.cyan("LLM")} ${c.dim(llmName + " (advisor/check/compass enabled)")}`)
49
+ } else {
50
+ console.log(` ${c.cyan("LLM")} ${c.dim("none — LLM tools return CLI fallback hints")}`)
51
+ }
52
+ console.log()
34
53
  console.log(c.bold("Claude Desktop") + c.dim(" — add to claude_desktop_config.json:"))
35
54
  console.log(c.dim(JSON.stringify({
36
55
  mcpServers: { quorum: { type: "streamable-http", url: `${base}/mcp` } }
package/bin/mcp/server.js CHANGED
@@ -27,8 +27,11 @@ import {
27
27
  toolBrief,
28
28
  toolCoverage,
29
29
  toolGrowth,
30
+ toolCompass,
30
31
  commitProposal,
31
32
  deleteProposal,
33
+ updateProposal,
34
+ setLLM,
32
35
  } from "./tools.js"
33
36
 
34
37
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -124,10 +127,16 @@ async function handleMCP(body, defaultProjectRoot) {
124
127
  description: "Chronicle health score, entry counts, and guidance.",
125
128
  mimeType: "application/json",
126
129
  },
130
+ {
131
+ uri: "chronicle://compass",
132
+ name: "Compass product direction",
133
+ description: "Latest Compass map: behaviours, gaps, and opportunities detected from the codebase.",
134
+ mimeType: "application/json",
135
+ },
127
136
  {
128
137
  uriTemplate: "chronicle://entry/{id}",
129
138
  name: "Chronicle entry",
130
- description: "A single committed Chronicle entry by ID (prefix or full UUID).",
139
+ description: "A single committed Chronicle entry by id or 8-char prefix.",
131
140
  mimeType: "application/json",
132
141
  },
133
142
  ],
@@ -160,6 +169,11 @@ async function handleMCP(body, defaultProjectRoot) {
160
169
  return rpcOk(id, { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(result, null, 2) }] })
161
170
  }
162
171
 
172
+ if (uri === "chronicle://compass") {
173
+ const result = await toolCompass({ subcommand: "map", projectRoot: defaultProjectRoot })
174
+ return rpcOk(id, { contents: [{ uri, mimeType: "application/json", text: JSON.stringify(result, null, 2) }] })
175
+ }
176
+
163
177
  // chronicle://entry/{id}
164
178
  const entryMatch = uri.match(/^chronicle:\/\/entry\/(.+)$/)
165
179
  if (entryMatch) {
@@ -198,7 +212,9 @@ function setCORS(res) {
198
212
 
199
213
  // ── Server factory ────────────────────────────────────────────────────────────
200
214
 
201
- export async function createServer({ projectRoot, chronicleDir }) {
215
+ export async function createServer({ projectRoot, chronicleDir, llm = null }) {
216
+ // Wire LLM into tools module so advisor/check/compass MCP tools work
217
+ setLLM(llm)
202
218
  let uiHtml
203
219
  try {
204
220
  uiHtml = await fs.readFile(UI_PATH, "utf8")
@@ -284,6 +300,24 @@ export async function createServer({ projectRoot, chronicleDir }) {
284
300
  }
285
301
  }
286
302
 
303
+ // ── REST: edit/patch proposal ───────────────────────────────────────────
304
+ if (proposalMatch && req.method === "PATCH") {
305
+ try {
306
+ const body = await readBody(req)
307
+ const result = await updateProposal(proposalMatch[1], body, chronicleDir)
308
+ return json(res, 200, result)
309
+ } catch (err) {
310
+ return json(res, 400, { error: err.message })
311
+ }
312
+ }
313
+
314
+ // ── REST: compass ───────────────────────────────────────────────────────
315
+ if (pathname === "/api/compass" && req.method === "GET") {
316
+ const subcommand = new URL(req.url, "http://localhost").searchParams.get("subcommand") ?? "map"
317
+ const result = await toolCompass({ subcommand, projectRoot })
318
+ return json(res, 200, result)
319
+ }
320
+
287
321
  // ── Web UI ──────────────────────────────────────────────────────────────
288
322
  if ((pathname === "/" || pathname === "/index.html") && req.method === "GET") {
289
323
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" })
package/bin/mcp/tools.js CHANGED
@@ -263,26 +263,70 @@ export async function toolHelp({ topic } = {}) {
263
263
  return { topic, content: section }
264
264
  }
265
265
 
266
- // ── [TODO] LLM-powered placeholders ──────────────────────────────────────────
267
- // These tools require a live LLM provider wired into quorum serve (--llm flag).
268
- // They are registered so AI clients can discover them, but return a clear
269
- // "not yet available" message rather than silently failing.
266
+ // ── LLM-powered tools ─────────────────────────────────────────────────────────
267
+ // These are activated when quorum serve detects an LLM provider.
268
+ // Without a provider they return a clear CLI-fallback hint.
270
269
 
271
- const TODO_MESSAGE = (name) =>
272
- `${name} requires an LLM provider. This is planned — run 'quorum ${name.replace("quorum_", "")}' from the CLI for now, or watch for a future 'quorum serve --llm' flag.`
270
+ let _llm = null // set by createTools() at server startup
273
271
 
274
- export async function toolAdvisor({ question } = {}) {
272
+ const NO_LLM = (name) => ({
273
+ status: "no-llm",
274
+ message: `${name} requires an LLM provider. No provider was detected at startup. ` +
275
+ `Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY and restart quorum serve. ` +
276
+ `Alternatively run 'quorum ${name.replace("quorum_", "")}' from the CLI.`,
277
+ })
278
+
279
+ export async function toolAdvisor({ question, projectRoot } = {}) {
275
280
  if (!question) throw new Error("question is required")
276
- return { status: "todo", message: TODO_MESSAGE("quorum_advisor") }
281
+ if (!_llm) return NO_LLM("quorum_advisor")
282
+
283
+ const { ask } = await import("../../dist/advisor/index.js")
284
+ const { chronicleDir } = await resolve(projectRoot)
285
+ const { createOracleClient } = await import("../../dist/oracle/index.js")
286
+ const { xenovaEmbed } = await import("../../dist/oracle/adapters/xenova-embedder.js")
287
+ const { createLanceDBStore } = await import("../../dist/oracle/adapters/lance-db.js")
288
+
289
+ const store = await createLanceDBStore(chronicleDir)
290
+ const oracle = createOracleClient({ store, embed: xenovaEmbed })
291
+ const result = await ask({ question, oracle, llm: _llm })
292
+ return result
277
293
  }
278
294
 
279
- export async function toolCheck({ outcome, design } = {}) {
295
+ export async function toolCheck({ outcome, design, projectRoot } = {}) {
296
+ // quorum check is LLM-free — uses the same preflight + risk classifier as the CLI
280
297
  if (!outcome && !design) throw new Error("outcome or design is required")
281
- return { status: "todo", message: TODO_MESSAGE("quorum_check") }
298
+ const { runPreflight, classifyRisk } = await import("../shared/patterns.js")
299
+ const preflight = runPreflight(outcome ?? "", design ?? "")
300
+ const risk = classifyRisk(outcome ?? "", design ?? "")
301
+ return { preflight, risk }
282
302
  }
283
303
 
284
- export async function toolCompass({ subcommand } = {}) {
285
- return { status: "todo", message: TODO_MESSAGE("quorum_compass") }
304
+ export async function toolCompass({ subcommand = "brief", goal, idea, projectRoot } = {}) {
305
+ if (!_llm) return NO_LLM("quorum_compass")
306
+
307
+ const { chronicleDir } = await resolve(projectRoot)
308
+ // Delegate to the compass CLI command handler for now
309
+ const { run: compassRun } = await import("../commands/compass.js")
310
+ // Capture stdout
311
+ const captured = []
312
+ const origWrite = process.stdout.write.bind(process.stdout)
313
+ process.stdout.write = (chunk, ...rest) => { captured.push(String(chunk)); return true }
314
+ try {
315
+ const extraArgs = []
316
+ if (subcommand === "pathways" && goal) extraArgs.push("--goal", goal)
317
+ if (subcommand === "score" && idea) extraArgs.push("--idea", idea)
318
+ await compassRun([subcommand, ...extraArgs])
319
+ } finally {
320
+ process.stdout.write = origWrite
321
+ }
322
+ return { subcommand, output: captured.join("").trim() }
323
+ }
324
+
325
+ /**
326
+ * Call once at server startup to wire the LLM provider into LLM-powered tools.
327
+ */
328
+ export function setLLM(llmProvider) {
329
+ _llm = llmProvider
286
330
  }
287
331
 
288
332
  // ── Proposal commit (human-gate — UI only, never an MCP AI tool) ──────────────
@@ -316,6 +360,27 @@ export async function deleteProposal(proposalId, chronicleDir) {
316
360
  return { deleted: match.replace(".json", "") }
317
361
  }
318
362
 
363
+ export async function updateProposal(proposalId, patch, chronicleDir) {
364
+ const ALLOWED = ["topic", "decision", "key_insight", "status", "confidence",
365
+ "affected_areas", "scope", "alternatives_considered", "rejected_reason"]
366
+ const proposalsDir = path.join(chronicleDir, "proposals")
367
+ const files = await fs.readdir(proposalsDir).catch(() => [])
368
+ const match = files.find(f => f === `${proposalId}.json` || f.startsWith(proposalId))
369
+ if (!match) throw new Error(`Proposal not found: ${proposalId}`)
370
+
371
+ const proposalPath = path.join(proposalsDir, match)
372
+ const raw = await fs.readFile(proposalPath, "utf8")
373
+ const current = JSON.parse(raw)
374
+
375
+ // Only apply allowed fields — never let PATCH overwrite id/proposalId/schema_version
376
+ for (const key of ALLOWED) {
377
+ if (patch[key] !== undefined) current[key] = patch[key]
378
+ }
379
+
380
+ await fs.writeFile(proposalPath, JSON.stringify(current, null, 2), "utf8")
381
+ return { updated: match.replace(".json", ""), topic: current.topic }
382
+ }
383
+
319
384
  // ── MCP tool registry ─────────────────────────────────────────────────────────
320
385
 
321
386
  export const MCP_TOOLS = [
@@ -416,7 +481,7 @@ export const MCP_TOOLS = [
416
481
  // ── [TODO] LLM-powered tools ──
417
482
  {
418
483
  name: "quorum_advisor",
419
- description: "[TODO] Ask a plain-language question answered from Chronicle using an LLM. Requires 'quorum serve --llm'. Use 'quorum advisor' CLI for now.",
484
+ description: "Ask a plain-language question answered from Chronicle using an LLM. Returns a synthesised answer with evidence citations. Auto-activated when quorum serve detects an API key.",
420
485
  inputSchema: {
421
486
  type: "object",
422
487
  properties: {
@@ -428,7 +493,7 @@ export const MCP_TOOLS = [
428
493
  },
429
494
  {
430
495
  name: "quorum_check",
431
- description: "[TODO] Run instant risk triage on a design against Chronicle evidence. Requires 'quorum serve --llm'. Use 'quorum check' CLI for now.",
496
+ description: "Run instant risk triage on a design against Chronicle patterns no LLM required. Returns preflight flags and a risk level (low/medium/high/critical).",
432
497
  inputSchema: {
433
498
  type: "object",
434
499
  properties: {
@@ -440,7 +505,7 @@ export const MCP_TOOLS = [
440
505
  },
441
506
  {
442
507
  name: "quorum_compass",
443
- description: "[TODO] Product-direction synthesis — behaviours, pathways, bets, idea scoring. Requires 'quorum serve --llm'. Use 'quorum compass' CLI for now.",
508
+ description: "Product-direction synthesis — behaviours, pathways, bets, idea scoring. Auto-activated when quorum serve detects an LLM provider. Use subcommand: brief | map | pathways | bets | score | opportunities.",
444
509
  inputSchema: {
445
510
  type: "object",
446
511
  properties: {
package/bin/ui/app.html CHANGED
@@ -351,6 +351,179 @@
351
351
  ::-webkit-scrollbar { width: 6px; }
352
352
  ::-webkit-scrollbar-track { background: transparent; }
353
353
  ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
354
+
355
+ /* ── Growth ── */
356
+ .health-grid {
357
+ display: grid;
358
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
359
+ gap: 12px;
360
+ margin-bottom: 24px;
361
+ }
362
+ .health-stat {
363
+ background: var(--surface);
364
+ border: 1px solid var(--border);
365
+ border-radius: var(--radius);
366
+ padding: 14px 16px;
367
+ }
368
+ .health-stat-val {
369
+ font-size: 28px;
370
+ font-weight: 700;
371
+ color: var(--text);
372
+ line-height: 1;
373
+ }
374
+ .health-stat-lbl {
375
+ font-size: 11px;
376
+ color: var(--muted);
377
+ margin-top: 4px;
378
+ text-transform: uppercase;
379
+ letter-spacing: .06em;
380
+ }
381
+ .health-score-wrap {
382
+ display: flex;
383
+ align-items: center;
384
+ gap: 16px;
385
+ margin-bottom: 24px;
386
+ background: var(--surface);
387
+ border: 1px solid var(--border);
388
+ border-radius: var(--radius);
389
+ padding: 20px 24px;
390
+ }
391
+ .health-score-num {
392
+ font-size: 52px;
393
+ font-weight: 800;
394
+ line-height: 1;
395
+ }
396
+ .health-score-text { flex: 1; }
397
+ .health-score-label {
398
+ font-size: 13px;
399
+ font-weight: 700;
400
+ text-transform: uppercase;
401
+ letter-spacing: .08em;
402
+ margin-bottom: 4px;
403
+ }
404
+ .health-hint { font-size: 13px; color: var(--muted); }
405
+ .hint-thriving { color: var(--green); }
406
+ .hint-healthy { color: var(--accent); }
407
+ .hint-slow { color: var(--yellow); }
408
+ .hint-stalled { color: var(--red); }
409
+
410
+ /* ── Compass ── */
411
+ .compass-output {
412
+ background: var(--surface);
413
+ border: 1px solid var(--border);
414
+ border-radius: var(--radius);
415
+ padding: 16px;
416
+ font-family: var(--mono);
417
+ font-size: 12px;
418
+ white-space: pre-wrap;
419
+ color: var(--muted);
420
+ max-height: 500px;
421
+ overflow-y: auto;
422
+ line-height: 1.6;
423
+ }
424
+
425
+ /* ── Edit modal ── */
426
+ .modal-overlay {
427
+ display: none;
428
+ position: fixed;
429
+ inset: 0;
430
+ background: rgba(0,0,0,.65);
431
+ z-index: 200;
432
+ align-items: center;
433
+ justify-content: center;
434
+ }
435
+ .modal-overlay.open { display: flex; }
436
+ .modal {
437
+ background: var(--surface);
438
+ border: 1px solid var(--border);
439
+ border-radius: 10px;
440
+ padding: 24px;
441
+ width: min(560px, 94vw);
442
+ max-height: 90vh;
443
+ overflow-y: auto;
444
+ }
445
+ .modal h3 { font-size: 15px; font-weight: 700; margin-bottom: 16px; }
446
+ .field { margin-bottom: 14px; }
447
+ .field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 5px; }
448
+ .field input, .field textarea, .field select {
449
+ width: 100%;
450
+ padding: 8px 12px;
451
+ background: var(--bg);
452
+ border: 1px solid var(--border);
453
+ border-radius: var(--radius);
454
+ color: var(--text);
455
+ font: inherit;
456
+ font-size: 13px;
457
+ outline: none;
458
+ transition: border-color .15s;
459
+ resize: vertical;
460
+ }
461
+ .field input:focus, .field textarea:focus, .field select:focus { border-color: var(--accent); }
462
+ .modal-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 20px; }
463
+
464
+ /* ── Evidence/jury breakdown on proposals ── */
465
+ .evidence-tags { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
466
+ .evidence-tag {
467
+ font-family: var(--mono);
468
+ font-size: 10px;
469
+ padding: 2px 6px;
470
+ border-radius: 4px;
471
+ background: rgba(124,110,255,.12);
472
+ color: var(--accent);
473
+ border: 1px solid rgba(124,110,255,.2);
474
+ }
475
+ .breakdown-row {
476
+ display: flex;
477
+ align-items: center;
478
+ gap: 8px;
479
+ font-size: 11px;
480
+ color: var(--muted);
481
+ margin-top: 3px;
482
+ }
483
+ .breakdown-label { width: 128px; flex-shrink: 0; }
484
+ .breakdown-bar {
485
+ flex: 1;
486
+ height: 3px;
487
+ border-radius: 2px;
488
+ background: var(--border);
489
+ overflow: hidden;
490
+ }
491
+ .breakdown-fill { height: 100%; border-radius: 2px; background: var(--accent); }
492
+ .breakdown-pct { width: 28px; text-align: right; color: var(--muted); }
493
+ .jury-section {
494
+ margin-top: 10px;
495
+ padding: 8px 10px;
496
+ background: rgba(124,110,255,.05);
497
+ border: 1px solid rgba(124,110,255,.12);
498
+ border-radius: 6px;
499
+ }
500
+ .jury-section-title {
501
+ font-size: 10px;
502
+ font-weight: 700;
503
+ text-transform: uppercase;
504
+ letter-spacing: .07em;
505
+ color: var(--muted);
506
+ margin-bottom: 6px;
507
+ }
508
+ .council-section {
509
+ margin-top: 8px;
510
+ padding: 8px 10px;
511
+ background: rgba(82,168,224,.05);
512
+ border: 1px solid rgba(82,168,224,.12);
513
+ border-radius: 6px;
514
+ font-size: 12px;
515
+ color: var(--blue);
516
+ }
517
+ .council-section-title {
518
+ font-size: 10px;
519
+ font-weight: 700;
520
+ text-transform: uppercase;
521
+ letter-spacing: .07em;
522
+ color: var(--muted);
523
+ margin-bottom: 6px;
524
+ }
525
+ .council-section ul { margin: 0 0 0 14px; }
526
+ .council-section li { margin-bottom: 2px; color: var(--blue); }
354
527
  </style>
355
528
  </head>
356
529
  <body>
@@ -361,6 +534,8 @@
361
534
  <button class="active" onclick="showTab('chronicle')">Chronicle</button>
362
535
  <button onclick="showTab('proposals')">Proposals <span class="badge" id="proposalCount" style="display:none"></span></button>
363
536
  <button onclick="showTab('coverage')">Coverage</button>
537
+ <button onclick="showTab('growth')">Growth</button>
538
+ <button onclick="showTab('compass')">Compass</button>
364
539
  </nav>
365
540
  </header>
366
541
 
@@ -386,8 +561,63 @@
386
561
  <p class="section-sub">Source files with Chronicle entries referencing them.</p>
387
562
  <div id="coverageView"><div class="loading">Loading…</div></div>
388
563
  </div>
564
+
565
+ <!-- ── Growth tab ───────────────────────────────────────────────── -->
566
+ <div id="tab-growth" class="tab">
567
+ <h2 class="section-heading">Growth</h2>
568
+ <p class="section-sub">Chronicle memory health — how actively this codebase is learning.</p>
569
+ <div id="growthView"><div class="loading">Loading…</div></div>
570
+ </div>
571
+
572
+ <!-- ── Compass tab ──────────────────────────────────────────────── -->
573
+ <div id="tab-compass" class="tab">
574
+ <h2 class="section-heading">Compass</h2>
575
+ <p class="section-sub">Product-direction synthesis — behaviours, gaps, and opportunities.</p>
576
+ <div class="toolbar" style="margin-bottom:20px">
577
+ <select id="compassSubcmd" style="max-width:200px;flex:none;padding:8px 12px;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);color:var(--text);font:inherit;font-size:13px;outline:none">
578
+ <option value="map">Behaviour map</option>
579
+ <option value="brief">Direction brief</option>
580
+ <option value="opportunities">Opportunities</option>
581
+ <option value="bets">Strategic bets</option>
582
+ </select>
583
+ <button class="btn" onclick="loadCompass()" style="flex:none">Run</button>
584
+ </div>
585
+ <div id="compassView"><div class="empty">Select a subcommand and click Run.<small>Requires an LLM provider configured for quorum serve.</small></div></div>
586
+ </div>
389
587
  </main>
390
588
 
589
+ <!-- ── Edit proposal modal ───────────────────────────────────────────── -->
590
+ <div class="modal-overlay" id="editModal" onclick="closeEditModal(event)">
591
+ <div class="modal" onclick="event.stopPropagation()">
592
+ <h3>Edit proposal</h3>
593
+ <input type="hidden" id="editId">
594
+ <div class="field">
595
+ <label>Topic</label>
596
+ <input type="text" id="editTopic">
597
+ </div>
598
+ <div class="field">
599
+ <label>Decision / key insight</label>
600
+ <textarea id="editDecision" rows="5"></textarea>
601
+ </div>
602
+ <div class="field">
603
+ <label>Status</label>
604
+ <select id="editStatus">
605
+ <option value="open">open</option>
606
+ <option value="validated">validated</option>
607
+ <option value="refuted">refuted</option>
608
+ </select>
609
+ </div>
610
+ <div class="field">
611
+ <label>Confidence (0–1)</label>
612
+ <input type="number" id="editConfidence" min="0" max="1" step="0.05">
613
+ </div>
614
+ <div class="modal-actions">
615
+ <button class="btn" onclick="closeEditModal()">Cancel</button>
616
+ <button class="btn btn-approve" onclick="saveEdit()">Save changes</button>
617
+ </div>
618
+ </div>
619
+ </div>
620
+
391
621
  <div id="toast"></div>
392
622
 
393
623
  <script>
@@ -396,6 +626,7 @@
396
626
  let allEntries = []
397
627
  let allProposals = []
398
628
  let coverageData = null
629
+ let growthData = null
399
630
  let searchTimer = null
400
631
  let activeTab = "chronicle"
401
632
 
@@ -404,19 +635,22 @@ let activeTab = "chronicle"
404
635
  window.addEventListener("DOMContentLoaded", () => {
405
636
  loadChronicle()
406
637
  loadProposals()
407
- // Lazy-load coverage when tab is first opened
638
+ // Other tabs loaded lazily on first open
408
639
  })
409
640
 
410
641
  // ── Tab switching ──────────────────────────────────────────────────────────
411
642
 
643
+ const TAB_NAMES = ["chronicle", "proposals", "coverage", "growth", "compass"]
644
+
412
645
  function showTab(name) {
413
646
  document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"))
414
647
  document.querySelectorAll("nav button").forEach((b, i) => {
415
- b.classList.toggle("active", ["chronicle","proposals","coverage"][i] === name)
648
+ b.classList.toggle("active", TAB_NAMES[i] === name)
416
649
  })
417
650
  document.getElementById(`tab-${name}`).classList.add("active")
418
651
  activeTab = name
419
652
  if (name === "coverage" && !coverageData) loadCoverage()
653
+ if (name === "growth" && !growthData) loadGrowth()
420
654
  }
421
655
 
422
656
  // ── Toast ──────────────────────────────────────────────────────────────────
@@ -541,10 +775,49 @@ async function loadProposals() {
541
775
  }
542
776
  }
543
777
 
778
+ function juryBreakdown(jury) {
779
+ if (!jury?.breakdown) return ""
780
+ const dims = [
781
+ ["Evidence support", jury.breakdown.evidence_support],
782
+ ["Feasibility", jury.breakdown.feasibility],
783
+ ["Risk", jury.breakdown.risk],
784
+ ["Completeness", jury.breakdown.completeness],
785
+ ]
786
+ const rows = dims.filter(([,v]) => v != null).map(([label, v]) => {
787
+ const pct = Math.round(v * 100)
788
+ return `<div class="breakdown-row">
789
+ <span class="breakdown-label">${esc(label)}</span>
790
+ <span class="breakdown-bar"><span class="breakdown-fill" style="width:${pct}%"></span></span>
791
+ <span class="breakdown-pct">${pct}%</span>
792
+ </div>`
793
+ }).join("")
794
+ if (!rows) return ""
795
+ return `<div class="jury-section">
796
+ <div class="jury-section-title">Jury · confidence ${Math.round((jury.confidence ?? 0) * 100)}% · ${esc(jury.recommendation ?? "")}</div>
797
+ ${rows}
798
+ </div>`
799
+ }
800
+
801
+ function councilConditions(council) {
802
+ if (!council?.conditions?.length) return ""
803
+ const items = council.conditions.map(c => `<li>${esc(c)}</li>`).join("")
804
+ const sat = council.satisfied ? "✓ satisfied" : "✗ not satisfied"
805
+ return `<div class="council-section">
806
+ <div class="council-section-title">Council · ${esc(sat)} · ${esc(council.recommendation ?? "")}</div>
807
+ <ul>${items}</ul>
808
+ </div>`
809
+ }
810
+
811
+ function evidenceTags(ids) {
812
+ if (!ids?.length) return ""
813
+ const tags = ids.map(id => `<span class="evidence-tag">${esc(String(id).slice(0,8))}</span>`).join("")
814
+ return `<div class="evidence-tags">${tags}</div>`
815
+ }
816
+
544
817
  function renderProposals(proposals) {
545
818
  const el = document.getElementById("proposalList")
546
819
  if (!proposals.length) {
547
- el.innerHTML = `<div class="empty">No pending proposals<small>AI agents stage proposals via <code>chronicle_propose</code> or <code>quorum commit --list</code></small></div>`
820
+ el.innerHTML = `<div class="empty">No pending proposals<small>AI agents stage proposals via <code>quorum_stage</code> MCP tool or <code>oracle.propose()</code></small></div>`
548
821
  return
549
822
  }
550
823
  el.innerHTML = proposals.map(p => `
@@ -555,13 +828,18 @@ function renderProposals(proposals) {
555
828
  </div>
556
829
  <div class="card-body">${esc(p.decision ?? p.key_insight ?? "")}</div>
557
830
  ${areas(p.affected_areas)}
831
+ ${evidenceTags(p.evidence_cited)}
832
+ ${juryBreakdown(p.jury)}
833
+ ${councilConditions(p.council)}
558
834
  <div class="card-meta" style="margin-top:10px">
559
835
  <span style="font-family:var(--mono);font-size:11px;color:var(--muted)">${esc(p.proposalId?.slice(0,8))}</span>
560
836
  ${confidenceBar(p.confidence)}
837
+ ${p.source_module ? `<span style="font-size:11px;color:var(--muted)">${esc(p.source_module)}</span>` : ""}
561
838
  </div>
562
839
  <div class="actions">
563
840
  <button class="btn btn-approve" onclick="approveProposal('${esc(p.proposalId)}', this)">✓ Approve</button>
564
841
  <button class="btn btn-reject" onclick="rejectProposal('${esc(p.proposalId)}', this)">✕ Reject</button>
842
+ <button class="btn" onclick="openEditModal('${esc(p.proposalId)}')">Edit</button>
565
843
  </div>
566
844
  </div>
567
845
  `).join("")
@@ -603,6 +881,48 @@ async function rejectProposal(id, btn) {
603
881
  }
604
882
  }
605
883
 
884
+ // ── Edit modal ────────────────────────────────────────────────────────────
885
+
886
+ function openEditModal(id) {
887
+ const p = allProposals.find(x => x.proposalId === id)
888
+ if (!p) return
889
+ document.getElementById("editId").value = id
890
+ document.getElementById("editTopic").value = p.topic ?? ""
891
+ document.getElementById("editDecision").value = p.decision ?? p.key_insight ?? ""
892
+ document.getElementById("editStatus").value = p.status ?? "open"
893
+ document.getElementById("editConfidence").value = p.confidence ?? 0.7
894
+ document.getElementById("editModal").classList.add("open")
895
+ }
896
+
897
+ function closeEditModal(e) {
898
+ if (e && e.target !== document.getElementById("editModal")) return
899
+ document.getElementById("editModal").classList.remove("open")
900
+ }
901
+
902
+ async function saveEdit() {
903
+ const id = document.getElementById("editId").value
904
+ const topic = document.getElementById("editTopic").value.trim()
905
+ const decision = document.getElementById("editDecision").value.trim()
906
+ const status = document.getElementById("editStatus").value
907
+ const confidence = parseFloat(document.getElementById("editConfidence").value)
908
+
909
+ if (!topic || !decision) { toast("Topic and decision are required", "err"); return }
910
+
911
+ try {
912
+ const res = await fetch(`/api/proposals/${encodeURIComponent(id)}`, {
913
+ method: "PATCH",
914
+ headers: { "content-type": "application/json" },
915
+ body: JSON.stringify({ topic, decision, key_insight: decision, status, confidence }),
916
+ })
917
+ if (!res.ok) throw new Error((await res.json()).error)
918
+ document.getElementById("editModal").classList.remove("open")
919
+ toast("Proposal updated")
920
+ loadProposals()
921
+ } catch (err) {
922
+ toast(err.message, "err")
923
+ }
924
+ }
925
+
606
926
  // ── Coverage ──────────────────────────────────────────────────────────────
607
927
 
608
928
  async function loadCoverage() {
@@ -671,6 +991,99 @@ function renderCoverage(data) {
671
991
  ${totalFiles === 0 ? `<div class="empty">No source files found</div>` : ""}
672
992
  `
673
993
  }
994
+
995
+ // ── Growth ────────────────────────────────────────────────────────────────
996
+
997
+ async function loadGrowth() {
998
+ document.getElementById("growthView").innerHTML = `<div class="loading">Loading…</div>`
999
+ try {
1000
+ const res = await fetch("/api/growth")
1001
+ growthData = await res.json()
1002
+ renderGrowth(growthData)
1003
+ } catch (err) {
1004
+ document.getElementById("growthView").innerHTML =
1005
+ `<div class="empty">Failed to load growth data<small>${esc(err.message)}</small></div>`
1006
+ }
1007
+ }
1008
+
1009
+ function renderGrowth(data) {
1010
+ const { health, entries, proposals, hint } = data
1011
+ const col = health >= 80 ? "var(--green)" : health >= 60 ? "var(--accent)" : health >= 40 ? "var(--yellow)" : "var(--red)"
1012
+ const label = health >= 80 ? "THRIVING" : health >= 60 ? "HEALTHY" : health >= 40 ? "SLOW" : "STALLED"
1013
+
1014
+ document.getElementById("growthView").innerHTML = `
1015
+ <div class="health-score-wrap">
1016
+ <div class="health-score-num" style="color:${col}">${health}</div>
1017
+ <div class="health-score-text">
1018
+ <div class="health-score-label" style="color:${col}">${label}</div>
1019
+ <div class="health-hint">${esc(hint ?? "")}</div>
1020
+ </div>
1021
+ </div>
1022
+
1023
+ <div class="health-grid">
1024
+ <div class="health-stat">
1025
+ <div class="health-stat-val">${entries.total}</div>
1026
+ <div class="health-stat-lbl">Total entries</div>
1027
+ </div>
1028
+ <div class="health-stat">
1029
+ <div class="health-stat-val" style="color:var(--green)">${entries.byStatus.validated ?? 0}</div>
1030
+ <div class="health-stat-lbl">Validated</div>
1031
+ </div>
1032
+ <div class="health-stat">
1033
+ <div class="health-stat-val" style="color:var(--blue)">${entries.byStatus.open ?? 0}</div>
1034
+ <div class="health-stat-lbl">Open</div>
1035
+ </div>
1036
+ <div class="health-stat">
1037
+ <div class="health-stat-val" style="color:var(--red)">${entries.byStatus.refuted ?? 0}</div>
1038
+ <div class="health-stat-lbl">Refuted</div>
1039
+ </div>
1040
+ <div class="health-stat">
1041
+ <div class="health-stat-val" style="color:var(--yellow)">${proposals.pending}</div>
1042
+ <div class="health-stat-lbl">Pending proposals</div>
1043
+ </div>
1044
+ <div class="health-stat">
1045
+ <div class="health-stat-val">${Math.round((entries.avgConfidence ?? 0) * 100)}%</div>
1046
+ <div class="health-stat-lbl">Avg confidence</div>
1047
+ </div>
1048
+ </div>
1049
+
1050
+ ${proposals.pending > 0 ? `
1051
+ <div class="card" style="border-color:rgba(224,185,82,.3);background:rgba(224,185,82,.04)">
1052
+ <div class="card-body" style="color:var(--yellow)">
1053
+ ${proposals.pending} proposal${proposals.pending === 1 ? "" : "s"} awaiting approval.
1054
+ <a href="#" onclick="showTab('proposals');return false" style="color:var(--accent);text-decoration:none;margin-left:6px">Review →</a>
1055
+ </div>
1056
+ </div>` : ""}
1057
+ `
1058
+ }
1059
+
1060
+ // ── Compass ───────────────────────────────────────────────────────────────
1061
+
1062
+ async function loadCompass() {
1063
+ const subcommand = document.getElementById("compassSubcmd").value
1064
+ document.getElementById("compassView").innerHTML = `<div class="loading">Running compass ${esc(subcommand)}…</div>`
1065
+ try {
1066
+ const res = await fetch(`/api/compass?subcommand=${encodeURIComponent(subcommand)}`)
1067
+ const data = await res.json()
1068
+ renderCompass(data, subcommand)
1069
+ } catch (err) {
1070
+ document.getElementById("compassView").innerHTML =
1071
+ `<div class="empty">Failed to run compass<small>${esc(err.message)}</small></div>`
1072
+ }
1073
+ }
1074
+
1075
+ function renderCompass(data, subcommand) {
1076
+ const el = document.getElementById("compassView")
1077
+ if (data.status === "no-llm") {
1078
+ el.innerHTML = `<div class="empty">${esc(data.message)}<small>Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY and restart quorum serve.</small></div>`
1079
+ return
1080
+ }
1081
+ const output = data.output ?? JSON.stringify(data, null, 2)
1082
+ el.innerHTML = `
1083
+ <div style="font-size:12px;color:var(--muted);margin-bottom:10px">compass ${esc(subcommand)}</div>
1084
+ <div class="compass-output">${esc(output)}</div>
1085
+ `
1086
+ }
674
1087
  </script>
675
1088
  </body>
676
1089
  </html>
@@ -1 +1 @@
1
- {"version":3,"file":"lance-db.d.ts","sourceRoot":"","sources":["../../../modules/oracle/adapters/lance-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAa9C,wBAAsB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAyDnF"}
1
+ {"version":3,"file":"lance-db.d.ts","sourceRoot":"","sources":["../../../modules/oracle/adapters/lance-db.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAa9C,wBAAsB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA0DnF"}
@@ -64,7 +64,8 @@ export async function createLanceDBStore(chronicleDir) {
64
64
  const t = await getOrCreateTable();
65
65
  if (!t)
66
66
  return [];
67
- const rows = await t.query().execute();
67
+ // vectordb v0.4.x has no t.query() — use filter with a tautology to fetch all rows
68
+ const rows = await t.filter("id IS NOT NULL").execute();
68
69
  return rows.map(row => JSON.parse(row.payload));
69
70
  },
70
71
  };
@@ -1 +1 @@
1
- {"version":3,"file":"lance-db.js","sourceRoot":"","sources":["../../../modules/oracle/adapters/lance-db.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lance-db.js","sourceRoot":"","sources":["../../../modules/oracle/adapters/lance-db.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@balpal4495/quorum",
3
- "version": "3.4.0",
3
+ "version": "3.5.0",
4
4
  "description": "Git-backed memory and design review for AI coding agents",
5
5
  "type": "module",
6
6
  "license": "MIT",