@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,227 @@
1
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, resolve } from 'node:path';
3
+
4
+ import { resolveIntentPathFromIndex } from './intentHierarchy.mjs';
5
+ import { buildIntentIndexStep } from './intentTreeMigration.mjs';
6
+ import { intentPathForNewWorkItem } from './bvcNewWritePolicy.mjs';
7
+ import { readBvcTextFile, resolveBvcReadablePath } from './bvcFileFormat.mjs';
8
+ import { parseWorkItems } from './workGraphRuntime.mjs';
9
+ import {
10
+ findWorkItemAtomSpan,
11
+ patchWorkItemInBacklogText,
12
+ } from './workGraphBacklogPersist.mjs';
13
+
14
+ const DEFAULT_INTENT_INDEX_PATH = 'intent/index.bvc';
15
+
16
+ export async function readIntentWorkItemTexts(options = {}) {
17
+ const cwd = options.cwd ?? process.cwd();
18
+ const indexPath = resolveBvcReadablePath(options.intentIndexPath ?? DEFAULT_INTENT_INDEX_PATH, cwd);
19
+ const indexText = options.indexText ?? await readBvcTextFile(options.intentIndexPath ?? DEFAULT_INTENT_INDEX_PATH, { cwd });
20
+ const entries = parseIntentIndexEntries(indexText);
21
+ const texts = [];
22
+
23
+ for (const entry of entries) {
24
+ texts.push(await readBvcTextFile(entry.path, { cwd }));
25
+ }
26
+
27
+ return {
28
+ indexPath,
29
+ indexText,
30
+ entries,
31
+ texts,
32
+ };
33
+ }
34
+
35
+ export async function readWorkItemsFromIntentTree(options = {}) {
36
+ const { texts } = await readIntentWorkItemTexts(options);
37
+ return parseWorkItems(texts.join('\n'));
38
+ }
39
+
40
+ export async function readWorkItemsFromRepo(options = {}) {
41
+ if (options.backlogText !== undefined) {
42
+ return parseWorkItems(options.backlogText);
43
+ }
44
+
45
+ if (options.backlogPath) {
46
+ const backlogPath = resolveBvcReadablePath(options.backlogPath, options.cwd ?? process.cwd());
47
+ const backlogText = await readBvcTextFile(options.backlogPath, { cwd: options.cwd });
48
+ return parseWorkItems(backlogText);
49
+ }
50
+
51
+ return readWorkItemsFromIntentTree(options);
52
+ }
53
+
54
+ export async function readWorkItemAtomFromRepo(workId, options = {}) {
55
+ const normalizedWorkId = String(workId ?? '').trim();
56
+ if (normalizedWorkId === '') {
57
+ throw new TypeError('workId is required');
58
+ }
59
+
60
+ if (options.backlogText !== undefined || options.backlogPath) {
61
+ const cwd = options.cwd ?? process.cwd();
62
+ const backlogPath = options.backlogPath
63
+ ? resolveBvcReadablePath(options.backlogPath, cwd)
64
+ : null;
65
+ const text = options.backlogText ?? await readBvcTextFile(options.backlogPath, { cwd });
66
+ const span = findWorkItemAtomSpan(text, normalizedWorkId);
67
+ if (!span) {
68
+ throw new Error(`work item atom not found: ${normalizedWorkId}`);
69
+ }
70
+ return {
71
+ mode: 'backlog',
72
+ path: backlogPath,
73
+ text,
74
+ atomText: span.fullMatch,
75
+ span,
76
+ };
77
+ }
78
+
79
+ const cwd = options.cwd ?? process.cwd();
80
+ const indexPath = options.intentIndexPath ?? DEFAULT_INTENT_INDEX_PATH;
81
+ const indexText = options.indexText ?? await readBvcTextFile(indexPath, { cwd });
82
+ const relativePath = resolveIntentPathFromIndex(indexText, normalizedWorkId);
83
+ if (!relativePath) {
84
+ throw new Error(`intent path not found for work item: ${normalizedWorkId}`);
85
+ }
86
+
87
+ const path = resolveBvcReadablePath(relativePath, cwd);
88
+ const text = await readBvcTextFile(relativePath, { cwd });
89
+ const span = findWorkItemAtomSpan(text, normalizedWorkId);
90
+ if (!span) {
91
+ throw new Error(`work item atom not found in ${relativePath}: ${normalizedWorkId}`);
92
+ }
93
+
94
+ return {
95
+ mode: 'intent',
96
+ path,
97
+ relativePath,
98
+ indexPath: resolveBvcReadablePath(indexPath, cwd),
99
+ indexText,
100
+ text,
101
+ atomText: span.fullMatch,
102
+ span,
103
+ };
104
+ }
105
+
106
+ export async function persistWorkItemUpdateToRepo(options = {}) {
107
+ const item = options.item;
108
+ if (!item?.id) {
109
+ throw new TypeError('item.id is required');
110
+ }
111
+
112
+ if (options.backlogPath || options.backlogText !== undefined) {
113
+ const cwd = options.cwd ?? process.cwd();
114
+ const backlogPath = options.backlogPath
115
+ ? resolveBvcReadablePath(options.backlogPath, cwd)
116
+ : null;
117
+ const sourceText = options.backlogText ?? await readBvcTextFile(options.backlogPath, { cwd });
118
+ const newText = patchWorkItemInBacklogText(sourceText, item);
119
+ if (options.persistBacklog !== false && backlogPath) {
120
+ await writeTextAtomically(backlogPath, newText);
121
+ }
122
+ return {
123
+ path: backlogPath,
124
+ workId: item.id,
125
+ status: item.status,
126
+ mode: 'backlog',
127
+ persisted: options.persistBacklog !== false && Boolean(backlogPath),
128
+ };
129
+ }
130
+
131
+ const source = await readWorkItemAtomFromRepo(item.id, options);
132
+ const newText = patchWorkItemInBacklogText(source.text, item);
133
+ if (options.persistIntent !== false) {
134
+ await writeTextAtomically(source.path, newText);
135
+ }
136
+ return {
137
+ path: source.relativePath ?? source.path,
138
+ workId: item.id,
139
+ status: item.status,
140
+ mode: 'intent',
141
+ persisted: options.persistIntent !== false,
142
+ };
143
+ }
144
+
145
+ export async function persistWorkItemUpdatesToRepo(items, options = {}) {
146
+ if (!Array.isArray(items) || items.length === 0) {
147
+ return [];
148
+ }
149
+
150
+ const results = [];
151
+ for (const item of items) {
152
+ results.push(await persistWorkItemUpdateToRepo({ ...options, item }));
153
+ }
154
+ return results;
155
+ }
156
+
157
+ export async function appendWorkItemAtomToIntentTree(atomText, options = {}) {
158
+ const atom = String(atomText ?? '').trim();
159
+ if (atom === '') {
160
+ throw new TypeError('atomText must be a non-empty string');
161
+ }
162
+
163
+ const [item] = parseWorkItems(atom);
164
+ if (!item?.id) {
165
+ throw new Error('atomText must contain a WorkItem');
166
+ }
167
+
168
+ const cwd = options.cwd ?? process.cwd();
169
+ const indexPath = resolveBvcReadablePath(options.intentIndexPath ?? DEFAULT_INTENT_INDEX_PATH, cwd);
170
+ let indexText = '';
171
+ let entries = [];
172
+ try {
173
+ indexText = await readBvcTextFile(options.intentIndexPath ?? DEFAULT_INTENT_INDEX_PATH, { cwd });
174
+ entries = parseIntentIndexEntries(indexText);
175
+ } catch (error) {
176
+ if (!error || typeof error !== 'object' || error.code !== 'ENOENT') {
177
+ throw error;
178
+ }
179
+ }
180
+
181
+ if (entries.some((entry) => entry.id === item.id)) {
182
+ throw new Error(`work.id already exists: ${item.id}`);
183
+ }
184
+
185
+ const path = options.path ?? intentPathForNewWorkItem(item);
186
+ const nextEntries = [...entries, { id: item.id, path }]
187
+ .sort((left, right) => compareText(left.id, right.id));
188
+ const absolutePath = resolve(cwd, path);
189
+
190
+ await mkdir(dirname(absolutePath), { recursive: true });
191
+ await writeTextAtomically(absolutePath, `${atom}\n`);
192
+ await mkdir(dirname(indexPath), { recursive: true });
193
+ await writeTextAtomically(indexPath, buildIntentIndexStep(nextEntries.map((entry) => ({
194
+ id: entry.id,
195
+ path: entry.path,
196
+ }))));
197
+
198
+ return {
199
+ workId: item.id,
200
+ path,
201
+ indexPath,
202
+ };
203
+ }
204
+
205
+ export function parseIntentIndexEntries(indexText) {
206
+ if (typeof indexText !== 'string') {
207
+ throw new TypeError('indexText must be a string');
208
+ }
209
+
210
+ return [...indexText.matchAll(/^\s*-\s*([^:\s]+):\s*(\S+)\s*$/gmu)]
211
+ .map((match) => ({ id: match[1], path: match[2] }))
212
+ .sort((left, right) => compareText(left.id, right.id));
213
+ }
214
+
215
+ function intentPathForItem(item) {
216
+ return intentPathForNewWorkItem(item);
217
+ }
218
+
219
+ async function writeTextAtomically(path, text) {
220
+ const tempPath = `${path}.tmp`;
221
+ await writeFile(tempPath, text, 'utf8');
222
+ await rename(tempPath, path);
223
+ }
224
+
225
+ function compareText(left, right) {
226
+ return String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
227
+ }
@@ -0,0 +1,58 @@
1
+ const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
2
+
3
+ export const KANBAN_BOARD_PROJECTION_SCHEMA = 'workgraph.kanban-board-projection.v1';
4
+
5
+ export const KANBAN_BOARD_COLUMNS = [
6
+ { id: 'backlog', title: 'Бэклог', statuses: ['backlog'] },
7
+ { id: 'ready', title: 'Ready', statuses: ['ready'] },
8
+ { id: 'in_progress', title: 'В работе', statuses: ['claimed', 'doing', 'in_progress', 'verify', 'blocked'] },
9
+ { id: 'done', title: 'Завершено', statuses: ['done', 'verified'] },
10
+ ];
11
+
12
+ function summarizeItem(item) {
13
+ return {
14
+ workId: item.id,
15
+ title: item.title,
16
+ status: item.status,
17
+ department: item.department ?? '',
18
+ priority: item.priority ?? '',
19
+ ownerRole: item.ownerRole ?? '',
20
+ };
21
+ }
22
+
23
+ export function buildKanbanBoardProjection(items, options = {}) {
24
+ if (!Array.isArray(items)) {
25
+ throw new TypeError('items must be an array');
26
+ }
27
+
28
+ const sortedItems = [...items].sort((left, right) => compareText(left.id, right.id));
29
+ const statusCounts = Object.create(null);
30
+
31
+ for (const item of sortedItems) {
32
+ statusCounts[item.status] = (statusCounts[item.status] ?? 0) + 1;
33
+ }
34
+
35
+ const columns = KANBAN_BOARD_COLUMNS.map((column) => {
36
+ const columnItems = sortedItems.filter((item) => column.statuses.includes(item.status));
37
+ return {
38
+ id: column.id,
39
+ title: column.title,
40
+ statuses: [...column.statuses],
41
+ count: columnItems.length,
42
+ workIds: columnItems.map((item) => item.id),
43
+ items: options.includeItems === true ? columnItems.map(summarizeItem) : undefined,
44
+ };
45
+ });
46
+
47
+ const columnCounts = Object.fromEntries(columns.map((column) => [column.id, column.count]));
48
+
49
+ return {
50
+ schema: KANBAN_BOARD_PROJECTION_SCHEMA,
51
+ readOnly: true,
52
+ dragEnabled: false,
53
+ itemCount: sortedItems.length,
54
+ statusCounts,
55
+ columnCounts,
56
+ columns,
57
+ };
58
+ }
@@ -0,0 +1,180 @@
1
+ import { extractGoFacts, goAdapter } from './languageAdapters/goAdapter.mjs';
2
+ import { extractJsonYamlFacts, jsonYamlAdapter } from './languageAdapters/jsonYamlAdapter.mjs';
3
+ import { extractJsTsFacts, jsTsAdapter } from './languageAdapters/jsTsAdapter.mjs';
4
+ import { extractOnebaseOsFacts, onebaseOsAdapter } from './languageAdapters/onebaseOsAdapter.mjs';
5
+ import { extractPlaintextFacts, plaintextAdapter } from './languageAdapters/plaintextAdapter.mjs';
6
+ import { extensionFromPath } from './languageAdapters/shared.mjs';
7
+ import { extractStepFacts, stepAdapter } from './languageAdapters/stepAdapter.mjs';
8
+
9
+ export const LANGUAGE_ADAPTER_REGISTRY_SCHEMA = 'workgraph.language-adapter-registry.v1';
10
+
11
+ const ADAPTERS = [
12
+ stepAdapter,
13
+ jsonYamlAdapter,
14
+ jsTsAdapter,
15
+ goAdapter,
16
+ onebaseOsAdapter,
17
+ ];
18
+
19
+ const EXTRACTORS = new Map([
20
+ [stepAdapter.id, extractStepFacts],
21
+ [jsonYamlAdapter.id, extractJsonYamlFacts],
22
+ [jsTsAdapter.id, extractJsTsFacts],
23
+ [goAdapter.id, extractGoFacts],
24
+ [onebaseOsAdapter.id, extractOnebaseOsFacts],
25
+ [plaintextAdapter.id, extractPlaintextFacts],
26
+ ]);
27
+
28
+ const ADAPTER_BY_ID = new Map([
29
+ ...ADAPTERS.map((adapter) => [adapter.id, adapter]),
30
+ [plaintextAdapter.id, plaintextAdapter],
31
+ ]);
32
+
33
+ export function buildLanguageAdapterRegistry() {
34
+ return {
35
+ schema: LANGUAGE_ADAPTER_REGISTRY_SCHEMA,
36
+ adapters: [...ADAPTERS, plaintextAdapter].map(toRegistryRecord),
37
+ fallbackAdapterId: plaintextAdapter.id,
38
+ };
39
+ }
40
+
41
+ export function resolveLanguageAdapter(input = {}) {
42
+ const filePath = String(input.filePath ?? '');
43
+ const languageId = input.languageId ? String(input.languageId) : null;
44
+ const extension = String(input.extension ?? extensionFromPath(filePath)).toLowerCase();
45
+
46
+ if (languageId) {
47
+ const byLanguage = ADAPTERS.find((adapter) => adapter.languageId === languageId);
48
+ if (byLanguage) {
49
+ return {
50
+ adapter: byLanguage,
51
+ match: 'languageId',
52
+ extension,
53
+ };
54
+ }
55
+ }
56
+
57
+ for (const adapter of ADAPTERS) {
58
+ if (adapter.extensions.includes(extension)) {
59
+ return {
60
+ adapter,
61
+ match: 'extension',
62
+ extension,
63
+ };
64
+ }
65
+ }
66
+
67
+ return {
68
+ adapter: plaintextAdapter,
69
+ match: 'fallback',
70
+ extension,
71
+ };
72
+ }
73
+
74
+ export function getAdapterCapabilities(input = {}) {
75
+ const resolved = resolveLanguageAdapter(input);
76
+ return {
77
+ adapterId: resolved.adapter.id,
78
+ languageId: resolved.adapter.languageId,
79
+ extension: resolved.extension,
80
+ match: resolved.match,
81
+ capabilities: { ...resolved.adapter.capabilities },
82
+ confidence: resolved.adapter.confidence,
83
+ fallback: resolved.adapter.fallback,
84
+ };
85
+ }
86
+
87
+ export function extractFileFacts(filePath, content, options = {}) {
88
+ const resolved = resolveLanguageAdapter({
89
+ filePath,
90
+ extension: options.extension,
91
+ languageId: options.languageId,
92
+ });
93
+ const extractor = EXTRACTORS.get(resolved.adapter.id) ?? extractPlaintextFacts;
94
+ const context = {
95
+ filePath,
96
+ extension: resolved.extension,
97
+ };
98
+
99
+ try {
100
+ return extractor(content, context);
101
+ } catch (error) {
102
+ const fallback = extractPlaintextFacts(content, context);
103
+ fallback.status = 'degraded';
104
+ fallback.confidence = 'low';
105
+ fallback.adapterId = plaintextAdapter.id;
106
+ fallback.languageId = plaintextAdapter.languageId;
107
+ fallback.diagnostics = [
108
+ ...(fallback.diagnostics ?? []),
109
+ error instanceof Error ? error.message : String(error),
110
+ ];
111
+ return fallback;
112
+ }
113
+ }
114
+
115
+ export function extractFileFactsBatch(entries, options = {}) {
116
+ const facts = (entries ?? []).map((entry) => extractFileFacts(entry.filePath, entry.content, options));
117
+ return {
118
+ schema: 'workgraph.language-file-facts.batch.v1',
119
+ facts,
120
+ summary: {
121
+ total: facts.length,
122
+ degraded: facts.filter((fact) => fact.status === 'degraded').length,
123
+ languages: [...new Set(facts.map((fact) => fact.languageId))].sort(),
124
+ },
125
+ };
126
+ }
127
+
128
+ export function buildTargetFileFactsProjection(readResult) {
129
+ const entries = (readResult?.files ?? [])
130
+ .filter((entry) => entry.ok)
131
+ .map((entry) => ({ filePath: entry.path, content: entry.content }));
132
+
133
+ return extractFileFactsBatch(entries);
134
+ }
135
+
136
+ export function formatLanguageFileFactsForPrompt(batch) {
137
+ if (!batch?.facts?.length) {
138
+ return '';
139
+ }
140
+
141
+ return batch.facts.map((fact) => {
142
+ const symbolNames = fact.symbols.slice(0, 8).map((symbol) => symbol.name).join(', ');
143
+ const importNames = fact.imports.slice(0, 6).map((entry) => entry.module).join(', ');
144
+ const domainSummary = Object.entries(fact.domainMetadata ?? {})
145
+ .filter(([, value]) => value !== null && value !== undefined && value !== '')
146
+ .slice(0, 6)
147
+ .map(([key, value]) => `${key}=${Array.isArray(value) ? value.join('|') : value}`)
148
+ .join('; ');
149
+
150
+ return [
151
+ `# facts:${fact.filePath}`,
152
+ `adapter=${fact.adapterId} language=${fact.languageId} status=${fact.status} confidence=${fact.confidence}`,
153
+ symbolNames ? `symbols: ${symbolNames}` : '',
154
+ importNames ? `imports: ${importNames}` : '',
155
+ domainSummary ? `domain: ${domainSummary}` : '',
156
+ ].filter(Boolean).join('\n');
157
+ }).join('\n\n');
158
+ }
159
+
160
+ function toRegistryRecord(adapter) {
161
+ return {
162
+ id: adapter.id,
163
+ languageId: adapter.languageId,
164
+ extensions: [...adapter.extensions],
165
+ artifactProfiles: [...adapter.artifactProfiles],
166
+ capabilities: { ...adapter.capabilities },
167
+ confidence: adapter.confidence,
168
+ fallback: adapter.fallback,
169
+ owner: adapter.owner,
170
+ };
171
+ }
172
+
173
+ export {
174
+ goAdapter,
175
+ jsonYamlAdapter,
176
+ jsTsAdapter,
177
+ onebaseOsAdapter,
178
+ plaintextAdapter,
179
+ stepAdapter,
180
+ };
@@ -0,0 +1,62 @@
1
+ import { buildFileFactsEnvelope, createCapabilities } from './shared.mjs';
2
+
3
+ export const goAdapter = {
4
+ id: 'go-v1',
5
+ languageId: 'go',
6
+ extensions: ['.go'],
7
+ artifactProfiles: ['go-package'],
8
+ capabilities: createCapabilities({
9
+ syntax: 'structured',
10
+ symbols: true,
11
+ imports: true,
12
+ tests: true,
13
+ semanticChunks: true,
14
+ verificationHints: true,
15
+ }),
16
+ confidence: 'medium',
17
+ fallback: 'plaintext-v1',
18
+ owner: 'work-graph',
19
+ };
20
+
21
+ const PACKAGE_PATTERN = /^package\s+([\w]+)/mu;
22
+ const FUNC_PATTERN = /^func\s+(?:\(\s*[\w*\s]+\s+\*?[\w]+\s*\)\s+)?([\w]+)\s*\(/gmu;
23
+
24
+ export function extractGoFacts(content, context = {}) {
25
+ const filePath = context.filePath ?? '';
26
+ const text = String(content);
27
+ const packageName = text.match(PACKAGE_PATTERN)?.[1] ?? null;
28
+ const imports = [];
29
+
30
+ for (const match of text.matchAll(/^import\s+"([^"]+)"/gmu)) {
31
+ imports.push({ module: match[1] });
32
+ }
33
+
34
+ for (const match of text.matchAll(/^import\s+\([\s\S]*?\)/gmu)) {
35
+ for (const inner of match[0].matchAll(/"([^"]+)"/gu)) {
36
+ imports.push({ module: inner[1] });
37
+ }
38
+ }
39
+
40
+ const symbols = [...text.matchAll(FUNC_PATTERN)].map((match) => ({
41
+ kind: 'function',
42
+ name: match[1],
43
+ }));
44
+
45
+ const testHints = filePath.endsWith('_test.go')
46
+ ? [{ kind: 'go-test', commandHint: 'go test ./...' }]
47
+ : symbols.length > 0
48
+ ? [{ kind: 'go-package', commandHint: 'go test ./...' }]
49
+ : [];
50
+
51
+ return buildFileFactsEnvelope(goAdapter, filePath, {
52
+ imports,
53
+ symbols,
54
+ testHints,
55
+ domainMetadata: packageName ? { package: packageName } : {},
56
+ chunks: symbols.slice(0, 12).map((symbol) => ({
57
+ ref: `func:${symbol.name}`,
58
+ kind: 'function',
59
+ text: symbol.name,
60
+ })),
61
+ });
62
+ }
@@ -0,0 +1,60 @@
1
+ import { buildFileFactsEnvelope, createCapabilities } from './shared.mjs';
2
+
3
+ export const jsTsAdapter = {
4
+ id: 'js-ts-v1',
5
+ languageId: 'typescript',
6
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'],
7
+ artifactProfiles: ['javascript-module'],
8
+ capabilities: createCapabilities({
9
+ syntax: 'structured',
10
+ symbols: true,
11
+ imports: true,
12
+ tests: true,
13
+ semanticChunks: true,
14
+ }),
15
+ confidence: 'medium',
16
+ fallback: 'plaintext-v1',
17
+ owner: 'work-graph',
18
+ };
19
+
20
+ const IMPORT_PATTERN = /^\s*import\s+(?:type\s+)?(?:[\w*{}\s,$]+\s+from\s+)?['"]([^'"]+)['"]/gmu;
21
+ const EXPORT_PATTERN = /^\s*export\s+(?:async\s+)?(?:function|class|const|let|var|type|interface)\s+([\w$]+)/gmu;
22
+ const FUNCTION_PATTERN = /^\s*(?:export\s+)?(?:async\s+)?function\s+([\w$]+)/gmu;
23
+ const TEST_HINT_PATTERN = /\.(?:test|spec)\.(?:js|mjs|cjs|ts|tsx)$/iu;
24
+
25
+ export function extractJsTsFacts(content, context = {}) {
26
+ const filePath = context.filePath ?? '';
27
+ const text = String(content);
28
+ const imports = [...text.matchAll(IMPORT_PATTERN)].map((match) => ({ module: match[1] }));
29
+ const symbols = [];
30
+ const seen = new Set();
31
+
32
+ for (const pattern of [EXPORT_PATTERN, FUNCTION_PATTERN]) {
33
+ for (const match of text.matchAll(pattern)) {
34
+ const name = match[1];
35
+ if (seen.has(name)) {
36
+ continue;
37
+ }
38
+
39
+ seen.add(name);
40
+ symbols.push({ kind: 'declaration', name });
41
+ }
42
+ }
43
+
44
+ const testHints = TEST_HINT_PATTERN.test(filePath)
45
+ ? [{ kind: 'test-file', commandHint: 'npm run test:deterministic' }]
46
+ : text.includes('describe(') || text.includes('it(')
47
+ ? [{ kind: 'test-suite', commandHint: 'npm run test:deterministic' }]
48
+ : [];
49
+
50
+ return buildFileFactsEnvelope(jsTsAdapter, filePath, {
51
+ imports,
52
+ symbols,
53
+ testHints,
54
+ chunks: symbols.slice(0, 12).map((symbol) => ({
55
+ ref: `symbol:${symbol.name}`,
56
+ kind: 'declaration',
57
+ text: symbol.name,
58
+ })),
59
+ });
60
+ }
@@ -0,0 +1,103 @@
1
+ import { basenameFromPath, buildFileFactsEnvelope, createCapabilities } from './shared.mjs';
2
+
3
+ export const jsonYamlAdapter = {
4
+ id: 'json-yaml-v1',
5
+ languageId: 'yaml',
6
+ extensions: ['.json', '.yaml', '.yml'],
7
+ artifactProfiles: ['structured-metadata', 'onebase-yaml'],
8
+ capabilities: createCapabilities({
9
+ syntax: 'structured',
10
+ symbols: true,
11
+ domainMetadata: true,
12
+ semanticChunks: true,
13
+ }),
14
+ confidence: 'medium',
15
+ fallback: 'plaintext-v1',
16
+ owner: 'work-graph',
17
+ };
18
+
19
+ export function extractJsonYamlFacts(content, context = {}) {
20
+ const filePath = context.filePath ?? '';
21
+ const extension = context.extension ?? '';
22
+ const text = String(content);
23
+ const domainMetadata = inferOnebaseYamlMetadata(filePath, text);
24
+ const symbols = [];
25
+
26
+ if (domainMetadata.name) {
27
+ symbols.push({
28
+ kind: 'artifact',
29
+ name: domainMetadata.name,
30
+ profile: domainMetadata.artifactKind ?? 'yaml',
31
+ });
32
+ }
33
+
34
+ for (const field of domainMetadata.fields ?? []) {
35
+ symbols.push({ kind: 'field', name: field.name, type: field.type ?? 'unknown' });
36
+ }
37
+
38
+ if (extension === '.json') {
39
+ try {
40
+ const parsed = JSON.parse(text);
41
+ domainMetadata.jsonKeys = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
42
+ ? Object.keys(parsed).sort()
43
+ : [];
44
+ } catch (error) {
45
+ return buildFileFactsEnvelope(jsonYamlAdapter, filePath, {
46
+ symbols,
47
+ domainMetadata,
48
+ diagnostics: [error instanceof Error ? error.message : String(error)],
49
+ chunks: [{ ref: 'json:invalid', kind: 'json', text: text.slice(0, 500) }],
50
+ }, { status: 'degraded', confidence: 'low' });
51
+ }
52
+ }
53
+
54
+ return buildFileFactsEnvelope(jsonYamlAdapter, filePath, {
55
+ symbols,
56
+ domainMetadata,
57
+ chunks: [{
58
+ ref: 'metadata:summary',
59
+ kind: 'yaml-metadata',
60
+ text: summarizeYamlMetadata(domainMetadata),
61
+ }],
62
+ });
63
+ }
64
+
65
+ function inferOnebaseYamlMetadata(filePath, text) {
66
+ const normalizedPath = String(filePath).replace(/\\/gu, '/').toLowerCase();
67
+ const artifactKind = normalizedPath.includes('/catalogs/')
68
+ ? 'catalog'
69
+ : normalizedPath.includes('/documents/')
70
+ ? 'document'
71
+ : normalizedPath.includes('/reports/')
72
+ ? 'report'
73
+ : normalizedPath.includes('/widgets/')
74
+ ? 'widget'
75
+ : normalizedPath.includes('/registers/')
76
+ ? 'register'
77
+ : 'yaml';
78
+
79
+ const nameMatch = text.match(/^name:\s*(.+)$/mu);
80
+ const postingMatch = text.match(/^posting:\s*(.+)$/mu);
81
+ const fields = [...text.matchAll(/-\s*\{\s*name:\s*([^,]+),\s*type:\s*([^}]+)\}/gu)]
82
+ .map((match) => ({ name: match[1].trim(), type: match[2].trim() }));
83
+
84
+ return {
85
+ artifactKind,
86
+ basename: basenameFromPath(filePath),
87
+ name: nameMatch?.[1]?.trim() ?? null,
88
+ posting: postingMatch?.[1]?.trim() ?? null,
89
+ fields,
90
+ hierarchical: /^hierarchical:\s*true/mu.test(text),
91
+ };
92
+ }
93
+
94
+ function summarizeYamlMetadata(metadata) {
95
+ const parts = [
96
+ metadata.artifactKind ? `kind=${metadata.artifactKind}` : '',
97
+ metadata.name ? `name=${metadata.name}` : '',
98
+ metadata.posting ? `posting=${metadata.posting}` : '',
99
+ metadata.fields?.length ? `fields=${metadata.fields.length}` : '',
100
+ ].filter(Boolean);
101
+
102
+ return parts.join(' ');
103
+ }