@bpmsoftwaresolutions/ai-engine-client 1.1.66 → 1.1.71
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/README.md +31 -1
- package/package.json +1 -1
- package/src/index.js +403 -0
package/README.md
CHANGED
|
@@ -94,6 +94,34 @@ const continuation = await client.resumeProjectWork({
|
|
|
94
94
|
|
|
95
95
|
That call resolves the canonical project, active roadmap state, workflow context, tool bindings, and the continuation brief in one governed response. Use the returned `continuation_brief_markdown` as the primary focus surface for LOGA and operator startup flows.
|
|
96
96
|
|
|
97
|
+
### Roadmap Closure Workflow
|
|
98
|
+
|
|
99
|
+
Use `closeRoadmapItemWorkflow(...)` when you want the client to compose the full roadmap-closure path end to end.
|
|
100
|
+
|
|
101
|
+
```js
|
|
102
|
+
const closure = await client.closeRoadmapItemWorkflow({
|
|
103
|
+
projectIdentifier: charter.project_id,
|
|
104
|
+
claimId: existingClaimId,
|
|
105
|
+
});
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The helper:
|
|
109
|
+
|
|
110
|
+
- resumes the project
|
|
111
|
+
- loads the active roadmap item
|
|
112
|
+
- validates a supplied claim with `claimIsValid(claimId)`
|
|
113
|
+
- starts a fresh claim when the supplied claim is missing or inactive
|
|
114
|
+
- loads and verifies acceptance checks
|
|
115
|
+
- verifies the required artifacts
|
|
116
|
+
- records the implementation gate decision
|
|
117
|
+
- advances the item to terminal status
|
|
118
|
+
- attaches closure evidence
|
|
119
|
+
- signs off the claim
|
|
120
|
+
- reloads the active item
|
|
121
|
+
- closes the active project when no open tasks remain
|
|
122
|
+
|
|
123
|
+
The backend still does not expose a project-scoped `getActiveClaim(projectId)` helper. This client records that gap and uses `claimIsValid(claimId)` plus claim creation when it needs a governed fallback path.
|
|
124
|
+
|
|
97
125
|
## Integration Notes
|
|
98
126
|
|
|
99
127
|
### Auth Modes
|
|
@@ -604,6 +632,7 @@ Generated scripts are ephemeral helpers, not source-of-truth. The server keeps S
|
|
|
604
632
|
| `getProjectRoadmap(projectId)` | Get full roadmap for a project. |
|
|
605
633
|
| `getProjectRoadmapSummary(projectId)` | Roadmap summary. |
|
|
606
634
|
| `getProjectRoadmapActiveItem(projectId)` | Current active roadmap item. |
|
|
635
|
+
| `closeRoadmapItemWorkflow({ projectIdentifier, claimId, actorMode, executionIntent, workflowRunLimit, requiredArtifacts, requiredAcceptanceCheckStatus, terminalItemStatus, gateType, gateDecision, closeProjectIfNoRemainingOpenItems })` | Compose roadmap closure end to end: resume, claim fallback, verify checks/artifacts, record a gate decision, sign off, and close the project when nothing remains open. |
|
|
607
636
|
| `getProjectImplementationRoadmapReport(projectId)` | Get the SQL-backed implementation roadmap report payload. |
|
|
608
637
|
| `downloadProjectImplementationRoadmapReportMarkdown(projectId)` | Download the implementation roadmap markdown report. |
|
|
609
638
|
| `ensureProjectRoadmapTaskSurface(projectId, { requestedBy, assignedTo, createAcceptanceSubtasks })` | Materialize the active roadmap item's parent task and acceptance subtasks. |
|
|
@@ -625,7 +654,7 @@ Generated scripts are ephemeral helpers, not source-of-truth. The server keeps S
|
|
|
625
654
|
|
|
626
655
|
### Governed Implementation
|
|
627
656
|
|
|
628
|
-
Current package version: `1.
|
|
657
|
+
Current package version: `1.1.71`.
|
|
629
658
|
|
|
630
659
|
| Method | Description |
|
|
631
660
|
|---|---|
|
|
@@ -734,6 +763,7 @@ Engine returns the required gate and next action.
|
|
|
734
763
|
| `getPortfolioProject(projectId)` | Project detail via portfolio. |
|
|
735
764
|
| `getPortfolioReport()` | Full portfolio report. |
|
|
736
765
|
| `getPortfolioBundle()` | Portfolio bundle. |
|
|
766
|
+
| `getPortfolioClosureReadiness({ projectLimit, includeInactive, includeLogaPortfolioProjection, includeLogaRoadmapProjections })` | Read-only portfolio readiness view that composes bundle, project list, roadmap summaries, active items, and open task counts into a conservative closure-ready flag. |
|
|
737
767
|
|
|
738
768
|
### Self-Learning
|
|
739
769
|
| Method | Description |
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -4395,6 +4395,285 @@ export class AIEngineClient {
|
|
|
4395
4395
|
});
|
|
4396
4396
|
}
|
|
4397
4397
|
|
|
4398
|
+
async closeRoadmapItemWorkflow({
|
|
4399
|
+
projectIdentifier,
|
|
4400
|
+
projectId,
|
|
4401
|
+
claimId,
|
|
4402
|
+
claimName,
|
|
4403
|
+
actorId,
|
|
4404
|
+
actorMode = 'operator',
|
|
4405
|
+
executionIntent = 'close roadmap item workflow',
|
|
4406
|
+
workflowRunLimit = 5,
|
|
4407
|
+
declaredScopeFiles,
|
|
4408
|
+
allowedMutationSurfaces,
|
|
4409
|
+
requiredArtifacts,
|
|
4410
|
+
requiredAcceptanceCheckStatus = 'verified',
|
|
4411
|
+
terminalItemStatus = 'accepted',
|
|
4412
|
+
gateType = 'roadmap_closure',
|
|
4413
|
+
gateDecision = 'pass',
|
|
4414
|
+
gateRationale,
|
|
4415
|
+
gateEvidenceRefs,
|
|
4416
|
+
remediationActions = [],
|
|
4417
|
+
closureEvidenceType = 'roadmap_closure',
|
|
4418
|
+
closureEvidenceRef,
|
|
4419
|
+
closureEvidenceTitle,
|
|
4420
|
+
closeProjectIfNoRemainingOpenItems = true,
|
|
4421
|
+
closeProjectReason,
|
|
4422
|
+
} = {}) {
|
|
4423
|
+
const normalizedProjectReference = cleanText(projectIdentifier) || cleanText(projectId);
|
|
4424
|
+
if (!normalizedProjectReference) {
|
|
4425
|
+
throw new Error('projectIdentifier is required.');
|
|
4426
|
+
}
|
|
4427
|
+
|
|
4428
|
+
const normalizedActorId = cleanText(actorId) || this.actorId;
|
|
4429
|
+
const continuation = await this.resumeProjectWork({
|
|
4430
|
+
projectIdentifier: normalizedProjectReference,
|
|
4431
|
+
projectId: normalizedProjectReference,
|
|
4432
|
+
actorMode,
|
|
4433
|
+
executionIntent,
|
|
4434
|
+
requireClaim: false,
|
|
4435
|
+
workflowRunLimit,
|
|
4436
|
+
});
|
|
4437
|
+
|
|
4438
|
+
const projectSummary = isPlainObject(continuation?.project) ? continuation.project : {};
|
|
4439
|
+
const resolvedProjectId = cleanText(projectSummary.project_id) || cleanText(projectSummary.projectId) || normalizedProjectReference;
|
|
4440
|
+
const resolvedWorkflowId = cleanText(projectSummary.workflow_id) || cleanText(projectSummary.workflowId);
|
|
4441
|
+
const resolvedWorkflowRunId = cleanText(projectSummary.workflow_run_id) || cleanText(projectSummary.workflowRunId) || null;
|
|
4442
|
+
const activeItem = await this.getProjectRoadmapActiveItem(resolvedProjectId);
|
|
4443
|
+
const normalizedActiveItem = isPlainObject(activeItem) ? activeItem : {};
|
|
4444
|
+
const implementationItemId = cleanText(normalizedActiveItem.implementation_item_id) || cleanText(normalizedActiveItem.implementationItemId);
|
|
4445
|
+
const itemKey = cleanText(normalizedActiveItem.item_key) || cleanText(normalizedActiveItem.itemKey) || implementationItemId;
|
|
4446
|
+
const itemStatus = cleanText(normalizedActiveItem.item_status) || cleanText(normalizedActiveItem.status);
|
|
4447
|
+
const packetId = cleanText(normalizedActiveItem.implementation_packet_id) || cleanText(normalizedActiveItem.implementationPacketId);
|
|
4448
|
+
|
|
4449
|
+
if (!implementationItemId || !resolvedWorkflowId || !packetId) {
|
|
4450
|
+
throw new Error('project closure state is ambiguous.');
|
|
4451
|
+
}
|
|
4452
|
+
|
|
4453
|
+
let effectiveClaimId = cleanText(claimId);
|
|
4454
|
+
let claimSource = 'provided';
|
|
4455
|
+
if (effectiveClaimId) {
|
|
4456
|
+
const claimStillValid = await this.claimIsValid(effectiveClaimId).catch(() => false);
|
|
4457
|
+
if (!claimStillValid) {
|
|
4458
|
+
effectiveClaimId = null;
|
|
4459
|
+
claimSource = 'fresh';
|
|
4460
|
+
}
|
|
4461
|
+
} else {
|
|
4462
|
+
claimSource = 'fresh';
|
|
4463
|
+
}
|
|
4464
|
+
|
|
4465
|
+
let freshClaim = null;
|
|
4466
|
+
if (!effectiveClaimId) {
|
|
4467
|
+
const declaredScope = cleanList(declaredScopeFiles);
|
|
4468
|
+
const surfaces = cleanList(allowedMutationSurfaces);
|
|
4469
|
+
const fallbackDeclaredScope = declaredScope.length > 0 ? declaredScope : [
|
|
4470
|
+
`${resolvedProjectId}`,
|
|
4471
|
+
itemKey || implementationItemId,
|
|
4472
|
+
].filter(Boolean);
|
|
4473
|
+
const fallbackSurfaces = surfaces.length > 0 ? surfaces : [
|
|
4474
|
+
'project_roadmap_task',
|
|
4475
|
+
'implementation_acceptance_check',
|
|
4476
|
+
'implementation_evidence',
|
|
4477
|
+
'implementation_gate',
|
|
4478
|
+
'project_close',
|
|
4479
|
+
];
|
|
4480
|
+
const closureTaskStatus = itemStatus && !new Set(['done', 'completed', 'complete', 'blocked', 'failed', 'rejected', 'cancelled']).has(itemStatus.toLowerCase())
|
|
4481
|
+
? itemStatus
|
|
4482
|
+
: 'in_progress';
|
|
4483
|
+
const closureTaskBinding = {
|
|
4484
|
+
task_id: implementationItemId || itemKey || resolvedProjectId,
|
|
4485
|
+
roadmap_item_id: implementationItemId || itemKey || resolvedProjectId,
|
|
4486
|
+
task_status: closureTaskStatus,
|
|
4487
|
+
allowed_actions: ['claim_work_item'],
|
|
4488
|
+
substrate_action: 'claim_work_item',
|
|
4489
|
+
task_type: 'roadmap_closure',
|
|
4490
|
+
};
|
|
4491
|
+
try {
|
|
4492
|
+
freshClaim = await this.startClaimedWork({
|
|
4493
|
+
claimName: cleanText(claimName) || `closeRoadmapItemWorkflow:${resolvedProjectId}:${itemKey || implementationItemId}`,
|
|
4494
|
+
actorId: normalizedActorId,
|
|
4495
|
+
intentId: 'code_mutation',
|
|
4496
|
+
declaredScopeFiles: fallbackDeclaredScope,
|
|
4497
|
+
allowedMutationSurfaces: fallbackSurfaces,
|
|
4498
|
+
runtimeSessionId: resolvedWorkflowRunId || resolvedProjectId,
|
|
4499
|
+
executionPurpose: cleanText(executionIntent) || `Close roadmap item ${itemKey || implementationItemId}`,
|
|
4500
|
+
successCriteria: 'Active roadmap item closed with verified acceptance checks, artifacts, gate decision, and claim signoff.',
|
|
4501
|
+
mutationAllowed: true,
|
|
4502
|
+
metadata: {
|
|
4503
|
+
source: 'closeRoadmapItemWorkflow',
|
|
4504
|
+
project_id: resolvedProjectId,
|
|
4505
|
+
workflow_id: resolvedWorkflowId,
|
|
4506
|
+
workflow_run_id: resolvedWorkflowRunId,
|
|
4507
|
+
task_binding: closureTaskBinding,
|
|
4508
|
+
},
|
|
4509
|
+
});
|
|
4510
|
+
} catch (error) {
|
|
4511
|
+
throw new Error(`claim cannot be established: ${error.message}`);
|
|
4512
|
+
}
|
|
4513
|
+
effectiveClaimId = cleanText(freshClaim?.claim_id) || cleanText(freshClaim?.claim?.claim_id);
|
|
4514
|
+
if (!effectiveClaimId) {
|
|
4515
|
+
throw new Error('claim cannot be established.');
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4518
|
+
|
|
4519
|
+
const acceptanceCheckResult = await this.getImplementationItemAcceptanceChecks(implementationItemId);
|
|
4520
|
+
const acceptanceChecks = Array.isArray(acceptanceCheckResult?.acceptance_checks)
|
|
4521
|
+
? acceptanceCheckResult.acceptance_checks.filter((check) => isPlainObject(check))
|
|
4522
|
+
: [];
|
|
4523
|
+
if (acceptanceChecks.length === 0) {
|
|
4524
|
+
throw new Error('acceptance checks are incomplete.');
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
const normalizedAcceptanceStatus = cleanText(requiredAcceptanceCheckStatus) || 'verified';
|
|
4528
|
+
const verifiedAcceptanceChecks = [];
|
|
4529
|
+
for (const check of acceptanceChecks) {
|
|
4530
|
+
const acceptanceCheckId = cleanText(check.acceptance_check_id) || cleanText(check.acceptanceCheckId);
|
|
4531
|
+
if (!acceptanceCheckId) {
|
|
4532
|
+
throw new Error('acceptance checks are incomplete.');
|
|
4533
|
+
}
|
|
4534
|
+
const status = cleanText(check.status).toLowerCase();
|
|
4535
|
+
if (status !== normalizedAcceptanceStatus.toLowerCase()) {
|
|
4536
|
+
try {
|
|
4537
|
+
const verification = await this.updateAcceptanceCheckStatusVerified(
|
|
4538
|
+
implementationItemId,
|
|
4539
|
+
acceptanceCheckId,
|
|
4540
|
+
normalizedAcceptanceStatus,
|
|
4541
|
+
{
|
|
4542
|
+
updated_by: normalizedActorId,
|
|
4543
|
+
workflowId: resolvedWorkflowId,
|
|
4544
|
+
claim_id: effectiveClaimId,
|
|
4545
|
+
status_reason: 'Roadmap closure workflow verified the required acceptance check.',
|
|
4546
|
+
},
|
|
4547
|
+
);
|
|
4548
|
+
verifiedAcceptanceChecks.push({
|
|
4549
|
+
acceptance_check_id: acceptanceCheckId,
|
|
4550
|
+
status: normalizedAcceptanceStatus,
|
|
4551
|
+
verification,
|
|
4552
|
+
});
|
|
4553
|
+
} catch (error) {
|
|
4554
|
+
throw new Error(`acceptance checks are incomplete: ${error.message}`);
|
|
4555
|
+
}
|
|
4556
|
+
continue;
|
|
4557
|
+
}
|
|
4558
|
+
verifiedAcceptanceChecks.push({
|
|
4559
|
+
acceptance_check_id: acceptanceCheckId,
|
|
4560
|
+
status,
|
|
4561
|
+
existing: true,
|
|
4562
|
+
});
|
|
4563
|
+
}
|
|
4564
|
+
|
|
4565
|
+
const artifacts = await this.verifyImplementationItemArtifacts(implementationItemId, {
|
|
4566
|
+
requiredArtifacts: cleanList(requiredArtifacts).length > 0
|
|
4567
|
+
? cleanList(requiredArtifacts)
|
|
4568
|
+
: ['artifact_manifest', 'decision_packet'],
|
|
4569
|
+
});
|
|
4570
|
+
|
|
4571
|
+
const evidenceRefs = cleanList(gateEvidenceRefs);
|
|
4572
|
+
if (evidenceRefs.length === 0) {
|
|
4573
|
+
evidenceRefs.push(closureEvidenceRef || `roadmap-closure:${resolvedProjectId}:${itemKey || implementationItemId}`);
|
|
4574
|
+
}
|
|
4575
|
+
|
|
4576
|
+
const gateDecisionResult = await this.createImplementationPacketGateDecision(packetId, {
|
|
4577
|
+
gate_type: cleanText(gateType) || 'roadmap_closure',
|
|
4578
|
+
decision: cleanText(gateDecision) || 'pass',
|
|
4579
|
+
rationale: cleanText(gateRationale) || `Roadmap item ${itemKey || implementationItemId} satisfied all closure checks.`,
|
|
4580
|
+
evidence_refs: evidenceRefs,
|
|
4581
|
+
remediation_actions: cleanList(remediationActions),
|
|
4582
|
+
decided_by: normalizedActorId,
|
|
4583
|
+
claim_id: effectiveClaimId,
|
|
4584
|
+
workflow_id: resolvedWorkflowId,
|
|
4585
|
+
workflow_run_id: resolvedWorkflowRunId,
|
|
4586
|
+
});
|
|
4587
|
+
|
|
4588
|
+
const itemStatusResult = await this.updateImplementationItemStatusVerified(
|
|
4589
|
+
implementationItemId,
|
|
4590
|
+
cleanText(terminalItemStatus) || 'accepted',
|
|
4591
|
+
{
|
|
4592
|
+
workflowId: resolvedWorkflowId,
|
|
4593
|
+
updated_by: normalizedActorId,
|
|
4594
|
+
claim_id: effectiveClaimId,
|
|
4595
|
+
status_reason: cleanText(closeProjectReason) || `Roadmap closure workflow advanced ${itemKey || implementationItemId} to terminal status.`,
|
|
4596
|
+
},
|
|
4597
|
+
);
|
|
4598
|
+
|
|
4599
|
+
const closureEvidenceRefValue = cleanText(closureEvidenceRef) || `roadmap-closure:${resolvedProjectId}:${itemKey || implementationItemId}`;
|
|
4600
|
+
const closureEvidence = await this.addImplementationItemEvidence(implementationItemId, {
|
|
4601
|
+
evidence_type: cleanText(closureEvidenceType) || 'roadmap_closure',
|
|
4602
|
+
evidence_ref: closureEvidenceRefValue,
|
|
4603
|
+
title: cleanText(closureEvidenceTitle) || `Closure evidence for ${itemKey || implementationItemId}`,
|
|
4604
|
+
recorded_by: normalizedActorId,
|
|
4605
|
+
metadata: {
|
|
4606
|
+
project_id: resolvedProjectId,
|
|
4607
|
+
workflow_id: resolvedWorkflowId,
|
|
4608
|
+
workflow_run_id: resolvedWorkflowRunId,
|
|
4609
|
+
claim_id: effectiveClaimId,
|
|
4610
|
+
gate_decision_id: cleanText(gateDecisionResult?.implementation_gate_decision_id) || cleanText(gateDecisionResult?.gate_decision_id),
|
|
4611
|
+
gate_type: cleanText(gateType) || 'roadmap_closure',
|
|
4612
|
+
},
|
|
4613
|
+
});
|
|
4614
|
+
|
|
4615
|
+
const signedClaim = await this.signoffClaim(effectiveClaimId, {
|
|
4616
|
+
actor_signature: normalizedActorId,
|
|
4617
|
+
intent_confirmation: cleanText(executionIntent) || 'I confirm this governed roadmap closure workflow.',
|
|
4618
|
+
});
|
|
4619
|
+
|
|
4620
|
+
const reloadedActiveItem = await this.getProjectRoadmapActiveItem(resolvedProjectId);
|
|
4621
|
+
const remainingOpenTasksPayload = await this.listProjectOpenTasks(resolvedProjectId);
|
|
4622
|
+
const remainingOpenTasks = Array.isArray(remainingOpenTasksPayload?.tasks)
|
|
4623
|
+
? remainingOpenTasksPayload.tasks.filter((task) => isPlainObject(task))
|
|
4624
|
+
: Array.isArray(remainingOpenTasksPayload)
|
|
4625
|
+
? remainingOpenTasksPayload.filter((task) => isPlainObject(task))
|
|
4626
|
+
: [];
|
|
4627
|
+
const reloadedItemStatus = cleanText(reloadedActiveItem?.item_status) || cleanText(reloadedActiveItem?.status) || null;
|
|
4628
|
+
const terminalStatuses = new Set([
|
|
4629
|
+
cleanText(terminalItemStatus) || 'accepted',
|
|
4630
|
+
'accepted',
|
|
4631
|
+
'verified',
|
|
4632
|
+
'done',
|
|
4633
|
+
'completed',
|
|
4634
|
+
'closed',
|
|
4635
|
+
].map((value) => String(value || '').toLowerCase()));
|
|
4636
|
+
const activeItemClosed = !reloadedActiveItem || terminalStatuses.has(String(reloadedItemStatus || '').toLowerCase());
|
|
4637
|
+
if (!activeItemClosed) {
|
|
4638
|
+
throw new Error('project closure state is ambiguous.');
|
|
4639
|
+
}
|
|
4640
|
+
|
|
4641
|
+
let closeProjectResult = null;
|
|
4642
|
+
if (closeProjectIfNoRemainingOpenItems && remainingOpenTasks.length === 0) {
|
|
4643
|
+
closeProjectResult = await this.closeActiveProject(resolvedProjectId, {
|
|
4644
|
+
workflowId: resolvedWorkflowId,
|
|
4645
|
+
workflowRunIds: resolvedWorkflowRunId ? [resolvedWorkflowRunId] : undefined,
|
|
4646
|
+
reason: cleanText(closeProjectReason) || `Roadmap item ${itemKey || implementationItemId} closed.`,
|
|
4647
|
+
operatorIdentity: normalizedActorId,
|
|
4648
|
+
runTerminalStatus: cleanText(terminalItemStatus) || 'accepted',
|
|
4649
|
+
});
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
return {
|
|
4653
|
+
status: closeProjectResult ? 'closed' : 'item_closed',
|
|
4654
|
+
project_id: resolvedProjectId,
|
|
4655
|
+
workflow_id: resolvedWorkflowId,
|
|
4656
|
+
workflow_run_id: resolvedWorkflowRunId,
|
|
4657
|
+
claim_id: effectiveClaimId,
|
|
4658
|
+
claim_source: claimSource,
|
|
4659
|
+
continuation_brief: continuation,
|
|
4660
|
+
project: projectSummary,
|
|
4661
|
+
active_item_before: normalizedActiveItem,
|
|
4662
|
+
active_item_after: reloadedActiveItem,
|
|
4663
|
+
acceptance_checks: {
|
|
4664
|
+
loaded: acceptanceChecks,
|
|
4665
|
+
verified: verifiedAcceptanceChecks,
|
|
4666
|
+
},
|
|
4667
|
+
artifact_verification: artifacts,
|
|
4668
|
+
gate_decision: gateDecisionResult,
|
|
4669
|
+
implementation_item_status: itemStatusResult,
|
|
4670
|
+
closure_evidence: closureEvidence,
|
|
4671
|
+
claim_signoff: signedClaim,
|
|
4672
|
+
remaining_open_task_count: remainingOpenTasks.length,
|
|
4673
|
+
close_project: closeProjectResult,
|
|
4674
|
+
};
|
|
4675
|
+
}
|
|
4676
|
+
|
|
4398
4677
|
// LOGA projections are governed runtime documents, not static reports.
|
|
4399
4678
|
|
|
4400
4679
|
async getLogaOperatorHomeProjection() {
|
|
@@ -5488,6 +5767,130 @@ export class AIEngineClient {
|
|
|
5488
5767
|
return this._request('/api/operator/portfolio/bundle');
|
|
5489
5768
|
}
|
|
5490
5769
|
|
|
5770
|
+
async getPortfolioClosureReadiness({
|
|
5771
|
+
projectLimit = 25,
|
|
5772
|
+
includeInactive = false,
|
|
5773
|
+
includeLogaPortfolioProjection = false,
|
|
5774
|
+
includeLogaRoadmapProjections = false,
|
|
5775
|
+
} = {}) {
|
|
5776
|
+
const portfolioBundle = await this.getPortfolioBundle();
|
|
5777
|
+
const projectListPayload = await this.listProjects({
|
|
5778
|
+
limit: projectLimit,
|
|
5779
|
+
includeInactive,
|
|
5780
|
+
});
|
|
5781
|
+
|
|
5782
|
+
const bundleProjects = Array.isArray(portfolioBundle?.projects)
|
|
5783
|
+
? portfolioBundle.projects.filter((project) => isPlainObject(project))
|
|
5784
|
+
: [];
|
|
5785
|
+
const listedProjects = Array.isArray(projectListPayload?.projects)
|
|
5786
|
+
? projectListPayload.projects.filter((project) => isPlainObject(project))
|
|
5787
|
+
: Array.isArray(projectListPayload)
|
|
5788
|
+
? projectListPayload.filter((project) => isPlainObject(project))
|
|
5789
|
+
: [];
|
|
5790
|
+
const activeProjects = listedProjects.length > 0 ? listedProjects : bundleProjects;
|
|
5791
|
+
const logaPortfolioProjection = includeLogaPortfolioProjection
|
|
5792
|
+
? await this.getLogaProjectPortfolioProjection()
|
|
5793
|
+
: null;
|
|
5794
|
+
const logaRoadmapProjections = {};
|
|
5795
|
+
|
|
5796
|
+
const projectReadiness = [];
|
|
5797
|
+
for (const project of activeProjects) {
|
|
5798
|
+
const projectId = cleanText(project.project_id) || cleanText(project.projectId);
|
|
5799
|
+
if (!projectId) {
|
|
5800
|
+
continue;
|
|
5801
|
+
}
|
|
5802
|
+
|
|
5803
|
+
const roadmapSummaryPayload = await this.getProjectRoadmapSummary(projectId);
|
|
5804
|
+
const roadmapSummary = isPlainObject(roadmapSummaryPayload?.summary) ? roadmapSummaryPayload.summary : {};
|
|
5805
|
+
const activeItemPayload = await this.getProjectRoadmapActiveItem(projectId);
|
|
5806
|
+
const activeItem = isPlainObject(activeItemPayload?.active_item)
|
|
5807
|
+
? activeItemPayload.active_item
|
|
5808
|
+
: isPlainObject(activeItemPayload)
|
|
5809
|
+
? activeItemPayload
|
|
5810
|
+
: {};
|
|
5811
|
+
const openTasksPayload = await this.listProjectOpenTasks(projectId);
|
|
5812
|
+
const openTasks = Array.isArray(openTasksPayload?.tasks)
|
|
5813
|
+
? openTasksPayload.tasks.filter((task) => isPlainObject(task))
|
|
5814
|
+
: Array.isArray(openTasksPayload)
|
|
5815
|
+
? openTasksPayload.filter((task) => isPlainObject(task))
|
|
5816
|
+
: [];
|
|
5817
|
+
|
|
5818
|
+
const activeItemStatus = cleanText(activeItem.item_status) || cleanText(activeItem.status);
|
|
5819
|
+
const completionPercentage = Number(
|
|
5820
|
+
roadmapSummary.completion_percentage
|
|
5821
|
+
?? roadmapSummary.completion_pct
|
|
5822
|
+
?? roadmapSummary.progress_percentage
|
|
5823
|
+
?? 0,
|
|
5824
|
+
);
|
|
5825
|
+
const totalItems = Number(
|
|
5826
|
+
roadmapSummary.total_items
|
|
5827
|
+
?? roadmapSummary.item_count
|
|
5828
|
+
?? roadmapSummary.total_count
|
|
5829
|
+
?? 0,
|
|
5830
|
+
);
|
|
5831
|
+
const openItems = Number(
|
|
5832
|
+
roadmapSummary.open_items
|
|
5833
|
+
?? roadmapSummary.open_item_count
|
|
5834
|
+
?? roadmapSummary.remaining_items
|
|
5835
|
+
?? openTasks.length,
|
|
5836
|
+
);
|
|
5837
|
+
const terminalStatuses = new Set(['accepted', 'verified', 'done', 'completed', 'closed']);
|
|
5838
|
+
const activeItemReady = !activeItemStatus || terminalStatuses.has(activeItemStatus.toLowerCase());
|
|
5839
|
+
const projectReady = openItems === 0 && openTasks.length === 0 && activeItemReady && completionPercentage >= 100;
|
|
5840
|
+
const blockingReason = projectReady
|
|
5841
|
+
? null
|
|
5842
|
+
: [
|
|
5843
|
+
openTasks.length > 0 ? `${openTasks.length} open task(s) remain` : null,
|
|
5844
|
+
openItems > 0 ? `${openItems} roadmap item(s) remain open` : null,
|
|
5845
|
+
!activeItemReady ? `active item status is ${activeItemStatus || 'missing'}` : null,
|
|
5846
|
+
completionPercentage < 100 ? `completion is ${completionPercentage.toFixed(1)}%` : null,
|
|
5847
|
+
].filter(Boolean).join('; ') || 'portfolio closure readiness is not satisfied';
|
|
5848
|
+
|
|
5849
|
+
const roadmapCompletion = {
|
|
5850
|
+
completion_percentage: completionPercentage,
|
|
5851
|
+
total_items: totalItems,
|
|
5852
|
+
open_items: openItems,
|
|
5853
|
+
completed_items: Math.max(totalItems - openItems, 0),
|
|
5854
|
+
};
|
|
5855
|
+
const projectReadinessEntry = {
|
|
5856
|
+
project: {
|
|
5857
|
+
project_id: projectId,
|
|
5858
|
+
project_name: cleanText(project.project_name) || cleanText(project.projectName) || null,
|
|
5859
|
+
project_slug: cleanText(project.project_slug) || cleanText(project.projectSlug) || null,
|
|
5860
|
+
process_status: cleanText(project.process_status) || cleanText(project.processStatus) || null,
|
|
5861
|
+
charter_status: cleanText(project.charter_status) || cleanText(project.charterStatus) || null,
|
|
5862
|
+
},
|
|
5863
|
+
roadmap_completion: roadmapCompletion,
|
|
5864
|
+
active_item: activeItem,
|
|
5865
|
+
open_task_count: openTasks.length,
|
|
5866
|
+
closure_ready: projectReady,
|
|
5867
|
+
blocking_reason: blockingReason,
|
|
5868
|
+
};
|
|
5869
|
+
if (includeLogaRoadmapProjections) {
|
|
5870
|
+
projectReadinessEntry.loga_roadmap_projection = await this.getLogaProjectRoadmapProjection(projectId);
|
|
5871
|
+
logaRoadmapProjections[projectId] = projectReadinessEntry.loga_roadmap_projection;
|
|
5872
|
+
}
|
|
5873
|
+
projectReadiness.push(projectReadinessEntry);
|
|
5874
|
+
}
|
|
5875
|
+
|
|
5876
|
+
const closureReady = projectReadiness.length === 0 || projectReadiness.every((project) => project.closure_ready === true);
|
|
5877
|
+
const blockingProject = projectReadiness.find((project) => project.closure_ready !== true);
|
|
5878
|
+
|
|
5879
|
+
return {
|
|
5880
|
+
portfolio_summary: isPlainObject(portfolioBundle?.summary) ? portfolioBundle.summary : {},
|
|
5881
|
+
portfolio_bundle: portfolioBundle,
|
|
5882
|
+
active_projects: projectReadiness,
|
|
5883
|
+
closure_readiness: closureReady,
|
|
5884
|
+
blocking_reason: closureReady ? null : `${cleanText(blockingProject?.project?.project_name) || cleanText(blockingProject?.project?.project_id) || 'project'}: ${blockingProject?.blocking_reason || 'closure readiness is not satisfied'}`,
|
|
5885
|
+
blocking_project_id: closureReady ? null : cleanText(blockingProject?.project?.project_id),
|
|
5886
|
+
blocking_project_name: closureReady ? null : cleanText(blockingProject?.project?.project_name),
|
|
5887
|
+
project_count: projectReadiness.length,
|
|
5888
|
+
open_task_count: projectReadiness.reduce((total, project) => total + Number(project.open_task_count || 0), 0),
|
|
5889
|
+
loga_portfolio_projection: logaPortfolioProjection,
|
|
5890
|
+
loga_roadmap_projections: includeLogaRoadmapProjections ? logaRoadmapProjections : null,
|
|
5891
|
+
};
|
|
5892
|
+
}
|
|
5893
|
+
|
|
5491
5894
|
// ─── Self-Learning ─────────────────────────────────────────────────────────
|
|
5492
5895
|
|
|
5493
5896
|
async getSelfLearningPosture({ workflowRunId, limit } = {}) {
|