@lumenflow/core 2.1.2 → 2.2.1

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.
@@ -7,7 +7,7 @@
7
7
  * - string risks → array
8
8
  * - test_paths → tests
9
9
  * - ISO/Date created → YYYY-MM-DD
10
- * - Removes deprecated fields: owner, context, spec_refs
10
+ * - Removes deprecated fields: owner, context
11
11
  */
12
12
  /**
13
13
  * Normalize a WU schema object, converting legacy formats to current schema.
@@ -7,7 +7,7 @@
7
7
  * - string risks → array
8
8
  * - test_paths → tests
9
9
  * - ISO/Date created → YYYY-MM-DD
10
- * - Removes deprecated fields: owner, context, spec_refs
10
+ * - Removes deprecated fields: owner, context
11
11
  */
12
12
  /**
13
13
  * Normalize a WU schema object, converting legacy formats to current schema.
@@ -77,6 +77,5 @@ export function normalizeWUSchema(wu) {
77
77
  }
78
78
  // 6. Remove deprecated fields
79
79
  delete result.owner;
80
- delete result.spec_refs;
81
80
  return result;
82
81
  }
@@ -14,6 +14,14 @@ export declare function resolveSkillsPaths(config: any, clientName: any): {
14
14
  configuredAgentsMissing: boolean;
15
15
  };
16
16
  export declare function generateSkillsCatalogGuidance(config: any, clientName: any): string;
17
- export declare function generateClientSkillsGuidance(clientContext: ClientContext): string;
17
+ /**
18
+ * WU-1142: Get byLane skills for a specific lane
19
+ *
20
+ * @param clientContext - Client context with config
21
+ * @param lane - Lane name (e.g., "Framework: Core")
22
+ * @returns Array of skill names for the lane, or empty array
23
+ */
24
+ export declare function getByLaneSkills(clientContext: ClientContext | undefined, lane: string): string[];
25
+ export declare function generateClientSkillsGuidance(clientContext: ClientContext | undefined, lane?: string): string;
18
26
  export declare function generateSkillsSelectionSection(doc: any, config: any, clientName: any): string;
19
27
  export {};
@@ -107,17 +107,40 @@ export function generateSkillsCatalogGuidance(config, clientName) {
107
107
  : 'agents.clients.<client>.skillsDir';
108
108
  return `${SECTION.skillsCatalog}\n\n${configuredHint}No skills directories configured or found. Set \`directories.skillsDir\` or \`${clientHint}\` in .lumenflow.config.yaml.\n`;
109
109
  }
