@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.
- package/Docs/README.md +50 -0
- package/Docs/adoption/consultancy-and-multi-project-rollout.md +135 -0
- package/Docs/adoption/non-technical-team-guide.md +126 -0
- package/Docs/archaeology-technology-and-hosting-discovery.md +212 -0
- package/Docs/blast-radius-and-impact-routing-guide.md +325 -0
- package/Docs/blueprints/internal-blueprint-catalogue.md +146 -0
- package/Docs/blueprints/maintaining-organisation-blueprints.md +154 -0
- package/Docs/blueprints/validation-and-troubleshooting.md +168 -0
- package/Docs/cli-reference.md +113 -0
- package/Docs/completed-phase-evidence-amendments.md +74 -0
- package/Docs/consultancy-network-rollout-control-plane-guide.md +202 -0
- package/Docs/context-aware-delivery-companion-guide.md +198 -0
- package/Docs/context-aware-delivery-companion-user-guide.md +184 -0
- package/Docs/context-management-and-token-efficiency.md +113 -0
- package/Docs/design-systems/design-system-implementation-guide.md +85 -0
- package/Docs/design-systems/design-system-pack-authoring-guide.md +95 -0
- package/Docs/design-systems/design-system-review-guide.md +51 -0
- package/Docs/design-systems/design-system-user-guide.md +96 -0
- package/Docs/design-systems/product-owner-guide.md +49 -0
- package/Docs/designing-organisation-blueprint-packs.md +384 -0
- package/Docs/developer-delivery-guide.md +224 -0
- package/Docs/error-reporting-guide.md +110 -0
- package/Docs/error-reporting-provider-guide.md +49 -0
- package/Docs/examples/error-report-adapter.md +70 -0
- package/Docs/examples/meeting-review.md +76 -0
- package/Docs/examples/minimal-design-system.md +67 -0
- package/Docs/examples/prototype-review-inputs.md +175 -0
- package/Docs/examples/reproducible-archaeology-depth-example.md +144 -0
- package/Docs/examples/test-scenario-input.md +68 -0
- package/Docs/examples/worked-examples.md +147 -0
- package/Docs/existing-project-onboarding-guide.md +214 -0
- package/Docs/explanation/core-concepts.md +26 -0
- package/Docs/explanation/delivery-workflow.md +48 -0
- package/Docs/governance/governance-team-guide.md +139 -0
- package/Docs/governed-starter-project-materialisation-guide.md +284 -0
- package/Docs/guide-catalogue.md +117 -0
- package/Docs/guided-discovery-facilitator-guide.md +172 -0
- package/Docs/guided-intent-workspace-guide.md +109 -0
- package/Docs/guided-phase-evidence-drafting-guide.md +119 -0
- package/Docs/human-approval-and-assurance-guide.md +146 -0
- package/Docs/knowledge-proposals-implementer-guide.md +106 -0
- package/Docs/knowledge-proposals-user-guide.md +247 -0
- package/Docs/maintainers/context-benchmarks.md +29 -0
- package/Docs/maintainers/contributing.md +58 -0
- package/Docs/maintainers/evidence-depth-acceptance.md +72 -0
- package/Docs/maintainers/verification-walkthroughs.md +104 -0
- package/Docs/meeting-evidence-implementer-guide.md +132 -0
- package/Docs/meeting-evidence-user-guide.md +200 -0
- package/Docs/operations/dashboard-and-delivery-state.md +153 -0
- package/Docs/operations/dashboard-configuration.md +87 -0
- package/Docs/operations/installation-updating-and-entitlements.md +135 -0
- package/Docs/operations/premium-personas-setup.md +76 -0
- package/Docs/operations/troubleshooting-and-recovery.md +205 -0
- package/Docs/organisation-rollout-guide.md +142 -0
- package/Docs/persona-entitlement-provider-guide.md +199 -0
- package/Docs/persona-guided-prototype-iteration.md +129 -0
- package/Docs/personas/organisation-specific-personas.md +103 -0
- package/Docs/personas/persona-authoring-cookbook.md +176 -0
- package/Docs/personas/persona-engagement-ui.md +133 -0
- package/Docs/personas/persona-governance.md +118 -0
- package/Docs/platform-export-analysis-guide.md +336 -0
- package/Docs/policies/governance-owner-guide.md +36 -0
- package/Docs/policies/implementation-guide.md +42 -0
- package/Docs/policies/organisation-policy-design-gates.md +58 -0
- package/Docs/policies/policy-pack-authoring-guide.md +108 -0
- package/Docs/policies/product-owner-guide.md +43 -0
- package/Docs/policies/technical-owner-guide.md +37 -0
- package/Docs/product-owner-guide.md +327 -0
- package/Docs/project-portfolio-orchestration-guide.md +199 -0
- package/Docs/quality/manual-qa-and-acceptance.md +162 -0
- package/Docs/quality/persona-driven-test-scenarios.md +172 -0
- package/Docs/quality/reproducible-archaeology-depth-review-checklist.md +89 -0
- package/Docs/reference/capabilities-and-project-layout.md +678 -0
- package/Docs/reference/cli-and-configuration.md +398 -0
- package/Docs/reference/contributions-api.md +23 -0
- package/Docs/reference/security-adapter-authoring.md +81 -0
- package/Docs/reference/starter-adapter-authoring.md +74 -0
- package/Docs/repository-source-map-guide.md +381 -0
- package/Docs/reproducible-archaeology-and-discovery-depth.md +292 -0
- package/Docs/screen-prototype-creation-guide.md +324 -0
- package/Docs/security-validation-guide.md +353 -0
- package/Docs/solution-readiness-review-guide.md +123 -0
- package/Docs/standards/project-standards-authoring.md +157 -0
- package/Docs/team-hub-guide.md +162 -0
- package/Docs/team-hub-resource-registry-guide.md +167 -0
- package/Docs/tutorials/first-delivery.md +83 -0
- package/Docs/tutorials/first-session.md +62 -0
- package/Docs/using-lifecycle-hooks.md +381 -0
- package/Docs/working-with-personas.md +274 -0
- package/LICENSE +165 -0
- package/README.md +96 -0
- package/agents-src/claude/ewai-security-reviewer.md +15 -0
- package/bin/ewai +5 -0
- package/config/archaeology-record-families.yaml +59 -0
- package/config/delivery-artifacts.yaml +121 -0
- package/config/delivery-stages.yaml +77 -0
- package/config/design-system.schema.json +46 -0
- package/config/error-reporting.schema.json +79 -0
- package/config/evidence-depth.schema.json +53 -0
- package/config/intent.schema.json +90 -0
- package/config/knowledge-proposals-proposal.schema.json +34 -0
- package/config/lifecycle-event.schema.json +68 -0
- package/config/lifecycle-handler.schema.json +45 -0
- package/config/lifecycle-hook-ack.schema.json +19 -0
- package/config/meeting-evidence-candidate.schema.json +102 -0
- package/config/organisation-policy.schema.json +137 -0
- package/config/pack.schema.json +250 -0
- package/config/persona-pack.schema.json +21 -0
- package/config/persona.schema.json +17 -0
- package/config/policy-evaluation.schema.json +77 -0
- package/config/policy-facts.schema.json +140 -0
- package/config/portfolio.schema.json +68 -0
- package/config/project.schema.json +313 -0
- package/config/prototype-iteration.schema.json +128 -0
- package/config/rollout.schema.json +87 -0
- package/config/security-adapter.schema.json +31 -0
- package/config/security-scan-request.schema.json +64 -0
- package/config/security-scan-response.schema.json +52 -0
- package/config/security-validation-policy.schema.json +74 -0
- package/config/starter-source-acknowledgement.schema.json +13 -0
- package/config/starter-source-adapter.schema.json +38 -0
- package/config/starter-source-request.schema.json +61 -0
- package/package.json +77 -0
- package/packs/core/pack.yaml +7 -0
- package/packs/design-systems/default/experience-promise.md +9 -0
- package/packs/design-systems/default/intentional-review.md +10 -0
- package/packs/design-systems/default/interaction-and-entry.md +9 -0
- package/packs/design-systems/default/meaningful-content-and-states.md +9 -0
- package/packs/design-systems/default/pack.yaml +44 -0
- package/packs/design-systems/default/principles.md +10 -0
- package/packs/personas/core/pack.yaml +7 -0
- package/packs/personas/core/personas/archaeologist.md +37 -0
- package/packs/personas/core/personas/end-user.md +17 -0
- package/packs/personas/core/personas/maintainer.md +17 -0
- package/packs/personas/core/personas/operator.md +17 -0
- package/packs/personas/core/personas/specs-knowledge-curator.md +35 -0
- package/packs/technologies/laravel/pack.yaml +30 -0
- package/packs/technologies/laravel-nuxt/pack.yaml +30 -0
- package/packs/technologies/nuxt/pack.yaml +30 -0
- package/packs/technologies/power-platform/pack.yaml +31 -0
- package/packs/technologies/salesforce/pack.yaml +25 -0
- package/public/app.js +4896 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/assets/backstory-icon.png +0 -0
- package/public/dashboard-navigation.js +98 -0
- package/public/favicon-16.png +0 -0
- package/public/favicon-32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/index.html +789 -0
- package/public/styles.css +2693 -0
- package/public/team-hub/app.js +202 -0
- package/public/team-hub/index.html +79 -0
- package/public/team-hub/styles.css +90 -0
- package/scripts/publication-check.mjs +140 -0
- package/scripts/setup.mjs +21 -0
- package/skills-src/ewai-archaeology/SKILL.md +334 -0
- package/skills-src/ewai-archaeology/agents/openai.yaml +4 -0
- package/skills-src/ewai-archaeology/references/archaeology-contract.md +201 -0
- package/skills-src/ewai-archaeology/references/lifecycle-reconstruction.md +177 -0
- package/skills-src/ewai-archaeology/references/maximum-detail-reconstruction.md +97 -0
- package/skills-src/ewai-archaeology/references/model-routing.md +26 -0
- package/skills-src/ewai-architecture/SKILL.md +108 -0
- package/skills-src/ewai-architecture/agents/openai.yaml +4 -0
- package/skills-src/ewai-architecture/references/architecture-contract.md +176 -0
- package/skills-src/ewai-context/SKILL.md +68 -0
- package/skills-src/ewai-context/agents/openai.yaml +4 -0
- package/skills-src/ewai-context-import/SKILL.md +118 -0
- package/skills-src/ewai-context-import/agents/openai.yaml +4 -0
- package/skills-src/ewai-context-import/references/context-import-contract.md +106 -0
- package/skills-src/ewai-dashboard-configuration/SKILL.md +20 -0
- package/skills-src/ewai-deliver/SKILL.md +122 -0
- package/skills-src/ewai-deliver/references/delivery-evidence.md +92 -0
- package/skills-src/ewai-deliver/references/phase-routing.md +31 -0
- package/skills-src/ewai-design-system-apply/SKILL.md +27 -0
- package/skills-src/ewai-design-system-apply/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-apply/references/application-contract.md +36 -0
- package/skills-src/ewai-design-system-author/SKILL.md +28 -0
- package/skills-src/ewai-design-system-author/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-author/references/authoring-contract.md +38 -0
- package/skills-src/ewai-design-system-review/SKILL.md +26 -0
- package/skills-src/ewai-design-system-review/agents/openai.yaml +4 -0
- package/skills-src/ewai-design-system-review/references/review-contract.md +40 -0
- package/skills-src/ewai-error-reporting/SKILL.md +46 -0
- package/skills-src/ewai-error-reporting/agents/openai.yaml +4 -0
- package/skills-src/ewai-error-reporting/references/provider-contract.md +74 -0
- package/skills-src/ewai-evidence-depth/SKILL.md +72 -0
- package/skills-src/ewai-evidence-depth/agents/openai.yaml +4 -0
- package/skills-src/ewai-evidence-depth/references/evidence-depth-contract.md +127 -0
- package/skills-src/ewai-intent/SKILL.md +68 -0
- package/skills-src/ewai-intent/agents/openai.yaml +4 -0
- package/skills-src/ewai-intent/references/intent-contract.md +42 -0
- package/skills-src/ewai-knowledge-proposals/SKILL.md +105 -0
- package/skills-src/ewai-knowledge-proposals/agents/openai.yaml +4 -0
- package/skills-src/ewai-knowledge-proposals/references/proposal-contract.md +59 -0
- package/skills-src/ewai-meeting-evidence/SKILL.md +106 -0
- package/skills-src/ewai-meeting-evidence/agents/openai.yaml +4 -0
- package/skills-src/ewai-meeting-evidence/references/candidate-contract.md +64 -0
- package/skills-src/ewai-organisation-policy/SKILL.md +62 -0
- package/skills-src/ewai-organisation-policy/agents/openai.yaml +4 -0
- package/skills-src/ewai-organisation-policy/references/policy-contract.md +94 -0
- package/skills-src/ewai-palace-housekeeping/SKILL.md +55 -0
- package/skills-src/ewai-palace-housekeeping/agents/openai.yaml +4 -0
- package/skills-src/ewai-persona-entitlement/SKILL.md +60 -0
- package/skills-src/ewai-persona-entitlement/agents/openai.yaml +4 -0
- package/skills-src/ewai-phase-evidence/SKILL.md +79 -0
- package/skills-src/ewai-phase-evidence/agents/openai.yaml +4 -0
- package/skills-src/ewai-pipeline/SKILL.md +130 -0
- package/skills-src/ewai-pipeline/agents/openai.yaml +4 -0
- package/skills-src/ewai-pipeline/references/cli.md +86 -0
- package/skills-src/ewai-pipeline/references/specs-contract.md +16 -0
- package/skills-src/ewai-portfolio/SKILL.md +70 -0
- package/skills-src/ewai-portfolio/agents/openai.yaml +4 -0
- package/skills-src/ewai-portfolio/references/portfolio-contract.md +67 -0
- package/skills-src/ewai-project-discovery/SKILL.md +95 -0
- package/skills-src/ewai-project-discovery/agents/openai.yaml +4 -0
- package/skills-src/ewai-project-discovery/references/discovery-contract.md +34 -0
- package/skills-src/ewai-prototype-iteration/SKILL.md +30 -0
- package/skills-src/ewai-prototype-iteration/agents/openai.yaml +4 -0
- package/skills-src/ewai-prototype-iteration/references/review-contract.md +49 -0
- package/skills-src/ewai-retro/SKILL.md +48 -0
- package/skills-src/ewai-retro/agents/openai.yaml +4 -0
- package/skills-src/ewai-retro/references/asset-routing.md +14 -0
- package/skills-src/ewai-rollout/SKILL.md +74 -0
- package/skills-src/ewai-rollout/agents/openai.yaml +4 -0
- package/skills-src/ewai-rollout/references/rollout-contract.md +74 -0
- package/skills-src/ewai-shape-intents/SKILL.md +84 -0
- package/skills-src/ewai-shape-intents/agents/openai.yaml +4 -0
- package/skills-src/ewai-shape-intents/references/intent-mapping-contract.md +109 -0
- package/skills-src/ewai-solution-readiness/SKILL.md +55 -0
- package/skills-src/ewai-solution-readiness/agents/openai.yaml +4 -0
- package/skills-src/ewai-standards-check/SKILL.md +93 -0
- package/skills-src/ewai-standards-check/agents/openai.yaml +4 -0
- package/skills-src/ewai-standards-check/references/report-contract.md +116 -0
- package/skills-src/ewai-test-scenarios/SKILL.md +94 -0
- package/skills-src/ewai-test-scenarios/agents/openai.yaml +4 -0
- package/skills-src/ewai-test-scenarios/references/scenario-contract.md +88 -0
- package/src/afk-worker.mjs +16 -0
- package/src/archaeology.mjs +1333 -0
- package/src/checkin.mjs +261 -0
- package/src/cli.mjs +2427 -0
- package/src/companion-guidance.mjs +257 -0
- package/src/companion-opening.mjs +62 -0
- package/src/companion.mjs +256 -0
- package/src/context.mjs +210 -0
- package/src/dashboard-preferences.mjs +80 -0
- package/src/delivery-artifacts.mjs +204 -0
- package/src/delivery-documents.mjs +248 -0
- package/src/delivery-gates.mjs +317 -0
- package/src/delivery.mjs +1433 -0
- package/src/design-system-application.mjs +291 -0
- package/src/design-system-authoring.mjs +101 -0
- package/src/design-systems.mjs +466 -0
- package/src/discovery.mjs +1314 -0
- package/src/error-reporting.mjs +323 -0
- package/src/evidence-depth.mjs +543 -0
- package/src/execution-state.mjs +243 -0
- package/src/install.mjs +166 -0
- package/src/intent-dependencies.mjs +117 -0
- package/src/intent-maps.mjs +402 -0
- package/src/intents.mjs +747 -0
- package/src/knowledge-proposals.mjs +717 -0
- package/src/launcher.mjs +51 -0
- package/src/meeting-evidence.mjs +703 -0
- package/src/network-rollout.mjs +386 -0
- package/src/organisation-blueprints.mjs +438 -0
- package/src/organisation-policies.mjs +448 -0
- package/src/packs.mjs +44 -0
- package/src/paths.mjs +62 -0
- package/src/persona-entitlements.mjs +438 -0
- package/src/persona-licence-config.mjs +98 -0
- package/src/persona-website-provider.mjs +134 -0
- package/src/persona-zip.mjs +87 -0
- package/src/personas.mjs +159 -0
- package/src/platform-metadata-analysis.mjs +314 -0
- package/src/policy-design-gates.mjs +623 -0
- package/src/policy-gate-integration.mjs +318 -0
- package/src/portfolio.mjs +509 -0
- package/src/power-platform-source-map.mjs +190 -0
- package/src/project.mjs +449 -0
- package/src/prototype-iterations.mjs +730 -0
- package/src/repository-source-map.mjs +603 -0
- package/src/runtime/afk-conductor.mjs +973 -0
- package/src/runtime/context-assembly.mjs +457 -0
- package/src/runtime/context-benchmarks.mjs +115 -0
- package/src/runtime/dashboard-actions.mjs +109 -0
- package/src/runtime/dashboard-handoffs.mjs +149 -0
- package/src/runtime/dashboard-server.mjs +1272 -0
- package/src/runtime/dashboard.mjs +197 -0
- package/src/runtime/database.mjs +789 -0
- package/src/runtime/error-reporting.mjs +581 -0
- package/src/runtime/evidence-depth-workspace.mjs +412 -0
- package/src/runtime/execution-leases.mjs +299 -0
- package/src/runtime/guided-discovery.mjs +350 -0
- package/src/runtime/guided-intents.mjs +517 -0
- package/src/runtime/impact-analysis.mjs +535 -0
- package/src/runtime/intents.mjs +222 -0
- package/src/runtime/knowledge.mjs +86 -0
- package/src/runtime/lifecycle-hooks.mjs +1239 -0
- package/src/runtime/mcp-config.mjs +110 -0
- package/src/runtime/mcp-server.mjs +1885 -0
- package/src/runtime/palace.mjs +362 -0
- package/src/runtime/paths.mjs +58 -0
- package/src/runtime/persona-engagement.mjs +255 -0
- package/src/runtime/phase-contributions.mjs +594 -0
- package/src/runtime/policy-workspace.mjs +170 -0
- package/src/runtime/prototype-iterations.mjs +235 -0
- package/src/runtime/provider-adapters.mjs +163 -0
- package/src/runtime/repository-index.mjs +838 -0
- package/src/runtime/runs.mjs +185 -0
- package/src/runtime/security-validation.mjs +1230 -0
- package/src/runtime/starter-materialisation.mjs +1155 -0
- package/src/runtime/team-hub-client.mjs +479 -0
- package/src/runtime/team-hub-database.mjs +288 -0
- package/src/runtime/team-hub-server.mjs +191 -0
- package/src/runtime/team-hub.mjs +110 -0
- package/src/runtime/tree-sitter-index.mjs +390 -0
- package/src/runtime/version.mjs +1 -0
- package/src/runtime/work.mjs +633 -0
- package/src/salesforce-source-map.mjs +212 -0
- package/src/security-validation-config.mjs +224 -0
- package/src/solution-readiness.mjs +620 -0
- package/src/starter-materialisation-contract.mjs +407 -0
- package/src/task-graph.mjs +544 -0
- package/src/team-hub-resources.mjs +239 -0
- package/src/team-hub.mjs +242 -0
- package/src/test-scenarios.mjs +622 -0
- package/src/validation-config.mjs +289 -0
- package/templates/SPECS/1.Scope/personas/registry.yaml +12 -0
- package/templates/SPECS/5.Strategy/patterns/context-packet.md +119 -0
- package/templates/SPECS/6.Build/_tracker-template.md +16 -0
- package/templates/SPECS/pipeline.yaml +62 -0
- package/templates/discovery-answers.yaml +86 -0
- package/templates/intent-body.md +21 -0
- 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
|
+
}
|