@balpal4495/quorum 3.0.3 → 3.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 (243) hide show
  1. package/bin/commands/compass.js +4 -4
  2. package/bin/shared/llm.js +2 -2
  3. package/dist/advisor/ask.d.ts +13 -0
  4. package/dist/advisor/ask.d.ts.map +1 -0
  5. package/dist/advisor/ask.js +67 -0
  6. package/dist/advisor/ask.js.map +1 -0
  7. package/dist/advisor/index.d.ts +3 -0
  8. package/dist/advisor/index.d.ts.map +1 -0
  9. package/dist/advisor/index.js +2 -0
  10. package/dist/advisor/index.js.map +1 -0
  11. package/dist/advisor/prompt.d.ts +5 -0
  12. package/dist/advisor/prompt.d.ts.map +1 -0
  13. package/{modules/advisor/prompt.ts → dist/advisor/prompt.js} +22 -26
  14. package/dist/advisor/prompt.js.map +1 -0
  15. package/dist/advisor/types.d.ts +23 -0
  16. package/dist/advisor/types.d.ts.map +1 -0
  17. package/dist/advisor/types.js +2 -0
  18. package/dist/advisor/types.js.map +1 -0
  19. package/dist/compass/behavior.d.ts +4 -0
  20. package/dist/compass/behavior.d.ts.map +1 -0
  21. package/dist/compass/behavior.js +138 -0
  22. package/dist/compass/behavior.js.map +1 -0
  23. package/dist/compass/create.d.ts +3 -0
  24. package/dist/compass/create.d.ts.map +1 -0
  25. package/dist/compass/create.js +289 -0
  26. package/dist/compass/create.js.map +1 -0
  27. package/dist/compass/evidence/collect.d.ts +11 -0
  28. package/dist/compass/evidence/collect.d.ts.map +1 -0
  29. package/dist/compass/evidence/collect.js +86 -0
  30. package/dist/compass/evidence/collect.js.map +1 -0
  31. package/dist/compass/index.d.ts +8 -0
  32. package/dist/compass/index.d.ts.map +1 -0
  33. package/dist/compass/index.js +8 -0
  34. package/dist/compass/index.js.map +1 -0
  35. package/dist/compass/prompts/index.d.ts +28 -0
  36. package/dist/compass/prompts/index.d.ts.map +1 -0
  37. package/{modules/compass/prompts/index.ts → dist/compass/prompts/index.js} +13 -38
  38. package/dist/compass/prompts/index.js.map +1 -0
  39. package/dist/compass/prompts/system.d.ts +2 -0
  40. package/dist/compass/prompts/system.d.ts.map +1 -0
  41. package/{modules/compass/prompts/system.ts → dist/compass/prompts/system.js} +2 -1
  42. package/dist/compass/prompts/system.js.map +1 -0
  43. package/dist/compass/propose.d.ts +15 -0
  44. package/dist/compass/propose.d.ts.map +1 -0
  45. package/dist/compass/propose.js +128 -0
  46. package/dist/compass/propose.js.map +1 -0
  47. package/dist/compass/schemas.d.ts +1271 -0
  48. package/dist/compass/schemas.d.ts.map +1 -0
  49. package/dist/compass/schemas.js +113 -0
  50. package/dist/compass/schemas.js.map +1 -0
  51. package/dist/compass/score.d.ts +25 -0
  52. package/dist/compass/score.d.ts.map +1 -0
  53. package/dist/compass/score.js +89 -0
  54. package/dist/compass/score.js.map +1 -0
  55. package/dist/compass/sources/index.d.ts +9 -0
  56. package/dist/compass/sources/index.d.ts.map +1 -0
  57. package/dist/compass/sources/index.js +408 -0
  58. package/dist/compass/sources/index.js.map +1 -0
  59. package/dist/compass/types.d.ts +334 -0
  60. package/dist/compass/types.d.ts.map +1 -0
  61. package/dist/compass/types.js +2 -0
  62. package/dist/compass/types.js.map +1 -0
  63. package/dist/council/advisors.d.ts +15 -0
  64. package/dist/council/advisors.d.ts.map +1 -0
  65. package/dist/council/advisors.js +46 -0
  66. package/dist/council/advisors.js.map +1 -0
  67. package/dist/council/chairman.d.ts +13 -0
  68. package/dist/council/chairman.d.ts.map +1 -0
  69. package/dist/council/chairman.js +145 -0
  70. package/dist/council/chairman.js.map +1 -0
  71. package/dist/council/deliberate.d.ts +22 -0
  72. package/dist/council/deliberate.d.ts.map +1 -0
  73. package/dist/council/deliberate.js +99 -0
  74. package/dist/council/deliberate.js.map +1 -0
  75. package/dist/council/frame.d.ts +8 -0
  76. package/dist/council/frame.d.ts.map +1 -0
  77. package/dist/council/frame.js +40 -0
  78. package/dist/council/frame.js.map +1 -0
  79. package/dist/council/index.d.ts +6 -0
  80. package/dist/council/index.d.ts.map +1 -0
  81. package/dist/council/index.js +4 -0
  82. package/dist/council/index.js.map +1 -0
  83. package/dist/council/personas.d.ts +18 -0
  84. package/dist/council/personas.d.ts.map +1 -0
  85. package/dist/council/personas.js +44 -0
  86. package/dist/council/personas.js.map +1 -0
  87. package/dist/council/reviewers.d.ts +13 -0
  88. package/dist/council/reviewers.d.ts.map +1 -0
  89. package/dist/council/reviewers.js +59 -0
  90. package/dist/council/reviewers.js.map +1 -0
  91. package/dist/council/risk.d.ts +16 -0
  92. package/dist/council/risk.d.ts.map +1 -0
  93. package/dist/council/risk.js +74 -0
  94. package/dist/council/risk.js.map +1 -0
  95. package/dist/council/types.d.ts +95 -0
  96. package/dist/council/types.d.ts.map +1 -0
  97. package/dist/council/types.js +2 -0
  98. package/dist/council/types.js.map +1 -0
  99. package/dist/jury/evaluate.d.ts +13 -0
  100. package/dist/jury/evaluate.d.ts.map +1 -0
  101. package/{modules/jury/evaluate.ts → dist/jury/evaluate.js} +60 -84
  102. package/dist/jury/evaluate.js.map +1 -0
  103. package/dist/jury/index.d.ts +6 -0
  104. package/dist/jury/index.d.ts.map +1 -0
  105. package/dist/jury/index.js +4 -0
  106. package/dist/jury/index.js.map +1 -0
  107. package/dist/jury/preflight.d.ts +26 -0
  108. package/dist/jury/preflight.d.ts.map +1 -0
  109. package/dist/jury/preflight.js +71 -0
  110. package/dist/jury/preflight.js.map +1 -0
  111. package/dist/jury/schema.d.ts +57 -0
  112. package/dist/jury/schema.d.ts.map +1 -0
  113. package/dist/jury/schema.js +21 -0
  114. package/dist/jury/schema.js.map +1 -0
  115. package/dist/jury/types.d.ts +47 -0
  116. package/dist/jury/types.d.ts.map +1 -0
  117. package/dist/jury/types.js +2 -0
  118. package/dist/jury/types.js.map +1 -0
  119. package/dist/oracle/adapters/lance-db.d.ts +15 -0
  120. package/dist/oracle/adapters/lance-db.d.ts.map +1 -0
  121. package/dist/oracle/adapters/lance-db.js +68 -0
  122. package/dist/oracle/adapters/lance-db.js.map +1 -0
  123. package/dist/oracle/adapters/xenova-embedder.d.ts +21 -0
  124. package/dist/oracle/adapters/xenova-embedder.d.ts.map +1 -0
  125. package/dist/oracle/adapters/xenova-embedder.js +36 -0
  126. package/dist/oracle/adapters/xenova-embedder.js.map +1 -0
  127. package/dist/oracle/bm25.d.ts +20 -0
  128. package/dist/oracle/bm25.d.ts.map +1 -0
  129. package/dist/oracle/bm25.js +82 -0
  130. package/dist/oracle/bm25.js.map +1 -0
  131. package/dist/oracle/index.d.ts +21 -0
  132. package/dist/oracle/index.d.ts.map +1 -0
  133. package/dist/oracle/index.js +25 -0
  134. package/dist/oracle/index.js.map +1 -0
  135. package/dist/oracle/log.d.ts +6 -0
  136. package/dist/oracle/log.d.ts.map +1 -0
  137. package/dist/oracle/log.js +12 -0
  138. package/dist/oracle/log.js.map +1 -0
  139. package/dist/oracle/propose.d.ts +25 -0
  140. package/dist/oracle/propose.d.ts.map +1 -0
  141. package/dist/oracle/propose.js +133 -0
  142. package/dist/oracle/propose.js.map +1 -0
  143. package/dist/oracle/query.d.ts +17 -0
  144. package/dist/oracle/query.d.ts.map +1 -0
  145. package/dist/oracle/query.js +106 -0
  146. package/dist/oracle/query.js.map +1 -0
  147. package/dist/oracle/summary.d.ts +11 -0
  148. package/dist/oracle/summary.d.ts.map +1 -0
  149. package/dist/oracle/summary.js +102 -0
  150. package/dist/oracle/summary.js.map +1 -0
  151. package/dist/oracle/types.d.ts +31 -0
  152. package/dist/oracle/types.d.ts.map +1 -0
  153. package/dist/oracle/types.js +2 -0
  154. package/dist/oracle/types.js.map +1 -0
  155. package/dist/sentinel/assert.d.ts +28 -0
  156. package/dist/sentinel/assert.d.ts.map +1 -0
  157. package/dist/sentinel/assert.js +63 -0
  158. package/dist/sentinel/assert.js.map +1 -0
  159. package/dist/sentinel/coverage.d.ts +14 -0
  160. package/dist/sentinel/coverage.d.ts.map +1 -0
  161. package/dist/sentinel/coverage.js +96 -0
  162. package/dist/sentinel/coverage.js.map +1 -0
  163. package/dist/sentinel/drift.d.ts +12 -0
  164. package/dist/sentinel/drift.d.ts.map +1 -0
  165. package/dist/sentinel/drift.js +149 -0
  166. package/dist/sentinel/drift.js.map +1 -0
  167. package/dist/sentinel/index.d.ts +7 -0
  168. package/dist/sentinel/index.d.ts.map +1 -0
  169. package/dist/sentinel/index.js +5 -0
  170. package/dist/sentinel/index.js.map +1 -0
  171. package/dist/sentinel/review.d.ts +15 -0
  172. package/dist/sentinel/review.d.ts.map +1 -0
  173. package/dist/sentinel/review.js +177 -0
  174. package/dist/sentinel/review.js.map +1 -0
  175. package/dist/setup.d.ts +103 -0
  176. package/dist/setup.d.ts.map +1 -0
  177. package/dist/setup.js +87 -0
  178. package/dist/setup.js.map +1 -0
  179. package/dist/shared/types.d.ts +173 -0
  180. package/dist/shared/types.d.ts.map +1 -0
  181. package/dist/shared/types.js +16 -0
  182. package/dist/shared/types.js.map +1 -0
  183. package/package.json +13 -8
  184. package/.github/copilot-instructions.md +0 -117
  185. package/CLAUDE.md +0 -146
  186. package/GEMINI.md +0 -73
  187. package/SETUP.md +0 -264
  188. package/evals/__tests__/eval.test.ts +0 -31
  189. package/evals/cases/auth_hs256_rejected.json +0 -46
  190. package/evals/cases/auth_rs256_valid.json +0 -30
  191. package/evals/cases/cache_missing_lock.json +0 -31
  192. package/evals/cases/db_naive_not_null.json +0 -32
  193. package/evals/cases/logging_pii_leak.json +0 -32
  194. package/evals/cases/migration_with_rollback.json +0 -43
  195. package/evals/cases/no_evidence_novel_design.json +0 -16
  196. package/evals/cases/payment_no_idempotency.json +0 -33
  197. package/evals/cases/redis_session_rejected.json +0 -32
  198. package/evals/cases/safe_refactor.json +0 -17
  199. package/evals/runner.ts +0 -226
  200. package/modules/AGENTS.md +0 -78
  201. package/modules/CLAUDE.md +0 -93
  202. package/modules/README.md +0 -504
  203. package/modules/advisor/ask.ts +0 -87
  204. package/modules/advisor/index.ts +0 -2
  205. package/modules/advisor/types.ts +0 -26
  206. package/modules/compass/behavior.ts +0 -161
  207. package/modules/compass/create.ts +0 -365
  208. package/modules/compass/evidence/collect.ts +0 -109
  209. package/modules/compass/index.ts +0 -7
  210. package/modules/compass/propose.ts +0 -152
  211. package/modules/compass/schemas.ts +0 -121
  212. package/modules/compass/score.ts +0 -77
  213. package/modules/compass/sources/index.ts +0 -413
  214. package/modules/compass/types.ts +0 -431
  215. package/modules/council/advisors.ts +0 -71
  216. package/modules/council/chairman.ts +0 -183
  217. package/modules/council/deliberate.ts +0 -141
  218. package/modules/council/frame.ts +0 -54
  219. package/modules/council/index.ts +0 -9
  220. package/modules/council/personas.ts +0 -57
  221. package/modules/council/reviewers.ts +0 -82
  222. package/modules/council/risk.ts +0 -89
  223. package/modules/council/types.ts +0 -107
  224. package/modules/jury/index.ts +0 -5
  225. package/modules/jury/preflight.ts +0 -101
  226. package/modules/jury/schema.ts +0 -24
  227. package/modules/jury/types.ts +0 -50
  228. package/modules/oracle/adapters/lance-db.ts +0 -81
  229. package/modules/oracle/adapters/xenova-embedder.ts +0 -43
  230. package/modules/oracle/bm25.ts +0 -92
  231. package/modules/oracle/index.ts +0 -36
  232. package/modules/oracle/log.ts +0 -15
  233. package/modules/oracle/propose.ts +0 -164
  234. package/modules/oracle/query.ts +0 -146
  235. package/modules/oracle/summary.ts +0 -116
  236. package/modules/oracle/types.ts +0 -32
  237. package/modules/sentinel/assert.ts +0 -95
  238. package/modules/sentinel/coverage.ts +0 -106
  239. package/modules/sentinel/drift.ts +0 -163
  240. package/modules/sentinel/index.ts +0 -6
  241. package/modules/sentinel/review.ts +0 -208
  242. package/modules/setup.ts +0 -202
  243. package/modules/shared/types.ts +0 -193
