@thebackstoryis/engineering-with-ai 0.2.9

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 (334) hide show
  1. package/Docs/README.md +50 -0
  2. package/Docs/adoption/consultancy-and-multi-project-rollout.md +135 -0
  3. package/Docs/adoption/non-technical-team-guide.md +126 -0
  4. package/Docs/archaeology-technology-and-hosting-discovery.md +212 -0
  5. package/Docs/blast-radius-and-impact-routing-guide.md +325 -0
  6. package/Docs/blueprints/internal-blueprint-catalogue.md +146 -0
  7. package/Docs/blueprints/maintaining-organisation-blueprints.md +154 -0
  8. package/Docs/blueprints/validation-and-troubleshooting.md +168 -0
  9. package/Docs/cli-reference.md +113 -0
  10. package/Docs/completed-phase-evidence-amendments.md +74 -0
  11. package/Docs/consultancy-network-rollout-control-plane-guide.md +202 -0
  12. package/Docs/context-aware-delivery-companion-guide.md +198 -0
  13. package/Docs/context-aware-delivery-companion-user-guide.md +184 -0
  14. package/Docs/context-management-and-token-efficiency.md +113 -0
  15. package/Docs/design-systems/design-system-implementation-guide.md +85 -0
  16. package/Docs/design-systems/design-system-pack-authoring-guide.md +95 -0
  17. package/Docs/design-systems/design-system-review-guide.md +51 -0
  18. package/Docs/design-systems/design-system-user-guide.md +96 -0
  19. package/Docs/design-systems/product-owner-guide.md +49 -0
  20. package/Docs/designing-organisation-blueprint-packs.md +384 -0
  21. package/Docs/developer-delivery-guide.md +224 -0
  22. package/Docs/error-reporting-guide.md +110 -0
  23. package/Docs/error-reporting-provider-guide.md +49 -0
  24. package/Docs/examples/error-report-adapter.md +70 -0
  25. package/Docs/examples/meeting-review.md +76 -0
  26. package/Docs/examples/minimal-design-system.md +67 -0
  27. package/Docs/examples/prototype-review-inputs.md +175 -0
  28. package/Docs/examples/reproducible-archaeology-depth-example.md +144 -0
  29. package/Docs/examples/test-scenario-input.md +68 -0
  30. package/Docs/examples/worked-examples.md +147 -0
  31. package/Docs/existing-project-onboarding-guide.md +214 -0
  32. package/Docs/explanation/core-concepts.md +26 -0
  33. package/Docs/explanation/delivery-workflow.md +48 -0
  34. package/Docs/governance/governance-team-guide.md +139 -0
  35. package/Docs/governed-starter-project-materialisation-guide.md +284 -0
  36. package/Docs/guide-catalogue.md +117 -0
  37. package/Docs/guided-discovery-facilitator-guide.md +172 -0
  38. package/Docs/guided-intent-workspace-guide.md +109 -0
  39. package/Docs/guided-phase-evidence-drafting-guide.md +119 -0
  40. package/Docs/human-approval-and-assurance-guide.md +146 -0
  41. package/Docs/knowledge-proposals-implementer-guide.md +106 -0
  42. package/Docs/knowledge-proposals-user-guide.md +247 -0
  43. package/Docs/maintainers/context-benchmarks.md +29 -0
  44. package/Docs/maintainers/contributing.md +58 -0
  45. package/Docs/maintainers/evidence-depth-acceptance.md +72 -0
  46. package/Docs/maintainers/verification-walkthroughs.md +104 -0
  47. package/Docs/meeting-evidence-implementer-guide.md +132 -0
  48. package/Docs/meeting-evidence-user-guide.md +200 -0
  49. package/Docs/operations/dashboard-and-delivery-state.md +153 -0
  50. package/Docs/operations/dashboard-configuration.md +87 -0
  51. package/Docs/operations/installation-updating-and-entitlements.md +135 -0
  52. package/Docs/operations/premium-personas-setup.md +76 -0
  53. package/Docs/operations/troubleshooting-and-recovery.md +205 -0
  54. package/Docs/organisation-rollout-guide.md +142 -0
  55. package/Docs/persona-entitlement-provider-guide.md +199 -0
  56. package/Docs/persona-guided-prototype-iteration.md +129 -0
  57. package/Docs/personas/organisation-specific-personas.md +103 -0
  58. package/Docs/personas/persona-authoring-cookbook.md +176 -0
  59. package/Docs/personas/persona-engagement-ui.md +133 -0
  60. package/Docs/personas/persona-governance.md +118 -0
  61. package/Docs/platform-export-analysis-guide.md +336 -0
  62. package/Docs/policies/governance-owner-guide.md +36 -0
  63. package/Docs/policies/implementation-guide.md +42 -0
  64. package/Docs/policies/organisation-policy-design-gates.md +58 -0
  65. package/Docs/policies/policy-pack-authoring-guide.md +108 -0
  66. package/Docs/policies/product-owner-guide.md +43 -0
  67. package/Docs/policies/technical-owner-guide.md +37 -0
  68. package/Docs/product-owner-guide.md +327 -0
  69. package/Docs/project-portfolio-orchestration-guide.md +199 -0
  70. package/Docs/quality/manual-qa-and-acceptance.md +162 -0
  71. package/Docs/quality/persona-driven-test-scenarios.md +172 -0
  72. package/Docs/quality/reproducible-archaeology-depth-review-checklist.md +89 -0
  73. package/Docs/reference/capabilities-and-project-layout.md +678 -0
  74. package/Docs/reference/cli-and-configuration.md +398 -0
  75. package/Docs/reference/contributions-api.md +23 -0
  76. package/Docs/reference/security-adapter-authoring.md +81 -0
  77. package/Docs/reference/starter-adapter-authoring.md +74 -0
  78. package/Docs/repository-source-map-guide.md +381 -0
  79. package/Docs/reproducible-archaeology-and-discovery-depth.md +292 -0
  80. package/Docs/screen-prototype-creation-guide.md +324 -0
  81. package/Docs/security-validation-guide.md +353 -0
  82. package/Docs/solution-readiness-review-guide.md +123 -0
  83. package/Docs/standards/project-standards-authoring.md +157 -0
  84. package/Docs/team-hub-guide.md +162 -0
  85. package/Docs/team-hub-resource-registry-guide.md +167 -0
  86. package/Docs/tutorials/first-delivery.md +83 -0
  87. package/Docs/tutorials/first-session.md +62 -0
  88. package/Docs/using-lifecycle-hooks.md +381 -0
  89. package/Docs/working-with-personas.md +274 -0
  90. package/LICENSE +165 -0
  91. package/README.md +96 -0
  92. package/agents-src/claude/ewai-security-reviewer.md +15 -0
  93. package/bin/ewai +5 -0
  94. package/config/archaeology-record-families.yaml +59 -0
  95. package/config/delivery-artifacts.yaml +121 -0
  96. package/config/delivery-stages.yaml +77 -0
  97. package/config/design-system.schema.json +46 -0
  98. package/config/error-reporting.schema.json +79 -0
  99. package/config/evidence-depth.schema.json +53 -0
  100. package/config/intent.schema.json +90 -0
  101. package/config/knowledge-proposals-proposal.schema.json +34 -0
  102. package/config/lifecycle-event.schema.json +68 -0
  103. package/config/lifecycle-handler.schema.json +45 -0
  104. package/config/lifecycle-hook-ack.schema.json +19 -0
  105. package/config/meeting-evidence-candidate.schema.json +102 -0
  106. package/config/organisation-policy.schema.json +137 -0
  107. package/config/pack.schema.json +250 -0
  108. package/config/persona-pack.schema.json +21 -0
  109. package/config/persona.schema.json +17 -0
  110. package/config/policy-evaluation.schema.json +77 -0
  111. package/config/policy-facts.schema.json +140 -0
  112. package/config/portfolio.schema.json +68 -0
  113. package/config/project.schema.json +313 -0
  114. package/config/prototype-iteration.schema.json +128 -0
  115. package/config/rollout.schema.json +87 -0
  116. package/config/security-adapter.schema.json +31 -0
  117. package/config/security-scan-request.schema.json +64 -0
  118. package/config/security-scan-response.schema.json +52 -0
  119. package/config/security-validation-policy.schema.json +74 -0
  120. package/config/starter-source-acknowledgement.schema.json +13 -0
  121. package/config/starter-source-adapter.schema.json +38 -0
  122. package/config/starter-source-request.schema.json +61 -0
  123. package/package.json +77 -0
  124. package/packs/core/pack.yaml +7 -0
  125. package/packs/design-systems/default/experience-promise.md +9 -0
  126. package/packs/design-systems/default/intentional-review.md +10 -0
  127. package/packs/design-systems/default/interaction-and-entry.md +9 -0
  128. package/packs/design-systems/default/meaningful-content-and-states.md +9 -0
  129. package/packs/design-systems/default/pack.yaml +44 -0
  130. package/packs/design-systems/default/principles.md +10 -0
  131. package/packs/personas/core/pack.yaml +7 -0
  132. package/packs/personas/core/personas/archaeologist.md +37 -0
  133. package/packs/personas/core/personas/end-user.md +17 -0
  134. package/packs/personas/core/personas/maintainer.md +17 -0
  135. package/packs/personas/core/personas/operator.md +17 -0
  136. package/packs/personas/core/personas/specs-knowledge-curator.md +35 -0
  137. package/packs/technologies/laravel/pack.yaml +30 -0
  138. package/packs/technologies/laravel-nuxt/pack.yaml +30 -0
  139. package/packs/technologies/nuxt/pack.yaml +30 -0
  140. package/packs/technologies/power-platform/pack.yaml +31 -0
  141. package/packs/technologies/salesforce/pack.yaml +25 -0
  142. package/public/app.js +4896 -0
  143. package/public/apple-touch-icon.png +0 -0
  144. package/public/assets/backstory-icon.png +0 -0
  145. package/public/dashboard-navigation.js +98 -0
  146. package/public/favicon-16.png +0 -0
  147. package/public/favicon-32.png +0 -0
  148. package/public/favicon.ico +0 -0
  149. package/public/index.html +789 -0
  150. package/public/styles.css +2693 -0
  151. package/public/team-hub/app.js +202 -0
  152. package/public/team-hub/index.html +79 -0
  153. package/public/team-hub/styles.css +90 -0
  154. package/scripts/publication-check.mjs +140 -0
  155. package/scripts/setup.mjs +21 -0
  156. package/skills-src/ewai-archaeology/SKILL.md +334 -0
  157. package/skills-src/ewai-archaeology/agents/openai.yaml +4 -0
  158. package/skills-src/ewai-archaeology/references/archaeology-contract.md +201 -0
  159. package/skills-src/ewai-archaeology/references/lifecycle-reconstruction.md +177 -0
  160. package/skills-src/ewai-archaeology/references/maximum-detail-reconstruction.md +97 -0
  161. package/skills-src/ewai-archaeology/references/model-routing.md +26 -0
  162. package/skills-src/ewai-architecture/SKILL.md +108 -0
  163. package/skills-src/ewai-architecture/agents/openai.yaml +4 -0
  164. package/skills-src/ewai-architecture/references/architecture-contract.md +176 -0
  165. package/skills-src/ewai-context/SKILL.md +68 -0
  166. package/skills-src/ewai-context/agents/openai.yaml +4 -0
  167. package/skills-src/ewai-context-import/SKILL.md +118 -0
  168. package/skills-src/ewai-context-import/agents/openai.yaml +4 -0
  169. package/skills-src/ewai-context-import/references/context-import-contract.md +106 -0
  170. package/skills-src/ewai-dashboard-configuration/SKILL.md +20 -0
  171. package/skills-src/ewai-deliver/SKILL.md +122 -0
  172. package/skills-src/ewai-deliver/references/delivery-evidence.md +92 -0
  173. package/skills-src/ewai-deliver/references/phase-routing.md +31 -0
  174. package/skills-src/ewai-design-system-apply/SKILL.md +27 -0
  175. package/skills-src/ewai-design-system-apply/agents/openai.yaml +4 -0
  176. package/skills-src/ewai-design-system-apply/references/application-contract.md +36 -0
  177. package/skills-src/ewai-design-system-author/SKILL.md +28 -0
  178. package/skills-src/ewai-design-system-author/agents/openai.yaml +4 -0
  179. package/skills-src/ewai-design-system-author/references/authoring-contract.md +38 -0
  180. package/skills-src/ewai-design-system-review/SKILL.md +26 -0
  181. package/skills-src/ewai-design-system-review/agents/openai.yaml +4 -0
  182. package/skills-src/ewai-design-system-review/references/review-contract.md +40 -0
  183. package/skills-src/ewai-error-reporting/SKILL.md +46 -0
  184. package/skills-src/ewai-error-reporting/agents/openai.yaml +4 -0
  185. package/skills-src/ewai-error-reporting/references/provider-contract.md +74 -0
  186. package/skills-src/ewai-evidence-depth/SKILL.md +72 -0
  187. package/skills-src/ewai-evidence-depth/agents/openai.yaml +4 -0
  188. package/skills-src/ewai-evidence-depth/references/evidence-depth-contract.md +127 -0
  189. package/skills-src/ewai-intent/SKILL.md +68 -0
  190. package/skills-src/ewai-intent/agents/openai.yaml +4 -0
  191. package/skills-src/ewai-intent/references/intent-contract.md +42 -0
  192. package/skills-src/ewai-knowledge-proposals/SKILL.md +105 -0
  193. package/skills-src/ewai-knowledge-proposals/agents/openai.yaml +4 -0
  194. package/skills-src/ewai-knowledge-proposals/references/proposal-contract.md +59 -0
  195. package/skills-src/ewai-meeting-evidence/SKILL.md +106 -0
  196. package/skills-src/ewai-meeting-evidence/agents/openai.yaml +4 -0
  197. package/skills-src/ewai-meeting-evidence/references/candidate-contract.md +64 -0
  198. package/skills-src/ewai-organisation-policy/SKILL.md +62 -0
  199. package/skills-src/ewai-organisation-policy/agents/openai.yaml +4 -0
  200. package/skills-src/ewai-organisation-policy/references/policy-contract.md +94 -0
  201. package/skills-src/ewai-palace-housekeeping/SKILL.md +55 -0
  202. package/skills-src/ewai-palace-housekeeping/agents/openai.yaml +4 -0
  203. package/skills-src/ewai-persona-entitlement/SKILL.md +60 -0
  204. package/skills-src/ewai-persona-entitlement/agents/openai.yaml +4 -0
  205. package/skills-src/ewai-phase-evidence/SKILL.md +79 -0
  206. package/skills-src/ewai-phase-evidence/agents/openai.yaml +4 -0
  207. package/skills-src/ewai-pipeline/SKILL.md +130 -0
  208. package/skills-src/ewai-pipeline/agents/openai.yaml +4 -0
  209. package/skills-src/ewai-pipeline/references/cli.md +86 -0
  210. package/skills-src/ewai-pipeline/references/specs-contract.md +16 -0
  211. package/skills-src/ewai-portfolio/SKILL.md +70 -0
  212. package/skills-src/ewai-portfolio/agents/openai.yaml +4 -0
  213. package/skills-src/ewai-portfolio/references/portfolio-contract.md +67 -0
  214. package/skills-src/ewai-project-discovery/SKILL.md +95 -0
  215. package/skills-src/ewai-project-discovery/agents/openai.yaml +4 -0
  216. package/skills-src/ewai-project-discovery/references/discovery-contract.md +34 -0
  217. package/skills-src/ewai-prototype-iteration/SKILL.md +30 -0
  218. package/skills-src/ewai-prototype-iteration/agents/openai.yaml +4 -0
  219. package/skills-src/ewai-prototype-iteration/references/review-contract.md +49 -0
  220. package/skills-src/ewai-retro/SKILL.md +48 -0
  221. package/skills-src/ewai-retro/agents/openai.yaml +4 -0
  222. package/skills-src/ewai-retro/references/asset-routing.md +14 -0
  223. package/skills-src/ewai-rollout/SKILL.md +74 -0
  224. package/skills-src/ewai-rollout/agents/openai.yaml +4 -0
  225. package/skills-src/ewai-rollout/references/rollout-contract.md +74 -0
  226. package/skills-src/ewai-shape-intents/SKILL.md +84 -0
  227. package/skills-src/ewai-shape-intents/agents/openai.yaml +4 -0
  228. package/skills-src/ewai-shape-intents/references/intent-mapping-contract.md +109 -0
  229. package/skills-src/ewai-solution-readiness/SKILL.md +55 -0
  230. package/skills-src/ewai-solution-readiness/agents/openai.yaml +4 -0
  231. package/skills-src/ewai-standards-check/SKILL.md +93 -0
  232. package/skills-src/ewai-standards-check/agents/openai.yaml +4 -0
  233. package/skills-src/ewai-standards-check/references/report-contract.md +116 -0
  234. package/skills-src/ewai-test-scenarios/SKILL.md +94 -0
  235. package/skills-src/ewai-test-scenarios/agents/openai.yaml +4 -0
  236. package/skills-src/ewai-test-scenarios/references/scenario-contract.md +88 -0
  237. package/src/afk-worker.mjs +16 -0
  238. package/src/archaeology.mjs +1333 -0
  239. package/src/checkin.mjs +261 -0
  240. package/src/cli.mjs +2427 -0
  241. package/src/companion-guidance.mjs +257 -0
  242. package/src/companion-opening.mjs +62 -0
  243. package/src/companion.mjs +256 -0
  244. package/src/context.mjs +210 -0
  245. package/src/dashboard-preferences.mjs +80 -0
  246. package/src/delivery-artifacts.mjs +204 -0
  247. package/src/delivery-documents.mjs +248 -0
  248. package/src/delivery-gates.mjs +317 -0
  249. package/src/delivery.mjs +1433 -0
  250. package/src/design-system-application.mjs +291 -0
  251. package/src/design-system-authoring.mjs +101 -0
  252. package/src/design-systems.mjs +466 -0
  253. package/src/discovery.mjs +1314 -0
  254. package/src/error-reporting.mjs +323 -0
  255. package/src/evidence-depth.mjs +543 -0
  256. package/src/execution-state.mjs +243 -0
  257. package/src/install.mjs +166 -0
  258. package/src/intent-dependencies.mjs +117 -0
  259. package/src/intent-maps.mjs +402 -0
  260. package/src/intents.mjs +747 -0
  261. package/src/knowledge-proposals.mjs +717 -0
  262. package/src/launcher.mjs +51 -0
  263. package/src/meeting-evidence.mjs +703 -0
  264. package/src/network-rollout.mjs +386 -0
  265. package/src/organisation-blueprints.mjs +438 -0
  266. package/src/organisation-policies.mjs +448 -0
  267. package/src/packs.mjs +44 -0
  268. package/src/paths.mjs +62 -0
  269. package/src/persona-entitlements.mjs +438 -0
  270. package/src/persona-licence-config.mjs +98 -0
  271. package/src/persona-website-provider.mjs +134 -0
  272. package/src/persona-zip.mjs +87 -0
  273. package/src/personas.mjs +159 -0
  274. package/src/platform-metadata-analysis.mjs +314 -0
  275. package/src/policy-design-gates.mjs +623 -0
  276. package/src/policy-gate-integration.mjs +318 -0
  277. package/src/portfolio.mjs +509 -0
  278. package/src/power-platform-source-map.mjs +190 -0
  279. package/src/project.mjs +449 -0
  280. package/src/prototype-iterations.mjs +730 -0
  281. package/src/repository-source-map.mjs +603 -0
  282. package/src/runtime/afk-conductor.mjs +973 -0
  283. package/src/runtime/context-assembly.mjs +457 -0
  284. package/src/runtime/context-benchmarks.mjs +115 -0
  285. package/src/runtime/dashboard-actions.mjs +109 -0
  286. package/src/runtime/dashboard-handoffs.mjs +149 -0
  287. package/src/runtime/dashboard-server.mjs +1272 -0
  288. package/src/runtime/dashboard.mjs +197 -0
  289. package/src/runtime/database.mjs +789 -0
  290. package/src/runtime/error-reporting.mjs +581 -0
  291. package/src/runtime/evidence-depth-workspace.mjs +412 -0
  292. package/src/runtime/execution-leases.mjs +299 -0
  293. package/src/runtime/guided-discovery.mjs +350 -0
  294. package/src/runtime/guided-intents.mjs +517 -0
  295. package/src/runtime/impact-analysis.mjs +535 -0
  296. package/src/runtime/intents.mjs +222 -0
  297. package/src/runtime/knowledge.mjs +86 -0
  298. package/src/runtime/lifecycle-hooks.mjs +1239 -0
  299. package/src/runtime/mcp-config.mjs +110 -0
  300. package/src/runtime/mcp-server.mjs +1885 -0
  301. package/src/runtime/palace.mjs +362 -0
  302. package/src/runtime/paths.mjs +58 -0
  303. package/src/runtime/persona-engagement.mjs +255 -0
  304. package/src/runtime/phase-contributions.mjs +594 -0
  305. package/src/runtime/policy-workspace.mjs +170 -0
  306. package/src/runtime/prototype-iterations.mjs +235 -0
  307. package/src/runtime/provider-adapters.mjs +163 -0
  308. package/src/runtime/repository-index.mjs +838 -0
  309. package/src/runtime/runs.mjs +185 -0
  310. package/src/runtime/security-validation.mjs +1230 -0
  311. package/src/runtime/starter-materialisation.mjs +1155 -0
  312. package/src/runtime/team-hub-client.mjs +479 -0
  313. package/src/runtime/team-hub-database.mjs +288 -0
  314. package/src/runtime/team-hub-server.mjs +191 -0
  315. package/src/runtime/team-hub.mjs +110 -0
  316. package/src/runtime/tree-sitter-index.mjs +390 -0
  317. package/src/runtime/version.mjs +1 -0
  318. package/src/runtime/work.mjs +633 -0
  319. package/src/salesforce-source-map.mjs +212 -0
  320. package/src/security-validation-config.mjs +224 -0
  321. package/src/solution-readiness.mjs +620 -0
  322. package/src/starter-materialisation-contract.mjs +407 -0
  323. package/src/task-graph.mjs +544 -0
  324. package/src/team-hub-resources.mjs +239 -0
  325. package/src/team-hub.mjs +242 -0
  326. package/src/test-scenarios.mjs +622 -0
  327. package/src/validation-config.mjs +289 -0
  328. package/templates/SPECS/1.Scope/personas/registry.yaml +12 -0
  329. package/templates/SPECS/5.Strategy/patterns/context-packet.md +119 -0
  330. package/templates/SPECS/6.Build/_tracker-template.md +16 -0
  331. package/templates/SPECS/pipeline.yaml +62 -0
  332. package/templates/discovery-answers.yaml +86 -0
  333. package/templates/intent-body.md +21 -0
  334. package/tests/fixtures/context-benchmarks.json +9 -0
