@work-graph/cli 0.2.0
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 -0
- package/bin/work-graph.mjs +238 -0
- package/package.json +38 -0
- package/vendor/packages/design-tokens/generated/gripe-dark-default.css +67 -0
- package/vendor/packages/design-tokens/generated/marketplace-default.css +67 -0
- package/vendor/packages/design-tokens/generated/workgraph-dark.css +67 -0
- package/vendor/packages/workgraph-mcp/README.md +28 -0
- package/vendor/packages/workgraph-mcp/bin/workgraph-mcp.mjs +21 -0
- package/vendor/packages/workgraph-mcp/package.json +37 -0
- package/vendor/packages/workgraph-mcp/src/handlers.mjs +761 -0
- package/vendor/packages/workgraph-mcp/src/index.mjs +638 -0
- package/vendor/packages/workgraph-mcp/src/prompts.mjs +162 -0
- package/vendor/public/assets/workgraph-logo.svg +11 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Medium.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Regular.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Semibold.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/stylesheet.css +25 -0
- package/vendor/public/graph-canvas-lit-flow.css +154 -0
- package/vendor/public/graph-canvas-lit-flow.css.map +7 -0
- package/vendor/public/graph-canvas-lit-flow.js +8530 -0
- package/vendor/public/graph-canvas-lit-flow.js.map +7 -0
- package/vendor/src/agentBehaviorRulesAudit.mjs +168 -0
- package/vendor/src/agentBehaviorRulesBundle.mjs +144 -0
- package/vendor/src/agentRunApi.mjs +136 -0
- package/vendor/src/agentToolLoopGuard.mjs +88 -0
- package/vendor/src/agentWorkerClaudeProvider.mjs +288 -0
- package/vendor/src/agentWorkerCursorSdkProvider.mjs +156 -0
- package/vendor/src/agentWorkerLiveLoop.mjs +455 -0
- package/vendor/src/agentWorkerLocalCliProvider.mjs +217 -0
- package/vendor/src/agentWorkerLocalRunner.mjs +246 -0
- package/vendor/src/agentWorkerOpenAiProvider.mjs +459 -0
- package/vendor/src/analyticsPanelProjection.mjs +212 -0
- package/vendor/src/analyticsRecordStore.mjs +165 -0
- package/vendor/src/analyticsRecordWorkItems.mjs +104 -0
- package/vendor/src/architectureL1Canon.mjs +419 -0
- package/vendor/src/architectureLayout.mjs +229 -0
- package/vendor/src/architectureSnapshot.mjs +490 -0
- package/vendor/src/architectureViewsProjection.mjs +116 -0
- package/vendor/src/atomInspector.mjs +253 -0
- package/vendor/src/atomInspectorApi.mjs +130 -0
- package/vendor/src/auditGapMatrixRefresh.mjs +121 -0
- package/vendor/src/backlogSchemaLint.mjs +176 -0
- package/vendor/src/blockedOnebaseGoPreflightEval.mjs +100 -0
- package/vendor/src/bracketIrTraceSignal.mjs +93 -0
- package/vendor/src/bvcAtomParser.mjs +210 -0
- package/vendor/src/bvcDialectRegistry.mjs +86 -0
- package/vendor/src/bvcFileFormat.mjs +218 -0
- package/vendor/src/bvcFormatCli.mjs +55 -0
- package/vendor/src/bvcLintCli.mjs +48 -0
- package/vendor/src/bvcNewWritePolicy.mjs +70 -0
- package/vendor/src/charterPreflightPromoteGate.mjs +194 -0
- package/vendor/src/claimNoEligibleEval.mjs +205 -0
- package/vendor/src/closingAnalysisSuggest.mjs +59 -0
- package/vendor/src/codeGapAnalyzer.mjs +308 -0
- package/vendor/src/codeGapBacklogFeeder.mjs +82 -0
- package/vendor/src/codeGapDraftIntakeApi.mjs +307 -0
- package/vendor/src/codeGapOperatorProjection.mjs +60 -0
- package/vendor/src/codeSyntaxHighlight.mjs +123 -0
- package/vendor/src/codegenEvidence.mjs +187 -0
- package/vendor/src/compilerRoundTripCli.mjs +164 -0
- package/vendor/src/dagreGraphLayout.mjs +78 -0
- package/vendor/src/draftIntakePromotionRules.mjs +205 -0
- package/vendor/src/epicWorkScope.mjs +85 -0
- package/vendor/src/evalLiveLlmEnv.mjs +63 -0
- package/vendor/src/evidenceReadModel.mjs +167 -0
- package/vendor/src/gfsOverlayProjectPassport.mjs +235 -0
- package/vendor/src/globalStepPathToBvcReferences.mjs +196 -0
- package/vendor/src/goldenPath.mjs +69 -0
- package/vendor/src/graphCanvasLayout.mjs +464 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasMinimap.ts +261 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasSvgEdges.ts +259 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasTheme.css +152 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCardNode.ts +328 -0
- package/vendor/src/graphCanvasLitFlow/client/mountGraphCanvasLitFlow.ts +322 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeLabels.mjs +58 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeRouter.mjs +142 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasLayoutProfile.mjs +32 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasNodeMetrics.mjs +45 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasProjection.mjs +115 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasProjectionToFlow.mjs +133 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasTraversal.mjs +77 -0
- package/vendor/src/graphCanvasLitFlow/layoutIntentRoadmapWorkStack.mjs +73 -0
- package/vendor/src/graphCanvasLitFlow/resolveGraphCanvasOverlaps.mjs +77 -0
- package/vendor/src/graphRagContextSlice.mjs +461 -0
- package/vendor/src/gvmVerifyWorkerGate.mjs +95 -0
- package/vendor/src/homeSnapshotApi.mjs +131 -0
- package/vendor/src/homeSnapshotProjection.mjs +275 -0
- package/vendor/src/inboxEventStream.mjs +140 -0
- package/vendor/src/intentComposerApi.mjs +245 -0
- package/vendor/src/intentGraphGbcSliceBoundary.mjs +258 -0
- package/vendor/src/intentGraphProjection.mjs +208 -0
- package/vendor/src/intentHierarchy.mjs +241 -0
- package/vendor/src/intentNodeLint.mjs +107 -0
- package/vendor/src/intentNodeRuntime.mjs +185 -0
- package/vendor/src/intentRoadmapCanvas.mjs +393 -0
- package/vendor/src/intentRoadmapEpicProjection.mjs +122 -0
- package/vendor/src/intentRoadmapMermaid.mjs +165 -0
- package/vendor/src/intentRoadmapProjection.mjs +85 -0
- package/vendor/src/intentTreeLint.mjs +114 -0
- package/vendor/src/intentTreeMigration.mjs +150 -0
- package/vendor/src/intentTreeWorkItems.mjs +227 -0
- package/vendor/src/kanbanBoardProjection.mjs +58 -0
- package/vendor/src/languageAdapterRegistry.mjs +180 -0
- package/vendor/src/languageAdapters/goAdapter.mjs +62 -0
- package/vendor/src/languageAdapters/jsTsAdapter.mjs +60 -0
- package/vendor/src/languageAdapters/jsonYamlAdapter.mjs +103 -0
- package/vendor/src/languageAdapters/onebaseOsAdapter.mjs +55 -0
- package/vendor/src/languageAdapters/plaintextAdapter.mjs +36 -0
- package/vendor/src/languageAdapters/shared.mjs +68 -0
- package/vendor/src/languageAdapters/stepAdapter.mjs +81 -0
- package/vendor/src/lintPlanWorkAlignment.mjs +136 -0
- package/vendor/src/loopHintRepeatToolEval.mjs +153 -0
- package/vendor/src/lowcodeScaffoldCli.mjs +386 -0
- package/vendor/src/markdownDocumentRender.mjs +208 -0
- package/vendor/src/memoryPanelProjection.mjs +116 -0
- package/vendor/src/memoryRecordWriter.mjs +243 -0
- package/vendor/src/memoryWorkerSlice.mjs +238 -0
- package/vendor/src/migrateStepToBvc.mjs +133 -0
- package/vendor/src/missionControlServerHandlers.mjs +195 -0
- package/vendor/src/missionControlUiClient.mjs +278 -0
- package/vendor/src/onebaseCliCapabilityProbe.mjs +107 -0
- package/vendor/src/onebaseCliRunner.mjs +145 -0
- package/vendor/src/onebaseGrossProfitStaticVerify.mjs +98 -0
- package/vendor/src/onebaseParityEvidenceSync.mjs +88 -0
- package/vendor/src/onebasePvrgGraphNodes.mjs +257 -0
- package/vendor/src/onebaseRestEvidenceAdapter.mjs +216 -0
- package/vendor/src/onebaseVectorDslCodegenReadiness.mjs +137 -0
- package/vendor/src/onebaseWorkItemTemplate.mjs +154 -0
- package/vendor/src/onebaseWorkerTools.mjs +586 -0
- package/vendor/src/operatorShellProjection.mjs +102 -0
- package/vendor/src/pipelineProseRender.mjs +180 -0
- package/vendor/src/pipelineStageLint.mjs +118 -0
- package/vendor/src/promptRulesEditorApi.mjs +174 -0
- package/vendor/src/promptRulesProjection.mjs +134 -0
- package/vendor/src/pvrg/bladeAdapter.mjs +40 -0
- package/vendor/src/pvrgTaskScope.mjs +152 -0
- package/vendor/src/releaseGateMatrix.mjs +188 -0
- package/vendor/src/schematicView.mjs +305 -0
- package/vendor/src/seedAnalyticsRecord.mjs +217 -0
- package/vendor/src/semanticSearchBm25.mjs +103 -0
- package/vendor/src/semanticSearchExcerpts.mjs +68 -0
- package/vendor/src/semanticSearchTfidfVector.mjs +86 -0
- package/vendor/src/semanticSearchWorkflow.mjs +366 -0
- package/vendor/src/stepAtomFormatter.mjs +413 -0
- package/vendor/src/stepGraphSlice.mjs +318 -0
- package/vendor/src/ui/atoms/badge.mjs +40 -0
- package/vendor/src/ui/atoms/badgeClient.mjs +32 -0
- package/vendor/src/ui/atoms/button.mjs +114 -0
- package/vendor/src/ui/atoms/buttonClient.mjs +49 -0
- package/vendor/src/ui/atoms/icon.mjs +23 -0
- package/vendor/src/ui/atoms/input.mjs +38 -0
- package/vendor/src/ui/atoms/modal.mjs +44 -0
- package/vendor/src/ui/atoms/select.mjs +98 -0
- package/vendor/src/ui/backlogShellButtons.mjs +238 -0
- package/vendor/src/ui/htmlEscape.mjs +11 -0
- package/vendor/src/ui/molecules/rating.mjs +48 -0
- package/vendor/src/ui/molecules/tabs.mjs +70 -0
- package/vendor/src/ui/organisms/modal.mjs +1 -0
- package/vendor/src/ui/pages/uiKitPage.mjs +147 -0
- package/vendor/src/ui/workItemStatusTone.mjs +36 -0
- package/vendor/src/unifiedLinkageProjection.mjs +264 -0
- package/vendor/src/verificationLoop.mjs +206 -0
- package/vendor/src/workGraphBacklogPersist.mjs +234 -0
- package/vendor/src/workGraphBacklogUiServer.mjs +9192 -0
- package/vendor/src/workGraphBoundedTargetFileRead.mjs +178 -0
- package/vendor/src/workGraphCycleSlice.mjs +184 -0
- package/vendor/src/workGraphDaemonTick.mjs +307 -0
- package/vendor/src/workGraphDaemonWatch.mjs +157 -0
- package/vendor/src/workGraphEngineRoot.mjs +136 -0
- package/vendor/src/workGraphInstallLayout.mjs +65 -0
- package/vendor/src/workGraphLlmUsefulnessEval.mjs +611 -0
- package/vendor/src/workGraphPhasePromoteReadyQueue.mjs +159 -0
- package/vendor/src/workGraphProjectHost.mjs +149 -0
- package/vendor/src/workGraphProjectInit.mjs +392 -0
- package/vendor/src/workGraphPromoteReadyApi.mjs +115 -0
- package/vendor/src/workGraphRecoveryPolicy.mjs +124 -0
- package/vendor/src/workGraphRunnerQueueProjection.mjs +187 -0
- package/vendor/src/workGraphRuntime.mjs +1008 -0
- package/vendor/src/workGraphToolSurfaceAudit.mjs +372 -0
- package/vendor/src/workGraphToolTransportRuntime.mjs +195 -0
- package/vendor/src/workGraphWorkerProvider.mjs +600 -0
- package/vendor/src/workItemBvcQuality.mjs +262 -0
- package/vendor/src/workItemCreateAnalysis.mjs +157 -0
- package/vendor/src/workItemDecisionPipeline.mjs +278 -0
- package/vendor/src/workItemEpicCascade.mjs +176 -0
- package/vendor/src/workItemExecutionGate.mjs +78 -0
- package/vendor/src/workItemHierarchy.mjs +226 -0
- package/vendor/src/workItemProseLint.mjs +133 -0
- package/vendor/src/workItemTextRusify.mjs +794 -0
- package/vendor/src/workItemTraceEnvelope.mjs +158 -0
- package/vendor/src/workItemUiReferences.mjs +272 -0
- package/vendor/src/workflowEpicGrouping.mjs +67 -0
- package/vendor/src/workflowTreeProjection.mjs +53 -0
- package/vendor/src/workspaceRegistry.mjs +150 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { buildEvidenceReadModelForTask } from './evidenceReadModel.mjs';
|
|
2
|
+
import { buildMemoryRecordCandidatesFromItems } from './memoryRecordWriter.mjs';
|
|
3
|
+
import { buildMemoryWorkerSliceForTask } from './memoryWorkerSlice.mjs';
|
|
4
|
+
import { buildPvrgTaskScopeSlice } from './pvrgTaskScope.mjs';
|
|
5
|
+
|
|
6
|
+
export const GRAPH_RAG_SLICE_SCHEMA = 'pvrg.graph_rag.slice.v1';
|
|
7
|
+
export const GRAPH_RAG_CONTEXT_SCHEMA = 'pvrg.graph_rag.context.v1';
|
|
8
|
+
|
|
9
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
10
|
+
|
|
11
|
+
function stableUnique(values) {
|
|
12
|
+
return [...new Set(values)].sort(compareText);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function workItemNode(item) {
|
|
16
|
+
return {
|
|
17
|
+
id: `work:${item.id}`,
|
|
18
|
+
kind: 'work_item',
|
|
19
|
+
label: item.title,
|
|
20
|
+
summary: String(item.nextAction || item.title || item.id).trim(),
|
|
21
|
+
sourceRefs: [`work:${item.id}`],
|
|
22
|
+
confidence: 'high',
|
|
23
|
+
sourceField: 'workgraph.snapshot.v1',
|
|
24
|
+
data: {
|
|
25
|
+
id: item.id,
|
|
26
|
+
title: item.title,
|
|
27
|
+
status: item.status,
|
|
28
|
+
ownerRole: item.ownerRole ?? '',
|
|
29
|
+
department: item.department ?? '',
|
|
30
|
+
priority: item.priority ?? '',
|
|
31
|
+
risk: item.risk ?? '',
|
|
32
|
+
dependsOn: [...(item.dependsOn ?? [])].sort(compareText),
|
|
33
|
+
targetFiles: [...(item.targetFiles ?? [])].sort(compareText),
|
|
34
|
+
traceStatus: item.traceStatus ?? '',
|
|
35
|
+
blocker: item.blocker ?? '',
|
|
36
|
+
nextAction: item.nextAction ?? '',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fileArtifactNode(path, options = {}) {
|
|
42
|
+
return {
|
|
43
|
+
id: `file:${path}`,
|
|
44
|
+
kind: 'file_artifact',
|
|
45
|
+
label: path,
|
|
46
|
+
summary: options.summary ?? path,
|
|
47
|
+
sourceRefs: [`file:${path}`],
|
|
48
|
+
confidence: options.confidence ?? 'medium',
|
|
49
|
+
sourceField: options.sourceField ?? 'work.target_files',
|
|
50
|
+
data: {
|
|
51
|
+
path,
|
|
52
|
+
adapterId: options.adapterId ?? '',
|
|
53
|
+
languageId: options.languageId ?? '',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function evidenceNode(record) {
|
|
59
|
+
return {
|
|
60
|
+
id: `evidence:${record.id}`,
|
|
61
|
+
kind: 'evidence',
|
|
62
|
+
label: record.type,
|
|
63
|
+
summary: record.summary,
|
|
64
|
+
sourceRefs: [record.id],
|
|
65
|
+
confidence: record.status === 'failed' ? 'high' : 'medium',
|
|
66
|
+
sourceField: 'evidence-record.v1',
|
|
67
|
+
data: {
|
|
68
|
+
taskId: record.taskId,
|
|
69
|
+
type: record.type,
|
|
70
|
+
status: record.status,
|
|
71
|
+
source: record.source,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function memoryRecordNode(record) {
|
|
77
|
+
return {
|
|
78
|
+
id: `memory:${record.id}`,
|
|
79
|
+
kind: 'memory_record',
|
|
80
|
+
label: record.type,
|
|
81
|
+
summary: record.summary,
|
|
82
|
+
sourceRefs: [record.id],
|
|
83
|
+
confidence: record.confidence ?? 'medium',
|
|
84
|
+
sourceField: 'memory-record.v1',
|
|
85
|
+
data: {
|
|
86
|
+
type: record.type,
|
|
87
|
+
status: record.status,
|
|
88
|
+
sourceWorkItem: record.sourceWorkItem,
|
|
89
|
+
relatedFiles: [...(record.relatedFiles ?? [])].sort(compareText),
|
|
90
|
+
relatedTasks: [...(record.relatedTasks ?? [])].sort(compareText),
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function edge(fromId, toId, relation, sourceField, confidence = 'medium') {
|
|
96
|
+
return {
|
|
97
|
+
id: `${fromId}\0${relation}\0${toId}`,
|
|
98
|
+
from: fromId,
|
|
99
|
+
to: toId,
|
|
100
|
+
relation,
|
|
101
|
+
sourceField,
|
|
102
|
+
confidence,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function collectRelatedWorkIds(items, seedWorkId, scopeSlice) {
|
|
107
|
+
const ids = new Set([seedWorkId]);
|
|
108
|
+
|
|
109
|
+
for (const node of scopeSlice.nodes) {
|
|
110
|
+
if (node.kind === 'work') {
|
|
111
|
+
ids.add(node.id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (const item of items) {
|
|
116
|
+
if (item.dependsOn?.includes(seedWorkId)) {
|
|
117
|
+
ids.add(item.id);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return [...ids].sort(compareText);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function buildGraphRagSlice(options = {}) {
|
|
125
|
+
const items = options.items ?? [];
|
|
126
|
+
const seedWorkId = String(options.seedWorkId ?? '').trim();
|
|
127
|
+
|
|
128
|
+
if (!Array.isArray(items)) {
|
|
129
|
+
throw new TypeError('items must be an array');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (seedWorkId === '') {
|
|
133
|
+
throw new TypeError('seedWorkId must be a non-empty string');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
137
|
+
const seed = itemById.get(seedWorkId);
|
|
138
|
+
if (seed === undefined) {
|
|
139
|
+
throw new Error(`unknown seed work id: ${seedWorkId}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const maxNodes = Number.isInteger(options.maxNodes) && options.maxNodes > 0 ? options.maxNodes : 32;
|
|
143
|
+
const maxDepth = Number.isInteger(options.maxDepth) && options.maxDepth >= 0 ? options.maxDepth : 2;
|
|
144
|
+
const scopeSlice = options.scopeSlice ?? buildPvrgTaskScopeSlice(items, seedWorkId, {
|
|
145
|
+
maxNodes,
|
|
146
|
+
maxDepth,
|
|
147
|
+
linkage: options.linkage,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const relatedWorkIds = collectRelatedWorkIds(items, seedWorkId, scopeSlice);
|
|
151
|
+
const nodeMap = new Map();
|
|
152
|
+
const edgeMap = new Map();
|
|
153
|
+
const warnings = [];
|
|
154
|
+
|
|
155
|
+
for (const workId of relatedWorkIds) {
|
|
156
|
+
const item = itemById.get(workId);
|
|
157
|
+
if (item === undefined) {
|
|
158
|
+
warnings.push(`missing work item for related id ${workId}`);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
nodeMap.set(`work:${item.id}`, workItemNode(item));
|
|
163
|
+
|
|
164
|
+
for (const path of item.targetFiles ?? []) {
|
|
165
|
+
nodeMap.set(`file:${path}`, fileArtifactNode(path));
|
|
166
|
+
edgeMap.set(
|
|
167
|
+
edge(`work:${item.id}`, `file:${path}`, 'targets', 'work.target_files', 'high').id,
|
|
168
|
+
edge(`work:${item.id}`, `file:${path}`, 'targets', 'work.target_files', 'high'),
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
for (const dependencyId of item.dependsOn ?? []) {
|
|
173
|
+
if (itemById.has(dependencyId)) {
|
|
174
|
+
nodeMap.set(`work:${dependencyId}`, workItemNode(itemById.get(dependencyId)));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
edgeMap.set(
|
|
178
|
+
edge(`work:${item.id}`, `work:${dependencyId}`, 'depends_on', 'work.depends_on', 'high').id,
|
|
179
|
+
edge(`work:${item.id}`, `work:${dependencyId}`, 'depends_on', 'work.depends_on', 'high'),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const evidenceModel = buildEvidenceReadModelForTask(items, item.id);
|
|
184
|
+
for (const record of evidenceModel.records) {
|
|
185
|
+
nodeMap.set(`evidence:${record.id}`, evidenceNode(record));
|
|
186
|
+
edgeMap.set(
|
|
187
|
+
edge(`work:${item.id}`, `evidence:${record.id}`, 'has_evidence', 'work.evidence', 'high').id,
|
|
188
|
+
edge(`work:${item.id}`, `evidence:${record.id}`, 'has_evidence', 'work.evidence', 'high'),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const memoryCandidates = options.memoryRecords
|
|
194
|
+
?? buildMemoryRecordCandidatesFromItems(items, options.memoryOptions).records;
|
|
195
|
+
|
|
196
|
+
for (const record of memoryCandidates) {
|
|
197
|
+
const related = record.sourceWorkItem === seedWorkId
|
|
198
|
+
|| record.relatedTasks.includes(seedWorkId)
|
|
199
|
+
|| relatedWorkIds.includes(record.sourceWorkItem);
|
|
200
|
+
|
|
201
|
+
if (!related) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
nodeMap.set(`memory:${record.id}`, memoryRecordNode(record));
|
|
206
|
+
edgeMap.set(
|
|
207
|
+
edge(`work:${record.sourceWorkItem}`, `memory:${record.id}`, 'writes_memory', 'memory-record.v1', record.confidence ?? 'medium').id,
|
|
208
|
+
edge(`work:${record.sourceWorkItem}`, `memory:${record.id}`, 'writes_memory', 'memory-record.v1', record.confidence ?? 'medium'),
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
for (const evidenceId of record.evidenceIds ?? []) {
|
|
212
|
+
edgeMap.set(
|
|
213
|
+
edge(`memory:${record.id}`, `evidence:${evidenceId}`, 'cites_evidence', 'memory.evidenceIds', 'medium').id,
|
|
214
|
+
edge(`memory:${record.id}`, `evidence:${evidenceId}`, 'cites_evidence', 'memory.evidenceIds', 'medium'),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
for (const scopeEdge of scopeSlice.edges) {
|
|
220
|
+
const fromId = `${scopeEdge.from.kind}:${scopeEdge.from.id}`;
|
|
221
|
+
const toId = `${scopeEdge.to.kind}:${scopeEdge.to.id}`;
|
|
222
|
+
const relation = scopeEdge.relation;
|
|
223
|
+
const mapped = edge(fromId, toId, relation, scopeEdge.source ?? 'unified-linkage.projection.v1', scopeEdge.confidence ?? 'medium');
|
|
224
|
+
edgeMap.set(mapped.id, mapped);
|
|
225
|
+
|
|
226
|
+
if (scopeEdge.to.kind === 'file') {
|
|
227
|
+
nodeMap.set(toId, fileArtifactNode(scopeEdge.to.id, {
|
|
228
|
+
sourceField: scopeEdge.source,
|
|
229
|
+
confidence: scopeEdge.confidence,
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const adapterFact of options.adapterProjections?.facts ?? []) {
|
|
235
|
+
const path = adapterFact.filePath;
|
|
236
|
+
if (!path) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
nodeMap.set(`file:${path}`, fileArtifactNode(path, {
|
|
241
|
+
adapterId: adapterFact.adapterId,
|
|
242
|
+
languageId: adapterFact.languageId,
|
|
243
|
+
confidence: adapterFact.confidence,
|
|
244
|
+
sourceField: 'language-adapter.facts',
|
|
245
|
+
summary: `${adapterFact.languageId} (${adapterFact.adapterId})`,
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
for (const run of options.providerRuns ?? []) {
|
|
250
|
+
if (run.taskId !== seedWorkId && !relatedWorkIds.includes(run.taskId)) {
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const runId = `provider_run:${run.runId ?? run.taskId}`;
|
|
255
|
+
nodeMap.set(runId, {
|
|
256
|
+
id: runId,
|
|
257
|
+
kind: 'provider_run',
|
|
258
|
+
label: run.provider ?? 'worker',
|
|
259
|
+
summary: run.summary ?? run.status ?? '',
|
|
260
|
+
sourceRefs: [run.runId ?? run.taskId],
|
|
261
|
+
confidence: 'high',
|
|
262
|
+
sourceField: 'worker-run.summary',
|
|
263
|
+
data: run,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
edgeMap.set(
|
|
267
|
+
edge(runId, `work:${run.taskId}`, 'produced_by_run', 'worker-run.summary', 'high').id,
|
|
268
|
+
edge(runId, `work:${run.taskId}`, 'produced_by_run', 'worker-run.summary', 'high'),
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const nodes = [...nodeMap.values()]
|
|
273
|
+
.sort((left, right) => compareText(left.id, right.id))
|
|
274
|
+
.slice(0, maxNodes);
|
|
275
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
276
|
+
const edges = [...edgeMap.values()]
|
|
277
|
+
.filter((entry) => nodeIds.has(entry.from) && nodeIds.has(entry.to))
|
|
278
|
+
.sort((left, right) => compareText(left.id, right.id));
|
|
279
|
+
|
|
280
|
+
const slice = {
|
|
281
|
+
schema: GRAPH_RAG_SLICE_SCHEMA,
|
|
282
|
+
sliceVersion: '1',
|
|
283
|
+
generatedAt: options.generatedAt ?? null,
|
|
284
|
+
seedWorkId,
|
|
285
|
+
truncated: nodeMap.size > maxNodes || scopeSlice.truncated,
|
|
286
|
+
nodeCount: nodes.length,
|
|
287
|
+
edgeCount: edges.length,
|
|
288
|
+
nodes,
|
|
289
|
+
edges,
|
|
290
|
+
sourceInputs: stableUnique([
|
|
291
|
+
'workgraph.snapshot.v1',
|
|
292
|
+
'pvrg.task-scope.slice.v1',
|
|
293
|
+
'evidence.read-model.v1',
|
|
294
|
+
'memory-record.candidates.v1',
|
|
295
|
+
...(options.adapterProjections ? ['workgraph.language-file-facts.batch.v1'] : []),
|
|
296
|
+
...(options.providerRuns?.length ? ['worker-run.summary'] : []),
|
|
297
|
+
]),
|
|
298
|
+
warnings,
|
|
299
|
+
retrievalProfiles: {
|
|
300
|
+
currentTaskContext: buildCurrentTaskContext(nodes, edges, seedWorkId),
|
|
301
|
+
relatedArtifacts: buildRelatedArtifacts(nodes, edges, seedWorkId),
|
|
302
|
+
blockers: buildBlockers(nodes, seedWorkId),
|
|
303
|
+
previousDecisions: buildPreviousDecisions(nodes),
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
return slice;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function buildCurrentTaskContext(nodes, edges, seedWorkId) {
|
|
311
|
+
const connected = new Set([`work:${seedWorkId}`]);
|
|
312
|
+
for (const entry of edges) {
|
|
313
|
+
if (entry.from === `work:${seedWorkId}` || entry.to === `work:${seedWorkId}`) {
|
|
314
|
+
connected.add(entry.from);
|
|
315
|
+
connected.add(entry.to);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const selected = nodes.filter((node) => connected.has(node.id));
|
|
320
|
+
return summarizeProfile(selected);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function buildRelatedArtifacts(nodes, edges, seedWorkId) {
|
|
324
|
+
const fileIds = new Set(
|
|
325
|
+
edges
|
|
326
|
+
.filter((entry) => entry.from === `work:${seedWorkId}` && entry.relation === 'targets')
|
|
327
|
+
.map((entry) => entry.to),
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
return summarizeProfile(nodes.filter((node) => fileIds.has(node.id) || (node.kind === 'file_artifact' && fileIds.has(node.id))));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function buildBlockers(nodes, seedWorkId) {
|
|
334
|
+
const seed = nodes.find((node) => node.id === `work:${seedWorkId}`);
|
|
335
|
+
const blockedItems = nodes.filter((node) =>
|
|
336
|
+
node.kind === 'work_item'
|
|
337
|
+
&& (node.data.status === 'blocked' || node.data.blocker),
|
|
338
|
+
);
|
|
339
|
+
const failedEvidence = nodes.filter((node) => node.kind === 'evidence' && node.data.status === 'failed');
|
|
340
|
+
|
|
341
|
+
return summarizeProfile([
|
|
342
|
+
...(seed?.data.blocker ? [seed] : []),
|
|
343
|
+
...blockedItems,
|
|
344
|
+
...failedEvidence,
|
|
345
|
+
]);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function buildPreviousDecisions(nodes) {
|
|
349
|
+
return summarizeProfile(nodes.filter((node) => node.kind === 'memory_record'));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function summarizeProfile(nodes) {
|
|
353
|
+
return {
|
|
354
|
+
nodeIds: nodes.map((node) => node.id).sort(compareText),
|
|
355
|
+
workItems: nodes.filter((node) => node.kind === 'work_item').map((node) => ({
|
|
356
|
+
id: node.data.id,
|
|
357
|
+
title: node.data.title,
|
|
358
|
+
status: node.data.status,
|
|
359
|
+
})).sort((left, right) => compareText(left.id, right.id)),
|
|
360
|
+
files: nodes.filter((node) => node.kind === 'file_artifact').map((node) => node.data.path).sort(compareText),
|
|
361
|
+
evidence: nodes.filter((node) => node.kind === 'evidence').map((node) => node.summary).sort(compareText),
|
|
362
|
+
memory: nodes.filter((node) => node.kind === 'memory_record').map((node) => ({
|
|
363
|
+
id: node.id.replace(/^memory:/u, ''),
|
|
364
|
+
type: node.data.type,
|
|
365
|
+
summary: node.summary,
|
|
366
|
+
})).sort((left, right) => compareText(left.id, right.id)),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export function getCurrentTaskContext(slice) {
|
|
371
|
+
return slice?.retrievalProfiles?.currentTaskContext ?? summarizeProfile([]);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export function getRelatedArtifacts(slice) {
|
|
375
|
+
return slice?.retrievalProfiles?.relatedArtifacts ?? summarizeProfile([]);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function getBlockers(slice) {
|
|
379
|
+
return slice?.retrievalProfiles?.blockers ?? summarizeProfile([]);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function getPreviousDecisions(slice) {
|
|
383
|
+
return slice?.retrievalProfiles?.previousDecisions ?? summarizeProfile([]);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
export function buildGraphRagContextForWorkerInput(items, taskId, options = {}) {
|
|
387
|
+
const memoryWorker = buildMemoryWorkerSliceForTask(items, taskId, options.memoryWorker ?? options);
|
|
388
|
+
const slice = buildGraphRagSlice({
|
|
389
|
+
items,
|
|
390
|
+
seedWorkId: taskId,
|
|
391
|
+
memoryRecords: memoryWorker.records,
|
|
392
|
+
...options,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
schema: GRAPH_RAG_CONTEXT_SCHEMA,
|
|
397
|
+
taskId,
|
|
398
|
+
sliceSchema: slice.schema,
|
|
399
|
+
seedWorkId: taskId,
|
|
400
|
+
truncated: slice.truncated || memoryWorker.truncated,
|
|
401
|
+
nodeCount: slice.nodeCount,
|
|
402
|
+
edgeCount: slice.edgeCount,
|
|
403
|
+
sourceInputs: stableUnique([
|
|
404
|
+
...slice.sourceInputs,
|
|
405
|
+
...(memoryWorker.recordCount > 0 ? ['memory-record.worker-slice.v1'] : []),
|
|
406
|
+
]),
|
|
407
|
+
memoryWorker,
|
|
408
|
+
memoryRecordCount: memoryWorker.recordCount,
|
|
409
|
+
memoryTruncated: memoryWorker.truncated,
|
|
410
|
+
currentTaskContext: getCurrentTaskContext(slice),
|
|
411
|
+
relatedArtifacts: getRelatedArtifacts(slice),
|
|
412
|
+
blockers: getBlockers(slice),
|
|
413
|
+
previousDecisions: getPreviousDecisions(slice),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export function buildGraphRagContextSlice(items, taskId, options = {}) {
|
|
418
|
+
return buildGraphRagContextForWorkerInput(items, taskId, options);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function formatGraphRagContextForPrompt(context) {
|
|
422
|
+
if (!context || context.schema !== GRAPH_RAG_CONTEXT_SCHEMA) {
|
|
423
|
+
return '';
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const lines = [
|
|
427
|
+
'Graph RAG context (derived, bounded):',
|
|
428
|
+
`seed=${context.seedWorkId} nodes=${context.nodeCount} edges=${context.edgeCount}${context.truncated ? ' truncated=true' : ''}`,
|
|
429
|
+
];
|
|
430
|
+
|
|
431
|
+
const workLines = context.currentTaskContext.workItems
|
|
432
|
+
.map((item) => `- work ${item.id} [${item.status}] ${item.title}`);
|
|
433
|
+
if (workLines.length > 0) {
|
|
434
|
+
lines.push('Related work items:', ...workLines);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (context.relatedArtifacts.files.length > 0) {
|
|
438
|
+
lines.push('Related files:', ...context.relatedArtifacts.files.map((path) => `- ${path}`));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (context.currentTaskContext.evidence.length > 0) {
|
|
442
|
+
lines.push('Evidence:', ...context.currentTaskContext.evidence.map((entry) => `- ${entry}`));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
if (context.previousDecisions.memory.length > 0) {
|
|
446
|
+
lines.push('Memory decisions:', ...context.previousDecisions.memory.map((entry) => `- [${entry.type}] ${entry.summary}`));
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (context.memoryWorker?.recordCount > 0) {
|
|
450
|
+
lines.push(
|
|
451
|
+
`Memory worker slice: records=${context.memoryRecordCount}${context.memoryTruncated ? ' truncated=true' : ''}`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (context.blockers.workItems.length > 0 || context.blockers.evidence.length > 0) {
|
|
456
|
+
lines.push('Blockers:', ...context.blockers.workItems.map((item) => `- ${item.id}: ${item.status}`));
|
|
457
|
+
lines.push(...context.blockers.evidence.map((entry) => `- evidence: ${entry}`));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return lines.join('\n');
|
|
461
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const GVM_VERIFY_ENV = 'IOHASC_GVM_VERIFY';
|
|
5
|
+
export const GVM_VERIFY_PREFLIGHT_SCHEMA = 'gvm.verify.preflight.v1';
|
|
6
|
+
|
|
7
|
+
export const GVM_WASM_PROBE_PATHS = [
|
|
8
|
+
{
|
|
9
|
+
id: 'module-object-slice-gbc',
|
|
10
|
+
relativePath: '.iohasc/cache/module-object-slice.gbc',
|
|
11
|
+
role: 'wasm-registry-transport',
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'step-file-slice-b64',
|
|
15
|
+
relativePath: '.iohasc/cache/step-file-slice.b64',
|
|
16
|
+
role: 'step-slice-transport',
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
export function isGvmVerifyEnabled(options = {}) {
|
|
21
|
+
const env = options.env ?? process.env;
|
|
22
|
+
return (options.enabled ?? env[GVM_VERIFY_ENV]) === '1';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function probeGvmWasmArtifacts(options = {}) {
|
|
26
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
27
|
+
const probes = [];
|
|
28
|
+
|
|
29
|
+
for (const entry of GVM_WASM_PROBE_PATHS) {
|
|
30
|
+
const absolutePath = resolve(cwd, entry.relativePath);
|
|
31
|
+
const exists = existsSync(absolutePath);
|
|
32
|
+
|
|
33
|
+
probes.push({
|
|
34
|
+
...entry,
|
|
35
|
+
absolutePath,
|
|
36
|
+
exists,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const foundCount = probes.filter((entry) => entry.exists).length;
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
cwd,
|
|
44
|
+
probes,
|
|
45
|
+
foundCount,
|
|
46
|
+
wasmPresent: foundCount > 0,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function runGvmVerifyPreflight(options = {}) {
|
|
51
|
+
if (!isGvmVerifyEnabled(options)) {
|
|
52
|
+
return {
|
|
53
|
+
schema: GVM_VERIFY_PREFLIGHT_SCHEMA,
|
|
54
|
+
status: 'skipped',
|
|
55
|
+
ok: true,
|
|
56
|
+
skipped: true,
|
|
57
|
+
reason: `${GVM_VERIFY_ENV} is not set; GVM verify gate inactive`,
|
|
58
|
+
evidence: [],
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const probe = probeGvmWasmArtifacts(options);
|
|
63
|
+
|
|
64
|
+
if (!probe.wasmPresent) {
|
|
65
|
+
return {
|
|
66
|
+
schema: GVM_VERIFY_PREFLIGHT_SCHEMA,
|
|
67
|
+
status: 'skipped',
|
|
68
|
+
ok: true,
|
|
69
|
+
skipped: true,
|
|
70
|
+
reason: 'GVM wasm/module slice missing; stub preflight only (full verify deferred)',
|
|
71
|
+
probe,
|
|
72
|
+
evidence: [{
|
|
73
|
+
kind: 'gvm_verify',
|
|
74
|
+
source: 'gvm-verify-gate',
|
|
75
|
+
result: 'skipped',
|
|
76
|
+
summary: 'IOHASC_GVM_VERIFY=1 but no wasm artifacts found under .iohasc/cache',
|
|
77
|
+
}],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
schema: GVM_VERIFY_PREFLIGHT_SCHEMA,
|
|
83
|
+
status: 'stub',
|
|
84
|
+
ok: true,
|
|
85
|
+
skipped: false,
|
|
86
|
+
reason: 'GVM verify preflight acknowledged; mandate enforcement deferred to ioHasC GVM Lite',
|
|
87
|
+
probe,
|
|
88
|
+
evidence: [{
|
|
89
|
+
kind: 'gvm_verify',
|
|
90
|
+
source: 'gvm-verify-gate',
|
|
91
|
+
result: 'stub',
|
|
92
|
+
summary: `GVM artifacts present (${probe.foundCount}); full wasm verify not ported to Work Graph worker`,
|
|
93
|
+
}],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { readWorkerRunJournal } from './agentWorkerLiveLoop.mjs';
|
|
5
|
+
import { buildHomeSnapshot } from './homeSnapshotProjection.mjs';
|
|
6
|
+
import {
|
|
7
|
+
applyInboxReadState,
|
|
8
|
+
buildInboxEventsFromSources,
|
|
9
|
+
readInboxReadState,
|
|
10
|
+
} from './inboxEventStream.mjs';
|
|
11
|
+
import { readWorkItemsFromRepo } from './intentTreeWorkItems.mjs';
|
|
12
|
+
import { buildSnapshot } from './workGraphRuntime.mjs';
|
|
13
|
+
import { readDaemonAuditJournal } from './workGraphDaemonTick.mjs';
|
|
14
|
+
import { DONE_STATUSES } from './workGraphCycleSlice.mjs';
|
|
15
|
+
import { resolveSessionEpicId } from './missionControlUiClient.mjs';
|
|
16
|
+
|
|
17
|
+
const ANALYTICS_JOURNAL = 'work/analytics-records.jsonl';
|
|
18
|
+
|
|
19
|
+
function startOfUtcDay(isoDate = new Date()) {
|
|
20
|
+
const date = new Date(isoDate);
|
|
21
|
+
date.setUTCHours(0, 0, 0, 0);
|
|
22
|
+
return date;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function countAgentRunsToday(workerRuns, now = new Date()) {
|
|
26
|
+
const dayStart = startOfUtcDay(now).getTime();
|
|
27
|
+
return workerRuns.filter((run) => {
|
|
28
|
+
const at = Date.parse(run.recordedAt ?? run.startedAt ?? '');
|
|
29
|
+
return Number.isFinite(at) && at >= dayStart;
|
|
30
|
+
}).length;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function computeVerifyPassRate(workerRuns, window = 20) {
|
|
34
|
+
const recent = workerRuns.slice(-window);
|
|
35
|
+
if (recent.length === 0) {
|
|
36
|
+
return { rate: null, windowRuns: 0 };
|
|
37
|
+
}
|
|
38
|
+
const passed = recent.filter((run) => run.ok !== false && run.status !== 'failed').length;
|
|
39
|
+
return { rate: passed / recent.length, windowRuns: recent.length };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function computeThroughputPerDay(items, windowDays = 7, now = new Date()) {
|
|
43
|
+
const windowStart = now.getTime() - windowDays * 86_400_000;
|
|
44
|
+
const doneInWindow = items.filter((item) => {
|
|
45
|
+
if (!DONE_STATUSES.has(item.status)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
const closedAt = item.labels?.['work.decision.at'] ?? item.labels?.['work.analysis.at'];
|
|
49
|
+
const parsed = Date.parse(closedAt ?? '');
|
|
50
|
+
return Number.isFinite(parsed) && parsed >= windowStart;
|
|
51
|
+
}).length;
|
|
52
|
+
return doneInWindow / windowDays;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findDaemonStartedAt(daemonAudit) {
|
|
56
|
+
for (let index = daemonAudit.length - 1; index >= 0; index -= 1) {
|
|
57
|
+
const entry = daemonAudit[index];
|
|
58
|
+
if (entry.event === 'started' || entry.event === 'start' || entry.summary?.includes('started')) {
|
|
59
|
+
return entry.recordedAt ?? null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const last = daemonAudit[daemonAudit.length - 1];
|
|
63
|
+
return last?.recordedAt ?? null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function readAnalyticsRecordsTail(options = {}) {
|
|
67
|
+
const cwd = options.cwd ?? process.cwd();
|
|
68
|
+
const limit = options.limit ?? 20;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const text = await readFile(join(cwd, ANALYTICS_JOURNAL), 'utf8');
|
|
72
|
+
const lines = text.split(/\r?\n/).filter(Boolean);
|
|
73
|
+
const records = [];
|
|
74
|
+
|
|
75
|
+
for (const line of lines.slice(-limit)) {
|
|
76
|
+
try {
|
|
77
|
+
const envelope = JSON.parse(line);
|
|
78
|
+
records.push(envelope.record ?? envelope);
|
|
79
|
+
} catch {
|
|
80
|
+
// skip malformed
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return records.reverse();
|
|
85
|
+
} catch {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function readHomeSnapshot(options = {}) {
|
|
91
|
+
const cwd = options.cwd ?? process.cwd();
|
|
92
|
+
const items = await readWorkItemsFromRepo({ ...options, cwd });
|
|
93
|
+
const workGraphSnapshot = buildSnapshot(items);
|
|
94
|
+
const workerRuns = await readWorkerRunJournal({ cwd, journalPath: options.journalPath });
|
|
95
|
+
const daemonAudit = await readDaemonAuditJournal({ cwd, auditPath: options.auditPath });
|
|
96
|
+
const analyticsRecords = await readAnalyticsRecordsTail({ cwd, limit: 15 });
|
|
97
|
+
const verifyItems = items.filter((item) => item.status === 'verify');
|
|
98
|
+
|
|
99
|
+
const inboxEvents = buildInboxEventsFromSources({
|
|
100
|
+
workerRuns: workerRuns.slice(-30).reverse(),
|
|
101
|
+
daemonAudit: daemonAudit.slice(-20).reverse(),
|
|
102
|
+
analyticsRecords,
|
|
103
|
+
verifyItems: verifyItems.slice(0, 10),
|
|
104
|
+
});
|
|
105
|
+
const readState = await readInboxReadState({ cwd, statePath: options.inboxStatePath });
|
|
106
|
+
const inboxWithRead = applyInboxReadState(inboxEvents, readState);
|
|
107
|
+
const verifyStats = computeVerifyPassRate(workerRuns);
|
|
108
|
+
const latestRun = workerRuns[workerRuns.length - 1];
|
|
109
|
+
const activeRunIds = items
|
|
110
|
+
.filter((item) => ['claimed', 'doing', 'in_progress', 'verify'].includes(item.status))
|
|
111
|
+
.map((item) => item.id);
|
|
112
|
+
const sessionEpicId = resolveSessionEpicId(items, {
|
|
113
|
+
focusTaskId: latestRun?.taskId ?? null,
|
|
114
|
+
activeRunIds,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return buildHomeSnapshot(workGraphSnapshot, {
|
|
118
|
+
ownerRole: options.ownerRole,
|
|
119
|
+
currentCycle: options.cycleId,
|
|
120
|
+
now: options.now,
|
|
121
|
+
inboxPreview: inboxWithRead,
|
|
122
|
+
inboxPreviewLimit: options.inboxPreviewLimit ?? 5,
|
|
123
|
+
verifyPassRate: verifyStats.rate,
|
|
124
|
+
verifyWindowRuns: verifyStats.windowRuns,
|
|
125
|
+
throughputPerDay: computeThroughputPerDay(items),
|
|
126
|
+
throughputWindowDays: 7,
|
|
127
|
+
daemonStartedAt: findDaemonStartedAt(daemonAudit),
|
|
128
|
+
agentRunsToday: countAgentRunsToday(workerRuns, options.now ? new Date(options.now) : new Date()),
|
|
129
|
+
sessionEpicId,
|
|
130
|
+
});
|
|
131
|
+
}
|