@lumenflow/cli 5.2.3 → 5.2.5
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/approval-request.js +153 -7
- package/dist/approval-request.js.map +1 -1
- package/dist/approval-review.js +223 -8
- package/dist/approval-review.js.map +1 -1
- package/dist/lumenflow-upgrade.js +29 -1
- package/dist/lumenflow-upgrade.js.map +1 -1
- package/dist/wu-spawn-completion.js +26 -1
- package/dist/wu-spawn-completion.js.map +1 -1
- package/dist/wu-spawn-prompt-builders.js +62 -7
- package/dist/wu-spawn-prompt-builders.js.map +1 -1
- package/dist/wu-spawn.js +1 -1
- package/dist/wu-spawn.js.map +1 -1
- package/package.json +11 -11
- package/packs/agent-runtime/package.json +1 -1
- package/packs/sidekick/package.json +1 -1
- package/packs/software-delivery/package.json +1 -1
package/dist/approval-request.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Copyright (c) 2026 Hellmai Ltd
|
|
3
3
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { Command } from 'commander';
|
|
7
|
+
import { CONTEXT_VALIDATION, resolveLocation } from '@lumenflow/core';
|
|
7
8
|
import { createHttpControlPlaneSyncPort, parseWorkspaceControlPlaneConfig, } from '@lumenflow/control-plane-sdk';
|
|
8
9
|
import { CONFIG_FILES } from '@lumenflow/core/wu-constants';
|
|
9
10
|
import { parseYAML } from '@lumenflow/core/wu-yaml';
|
|
@@ -12,6 +13,20 @@ const LOG_PREFIX = '[approval:request]';
|
|
|
12
13
|
const WORKSPACE_PATH = CONFIG_FILES.WORKSPACE_CONFIG;
|
|
13
14
|
const DEFAULT_REQUESTER_TYPE = 'agent';
|
|
14
15
|
const REQUESTER_TYPE_VALUES = new Set(['agent', 'user']);
|
|
16
|
+
const HUMAN_ESCALATION_RESERVED_CONTEXT_KEYS = new Set([
|
|
17
|
+
'triggers',
|
|
18
|
+
'summary',
|
|
19
|
+
'resolver_command',
|
|
20
|
+
'skip_gates_reason',
|
|
21
|
+
'fix_wu',
|
|
22
|
+
]);
|
|
23
|
+
/**
|
|
24
|
+
* WU-2874: Approval type constant for worker-agent human-escalation handoff.
|
|
25
|
+
* Workers emit this instead of prose-dumping wu:escalate/wu:done commands.
|
|
26
|
+
*/
|
|
27
|
+
export const HUMAN_ESCALATION_APPROVAL_TYPE = 'human-escalation';
|
|
28
|
+
/** WU-2874: Local sidecar directory for structured escalation context. */
|
|
29
|
+
export const ESCALATION_SIDECAR_DIR = join('.lumenflow', 'state', 'approvals');
|
|
15
30
|
function asRecord(value) {
|
|
16
31
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
17
32
|
throw new Error('Expected a JSON object payload');
|
|
@@ -27,18 +42,28 @@ function parseJsonRecord(value, label) {
|
|
|
27
42
|
throw new Error(`Invalid ${label}: ${reason}`, { cause: error });
|
|
28
43
|
}
|
|
29
44
|
}
|
|
45
|
+
function parseHumanEscalationExtraContext(value) {
|
|
46
|
+
const parsed = parseJsonRecord(value, '--context');
|
|
47
|
+
return Object.fromEntries(Object.entries(parsed).filter(([key]) => !HUMAN_ESCALATION_RESERVED_CONTEXT_KEYS.has(key)));
|
|
48
|
+
}
|
|
30
49
|
function parseOptions(argv = process.argv) {
|
|
31
50
|
const program = new Command()
|
|
32
51
|
.name('approval-request')
|
|
33
52
|
.description('Request an approval from the configured control-plane')
|
|
34
53
|
.requiredOption('--type <type>', 'Approval type (for example: wu_assignment)')
|
|
35
|
-
.
|
|
54
|
+
.option('--subject <json>', 'JSON object describing the approval subject')
|
|
36
55
|
.option('--context <json>', 'Optional JSON object with additional context')
|
|
37
56
|
.option('--requester-id <id>', 'Requester identifier')
|
|
38
57
|
.option('--requester-type <type>', 'Requester type: agent or user', DEFAULT_REQUESTER_TYPE)
|
|
39
58
|
.option('--expires-at <iso>', 'Optional expiry timestamp (ISO-8601)')
|
|
40
59
|
.option('--workspace-id <id>', 'Override workspace_id from workspace.yaml')
|
|
41
60
|
.option('--json', 'Output JSON response', false)
|
|
61
|
+
// WU-2874: Structured flags for --type human-escalation
|
|
62
|
+
.option('--wu <id>', 'WU id (human-escalation: required)')
|
|
63
|
+
.option('--triggers <csv>', 'Comma-separated escalation_triggers that fired (human-escalation)')
|
|
64
|
+
.option('--summary <text>', 'Summary of worker changes (human-escalation: required)')
|
|
65
|
+
.option('--skip-gates-reason <text>', 'If approval should chain --skip-gates, the reason')
|
|
66
|
+
.option('--fix-wu <id>', 'Paired fix-WU id when skip-gates-reason is set')
|
|
42
67
|
.exitOverride();
|
|
43
68
|
try {
|
|
44
69
|
program.parse(argv);
|
|
@@ -65,7 +90,85 @@ function parseOptions(argv = process.argv) {
|
|
|
65
90
|
expiresAt: parsed.expiresAt?.trim(),
|
|
66
91
|
workspaceId: parsed.workspaceId?.trim(),
|
|
67
92
|
json: parsed.json ?? false,
|
|
93
|
+
wu: parsed.wu?.trim(),
|
|
94
|
+
triggers: parsed.triggers,
|
|
95
|
+
summary: parsed.summary,
|
|
96
|
+
skipGatesReason: parsed.skipGatesReason,
|
|
97
|
+
fixWu: parsed.fixWu?.trim(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* WU-2874: Validate + assemble structured subject/context from human-escalation flags.
|
|
102
|
+
* Keeps the original free-form --subject/--context path untouched for other types.
|
|
103
|
+
*/
|
|
104
|
+
export function buildHumanEscalationPayload(options) {
|
|
105
|
+
if (!options.wu) {
|
|
106
|
+
throw new Error('--wu is required when --type human-escalation');
|
|
107
|
+
}
|
|
108
|
+
if (!options.summary) {
|
|
109
|
+
throw new Error('--summary is required when --type human-escalation');
|
|
110
|
+
}
|
|
111
|
+
const triggers = (options.triggers ?? '')
|
|
112
|
+
.split(',')
|
|
113
|
+
.map((t) => t.trim())
|
|
114
|
+
.filter((t) => t.length > 0);
|
|
115
|
+
const payload = {
|
|
116
|
+
wu: options.wu,
|
|
117
|
+
triggers,
|
|
118
|
+
summary: options.summary,
|
|
68
119
|
};
|
|
120
|
+
if (options.skipGatesReason)
|
|
121
|
+
payload.skipGatesReason = options.skipGatesReason;
|
|
122
|
+
if (options.fixWu)
|
|
123
|
+
payload.fixWu = options.fixWu;
|
|
124
|
+
return payload;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Resolve where a human-escalation sidecar should live. Worker agents request
|
|
128
|
+
* approvals from their claimed worktree, but reviewers run approval:review from
|
|
129
|
+
* the main checkout so the sidecar must be written where the reviewer can read
|
|
130
|
+
* it. Unknown/non-git roots fall back to the caller's root for unit tests and
|
|
131
|
+
* non-worktree control-plane use.
|
|
132
|
+
*/
|
|
133
|
+
export async function resolveEscalationSidecarRoot(rootDir = process.cwd(), location) {
|
|
134
|
+
const resolvedLocation = location ?? (await resolveLocation(rootDir));
|
|
135
|
+
const { LOCATION_TYPES } = CONTEXT_VALIDATION;
|
|
136
|
+
if (resolvedLocation.type === LOCATION_TYPES.WORKTREE) {
|
|
137
|
+
return resolvedLocation.mainCheckout;
|
|
138
|
+
}
|
|
139
|
+
if (resolvedLocation.type === LOCATION_TYPES.MAIN ||
|
|
140
|
+
resolvedLocation.type === LOCATION_TYPES.DETACHED) {
|
|
141
|
+
return resolvedLocation.gitRoot;
|
|
142
|
+
}
|
|
143
|
+
return rootDir;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* WU-2874: Write sidecar file so approval:review --approve can chain the
|
|
147
|
+
* resolver without a list/fetch roundtrip. Idempotent — overwrites on re-run.
|
|
148
|
+
*/
|
|
149
|
+
export function writeEscalationSidecar(approvalId, payload, rootDir = process.cwd()) {
|
|
150
|
+
const dir = join(rootDir, ESCALATION_SIDECAR_DIR);
|
|
151
|
+
if (!existsSync(dir)) {
|
|
152
|
+
mkdirSync(dir, { recursive: true });
|
|
153
|
+
}
|
|
154
|
+
const path = join(dir, `${approvalId}.json`);
|
|
155
|
+
writeFileSync(path, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
156
|
+
return path;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* WU-2874: Read sidecar written by writeEscalationSidecar. Returns undefined
|
|
160
|
+
* when missing (e.g. approval was not a human-escalation type).
|
|
161
|
+
*/
|
|
162
|
+
export function readEscalationSidecar(approvalId, rootDir = process.cwd()) {
|
|
163
|
+
const path = join(rootDir, ESCALATION_SIDECAR_DIR, `${approvalId}.json`);
|
|
164
|
+
if (!existsSync(path))
|
|
165
|
+
return undefined;
|
|
166
|
+
const raw = readFileSync(path, 'utf8');
|
|
167
|
+
return JSON.parse(raw);
|
|
168
|
+
}
|
|
169
|
+
/** WU-2874: Return the absolute sidecar path for a given approval id. */
|
|
170
|
+
export function getEscalationSidecarPath(approvalId, rootDir = process.cwd()) {
|
|
171
|
+
return join(rootDir, ESCALATION_SIDECAR_DIR, `${approvalId}.json`);
|
|
69
172
|
}
|
|
70
173
|
function resolveApprovalContext(workspaceRoot, environment, workspaceIdOverride) {
|
|
71
174
|
const workspaceFilePath = join(workspaceRoot, WORKSPACE_PATH);
|
|
@@ -89,9 +192,39 @@ function formatResultLine(result) {
|
|
|
89
192
|
return `${LOG_PREFIX} Created ${result.approval_id} (${result.status}) for type ${result.type}`;
|
|
90
193
|
}
|
|
91
194
|
export async function runApprovalRequest(options, input = {}) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
195
|
+
const rootDir = input.workspaceRoot ?? process.cwd();
|
|
196
|
+
// WU-2874: human-escalation synthesises subject+context from structured flags
|
|
197
|
+
// so the worker does not have to hand-craft JSON. The existing free-form
|
|
198
|
+
// API (--subject/--context) remains untouched for every other type.
|
|
199
|
+
let subject;
|
|
200
|
+
let context;
|
|
201
|
+
let escalationPayload;
|
|
202
|
+
if (options.type === HUMAN_ESCALATION_APPROVAL_TYPE) {
|
|
203
|
+
escalationPayload = buildHumanEscalationPayload(options);
|
|
204
|
+
subject = { wu: escalationPayload.wu };
|
|
205
|
+
const structuredContext = {
|
|
206
|
+
triggers: escalationPayload.triggers,
|
|
207
|
+
summary: escalationPayload.summary,
|
|
208
|
+
...(escalationPayload.skipGatesReason
|
|
209
|
+
? { skip_gates_reason: escalationPayload.skipGatesReason }
|
|
210
|
+
: {}),
|
|
211
|
+
...(escalationPayload.fixWu ? { fix_wu: escalationPayload.fixWu } : {}),
|
|
212
|
+
};
|
|
213
|
+
context = structuredContext;
|
|
214
|
+
// Free-form --context lets operators add trace metadata, but structured
|
|
215
|
+
// escalation fields remain authoritative.
|
|
216
|
+
if (options.context) {
|
|
217
|
+
context = { ...parseHumanEscalationExtraContext(options.context), ...structuredContext };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
if (!options.subject) {
|
|
222
|
+
throw new Error('--subject is required (unless --type human-escalation)');
|
|
223
|
+
}
|
|
224
|
+
subject = parseJsonRecord(options.subject, '--subject');
|
|
225
|
+
context = options.context ? parseJsonRecord(options.context, '--context') : undefined;
|
|
226
|
+
}
|
|
227
|
+
const { workspaceId, syncPort } = resolveApprovalContext(rootDir, input.environment ?? process.env, options.workspaceId);
|
|
95
228
|
if (!syncPort.requestApproval) {
|
|
96
229
|
throw new Error('Control-plane adapter does not support requestApproval');
|
|
97
230
|
}
|
|
@@ -104,16 +237,29 @@ export async function runApprovalRequest(options, input = {}) {
|
|
|
104
237
|
...(options.expiresAt ? { expires_at: options.expiresAt } : {}),
|
|
105
238
|
...(context ? { context } : {}),
|
|
106
239
|
};
|
|
107
|
-
|
|
240
|
+
const record = await syncPort.requestApproval(payload);
|
|
241
|
+
// WU-2874: write sidecar after the control-plane acknowledges so the
|
|
242
|
+
// reviewer's approval:review --approve path can chain the resolver locally
|
|
243
|
+
// without a lookup. Sidecar-write is side-effect only; we never auto-run
|
|
244
|
+
// wu:escalate/wu:done on the worker's behalf (Acceptance criterion: no
|
|
245
|
+
// auto-run on creation).
|
|
246
|
+
if (escalationPayload) {
|
|
247
|
+
const sidecarRoot = await resolveEscalationSidecarRoot(rootDir);
|
|
248
|
+
writeEscalationSidecar(record.approval_id, escalationPayload, sidecarRoot);
|
|
249
|
+
}
|
|
250
|
+
return record;
|
|
108
251
|
}
|
|
109
252
|
export async function main(argv = process.argv) {
|
|
110
253
|
const options = parseOptions(argv);
|
|
111
|
-
const result =
|
|
254
|
+
const result = await runApprovalRequest(options);
|
|
112
255
|
if (options.json) {
|
|
113
256
|
console.log(JSON.stringify(result, null, 2));
|
|
114
257
|
return;
|
|
115
258
|
}
|
|
116
259
|
console.log(formatResultLine(result));
|
|
260
|
+
if (options.type === HUMAN_ESCALATION_APPROVAL_TYPE) {
|
|
261
|
+
console.log(`${LOG_PREFIX} Reviewer resolves via: pnpm approval:review --approve ${result.approval_id}`);
|
|
262
|
+
}
|
|
117
263
|
}
|
|
118
264
|
if (import.meta.main) {
|
|
119
265
|
void runCLI(main);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-request.js","sourceRoot":"","sources":["../src/approval-request.ts"],"names":[],"mappings":";AACA,iCAAiC;AACjC,yCAAyC;AAEzC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"approval-request.js","sourceRoot":"","sources":["../src/approval-request.ts"],"names":[],"mappings":"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"}
|
package/dist/approval-review.js
CHANGED
|
@@ -1,29 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Copyright (c) 2026 Hellmai Ltd
|
|
3
3
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
-
import {
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { existsSync, readFileSync, unlinkSync } from 'node:fs';
|
|
5
6
|
import { join } from 'node:path';
|
|
6
7
|
import { Command } from 'commander';
|
|
8
|
+
import { CONTEXT_VALIDATION, resolveLocation } from '@lumenflow/core';
|
|
7
9
|
import { createHttpControlPlaneSyncPort, parseWorkspaceControlPlaneConfig, } from '@lumenflow/control-plane-sdk';
|
|
8
10
|
import { CONFIG_FILES } from '@lumenflow/core/wu-constants';
|
|
11
|
+
import { getGitForCwd } from '@lumenflow/core/git-adapter';
|
|
9
12
|
import { parseYAML } from '@lumenflow/core/wu-yaml';
|
|
10
13
|
import { runCLI } from './cli-entry-point.js';
|
|
14
|
+
import { HUMAN_ESCALATION_APPROVAL_TYPE, getEscalationSidecarPath, readEscalationSidecar, } from './approval-request.js';
|
|
11
15
|
const LOG_PREFIX = '[approval:review]';
|
|
12
16
|
const WORKSPACE_PATH = CONFIG_FILES.WORKSPACE_CONFIG;
|
|
13
17
|
const DEFAULT_REVIEWER_TYPE = 'user';
|
|
14
18
|
const REVIEWER_TYPE_VALUES = new Set(['agent', 'user']);
|
|
15
19
|
const DECISION_VALUES = new Set(['approved', 'rejected', 'expired']);
|
|
20
|
+
const HUMAN_ESCALATION_LOOKUP_LIMIT = 100;
|
|
16
21
|
function parseOptions(argv = process.argv) {
|
|
17
22
|
const program = new Command()
|
|
18
23
|
.name('approval-review')
|
|
19
24
|
.description('Resolve a pending approval in the configured control-plane')
|
|
20
|
-
|
|
21
|
-
.
|
|
25
|
+
// WU-2874: `--id` accepted as option, positional, or via --approve/--deny <id>
|
|
26
|
+
.option('--id <approvalId>', 'Approval identifier')
|
|
27
|
+
.option('--decision <decision>', 'Decision: approved, rejected, or expired')
|
|
28
|
+
.option('--approve [approvalId]', 'Convenience for --decision approved')
|
|
29
|
+
.option('--deny [approvalId]', 'Convenience for --decision rejected')
|
|
22
30
|
.option('--reason <reason>', 'Optional decision reason')
|
|
23
31
|
.option('--reviewer-id <id>', 'Reviewer identifier')
|
|
24
32
|
.option('--reviewer-type <type>', 'Reviewer type: agent or user', DEFAULT_REVIEWER_TYPE)
|
|
33
|
+
.option('--reviewer <email>', 'Reviewer email (used as --resolver for human-escalation chains); defaults to git user.email')
|
|
25
34
|
.option('--workspace-id <id>', 'Override workspace_id from workspace.yaml')
|
|
26
35
|
.option('--json', 'Output JSON response', false)
|
|
36
|
+
.argument('[approvalId]', 'Approval identifier (positional alternative to --id)')
|
|
27
37
|
.exitOverride();
|
|
28
38
|
try {
|
|
29
39
|
program.parse(argv);
|
|
@@ -37,20 +47,42 @@ function parseOptions(argv = process.argv) {
|
|
|
37
47
|
throw error;
|
|
38
48
|
}
|
|
39
49
|
const parsed = program.opts();
|
|
40
|
-
const
|
|
50
|
+
const positional = program.args[0];
|
|
51
|
+
// WU-2874: derive approval id from --id | --approve <id> | --deny <id> | positional
|
|
52
|
+
const approvalIdRaw = parsed.id ??
|
|
53
|
+
(typeof parsed.approve === 'string' ? parsed.approve : undefined) ??
|
|
54
|
+
(typeof parsed.deny === 'string' ? parsed.deny : undefined) ??
|
|
55
|
+
positional;
|
|
56
|
+
if (!approvalIdRaw) {
|
|
57
|
+
throw new Error('Missing approval id. Pass --id <approvalId> or --approve <approvalId>.');
|
|
58
|
+
}
|
|
59
|
+
// WU-2874: derive decision from --decision or the convenience flags.
|
|
60
|
+
let decisionRaw = parsed.decision;
|
|
61
|
+
if (!decisionRaw && parsed.approve !== undefined)
|
|
62
|
+
decisionRaw = 'approved';
|
|
63
|
+
if (!decisionRaw && parsed.deny !== undefined)
|
|
64
|
+
decisionRaw = 'rejected';
|
|
65
|
+
if (parsed.approve !== undefined && parsed.deny !== undefined) {
|
|
66
|
+
throw new Error('--approve and --deny are mutually exclusive');
|
|
67
|
+
}
|
|
68
|
+
if (!decisionRaw) {
|
|
69
|
+
throw new Error('Missing decision. Use --approve, --deny, or --decision <value>.');
|
|
70
|
+
}
|
|
71
|
+
const decision = decisionRaw.trim();
|
|
41
72
|
if (!DECISION_VALUES.has(decision)) {
|
|
42
|
-
throw new Error(`Invalid --decision: ${
|
|
73
|
+
throw new Error(`Invalid --decision: ${decisionRaw}`);
|
|
43
74
|
}
|
|
44
75
|
const reviewerTypeRaw = (parsed.reviewerType ?? DEFAULT_REVIEWER_TYPE).trim();
|
|
45
76
|
if (!REVIEWER_TYPE_VALUES.has(reviewerTypeRaw)) {
|
|
46
77
|
throw new Error(`Invalid --reviewer-type: ${reviewerTypeRaw}`);
|
|
47
78
|
}
|
|
48
79
|
return {
|
|
49
|
-
approvalId:
|
|
80
|
+
approvalId: approvalIdRaw.trim(),
|
|
50
81
|
decision,
|
|
51
82
|
reason: parsed.reason?.trim(),
|
|
52
83
|
reviewerId: parsed.reviewerId?.trim(),
|
|
53
84
|
reviewerType: reviewerTypeRaw,
|
|
85
|
+
reviewer: parsed.reviewer?.trim(),
|
|
54
86
|
workspaceId: parsed.workspaceId?.trim(),
|
|
55
87
|
json: parsed.json ?? false,
|
|
56
88
|
};
|
|
@@ -77,11 +109,192 @@ function formatResultLine(result) {
|
|
|
77
109
|
const reasonSuffix = result.decision_reason ? ` (${result.decision_reason})` : '';
|
|
78
110
|
return `${LOG_PREFIX} Updated ${result.approval_id} -> ${result.status}${reasonSuffix}`;
|
|
79
111
|
}
|
|
112
|
+
const defaultRunner = (command, args, options) => {
|
|
113
|
+
const result = spawnSync(command, args, {
|
|
114
|
+
cwd: options.cwd,
|
|
115
|
+
encoding: 'utf8',
|
|
116
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
status: result.status,
|
|
120
|
+
stdout: typeof result.stdout === 'string' ? result.stdout : undefined,
|
|
121
|
+
stderr: typeof result.stderr === 'string' ? result.stderr : undefined,
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
export async function resolveEscalationReviewRoot(rootDir = process.cwd(), location) {
|
|
125
|
+
const resolvedLocation = location ?? (await resolveLocation(rootDir));
|
|
126
|
+
const { LOCATION_TYPES } = CONTEXT_VALIDATION;
|
|
127
|
+
if (resolvedLocation.type === LOCATION_TYPES.WORKTREE) {
|
|
128
|
+
return resolvedLocation.mainCheckout;
|
|
129
|
+
}
|
|
130
|
+
if (resolvedLocation.type === LOCATION_TYPES.MAIN ||
|
|
131
|
+
resolvedLocation.type === LOCATION_TYPES.DETACHED) {
|
|
132
|
+
return resolvedLocation.gitRoot;
|
|
133
|
+
}
|
|
134
|
+
return rootDir;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* WU-2874: Chain wu:escalate --resolve + wu:done for an approved human-escalation.
|
|
138
|
+
*
|
|
139
|
+
* Pure-ish: accepts a runner so tests can assert call ordering without spawning
|
|
140
|
+
* real processes. Stops at the first non-zero exit and surfaces the failing step.
|
|
141
|
+
* The approval record is only marked resolved when every step returns status 0.
|
|
142
|
+
*/
|
|
143
|
+
export function runEscalationChain(input) {
|
|
144
|
+
const runner = input.runner ?? defaultRunner;
|
|
145
|
+
const cwd = input.cwd;
|
|
146
|
+
const steps = [];
|
|
147
|
+
const escalateArgs = [
|
|
148
|
+
'wu:escalate',
|
|
149
|
+
'--resolve',
|
|
150
|
+
'--id',
|
|
151
|
+
input.payload.wu,
|
|
152
|
+
'--resolver',
|
|
153
|
+
input.reviewerEmail,
|
|
154
|
+
];
|
|
155
|
+
const escalate = runner('pnpm', escalateArgs, { cwd });
|
|
156
|
+
const escalateStep = {
|
|
157
|
+
command: 'pnpm',
|
|
158
|
+
args: escalateArgs,
|
|
159
|
+
status: escalate.status,
|
|
160
|
+
};
|
|
161
|
+
steps.push(escalateStep);
|
|
162
|
+
if (escalate.status !== 0) {
|
|
163
|
+
return { steps, success: false, failedStep: escalateStep };
|
|
164
|
+
}
|
|
165
|
+
const doneArgs = ['wu:done', '--id', input.payload.wu];
|
|
166
|
+
if (input.payload.skipGatesReason) {
|
|
167
|
+
doneArgs.push('--skip-gates', '--reason', input.payload.skipGatesReason);
|
|
168
|
+
if (input.payload.fixWu) {
|
|
169
|
+
doneArgs.push('--fix-wu', input.payload.fixWu);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const done = runner('pnpm', doneArgs, { cwd });
|
|
173
|
+
const doneStep = {
|
|
174
|
+
command: 'pnpm',
|
|
175
|
+
args: doneArgs,
|
|
176
|
+
status: done.status,
|
|
177
|
+
};
|
|
178
|
+
steps.push(doneStep);
|
|
179
|
+
if (done.status !== 0) {
|
|
180
|
+
return { steps, success: false, failedStep: doneStep };
|
|
181
|
+
}
|
|
182
|
+
return { steps, success: true };
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* WU-2874: Resolve the reviewer's email. Explicit --reviewer wins; otherwise
|
|
186
|
+
* fall back to git user.email so the approver identity is never the approval
|
|
187
|
+
* creator (per acceptance criteria).
|
|
188
|
+
*/
|
|
189
|
+
async function resolveReviewerEmail(override) {
|
|
190
|
+
if (override)
|
|
191
|
+
return override;
|
|
192
|
+
const email = await getGitForCwd().getConfigValue('user.email');
|
|
193
|
+
if (!email) {
|
|
194
|
+
throw new Error('Cannot determine reviewer email. Pass --reviewer <email> or configure git user.email.');
|
|
195
|
+
}
|
|
196
|
+
return email;
|
|
197
|
+
}
|
|
198
|
+
function missingHumanEscalationContextMessage(approvalId, detail) {
|
|
199
|
+
return (`${LOG_PREFIX} Missing human-escalation context for approval ${approvalId}: ${detail}. ` +
|
|
200
|
+
'Re-run pnpm approval:request --type human-escalation from the claimed worktree, ' +
|
|
201
|
+
'then retry approval:review.');
|
|
202
|
+
}
|
|
203
|
+
function readRequiredString(value, fieldName, approvalId) {
|
|
204
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
throw new Error(missingHumanEscalationContextMessage(approvalId, `expected ${fieldName}`));
|
|
208
|
+
}
|
|
209
|
+
function readOptionalString(value) {
|
|
210
|
+
return typeof value === 'string' && value.trim().length > 0 ? value : undefined;
|
|
211
|
+
}
|
|
212
|
+
function readTriggers(value, approvalId) {
|
|
213
|
+
if (Array.isArray(value) && value.every((trigger) => typeof trigger === 'string')) {
|
|
214
|
+
return value;
|
|
215
|
+
}
|
|
216
|
+
throw new Error(missingHumanEscalationContextMessage(approvalId, 'expected context.triggers to be an array of strings'));
|
|
217
|
+
}
|
|
218
|
+
export function humanEscalationPayloadFromApprovalRecord(record) {
|
|
219
|
+
if (record.type !== HUMAN_ESCALATION_APPROVAL_TYPE) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
if (!record.context) {
|
|
223
|
+
throw new Error(missingHumanEscalationContextMessage(record.approval_id, 'control-plane record has no context'));
|
|
224
|
+
}
|
|
225
|
+
const payload = {
|
|
226
|
+
wu: readRequiredString(record.subject.wu, 'subject.wu', record.approval_id),
|
|
227
|
+
triggers: readTriggers(record.context.triggers, record.approval_id),
|
|
228
|
+
summary: readRequiredString(record.context.summary, 'context.summary', record.approval_id),
|
|
229
|
+
};
|
|
230
|
+
const skipGatesReason = readOptionalString(record.context.skip_gates_reason);
|
|
231
|
+
if (skipGatesReason)
|
|
232
|
+
payload.skipGatesReason = skipGatesReason;
|
|
233
|
+
const fixWu = readOptionalString(record.context.fix_wu);
|
|
234
|
+
if (fixWu)
|
|
235
|
+
payload.fixWu = fixWu;
|
|
236
|
+
return payload;
|
|
237
|
+
}
|
|
238
|
+
async function findPendingHumanEscalationPayload(input) {
|
|
239
|
+
if (!input.syncPort.listApprovals) {
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
const result = await input.syncPort.listApprovals({
|
|
243
|
+
workspace_id: input.workspaceId,
|
|
244
|
+
status: 'pending',
|
|
245
|
+
type: HUMAN_ESCALATION_APPROVAL_TYPE,
|
|
246
|
+
limit: HUMAN_ESCALATION_LOOKUP_LIMIT,
|
|
247
|
+
});
|
|
248
|
+
const matchingRecord = result.approvals.find((approval) => approval.approval_id === input.approvalId);
|
|
249
|
+
return matchingRecord ? humanEscalationPayloadFromApprovalRecord(matchingRecord) : undefined;
|
|
250
|
+
}
|
|
80
251
|
export async function runApprovalReview(options, input = {}) {
|
|
81
|
-
const
|
|
252
|
+
const rootDir = input.workspaceRoot ?? process.cwd();
|
|
253
|
+
const reviewRoot = await resolveEscalationReviewRoot(rootDir);
|
|
254
|
+
const { workspaceId, syncPort } = resolveApprovalContext(rootDir, input.environment ?? process.env, options.workspaceId);
|
|
82
255
|
if (!syncPort.resolveApproval) {
|
|
83
256
|
throw new Error('Control-plane adapter does not support resolveApproval');
|
|
84
257
|
}
|
|
258
|
+
// WU-2874: Human-escalation approve-chain runs BEFORE marking the approval
|
|
259
|
+
// resolved. If either wu:escalate or wu:done fails, the approval stays
|
|
260
|
+
// pending so the reviewer can re-approve after fixes (replayable).
|
|
261
|
+
const sidecar = options.decision === 'approved'
|
|
262
|
+
? readEscalationSidecar(options.approvalId, reviewRoot)
|
|
263
|
+
: undefined;
|
|
264
|
+
const escalationPayload = sidecar ??
|
|
265
|
+
(options.decision === 'approved'
|
|
266
|
+
? await findPendingHumanEscalationPayload({
|
|
267
|
+
approvalId: options.approvalId,
|
|
268
|
+
workspaceId,
|
|
269
|
+
syncPort,
|
|
270
|
+
})
|
|
271
|
+
: undefined);
|
|
272
|
+
if (escalationPayload) {
|
|
273
|
+
const reviewerEmail = await resolveReviewerEmail(options.reviewer);
|
|
274
|
+
const chainResult = runEscalationChain({
|
|
275
|
+
approvalId: options.approvalId,
|
|
276
|
+
payload: escalationPayload,
|
|
277
|
+
reviewerEmail,
|
|
278
|
+
runner: input.runner,
|
|
279
|
+
cwd: reviewRoot,
|
|
280
|
+
});
|
|
281
|
+
if (!chainResult.success) {
|
|
282
|
+
const step = chainResult.failedStep;
|
|
283
|
+
const joined = step ? `${step.command} ${step.args.join(' ')}` : 'unknown step';
|
|
284
|
+
throw new Error(`${LOG_PREFIX} Resolver chain failed at: ${joined} (exit ${step?.status ?? 'null'}). ` +
|
|
285
|
+
`Approval ${options.approvalId} left pending — fix the underlying issue and re-run ` +
|
|
286
|
+
`pnpm approval:review --approve ${options.approvalId}.`);
|
|
287
|
+
}
|
|
288
|
+
// Successful chain → remove the sidecar so re-approval is a no-op.
|
|
289
|
+
try {
|
|
290
|
+
const sidecarPath = getEscalationSidecarPath(options.approvalId, reviewRoot);
|
|
291
|
+
if (existsSync(sidecarPath))
|
|
292
|
+
unlinkSync(sidecarPath);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// Non-fatal — resolver already succeeded.
|
|
296
|
+
}
|
|
297
|
+
}
|
|
85
298
|
const payload = {
|
|
86
299
|
workspace_id: workspaceId,
|
|
87
300
|
approval_id: options.approvalId,
|
|
@@ -92,9 +305,11 @@ export async function runApprovalReview(options, input = {}) {
|
|
|
92
305
|
};
|
|
93
306
|
return syncPort.resolveApproval(payload);
|
|
94
307
|
}
|
|
308
|
+
/** WU-2874: Exported for integration tests that assert on approval type guards. */
|
|
309
|
+
export { HUMAN_ESCALATION_APPROVAL_TYPE };
|
|
95
310
|
export async function main(argv = process.argv) {
|
|
96
311
|
const options = parseOptions(argv);
|
|
97
|
-
const result =
|
|
312
|
+
const result = await runApprovalReview(options);
|
|
98
313
|
if (options.json) {
|
|
99
314
|
console.log(JSON.stringify(result, null, 2));
|
|
100
315
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-review.js","sourceRoot":"","sources":["../src/approval-review.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"approval-review.js","sourceRoot":"","sources":["../src/approval-review.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,7 +39,7 @@ import { generateScriptsFromManifest } from './public-manifest.js';
|
|
|
39
39
|
import { loadTemplate, processTemplate, CORE_DOC_TEMPLATE_PATHS, buildCoreDocTokens, executeDocsSyncInDir, } from './docs-sync.js';
|
|
40
40
|
// WU-2864: propagate the .gitattributes merge=union rule to consumer repos
|
|
41
41
|
// on upgrade so rebase conflicts on append-only JSONL state files auto-resolve.
|
|
42
|
-
import { scaffoldGitattributes } from './init-safety-scripts.js';
|
|
42
|
+
import { scaffoldGitattributes, scaffoldGitignore } from './init-safety-scripts.js';
|
|
43
43
|
/** Log prefix for console output */
|
|
44
44
|
const LOG_PREFIX = '[lumenflow:upgrade]';
|
|
45
45
|
/** Operation name for micro-worktree */
|
|
@@ -813,6 +813,33 @@ async function _runUpgradeTransaction(addCommand, versionSpec, upgradeId) {
|
|
|
813
813
|
if (docsSyncResult.created.length > 0) {
|
|
814
814
|
console.log(`${LOG_PREFIX} Synced ${docsSyncResult.created.length} doc file(s)`);
|
|
815
815
|
}
|
|
816
|
+
// WU-2876: Reconcile .gitignore against LUMENFLOW_RUNTIME_WRITE_PATHS +
|
|
817
|
+
// REQUIRED_GITIGNORE_EXCLUSIONS for existing consumers. Consumers that
|
|
818
|
+
// initialised before v5.2 never received the `.lumenflow/state/*.jsonl`
|
|
819
|
+
// glob and see runtime-write files (e.g. display-name-reservations.jsonl)
|
|
820
|
+
// show up as dirty in source control. scaffoldGitignore's merge path
|
|
821
|
+
// preserves user-owned lines outside the LumenFlow block byte-for-byte,
|
|
822
|
+
// and when no .gitignore exists it writes the full GITIGNORE_TEMPLATE.
|
|
823
|
+
// Called before scaffoldGitattributes to mirror init.ts ordering.
|
|
824
|
+
const giResult = {
|
|
825
|
+
created: [],
|
|
826
|
+
skipped: [],
|
|
827
|
+
merged: [],
|
|
828
|
+
warnings: [],
|
|
829
|
+
overwritten: [],
|
|
830
|
+
};
|
|
831
|
+
await scaffoldGitignore(worktreePath, { force: false, full: false }, giResult);
|
|
832
|
+
const giTouched = giResult.created.length > 0 ||
|
|
833
|
+
(giResult.merged?.length ?? 0) > 0 ||
|
|
834
|
+
(giResult.overwritten?.length ?? 0) > 0;
|
|
835
|
+
if (giTouched) {
|
|
836
|
+
const action = giResult.created.length > 0
|
|
837
|
+
? 'Created'
|
|
838
|
+
: (giResult.overwritten?.length ?? 0) > 0
|
|
839
|
+
? 'Rewrote'
|
|
840
|
+
: 'Updated managed block in';
|
|
841
|
+
console.log(`${LOG_PREFIX} ${action} .gitignore (WU-2876 runtime-write-path reconciliation)`);
|
|
842
|
+
}
|
|
816
843
|
// WU-2864: Propagate the .gitattributes merge=union rule for append-only
|
|
817
844
|
// JSONL state files. This is the sibling of syncCoreDocs — a managed
|
|
818
845
|
// block that consumer repos need to receive on every upgrade so future
|
|
@@ -846,6 +873,7 @@ async function _runUpgradeTransaction(addCommand, versionSpec, upgradeId) {
|
|
|
846
873
|
UPGRADE_MARKER_RELATIVE_PATH,
|
|
847
874
|
...docsSyncResult.allFiles,
|
|
848
875
|
...(gaTouched ? ['.gitattributes'] : []),
|
|
876
|
+
...(giTouched ? ['.gitignore'] : []),
|
|
849
877
|
],
|
|
850
878
|
};
|
|
851
879
|
},
|