@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,258 @@
|
|
|
1
|
+
import { access, constants, readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { RELEASE_GATE_ROWS } from './releaseGateMatrix.mjs';
|
|
5
|
+
|
|
6
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
7
|
+
|
|
8
|
+
export const INTENT_GRAPH_GBC_SLICE_SCHEMA = 'intent.graph.gbc.slice.boundary.v1';
|
|
9
|
+
|
|
10
|
+
export const INTENT_GRAPH_GBC_SOURCE_INPUTS = [
|
|
11
|
+
{
|
|
12
|
+
id: 'intent-index',
|
|
13
|
+
path: 'intent/index.bvc',
|
|
14
|
+
role: 'canonical-index',
|
|
15
|
+
required: true,
|
|
16
|
+
format: 'bvc',
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: 'intent-work-items',
|
|
20
|
+
path: 'intent/**/**/*.work.bvc',
|
|
21
|
+
role: 'canonical-task-mirror',
|
|
22
|
+
required: true,
|
|
23
|
+
format: 'bvc',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'backlog-projection',
|
|
27
|
+
path: 'work/backlog.bvc',
|
|
28
|
+
role: 'migration-compatibility-projection',
|
|
29
|
+
required: false,
|
|
30
|
+
format: 'bvc',
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
export const INTENT_GRAPH_GBC_DERIVED_OUTPUTS = [
|
|
35
|
+
{
|
|
36
|
+
id: 'intent-graph-slice-gbc',
|
|
37
|
+
path: '.iohasc/cache/intent-graph-slice.gbc',
|
|
38
|
+
role: 'optional-derived-binary',
|
|
39
|
+
required: false,
|
|
40
|
+
format: 'flatbuffers',
|
|
41
|
+
status: 'deferred',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: 'intent-graph-slice-b64',
|
|
45
|
+
path: '.iohasc/cache/intent-graph-slice.b64',
|
|
46
|
+
role: 'optional-transport',
|
|
47
|
+
required: false,
|
|
48
|
+
format: 'base64-flatbuffers',
|
|
49
|
+
status: 'deferred',
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'intent-graph-json-cache',
|
|
53
|
+
path: '.iohasc/cache/intent-graph.v1.json',
|
|
54
|
+
role: 'optional-json-cache',
|
|
55
|
+
required: false,
|
|
56
|
+
format: 'json',
|
|
57
|
+
status: 'roadmap',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export const INTENT_GRAPH_GBC_CONSUMERS = [
|
|
62
|
+
{
|
|
63
|
+
id: 'agent-context',
|
|
64
|
+
title: 'Agent context assembly',
|
|
65
|
+
requiredWithoutGbc: true,
|
|
66
|
+
jsonFallback: 'workgraph.snapshot.v1 + intent tree summary',
|
|
67
|
+
gbcOptional: true,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 'pvrg-graph-augment',
|
|
71
|
+
title: 'PVRG / trace-link graph augment',
|
|
72
|
+
requiredWithoutGbc: false,
|
|
73
|
+
jsonFallback: 'unifiedLinkageProjection.v1 + trace envelope',
|
|
74
|
+
gbcOptional: true,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'operator-dashboard',
|
|
78
|
+
title: 'Operator dashboard / architecture map',
|
|
79
|
+
requiredWithoutGbc: true,
|
|
80
|
+
jsonFallback: 'operator-shell.snapshot.v2 + architecture.snapshot.v1',
|
|
81
|
+
gbcOptional: true,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'cross-process-worker',
|
|
85
|
+
title: 'Cross-process worker interchange',
|
|
86
|
+
requiredWithoutGbc: false,
|
|
87
|
+
jsonFallback: 'worker-input.v1 JSON payload',
|
|
88
|
+
gbcOptional: true,
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
export const INTENT_GRAPH_GBC_RETURN_TRIGGERS = [
|
|
93
|
+
'JSON snapshot parse/render or agent context assembly becomes measured bottleneck',
|
|
94
|
+
'Cross-process worker needs schema-stable binary interchange',
|
|
95
|
+
'GFS overlay requires mapped binary slices for intent graph',
|
|
96
|
+
'Dashboard or graph needs zero-copy read path at scale',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const MANDATORY_GATE_IDS = RELEASE_GATE_ROWS.filter((row) => row.blocksRelease).map((row) => row.id);
|
|
100
|
+
|
|
101
|
+
export function buildIntentGraphGbcSliceBoundary() {
|
|
102
|
+
const sourceInputs = [...INTENT_GRAPH_GBC_SOURCE_INPUTS].sort((left, right) => compareText(left.id, right.id));
|
|
103
|
+
const derivedOutputs = [...INTENT_GRAPH_GBC_DERIVED_OUTPUTS].sort((left, right) => compareText(left.id, right.id));
|
|
104
|
+
const consumers = [...INTENT_GRAPH_GBC_CONSUMERS].sort((left, right) => compareText(left.id, right.id));
|
|
105
|
+
const returnTriggers = [...INTENT_GRAPH_GBC_RETURN_TRIGGERS];
|
|
106
|
+
|
|
107
|
+
const mandatoryConsumersWithoutGbc = consumers.filter((entry) => entry.requiredWithoutGbc);
|
|
108
|
+
const optionalDerivedOutputs = derivedOutputs.filter((entry) => !entry.required);
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
schema: INTENT_GRAPH_GBC_SLICE_SCHEMA,
|
|
112
|
+
policy: {
|
|
113
|
+
canonicalStore: 'intent-tree-step',
|
|
114
|
+
interchangeFirst: 'json-snapshot-v1',
|
|
115
|
+
gbcStatus: 'deferred-optional-derived',
|
|
116
|
+
donorReference: '../project/docs/adr-iohasc-gbc-gfs-slice-consumer-matrix.md',
|
|
117
|
+
relatedProtocols: [
|
|
118
|
+
'gbc-gvm-zig-deferral-boundary',
|
|
119
|
+
'flatbuffers-gbc-slice-evaluation',
|
|
120
|
+
'intent-graph-gbc-slice-boundary-v1',
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
sourceInputs,
|
|
124
|
+
derivedOutputs,
|
|
125
|
+
consumers,
|
|
126
|
+
returnTriggers,
|
|
127
|
+
mvpIndependence: {
|
|
128
|
+
blocksMandatoryGate: false,
|
|
129
|
+
mandatoryGateIds: [...MANDATORY_GATE_IDS].sort(compareText),
|
|
130
|
+
mandatoryConsumersCoveredByJson: mandatoryConsumersWithoutGbc.every((entry) => Boolean(entry.jsonFallback)),
|
|
131
|
+
optionalDerivedOutputCount: optionalDerivedOutputs.length,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function evaluateIntentGraphGbcMvpIndependence(boundary = buildIntentGraphGbcSliceBoundary()) {
|
|
137
|
+
const gbcRequiredOutputs = boundary.derivedOutputs.filter((entry) => entry.required);
|
|
138
|
+
const blockingConsumers = boundary.consumers.filter((entry) => !entry.requiredWithoutGbc && !entry.gbcOptional);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
schema: 'intent.graph.gbc.slice.mvp-independence.v1',
|
|
142
|
+
ok: boundary.mvpIndependence.blocksMandatoryGate === false
|
|
143
|
+
&& gbcRequiredOutputs.length === 0
|
|
144
|
+
&& blockingConsumers.length === 0
|
|
145
|
+
&& boundary.mvpIndependence.mandatoryConsumersCoveredByJson === true,
|
|
146
|
+
gbcRequiredOutputCount: gbcRequiredOutputs.length,
|
|
147
|
+
blockingConsumerCount: blockingConsumers.length,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const INTENT_GRAPH_GBC_PILOT_SCHEMA = 'intent.graph.gbc.slice.pilot.v1';
|
|
152
|
+
|
|
153
|
+
export const DONOR_GBC_MODULE_SLICE_CANDIDATES = [
|
|
154
|
+
{
|
|
155
|
+
id: 'module-registry-json',
|
|
156
|
+
relativePath: '.iohasc/cache/iohasc-gbc-module-registry.json',
|
|
157
|
+
format: 'json',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: 'module-object-slice-gbc',
|
|
161
|
+
relativePath: '.iohasc/cache/module-object-slice.gbc',
|
|
162
|
+
format: 'flatbuffers',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'step-file-slice-b64',
|
|
166
|
+
relativePath: '.iohasc/cache/step-file-slice.b64',
|
|
167
|
+
format: 'base64-flatbuffers',
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
export function resolveDonorProjectRoot(options = {}) {
|
|
172
|
+
const env = options.env ?? process.env;
|
|
173
|
+
const raw = options.donorRoot ?? env.WORKGRAPH_IOHASC_DONOR_ROOT ?? '../project';
|
|
174
|
+
return resolve(options.cwd ?? process.cwd(), raw);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function probeDonorGbcModuleSlice(options = {}) {
|
|
178
|
+
const donorRoot = options.donorRoot
|
|
179
|
+
? resolve(options.cwd ?? process.cwd(), options.donorRoot)
|
|
180
|
+
: resolveDonorProjectRoot(options);
|
|
181
|
+
const candidates = [];
|
|
182
|
+
|
|
183
|
+
for (const candidate of DONOR_GBC_MODULE_SLICE_CANDIDATES) {
|
|
184
|
+
const absolutePath = resolve(donorRoot, candidate.relativePath);
|
|
185
|
+
let exists = false;
|
|
186
|
+
try {
|
|
187
|
+
await access(absolutePath, constants.R_OK);
|
|
188
|
+
exists = true;
|
|
189
|
+
} catch {
|
|
190
|
+
exists = false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
candidates.push({
|
|
194
|
+
...candidate,
|
|
195
|
+
absolutePath,
|
|
196
|
+
exists,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const foundCount = candidates.filter((entry) => entry.exists).length;
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
donorRoot,
|
|
204
|
+
candidates,
|
|
205
|
+
foundCount,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async function summarizeModuleRegistryJson(absolutePath) {
|
|
210
|
+
try {
|
|
211
|
+
const text = await readFile(absolutePath, 'utf8');
|
|
212
|
+
const parsed = JSON.parse(text);
|
|
213
|
+
return {
|
|
214
|
+
schemaVersion: parsed.schema_version ?? parsed.schemaVersion ?? null,
|
|
215
|
+
moduleCount: Array.isArray(parsed.modules) ? parsed.modules.length : 0,
|
|
216
|
+
};
|
|
217
|
+
} catch (error) {
|
|
218
|
+
return {
|
|
219
|
+
parseError: error instanceof Error ? error.message : String(error),
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function buildIntentGraphGbcSlicePilotReport(options = {}) {
|
|
225
|
+
const boundary = buildIntentGraphGbcSliceBoundary();
|
|
226
|
+
const cwd = options.cwd ?? process.cwd();
|
|
227
|
+
let probe = await probeDonorGbcModuleSlice({ ...options, cwd });
|
|
228
|
+
let source = 'donor';
|
|
229
|
+
|
|
230
|
+
if (probe.foundCount === 0 && options.sampleRoot) {
|
|
231
|
+
probe = await probeDonorGbcModuleSlice({
|
|
232
|
+
...options,
|
|
233
|
+
cwd,
|
|
234
|
+
donorRoot: options.sampleRoot,
|
|
235
|
+
});
|
|
236
|
+
source = probe.foundCount > 0 ? 'sample-fixture' : 'skip';
|
|
237
|
+
} else if (probe.foundCount === 0) {
|
|
238
|
+
source = 'skip';
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const registryCandidate = probe.candidates.find(
|
|
242
|
+
(entry) => entry.id === 'module-registry-json' && entry.exists,
|
|
243
|
+
);
|
|
244
|
+
const moduleRegistrySummary = registryCandidate
|
|
245
|
+
? await summarizeModuleRegistryJson(registryCandidate.absolutePath)
|
|
246
|
+
: null;
|
|
247
|
+
|
|
248
|
+
return {
|
|
249
|
+
schema: INTENT_GRAPH_GBC_PILOT_SCHEMA,
|
|
250
|
+
status: source === 'skip' ? 'skip' : (source === 'sample-fixture' ? 'sample' : 'donor'),
|
|
251
|
+
source,
|
|
252
|
+
boundarySchema: boundary.schema,
|
|
253
|
+
mvpIndependence: evaluateIntentGraphGbcMvpIndependence(boundary),
|
|
254
|
+
probe,
|
|
255
|
+
moduleRegistrySummary,
|
|
256
|
+
blocksMandatoryGate: false,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { attachDerivedWorkItemHierarchy, readWorkItemParentId } from './workItemHierarchy.mjs';
|
|
2
|
+
import {
|
|
3
|
+
attachDerivedIntentNodeChildren,
|
|
4
|
+
readIntentNodeLink,
|
|
5
|
+
} from './intentNodeRuntime.mjs';
|
|
6
|
+
|
|
7
|
+
export const INTENT_GRAPH_PROJECTION_SCHEMA = 'workgraph.intent-graph.projection.v1';
|
|
8
|
+
|
|
9
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
10
|
+
const DONE_STATUSES = new Set(['done', 'verified']);
|
|
11
|
+
|
|
12
|
+
function summarizeIntentNode(node) {
|
|
13
|
+
return {
|
|
14
|
+
id: node.id,
|
|
15
|
+
nodeKind: node.nodeKind,
|
|
16
|
+
parentId: node.parentId,
|
|
17
|
+
title: node.title,
|
|
18
|
+
selected: node.selected === true,
|
|
19
|
+
childIds: [...(node.childIds ?? [])],
|
|
20
|
+
links: { ...(node.links ?? {}) },
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function summarizeWorkItemBranch(item, itemsById) {
|
|
25
|
+
const enriched = itemsById.get(item.id) ?? item;
|
|
26
|
+
const childIds = enriched.childIds ?? [];
|
|
27
|
+
const doneChildCount = childIds.filter((childId) => {
|
|
28
|
+
const child = itemsById.get(childId);
|
|
29
|
+
return child && DONE_STATUSES.has(child.status);
|
|
30
|
+
}).length;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
id: item.id,
|
|
34
|
+
title: item.title ?? item.id,
|
|
35
|
+
status: item.status ?? '',
|
|
36
|
+
parentId: readWorkItemParentId(item),
|
|
37
|
+
childIds,
|
|
38
|
+
doneChildCount,
|
|
39
|
+
childCount: childIds.length,
|
|
40
|
+
intentQuestionId: String(item.labels?.['intent.question_id'] ?? '').trim(),
|
|
41
|
+
intentOptionId: String(item.labels?.['intent.option_id'] ?? '').trim(),
|
|
42
|
+
intentDecisionId: String(item.labels?.['intent.decision_id'] ?? '').trim(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildIntentGraphLinks(nodes) {
|
|
47
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
48
|
+
const links = [];
|
|
49
|
+
|
|
50
|
+
for (const node of nodes) {
|
|
51
|
+
if (node.parentId !== '' && nodeById.has(node.parentId)) {
|
|
52
|
+
const parent = nodeById.get(node.parentId);
|
|
53
|
+
let relation = 'child_of';
|
|
54
|
+
if (parent.nodeKind === 'question' && node.nodeKind === 'option') {
|
|
55
|
+
relation = 'offers_option';
|
|
56
|
+
} else if (parent.nodeKind === 'question' && node.nodeKind === 'decision') {
|
|
57
|
+
relation = 'analyzes';
|
|
58
|
+
} else if (node.nodeKind === 'work_ref') {
|
|
59
|
+
relation = 'creates_work';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
links.push({
|
|
63
|
+
id: `intent:${parent.id}:${relation}:${node.id}`,
|
|
64
|
+
from: { kind: 'intent_node', id: parent.id },
|
|
65
|
+
to: { kind: 'intent_node', id: node.id },
|
|
66
|
+
relation,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const analyticsRef = readIntentNodeLink(node, 'analytics_ref');
|
|
71
|
+
if (analyticsRef !== '') {
|
|
72
|
+
links.push({
|
|
73
|
+
id: `intent:${node.id}:analyzes:${analyticsRef}`,
|
|
74
|
+
from: { kind: 'intent_node', id: node.id },
|
|
75
|
+
to: { kind: 'analytics_record', id: analyticsRef },
|
|
76
|
+
relation: 'analyzes',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const optionId = readIntentNodeLink(node, 'option_id');
|
|
81
|
+
if (node.nodeKind === 'decision' && optionId !== '') {
|
|
82
|
+
links.push({
|
|
83
|
+
id: `intent:${node.id}:selects:${optionId}`,
|
|
84
|
+
from: { kind: 'intent_node', id: node.id },
|
|
85
|
+
to: { kind: 'intent_node', id: optionId },
|
|
86
|
+
relation: 'selects',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const workId = readIntentNodeLink(node, 'work_id');
|
|
91
|
+
if (node.nodeKind === 'work_ref' && workId !== '') {
|
|
92
|
+
links.push({
|
|
93
|
+
id: `intent:${node.id}:creates_work:${workId}`,
|
|
94
|
+
from: { kind: 'intent_node', id: node.id },
|
|
95
|
+
to: { kind: 'work', id: workId },
|
|
96
|
+
relation: 'creates_work',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return links.sort((left, right) => compareText(left.id, right.id));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function buildIntentGraphProjection(intentNodes, workItems = [], options = {}) {
|
|
105
|
+
const nodes = attachDerivedIntentNodeChildren(intentNodes);
|
|
106
|
+
const enrichedWorkItems = attachDerivedWorkItemHierarchy(workItems);
|
|
107
|
+
const itemsById = new Map(enrichedWorkItems.map((item) => [item.id, item]));
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
schema: INTENT_GRAPH_PROJECTION_SCHEMA,
|
|
111
|
+
readOnly: true,
|
|
112
|
+
source: options.source ?? 'intent-tree',
|
|
113
|
+
nodeCount: nodes.length,
|
|
114
|
+
linkCount: 0,
|
|
115
|
+
nodes: nodes.map(summarizeIntentNode),
|
|
116
|
+
links: buildIntentGraphLinks(nodes),
|
|
117
|
+
workItems: enrichedWorkItems.map((item) => summarizeWorkItemBranch(item, itemsById)),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function resolveIntentBranchForAnalyticsRecord(record, projection) {
|
|
122
|
+
const analyticsRef = String(record?.id ?? '').trim();
|
|
123
|
+
if (analyticsRef === '' || !projection) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const nodes = projection.nodes ?? [];
|
|
128
|
+
const question = nodes.find((node) =>
|
|
129
|
+
node.nodeKind === 'question'
|
|
130
|
+
&& (readIntentNodeLink(node, 'analytics_ref') === analyticsRef
|
|
131
|
+
|| node.links?.analytics_ref === analyticsRef),
|
|
132
|
+
) ?? nodes.find((node) => node.nodeKind === 'question' && node.childIds?.length > 0
|
|
133
|
+
&& nodes.some((child) => child.parentId === node.id && readIntentNodeLink(child, 'analytics_ref') === analyticsRef));
|
|
134
|
+
|
|
135
|
+
if (!question) {
|
|
136
|
+
const fallbackQuestion = nodes.find((node) =>
|
|
137
|
+
node.nodeKind === 'question'
|
|
138
|
+
&& String(node.id).includes(analyticsRef.replace(/^analytics:/u, '')),
|
|
139
|
+
);
|
|
140
|
+
if (!fallbackQuestion) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return buildBranchFromQuestion(fallbackQuestion, nodes, projection.workItems ?? []);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return buildBranchFromQuestion(question, nodes, projection.workItems ?? []);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function buildBranchFromQuestion(question, nodes, workItems) {
|
|
150
|
+
const nodeById = new Map(nodes.map((node) => [node.id, node]));
|
|
151
|
+
const decisions = nodes.filter((node) => node.nodeKind === 'decision');
|
|
152
|
+
const selectedDecision = decisions.find((node) => node.selected === true)
|
|
153
|
+
?? decisions.find((node) => readIntentNodeLink(node, 'analytics_ref') !== '')
|
|
154
|
+
?? null;
|
|
155
|
+
const selectedOptionId = selectedDecision
|
|
156
|
+
? readIntentNodeLink(selectedDecision, 'option_id')
|
|
157
|
+
: '';
|
|
158
|
+
|
|
159
|
+
const options = (question.childIds ?? [])
|
|
160
|
+
.map((childId) => nodeById.get(childId))
|
|
161
|
+
.filter((node) => node?.nodeKind === 'option')
|
|
162
|
+
.map((node) => ({
|
|
163
|
+
...summarizeIntentNode(node),
|
|
164
|
+
selected: node.selected === true || node.id === selectedOptionId,
|
|
165
|
+
}));
|
|
166
|
+
|
|
167
|
+
const decisionId = selectedDecision?.id ?? '';
|
|
168
|
+
const branchWorkItems = workItems.filter((item) =>
|
|
169
|
+
item.intentDecisionId === decisionId
|
|
170
|
+
|| (decisionId !== '' && item.intentQuestionId === question.id),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const relatedIntentNodes = nodes.filter((node) =>
|
|
174
|
+
node.id === question.id
|
|
175
|
+
|| node.parentId === question.id
|
|
176
|
+
|| (selectedDecision && (node.id === selectedDecision.id || node.parentId === selectedDecision.id)),
|
|
177
|
+
).map(summarizeIntentNode);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
question: summarizeIntentNode(question),
|
|
181
|
+
options,
|
|
182
|
+
selectedDecision: selectedDecision ? summarizeIntentNode(selectedDecision) : null,
|
|
183
|
+
relatedIntentNodes,
|
|
184
|
+
branchWorkItems,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function attachIntentGraphToAnalyticsRecords(records, intentGraph) {
|
|
189
|
+
if (!Array.isArray(records)) {
|
|
190
|
+
throw new TypeError('records must be an array');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return records.map((record) => {
|
|
194
|
+
const branch = resolveIntentBranchForAnalyticsRecord(record, intentGraph);
|
|
195
|
+
if (!branch) {
|
|
196
|
+
return record;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
...record,
|
|
201
|
+
intentGraph: branch,
|
|
202
|
+
relatedIntentNodes: branch.relatedIntentNodes,
|
|
203
|
+
intentQuestion: branch.question,
|
|
204
|
+
intentOptions: branch.options,
|
|
205
|
+
selectedDecision: branch.selectedDecision,
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { workItemPathInFolder } from './bvcNewWritePolicy.mjs';
|
|
2
|
+
|
|
3
|
+
export const INTENT_HIERARCHY_SCHEMA = 'intent.hierarchy.snapshot.v1';
|
|
4
|
+
|
|
5
|
+
export const INTENT_LAYERS = ['system', 'ui', 'domain', 'research', 'memory', 'plans'];
|
|
6
|
+
|
|
7
|
+
export const INTENT_FEATURES = {
|
|
8
|
+
runtime: 'runtime',
|
|
9
|
+
storage: 'storage',
|
|
10
|
+
dashboard: 'dashboard',
|
|
11
|
+
onebase: 'onebase',
|
|
12
|
+
marketplace: 'marketplace',
|
|
13
|
+
pvrg: 'pvrg',
|
|
14
|
+
memory: 'memory',
|
|
15
|
+
plans: 'plans',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const DOMAIN_LABELS = {
|
|
19
|
+
'system/runtime': 'Системный runtime',
|
|
20
|
+
'system/storage': 'Системное хранилище',
|
|
21
|
+
'ui/dashboard': 'UI и dashboard',
|
|
22
|
+
'domain/onebase': 'OneBase',
|
|
23
|
+
'domain/marketplace': 'Marketplace',
|
|
24
|
+
'research/pvrg': 'PVRG и retrieval',
|
|
25
|
+
'memory/work': 'Project Memory',
|
|
26
|
+
'plans/work': 'Планы',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function classifyIntentFolder(item) {
|
|
30
|
+
const searchable = [
|
|
31
|
+
item.id,
|
|
32
|
+
item.title,
|
|
33
|
+
item.department,
|
|
34
|
+
item.ownerRole,
|
|
35
|
+
item.nextAction,
|
|
36
|
+
...item.targetFiles,
|
|
37
|
+
]
|
|
38
|
+
.join(' ')
|
|
39
|
+
.toLowerCase();
|
|
40
|
+
|
|
41
|
+
if (item.department === 'domain-marketplace' || searchable.includes('intent/domains/marketplace') || /\bmarketplace\b/u.test(searchable)) {
|
|
42
|
+
return 'intent/domains/marketplace/work';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (searchable.includes('onebase') || searchable.includes('../onebase')) {
|
|
46
|
+
return 'intent/domains/onebase/work';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (item.department === 'frontend-ui' || /\b(ui|dashboard|board|graph-viewer|atom-inspector)\b/u.test(searchable)) {
|
|
50
|
+
return 'intent/ui/dashboard/work';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (searchable.includes('memory')) {
|
|
54
|
+
return 'intent/memory/work';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (searchable.includes('pvrg') || searchable.includes('rag') || searchable.includes('retrieval')) {
|
|
58
|
+
return 'intent/research/pvrg/work';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (
|
|
62
|
+
item.department === 'devops-runtime' ||
|
|
63
|
+
searchable.includes('storage') ||
|
|
64
|
+
searchable.includes('serialization') ||
|
|
65
|
+
searchable.includes('sqlite') ||
|
|
66
|
+
searchable.includes('gbc') ||
|
|
67
|
+
searchable.includes('flatbuffers') ||
|
|
68
|
+
searchable.includes('intent tree') ||
|
|
69
|
+
searchable.includes('intent hierarchy')
|
|
70
|
+
) {
|
|
71
|
+
return 'intent/system/storage/work';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return 'intent/system/runtime/work';
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function classifyIntentNode(item) {
|
|
78
|
+
const folder = classifyIntentFolder(item);
|
|
79
|
+
const segments = folder.split('/');
|
|
80
|
+
const layer = segments[1] === 'domains' ? 'domain' : segments[1];
|
|
81
|
+
const feature = segments[2] ?? 'runtime';
|
|
82
|
+
const domain = `${layer}/${feature}`;
|
|
83
|
+
const path = workItemPathInFolder(folder, item.id, { preferCanon: true });
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
workId: item.id,
|
|
87
|
+
title: item.title,
|
|
88
|
+
layer,
|
|
89
|
+
feature,
|
|
90
|
+
domain,
|
|
91
|
+
domainLabel: DOMAIN_LABELS[domain] ?? domain,
|
|
92
|
+
folder,
|
|
93
|
+
path,
|
|
94
|
+
projectedLabels: {
|
|
95
|
+
'intent.domain': layer,
|
|
96
|
+
'intent.feature': feature,
|
|
97
|
+
'intent.path': path,
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function toIntentPath(item) {
|
|
103
|
+
return classifyIntentNode(item).path;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function buildIntentHierarchySnapshot(entries) {
|
|
107
|
+
const nodes = entries.map((entry) => {
|
|
108
|
+
const item = entry.item ?? entry;
|
|
109
|
+
return classifyIntentNode(item);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const domains = new Map();
|
|
113
|
+
for (const node of nodes) {
|
|
114
|
+
const bucket = domains.get(node.domain) ?? {
|
|
115
|
+
id: node.domain,
|
|
116
|
+
label: node.domainLabel,
|
|
117
|
+
layer: node.layer,
|
|
118
|
+
feature: node.feature,
|
|
119
|
+
count: 0,
|
|
120
|
+
workIds: [],
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
bucket.count += 1;
|
|
124
|
+
bucket.workIds.push(node.workId);
|
|
125
|
+
domains.set(node.domain, bucket);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
schema: INTENT_HIERARCHY_SCHEMA,
|
|
130
|
+
count: nodes.length,
|
|
131
|
+
domains: [...domains.values()]
|
|
132
|
+
.map((domain) => ({
|
|
133
|
+
...domain,
|
|
134
|
+
workIds: [...domain.workIds].sort(compareText),
|
|
135
|
+
}))
|
|
136
|
+
.sort((left, right) => compareText(left.id, right.id)),
|
|
137
|
+
nodes: nodes.sort((left, right) => compareText(left.workId, right.workId)),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function resolveIntentPathFromIndex(indexText, workId) {
|
|
142
|
+
if (typeof indexText !== 'string' || typeof workId !== 'string' || workId.trim() === '') {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const pattern = new RegExp(`^\\s*-\\s*${escapeRegExp(workId)}:\\s*(\\S+)\\s*$`, 'mu');
|
|
147
|
+
const match = indexText.match(pattern);
|
|
148
|
+
return match?.[1] ?? null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function buildCatalogPassportAlignment(entries, options = {}) {
|
|
152
|
+
const catalogSource = options.catalogIndexPath ?? '../project/.iohasc/catalog-index.v1.json';
|
|
153
|
+
const indexByWorkId = new Map((options.indexEntries ?? []).map((entry) => [entry.id, entry.path]));
|
|
154
|
+
const nodes = entries.map((entry) => classifyIntentNode(entry.item ?? entry));
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
schema: 'catalog-passport.intent-alignment.v1',
|
|
158
|
+
catalogSource,
|
|
159
|
+
passportField: 'stepGuid | stepTitle | catalog.packageId',
|
|
160
|
+
mappings: nodes.map((node) => {
|
|
161
|
+
const indexedPath = indexByWorkId.get(node.workId) ?? null;
|
|
162
|
+
const intentPath = indexedPath ?? node.path;
|
|
163
|
+
return {
|
|
164
|
+
workId: node.workId,
|
|
165
|
+
intentPath,
|
|
166
|
+
projectedIntentPath: indexedPath && indexedPath !== node.path ? node.path : null,
|
|
167
|
+
intentDomain: node.domain,
|
|
168
|
+
passportLookup: `catalog-index -> stepTitle/work.id match -> ${node.workId}`,
|
|
169
|
+
traceRefs: [`trace.artifact_refs: ${intentPath}`, `trace.links: work:${node.workId}`],
|
|
170
|
+
};
|
|
171
|
+
}),
|
|
172
|
+
rules: [
|
|
173
|
+
'catalog-index остаётся derived/read model из старого ioHasC',
|
|
174
|
+
'canonical identity для rebuild — work.id + intent.path из intent/index.bvc',
|
|
175
|
+
'passport section в .bvc atom сопоставляется через stepTitle/guid, не через file path alone',
|
|
176
|
+
'projectedIntentPath — heuristic drift hint only; index path wins for validation',
|
|
177
|
+
],
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param {ReturnType<typeof buildCatalogPassportAlignment>} alignment
|
|
183
|
+
* @param {{ cwd?: string, indexEntries?: Array<{ id: string, path: string }> }} [options]
|
|
184
|
+
*/
|
|
185
|
+
export async function validateCatalogPassportIntentAlignment(alignment, options = {}) {
|
|
186
|
+
const cwd = options.cwd ?? process.cwd();
|
|
187
|
+
const { access } = await import('node:fs/promises');
|
|
188
|
+
const { join } = await import('node:path');
|
|
189
|
+
const errors = [];
|
|
190
|
+
|
|
191
|
+
if (!alignment?.mappings?.length) {
|
|
192
|
+
errors.push('alignment mappings are empty');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const indexEntries = options.indexEntries ?? [];
|
|
196
|
+
const indexByWorkId = new Map(indexEntries.map((entry) => [entry.id, entry.path]));
|
|
197
|
+
|
|
198
|
+
for (const row of alignment.mappings ?? []) {
|
|
199
|
+
if (!row.workId || !row.intentPath) {
|
|
200
|
+
errors.push(`mapping missing workId or intentPath: ${JSON.stringify(row)}`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const indexedPath = indexByWorkId.get(row.workId);
|
|
205
|
+
const canonicalPath = indexedPath ?? row.intentPath;
|
|
206
|
+
if (indexedPath && row.projectedIntentPath && indexedPath.replace(/\\/g, '/') !== row.projectedIntentPath.replace(/\\/g, '/')) {
|
|
207
|
+
// Drift between index manifest and classifyIntentFolder heuristic — index wins; not a gate failure.
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
await access(join(cwd, canonicalPath));
|
|
212
|
+
} catch {
|
|
213
|
+
errors.push(`intent file missing for ${row.workId}: ${canonicalPath}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!Array.isArray(row.traceRefs) || row.traceRefs.length < 1) {
|
|
217
|
+
errors.push(`trace refs missing for ${row.workId}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
schema: 'catalog-passport.intent-alignment.validation.v1',
|
|
223
|
+
ok: errors.length === 0,
|
|
224
|
+
mappingCount: alignment?.mappings?.length ?? 0,
|
|
225
|
+
errors,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function buildAndValidateCatalogPassportAlignment(entries, options = {}) {
|
|
230
|
+
const alignment = buildCatalogPassportAlignment(entries, options);
|
|
231
|
+
const validation = await validateCatalogPassportIntentAlignment(alignment, options);
|
|
232
|
+
return { alignment, validation };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function escapeRegExp(value) {
|
|
236
|
+
return value.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function compareText(left, right) {
|
|
240
|
+
return left.localeCompare(right, 'en', { sensitivity: 'variant' });
|
|
241
|
+
}
|