@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,159 @@
|
|
|
1
|
+
import {
|
|
2
|
+
evaluatePromoteReadyEligibility,
|
|
3
|
+
isPromotableBacklogItem,
|
|
4
|
+
} from './workGraphRuntime.mjs';
|
|
5
|
+
|
|
6
|
+
export const PHASE_8_PLUS_ANCHORS = [
|
|
7
|
+
'phase-8-agent-prompt-eval-tools',
|
|
8
|
+
'phase-9-ui-operator-shell',
|
|
9
|
+
'phase-10-ci-e2e-release-gates',
|
|
10
|
+
'phase-11-gbc-gfs-gvm-deferred',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const PRIORITY_ORDER = new Map([
|
|
14
|
+
['critical', 0],
|
|
15
|
+
['high', 1],
|
|
16
|
+
['medium', 2],
|
|
17
|
+
['low', 3],
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
|
|
21
|
+
|
|
22
|
+
export function extractPhaseNumberFromWorkId(workId) {
|
|
23
|
+
const match = String(workId ?? '').match(/^phase-(\d+)/u);
|
|
24
|
+
return match ? Number(match[1]) : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function extractPhaseNumberFromLabels(labels = {}) {
|
|
28
|
+
const targetPhase = String(labels['migration.target_phase'] ?? '').trim();
|
|
29
|
+
const fromTarget = extractPhaseNumberFromWorkId(targetPhase);
|
|
30
|
+
if (fromTarget !== null) {
|
|
31
|
+
return fromTarget;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function buildPhaseAffinityIndex(items) {
|
|
38
|
+
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
39
|
+
const affinity = new Map();
|
|
40
|
+
|
|
41
|
+
for (const item of items) {
|
|
42
|
+
const direct = extractPhaseNumberFromWorkId(item.id) ?? extractPhaseNumberFromLabels(item.labels);
|
|
43
|
+
if (direct !== null) {
|
|
44
|
+
affinity.set(item.id, direct);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let changed = true;
|
|
49
|
+
while (changed) {
|
|
50
|
+
changed = false;
|
|
51
|
+
for (const item of items) {
|
|
52
|
+
if (affinity.has(item.id)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const deps = item.dependsOn ?? [];
|
|
57
|
+
const depPhases = deps
|
|
58
|
+
.map((depId) => affinity.get(depId))
|
|
59
|
+
.filter((phase) => Number.isInteger(phase));
|
|
60
|
+
|
|
61
|
+
if (depPhases.length > 0) {
|
|
62
|
+
affinity.set(item.id, Math.min(...depPhases));
|
|
63
|
+
changed = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (PHASE_8_PLUS_ANCHORS.some((anchorId) => deps.includes(anchorId))) {
|
|
68
|
+
const anchorPhase = Math.min(
|
|
69
|
+
...PHASE_8_PLUS_ANCHORS
|
|
70
|
+
.filter((anchorId) => deps.includes(anchorId))
|
|
71
|
+
.map((anchorId) => extractPhaseNumberFromWorkId(anchorId) ?? 99),
|
|
72
|
+
);
|
|
73
|
+
affinity.set(item.id, anchorPhase);
|
|
74
|
+
changed = true;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const anchorId of PHASE_8_PLUS_ANCHORS) {
|
|
80
|
+
const phase = extractPhaseNumberFromWorkId(anchorId);
|
|
81
|
+
if (phase !== null) {
|
|
82
|
+
affinity.set(anchorId, phase);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { affinity, itemById };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function resolveWorkItemPhase(item, affinityIndex) {
|
|
90
|
+
return affinityIndex.affinity.get(item.id)
|
|
91
|
+
?? extractPhaseNumberFromWorkId(item.id)
|
|
92
|
+
?? extractPhaseNumberFromLabels(item.labels);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isWorkItemAtLeastMinPhase(item, affinityIndex, minPhase = 8) {
|
|
96
|
+
const phase = resolveWorkItemPhase(item, affinityIndex);
|
|
97
|
+
return Number.isInteger(phase) && phase >= minPhase;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** @deprecated Prefer {@link isWorkItemAtLeastMinPhase} with explicit minPhase. */
|
|
101
|
+
export function isPhase8PlusWorkItem(item, affinityIndex) {
|
|
102
|
+
return isWorkItemAtLeastMinPhase(item, affinityIndex, 8);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function comparePromoteQueueItems(left, right) {
|
|
106
|
+
const byPriority = (PRIORITY_ORDER.get(left.priority) ?? 9) - (PRIORITY_ORDER.get(right.priority) ?? 9);
|
|
107
|
+
if (byPriority !== 0) {
|
|
108
|
+
return byPriority;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return compareText(left.id, right.id);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function buildPhasePromoteReadyQueue(items, options = {}) {
|
|
115
|
+
const minPhase = Number.isInteger(options.minPhase) ? options.minPhase : 8;
|
|
116
|
+
const limit = Number.isInteger(options.limit) ? options.limit : 12;
|
|
117
|
+
const affinityIndex = buildPhaseAffinityIndex(items);
|
|
118
|
+
|
|
119
|
+
const backlogPhaseItems = items.filter(
|
|
120
|
+
(item) => item.status === 'backlog' && isWorkItemAtLeastMinPhase(item, affinityIndex, minPhase),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const promotable = backlogPhaseItems
|
|
124
|
+
.filter((item) => isPromotableBacklogItem(items, item))
|
|
125
|
+
.map((item) => ({
|
|
126
|
+
workId: item.id,
|
|
127
|
+
title: item.title,
|
|
128
|
+
priority: item.priority,
|
|
129
|
+
phase: affinityIndex.affinity.get(item.id) ?? null,
|
|
130
|
+
dependsOn: [...(item.dependsOn ?? [])],
|
|
131
|
+
nextAction: item.nextAction,
|
|
132
|
+
}))
|
|
133
|
+
.sort(comparePromoteQueueItems)
|
|
134
|
+
.slice(0, limit);
|
|
135
|
+
|
|
136
|
+
const blocked = backlogPhaseItems
|
|
137
|
+
.filter((item) => !isPromotableBacklogItem(items, item))
|
|
138
|
+
.map((item) => {
|
|
139
|
+
const eligibility = evaluatePromoteReadyEligibility(items, item.id);
|
|
140
|
+
return {
|
|
141
|
+
workId: item.id,
|
|
142
|
+
title: item.title,
|
|
143
|
+
phase: affinityIndex.affinity.get(item.id) ?? null,
|
|
144
|
+
error: eligibility.error ?? 'not_promotable',
|
|
145
|
+
unsatisfiedDependencies: eligibility.unsatisfiedDependencies ?? [],
|
|
146
|
+
};
|
|
147
|
+
})
|
|
148
|
+
.sort((left, right) => compareText(left.workId, right.workId));
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
schema: 'workgraph.promote-ready-queue.v1',
|
|
152
|
+
minPhase,
|
|
153
|
+
phaseAnchors: [...PHASE_8_PLUS_ANCHORS],
|
|
154
|
+
queue: promotable,
|
|
155
|
+
blocked,
|
|
156
|
+
backlogCount: backlogPhaseItems.length,
|
|
157
|
+
promotableCount: promotable.length,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
defaultRegistryPath,
|
|
5
|
+
findWorkspace,
|
|
6
|
+
listWorkspaces,
|
|
7
|
+
readWorkspaceRegistry,
|
|
8
|
+
registerWorkspace,
|
|
9
|
+
resolveWorkspaceRoot,
|
|
10
|
+
setActiveWorkspace,
|
|
11
|
+
slugFromRoot,
|
|
12
|
+
} from './workspaceRegistry.mjs';
|
|
13
|
+
|
|
14
|
+
export function createWorkGraphHostState(options = {}) {
|
|
15
|
+
const hostRoot = resolve(options.hostRoot ?? options.cwd ?? process.cwd());
|
|
16
|
+
return {
|
|
17
|
+
hostRoot,
|
|
18
|
+
registryPath: options.registryPath ?? defaultRegistryPath(options),
|
|
19
|
+
activeProjectId: null,
|
|
20
|
+
initialized: false,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function resolveRepoRootFromEnv() {
|
|
25
|
+
const fromEnv = String(process.env.WG_PROJECT_ROOT ?? '').trim();
|
|
26
|
+
return fromEnv === '' ? null : resolve(fromEnv);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function ensureHostStateInitialized(hostState, options = {}) {
|
|
30
|
+
if (hostState.initialized) {
|
|
31
|
+
return hostState;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const hostRoot = hostState.hostRoot;
|
|
35
|
+
let registry = await readWorkspaceRegistry({ registryPath: hostState.registryPath });
|
|
36
|
+
|
|
37
|
+
const hostSlug = slugFromRoot(hostRoot);
|
|
38
|
+
if (!findWorkspace(registry, hostSlug)) {
|
|
39
|
+
registry = await registerWorkspace({
|
|
40
|
+
id: hostSlug,
|
|
41
|
+
root: hostRoot,
|
|
42
|
+
label: options.hostLabel ?? 'Work Graph',
|
|
43
|
+
}, { registryPath: hostState.registryPath });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const envRoot = resolveRepoRootFromEnv();
|
|
47
|
+
if (envRoot) {
|
|
48
|
+
const envId = slugFromRoot(envRoot);
|
|
49
|
+
registry = await registerWorkspace({
|
|
50
|
+
id: envId,
|
|
51
|
+
root: envRoot,
|
|
52
|
+
label: options.envLabel ?? basenameOrSlug(envRoot),
|
|
53
|
+
}, { registryPath: hostState.registryPath });
|
|
54
|
+
registry = await setActiveWorkspace(envId, { registryPath: hostState.registryPath });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
hostState.activeProjectId = registry.activeProjectId
|
|
58
|
+
?? findWorkspace(registry, hostSlug)?.id
|
|
59
|
+
?? hostSlug;
|
|
60
|
+
hostState._registryCache = registry;
|
|
61
|
+
hostState.initialized = true;
|
|
62
|
+
return hostState;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function basenameOrSlug(root) {
|
|
66
|
+
const parts = resolve(root).split(/[\\/]/u);
|
|
67
|
+
return parts[parts.length - 1] || slugFromRoot(root);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveWorkGraphRequestContext(hostState, url, options = {}) {
|
|
71
|
+
if (!hostState?.initialized) {
|
|
72
|
+
throw new Error('hostState is not initialized');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const queryProjectId = url?.searchParams?.get('projectId')?.trim() || null;
|
|
76
|
+
const queryRepoRoot = url?.searchParams?.get('repoRoot')?.trim() || null;
|
|
77
|
+
const projectId = queryProjectId || hostState.activeProjectId;
|
|
78
|
+
let repoRoot = queryRepoRoot ? resolve(queryRepoRoot) : null;
|
|
79
|
+
|
|
80
|
+
if (!repoRoot && projectId) {
|
|
81
|
+
const registry = hostState._registryCache ?? { workspaces: [] };
|
|
82
|
+
repoRoot = resolveWorkspaceRoot(registry, projectId);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (!repoRoot) {
|
|
86
|
+
repoRoot = hostState.hostRoot;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
hostRoot: hostState.hostRoot,
|
|
91
|
+
registryPath: hostState.registryPath,
|
|
92
|
+
activeProjectId: hostState.activeProjectId,
|
|
93
|
+
projectId: projectId ?? slugFromRoot(repoRoot),
|
|
94
|
+
repoRoot,
|
|
95
|
+
backlogPath: options.backlogPath,
|
|
96
|
+
journalPath: options.journalPath,
|
|
97
|
+
auditPath: options.auditPath,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function refreshHostRegistryCache(hostState) {
|
|
102
|
+
const registry = await readWorkspaceRegistry({ registryPath: hostState.registryPath });
|
|
103
|
+
hostState._registryCache = registry;
|
|
104
|
+
hostState.activeProjectId = registry.activeProjectId ?? hostState.activeProjectId;
|
|
105
|
+
return registry;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function buildWorkspacesApiResponse(hostState) {
|
|
109
|
+
await refreshHostRegistryCache(hostState);
|
|
110
|
+
const payload = await listWorkspaces({ registryPath: hostState.registryPath });
|
|
111
|
+
const active = findWorkspace(
|
|
112
|
+
{ workspaces: payload.workspaces },
|
|
113
|
+
hostState.activeProjectId,
|
|
114
|
+
);
|
|
115
|
+
return {
|
|
116
|
+
...payload,
|
|
117
|
+
activeProjectId: hostState.activeProjectId,
|
|
118
|
+
activeRepoRoot: active?.root ?? hostState.hostRoot,
|
|
119
|
+
hostRoot: hostState.hostRoot,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function switchHostWorkspace(hostState, projectId) {
|
|
124
|
+
const registry = await setActiveWorkspace(projectId, { registryPath: hostState.registryPath });
|
|
125
|
+
hostState._registryCache = registry;
|
|
126
|
+
hostState.activeProjectId = registry.activeProjectId;
|
|
127
|
+
const active = findWorkspace(registry, hostState.activeProjectId);
|
|
128
|
+
return {
|
|
129
|
+
ok: true,
|
|
130
|
+
schema: 'workgraph.workspace.switch.v1',
|
|
131
|
+
activeProjectId: hostState.activeProjectId,
|
|
132
|
+
activeRepoRoot: active?.root ?? hostState.hostRoot,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function registerHostWorkspace(hostState, input = {}) {
|
|
137
|
+
const registry = await registerWorkspace(input, { registryPath: hostState.registryPath });
|
|
138
|
+
hostState._registryCache = registry;
|
|
139
|
+
if (!hostState.activeProjectId) {
|
|
140
|
+
hostState.activeProjectId = registry.activeProjectId;
|
|
141
|
+
}
|
|
142
|
+
const entry = findWorkspace(registry, String(input.id ?? input.projectId ?? slugFromRoot(input.root ?? input.path)));
|
|
143
|
+
return {
|
|
144
|
+
ok: true,
|
|
145
|
+
schema: 'workgraph.workspace.register.v1',
|
|
146
|
+
workspace: entry,
|
|
147
|
+
activeProjectId: hostState.activeProjectId,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { access, mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
buildDoctorReport,
|
|
7
|
+
defaultEngineRootFromCliModule,
|
|
8
|
+
importBacklogUiServer,
|
|
9
|
+
importMcpServer,
|
|
10
|
+
resolveEngineRoot,
|
|
11
|
+
} from './workGraphEngineRoot.mjs';
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
buildDoctorReport,
|
|
15
|
+
defaultEngineRootFromCliModule,
|
|
16
|
+
importBacklogUiServer,
|
|
17
|
+
importMcpServer,
|
|
18
|
+
resolveEngineRoot,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const CONFIG_SCHEMA_V1 = 'workgraph.project.config.v1';
|
|
22
|
+
const CONFIG_SCHEMA_V2 = 'workgraph.project.config.v2';
|
|
23
|
+
const DEFAULT_CLI_VERSION = '0.2.0';
|
|
24
|
+
const DEFAULT_MCP_VERSION = '0.2.0';
|
|
25
|
+
|
|
26
|
+
const INDEX_STUB = `#Index<[
|
|
27
|
+
WorkItems:
|
|
28
|
+
]>
|
|
29
|
+
`;
|
|
30
|
+
const MAIN_BVC_STUB = `#Architecture_Main<[
|
|
31
|
+
Базис:
|
|
32
|
+
Каркас architecture/main.bvc создан через work-graph init.
|
|
33
|
+
]>
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
export function buildProjectConfig({ projectRoot, engineRoot, label, id, uiPort, schemaVersion = 2 } = {}) {
|
|
37
|
+
const root = resolve(projectRoot);
|
|
38
|
+
const config = {
|
|
39
|
+
schema: schemaVersion === 1 ? CONFIG_SCHEMA_V1 : CONFIG_SCHEMA_V2,
|
|
40
|
+
projectRoot: root,
|
|
41
|
+
projectId: id ?? slugFromPath(root),
|
|
42
|
+
label: label ?? basename(root),
|
|
43
|
+
uiPort: uiPort ?? 4177,
|
|
44
|
+
createdAt: new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
if (schemaVersion === 1 && engineRoot) {
|
|
47
|
+
config.engineRoot = resolve(engineRoot);
|
|
48
|
+
}
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function slugFromPath(root) {
|
|
53
|
+
const name = basename(resolve(root));
|
|
54
|
+
return name
|
|
55
|
+
.toLowerCase()
|
|
56
|
+
.replace(/[^a-z0-9]+/gu, '-')
|
|
57
|
+
.replace(/^-+|-+$/gu, '') || 'project';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function buildRunUiScriptContent() {
|
|
61
|
+
return `#!/usr/bin/env node
|
|
62
|
+
import { readFileSync } from 'node:fs';
|
|
63
|
+
import { dirname, join, resolve } from 'node:path';
|
|
64
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
65
|
+
import { createRequire } from 'node:module';
|
|
66
|
+
|
|
67
|
+
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
68
|
+
const config = JSON.parse(readFileSync(join(projectRoot, '.work-graph/config.json'), 'utf8'));
|
|
69
|
+
|
|
70
|
+
process.env.WG_PROJECT_ROOT = projectRoot;
|
|
71
|
+
process.env.WORKGRAPH_ROOT = projectRoot;
|
|
72
|
+
|
|
73
|
+
function resolveInstallRoot() {
|
|
74
|
+
if (process.env.WORKGRAPH_ENGINE_ROOT) {
|
|
75
|
+
return resolve(process.env.WORKGRAPH_ENGINE_ROOT);
|
|
76
|
+
}
|
|
77
|
+
if (config.engineRoot) {
|
|
78
|
+
console.warn('[work-graph] config.engineRoot устарел — используйте npm packages');
|
|
79
|
+
return resolve(config.engineRoot);
|
|
80
|
+
}
|
|
81
|
+
const require = createRequire(join(projectRoot, 'package.json'));
|
|
82
|
+
const cliPkg = require.resolve('@work-graph/cli/package.json');
|
|
83
|
+
return dirname(cliPkg);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function resolveUiModule(installRoot) {
|
|
87
|
+
const candidates = [
|
|
88
|
+
join(installRoot, 'vendor/src/workGraphBacklogUiServer.mjs'),
|
|
89
|
+
join(installRoot, 'src/workGraphBacklogUiServer.mjs'),
|
|
90
|
+
];
|
|
91
|
+
for (const candidate of candidates) {
|
|
92
|
+
try {
|
|
93
|
+
readFileSync(candidate);
|
|
94
|
+
return candidate;
|
|
95
|
+
} catch {
|
|
96
|
+
// try next
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
throw new Error('workGraphBacklogUiServer.mjs не найден — npm install -D @work-graph/cli');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const installRoot = resolveInstallRoot();
|
|
103
|
+
const { startBacklogUiServer } = await import(pathToFileURL(resolveUiModule(installRoot)).href);
|
|
104
|
+
|
|
105
|
+
const port = Number(process.env.WORKGRAPH_BACKLOG_UI_PORT ?? config.uiPort ?? 4177);
|
|
106
|
+
const { host, port: boundPort } = await startBacklogUiServer({
|
|
107
|
+
hostRoot: projectRoot,
|
|
108
|
+
cwd: projectRoot,
|
|
109
|
+
hostLabel: config.label,
|
|
110
|
+
port,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
console.log(\`Work Graph UI (\${config.label ?? projectRoot}): http://\${host}:\${boundPort}/\`);
|
|
114
|
+
`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function buildRunMcpScriptContent() {
|
|
118
|
+
return `#!/usr/bin/env node
|
|
119
|
+
import { readFileSync } from 'node:fs';
|
|
120
|
+
import { dirname, join, resolve } from 'node:path';
|
|
121
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
122
|
+
import { createRequire } from 'node:module';
|
|
123
|
+
|
|
124
|
+
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
125
|
+
const config = JSON.parse(readFileSync(join(projectRoot, '.work-graph/config.json'), 'utf8'));
|
|
126
|
+
|
|
127
|
+
process.env.WG_PROJECT_ROOT = projectRoot;
|
|
128
|
+
process.env.WORKGRAPH_ROOT = projectRoot;
|
|
129
|
+
|
|
130
|
+
function resolveInstallRoot() {
|
|
131
|
+
if (process.env.WORKGRAPH_ENGINE_ROOT) {
|
|
132
|
+
return resolve(process.env.WORKGRAPH_ENGINE_ROOT);
|
|
133
|
+
}
|
|
134
|
+
if (config.engineRoot) {
|
|
135
|
+
console.warn('[work-graph] config.engineRoot устарел — используйте npm packages');
|
|
136
|
+
return resolve(config.engineRoot);
|
|
137
|
+
}
|
|
138
|
+
const require = createRequire(join(projectRoot, 'package.json'));
|
|
139
|
+
const cliPkg = require.resolve('@work-graph/cli/package.json');
|
|
140
|
+
return dirname(cliPkg);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function resolveMcpModule(installRoot) {
|
|
144
|
+
const candidates = [
|
|
145
|
+
join(installRoot, 'vendor/packages/workgraph-mcp/src/index.mjs'),
|
|
146
|
+
join(installRoot, 'packages/workgraph-mcp/src/index.mjs'),
|
|
147
|
+
];
|
|
148
|
+
for (const candidate of candidates) {
|
|
149
|
+
try {
|
|
150
|
+
readFileSync(candidate);
|
|
151
|
+
return candidate;
|
|
152
|
+
} catch {
|
|
153
|
+
// try next
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
throw new Error('workgraph-mcp entry не найден — npm install -D @work-graph/mcp');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const installRoot = resolveInstallRoot();
|
|
160
|
+
await import(pathToFileURL(resolveMcpModule(installRoot)).href);
|
|
161
|
+
`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function buildCursorRuleContent({ label } = {}) {
|
|
165
|
+
const projectLabel = label ?? 'этот проект';
|
|
166
|
+
return `---
|
|
167
|
+
description: Work Graph — канон и бэклог в intent/ (${projectLabel})
|
|
168
|
+
alwaysApply: true
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
# Work Graph в проекте
|
|
172
|
+
|
|
173
|
+
- Канон задач: \`intent/**/work/*.work.bvc\`, индекс \`intent/index.bvc\`.
|
|
174
|
+
- Trackable work — только через \`work.id\`; не дублировать в чат-todo.
|
|
175
|
+
- Перед закрытием задачи — \`Свидетельства:\` в atom и проверки из \`Проверки:\`.
|
|
176
|
+
- UI: \`npm run workgraph:ui\` → http://127.0.0.1:4177/
|
|
177
|
+
- MCP: сервер \`workgraph\` в \`.cursor/mcp.json\` (если настроен при init).
|
|
178
|
+
|
|
179
|
+
Установка: \`npx @work-graph/cli init .\` или «установи Work Graph в этот проект».
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function mergePackageJsonScripts(existingJson, scripts, devDependencies = {}) {
|
|
184
|
+
const pkg = existingJson ? JSON.parse(existingJson) : { name: 'project', version: '0.0.0', private: true };
|
|
185
|
+
pkg.scripts = { ...(pkg.scripts ?? {}), ...scripts };
|
|
186
|
+
if (Object.keys(devDependencies).length > 0) {
|
|
187
|
+
pkg.devDependencies = { ...(pkg.devDependencies ?? {}), ...devDependencies };
|
|
188
|
+
}
|
|
189
|
+
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function mergeCursorMcpConfig(existingJson, {
|
|
193
|
+
projectRootVar = '${workspaceFolder}',
|
|
194
|
+
useNpxMcp = true,
|
|
195
|
+
} = {}) {
|
|
196
|
+
const base = existingJson ? JSON.parse(existingJson) : { mcpServers: {} };
|
|
197
|
+
if (!base.mcpServers || typeof base.mcpServers !== 'object') {
|
|
198
|
+
base.mcpServers = {};
|
|
199
|
+
}
|
|
200
|
+
if (useNpxMcp) {
|
|
201
|
+
base.mcpServers.workgraph = {
|
|
202
|
+
type: 'stdio',
|
|
203
|
+
command: 'npx',
|
|
204
|
+
args: ['-y', '@work-graph/mcp'],
|
|
205
|
+
env: {
|
|
206
|
+
WORKGRAPH_ROOT: projectRootVar,
|
|
207
|
+
WG_PROJECT_ROOT: projectRootVar,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
} else {
|
|
211
|
+
base.mcpServers.workgraph = {
|
|
212
|
+
type: 'stdio',
|
|
213
|
+
command: 'node',
|
|
214
|
+
args: [`${projectRootVar}/.work-graph/run-mcp.mjs`],
|
|
215
|
+
env: {
|
|
216
|
+
WORKGRAPH_ROOT: projectRootVar,
|
|
217
|
+
WG_PROJECT_ROOT: projectRootVar,
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
return `${JSON.stringify(base, null, 2)}\n`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function pathExists(path) {
|
|
225
|
+
try {
|
|
226
|
+
await access(path);
|
|
227
|
+
return true;
|
|
228
|
+
} catch {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function writeIfMissing(path, content) {
|
|
234
|
+
if (await pathExists(path)) {
|
|
235
|
+
return { path, written: false };
|
|
236
|
+
}
|
|
237
|
+
await writeFile(path, content, 'utf8');
|
|
238
|
+
return { path, written: true };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function readProjectConfig(projectRoot) {
|
|
242
|
+
const configPath = join(resolve(projectRoot), '.work-graph/config.json');
|
|
243
|
+
if (!(await pathExists(configPath))) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
const raw = await readFile(configPath, 'utf8');
|
|
247
|
+
return JSON.parse(raw);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function initWorkGraphProject(options = {}) {
|
|
251
|
+
const projectRoot = resolve(options.projectRoot ?? process.cwd());
|
|
252
|
+
const label = options.label ?? basename(projectRoot);
|
|
253
|
+
const projectId = options.id ?? slugFromPath(projectRoot);
|
|
254
|
+
const npmFirst = options.npmFirst !== false && !options.engineRoot;
|
|
255
|
+
const engineRoot = options.engineRoot
|
|
256
|
+
? resolve(options.engineRoot)
|
|
257
|
+
: (options.cliModuleUrl ? defaultEngineRootFromCliModule(options.cliModuleUrl) : null);
|
|
258
|
+
|
|
259
|
+
await mkdir(join(projectRoot, 'intent'), { recursive: true });
|
|
260
|
+
await mkdir(join(projectRoot, 'charter'), { recursive: true });
|
|
261
|
+
await mkdir(join(projectRoot, 'architecture'), { recursive: true });
|
|
262
|
+
await mkdir(join(projectRoot, '.work-graph'), { recursive: true });
|
|
263
|
+
await mkdir(join(projectRoot, '.cursor/rules'), { recursive: true });
|
|
264
|
+
|
|
265
|
+
const config = buildProjectConfig({
|
|
266
|
+
projectRoot,
|
|
267
|
+
engineRoot: npmFirst ? undefined : engineRoot,
|
|
268
|
+
label,
|
|
269
|
+
id: projectId,
|
|
270
|
+
uiPort: options.uiPort,
|
|
271
|
+
schemaVersion: npmFirst ? 2 : 1,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const configPath = join(projectRoot, '.work-graph/config.json');
|
|
275
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
276
|
+
|
|
277
|
+
const runUiPath = join(projectRoot, '.work-graph/run-ui.mjs');
|
|
278
|
+
const runMcpPath = join(projectRoot, '.work-graph/run-mcp.mjs');
|
|
279
|
+
await writeFile(runUiPath, buildRunUiScriptContent(), 'utf8');
|
|
280
|
+
await writeFile(runMcpPath, buildRunMcpScriptContent(), 'utf8');
|
|
281
|
+
|
|
282
|
+
const canonWrites = [];
|
|
283
|
+
canonWrites.push(await writeIfMissing(join(projectRoot, 'intent/index.bvc'), INDEX_STUB));
|
|
284
|
+
canonWrites.push(await writeIfMissing(join(projectRoot, 'architecture/main.bvc'), MAIN_BVC_STUB));
|
|
285
|
+
|
|
286
|
+
const cliVersion = options.cliVersion ?? DEFAULT_CLI_VERSION;
|
|
287
|
+
const mcpVersion = options.mcpVersion ?? DEFAULT_MCP_VERSION;
|
|
288
|
+
|
|
289
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
290
|
+
const pkgScripts = {
|
|
291
|
+
'workgraph:ui': 'node .work-graph/run-ui.mjs',
|
|
292
|
+
'workgraph:mcp': 'node .work-graph/run-mcp.mjs',
|
|
293
|
+
'workgraph:doctor': 'work-graph doctor',
|
|
294
|
+
};
|
|
295
|
+
let packageJsonWritten = false;
|
|
296
|
+
if (options.mergePackageJson !== false) {
|
|
297
|
+
const existingPkg = (await pathExists(pkgPath)) ? await readFile(pkgPath, 'utf8') : null;
|
|
298
|
+
const devDeps = npmFirst
|
|
299
|
+
? { '@work-graph/cli': `^${cliVersion}`, '@work-graph/mcp': `^${mcpVersion}` }
|
|
300
|
+
: {};
|
|
301
|
+
await writeFile(pkgPath, mergePackageJsonScripts(existingPkg, pkgScripts, devDeps), 'utf8');
|
|
302
|
+
packageJsonWritten = true;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const mcpPath = join(projectRoot, '.cursor/mcp.json');
|
|
306
|
+
let mcpWritten = false;
|
|
307
|
+
if (options.mergeMcp !== false) {
|
|
308
|
+
const existingMcp = (await pathExists(mcpPath)) ? await readFile(mcpPath, 'utf8') : null;
|
|
309
|
+
await writeFile(mcpPath, mergeCursorMcpConfig(existingMcp, { useNpxMcp: npmFirst }), 'utf8');
|
|
310
|
+
mcpWritten = true;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const rulePath = join(projectRoot, '.cursor/rules/work-graph-project.mdc');
|
|
314
|
+
let ruleWritten = false;
|
|
315
|
+
if (options.writeCursorRule !== false) {
|
|
316
|
+
const wrote = await writeIfMissing(rulePath, buildCursorRuleContent({ label }));
|
|
317
|
+
ruleWritten = wrote.written;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const nextSteps = npmFirst
|
|
321
|
+
? [
|
|
322
|
+
'npm install',
|
|
323
|
+
'npm run workgraph:ui',
|
|
324
|
+
'Открыть http://127.0.0.1:4177/',
|
|
325
|
+
'Перезагрузить MCP в Cursor (workgraph)',
|
|
326
|
+
]
|
|
327
|
+
: [
|
|
328
|
+
'npm run workgraph:ui',
|
|
329
|
+
'Открыть http://127.0.0.1:4177/',
|
|
330
|
+
'Перезагрузить MCP в Cursor (workgraph)',
|
|
331
|
+
];
|
|
332
|
+
|
|
333
|
+
return {
|
|
334
|
+
ok: true,
|
|
335
|
+
schema: npmFirst ? 'workgraph.cli.init.v2' : 'workgraph.cli.init.v1',
|
|
336
|
+
npmFirst,
|
|
337
|
+
projectRoot,
|
|
338
|
+
engineRoot: engineRoot ?? null,
|
|
339
|
+
configPath,
|
|
340
|
+
projectId,
|
|
341
|
+
label,
|
|
342
|
+
packageJsonWritten,
|
|
343
|
+
mcpWritten,
|
|
344
|
+
ruleWritten,
|
|
345
|
+
canonWrites,
|
|
346
|
+
nextSteps,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export async function startProjectUiFromCwd(options = {}) {
|
|
351
|
+
const cwd = resolve(options.cwd ?? process.cwd());
|
|
352
|
+
const config = await readProjectConfig(cwd);
|
|
353
|
+
if (!config) {
|
|
354
|
+
throw new Error('нет .work-graph/config.json — сначала выполните work-graph init');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const projectRoot = resolve(config.projectRoot ?? cwd);
|
|
358
|
+
const engineRoot = resolveEngineRoot({
|
|
359
|
+
projectRoot,
|
|
360
|
+
config,
|
|
361
|
+
cliModuleUrl: options.cliModuleUrl,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
process.env.WG_PROJECT_ROOT = projectRoot;
|
|
365
|
+
process.env.WORKGRAPH_ROOT = projectRoot;
|
|
366
|
+
|
|
367
|
+
const { startBacklogUiServer } = await importBacklogUiServer(engineRoot);
|
|
368
|
+
|
|
369
|
+
const port = Number(options.port ?? process.env.WORKGRAPH_BACKLOG_UI_PORT ?? config.uiPort ?? 4177);
|
|
370
|
+
return startBacklogUiServer({
|
|
371
|
+
hostRoot: projectRoot,
|
|
372
|
+
cwd: projectRoot,
|
|
373
|
+
hostLabel: config.label,
|
|
374
|
+
port,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function runWorkGraphDoctor(options = {}) {
|
|
379
|
+
const projectRoot = resolve(options.cwd ?? process.cwd());
|
|
380
|
+
const config = await readProjectConfig(projectRoot);
|
|
381
|
+
let engineRoot = null;
|
|
382
|
+
try {
|
|
383
|
+
engineRoot = resolveEngineRoot({
|
|
384
|
+
projectRoot,
|
|
385
|
+
config: config ?? {},
|
|
386
|
+
cliModuleUrl: options.cliModuleUrl,
|
|
387
|
+
});
|
|
388
|
+
} catch {
|
|
389
|
+
engineRoot = null;
|
|
390
|
+
}
|
|
391
|
+
return buildDoctorReport({ projectRoot, config, engineRoot });
|
|
392
|
+
}
|