@balpal4495/quorum 0.4.2 → 2.0.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/CLAUDE.md +102 -42
- package/README.md +317 -223
- package/SETUP.md +68 -94
- package/bin/commands/advisor.js +301 -0
- package/bin/commands/commit.js +42 -52
- package/bin/commands/evolve.js +285 -0
- package/bin/commands/growth.js +139 -0
- package/bin/commands/init.js +59 -60
- package/bin/commands/sentinel.js +1 -1
- package/bin/quorum.js +28 -0
- package/bin/shared/llm.js +228 -0
- package/modules/AGENTS.md +8 -0
- package/modules/CLAUDE.md +8 -2
- package/modules/README.md +72 -13
- package/modules/advisor/ask.ts +87 -0
- package/modules/advisor/index.ts +2 -0
- package/modules/advisor/prompt.ts +50 -0
- package/modules/advisor/types.ts +26 -0
- package/modules/setup.ts +15 -0
- package/package.json +4 -2
- package/bin/init.js +0 -378
package/modules/README.md
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
# Oracle · Jury · Council · Sentinel
|
|
1
|
+
# Advisor · Oracle · Jury · Council · Sentinel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Five portable modules for the knowledge and reasoning layer of any agentic workflow.
|
|
4
4
|
Drop the `modules/` folder into your project and wire up the dependencies.
|
|
5
5
|
|
|
6
6
|
```
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Advisor → plain-language questions answered from Chronicle
|
|
8
|
+
Oracle → Jury → Council → human gate → Executor
|
|
9
|
+
Sentinel → coverage + drift + PR coverage map
|
|
9
10
|
```
|
|
10
11
|
|
|
11
12
|
---
|
|
@@ -14,6 +15,7 @@ Sentinel → coverage + drift + PR coverage map
|
|
|
14
15
|
|
|
15
16
|
| Module | Responsibility | LLM? |
|
|
16
17
|
|---|---|---|
|
|
18
|
+
| **Advisor** | Ask a plain-language question — synthesises Chronicle evidence into a concise answer with an internal validation loop | Yes |
|
|
17
19
|
| **Oracle** | Query and write interface to Chronicle (the persistent knowledge store) | No |
|
|
18
20
|
| **Jury** | Evaluate a design against Oracle evidence — produces a confidence score | Yes |
|
|
19
21
|
| **Council** | Adversarial validation via parallel advisor/reviewer fan-out — produces a verdict | Yes |
|
|
@@ -107,18 +109,34 @@ Recommended `tsconfig.json` settings:
|
|
|
107
109
|
|
|
108
110
|
## Quick start
|
|
109
111
|
|
|
112
|
+
```typescript
|
|
113
|
+
import { setup } from "./modules/setup"
|
|
114
|
+
|
|
115
|
+
// The simplest entry point — wires all modules from one call
|
|
116
|
+
const { oracle, evaluate, deliberate, ask } = await setup({ llm: myLLMProvider })
|
|
117
|
+
|
|
118
|
+
// Ask a plain-language question (Advisor)
|
|
119
|
+
const answer = await ask("what did the team decide about authentication?")
|
|
120
|
+
// answer.what_we_know, .recommendation, .next_step, .risks, .blockers, .retries
|
|
121
|
+
|
|
122
|
+
// Or run the full evaluation pipeline for a proposed design:
|
|
123
|
+
const evidence = await oracle.query("authentication patterns in this codebase")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Manual wiring (without setup())
|
|
127
|
+
|
|
110
128
|
```typescript
|
|
111
129
|
import { createOracleClient, xenovaEmbed, createLanceDBStore } from "./modules/oracle"
|
|
112
130
|
import { evaluate } from "./modules/jury"
|
|
113
131
|
import { deliberate } from "./modules/council"
|
|
114
132
|
|
|
115
|
-
//
|
|
133
|
+
// Wire Oracle manually
|
|
116
134
|
const oracle = createOracleClient({
|
|
117
135
|
embedder: xenovaEmbed,
|
|
118
136
|
vectorStore: await createLanceDBStore(".chronicle"),
|
|
119
137
|
})
|
|
120
138
|
|
|
121
|
-
//
|
|
139
|
+
// Retrieve evidence for the task at hand
|
|
122
140
|
const evidence = await oracle.query("authentication patterns in this codebase")
|
|
123
141
|
|
|
124
142
|
// 3. Jury evaluates the design against the evidence
|
|
@@ -167,6 +185,47 @@ if (verdict.satisfied) {
|
|
|
167
185
|
|
|
168
186
|
---
|
|
169
187
|
|
|
188
|
+
## Advisor
|
|
189
|
+
|
|
190
|
+
The Advisor is the plain-language interface to Chronicle. Use it to answer questions rather than to evaluate designs. It is a **read-only** path — it never calls `oracle.propose()` or `oracle.commit()`.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { ask } from "./modules/advisor"
|
|
194
|
+
|
|
195
|
+
const answer = await ask(
|
|
196
|
+
{ question: "What did the team decide about session handling?", evidence },
|
|
197
|
+
{ llm: myLLMProvider },
|
|
198
|
+
)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Or via `setup()`, which queries Oracle automatically:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const { ask } = await setup({ llm: myLLMProvider })
|
|
205
|
+
const answer = await ask("What did the team decide about session handling?")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Advisor output
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
interface AdvisorOutput {
|
|
212
|
+
question: string
|
|
213
|
+
confidence: number // 0–1 — how confident the answer is given the evidence
|
|
214
|
+
what_we_know: string // plain-language summary of relevant Chronicle knowledge
|
|
215
|
+
risks: string[] // real risks worth knowing
|
|
216
|
+
blockers: string[] // hard blockers — must be resolved before acting (often empty)
|
|
217
|
+
recommendation: string // one clear recommended action
|
|
218
|
+
next_step: string // specific next step or quorum command
|
|
219
|
+
retries: number // how many retry attempts were needed (0 = first try succeeded)
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Validation loop
|
|
224
|
+
|
|
225
|
+
Advisor validates its own answer before returning. If `confidence < 0.7` or `blockers.length > 0`, it retries the LLM call with the previous answer as context — up to 2 retries. After the retry budget is exhausted, the best answer is returned regardless. Throws on non-JSON or schema-invalid LLM output.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
170
229
|
## LLM provider interface
|
|
171
230
|
|
|
172
231
|
The `LLMProvider` type is a simple function. Wire it to any provider:
|
|
@@ -284,14 +343,14 @@ const risk = classifyRisk(outcome, design, evidence)
|
|
|
284
343
|
// risk.council_mode — "jury-only" | "lite" | "full"
|
|
285
344
|
```
|
|
286
345
|
|
|
287
|
-
| Risk | Triggers | Advisor + Reviewer
|
|
288
|
-
|
|
289
|
-
| Low | Nothing sensitive detected |
|
|
290
|
-
| Medium | Cache, queues, deployments, rate limiting | 1 + 2 |
|
|
291
|
-
| High | DB migrations, permissions, PII, secrets | 5 + 5 |
|
|
292
|
-
| Critical | Auth, payments, crypto, data deletion | 5 + 5 |
|
|
346
|
+
| Risk | Triggers | Council mode | Advisor + Reviewer |
|
|
347
|
+
|---|---|---|---|
|
|
348
|
+
| Low | Nothing sensitive detected | jury-only — skipped | 0 + 0 |
|
|
349
|
+
| Medium | Cache, queues, deployments, rate limiting | lite | 1 + 2 |
|
|
350
|
+
| High | DB migrations, permissions, PII, secrets | full | 5 + 5 |
|
|
351
|
+
| Critical | Auth, payments, crypto, data deletion | full + human flag | 5 + 5 |
|
|
293
352
|
|
|
294
|
-
Refuted entries in the evidence pack always elevate risk by at least one level.
|
|
353
|
+
Refuted entries in the evidence pack always elevate risk by at least one level. `jury-only` means Council is not called at all — Jury alone is sufficient for low-risk designs.
|
|
295
354
|
|
|
296
355
|
### Council output routing
|
|
297
356
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import type { AdvisorInput, AdvisorOutput, AdvisorAnswer, AdvisorDeps } from "./types"
|
|
3
|
+
import { SYSTEM_PROMPT, buildUserPrompt } from "./prompt"
|
|
4
|
+
|
|
5
|
+
const SATISFACTION_THRESHOLD = 0.7
|
|
6
|
+
const MAX_RETRIES = 2
|
|
7
|
+
|
|
8
|
+
const AdvisorAnswerSchema = z.object({
|
|
9
|
+
confidence: z.number().min(0).max(1),
|
|
10
|
+
what_we_know: z.string().min(1),
|
|
11
|
+
risks: z.array(z.string()),
|
|
12
|
+
blockers: z.array(z.string()),
|
|
13
|
+
recommendation: z.string().min(1),
|
|
14
|
+
next_step: z.string().min(1),
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
async function callLLM(
|
|
18
|
+
input: AdvisorInput,
|
|
19
|
+
deps: AdvisorDeps,
|
|
20
|
+
attempt: number,
|
|
21
|
+
previous: AdvisorAnswer | null,
|
|
22
|
+
): Promise<AdvisorAnswer> {
|
|
23
|
+
const { llm, model } = deps
|
|
24
|
+
|
|
25
|
+
let userPrompt = buildUserPrompt(input.question, input.evidence)
|
|
26
|
+
|
|
27
|
+
if (attempt > 0 && previous) {
|
|
28
|
+
userPrompt += [
|
|
29
|
+
"",
|
|
30
|
+
`## Previous Answer (attempt ${attempt} — did not meet quality threshold)`,
|
|
31
|
+
`Confidence: ${previous.confidence.toFixed(2)} (need ≥ ${SATISFACTION_THRESHOLD})`,
|
|
32
|
+
previous.blockers.length > 0
|
|
33
|
+
? `Unresolved blockers: ${previous.blockers.join("; ")}`
|
|
34
|
+
: "",
|
|
35
|
+
"Please produce a more specific and concrete answer.",
|
|
36
|
+
].filter(Boolean).join("\n")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const raw = await llm(
|
|
40
|
+
[
|
|
41
|
+
{ role: "system", content: SYSTEM_PROMPT },
|
|
42
|
+
{ role: "user", content: userPrompt },
|
|
43
|
+
],
|
|
44
|
+
model,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
let parsed: unknown
|
|
48
|
+
try {
|
|
49
|
+
const cleaned = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```$/m, "").trim()
|
|
50
|
+
parsed = JSON.parse(cleaned)
|
|
51
|
+
} catch {
|
|
52
|
+
throw new Error(`Advisor: LLM returned non-JSON. Raw (first 300 chars): ${raw.slice(0, 300)}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const result = AdvisorAnswerSchema.safeParse(parsed)
|
|
56
|
+
if (!result.success) {
|
|
57
|
+
throw new Error(`Advisor: LLM output failed validation. Issues: ${JSON.stringify(result.error.issues)}`)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result.data
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Ask the Advisor a plain-language question.
|
|
65
|
+
*
|
|
66
|
+
* Internally calls the LLM and validates the answer against a satisfaction
|
|
67
|
+
* threshold (confidence ≥ 0.7, no blockers). Retries up to MAX_RETRIES times
|
|
68
|
+
* with the previous answer included as context. Returns the best answer found
|
|
69
|
+
* within the retry budget regardless of whether the threshold was met.
|
|
70
|
+
*
|
|
71
|
+
* Throws if the LLM returns non-JSON or output that fails schema validation.
|
|
72
|
+
*/
|
|
73
|
+
export async function ask(input: AdvisorInput, deps: AdvisorDeps): Promise<AdvisorOutput> {
|
|
74
|
+
let last: AdvisorAnswer | null = null
|
|
75
|
+
|
|
76
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
77
|
+
const answer = await callLLM(input, deps, attempt, last)
|
|
78
|
+
last = answer
|
|
79
|
+
|
|
80
|
+
const satisfied = answer.confidence >= SATISFACTION_THRESHOLD && answer.blockers.length === 0
|
|
81
|
+
if (satisfied || attempt === MAX_RETRIES) {
|
|
82
|
+
return { ...answer, question: input.question, retries: attempt }
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { ...last!, question: input.question, retries: MAX_RETRIES }
|
|
87
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { OracleResult } from "../shared/types"
|
|
2
|
+
import { entryText } from "../shared/types"
|
|
3
|
+
|
|
4
|
+
export const SYSTEM_PROMPT = `You are the Quorum Advisor — the plain-language interface to a team's collective knowledge.
|
|
5
|
+
|
|
6
|
+
You receive a question from a developer or engineering manager, along with relevant Chronicle evidence.
|
|
7
|
+
Synthesise that evidence into a clear, concise answer a human can act on.
|
|
8
|
+
|
|
9
|
+
Rules:
|
|
10
|
+
- Write for a human who does not know what "Chronicle entries" or "vector search" mean.
|
|
11
|
+
- Be direct. One clear recommendation, not a list of options unless genuinely necessary.
|
|
12
|
+
- If Chronicle has relevant evidence, reference it plainly: "the team already decided X".
|
|
13
|
+
- If Chronicle has no evidence, say so honestly — do not invent history.
|
|
14
|
+
- Blockers are hard blockers only — things that MUST be resolved before moving forward.
|
|
15
|
+
- risks are real concerns worth knowing, not theoretical edge cases.
|
|
16
|
+
|
|
17
|
+
Return ONLY valid JSON matching this schema (no markdown fences, no explanation):
|
|
18
|
+
{
|
|
19
|
+
"confidence": <number 0–1 — how confident are you given the available evidence>,
|
|
20
|
+
"what_we_know": <string — what Chronicle knows about this topic. Plain English, 1–3 sentences.>,
|
|
21
|
+
"risks": [<string — each real risk, plain English, one per item. Empty array if none.>],
|
|
22
|
+
"blockers": [<string — hard blockers only. Empty array if none.>],
|
|
23
|
+
"recommendation": <string — one clear recommended action>,
|
|
24
|
+
"next_step": <string — the specific next thing to do, e.g. a quorum command or a decision>
|
|
25
|
+
}`
|
|
26
|
+
|
|
27
|
+
export function formatEvidence(evidence: OracleResult[]): string {
|
|
28
|
+
if (evidence.length === 0) {
|
|
29
|
+
return "Chronicle has no prior entries on this topic."
|
|
30
|
+
}
|
|
31
|
+
return evidence
|
|
32
|
+
.map(e => {
|
|
33
|
+
const text = entryText(e)
|
|
34
|
+
const statusTag =
|
|
35
|
+
e.status === "refuted" ? " [REJECTED]" :
|
|
36
|
+
e.status === "validated" ? " [VALIDATED]" : ""
|
|
37
|
+
return `[${e.id.slice(0, 8)}]${statusTag} ${text}\n Areas: ${e.affected_areas.join(", ")}`
|
|
38
|
+
})
|
|
39
|
+
.join("\n\n")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function buildUserPrompt(question: string, evidence: OracleResult[]): string {
|
|
43
|
+
return [
|
|
44
|
+
"## Question",
|
|
45
|
+
question,
|
|
46
|
+
"",
|
|
47
|
+
"## Chronicle Evidence",
|
|
48
|
+
formatEvidence(evidence),
|
|
49
|
+
].join("\n")
|
|
50
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { OracleResult, LLMProvider } from "../shared/types"
|
|
2
|
+
|
|
3
|
+
export interface AdvisorInput {
|
|
4
|
+
question: string
|
|
5
|
+
evidence: OracleResult[]
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface AdvisorAnswer {
|
|
9
|
+
confidence: number
|
|
10
|
+
what_we_know: string
|
|
11
|
+
risks: string[]
|
|
12
|
+
blockers: string[]
|
|
13
|
+
recommendation: string
|
|
14
|
+
next_step: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AdvisorOutput extends AdvisorAnswer {
|
|
18
|
+
question: string
|
|
19
|
+
/** Number of retries taken before the answer met the satisfaction threshold. */
|
|
20
|
+
retries: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AdvisorDeps {
|
|
24
|
+
llm: LLMProvider
|
|
25
|
+
model?: string
|
|
26
|
+
}
|
package/modules/setup.ts
CHANGED
|
@@ -5,10 +5,12 @@ import { xenovaEmbed, warmEmbedder } from "./oracle/adapters/xenova-embedder"
|
|
|
5
5
|
import { createLanceDBStore } from "./oracle/adapters/lance-db"
|
|
6
6
|
import { evaluate } from "./jury/evaluate"
|
|
7
7
|
import { deliberate } from "./council/deliberate"
|
|
8
|
+
import { ask as advisorAsk } from "./advisor/ask"
|
|
8
9
|
import type { LLMProvider, OracleClient } from "./shared/types"
|
|
9
10
|
import { entryText } from "./shared/types"
|
|
10
11
|
import type { JuryInput, JuryOutput, JuryDeps } from "./jury/types"
|
|
11
12
|
import type { CouncilInput, CouncilOutput, CouncilDeps, CouncilModels } from "./council/types"
|
|
13
|
+
import type { AdvisorOutput } from "./advisor/types"
|
|
12
14
|
|
|
13
15
|
export interface SetupOptions {
|
|
14
16
|
/**
|
|
@@ -69,6 +71,14 @@ export interface Modules {
|
|
|
69
71
|
deliberate: (
|
|
70
72
|
input: Omit<CouncilInput, "jury_output"> & { jury_output: JuryOutput },
|
|
71
73
|
) => Promise<CouncilOutput>
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Ask the Advisor a plain-language question.
|
|
77
|
+
* Queries Oracle automatically, synthesises Chronicle evidence into a
|
|
78
|
+
* human-readable answer, and retries internally until the answer meets
|
|
79
|
+
* the confidence threshold (≥ 0.7, no blockers) or the retry budget runs out.
|
|
80
|
+
*/
|
|
81
|
+
ask: (question: string) => Promise<AdvisorOutput>
|
|
72
82
|
}
|
|
73
83
|
|
|
74
84
|
/**
|
|
@@ -150,5 +160,10 @@ export async function setup(options: SetupOptions): Promise<Modules> {
|
|
|
150
160
|
oracle,
|
|
151
161
|
models: models.council,
|
|
152
162
|
}),
|
|
163
|
+
|
|
164
|
+
ask: async (question: string) => {
|
|
165
|
+
const evidence = await oracle.query(question)
|
|
166
|
+
return advisorAsk({ question, evidence }, { llm })
|
|
167
|
+
},
|
|
153
168
|
}
|
|
154
169
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@balpal4495/quorum",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Portable reasoning layer for agentic codebases — Oracle, Jury, Council, Sentinel",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,8 +22,10 @@
|
|
|
22
22
|
"bin": {
|
|
23
23
|
"quorum": "bin/quorum.js"
|
|
24
24
|
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": "./modules/setup.ts"
|
|
27
|
+
},
|
|
25
28
|
"scripts": {
|
|
26
|
-
"init": "node bin/init.js",
|
|
27
29
|
"test": "vitest run modules/ evals/",
|
|
28
30
|
"test:watch": "vitest modules/",
|
|
29
31
|
"typecheck": "tsc --noEmit"
|