@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,180 @@
|
|
|
1
|
+
export const VERDICT_RU = {
|
|
2
|
+
useful: 'полезно',
|
|
3
|
+
harmful: 'вредно',
|
|
4
|
+
defer: 'отложить',
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export function formatVerdictRu(verdict) {
|
|
8
|
+
const key = String(verdict ?? '').trim();
|
|
9
|
+
return VERDICT_RU[key] ?? key;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function escapeHtml(value) {
|
|
13
|
+
return String(value ?? '')
|
|
14
|
+
.replace(/&/g, '&')
|
|
15
|
+
.replace(/</g, '<')
|
|
16
|
+
.replace(/>/g, '>')
|
|
17
|
+
.replace(/"/g, '"');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PIPELINE_INLINE_CODE_PATTERN = /(`[^`]+`|\bdepends_on=[^\s;,]+|\b(?:protocols|schemas|src|work|intent)\/(?:[\w.-]+\/)*[\w.-]+\.(?:bvc|step|json|mjs|md)\b|\b[\w.-]+\.(?:bvc|step|json|mjs|md)\b)/gu;
|
|
21
|
+
|
|
22
|
+
export function renderPipelineInlineText(text) {
|
|
23
|
+
const tick = String.fromCharCode(96);
|
|
24
|
+
const source = String(text ?? '');
|
|
25
|
+
let result = '';
|
|
26
|
+
let lastIndex = 0;
|
|
27
|
+
let match;
|
|
28
|
+
|
|
29
|
+
PIPELINE_INLINE_CODE_PATTERN.lastIndex = 0;
|
|
30
|
+
while ((match = PIPELINE_INLINE_CODE_PATTERN.exec(source)) !== null) {
|
|
31
|
+
result += escapeHtml(source.slice(lastIndex, match.index));
|
|
32
|
+
const token = match[0];
|
|
33
|
+
if (token.startsWith(tick)) {
|
|
34
|
+
result += `<code class="inline-term">${escapeHtml(token.slice(1, -1))}</code>`;
|
|
35
|
+
} else {
|
|
36
|
+
result += `<code class="inline-term">${escapeHtml(token)}</code>`;
|
|
37
|
+
}
|
|
38
|
+
lastIndex = match.index + token.length;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
result += escapeHtml(source.slice(lastIndex));
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isStandaloneSectionHeading(line) {
|
|
46
|
+
return /^[A-Za-zА-Яа-яЁё0-9][^:\n]{0,80}:$/u.test(line)
|
|
47
|
+
|| /^[A-Za-zА-Яа-яЁё0-9][^:\n]{0,40}\s\/\s[^:\n]{0,40}:$/u.test(line);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function splitInlineSectionLine(line) {
|
|
51
|
+
const match = String(line ?? '').trim().match(/^([A-Za-zА-Яа-яЁё][^:]{0,78}):\s+(.+)$/u);
|
|
52
|
+
if (!match) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
label: match[1].trim(),
|
|
58
|
+
body: match[2].trim(),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const PIPELINE_SECTION_LABELS = [
|
|
63
|
+
'Целесообразность',
|
|
64
|
+
'Контекст и границы',
|
|
65
|
+
'Вердикт',
|
|
66
|
+
'Решение',
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
export function normalizePipelineMultilineText(text) {
|
|
70
|
+
return String(text ?? '')
|
|
71
|
+
.replace(/\\n/g, '\n')
|
|
72
|
+
.replace(/\r\n/g, '\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function expandPipelineSectionLines(text) {
|
|
76
|
+
const normalized = normalizePipelineMultilineText(text);
|
|
77
|
+
/** @type {string[]} */
|
|
78
|
+
const expanded = [];
|
|
79
|
+
|
|
80
|
+
for (const rawLine of normalized.split('\n')) {
|
|
81
|
+
const line = rawLine.trim();
|
|
82
|
+
if (line === '') {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const pattern = new RegExp(
|
|
87
|
+
`(?:^|\\s)(${PIPELINE_SECTION_LABELS.join('|')}):\\s*`,
|
|
88
|
+
'gu',
|
|
89
|
+
);
|
|
90
|
+
const matches = [...line.matchAll(pattern)];
|
|
91
|
+
if (matches.length <= 1) {
|
|
92
|
+
expanded.push(line);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
97
|
+
const match = matches[index];
|
|
98
|
+
const label = match[1];
|
|
99
|
+
const bodyStart = match.index + match[0].length;
|
|
100
|
+
const bodyEnd = index + 1 < matches.length ? matches[index + 1].index : line.length;
|
|
101
|
+
const body = line.slice(bodyStart, bodyEnd).trim();
|
|
102
|
+
expanded.push(`${label}: ${body}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return expanded;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function renderPipelineProse(text) {
|
|
110
|
+
const lines = expandPipelineSectionLines(text);
|
|
111
|
+
const parts = [];
|
|
112
|
+
let listItems = [];
|
|
113
|
+
let listKind = null;
|
|
114
|
+
|
|
115
|
+
function flushList() {
|
|
116
|
+
if (listItems.length === 0) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const listClass = listKind === 'check'
|
|
121
|
+
? 'pipeline-prose-list pipeline-prose-list--check'
|
|
122
|
+
: 'pipeline-prose-list';
|
|
123
|
+
parts.push(`<ul class="${listClass}">${
|
|
124
|
+
listItems.map((entry) => `<li>${renderPipelineInlineText(entry)}</li>`).join('')
|
|
125
|
+
}</ul>`);
|
|
126
|
+
listItems = [];
|
|
127
|
+
listKind = null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const rawLine of lines) {
|
|
131
|
+
const line = rawLine.trim();
|
|
132
|
+
if (line === '') {
|
|
133
|
+
flushList();
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (isStandaloneSectionHeading(line)) {
|
|
138
|
+
flushList();
|
|
139
|
+
parts.push(`<h4 class="pipeline-prose-heading">${escapeHtml(line.slice(0, -1))}</h4>`);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const inlineSection = splitInlineSectionLine(line);
|
|
144
|
+
if (inlineSection) {
|
|
145
|
+
flushList();
|
|
146
|
+
parts.push(`<h4 class="pipeline-prose-heading">${escapeHtml(inlineSection.label)}</h4>`);
|
|
147
|
+
parts.push(`<p class="pipeline-prose-p">${renderPipelineInlineText(inlineSection.body)}</p>`);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (line.startsWith('- ') || line.startsWith('• ')) {
|
|
152
|
+
if (listKind && listKind !== 'bullet') {
|
|
153
|
+
flushList();
|
|
154
|
+
}
|
|
155
|
+
listKind = 'bullet';
|
|
156
|
+
listItems.push(line.slice(2));
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (line.startsWith('✓ ') || line.startsWith('✗ ') || line.startsWith('☑ ') || line.startsWith('☐ ')) {
|
|
161
|
+
if (listKind && listKind !== 'check') {
|
|
162
|
+
flushList();
|
|
163
|
+
}
|
|
164
|
+
listKind = 'check';
|
|
165
|
+
listItems.push(line);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
flushList();
|
|
170
|
+
parts.push(`<p class="pipeline-prose-p">${renderPipelineInlineText(line)}</p>`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
flushList();
|
|
174
|
+
return `<div class="pipeline-prose">${parts.join('')}</div>`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** @deprecated use renderPipelineInlineText */
|
|
178
|
+
export function renderInlineCode(text) {
|
|
179
|
+
return renderPipelineInlineText(text);
|
|
180
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { inferPipelineStage, PIPELINE_VERDICTS } from './workItemDecisionPipeline.mjs';
|
|
2
|
+
|
|
3
|
+
const DONE_STATUSES = new Set(['done', 'verified']);
|
|
4
|
+
|
|
5
|
+
function isOperationalBypass(item) {
|
|
6
|
+
return String(item?.labels?.['work.intake.bypass'] ?? '').trim() === 'operational';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function hasAnalyticsIntake(item) {
|
|
10
|
+
const key = String(item?.labels?.['intake.analytics_key'] ?? '').trim();
|
|
11
|
+
const keys = String(item?.labels?.['intake.analytics_keys'] ?? '').trim();
|
|
12
|
+
return key !== '' || keys !== '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function nonEmptySection(value) {
|
|
16
|
+
return String(value ?? '').trim().length > 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* DoR/DoD checks for decision pipeline stages (AN-22 T3).
|
|
21
|
+
* Errors apply to explicit pipeline_stage mismatches; warnings tolerate legacy items.
|
|
22
|
+
*
|
|
23
|
+
* @param {Array<{ id: string, status: string, ownerRole?: string, analysis?: string, decision?: string, dependsOn?: string[], itemKind?: string, labels?: Record<string, string> }>} items
|
|
24
|
+
*/
|
|
25
|
+
export function lintPipelineStageIssues(items) {
|
|
26
|
+
if (!Array.isArray(items)) {
|
|
27
|
+
throw new TypeError('items must be an array');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const issues = [];
|
|
31
|
+
const doneIds = new Set(items.filter((item) => DONE_STATUSES.has(item.status)).map((item) => item.id));
|
|
32
|
+
|
|
33
|
+
for (const item of items) {
|
|
34
|
+
const bypass = isOperationalBypass(item);
|
|
35
|
+
const explicitStage = String(item.labels?.['work.pipeline_stage'] ?? '').trim();
|
|
36
|
+
const inferredStage = inferPipelineStage(item);
|
|
37
|
+
const stage = explicitStage !== '' ? explicitStage : inferredStage;
|
|
38
|
+
|
|
39
|
+
if (explicitStage === 'analyzed' && !nonEmptySection(item.analysis) && !bypass) {
|
|
40
|
+
issues.push({
|
|
41
|
+
severity: 'error',
|
|
42
|
+
code: 'pipeline_analyzed_without_analysis',
|
|
43
|
+
message: `WorkItem ${item.id} has work.pipeline_stage=analyzed but empty Анализ block`,
|
|
44
|
+
workId: item.id,
|
|
45
|
+
pipelineStage: explicitStage,
|
|
46
|
+
});
|
|
47
|
+
} else if (inferredStage === 'analyzed' && !nonEmptySection(item.analysis) && !bypass) {
|
|
48
|
+
issues.push({
|
|
49
|
+
severity: 'warning',
|
|
50
|
+
code: 'pipeline_inferred_analyzed_without_analysis',
|
|
51
|
+
message: `WorkItem ${item.id} infers analyzed stage but Анализ block is empty`,
|
|
52
|
+
workId: item.id,
|
|
53
|
+
pipelineStage: inferredStage,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (explicitStage === 'decided' || (inferredStage === 'decided' && nonEmptySection(item.decision))) {
|
|
58
|
+
const verdict = String(item.labels?.['work.decision.verdict'] ?? '').trim();
|
|
59
|
+
if (!PIPELINE_VERDICTS.includes(verdict)) {
|
|
60
|
+
issues.push({
|
|
61
|
+
severity: explicitStage === 'decided' ? 'error' : 'warning',
|
|
62
|
+
code: 'pipeline_decided_without_verdict',
|
|
63
|
+
message: `WorkItem ${item.id} at decided stage missing valid work.decision.verdict`,
|
|
64
|
+
workId: item.id,
|
|
65
|
+
pipelineStage: stage,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (item.status === 'ready' || stage === 'ready') {
|
|
71
|
+
if (!nonEmptySection(item.ownerRole) && !bypass) {
|
|
72
|
+
issues.push({
|
|
73
|
+
severity: 'warning',
|
|
74
|
+
code: 'pipeline_ready_without_owner',
|
|
75
|
+
message: `WorkItem ${item.id} is ready but work.owner_role is empty`,
|
|
76
|
+
workId: item.id,
|
|
77
|
+
status: item.status,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const dependencyId of item.dependsOn ?? []) {
|
|
82
|
+
if (!doneIds.has(dependencyId)) {
|
|
83
|
+
issues.push({
|
|
84
|
+
severity: 'error',
|
|
85
|
+
code: 'pipeline_ready_open_dependency',
|
|
86
|
+
message: `WorkItem ${item.id} is ready but dependency "${dependencyId}" is not done/verified`,
|
|
87
|
+
workId: item.id,
|
|
88
|
+
dependencyId,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (!bypass && !hasAnalyticsIntake(item) && stage === 'intake' && item.status === 'backlog') {
|
|
95
|
+
issues.push({
|
|
96
|
+
severity: 'warning',
|
|
97
|
+
code: 'pipeline_intake_without_analytics',
|
|
98
|
+
message: `WorkItem ${item.id} at intake without intake.analytics_key and without work.intake.bypass=operational`,
|
|
99
|
+
workId: item.id,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (String(item.itemKind ?? '') === 'epic' && DONE_STATUSES.has(item.status)) {
|
|
104
|
+
const closingRef = String(item.labels?.['analytics.closing_ref'] ?? '').trim();
|
|
105
|
+
if (closingRef === '') {
|
|
106
|
+
issues.push({
|
|
107
|
+
severity: 'warning',
|
|
108
|
+
code: 'epic_closed_without_closing_analysis',
|
|
109
|
+
message: `Epic ${item.id} is closed but analytics.closing_ref is not set (closing-AN pending)`,
|
|
110
|
+
workId: item.id,
|
|
111
|
+
status: item.status,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return issues;
|
|
118
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname, join, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { buildPromptRulesProjection } from './promptRulesProjection.mjs';
|
|
5
|
+
import { parseStepAtomDrafts } from './stepAtomFormatter.mjs';
|
|
6
|
+
|
|
7
|
+
export const PROMPT_RULES_EDITOR_SCHEMA = 'workgraph.prompt-rules-editor.v1';
|
|
8
|
+
export const PROMPT_RULES_EDITOR_ALLOWED_ROOT = 'rules/agent-behavior/';
|
|
9
|
+
|
|
10
|
+
function normalizeRelativePath(filePath) {
|
|
11
|
+
return String(filePath ?? '').trim().replace(/\\/g, '/');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isPromptRulesEditorPathAllowed(filePath) {
|
|
15
|
+
const normalized = normalizeRelativePath(filePath);
|
|
16
|
+
return normalized.startsWith(PROMPT_RULES_EDITOR_ALLOWED_ROOT)
|
|
17
|
+
&& (normalized.endsWith('.bvc') || normalized.endsWith('.bvc'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function validatePromptRuleSourceText(sourceText) {
|
|
21
|
+
const parsedAtoms = parseStepAtomDrafts(sourceText);
|
|
22
|
+
const promptRules = parsedAtoms.filter((parsed) => parsed.draft.profile === 'prompt_rule');
|
|
23
|
+
const errors = [];
|
|
24
|
+
|
|
25
|
+
if (promptRules.length === 0) {
|
|
26
|
+
errors.push('file must contain at least one prompt_rule atom');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const parsed of parsedAtoms) {
|
|
30
|
+
errors.push(...parsed.errors.map((message) => `${parsed.draft.name}: ${message}`));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
ok: errors.length === 0,
|
|
35
|
+
atomCount: parsedAtoms.length,
|
|
36
|
+
promptRuleCount: promptRules.length,
|
|
37
|
+
errors,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function readPromptRuleSource(options = {}) {
|
|
42
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
43
|
+
const ruleId = String(options.ruleId ?? options.id ?? '').trim();
|
|
44
|
+
if (ruleId === '') {
|
|
45
|
+
throw new TypeError('ruleId is required');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const projection = await buildPromptRulesProjection({
|
|
49
|
+
cwd,
|
|
50
|
+
ruleId,
|
|
51
|
+
roots: ['rules/agent-behavior'],
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const rule = projection.rules.find((entry) => entry.id === ruleId);
|
|
55
|
+
if (!rule) {
|
|
56
|
+
throw new Error(`prompt rule not found in ${PROMPT_RULES_EDITOR_ALLOWED_ROOT}: ${ruleId}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!isPromptRulesEditorPathAllowed(rule.filePath)) {
|
|
60
|
+
throw new Error(`prompt rule path not editable: ${rule.filePath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const absolutePath = resolve(cwd, rule.filePath);
|
|
64
|
+
const sourceText = await readFile(absolutePath, 'utf8');
|
|
65
|
+
const validation = validatePromptRuleSourceText(sourceText);
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
schema: PROMPT_RULES_EDITOR_SCHEMA,
|
|
69
|
+
ruleId,
|
|
70
|
+
filePath: rule.filePath,
|
|
71
|
+
sourceText,
|
|
72
|
+
rule,
|
|
73
|
+
validation,
|
|
74
|
+
editable: true,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function writeTextAtomically(path, text) {
|
|
79
|
+
const tempPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
80
|
+
await writeFile(tempPath, text, 'utf8');
|
|
81
|
+
await rename(tempPath, path);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function savePromptRuleSource(options = {}) {
|
|
85
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
86
|
+
const filePath = normalizeRelativePath(options.filePath);
|
|
87
|
+
const sourceText = String(options.sourceText ?? '');
|
|
88
|
+
|
|
89
|
+
if (!isPromptRulesEditorPathAllowed(filePath)) {
|
|
90
|
+
return {
|
|
91
|
+
schema: 'workgraph.prompt-rules-editor.save.v1',
|
|
92
|
+
ok: false,
|
|
93
|
+
error: 'path_not_allowed',
|
|
94
|
+
filePath,
|
|
95
|
+
persisted: false,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const validation = validatePromptRuleSourceText(sourceText);
|
|
100
|
+
if (!validation.ok) {
|
|
101
|
+
return {
|
|
102
|
+
schema: 'workgraph.prompt-rules-editor.save.v1',
|
|
103
|
+
ok: false,
|
|
104
|
+
error: 'validation_failed',
|
|
105
|
+
filePath,
|
|
106
|
+
persisted: false,
|
|
107
|
+
validation,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const absolutePath = resolve(cwd, filePath);
|
|
112
|
+
if (options.persist !== false) {
|
|
113
|
+
await mkdirSafe(dirname(absolutePath));
|
|
114
|
+
await writeTextAtomically(absolutePath, sourceText.endsWith('\n') ? sourceText : `${sourceText}\n`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
schema: 'workgraph.prompt-rules-editor.save.v1',
|
|
119
|
+
ok: true,
|
|
120
|
+
filePath,
|
|
121
|
+
persisted: options.persist !== false,
|
|
122
|
+
validation,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function mkdirSafe(path) {
|
|
127
|
+
const { mkdir } = await import('node:fs/promises');
|
|
128
|
+
await mkdir(path, { recursive: true });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function parsePromptRulesEditorRequestBody(rawBody) {
|
|
132
|
+
if (rawBody === undefined || rawBody === null) {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (typeof rawBody === 'string' && rawBody.trim() === '') {
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const body = typeof rawBody === 'string' ? JSON.parse(rawBody) : rawBody;
|
|
141
|
+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
|
|
142
|
+
throw new TypeError('prompt rules editor body must be a JSON object');
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return body;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function executePromptRuleSourceRead(options = {}) {
|
|
149
|
+
const body = parsePromptRulesEditorRequestBody(options.body ?? {});
|
|
150
|
+
const ruleId = String(options.ruleId ?? body.ruleId ?? body.id ?? '').trim();
|
|
151
|
+
return readPromptRuleSource({ cwd: options.cwd, ruleId });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function executePromptRuleSourceSave(options = {}) {
|
|
155
|
+
const body = parsePromptRulesEditorRequestBody(options.body ?? {});
|
|
156
|
+
const filePath = normalizeRelativePath(body.filePath);
|
|
157
|
+
const sourceText = String(body.sourceText ?? '');
|
|
158
|
+
|
|
159
|
+
if (filePath === '' || sourceText.trim() === '') {
|
|
160
|
+
return {
|
|
161
|
+
schema: 'workgraph.prompt-rules-editor.save.v1',
|
|
162
|
+
ok: false,
|
|
163
|
+
error: 'file_path_and_source_required',
|
|
164
|
+
persisted: false,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return savePromptRuleSource({
|
|
169
|
+
cwd: options.cwd,
|
|
170
|
+
filePath,
|
|
171
|
+
sourceText,
|
|
172
|
+
persist: body.persist !== false && options.persist !== false,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
2
|
+
import { join, relative, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { parseStepAtomDrafts } from './stepAtomFormatter.mjs';
|
|
5
|
+
|
|
6
|
+
export const PROMPT_RULES_PROJECTION_SCHEMA = 'workgraph.prompt-rules-projection.v1';
|
|
7
|
+
export const PROMPT_RULES_SCAN_ROOTS = ['protocols', 'rules'];
|
|
8
|
+
|
|
9
|
+
export async function collectStepFilePaths(cwd, roots = PROMPT_RULES_SCAN_ROOTS) {
|
|
10
|
+
const paths = [];
|
|
11
|
+
|
|
12
|
+
for (const root of roots) {
|
|
13
|
+
paths.push(...await walkStepFiles(resolve(cwd, root), cwd));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return [...new Set(paths)].sort((left, right) => left.localeCompare(right, 'en'));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function walkStepFiles(directory, cwd) {
|
|
20
|
+
let entries;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
24
|
+
} catch (error) {
|
|
25
|
+
if (error && typeof error === 'object' && error.code === 'ENOENT') {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const paths = [];
|
|
33
|
+
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
const absolutePath = join(directory, entry.name);
|
|
36
|
+
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
paths.push(...await walkStepFiles(absolutePath, cwd));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (entry.isFile() && entry.name.endsWith('.bvc')) {
|
|
43
|
+
paths.push(relative(cwd, absolutePath).replace(/\\/g, '/'));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return paths;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function buildPromptRuleProjectionEntry(filePath, parsed) {
|
|
51
|
+
const { draft, errors, warnings } = parsed;
|
|
52
|
+
const labels = draft.labels ?? {};
|
|
53
|
+
const ruleId = labels['rule.id']
|
|
54
|
+
?? labels['protocol.id']
|
|
55
|
+
?? labels['decision.id']
|
|
56
|
+
?? draft.name;
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
id: ruleId,
|
|
60
|
+
name: draft.name,
|
|
61
|
+
filePath,
|
|
62
|
+
profile: draft.profile,
|
|
63
|
+
basis: joinTextArray(draft.basis),
|
|
64
|
+
vector: joinTextArray(draft.vector),
|
|
65
|
+
goal: joinTextArray(draft.goal),
|
|
66
|
+
checks: joinTextArray(draft.checks ?? []),
|
|
67
|
+
evidence: joinTextArray(draft.evidence ?? []),
|
|
68
|
+
labels,
|
|
69
|
+
traceStatus: labels['trace.status'] ?? 'unknown',
|
|
70
|
+
protocolId: labels['protocol.id'] ?? null,
|
|
71
|
+
decisionId: labels['decision.id'] ?? null,
|
|
72
|
+
validationStatus: errors.length === 0 ? 'valid' : 'invalid',
|
|
73
|
+
validationErrors: errors,
|
|
74
|
+
validationWarnings: warnings,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function buildPromptRulesProjection(options = {}) {
|
|
79
|
+
const cwd = options.cwd ?? process.cwd();
|
|
80
|
+
const roots = options.roots ?? PROMPT_RULES_SCAN_ROOTS;
|
|
81
|
+
const ruleIdFilter = options.ruleId ? String(options.ruleId).trim() : '';
|
|
82
|
+
const filePaths = options.filePaths ?? await collectStepFilePaths(cwd, roots);
|
|
83
|
+
const rules = [];
|
|
84
|
+
|
|
85
|
+
for (const filePath of filePaths) {
|
|
86
|
+
const absolutePath = resolve(cwd, filePath);
|
|
87
|
+
const text = options.fileContents?.[filePath] ?? await readFile(absolutePath, 'utf8');
|
|
88
|
+
const parsedAtoms = parseStepAtomDrafts(text);
|
|
89
|
+
|
|
90
|
+
for (const parsed of parsedAtoms) {
|
|
91
|
+
if (parsed.draft.profile !== 'prompt_rule') {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const entry = buildPromptRuleProjectionEntry(filePath, parsed);
|
|
96
|
+
rules.push(entry);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
rules.sort((left, right) => {
|
|
101
|
+
const byFile = left.filePath.localeCompare(right.filePath, 'en');
|
|
102
|
+
if (byFile !== 0) {
|
|
103
|
+
return byFile;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return left.id.localeCompare(right.id, 'en');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const filteredRules = ruleIdFilter
|
|
110
|
+
? rules.filter((rule) => rule.id === ruleIdFilter)
|
|
111
|
+
: rules;
|
|
112
|
+
|
|
113
|
+
const validCount = rules.filter((rule) => rule.validationStatus === 'valid').length;
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
schema: PROMPT_RULES_PROJECTION_SCHEMA,
|
|
117
|
+
scannedRoots: roots,
|
|
118
|
+
rules: filteredRules,
|
|
119
|
+
summary: {
|
|
120
|
+
total: rules.length,
|
|
121
|
+
valid: validCount,
|
|
122
|
+
invalid: rules.length - validCount,
|
|
123
|
+
filtered: filteredRules.length,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function joinTextArray(values) {
|
|
129
|
+
if (!Array.isArray(values) || values.length === 0) {
|
|
130
|
+
return '';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return values.map((value) => String(value).trim()).filter(Boolean).join('\n');
|
|
134
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PVRG language adapter stub for Blade templates (AN-21 P2).
|
|
3
|
+
* Registers *.blade.php as traceable UI artifacts alongside PHP/docs.
|
|
4
|
+
*/
|
|
5
|
+
export const BLADE_ADAPTER_ID = 'blade';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} filePath
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function isBladeFile(filePath) {
|
|
12
|
+
return String(filePath ?? '').toLowerCase().endsWith('.blade.php');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract x-ui component tags for lightweight trace edges.
|
|
17
|
+
* @param {string} source
|
|
18
|
+
* @returns {string[]}
|
|
19
|
+
*/
|
|
20
|
+
export function extractBladeUiComponentRefs(source) {
|
|
21
|
+
const refs = new Set();
|
|
22
|
+
const pattern = /<x-ui\.([a-z0-9_.-]+)/gi;
|
|
23
|
+
let match = pattern.exec(source);
|
|
24
|
+
while (match) {
|
|
25
|
+
refs.add(`x-ui.${match[1]}`);
|
|
26
|
+
match = pattern.exec(source);
|
|
27
|
+
}
|
|
28
|
+
return [...refs];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {{ filePath: string, content: string }} file
|
|
33
|
+
*/
|
|
34
|
+
export function parseBladeFileForPvrg(file) {
|
|
35
|
+
return {
|
|
36
|
+
adapter: BLADE_ADAPTER_ID,
|
|
37
|
+
path: file.filePath,
|
|
38
|
+
uiComponentRefs: extractBladeUiComponentRefs(file.content ?? ''),
|
|
39
|
+
};
|
|
40
|
+
}
|