@@ -0,0 +1,633 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
3
+ import { deriveExecutionState } from '../execution-state.mjs';
4
+ import { validatePrototypeManifest } from '../delivery-artifacts.mjs';
5
+ import { projectPaths } from '../paths.mjs';
6
+ import { openRuntimeDatabase } from './database.mjs';
7
+ import { readRuntimeIntent, syncIntentIndex } from './intents.mjs';
8
+
9
+ const lanes = ['backlog', 'ready', 'active', 'qa', 'blocked', 'done'];
10
+
11
+ function groupName(slug) {
12
+ return String(slug || 'general')
13
+ .split('-')
14
+ .map((part) => `${part[0]?.toUpperCase() ?? ''}${part.slice(1)}`)
15
+ .join(' ');
16
+ }
17
+
18
+ function intentFromRow(row) {
19
+ return {
20
+ id: row.id,
21
+ slug: row.slug,
22
+ domain: row.domain,
23
+ title: row.title,
24
+ status: row.status,
25
+ deliveryStatus: row.delivery_status,
26
+ currentPhase: row.current_phase,
27
+ deliveryStatePath: row.delivery_state_path,
28
+ path: row.path,
29
+ relationships: JSON.parse(row.relationships_json || '[]'),
30
+ };
31
+ }
32
+
33
+ function executionInputs(database) {
34
+ const intents = database.prepare(`
35
+ SELECT id, slug, domain, title, status, delivery_status, current_phase,
36
+ delivery_state_path, path, relationships_json
37
+ FROM intents ORDER BY id
38
+ `).all();
39
+ const catalog = new Map(intents.map((row) => [row.id, intentFromRow(row)]));
40
+ const current = new Date().toISOString();
41
+ const leases = database.prepare(`
42
+ SELECT id, work_item_id, task_id, owner_id, tool, run_id, status,
43
+ acquired_at, heartbeat_at, expires_at, released_at, outcome
44
+ FROM execution_leases WHERE status = 'active' AND expires_at > ?
45
+ `).all(current).map((row) => ({
46
+ id: row.id,
47
+ workItemId: row.work_item_id,
48
+ taskId: row.task_id,
49
+ ownerId: row.owner_id,
50
+ tool: row.tool,
51
+ runId: row.run_id,
52
+ status: row.status,
53
+ acquiredAt: row.acquired_at,
54
+ heartbeatAt: row.heartbeat_at,
55
+ expiresAt: row.expires_at,
56
+ releasedAt: row.released_at,
57
+ outcome: row.outcome,
58
+ }));
59
+ const byWorkItem = new Map();
60
+ for (const lease of leases) {
61
+ if (!byWorkItem.has(lease.workItemId)) byWorkItem.set(lease.workItemId, []);
62
+ byWorkItem.get(lease.workItemId).push(lease);
63
+ }
64
+ return { intents, catalog, leases: byWorkItem };
65
+ }
66
+
67
+ function rowToWorkItem(row) {
68
+ return {
69
+ id: row.id,
70
+ intentId: row.intent_id,
71
+ slug: row.slug,
72
+ domain: row.domain,
73
+ title: row.title,
74
+ intentPath: row.intent_path,
75
+ lane: row.lane,
76
+ state: row.state,
77
+ intentState: row.intent_state,
78
+ completionPercent: row.completion_percent,
79
+ priority: row.priority,
80
+ currentSprint: Boolean(row.current_sprint),
81
+ currentPhase: row.current_phase,
82
+ blockedBy: row.blocked_by,
83
+ notes: row.notes,
84
+ createdAt: row.created_at,
85
+ updatedAt: row.updated_at
86
+ };
87
+ }
88
+
89
+ function rowToEvent(row) {
90
+ return {
91
+ id: row.id,
92
+ sessionId: row.session_id,
93
+ workItemId: row.work_item_id,
94
+ eventType: row.event_type,
95
+ tool: row.tool,
96
+ phaseKey: row.phase_key,
97
+ summary: row.summary,
98
+ details: row.details,
99
+ requiresHuman: Boolean(row.requires_human),
100
+ metadata: JSON.parse(row.metadata_json || '{}'),
101
+ createdAt: row.created_at
102
+ };
103
+ }
104
+
105
+ function rowToSession(row) {
106
+ return {
107
+ id: row.id,
108
+ workItemId: row.work_item_id,
109
+ ownerId: row.owner_id,
110
+ slug: row.slug,
111
+ title: row.title,
112
+ domain: row.domain,
113
+ tool: row.tool,
114
+ phaseKey: row.phase_key,
115
+ status: row.status,
116
+ summary: row.summary,
117
+ awaitingHuman: Boolean(row.awaiting_human),
118
+ question: row.question,
119
+ startedAt: row.started_at,
120
+ updatedAt: row.updated_at,
121
+ endedAt: row.ended_at,
122
+ latestEventId: row.latest_event_id ?? null,
123
+ latestEventType: row.latest_event_type ?? '',
124
+ latestEventSummary: row.latest_event_summary ?? '',
125
+ latestEventAt: row.latest_event_at ?? row.updated_at,
126
+ eventCount: row.event_count ?? 0
127
+ };
128
+ }
129
+
130
+ function withDatabase(projectRoot, supplied, operation) {
131
+ const ownsDatabase = !supplied;
132
+ const database = supplied ?? openRuntimeDatabase(projectRoot);
133
+ try {
134
+ return operation(database);
135
+ } finally {
136
+ if (ownsDatabase) database.close();
137
+ }
138
+ }
139
+
140
+ export function syncWorkItems(projectRoot, suppliedDatabase = null) {
141
+ return withDatabase(projectRoot, suppliedDatabase, (database) => {
142
+ syncIntentIndex(projectRoot, database);
143
+ const execution = executionInputs(database);
144
+ const { intents } = execution;
145
+ const insertItem = database.prepare(`
146
+ INSERT INTO work_items (
147
+ id, intent_id, slug, domain, title, intent_path, lane, state, intent_state,
148
+ completion_percent, current_phase
149
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
150
+ ON CONFLICT(id) DO UPDATE SET
151
+ intent_id = excluded.intent_id,
152
+ slug = excluded.slug,
153
+ domain = excluded.domain,
154
+ title = excluded.title,
155
+ intent_path = excluded.intent_path,
156
+ updated_at = CASE
157
+ WHEN work_items.title <> excluded.title OR work_items.domain <> excluded.domain OR work_items.intent_path <> excluded.intent_path
158
+ THEN CURRENT_TIMESTAMP ELSE work_items.updated_at END
159
+ `);
160
+ const insertGroup = database.prepare(`
161
+ INSERT INTO delivery_groups (slug, name, description)
162
+ VALUES (?, ?, 'Derived from the project intent domain.')
163
+ ON CONFLICT(slug) DO UPDATE SET name = excluded.name, updated_at = CURRENT_TIMESTAMP
164
+ `);
165
+ const findGroup = database.prepare('SELECT id FROM delivery_groups WHERE slug = ?');
166
+ const linkGroup = database.prepare(`
167
+ INSERT INTO group_items (group_id, work_item_id, position, source)
168
+ VALUES (?, ?, ?, 'derived')
169
+ ON CONFLICT(group_id, work_item_id) DO UPDATE SET position = excluded.position, source = excluded.source
170
+ `);
171
+ const clearDerivedGroups = database.prepare("DELETE FROM group_items WHERE work_item_id = ? AND source = 'derived'");
172
+ const restoreCommittedState = database.prepare(`
173
+ UPDATE work_items SET lane = ?, state = ?, intent_state = ?, completion_percent = ?,
174
+ current_phase = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
175
+ `);
176
+ const supersedePrototypes = database.prepare(`
177
+ UPDATE artefacts SET status = 'superseded'
178
+ WHERE work_item_id = ? AND kind = 'prototype' AND status = 'active'
179
+ `);
180
+ const registerPrototype = database.prepare(`
181
+ INSERT INTO artefacts (work_item_id, kind, path, title, status)
182
+ VALUES (?, 'prototype', ?, ?, 'active')
183
+ ON CONFLICT(work_item_id, kind, path) DO UPDATE SET
184
+ title = excluded.title, status = 'active'
185
+ `);
186
+
187
+ database.exec('BEGIN IMMEDIATE;');
188
+ try {
189
+ intents.forEach((intent, index) => {
190
+ const derived = deriveExecutionState(projectRoot, intentFromRow(intent), {
191
+ intentCatalog: execution.catalog,
192
+ leases: execution.leases.get(intent.id) ?? [],
193
+ });
194
+ const defaults = derived.projection;
195
+ insertItem.run(
196
+ intent.id,
197
+ intent.id,
198
+ intent.slug,
199
+ intent.domain,
200
+ intent.title,
201
+ intent.path,
202
+ defaults.lane,
203
+ defaults.state,
204
+ defaults.intentState,
205
+ defaults.progress,
206
+ defaults.phase
207
+ );
208
+ restoreCommittedState.run(
209
+ defaults.lane,
210
+ defaults.state,
211
+ defaults.intentState,
212
+ defaults.progress,
213
+ defaults.phase,
214
+ intent.id
215
+ );
216
+ insertGroup.run(intent.domain, groupName(intent.domain));
217
+ clearDerivedGroups.run(intent.id);
218
+ linkGroup.run(findGroup.get(intent.domain).id, intent.id, index);
219
+ const deliveryRoot = resolve(projectPaths(projectRoot).buildRoot, intent.slug);
220
+ const prototypeManifest = resolve(deliveryRoot, 'ui-design-assets/prototypes/manifest.json');
221
+ if (existsSync(prototypeManifest)) {
222
+ try {
223
+ const manifest = validatePrototypeManifest(deliveryRoot);
224
+ const prototypePath = relative(projectRoot, resolve(deliveryRoot, manifest.selected.path)).replaceAll('\\', '/');
225
+ supersedePrototypes.run(intent.id);
226
+ registerPrototype.run(intent.id, prototypePath, manifest.selected.title);
227
+ } catch {
228
+ // The UI Design gate reports invalid manifests; dashboard sync remains available.
229
+ }
230
+ }
231
+ });
232
+ database.exec(`
233
+ DELETE FROM delivery_groups
234
+ WHERE id NOT IN (SELECT DISTINCT group_id FROM group_items)
235
+ `);
236
+ database.exec('COMMIT;');
237
+ } catch (error) {
238
+ database.exec('ROLLBACK;');
239
+ throw error;
240
+ }
241
+ return { count: intents.length };
242
+ });
243
+ }
244
+
245
+ function collectionMaps(database) {
246
+ const groups = new Map();
247
+ for (const row of database.prepare(`
248
+ SELECT gi.work_item_id, dg.id, dg.slug, dg.name, dg.status, dg.priority
249
+ FROM group_items gi JOIN delivery_groups dg ON dg.id = gi.group_id
250
+ ORDER BY dg.name
251
+ `).all()) {
252
+ if (!groups.has(row.work_item_id)) groups.set(row.work_item_id, []);
253
+ groups.get(row.work_item_id).push({ id: row.id, slug: row.slug, name: row.name, status: row.status, priority: row.priority });
254
+ }
255
+
256
+ const artefacts = new Map();
257
+ for (const row of database.prepare('SELECT * FROM artefacts ORDER BY kind, title, path').all()) {
258
+ if (!artefacts.has(row.work_item_id)) artefacts.set(row.work_item_id, []);
259
+ artefacts.get(row.work_item_id).push({
260
+ id: row.id, kind: row.kind, path: row.path, title: row.title, status: row.status, createdAt: row.created_at
261
+ });
262
+ }
263
+
264
+ const phases = new Map();
265
+ for (const row of database.prepare('SELECT * FROM pipeline_phases ORDER BY id').all()) {
266
+ if (!phases.has(row.work_item_id)) phases.set(row.work_item_id, []);
267
+ phases.get(row.work_item_id).push({
268
+ id: row.id,
269
+ phaseKey: row.phase_key,
270
+ status: row.status,
271
+ startedAt: row.started_at,
272
+ completedAt: row.completed_at,
273
+ artefactPath: row.artefact_path,
274
+ iterationCount: row.iteration_count,
275
+ notes: row.notes
276
+ });
277
+ }
278
+ return { groups, artefacts, phases };
279
+ }
280
+
281
+ export function listWorkItems(projectRoot, options = {}) {
282
+ return withDatabase(projectRoot, null, (database) => {
283
+ syncWorkItems(projectRoot, database);
284
+ const maps = collectionMaps(database);
285
+ const execution = executionInputs(database);
286
+ const query = String(options.query ?? '').trim().toLowerCase();
287
+ const group = String(options.group ?? '').trim();
288
+ const lane = String(options.lane ?? '').trim();
289
+ const sprint = String(options.sprint ?? '').trim();
290
+ return database.prepare('SELECT * FROM work_items ORDER BY priority, updated_at DESC, title COLLATE NOCASE').all()
291
+ .map((row) => {
292
+ const item = rowToWorkItem(row);
293
+ return {
294
+ ...item,
295
+ groups: maps.groups.get(item.id) ?? [],
296
+ artefacts: maps.artefacts.get(item.id) ?? [],
297
+ phases: maps.phases.get(item.id) ?? [],
298
+ execution: deriveExecutionState(projectRoot, execution.catalog.get(item.intentId), {
299
+ intentCatalog: execution.catalog,
300
+ leases: execution.leases.get(item.id) ?? [],
301
+ }),
302
+ };
303
+ })
304
+ .filter((item) => !lane || item.lane === lane)
305
+ .filter((item) => !group || item.groups.some((candidate) => candidate.slug === group || String(candidate.id) === group))
306
+ .filter((item) => sprint !== 'current' || item.currentSprint)
307
+ .filter((item) => sprint !== 'backlog' || !item.currentSprint)
308
+ .filter((item) => !query || `${item.id} ${item.slug} ${item.domain} ${item.title} ${item.state} ${item.currentPhase}`.toLowerCase().includes(query));
309
+ });
310
+ }
311
+
312
+ export function listGroups(projectRoot) {
313
+ return withDatabase(projectRoot, null, (database) => {
314
+ syncWorkItems(projectRoot, database);
315
+ return database.prepare(`
316
+ SELECT dg.*, COUNT(gi.work_item_id) AS item_count,
317
+ SUM(CASE WHEN wi.current_sprint = 1 THEN 1 ELSE 0 END) AS sprint_count
318
+ FROM delivery_groups dg
319
+ LEFT JOIN group_items gi ON gi.group_id = dg.id
320
+ LEFT JOIN work_items wi ON wi.id = gi.work_item_id
321
+ GROUP BY dg.id
322
+ ORDER BY dg.name COLLATE NOCASE
323
+ `).all().map((row) => ({
324
+ id: row.id,
325
+ slug: row.slug,
326
+ name: row.name,
327
+ description: row.description,
328
+ status: row.status,
329
+ priority: row.priority,
330
+ itemCount: row.item_count,
331
+ sprintCount: row.sprint_count
332
+ }));
333
+ });
334
+ }
335
+
336
+ export function auditOperationalState(projectRoot) {
337
+ return withDatabase(projectRoot, null, (database) => {
338
+ syncWorkItems(projectRoot, database);
339
+ const rows = database.prepare(`
340
+ SELECT i.id, i.slug, i.status AS intent_status, i.delivery_status,
341
+ i.current_phase AS intent_phase, w.intent_state, w.state AS delivery_state,
342
+ w.current_phase AS work_phase
343
+ FROM intents i JOIN work_items w ON w.intent_id = i.id
344
+ ORDER BY i.id
345
+ `).all();
346
+ const drift = rows.filter((row) => (
347
+ row.intent_status !== row.intent_state
348
+ || row.delivery_status !== row.delivery_state
349
+ || row.intent_phase !== row.work_phase
350
+ ));
351
+ return {
352
+ schema: 'ewai.operational-state-audit/v1',
353
+ status: drift.length ? 'drift-detected' : 'consistent',
354
+ checked: rows.length,
355
+ drift
356
+ };
357
+ });
358
+ }
359
+
360
+ function findItem(database, reference) {
361
+ const byId = database.prepare('SELECT * FROM work_items WHERE id = ?').get(reference);
362
+ if (byId) return byId;
363
+ const matches = database.prepare('SELECT * FROM work_items WHERE slug = ? ORDER BY id LIMIT 2').all(reference);
364
+ if (matches.length > 1) throw new Error(`Ambiguous work item slug: ${reference}`);
365
+ return matches[0];
366
+ }
367
+
368
+ function isWithin(root, path) {
369
+ const rel = relative(root, path);
370
+ return rel === '' || (rel && !rel.startsWith('..') && !isAbsolute(rel));
371
+ }
372
+
373
+ export function resolvePrototypeAsset(projectRoot, reference, artefactId, assetPath = '') {
374
+ return withDatabase(projectRoot, null, (database) => {
375
+ syncWorkItems(projectRoot, database);
376
+ const item = findItem(database, reference);
377
+ if (!item) return null;
378
+ const artefact = database.prepare(`
379
+ SELECT * FROM artefacts WHERE id = ? AND work_item_id = ? AND kind = 'prototype' AND status = 'active'
380
+ `).get(Number(artefactId), item.id);
381
+ if (!artefact) return null;
382
+ const root = resolve(projectRoot);
383
+ const source = isAbsolute(artefact.path) ? resolve(artefact.path) : resolve(root, artefact.path);
384
+ if (!isWithin(root, source)) return null;
385
+ const prototypeRoot = dirname(source);
386
+ const target = assetPath ? resolve(prototypeRoot, assetPath) : source;
387
+ if (!isWithin(root, target) || !isWithin(prototypeRoot, target) || !existsSync(target) || !statSync(target).isFile()) return null;
388
+ return {
389
+ path: target,
390
+ artefact: { id: artefact.id, kind: artefact.kind, path: artefact.path, title: artefact.title, status: artefact.status },
391
+ item: rowToWorkItem(item)
392
+ };
393
+ });
394
+ }
395
+
396
+ export function updateWorkItem(projectRoot, reference, input) {
397
+ return withDatabase(projectRoot, null, (database) => {
398
+ syncWorkItems(projectRoot, database);
399
+ const item = findItem(database, reference);
400
+ if (!item) throw new Error(`Unknown work item: ${reference}`);
401
+ const allowed = {
402
+ lane: 'lane',
403
+ state: 'state',
404
+ intentState: 'intent_state',
405
+ completionPercent: 'completion_percent',
406
+ priority: 'priority',
407
+ currentSprint: 'current_sprint',
408
+ currentPhase: 'current_phase',
409
+ blockedBy: 'blocked_by',
410
+ notes: 'notes'
411
+ };
412
+ const entries = Object.entries(input).filter(([key]) => allowed[key]);
413
+ if (entries.some(([key, value]) => key === 'lane' && !lanes.includes(value))) {
414
+ throw new Error(`Unsupported work item lane: ${input.lane}`);
415
+ }
416
+ if (entries.length) {
417
+ const values = entries.map(([key, value]) => {
418
+ if (key === 'currentSprint') return value ? 1 : 0;
419
+ if (key === 'completionPercent') return Math.max(0, Math.min(100, Number(value)));
420
+ return value;
421
+ });
422
+ database.prepare(`UPDATE work_items SET ${entries.map(([key]) => `${allowed[key]} = ?`).join(', ')}, updated_at = CURRENT_TIMESTAMP WHERE id = ?`)
423
+ .run(...values, item.id);
424
+ }
425
+ return rowToWorkItem(findItem(database, item.id));
426
+ });
427
+ }
428
+
429
+ export function setPhase(projectRoot, reference, phaseKey, input = {}) {
430
+ return withDatabase(projectRoot, null, (database) => {
431
+ syncWorkItems(projectRoot, database);
432
+ const item = findItem(database, reference);
433
+ if (!item) throw new Error(`Unknown work item: ${reference}`);
434
+ const existing = database.prepare(`
435
+ SELECT status, iteration_count FROM pipeline_phases WHERE work_item_id = ? AND phase_key = ?
436
+ `).get(item.id, phaseKey);
437
+ database.prepare(`
438
+ INSERT INTO pipeline_phases (work_item_id, phase_key, status, started_at, completed_at, artefact_path, iteration_count, notes)
439
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
440
+ ON CONFLICT(work_item_id, phase_key) DO UPDATE SET
441
+ status = excluded.status,
442
+ started_at = COALESCE(excluded.started_at, pipeline_phases.started_at),
443
+ completed_at = COALESCE(excluded.completed_at, pipeline_phases.completed_at),
444
+ artefact_path = CASE WHEN excluded.artefact_path = '' THEN pipeline_phases.artefact_path ELSE excluded.artefact_path END,
445
+ iteration_count = excluded.iteration_count,
446
+ notes = CASE WHEN excluded.notes = '' THEN pipeline_phases.notes ELSE excluded.notes END
447
+ `).run(
448
+ item.id,
449
+ phaseKey,
450
+ input.status ?? existing?.status ?? 'pending',
451
+ input.startedAt ?? null,
452
+ input.completedAt ?? null,
453
+ input.artefactPath ?? '',
454
+ input.iterationCount ?? existing?.iteration_count ?? 0,
455
+ input.notes ?? ''
456
+ );
457
+ database.prepare('UPDATE work_items SET current_phase = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(phaseKey, item.id);
458
+ return database.prepare('SELECT * FROM pipeline_phases WHERE work_item_id = ? AND phase_key = ?').get(item.id, phaseKey);
459
+ });
460
+ }
461
+
462
+ export function addArtefact(projectRoot, reference, input) {
463
+ return withDatabase(projectRoot, null, (database) => {
464
+ syncWorkItems(projectRoot, database);
465
+ const item = findItem(database, reference);
466
+ if (!item) throw new Error(`Unknown work item: ${reference}`);
467
+ database.prepare(`
468
+ INSERT INTO artefacts (work_item_id, kind, path, title, status)
469
+ VALUES (?, ?, ?, ?, ?)
470
+ ON CONFLICT(work_item_id, kind, path) DO UPDATE SET
471
+ title = excluded.title, status = excluded.status
472
+ `).run(item.id, input.kind ?? 'reference', input.path, input.title ?? input.path, input.status ?? 'active');
473
+ return database.prepare('SELECT * FROM artefacts WHERE work_item_id = ? AND kind = ? AND path = ?').get(item.id, input.kind ?? 'reference', input.path);
474
+ });
475
+ }
476
+
477
+ export function listActivityEvents(projectRoot, reference, options = {}) {
478
+ return withDatabase(projectRoot, null, (database) => {
479
+ syncWorkItems(projectRoot, database);
480
+ const item = findItem(database, reference);
481
+ if (!item) throw new Error(`Unknown work item: ${reference}`);
482
+ return database.prepare(`
483
+ SELECT * FROM activity_events WHERE work_item_id = ? ORDER BY created_at DESC, id DESC LIMIT ?
484
+ `).all(item.id, Number(options.limit ?? 100)).map(rowToEvent);
485
+ });
486
+ }
487
+
488
+ export function listActiveSessions(projectRoot, options = {}) {
489
+ return withDatabase(projectRoot, null, (database) => {
490
+ syncWorkItems(projectRoot, database);
491
+ const includeCompleted = options.includeCompleted !== false;
492
+ return database.prepare(`
493
+ SELECT s.*, w.slug, w.title, w.domain,
494
+ (SELECT e.id FROM activity_events e WHERE e.work_item_id = s.work_item_id ORDER BY e.created_at DESC, e.id DESC LIMIT 1) AS latest_event_id,
495
+ (SELECT e.event_type FROM activity_events e WHERE e.work_item_id = s.work_item_id ORDER BY e.created_at DESC, e.id DESC LIMIT 1) AS latest_event_type,
496
+ (SELECT e.summary FROM activity_events e WHERE e.work_item_id = s.work_item_id ORDER BY e.created_at DESC, e.id DESC LIMIT 1) AS latest_event_summary,
497
+ (SELECT e.created_at FROM activity_events e WHERE e.work_item_id = s.work_item_id ORDER BY e.created_at DESC, e.id DESC LIMIT 1) AS latest_event_at,
498
+ (SELECT COUNT(*) FROM activity_events e WHERE e.work_item_id = s.work_item_id) AS event_count
499
+ FROM active_sessions s JOIN work_items w ON w.id = s.work_item_id
500
+ WHERE (? = 1 OR s.status <> 'completed')
501
+ ORDER BY s.updated_at DESC
502
+ `).all(includeCompleted ? 1 : 0).map(rowToSession);
503
+ });
504
+ }
505
+
506
+ function eventStatus(eventType, fallback = 'running') {
507
+ if (eventType === 'question') return 'awaiting-human';
508
+ if (eventType === 'blocked') return 'blocked';
509
+ if (['completed', 'finish'].includes(eventType)) return 'completed';
510
+ if (eventType === 'stale') return 'stale';
511
+ return fallback;
512
+ }
513
+
514
+ function recordActivity(projectRoot, reference, input, mode) {
515
+ return withDatabase(projectRoot, null, (database) => {
516
+ syncWorkItems(projectRoot, database);
517
+ const item = findItem(database, reference);
518
+ if (!item) throw new Error(`Unknown work item: ${reference}`);
519
+ const eventType = mode === 'start' ? 'start' : mode === 'finish' ? (input.status === 'completed' ? 'completed' : 'finish') : input.eventType ?? 'progress';
520
+ const status = mode === 'start' ? 'running' : mode === 'finish' ? input.status ?? 'completed' : input.status ?? eventStatus(eventType);
521
+ const requiresHuman = input.requiresHuman ?? eventType === 'question';
522
+ const endedAt = mode === 'finish' || status === 'completed' ? new Date().toISOString() : null;
523
+ let session;
524
+ let event;
525
+ database.exec('BEGIN IMMEDIATE;');
526
+ try {
527
+ const existingSession = database.prepare('SELECT * FROM active_sessions WHERE work_item_id = ?').get(item.id);
528
+ if (mode === 'start' && existingSession && existingSession.ended_at === null) {
529
+ const requestedOwner = String(input.ownerId ?? '').trim();
530
+ const requestedTool = String(input.tool ?? '').trim();
531
+ const ownerConflict = existingSession.owner_id && requestedOwner && existingSession.owner_id !== requestedOwner;
532
+ const toolConflict = existingSession.tool && requestedTool && existingSession.tool !== requestedTool;
533
+ if (ownerConflict || toolConflict) {
534
+ throw new Error(`Active session is owned by ${existingSession.owner_id || existingSession.tool}; finish or hand off that session before starting another.`);
535
+ }
536
+ }
537
+ if (mode !== 'start' && existingSession && existingSession.ended_at === null) {
538
+ const requestedOwner = String(input.ownerId ?? '').trim();
539
+ if (existingSession.owner_id && requestedOwner && existingSession.owner_id !== requestedOwner) {
540
+ throw new Error(`Active session is owned by ${existingSession.owner_id}; this update requires that owner or an explicit handoff.`);
541
+ }
542
+ }
543
+ database.prepare(`
544
+ INSERT INTO active_sessions (work_item_id, owner_id, tool, phase_key, status, summary, awaiting_human, question, ended_at)
545
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
546
+ ON CONFLICT(work_item_id) DO UPDATE SET
547
+ owner_id = CASE WHEN excluded.owner_id = '' THEN active_sessions.owner_id ELSE excluded.owner_id END,
548
+ tool = CASE WHEN excluded.tool = '' THEN active_sessions.tool ELSE excluded.tool END,
549
+ phase_key = CASE WHEN excluded.phase_key = '' THEN active_sessions.phase_key ELSE excluded.phase_key END,
550
+ status = excluded.status,
551
+ summary = CASE WHEN excluded.summary = '' THEN active_sessions.summary ELSE excluded.summary END,
552
+ awaiting_human = excluded.awaiting_human,
553
+ question = excluded.question,
554
+ started_at = CASE WHEN active_sessions.ended_at IS NOT NULL AND excluded.ended_at IS NULL THEN CURRENT_TIMESTAMP ELSE active_sessions.started_at END,
555
+ updated_at = CURRENT_TIMESTAMP,
556
+ ended_at = excluded.ended_at
557
+ `).run(
558
+ item.id,
559
+ input.ownerId ?? '',
560
+ input.tool ?? '',
561
+ input.phaseKey ?? '',
562
+ status,
563
+ input.summary ?? '',
564
+ requiresHuman ? 1 : 0,
565
+ requiresHuman ? input.question ?? input.summary ?? '' : '',
566
+ endedAt
567
+ );
568
+ session = database.prepare('SELECT * FROM active_sessions WHERE work_item_id = ?').get(item.id);
569
+ event = database.prepare(`
570
+ INSERT INTO activity_events (session_id, work_item_id, event_type, tool, phase_key, summary, details, requires_human, metadata_json)
571
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
572
+ `).run(
573
+ session.id,
574
+ item.id,
575
+ eventType,
576
+ input.tool ?? session.tool,
577
+ input.phaseKey ?? session.phase_key,
578
+ input.summary ?? (mode === 'start' ? 'Started active work.' : mode === 'finish' ? 'Finished active work.' : ''),
579
+ input.details ?? '',
580
+ requiresHuman ? 1 : 0,
581
+ JSON.stringify(input.metadata ?? {})
582
+ );
583
+ database.prepare('UPDATE work_items SET updated_at = CURRENT_TIMESTAMP WHERE id = ?').run(item.id);
584
+ database.exec('COMMIT;');
585
+ } catch (error) {
586
+ database.exec('ROLLBACK;');
587
+ throw error;
588
+ }
589
+ const insertedEvent = database.prepare('SELECT * FROM activity_events WHERE id = ?').get(event.lastInsertRowid);
590
+ const eventCount = database.prepare('SELECT COUNT(*) AS count FROM activity_events WHERE work_item_id = ?').get(item.id).count;
591
+ return {
592
+ session: rowToSession({
593
+ ...session,
594
+ slug: item.slug,
595
+ title: item.title,
596
+ domain: item.domain,
597
+ latest_event_id: insertedEvent.id,
598
+ latest_event_type: insertedEvent.event_type,
599
+ latest_event_summary: insertedEvent.summary,
600
+ latest_event_at: insertedEvent.created_at,
601
+ event_count: eventCount
602
+ }),
603
+ event: rowToEvent(insertedEvent)
604
+ };
605
+ });
606
+ }
607
+
608
+ export function startActiveSession(projectRoot, reference, input = {}) {
609
+ return recordActivity(projectRoot, reference, input, 'start');
610
+ }
611
+
612
+ export function addActivityEvent(projectRoot, reference, input = {}) {
613
+ return recordActivity(projectRoot, reference, input, 'event');
614
+ }
615
+
616
+ export function finishActiveSession(projectRoot, reference, input = {}) {
617
+ return recordActivity(projectRoot, reference, input, 'finish');
618
+ }
619
+
620
+ export function readWorkItemView(projectRoot, reference) {
621
+ const item = listWorkItems(projectRoot).find((candidate) => candidate.id === reference || candidate.slug === reference);
622
+ if (!item) return null;
623
+ return {
624
+ item,
625
+ intent: readRuntimeIntent(projectRoot, item.intentId),
626
+ phases: item.phases,
627
+ artefacts: item.artefacts,
628
+ activity: {
629
+ session: listActiveSessions(projectRoot).find((candidate) => candidate.workItemId === item.id) ?? null,
630
+ events: listActivityEvents(projectRoot, item.id)
631
+ }
632
+ };
633
+ }