@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,262 @@
|
|
|
1
|
+
const BVC_FIELDS = ['basis', 'vector', 'goal'];
|
|
2
|
+
|
|
3
|
+
const SECTION_TITLES = {
|
|
4
|
+
basis: 'Базис',
|
|
5
|
+
vector: 'Вектор',
|
|
6
|
+
goal: 'Цель',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const WORK_ITEM_BVC_LIMITS = {
|
|
10
|
+
basis: { minLines: 2, minChars: 120 },
|
|
11
|
+
vector: { minLines: 2, minChars: 100 },
|
|
12
|
+
goal: { minLines: 1, minChars: 80 },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const DEPARTMENT_LABELS = {
|
|
16
|
+
'agent-platform': 'платформы агента',
|
|
17
|
+
'product-architecture': 'архитектуры продукта',
|
|
18
|
+
'knowledge-publishing': 'графа знаний и retrieval',
|
|
19
|
+
'domain-vertical': 'доменной вертикали',
|
|
20
|
+
'operator-ui': 'operator UI',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'ru', { sensitivity: 'variant' });
|
|
24
|
+
|
|
25
|
+
export function sectionToLines(value) {
|
|
26
|
+
if (Array.isArray(value)) {
|
|
27
|
+
return value.map((line) => String(line).trim()).filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return String(value ?? '')
|
|
31
|
+
.split(/\n+/u)
|
|
32
|
+
.map((line) => line.trim())
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function measureBvcSection(lines) {
|
|
37
|
+
const normalized = sectionToLines(lines);
|
|
38
|
+
const text = normalized.join(' ');
|
|
39
|
+
const sentenceCount = (text.match(/[.!?…](?:\s|$)/gu) ?? []).length || (text.length > 0 ? 1 : 0);
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
lineCount: normalized.length,
|
|
43
|
+
charCount: text.length,
|
|
44
|
+
sentenceCount,
|
|
45
|
+
lines: normalized,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function meetsBvcLimits(lines, limits) {
|
|
50
|
+
const metrics = measureBvcSection(lines);
|
|
51
|
+
return metrics.lineCount >= limits.minLines && metrics.charCount >= limits.minChars;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function evaluateWorkItemBvcQuality(item) {
|
|
55
|
+
const issues = [];
|
|
56
|
+
|
|
57
|
+
for (const field of BVC_FIELDS) {
|
|
58
|
+
const limits = WORK_ITEM_BVC_LIMITS[field];
|
|
59
|
+
const metrics = measureBvcSection(item[field]);
|
|
60
|
+
const workId = item.id ?? '';
|
|
61
|
+
|
|
62
|
+
if (metrics.lineCount < limits.minLines) {
|
|
63
|
+
issues.push({
|
|
64
|
+
severity: 'warning',
|
|
65
|
+
code: `short_${field}_lines`,
|
|
66
|
+
message: `${SECTION_TITLES[field]} для ${workId}: нужно минимум ${limits.minLines} строк(и), сейчас ${metrics.lineCount}`,
|
|
67
|
+
workId,
|
|
68
|
+
field,
|
|
69
|
+
lineCount: metrics.lineCount,
|
|
70
|
+
minLines: limits.minLines,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (metrics.charCount < limits.minChars) {
|
|
75
|
+
issues.push({
|
|
76
|
+
severity: 'warning',
|
|
77
|
+
code: `short_${field}_chars`,
|
|
78
|
+
message: `${SECTION_TITLES[field]} для ${workId}: нужно минимум ${limits.minChars} символов, сейчас ${metrics.charCount}`,
|
|
79
|
+
workId,
|
|
80
|
+
field,
|
|
81
|
+
charCount: metrics.charCount,
|
|
82
|
+
minChars: limits.minChars,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return issues.sort((left, right) => compareText(left.workId, right.workId) || compareText(left.field, right.field));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function uniqueLines(lines) {
|
|
91
|
+
const seen = new Set();
|
|
92
|
+
const output = [];
|
|
93
|
+
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
const trimmed = String(line).trim();
|
|
96
|
+
if (trimmed === '' || seen.has(trimmed)) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
seen.add(trimmed);
|
|
100
|
+
output.push(trimmed);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return output;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function departmentLabel(department) {
|
|
107
|
+
return DEPARTMENT_LABELS[department] ?? 'Work Graph rebuild';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function formatList(items, limit = 4) {
|
|
111
|
+
const list = (items ?? []).filter(Boolean);
|
|
112
|
+
if (list.length === 0) {
|
|
113
|
+
return '';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (list.length <= limit) {
|
|
117
|
+
return list.join(', ');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return `${list.slice(0, limit).join(', ')} и ещё ${list.length - limit}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function ensureSentence(text) {
|
|
124
|
+
const trimmed = String(text).trim();
|
|
125
|
+
if (trimmed === '') {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return /[.!?…]$/u.test(trimmed) ? trimmed : `${trimmed}.`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function enrichBasis(lines, item) {
|
|
133
|
+
const output = [...lines];
|
|
134
|
+
const title = String(item.title ?? item.id ?? 'задача').trim();
|
|
135
|
+
|
|
136
|
+
if (output.length < 2) {
|
|
137
|
+
output.push(
|
|
138
|
+
ensureSentence(
|
|
139
|
+
`Задача «${title}» относится к контуру ${departmentLabel(item.department)} и должна быть понятна агенту без чтения всего backlog`,
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if ((item.dependsOn ?? []).length > 0 && !output.some((line) => line.includes('зависим'))) {
|
|
145
|
+
output.push(
|
|
146
|
+
ensureSentence(
|
|
147
|
+
`Предварительно должны быть закрыты зависимости: ${formatList(item.dependsOn)}`,
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (item.status === 'blocked' && item.blocker && !output.some((line) => line.includes('блок'))) {
|
|
153
|
+
output.push(ensureSentence(`Сейчас задача заблокирована: ${item.blocker}`));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return uniqueLines(output);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function enrichVector(lines, item) {
|
|
160
|
+
const output = [...lines];
|
|
161
|
+
const title = String(item.title ?? item.id ?? 'задача').trim();
|
|
162
|
+
|
|
163
|
+
if (output.length < 2 && item.nextAction) {
|
|
164
|
+
output.push(ensureSentence(`Ближайший шаг исполнения: ${item.nextAction}`));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if ((item.targetFiles ?? []).length > 0) {
|
|
168
|
+
const targetLine = ensureSentence(`Основные артефакты изменений: ${formatList(item.targetFiles)}`);
|
|
169
|
+
if (!output.some((line) => line.includes('артефакты изменений'))) {
|
|
170
|
+
output.push(targetLine);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
for (const check of item.checks ?? []) {
|
|
175
|
+
if (meetsBvcLimits(output, WORK_ITEM_BVC_LIMITS.vector)) {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!output.some((line) => line.includes(check.slice(0, Math.min(24, check.length))))) {
|
|
180
|
+
output.push(ensureSentence(`Проверка результата: ${check}`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!meetsBvcLimits(output, WORK_ITEM_BVC_LIMITS.vector) && output.length < 2) {
|
|
185
|
+
output.push(
|
|
186
|
+
ensureSentence(
|
|
187
|
+
`Работа ведётся итерациями с evidence и обновлением intent tree для «${title}»`,
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (!meetsBvcLimits(output, WORK_ITEM_BVC_LIMITS.vector)) {
|
|
193
|
+
output.push(
|
|
194
|
+
ensureSentence(
|
|
195
|
+
`Изменения должны быть трассируемы через work.id=${item.id} и секцию Свидетельства`,
|
|
196
|
+
),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return uniqueLines(output);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function enrichGoal(lines, item) {
|
|
204
|
+
const output = [...lines];
|
|
205
|
+
const title = String(item.title ?? item.id ?? 'задача').trim();
|
|
206
|
+
|
|
207
|
+
if (output.length === 0) {
|
|
208
|
+
output.push(ensureSentence(`Закрыть задачу «${title}» с проверяемым результатом для оператора и MCP-агента`));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const metrics = measureBvcSection(output);
|
|
212
|
+
if (metrics.charCount < WORK_ITEM_BVC_LIMITS.goal.minChars) {
|
|
213
|
+
if ((item.checks ?? []).length > 0) {
|
|
214
|
+
output.push(ensureSentence(`Готово, когда выполнены проверки: ${item.checks[0]}`));
|
|
215
|
+
} else {
|
|
216
|
+
output.push(
|
|
217
|
+
ensureSentence(
|
|
218
|
+
`Готово, когда изменения покрыты тестами или evidence и work.id=${item.id} можно перевести в done/verified`,
|
|
219
|
+
),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return uniqueLines(output);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function enrichWorkItemBvcDraft(draft, item = {}) {
|
|
228
|
+
const context = {
|
|
229
|
+
...item,
|
|
230
|
+
title: item.title ?? draft.labels?.['work.title'] ?? draft.labels?.['work.id'] ?? draft.name,
|
|
231
|
+
department: item.department ?? draft.labels?.['work.department'] ?? '',
|
|
232
|
+
status: item.status ?? draft.labels?.['work.status'] ?? '',
|
|
233
|
+
nextAction: item.nextAction ?? draft.labels?.['work.next_action'] ?? '',
|
|
234
|
+
dependsOn: item.dependsOn ?? parseList(draft.labels?.['work.depends_on']),
|
|
235
|
+
targetFiles: item.targetFiles ?? parseList(draft.labels?.['work.target_files']),
|
|
236
|
+
checks: item.checks ?? draft.checks ?? [],
|
|
237
|
+
blocker: item.blocker ?? draft.labels?.['work.blocker'] ?? draft.labels?.['work.blocked_reason'] ?? '',
|
|
238
|
+
id: item.id ?? draft.labels?.['work.id'] ?? '',
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
...draft,
|
|
243
|
+
basis: enrichBasis(sectionToLines(draft.basis), context),
|
|
244
|
+
vector: enrichVector(sectionToLines(draft.vector), context),
|
|
245
|
+
goal: enrichGoal(sectionToLines(draft.goal), context),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function needsWorkItemBvcEnrichment(item) {
|
|
250
|
+
return evaluateWorkItemBvcQuality(item).length > 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function parseList(value) {
|
|
254
|
+
if (Array.isArray(value)) {
|
|
255
|
+
return value.map((entry) => String(entry).trim()).filter(Boolean);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return String(value ?? '')
|
|
259
|
+
.split(/\s*,\s*/u)
|
|
260
|
+
.map((entry) => entry.trim())
|
|
261
|
+
.filter(Boolean);
|
|
262
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { PIPELINE_VERDICTS } from './workItemDecisionPipeline.mjs';
|
|
2
|
+
import { formatVerdictRu, VERDICT_RU } from './pipelineProseRender.mjs';
|
|
3
|
+
|
|
4
|
+
export { formatVerdictRu, VERDICT_RU };
|
|
5
|
+
|
|
6
|
+
export function normalizeCreateWorkItemLines(value) {
|
|
7
|
+
if (value === undefined || value === null) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return value.map((line) => String(line).trim()).filter(Boolean);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return String(value)
|
|
16
|
+
.split(/\r?\n/u)
|
|
17
|
+
.map((line) => line.trim())
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
function describeIntakeSource(args) {
|
|
23
|
+
const kind = String(args.intakeSourceKind ?? args.intake?.sourceKind ?? '').trim();
|
|
24
|
+
const ref = String(
|
|
25
|
+
args.intakeSourceRef
|
|
26
|
+
?? args.intake?.sourceRef
|
|
27
|
+
?? args.analyticsRef
|
|
28
|
+
?? '',
|
|
29
|
+
).trim();
|
|
30
|
+
const key = String(args.analyticsKey ?? args.intake?.analyticsKey ?? '').trim();
|
|
31
|
+
|
|
32
|
+
if (kind === 'analytics-record') {
|
|
33
|
+
if (key && ref) {
|
|
34
|
+
return `${key} (${ref})`;
|
|
35
|
+
}
|
|
36
|
+
return ref || key || 'запись аналитики';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (kind !== '' && ref !== '') {
|
|
40
|
+
return `${kind}: ${ref}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (kind !== '') {
|
|
44
|
+
return kind;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return ref;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
export function buildDefaultWorkItemAnalysis(args = {}, verdict = 'useful') {
|
|
52
|
+
const basis = normalizeCreateWorkItemLines(args.basis);
|
|
53
|
+
const vector = normalizeCreateWorkItemLines(args.vector);
|
|
54
|
+
const goal = normalizeCreateWorkItemLines(args.goal);
|
|
55
|
+
const dependsOn = normalizeCreateWorkItemLines(args.dependsOn ?? args.depends_on);
|
|
56
|
+
const intake = describeIntakeSource(args);
|
|
57
|
+
const title = String(args.title ?? args.workId ?? 'задача').trim();
|
|
58
|
+
|
|
59
|
+
const why = basis[0]
|
|
60
|
+
|| (intake ? `Закрывает пробел, выявленный в ${intake.split(' ')[0] ?? 'разборе'}.` : `Задача «${title}» нужна для текущего контура Work Graph.`);
|
|
61
|
+
|
|
62
|
+
const context = basis[1] || vector[0] || '';
|
|
63
|
+
const when = dependsOn.length > 0
|
|
64
|
+
? `После готовности: ${dependsOn.join(', ')}.`
|
|
65
|
+
: 'Можно начинать, когда оператор перевёл задачу в ready.';
|
|
66
|
+
const doneWhen = goal[0] || `Достигнута цель задачи «${title}».`;
|
|
67
|
+
|
|
68
|
+
return [
|
|
69
|
+
'Зачем:',
|
|
70
|
+
why,
|
|
71
|
+
...(context !== '' ? [`Контекст: ${context}`] : []),
|
|
72
|
+
'Когда:',
|
|
73
|
+
when,
|
|
74
|
+
'Готово, когда:',
|
|
75
|
+
doneWhen,
|
|
76
|
+
];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function buildDefaultWorkItemDecision(args = {}, verdict = 'useful') {
|
|
80
|
+
const dependsOn = normalizeCreateWorkItemLines(args.dependsOn ?? args.depends_on);
|
|
81
|
+
const verdictRu = formatVerdictRu(verdict);
|
|
82
|
+
|
|
83
|
+
return [
|
|
84
|
+
'Вердикт:',
|
|
85
|
+
verdictRu,
|
|
86
|
+
dependsOn.length > 0
|
|
87
|
+
? `Брать в работу после закрытия зависимостей: ${dependsOn.join(', ')}.`
|
|
88
|
+
: 'Можно брать в работу после перевода в ready.',
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function isLegacyEnglishAnalysisDecisionTemplate(text) {
|
|
93
|
+
return /actionable после intake-разбор|upstream deps|Scope drift|Verdict:\s*useful|intake \w/u.test(String(text ?? ''));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function buildWorkItemCreateAnalysisDecision(args = {}) {
|
|
97
|
+
if (args.skipAnalysisDecision === true) {
|
|
98
|
+
return {
|
|
99
|
+
analysis: normalizeCreateWorkItemLines(args.analysis),
|
|
100
|
+
decision: normalizeCreateWorkItemLines(args.decision ?? args.decisionNotes),
|
|
101
|
+
pipelineLabels: {},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const requestedVerdict = String(args.decisionVerdict ?? args.verdict ?? 'useful').trim();
|
|
106
|
+
const verdict = PIPELINE_VERDICTS.includes(requestedVerdict) ? requestedVerdict : 'useful';
|
|
107
|
+
|
|
108
|
+
const analysis = normalizeCreateWorkItemLines(args.analysis);
|
|
109
|
+
const decision = normalizeCreateWorkItemLines(args.decision ?? args.decisionNotes);
|
|
110
|
+
|
|
111
|
+
const resolvedAnalysis = analysis.length > 0
|
|
112
|
+
? analysis
|
|
113
|
+
: buildDefaultWorkItemAnalysis(args, verdict);
|
|
114
|
+
const resolvedDecision = decision.length > 0
|
|
115
|
+
? decision
|
|
116
|
+
: buildDefaultWorkItemDecision(args, verdict);
|
|
117
|
+
|
|
118
|
+
const now = new Date().toISOString();
|
|
119
|
+
const pipelineLabels = {
|
|
120
|
+
'work.pipeline_stage': 'decided',
|
|
121
|
+
'work.analysis.at': now,
|
|
122
|
+
'work.analysis.source': String(
|
|
123
|
+
args.analysisSource ?? args.intakeSourceKind ?? 'create_work_item',
|
|
124
|
+
).trim(),
|
|
125
|
+
'work.decision.verdict': verdict,
|
|
126
|
+
'work.decision.at': now,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const intakeRef = String(args.intakeSourceRef ?? args.analyticsRef ?? '').trim();
|
|
130
|
+
if (intakeRef !== '') {
|
|
131
|
+
pipelineLabels['intake.source_ref'] = intakeRef;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const analyticsKey = String(args.analyticsKey ?? '').trim();
|
|
135
|
+
if (analyticsKey !== '') {
|
|
136
|
+
pipelineLabels['intake.analytics_key'] = analyticsKey;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const intentQuestionId = String(args.intentQuestionId ?? args.intent_question_id ?? '').trim();
|
|
140
|
+
const intentOptionId = String(args.intentOptionId ?? args.intent_option_id ?? '').trim();
|
|
141
|
+
const intentDecisionId = String(args.intentDecisionId ?? args.intent_decision_id ?? '').trim();
|
|
142
|
+
if (intentQuestionId !== '') {
|
|
143
|
+
pipelineLabels['intent.question_id'] = intentQuestionId;
|
|
144
|
+
}
|
|
145
|
+
if (intentOptionId !== '') {
|
|
146
|
+
pipelineLabels['intent.option_id'] = intentOptionId;
|
|
147
|
+
}
|
|
148
|
+
if (intentDecisionId !== '') {
|
|
149
|
+
pipelineLabels['intent.decision_id'] = intentDecisionId;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
analysis: resolvedAnalysis,
|
|
154
|
+
decision: resolvedDecision,
|
|
155
|
+
pipelineLabels,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applyAtomInspectorProposalToBacklogFile,
|
|
3
|
+
importStepAtomDraftForWorkItem,
|
|
4
|
+
} from './atomInspector.mjs';
|
|
5
|
+
import { readWorkItemsFromRepo } from './intentTreeWorkItems.mjs';
|
|
6
|
+
|
|
7
|
+
export const WORK_PIPELINE_SCHEMA = 'workgraph.work-item.pipeline.v1';
|
|
8
|
+
export const PIPELINE_STAGES = ['intake', 'analyzed', 'decided', 'ready', 'executing', 'closed'];
|
|
9
|
+
export const PIPELINE_VERDICTS = ['useful', 'harmful', 'defer'];
|
|
10
|
+
|
|
11
|
+
export function normalizePipelineLines(value) {
|
|
12
|
+
if (value === undefined || value === null) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (Array.isArray(value)) {
|
|
17
|
+
return value.map((line) => String(line).trim()).filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return String(value)
|
|
21
|
+
.split(/\r?\n/u)
|
|
22
|
+
.map((line) => line.trim())
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function inferPipelineStage(item) {
|
|
27
|
+
const explicit = String(item?.labels?.['work.pipeline_stage'] ?? '').trim();
|
|
28
|
+
if (explicit !== '' && PIPELINE_STAGES.includes(explicit)) {
|
|
29
|
+
return explicit;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const verdict = String(item?.labels?.['work.decision.verdict'] ?? '').trim();
|
|
33
|
+
if (verdict !== '') {
|
|
34
|
+
return 'decided';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (normalizePipelineLines(item?.analysis).length > 0) {
|
|
38
|
+
return 'analyzed';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (item?.status === 'ready' || item?.status === 'claimed' || item?.status === 'doing' || item?.status === 'verify') {
|
|
42
|
+
return item.status === 'ready' ? 'ready' : 'executing';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (item?.status === 'done' || item?.status === 'blocked') {
|
|
46
|
+
return 'closed';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return 'intake';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function buildWorkItemPipelineView(item) {
|
|
53
|
+
if (!item) {
|
|
54
|
+
throw new TypeError('item is required');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const analysis = normalizePipelineLines(item.analysis);
|
|
58
|
+
const decision = normalizePipelineLines(item.decision);
|
|
59
|
+
const stage = inferPipelineStage(item);
|
|
60
|
+
const verdict = String(item.labels?.['work.decision.verdict'] ?? '').trim();
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
schema: WORK_PIPELINE_SCHEMA,
|
|
64
|
+
workId: item.id,
|
|
65
|
+
stage,
|
|
66
|
+
verdict: PIPELINE_VERDICTS.includes(verdict) ? verdict : null,
|
|
67
|
+
analysis,
|
|
68
|
+
decision,
|
|
69
|
+
analysisAt: item.labels?.['work.analysis.at'] ?? null,
|
|
70
|
+
decisionAt: item.labels?.['work.decision.at'] ?? null,
|
|
71
|
+
status: item.status,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function mergeDraftWithPipelineFields(draft, patch) {
|
|
76
|
+
const next = {
|
|
77
|
+
...draft,
|
|
78
|
+
labels: { ...(draft.labels ?? {}), ...(patch.labels ?? {}) },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (patch.analysis !== undefined) {
|
|
82
|
+
next.analysis = normalizePipelineLines(patch.analysis);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (patch.decision !== undefined) {
|
|
86
|
+
next.decision = normalizePipelineLines(patch.decision);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return next;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function applyVerdictToDraftLabels(draft, verdict, notes) {
|
|
93
|
+
const now = new Date().toISOString();
|
|
94
|
+
const labels = {
|
|
95
|
+
...(draft.labels ?? {}),
|
|
96
|
+
'work.pipeline_stage': 'decided',
|
|
97
|
+
'work.decision.verdict': verdict,
|
|
98
|
+
'work.decision.at': now,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (verdict === 'useful') {
|
|
102
|
+
labels['work.next_action'] = 'promote в ready после depends_on, затем claim/execute';
|
|
103
|
+
if (labels['work.blocker']) {
|
|
104
|
+
delete labels['work.blocker'];
|
|
105
|
+
}
|
|
106
|
+
} else if (verdict === 'harmful') {
|
|
107
|
+
labels['work.status'] = 'blocked';
|
|
108
|
+
labels['work.blocker'] = 'pipeline verdict harmful';
|
|
109
|
+
labels['work.next_action'] = 'review rejection; не исполнять';
|
|
110
|
+
} else if (verdict === 'defer') {
|
|
111
|
+
labels['work.next_action'] = 'defer: дождаться критериев пересмотра из анализа';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return mergeDraftWithPipelineFields(draft, {
|
|
115
|
+
labels,
|
|
116
|
+
decision: notes,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function importWorkItemSource(workId, options) {
|
|
121
|
+
const { readWorkItemAtomFromRepo } = await import('./intentTreeWorkItems.mjs');
|
|
122
|
+
return readWorkItemAtomFromRepo(workId, options);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function applyWorkItemAnalysisToRepo(workId, analysisLines, options = {}) {
|
|
126
|
+
const sourceAtom = await importWorkItemSource(workId, options);
|
|
127
|
+
const source = importStepAtomDraftForWorkItem(sourceAtom.atomText, workId);
|
|
128
|
+
|
|
129
|
+
const now = new Date().toISOString();
|
|
130
|
+
const draft = mergeDraftWithPipelineFields(source.draft, {
|
|
131
|
+
analysis: analysisLines,
|
|
132
|
+
labels: {
|
|
133
|
+
'work.pipeline_stage': 'analyzed',
|
|
134
|
+
'work.analysis.at': now,
|
|
135
|
+
'work.analysis.source': options.analysisSource ?? 'cursor',
|
|
136
|
+
'work.next_action': options.nextAction ?? 'Cursor: record_work_item_decision (useful/harmful/defer)',
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return applyAtomInspectorProposalToBacklogFile({
|
|
141
|
+
...options,
|
|
142
|
+
workId,
|
|
143
|
+
draft,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export async function applyWorkItemDecisionToRepo(workId, verdict, options = {}) {
|
|
148
|
+
if (!PIPELINE_VERDICTS.includes(verdict)) {
|
|
149
|
+
throw new Error(`unsupported pipeline verdict: ${verdict}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const notes = normalizePipelineLines(options.notes);
|
|
153
|
+
if (notes.length === 0) {
|
|
154
|
+
throw new Error('decision notes are required — write them in Cursor and pass via record_work_item_decision');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const sourceAtom = await importWorkItemSource(workId, options);
|
|
158
|
+
const source = importStepAtomDraftForWorkItem(sourceAtom.atomText, workId);
|
|
159
|
+
const draft = applyVerdictToDraftLabels(source.draft, verdict, notes);
|
|
160
|
+
|
|
161
|
+
return applyAtomInspectorProposalToBacklogFile({
|
|
162
|
+
...options,
|
|
163
|
+
workId,
|
|
164
|
+
draft,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function recordWorkItemAnalysis(options = {}) {
|
|
169
|
+
const workId = String(options.workId ?? '').trim();
|
|
170
|
+
if (workId === '') {
|
|
171
|
+
return { schema: WORK_PIPELINE_SCHEMA, ok: false, error: 'work_id_required' };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const analysisText = String(options.analysis ?? '').trim();
|
|
175
|
+
if (analysisText === '') {
|
|
176
|
+
return {
|
|
177
|
+
schema: WORK_PIPELINE_SCHEMA,
|
|
178
|
+
ok: false,
|
|
179
|
+
error: 'analysis_required',
|
|
180
|
+
message: 'analysis is required — produce it in Cursor (your connected LLM) and pass the full text',
|
|
181
|
+
workId,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const items = await readWorkItemsFromRepo(options);
|
|
186
|
+
const item = items.find((entry) => entry.id === workId);
|
|
187
|
+
if (!item) {
|
|
188
|
+
return { schema: WORK_PIPELINE_SCHEMA, ok: false, error: 'work_item_not_found', workId };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const analysisLines = normalizePipelineLines(analysisText);
|
|
192
|
+
const applyResult = await applyWorkItemAnalysisToRepo(workId, analysisLines, {
|
|
193
|
+
...options,
|
|
194
|
+
analysisSource: options.analysisSource ?? 'cursor',
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (!applyResult.ok) {
|
|
198
|
+
return {
|
|
199
|
+
schema: WORK_PIPELINE_SCHEMA,
|
|
200
|
+
ok: false,
|
|
201
|
+
error: applyResult.error ?? 'apply_failed',
|
|
202
|
+
workId,
|
|
203
|
+
applyResult,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const refreshed = (await readWorkItemsFromRepo(options)).find((entry) => entry.id === workId);
|
|
208
|
+
return {
|
|
209
|
+
schema: WORK_PIPELINE_SCHEMA,
|
|
210
|
+
ok: true,
|
|
211
|
+
action: 'record_analysis',
|
|
212
|
+
workId,
|
|
213
|
+
pipeline: buildWorkItemPipelineView(refreshed),
|
|
214
|
+
path: applyResult.path,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function recordWorkItemDecision(options = {}) {
|
|
219
|
+
const workId = String(options.workId ?? '').trim();
|
|
220
|
+
const verdict = String(options.verdict ?? '').trim();
|
|
221
|
+
|
|
222
|
+
if (workId === '') {
|
|
223
|
+
return { schema: WORK_PIPELINE_SCHEMA, ok: false, error: 'work_id_required' };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!PIPELINE_VERDICTS.includes(verdict)) {
|
|
227
|
+
return {
|
|
228
|
+
schema: WORK_PIPELINE_SCHEMA,
|
|
229
|
+
ok: false,
|
|
230
|
+
error: 'invalid_verdict',
|
|
231
|
+
message: 'verdict must be useful | harmful | defer',
|
|
232
|
+
workId,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const notesText = String(options.notes ?? options.decision ?? '').trim();
|
|
237
|
+
if (notesText === '') {
|
|
238
|
+
return {
|
|
239
|
+
schema: WORK_PIPELINE_SCHEMA,
|
|
240
|
+
ok: false,
|
|
241
|
+
error: 'decision_notes_required',
|
|
242
|
+
message: 'notes/decision text is required — justify the verdict in Cursor before recording',
|
|
243
|
+
workId,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const items = await readWorkItemsFromRepo(options);
|
|
248
|
+
const item = items.find((entry) => entry.id === workId);
|
|
249
|
+
if (!item) {
|
|
250
|
+
return { schema: WORK_PIPELINE_SCHEMA, ok: false, error: 'work_item_not_found', workId };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const applyResult = await applyWorkItemDecisionToRepo(workId, verdict, {
|
|
254
|
+
...options,
|
|
255
|
+
notes: notesText,
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
if (!applyResult.ok) {
|
|
259
|
+
return {
|
|
260
|
+
schema: WORK_PIPELINE_SCHEMA,
|
|
261
|
+
ok: false,
|
|
262
|
+
error: applyResult.error ?? 'apply_failed',
|
|
263
|
+
workId,
|
|
264
|
+
applyResult,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const refreshed = (await readWorkItemsFromRepo(options)).find((entry) => entry.id === workId);
|
|
269
|
+
return {
|
|
270
|
+
schema: WORK_PIPELINE_SCHEMA,
|
|
271
|
+
ok: true,
|
|
272
|
+
action: 'record_decision',
|
|
273
|
+
workId,
|
|
274
|
+
verdict,
|
|
275
|
+
pipeline: buildWorkItemPipelineView(refreshed),
|
|
276
|
+
path: applyResult.path,
|
|
277
|
+
};
|
|
278
|
+
}
|