@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,217 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, relative, resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_ANALYTICS_RECORD_JOURNAL_PATH,
|
|
6
|
+
appendAnalyticsRecordJournal,
|
|
7
|
+
buildAnalyticsRecord,
|
|
8
|
+
readAnalyticsRecordJournal,
|
|
9
|
+
} from './analyticsRecordStore.mjs';
|
|
10
|
+
|
|
11
|
+
export const SEED_ANALYTICS_RECORD_SCHEMA = 'workgraph.seed-analytics-record.v1';
|
|
12
|
+
|
|
13
|
+
const H1_PATTERN = /^#\s+(AN-\d+(?:-[A-Z0-9]+)?)\s*:\s*(.+)$/mu;
|
|
14
|
+
const QUERY_PATTERN = /^\*\*Запрос:\*\*\s*(.+)$/mu;
|
|
15
|
+
|
|
16
|
+
function splitCsv(value) {
|
|
17
|
+
return String(value ?? '')
|
|
18
|
+
.split(',')
|
|
19
|
+
.map((entry) => entry.trim())
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeBodyPath(bodyPath, cwd) {
|
|
24
|
+
const absolutePath = resolve(cwd, bodyPath);
|
|
25
|
+
return relative(cwd, absolutePath).replace(/\\/gu, '/');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function slugFromBodyPath(bodyPath) {
|
|
29
|
+
return basename(bodyPath, '.md')
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/[^a-z0-9]+/gu, '-')
|
|
32
|
+
.replace(/^-+|-+$/gu, '')
|
|
33
|
+
.slice(0, 48);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseAnalyticsMarkdownMetadata(body) {
|
|
37
|
+
const h1Match = body.match(H1_PATTERN);
|
|
38
|
+
const queryMatch = body.match(QUERY_PATTERN);
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
key: h1Match?.[1]?.trim() ?? '',
|
|
42
|
+
title: h1Match?.[2]?.trim() ?? '',
|
|
43
|
+
query: queryMatch?.[1]?.trim() ?? '',
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function parseSeedAnalyticsRecordArgs(argv, options = {}) {
|
|
48
|
+
const cwd = options.cwd ?? process.cwd();
|
|
49
|
+
const parsed = {
|
|
50
|
+
cwd,
|
|
51
|
+
body: '',
|
|
52
|
+
key: '',
|
|
53
|
+
title: '',
|
|
54
|
+
query: '',
|
|
55
|
+
topic: 'general',
|
|
56
|
+
tags: [],
|
|
57
|
+
relatedFiles: [],
|
|
58
|
+
journalPath: DEFAULT_ANALYTICS_RECORD_JOURNAL_PATH,
|
|
59
|
+
dryRun: false,
|
|
60
|
+
force: false,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
64
|
+
const arg = argv[index];
|
|
65
|
+
const next = argv[index + 1];
|
|
66
|
+
|
|
67
|
+
if (arg === '--body' && next) {
|
|
68
|
+
parsed.body = next;
|
|
69
|
+
index += 1;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (arg === '--key' && next) {
|
|
73
|
+
parsed.key = next.trim();
|
|
74
|
+
index += 1;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (arg === '--title' && next) {
|
|
78
|
+
parsed.title = next.trim();
|
|
79
|
+
index += 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (arg === '--query' && next) {
|
|
83
|
+
parsed.query = next.trim();
|
|
84
|
+
index += 1;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (arg === '--topic' && next) {
|
|
88
|
+
parsed.topic = next.trim();
|
|
89
|
+
index += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (arg === '--tags' && next) {
|
|
93
|
+
parsed.tags = splitCsv(next);
|
|
94
|
+
index += 1;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (arg === '--related-files' && next) {
|
|
98
|
+
parsed.relatedFiles = splitCsv(next);
|
|
99
|
+
index += 1;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (arg === '--journal' && next) {
|
|
103
|
+
parsed.journalPath = next;
|
|
104
|
+
index += 1;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (arg === '--cwd' && next) {
|
|
108
|
+
parsed.cwd = resolve(next);
|
|
109
|
+
index += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (arg === '--dry-run') {
|
|
113
|
+
parsed.dryRun = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (arg === '--force') {
|
|
117
|
+
parsed.force = true;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!parsed.body) {
|
|
122
|
+
throw new TypeError('--body is required (path to work/analytics/*.md)');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
parsed.bodyPath = normalizeBodyPath(parsed.body, parsed.cwd);
|
|
126
|
+
return parsed;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function pickNonEmpty(explicit, fallback) {
|
|
130
|
+
const value = String(explicit ?? '').trim();
|
|
131
|
+
return value || String(fallback ?? '').trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function buildAnalyticsRecordInputFromMarkdown(options) {
|
|
135
|
+
const bodyPath = normalizeBodyPath(options.body ?? options.bodyPath, options.cwd ?? process.cwd());
|
|
136
|
+
const absolutePath = resolve(options.cwd ?? process.cwd(), bodyPath);
|
|
137
|
+
const body = await readFile(absolutePath, 'utf8');
|
|
138
|
+
const metadata = parseAnalyticsMarkdownMetadata(body);
|
|
139
|
+
|
|
140
|
+
const key = pickNonEmpty(options.key, metadata.key);
|
|
141
|
+
const title = pickNonEmpty(options.title, metadata.title);
|
|
142
|
+
const query = pickNonEmpty(options.query, metadata.query);
|
|
143
|
+
|
|
144
|
+
if (!title) {
|
|
145
|
+
throw new TypeError(`title is required (--title or H1 "# AN-XX: title" in ${bodyPath})`);
|
|
146
|
+
}
|
|
147
|
+
if (!query) {
|
|
148
|
+
throw new TypeError(`query is required (--query or "**Запрос:**" in ${bodyPath})`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const slug = slugFromBodyPath(bodyPath);
|
|
152
|
+
const tags = [...(options.tags ?? [])];
|
|
153
|
+
if (key && !tags.includes(key)) {
|
|
154
|
+
tags.push(key);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const relatedFiles = [...(options.relatedFiles ?? [])];
|
|
158
|
+
if (!relatedFiles.includes(bodyPath)) {
|
|
159
|
+
relatedFiles.unshift(bodyPath);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
title: key ? `${key}: ${title}` : title,
|
|
164
|
+
query,
|
|
165
|
+
slug,
|
|
166
|
+
topic: options.topic ?? 'general',
|
|
167
|
+
status: options.status ?? 'published',
|
|
168
|
+
tags,
|
|
169
|
+
relatedFiles,
|
|
170
|
+
bodyPath,
|
|
171
|
+
author: options.author ?? 'agent',
|
|
172
|
+
...(key ? { key } : {}),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function seedAnalyticsRecord(options) {
|
|
177
|
+
const cwd = options.cwd ?? process.cwd();
|
|
178
|
+
const journalPath = options.journalPath ?? DEFAULT_ANALYTICS_RECORD_JOURNAL_PATH;
|
|
179
|
+
const recordInput = await buildAnalyticsRecordInputFromMarkdown({ ...options, cwd });
|
|
180
|
+
const record = buildAnalyticsRecord(recordInput);
|
|
181
|
+
|
|
182
|
+
const journal = await readAnalyticsRecordJournal({ cwd, journalPath });
|
|
183
|
+
const existingById = journal.records.find((entry) => entry.id === record.id);
|
|
184
|
+
const existingByKey = record.key
|
|
185
|
+
? journal.records.find((entry) => entry.key === record.key)
|
|
186
|
+
: null;
|
|
187
|
+
const existingByBodyPath = journal.records.find((entry) => entry.bodyPath === record.bodyPath);
|
|
188
|
+
|
|
189
|
+
if (!options.force && (existingById || existingByKey || existingByBodyPath)) {
|
|
190
|
+
return {
|
|
191
|
+
schema: SEED_ANALYTICS_RECORD_SCHEMA,
|
|
192
|
+
skipped: true,
|
|
193
|
+
reason: 'record_exists',
|
|
194
|
+
id: existingById?.id ?? existingByKey?.id ?? existingByBodyPath?.id ?? record.id,
|
|
195
|
+
key: existingById?.key ?? existingByKey?.key ?? existingByBodyPath?.key ?? record.key ?? null,
|
|
196
|
+
bodyPath: record.bodyPath,
|
|
197
|
+
journalPath: resolve(cwd, journalPath),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const appendResult = await appendAnalyticsRecordJournal([record], journalPath, {
|
|
202
|
+
cwd,
|
|
203
|
+
dryRun: options.dryRun === true,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
schema: SEED_ANALYTICS_RECORD_SCHEMA,
|
|
208
|
+
skipped: false,
|
|
209
|
+
appended: appendResult.appended,
|
|
210
|
+
dryRun: options.dryRun === true,
|
|
211
|
+
id: record.id,
|
|
212
|
+
key: record.key ?? null,
|
|
213
|
+
title: record.title,
|
|
214
|
+
bodyPath: record.bodyPath,
|
|
215
|
+
journalPath: resolve(cwd, journalPath),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
2
|
+
|
|
3
|
+
export function tokenizeForBm25(text) {
|
|
4
|
+
return String(text ?? '')
|
|
5
|
+
.toLowerCase()
|
|
6
|
+
.split(/[^a-z0-9\u0400-\u04ff]+/u)
|
|
7
|
+
.map((token) => token.trim())
|
|
8
|
+
.filter((token) => token.length >= 2);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function buildBm25Index(documents) {
|
|
12
|
+
const docTokens = documents.map((document) => {
|
|
13
|
+
const corpusText = [
|
|
14
|
+
document.label,
|
|
15
|
+
document.summary,
|
|
16
|
+
...(document.parts ?? []).map((part) => part.text),
|
|
17
|
+
document.excerpt ?? '',
|
|
18
|
+
].filter(Boolean).join(' ');
|
|
19
|
+
|
|
20
|
+
const tokens = tokenizeForBm25(corpusText);
|
|
21
|
+
const termFreq = new Map();
|
|
22
|
+
for (const token of tokens) {
|
|
23
|
+
termFreq.set(token, (termFreq.get(token) ?? 0) + 1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
id: document.id,
|
|
28
|
+
length: tokens.length,
|
|
29
|
+
termFreq,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const documentFrequency = new Map();
|
|
34
|
+
for (const doc of docTokens) {
|
|
35
|
+
for (const term of doc.termFreq.keys()) {
|
|
36
|
+
documentFrequency.set(term, (documentFrequency.get(term) ?? 0) + 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const avgDocLength = docTokens.length === 0
|
|
41
|
+
? 0
|
|
42
|
+
: docTokens.reduce((sum, doc) => sum + doc.length, 0) / docTokens.length;
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
docTokens,
|
|
46
|
+
documentFrequency,
|
|
47
|
+
avgDocLength,
|
|
48
|
+
documentCount: docTokens.length,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function scoreBm25(queryTokens, index, options = {}) {
|
|
53
|
+
const k1 = options.k1 ?? 1.2;
|
|
54
|
+
const b = options.b ?? 0.75;
|
|
55
|
+
const scores = new Map();
|
|
56
|
+
|
|
57
|
+
if (queryTokens.length === 0 || index.documentCount === 0) {
|
|
58
|
+
return scores;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
for (const doc of index.docTokens) {
|
|
62
|
+
let score = 0;
|
|
63
|
+
|
|
64
|
+
for (const term of queryTokens) {
|
|
65
|
+
const tf = doc.termFreq.get(term) ?? 0;
|
|
66
|
+
if (tf === 0) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const df = index.documentFrequency.get(term) ?? 0;
|
|
71
|
+
const idf = Math.log(1 + (index.documentCount - df + 0.5) / (df + 0.5));
|
|
72
|
+
const numerator = tf * (k1 + 1);
|
|
73
|
+
const denominator = tf + k1 * (1 - b + b * (doc.length / Math.max(index.avgDocLength, 1)));
|
|
74
|
+
score += idf * (numerator / denominator);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (score > 0) {
|
|
78
|
+
scores.set(doc.id, Number(score.toFixed(4)));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return scores;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function mergeLexicalAndBm25Scores(entries, bm25Scores, options = {}) {
|
|
86
|
+
const bm25Weight = Number(options.bm25Weight ?? 4);
|
|
87
|
+
|
|
88
|
+
return entries
|
|
89
|
+
.map((entry) => ({
|
|
90
|
+
...entry,
|
|
91
|
+
lexicalScore: entry.score,
|
|
92
|
+
bm25Score: bm25Scores.get(entry.id) ?? 0,
|
|
93
|
+
score: entry.score + bm25Weight * (bm25Scores.get(entry.id) ?? 0),
|
|
94
|
+
}))
|
|
95
|
+
.sort((left, right) => {
|
|
96
|
+
const byScore = right.score - left.score;
|
|
97
|
+
if (byScore !== 0) {
|
|
98
|
+
return byScore;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return compareText(left.id, right.id);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { normalizeBoundedTargetPath } from './workGraphBoundedTargetFileRead.mjs';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_EXCERPT_CHARS = 1200;
|
|
7
|
+
|
|
8
|
+
export async function readSemanticSearchFileExcerpt(filePath, options = {}) {
|
|
9
|
+
const repoRoot = resolve(options.repoRoot ?? options.cwd ?? process.cwd());
|
|
10
|
+
const normalized = normalizeBoundedTargetPath(filePath, repoRoot);
|
|
11
|
+
if (!normalized.ok) {
|
|
12
|
+
return { ok: false, path: filePath, excerpt: '', error: normalized.error };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const readFileImpl = options.readFile ?? readFile;
|
|
16
|
+
const maxChars = options.maxExcerptChars ?? DEFAULT_EXCERPT_CHARS;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const absolute = resolve(repoRoot, normalized.path);
|
|
20
|
+
const content = await readFileImpl(absolute, 'utf8');
|
|
21
|
+
const excerpt = content.slice(0, maxChars).trim();
|
|
22
|
+
return {
|
|
23
|
+
ok: true,
|
|
24
|
+
path: normalized.path,
|
|
25
|
+
excerpt,
|
|
26
|
+
truncated: content.length > maxChars,
|
|
27
|
+
};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
+
return { ok: false, path: normalized.path, excerpt: '', error: message };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function enrichSemanticDocumentsWithExcerpts(documents, options = {}) {
|
|
35
|
+
const repoRoot = resolve(options.repoRoot ?? options.cwd ?? process.cwd());
|
|
36
|
+
const targetFilesByWorkId = options.targetFilesByWorkId ?? new Map();
|
|
37
|
+
const enriched = [];
|
|
38
|
+
|
|
39
|
+
for (const document of documents) {
|
|
40
|
+
const excerptPaths = document.kind === 'file_artifact' && document.filePath
|
|
41
|
+
? [document.filePath]
|
|
42
|
+
: document.kind === 'work_item'
|
|
43
|
+
? (targetFilesByWorkId.get(document.workId) ?? []).slice(0, options.maxFilesPerWorkItem ?? 2)
|
|
44
|
+
: [];
|
|
45
|
+
|
|
46
|
+
let excerpt = '';
|
|
47
|
+
const excerptSources = [];
|
|
48
|
+
|
|
49
|
+
for (const filePath of excerptPaths) {
|
|
50
|
+
const loaded = await readSemanticSearchFileExcerpt(filePath, { ...options, repoRoot });
|
|
51
|
+
if (loaded.ok && loaded.excerpt) {
|
|
52
|
+
excerpt += `${loaded.excerpt}\n`;
|
|
53
|
+
excerptSources.push(loaded.path);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
enriched.push({
|
|
58
|
+
...document,
|
|
59
|
+
excerpt: excerpt.trim(),
|
|
60
|
+
excerptSources,
|
|
61
|
+
parts: excerpt
|
|
62
|
+
? [...(document.parts ?? []), { text: excerpt, weight: 3 }]
|
|
63
|
+
: [...(document.parts ?? [])],
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return enriched;
|
|
68
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { tokenizeForBm25 } from './semanticSearchBm25.mjs';
|
|
2
|
+
|
|
3
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
4
|
+
|
|
5
|
+
function documentText(document) {
|
|
6
|
+
const parts = document.parts ?? [];
|
|
7
|
+
return parts.map((part) => String(part.text ?? '')).join('\n');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function buildTfidfIndex(documents) {
|
|
11
|
+
const docCount = documents.length;
|
|
12
|
+
const docFreq = new Map();
|
|
13
|
+
const docTokens = documents.map((document) => {
|
|
14
|
+
const counts = new Map();
|
|
15
|
+
for (const token of tokenizeForBm25(documentText(document))) {
|
|
16
|
+
counts.set(token, (counts.get(token) ?? 0) + 1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
for (const token of counts.keys()) {
|
|
20
|
+
docFreq.set(token, (docFreq.get(token) ?? 0) + 1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return { id: document.id, counts };
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const vectors = docTokens.map((entry) => {
|
|
27
|
+
const total = [...entry.counts.values()].reduce((sum, value) => sum + value, 0) || 1;
|
|
28
|
+
const weights = new Map();
|
|
29
|
+
|
|
30
|
+
for (const [token, count] of entry.counts.entries()) {
|
|
31
|
+
const tf = count / total;
|
|
32
|
+
const idf = Math.log((docCount + 1) / ((docFreq.get(token) ?? 0) + 1)) + 1;
|
|
33
|
+
weights.set(token, tf * idf);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { id: entry.id, weights };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
schema: 'semantic-search.tfidf-index.v1',
|
|
41
|
+
docCount,
|
|
42
|
+
vectors,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function vectorNorm(weights) {
|
|
47
|
+
let sum = 0;
|
|
48
|
+
for (const value of weights.values()) {
|
|
49
|
+
sum += value * value;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return Math.sqrt(sum) || 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function cosineSimilarity(left, right) {
|
|
56
|
+
let dot = 0;
|
|
57
|
+
for (const [token, weight] of left.entries()) {
|
|
58
|
+
dot += weight * (right.get(token) ?? 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return dot / (vectorNorm(left) * vectorNorm(right));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function scoreTfidfCosine(query, index) {
|
|
65
|
+
const queryCounts = new Map();
|
|
66
|
+
for (const token of tokenizeForBm25(String(query ?? ''))) {
|
|
67
|
+
queryCounts.set(token, (queryCounts.get(token) ?? 0) + 1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const total = [...queryCounts.values()].reduce((sum, value) => sum + value, 0) || 1;
|
|
71
|
+
const queryWeights = new Map();
|
|
72
|
+
for (const [token, count] of queryCounts.entries()) {
|
|
73
|
+
queryWeights.set(token, count / total);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return index.vectors
|
|
77
|
+
.map((entry) => ({
|
|
78
|
+
id: entry.id,
|
|
79
|
+
score: cosineSimilarity(queryWeights, entry.weights),
|
|
80
|
+
}))
|
|
81
|
+
.filter((entry) => entry.score > 0)
|
|
82
|
+
.sort((left, right) => {
|
|
83
|
+
const byScore = right.score - left.score;
|
|
84
|
+
return byScore !== 0 ? byScore : compareText(left.id, right.id);
|
|
85
|
+
});
|
|
86
|
+
}
|