@juzser/blacksmith 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/.claude/agents/auditor.md +146 -0
  2. package/.claude/agents/coder.md +164 -0
  3. package/.claude/agents/grader.md +110 -0
  4. package/.claude/agents/merger.md +107 -0
  5. package/.claude/agents/planner.md +92 -0
  6. package/.claude/agents/researcher.md +105 -0
  7. package/.claude/agents/reviewer.md +171 -0
  8. package/.claude/agents/scribe.md +72 -0
  9. package/.claude/agents/security-reviewer.md +130 -0
  10. package/.claude/agents/spec-reviewer.md +215 -0
  11. package/.claude/agents/tester.md +121 -0
  12. package/.claude/agents/uiux.md +126 -0
  13. package/.claude/agents/verifier.md +103 -0
  14. package/.claude/agents/wave-runner.md +74 -0
  15. package/LICENSE +21 -0
  16. package/README.md +389 -0
  17. package/factory/orchestrator/dist/actors.js +46 -0
  18. package/factory/orchestrator/dist/agents-registry.js +308 -0
  19. package/factory/orchestrator/dist/args.js +84 -0
  20. package/factory/orchestrator/dist/artifacts.js +111 -0
  21. package/factory/orchestrator/dist/attribution.js +160 -0
  22. package/factory/orchestrator/dist/audit.js +812 -0
  23. package/factory/orchestrator/dist/autonomy.js +151 -0
  24. package/factory/orchestrator/dist/budgetAlarm.js +366 -0
  25. package/factory/orchestrator/dist/budgets.js +196 -0
  26. package/factory/orchestrator/dist/claims.js +633 -0
  27. package/factory/orchestrator/dist/cli.js +3185 -0
  28. package/factory/orchestrator/dist/commit.js +152 -0
  29. package/factory/orchestrator/dist/coverage.js +188 -0
  30. package/factory/orchestrator/dist/crossFinding.js +479 -0
  31. package/factory/orchestrator/dist/crosscheck.js +396 -0
  32. package/factory/orchestrator/dist/daemon.js +707 -0
  33. package/factory/orchestrator/dist/db/projector.js +1317 -0
  34. package/factory/orchestrator/dist/db/queries.js +1714 -0
  35. package/factory/orchestrator/dist/db/schema.js +305 -0
  36. package/factory/orchestrator/dist/delegation.js +400 -0
  37. package/factory/orchestrator/dist/diffstat.js +164 -0
  38. package/factory/orchestrator/dist/dispatchAudit.js +275 -0
  39. package/factory/orchestrator/dist/dotenv.js +72 -0
  40. package/factory/orchestrator/dist/effort.js +202 -0
  41. package/factory/orchestrator/dist/epic.js +1018 -0
  42. package/factory/orchestrator/dist/epicWidth.js +238 -0
  43. package/factory/orchestrator/dist/errorIssues.js +249 -0
  44. package/factory/orchestrator/dist/errors.js +11 -0
  45. package/factory/orchestrator/dist/escalation.js +500 -0
  46. package/factory/orchestrator/dist/events.js +1015 -0
  47. package/factory/orchestrator/dist/findingAge.js +91 -0
  48. package/factory/orchestrator/dist/findingContext.js +103 -0
  49. package/factory/orchestrator/dist/findings.js +1156 -0
  50. package/factory/orchestrator/dist/gate.js +911 -0
  51. package/factory/orchestrator/dist/gh.js +146 -0
  52. package/factory/orchestrator/dist/git.js +87 -0
  53. package/factory/orchestrator/dist/goalCheck.js +380 -0
  54. package/factory/orchestrator/dist/graph.js +61 -0
  55. package/factory/orchestrator/dist/hookDecision.js +106 -0
  56. package/factory/orchestrator/dist/immutability.js +171 -0
  57. package/factory/orchestrator/dist/impact.js +280 -0
  58. package/factory/orchestrator/dist/integration.js +141 -0
  59. package/factory/orchestrator/dist/judgePreflight.js +155 -0
  60. package/factory/orchestrator/dist/judges.js +310 -0
  61. package/factory/orchestrator/dist/lessonAudit.js +399 -0
  62. package/factory/orchestrator/dist/lessons.js +1329 -0
  63. package/factory/orchestrator/dist/mcp.js +555 -0
  64. package/factory/orchestrator/dist/paths.js +82 -0
  65. package/factory/orchestrator/dist/plan.js +502 -0
  66. package/factory/orchestrator/dist/planQuorum.js +421 -0
  67. package/factory/orchestrator/dist/policy.js +1308 -0
  68. package/factory/orchestrator/dist/policyHook.js +28 -0
  69. package/factory/orchestrator/dist/preconditions.js +47 -0
  70. package/factory/orchestrator/dist/projects.js +134 -0
  71. package/factory/orchestrator/dist/prompts.js +57 -0
  72. package/factory/orchestrator/dist/provenance.js +201 -0
  73. package/factory/orchestrator/dist/providers/api-transport.js +117 -0
  74. package/factory/orchestrator/dist/providers/cli-transport.js +208 -0
  75. package/factory/orchestrator/dist/providers/index.js +54 -0
  76. package/factory/orchestrator/dist/providers/schema-validate.js +152 -0
  77. package/factory/orchestrator/dist/providers/types.js +49 -0
  78. package/factory/orchestrator/dist/queue.js +263 -0
  79. package/factory/orchestrator/dist/quorum.js +347 -0
  80. package/factory/orchestrator/dist/quorumEscalations.js +132 -0
  81. package/factory/orchestrator/dist/results.js +65 -0
  82. package/factory/orchestrator/dist/roadmap.js +437 -0
  83. package/factory/orchestrator/dist/runtime.js +86 -0
  84. package/factory/orchestrator/dist/sameMistakeKpi.js +243 -0
  85. package/factory/orchestrator/dist/sandbox.js +165 -0
  86. package/factory/orchestrator/dist/scaffold.js +373 -0
  87. package/factory/orchestrator/dist/scheduler.js +408 -0
  88. package/factory/orchestrator/dist/schemas.js +195 -0
  89. package/factory/orchestrator/dist/security.js +222 -0
  90. package/factory/orchestrator/dist/severity.js +293 -0
  91. package/factory/orchestrator/dist/spec.js +376 -0
  92. package/factory/orchestrator/dist/specChange.js +497 -0
  93. package/factory/orchestrator/dist/stack.js +324 -0
  94. package/factory/orchestrator/dist/symbols.js +966 -0
  95. package/factory/orchestrator/dist/taskEvents.js +399 -0
  96. package/factory/orchestrator/dist/taskId.js +126 -0
  97. package/factory/orchestrator/dist/taxonomy.js +113 -0
  98. package/factory/orchestrator/dist/testSelect.js +129 -0
  99. package/factory/orchestrator/dist/testerAudit.js +264 -0
  100. package/factory/orchestrator/dist/testgate.js +171 -0
  101. package/factory/orchestrator/dist/usage.js +874 -0
  102. package/factory/orchestrator/dist/waivers.js +228 -0
  103. package/factory/orchestrator/dist/waveBudget.js +223 -0
  104. package/factory/orchestrator/dist/waveConcurrency.js +250 -0
  105. package/factory/orchestrator/dist/waveNext.js +210 -0
  106. package/factory/orchestrator/dist/waveSchedule.js +146 -0
  107. package/factory/orchestrator/dist/worktree.js +207 -0
  108. package/factory/orchestrator/drizzle/0000_broken_rockslide.sql +183 -0
  109. package/factory/orchestrator/drizzle/0001_luxuriant_zaladane.sql +1 -0
  110. package/factory/orchestrator/drizzle/0002_yellow_wild_pack.sql +8 -0
  111. package/factory/orchestrator/drizzle/0003_damp_riptide.sql +12 -0
  112. package/factory/orchestrator/drizzle/0004_broad_lady_vermin.sql +1 -0
  113. package/factory/orchestrator/drizzle/0005_romantic_jocasta.sql +2 -0
  114. package/factory/orchestrator/drizzle/0006_bent_madelyne_pryor.sql +16 -0
  115. package/factory/orchestrator/drizzle/0007_overjoyed_goliath.sql +1 -0
  116. package/factory/orchestrator/drizzle/0008_smart_iron_patriot.sql +2 -0
  117. package/factory/orchestrator/drizzle/0009_agents_epic_id.sql +2 -0
  118. package/factory/orchestrator/drizzle/0010_milestone_kind.sql +1 -0
  119. package/factory/orchestrator/drizzle/meta/_journal.json +83 -0
  120. package/factory/policies/budgets.yml +262 -0
  121. package/factory/policies/crosscheck.yml +308 -0
  122. package/factory/policies/delegation.yml +44 -0
  123. package/factory/policies/effort.yml +142 -0
  124. package/factory/policies/guardrails.yml +306 -0
  125. package/factory/policies/lessons.md +239 -0
  126. package/factory/policies/scheduler.yml +90 -0
  127. package/factory/policies/sensitive-paths.yml +148 -0
  128. package/factory/policies/severity.yml +83 -0
  129. package/factory/policies/stack.yml +60 -0
  130. package/factory/policies/taxonomy.yml +340 -0
  131. package/factory/policies/worktree.yml +95 -0
  132. package/factory/scaffold/base/.github/workflows/ci.yml +34 -0
  133. package/factory/scaffold/base/.gitignore.tmpl +11 -0
  134. package/factory/scaffold/base/AGENTS.md +33 -0
  135. package/factory/scaffold/base/README.md +25 -0
  136. package/factory/scaffold/base/biome.json.tmpl +30 -0
  137. package/factory/scaffold/base/package.json +25 -0
  138. package/factory/scaffold/base/pnpm-workspace.yaml.tmpl +16 -0
  139. package/factory/scaffold/base/src/index.ts +4 -0
  140. package/factory/scaffold/base/test/index.test.ts +8 -0
  141. package/factory/scaffold/base/tsconfig.json +20 -0
  142. package/factory/scaffold/base/tsconfig.test.json +9 -0
  143. package/factory/scaffold/base/vitest.config.ts +29 -0
  144. package/factory/scaffold/mcp/mcp.manifest.json +22 -0
  145. package/factory/scaffold/mcp/package.fragment.json +9 -0
  146. package/factory/scaffold/mcp/src/mcp/README.md +63 -0
  147. package/factory/scaffold/mcp/src/mcp/guard.ts +110 -0
  148. package/factory/scaffold/mcp/src/mcp/redact.ts +69 -0
  149. package/factory/scaffold/mcp/src/mcp/server.ts +55 -0
  150. package/factory/scaffold/mcp/src/mcp/tools/health.ts +60 -0
  151. package/factory/scaffold/mcp/test/mcp/guard.test.ts +108 -0
  152. package/factory/scaffold/mcp/test/mcp/redact.test.ts +61 -0
  153. package/factory/scaffold/mcp/test/mcp/surface.test.ts +100 -0
  154. package/factory/scaffold/ui/index.html +12 -0
  155. package/factory/scaffold/ui/package.fragment.json +13 -0
  156. package/factory/scaffold/ui/src/App.vue +10 -0
  157. package/factory/scaffold/ui/src/main.ts +5 -0
  158. package/factory/scaffold/ui/src/shims.d.ts +11 -0
  159. package/factory/scaffold/ui/vite.config.ts +20 -0
  160. package/factory/scaffold/ui-tailwind/package.fragment.json +6 -0
  161. package/factory/scaffold/ui-tailwind/vite.config.ts +26 -0
  162. package/factory/specs/schema/event.schema.json +82 -0
  163. package/factory/specs/schema/finding-evidence.schema.json +45 -0
  164. package/factory/specs/schema/finding.schema.json +115 -0
  165. package/factory/specs/schema/grader-verdict.schema.json +50 -0
  166. package/factory/specs/schema/judge-verdict.schema.json +19 -0
  167. package/factory/specs/schema/lesson.schema.json +85 -0
  168. package/factory/specs/schema/mcp-manifest.schema.json +230 -0
  169. package/factory/specs/schema/result.schema.json +81 -0
  170. package/factory/specs/schema/spec-change-request.schema.json +46 -0
  171. package/factory/specs/schema/task-spec.schema.json +134 -0
  172. package/package.json +98 -0
