@bpmsoftwaresolutions/ai-engine-client 1.1.87 → 1.1.89
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/package.json +1 -1
- package/src/client.js +192 -4903
- package/src/domains/charters.js +5 -0
- package/src/domains/claims.js +58 -0
- package/src/domains/commit-governance.js +68 -0
- package/src/domains/context-orientation.js +48 -0
- package/src/domains/context-sessions.js +46 -0
- package/src/domains/execution-eligibility.js +44 -0
- package/src/domains/governed-implementation.js +7 -0
- package/src/domains/implementation-artifacts.js +61 -0
- package/src/domains/implementation-checks.js +41 -0
- package/src/domains/implementation-items.js +82 -89
- package/src/domains/implementation-packets.js +173 -0
- package/src/domains/implementation-tasks.js +125 -6
- package/src/domains/portfolio.js +125 -1
- package/src/domains/project-chartering.js +18 -0
- package/src/domains/project-reports.js +14 -0
- package/src/domains/project-resume.js +18 -0
- package/src/domains/projects.js +348 -24
- package/src/domains/roadmap-reports.js +6 -0
- package/src/domains/session-governance.js +64 -0
- package/src/domains/tool-binding-approvals.js +102 -0
- package/src/domains/verified-mutations.js +7 -0
- package/src/domains/work-start.js +192 -0
- package/src/domains/workflow-composition.js +3416 -7
- package/src/domains/workflow-turns.js +6 -0
- package/src/index.js +1 -1
- package/src/utils/task-binding.js +31 -0
- package/src/utils/verified-mutations.js +128 -0
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
import { cleanList, cleanText, isPlainObject } from '../utils/text.js';
|
|
2
|
+
|
|
3
|
+
function looksLikeUuid(value) {
|
|
4
|
+
const text = cleanText(value);
|
|
5
|
+
return Boolean(text && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(text));
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function countRoadmapProjectionLines(markdown) {
|
|
9
|
+
const lines = [];
|
|
10
|
+
let inSummary = false;
|
|
11
|
+
for (const line of String(markdown || '').split(/\r?\n/)) {
|
|
12
|
+
if (line.startsWith('## Roadmap Summary')) {
|
|
13
|
+
inSummary = true;
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
if (inSummary && line.startsWith('## ')) {
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
if (inSummary && line.startsWith('- ')) {
|
|
20
|
+
lines.push(line);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const wanted = [
|
|
24
|
+
'Total Items',
|
|
25
|
+
'Total Tasks',
|
|
26
|
+
'Total Subtasks',
|
|
27
|
+
'Total Acceptance Checks',
|
|
28
|
+
'Open Acceptance Checks',
|
|
29
|
+
'Completion Percentage',
|
|
30
|
+
];
|
|
31
|
+
return lines.filter((line) => wanted.some((label) => line.startsWith(`- ${label}:`)));
|
|
32
|
+
}
|
|
33
|
+
|
|
1
34
|
export function createImplementationPacketsDomain(client) {
|
|
2
35
|
return {
|
|
3
36
|
importImplementationPacket: (body) => client._request('/api/governed-implementation/packets/import', { method: 'POST', body }),
|
|
@@ -11,5 +44,145 @@ export function createImplementationPacketsDomain(client) {
|
|
|
11
44
|
}),
|
|
12
45
|
getWorkflowImplementationRoadmap: (workflowId) => client._request(`/api/governed-implementation/workflows/${workflowId}/roadmap`),
|
|
13
46
|
getWorkflowResumeContext: (workflowId) => client._request(`/api/governed-implementation/workflows/${workflowId}/resume-context`),
|
|
47
|
+
importImplementationPacketAndMaterializeRoadmap: async (packetPayload, {
|
|
48
|
+
importedBy,
|
|
49
|
+
requestedBy,
|
|
50
|
+
createdBy,
|
|
51
|
+
workflowId,
|
|
52
|
+
bindingRole = 'active',
|
|
53
|
+
assignedTo,
|
|
54
|
+
notes,
|
|
55
|
+
createAcceptanceSubtasks = true,
|
|
56
|
+
} = {}) => {
|
|
57
|
+
if (!isPlainObject(packetPayload)) {
|
|
58
|
+
throw new Error('packetPayload must be a JSON object.');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const normalizedImportedBy = cleanText(importedBy) || cleanText(requestedBy) || client.actorId;
|
|
62
|
+
const normalizedRequestedBy = cleanText(requestedBy) || normalizedImportedBy;
|
|
63
|
+
const normalizedCreatedBy = cleanText(createdBy) || normalizedRequestedBy;
|
|
64
|
+
|
|
65
|
+
const importedPacket = await client.importImplementationPacket({
|
|
66
|
+
...packetPayload,
|
|
67
|
+
imported_by: normalizedImportedBy,
|
|
68
|
+
});
|
|
69
|
+
const packetId = cleanText(importedPacket?.packet_id || importedPacket?.packetId || importedPacket?.implementation_packet_key || importedPacket?.implementationPacketKey || packetPayload.packetId || packetPayload.packet_id);
|
|
70
|
+
const implementationPacketId = cleanText(importedPacket?.implementation_packet_id || importedPacket?.implementationPacketId);
|
|
71
|
+
if (!implementationPacketId) {
|
|
72
|
+
throw new Error('Imported implementation packet response must include implementation_packet_id.');
|
|
73
|
+
}
|
|
74
|
+
if (!looksLikeUuid(implementationPacketId)) {
|
|
75
|
+
throw new Error('Imported implementation packet response returned a non-UUID implementation_packet_id.');
|
|
76
|
+
}
|
|
77
|
+
const projectReference = client._resolveImplementationPacketProjectReference(packetPayload);
|
|
78
|
+
if (!projectReference) {
|
|
79
|
+
throw new Error('Could not resolve a project reference from the packet payload.');
|
|
80
|
+
}
|
|
81
|
+
const projectPayload = await client.getProject(projectReference);
|
|
82
|
+
const projectSummary = isPlainObject(projectPayload?.summary) ? projectPayload.summary : {};
|
|
83
|
+
const projectId = cleanText(projectSummary.project_id);
|
|
84
|
+
if (!projectId) {
|
|
85
|
+
throw new Error('Project id could not be resolved.');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const workflowReference = client._resolveImplementationPacketWorkflowReference(packetPayload, {
|
|
89
|
+
projectSummary,
|
|
90
|
+
workflowIdOverride: cleanText(workflowId),
|
|
91
|
+
});
|
|
92
|
+
if (!workflowReference) {
|
|
93
|
+
throw new Error('Could not resolve a workflow reference from the packet payload or project summary.');
|
|
94
|
+
}
|
|
95
|
+
const resolvedWorkflowId = await client._resolveImplementationPacketWorkflowId(workflowReference);
|
|
96
|
+
const workflowSlug = cleanText(projectSummary.workflow_slug);
|
|
97
|
+
|
|
98
|
+
const binding = await client.bindImplementationPacketToWorkflow(resolvedWorkflowId, {
|
|
99
|
+
packet_id: packetId,
|
|
100
|
+
binding_role: cleanText(bindingRole) || 'active',
|
|
101
|
+
created_by: normalizedCreatedBy,
|
|
102
|
+
notes: cleanText(notes),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const taskSurface = await client.ensureProjectRoadmapTaskSurface(projectId, {
|
|
106
|
+
requestedBy: normalizedRequestedBy,
|
|
107
|
+
assignedTo,
|
|
108
|
+
createAcceptanceSubtasks,
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const roadmapReport = await client.getProjectImplementationRoadmapReport(projectId);
|
|
112
|
+
const roadmapMarkdown = await client.downloadProjectImplementationRoadmapReportMarkdown(projectId);
|
|
113
|
+
const phases = Array.isArray(packetPayload.phases) ? packetPayload.phases.filter(isPlainObject) : [];
|
|
114
|
+
const roadmapItems = phases.flatMap((phase) => (
|
|
115
|
+
Array.isArray(phase.items) ? phase.items.filter(isPlainObject) : []
|
|
116
|
+
));
|
|
117
|
+
const taskRows = Array.isArray(taskSurface?.task_surfaces) ? taskSurface.task_surfaces.filter(isPlainObject) : [];
|
|
118
|
+
const subtaskRows = taskRows.filter((task) => cleanText(task.parent_task_id) !== null);
|
|
119
|
+
const acceptanceCheckCount = roadmapItems.reduce(
|
|
120
|
+
(total, item) => total + (Array.isArray(item.acceptanceChecks) ? item.acceptanceChecks.filter(Boolean).length : 0),
|
|
121
|
+
0,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
status: 'materialized',
|
|
126
|
+
project_id: projectId,
|
|
127
|
+
project_slug: cleanText(projectSummary.project_slug),
|
|
128
|
+
workflow_id: resolvedWorkflowId,
|
|
129
|
+
workflow_slug: workflowSlug,
|
|
130
|
+
implementation_packet_id: implementationPacketId,
|
|
131
|
+
implementation_packet_key: packetId,
|
|
132
|
+
binding,
|
|
133
|
+
task_surface: taskSurface,
|
|
134
|
+
roadmap_projection: {
|
|
135
|
+
report: roadmapReport,
|
|
136
|
+
markdown: roadmapMarkdown,
|
|
137
|
+
count_lines: countRoadmapProjectionLines(roadmapMarkdown),
|
|
138
|
+
phase_count: phases.length,
|
|
139
|
+
roadmap_item_count: roadmapItems.length,
|
|
140
|
+
task_count: taskRows.length,
|
|
141
|
+
subtask_count: subtaskRows.length,
|
|
142
|
+
acceptance_check_count: acceptanceCheckCount,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
},
|
|
146
|
+
_resolveImplementationPacketProjectReference: (packetPayload) => cleanText(packetPayload.projectId)
|
|
147
|
+
|| cleanText(packetPayload.project_id)
|
|
148
|
+
|| cleanText(packetPayload.projectSlug)
|
|
149
|
+
|| cleanText(packetPayload.project_slug)
|
|
150
|
+
|| cleanText(packetPayload?.scope?.projectId)
|
|
151
|
+
|| cleanText(packetPayload?.scope?.project_id)
|
|
152
|
+
|| cleanText(packetPayload?.scope?.projectSlug)
|
|
153
|
+
|| cleanText(packetPayload?.scope?.project_slug)
|
|
154
|
+
|| cleanText(packetPayload?.system?.slug),
|
|
155
|
+
_resolveImplementationPacketWorkflowReference: (packetPayload, { projectSummary, workflowIdOverride } = {}) => {
|
|
156
|
+
if (workflowIdOverride) return workflowIdOverride;
|
|
157
|
+
return cleanText(packetPayload.workflowId)
|
|
158
|
+
|| cleanText(packetPayload.workflow_id)
|
|
159
|
+
|| cleanText(packetPayload.workflowSlug)
|
|
160
|
+
|| cleanText(packetPayload.workflow_slug)
|
|
161
|
+
|| cleanText(packetPayload?.scope?.workflowId)
|
|
162
|
+
|| cleanText(packetPayload?.scope?.workflow_id)
|
|
163
|
+
|| cleanText(packetPayload?.scope?.workflowSlug)
|
|
164
|
+
|| cleanText(packetPayload?.scope?.workflow_slug)
|
|
165
|
+
|| cleanText(projectSummary?.workflow_id)
|
|
166
|
+
|| cleanText(projectSummary?.workflow_slug);
|
|
167
|
+
},
|
|
168
|
+
_resolveImplementationPacketWorkflowId: async (workflowReference) => {
|
|
169
|
+
const normalized = cleanText(workflowReference);
|
|
170
|
+
if (!normalized) {
|
|
171
|
+
throw new Error('workflow reference is required.');
|
|
172
|
+
}
|
|
173
|
+
const workflow = await client.getWorkflow(normalized);
|
|
174
|
+
if (workflow) {
|
|
175
|
+
return cleanText(workflow.workflow_id) || cleanText(workflow.workflowId) || normalized;
|
|
176
|
+
}
|
|
177
|
+
const workflowList = await client.listWorkflows();
|
|
178
|
+
const workflows = Array.isArray(workflowList) ? workflowList : workflowList?.workflows;
|
|
179
|
+
const matches = Array.isArray(workflows)
|
|
180
|
+
? workflows.filter((item) => cleanText(item?.slug) === normalized)
|
|
181
|
+
: [];
|
|
182
|
+
if (matches.length === 1) {
|
|
183
|
+
return cleanText(matches[0].workflow_id) || cleanText(matches[0].workflowId) || normalized;
|
|
184
|
+
}
|
|
185
|
+
throw new Error(`Workflow not found: ${normalized}`);
|
|
186
|
+
},
|
|
14
187
|
};
|
|
15
188
|
}
|
|
@@ -1,10 +1,129 @@
|
|
|
1
1
|
export function createImplementationTasksDomain(client) {
|
|
2
2
|
return {
|
|
3
|
-
createImplementationTask: (implementationItemId, request) =>
|
|
4
|
-
listImplementationTasks: (implementationItemId) =>
|
|
5
|
-
listImplementationSubtasks: (taskId) =>
|
|
6
|
-
updateImplementationTask: (taskId, updates) =>
|
|
7
|
-
assignImplementationTask: (taskId, request) =>
|
|
8
|
-
completeImplementationTask: (taskId, request) =>
|
|
3
|
+
createImplementationTask: (implementationItemId, request) => createImplementationTask(client, implementationItemId, request),
|
|
4
|
+
listImplementationTasks: (implementationItemId) => listImplementationTasks(client, implementationItemId),
|
|
5
|
+
listImplementationSubtasks: (taskId) => listImplementationSubtasks(client, taskId),
|
|
6
|
+
updateImplementationTask: (taskId, updates) => updateImplementationTask(client, taskId, updates),
|
|
7
|
+
assignImplementationTask: (taskId, request) => assignImplementationTask(client, taskId, request),
|
|
8
|
+
completeImplementationTask: (taskId, request) => completeImplementationTask(client, taskId, request),
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
|
+
|
|
12
|
+
export async function createImplementationTask(client, implementationItemId, {
|
|
13
|
+
title,
|
|
14
|
+
implementationPacketId,
|
|
15
|
+
description,
|
|
16
|
+
priority,
|
|
17
|
+
assignedTo,
|
|
18
|
+
assignedBy,
|
|
19
|
+
dueAt,
|
|
20
|
+
sortOrder,
|
|
21
|
+
notes,
|
|
22
|
+
createdBy,
|
|
23
|
+
parentTaskId,
|
|
24
|
+
claimId,
|
|
25
|
+
claimProjectId,
|
|
26
|
+
projectId,
|
|
27
|
+
claimedItemId,
|
|
28
|
+
workflowRunId,
|
|
29
|
+
agentSessionId,
|
|
30
|
+
allowedMutationSurfaces,
|
|
31
|
+
acknowledgedReminders,
|
|
32
|
+
executionIntent,
|
|
33
|
+
executionType,
|
|
34
|
+
executionPurpose,
|
|
35
|
+
} = {}) {
|
|
36
|
+
const normalizedClaimId = String(claimId || '').trim() || null;
|
|
37
|
+
const normalizedClaimProjectId = String(claimProjectId || projectId || '').trim() || null;
|
|
38
|
+
const normalizedClaimedItemId = String(claimedItemId || implementationItemId || '').trim() || null;
|
|
39
|
+
const normalizedWorkflowRunId = String(workflowRunId || '').trim() || null;
|
|
40
|
+
const normalizedAgentSessionId = String(agentSessionId || '').trim() || null;
|
|
41
|
+
const normalizedAllowedMutationSurfaces = Array.isArray(allowedMutationSurfaces) && allowedMutationSurfaces.length > 0
|
|
42
|
+
? allowedMutationSurfaces
|
|
43
|
+
: ['project_roadmap_task'];
|
|
44
|
+
const normalizedAcknowledgedReminders = Array.isArray(acknowledgedReminders)
|
|
45
|
+
? acknowledgedReminders
|
|
46
|
+
: undefined;
|
|
47
|
+
const normalizedExecutionIntent = (() => {
|
|
48
|
+
if (executionIntent && typeof executionIntent === 'object') {
|
|
49
|
+
const execution_type = String(executionIntent.execution_type || executionIntent.executionType || '').trim();
|
|
50
|
+
const execution_purpose = String(executionIntent.execution_purpose || executionIntent.executionPurpose || '').trim();
|
|
51
|
+
if (execution_type || execution_purpose) {
|
|
52
|
+
return {
|
|
53
|
+
...(execution_type ? { execution_type } : {}),
|
|
54
|
+
...(execution_purpose ? { execution_purpose } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const normalizedExecutionType = String(executionType || '').trim() || 'implementation_task';
|
|
59
|
+
const normalizedExecutionPurpose = String(executionPurpose || title || '').trim() || 'implementation task';
|
|
60
|
+
return {
|
|
61
|
+
execution_type: normalizedExecutionType,
|
|
62
|
+
execution_purpose: normalizedExecutionPurpose,
|
|
63
|
+
};
|
|
64
|
+
})();
|
|
65
|
+
await client._assertExecutionEligibility({
|
|
66
|
+
claim_id: normalizedClaimId,
|
|
67
|
+
claim_project_id: normalizedClaimProjectId,
|
|
68
|
+
claimed_item_id: normalizedClaimedItemId,
|
|
69
|
+
workflow_run_id: normalizedWorkflowRunId,
|
|
70
|
+
agent_session_id: normalizedAgentSessionId,
|
|
71
|
+
allowed_mutation_surfaces: normalizedAllowedMutationSurfaces,
|
|
72
|
+
acknowledged_reminders: normalizedAcknowledgedReminders || [],
|
|
73
|
+
requested_mutation_surface: 'project_roadmap_task',
|
|
74
|
+
verification_required: true,
|
|
75
|
+
});
|
|
76
|
+
return client._request(`/api/operator/implementation-items/${implementationItemId}/tasks`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
body: {
|
|
79
|
+
title,
|
|
80
|
+
implementation_packet_id: implementationPacketId,
|
|
81
|
+
description,
|
|
82
|
+
priority,
|
|
83
|
+
assigned_to: assignedTo,
|
|
84
|
+
assigned_by: assignedBy,
|
|
85
|
+
due_at: dueAt,
|
|
86
|
+
sort_order: sortOrder,
|
|
87
|
+
notes,
|
|
88
|
+
created_by: createdBy,
|
|
89
|
+
parent_task_id: parentTaskId,
|
|
90
|
+
claim_id: normalizedClaimId,
|
|
91
|
+
claim_project_id: normalizedClaimProjectId,
|
|
92
|
+
claimed_item_id: normalizedClaimedItemId,
|
|
93
|
+
workflow_run_id: normalizedWorkflowRunId,
|
|
94
|
+
agent_session_id: normalizedAgentSessionId,
|
|
95
|
+
allowed_mutation_surfaces: normalizedAllowedMutationSurfaces,
|
|
96
|
+
acknowledged_reminders: normalizedAcknowledgedReminders,
|
|
97
|
+
execution_intent: normalizedExecutionIntent,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function listImplementationTasks(client, implementationItemId) {
|
|
103
|
+
return client._request(`/api/operator/implementation-items/${implementationItemId}/tasks`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function listImplementationSubtasks(client, taskId) {
|
|
107
|
+
return client._request(`/api/operator/implementation-item-tasks/${taskId}/subtasks`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function updateImplementationTask(client, taskId, updates) {
|
|
111
|
+
return client._request(`/api/operator/implementation-item-tasks/${taskId}`, {
|
|
112
|
+
method: 'PATCH',
|
|
113
|
+
body: updates,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function assignImplementationTask(client, taskId, request = {}) {
|
|
118
|
+
return client._request(`/api/operator/implementation-item-tasks/${taskId}/assign`, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
body: { assigned_to: request.assignedTo, assigned_by: request.assignedBy },
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function completeImplementationTask(client, taskId, request = {}) {
|
|
125
|
+
return client._request(`/api/operator/implementation-item-tasks/${taskId}/complete`, {
|
|
126
|
+
method: 'POST',
|
|
127
|
+
body: { completed_by: request.completedBy },
|
|
128
|
+
});
|
|
129
|
+
}
|
package/src/domains/portfolio.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { cleanText, isPlainObject } from '../utils/text.js';
|
|
2
|
+
|
|
1
3
|
export function createPortfolioDomain(client) {
|
|
2
4
|
return {
|
|
3
5
|
getPortfolioStatus: (request) => client.getPortfolioStatus(request),
|
|
@@ -6,6 +8,128 @@ export function createPortfolioDomain(client) {
|
|
|
6
8
|
getPortfolioProject: (projectId) => client.getPortfolioProject(projectId),
|
|
7
9
|
getPortfolioReport: () => client.getPortfolioReport(),
|
|
8
10
|
getPortfolioBundle: () => client.getPortfolioBundle(),
|
|
9
|
-
getPortfolioClosureReadiness:
|
|
11
|
+
getPortfolioClosureReadiness: async ({
|
|
12
|
+
projectLimit = 25,
|
|
13
|
+
includeInactive = false,
|
|
14
|
+
includeLogaPortfolioProjection = false,
|
|
15
|
+
includeLogaRoadmapProjections = false,
|
|
16
|
+
} = {}) => {
|
|
17
|
+
const portfolioBundle = await client.getPortfolioBundle();
|
|
18
|
+
const projectListPayload = await client.listProjects({
|
|
19
|
+
limit: projectLimit,
|
|
20
|
+
includeInactive,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const bundleProjects = Array.isArray(portfolioBundle?.projects)
|
|
24
|
+
? portfolioBundle.projects.filter((project) => isPlainObject(project))
|
|
25
|
+
: [];
|
|
26
|
+
const listedProjects = Array.isArray(projectListPayload?.projects)
|
|
27
|
+
? projectListPayload.projects.filter((project) => isPlainObject(project))
|
|
28
|
+
: Array.isArray(projectListPayload)
|
|
29
|
+
? projectListPayload.filter((project) => isPlainObject(project))
|
|
30
|
+
: [];
|
|
31
|
+
const activeProjects = listedProjects.length > 0 ? listedProjects : bundleProjects;
|
|
32
|
+
const logaPortfolioProjection = includeLogaPortfolioProjection
|
|
33
|
+
? await client.getLogaProjectPortfolioProjection()
|
|
34
|
+
: null;
|
|
35
|
+
const logaRoadmapProjections = {};
|
|
36
|
+
|
|
37
|
+
const projectReadiness = [];
|
|
38
|
+
for (const project of activeProjects) {
|
|
39
|
+
const projectId = cleanText(project.project_id) || cleanText(project.projectId);
|
|
40
|
+
if (!projectId) {
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const roadmapSummaryPayload = await client.getProjectRoadmapSummary(projectId);
|
|
45
|
+
const roadmapSummary = isPlainObject(roadmapSummaryPayload?.summary) ? roadmapSummaryPayload.summary : {};
|
|
46
|
+
const activeItemPayload = await client.getProjectRoadmapActiveItem(projectId);
|
|
47
|
+
const activeItem = isPlainObject(activeItemPayload?.active_item)
|
|
48
|
+
? activeItemPayload.active_item
|
|
49
|
+
: isPlainObject(activeItemPayload)
|
|
50
|
+
? activeItemPayload
|
|
51
|
+
: {};
|
|
52
|
+
const openTasksPayload = await client.listProjectOpenTasks(projectId);
|
|
53
|
+
const openTasks = Array.isArray(openTasksPayload?.tasks)
|
|
54
|
+
? openTasksPayload.tasks.filter((task) => isPlainObject(task))
|
|
55
|
+
: Array.isArray(openTasksPayload)
|
|
56
|
+
? openTasksPayload.filter((task) => isPlainObject(task))
|
|
57
|
+
: [];
|
|
58
|
+
|
|
59
|
+
const activeItemStatus = cleanText(activeItem.item_status) || cleanText(activeItem.status);
|
|
60
|
+
const completionPercentage = Number(
|
|
61
|
+
roadmapSummary.completion_percentage
|
|
62
|
+
?? roadmapSummary.completion_pct
|
|
63
|
+
?? roadmapSummary.progress_percentage
|
|
64
|
+
?? 0,
|
|
65
|
+
);
|
|
66
|
+
const totalItems = Number(
|
|
67
|
+
roadmapSummary.total_items
|
|
68
|
+
?? roadmapSummary.item_count
|
|
69
|
+
?? roadmapSummary.total_count
|
|
70
|
+
?? 0,
|
|
71
|
+
);
|
|
72
|
+
const openItems = Number(
|
|
73
|
+
roadmapSummary.open_items
|
|
74
|
+
?? roadmapSummary.open_item_count
|
|
75
|
+
?? roadmapSummary.remaining_items
|
|
76
|
+
?? openTasks.length,
|
|
77
|
+
);
|
|
78
|
+
const terminalStatuses = new Set(['accepted', 'verified', 'done', 'completed', 'closed']);
|
|
79
|
+
const activeItemReady = !activeItemStatus || terminalStatuses.has(activeItemStatus.toLowerCase());
|
|
80
|
+
const projectReady = openItems === 0 && openTasks.length === 0 && activeItemReady && completionPercentage >= 100;
|
|
81
|
+
const blockingReason = projectReady
|
|
82
|
+
? null
|
|
83
|
+
: [
|
|
84
|
+
openTasks.length > 0 ? `${openTasks.length} open task(s) remain` : null,
|
|
85
|
+
openItems > 0 ? `${openItems} roadmap item(s) remain open` : null,
|
|
86
|
+
!activeItemReady ? `active item status is ${activeItemStatus || 'missing'}` : null,
|
|
87
|
+
completionPercentage < 100 ? `completion is ${completionPercentage.toFixed(1)}%` : null,
|
|
88
|
+
].filter(Boolean).join('; ') || 'portfolio closure readiness is not satisfied';
|
|
89
|
+
|
|
90
|
+
const roadmapCompletion = {
|
|
91
|
+
completion_percentage: completionPercentage,
|
|
92
|
+
total_items: totalItems,
|
|
93
|
+
open_items: openItems,
|
|
94
|
+
completed_items: Math.max(totalItems - openItems, 0),
|
|
95
|
+
};
|
|
96
|
+
const projectReadinessEntry = {
|
|
97
|
+
project: {
|
|
98
|
+
project_id: projectId,
|
|
99
|
+
project_name: cleanText(project.project_name) || cleanText(project.projectName) || null,
|
|
100
|
+
project_slug: cleanText(project.project_slug) || cleanText(project.projectSlug) || null,
|
|
101
|
+
process_status: cleanText(project.process_status) || cleanText(project.processStatus) || null,
|
|
102
|
+
charter_status: cleanText(project.charter_status) || cleanText(project.charterStatus) || null,
|
|
103
|
+
},
|
|
104
|
+
roadmap_completion: roadmapCompletion,
|
|
105
|
+
active_item: activeItem,
|
|
106
|
+
open_task_count: openTasks.length,
|
|
107
|
+
closure_ready: projectReady,
|
|
108
|
+
blocking_reason: blockingReason,
|
|
109
|
+
};
|
|
110
|
+
if (includeLogaRoadmapProjections) {
|
|
111
|
+
projectReadinessEntry.loga_roadmap_projection = await client.getLogaProjectRoadmapProjection(projectId);
|
|
112
|
+
logaRoadmapProjections[projectId] = projectReadinessEntry.loga_roadmap_projection;
|
|
113
|
+
}
|
|
114
|
+
projectReadiness.push(projectReadinessEntry);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const closureReady = projectReadiness.length === 0 || projectReadiness.every((project) => project.closure_ready === true);
|
|
118
|
+
const blockingProject = projectReadiness.find((project) => project.closure_ready !== true);
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
portfolio_summary: isPlainObject(portfolioBundle?.summary) ? portfolioBundle.summary : {},
|
|
122
|
+
portfolio_bundle: portfolioBundle,
|
|
123
|
+
active_projects: projectReadiness,
|
|
124
|
+
closure_readiness: closureReady,
|
|
125
|
+
blocking_reason: closureReady ? null : `${cleanText(blockingProject?.project?.project_name) || cleanText(blockingProject?.project?.project_id) || 'project'}: ${blockingProject?.blocking_reason || 'closure readiness is not satisfied'}`,
|
|
126
|
+
blocking_project_id: closureReady ? null : cleanText(blockingProject?.project?.project_id),
|
|
127
|
+
blocking_project_name: closureReady ? null : cleanText(blockingProject?.project?.project_name),
|
|
128
|
+
project_count: projectReadiness.length,
|
|
129
|
+
open_task_count: projectReadiness.reduce((total, project) => total + Number(project.open_task_count || 0), 0),
|
|
130
|
+
loga_portfolio_projection: logaPortfolioProjection,
|
|
131
|
+
loga_roadmap_projections: includeLogaRoadmapProjections ? logaRoadmapProjections : null,
|
|
132
|
+
};
|
|
133
|
+
},
|
|
10
134
|
};
|
|
11
135
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { isPlainObject } from '../utils/text.js';
|
|
2
|
+
import { normalizeTaskBindingPolicy } from '../utils/task-binding.js';
|
|
3
|
+
|
|
1
4
|
export function createProjectCharteringDomain(client) {
|
|
2
5
|
return {
|
|
3
6
|
createProjectDelivery: (body = {}) => client._request('/api/project-delivery', { method: 'POST', body }),
|
|
@@ -29,6 +32,21 @@ export function createProjectCharteringDomain(client) {
|
|
|
29
32
|
body,
|
|
30
33
|
});
|
|
31
34
|
},
|
|
35
|
+
routeImplementationItem: (implementationItemId, body = {}) => {
|
|
36
|
+
if (!implementationItemId) throw new Error('implementationItemId is required.');
|
|
37
|
+
const normalizedBody = isPlainObject(body)
|
|
38
|
+
? {
|
|
39
|
+
...body,
|
|
40
|
+
...(isPlainObject(body.task_binding)
|
|
41
|
+
? { task_binding: normalizeTaskBindingPolicy(body.task_binding) }
|
|
42
|
+
: {}),
|
|
43
|
+
}
|
|
44
|
+
: body;
|
|
45
|
+
return client._request(`/api/governed-implementation/items/${encodeURIComponent(implementationItemId)}/routing`, {
|
|
46
|
+
method: 'PATCH',
|
|
47
|
+
body: normalizedBody,
|
|
48
|
+
});
|
|
49
|
+
},
|
|
32
50
|
createProjectCharter: (request = {}) => client._request('/api/projects/charter', {
|
|
33
51
|
method: 'POST',
|
|
34
52
|
body: {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function createProjectReportsDomain(client) {
|
|
2
|
+
return {
|
|
3
|
+
getProjectCharterReport: (projectId) => client._request(`/api/operator/projects/${projectId}/charter/report`),
|
|
4
|
+
createProjectMarkdownDownload: (projectId, request = {}) => client._request(`/api/operator/projects/${projectId}/markdown-report-downloads`, {
|
|
5
|
+
method: 'POST',
|
|
6
|
+
body: {
|
|
7
|
+
report_type: request.reportType,
|
|
8
|
+
include_markdown: request.includeMarkdown ?? false,
|
|
9
|
+
},
|
|
10
|
+
}),
|
|
11
|
+
downloadProjectMarkdownReport: (projectId, reportType) => client._requestText(`/api/operator/projects/${projectId}/markdown-reports/${reportType}/download`),
|
|
12
|
+
downloadProjectCharterReportMarkdown: (projectId) => client._requestText(`/api/operator/projects/${projectId}/markdown-reports/charter/download`),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function createProjectResumeDomain(client) {
|
|
2
|
+
return {
|
|
3
|
+
resumeProjectWork: (request = {}) => {
|
|
4
|
+
const normalizedProjectIdentifier = String(request.projectIdentifier || request.projectId || '').trim();
|
|
5
|
+
if (!normalizedProjectIdentifier) {
|
|
6
|
+
throw new Error('projectIdentifier is required.');
|
|
7
|
+
}
|
|
8
|
+
return client._request(`/api/operator/projects/${encodeURIComponent(normalizedProjectIdentifier)}/resume-context`, {
|
|
9
|
+
query: {
|
|
10
|
+
actor_mode: request.actorMode,
|
|
11
|
+
execution_intent: request.executionIntent,
|
|
12
|
+
require_claim: request.requireClaim,
|
|
13
|
+
workflow_run_limit: request.workflowRunLimit,
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|