@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.
Files changed (194) hide show
  1. package/README.md +31 -0
  2. package/bin/work-graph.mjs +238 -0
  3. package/package.json +38 -0
  4. package/vendor/packages/design-tokens/generated/gripe-dark-default.css +67 -0
  5. package/vendor/packages/design-tokens/generated/marketplace-default.css +67 -0
  6. package/vendor/packages/design-tokens/generated/workgraph-dark.css +67 -0
  7. package/vendor/packages/workgraph-mcp/README.md +28 -0
  8. package/vendor/packages/workgraph-mcp/bin/workgraph-mcp.mjs +21 -0
  9. package/vendor/packages/workgraph-mcp/package.json +37 -0
  10. package/vendor/packages/workgraph-mcp/src/handlers.mjs +761 -0
  11. package/vendor/packages/workgraph-mcp/src/index.mjs +638 -0
  12. package/vendor/packages/workgraph-mcp/src/prompts.mjs +162 -0
  13. package/vendor/public/assets/workgraph-logo.svg +11 -0
  14. package/vendor/public/fonts/GraphikLCG/GraphikLCG-Medium.woff2 +0 -0
  15. package/vendor/public/fonts/GraphikLCG/GraphikLCG-Regular.woff2 +0 -0
  16. package/vendor/public/fonts/GraphikLCG/GraphikLCG-Semibold.woff2 +0 -0
  17. package/vendor/public/fonts/GraphikLCG/stylesheet.css +25 -0
  18. package/vendor/public/graph-canvas-lit-flow.css +154 -0
  19. package/vendor/public/graph-canvas-lit-flow.css.map +7 -0
  20. package/vendor/public/graph-canvas-lit-flow.js +8530 -0
  21. package/vendor/public/graph-canvas-lit-flow.js.map +7 -0
  22. package/vendor/src/agentBehaviorRulesAudit.mjs +168 -0
  23. package/vendor/src/agentBehaviorRulesBundle.mjs +144 -0
  24. package/vendor/src/agentRunApi.mjs +136 -0
  25. package/vendor/src/agentToolLoopGuard.mjs +88 -0
  26. package/vendor/src/agentWorkerClaudeProvider.mjs +288 -0
  27. package/vendor/src/agentWorkerCursorSdkProvider.mjs +156 -0
  28. package/vendor/src/agentWorkerLiveLoop.mjs +455 -0
  29. package/vendor/src/agentWorkerLocalCliProvider.mjs +217 -0
  30. package/vendor/src/agentWorkerLocalRunner.mjs +246 -0
  31. package/vendor/src/agentWorkerOpenAiProvider.mjs +459 -0
  32. package/vendor/src/analyticsPanelProjection.mjs +212 -0
  33. package/vendor/src/analyticsRecordStore.mjs +165 -0
  34. package/vendor/src/analyticsRecordWorkItems.mjs +104 -0
  35. package/vendor/src/architectureL1Canon.mjs +419 -0
  36. package/vendor/src/architectureLayout.mjs +229 -0
  37. package/vendor/src/architectureSnapshot.mjs +490 -0
  38. package/vendor/src/architectureViewsProjection.mjs +116 -0
  39. package/vendor/src/atomInspector.mjs +253 -0
  40. package/vendor/src/atomInspectorApi.mjs +130 -0
  41. package/vendor/src/auditGapMatrixRefresh.mjs +121 -0
  42. package/vendor/src/backlogSchemaLint.mjs +176 -0
  43. package/vendor/src/blockedOnebaseGoPreflightEval.mjs +100 -0
  44. package/vendor/src/bracketIrTraceSignal.mjs +93 -0
  45. package/vendor/src/bvcAtomParser.mjs +210 -0
  46. package/vendor/src/bvcDialectRegistry.mjs +86 -0
  47. package/vendor/src/bvcFileFormat.mjs +218 -0
  48. package/vendor/src/bvcFormatCli.mjs +55 -0
  49. package/vendor/src/bvcLintCli.mjs +48 -0
  50. package/vendor/src/bvcNewWritePolicy.mjs +70 -0
  51. package/vendor/src/charterPreflightPromoteGate.mjs +194 -0
  52. package/vendor/src/claimNoEligibleEval.mjs +205 -0
  53. package/vendor/src/closingAnalysisSuggest.mjs +59 -0
  54. package/vendor/src/codeGapAnalyzer.mjs +308 -0
  55. package/vendor/src/codeGapBacklogFeeder.mjs +82 -0
  56. package/vendor/src/codeGapDraftIntakeApi.mjs +307 -0
  57. package/vendor/src/codeGapOperatorProjection.mjs +60 -0
  58. package/vendor/src/codeSyntaxHighlight.mjs +123 -0
  59. package/vendor/src/codegenEvidence.mjs +187 -0
  60. package/vendor/src/compilerRoundTripCli.mjs +164 -0
  61. package/vendor/src/dagreGraphLayout.mjs +78 -0
  62. package/vendor/src/draftIntakePromotionRules.mjs +205 -0
  63. package/vendor/src/epicWorkScope.mjs +85 -0
  64. package/vendor/src/evalLiveLlmEnv.mjs +63 -0
  65. package/vendor/src/evidenceReadModel.mjs +167 -0
  66. package/vendor/src/gfsOverlayProjectPassport.mjs +235 -0
  67. package/vendor/src/globalStepPathToBvcReferences.mjs +196 -0
  68. package/vendor/src/goldenPath.mjs +69 -0
  69. package/vendor/src/graphCanvasLayout.mjs +464 -0
  70. package/vendor/src/graphCanvasLitFlow/client/graphCanvasMinimap.ts +261 -0
  71. package/vendor/src/graphCanvasLitFlow/client/graphCanvasSvgEdges.ts +259 -0
  72. package/vendor/src/graphCanvasLitFlow/client/graphCanvasTheme.css +152 -0
  73. package/vendor/src/graphCanvasLitFlow/client/graphCardNode.ts +328 -0
  74. package/vendor/src/graphCanvasLitFlow/client/mountGraphCanvasLitFlow.ts +322 -0
  75. package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeLabels.mjs +58 -0
  76. package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeRouter.mjs +142 -0
  77. package/vendor/src/graphCanvasLitFlow/graphCanvasLayoutProfile.mjs +32 -0
  78. package/vendor/src/graphCanvasLitFlow/graphCanvasNodeMetrics.mjs +45 -0
  79. package/vendor/src/graphCanvasLitFlow/graphCanvasProjection.mjs +115 -0
  80. package/vendor/src/graphCanvasLitFlow/graphCanvasProjectionToFlow.mjs +133 -0
  81. package/vendor/src/graphCanvasLitFlow/graphCanvasTraversal.mjs +77 -0
  82. package/vendor/src/graphCanvasLitFlow/layoutIntentRoadmapWorkStack.mjs +73 -0
  83. package/vendor/src/graphCanvasLitFlow/resolveGraphCanvasOverlaps.mjs +77 -0
  84. package/vendor/src/graphRagContextSlice.mjs +461 -0
  85. package/vendor/src/gvmVerifyWorkerGate.mjs +95 -0
  86. package/vendor/src/homeSnapshotApi.mjs +131 -0
  87. package/vendor/src/homeSnapshotProjection.mjs +275 -0
  88. package/vendor/src/inboxEventStream.mjs +140 -0
  89. package/vendor/src/intentComposerApi.mjs +245 -0
  90. package/vendor/src/intentGraphGbcSliceBoundary.mjs +258 -0
  91. package/vendor/src/intentGraphProjection.mjs +208 -0
  92. package/vendor/src/intentHierarchy.mjs +241 -0
  93. package/vendor/src/intentNodeLint.mjs +107 -0
  94. package/vendor/src/intentNodeRuntime.mjs +185 -0
  95. package/vendor/src/intentRoadmapCanvas.mjs +393 -0
  96. package/vendor/src/intentRoadmapEpicProjection.mjs +122 -0
  97. package/vendor/src/intentRoadmapMermaid.mjs +165 -0
  98. package/vendor/src/intentRoadmapProjection.mjs +85 -0
  99. package/vendor/src/intentTreeLint.mjs +114 -0
  100. package/vendor/src/intentTreeMigration.mjs +150 -0
  101. package/vendor/src/intentTreeWorkItems.mjs +227 -0
  102. package/vendor/src/kanbanBoardProjection.mjs +58 -0
  103. package/vendor/src/languageAdapterRegistry.mjs +180 -0
  104. package/vendor/src/languageAdapters/goAdapter.mjs +62 -0
  105. package/vendor/src/languageAdapters/jsTsAdapter.mjs +60 -0
  106. package/vendor/src/languageAdapters/jsonYamlAdapter.mjs +103 -0
  107. package/vendor/src/languageAdapters/onebaseOsAdapter.mjs +55 -0
  108. package/vendor/src/languageAdapters/plaintextAdapter.mjs +36 -0
  109. package/vendor/src/languageAdapters/shared.mjs +68 -0
  110. package/vendor/src/languageAdapters/stepAdapter.mjs +81 -0
  111. package/vendor/src/lintPlanWorkAlignment.mjs +136 -0
  112. package/vendor/src/loopHintRepeatToolEval.mjs +153 -0
  113. package/vendor/src/lowcodeScaffoldCli.mjs +386 -0
  114. package/vendor/src/markdownDocumentRender.mjs +208 -0
  115. package/vendor/src/memoryPanelProjection.mjs +116 -0
  116. package/vendor/src/memoryRecordWriter.mjs +243 -0
  117. package/vendor/src/memoryWorkerSlice.mjs +238 -0
  118. package/vendor/src/migrateStepToBvc.mjs +133 -0
  119. package/vendor/src/missionControlServerHandlers.mjs +195 -0
  120. package/vendor/src/missionControlUiClient.mjs +278 -0
  121. package/vendor/src/onebaseCliCapabilityProbe.mjs +107 -0
  122. package/vendor/src/onebaseCliRunner.mjs +145 -0
  123. package/vendor/src/onebaseGrossProfitStaticVerify.mjs +98 -0
  124. package/vendor/src/onebaseParityEvidenceSync.mjs +88 -0
  125. package/vendor/src/onebasePvrgGraphNodes.mjs +257 -0
  126. package/vendor/src/onebaseRestEvidenceAdapter.mjs +216 -0
  127. package/vendor/src/onebaseVectorDslCodegenReadiness.mjs +137 -0
  128. package/vendor/src/onebaseWorkItemTemplate.mjs +154 -0
  129. package/vendor/src/onebaseWorkerTools.mjs +586 -0
  130. package/vendor/src/operatorShellProjection.mjs +102 -0
  131. package/vendor/src/pipelineProseRender.mjs +180 -0
  132. package/vendor/src/pipelineStageLint.mjs +118 -0
  133. package/vendor/src/promptRulesEditorApi.mjs +174 -0
  134. package/vendor/src/promptRulesProjection.mjs +134 -0
  135. package/vendor/src/pvrg/bladeAdapter.mjs +40 -0
  136. package/vendor/src/pvrgTaskScope.mjs +152 -0
  137. package/vendor/src/releaseGateMatrix.mjs +188 -0
  138. package/vendor/src/schematicView.mjs +305 -0
  139. package/vendor/src/seedAnalyticsRecord.mjs +217 -0
  140. package/vendor/src/semanticSearchBm25.mjs +103 -0
  141. package/vendor/src/semanticSearchExcerpts.mjs +68 -0
  142. package/vendor/src/semanticSearchTfidfVector.mjs +86 -0
  143. package/vendor/src/semanticSearchWorkflow.mjs +366 -0
  144. package/vendor/src/stepAtomFormatter.mjs +413 -0
  145. package/vendor/src/stepGraphSlice.mjs +318 -0
  146. package/vendor/src/ui/atoms/badge.mjs +40 -0
  147. package/vendor/src/ui/atoms/badgeClient.mjs +32 -0
  148. package/vendor/src/ui/atoms/button.mjs +114 -0
  149. package/vendor/src/ui/atoms/buttonClient.mjs +49 -0
  150. package/vendor/src/ui/atoms/icon.mjs +23 -0
  151. package/vendor/src/ui/atoms/input.mjs +38 -0
  152. package/vendor/src/ui/atoms/modal.mjs +44 -0
  153. package/vendor/src/ui/atoms/select.mjs +98 -0
  154. package/vendor/src/ui/backlogShellButtons.mjs +238 -0
  155. package/vendor/src/ui/htmlEscape.mjs +11 -0
  156. package/vendor/src/ui/molecules/rating.mjs +48 -0
  157. package/vendor/src/ui/molecules/tabs.mjs +70 -0
  158. package/vendor/src/ui/organisms/modal.mjs +1 -0
  159. package/vendor/src/ui/pages/uiKitPage.mjs +147 -0
  160. package/vendor/src/ui/workItemStatusTone.mjs +36 -0
  161. package/vendor/src/unifiedLinkageProjection.mjs +264 -0
  162. package/vendor/src/verificationLoop.mjs +206 -0
  163. package/vendor/src/workGraphBacklogPersist.mjs +234 -0
  164. package/vendor/src/workGraphBacklogUiServer.mjs +9192 -0
  165. package/vendor/src/workGraphBoundedTargetFileRead.mjs +178 -0
  166. package/vendor/src/workGraphCycleSlice.mjs +184 -0
  167. package/vendor/src/workGraphDaemonTick.mjs +307 -0
  168. package/vendor/src/workGraphDaemonWatch.mjs +157 -0
  169. package/vendor/src/workGraphEngineRoot.mjs +136 -0
  170. package/vendor/src/workGraphInstallLayout.mjs +65 -0
  171. package/vendor/src/workGraphLlmUsefulnessEval.mjs +611 -0
  172. package/vendor/src/workGraphPhasePromoteReadyQueue.mjs +159 -0
  173. package/vendor/src/workGraphProjectHost.mjs +149 -0
  174. package/vendor/src/workGraphProjectInit.mjs +392 -0
  175. package/vendor/src/workGraphPromoteReadyApi.mjs +115 -0
  176. package/vendor/src/workGraphRecoveryPolicy.mjs +124 -0
  177. package/vendor/src/workGraphRunnerQueueProjection.mjs +187 -0
  178. package/vendor/src/workGraphRuntime.mjs +1008 -0
  179. package/vendor/src/workGraphToolSurfaceAudit.mjs +372 -0
  180. package/vendor/src/workGraphToolTransportRuntime.mjs +195 -0
  181. package/vendor/src/workGraphWorkerProvider.mjs +600 -0
  182. package/vendor/src/workItemBvcQuality.mjs +262 -0
  183. package/vendor/src/workItemCreateAnalysis.mjs +157 -0
  184. package/vendor/src/workItemDecisionPipeline.mjs +278 -0
  185. package/vendor/src/workItemEpicCascade.mjs +176 -0
  186. package/vendor/src/workItemExecutionGate.mjs +78 -0
  187. package/vendor/src/workItemHierarchy.mjs +226 -0
  188. package/vendor/src/workItemProseLint.mjs +133 -0
  189. package/vendor/src/workItemTextRusify.mjs +794 -0
  190. package/vendor/src/workItemTraceEnvelope.mjs +158 -0
  191. package/vendor/src/workItemUiReferences.mjs +272 -0
  192. package/vendor/src/workflowEpicGrouping.mjs +67 -0
  193. package/vendor/src/workflowTreeProjection.mjs +53 -0
  194. package/vendor/src/workspaceRegistry.mjs +150 -0