@@ -1,208 +0,0 @@
1
- import { promises as fs } from "fs"
2
- import path from "path"
3
- import type { ChronicleEntry } from "../shared/types"
4
- import { entryText } from "../shared/types"
5
- import { coverage as runCoverage } from "./coverage"
6
-
7
- function extractModule(filePath: string): string {
8
- const normalised = filePath.replace(/\\/g, "/").replace(/^\/+/, "")
9
- const stripped = normalised.replace(/^modules\//, "")
10
- const parts = stripped.split("/")
11
- return parts.length === 1 ? "(root)" : parts[0]
12
- }
13
-
14
- function mermaidSafe(str: string): string {
15
- return str.replace(/[^a-zA-Z0-9_]/g, "_")
16
- }
17
-
18
- function riskClass(pct: number): "high" | "medium" | "good" {
19
- if (pct === 0) return "high"
20
- if (pct < 50) return "medium"
21
- return "good"
22
- }
23
-
24
- function riskLabel(pct: number): string {
25
- if (pct === 0) return "high"
26
- if (pct < 50) return "medium"
27
- return "low"
28
- }
29
-
30
- function isoWeekKey(date: Date): string {
31
- const d = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()))
32
- const day = d.getUTCDay() || 7
33
- d.setUTCDate(d.getUTCDate() + 4 - day)
34
- const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
35
- const week = Math.ceil(((d.getTime() - yearStart.getTime()) / 86_400_000 + 1) / 7)
36
- return `${d.getUTCFullYear()}-W${String(week).padStart(2, "0")}`
37
- }
38
-
39
- async function readCommittedEntries(chronicleDir: string): Promise<ChronicleEntry[]> {
40
- const committedDir = path.join(chronicleDir, "committed")
41
- let files: string[]
42
- try {
43
- files = await fs.readdir(committedDir)
44
- } catch {
45
- return []
46
- }
47
- const entries: ChronicleEntry[] = []
48
- for (const file of files) {
49
- if (!file.endsWith(".json")) continue
50
- try {
51
- const raw = await fs.readFile(path.join(committedDir, file), "utf8")
52
- entries.push(JSON.parse(raw) as ChronicleEntry)
53
- } catch {
54
- // skip malformed
55
- }
56
- }
57
- return entries
58
- }
59
-
60
- type ModuleStat = {
61
- name: string
62
- totalFiles: number
63
- coveredFiles: number
64
- entryIds: string[]
65
- changedFiles: number
66
- percentage: number
67
- }
68
-
69
- /**
70
- * Generate a PR-level Chronicle coverage map as a markdown string ready to
71
- * post as a PR comment.
72
- *
73
- * Produces three zones:
74
- * 1. Coverage table — all modules with coverage %, entry count, file count,
75
- * PR delta, and risk. Changed modules are bolded.
76
- * 2. Heatmap diagram — Chronicle → modules, nodes coloured by risk level,
77
- * labels show coverage % and change count in one visual.
78
- * 3. Chronicle context — entries for touched modules only.
79
- *
80
- * Deterministic — no LLM required. Pass changedFiles from `git diff --name-only`.
81
- */
82
- export async function reviewContext(
83
- changedFiles: string[],
84
- chronicleDir: string,
85
- codebasePath: string,
86
- ): Promise<string> {
87
- const filtered = changedFiles.filter(f => f.trim().length > 0)
88
- if (filtered.length === 0) return "<!-- sentinel: no changed files -->"
89
-
90
- const [report, allEntries] = await Promise.all([
91
- runCoverage(chronicleDir, codebasePath),
92
- readCommittedEntries(chronicleDir),
93
- ])
94
-
95
- // Count changed files per module
96
- const changedByModule = new Map<string, number>()
97
- for (const file of filtered) {
98
- const mod = extractModule(file)
99
- changedByModule.set(mod, (changedByModule.get(mod) ?? 0) + 1)
100
- }
101
-
102
- // Build per-module stats from coverage report
103
- const moduleStats = new Map<string, ModuleStat>()
104
- for (const f of report.coverageByFile) {
105
- const mod = extractModule(f.file)
106
- const stat = moduleStats.get(mod) ?? {
107
- name: mod, totalFiles: 0, coveredFiles: 0,
108
- entryIds: [], changedFiles: changedByModule.get(mod) ?? 0, percentage: 0,
109
- }
110
- stat.totalFiles++
111
- if (f.covered) {
112
- stat.coveredFiles++
113
- for (const id of f.entryIds) {
114
- if (!stat.entryIds.includes(id)) stat.entryIds.push(id)
115
- }
116
- }
117
- moduleStats.set(mod, stat)
118
- }
119
-
120
- // Include modules only referenced by changedFiles but not in codebase scan
121
- for (const [mod, count] of changedByModule) {
122
- if (!moduleStats.has(mod)) {
123
- moduleStats.set(mod, {
124
- name: mod, totalFiles: count, coveredFiles: 0,
125
- entryIds: [], changedFiles: count, percentage: 0,
126
- })
127
- }
128
- }
129
-
130
- for (const stat of moduleStats.values()) {
131
- stat.percentage = stat.totalFiles === 0
132
- ? 0
133
- : Math.round((stat.coveredFiles / stat.totalFiles) * 100)
134
- }
135
-
136
- const allModules = [...moduleStats.values()].sort((a, b) =>
137
- a.name === "(root)" ? 1 : b.name === "(root)" ? -1 : a.name.localeCompare(b.name),
138
- )
139
- const touchedModules = allModules.filter(m => m.changedFiles > 0)
140
-
141
- const lines: string[] = []
142
- const week = isoWeekKey(new Date())
143
- const chronicleIsEmpty = allEntries.length === 0
144
-
145
- // ── Header ────────────────────────────────────────────────────────────────
146
- lines.push(`## Sentinel — Chronicle Coverage Map — ${week}`)
147
- lines.push("")
148
-
149
- if (chronicleIsEmpty) {
150
- lines.push(
151
- "> **Chronicle has no entries yet.** Every module shows as uncovered because this project has no documented knowledge. " +
152
- "The modules touched by this PR are a good starting point — run `oracle.propose()` after this lands to begin building Chronicle.",
153
- )
154
- lines.push("")
155
- }
156
-
157
- // ── Coverage table ────────────────────────────────────────────────────────
158
- lines.push("| Module | Coverage | Entries | Files | PR Changes | Risk |")
159
- lines.push("|--------|----------|---------|-------|------------|------|")
160
- for (const stat of allModules) {
161
- const name = stat.changedFiles > 0 ? `**${stat.name}/**` : `${stat.name}/`
162
- const pct = `${stat.percentage}%`
163
- const changed = stat.changedFiles > 0 ? `**${stat.changedFiles} files**` : "—"
164
- lines.push(
165
- `| ${name} | ${pct} | ${stat.entryIds.length} | ${stat.totalFiles} | ${changed} | ${riskLabel(stat.percentage)} |`,
166
- )
167
- }
168
- lines.push("")
169
-
170
- // ── Heatmap diagram ───────────────────────────────────────────────────────
171
- lines.push("```mermaid")
172
- lines.push("flowchart TD")
173
- lines.push(" classDef high fill:#fca5a5,stroke:#dc2626")
174
- lines.push(" classDef medium fill:#fde68a,stroke:#d97706")
175
- lines.push(" classDef good fill:#bbf7d0,stroke:#16a34a")
176
- lines.push(" Chronicle[(Chronicle)]")
177
- for (const stat of allModules) {
178
- const nodeId = mermaidSafe(stat.name)
179
- const changed = stat.changedFiles > 0 ? ` — ${stat.changedFiles} changed` : ""
180
- const label = `${stat.name} — ${stat.percentage}%${changed}`
181
- const cls = riskClass(stat.percentage)
182
- lines.push(` Chronicle --> ${nodeId}["${label}"]:::${cls}`)
183
- }
184
- lines.push("```")
185
- lines.push("")
186
-
187
- // ── Chronicle context for touched modules ─────────────────────────────────
188
- const touchedWithEntries = touchedModules.filter(m => m.entryIds.length > 0)
189
- if (touchedWithEntries.length > 0) {
190
- lines.push("### Chronicle context for changed modules")
191
- lines.push("")
192
- for (const stat of touchedWithEntries) {
193
- lines.push(`**${stat.name}/**`)
194
- const relevant = allEntries.filter(e => stat.entryIds.includes(e.id))
195
- for (const entry of relevant) {
196
- lines.push(`- \`[${entry.id.slice(0, 8)}]\` ${entryText(entry)}`)
197
- lines.push(` *${entry.status} — confidence ${entry.confidence.toFixed(2)}*`)
198
- }
199
- lines.push("")
200
- }
201
- }
202
-
203
- // ── Footer ────────────────────────────────────────────────────────────────
204
- lines.push("---")
205
- lines.push("*Risk: high = 0% coverage, medium = 1-49%, low = 50%+*")
206
-
207
- return lines.join("\n")
208
- }
package/modules/setup.ts DELETED
@@ -1,202 +0,0 @@
1
- import path from "path"
2
- import { promises as fs } from "fs"
3
- import { createOracleClient } from "./oracle/index"
4
- import { xenovaEmbed, warmEmbedder } from "./oracle/adapters/xenova-embedder"
5
- import { createLanceDBStore } from "./oracle/adapters/lance-db"
6
- import { evaluate } from "./jury/evaluate"
7
- import { deliberate } from "./council/deliberate"
8
- import { ask as advisorAsk } from "./advisor/ask"
9
- import type { LLMProvider, OracleClient } from "./shared/types"
10
- import { entryText } from "./shared/types"
11
- import type { JuryInput, JuryOutput, JuryDeps } from "./jury/types"
12
- import type { CouncilInput, CouncilOutput, CouncilDeps, CouncilModels } from "./council/types"
13
- import type { AdvisorOutput } from "./advisor/types"
14
- import { createCompass } from "./compass/create"
15
- import { defaultSources } from "./compass/sources/index"
16
- import type { Compass, CreateCompassOptions } from "./compass/types"
17
-
18
- export interface SetupOptions {
19
- /**
20
- * Injectable LLM provider.
21
- * All modules that need an LLM receive this function.
22
- * Ignored by Oracle (which has no LLM dependency).
23
- */
24
- llm: LLMProvider
25
-
26
- /**
27
- * Root directory for Chronicle data.
28
- * Default: ".chronicle" (relative to process.cwd())
29
- */
30
- chronicleDir?: string
31
-
32
- /**
33
- * Model overrides for each reasoning step.
34
- * If omitted, the LLM provider's default model is used for all steps.
35
- */
36
- models?: {
37
- jury?: string
38
- council?: CouncilModels
39
- compass?: {
40
- brief?: string
41
- pathways?: string
42
- bets?: string
43
- score?: string
44
- }
45
- }
46
-
47
- /**
48
- * Root directory for scanning source files (used by Compass).
49
- * Default: process.cwd()
50
- */
51
- rootDir?: string
52
-
53
- /**
54
- * Pre-warm the local ONNX embedder during setup so the first query
55
- * is not slow. Set to false to skip (e.g. in test environments).
56
- * Default: true
57
- */
58
- warmEmbedder?: boolean
59
-
60
- /**
61
- * Swap the default embedder (Xenova all-MiniLM-L6-v2) for your own.
62
- * Must return a vector of consistent dimension.
63
- */
64
- embedder?: (text: string) => Promise<number[]>
65
- }
66
-
67
- export interface Modules {
68
- /**
69
- * Fully wired OracleClient.
70
- * Use oracle.query() to retrieve evidence.
71
- * Use oracle.propose() + oracle.commit() for the human-gated write path.
72
- */
73
- oracle: OracleClient
74
-
75
- /**
76
- * Evaluate a proposed design against Oracle evidence.
77
- * Returns a confidence score and the Council brief for the next step.
78
- */
79
- evaluate: (input: Omit<JuryInput, never>) => Promise<JuryOutput>
80
-
81
- /**
82
- * Run the full Council deliberation pipeline.
83
- * Proposes the verdict to Oracle automatically — a human must call
84
- * oracle.commit(proposalId) to index it into Chronicle.
85
- */
86
- deliberate: (
87
- input: Omit<CouncilInput, "jury_output"> & { jury_output: JuryOutput },
88
- ) => Promise<CouncilOutput>
89
-
90
- /**
91
- * Ask the Advisor a plain-language question.
92
- * Queries Oracle automatically, synthesises Chronicle evidence into a
93
- * human-readable answer, and retries internally until the answer meets
94
- * the confidence threshold (≥ 0.7, no blockers) or the retry budget runs out.
95
- */
96
- ask: (question: string) => Promise<AdvisorOutput>
97
-
98
- /**
99
- * Product-direction module.
100
- * Synthesises Chronicle memory and current codebase behaviour into
101
- * pathways, bets, briefs, and opportunities.
102
- * All writes go through oracle.propose() — never auto-committed.
103
- */
104
- compass: Compass
105
- }
106
-
107
- /**
108
- * Wire up all three modules from a single call.
109
- *
110
- * @example
111
- * import { setup } from "./modules/setup"
112
- *
113
- * const { oracle, evaluate, deliberate } = await setup({
114
- * llm: myLLMProvider,
115
- * })
116
- *
117
- * const evidence = await oracle.query("authentication patterns")
118
- * const jury = await evaluate({ outcome, design, evidence })
119
- * const verdict = await deliberate({ outcome, design, evidence, jury_output: jury })
120
- *
121
- * if (verdict.satisfied) {
122
- * // → human gate → Executor
123
- * }
124
- */
125
- export async function setup(options: SetupOptions): Promise<Modules> {
126
- const {
127
- llm,
128
- chronicleDir = ".chronicle",
129
- rootDir = process.cwd(),
130
- models = {},
131
- warmEmbedder: shouldWarm = true,
132
- embedder = xenovaEmbed,
133
- } = options
134
-
135
- // Ensure Chronicle directories exist before anything tries to write to them
136
- await fs.mkdir(path.join(chronicleDir, "proposals"), { recursive: true })
137
- await fs.mkdir(path.join(chronicleDir, "committed"), { recursive: true })
138
-
139
- // Pre-warm the embedder if using the default (downloads model on first use)
140
- if (shouldWarm && embedder === xenovaEmbed) {
141
- await warmEmbedder()
142
- }
143
-
144
- const vectorStore = await createLanceDBStore(chronicleDir)
145
-
146
- // Rebuild local index from committed entries if any are missing.
147
- // This brings a fresh machine (or a post-git-pull state) up to date
148
- // without requiring any manual step.
149
- const committedDir = path.join(chronicleDir, "committed")
150
- const committedFiles = (await fs.readdir(committedDir)).filter(f => f.endsWith(".json"))
151
-
152
- if (committedFiles.length > 0) {
153
- const existing = await vectorStore.getAll()
154
- const existingIds = new Set(existing.map(e => e.id))
155
- const missing = committedFiles.filter(f => !existingIds.has(f.replace(".json", "")))
156
-
157
- if (missing.length > 0) {
158
- console.log(`[Chronicle] Rebuilding index from ${missing.length} committed ${missing.length === 1 ? "entry" : "entries"}…`)
159
- for (const file of missing) {
160
- const raw = await fs.readFile(path.join(committedDir, file), "utf8")
161
- const entry = JSON.parse(raw) as import("./shared/types").ChronicleEntry
162
- const embeddingText = [entryText(entry), ...entry.affected_areas, ...(entry.scope ?? [])].join(" ")
163
- const vector = await embedder(embeddingText)
164
- await vectorStore.upsert(entry.id, vector, entry)
165
- }
166
- }
167
- }
168
-
169
- const oracle = createOracleClient({
170
- embedder,
171
- vectorStore,
172
- chronicleDir,
173
- })
174
-
175
- return {
176
- oracle,
177
-
178
- evaluate: (input: JuryInput) =>
179
- evaluate(input, { llm, model: models.jury }),
180
-
181
- deliberate: (input: CouncilInput) =>
182
- deliberate(input, {
183
- llm,
184
- oracle,
185
- models: models.council,
186
- }),
187
-
188
- ask: async (question: string) => {
189
- const evidence = await oracle.query(question)
190
- return advisorAsk({ question, evidence }, { llm })
191
- },
192
-
193
- compass: createCompass({
194
- oracle,
195
- llm,
196
- rootDir,
197
- chronicleDir,
198
- sources: defaultSources(),
199
- models: models.compass,
200
- }),
201
- }
202
- }
@@ -1,193 +0,0 @@
1
- /**
2
- * Shared types used across Oracle, Jury, and Council modules.
3
- * These are the only types that cross module boundaries.
4
- */
5
-
6
- export type Message = {
7
- role: "system" | "user" | "assistant"
8
- content: string
9
- }
10
-
11
- /**
12
- * Injectable LLM provider. Accepts a message array and optional model override.
13
- * Returns the assistant response as a string.
14
- *
15
- * The modules never hardcode a provider — wire this at the application level.
16
- */
17
- export type LLMProvider = (messages: Message[], model?: string) => Promise<string>
18
-
19
- /**
20
- * Links a Chronicle entry to the unit of work that triggered it.
21
- * Gives agents the "why now" context that key_insight alone cannot convey.
22
- */
23
- export type WorkRef = {
24
- type: "bug" | "story" | "epic" | "pr" | "spike"
25
- /** Ticket number, PR reference, or branch name. e.g. "PROJ-123", "PR #4" */
26
- ref?: string
27
- }
28
-
29
- /**
30
- * A durable knowledge record stored in Chronicle.
31
- * This is the canonical unit of institutional memory.
32
- *
33
- * Schema versions:
34
- * v1 (no schema_version field): key_insight is the primary text field.
35
- * v2 (schema_version: 2): decision is the primary text field; key_insight is a copy
36
- * of decision written for backwards compatibility. Always use entryText() to read.
37
- */
38
- export type ChronicleEntry = {
39
- id: string
40
-
41
- // ── v1 fields (required — always present) ───────────────────────────────
42
- /** The core finding or decision, in one clear sentence. v2: copy of decision. */
43
- key_insight: string
44
- /** File paths or system areas this entry applies to. Used by Sentinel for file matching. */
45
- affected_areas: string[]
46
- status: "validated" | "refuted" | "open"
47
- /** 0–1. How strongly this was confirmed at write time. */
48
- confidence: number
49
- /** Which module produced this entry (detective, council, executor, etc.). */
50
- source_module: string
51
- /** IDs of Chronicle entries this decision was based on. */
52
- evidence_cited: string[]
53
- /** What actually happened when this was acted on. Added post-execution by Scribe. */
54
- outcome?: string
55
- /** The unit of work that triggered this entry. Used to build SUMMARY.md temporal context. */
56
- work_ref?: WorkRef
57
- timestamp: string
58
-
59
- // ── outcome tracking fields (optional — filled in post-execution) ────────────
60
- /** Steps that must pass to confirm this decision was correct. */
61
- validation_plan?: string[]
62
- /** ISO date after which this entry should be re-evaluated for drift. */
63
- review_after?: string
64
- /** What actually happened after the decision was acted on in production. */
65
- post_merge_result?: "successful" | "bug" | "partial" | "rolled-back"
66
-
67
- // ── v2 fields (optional — absent on legacy entries) ──────────────────────
68
- /** 2 = decision record format. Absent = v1 legacy entry. */
69
- schema_version?: 2
70
- /** Short label for this decision. e.g. "auth/session strategy" */
71
- topic?: string
72
- /** The decision itself — the primary text field in v2. Use entryText() to read. */
73
- decision?: string
74
- /** Domain/category tags. Additive — does NOT replace affected_areas. e.g. ["auth", "sessions"] */
75
- scope?: string[]
76
- /** Approaches that were considered but not chosen. */
77
- alternatives_considered?: string[]
78
- /** Why the alternatives were rejected. */
79
- rejected_reason?: string[]
80
- /** ID of the Chronicle entry this supersedes. */
81
- supersedes?: string | null
82
- /** ID of the Chronicle entry that superseded this one. */
83
- superseded_by?: string | null
84
- }
85
-
86
- /**
87
- * Return the primary text for a Chronicle entry regardless of schema version.
88
- * v2 entries use decision; v1 entries use key_insight.
89
- * All callsites that render or embed entry text must use this function.
90
- *
91
- * Accepts any object with key_insight and optional decision — works for both
92
- * full ChronicleEntry and Omit<ChronicleEntry, "id" | "timestamp"> from propose().
93
- */
94
- export function entryText(entry: { key_insight: string; decision?: string }): string {
95
- return entry.decision ?? entry.key_insight
96
- }
97
-
98
- /**
99
- * A Chronicle entry enriched with its retrieval score and relevance tier.
100
- * Returned by Oracle.query().
101
- *
102
- * Tiers indicate relevance within the result set:
103
- * primary — top ~30%: directly answers the query, should be foregrounded
104
- * supporting — middle ~40%: contextually relevant, useful but not central
105
- * background — bottom ~30%: loosely related, de-emphasise but do not hide
106
- */
107
- export type OracleResult = ChronicleEntry & {
108
- score: number
109
- tier: "primary" | "supporting" | "background"
110
- }
111
-
112
- /**
113
- * Returned by oracle.propose() when a high-similarity entry already exists.
114
- * The human gate should surface this before approving the commit.
115
- */
116
- export type SimilarityWarning = {
117
- entry: ChronicleEntry
118
- score: number
119
- /** potential-duplicate: near-identical insight. potential-supersession: likely a correction. */
120
- warning: "potential-duplicate" | "potential-supersession"
121
- }
122
-
123
- export type QueryOptions = {
124
- statusFilter?: Array<"validated" | "refuted" | "open">
125
- /** Maximum results to return. Default: 10. */
126
- limit?: number
127
- /**
128
- * Minimum RRF score to include a result.
129
- * Results below this threshold are dropped entirely — better to return nothing than noise.
130
- * Default: 0.031.
131
- */
132
- scoreThreshold?: number
133
- }
134
-
135
- // ── Sentinel types ────────────────────────────────────────────────────────────
136
-
137
- /** Per-file result from sentinel.coverage(). */
138
- export type FileCoverage = {
139
- file: string
140
- covered: boolean
141
- /** IDs of Chronicle entries that reference this file in affected_areas. */
142
- entryIds: string[]
143
- }
144
-
145
- /** Returned by sentinel.coverage(). */
146
- export type CoverageReport = {
147
- totalFiles: number
148
- coveredFiles: number
149
- uncoveredFiles: string[]
150
- coverageByFile: FileCoverage[]
151
- /** Integer 0–100. Treat as directional signal, not a precision metric. */
152
- percentage: number
153
- }
154
-
155
- /**
156
- * Advisory result for a single Chronicle entry from sentinel.detectDrift().
157
- * Never auto-updates an entry — human reviews the flag and decides.
158
- */
159
- export type DriftFlag = {
160
- entryId: string
161
- keyInsight: string
162
- affectedFiles: string[]
163
- stillValid: boolean
164
- /** 0–1 confidence in the LLM's verdict. Low confidence = needs closer human review. */
165
- confidence: number
166
- reasoning: string
167
- }
168
-
169
- /** Returned by sentinel.detectDrift(). */
170
- export type DriftReport = {
171
- checkedAt: string
172
- /** Entries the LLM judged as no longer accurate — review and consider updating status. */
173
- flags: DriftFlag[]
174
- /** Entries the LLM judged as still current. */
175
- confirmed: DriftFlag[]
176
- /** Entry IDs skipped because no affected_areas value resolved to a local file. */
177
- skipped: string[]
178
- }
179
-
180
- // ── Oracle client ─────────────────────────────────────────────────────────────
181
-
182
- /**
183
- * The public interface any module uses to interact with Chronicle.
184
- * Inject this into Jury and Council — do not couple them to Oracle internals.
185
- */
186
- export interface OracleClient {
187
- query: (text: string, options?: QueryOptions) => Promise<OracleResult[]>
188
- propose: (
189
- entry: Omit<ChronicleEntry, "id" | "timestamp">,
190
- ) => Promise<{ proposalId: string; similarity?: SimilarityWarning }>
191
- /** Called after human approval. Indexes the proposal into Chronicle. */
192
- commit: (proposalId: string) => Promise<ChronicleEntry>
193
- }