@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,243 @@
1
+ import { appendFile, mkdir, readFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+
4
+ const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
5
+
6
+ export const MEMORY_RECORD_JOURNAL_SCHEMA = 'memory-record.journal.v1';
7
+ export const MEMORY_RECORD_JOURNAL_APPEND_SCHEMA = 'memory-record.journal.append.v1';
8
+ export const MEMORY_RECORD_JOURNAL_READ_SCHEMA = 'memory-record.journal.read.v1';
9
+ export const DEFAULT_MEMORY_RECORD_JOURNAL_PATH = 'work/memory-records.jsonl';
10
+
11
+ const POLICY_ALLOWED_TRANSITION_STATUSES = new Set(['done', 'verified']);
12
+
13
+ function stableMemoryId(sourceWorkItem, type, summary) {
14
+ const slug = String(summary ?? '').toLowerCase().replace(/[^a-z0-9]+/gu, '-').slice(0, 32);
15
+ return `mem:${sourceWorkItem}:${type}:${slug || 'record'}`;
16
+ }
17
+
18
+ function inferMemoryType(item) {
19
+ const searchable = `${item.title} ${item.goal} ${item.vector}`.toLowerCase();
20
+ if (/risk|риск/u.test(searchable)) {
21
+ return 'risk';
22
+ }
23
+
24
+ if (/invariant|инвариант/u.test(searchable)) {
25
+ return 'invariant';
26
+ }
27
+
28
+ if (/decision|решени/u.test(searchable)) {
29
+ return 'decision';
30
+ }
31
+
32
+ if (/architecture|архитектур/u.test(searchable)) {
33
+ return 'architecture-fact';
34
+ }
35
+
36
+ return 'evidence-summary';
37
+ }
38
+
39
+ export function buildMemoryRecordFromWorkItem(item, options = {}) {
40
+ if (item === undefined || item === null) {
41
+ throw new TypeError('item is required');
42
+ }
43
+
44
+ const type = options.type ?? inferMemoryType(item);
45
+ const summary = options.summary ?? (item.goal || item.title || item.id);
46
+ const status = options.status ?? (options.reviewRequired === false ? 'active' : 'draft');
47
+ const evidenceIds = (item.evidence ?? []).map((_, index) => `${item.id}:legacy-evidence:${index + 1}`);
48
+
49
+ return {
50
+ schema: 'memory-record.v1',
51
+ id: options.id ?? stableMemoryId(item.id, type, summary),
52
+ type,
53
+ summary: String(summary).trim(),
54
+ sourceWorkItem: item.id,
55
+ confidence: options.confidence ?? 'medium',
56
+ status,
57
+ updatedAt: options.updatedAt ?? null,
58
+ basisLink: item.basis ? `work:${item.id}:basis` : '',
59
+ vectorLink: item.vector ? `work:${item.id}:vector` : '',
60
+ goalLink: item.goal ? `work:${item.id}:goal` : '',
61
+ evidenceIds: [...evidenceIds].sort(compareText),
62
+ relatedFiles: [...(item.targetFiles ?? [])].sort(compareText),
63
+ relatedTasks: [...(item.dependsOn ?? [])].sort(compareText),
64
+ reviewRequired: status === 'draft' || status === 'needs-review',
65
+ dedupKey: `${item.id}\0${type}\0${summary}`,
66
+ };
67
+ }
68
+
69
+ export function buildMemoryRecordCandidatesFromItems(items, options = {}) {
70
+ if (!Array.isArray(items)) {
71
+ throw new TypeError('items must be an array');
72
+ }
73
+
74
+ const doneStatuses = new Set(['done', 'verified']);
75
+ const candidates = items
76
+ .filter((item) => doneStatuses.has(item.status))
77
+ .map((item) => buildMemoryRecordFromWorkItem(item, options))
78
+ .sort((left, right) => compareText(left.id, right.id));
79
+
80
+ const seen = new Set();
81
+ const deduped = [];
82
+ for (const record of candidates) {
83
+ if (seen.has(record.dedupKey)) {
84
+ continue;
85
+ }
86
+
87
+ seen.add(record.dedupKey);
88
+ deduped.push(record);
89
+ }
90
+
91
+ return {
92
+ schema: 'memory-record.candidates.v1',
93
+ count: deduped.length,
94
+ reviewRequired: true,
95
+ records: deduped,
96
+ };
97
+ }
98
+
99
+ export function validateMemoryJournalTransition(transition) {
100
+ if (!transition || typeof transition !== 'object') {
101
+ throw new TypeError('transition is required for memory journal append');
102
+ }
103
+
104
+ const { kind, sourceWorkItem, toStatus } = transition;
105
+ if (kind !== 'work-item-status') {
106
+ throw new Error(`unsupported memory journal transition kind: ${String(kind)}`);
107
+ }
108
+
109
+ if (!sourceWorkItem) {
110
+ throw new TypeError('transition.sourceWorkItem is required');
111
+ }
112
+
113
+ if (!POLICY_ALLOWED_TRANSITION_STATUSES.has(toStatus)) {
114
+ throw new Error(`memory journal append blocked: transition toStatus must be done or verified, got ${String(toStatus)}`);
115
+ }
116
+
117
+ return true;
118
+ }
119
+
120
+ function normalizeJournalRecord(record, transition, options = {}) {
121
+ if (!record || record.schema !== 'memory-record.v1') {
122
+ throw new TypeError('each journal record must use schema memory-record.v1');
123
+ }
124
+
125
+ if (record.sourceWorkItem !== transition.sourceWorkItem) {
126
+ throw new Error(`memory journal record ${record.id} sourceWorkItem must match transition.sourceWorkItem`);
127
+ }
128
+
129
+ const updatedAt = options.appendedAt ?? new Date().toISOString();
130
+ return {
131
+ ...record,
132
+ updatedAt: record.updatedAt ?? updatedAt,
133
+ };
134
+ }
135
+
136
+ export function buildMemoryJournalEntry(record, transition, options = {}) {
137
+ validateMemoryJournalTransition(transition);
138
+ const appendedAt = options.appendedAt ?? new Date().toISOString();
139
+
140
+ return {
141
+ schema: MEMORY_RECORD_JOURNAL_SCHEMA,
142
+ appendedAt,
143
+ transition: {
144
+ kind: transition.kind,
145
+ sourceWorkItem: transition.sourceWorkItem,
146
+ fromStatus: transition.fromStatus ?? null,
147
+ toStatus: transition.toStatus,
148
+ },
149
+ record: normalizeJournalRecord(record, transition, { appendedAt }),
150
+ };
151
+ }
152
+
153
+ export async function readMemoryRecordJournal(options = {}) {
154
+ const journalPath = resolve(options.cwd ?? process.cwd(), options.journalPath ?? DEFAULT_MEMORY_RECORD_JOURNAL_PATH);
155
+
156
+ let text = '';
157
+ try {
158
+ text = await readFile(journalPath, 'utf8');
159
+ } catch (error) {
160
+ if (error && typeof error === 'object' && error.code === 'ENOENT') {
161
+ return {
162
+ schema: MEMORY_RECORD_JOURNAL_READ_SCHEMA,
163
+ journalPath,
164
+ entryCount: 0,
165
+ entries: [],
166
+ records: [],
167
+ };
168
+ }
169
+
170
+ throw error;
171
+ }
172
+
173
+ const entries = text
174
+ .split(/\r?\n/u)
175
+ .map((line) => line.trim())
176
+ .filter(Boolean)
177
+ .map((line, index) => {
178
+ const parsed = JSON.parse(line);
179
+ if (parsed.schema !== MEMORY_RECORD_JOURNAL_SCHEMA) {
180
+ throw new Error(`invalid memory journal entry at line ${index + 1}: expected ${MEMORY_RECORD_JOURNAL_SCHEMA}`);
181
+ }
182
+
183
+ return parsed;
184
+ });
185
+
186
+ const latestById = new Map();
187
+ for (const entry of entries) {
188
+ latestById.set(entry.record.id, entry.record);
189
+ }
190
+
191
+ const records = [...latestById.values()].sort((left, right) => compareText(left.id, right.id));
192
+
193
+ return {
194
+ schema: MEMORY_RECORD_JOURNAL_READ_SCHEMA,
195
+ journalPath,
196
+ entryCount: entries.length,
197
+ entries,
198
+ records,
199
+ };
200
+ }
201
+
202
+ export function mergeMemoryJournalWithCandidates(candidates, journalRecords) {
203
+ const candidateList = Array.isArray(candidates) ? candidates : [];
204
+ const journalList = Array.isArray(journalRecords) ? journalRecords : [];
205
+ const merged = new Map();
206
+
207
+ for (const record of candidateList) {
208
+ merged.set(record.id, record);
209
+ }
210
+
211
+ for (const record of journalList) {
212
+ merged.set(record.id, record);
213
+ }
214
+
215
+ return [...merged.values()].sort((left, right) => compareText(left.id, right.id));
216
+ }
217
+
218
+ export async function appendMemoryRecordJournal(records, journalPath, options = {}) {
219
+ if (!Array.isArray(records) || records.length === 0) {
220
+ throw new TypeError('records must be a non-empty array');
221
+ }
222
+
223
+ validateMemoryJournalTransition(options.transition);
224
+
225
+ const resolvedPath = resolve(options.cwd ?? process.cwd(), journalPath ?? DEFAULT_MEMORY_RECORD_JOURNAL_PATH);
226
+ const appendedAt = options.appendedAt ?? new Date().toISOString();
227
+ const entries = records.map((record) => buildMemoryJournalEntry(record, options.transition, { appendedAt }));
228
+
229
+ if (options.dryRun !== true) {
230
+ await mkdir(dirname(resolvedPath), { recursive: true });
231
+ for (const entry of entries) {
232
+ await appendFile(resolvedPath, `${JSON.stringify(entry)}\n`, 'utf8');
233
+ }
234
+ }
235
+
236
+ return {
237
+ schema: MEMORY_RECORD_JOURNAL_APPEND_SCHEMA,
238
+ journalPath: resolvedPath,
239
+ appended: entries.length,
240
+ entries,
241
+ records: entries.map((entry) => entry.record),
242
+ };
243
+ }
@@ -0,0 +1,238 @@
1
+ import {
2
+ buildMemoryRecordCandidatesFromItems,
3
+ mergeMemoryJournalWithCandidates,
4
+ readMemoryRecordJournal,
5
+ } from './memoryRecordWriter.mjs';
6
+
7
+ export const MEMORY_WORKER_SLICE_SCHEMA = 'memory-record.worker-slice.v1';
8
+
9
+ const TYPE_PRIORITY = [
10
+ 'invariant',
11
+ 'decision',
12
+ 'architecture-fact',
13
+ 'domain-fact',
14
+ 'evidence-summary',
15
+ 'risk',
16
+ 'open-question',
17
+ 'provider-capability',
18
+ 'user-preference',
19
+ ];
20
+
21
+ const DEFAULT_MAX_RECORDS = 12;
22
+ const DEFAULT_MAX_CHARS = 4000;
23
+ const DEFAULT_MAX_RELATED_FILES = 8;
24
+
25
+ const compareText = (left, right) => String(left).localeCompare(String(right), 'en', { sensitivity: 'variant' });
26
+
27
+ function typePriorityIndex(type) {
28
+ const index = TYPE_PRIORITY.indexOf(type);
29
+ return index === -1 ? TYPE_PRIORITY.length : index;
30
+ }
31
+
32
+ function statusPriority(status) {
33
+ if (status === 'active') {
34
+ return 0;
35
+ }
36
+
37
+ if (status === 'needs-review') {
38
+ return 1;
39
+ }
40
+
41
+ if (status === 'draft') {
42
+ return 2;
43
+ }
44
+
45
+ return 3;
46
+ }
47
+
48
+ function isRecordRelevant(record, task, itemById) {
49
+ if (record.sourceWorkItem === task.id) {
50
+ return true;
51
+ }
52
+
53
+ if (record.relatedTasks?.includes(task.id)) {
54
+ return true;
55
+ }
56
+
57
+ const targetFiles = new Set(task.targetFiles ?? []);
58
+ if (record.relatedFiles?.some((path) => targetFiles.has(path))) {
59
+ return true;
60
+ }
61
+
62
+ for (const dependencyId of task.dependsOn ?? []) {
63
+ if (record.sourceWorkItem === dependencyId) {
64
+ return true;
65
+ }
66
+
67
+ const dependency = itemById.get(dependencyId);
68
+ if (dependency?.dependsOn?.includes(record.sourceWorkItem)) {
69
+ return true;
70
+ }
71
+ }
72
+
73
+ return false;
74
+ }
75
+
76
+ function sortRecords(records) {
77
+ return [...records].sort((left, right) => {
78
+ const byType = typePriorityIndex(left.type) - typePriorityIndex(right.type);
79
+ if (byType !== 0) {
80
+ return byType;
81
+ }
82
+
83
+ const byStatus = statusPriority(left.status) - statusPriority(right.status);
84
+ if (byStatus !== 0) {
85
+ return byStatus;
86
+ }
87
+
88
+ return compareText(left.id, right.id);
89
+ });
90
+ }
91
+
92
+ function applyBudget(records, options = {}) {
93
+ const maxRecords = Number.isInteger(options.maxRecords) && options.maxRecords > 0
94
+ ? options.maxRecords
95
+ : DEFAULT_MAX_RECORDS;
96
+ const maxChars = Number.isInteger(options.maxChars) && options.maxChars > 0
97
+ ? options.maxChars
98
+ : DEFAULT_MAX_CHARS;
99
+ const maxRelatedFiles = Number.isInteger(options.maxRelatedFiles) && options.maxRelatedFiles > 0
100
+ ? options.maxRelatedFiles
101
+ : DEFAULT_MAX_RELATED_FILES;
102
+
103
+ const selected = [];
104
+ let summaryChars = 0;
105
+ const relatedFiles = new Set();
106
+
107
+ for (const record of sortRecords(records)) {
108
+ if (selected.length >= maxRecords) {
109
+ break;
110
+ }
111
+
112
+ const nextSummaryChars = summaryChars + String(record.summary ?? '').length;
113
+ if (nextSummaryChars > maxChars && selected.length > 0) {
114
+ continue;
115
+ }
116
+
117
+ const nextRelatedFiles = [...relatedFiles];
118
+ for (const path of record.relatedFiles ?? []) {
119
+ if (!nextRelatedFiles.includes(path)) {
120
+ nextRelatedFiles.push(path);
121
+ }
122
+ }
123
+
124
+ if (nextRelatedFiles.length > maxRelatedFiles && selected.length > 0) {
125
+ continue;
126
+ }
127
+
128
+ selected.push(record);
129
+ summaryChars = nextSummaryChars;
130
+ for (const path of record.relatedFiles ?? []) {
131
+ relatedFiles.add(path);
132
+ }
133
+ }
134
+
135
+ return {
136
+ records: selected,
137
+ truncated: selected.length < records.length,
138
+ summaryChars,
139
+ relatedFileCount: relatedFiles.size,
140
+ };
141
+ }
142
+
143
+ export function selectMemoryRecordsForTask(items, taskId, options = {}) {
144
+ if (!Array.isArray(items)) {
145
+ throw new TypeError('items must be an array');
146
+ }
147
+
148
+ const task = items.find((item) => item.id === taskId);
149
+ if (task === undefined) {
150
+ throw new Error(`unknown task id: ${taskId}`);
151
+ }
152
+
153
+ const itemById = new Map(items.map((item) => [item.id, item]));
154
+ const derivedCandidates = buildMemoryRecordCandidatesFromItems(items, options.memoryOptions).records;
155
+ const candidates = options.memoryRecords
156
+ ?? mergeMemoryJournalWithCandidates(derivedCandidates, options.journalRecords ?? []);
157
+
158
+ const relevant = candidates.filter((record) => {
159
+ if (record.status === 'retired') {
160
+ return false;
161
+ }
162
+
163
+ return isRecordRelevant(record, task, itemById);
164
+ });
165
+
166
+ return applyBudget(relevant, options);
167
+ }
168
+
169
+ export function buildMemoryWorkerSliceForTask(items, taskId, options = {}) {
170
+ const selection = selectMemoryRecordsForTask(items, taskId, options);
171
+
172
+ return {
173
+ schema: MEMORY_WORKER_SLICE_SCHEMA,
174
+ taskId,
175
+ truncated: selection.truncated,
176
+ recordCount: selection.records.length,
177
+ summaryChars: selection.summaryChars,
178
+ relatedFileCount: selection.relatedFileCount,
179
+ budget: {
180
+ maxRecords: options.maxRecords ?? DEFAULT_MAX_RECORDS,
181
+ maxChars: options.maxChars ?? DEFAULT_MAX_CHARS,
182
+ maxRelatedFiles: options.maxRelatedFiles ?? DEFAULT_MAX_RELATED_FILES,
183
+ },
184
+ records: selection.records.map((record) => ({
185
+ id: record.id,
186
+ type: record.type,
187
+ summary: record.summary,
188
+ status: record.status,
189
+ confidence: record.confidence,
190
+ sourceWorkItem: record.sourceWorkItem,
191
+ relatedFiles: [...(record.relatedFiles ?? [])].sort(compareText),
192
+ relatedTasks: [...(record.relatedTasks ?? [])].sort(compareText),
193
+ reviewRequired: Boolean(record.reviewRequired),
194
+ })),
195
+ sourceInputs: [
196
+ 'memory-record.candidates.v1',
197
+ 'memory-contract-v1',
198
+ ...(options.journalRecords?.length ? ['memory-record.journal.v1'] : []),
199
+ ],
200
+ };
201
+ }
202
+
203
+ export async function buildMemoryWorkerSliceForTaskWithJournal(items, taskId, options = {}) {
204
+ let journalRecords = options.journalRecords;
205
+
206
+ if (journalRecords === undefined && options.journalPath) {
207
+ const journal = await readMemoryRecordJournal({
208
+ cwd: options.cwd,
209
+ journalPath: options.journalPath,
210
+ });
211
+ journalRecords = journal.records;
212
+ }
213
+
214
+ return buildMemoryWorkerSliceForTask(items, taskId, {
215
+ ...options,
216
+ journalRecords,
217
+ });
218
+ }
219
+
220
+ export function formatMemoryWorkerSliceForPrompt(slice) {
221
+ if (!slice || slice.schema !== MEMORY_WORKER_SLICE_SCHEMA) {
222
+ return '';
223
+ }
224
+
225
+ if (!Array.isArray(slice.records) || slice.records.length === 0) {
226
+ return '';
227
+ }
228
+
229
+ const lines = [
230
+ 'Project memory slice (bounded, memory-contract-v1):',
231
+ `task=${slice.taskId} records=${slice.recordCount}${slice.truncated ? ' truncated=true' : ''}`,
232
+ ...slice.records.map((record) =>
233
+ `- [${record.type}/${record.status}] ${record.summary} (from ${record.sourceWorkItem}, confidence=${record.confidence})`,
234
+ ),
235
+ ];
236
+
237
+ return lines.join('\n');
238
+ }
@@ -0,0 +1,133 @@
1
+ import { readdir, stat } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+
4
+ import { swapBvcExtension } from './bvcFileFormat.mjs';
5
+
6
+ const DEFAULT_SCAN_ROOTS = Object.freeze([
7
+ 'architecture',
8
+ 'charter',
9
+ 'protocols',
10
+ 'intent',
11
+ 'work',
12
+ 'rules',
13
+ 'ui',
14
+ 'domains',
15
+ 'plans',
16
+ 'skills',
17
+ 'tests',
18
+ ]);
19
+
20
+ /** @type {Set<string>} */
21
+ export const STEP_RENAME_SKIP_RELATIVE_PATHS = new Set([
22
+ 'tests/conformance/minimal.en.step',
23
+ ]);
24
+
25
+ /**
26
+ * @param {string} filePath
27
+ */
28
+ export function isStepMigrationCandidate(filePath) {
29
+ return String(filePath).toLowerCase().endsWith('.step');
30
+ }
31
+
32
+ /**
33
+ * @param {string} filePath
34
+ * @param {{ preferCanon?: boolean }} [options]
35
+ */
36
+ export function stepToBvcTargetPath(filePath, options = {}) {
37
+ if (!isStepMigrationCandidate(filePath)) {
38
+ return null;
39
+ }
40
+ return swapBvcExtension(filePath, options);
41
+ }
42
+
43
+ /**
44
+ * @param {string[]} filePaths
45
+ * @param {{ preferCanon?: boolean }} [options]
46
+ */
47
+ export function buildStepToBvcMigrationPlan(filePaths, options = {}) {
48
+ /** @type {Array<{ from: string, to: string }>} */
49
+ const renames = [];
50
+
51
+ for (const filePath of filePaths) {
52
+ const to = stepToBvcTargetPath(filePath, options);
53
+ if (to === null || to === filePath) {
54
+ continue;
55
+ }
56
+ renames.push({ from: filePath, to });
57
+ }
58
+
59
+ renames.sort((left, right) => left.from.localeCompare(right.from, 'en', { sensitivity: 'variant' }));
60
+ return renames;
61
+ }
62
+
63
+ /**
64
+ * @param {string} directory
65
+ * @param {{ cwd?: string, roots?: string[] }} [options]
66
+ */
67
+ export async function collectStepFilesUnderRoots(options = {}) {
68
+ const cwd = options.cwd ?? process.cwd();
69
+ const roots = options.roots ?? DEFAULT_SCAN_ROOTS;
70
+ /** @type {string[]} */
71
+ const files = [];
72
+
73
+ async function walk(relativeDir) {
74
+ const absoluteDir = join(cwd, relativeDir);
75
+ let entries;
76
+ try {
77
+ entries = await readdir(absoluteDir, { withFileTypes: true });
78
+ } catch {
79
+ return;
80
+ }
81
+
82
+ for (const entry of entries) {
83
+ const relativePath = join(relativeDir, entry.name).replace(/\\/g, '/');
84
+ if (entry.isDirectory()) {
85
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist') {
86
+ continue;
87
+ }
88
+ await walk(relativePath);
89
+ continue;
90
+ }
91
+ if (entry.isFile() && isStepMigrationCandidate(relativePath)) {
92
+ if (STEP_RENAME_SKIP_RELATIVE_PATHS.has(relativePath)) {
93
+ continue;
94
+ }
95
+ files.push(relativePath);
96
+ }
97
+ }
98
+ }
99
+
100
+ for (const root of roots) {
101
+ const absoluteRoot = join(cwd, root);
102
+ try {
103
+ const rootStat = await stat(absoluteRoot);
104
+ if (!rootStat.isDirectory()) {
105
+ continue;
106
+ }
107
+ } catch {
108
+ continue;
109
+ }
110
+ await walk(root);
111
+ }
112
+
113
+ files.sort((left, right) => left.localeCompare(right, 'en', { sensitivity: 'variant' }));
114
+ return files;
115
+ }
116
+
117
+ /**
118
+ * @param {string} cwd
119
+ * @param {{ roots?: string[], paths?: string[] }} [options]
120
+ */
121
+ export async function buildDefaultStepToBvcMigrationPlan(cwd, options = {}) {
122
+ const explicitPaths = (options.paths ?? [])
123
+ .map((entry) => relative(cwd, entry).replace(/\\/g, '/'))
124
+ .filter((entry) => entry && !entry.startsWith('..'));
125
+
126
+ const filePaths = explicitPaths.length > 0
127
+ ? explicitPaths
128
+ : await collectStepFilesUnderRoots({ cwd, roots: options.roots });
129
+
130
+ return buildStepToBvcMigrationPlan(filePaths);
131
+ }
132
+
133
+ export { DEFAULT_SCAN_ROOTS };