@@ -0,0 +1,308 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
+ import { join, relative } from 'node:path';
3
+
4
+ export const CODE_GAP_REPORT_SCHEMA = 'code-gap.report.v1';
5
+
6
+ const SKIP_DIRS = new Set([
7
+ 'node_modules',
8
+ '.git',
9
+ 'dist',
10
+ 'coverage',
11
+ 'out',
12
+ 'build',
13
+ '.vite',
14
+ 'vendor',
15
+ ]);
16
+
17
+ const STEP_BLOCK_PATTERN = /^#([^\n<]+)<\[\n([\s\S]*?)\n\]>/gmu;
18
+ const LINK_MODE_RE = /(?:^|\n)\s*(?:compiler\.mode|mode):\s*link\s*(?:\n|$)/iu;
19
+ const GUID_LABEL_RE = /(?:^|\n)\s*guid:\s*([a-fA-F0-9-]{8}-[a-fA-F0-9-]{4}-[a-fA-F0-9-]{4}-[a-fA-F0-9-]{4}-[a-fA-F0-9-]{12})/iu;
20
+ const TUR_ID_LABEL_RE = /(?:^|\n)\s*tur_id:\s*([A-Za-z0-9_.-]+)/iu;
21
+ const EXPORT_PATTERN = /^\s*export\s+(?:async\s+)?(?:function|class|const|let|var)\s+([A-Za-z_$][\w$]*)/gmu;
22
+
23
+ function posixRel(root, absPath) {
24
+ return relative(root, absPath).replace(/\\/g, '/');
25
+ }
26
+
27
+ function walkFiles(absDir, predicate, out = []) {
28
+ if (!existsSync(absDir)) {
29
+ return out;
30
+ }
31
+
32
+ for (const entry of readdirSync(absDir, { withFileTypes: true })) {
33
+ const entryPath = join(absDir, entry.name);
34
+ if (entry.isDirectory()) {
35
+ if (SKIP_DIRS.has(entry.name)) {
36
+ continue;
37
+ }
38
+ walkFiles(entryPath, predicate, out);
39
+ continue;
40
+ }
41
+
42
+ if (entry.isFile() && predicate(entry.name)) {
43
+ out.push(entryPath);
44
+ }
45
+ }
46
+
47
+ return out;
48
+ }
49
+
50
+ export function collectStepFilePaths(repoRoot, relDirs) {
51
+ const out = [];
52
+ for (const relDir of relDirs) {
53
+ walkFiles(join(repoRoot, relDir), (name) => name.endsWith('.bvc'), out);
54
+ }
55
+
56
+ return out.sort();
57
+ }
58
+
59
+ export function collectCodeFilePaths(repoRoot, relDirs) {
60
+ const out = [];
61
+ for (const relDir of relDirs) {
62
+ walkFiles(
63
+ join(repoRoot, relDir),
64
+ (name) => /\.(mjs|cjs|js|ts|mts)$/u.test(name) && !/\.(test|spec)\.[cm]?[jt]s$/iu.test(name),
65
+ out,
66
+ );
67
+ }
68
+
69
+ return out.sort();
70
+ }
71
+
72
+ function extractStepBlocks(text) {
73
+ return [...text.matchAll(STEP_BLOCK_PATTERN)].map((match) => ({
74
+ name: match[1].trim(),
75
+ body: match[2],
76
+ }));
77
+ }
78
+
79
+ function parseLinkSteps(stepAbsPaths, repoRoot) {
80
+ const linkSteps = [];
81
+
82
+ for (const absPath of stepAbsPaths) {
83
+ let text;
84
+ try {
85
+ text = readFileSync(absPath, 'utf8');
86
+ } catch {
87
+ continue;
88
+ }
89
+
90
+ for (const block of extractStepBlocks(text)) {
91
+ if (!LINK_MODE_RE.test(block.body)) {
92
+ continue;
93
+ }
94
+
95
+ linkSteps.push({
96
+ stepName: block.name,
97
+ stepGuid: block.body.match(GUID_LABEL_RE)?.[1] ?? '',
98
+ stepFile: posixRel(repoRoot, absPath),
99
+ stepTurId: block.body.match(TUR_ID_LABEL_RE)?.[1] ?? block.name,
100
+ });
101
+ }
102
+ }
103
+
104
+ return linkSteps;
105
+ }
106
+
107
+ function collectStepNames(stepAbsPaths) {
108
+ const names = new Set();
109
+ for (const absPath of stepAbsPaths) {
110
+ try {
111
+ for (const block of extractStepBlocks(readFileSync(absPath, 'utf8'))) {
112
+ names.add(block.name);
113
+ }
114
+ } catch {
115
+ continue;
116
+ }
117
+ }
118
+
119
+ return names;
120
+ }
121
+
122
+ function scanTurImplementations(codeAbsPaths, repoRoot) {
123
+ const implementations = [];
124
+
125
+ for (const absPath of codeAbsPaths) {
126
+ let text;
127
+ try {
128
+ text = readFileSync(absPath, 'utf8');
129
+ } catch {
130
+ continue;
131
+ }
132
+
133
+ const lines = text.split(/\r?\n/u);
134
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
135
+ const turMatch = lines[lineIndex].match(/@iohasc-tur:\s*([A-Za-z0-9_.-]+)/u);
136
+ if (!turMatch) {
137
+ continue;
138
+ }
139
+
140
+ const stepGuidMatch = lines.slice(lineIndex, lineIndex + 6).join('\n').match(
141
+ /@iohasc-step:\s*([a-fA-F0-9-]{8}-[a-fA-F0-9-]{4}-[a-fA-F0-9-]{4}-[a-fA-F0-9-]{4}-[a-fA-F0-9-]{12})/iu,
142
+ );
143
+
144
+ const fnMatch = lines.slice(lineIndex, lineIndex + 4).join('\n').match(/(?:function|const)\s+([A-Za-z_$][\w$]*)/u);
145
+
146
+ implementations.push({
147
+ turId: turMatch[1],
148
+ stepGuid: stepGuidMatch?.[1] ?? '',
149
+ filePath: posixRel(repoRoot, absPath),
150
+ lineNumber: lineIndex + 1,
151
+ functionName: fnMatch?.[1] ?? turMatch[1],
152
+ });
153
+ }
154
+ }
155
+
156
+ return implementations;
157
+ }
158
+
159
+ function listExportedSymbols(text) {
160
+ const symbols = new Set();
161
+ for (const match of text.matchAll(EXPORT_PATTERN)) {
162
+ symbols.add(match[1]);
163
+ }
164
+
165
+ return [...symbols];
166
+ }
167
+
168
+ function shouldSkipUntrackedSymbol(name) {
169
+ return !name || name.startsWith('_');
170
+ }
171
+
172
+ export function analyzeCodeGaps(options = {}) {
173
+ const repoRoot = String(options.repoRoot ?? process.cwd()).replace(/\\/g, '/').replace(/\/+$/, '');
174
+ const codeRelDirs = options.codeRelDirs?.length ? options.codeRelDirs : ['src'];
175
+ const stepRelDirs = options.stepSearchRelDirs?.length
176
+ ? options.stepSearchRelDirs
177
+ : ['steps', 'charter', 'protocols', 'intent'];
178
+
179
+ const stepFiles = collectStepFilePaths(repoRoot, stepRelDirs);
180
+ const codeFiles = collectCodeFilePaths(repoRoot, codeRelDirs);
181
+ const linkSteps = parseLinkSteps(stepFiles, repoRoot);
182
+ const stepNames = collectStepNames(stepFiles);
183
+ const implementations = scanTurImplementations(codeFiles, repoRoot);
184
+
185
+ const implByGuid = new Map(
186
+ implementations
187
+ .filter((entry) => entry.stepGuid)
188
+ .map((entry) => [entry.stepGuid, entry]),
189
+ );
190
+ const implTurIds = new Set(implementations.map((entry) => entry.turId));
191
+ const implFunctionNames = new Set(implementations.map((entry) => entry.functionName));
192
+
193
+ const entries = [];
194
+
195
+ for (const linkStep of linkSteps) {
196
+ if (!implByGuid.has(linkStep.stepGuid)) {
197
+ entries.push({
198
+ kind: 'missing_implementation',
199
+ filePath: linkStep.stepFile,
200
+ stepGuid: linkStep.stepGuid,
201
+ reason: `Link step «${linkStep.stepName}» without @iohasc-tur implementation (guid ${linkStep.stepGuid}).`,
202
+ });
203
+ }
204
+ }
205
+
206
+ for (const impl of implementations) {
207
+ const linked = linkSteps.some((step) => step.stepGuid === impl.stepGuid);
208
+ if (!linked) {
209
+ entries.push({
210
+ kind: 'orphaned_tur',
211
+ filePath: impl.filePath,
212
+ symbol: impl.functionName,
213
+ line: impl.lineNumber,
214
+ turId: impl.turId,
215
+ stepGuid: impl.stepGuid,
216
+ reason: `@iohasc-tur «${impl.turId}» is not linked to a link-mode .bvc block.`,
217
+ });
218
+ }
219
+
220
+ const linkedStep = linkSteps.find((step) => step.stepGuid === impl.stepGuid);
221
+ if (linkedStep && linkedStep.stepTurId && linkedStep.stepTurId !== impl.turId) {
222
+ entries.push({
223
+ kind: 'tur_id_mismatch',
224
+ filePath: impl.filePath,
225
+ symbol: impl.functionName,
226
+ line: impl.lineNumber,
227
+ turId: impl.turId,
228
+ stepGuid: impl.stepGuid,
229
+ reason: `tur_id «${linkedStep.stepTurId}» in step ≠ @iohasc-tur «${impl.turId}».`,
230
+ });
231
+ }
232
+ }
233
+
234
+ for (const absPath of codeFiles) {
235
+ let text;
236
+ try {
237
+ text = readFileSync(absPath, 'utf8');
238
+ } catch {
239
+ continue;
240
+ }
241
+
242
+ const rel = posixRel(repoRoot, absPath);
243
+ for (const symbol of listExportedSymbols(text)) {
244
+ if (shouldSkipUntrackedSymbol(symbol)) {
245
+ continue;
246
+ }
247
+
248
+ if (stepNames.has(symbol) || implTurIds.has(symbol) || implFunctionNames.has(symbol)) {
249
+ continue;
250
+ }
251
+
252
+ entries.push({
253
+ kind: 'untracked_export',
254
+ filePath: rel,
255
+ symbol,
256
+ reason: 'Export is not covered by step block name or known @iohasc-tur marker.',
257
+ });
258
+ }
259
+ }
260
+
261
+ entries.sort((left, right) => {
262
+ const kind = left.kind.localeCompare(right.kind);
263
+ if (kind !== 0) {
264
+ return kind;
265
+ }
266
+
267
+ return `${left.filePath}:${left.symbol ?? ''}`.localeCompare(`${right.filePath}:${right.symbol ?? ''}`);
268
+ });
269
+
270
+ const summary = {
271
+ total: entries.length,
272
+ untrackedExports: entries.filter((entry) => entry.kind === 'untracked_export').length,
273
+ missingImplementation: entries.filter((entry) => entry.kind === 'missing_implementation').length,
274
+ orphanedTur: entries.filter((entry) => entry.kind === 'orphaned_tur').length,
275
+ turIdMismatch: entries.filter((entry) => entry.kind === 'tur_id_mismatch').length,
276
+ };
277
+
278
+ return {
279
+ schema: CODE_GAP_REPORT_SCHEMA,
280
+ summary,
281
+ entries,
282
+ tur: {
283
+ linkStepCount: linkSteps.length,
284
+ implementationCount: implementations.length,
285
+ },
286
+ };
287
+ }
288
+
289
+ export function formatCodeGapReportMarkdown(report) {
290
+ const lines = [
291
+ '## Code gap',
292
+ '',
293
+ `- missing_implementation: **${report.summary.missingImplementation}**`,
294
+ `- orphaned_tur: **${report.summary.orphanedTur}**`,
295
+ `- tur_id_mismatch: **${report.summary.turIdMismatch}**`,
296
+ `- untracked_export: **${report.summary.untrackedExports}**`,
297
+ `- **total entries:** ${report.summary.total}`,
298
+ '',
299
+ ];
300
+
301
+ for (const entry of report.entries) {
302
+ const location = entry.line != null ? `${entry.filePath}:${entry.line}` : entry.filePath;
303
+ const symbol = entry.symbol ? ` \`${entry.symbol}\`` : '';
304
+ lines.push(`- **${entry.kind}**${symbol} — ${location}: ${entry.reason}`);
305
+ }
306
+
307
+ return lines.join('\n');
308
+ }
@@ -0,0 +1,82 @@
1
+ const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
2
+
3
+ function slugify(value) {
4
+ return String(value ?? '')
5
+ .toLowerCase()
6
+ .replace(/[^a-z0-9]+/gu, '-')
7
+ .replace(/^-+|-+$/gu, '')
8
+ .slice(0, 48) || 'gap-item';
9
+ }
10
+
11
+ function confidenceForGapKind(kind) {
12
+ if (kind === 'missing_implementation') {
13
+ return 'high';
14
+ }
15
+
16
+ if (kind === 'untracked_export') {
17
+ return 'medium';
18
+ }
19
+
20
+ return 'low';
21
+ }
22
+
23
+ function titleForGapEntry(entry) {
24
+ if (entry.kind === 'untracked_export') {
25
+ return `покрыть step для экспорта ${entry.symbol ?? entry.filePath}`;
26
+ }
27
+
28
+ if (entry.kind === 'missing_implementation') {
29
+ return `реализовать TUR/link для ${entry.stepGuid ?? entry.filePath}`;
30
+ }
31
+
32
+ if (entry.kind === 'orphaned_tur') {
33
+ return `сопоставить orphaned TUR ${entry.turId ?? entry.symbol ?? ''}`.trim();
34
+ }
35
+
36
+ return `устранить code-gap: ${entry.kind}`;
37
+ }
38
+
39
+ export function mapGapEntryToWorkItemDraft(entry, options = {}) {
40
+ const prefix = options.idPrefix ?? 'gap';
41
+ const baseId = entry.symbol ? `${prefix}-${slugify(entry.symbol)}` : `${prefix}-${slugify(entry.filePath)}`;
42
+
43
+ return {
44
+ schema: 'workitem.draft.v1',
45
+ suggestedWorkId: baseId,
46
+ title: titleForGapEntry(entry),
47
+ status: 'backlog',
48
+ targetFiles: [entry.filePath].filter(Boolean),
49
+ confidence: confidenceForGapKind(entry.kind),
50
+ gapKind: entry.kind,
51
+ reason: entry.reason,
52
+ provenance: {
53
+ source: 'code-gap-analyzer',
54
+ repo: options.sourceRepo ?? '../project',
55
+ gapKind: entry.kind,
56
+ symbol: entry.symbol ?? '',
57
+ turId: entry.turId ?? '',
58
+ stepGuid: entry.stepGuid ?? '',
59
+ },
60
+ reviewRequired: true,
61
+ };
62
+ }
63
+
64
+ export function buildCodeGapBacklogFeed(report, options = {}) {
65
+ if (report === undefined || report === null) {
66
+ throw new TypeError('report is required');
67
+ }
68
+
69
+ const entries = Array.isArray(report.entries) ? report.entries : [];
70
+ const suggestions = entries
71
+ .map((entry) => mapGapEntryToWorkItemDraft(entry, options))
72
+ .sort((left, right) => compareText(left.suggestedWorkId, right.suggestedWorkId));
73
+
74
+ return {
75
+ schema: 'code-gap.backlog-feed.v1',
76
+ sourceReport: report.summary ?? {},
77
+ suggestionCount: suggestions.length,
78
+ suggestions,
79
+ promotionProtocol: 'protocols/workgraph-draft-intake.bvc',
80
+ reviewRequired: true,
81
+ };
82
+ }
@@ -0,0 +1,307 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+
4
+ import {
5
+ appendWorkItemAtomToBacklogText,
6
+ findWorkItemAtomSpan,
7
+ writeBacklogTextAtomically,
8
+ } from './workGraphBacklogPersist.mjs';
9
+ import {
10
+ appendWorkItemAtomToIntentTree,
11
+ readWorkItemAtomFromRepo,
12
+ } from './intentTreeWorkItems.mjs';
13
+ import { formatStepAtomDraft, validateStepAtomDraft } from './stepAtomFormatter.mjs';
14
+ import {
15
+ evaluateDraftIntakePromotion,
16
+ partitionDraftIntakeCandidates,
17
+ } from './draftIntakePromotionRules.mjs';
18
+ import { DEFAULT_CODE_GAP_REPORT_PATH } from './codeGapOperatorProjection.mjs';
19
+
20
+ export const CODE_GAP_DRAFT_PROPOSAL_SCHEMA = 'code-gap.draft-intake.proposal.v1';
21
+ export const CODE_GAP_DRAFT_APPLY_SCHEMA = 'code-gap.draft-intake.apply.response.v1';
22
+ export const CODE_GAP_DRAFT_SCHEMA = 'code-gap.draft-intake.draft.v1';
23
+
24
+ function atomNameFromWorkId(workId) {
25
+ const safe = String(workId ?? '').trim().replace(/-/g, '_');
26
+ return `Задача_${safe}`;
27
+ }
28
+
29
+ function priorityForConfidence(confidence) {
30
+ if (confidence === 'high') {
31
+ return 'high';
32
+ }
33
+
34
+ if (confidence === 'low') {
35
+ return 'low';
36
+ }
37
+
38
+ return 'medium';
39
+ }
40
+
41
+ export function buildWorkItemDraftFromCodeGapSuggestion(suggestion, options = {}) {
42
+ if (!suggestion || typeof suggestion !== 'object') {
43
+ throw new TypeError('suggestion is required');
44
+ }
45
+
46
+ const workId = String(suggestion.suggestedWorkId ?? '').trim();
47
+ if (workId === '') {
48
+ throw new TypeError('suggestion.suggestedWorkId is required');
49
+ }
50
+
51
+ const title = String(suggestion.title ?? workId).trim();
52
+ const targetFiles = [...(suggestion.targetFiles ?? [])];
53
+ const provenance = suggestion.provenance ?? {};
54
+ const sourceReportPath = String(
55
+ options.sourceReportPath ?? options.intakeSourcePath ?? DEFAULT_CODE_GAP_REPORT_PATH,
56
+ ).trim();
57
+
58
+ const draft = {
59
+ name: atomNameFromWorkId(workId),
60
+ profile: 'work_item',
61
+ basis: [
62
+ `Code-gap analyzer обнаружил ${suggestion.gapKind ?? 'gap'}: ${suggestion.reason ?? title}.`,
63
+ provenance.symbol ? `Symbol: ${provenance.symbol}.` : '',
64
+ provenance.stepGuid ? `Step GUID: ${provenance.stepGuid}.` : '',
65
+ ].filter(Boolean),
66
+ vector: [
67
+ 'Оформить WorkItem из code-gap suggestion через reviewed draft intake.',
68
+ 'Связать задачу с target files и закрыть gap после verification.',
69
+ ],
70
+ goal: [title],
71
+ checks: [
72
+ 'draft проходит StepAtomDraft validation',
73
+ 'operator подтвердил explicit apply в backlog',
74
+ 'duplicate work.id отсутствует',
75
+ ],
76
+ labels: {
77
+ 'atom.profile': 'work_item',
78
+ 'work.id': workId,
79
+ 'work.title': title,
80
+ 'work.status': 'backlog',
81
+ 'work.owner_role': options.ownerRole ?? 'integration_architect',
82
+ 'work.department': options.department ?? 'memory',
83
+ 'work.priority': priorityForConfidence(suggestion.confidence),
84
+ 'work.risk': 'medium',
85
+ 'work.next_action': options.nextAction ?? 'review draft и promote в ready',
86
+ 'work.target_files': targetFiles.join(', '),
87
+ 'intake.source_kind': 'code-gap-analyzer',
88
+ 'intake.review_status': 'pending',
89
+ 'intake.source_path': sourceReportPath,
90
+ 'intake.promoted_work.id': workId,
91
+ 'trace.status': 'pending',
92
+ 'migration.strategy': 'rebuild',
93
+ },
94
+ };
95
+
96
+ return {
97
+ schema: CODE_GAP_DRAFT_SCHEMA,
98
+ suggestedWorkId: workId,
99
+ reviewRequired: suggestion.reviewRequired !== false,
100
+ gapKind: suggestion.gapKind ?? '',
101
+ confidence: suggestion.confidence ?? 'medium',
102
+ targetFiles,
103
+ provenance,
104
+ draft,
105
+ };
106
+ }
107
+
108
+ export function buildCodeGapDraftProposal(body, options = {}) {
109
+ const suggestion = body?.suggestion;
110
+
111
+ if (!suggestion || typeof suggestion !== 'object') {
112
+ const emptyProposal = {
113
+ ok: false,
114
+ error: 'suggestion_required',
115
+ validationErrors: ['suggestion is required'],
116
+ codeGapDraft: null,
117
+ };
118
+
119
+ return {
120
+ schema: CODE_GAP_DRAFT_PROPOSAL_SCHEMA,
121
+ ...emptyProposal,
122
+ reviewRequired: true,
123
+ promotionProtocol: 'protocols/workgraph-draft-intake.bvc',
124
+ promotionEvaluation: evaluateDraftIntakePromotion(emptyProposal, options),
125
+ };
126
+ }
127
+
128
+ const codeGapDraft = buildWorkItemDraftFromCodeGapSuggestion(suggestion, {
129
+ ...options,
130
+ sourceReportPath: body.sourceReportPath ?? options.sourceReportPath,
131
+ });
132
+
133
+ const validationErrors = validateStepAtomDraft(codeGapDraft.draft);
134
+ let formattedAtom = null;
135
+
136
+ if (validationErrors.length === 0) {
137
+ try {
138
+ formattedAtom = formatStepAtomDraft(codeGapDraft.draft);
139
+ } catch (error) {
140
+ validationErrors.push(error instanceof Error ? error.message : String(error));
141
+ }
142
+ }
143
+
144
+ return {
145
+ schema: CODE_GAP_DRAFT_PROPOSAL_SCHEMA,
146
+ ok: validationErrors.length === 0,
147
+ error: validationErrors.length === 0 ? null : 'validation_failed',
148
+ codeGapDraft,
149
+ formattedAtom,
150
+ validationErrors,
151
+ reviewRequired: true,
152
+ promotionProtocol: 'protocols/workgraph-draft-intake.bvc',
153
+ promotionEvaluation: evaluateDraftIntakePromotion({
154
+ ok: validationErrors.length === 0,
155
+ validationErrors,
156
+ codeGapDraft,
157
+ }, options),
158
+ };
159
+ }
160
+
161
+ export function parseCodeGapDraftIntakeRequestBody(rawBody) {
162
+ if (rawBody === undefined || rawBody === null) {
163
+ return {};
164
+ }
165
+
166
+ if (typeof rawBody === 'string' && rawBody.trim() === '') {
167
+ return {};
168
+ }
169
+
170
+ const body = typeof rawBody === 'string' ? JSON.parse(rawBody) : rawBody;
171
+
172
+ if (!body || typeof body !== 'object' || Array.isArray(body)) {
173
+ throw new TypeError('code-gap draft intake body must be a JSON object');
174
+ }
175
+
176
+ return body;
177
+ }
178
+
179
+ export async function executeCodeGapDraftProposal(options = {}) {
180
+ const body = parseCodeGapDraftIntakeRequestBody(options.body ?? {});
181
+ return buildCodeGapDraftProposal(body, options);
182
+ }
183
+
184
+ export function buildCodeGapBacklogCandidateList(feedOrProjection, options = {}) {
185
+ const suggestions = Array.isArray(feedOrProjection?.suggestions)
186
+ ? feedOrProjection.suggestions
187
+ : [];
188
+
189
+ const entries = suggestions.map((suggestion) => ({
190
+ suggestion,
191
+ proposal: buildCodeGapDraftProposal(
192
+ {
193
+ suggestion,
194
+ sourceReportPath: feedOrProjection?.sourceReportPath ?? options.sourceReportPath,
195
+ },
196
+ options,
197
+ ),
198
+ }));
199
+
200
+ return partitionDraftIntakeCandidates(entries, options);
201
+ }
202
+
203
+ export async function executeCodeGapDraftApply(options = {}) {
204
+ const cwd = options.cwd ?? process.cwd();
205
+ const body = parseCodeGapDraftIntakeRequestBody(options.body ?? {});
206
+
207
+ const proposal = body.proposal ?? buildCodeGapDraftProposal(body, options);
208
+
209
+ if (!proposal.ok || !proposal.codeGapDraft?.draft) {
210
+ return {
211
+ schema: CODE_GAP_DRAFT_APPLY_SCHEMA,
212
+ ok: false,
213
+ error: proposal.error ?? 'invalid_proposal',
214
+ workId: null,
215
+ persistedBacklog: false,
216
+ validationErrors: proposal.validationErrors ?? [],
217
+ };
218
+ }
219
+
220
+ const workId = String(proposal.codeGapDraft.suggestedWorkId ?? '').trim();
221
+ const draft = {
222
+ ...proposal.codeGapDraft.draft,
223
+ labels: {
224
+ ...proposal.codeGapDraft.draft.labels,
225
+ 'intake.review_status': 'approved',
226
+ 'intake.promoted_work.id': workId,
227
+ },
228
+ };
229
+
230
+ let formattedAtom = proposal.formattedAtom;
231
+ try {
232
+ formattedAtom = formatStepAtomDraft(draft);
233
+ } catch (error) {
234
+ return {
235
+ schema: CODE_GAP_DRAFT_APPLY_SCHEMA,
236
+ ok: false,
237
+ error: 'validation_failed',
238
+ workId,
239
+ persistedBacklog: false,
240
+ validationErrors: [error instanceof Error ? error.message : String(error)],
241
+ };
242
+ }
243
+
244
+ if (options.backlogText !== undefined || options.backlogPath) {
245
+ const backlogPath = resolve(cwd, options.backlogPath ?? 'work/backlog.bvc');
246
+ const backlogText = options.backlogText ?? await readFile(backlogPath, 'utf8');
247
+
248
+ if (findWorkItemAtomSpan(backlogText, workId)) {
249
+ return {
250
+ schema: CODE_GAP_DRAFT_APPLY_SCHEMA,
251
+ ok: false,
252
+ error: 'duplicate_work_id',
253
+ workId,
254
+ persistedBacklog: false,
255
+ validationErrors: [`work.id already exists: ${workId}`],
256
+ };
257
+ }
258
+
259
+ const nextText = appendWorkItemAtomToBacklogText(backlogText, formattedAtom);
260
+
261
+ if (options.persistBacklog !== false) {
262
+ await writeBacklogTextAtomically(backlogPath, nextText);
263
+ }
264
+
265
+ return {
266
+ schema: CODE_GAP_DRAFT_APPLY_SCHEMA,
267
+ ok: true,
268
+ error: null,
269
+ workId,
270
+ persistedBacklog: options.persistBacklog !== false,
271
+ validationErrors: [],
272
+ formattedAtom,
273
+ intakeSourceKind: 'code-gap-analyzer',
274
+ };
275
+ }
276
+
277
+ try {
278
+ await readWorkItemAtomFromRepo(workId, { ...options, cwd });
279
+ return {
280
+ schema: CODE_GAP_DRAFT_APPLY_SCHEMA,
281
+ ok: false,
282
+ error: 'duplicate_work_id',
283
+ workId,
284
+ persistedBacklog: false,
285
+ validationErrors: [`work.id already exists: ${workId}`],
286
+ };
287
+ } catch (error) {
288
+ if (!String(error instanceof Error ? error.message : error).includes('not found')) {
289
+ throw error;
290
+ }
291
+ }
292
+
293
+ if (options.persistBacklog !== false) {
294
+ await appendWorkItemAtomToIntentTree(formattedAtom, { ...options, cwd });
295
+ }
296
+
297
+ return {
298
+ schema: CODE_GAP_DRAFT_APPLY_SCHEMA,
299
+ ok: true,
300
+ error: null,
301
+ workId,
302
+ persistedBacklog: options.persistBacklog !== false,
303
+ validationErrors: [],
304
+ formattedAtom,
305
+ intakeSourceKind: 'code-gap-analyzer',
306
+ };
307
+ }