@lumenflow/core 2.1.2 → 2.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/dist/backlog-generator.d.ts +9 -19
- package/dist/backlog-generator.js +133 -41
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/lumenflow-config-schema.d.ts +5 -0
- package/dist/lumenflow-config-schema.js +9 -0
- package/dist/spawn-prompt-schema.d.ts +106 -0
- package/dist/spawn-prompt-schema.js +160 -0
- package/dist/system-map-validator.js +50 -0
- package/dist/wu-constants.js +3 -3
- package/dist/wu-done-concurrent-merge.d.ts +102 -0
- package/dist/wu-done-concurrent-merge.js +330 -0
- package/dist/wu-done-docs-generate.d.ts +7 -0
- package/dist/wu-done-docs-generate.js +18 -1
- package/dist/wu-done-preflight.js +4 -11
- package/dist/wu-schema-normalization.d.ts +1 -1
- package/dist/wu-schema-normalization.js +1 -2
- package/dist/wu-spawn-skills.d.ts +9 -1
- package/dist/wu-spawn-skills.js +29 -6
- package/dist/wu-transaction-collectors.d.ts +12 -0
- package/dist/wu-transaction-collectors.js +19 -16
- package/package.json +3 -2
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
* @see {@link tools/__tests__/status-date-from-event.test.mjs} - Date tests
|
|
17
17
|
* @see {@link tools/lib/wu-state-store.mjs} - State store
|
|
18
18
|
*/
|
|
19
|
+
interface BacklogYamlOptions {
|
|
20
|
+
wuDir?: string;
|
|
21
|
+
projectRoot?: string;
|
|
22
|
+
}
|
|
19
23
|
/**
|
|
20
24
|
* Generates backlog.md markdown from WUStateStore
|
|
21
25
|
*
|
|
@@ -26,6 +30,9 @@
|
|
|
26
30
|
* - Placeholder text for empty sections
|
|
27
31
|
*
|
|
28
32
|
* @param {import('./wu-state-store.js').WUStateStore} store - State store to read from
|
|
33
|
+
* @param {object} [options] - Optional settings
|
|
34
|
+
* @param {string} [options.wuDir] - Absolute or repo-relative path to WU YAML directory
|
|
35
|
+
* @param {string} [options.projectRoot] - Project root override for path resolution
|
|
29
36
|
* @returns {Promise<string>} Markdown content for backlog.md
|
|
30
37
|
*
|
|
31
38
|
* @example
|
|
@@ -34,25 +41,7 @@
|
|
|
34
41
|
* const markdown = await generateBacklog(store);
|
|
35
42
|
* await fs.writeFile('backlog.md', markdown, 'utf-8');
|
|
36
43
|
*/
|
|
37
|
-
export declare function generateBacklog(store: any): Promise<string>;
|
|
38
|
-
/**
|
|
39
|
-
* Generates status.md markdown from WUStateStore
|
|
40
|
-
*
|
|
41
|
-
* Format matches current status.md exactly:
|
|
42
|
-
* - Header with last updated timestamp
|
|
43
|
-
* - In Progress section
|
|
44
|
-
* - Completed section with dates
|
|
45
|
-
* - Placeholder for empty sections
|
|
46
|
-
*
|
|
47
|
-
* @param {import('./wu-state-store.js').WUStateStore} store - State store to read from
|
|
48
|
-
* @returns {Promise<string>} Markdown content for status.md
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* const store = new WUStateStore('/path/to/state');
|
|
52
|
-
* await store.load();
|
|
53
|
-
* const markdown = await generateStatus(store);
|
|
54
|
-
* await fs.writeFile('status.md', markdown, 'utf-8');
|
|
55
|
-
*/
|
|
44
|
+
export declare function generateBacklog(store: any, options?: BacklogYamlOptions): Promise<string>;
|
|
56
45
|
export declare function generateStatus(store: any): Promise<string>;
|
|
57
46
|
/**
|
|
58
47
|
* WU-2244: Get completion date for a WU from state store
|
|
@@ -109,3 +98,4 @@ export declare function validateBacklogConsistency(store: any, markdown: any): P
|
|
|
109
98
|
valid: boolean;
|
|
110
99
|
errors: any[];
|
|
111
100
|
}>;
|
|
101
|
+
export {};
|
|
@@ -17,6 +17,82 @@
|
|
|
17
17
|
* @see {@link tools/lib/wu-state-store.mjs} - State store
|
|
18
18
|
*/
|
|
19
19
|
import { createHash } from 'node:crypto';
|
|
20
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
import { readWURaw } from './wu-yaml.js';
|
|
23
|
+
import { WU_STATUS, WU_STATUS_GROUPS } from './wu-constants.js';
|
|
24
|
+
import { createWuPaths, resolveFromProjectRoot } from './wu-paths.js';
|
|
25
|
+
const WU_FILENAME_PATTERN = /^WU-\d+\.yaml$/;
|
|
26
|
+
function normalizeYamlScalar(value) {
|
|
27
|
+
if (value === undefined || value === null) {
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
return typeof value === 'string' ? value : String(value);
|
|
31
|
+
}
|
|
32
|
+
function normalizeYamlStatus(value) {
|
|
33
|
+
const normalized = normalizeYamlScalar(value).trim().toLowerCase();
|
|
34
|
+
return normalized === '' ? WU_STATUS.READY : normalized;
|
|
35
|
+
}
|
|
36
|
+
function mapYamlStatusToSection(status) {
|
|
37
|
+
if (WU_STATUS_GROUPS.UNCLAIMED.includes(status)) {
|
|
38
|
+
return WU_STATUS.READY;
|
|
39
|
+
}
|
|
40
|
+
if (status === WU_STATUS.IN_PROGRESS) {
|
|
41
|
+
return WU_STATUS.IN_PROGRESS;
|
|
42
|
+
}
|
|
43
|
+
if (status === WU_STATUS.BLOCKED) {
|
|
44
|
+
return WU_STATUS.BLOCKED;
|
|
45
|
+
}
|
|
46
|
+
if (WU_STATUS_GROUPS.TERMINAL.includes(status)) {
|
|
47
|
+
return WU_STATUS.DONE;
|
|
48
|
+
}
|
|
49
|
+
return WU_STATUS.READY;
|
|
50
|
+
}
|
|
51
|
+
function compareWuIds(a, b) {
|
|
52
|
+
const numA = Number.parseInt(a.replace(/^WU-/, ''), 10);
|
|
53
|
+
const numB = Number.parseInt(b.replace(/^WU-/, ''), 10);
|
|
54
|
+
if (!Number.isNaN(numA) && !Number.isNaN(numB) && numA !== numB) {
|
|
55
|
+
return numA - numB;
|
|
56
|
+
}
|
|
57
|
+
return a.localeCompare(b);
|
|
58
|
+
}
|
|
59
|
+
function resolveWuDir(options = {}) {
|
|
60
|
+
const paths = createWuPaths({ projectRoot: options.projectRoot });
|
|
61
|
+
const configured = options.wuDir || paths.WU_DIR();
|
|
62
|
+
return path.isAbsolute(configured) ? configured : resolveFromProjectRoot(configured);
|
|
63
|
+
}
|
|
64
|
+
function loadYamlWuEntries(wuDir) {
|
|
65
|
+
if (!existsSync(wuDir)) {
|
|
66
|
+
return new Map();
|
|
67
|
+
}
|
|
68
|
+
const files = readdirSync(wuDir).filter((file) => WU_FILENAME_PATTERN.test(file));
|
|
69
|
+
files.sort((a, b) => compareWuIds(a.replace(/\.yaml$/, ''), b.replace(/\.yaml$/, '')));
|
|
70
|
+
const entries = new Map();
|
|
71
|
+
for (const file of files) {
|
|
72
|
+
const wuId = file.replace(/\.yaml$/, '');
|
|
73
|
+
const doc = readWURaw(path.join(wuDir, file));
|
|
74
|
+
if (!doc || typeof doc !== 'object') {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
entries.set(wuId, {
|
|
78
|
+
status: normalizeYamlStatus(doc.status),
|
|
79
|
+
title: normalizeYamlScalar(doc.title),
|
|
80
|
+
lane: normalizeYamlScalar(doc.lane),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return entries;
|
|
84
|
+
}
|
|
85
|
+
function getMergedBacklogEntry(store, yamlEntries, wuId) {
|
|
86
|
+
const state = typeof store.getWUState === 'function' ? store.getWUState(wuId) : store.wuState.get(wuId);
|
|
87
|
+
if (state) {
|
|
88
|
+
return { title: state.title, lane: state.lane };
|
|
89
|
+
}
|
|
90
|
+
const yamlEntry = yamlEntries.get(wuId);
|
|
91
|
+
if (!yamlEntry) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
return { title: yamlEntry.title, lane: yamlEntry.lane };
|
|
95
|
+
}
|
|
20
96
|
/**
|
|
21
97
|
* Generates backlog.md markdown from WUStateStore
|
|
22
98
|
*
|
|
@@ -27,6 +103,9 @@ import { createHash } from 'node:crypto';
|
|
|
27
103
|
* - Placeholder text for empty sections
|
|
28
104
|
*
|
|
29
105
|
* @param {import('./wu-state-store.js').WUStateStore} store - State store to read from
|
|
106
|
+
* @param {object} [options] - Optional settings
|
|
107
|
+
* @param {string} [options.wuDir] - Absolute or repo-relative path to WU YAML directory
|
|
108
|
+
* @param {string} [options.projectRoot] - Project root override for path resolution
|
|
30
109
|
* @returns {Promise<string>} Markdown content for backlog.md
|
|
31
110
|
*
|
|
32
111
|
* @example
|
|
@@ -36,7 +115,7 @@ import { createHash } from 'node:crypto';
|
|
|
36
115
|
* await fs.writeFile('backlog.md', markdown, 'utf-8');
|
|
37
116
|
*/
|
|
38
117
|
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing complexity, refactor tracked separately
|
|
39
|
-
export async function generateBacklog(store) {
|
|
118
|
+
export async function generateBacklog(store, options = {}) {
|
|
40
119
|
// Start with frontmatter
|
|
41
120
|
const frontmatter = `---
|
|
42
121
|
sections:
|
|
@@ -54,25 +133,60 @@ sections:
|
|
|
54
133
|
insertion: after_heading_blank_line
|
|
55
134
|
---
|
|
56
135
|
|
|
57
|
-
> Agent: Read **docs/04-operations/_frameworks/lumenflow/agent/onboarding/starting-prompt.md** first, then follow **docs/04-operations
|
|
136
|
+
> Agent: Read **docs/04-operations/_frameworks/lumenflow/agent/onboarding/starting-prompt.md** first, then follow **docs/04-operations/\_frameworks/lumenflow/lumenflow-complete.md** for execution.
|
|
58
137
|
|
|
59
138
|
# Backlog (single source of truth)
|
|
60
139
|
|
|
61
140
|
`;
|
|
141
|
+
const yamlEntries = loadYamlWuEntries(resolveWuDir(options));
|
|
142
|
+
const storeReady = Array.from(store.getByStatus('ready'));
|
|
143
|
+
const storeInProgress = Array.from(store.getByStatus('in_progress'));
|
|
144
|
+
const storeBlocked = Array.from(store.getByStatus('blocked'));
|
|
145
|
+
const storeDone = Array.from(store.getByStatus('done'));
|
|
146
|
+
const storeIds = new Set([...storeReady, ...storeInProgress, ...storeBlocked, ...storeDone]);
|
|
147
|
+
const yamlReady = [];
|
|
148
|
+
const yamlInProgress = [];
|
|
149
|
+
const yamlBlocked = [];
|
|
150
|
+
const yamlDone = [];
|
|
151
|
+
for (const [wuId, entry] of yamlEntries.entries()) {
|
|
152
|
+
if (storeIds.has(wuId)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const status = mapYamlStatusToSection(entry.status);
|
|
156
|
+
if (status === WU_STATUS.IN_PROGRESS) {
|
|
157
|
+
yamlInProgress.push(wuId);
|
|
158
|
+
}
|
|
159
|
+
else if (status === WU_STATUS.BLOCKED) {
|
|
160
|
+
yamlBlocked.push(wuId);
|
|
161
|
+
}
|
|
162
|
+
else if (status === WU_STATUS.DONE) {
|
|
163
|
+
yamlDone.push(wuId);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
yamlReady.push(wuId);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
yamlReady.sort(compareWuIds);
|
|
170
|
+
yamlInProgress.sort(compareWuIds);
|
|
171
|
+
yamlBlocked.sort(compareWuIds);
|
|
172
|
+
yamlDone.sort(compareWuIds);
|
|
173
|
+
const ready = [...storeReady, ...yamlReady];
|
|
174
|
+
const inProgress = [...storeInProgress, ...yamlInProgress];
|
|
175
|
+
const blocked = [...storeBlocked, ...yamlBlocked];
|
|
176
|
+
const done = [...storeDone, ...yamlDone];
|
|
62
177
|
// Generate sections
|
|
63
178
|
const sections = [];
|
|
64
179
|
// Ready section (WUs with status: ready)
|
|
65
180
|
sections.push('## 🚀 Ready (pull from here)');
|
|
66
181
|
sections.push('');
|
|
67
|
-
|
|
68
|
-
if (ready.size === 0) {
|
|
182
|
+
if (ready.length === 0) {
|
|
69
183
|
sections.push('(No items ready)');
|
|
70
184
|
}
|
|
71
185
|
else {
|
|
72
186
|
for (const wuId of ready) {
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
75
|
-
sections.push(`- [${wuId} — ${
|
|
187
|
+
const entry = getMergedBacklogEntry(store, yamlEntries, wuId);
|
|
188
|
+
if (entry) {
|
|
189
|
+
sections.push(`- [${wuId} — ${entry.title}](wu/${wuId}.yaml) — ${entry.lane}`);
|
|
76
190
|
}
|
|
77
191
|
}
|
|
78
192
|
}
|
|
@@ -80,15 +194,14 @@ sections:
|
|
|
80
194
|
sections.push('');
|
|
81
195
|
sections.push('## 🔧 In progress');
|
|
82
196
|
sections.push('');
|
|
83
|
-
|
|
84
|
-
if (inProgress.size === 0) {
|
|
197
|
+
if (inProgress.length === 0) {
|
|
85
198
|
sections.push('(No items currently in progress)');
|
|
86
199
|
}
|
|
87
200
|
else {
|
|
88
201
|
for (const wuId of inProgress) {
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
sections.push(`- [${wuId} — ${
|
|
202
|
+
const entry = getMergedBacklogEntry(store, yamlEntries, wuId);
|
|
203
|
+
if (entry) {
|
|
204
|
+
sections.push(`- [${wuId} — ${entry.title}](wu/${wuId}.yaml) — ${entry.lane}`);
|
|
92
205
|
}
|
|
93
206
|
}
|
|
94
207
|
}
|
|
@@ -96,15 +209,14 @@ sections:
|
|
|
96
209
|
sections.push('');
|
|
97
210
|
sections.push('## ⛔ Blocked');
|
|
98
211
|
sections.push('');
|
|
99
|
-
|
|
100
|
-
if (blocked.size === 0) {
|
|
212
|
+
if (blocked.length === 0) {
|
|
101
213
|
sections.push('(No items currently blocked)');
|
|
102
214
|
}
|
|
103
215
|
else {
|
|
104
216
|
for (const wuId of blocked) {
|
|
105
|
-
const
|
|
106
|
-
if (
|
|
107
|
-
sections.push(`- [${wuId} — ${
|
|
217
|
+
const entry = getMergedBacklogEntry(store, yamlEntries, wuId);
|
|
218
|
+
if (entry) {
|
|
219
|
+
sections.push(`- [${wuId} — ${entry.title}](wu/${wuId}.yaml) — ${entry.lane}`);
|
|
108
220
|
}
|
|
109
221
|
}
|
|
110
222
|
}
|
|
@@ -112,39 +224,19 @@ sections:
|
|
|
112
224
|
sections.push('');
|
|
113
225
|
sections.push('## ✅ Done');
|
|
114
226
|
sections.push('');
|
|
115
|
-
|
|
116
|
-
if (done.size === 0) {
|
|
227
|
+
if (done.length === 0) {
|
|
117
228
|
sections.push('(No completed items)');
|
|
118
229
|
}
|
|
119
230
|
else {
|
|
120
231
|
for (const wuId of done) {
|
|
121
|
-
const
|
|
122
|
-
if (
|
|
123
|
-
sections.push(`- [${wuId} — ${
|
|
232
|
+
const entry = getMergedBacklogEntry(store, yamlEntries, wuId);
|
|
233
|
+
if (entry) {
|
|
234
|
+
sections.push(`- [${wuId} — ${entry.title}](wu/${wuId}.yaml)`);
|
|
124
235
|
}
|
|
125
236
|
}
|
|
126
237
|
}
|
|
127
238
|
return frontmatter + sections.join('\n');
|
|
128
239
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Generates status.md markdown from WUStateStore
|
|
131
|
-
*
|
|
132
|
-
* Format matches current status.md exactly:
|
|
133
|
-
* - Header with last updated timestamp
|
|
134
|
-
* - In Progress section
|
|
135
|
-
* - Completed section with dates
|
|
136
|
-
* - Placeholder for empty sections
|
|
137
|
-
*
|
|
138
|
-
* @param {import('./wu-state-store.js').WUStateStore} store - State store to read from
|
|
139
|
-
* @returns {Promise<string>} Markdown content for status.md
|
|
140
|
-
*
|
|
141
|
-
* @example
|
|
142
|
-
* const store = new WUStateStore('/path/to/state');
|
|
143
|
-
* await store.load();
|
|
144
|
-
* const markdown = await generateStatus(store);
|
|
145
|
-
* await fs.writeFile('status.md', markdown, 'utf-8');
|
|
146
|
-
*/
|
|
147
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity -- Pre-existing complexity, refactor tracked separately
|
|
148
240
|
export async function generateStatus(store) {
|
|
149
241
|
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
150
242
|
// Header
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export * from './wu-yaml.js';
|
|
|
22
22
|
export { getAssignedEmail } from './wu-claim-helpers.js';
|
|
23
23
|
export * from './wu-done-worktree.js';
|
|
24
24
|
export * from './wu-done-validators.js';
|
|
25
|
+
export * from './wu-done-concurrent-merge.js';
|
|
25
26
|
export * from './wu-helpers.js';
|
|
26
27
|
export * from './wu-schema.js';
|
|
27
28
|
export * from './wu-validator.js';
|
|
@@ -31,6 +32,7 @@ export * from './spawn-tree.js';
|
|
|
31
32
|
export * from './spawn-recovery.js';
|
|
32
33
|
export * from './spawn-monitor.js';
|
|
33
34
|
export * from './spawn-escalation.js';
|
|
35
|
+
export { SPAWN_SENTINEL, SPAWN_PROMPT_VERSION, SpawnPromptSchema, computeChecksum, createSpawnPrompt, validateSpawnPrompt, parseSpawnPrompt, serializeSpawnPrompt, checkSentinel, type SpawnPrompt, type SpawnPromptValidationResult, type ParseResult as SpawnPromptParseResult, } from './spawn-prompt-schema.js';
|
|
34
36
|
export * from './backlog-generator.js';
|
|
35
37
|
export * from './backlog-parser.js';
|
|
36
38
|
export * from './backlog-editor.js';
|
package/dist/index.js
CHANGED
|
@@ -37,6 +37,8 @@ export * from './wu-yaml.js';
|
|
|
37
37
|
export { getAssignedEmail } from './wu-claim-helpers.js';
|
|
38
38
|
export * from './wu-done-worktree.js';
|
|
39
39
|
export * from './wu-done-validators.js';
|
|
40
|
+
// WU-1145: Concurrent backlog merge utilities
|
|
41
|
+
export * from './wu-done-concurrent-merge.js';
|
|
40
42
|
export * from './wu-helpers.js';
|
|
41
43
|
export * from './wu-schema.js';
|
|
42
44
|
export * from './wu-validator.js';
|
|
@@ -47,6 +49,9 @@ export * from './spawn-tree.js';
|
|
|
47
49
|
export * from './spawn-recovery.js';
|
|
48
50
|
export * from './spawn-monitor.js';
|
|
49
51
|
export * from './spawn-escalation.js';
|
|
52
|
+
// WU-1142: Spawn prompt schema for truncation-resistant prompts
|
|
53
|
+
// Explicit exports to avoid ValidationResult conflict with validation/index.js
|
|
54
|
+
export { SPAWN_SENTINEL, SPAWN_PROMPT_VERSION, SpawnPromptSchema, computeChecksum, createSpawnPrompt, validateSpawnPrompt, parseSpawnPrompt, serializeSpawnPrompt, checkSentinel, } from './spawn-prompt-schema.js';
|
|
50
55
|
// Backlog management
|
|
51
56
|
export * from './backlog-generator.js';
|
|
52
57
|
export * from './backlog-parser.js';
|
|
@@ -162,6 +162,7 @@ export declare const ClientBlockSchema: z.ZodObject<{
|
|
|
162
162
|
export declare const ClientSkillsSchema: z.ZodObject<{
|
|
163
163
|
instructions: z.ZodOptional<z.ZodString>;
|
|
164
164
|
recommended: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
165
|
+
byLane: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
165
166
|
}, z.core.$strip>;
|
|
166
167
|
/**
|
|
167
168
|
* Client configuration (per-client settings)
|
|
@@ -176,6 +177,7 @@ export declare const ClientConfigSchema: z.ZodObject<{
|
|
|
176
177
|
skills: z.ZodOptional<z.ZodObject<{
|
|
177
178
|
instructions: z.ZodOptional<z.ZodString>;
|
|
178
179
|
recommended: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
180
|
+
byLane: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
179
181
|
}, z.core.$strip>>;
|
|
180
182
|
}, z.core.$strip>;
|
|
181
183
|
/**
|
|
@@ -193,6 +195,7 @@ export declare const AgentsConfigSchema: z.ZodObject<{
|
|
|
193
195
|
skills: z.ZodOptional<z.ZodObject<{
|
|
194
196
|
instructions: z.ZodOptional<z.ZodString>;
|
|
195
197
|
recommended: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
198
|
+
byLane: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
196
199
|
}, z.core.$strip>>;
|
|
197
200
|
}, z.core.$strip>>>;
|
|
198
201
|
methodology: z.ZodDefault<z.ZodObject<{
|
|
@@ -346,6 +349,7 @@ export declare const LumenFlowConfigSchema: z.ZodObject<{
|
|
|
346
349
|
skills: z.ZodOptional<z.ZodObject<{
|
|
347
350
|
instructions: z.ZodOptional<z.ZodString>;
|
|
348
351
|
recommended: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
352
|
+
byLane: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
349
353
|
}, z.core.$strip>>;
|
|
350
354
|
}, z.core.$strip>>>;
|
|
351
355
|
methodology: z.ZodDefault<z.ZodObject<{
|
|
@@ -509,6 +513,7 @@ export declare function validateConfig(data: unknown): z.ZodSafeParseResult<{
|
|
|
509
513
|
skills?: {
|
|
510
514
|
recommended: string[];
|
|
511
515
|
instructions?: string;
|
|
516
|
+
byLane?: Record<string, string[]>;
|
|
512
517
|
};
|
|
513
518
|
}>;
|
|
514
519
|
methodology: {
|
|
@@ -256,6 +256,15 @@ export const ClientSkillsSchema = z.object({
|
|
|
256
256
|
instructions: z.string().optional(),
|
|
257
257
|
/** Recommended skills to load for this client */
|
|
258
258
|
recommended: z.array(z.string()).default([]),
|
|
259
|
+
/**
|
|
260
|
+
* WU-1142: Lane-specific skills to recommend
|
|
261
|
+
* Maps lane names to arrays of skill names
|
|
262
|
+
* @example
|
|
263
|
+
* byLane:
|
|
264
|
+
* 'Framework: Core': ['tdd-workflow', 'lumenflow-gates']
|
|
265
|
+
* 'Content: Documentation': ['worktree-discipline']
|
|
266
|
+
*/
|
|
267
|
+
byLane: z.record(z.string(), z.array(z.string())).optional(),
|
|
259
268
|
});
|
|
260
269
|
/**
|
|
261
270
|
* Client configuration (per-client settings)
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn Prompt Schema (WU-1142)
|
|
3
|
+
*
|
|
4
|
+
* Zod schema for truncation-resistant spawn prompts.
|
|
5
|
+
* Implements a YAML envelope format with:
|
|
6
|
+
* - SHA256 checksums for integrity validation
|
|
7
|
+
* - Sentinel values for truncation detection
|
|
8
|
+
* - Schema validation for agent consumers
|
|
9
|
+
*
|
|
10
|
+
* Three-Layer Defense:
|
|
11
|
+
* 1. YAML Envelope - head/tail truncation breaks YAML parse
|
|
12
|
+
* 2. Checksum - validates content integrity
|
|
13
|
+
* 3. Sentinel - confirms complete transmission
|
|
14
|
+
*
|
|
15
|
+
* @module spawn-prompt-schema
|
|
16
|
+
*/
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
/**
|
|
19
|
+
* Sentinel value that marks complete spawn prompt transmission
|
|
20
|
+
*/
|
|
21
|
+
export declare const SPAWN_SENTINEL = "LUMENFLOW_SPAWN_COMPLETE";
|
|
22
|
+
/**
|
|
23
|
+
* Current schema version
|
|
24
|
+
*/
|
|
25
|
+
export declare const SPAWN_PROMPT_VERSION = "1.0.0";
|
|
26
|
+
/**
|
|
27
|
+
* Zod schema for spawn prompt envelope
|
|
28
|
+
*/
|
|
29
|
+
export declare const SpawnPromptSchema: z.ZodObject<{
|
|
30
|
+
wu_id: z.ZodString;
|
|
31
|
+
version: z.ZodDefault<z.ZodString>;
|
|
32
|
+
checksum: z.ZodString;
|
|
33
|
+
content: z.ZodString;
|
|
34
|
+
sentinel: z.ZodLiteral<"LUMENFLOW_SPAWN_COMPLETE">;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
/**
|
|
37
|
+
* Type for a valid spawn prompt
|
|
38
|
+
*/
|
|
39
|
+
export type SpawnPrompt = z.infer<typeof SpawnPromptSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* Compute SHA256 checksum of content
|
|
42
|
+
*
|
|
43
|
+
* @param content - Content to checksum
|
|
44
|
+
* @returns Hex-encoded SHA256 hash
|
|
45
|
+
*/
|
|
46
|
+
export declare function computeChecksum(content: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* Create a spawn prompt envelope with computed checksum
|
|
49
|
+
*
|
|
50
|
+
* @param wuId - WU ID for the spawn prompt
|
|
51
|
+
* @param content - The spawn prompt content
|
|
52
|
+
* @returns Valid spawn prompt envelope
|
|
53
|
+
*/
|
|
54
|
+
export declare function createSpawnPrompt(wuId: string, content: string): SpawnPrompt;
|
|
55
|
+
/**
|
|
56
|
+
* Spawn prompt validation result type
|
|
57
|
+
*/
|
|
58
|
+
export interface SpawnPromptValidationResult {
|
|
59
|
+
valid: boolean;
|
|
60
|
+
error?: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate a spawn prompt, checking both schema and checksum
|
|
64
|
+
*
|
|
65
|
+
* @param data - Data to validate
|
|
66
|
+
* @returns Validation result with error message if invalid
|
|
67
|
+
*/
|
|
68
|
+
export declare function validateSpawnPrompt(data: unknown): SpawnPromptValidationResult;
|
|
69
|
+
/**
|
|
70
|
+
* Parse result type
|
|
71
|
+
*/
|
|
72
|
+
export interface ParseResult {
|
|
73
|
+
success: boolean;
|
|
74
|
+
data?: SpawnPrompt;
|
|
75
|
+
error?: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse a YAML spawn prompt string
|
|
79
|
+
*
|
|
80
|
+
* This function handles:
|
|
81
|
+
* 1. YAML parsing (head/tail truncation breaks parse)
|
|
82
|
+
* 2. Schema validation
|
|
83
|
+
* 3. Checksum validation (detects content corruption)
|
|
84
|
+
*
|
|
85
|
+
* @param yamlString - YAML string to parse
|
|
86
|
+
* @returns Parse result with data or error
|
|
87
|
+
*/
|
|
88
|
+
export declare function parseSpawnPrompt(yamlString: string): ParseResult;
|
|
89
|
+
/**
|
|
90
|
+
* Serialize a spawn prompt to YAML format
|
|
91
|
+
*
|
|
92
|
+
* @param prompt - Spawn prompt to serialize
|
|
93
|
+
* @returns YAML string
|
|
94
|
+
*/
|
|
95
|
+
export declare function serializeSpawnPrompt(prompt: SpawnPrompt): string;
|
|
96
|
+
/**
|
|
97
|
+
* Quick validation check for truncated output
|
|
98
|
+
*
|
|
99
|
+
* This is a fast check that can be used before full parsing:
|
|
100
|
+
* - Checks if sentinel appears at the end of the string
|
|
101
|
+
* - Does not validate checksum (use parseSpawnPrompt for full validation)
|
|
102
|
+
*
|
|
103
|
+
* @param yamlString - String to check
|
|
104
|
+
* @returns true if sentinel found near end, false otherwise
|
|
105
|
+
*/
|
|
106
|
+
export declare function checkSentinel(yamlString: string): boolean;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn Prompt Schema (WU-1142)
|
|
3
|
+
*
|
|
4
|
+
* Zod schema for truncation-resistant spawn prompts.
|
|
5
|
+
* Implements a YAML envelope format with:
|
|
6
|
+
* - SHA256 checksums for integrity validation
|
|
7
|
+
* - Sentinel values for truncation detection
|
|
8
|
+
* - Schema validation for agent consumers
|
|
9
|
+
*
|
|
10
|
+
* Three-Layer Defense:
|
|
11
|
+
* 1. YAML Envelope - head/tail truncation breaks YAML parse
|
|
12
|
+
* 2. Checksum - validates content integrity
|
|
13
|
+
* 3. Sentinel - confirms complete transmission
|
|
14
|
+
*
|
|
15
|
+
* @module spawn-prompt-schema
|
|
16
|
+
*/
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import { createHash } from 'node:crypto';
|
|
19
|
+
import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
|
|
20
|
+
/**
|
|
21
|
+
* Sentinel value that marks complete spawn prompt transmission
|
|
22
|
+
*/
|
|
23
|
+
export const SPAWN_SENTINEL = 'LUMENFLOW_SPAWN_COMPLETE';
|
|
24
|
+
/**
|
|
25
|
+
* Current schema version
|
|
26
|
+
*/
|
|
27
|
+
export const SPAWN_PROMPT_VERSION = '1.0.0';
|
|
28
|
+
/**
|
|
29
|
+
* Zod schema for spawn prompt envelope
|
|
30
|
+
*/
|
|
31
|
+
export const SpawnPromptSchema = z.object({
|
|
32
|
+
/** WU ID for this spawn prompt */
|
|
33
|
+
wu_id: z.string().regex(/^WU-\d+$/i, 'Invalid WU ID format'),
|
|
34
|
+
/** Schema version */
|
|
35
|
+
version: z.string().default(SPAWN_PROMPT_VERSION),
|
|
36
|
+
/** SHA256 checksum of content field */
|
|
37
|
+
checksum: z.string().length(64, 'Checksum must be 64 hex characters'),
|
|
38
|
+
/** The actual spawn prompt content */
|
|
39
|
+
content: z.string().min(1, 'Content cannot be empty'),
|
|
40
|
+
/** Sentinel value confirming complete transmission */
|
|
41
|
+
sentinel: z.literal(SPAWN_SENTINEL),
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* Compute SHA256 checksum of content
|
|
45
|
+
*
|
|
46
|
+
* @param content - Content to checksum
|
|
47
|
+
* @returns Hex-encoded SHA256 hash
|
|
48
|
+
*/
|
|
49
|
+
export function computeChecksum(content) {
|
|
50
|
+
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a spawn prompt envelope with computed checksum
|
|
54
|
+
*
|
|
55
|
+
* @param wuId - WU ID for the spawn prompt
|
|
56
|
+
* @param content - The spawn prompt content
|
|
57
|
+
* @returns Valid spawn prompt envelope
|
|
58
|
+
*/
|
|
59
|
+
export function createSpawnPrompt(wuId, content) {
|
|
60
|
+
const checksum = computeChecksum(content);
|
|
61
|
+
return {
|
|
62
|
+
wu_id: wuId,
|
|
63
|
+
version: SPAWN_PROMPT_VERSION,
|
|
64
|
+
checksum,
|
|
65
|
+
content,
|
|
66
|
+
sentinel: SPAWN_SENTINEL,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Validate a spawn prompt, checking both schema and checksum
|
|
71
|
+
*
|
|
72
|
+
* @param data - Data to validate
|
|
73
|
+
* @returns Validation result with error message if invalid
|
|
74
|
+
*/
|
|
75
|
+
export function validateSpawnPrompt(data) {
|
|
76
|
+
// First, validate schema
|
|
77
|
+
const schemaResult = SpawnPromptSchema.safeParse(data);
|
|
78
|
+
if (!schemaResult.success) {
|
|
79
|
+
// Zod v4: use schemaResult.error.issues instead of .errors
|
|
80
|
+
const errorMessages = schemaResult.error.issues
|
|
81
|
+
.map((e) => `${e.path.join('.')}: ${e.message}`)
|
|
82
|
+
.join('; ');
|
|
83
|
+
return {
|
|
84
|
+
valid: false,
|
|
85
|
+
error: `Schema validation failed: ${errorMessages}`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Then, validate checksum matches content
|
|
89
|
+
const prompt = schemaResult.data;
|
|
90
|
+
const computedChecksum = computeChecksum(prompt.content);
|
|
91
|
+
if (computedChecksum !== prompt.checksum) {
|
|
92
|
+
return {
|
|
93
|
+
valid: false,
|
|
94
|
+
error: `Checksum mismatch: expected ${prompt.checksum}, computed ${computedChecksum}. Content may be corrupted or truncated.`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { valid: true };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse a YAML spawn prompt string
|
|
101
|
+
*
|
|
102
|
+
* This function handles:
|
|
103
|
+
* 1. YAML parsing (head/tail truncation breaks parse)
|
|
104
|
+
* 2. Schema validation
|
|
105
|
+
* 3. Checksum validation (detects content corruption)
|
|
106
|
+
*
|
|
107
|
+
* @param yamlString - YAML string to parse
|
|
108
|
+
* @returns Parse result with data or error
|
|
109
|
+
*/
|
|
110
|
+
export function parseSpawnPrompt(yamlString) {
|
|
111
|
+
// Step 1: Parse YAML
|
|
112
|
+
let parsed;
|
|
113
|
+
try {
|
|
114
|
+
parsed = parseYaml(yamlString);
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
return {
|
|
118
|
+
success: false,
|
|
119
|
+
error: `YAML parse failed: ${e instanceof Error ? e.message : 'Unknown error'}. Output may be truncated.`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Step 2: Validate schema and checksum
|
|
123
|
+
const validationResult = validateSpawnPrompt(parsed);
|
|
124
|
+
if (!validationResult.valid) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
error: validationResult.error,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// Safe cast since validation passed
|
|
131
|
+
return {
|
|
132
|
+
success: true,
|
|
133
|
+
data: parsed,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Serialize a spawn prompt to YAML format
|
|
138
|
+
*
|
|
139
|
+
* @param prompt - Spawn prompt to serialize
|
|
140
|
+
* @returns YAML string
|
|
141
|
+
*/
|
|
142
|
+
export function serializeSpawnPrompt(prompt) {
|
|
143
|
+
return stringifyYaml(prompt, {
|
|
144
|
+
lineWidth: 0, // Don't wrap lines
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Quick validation check for truncated output
|
|
149
|
+
*
|
|
150
|
+
* This is a fast check that can be used before full parsing:
|
|
151
|
+
* - Checks if sentinel appears at the end of the string
|
|
152
|
+
* - Does not validate checksum (use parseSpawnPrompt for full validation)
|
|
153
|
+
*
|
|
154
|
+
* @param yamlString - String to check
|
|
155
|
+
* @returns true if sentinel found near end, false otherwise
|
|
156
|
+
*/
|
|
157
|
+
export function checkSentinel(yamlString) {
|
|
158
|
+
const trimmed = yamlString.trim();
|
|
159
|
+
return trimmed.endsWith(SPAWN_SENTINEL) || trimmed.includes(`sentinel: ${SPAWN_SENTINEL}`);
|
|
160
|
+
}
|