@@ -0,0 +1,305 @@
1
+ // Drizzle schema for the SQLite projection layer (architecture §7, §10).
2
+ //
3
+ // Events (`state/events/*.jsonl`) are the source of truth; every table here
4
+ // is a DERIVED read-model rebuilt by db/projector.ts. Nothing outside the
5
+ // projector writes to these tables. Taxonomy-valued columns are stored as
6
+ // plain text — validation already happened at event-write time in events.ts
7
+ // (architecture §8: "the event logger rejects unknown tags at write time").
8
+ //
9
+ // One schema, both targets: this file is the single source for the
10
+ // orchestrator's local SQLite and for D1 later, via Drizzle. It describes
11
+ // this repo's own database, not whatever a scaffolded project answered for
12
+ // `database` in factory/policies/stack.yml.
13
+ import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
14
+ /**
15
+ * Every event ever appended, exactly as logged — the append-only substrate mirrored 1:1.
16
+ *
17
+ * `project` (Phase 6b, architecture §8 note): a PLAIN STRING identifier, not
18
+ * a closed taxonomy vocabulary value — projects are opened/closed by the
19
+ * operator far more often than any §8 dimension, and forcing a taxonomy PR
20
+ * per new project would defeat the point of a multi-project hub. Nullable
21
+ * for migration safety (every event logged before this column existed has
22
+ * no value here); queries.ts's helpers COALESCE it to 'black-smith' so old
23
+ * rows and new single-project fixtures both resolve to the same default
24
+ * project rather than an ungrouped null bucket.
25
+ */
26
+ export const eventsRaw = sqliteTable('events_raw', {
27
+ eventId: text('event_id').primaryKey(),
28
+ sessionId: text('session_id').notNull(),
29
+ ts: text('ts').notNull(),
30
+ eventType: text('event_type').notNull(),
31
+ taskId: text('task_id'),
32
+ agentId: text('agent_id'),
33
+ planVersion: integer('plan_version').notNull(),
34
+ causalParent: text('causal_parent'),
35
+ payload: text('payload').notNull(), // JSON
36
+ project: text('project'),
37
+ // Phase 6b: who wrote the event (EventRecord.actor — 'user', 'system', or
38
+ // an agent role/id), needed to tell an operator-authored decision event
39
+ // (Timeline's "Decisions" lens, architecture §8) apart from a system/
40
+ // agent one of the same event_type. Nullable for migration safety —
41
+ // events logged before this column existed simply can't be attributed.
42
+ actor: text('actor'),
43
+ }, (t) => [
44
+ index('events_raw_session_idx').on(t.sessionId),
45
+ index('events_raw_task_idx').on(t.taskId),
46
+ index('events_raw_type_idx').on(t.eventType),
47
+ index('events_raw_ts_idx').on(t.ts),
48
+ index('events_raw_project_idx').on(t.project),
49
+ ]);
50
+ /** One row per session, folded from its session-start event + last-seen ts. */
51
+ export const sessions = sqliteTable('sessions', {
52
+ sessionId: text('session_id').primaryKey(),
53
+ startedAt: text('started_at').notNull(),
54
+ lastEventAt: text('last_event_at').notNull(),
55
+ eventCount: integer('event_count').notNull(),
56
+ });
57
+ /** `user_prompt` events (architecture §7), stored verbatim. */
58
+ export const prompts = sqliteTable('prompts', {
59
+ eventId: text('event_id').primaryKey(),
60
+ sessionId: text('session_id').notNull(),
61
+ ts: text('ts').notNull(),
62
+ prompt: text('prompt').notNull(),
63
+ causalParent: text('causal_parent'),
64
+ }, (t) => [index('prompts_session_idx').on(t.sessionId)]);
65
+ /** `dispatch_decision` events (architecture §7). */
66
+ export const dispatches = sqliteTable('dispatches', {
67
+ eventId: text('event_id').primaryKey(),
68
+ sessionId: text('session_id').notNull(),
69
+ ts: text('ts').notNull(),
70
+ taskId: text('task_id'),
71
+ agentId: text('agent_id'),
72
+ agentRole: text('agent_role').notNull(),
73
+ provider: text('provider').notNull(),
74
+ modelTier: text('model_tier').notNull(),
75
+ specRef: text('spec_ref'),
76
+ reason: text('reason'),
77
+ parentPromptId: text('parent_prompt_id'),
78
+ causalParent: text('causal_parent'),
79
+ project: text('project'), // Phase 6b — see eventsRaw's project comment above.
80
+ }, (t) => [
81
+ index('dispatches_session_idx').on(t.sessionId),
82
+ index('dispatches_task_idx').on(t.taskId),
83
+ index('dispatches_role_idx').on(t.agentRole),
84
+ index('dispatches_provider_idx').on(t.provider),
85
+ index('dispatches_model_tier_idx').on(t.modelTier),
86
+ index('dispatches_project_idx').on(t.project),
87
+ ]);
88
+ /**
89
+ * Live agent registry (agents-registry.ts owns the fold semantics): one row
90
+ * per dispatch_decision, closed by whichever terminal event (task-result-
91
+ * recorded / judge-reported / error-logged) comes next for the same
92
+ * `(task_id, agent_role)`, superseded by a redispatch of that same pair
93
+ * before any terminal event arrives, or abandoned by the `epic-closed` that
94
+ * ended the run it belonged to (D-187). The pair, not `task_id` alone: `/bs run`
95
+ * puts a coder, a grader, a reviewer and a security-reviewer on one task at
96
+ * once (D-23/P9-12).
97
+ */
98
+ export const agents = sqliteTable('agents', {
99
+ id: text('id').primaryKey(), // dispatch event_id
100
+ sessionId: text('session_id').notNull(),
101
+ taskId: text('task_id'),
102
+ /** The epic this agent works on: an epic-level dispatch names one and has no task (D-234). */
103
+ epicId: text('epic_id'),
104
+ agentId: text('agent_id'),
105
+ agentRole: text('agent_role').notNull(),
106
+ /** Dispatch round — 1 for a worker whose dispatch declares none. Carried, not keyed on. */
107
+ round: integer('round').notNull().default(1),
108
+ provider: text('provider').notNull(),
109
+ modelTier: text('model_tier').notNull(),
110
+ dispatchedAt: text('dispatched_at').notNull(),
111
+ terminalEventId: text('terminal_event_id'),
112
+ terminalAt: text('terminal_at'),
113
+ terminalType: text('terminal_type'), // 'result' | 'error' | 'superseded' | 'abandoned'
114
+ status: text('status').notNull(), // 'live' | 'done' | 'error' | 'superseded' | 'abandoned'
115
+ }, (t) => [
116
+ index('agents_session_idx').on(t.sessionId),
117
+ index('agents_status_idx').on(t.status),
118
+ index('agents_role_idx').on(t.agentRole),
119
+ index('agents_epic_idx').on(t.epicId),
120
+ ]);
121
+ /** One row per task_id, folded from task-added, wave-admitted/merged, gate-outcome, and error events. */
122
+ export const tasks = sqliteTable('tasks', {
123
+ taskId: text('task_id').primaryKey(),
124
+ sessionId: text('session_id').notNull(),
125
+ epicId: text('epic_id'),
126
+ caseTag: text('case_tag'),
127
+ origin: text('origin'),
128
+ taskStatus: text('task_status').notNull(),
129
+ planVersion: integer('plan_version'),
130
+ objective: text('objective'),
131
+ claims: text('claims'), // JSON array
132
+ budgetTokens: integer('budget_tokens'),
133
+ branch: text('branch'),
134
+ createdAt: text('created_at').notNull(),
135
+ updatedAt: text('updated_at').notNull(),
136
+ project: text('project'), // Phase 6b — see eventsRaw's project comment above.
137
+ }, (t) => [
138
+ index('tasks_session_idx').on(t.sessionId),
139
+ index('tasks_epic_idx').on(t.epicId),
140
+ index('tasks_status_idx').on(t.taskStatus),
141
+ index('tasks_project_idx').on(t.project),
142
+ ]);
143
+ /** `edge-recorded` events (architecture §7 dependency edges). */
144
+ export const edges = sqliteTable('edges', {
145
+ eventId: text('event_id').primaryKey(),
146
+ sessionId: text('session_id').notNull(),
147
+ ts: text('ts').notNull(),
148
+ taskId: text('task_id').notNull(),
149
+ dependsOn: text('depends_on').notNull(),
150
+ edgeType: text('edge_type').notNull(),
151
+ edgeProvenance: text('edge_provenance').notNull(),
152
+ }, (t) => [index('edges_session_idx').on(t.sessionId), index('edges_task_idx').on(t.taskId)]);
153
+ /** `error-logged` events. */
154
+ export const errors = sqliteTable('errors', {
155
+ eventId: text('event_id').primaryKey(),
156
+ sessionId: text('session_id').notNull(),
157
+ ts: text('ts').notNull(),
158
+ taskRef: text('task_ref'),
159
+ agentId: text('agent_id'),
160
+ errorGroup: text('error_group').notNull(),
161
+ errorClass: text('error_class').notNull(),
162
+ severity: text('severity').notNull(),
163
+ detail: text('detail'),
164
+ project: text('project'), // Phase 6b — see eventsRaw's project comment above.
165
+ }, (t) => [
166
+ index('errors_session_idx').on(t.sessionId),
167
+ index('errors_group_idx').on(t.errorGroup),
168
+ index('errors_severity_idx').on(t.severity),
169
+ index('errors_ts_idx').on(t.ts),
170
+ index('errors_project_idx').on(t.project),
171
+ ]);
172
+ /** Current finding state — folded via findings.ts's listFindings(), REUSED not duplicated. */
173
+ export const findings = sqliteTable('findings', {
174
+ findingId: text('finding_id').primaryKey(),
175
+ sessionId: text('session_id').notNull(),
176
+ taskId: text('task_id').notNull(),
177
+ epicId: text('epic_id'),
178
+ fingerprint: text('fingerprint').notNull(),
179
+ findingCategory: text('finding_category').notNull(),
180
+ severity: text('severity').notNull(),
181
+ findingStatus: text('finding_status').notNull(),
182
+ summary: text('summary').notNull(),
183
+ foundBy: text('found_by').notNull(),
184
+ foundByProvider: text('found_by_provider'),
185
+ verifiedBy: text('verified_by'),
186
+ verifiedByProvider: text('verified_by_provider'),
187
+ sameMistakeOfLessonId: text('same_mistake_of_lesson_id'),
188
+ waiverId: text('waiver_id'),
189
+ raisedAt: text('raised_at').notNull(),
190
+ updatedAt: text('updated_at').notNull(),
191
+ project: text('project'), // Phase 6b — see eventsRaw's project comment above.
192
+ }, (t) => [
193
+ index('findings_session_idx').on(t.sessionId),
194
+ index('findings_task_idx').on(t.taskId),
195
+ index('findings_status_idx').on(t.findingStatus),
196
+ index('findings_severity_idx').on(t.severity),
197
+ index('findings_fingerprint_idx').on(t.fingerprint),
198
+ index('findings_project_idx').on(t.project),
199
+ ]);
200
+ /** `waiver-granted` / `waiver-denied` events — folded via waivers.ts semantics, REUSED. */
201
+ export const waivers = sqliteTable('waivers', {
202
+ eventId: text('event_id').primaryKey(),
203
+ sessionId: text('session_id').notNull(),
204
+ ts: text('ts').notNull(),
205
+ fingerprint: text('fingerprint').notNull(),
206
+ decision: text('decision').notNull(), // 'granted' | 'denied'
207
+ operatorNote: text('operator_note'),
208
+ }, (t) => [
209
+ index('waivers_session_idx').on(t.sessionId),
210
+ index('waivers_fingerprint_idx').on(t.fingerprint),
211
+ ]);
212
+ /**
213
+ * `lesson-candidate-raised` / `lesson-status-changed` events (Phase 5
214
+ * deviation — see db/projector.ts header comment; the full lessons loop is
215
+ * Phase 7, this table is forward-compatible with it).
216
+ */
217
+ export const lessons = sqliteTable('lessons', {
218
+ lessonId: text('lesson_id').primaryKey(),
219
+ sessionId: text('session_id').notNull(),
220
+ lessonType: text('lesson_type').notNull(),
221
+ lessonLevel: text('lesson_level').notNull(),
222
+ lessonStatus: text('lesson_status').notNull(),
223
+ lessonScope: text('lesson_scope').notNull(),
224
+ statement: text('statement').notNull(),
225
+ validFrom: text('valid_from').notNull(),
226
+ supersededBy: text('superseded_by'),
227
+ invalidatedByEventId: text('invalidated_by_event_id'),
228
+ provenanceEventIds: text('provenance_event_ids'), // JSON array
229
+ evidence: text('evidence'),
230
+ timesPrevented: integer('times_prevented').notNull().default(0),
231
+ // Phase 7 (lessons.ts) — see lesson.schema.json's field comments: only
232
+ // meaningful for a `rule`-typed, claim-path/stack-wide-scoped entry;
233
+ // nullable, most lessons never set either.
234
+ findingCategory: text('finding_category'),
235
+ claimPath: text('claim_path'),
236
+ // D-129: the selectors the other two selector scopes filter on at
237
+ // dispatch. Nullable for the same reason claim_path is — each is
238
+ // meaningful for exactly one scope — and null on every lesson raised
239
+ // before the selectors existed.
240
+ agentRole: text('agent_role'),
241
+ caseType: text('case_type'),
242
+ }, (t) => [
243
+ index('lessons_session_idx').on(t.sessionId),
244
+ index('lessons_status_idx').on(t.lessonStatus),
245
+ ]);
246
+ /**
247
+ * `epic-closed` events (D-43/D-44/P9-27). One row per closed epic — an epic
248
+ * has no other lifecycle event, so there is no "open" row here and absence
249
+ * means "not closed". Keyed on the payload's `epic_id`, never on task_id:
250
+ * the hand-written dogfood close used `<epic>/epic` as its task_id, which is
251
+ * not the reserved suffix, so anything routing through the task fold would
252
+ * have minted a phantom task card for it.
253
+ */
254
+ export const epics = sqliteTable('epics', {
255
+ epicId: text('epic_id').primaryKey(),
256
+ sessionId: text('session_id').notNull(),
257
+ epicStatus: text('epic_status').notNull(),
258
+ /** 'verdict' | 'operator-override' — who the close is attributable to. */
259
+ closedBy: text('closed_by').notNull(),
260
+ /** What the machine said, kept even when a human closed against it. */
261
+ machineVerdict: text('machine_verdict'),
262
+ machineReason: text('machine_reason'),
263
+ overrideRationale: text('override_rationale'),
264
+ blockers: text('blockers'), // JSON array
265
+ closedAt: text('closed_at').notNull(),
266
+ eventId: text('event_id').notNull(),
267
+ project: text('project'),
268
+ }, (t) => [index('epics_session_idx').on(t.sessionId), index('epics_project_idx').on(t.project)]);
269
+ /**
270
+ * Parsed from `factory/specs/roadmap.md` (roadmap.ts) — NOT session-scoped:
271
+ * one global roadmap, fully replaced (delete-all + reinsert) on every
272
+ * rebuild()/apply() call by db/projector.ts's projectMilestones(). Cheap:
273
+ * a handful of rows, re-parsed from a small committed file every time.
274
+ */
275
+ export const milestones = sqliteTable('milestones', {
276
+ milestoneId: text('milestone_id').primaryKey(),
277
+ name: text('name').notNull(),
278
+ status: text('status').notNull(), // 'planned' | 'in-progress' | 'completed'
279
+ sequence: integer('sequence').notNull(),
280
+ goal: text('goal'),
281
+ epicIds: text('epic_ids').notNull(), // JSON array
282
+ // Phase 6b — see eventsRaw's project comment above. roadmap.ts defaults
283
+ // an unspecified `- project:` bullet to 'black-smith' at parse time (not
284
+ // left null here), since factory/specs/roadmap.md (ROADMAP_PATH) is a
285
+ // hand-authored declaration, not an event replay — there is no "old row"
286
+ // to migrate.
287
+ project: text('project').notNull().default('black-smith'),
288
+ // Phase 10 -- 'factory' | 'dogfood' | 'product' (roadmap.ts's
289
+ // MilestoneKind). Defaults to 'factory' here for the same reason project
290
+ // defaults to 'black-smith': the column default has to agree with what
291
+ // roadmap.ts resolves for a milestone that declares neither bullet, and
292
+ // for those two that answer is the factory's own.
293
+ kind: text('kind').notNull().default('factory'),
294
+ }, (t) => [index('milestones_project_idx').on(t.project)]);
295
+ /** One row per artifact entry on a `task-result-recorded` event's payload. */
296
+ export const artifacts = sqliteTable('artifacts', {
297
+ id: text('id').primaryKey(), // `${event_id}#${index}`
298
+ sessionId: text('session_id').notNull(),
299
+ taskId: text('task_id').notNull(),
300
+ eventId: text('event_id').notNull(),
301
+ ts: text('ts').notNull(),
302
+ type: text('type').notNull(),
303
+ path: text('path').notNull(),
304
+ description: text('description'),
305
+ }, (t) => [index('artifacts_session_idx').on(t.sessionId), index('artifacts_task_idx').on(t.taskId)]);