@lumenflow/cli 3.9.2 → 3.9.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/cli",
3
- "version": "3.9.2",
3
+ "version": "3.9.6",
4
4
  "description": "Command-line interface for LumenFlow workflow framework",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -180,13 +180,13 @@
180
180
  "xstate": "^5.28.0",
181
181
  "yaml": "^2.8.2",
182
182
  "zod": "^4.3.6",
183
- "@lumenflow/agent": "3.9.2",
184
- "@lumenflow/control-plane-sdk": "3.9.2",
185
- "@lumenflow/kernel": "3.9.2",
186
- "@lumenflow/initiatives": "3.9.2",
187
- "@lumenflow/memory": "3.9.2",
188
- "@lumenflow/core": "3.9.2",
189
- "@lumenflow/metrics": "3.9.2"
183
+ "@lumenflow/agent": "3.9.6",
184
+ "@lumenflow/core": "3.9.6",
185
+ "@lumenflow/control-plane-sdk": "3.9.6",
186
+ "@lumenflow/initiatives": "3.9.6",
187
+ "@lumenflow/memory": "3.9.6",
188
+ "@lumenflow/kernel": "3.9.6",
189
+ "@lumenflow/metrics": "3.9.6"
190
190
  },
191
191
  "devDependencies": {
192
192
  "@vitest/coverage-v8": "^4.0.18",
@@ -1,4 +1,4 @@
1
1
 
2
- > @lumenflow/packs-sidekick@3.9.1 build /home/tom/source/hellmai/lumenflow-dev/packages/@lumenflow/packs/sidekick
2
+ > @lumenflow/packs-sidekick@3.9.6 build /home/runner/work/lumenflow-dev/lumenflow-dev/packages/@lumenflow/packs/sidekick
3
3
  > tsc
4
4
 
@@ -182,9 +182,11 @@ const TOOL_INPUT_SCHEMAS: Record<SidekickToolName, Record<string, unknown>> = {
182
182
  'channel:send': {
183
183
  type: 'object',
184
184
  properties: {
185
+ provider: { type: 'string' },
185
186
  channel: { type: 'string' },
186
187
  content: { type: 'string', minLength: 1 },
187
188
  sender: { type: 'string' },
189
+ metadata: { type: 'object', additionalProperties: true },
188
190
  dry_run: { type: 'boolean' },
189
191
  },
190
192
  required: ['content'],
@@ -193,9 +195,12 @@ const TOOL_INPUT_SCHEMAS: Record<SidekickToolName, Record<string, unknown>> = {
193
195
  'channel:receive': {
194
196
  type: 'object',
195
197
  properties: {
198
+ provider: { type: 'string' },
196
199
  channel: { type: 'string' },
200
+ cursor: { type: 'string' },
197
201
  limit: { type: 'integer', minimum: 1 },
198
202
  since: { type: 'string' },
203
+ metadata: { type: 'object', additionalProperties: true },
199
204
  },
200
205
  additionalProperties: false,
201
206
  },
@@ -235,6 +235,8 @@ tools:
235
235
  input_schema:
236
236
  type: object
237
237
  properties:
238
+ provider:
239
+ type: string
238
240
  channel:
239
241
  type: string
240
242
  content:
@@ -242,6 +244,9 @@ tools:
242
244
  minLength: 1
243
245
  sender:
244
246
  type: string
247
+ metadata:
248
+ type: object
249
+ additionalProperties: true
245
250
  dry_run:
246
251
  type: boolean
247
252
  required: [content]
@@ -259,13 +264,20 @@ tools:
259
264
  input_schema:
260
265
  type: object
261
266
  properties:
267
+ provider:
268
+ type: string
262
269
  channel:
263
270
  type: string
271
+ cursor:
272
+ type: string
264
273
  limit:
265
274
  type: integer
266
275
  minimum: 1
267
276
  since:
268
277
  type: string
278
+ metadata:
279
+ type: object
280
+ additionalProperties: true
269
281
  additionalProperties: false
270
282
  output_schema:
271
283
  type: object
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/packs-sidekick",
3
- "version": "3.9.2",
3
+ "version": "3.9.6",
4
4
  "description": "Sidekick personal assistant pack for LumenFlow — 16 tools for task management, typed memory, channels, routines, and audit",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -27,6 +27,7 @@
27
27
  "./storage": "./dist/tool-impl/storage.js",
28
28
  "./tools": "./dist/tools/index.js",
29
29
  "./tool-impl": "./dist/tool-impl/index.js",
30
+ "./tool-impl/channel-transports": "./dist/tool-impl/channel-transports.js",
30
31
  "./tool-impl/channel-tools": "./dist/tool-impl/channel-tools.js",
31
32
  "./tool-impl/memory-tools": "./dist/tool-impl/memory-tools.js",
32
33
  "./tool-impl/routine-tools": "./dist/tool-impl/routine-tools.js",
@@ -1,6 +1,7 @@
1
1
  // Copyright (c) 2026 Hellmai Ltd
2
2
  // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
+ import { getChannelTransport } from './channel-transports.js';
4
5
  import { getStoragePort, type ChannelMessageRecord, type ChannelRecord } from './storage.js';
5
6
  import {
6
7
  asInteger,
@@ -29,6 +30,26 @@ const TOOL_NAMES = {
29
30
  const OUTBOX_CAP = 100;
30
31
  const DEFAULT_CHANNEL_NAME = 'default';
31
32
 
33
+ function asOptionalRecord(value: unknown): Record<string, unknown> | undefined {
34
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
35
+ return undefined;
36
+ }
37
+ return value as Record<string, unknown>;
38
+ }
39
+
40
+ function withTransportMetadata<T extends Record<string, unknown>>(
41
+ data: T,
42
+ metadata: Record<string, unknown> | undefined,
43
+ ): T & Record<string, unknown> {
44
+ if (!metadata) {
45
+ return data;
46
+ }
47
+ return {
48
+ ...data,
49
+ metadata,
50
+ };
51
+ }
52
+
32
53
  // ---------------------------------------------------------------------------
33
54
  // channel:configure
34
55
  // ---------------------------------------------------------------------------
@@ -99,6 +120,77 @@ async function channelConfigureTool(
99
120
  // channel:send
100
121
  // ---------------------------------------------------------------------------
101
122
 
123
+ async function channelSendViaTransport(
124
+ input: Record<string, unknown>,
125
+ context: ToolContextLike | undefined,
126
+ provider: string,
127
+ channel: string,
128
+ content: string,
129
+ ): Promise<ToolOutput> {
130
+ const workspaceId = asNonEmptyString(context?.workspace_id);
131
+ if (!workspaceId) {
132
+ return failure(
133
+ 'WORKSPACE_CONTEXT_REQUIRED',
134
+ 'workspace_id is required when provider is specified.',
135
+ );
136
+ }
137
+
138
+ const transport = getChannelTransport(provider);
139
+ if (!transport) {
140
+ return failure(
141
+ 'INTEGRATION_PROVIDER_NOT_REGISTERED',
142
+ `No channel transport registered for provider: ${provider}`,
143
+ );
144
+ }
145
+
146
+ if (isDryRun(input)) {
147
+ return success({
148
+ dry_run: true,
149
+ provider,
150
+ capability: 'send',
151
+ channel,
152
+ content,
153
+ });
154
+ }
155
+
156
+ const transportResult = await transport.send({
157
+ workspaceId,
158
+ provider,
159
+ channel,
160
+ content,
161
+ metadata: asOptionalRecord(input.metadata),
162
+ });
163
+
164
+ const outputData = withTransportMetadata(
165
+ {
166
+ provider,
167
+ capability: 'send',
168
+ channel,
169
+ ...(transportResult.externalMessageId !== undefined
170
+ ? { externalMessageId: transportResult.externalMessageId }
171
+ : {}),
172
+ ...(transportResult.failureClass ? { failureClass: transportResult.failureClass } : {}),
173
+ ...(transportResult.retryAfterSeconds !== undefined
174
+ ? { retryAfterSeconds: transportResult.retryAfterSeconds }
175
+ : {}),
176
+ },
177
+ transportResult.metadata,
178
+ );
179
+
180
+ if (!transportResult.success) {
181
+ return {
182
+ success: false,
183
+ error: {
184
+ code: 'INTEGRATION_PROVIDER_SEND_FAILED',
185
+ message: transportResult.error ?? `Provider send failed for provider: ${provider}`,
186
+ },
187
+ data: outputData,
188
+ };
189
+ }
190
+
191
+ return success(outputData);
192
+ }
193
+
102
194
  async function channelSendTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
103
195
  const parsed = toRecord(input);
104
196
  const content = asNonEmptyString(parsed.content);
@@ -108,6 +200,12 @@ async function channelSendTool(input: unknown, context?: ToolContextLike): Promi
108
200
  }
109
201
 
110
202
  const channelName = asNonEmptyString(parsed.channel) ?? DEFAULT_CHANNEL_NAME;
203
+ const provider = asNonEmptyString(parsed.provider);
204
+
205
+ if (provider) {
206
+ return channelSendViaTransport(parsed, context, provider, channelName, content);
207
+ }
208
+
111
209
  const sender = asNonEmptyString(parsed.sender) ?? 'assistant';
112
210
  const now = nowIso();
113
211
 
@@ -175,10 +273,86 @@ async function channelSendTool(input: unknown, context?: ToolContextLike): Promi
175
273
  // channel:receive
176
274
  // ---------------------------------------------------------------------------
177
275
 
276
+ async function channelReceiveViaTransport(
277
+ input: Record<string, unknown>,
278
+ context: ToolContextLike | undefined,
279
+ provider: string,
280
+ channel: string,
281
+ limit: number | null,
282
+ ): Promise<ToolOutput> {
283
+ const workspaceId = asNonEmptyString(context?.workspace_id);
284
+ if (!workspaceId) {
285
+ return failure(
286
+ 'WORKSPACE_CONTEXT_REQUIRED',
287
+ 'workspace_id is required when provider is specified.',
288
+ );
289
+ }
290
+
291
+ const transport = getChannelTransport(provider);
292
+ if (!transport) {
293
+ return failure(
294
+ 'INTEGRATION_PROVIDER_NOT_REGISTERED',
295
+ `No channel transport registered for provider: ${provider}`,
296
+ );
297
+ }
298
+
299
+ const cursor = asNonEmptyString(input.cursor) ?? undefined;
300
+
301
+ const transportResult = await transport.receive({
302
+ workspaceId,
303
+ provider,
304
+ channel,
305
+ cursor,
306
+ limit: limit && limit > 0 ? limit : undefined,
307
+ metadata: asOptionalRecord(input.metadata),
308
+ });
309
+
310
+ const outputData = withTransportMetadata(
311
+ {
312
+ provider,
313
+ capability: 'read',
314
+ channel,
315
+ ...(transportResult.records !== undefined ? { records: transportResult.records } : {}),
316
+ ...(transportResult.nextCursor !== undefined
317
+ ? { nextCursor: transportResult.nextCursor }
318
+ : {}),
319
+ ...(transportResult.failureClass ? { failureClass: transportResult.failureClass } : {}),
320
+ ...(transportResult.retryAfterSeconds !== undefined
321
+ ? { retryAfterSeconds: transportResult.retryAfterSeconds }
322
+ : {}),
323
+ },
324
+ transportResult.metadata,
325
+ );
326
+
327
+ if (!transportResult.success) {
328
+ return {
329
+ success: false,
330
+ error: {
331
+ code: 'INTEGRATION_PROVIDER_RECEIVE_FAILED',
332
+ message: transportResult.error ?? `Provider receive failed for provider: ${provider}`,
333
+ },
334
+ data: outputData,
335
+ };
336
+ }
337
+
338
+ return success(outputData);
339
+ }
340
+
178
341
  async function channelReceiveTool(input: unknown, _context?: ToolContextLike): Promise<ToolOutput> {
179
342
  const parsed = toRecord(input);
180
343
  const channelName = asNonEmptyString(parsed.channel);
181
344
  const limit = asInteger(parsed.limit);
345
+ const provider = asNonEmptyString(parsed.provider);
346
+
347
+ if (provider) {
348
+ return channelReceiveViaTransport(
349
+ parsed,
350
+ _context,
351
+ provider,
352
+ channelName ?? DEFAULT_CHANNEL_NAME,
353
+ limit,
354
+ );
355
+ }
182
356
 
183
357
  const storage = getStoragePort();
184
358
  const messages = await storage.readStore('messages');
@@ -0,0 +1,75 @@
1
+ // Copyright (c) 2026 Hellmai Ltd
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ import { getSidekickRuntimeContext } from './runtime-context.js';
5
+
6
+ export interface ChannelTransport {
7
+ provider: string;
8
+ send(req: {
9
+ workspaceId: string;
10
+ provider: string;
11
+ channel: string;
12
+ content: string;
13
+ metadata?: Record<string, unknown>;
14
+ }): Promise<{
15
+ success: boolean;
16
+ error?: string;
17
+ externalMessageId?: string;
18
+ failureClass?: 'retryable' | 'terminal';
19
+ retryAfterSeconds?: number;
20
+ metadata?: Record<string, unknown>;
21
+ }>;
22
+ receive(req: {
23
+ workspaceId: string;
24
+ provider: string;
25
+ channel: string;
26
+ cursor?: string;
27
+ limit?: number;
28
+ metadata?: Record<string, unknown>;
29
+ }): Promise<{
30
+ success: boolean;
31
+ error?: string;
32
+ records?: unknown[];
33
+ nextCursor?: string;
34
+ failureClass?: 'retryable' | 'terminal';
35
+ retryAfterSeconds?: number;
36
+ metadata?: Record<string, unknown>;
37
+ }>;
38
+ }
39
+
40
+ function normalizeProvider(provider: string): string {
41
+ return provider.trim().toLowerCase();
42
+ }
43
+
44
+ function getRegistry(): Map<string, ChannelTransport> | undefined {
45
+ return getSidekickRuntimeContext()?.channelTransports;
46
+ }
47
+
48
+ export function registerChannelTransport(transport: ChannelTransport): void {
49
+ const provider = normalizeProvider(transport.provider);
50
+ if (provider.length === 0) {
51
+ throw new Error('channel transport provider must be a non-empty string.');
52
+ }
53
+ const registry = getRegistry();
54
+ if (!registry) {
55
+ throw new Error('channel transport registry is unavailable outside sidekick runtime context.');
56
+ }
57
+ registry.set(provider, transport);
58
+ }
59
+
60
+ export function getChannelTransport(provider: string): ChannelTransport | undefined {
61
+ const normalized = normalizeProvider(provider);
62
+ if (normalized.length === 0) {
63
+ return undefined;
64
+ }
65
+ const registry = getRegistry();
66
+ return registry?.get(normalized);
67
+ }
68
+
69
+ export function clearChannelTransports(): void {
70
+ const registry = getRegistry();
71
+ if (!registry) {
72
+ return;
73
+ }
74
+ registry.clear();
75
+ }
@@ -20,3 +20,10 @@ export {
20
20
  runWithStoragePort,
21
21
  setDefaultStoragePort,
22
22
  } from './storage.js';
23
+
24
+ export {
25
+ type ChannelTransport,
26
+ clearChannelTransports,
27
+ getChannelTransport,
28
+ registerChannelTransport,
29
+ } from './channel-transports.js';
@@ -0,0 +1,24 @@
1
+ // Copyright (c) 2026 Hellmai Ltd
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ import { AsyncLocalStorage } from 'node:async_hooks';
5
+ import type { ChannelTransport } from './channel-transports.js';
6
+ import type { StoragePort } from './storage.js';
7
+
8
+ export interface SidekickRuntimeContext {
9
+ storagePort: StoragePort;
10
+ channelTransports: Map<string, ChannelTransport>;
11
+ }
12
+
13
+ const runtimeContext = new AsyncLocalStorage<SidekickRuntimeContext>();
14
+
15
+ export function getSidekickRuntimeContext(): SidekickRuntimeContext | undefined {
16
+ return runtimeContext.getStore();
17
+ }
18
+
19
+ export async function runWithSidekickRuntimeContext<T>(
20
+ context: SidekickRuntimeContext,
21
+ fn: () => Promise<T>,
22
+ ): Promise<T> {
23
+ return runtimeContext.run(context, fn);
24
+ }
@@ -7,6 +7,7 @@ import type { AuditEvent } from './storage.js';
7
7
  export interface ToolContextLike {
8
8
  tool_name?: string;
9
9
  receipt_id?: string;
10
+ workspace_id?: string;
10
11
  }
11
12
 
12
13
  export interface ToolOutput {
@@ -1,10 +1,10 @@
1
1
  // Copyright (c) 2026 Hellmai Ltd
2
2
  // SPDX-License-Identifier: AGPL-3.0-only
3
3
 
4
- import { AsyncLocalStorage } from 'node:async_hooks';
5
4
  import { randomBytes } from 'node:crypto';
6
5
  import { appendFile, mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
7
6
  import path from 'node:path';
7
+ import { getSidekickRuntimeContext, runWithSidekickRuntimeContext } from './runtime-context.js';
8
8
 
9
9
  // ---------------------------------------------------------------------------
10
10
  // Constants
@@ -299,7 +299,6 @@ export class FsStoragePort implements StoragePort {
299
299
  // Injection helpers (AsyncLocalStorage-based)
300
300
  // ---------------------------------------------------------------------------
301
301
 
302
- const storageContext = new AsyncLocalStorage<StoragePort>();
303
302
  let defaultStoragePort: StoragePort = new FsStoragePort();
304
303
 
305
304
  export function setDefaultStoragePort(port: StoragePort): void {
@@ -307,9 +306,16 @@ export function setDefaultStoragePort(port: StoragePort): void {
307
306
  }
308
307
 
309
308
  export function getStoragePort(): StoragePort {
310
- return storageContext.getStore() ?? defaultStoragePort;
309
+ return getSidekickRuntimeContext()?.storagePort ?? defaultStoragePort;
311
310
  }
312
311
 
313
312
  export async function runWithStoragePort<T>(port: StoragePort, fn: () => Promise<T>): Promise<T> {
314
- return storageContext.run(port, fn);
313
+ const existingContext = getSidekickRuntimeContext();
314
+ return runWithSidekickRuntimeContext(
315
+ {
316
+ storagePort: port,
317
+ channelTransports: existingContext?.channelTransports ?? new Map(),
318
+ },
319
+ fn,
320
+ );
315
321
  }
@@ -1,4 +1,4 @@
1
1
 
2
- > @lumenflow/packs-software-delivery@3.9.1 build /home/tom/source/hellmai/lumenflow-dev/packages/@lumenflow/packs/software-delivery
2
+ > @lumenflow/packs-software-delivery@3.9.6 build /home/runner/work/lumenflow-dev/lumenflow-dev/packages/@lumenflow/packs/software-delivery
3
3
  > tsc
4
4
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumenflow/packs-software-delivery",
3
- "version": "3.9.2",
3
+ "version": "3.9.6",
4
4
  "description": "Software delivery pack for LumenFlow — work units, gates, lanes, initiatives, and agent coordination",
5
5
  "keywords": [
6
6
  "lumenflow",
@@ -230,30 +230,30 @@ For the full worktree lifecycle (parallel execution, bootstrap, isolation guaran
230
230
 
231
231
  > **Complete CLI reference (100+ commands):** See [quick-ref-commands.md](docs/04-operations/_frameworks/lumenflow/agent/onboarding/quick-ref-commands.md). Always run `<command> --help` for the authoritative option list.
232
232
 
233
- | Command | Description |
234
- | ------------------------- | ------------------------------------------------------ |
235
- | `pnpm wu:create` | Create new WU spec |
236
- | `pnpm wu:claim` | Claim WU, update canonical state, create worktree |
237
- | `pnpm wu:prep` | Run gates in worktree, prep for wu:done |
238
- | `pnpm wu:done` | Complete WU (merge, stamp, cleanup) |
239
- | `pnpm wu:status` | Show WU status, location, and valid commands |
240
- | `pnpm wu:recover` | Analyze and fix WU state inconsistencies |
241
- | `pnpm wu:block` | Block WU (transitions to blocked, frees lane) |
242
- | `pnpm wu:unblock` | Unblock WU (transitions to in_progress) |
243
- | `pnpm wu:release` | Release orphaned WU (in_progress to ready for reclaim) |
244
- | `pnpm wu:brief` | Generate handoff prompt + record evidence |
245
- | `pnpm wu:delegate` | Generate prompt + record delegation lineage |
246
- | `pnpm wu:escalate` | Show or resolve WU escalation status |
247
- | `pnpm wu:delete` | Delete WU spec and cleanup |
248
- | `pnpm gates` | Run quality gates (`--docs-only` for docs WUs) |
249
- | `pnpm lumenflow:commands` | List all public commands (primary + alias + legacy) |
250
- | `pnpm docs:generate` | Regenerate CLI/config reference docs from source |
251
- | `pnpm docs:validate` | Verify generated docs are up-to-date |
252
- | `pnpm lane:status` | Show lane lifecycle status + next step |
253
- | `pnpm lane:setup` | Create/update draft lane artifacts |
254
- | `pnpm lane:validate` | Validate lane artifacts before lock |
255
- | `pnpm lane:lock` | Lock lane lifecycle for delivery WUs |
256
- | `pnpm mem:checkpoint` | Save memory checkpoint |
233
+ | Command | Description |
234
+ | ------------------------- | --------------------------------------------------------- |
235
+ | `pnpm wu:create` | Create new WU spec |
236
+ | `pnpm wu:claim` | Claim WU, update canonical state, create worktree |
237
+ | `pnpm wu:prep` | Run gates in worktree, prep for wu:done |
238
+ | `pnpm wu:done` | Complete WU (merge, stamp, cleanup) |
239
+ | `pnpm wu:status` | Show WU status, location, and valid commands |
240
+ | `pnpm wu:recover` | Analyze and fix WU state inconsistencies |
241
+ | `pnpm wu:block` | Block WU (transitions to blocked, frees lane) |
242
+ | `pnpm wu:unblock` | Unblock WU (transitions to in_progress) |
243
+ | `pnpm wu:release` | Release orphaned WU (in_progress to ready for reclaim) |
244
+ | `pnpm wu:brief` | Generate handoff prompt + record evidence |
245
+ | `pnpm wu:delegate` | Generate prompt + record lineage + brief hash attestation |
246
+ | `pnpm wu:escalate` | Show or resolve WU escalation status |
247
+ | `pnpm wu:delete` | Delete WU spec and cleanup |
248
+ | `pnpm gates` | Run quality gates (`--docs-only` for docs WUs) |
249
+ | `pnpm lumenflow:commands` | List all public commands (primary + alias + legacy) |
250
+ | `pnpm docs:generate` | Regenerate CLI/config reference docs from source |
251
+ | `pnpm docs:validate` | Verify generated docs are up-to-date |
252
+ | `pnpm lane:status` | Show lane lifecycle status + next step |
253
+ | `pnpm lane:setup` | Create/update draft lane artifacts |
254
+ | `pnpm lane:validate` | Validate lane artifacts before lock |
255
+ | `pnpm lane:lock` | Lock lane lifecycle for delivery WUs |
256
+ | `pnpm mem:checkpoint` | Save memory checkpoint |
257
257
 
258
258
  Commands include **context-aware validation** that checks location, WU status, and git state. When validation fails, commands provide copy-paste ready fix commands. Configure in `workspace.yaml` under `software_delivery.experimental.context_validation`.
259
259
  The Starlight CLI reference page is intentionally curated to primary commands; use `pnpm lumenflow:commands` for complete discovery.
@@ -106,8 +106,17 @@ This is useful when:
106
106
 
107
107
  `wu:delegate` records **delegation intent**: that a brief was generated for a target WU with explicit parent lineage.
108
108
 
109
+ It also stores **brief attestation metadata** (SHA-256 prompt hash, client, timestamp, prompt length) for the generated handoff payload.
110
+
109
111
  It does **not** by itself prove pickup or execution. Pickup/execution confirmation comes from lifecycle evidence (claim/completion events, checkpoints, signals, and final `wu:done`).
110
112
 
113
+ `wu:done` now enforces, for initiative-governed and explicitly delegated WUs:
114
+
115
+ - Delegation lineage exists
116
+ - Claim-time pickup evidence exists
117
+ - Delegation brief attestation exists
118
+ - Attested prompt hash matches recorded `wu:brief` evidence for the target WU
119
+
111
120
  ---
112
121
 
113
122
  ## 2c) Self-Implementation Evidence-Only Flow (WU-2222)
@@ -133,7 +133,7 @@ files, no manual git, no WU ceremony. Only actual **code changes** need WUs.
133
133
  | `pnpm wu:brief --id WU-XXX --client <client>` | Generate handoff prompt + record evidence (claimed workspace/branch) |
134
134
  | `pnpm wu:brief --id WU-XXX --evidence-only` | Record wu:brief evidence only (self-implementation, no prompt output) |
135
135
  | `pnpm wu:brief --id WU-XXX --no-context` | Generate prompt without memory context injection |
136
- | `pnpm wu:delegate --id WU-XXX --parent-wu <P>` | Generate prompt and record delegation lineage |
136
+ | `pnpm wu:delegate --id WU-XXX --parent-wu <P>` | Generate prompt, record lineage, and store brief hash attestation |
137
137
  | `pnpm wu:sandbox --id WU-XXX -- <cmd>` | Run command through hardened WU sandbox backend |
138
138
 
139
139
  ### WU Maintenance
@@ -10,20 +10,20 @@ This document covers the complete release process for LumenFlow, including versi
10
10
 
11
11
  LumenFlow has several components that need to stay in sync:
12
12
 
13
- | Component | Location | Deployment |
14
- | -------------- | --------------------------- | ----------------------------------- |
15
- | npm packages | `packages/@lumenflow/*` | Auto via GitHub Actions on tag push |
16
- | Starlight docs | `apps/docs/` | Manual via Vercel CLI |
17
- | Pack registry | `apps/web/` + pack tarballs | Manual deploy + curl publish |
13
+ | Component | Location | Deployment |
14
+ | -------------- | --------------------------- | --------------------------------------------------- |
15
+ | npm packages | `packages/@lumenflow/*` | Auto via GitHub Actions on tag push (`publish.yml`) |
16
+ | Starlight docs | `apps/docs/` | Separate GitHub Actions workflow (`docs.yml`) |
17
+ | Pack registry | `apps/web/` + pack tarballs | Manual deploy + curl publish |
18
18
 
19
19
  ---
20
20
 
21
21
  ## Release Command
22
22
 
23
- The `pnpm release` command automates the entire release process:
23
+ The `pnpm release` command automates version bump + tagging, and optionally npm publish:
24
24
 
25
25
  ```bash
26
- pnpm release --release-version 1.3.0
26
+ pnpm release --release-version 1.3.0 --skip-publish
27
27
  ```
28
28
 
29
29
  ### What It Does
@@ -34,7 +34,7 @@ pnpm release --release-version 1.3.0
34
34
  4. Bumps all `@lumenflow/*` package versions using micro-worktree isolation
35
35
  5. Builds all packages
36
36
  6. Creates and pushes git tag `vX.Y.Z`
37
- 7. Publishes to npm
37
+ 7. Optionally publishes to npm (unless `--skip-publish`)
38
38
 
39
39
  ### Options
40
40
 
@@ -50,19 +50,16 @@ pnpm release --release-version 1.3.0
50
50
  ### Examples
51
51
 
52
52
  ```bash
53
- # Full release
54
- pnpm release --release-version 1.3.0
55
-
56
- # Preview what would happen
57
- pnpm release --release-version 1.3.0 --dry-run
58
-
59
53
  # Version bump and tag only (CI will publish)
60
54
  pnpm release --release-version 1.3.0 --skip-publish
55
+
56
+ # Preview what would happen
57
+ pnpm release --release-version 1.3.0 --skip-publish --dry-run
61
58
  ```
62
59
 
63
60
  ### Authentication
64
61
 
65
- For npm publish, set one of these environment variables:
62
+ For direct CLI npm publish (without `--skip-publish`), set one of these environment variables:
66
63
 
67
64
  ```bash
68
65
  export NPM_TOKEN=<your-npm-token>
@@ -179,8 +176,11 @@ The `.github/workflows/publish.yml` workflow:
179
176
 
180
177
  1. Checks out the tagged commit
181
178
  2. Installs dependencies
182
- 3. Builds all packages
183
- 4. Publishes to npm with `pnpm -r publish --access public`
179
+ 3. Builds publishable workspace packages only (`./packages/**`)
180
+ 4. Publishes to npm with `pnpm -r --filter "./packages/**" publish --access public --no-git-checks`
181
+ 5. Creates the GitHub release object (`gh release create ... --generate-notes`)
182
+
183
+ `publish.yml` intentionally does **not** build `apps/docs` or `apps/web`, so docs/tooling dependencies cannot block npm publishing.
184
184
 
185
185
  ### Required Secrets
186
186
 
@@ -226,9 +226,25 @@ The public docs at <https://lumenflow.dev> are built from `apps/docs/`.
226
226
 
227
227
  **Automatic Generation:** CLI and config reference docs are automatically generated from code. See [Automatic Docs Generation](./docs-generation.md) for details on the single-source-of-truth pattern.
228
228
 
229
+ ### Build Workflow
230
+
231
+ Starlight docs build in a separate workflow: `.github/workflows/docs.yml`.
232
+
233
+ Trigger:
234
+
235
+ - Tag push (`v*`)
236
+ - Manual run (`workflow_dispatch`)
237
+
238
+ The workflow:
239
+
240
+ 1. Installs D2 (`astro-d2` prerequisite)
241
+ 2. Builds docs with `pnpm --filter @lumenflow/docs build`
242
+ 3. Uploads `apps/docs/dist` as a workflow artifact
243
+ 4. Deploys to Vercel when `VERCEL_TOKEN`, `VERCEL_ORG_ID`, and `VERCEL_PROJECT_ID` secrets are configured
244
+
229
245
  ### Deployment
230
246
 
231
- Starlight docs are deployed **manually** via Vercel CLI:
247
+ Production deploy is still manual via Vercel CLI:
232
248
 
233
249
  ```bash
234
250
  cd apps/docs
@@ -368,17 +384,17 @@ The `@lumenflow/docs` package requires `d2` (diagramming tool) which is not avai
368
384
 
369
385
  ### Release Steps (Automated)
370
386
 
371
- Use the release command for the standard workflow:
387
+ Use this as the standard workflow:
372
388
 
373
389
  ```bash
374
390
  # Preview first
375
- pnpm release --release-version 1.3.0 --dry-run
391
+ pnpm release --release-version 1.3.0 --skip-publish --dry-run
376
392
 
377
- # Execute release
378
- pnpm release --release-version 1.3.0
393
+ # Execute (version bump + tag, publish delegated to GitHub Actions)
394
+ pnpm release --release-version 1.3.0 --skip-publish
379
395
  ```
380
396
 
381
- This handles version bump, build, tag, and npm publish automatically.
397
+ This handles version bump and tag locally; GitHub Actions handles npm publish, docs build, and GitHub release creation.
382
398
 
383
399
  ### Release Steps (Manual)
384
400
 
@@ -398,21 +414,22 @@ If you need more control:
398
414
  git push origin v1.3.0
399
415
  ```
400
416
 
401
- 3. **Verify npm publish**
417
+ 3. **Verify npm publish + GitHub release**
402
418
 
403
419
  ```bash
404
420
  gh run list --workflow=publish.yml --limit 1
405
421
  # Wait for completion, then verify
406
422
  npm view @lumenflow/cli version
423
+ gh release view v1.3.0
407
424
  ```
408
425
 
409
- 4. **Create GitHub release**
426
+ 4. **Verify docs workflow** (if docs changed)
410
427
 
411
428
  ```bash
412
- gh release create v1.3.0 --title "v1.3.0 - Title" --notes "Release notes..."
429
+ gh run list --workflow=docs.yml --limit 1
413
430
  ```
414
431
 
415
- 5. **Deploy Starlight docs** (if content changed)
432
+ 5. **Deploy Starlight docs** (if content changed and you are deploying manually)
416
433
 
417
434
  ```bash
418
435
  cd apps/docs && pnpm build && vercel --prod
@@ -428,13 +445,13 @@ If you need more control:
428
445
 
429
446
  ## Keeping Components in Sync
430
447
 
431
- | Change Type | npm | Docs | Packs |
432
- | ---------------- | ------------- | ------ | ------- |
433
- | Bug fix in CLI | Tag + publish | Auto\* | No |
434
- | New CLI command | Tag + publish | Auto\* | No |
435
- | Pack changes | Tag + publish | No | Publish |
436
- | Docs-only update | No | Deploy | No |
437
- | Full release | Tag + publish | Deploy | Publish |
448
+ | Change Type | npm | Docs | Packs |
449
+ | ---------------- | ------------- | ----------------------- | ------- |
450
+ | Bug fix in CLI | Tag + publish | Auto\* | No |
451
+ | New CLI command | Tag + publish | Auto\* | No |
452
+ | Pack changes | Tag + publish | No | Publish |
453
+ | Docs-only update | No | Build workflow + deploy | No |
454
+ | Full release | Tag + publish | Build workflow + deploy | Publish |
438
455
 
439
456
  \* CLI/config reference docs regenerate automatically during `wu:done` when trigger files change. See [Automatic Docs Generation](./docs-generation.md).
440
457