@multileaf/ai-workflow 0.9.0 → 0.10.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/CHANGELOG.md +23 -0
- package/README.md +12 -1
- package/dist/blocking-reasons.d.ts +67 -0
- package/dist/blocking-reasons.js +181 -0
- package/dist/command-evidence.d.ts +32 -0
- package/dist/command-evidence.js +61 -0
- package/dist/human-approval.d.ts +16 -1
- package/dist/human-approval.js +69 -5
- package/dist/recommendations.js +16 -1
- package/dist/risk-assess.d.ts +50 -0
- package/dist/risk-assess.js +273 -0
- package/dist/workflow.js +233 -24
- package/docs/README.md +2 -0
- package/docs/architecture.md +50 -0
- package/docs/backlog.md +13 -0
- package/docs/continuation-inventory.md +21 -0
- package/docs/decision-model-evaluation.md +4 -0
- package/docs/opportunities/OPP-001-proportional-ceremony-light-lane.md +62 -0
- package/docs/opportunities/OPP-002-risk-assess-command.md +70 -0
- package/docs/opportunities/OPP-003-adversarial-review-package.md +63 -0
- package/docs/opportunities/OPP-004-exact-continuation-commands.md +59 -0
- package/docs/opportunities/OPP-005-friction-benchmark.md +62 -0
- package/docs/opportunities/OPP-006-emission-usability-test-policy.md +57 -0
- package/docs/opportunities/OPP-007-decision-findings-triage-scope-watch.md +54 -0
- package/docs/opportunities/OPP-008-embedding-covering-tests-judge-context.md +52 -0
- package/docs/opportunities/README.md +33 -0
- package/docs/product-requirements.md +3 -0
- package/docs/product-vision.md +1 -0
- package/docs/quality.md +10 -0
- package/docs/release-process.md +9 -1
- package/docs/semantic-mapping.md +4 -0
- package/package.json +1 -1
- package/resources/package.yaml +3 -0
- package/resources/skills/code-review.md +2 -0
- package/resources/skills/review-judge-a.md +24 -0
- package/resources/skills/review-judge-b.md +24 -0
- package/resources/skills/review-refuter.md +28 -0
- package/resources/skills/ticket.md +2 -0
package/dist/workflow.js
CHANGED
|
@@ -50,7 +50,8 @@ import { composeContext, CONTEXT_LAYERS, readContextStore, writeContextLayer, }
|
|
|
50
50
|
import { loadContextDelta, retrieveTaskContext } from "./context-retrieval.js";
|
|
51
51
|
import { createSummaryCache, isSummaryFresh, parseSummaryCache, serializeSummaryCache, } from "./context-cache.js";
|
|
52
52
|
import { parseTokenUsage, recordTokenUsage, serializeTokenUsage, summarizeUsageByModel, } from "./token-usage.js";
|
|
53
|
-
import { APPROVAL_STAGES, assertWorkflowId, validateTaskTicket, approveStage, beginStageApproval, createApprovalLedger, getUnresolvedPrerequisites, invalidateStageAndDescendants, parseApprovalLedger, parseWorkflowSession, rejectStage, serializeApprovalLedger, serializeWorkflowSession, skipOptionalStage, stageDefinition, stageFingerprint, } from "./human-approval.js";
|
|
53
|
+
import { APPROVAL_STAGES, assertWorkflowId, validateTaskTicket, approveStage, beginStageApproval, createApprovalLedger, getUnresolvedPrerequisites, invalidateStageAndDescendants, parseApprovalLedger, parseWorkflowSession, rejectStage, serializeApprovalLedger, serializeWorkflowSession, skipOptionalStage, stageDefinition, stageFingerprint, effectiveLane, } from "./human-approval.js";
|
|
54
|
+
import { parse as parseYaml, stringify } from "yaml";
|
|
54
55
|
import { measureContextQuality, serializeContextQuality } from "./context-quality.js";
|
|
55
56
|
import { collectContextBench, compareContextBench, parseContextBench, serializeContextBench, } from "./context-benchmark.js";
|
|
56
57
|
import { materializeParityTask, parseParityTask, runParitySuite, serializeParitySuite, TASK_ASSEMBLER, } from "./task-parity.js";
|
|
@@ -64,6 +65,8 @@ import { parseTraceabilityGraph } from "./traceability.js";
|
|
|
64
65
|
import { serializeAdapterCapabilities } from "./adapter-contract.js";
|
|
65
66
|
import { buildTraceabilityGraph, serializeTraceabilityGraph } from "./traceability.js";
|
|
66
67
|
import { evaluateQualityGate } from "./quality-gates.js";
|
|
68
|
+
import { BlockingError, blockingResultToJson, describeBlockingReason } from "./blocking-reasons.js";
|
|
69
|
+
import { runRiskAssess } from "./risk-assess.js";
|
|
67
70
|
import { parseMigrationCheckpoint, planNeutralResourceMigration, readResourceBytes, serializeMigrationPreview, serializeMigrationCheckpoint, validateNeutralResourceDocument, writeResourceBytes, } from "./migration.js";
|
|
68
71
|
import { parsePrivateRegistries, parseTeamPreset, serializePrivateRegistries, serializeTeamPreset, fetchPrivateRegistryClient, } from "./team-configuration.js";
|
|
69
72
|
import { enforcePolicyGate, gitPolicyTracking } from "./policy-gate.js";
|
|
@@ -110,8 +113,15 @@ function beginWorkflowSession(fs, aiw, options) {
|
|
|
110
113
|
updatedAt: now,
|
|
111
114
|
...(options.ticket ? { ticket: options.ticket } : {}),
|
|
112
115
|
...(options.title ? { title: options.title } : {}),
|
|
116
|
+
...(options.lane === "light" && options.laneReason
|
|
117
|
+
? { lane: "light", laneReason: options.laneReason }
|
|
118
|
+
: {}),
|
|
113
119
|
};
|
|
114
|
-
const ledger = createApprovalLedger(workflowId
|
|
120
|
+
const ledger = createApprovalLedger(workflowId, {
|
|
121
|
+
...(options.lane === "light" && options.laneReason
|
|
122
|
+
? { lane: "light", laneReason: options.laneReason }
|
|
123
|
+
: {}),
|
|
124
|
+
});
|
|
115
125
|
fs.write(ledgerPath, serializeApprovalLedger(ledger));
|
|
116
126
|
fs.write(sessionPath, serializeWorkflowSession(session));
|
|
117
127
|
return { exitCode: 0, output: `Workflow started: ${workflowId}.` };
|
|
@@ -124,14 +134,35 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
124
134
|
if (command === "task") {
|
|
125
135
|
if (!fs.exists(join(aiw, "manifest.yml")))
|
|
126
136
|
return { exitCode: 1, error: "Run `aiw install` first." };
|
|
127
|
-
const usage = "Usage: aiw task start --ticket=TEAM-123 [--title=text] [--id=workflow-id] | aiw task status";
|
|
137
|
+
const usage = "Usage: aiw task start --ticket=TEAM-123 [--title=text] [--id=workflow-id] [--lane=light --lane-reason=text] | aiw task status | aiw task escalate --reason=text | aiw task evidence --validation=text";
|
|
128
138
|
const action = args[1];
|
|
129
139
|
const sessionPath = join(aiw, "workflow.yml");
|
|
130
140
|
if (action === "start") {
|
|
131
|
-
const options = parseNamedOptions(args.slice(2), [
|
|
141
|
+
const options = parseNamedOptions(args.slice(2), [
|
|
142
|
+
"ticket",
|
|
143
|
+
"title",
|
|
144
|
+
"id",
|
|
145
|
+
"lane",
|
|
146
|
+
"lane-reason",
|
|
147
|
+
]);
|
|
132
148
|
if (!options?.ticket)
|
|
133
149
|
return { exitCode: 1, error: usage };
|
|
134
150
|
validateTaskTicket(options.ticket);
|
|
151
|
+
if (options.lane !== undefined && options.lane !== "light")
|
|
152
|
+
return { exitCode: 1, error: "Only --lane=light exists; omit it for the full lane." };
|
|
153
|
+
const laneReason = options["lane-reason"];
|
|
154
|
+
if (options.lane === "light") {
|
|
155
|
+
if (!laneReason)
|
|
156
|
+
return {
|
|
157
|
+
exitCode: 1,
|
|
158
|
+
error: "--lane=light requires --lane-reason: the resume-test call, recorded for audit.",
|
|
159
|
+
};
|
|
160
|
+
if (laneReason.length > 500 || /[\r\n]/.test(laneReason))
|
|
161
|
+
return {
|
|
162
|
+
exitCode: 1,
|
|
163
|
+
error: "--lane-reason must be a single line of at most 500 bytes.",
|
|
164
|
+
};
|
|
165
|
+
}
|
|
135
166
|
if (options.id !== undefined) {
|
|
136
167
|
try {
|
|
137
168
|
assertWorkflowId(options.id);
|
|
@@ -140,20 +171,104 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
140
171
|
return { exitCode: 1, error: "Workflow ID must be a UUID." };
|
|
141
172
|
}
|
|
142
173
|
}
|
|
174
|
+
const lane = options.lane === "light" ? "light" : undefined;
|
|
143
175
|
const result = beginWorkflowSession(fs, aiw, {
|
|
144
176
|
...(options.id ? { id: options.id } : {}),
|
|
145
177
|
ticket: options.ticket,
|
|
146
178
|
...(options.title ? { title: options.title } : {}),
|
|
179
|
+
...(lane ? { lane, laneReason: laneReason } : {}),
|
|
147
180
|
});
|
|
148
181
|
if (result.exitCode !== 0)
|
|
149
182
|
return result;
|
|
150
183
|
const title = options.title ? ` titled "${options.title}"` : "";
|
|
184
|
+
if (lane)
|
|
185
|
+
return {
|
|
186
|
+
exitCode: 0,
|
|
187
|
+
output: `Task started for ticket ${options.ticket}${title} (light lane): ${result.output}\n` +
|
|
188
|
+
"Resume test: this work is small when it is understood, risk is contained, and the work could be resumed from your request plus `git diff`.\n" +
|
|
189
|
+
`Rationale recorded: ${laneReason}\n` +
|
|
190
|
+
'No durable spec/plan artifacts are required. Record validation with `aiw task evidence --validation="<command and result>"`, then complete with `aiw workflow complete`. If scope grows, escalate with `aiw task escalate --reason=...`.',
|
|
191
|
+
};
|
|
151
192
|
return {
|
|
152
193
|
exitCode: 0,
|
|
153
194
|
output: `Task started for ticket ${options.ticket}${title}: ${result.output}\n` +
|
|
154
195
|
`Next steps: ask the user whether the requirement is clear enough to skip brainstorming (record an accepted skip with \`aiw skip brainstorming --reason="Requirement provided by ticket ${options.ticket}"\`), then create the specification with \`aiw spec --ticket=${options.ticket} --title=...\`.`,
|
|
155
196
|
};
|
|
156
197
|
}
|
|
198
|
+
if (action === "escalate") {
|
|
199
|
+
const options = parseNamedOptions(args.slice(2), ["reason"]);
|
|
200
|
+
if (!options?.reason)
|
|
201
|
+
return { exitCode: 1, error: "Usage: aiw task escalate --reason=why-scope-grew" };
|
|
202
|
+
if (!fs.exists(sessionPath))
|
|
203
|
+
return { exitCode: 1, error: "No SDD workflow is active. Start one first." };
|
|
204
|
+
const { session } = readWorkflowState(aiw, fs);
|
|
205
|
+
if (session.lane !== "light")
|
|
206
|
+
return {
|
|
207
|
+
exitCode: 1,
|
|
208
|
+
error: "Only light-lane workflows escalate; this one already runs the full lane.",
|
|
209
|
+
};
|
|
210
|
+
if (session.laneEscalatedAt)
|
|
211
|
+
return {
|
|
212
|
+
exitCode: 1,
|
|
213
|
+
error: `Workflow ${session.workflowId} already escalated to the full lane.`,
|
|
214
|
+
};
|
|
215
|
+
const now = new Date().toISOString();
|
|
216
|
+
fs.write(sessionPath, serializeWorkflowSession({
|
|
217
|
+
...session,
|
|
218
|
+
laneEscalatedAt: now,
|
|
219
|
+
laneEscalationReason: options.reason,
|
|
220
|
+
updatedAt: now,
|
|
221
|
+
}));
|
|
222
|
+
const evidencePath = join(aiw, `checkpoints/task-light-${session.workflowId}.yml`);
|
|
223
|
+
return {
|
|
224
|
+
exitCode: 0,
|
|
225
|
+
output: `Workflow ${session.workflowId} escalated to the full lane.\n` +
|
|
226
|
+
(fs.exists(evidencePath)
|
|
227
|
+
? `Light-lane evidence preserved and linked: ${evidencePath}\n`
|
|
228
|
+
: "No light-lane evidence had been recorded yet.\n") +
|
|
229
|
+
"Stages now run the full approval sequence from brainstorming.",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (action === "evidence") {
|
|
233
|
+
const options = parseNamedOptions(args.slice(2), ["validation"]);
|
|
234
|
+
if (!options?.validation)
|
|
235
|
+
return {
|
|
236
|
+
exitCode: 1,
|
|
237
|
+
error: 'Usage: aiw task evidence --validation="<command and its observed result>"',
|
|
238
|
+
};
|
|
239
|
+
if (!fs.exists(sessionPath))
|
|
240
|
+
return { exitCode: 1, error: "No SDD workflow is active. Start one first." };
|
|
241
|
+
const { session } = readWorkflowState(aiw, fs);
|
|
242
|
+
if (effectiveLane(session) !== "light")
|
|
243
|
+
return {
|
|
244
|
+
exitCode: 1,
|
|
245
|
+
error: "Evidence entries are a light-lane command; full-lane workflows record stage artifacts and gates.",
|
|
246
|
+
};
|
|
247
|
+
const evidencePath = join(aiw, `checkpoints/task-light-${session.workflowId}.yml`);
|
|
248
|
+
const existing = fs.exists(evidencePath)
|
|
249
|
+
? parseLightEvidence(fs.read(evidencePath))
|
|
250
|
+
: {
|
|
251
|
+
schema: 1,
|
|
252
|
+
workflowId: session.workflowId,
|
|
253
|
+
...(session.ticket ? { ticket: session.ticket } : {}),
|
|
254
|
+
lane: "light",
|
|
255
|
+
...(session.laneReason ? { laneReason: session.laneReason } : {}),
|
|
256
|
+
entries: [],
|
|
257
|
+
};
|
|
258
|
+
const updated = {
|
|
259
|
+
...existing,
|
|
260
|
+
entries: [
|
|
261
|
+
...existing.entries,
|
|
262
|
+
{ note: options.validation, recordedAt: new Date().toISOString() },
|
|
263
|
+
],
|
|
264
|
+
};
|
|
265
|
+
fs.mkdir(join(aiw, "checkpoints"));
|
|
266
|
+
fs.write(evidencePath, stringify({ ...updated }, { lineWidth: 0 }));
|
|
267
|
+
return {
|
|
268
|
+
exitCode: 0,
|
|
269
|
+
output: `Validation evidence recorded (${updated.entries.length} entr${updated.entries.length === 1 ? "y" : "ies"}): ${options.validation}`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
157
272
|
if (action === "status") {
|
|
158
273
|
if (!fs.exists(sessionPath))
|
|
159
274
|
return {
|
|
@@ -161,14 +276,24 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
161
276
|
output: "No SDD workflow is active. Start one with `aiw task start --ticket=TEAM-123`.",
|
|
162
277
|
};
|
|
163
278
|
const { session, ledger } = readWorkflowState(aiw, fs, false);
|
|
279
|
+
const lane = session.lane === "light"
|
|
280
|
+
? [
|
|
281
|
+
`Lane: light — ${session.laneEscalatedAt ? `escalated to the full lane (${session.laneEscalationReason})` : session.laneReason}`,
|
|
282
|
+
]
|
|
283
|
+
: [];
|
|
164
284
|
const lines = [
|
|
165
285
|
...(session.ticket
|
|
166
286
|
? [`Ticket: ${session.ticket}${session.title ? ` — ${session.title}` : ""}`]
|
|
167
287
|
: []),
|
|
288
|
+
...lane,
|
|
168
289
|
`Workflow: ${session.workflowId} (${session.status})`,
|
|
169
290
|
"Stages:",
|
|
170
291
|
...APPROVAL_STAGES.map((stage) => `- ${stage}: ${ledger.stages[stage]?.status ?? "not-started"}`),
|
|
171
292
|
];
|
|
293
|
+
if (session.status === "active" && effectiveLane(session) === "light") {
|
|
294
|
+
lines.push('Next: record validation with `aiw task evidence --validation="<command and result>"`, then `aiw workflow complete`. If scope grows: `aiw task escalate --reason=...`.');
|
|
295
|
+
return { exitCode: 0, output: lines.join("\n") };
|
|
296
|
+
}
|
|
172
297
|
const next = APPROVAL_STAGES.find((stage) => (ledger.stages[stage]?.status ?? "not-started") === "not-started");
|
|
173
298
|
if (session.status === "active" && next)
|
|
174
299
|
lines.push(`Next: ${next === "brainstorming" && session.ticket ? `decide brainstorm or record an accepted skip (ticket ${session.ticket} already provides the requirement)` : `advance ${next} (gate, approval)`}.`);
|
|
@@ -199,6 +324,25 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
199
324
|
}
|
|
200
325
|
if (action === "complete") {
|
|
201
326
|
const { session, ledger } = readActiveWorkflow(aiw, fs);
|
|
327
|
+
if (effectiveLane(session) === "light") {
|
|
328
|
+
const evidencePath = join(aiw, `checkpoints/task-light-${session.workflowId}.yml`);
|
|
329
|
+
const hasEntries = fs.exists(evidencePath)
|
|
330
|
+
? parseLightEvidence(fs.read(evidencePath)).entries.length > 0
|
|
331
|
+
: false;
|
|
332
|
+
if (!hasEntries)
|
|
333
|
+
throw new BlockingError(describeBlockingReason("light_evidence_missing", {
|
|
334
|
+
detail: `Light-lane workflow ${session.workflowId} cannot complete without recorded validation evidence.`,
|
|
335
|
+
}));
|
|
336
|
+
fs.write(sessionPath, serializeWorkflowSession({
|
|
337
|
+
...session,
|
|
338
|
+
status: "complete",
|
|
339
|
+
updatedAt: new Date().toISOString(),
|
|
340
|
+
}));
|
|
341
|
+
return {
|
|
342
|
+
exitCode: 0,
|
|
343
|
+
output: `Workflow completed (light lane): ${session.workflowId}. Evidence: ${evidencePath}`,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
202
346
|
const unresolved = APPROVAL_STAGES.filter((stage) => ledger.stages[stage]?.status !== "approved" &&
|
|
203
347
|
ledger.stages[stage]?.status !== "skipped");
|
|
204
348
|
if (unresolved.length)
|
|
@@ -222,10 +366,15 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
222
366
|
const stage = args[1];
|
|
223
367
|
if (!stage || !APPROVAL_STAGES.includes(stage))
|
|
224
368
|
return { exitCode: 1, error: `Usage: aiw ${command} <workflow-stage> [options]` };
|
|
225
|
-
const
|
|
369
|
+
const rest = args.slice(2).filter((argument) => argument !== "--json");
|
|
370
|
+
const { session, ledger } = readActiveWorkflow(aiw, fs);
|
|
371
|
+
if (effectiveLane(session) === "light")
|
|
372
|
+
throw new BlockingError(describeBlockingReason("lane_escalation_required", {
|
|
373
|
+
detail: `Light-lane workflow ${session.workflowId} does not approve stages; escalate to the full lane to continue.`,
|
|
374
|
+
}));
|
|
226
375
|
const workflowStage = stage;
|
|
227
376
|
if (command === "skip") {
|
|
228
|
-
const options = parseNamedOptions(
|
|
377
|
+
const options = parseNamedOptions(rest, ["reason"]);
|
|
229
378
|
if (!options?.reason)
|
|
230
379
|
return {
|
|
231
380
|
exitCode: 1,
|
|
@@ -241,19 +390,28 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
241
390
|
}
|
|
242
391
|
const artifact = approvalArtifactPath(aiw, workflowStage);
|
|
243
392
|
if (!artifact || !fs.exists(artifact))
|
|
244
|
-
|
|
393
|
+
throw new BlockingError(describeBlockingReason("approval_evidence_missing", {
|
|
394
|
+
stage: workflowStage,
|
|
395
|
+
artifactPath: artifact ?? undefined,
|
|
396
|
+
detail: `Approval evidence is missing for ${workflowStage}.`,
|
|
397
|
+
}));
|
|
245
398
|
const artifactHash = stageFingerprint(fs.read(artifact));
|
|
246
399
|
if (command === "approve") {
|
|
247
400
|
if (ledger.stages[workflowStage]?.artifactHash !== artifactHash) {
|
|
248
401
|
const invalidated = invalidateStageAndDescendants(ledger, workflowStage, new Date().toISOString());
|
|
249
402
|
fs.write(join(aiw, "approvals.yml"), serializeApprovalLedger(invalidated));
|
|
250
403
|
const existing = ledger.stages[workflowStage];
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
: `
|
|
256
|
-
|
|
404
|
+
if (existing)
|
|
405
|
+
throw new BlockingError(describeBlockingReason("approval_stale", {
|
|
406
|
+
stage: workflowStage,
|
|
407
|
+
artifactPath: artifact,
|
|
408
|
+
detail: `The ${workflowStage} artifact changed after review; its approval is stale. Rerun the quality gate and request approval again.`,
|
|
409
|
+
}));
|
|
410
|
+
throw new BlockingError(describeBlockingReason("approval_gate_missing", {
|
|
411
|
+
stage: workflowStage,
|
|
412
|
+
artifactPath: artifact,
|
|
413
|
+
detail: `No passing quality gate recorded for ${workflowStage}. Run aiw gate ${workflowStage} before approving.`,
|
|
414
|
+
}));
|
|
257
415
|
}
|
|
258
416
|
const updated = approveStage(ledger, workflowStage, {
|
|
259
417
|
artifactHash,
|
|
@@ -262,7 +420,7 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
262
420
|
fs.write(join(aiw, "approvals.yml"), serializeApprovalLedger(updated));
|
|
263
421
|
return { exitCode: 0, output: `Human approval recorded for ${workflowStage}.` };
|
|
264
422
|
}
|
|
265
|
-
const options = parseNamedOptions(
|
|
423
|
+
const options = parseNamedOptions(rest, ["reason"]);
|
|
266
424
|
if (!options?.reason)
|
|
267
425
|
return { exitCode: 1, error: "Usage: aiw reject <stage> --reason=human-feedback" };
|
|
268
426
|
const updated = rejectStage(ledger, workflowStage, options.reason, new Date().toISOString());
|
|
@@ -1749,6 +1907,11 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
1749
1907
|
fs.write(path, serialized);
|
|
1750
1908
|
return { exitCode: 0, output: `Traceability graph created: ${path}` };
|
|
1751
1909
|
}
|
|
1910
|
+
if (command === "risk") {
|
|
1911
|
+
if (args[1] !== "assess")
|
|
1912
|
+
return { exitCode: 1, error: "Usage: aiw risk assess [--base-ref=ref] [--json]" };
|
|
1913
|
+
return runRiskAssess(args.slice(2), root, fs);
|
|
1914
|
+
}
|
|
1752
1915
|
if (command === "gate") {
|
|
1753
1916
|
if (!fs.exists(join(aiw, "manifest.yml")))
|
|
1754
1917
|
return { exitCode: 1, error: "Run `aiw install` first." };
|
|
@@ -1762,15 +1925,21 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
1762
1925
|
assertStageReady(aiw, fs, workflowStage);
|
|
1763
1926
|
const artifactPath = approvalArtifactPath(aiw, workflowStage);
|
|
1764
1927
|
if (!artifactPath || !fs.exists(artifactPath))
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1928
|
+
throw new BlockingError(describeBlockingReason("gate_evidence_missing", {
|
|
1929
|
+
stage,
|
|
1930
|
+
artifactPath: artifactPath ?? undefined,
|
|
1931
|
+
detail: `Quality gate blocked: required evidence is missing for ${stage}.`,
|
|
1932
|
+
}));
|
|
1769
1933
|
const hasQualityGate = stageDefinition(workflowStage).requiresQualityGate;
|
|
1770
1934
|
if (hasQualityGate) {
|
|
1771
1935
|
const issues = evaluateQualityGate(stage, fs.read(artifactPath));
|
|
1772
1936
|
if (issues.length)
|
|
1773
|
-
|
|
1937
|
+
throw new BlockingError(describeBlockingReason("gate_incomplete", {
|
|
1938
|
+
stage,
|
|
1939
|
+
artifactPath,
|
|
1940
|
+
gateIssues: issues,
|
|
1941
|
+
detail: `Quality gate blocked: ${issues.join(" ")}`,
|
|
1942
|
+
}));
|
|
1774
1943
|
}
|
|
1775
1944
|
const { ledger } = readActiveWorkflow(aiw, fs);
|
|
1776
1945
|
const updated = beginStageApproval(ledger, workflowStage, {
|
|
@@ -2519,6 +2688,9 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
2519
2688
|
};
|
|
2520
2689
|
}
|
|
2521
2690
|
catch (error) {
|
|
2691
|
+
if (error instanceof BlockingError && args.includes("--json")) {
|
|
2692
|
+
return { exitCode: 1, output: JSON.stringify(blockingResultToJson(error.result)) };
|
|
2693
|
+
}
|
|
2522
2694
|
return { exitCode: 1, error: error instanceof Error ? error.message : String(error) };
|
|
2523
2695
|
}
|
|
2524
2696
|
}
|
|
@@ -2561,20 +2733,57 @@ function workflowNextAction(ledger) {
|
|
|
2561
2733
|
return `Next action: revise ${next}, rerun its gate, and request human approval.`;
|
|
2562
2734
|
return `Next action: prepare ${next} evidence.`;
|
|
2563
2735
|
}
|
|
2736
|
+
function parseLightEvidence(content) {
|
|
2737
|
+
const value = parseYaml(content);
|
|
2738
|
+
if (!value || typeof value !== "object" || value.schema !== 1)
|
|
2739
|
+
throw new Error("Light-lane evidence checkpoint must be schema version 1.");
|
|
2740
|
+
const record = value;
|
|
2741
|
+
const entries = Array.isArray(record.entries) ? record.entries : [];
|
|
2742
|
+
return {
|
|
2743
|
+
schema: 1,
|
|
2744
|
+
workflowId: String(record.workflowId ?? ""),
|
|
2745
|
+
...(record.ticket !== undefined ? { ticket: String(record.ticket) } : {}),
|
|
2746
|
+
lane: "light",
|
|
2747
|
+
...(record.laneReason !== undefined ? { laneReason: String(record.laneReason) } : {}),
|
|
2748
|
+
entries: entries.map((entry) => ({
|
|
2749
|
+
note: String(entry.note ?? ""),
|
|
2750
|
+
recordedAt: String(entry.recordedAt ?? ""),
|
|
2751
|
+
})),
|
|
2752
|
+
};
|
|
2753
|
+
}
|
|
2564
2754
|
function assertStageReady(aiw, fs, stage) {
|
|
2565
|
-
const { ledger } = readActiveWorkflow(aiw, fs);
|
|
2755
|
+
const { session, ledger } = readActiveWorkflow(aiw, fs);
|
|
2756
|
+
if (effectiveLane(session) === "light")
|
|
2757
|
+
throw new BlockingError(describeBlockingReason("lane_escalation_required", {
|
|
2758
|
+
stage,
|
|
2759
|
+
detail: `Light-lane workflow ${session.workflowId} does not author stage artifacts; escalate to the full lane to continue.`,
|
|
2760
|
+
}));
|
|
2566
2761
|
const unresolved = getUnresolvedPrerequisites(ledger, stage);
|
|
2567
2762
|
if (unresolved.length) {
|
|
2568
2763
|
const pending = unresolved.filter((prerequisite) => ledger.stages[prerequisite]?.status === "pending-human");
|
|
2569
2764
|
if (pending.length)
|
|
2570
|
-
throw new
|
|
2571
|
-
|
|
2765
|
+
throw new BlockingError(describeBlockingReason("stage_prerequisite_pending", {
|
|
2766
|
+
stage,
|
|
2767
|
+
pendingStages: pending,
|
|
2768
|
+
detail: `Cannot enter ${stage}; previous stage(s) are awaiting human approval: ${pending.join(", ")}.`,
|
|
2769
|
+
}));
|
|
2770
|
+
throw new BlockingError(describeBlockingReason("stage_prerequisite_unresolved", {
|
|
2771
|
+
stage,
|
|
2772
|
+
unresolvedStages: unresolved,
|
|
2773
|
+
detail: `Cannot enter ${stage}; previous stages require human approval or an accepted optional skip: ${unresolved.join(", ")}.`,
|
|
2774
|
+
}));
|
|
2572
2775
|
}
|
|
2573
2776
|
const current = ledger.stages[stage];
|
|
2574
2777
|
if (current?.status === "pending-human")
|
|
2575
|
-
throw new
|
|
2778
|
+
throw new BlockingError(describeBlockingReason("stage_awaiting_approval", {
|
|
2779
|
+
stage,
|
|
2780
|
+
detail: `${stage} is awaiting human approval. Approve or reject it before continuing.`,
|
|
2781
|
+
}));
|
|
2576
2782
|
if (current?.status === "approved")
|
|
2577
|
-
throw new
|
|
2783
|
+
throw new BlockingError(describeBlockingReason("stage_already_approved", {
|
|
2784
|
+
stage,
|
|
2785
|
+
detail: `${stage} is already approved. Evidence changes require rerunning its gate.`,
|
|
2786
|
+
}));
|
|
2578
2787
|
}
|
|
2579
2788
|
function approvalArtifactPath(aiw, stage) {
|
|
2580
2789
|
if (stage === "technical-design")
|
package/docs/README.md
CHANGED
|
@@ -22,6 +22,8 @@ AI Workflow is a portable, spec-driven development system for AI coding agents.
|
|
|
22
22
|
- [Plugin authoring](plugin-authoring.md)
|
|
23
23
|
- [Release process](release-process.md)
|
|
24
24
|
- [Release backlog](release-backlog.md)
|
|
25
|
+
- [Continuation inventory](continuation-inventory.md)
|
|
26
|
+
- [Workflow proportionality opportunities](opportunities/README.md) (ODD/RDD analysis, ADR-001)
|
|
25
27
|
- [Backlog](backlog.md)
|
|
26
28
|
|
|
27
29
|
All generated AI Workflow artifacts are written in English unless the user explicitly requests another language.
|
package/docs/architecture.md
CHANGED
|
@@ -45,6 +45,56 @@ Project analysis is hybrid: deterministic detectors establish observable facts,
|
|
|
45
45
|
|
|
46
46
|
## Human workflow approvals
|
|
47
47
|
|
|
48
|
+
Workflows run in one of two lanes. The **full lane** (default, and the only non-interactive default) is the staged approval sequence below. The **light lane** (`aiw task start --lane=light --lane-reason=...`, human-chosen via the resume test — work is small when it could be resumed from the request plus `git diff`) skips durable spec/plan artifacts: validation evidence is recorded per entry (`aiw task evidence`), completion requires at least one entry, and the lane plus rationale are written into the session and approval ledger for audit. Stage commands in the light lane block with a `lane_escalation_required` reason until `aiw task escalate --reason=...` moves the workflow to the full lane; recorded evidence is preserved and linked. Escalation is one-way; the full lane then runs the complete approval sequence.
|
|
49
|
+
|
|
50
|
+
## Current flow
|
|
51
|
+
|
|
52
|
+
```mermaid
|
|
53
|
+
flowchart TD
|
|
54
|
+
REQ["Change request / ticket"] --> RA["aiw risk assess<br/>read-only · content-based · deterministic"]
|
|
55
|
+
RA -->|"passive — docs-only"| LANE
|
|
56
|
+
RA -->|"medium — plain code"| LANE
|
|
57
|
+
RA -->|"high — security · contracts · concurrency ·<br/>data · delivery · untested (risk due: yes)"| FS
|
|
58
|
+
|
|
59
|
+
LANE{"Human lane decision<br/>resume test: resumable from<br/>request + git diff?"}
|
|
60
|
+
LANE -->|"light — human chooses"| LS
|
|
61
|
+
LANE -->|"full — default"| FS
|
|
62
|
+
|
|
63
|
+
subgraph LIGHT["Light lane — no durable artifacts"]
|
|
64
|
+
LS["aiw task start --lane=light --lane-reason<br/>rationale into session + ledger"] --> LE["aiw task evidence --validation<br/>compact checkpoint"]
|
|
65
|
+
LE --> LC["aiw workflow complete<br/>requires at least one entry"]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
subgraph FULL["Full lane — staged SDD, gate + human approve per stage"]
|
|
69
|
+
FS["workflow start"] --> BR["brainstorming"] --> SP["specification"] --> TD["technical-design<br/>optional, skip needs reason"] --> PL["plan"] --> IM["implementation<br/>TDD"] --> VE["verification"] --> RV["code review"] --> TR["traceability"] --> FC["workflow complete"]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
LS -.->|"stage or approve attempted"| B1["Blocked<br/>reason: lane_escalation_required"]
|
|
73
|
+
B1 --> ESC["aiw task escalate --reason<br/>one-way · evidence preserved"]
|
|
74
|
+
ESC --> FS
|
|
75
|
+
|
|
76
|
+
RV -.->|"adversarial-review skills installed"| ADV["judge A + judge B — blind, independent<br/>refuter: corroborated severe blocks<br/>refuted / inconclusive → advisory<br/>unevidenced severe → advisory"]
|
|
77
|
+
|
|
78
|
+
LC --> AUD["Audit trail<br/>approvals ledger · checkpoints · light evidence"]
|
|
79
|
+
FC --> AUD
|
|
80
|
+
|
|
81
|
+
subgraph CI["CI enforcement layer"]
|
|
82
|
+
E1["OPP-004: every block prints a reason code<br/>plus an exact next_command — continuations<br/>are executed by tests before they ship"]
|
|
83
|
+
E2["OPP-006 ratchet: every aiw command named<br/>by skills and READMEs executes in tests"]
|
|
84
|
+
E3["OPP-005 friction benchmark:<br/>in-band / out-of-band / dead-end<br/>budget: zero dead ends"]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
B1 -.-> E1
|
|
88
|
+
E1 -.-> E2 -.-> E3
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Reading notes:
|
|
92
|
+
|
|
93
|
+
- `aiw risk assess` is **advisory**: it informs the lane decision and flags risk due; it never forces a lane or bypasses approval.
|
|
94
|
+
- The light lane is never agent-chosen: `resources/skills/ticket.md` routes it through the host's blocking question widget with the resume test stated verbatim.
|
|
95
|
+
- Every blocking exit on the diagram prints `[reason: <code>]` and either `Next: run \`aiw ...\`` or an explicit terminal statement — the full mapping lives in [Continuation inventory](continuation-inventory.md).
|
|
96
|
+
- A `high` risk assessment with an active workflow points at continuing the workflow (gate and approve); without one it names `aiw workflow start`.
|
|
97
|
+
|
|
48
98
|
The SDD approval contract models the ordered stages brainstorming, specification, technical design, plan, implementation, verification, review, and traceability. Brainstorming and technical design are optional; skipping either requires a human-accepted reason. Every other stage must be approved. Automated quality-gate results are stored separately from human decisions and can only move an artifact to pending review, never directly to approved.
|
|
49
99
|
|
|
50
100
|
Each approval is scoped to one workflow ID and bound to a SHA-256 fingerprint of the exact artifact reviewed, so a later feature cannot reuse earlier approvals. A content change or rejection makes that stage and its descendants stale. The CLI persists the versioned ledger in `.aiw/approvals.yml`, blocks stage commands when prerequisites are unresolved, and keeps passing quality gates distinct from human approval. The generated bootstrap and stage skills require the host's native blocking question widget for human decisions when available, with a waiting chat prompt as fallback. Approval records provide an audit trail but cannot prove the identity of a respondent or independently verify that the host widget was used.
|
package/docs/backlog.md
CHANGED
|
@@ -109,6 +109,19 @@ Priority levels: **P0** blocks the product or makes it unsafe; **P1** is require
|
|
|
109
109
|
|
|
110
110
|
- [x] **ENG-001** Update GitHub Actions to Node 24-native versions. _Acceptance:_ `actions/checkout` and `actions/setup-node` upgraded past v4 so release workflows no longer emit Node.js 20 deprecation warnings; CI and Release workflows pass on `ubuntu-latest` before and after the October 19, 2026 Ubuntu 26 runner migration.
|
|
111
111
|
|
|
112
|
+
## Workflow proportionality (ODD/RDD analysis) — P1–P3
|
|
113
|
+
|
|
114
|
+
Derived from the Gentle-AI ODD/RDD analysis; scope and rejected alternatives recorded in [ADR-001](../.context/adrs/ADR-001-odd-rdd-proportionality-scope.md). Each item links to a self-contained opportunity document under `docs/opportunities/` and can be implemented independently.
|
|
115
|
+
|
|
116
|
+
- [x] **OPP-001** Add a light lane for small, understood work (proportional ceremony). _Acceptance:_ see [OPP-001](../opportunities/OPP-001-proportional-ceremony-light-lane.md); no durable spec/plan artifacts for light tasks, evidence and lane rationale still recorded, explicit escalation to the full lane on scope growth, full lane unchanged.
|
|
117
|
+
- [x] **OPP-002** Add `aiw risk assess` — read-only, content-based risk classification with an exact runnable next command. _Acceptance:_ see [OPP-002](../opportunities/OPP-002-risk-assess-command.md); deterministic and offline by default, no `.aiw/` mutation, escalation raises and never lowers tier, `next_command` executes as printed.
|
|
118
|
+
- [x] **OPP-003** Ship an optional `adversarial-review` package (blind judges + refuter with proof-carrying findings). _Acceptance:_ see [OPP-003](../opportunities/OPP-003-adversarial-review-package.md); severe findings without reproducible evidence degrade to advisory; refutation outcomes are corroborated/refuted/inconclusive with proof references; human acceptance still required.
|
|
119
|
+
- [ ] **OPP-004** Emit typed reason codes and exact continuation commands on every blocking exit. _Acceptance:_ see [OPP-004](../opportunities/OPP-004-exact-continuation-commands.md); every block carries a reason code and either a tested runnable `next_command` or an explicit terminal statement; continuation inventory stays complete in CI.
|
|
120
|
+
- [x] **OPP-005** Add a friction benchmark classifying blocks as in-band / out-of-band / dead-end over packaged-CLI journeys. _Acceptance:_ see [OPP-005](../opportunities/OPP-005-friction-benchmark.md); deterministic classification, continuations actually executed, CI fails on any dead end.
|
|
121
|
+
- [x] **OPP-006** Adopt the emission ≠ usability test policy: messages naming a command must have a test executing it. _Acceptance:_ see [OPP-006](../opportunities/OPP-006-emission-usability-test-policy.md); extraction helper covers emitted and generated command mentions; generated `ai-init` skills' commands are executed in e2e tests.
|
|
122
|
+
- [ ] **007-decision-findings-triage-scope-watch** Decision model — findings-evidence triage and lane scope-watch (advisory-only). _Acceptance:_ see [OPP-007](../opportunities/OPP-007-decision-findings-triage-scope-watch.md); unconfigured = byte-identical offline behavior; advisory flags never block, never lower tiers, never choose lanes.
|
|
123
|
+
- [ ] **008-embedding-covering-tests-judge-context** Embedding retrieval — covering-test detection and judge context scoping. _Acceptance:_ see [OPP-008](../opportunities/OPP-008-embedding-covering-tests-judge-context.md); retrieval only prevents false `untested` flags (absence fails safe toward high); scoped judge prompts labeled as context aids within token ceilings.
|
|
124
|
+
|
|
112
125
|
## Backlog rules
|
|
113
126
|
|
|
114
127
|
- [x] **FND-009** Add self-validation workflow. _Acceptance:_ changes affecting AIW behavior execute against this repository and record generated-output evidence.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Continuation Inventory
|
|
2
|
+
|
|
3
|
+
Every blocking `aiw` exit carries a stable reason code plus either an exact runnable continuation or an explicit terminal statement. A message may name a command only when running that command resolves the block it was printed for (OPP-004, [ADR-001](../.context/adrs/ADR-001-odd-rdd-proportionality-scope.md)).
|
|
4
|
+
|
|
5
|
+
- Codes are declared in `src/blocking-reasons.ts` (`BLOCKING_REASON_CODES`); adding a code requires a definition with a continuation producer and a test.
|
|
6
|
+
- With `--json`, blocking exits return `{ "reason_code": ..., "next_command" | "next_action" | "terminal": ... }`.
|
|
7
|
+
- The table below is generated from the registry; a test in `src/blocking-reasons.test.ts` fails CI when this page drifts from the code.
|
|
8
|
+
|
|
9
|
+
| Reason code | Description | Continuation |
|
|
10
|
+
| ------------------------------- | --------------------------------------------------------------------------------------------- | ---------------- |
|
|
11
|
+
| `gate_evidence_missing` | The quality gate is blocked because the required evidence artifact is missing. | explicit action |
|
|
12
|
+
| `gate_incomplete` | The quality gate found completeness issues in the stage artifact. | explicit action |
|
|
13
|
+
| `stage_prerequisite_pending` | A previous stage is awaiting human approval before this stage can start. | runnable command |
|
|
14
|
+
| `stage_prerequisite_unresolved` | Previous stages need human approval or an accepted optional skip before this stage can start. | runnable command |
|
|
15
|
+
| `stage_awaiting_approval` | This stage is awaiting human approval; approve or reject it before continuing. | runnable command |
|
|
16
|
+
| `stage_already_approved` | This stage is already approved; changed evidence requires rerunning its quality gate. | runnable command |
|
|
17
|
+
| `approval_evidence_missing` | Approval cannot be recorded because the stage evidence artifact is missing. | explicit action |
|
|
18
|
+
| `approval_stale` | The artifact changed after review; its approval is stale and the gate must run again. | runnable command |
|
|
19
|
+
| `approval_gate_missing` | No passing quality gate is recorded for this stage; gate it before approving. | runnable command |
|
|
20
|
+
| `lane_escalation_required` | Light-lane workflows do not author stage artifacts; escalate to the full lane to continue. | explicit action |
|
|
21
|
+
| `light_evidence_missing` | A light-lane workflow cannot complete without at least one recorded validation entry. | explicit action |
|
|
@@ -40,3 +40,7 @@ Observed behavior on real plans: an evidence-free plan scored `gate: ready` with
|
|
|
40
40
|
## Hard boundary (unchanged)
|
|
41
41
|
|
|
42
42
|
Decision-model output is `inferred` evidence with confidence, never `confirmed`. It can move an artifact to _pending review_, never to _approved_, and cannot replace the fingerprinted human approval ledger. This matches the existing approval contract and keeps the audit trail human-owned.
|
|
43
|
+
|
|
44
|
+
## Planned integration (OPP-007)
|
|
45
|
+
|
|
46
|
+
The next planned decision-model roles follow the same contracts: findings-evidence triage for severe review findings (does the cited proof support the claim — advisory flag, the refuter still decides) and a lane scope-watch that flags mismatch between a chosen light lane and deterministic risk signals. Both stay advisory-only with the deterministic floor unchanged; unconfigured behavior is byte-identical. See [OPP-007](opportunities/OPP-007-decision-findings-triage-scope-watch.md).
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# OPP-001: Proportional ceremony — light lane for small, understood work
|
|
2
|
+
|
|
3
|
+
- Status: Implemented (2026-10-30, plan Wave 4)
|
|
4
|
+
- Priority: P2
|
|
5
|
+
- Added: 2026-10-30
|
|
6
|
+
- Decision: [ADR-001](../../.context/adrs/ADR-001-odd-rdd-proportionality-scope.md), Decision A
|
|
7
|
+
|
|
8
|
+
## Problem
|
|
9
|
+
|
|
10
|
+
Every AI Workflow task, regardless of size or risk, traverses the same staged pipeline (brainstorm → specification → ADR → plan → implementation → verification → review → traceability), each stage producing durable artifacts under `.aiw/generated/`. For small, well-understood changes this generates artifacts nobody reads, and users bypass the workflow manually — which then produces no evidence at all. Gentle-AI retired SDD for exactly this reason; the market signal is that uniform ceremony causes abandonment in daily use.
|
|
11
|
+
|
|
12
|
+
## Source concept (ODD)
|
|
13
|
+
|
|
14
|
+
ODD classifies task size with the **resume test**: work is _small_ when it is understood, risk is contained, and the work could be resumed from the original request plus `git diff`. Small work creates **no durable task artifacts**; substantial work creates exactly one feature document before the first source write. ODD also holds that findings never silently expand authorization, and scope growth is an explicit event.
|
|
15
|
+
|
|
16
|
+
References:
|
|
17
|
+
|
|
18
|
+
- Gentle-AI `docs/usage.md`, section "Organic Driven Development (ODD)" — protocol steps 4 (Classify) and 5 (Track before the first write)
|
|
19
|
+
- Gentle-AI `docs/intended-usage.md` — "Small, understood changes need no persistent task artifact"
|
|
20
|
+
|
|
21
|
+
## Proposal
|
|
22
|
+
|
|
23
|
+
Add a **light lane** alongside the existing full SDD lane:
|
|
24
|
+
|
|
25
|
+
1. New flag `aiw task start --lane=light` (full lane remains the default).
|
|
26
|
+
2. Entry criteria, evaluated and recorded at start — all must hold:
|
|
27
|
+
- the change is understood well enough to resume from the request plus `git diff` (the ODD resume test, stated verbatim in the command output so the human makes the call);
|
|
28
|
+
- expected blast radius is contained (no new requirement IDs, no architecture decision needed);
|
|
29
|
+
- TDD applies or an exception is recorded with reason.
|
|
30
|
+
3. Light-lane behavior:
|
|
31
|
+
- No durable spec/plan/report artifacts are scaffolded.
|
|
32
|
+
- Evidence is still required: task validation commands run and completion evidence is recorded in `.aiw/checkpoints/` (compact form, linked to the ticket if present).
|
|
33
|
+
- The lane decision and its entry criteria are persisted, so audits can see why the light lane was used.
|
|
34
|
+
4. Escalation: if scope grows beyond the entry criteria (new requirement, ADR needed, risk found), the task must escalate to the full lane; the escalation is an explicit recorded event, never a silent expansion. Escalation preserves already-recorded evidence.
|
|
35
|
+
|
|
36
|
+
## Affected areas
|
|
37
|
+
|
|
38
|
+
- `src/workflow.ts`, `src/types.ts` (lane state, escalation event)
|
|
39
|
+
- CLI: task/workflow commands (`src/cli.ts`)
|
|
40
|
+
- Stage skills and templates rendered through adapters (resources that describe the SDD sequence)
|
|
41
|
+
- `docs/README.md` (main README workflow section), `docs/architecture.md` (approval contract description)
|
|
42
|
+
- Checkpoint/evidence recording (`src/evidence.ts`, `src/human-approval.ts` interactions)
|
|
43
|
+
|
|
44
|
+
## Acceptance criteria
|
|
45
|
+
|
|
46
|
+
- Given a task started with `--lane=light`, when no scope growth occurs, then completion requires no `specification.md`/`implementation-plan.md` artifacts and records validation evidence plus the lane rationale in checkpoints.
|
|
47
|
+
- Given a light-lane task, when scope growth is detected (or declared), then escalation to the full lane is required before further stage commands run, and prior evidence is preserved and linked.
|
|
48
|
+
- Given the full lane (default), when a task starts, then behavior is unchanged from today.
|
|
49
|
+
- Given a light-lane completion, when `.aiw/approvals.yml` is inspected, then the ledger shows the light-lane rationale, not a missing approval.
|
|
50
|
+
- Non-interactive runs without an explicit lane default to the full lane.
|
|
51
|
+
|
|
52
|
+
## Non-goals
|
|
53
|
+
|
|
54
|
+
- No global "workflow off" switch (see ADR-001 Decision B).
|
|
55
|
+
- No change to full-lane stage order, gates, or approval semantics.
|
|
56
|
+
- No automatic lane detection that overrides the human's classification; automation may only suggest.
|
|
57
|
+
|
|
58
|
+
## Open questions
|
|
59
|
+
|
|
60
|
+
- Should the light lane allow skipping the brainstorm/spec stages only, or also merge implementation + verification evidence into a single checkpoint artifact?
|
|
61
|
+
- Should escalation be self-declared by the agent, human-confirmed, or both (recommend both: agent proposes, human confirms)?
|
|
62
|
+
- Does the lane need representation in `traceability` output (e.g., a light-lane marker on the ticket)?
|