110
- export function generateClientSkillsGuidance(clientContext) {
110
+ /**
111
+ * WU-1142: Get byLane skills for a specific lane
112
+ *
113
+ * @param clientContext - Client context with config
114
+ * @param lane - Lane name (e.g., "Framework: Core")
115
+ * @returns Array of skill names for the lane, or empty array
116
+ */
117
+ export function getByLaneSkills(clientContext, lane) {
118
+ const byLane = clientContext?.config?.skills?.byLane;
119
+ if (!byLane || !lane)
120
+ return [];
121
+ return byLane[lane] || [];
122
+ }
123
+ export function generateClientSkillsGuidance(clientContext, lane) {
111
124
  const skills = clientContext?.config?.skills;
112
- if (!skills ||
113
- (!skills.instructions && (!skills.recommended || skills.recommended.length === 0))) {
125
+ if (!skills) {
126
+ return '';
127
+ }
128
+ // WU-1142: Check for byLane skills
129
+ const byLaneSkills = lane ? getByLaneSkills(clientContext, lane) : [];
130
+ const hasRecommended = skills.recommended && skills.recommended.length > 0;
131
+ const hasByLane = byLaneSkills.length > 0;
132
+ const hasInstructions = Boolean(skills.instructions);
133
+ if (!hasInstructions && !hasRecommended && !hasByLane) {
114
134
  return '';
115
135
  }
116
136
  const instructions = skills.instructions ? `${skills.instructions.trim()}\n\n` : '';
117
- const recommended = skills.recommended && skills.recommended.length > 0
118
- ? `Recommended skills:\n${skills.recommended.map((s) => `- \`${s}\``).join('\n')}\n`
137
+ const recommendedSection = hasRecommended || hasByLane
138
+ ? `Recommended skills:\n${[...(skills.recommended || []), ...byLaneSkills]
139
+ .filter((s, i, arr) => arr.indexOf(s) === i) // dedupe
140
+ .map((s) => `- \`${s}\``)
141
+ .join('\n')}\n`
119
142
  : '';
120
- return `${SECTION.clientSkills} (${clientContext.name})\n\n${instructions}${recommended}`;
143
+ return `${SECTION.clientSkills} (${clientContext?.name})\n\n${instructions}${recommendedSection}`;
121
144
  }
122
145
  export function generateSkillsSelectionSection(doc, config, clientName) {
123
146
  const lane = doc.lane || '';
@@ -45,13 +45,25 @@ export declare function computeWUYAMLContent(doc: any): string;
45
45
  * @throws {Error} If file not found or section not found
46
46
  */
47
47
  export declare function computeStatusContent(statusPath: any, id: any, title: any): string;
48
+ /**
49
+ * Compute wu-events.jsonl content after completing a WU.
50
+ *
51
+ * WU-1145: Now merges with origin/main to preserve concurrent changes.
52
+ * This prevents loss of WU entries when multiple WUs are completed concurrently.
53
+ *
54
+ * @param {string} backlogPath - Path to backlog.md
55
+ * @param {string} wuId - WU ID being completed
56
+ * @returns {Promise<{eventsPath: string, content: string} | null>}
57
+ */
48
58
  export declare function computeWUEventsContentAfterComplete(backlogPath: any, wuId: any): Promise<{
49
59
  eventsPath: string;
50
60
  content: string;
51
61
  }>;
52
62
  /**
53
63
  * Compute updated backlog.md content
64
+ *
54
65
  * WU-1574: Simplified to generate from state store
66
+ * WU-1145: Now merges with origin/main to preserve concurrent changes
55
67
  *
56
68
  * @param {string} backlogPath - Path to backlog.md
57
69
  * @param {string} id - WU ID to mark complete
@@ -24,7 +24,6 @@
24
24
  */
25
25
  /* eslint-disable security/detect-non-literal-fs-filename, security/detect-object-injection */
26
26
  import { existsSync, readFileSync } from 'node:fs';
27
- import path from 'node:path';
28
27
  import { stringifyYAML } from './wu-yaml.js';
29
28
  import { parseBacklogFrontmatter } from './backlog-parser.js';
30
29
  import { getSectionHeadingsWithDefaults } from './section-headings.js';
@@ -32,10 +31,11 @@ import { todayISO } from './date-utils.js';
32
31
  import { createError, ErrorCodes } from './error-handler.js';
33
32
  import { STRING_LITERALS } from './wu-constants.js';
34
33
  // WU-1574: BacklogManager removed - using state store + generator
35
- import { WUStateStore, WU_EVENTS_FILE_NAME } from './wu-state-store.js';
36
- import { generateBacklog } from './backlog-generator.js';
34
+ import { WUStateStore } from './wu-state-store.js';
37
35
  // WU-1734: Import proper path resolution utility
38
36
  import { getStateStoreDirFromBacklog } from './wu-paths.js';
37
+ // WU-1145: Import concurrent merge utilities
38
+ import { computeBacklogContentWithMainMerge, computeWUEventsContentWithMainMerge, } from './wu-done-concurrent-merge.js';
39
39
  /**
40
40
  * Compute WU YAML content for done state
41
41
  *
@@ -192,22 +192,25 @@ async function computeCompletionUpdatesFromStateStore(backlogPath, wuId) {
192
192
  store.applyEvent(completeEvent);
193
193
  return { store, stateDir, shouldAppendCompleteEvent: true, completeEvent };
194
194
  }
195
+ /**
196
+ * Compute wu-events.jsonl content after completing a WU.
197
+ *
198
+ * WU-1145: Now merges with origin/main to preserve concurrent changes.
199
+ * This prevents loss of WU entries when multiple WUs are completed concurrently.
200
+ *
201
+ * @param {string} backlogPath - Path to backlog.md
202
+ * @param {string} wuId - WU ID being completed
203
+ * @returns {Promise<{eventsPath: string, content: string} | null>}
204
+ */
195
205
  export async function computeWUEventsContentAfterComplete(backlogPath, wuId) {
196
- const { stateDir, shouldAppendCompleteEvent, completeEvent } = await computeCompletionUpdatesFromStateStore(backlogPath, wuId);
197
- if (!shouldAppendCompleteEvent) {
198
- return null;
199
- }
200
- const eventsPath = path.join(stateDir, WU_EVENTS_FILE_NAME);
201
- const existing = existsSync(eventsPath) ? readFileSync(eventsPath, { encoding: 'utf-8' }) : '';
202
- const withNewline = ensureTrailingNewline(existing);
203
- return {
204
- eventsPath,
205
- content: withNewline + JSON.stringify(completeEvent) + STRING_LITERALS.NEWLINE,
206
- };
206
+ // WU-1145: Use merged state to preserve concurrent changes
207
+ return computeWUEventsContentWithMainMerge(backlogPath, wuId);
207
208
  }
208
209
  /**
209
210
  * Compute updated backlog.md content
211
+ *
210
212
  * WU-1574: Simplified to generate from state store
213
+ * WU-1145: Now merges with origin/main to preserve concurrent changes
211
214
  *
212
215
  * @param {string} backlogPath - Path to backlog.md
213
216
  * @param {string} id - WU ID to mark complete
@@ -215,8 +218,8 @@ export async function computeWUEventsContentAfterComplete(backlogPath, wuId) {
215
218
  * @returns {Promise<string>} New backlog.md content
216
219
  */
217
220
  export async function computeBacklogContent(backlogPath, id, _title) {
218
- const { store } = await computeCompletionUpdatesFromStateStore(backlogPath, id);
219
- return generateBacklog(store);
221
+ // WU-1145: Use merged state to preserve concurrent changes
222
+ return computeBacklogContentWithMainMerge(backlogPath, id);
220
223
  }
221
224
  /**
222
225
  * Compute stamp file content
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/core",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
4
4
  "description": "Core WU lifecycle tools for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -39,6 +39,7 @@
39
39
  "./spawn-recovery": "./dist/spawn-recovery.js",
40
40
  "./spawn-monitor": "./dist/spawn-monitor.js",
41
41
  "./spawn-escalation": "./dist/spawn-escalation.js",
42
+ "./spawn-prompt-schema": "./dist/spawn-prompt-schema.js",
42
43
  "./backlog-generator": "./dist/backlog-generator.js",
43
44
  "./backlog-parser": "./dist/backlog-parser.js",
44
45
  "./backlog-editor": "./dist/backlog-editor.js",
@@ -94,7 +95,7 @@
94
95
  "vitest": "^4.0.17"
95
96
  },
96
97
  "peerDependencies": {
97
- "@lumenflow/memory": "2.1.2"
98
+ "@lumenflow/memory": "2.2.1"
98
99
  },
99
100
  "peerDependenciesMeta": {
100
101
  "@lumenflow/memory": {