@bpmsoftwaresolutions/ai-engine-client 1.1.35 → 1.1.36

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 (3) hide show
  1. package/README.md +3 -1
  2. package/package.json +1 -1
  3. package/src/index.js +167 -1
package/README.md CHANGED
@@ -374,7 +374,7 @@ await client.importImplementationPacket({
374
374
  });
375
375
  ```
376
376
 
377
- The package does not auto-convert nested human planning structures like `tasks` and `subtasks` into durable implementation-item tasks. To create persisted task records after import, use `ensureProjectRoadmapTaskSurface()` or `createImplementationTask()`.
377
+ The package does not auto-convert nested human planning structures like `tasks` and `subtasks` into durable implementation-item tasks. To import a packet and immediately materialize a visible roadmap surface, use `importImplementationPacketAndMaterializeRoadmap()`. If you only need the task records, use `ensureProjectRoadmapTaskSurface()` or `createImplementationTask()`.
378
378
 
379
379
  A copyable example payload is packaged at `examples/implementation-plan-packet.minimal.json`.
380
380
 
@@ -587,6 +587,7 @@ Generated scripts are ephemeral helpers, not source-of-truth. The server keeps S
587
587
  | `getProjectImplementationRoadmapReport(projectId)` | Get the SQL-backed implementation roadmap report payload. |
588
588
  | `downloadProjectImplementationRoadmapReportMarkdown(projectId)` | Download the implementation roadmap markdown report. |
589
589
  | `ensureProjectRoadmapTaskSurface(projectId, { requestedBy, assignedTo, createAcceptanceSubtasks })` | Materialize the active roadmap item's parent task and acceptance subtasks. |
590
+ | `importImplementationPacketAndMaterializeRoadmap(packetPayload, { importedBy, requestedBy, createdBy, workflowId, bindingRole, assignedTo, notes, createAcceptanceSubtasks })` | Import a governed implementation packet, bind it to the project workflow, and materialize the roadmap projection. |
590
591
  | `listProjectOpenTasks(projectId)` | Open tasks for a project. |
591
592
  | `getProjectPerformanceMetrics(projectId, { workflowId, workflowRunId, sinceUtc })` | Performance metrics. |
592
593
 
@@ -618,6 +619,7 @@ Current package version: `1.0.0-beta.13`.
618
619
  | `updateAcceptanceCheckStatus(itemId, checkId, body)` | Update acceptance check. |
619
620
  | `createImplementationPacketGateDecision(packetId, body)` | Record gate decision. |
620
621
  | `bindImplementationPacketToWorkflow(workflowId, body)` | Bind packet to workflow. |
622
+ | `importImplementationPacketAndMaterializeRoadmap(packetPayload, { importedBy, requestedBy, createdBy, workflowId, bindingRole, assignedTo, notes, createAcceptanceSubtasks })` | Import, bind, materialize tasks, and read back roadmap counts in one call. |
621
623
  | `getWorkflowImplementationRoadmap(workflowId)` | Workflow-linked roadmap. |
622
624
  | `getWorkflowResumeContext(workflowId)` | Resume context. |
623
625
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmsoftwaresolutions/ai-engine-client",
3
- "version": "1.1.35",
3
+ "version": "1.1.36",
4
4
  "description": "Thin npm client for the AI Engine operator and retrieval APIs",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const DEFAULT_TIMEOUT_MS = 30000;
2
- export const AI_ENGINE_CLIENT_VERSION = '1.1.35';
2
+ export const AI_ENGINE_CLIENT_VERSION = '1.1.36';
3
3
  export const GOVERNED_MUTATION_REQUIRED_CAPABILITIES = [
4
4
  'executeVerifiedMutation',
5
5
  'post_mutation_verification',
@@ -114,6 +114,32 @@ function cleanText(value) {
114
114
  return text || null;
115
115
  }
116
116
 
117
+ function countRoadmapProjectionLines(markdown) {
118
+ const lines = [];
119
+ let inSummary = false;
120
+ for (const line of String(markdown || '').split(/\r?\n/)) {
121
+ if (line.startsWith('## Roadmap Summary')) {
122
+ inSummary = true;
123
+ continue;
124
+ }
125
+ if (inSummary && line.startsWith('## ')) {
126
+ break;
127
+ }
128
+ if (inSummary && line.startsWith('- ')) {
129
+ lines.push(line);
130
+ }
131
+ }
132
+ const wanted = [
133
+ 'Total Items',
134
+ 'Total Tasks',
135
+ 'Total Subtasks',
136
+ 'Total Acceptance Checks',
137
+ 'Open Acceptance Checks',
138
+ 'Completion Percentage',
139
+ ];
140
+ return lines.filter((line) => wanted.some((label) => line.startsWith(`- ${label}:`)));
141
+ }
142
+
117
143
  function cleanList(value) {
118
144
  if (!Array.isArray(value)) return [];
119
145
  return value.map((item) => cleanText(item)).filter(Boolean);
@@ -1626,6 +1652,100 @@ export class AIEngineClient {
1626
1652
  });
1627
1653
  }
1628
1654
 
1655
+ async importImplementationPacketAndMaterializeRoadmap(packetPayload, {
1656
+ importedBy,
1657
+ requestedBy,
1658
+ createdBy,
1659
+ workflowId,
1660
+ bindingRole = 'active',
1661
+ assignedTo,
1662
+ notes,
1663
+ createAcceptanceSubtasks = true,
1664
+ } = {}) {
1665
+ if (!isPlainObject(packetPayload)) {
1666
+ throw new Error('packetPayload must be a JSON object.');
1667
+ }
1668
+
1669
+ const normalizedImportedBy = cleanText(importedBy) || cleanText(requestedBy) || this.actorId;
1670
+ const normalizedRequestedBy = cleanText(requestedBy) || normalizedImportedBy;
1671
+ const normalizedCreatedBy = cleanText(createdBy) || normalizedRequestedBy;
1672
+
1673
+ const importedPacket = await this.importImplementationPacket({
1674
+ ...packetPayload,
1675
+ imported_by: normalizedImportedBy,
1676
+ });
1677
+ const projectReference = this._resolveImplementationPacketProjectReference(packetPayload);
1678
+ if (!projectReference) {
1679
+ throw new Error('Could not resolve a project reference from the packet payload.');
1680
+ }
1681
+ const projectPayload = await this.getProject(projectReference);
1682
+ const projectSummary = isPlainObject(projectPayload?.summary) ? projectPayload.summary : {};
1683
+ const projectId = cleanText(projectSummary.project_id);
1684
+ if (!projectId) {
1685
+ throw new Error('Project id could not be resolved.');
1686
+ }
1687
+
1688
+ const workflowReference = this._resolveImplementationPacketWorkflowReference(packetPayload, {
1689
+ projectSummary,
1690
+ workflowIdOverride: cleanText(workflowId),
1691
+ });
1692
+ if (!workflowReference) {
1693
+ throw new Error('Could not resolve a workflow reference from the packet payload or project summary.');
1694
+ }
1695
+ const resolvedWorkflowId = await this._resolveImplementationPacketWorkflowId(workflowReference);
1696
+ const workflowSlug = cleanText(projectSummary.workflow_slug);
1697
+ const packetId = cleanText(importedPacket?.packet_id || importedPacket?.packetId || importedPacket?.implementation_packet_key || importedPacket?.implementationPacketKey || packetPayload.packetId || packetPayload.packet_id);
1698
+ const implementationPacketId = cleanText(importedPacket?.implementation_packet_id || importedPacket?.implementationPacketId || packetId);
1699
+
1700
+ const binding = await this.bindImplementationPacketToWorkflow(resolvedWorkflowId, {
1701
+ packet_id: packetId,
1702
+ binding_role: cleanText(bindingRole) || 'active',
1703
+ created_by: normalizedCreatedBy,
1704
+ notes: cleanText(notes),
1705
+ });
1706
+
1707
+ const taskSurface = await this.ensureProjectRoadmapTaskSurface(projectId, {
1708
+ requestedBy: normalizedRequestedBy,
1709
+ assignedTo,
1710
+ createAcceptanceSubtasks,
1711
+ });
1712
+
1713
+ const roadmapReport = await this.getProjectImplementationRoadmapReport(projectId);
1714
+ const roadmapMarkdown = await this.downloadProjectImplementationRoadmapReportMarkdown(projectId);
1715
+ const phases = Array.isArray(packetPayload.phases) ? packetPayload.phases.filter(isPlainObject) : [];
1716
+ const roadmapItems = phases.flatMap((phase) => (
1717
+ Array.isArray(phase.items) ? phase.items.filter(isPlainObject) : []
1718
+ ));
1719
+ const taskRows = Array.isArray(taskSurface?.task_surfaces) ? taskSurface.task_surfaces.filter(isPlainObject) : [];
1720
+ const subtaskRows = taskRows.filter((task) => cleanText(task.parent_task_id) !== null);
1721
+ const acceptanceCheckCount = roadmapItems.reduce(
1722
+ (total, item) => total + (Array.isArray(item.acceptanceChecks) ? item.acceptanceChecks.filter(Boolean).length : 0),
1723
+ 0,
1724
+ );
1725
+
1726
+ return {
1727
+ status: 'materialized',
1728
+ project_id: projectId,
1729
+ project_slug: cleanText(projectSummary.project_slug),
1730
+ workflow_id: resolvedWorkflowId,
1731
+ workflow_slug: workflowSlug,
1732
+ implementation_packet_id: implementationPacketId,
1733
+ implementation_packet_key: packetId,
1734
+ binding,
1735
+ task_surface: taskSurface,
1736
+ roadmap_projection: {
1737
+ report: roadmapReport,
1738
+ markdown: roadmapMarkdown,
1739
+ count_lines: countRoadmapProjectionLines(roadmapMarkdown),
1740
+ phase_count: phases.length,
1741
+ roadmap_item_count: roadmapItems.length,
1742
+ task_count: taskRows.length,
1743
+ subtask_count: subtaskRows.length,
1744
+ acceptance_check_count: acceptanceCheckCount,
1745
+ },
1746
+ };
1747
+ }
1748
+
1629
1749
  async listProjectOpenTasks(projectId) {
1630
1750
  return this._request(`/api/operator/projects/${projectId}/open-tasks`);
1631
1751
  }
@@ -1636,6 +1756,52 @@ export class AIEngineClient {
1636
1756
  });
1637
1757
  }
1638
1758
 
1759
+ _resolveImplementationPacketProjectReference(packetPayload) {
1760
+ return cleanText(packetPayload.projectId)
1761
+ || cleanText(packetPayload.project_id)
1762
+ || cleanText(packetPayload.projectSlug)
1763
+ || cleanText(packetPayload.project_slug)
1764
+ || cleanText(packetPayload?.scope?.projectId)
1765
+ || cleanText(packetPayload?.scope?.project_id)
1766
+ || cleanText(packetPayload?.scope?.projectSlug)
1767
+ || cleanText(packetPayload?.scope?.project_slug)
1768
+ || cleanText(packetPayload?.system?.slug);
1769
+ }
1770
+
1771
+ _resolveImplementationPacketWorkflowReference(packetPayload, { projectSummary, workflowIdOverride } = {}) {
1772
+ if (workflowIdOverride) return workflowIdOverride;
1773
+ return cleanText(packetPayload.workflowId)
1774
+ || cleanText(packetPayload.workflow_id)
1775
+ || cleanText(packetPayload.workflowSlug)
1776
+ || cleanText(packetPayload.workflow_slug)
1777
+ || cleanText(packetPayload?.scope?.workflowId)
1778
+ || cleanText(packetPayload?.scope?.workflow_id)
1779
+ || cleanText(packetPayload?.scope?.workflowSlug)
1780
+ || cleanText(packetPayload?.scope?.workflow_slug)
1781
+ || cleanText(projectSummary?.workflow_id)
1782
+ || cleanText(projectSummary?.workflow_slug);
1783
+ }
1784
+
1785
+ async _resolveImplementationPacketWorkflowId(workflowReference) {
1786
+ const normalized = cleanText(workflowReference);
1787
+ if (!normalized) {
1788
+ throw new Error('workflow reference is required.');
1789
+ }
1790
+ const workflow = await this.getWorkflow(normalized);
1791
+ if (workflow) {
1792
+ return cleanText(workflow.workflow_id) || cleanText(workflow.workflowId) || normalized;
1793
+ }
1794
+ const workflowList = await this.listWorkflows();
1795
+ const workflows = Array.isArray(workflowList) ? workflowList : workflowList?.workflows;
1796
+ const matches = Array.isArray(workflows)
1797
+ ? workflows.filter((item) => cleanText(item?.slug) === normalized)
1798
+ : [];
1799
+ if (matches.length === 1) {
1800
+ return cleanText(matches[0].workflow_id) || cleanText(matches[0].workflowId) || normalized;
1801
+ }
1802
+ throw new Error(`Workflow not found: ${normalized}`);
1803
+ }
1804
+
1639
1805
  // ─── Implementation Tasks ──────────────────────────────────────────────────
1640
1806
 
1641
1807
  async createImplementationTask(implementationItemId, {