@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,205 @@
1
+ import { validateStepAtomDraft } from './stepAtomFormatter.mjs';
2
+
3
+ export const DRAFT_INTAKE_PROMOTION_EVAL_SCHEMA = 'workgraph.draft-intake.promotion-eval.v1';
4
+ export const DRAFT_INTAKE_CANDIDATE_LIST_SCHEMA = 'workgraph.draft-intake.candidate-list.v1';
5
+
6
+ const ALLOWED_MIGRATION_STRATEGIES = new Set(['port', 'rebuild', 'replace', 'defer']);
7
+ const ALLOWED_INTAKE_SOURCE_KINDS = new Set([
8
+ 'code-gap-analyzer',
9
+ 'suggestion',
10
+ 'llm_draft',
11
+ 'manual',
12
+ ]);
13
+
14
+ /**
15
+ * Deterministic promotion gates from protocols/workgraph-draft-intake.bvc.
16
+ * No silent backlog mutation — eligible drafts become operator-reviewed candidates only.
17
+ */
18
+ export const DRAFT_INTAKE_PROMOTION_RULES = Object.freeze([
19
+ {
20
+ id: 'proposal_validation',
21
+ description: 'StepAtomDraft validation and formatter pass without errors.',
22
+ },
23
+ {
24
+ id: 'work_id_present',
25
+ description: 'Draft carries non-empty work.id / suggestedWorkId.',
26
+ },
27
+ {
28
+ id: 'migration_strategy',
29
+ description: 'Draft labels include allowed migration.strategy.',
30
+ },
31
+ {
32
+ id: 'intake_provenance',
33
+ description: 'Draft includes intake.source_kind, intake.source_path, intake.review_status=pending.',
34
+ },
35
+ {
36
+ id: 'target_files',
37
+ description: 'Code-gap drafts reference at least one target file.',
38
+ },
39
+ {
40
+ id: 'duplicate_work_id',
41
+ description: 'Suggested work.id is absent from canonical backlog / existingWorkIds.',
42
+ },
43
+ {
44
+ id: 'confidence_gate',
45
+ description: 'Suggestion confidence is not low unless explicitly allowed.',
46
+ },
47
+ ]);
48
+
49
+ function normalizeWorkIdSet(existingWorkIds) {
50
+ if (existingWorkIds instanceof Set) {
51
+ return existingWorkIds;
52
+ }
53
+
54
+ if (Array.isArray(existingWorkIds)) {
55
+ return new Set(existingWorkIds.map((value) => String(value).trim()).filter(Boolean));
56
+ }
57
+
58
+ return new Set();
59
+ }
60
+
61
+ function buildCheck(id, passed, message) {
62
+ return { id, passed, message };
63
+ }
64
+
65
+ /**
66
+ * @param {{
67
+ * ok?: boolean,
68
+ * validationErrors?: string[],
69
+ * codeGapDraft?: { suggestedWorkId?: string, confidence?: string, targetFiles?: string[], draft?: { labels?: Record<string, string> } },
70
+ * }} proposal
71
+ * @param {{ existingWorkIds?: Iterable<string>, allowLowConfidence?: boolean, requireTargetFiles?: boolean }} [options]
72
+ */
73
+ export function evaluateDraftIntakePromotion(proposal, options = {}) {
74
+ if (!proposal || typeof proposal !== 'object') {
75
+ throw new TypeError('proposal is required');
76
+ }
77
+
78
+ const draft = proposal.codeGapDraft?.draft ?? {};
79
+ const labels = draft.labels ?? {};
80
+ const workId = String(proposal.codeGapDraft?.suggestedWorkId ?? labels['work.id'] ?? '').trim();
81
+ const validationErrors = [...(proposal.validationErrors ?? [])];
82
+ const existingWorkIds = normalizeWorkIdSet(options.existingWorkIds);
83
+ const requireTargetFiles = options.requireTargetFiles !== false;
84
+ const allowLowConfidence = options.allowLowConfidence === true;
85
+
86
+ const checks = [
87
+ buildCheck(
88
+ 'proposal_validation',
89
+ proposal.ok === true && validationErrors.length === 0,
90
+ proposal.ok === true && validationErrors.length === 0
91
+ ? 'Proposal validation passed.'
92
+ : `Proposal validation failed: ${validationErrors.join('; ') || proposal.error || 'unknown'}`,
93
+ ),
94
+ buildCheck(
95
+ 'work_id_present',
96
+ workId !== '',
97
+ workId !== '' ? `work.id ${workId} present.` : 'work.id is missing.',
98
+ ),
99
+ buildCheck(
100
+ 'migration_strategy',
101
+ ALLOWED_MIGRATION_STRATEGIES.has(String(labels['migration.strategy'] ?? '').trim()),
102
+ ALLOWED_MIGRATION_STRATEGIES.has(String(labels['migration.strategy'] ?? '').trim())
103
+ ? `migration.strategy=${labels['migration.strategy']}.`
104
+ : 'migration.strategy missing or unsupported.',
105
+ ),
106
+ buildCheck(
107
+ 'intake_provenance',
108
+ ALLOWED_INTAKE_SOURCE_KINDS.has(String(labels['intake.source_kind'] ?? '').trim())
109
+ && String(labels['intake.source_path'] ?? '').trim() !== ''
110
+ && String(labels['intake.review_status'] ?? '').trim() === 'pending',
111
+ ALLOWED_INTAKE_SOURCE_KINDS.has(String(labels['intake.source_kind'] ?? '').trim())
112
+ && String(labels['intake.source_path'] ?? '').trim() !== ''
113
+ && String(labels['intake.review_status'] ?? '').trim() === 'pending'
114
+ ? 'intake provenance labels complete and review_status=pending.'
115
+ : 'intake provenance incomplete or review_status is not pending.',
116
+ ),
117
+ buildCheck(
118
+ 'target_files',
119
+ !requireTargetFiles || (proposal.codeGapDraft?.targetFiles?.length ?? 0) > 0 || String(labels['work.target_files'] ?? '').trim() !== '',
120
+ requireTargetFiles
121
+ ? 'At least one target file is referenced.'
122
+ : 'Target files optional for this intake kind.',
123
+ ),
124
+ buildCheck(
125
+ 'duplicate_work_id',
126
+ workId === '' || !existingWorkIds.has(workId),
127
+ workId !== '' && existingWorkIds.has(workId)
128
+ ? `work.id already exists: ${workId}`
129
+ : 'No duplicate work.id in canonical backlog.',
130
+ ),
131
+ buildCheck(
132
+ 'confidence_gate',
133
+ allowLowConfidence || proposal.codeGapDraft?.confidence !== 'low',
134
+ allowLowConfidence || proposal.codeGapDraft?.confidence !== 'low'
135
+ ? 'Confidence gate passed.'
136
+ : 'Low-confidence suggestions require explicit operator review before candidacy.',
137
+ ),
138
+ ];
139
+
140
+ const failedChecks = checks.filter((check) => !check.passed);
141
+
142
+ return {
143
+ schema: DRAFT_INTAKE_PROMOTION_EVAL_SCHEMA,
144
+ eligible: failedChecks.length === 0,
145
+ status: failedChecks.length === 0 ? 'candidate' : 'rejected',
146
+ workId: workId || null,
147
+ failedCheckIds: failedChecks.map((check) => check.id),
148
+ checks,
149
+ promotionProtocol: 'protocols/workgraph-draft-intake.bvc',
150
+ reviewRequired: true,
151
+ };
152
+ }
153
+
154
+ export function evaluateDraftIntakePromotionFromDraft(draft, options = {}) {
155
+ const validationErrors = validateStepAtomDraft(draft);
156
+ const proposal = {
157
+ ok: validationErrors.length === 0,
158
+ validationErrors,
159
+ codeGapDraft: {
160
+ suggestedWorkId: draft.labels?.['work.id'] ?? '',
161
+ confidence: options.confidence ?? 'medium',
162
+ targetFiles: String(draft.labels?.['work.target_files'] ?? '')
163
+ .split(',')
164
+ .map((value) => value.trim())
165
+ .filter(Boolean),
166
+ draft,
167
+ },
168
+ };
169
+
170
+ return evaluateDraftIntakePromotion(proposal, options);
171
+ }
172
+
173
+ /**
174
+ * @param {Array<{ proposal: object, suggestion?: object }>} entries
175
+ */
176
+ export function partitionDraftIntakeCandidates(entries, options = {}) {
177
+ const candidates = [];
178
+ const rejected = [];
179
+
180
+ for (const entry of entries) {
181
+ const evaluation = evaluateDraftIntakePromotion(entry.proposal, options);
182
+ const row = {
183
+ workId: evaluation.workId,
184
+ evaluation,
185
+ proposal: entry.proposal,
186
+ suggestion: entry.suggestion ?? null,
187
+ };
188
+
189
+ if (evaluation.eligible) {
190
+ candidates.push(row);
191
+ } else {
192
+ rejected.push(row);
193
+ }
194
+ }
195
+
196
+ return {
197
+ schema: DRAFT_INTAKE_CANDIDATE_LIST_SCHEMA,
198
+ candidateCount: candidates.length,
199
+ rejectedCount: rejected.length,
200
+ candidates,
201
+ rejected,
202
+ promotionProtocol: 'protocols/workgraph-draft-intake.bvc',
203
+ rules: DRAFT_INTAKE_PROMOTION_RULES.map((rule) => rule.id),
204
+ };
205
+ }
@@ -0,0 +1,85 @@
1
+ import { buildEpicRoadmapProjection } from './intentRoadmapEpicProjection.mjs';
2
+ import { attachDerivedWorkItemHierarchy, DONE_STATUSES, readWorkItemKind } from './workItemHierarchy.mjs';
3
+
4
+ export const EPIC_WORK_SCOPE_SCHEMA = 'workgraph.epic-work-scope.v1';
5
+
6
+ const ACTIVE_SCOPE_STATUSES = new Set(['claimed', 'doing', 'in_progress', 'verify']);
7
+
8
+ /**
9
+ * Map work.status to markdown checklist mark for read-only scope blocks.
10
+ * @param {string} status
11
+ * @returns {'x' | '~' | ' '}
12
+ */
13
+ export function scopeChecklistMark(status) {
14
+ const normalized = String(status ?? '').trim().toLowerCase();
15
+ if (DONE_STATUSES.has(normalized)) {
16
+ return 'x';
17
+ }
18
+ if (ACTIVE_SCOPE_STATUSES.has(normalized)) {
19
+ return '~';
20
+ }
21
+ return ' ';
22
+ }
23
+
24
+ /**
25
+ * Render AN-28 «Scope (read-only)» markdown block from epic scope slice.
26
+ * @param {object} scopeSlice — output of buildEpicWorkScopeSlice
27
+ * @param {{ title?: string }} [options]
28
+ */
29
+ export function formatEpicScopeMarkdown(scopeSlice, options = {}) {
30
+ const title = options.title ?? 'Scope (read-only, Work Graph)';
31
+ const lines = [`## ${title}`, ''];
32
+ for (const child of scopeSlice.children ?? []) {
33
+ const mark = scopeChecklistMark(child.status);
34
+ const label = child.title ? ` — ${child.title}` : '';
35
+ lines.push(`- [${mark}] \`${child.id}\` — ${child.status}${label}`);
36
+ }
37
+ return lines.join('\n');
38
+ }
39
+
40
+ /**
41
+ * Compact read-only rollup of direct epic children for chat/UI scope panels.
42
+ *
43
+ * @param {Array<object>} workItems
44
+ * @param {string} epicId
45
+ * @returns {object} schema workgraph.epic-work-scope.v1
46
+ */
47
+ export function buildEpicWorkScopeSlice(workItems, epicId) {
48
+ const normalizedEpicId = String(epicId ?? '').trim();
49
+ if (normalizedEpicId === '') {
50
+ throw new TypeError('epicId is required');
51
+ }
52
+
53
+ const enriched = attachDerivedWorkItemHierarchy(workItems);
54
+ const epic = enriched.find((item) => item.id === normalizedEpicId);
55
+ if (!epic) {
56
+ throw new Error(`unknown epic id: ${normalizedEpicId}`);
57
+ }
58
+ if (readWorkItemKind(epic) !== 'epic') {
59
+ throw new Error(`work item is not an epic: ${normalizedEpicId}`);
60
+ }
61
+
62
+ const projection = buildEpicRoadmapProjection(enriched);
63
+ const entry = projection.epics.find((candidate) => candidate.epicId === normalizedEpicId);
64
+ if (!entry) {
65
+ throw new Error(`unknown epic id: ${normalizedEpicId}`);
66
+ }
67
+
68
+ return {
69
+ schema: EPIC_WORK_SCOPE_SCHEMA,
70
+ readOnly: true,
71
+ epicId: entry.epicId,
72
+ title: entry.title,
73
+ status: entry.status,
74
+ childCount: entry.childCount,
75
+ doneChildCount: entry.doneChildCount,
76
+ closeBlocked: entry.closeBlocked,
77
+ rollup: entry.rollup,
78
+ children: entry.children.map((child) => ({
79
+ id: child.workId,
80
+ title: child.title,
81
+ status: child.status,
82
+ itemKind: child.itemKind,
83
+ })),
84
+ };
85
+ }
@@ -0,0 +1,63 @@
1
+ const DEFAULT_LLM_BASE_URL = 'http://127.0.0.1:1234/v1';
2
+
3
+ export function resolveLiveLlmEnv(env = process.env) {
4
+ const baseUrl = String(env.IOHASC_LLM_BASE_URL ?? DEFAULT_LLM_BASE_URL).trim().replace(/\/+$/u, '');
5
+ const model = String(env.IOHASC_LLM_MODEL ?? '').trim();
6
+ const apiKey = String(env.IOHASC_LLM_API_KEY ?? '').trim();
7
+
8
+ return {
9
+ IOHASC_E2E_REAL_LLM: '1',
10
+ IOHASC_LLM_BASE_URL: baseUrl,
11
+ IOHASC_LLM_MODEL: model,
12
+ IOHASC_LLM_API_KEY: apiKey,
13
+ };
14
+ }
15
+
16
+ export function validateLiveLlmEnv(env = process.env) {
17
+ const errors = [];
18
+ const resolved = resolveLiveLlmEnv(env);
19
+ const baseUrl = String(env.IOHASC_LLM_BASE_URL ?? DEFAULT_LLM_BASE_URL).trim();
20
+ const model = String(env.IOHASC_LLM_MODEL ?? '').trim();
21
+
22
+ if (!model) {
23
+ errors.push({
24
+ code: 'missing_model',
25
+ message: 'Set IOHASC_LLM_MODEL to the model id exposed by your OpenAI-compatible server.',
26
+ });
27
+ }
28
+
29
+ if (baseUrl !== '' && !/^https?:\/\//iu.test(baseUrl)) {
30
+ errors.push({
31
+ code: 'invalid_base_url',
32
+ message: 'IOHASC_LLM_BASE_URL must be an http(s) URL (example: http://127.0.0.1:1234/v1).',
33
+ });
34
+ }
35
+
36
+ return {
37
+ ok: errors.length === 0,
38
+ errors,
39
+ config: resolved,
40
+ hints: {
41
+ powershell: "$env:IOHASC_E2E_REAL_LLM='1'; $env:IOHASC_LLM_BASE_URL='http://127.0.0.1:1234/v1'; $env:IOHASC_LLM_MODEL='your-model'; npm run eval:live-llm",
42
+ bash: "IOHASC_E2E_REAL_LLM=1 IOHASC_LLM_BASE_URL=http://127.0.0.1:1234/v1 IOHASC_LLM_MODEL=your-model npm run eval:live-llm",
43
+ },
44
+ };
45
+ }
46
+
47
+ export function formatLiveLlmEnvHelp(validation = validateLiveLlmEnv()) {
48
+ return [
49
+ 'Live LLM eval wrapper (optional Tier B, non-blocking CI)',
50
+ 'Runs: worker live-loop (--provider openai) + optional OneBase gate',
51
+ '',
52
+ 'Required env:',
53
+ ' IOHASC_LLM_MODEL — model id on OpenAI-compatible proxy (LM Studio / LiteLLM / Ollama OpenAI route)',
54
+ '',
55
+ 'Optional env:',
56
+ ' IOHASC_LLM_BASE_URL — default http://127.0.0.1:1234/v1',
57
+ ' IOHASC_LLM_API_KEY — Bearer token when proxy requires auth',
58
+ ' IOHASC_E2E_REAL_LLM=1 — set automatically by this wrapper',
59
+ '',
60
+ 'Example:',
61
+ ` ${validation.hints.powershell}`,
62
+ ].join('\n');
63
+ }
@@ -0,0 +1,167 @@
1
+ const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
2
+
3
+ function inferEvidenceType(summary) {
4
+ const text = String(summary);
5
+ if (/npm test|vitest|jest|go test/iu.test(text)) {
6
+ return 'test';
7
+ }
8
+
9
+ if (/npm run|node |command/iu.test(text)) {
10
+ return 'command';
11
+ }
12
+
13
+ if (/decision|решени/iu.test(text)) {
14
+ return 'decision';
15
+ }
16
+
17
+ if (/blocked|blocker/iu.test(text)) {
18
+ return 'blocker';
19
+ }
20
+
21
+ if (/worker|dry-run/iu.test(text)) {
22
+ return 'worker-run';
23
+ }
24
+
25
+ return 'change';
26
+ }
27
+
28
+ export function buildEvidenceRecordFromLegacyLine(taskId, summary, index) {
29
+ const type = inferEvidenceType(summary);
30
+ return {
31
+ schema: 'evidence-record.v1',
32
+ id: `${taskId}:legacy-evidence:${index + 1}`,
33
+ time: null,
34
+ source: 'workgraph.snapshot.v1',
35
+ taskId,
36
+ type,
37
+ summary: String(summary).trim(),
38
+ status: /fail|error|blocked/iu.test(summary) ? 'failed' : 'succeeded',
39
+ details: { importedFrom: 'legacy-evidence-string' },
40
+ artifacts: [],
41
+ };
42
+ }
43
+
44
+ export function buildEvidenceReadModelFromItems(items) {
45
+ if (!Array.isArray(items)) {
46
+ throw new TypeError('items must be an array');
47
+ }
48
+
49
+ const records = items.flatMap((item) =>
50
+ (item.evidence ?? []).map((summary, index) => buildEvidenceRecordFromLegacyLine(item.id, summary, index)),
51
+ ).sort((left, right) => compareText(left.id, right.id));
52
+
53
+ return {
54
+ schema: 'evidence.read-model.v1',
55
+ count: records.length,
56
+ records,
57
+ compatibility: {
58
+ legacyStringEvidence: true,
59
+ structuredEvidenceV1: true,
60
+ },
61
+ };
62
+ }
63
+
64
+ export function buildEvidenceReadModelForTask(items, taskId) {
65
+ const item = items.find((entry) => entry.id === taskId);
66
+ if (item === undefined) {
67
+ return { schema: 'evidence.read-model.v1', count: 0, records: [], compatibility: { legacyStringEvidence: true, structuredEvidenceV1: true } };
68
+ }
69
+
70
+ return buildEvidenceReadModelFromItems([item]);
71
+ }
72
+
73
+ export function buildEvidenceTimelineForTask(items, taskId, options = {}) {
74
+ if (!Array.isArray(items)) {
75
+ throw new TypeError('items must be an array');
76
+ }
77
+
78
+ const normalizedTaskId = String(taskId ?? '').trim();
79
+ if (normalizedTaskId === '') {
80
+ throw new TypeError('taskId must be a non-empty string');
81
+ }
82
+
83
+ const item = items.find((entry) => entry.id === normalizedTaskId);
84
+ if (item === undefined) {
85
+ return {
86
+ schema: 'evidence.timeline.v1',
87
+ taskId: normalizedTaskId,
88
+ count: 0,
89
+ events: [],
90
+ };
91
+ }
92
+
93
+ const events = [];
94
+
95
+ for (const [index, summary] of (item.evidence ?? []).entries()) {
96
+ const record = buildEvidenceRecordFromLegacyLine(item.id, summary, index);
97
+ events.push({
98
+ id: record.id,
99
+ kind: 'evidence',
100
+ sequence: index + 1,
101
+ time: null,
102
+ title: record.type,
103
+ summary: record.summary,
104
+ status: record.status,
105
+ });
106
+ }
107
+
108
+ for (const [index, run] of (options.workerRuns ?? []).entries()) {
109
+ if (run.taskId !== normalizedTaskId) {
110
+ continue;
111
+ }
112
+
113
+ events.push({
114
+ id: `worker-run:${run.runId ?? `${normalizedTaskId}:${index + 1}`}`,
115
+ kind: 'transition',
116
+ sequence: 10_000 + index,
117
+ time: run.recordedAt ?? null,
118
+ title: 'worker transition',
119
+ summary: run.appliedTransition
120
+ ? `transition → ${run.appliedTransition}`
121
+ : String(run.summary ?? run.status ?? 'worker run'),
122
+ status: run.status ?? null,
123
+ });
124
+ }
125
+
126
+ if (item.blocker) {
127
+ events.push({
128
+ id: `${normalizedTaskId}:blocker`,
129
+ kind: 'transition',
130
+ sequence: 20_000,
131
+ time: null,
132
+ title: 'blocked',
133
+ summary: String(item.blocker),
134
+ status: 'blocked',
135
+ });
136
+ }
137
+
138
+ const sorted = [...events].sort((left, right) => {
139
+ const leftTime = left.time ? Date.parse(left.time) : null;
140
+ const rightTime = right.time ? Date.parse(right.time) : null;
141
+
142
+ if (leftTime === null && rightTime === null) {
143
+ return left.sequence - right.sequence;
144
+ }
145
+
146
+ if (leftTime === null) {
147
+ return -1;
148
+ }
149
+
150
+ if (rightTime === null) {
151
+ return 1;
152
+ }
153
+
154
+ if (leftTime !== rightTime) {
155
+ return leftTime - rightTime;
156
+ }
157
+
158
+ return left.sequence - right.sequence;
159
+ });
160
+
161
+ return {
162
+ schema: 'evidence.timeline.v1',
163
+ taskId: normalizedTaskId,
164
+ count: sorted.length,
165
+ events: sorted,
166
+ };
167
+ }