cclaw-cli 6.1.1 → 6.3.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/README.md +2 -2
- package/dist/artifact-linter/brainstorm.js +13 -13
- package/dist/artifact-linter/design.js +5 -5
- package/dist/artifact-linter/scope.js +3 -3
- package/dist/artifact-linter/shared.d.ts +18 -19
- package/dist/artifact-linter/shared.js +34 -31
- package/dist/artifact-linter.js +4 -0
- package/dist/content/hooks.js +154 -2
- package/dist/content/skills-elicitation.js +8 -19
- package/dist/content/skills.js +1 -0
- package/dist/content/stage-schema.d.ts +3 -3
- package/dist/content/stage-schema.js +31 -6
- package/dist/content/stages/brainstorm.js +5 -5
- package/dist/content/stages/design.js +1 -1
- package/dist/content/stages/schema-types.d.ts +6 -0
- package/dist/content/stages/scope.js +2 -2
- package/dist/content/start-command.d.ts +2 -2
- package/dist/content/start-command.js +23 -18
- package/dist/content/subagents.js +1 -1
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +1 -0
- package/dist/delegation.js +2 -2
- package/dist/flow-state.d.ts +14 -1
- package/dist/flow-state.js +6 -1
- package/dist/gate-evidence.js +4 -3
- package/dist/internal/advance-stage/advance.js +20 -4
- package/dist/internal/advance-stage/parsers.d.ts +2 -1
- package/dist/internal/advance-stage/parsers.js +12 -1
- package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +21 -0
- package/dist/internal/advance-stage/proactive-delegation-trace.js +60 -0
- package/dist/internal/advance-stage/start-flow.d.ts +3 -1
- package/dist/internal/advance-stage/start-flow.js +81 -2
- package/dist/internal/advance-stage/verify.d.ts +0 -8
- package/dist/internal/advance-stage/verify.js +2 -30
- package/dist/run-persistence.js +37 -2
- package/dist/track-heuristics.d.ts +2 -2
- package/dist/track-heuristics.js +11 -6
- package/dist/types.d.ts +2 -0
- package/dist/types.js +1 -0
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { stageSchema } from "../../content/stage-schema.js";
|
|
|
11
11
|
import { extractReviewLoopEnvelopeFromArtifact } from "../../content/review-loop.js";
|
|
12
12
|
import { unique } from "./helpers.js";
|
|
13
13
|
import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, reviewLoopEnvelopeExample, validateGateEvidenceShape } from "./review-loop.js";
|
|
14
|
-
import { ensureProactiveDelegationTrace } from "./
|
|
14
|
+
import { ensureProactiveDelegationTrace } from "./proactive-delegation-trace.js";
|
|
15
15
|
function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGuards, selectedTransitionGuards) {
|
|
16
16
|
const natural = transitionTargets[0] ?? null;
|
|
17
17
|
const specialTargets = transitionTargets.filter((target) => target !== natural);
|
|
@@ -299,7 +299,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
299
299
|
io.stderr.write(`cclaw internal advance-stage: current stage is "${flowState.currentStage}", not "${args.stage}".\n`);
|
|
300
300
|
return 1;
|
|
301
301
|
}
|
|
302
|
-
const schema = stageSchema(args.stage, flowState.track);
|
|
302
|
+
const schema = stageSchema(args.stage, flowState.track, flowState.discoveryMode, flowState.taskClass ?? null);
|
|
303
303
|
const requiredGateIds = schema.requiredGates
|
|
304
304
|
.filter((gate) => gate.tier === "required")
|
|
305
305
|
.map((gate) => gate.id);
|
|
@@ -434,6 +434,19 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
434
434
|
extraStageFlags: args.skipQuestions ? ["--skip-questions"] : undefined
|
|
435
435
|
});
|
|
436
436
|
if (!validation.ok) {
|
|
437
|
+
const delegationFailureCount = validation.delegation.missing.length +
|
|
438
|
+
validation.delegation.missingEvidence.length +
|
|
439
|
+
validation.delegation.missingDispatchProof.length +
|
|
440
|
+
validation.delegation.legacyInferredCompletions.length +
|
|
441
|
+
validation.delegation.corruptEventLines.length +
|
|
442
|
+
validation.delegation.staleWorkers.length;
|
|
443
|
+
const gatesFailureCount = validation.gates.issues.length;
|
|
444
|
+
const closureFailureCount = validation.completedStages.issues.length;
|
|
445
|
+
const failureCounts = {
|
|
446
|
+
delegation: delegationFailureCount,
|
|
447
|
+
gates: gatesFailureCount,
|
|
448
|
+
closure: closureFailureCount
|
|
449
|
+
};
|
|
437
450
|
const ledgerForDiag = await readDelegationLedger(projectRoot).catch(() => ({ entries: [] }));
|
|
438
451
|
const eventsForDiag = await readDelegationEvents(projectRoot).catch(() => ({ events: [], corruptLines: [] }));
|
|
439
452
|
const ledgerEntriesText = await fs.readFile(path.join(projectRoot, ".cclaw/state/delegation-events.jsonl"), "utf8").catch(() => "");
|
|
@@ -482,6 +495,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
482
495
|
command: "advance-stage",
|
|
483
496
|
stage: args.stage,
|
|
484
497
|
kind: "validation-failed",
|
|
498
|
+
failureCounts,
|
|
485
499
|
delegation: validation.delegation,
|
|
486
500
|
gates: validation.gates,
|
|
487
501
|
completedStages: validation.completedStages,
|
|
@@ -493,7 +507,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
493
507
|
nextActions
|
|
494
508
|
})}\n`);
|
|
495
509
|
}
|
|
496
|
-
io.stderr.write(`cclaw internal advance-stage: validation failed for stage "${args.stage}".\n`);
|
|
510
|
+
io.stderr.write(`cclaw internal advance-stage: validation failed for stage "${args.stage}" (delegation=${failureCounts.delegation}, gates=${failureCounts.gates}, closure=${failureCounts.closure}).\n`);
|
|
497
511
|
if (validation.delegation.missing.length > 0) {
|
|
498
512
|
io.stderr.write(`- missing delegations: ${validation.delegation.missing.join(", ")}\n`);
|
|
499
513
|
io.stderr.write(` next action: run the named agent(s) for this stage, or rerun with --waive-delegation=${validation.delegation.missing.join(",")} --waiver-reason="<why safe>" only when the user accepts the safety trade-off.\n`);
|
|
@@ -530,7 +544,9 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
530
544
|
}
|
|
531
545
|
const proactiveTrace = await ensureProactiveDelegationTrace(projectRoot, args.stage, {
|
|
532
546
|
acceptWaiver: args.acceptProactiveWaiver,
|
|
533
|
-
waiverReason: args.acceptProactiveWaiverReason
|
|
547
|
+
waiverReason: args.acceptProactiveWaiverReason,
|
|
548
|
+
discoveryMode: flowState.discoveryMode,
|
|
549
|
+
repoSignals: flowState.repoSignals
|
|
534
550
|
});
|
|
535
551
|
if (proactiveTrace.missingRules.length > 0) {
|
|
536
552
|
const missingSummary = proactiveTrace.missingRules
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type FlowStage, type FlowTrack } from "../../types.js";
|
|
1
|
+
import { type DiscoveryMode, type FlowStage, type FlowTrack } from "../../types.js";
|
|
2
2
|
import type { ArchiveDisposition } from "../../runs.js";
|
|
3
3
|
export interface AdvanceStageArgs {
|
|
4
4
|
stage: FlowStage;
|
|
@@ -32,6 +32,7 @@ export interface RewindArgs {
|
|
|
32
32
|
}
|
|
33
33
|
export interface StartFlowArgs {
|
|
34
34
|
track: FlowTrack;
|
|
35
|
+
discoveryMode: DiscoveryMode;
|
|
35
36
|
className?: string;
|
|
36
37
|
prompt?: string;
|
|
37
38
|
reason?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FLOW_STAGES } from "../../types.js";
|
|
2
|
-
import { isFlowTrack } from "../../flow-state.js";
|
|
2
|
+
import { isDiscoveryMode, isFlowTrack } from "../../flow-state.js";
|
|
3
3
|
import { isFlowStageValue, parseCsv, parseEvidenceByGate, unique } from "./helpers.js";
|
|
4
4
|
export function parseAdvanceStageArgs(tokens) {
|
|
5
5
|
const [stageRaw, ...flagTokens] = tokens;
|
|
@@ -204,6 +204,7 @@ export function parseHookArgs(tokens) {
|
|
|
204
204
|
}
|
|
205
205
|
export function parseStartFlowArgs(tokens) {
|
|
206
206
|
let track;
|
|
207
|
+
let discoveryMode = "guided";
|
|
207
208
|
let className;
|
|
208
209
|
let prompt;
|
|
209
210
|
let reason;
|
|
@@ -245,6 +246,15 @@ export function parseStartFlowArgs(tokens) {
|
|
|
245
246
|
track = raw;
|
|
246
247
|
continue;
|
|
247
248
|
}
|
|
249
|
+
if (token === "--discovery-mode" || token.startsWith("--discovery-mode=")) {
|
|
250
|
+
const raw = readValue("--discovery-mode").trim();
|
|
251
|
+
const normalized = raw.toLowerCase();
|
|
252
|
+
if (!isDiscoveryMode(normalized)) {
|
|
253
|
+
throw new Error(`--discovery-mode must be one of: lean, guided, deep.`);
|
|
254
|
+
}
|
|
255
|
+
discoveryMode = normalized;
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
248
258
|
if (token === "--class" || token.startsWith("--class=")) {
|
|
249
259
|
className = readValue("--class").trim();
|
|
250
260
|
continue;
|
|
@@ -281,6 +291,7 @@ export function parseStartFlowArgs(tokens) {
|
|
|
281
291
|
}
|
|
282
292
|
return {
|
|
283
293
|
track,
|
|
294
|
+
discoveryMode,
|
|
284
295
|
className,
|
|
285
296
|
prompt,
|
|
286
297
|
reason,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type StageAutoSubagentDispatch } from "../../content/stage-schema.js";
|
|
2
|
+
import type { DiscoveryMode, FlowStage } from "../../types.js";
|
|
3
|
+
import type { RepoSignals } from "../../flow-state.js";
|
|
4
|
+
export interface ProactiveDelegationTraceResult {
|
|
5
|
+
missingRules: StageAutoSubagentDispatch[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Ensure every proactive dispatch rule for the stage has a ledger row for the
|
|
9
|
+
* active run, or an explicit user-flag waiver.
|
|
10
|
+
*
|
|
11
|
+
* Lean/guided discovery on early elicitation stages intentionally does not
|
|
12
|
+
* require a full proactive trace: specialists run only when triggers warrant
|
|
13
|
+
* them. Deep discovery keeps the blanket trace so mandatory + proactive
|
|
14
|
+
* coverage stays auditably complete before advance.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ensureProactiveDelegationTrace(projectRoot: string, stage: FlowStage, options: {
|
|
17
|
+
acceptWaiver: boolean;
|
|
18
|
+
waiverReason?: string;
|
|
19
|
+
discoveryMode: DiscoveryMode;
|
|
20
|
+
repoSignals?: RepoSignals;
|
|
21
|
+
}): Promise<ProactiveDelegationTraceResult>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { appendDelegation, readDelegationLedger } from "../../delegation.js";
|
|
2
|
+
import { stageAutoSubagentDispatch } from "../../content/stage-schema.js";
|
|
3
|
+
function isEarlyElicitationStage(stage) {
|
|
4
|
+
return stage === "brainstorm" || stage === "scope" || stage === "design";
|
|
5
|
+
}
|
|
6
|
+
function isSparseRepoForResearcherSkip(repoSignals) {
|
|
7
|
+
if (!repoSignals)
|
|
8
|
+
return false;
|
|
9
|
+
return repoSignals.fileCount < 5 && !repoSignals.hasReadme && !repoSignals.hasPackageManifest;
|
|
10
|
+
}
|
|
11
|
+
function skipRepoDependentProactiveRule(rule, stage, discoveryMode, repoSignals) {
|
|
12
|
+
if (discoveryMode !== "deep")
|
|
13
|
+
return false;
|
|
14
|
+
if (stage !== "brainstorm" && stage !== "scope")
|
|
15
|
+
return false;
|
|
16
|
+
if (!rule.dependsOnInternalRepoSignals)
|
|
17
|
+
return false;
|
|
18
|
+
return isSparseRepoForResearcherSkip(repoSignals);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Ensure every proactive dispatch rule for the stage has a ledger row for the
|
|
22
|
+
* active run, or an explicit user-flag waiver.
|
|
23
|
+
*
|
|
24
|
+
* Lean/guided discovery on early elicitation stages intentionally does not
|
|
25
|
+
* require a full proactive trace: specialists run only when triggers warrant
|
|
26
|
+
* them. Deep discovery keeps the blanket trace so mandatory + proactive
|
|
27
|
+
* coverage stays auditably complete before advance.
|
|
28
|
+
*/
|
|
29
|
+
export async function ensureProactiveDelegationTrace(projectRoot, stage, options) {
|
|
30
|
+
if (isEarlyElicitationStage(stage) && (options.discoveryMode === "lean" || options.discoveryMode === "guided")) {
|
|
31
|
+
return { missingRules: [] };
|
|
32
|
+
}
|
|
33
|
+
const proactiveRules = stageAutoSubagentDispatch(stage)
|
|
34
|
+
.filter((rule) => rule.mode === "proactive")
|
|
35
|
+
.filter((rule) => !skipRepoDependentProactiveRule(rule, stage, options.discoveryMode, options.repoSignals));
|
|
36
|
+
if (proactiveRules.length === 0)
|
|
37
|
+
return { missingRules: [] };
|
|
38
|
+
const ledger = await readDelegationLedger(projectRoot);
|
|
39
|
+
const currentRunEntries = ledger.entries.filter((entry) => entry.runId === ledger.runId);
|
|
40
|
+
const missingRules = proactiveRules.filter((rule) => !currentRunEntries.some((entry) => entry.stage === stage && entry.agent === rule.agent && entry.mode === "proactive"));
|
|
41
|
+
if (missingRules.length === 0)
|
|
42
|
+
return { missingRules: [] };
|
|
43
|
+
if (!options.acceptWaiver)
|
|
44
|
+
return { missingRules };
|
|
45
|
+
const waiverReason = options.waiverReason?.trim() || "accepted via --accept-proactive-waiver";
|
|
46
|
+
for (const rule of missingRules) {
|
|
47
|
+
await appendDelegation(projectRoot, {
|
|
48
|
+
stage,
|
|
49
|
+
agent: rule.agent,
|
|
50
|
+
mode: "proactive",
|
|
51
|
+
status: "waived",
|
|
52
|
+
waiverReason,
|
|
53
|
+
acceptedBy: "user-flag",
|
|
54
|
+
conditionTrigger: rule.when,
|
|
55
|
+
skill: rule.skill,
|
|
56
|
+
ts: new Date().toISOString()
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return { missingRules: [] };
|
|
60
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { type FlowState } from "../../flow-state.js";
|
|
1
|
+
import { type FlowState, type RepoSignals } from "../../flow-state.js";
|
|
2
2
|
import type { StartFlowArgs } from "./parsers.js";
|
|
3
3
|
import type { Writable } from "node:stream";
|
|
4
4
|
interface InternalIo {
|
|
5
5
|
stdout: Writable;
|
|
6
6
|
stderr: Writable;
|
|
7
7
|
}
|
|
8
|
+
/** One-pass repo snapshot (max ~200 files, skips `node_modules`/`.git`). */
|
|
9
|
+
export declare function collectRepoSignals(projectRoot: string): Promise<RepoSignals>;
|
|
8
10
|
export declare function discoverStartFlowContext(projectRoot: string): Promise<string[]>;
|
|
9
11
|
export declare function appendIdeaArtifact(projectRoot: string, args: StartFlowArgs, previous?: FlowState): Promise<void>;
|
|
10
12
|
export declare function runStartFlow(projectRoot: string, args: StartFlowArgs, io: InternalIo): Promise<number>;
|
|
@@ -7,6 +7,74 @@ import { listExistingFiles, listFilesUnder, pathExists } from "./helpers.js";
|
|
|
7
7
|
import { TRACK_STAGES } from "../../types.js";
|
|
8
8
|
import { buildValidationReport } from "./advance.js";
|
|
9
9
|
import { carriedCompletedStageCatalog, completedStageClosureEvidenceIssues, firstIncompleteStageForTrack } from "./verify.js";
|
|
10
|
+
function resolveTaskClass(className, fallback) {
|
|
11
|
+
if (className === "software-standard" || className === "software-trivial" || className === "software-bugfix") {
|
|
12
|
+
return className;
|
|
13
|
+
}
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
const REPO_SIGNAL_SKIP_DIRS = new Set(["node_modules", ".git"]);
|
|
17
|
+
/** One-pass repo snapshot (max ~200 files, skips `node_modules`/`.git`). */
|
|
18
|
+
export async function collectRepoSignals(projectRoot) {
|
|
19
|
+
const capturedAt = new Date().toISOString();
|
|
20
|
+
const cap = 200;
|
|
21
|
+
let fileCount = 0;
|
|
22
|
+
async function visit(absDir, depth) {
|
|
23
|
+
if (fileCount >= cap)
|
|
24
|
+
return;
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = await fs.readdir(absDir, { withFileTypes: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const ent of entries) {
|
|
33
|
+
if (fileCount >= cap)
|
|
34
|
+
return;
|
|
35
|
+
const name = ent.name;
|
|
36
|
+
if (REPO_SIGNAL_SKIP_DIRS.has(name))
|
|
37
|
+
continue;
|
|
38
|
+
const abs = path.join(absDir, name);
|
|
39
|
+
if (ent.isFile()) {
|
|
40
|
+
fileCount += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ent.isDirectory() && depth < 1) {
|
|
44
|
+
await visit(abs, depth + 1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let hasReadme = false;
|
|
49
|
+
let hasPackageManifest = false;
|
|
50
|
+
for (const fname of ["README.md", "readme.md", "Readme.md"]) {
|
|
51
|
+
try {
|
|
52
|
+
const st = await fs.stat(path.join(projectRoot, fname));
|
|
53
|
+
if (st.isFile())
|
|
54
|
+
hasReadme = true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const manifest of ["package.json", "pyproject.toml", "Cargo.toml"]) {
|
|
61
|
+
try {
|
|
62
|
+
const st = await fs.stat(path.join(projectRoot, manifest));
|
|
63
|
+
if (st.isFile())
|
|
64
|
+
hasPackageManifest = true;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
await visit(projectRoot, 0);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
fileCount = Math.min(fileCount, cap);
|
|
75
|
+
}
|
|
76
|
+
return { fileCount, hasReadme, hasPackageManifest, capturedAt };
|
|
77
|
+
}
|
|
10
78
|
export async function discoverStartFlowContext(projectRoot) {
|
|
11
79
|
const lines = [];
|
|
12
80
|
const seedFiles = (await listFilesUnder(projectRoot, path.join(RUNTIME_ROOT, "seeds"), 10))
|
|
@@ -58,6 +126,7 @@ export async function appendIdeaArtifact(projectRoot, args, previous) {
|
|
|
58
126
|
`- From: ${previous?.track ?? "unknown"}`,
|
|
59
127
|
`- To: ${args.track}`,
|
|
60
128
|
`- Class: ${args.className || "unspecified"}`,
|
|
129
|
+
`- Discovery mode: ${args.discoveryMode}`,
|
|
61
130
|
`- Reason: ${args.reason || "unspecified"}`
|
|
62
131
|
].join("\n") + "\n";
|
|
63
132
|
await fs.appendFile(artifactPath, entry, "utf8");
|
|
@@ -68,6 +137,7 @@ export async function appendIdeaArtifact(projectRoot, args, previous) {
|
|
|
68
137
|
"# Idea",
|
|
69
138
|
`Class: ${args.className || "unspecified"}`,
|
|
70
139
|
`Track: ${args.track}${args.reason ? ` (${args.reason})` : ""}`,
|
|
140
|
+
`Discovery mode: ${args.discoveryMode}`,
|
|
71
141
|
`Stack: ${args.stack || "unknown"}`,
|
|
72
142
|
"",
|
|
73
143
|
"## User prompt",
|
|
@@ -85,10 +155,11 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
85
155
|
io.stderr.write("cclaw internal start-flow: refusing to reset an active flow with completed stages without --force-reset. Ask the user before resetting.\n");
|
|
86
156
|
return 1;
|
|
87
157
|
}
|
|
158
|
+
const nextTaskClass = resolveTaskClass(args.className, current.taskClass);
|
|
88
159
|
let nextState;
|
|
89
160
|
if (args.reclassify) {
|
|
90
161
|
const completedInNewTrack = current.completedStages.filter((stage) => TRACK_STAGES[args.track].includes(stage));
|
|
91
|
-
const fresh = createInitialFlowState({ activeRunId: current.activeRunId, track: args.track });
|
|
162
|
+
const fresh = createInitialFlowState({ activeRunId: current.activeRunId, track: args.track, discoveryMode: args.discoveryMode });
|
|
92
163
|
const stageGateCatalog = { ...fresh.stageGateCatalog };
|
|
93
164
|
const guardEvidence = {};
|
|
94
165
|
for (const stage of completedInNewTrack) {
|
|
@@ -98,6 +169,7 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
98
169
|
}
|
|
99
170
|
nextState = {
|
|
100
171
|
...fresh,
|
|
172
|
+
...(nextTaskClass !== undefined ? { taskClass: nextTaskClass } : {}),
|
|
101
173
|
completedStages: completedInNewTrack,
|
|
102
174
|
currentStage: firstIncompleteStageForTrack(args.track, completedInNewTrack),
|
|
103
175
|
guardEvidence,
|
|
@@ -117,7 +189,10 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
117
189
|
}
|
|
118
190
|
}
|
|
119
191
|
else {
|
|
120
|
-
nextState = createInitialFlowState({ track: args.track });
|
|
192
|
+
nextState = createInitialFlowState({ track: args.track, discoveryMode: args.discoveryMode });
|
|
193
|
+
if (nextTaskClass !== undefined) {
|
|
194
|
+
nextState = { ...nextState, taskClass: nextTaskClass };
|
|
195
|
+
}
|
|
121
196
|
}
|
|
122
197
|
if (args.fromIdeaArtifact) {
|
|
123
198
|
const existingHints = nextState.interactionHints ?? {};
|
|
@@ -132,6 +207,8 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
132
207
|
}
|
|
133
208
|
};
|
|
134
209
|
}
|
|
210
|
+
const repoSignals = await collectRepoSignals(projectRoot);
|
|
211
|
+
nextState = { ...nextState, repoSignals };
|
|
135
212
|
await writeFlowState(projectRoot, nextState, { allowReset: true });
|
|
136
213
|
await appendIdeaArtifact(projectRoot, args, current);
|
|
137
214
|
if (!args.quiet) {
|
|
@@ -140,6 +217,8 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
140
217
|
command: "start-flow",
|
|
141
218
|
reclassify: args.reclassify,
|
|
142
219
|
track: nextState.track,
|
|
220
|
+
discoveryMode: nextState.discoveryMode,
|
|
221
|
+
taskClass: nextState.taskClass ?? null,
|
|
143
222
|
currentStage: nextState.currentStage,
|
|
144
223
|
skippedStages: nextState.skippedStages,
|
|
145
224
|
activeRunId: nextState.activeRunId
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type StageAutoSubagentDispatch } from "../../content/stage-schema.js";
|
|
2
1
|
import { type FlowState, type StageGateState } from "../../flow-state.js";
|
|
3
2
|
import { type FlowStage, type FlowTrack } from "../../types.js";
|
|
4
3
|
import type { VerifyCurrentStateArgs, VerifyFlowStateDiffArgs } from "./parsers.js";
|
|
@@ -7,9 +6,6 @@ interface InternalIo {
|
|
|
7
6
|
stdout: Writable;
|
|
8
7
|
stderr: Writable;
|
|
9
8
|
}
|
|
10
|
-
interface ProactiveDelegationTraceResult {
|
|
11
|
-
missingRules: StageAutoSubagentDispatch[];
|
|
12
|
-
}
|
|
13
9
|
export declare function runVerifyFlowStateDiff(projectRoot: string, args: VerifyFlowStateDiffArgs, io: InternalIo): Promise<number>;
|
|
14
10
|
export declare function runVerifyCurrentState(projectRoot: string, args: VerifyCurrentStateArgs, io: InternalIo): Promise<number>;
|
|
15
11
|
export declare function firstIncompleteStageForTrack(track: FlowTrack, completedStages: FlowStage[]): FlowStage;
|
|
@@ -18,10 +14,6 @@ export declare function carriedCompletedStageCatalog(current: FlowState, fresh:
|
|
|
18
14
|
evidence: Record<string, string>;
|
|
19
15
|
};
|
|
20
16
|
export declare function completedStageClosureEvidenceIssues(flowState: FlowState): string[];
|
|
21
|
-
export declare function ensureProactiveDelegationTrace(projectRoot: string, stage: FlowStage, options: {
|
|
22
|
-
acceptWaiver: boolean;
|
|
23
|
-
waiverReason?: string;
|
|
24
|
-
}): Promise<ProactiveDelegationTraceResult>;
|
|
25
17
|
export declare function pathExists(projectRoot: string, relPath: string): Promise<boolean>;
|
|
26
18
|
export declare function listExistingFiles(projectRoot: string, relPaths: string[]): Promise<string[]>;
|
|
27
19
|
export declare function listFilesUnder(projectRoot: string, relDir: string, limit?: number): Promise<string[]>;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { RUNTIME_ROOT } from "../../constants.js";
|
|
3
|
-
import {
|
|
4
|
-
import { appendDelegation, readDelegationLedger } from "../../delegation.js";
|
|
3
|
+
import { stageSchema } from "../../content/stage-schema.js";
|
|
5
4
|
import { readFlowState } from "../../runs.js";
|
|
6
5
|
import { TRACK_STAGES } from "../../types.js";
|
|
7
6
|
import { coerceCandidateFlowState } from "./flow-state-coercion.js";
|
|
@@ -87,7 +86,7 @@ export function carriedCompletedStageCatalog(current, fresh, stage) {
|
|
|
87
86
|
export function completedStageClosureEvidenceIssues(flowState) {
|
|
88
87
|
const issues = [];
|
|
89
88
|
for (const stage of flowState.completedStages) {
|
|
90
|
-
const schema = stageSchema(stage, flowState.track);
|
|
89
|
+
const schema = stageSchema(stage, flowState.track, flowState.discoveryMode, flowState.taskClass ?? null);
|
|
91
90
|
const catalog = flowState.stageGateCatalog[stage];
|
|
92
91
|
const required = schema.requiredGates
|
|
93
92
|
.filter((gate) => gate.tier === "required")
|
|
@@ -103,33 +102,6 @@ export function completedStageClosureEvidenceIssues(flowState) {
|
|
|
103
102
|
}
|
|
104
103
|
return issues;
|
|
105
104
|
}
|
|
106
|
-
export async function ensureProactiveDelegationTrace(projectRoot, stage, options) {
|
|
107
|
-
const proactiveRules = stageAutoSubagentDispatch(stage).filter((rule) => rule.mode === "proactive");
|
|
108
|
-
if (proactiveRules.length === 0)
|
|
109
|
-
return { missingRules: [] };
|
|
110
|
-
const ledger = await readDelegationLedger(projectRoot);
|
|
111
|
-
const currentRunEntries = ledger.entries.filter((entry) => entry.runId === ledger.runId);
|
|
112
|
-
const missingRules = proactiveRules.filter((rule) => !currentRunEntries.some((entry) => entry.stage === stage && entry.agent === rule.agent && entry.mode === "proactive"));
|
|
113
|
-
if (missingRules.length === 0)
|
|
114
|
-
return { missingRules: [] };
|
|
115
|
-
if (!options.acceptWaiver)
|
|
116
|
-
return { missingRules };
|
|
117
|
-
const waiverReason = options.waiverReason?.trim() || "accepted via --accept-proactive-waiver";
|
|
118
|
-
for (const rule of missingRules) {
|
|
119
|
-
await appendDelegation(projectRoot, {
|
|
120
|
-
stage,
|
|
121
|
-
agent: rule.agent,
|
|
122
|
-
mode: "proactive",
|
|
123
|
-
status: "waived",
|
|
124
|
-
waiverReason,
|
|
125
|
-
acceptedBy: "user-flag",
|
|
126
|
-
conditionTrigger: rule.when,
|
|
127
|
-
skill: rule.skill,
|
|
128
|
-
ts: new Date().toISOString()
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
return { missingRules: [] };
|
|
132
|
-
}
|
|
133
105
|
export async function pathExists(projectRoot, relPath) {
|
|
134
106
|
try {
|
|
135
107
|
await fs.stat(path.join(projectRoot, relPath));
|
package/dist/run-persistence.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { RUNTIME_ROOT } from "./constants.js";
|
|
4
|
-
import { nextStage, createInitialCloseoutState, createInitialFlowState, FLOW_STATE_SCHEMA_VERSION, isFlowTrack, skippedStagesForTrack, SHIP_SUBSTATES } from "./flow-state.js";
|
|
4
|
+
import { nextStage, createInitialCloseoutState, createInitialFlowState, FLOW_STATE_SCHEMA_VERSION, isDiscoveryMode, isFlowTrack, skippedStagesForTrack, SHIP_SUBSTATES } from "./flow-state.js";
|
|
5
5
|
import { ensureDir, exists, withDirectoryLock, writeFileSafe } from "./fs-utils.js";
|
|
6
6
|
import { FLOW_STAGES } from "./types.js";
|
|
7
7
|
export class InvalidStageTransitionError extends Error {
|
|
@@ -30,6 +30,9 @@ function validateFlowTransition(prev, next) {
|
|
|
30
30
|
if (prev.track !== next.track) {
|
|
31
31
|
throw new InvalidStageTransitionError(prev.currentStage, next.currentStage, `cannot change track from "${prev.track}" to "${next.track}" mid-run (activeRunId="${prev.activeRunId}"). Archive the run and start a new one to switch tracks.`);
|
|
32
32
|
}
|
|
33
|
+
if (prev.discoveryMode !== next.discoveryMode) {
|
|
34
|
+
throw new InvalidStageTransitionError(prev.currentStage, next.currentStage, `cannot change discoveryMode from "${prev.discoveryMode}" to "${next.discoveryMode}" mid-run (activeRunId="${prev.activeRunId}"). Reclassify through start-flow or start a new run.`);
|
|
35
|
+
}
|
|
33
36
|
const newRewind = next.rewinds.length === prev.rewinds.length + 1
|
|
34
37
|
? next.rewinds[next.rewinds.length - 1]
|
|
35
38
|
: undefined;
|
|
@@ -159,6 +162,34 @@ function sanitizeStageGateCatalog(value, fallback) {
|
|
|
159
162
|
function coerceTrack(value) {
|
|
160
163
|
return isFlowTrack(value) ? value : "standard";
|
|
161
164
|
}
|
|
165
|
+
function coerceDiscoveryMode(value) {
|
|
166
|
+
if (typeof value === "string") {
|
|
167
|
+
const normalized = value.trim().toLowerCase();
|
|
168
|
+
if (isDiscoveryMode(normalized))
|
|
169
|
+
return normalized;
|
|
170
|
+
}
|
|
171
|
+
return "guided";
|
|
172
|
+
}
|
|
173
|
+
function coerceRepoSignals(value) {
|
|
174
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const typed = value;
|
|
178
|
+
const fileCountRaw = typed.fileCount;
|
|
179
|
+
const fileCount = typeof fileCountRaw === "number" && Number.isFinite(fileCountRaw) && fileCountRaw >= 0
|
|
180
|
+
? Math.min(Math.floor(fileCountRaw), 1_000_000)
|
|
181
|
+
: undefined;
|
|
182
|
+
const capturedAt = typeof typed.capturedAt === "string" ? typed.capturedAt.trim() : "";
|
|
183
|
+
if (fileCount === undefined || !capturedAt) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
fileCount,
|
|
188
|
+
hasReadme: typed.hasReadme === true,
|
|
189
|
+
hasPackageManifest: typed.hasPackageManifest === true,
|
|
190
|
+
capturedAt
|
|
191
|
+
};
|
|
192
|
+
}
|
|
162
193
|
/**
|
|
163
194
|
* Wave 24 follow-up (v6.1.1) — preserve `flow-state.json#taskClass`
|
|
164
195
|
* across read/write round-trips. Before this audit fix the persistence
|
|
@@ -372,12 +403,14 @@ function sanitizeCloseoutState(value) {
|
|
|
372
403
|
}
|
|
373
404
|
function coerceFlowState(parsed) {
|
|
374
405
|
const track = coerceTrack(parsed.track);
|
|
375
|
-
const
|
|
406
|
+
const discoveryMode = coerceDiscoveryMode(parsed.discoveryMode);
|
|
407
|
+
const next = createInitialFlowState({ track, discoveryMode });
|
|
376
408
|
const activeRunIdRaw = parsed.activeRunId;
|
|
377
409
|
const activeRunId = typeof activeRunIdRaw === "string" && activeRunIdRaw.trim().length > 0
|
|
378
410
|
? activeRunIdRaw.trim()
|
|
379
411
|
: next.activeRunId;
|
|
380
412
|
const taskClass = coerceTaskClass(parsed.taskClass);
|
|
413
|
+
const repoSignals = coerceRepoSignals(parsed.repoSignals);
|
|
381
414
|
const state = {
|
|
382
415
|
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
383
416
|
activeRunId,
|
|
@@ -386,7 +419,9 @@ function coerceFlowState(parsed) {
|
|
|
386
419
|
guardEvidence: sanitizeGuardEvidence(parsed.guardEvidence),
|
|
387
420
|
stageGateCatalog: sanitizeStageGateCatalog(parsed.stageGateCatalog, next.stageGateCatalog),
|
|
388
421
|
track,
|
|
422
|
+
discoveryMode,
|
|
389
423
|
...(taskClass !== undefined ? { taskClass } : {}),
|
|
424
|
+
...(repoSignals ? { repoSignals } : {}),
|
|
390
425
|
skippedStages: sanitizeSkippedStages(parsed.skippedStages, track),
|
|
391
426
|
staleStages: sanitizeStaleStages(parsed.staleStages),
|
|
392
427
|
rewinds: sanitizeRewinds(parsed.rewinds),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FlowStage, FlowTrack, TrackHeuristicRule, TrackHeuristicsConfig } from "./types.js";
|
|
1
|
+
import type { DiscoveryMode, FlowStage, FlowTrack, TrackHeuristicRule, TrackHeuristicsConfig } from "./types.js";
|
|
2
2
|
export interface TrackResolution {
|
|
3
3
|
track: FlowTrack;
|
|
4
4
|
reason: string;
|
|
@@ -19,7 +19,7 @@ export interface QuestionBudgetHint {
|
|
|
19
19
|
* "advisory" language.
|
|
20
20
|
*/
|
|
21
21
|
export declare function resolveTrackFromPrompt(prompt: string, config: TrackHeuristicsConfig | undefined): TrackResolution;
|
|
22
|
-
export declare function questionBudgetHint(
|
|
22
|
+
export declare function questionBudgetHint(modeOrTrack: DiscoveryMode | FlowTrack, stage: FlowStage): QuestionBudgetHint;
|
|
23
23
|
export declare const TRACK_HEURISTICS_DEFAULTS: {
|
|
24
24
|
readonly fallback: "standard";
|
|
25
25
|
readonly evaluationOrder: readonly ("quick" | "medium" | "standard")[];
|
package/dist/track-heuristics.js
CHANGED
|
@@ -55,10 +55,10 @@ const DEFAULT_RULES = {
|
|
|
55
55
|
const EVALUATION_ORDER = ["standard", "medium", "quick"];
|
|
56
56
|
const DEFAULT_FALLBACK = "standard";
|
|
57
57
|
const ADAPTIVE_ELICITATION_STAGES = new Set(["brainstorm", "scope", "design"]);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
const QUESTION_BUDGET_HINTS_BY_DISCOVERY_MODE = {
|
|
59
|
+
lean: { min: 3, recommended: 4, hardCapWarning: 6 },
|
|
60
|
+
guided: { min: 5, recommended: 7, hardCapWarning: 10 },
|
|
61
|
+
deep: { min: 7, recommended: 10, hardCapWarning: 14 }
|
|
62
62
|
};
|
|
63
63
|
function hasToken(promptLower, token) {
|
|
64
64
|
return promptLower.includes(token.toLowerCase());
|
|
@@ -136,11 +136,16 @@ export function resolveTrackFromPrompt(prompt, config) {
|
|
|
136
136
|
overrideGuidance: "Confirm or override before state is written; choose quick only for known low-blast-radius work, medium for known architecture with product framing, standard for uncertainty."
|
|
137
137
|
};
|
|
138
138
|
}
|
|
139
|
-
export function questionBudgetHint(
|
|
139
|
+
export function questionBudgetHint(modeOrTrack, stage) {
|
|
140
140
|
if (!ADAPTIVE_ELICITATION_STAGES.has(stage)) {
|
|
141
141
|
return { min: 0, recommended: 0, hardCapWarning: 0 };
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
if (modeOrTrack === "lean" || modeOrTrack === "guided" || modeOrTrack === "deep") {
|
|
144
|
+
return QUESTION_BUDGET_HINTS_BY_DISCOVERY_MODE[modeOrTrack];
|
|
145
|
+
}
|
|
146
|
+
if (modeOrTrack === "quick")
|
|
147
|
+
return QUESTION_BUDGET_HINTS_BY_DISCOVERY_MODE.lean;
|
|
148
|
+
return QUESTION_BUDGET_HINTS_BY_DISCOVERY_MODE.guided;
|
|
144
149
|
}
|
|
145
150
|
export const TRACK_HEURISTICS_DEFAULTS = {
|
|
146
151
|
fallback: DEFAULT_FALLBACK,
|
package/dist/types.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export declare const FLOW_STAGES: readonly ["brainstorm", "scope", "design", "sp
|
|
|
2
2
|
export type FlowStage = (typeof FLOW_STAGES)[number];
|
|
3
3
|
export declare const FLOW_TRACKS: readonly ["quick", "medium", "standard"];
|
|
4
4
|
export type FlowTrack = (typeof FLOW_TRACKS)[number];
|
|
5
|
+
export declare const DISCOVERY_MODES: readonly ["lean", "guided", "deep"];
|
|
6
|
+
export type DiscoveryMode = (typeof DISCOVERY_MODES)[number];
|
|
5
7
|
/**
|
|
6
8
|
* Ordered stages that make up each flow track.
|
|
7
9
|
*
|
package/dist/types.js
CHANGED