cclaw-cli 0.51.29 → 0.55.2
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 +22 -16
- package/dist/artifact-linter/brainstorm.d.ts +2 -0
- package/dist/artifact-linter/brainstorm.js +245 -0
- package/dist/artifact-linter/design.d.ts +2 -0
- package/dist/artifact-linter/design.js +323 -0
- package/dist/artifact-linter/plan.d.ts +2 -0
- package/dist/artifact-linter/plan.js +162 -0
- package/dist/artifact-linter/review-army.d.ts +24 -0
- package/dist/artifact-linter/review-army.js +365 -0
- package/dist/artifact-linter/review.d.ts +2 -0
- package/dist/artifact-linter/review.js +65 -0
- package/dist/artifact-linter/scope.d.ts +2 -0
- package/dist/artifact-linter/scope.js +115 -0
- package/dist/artifact-linter/shared.d.ts +246 -0
- package/dist/artifact-linter/shared.js +1488 -0
- package/dist/artifact-linter/ship.d.ts +2 -0
- package/dist/artifact-linter/ship.js +46 -0
- package/dist/artifact-linter/spec.d.ts +2 -0
- package/dist/artifact-linter/spec.js +108 -0
- package/dist/artifact-linter/tdd.d.ts +2 -0
- package/dist/artifact-linter/tdd.js +124 -0
- package/dist/artifact-linter.d.ts +4 -76
- package/dist/artifact-linter.js +56 -2949
- package/dist/cli.d.ts +2 -18
- package/dist/cli.js +8 -246
- package/dist/codex-feature-flag.d.ts +1 -1
- package/dist/codex-feature-flag.js +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js +67 -3
- package/dist/constants.d.ts +1 -7
- package/dist/constants.js +9 -15
- package/dist/content/cancel-command.js +2 -2
- package/dist/content/closeout-guidance.js +13 -10
- package/dist/content/core-agents.d.ts +18 -0
- package/dist/content/core-agents.js +51 -7
- package/dist/content/decision-protocol.d.ts +1 -1
- package/dist/content/decision-protocol.js +1 -1
- package/dist/content/examples.js +6 -6
- package/dist/content/harness-doc.js +20 -2
- package/dist/content/hook-inline-snippets.d.ts +17 -4
- package/dist/content/hook-inline-snippets.js +218 -5
- package/dist/content/hook-manifest.d.ts +2 -2
- package/dist/content/hook-manifest.js +2 -2
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +32 -137
- package/dist/content/idea-command.d.ts +8 -0
- package/dist/content/{ideate-command.js → idea-command.js} +57 -50
- package/dist/content/idea-frames.d.ts +31 -0
- package/dist/content/{ideate-frames.js → idea-frames.js} +9 -9
- package/dist/content/idea-ranking.d.ts +25 -0
- package/dist/content/{ideate-ranking.js → idea-ranking.js} +5 -5
- package/dist/content/iron-laws.d.ts +0 -1
- package/dist/content/iron-laws.js +31 -16
- package/dist/content/learnings.js +1 -1
- package/dist/content/meta-skill.js +11 -13
- package/dist/content/node-hooks.d.ts +10 -0
- package/dist/content/node-hooks.js +45 -11
- package/dist/content/opencode-plugin.js +3 -3
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/skills.js +19 -7
- package/dist/content/stage-command.js +1 -1
- package/dist/content/stage-schema.js +44 -2
- package/dist/content/stages/_lint-metadata/index.js +26 -2
- package/dist/content/stages/brainstorm.js +13 -7
- package/dist/content/stages/design.js +16 -11
- package/dist/content/stages/plan.js +9 -6
- package/dist/content/stages/review.js +4 -4
- package/dist/content/stages/schema-types.d.ts +1 -1
- package/dist/content/stages/scope.js +15 -12
- package/dist/content/stages/ship.js +2 -2
- package/dist/content/stages/spec.js +9 -3
- package/dist/content/stages/tdd.js +14 -4
- package/dist/content/start-command.d.ts +2 -2
- package/dist/content/start-command.js +24 -21
- package/dist/content/status-command.js +8 -8
- package/dist/content/subagents.js +61 -7
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +104 -152
- package/dist/content/tree-command.js +2 -2
- package/dist/content/utility-skills.d.ts +2 -2
- package/dist/content/utility-skills.js +2 -2
- package/dist/content/view-command.js +4 -2
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +2 -1
- package/dist/early-loop.d.ts +66 -0
- package/dist/early-loop.js +275 -0
- package/dist/flow-state.d.ts +1 -1
- package/dist/flow-state.js +1 -1
- package/dist/gate-evidence.d.ts +8 -0
- package/dist/gate-evidence.js +141 -5
- package/dist/harness-adapters.d.ts +2 -2
- package/dist/harness-adapters.js +54 -122
- package/dist/harness-selection.d.ts +31 -0
- package/dist/harness-selection.js +214 -0
- package/dist/install.js +166 -38
- package/dist/internal/advance-stage/advance.d.ts +50 -0
- package/dist/internal/advance-stage/advance.js +480 -0
- package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
- package/dist/internal/advance-stage/cancel-run.js +19 -0
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
- package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
- package/dist/internal/advance-stage/helpers.d.ts +14 -0
- package/dist/internal/advance-stage/helpers.js +145 -0
- package/dist/internal/advance-stage/hook.d.ts +8 -0
- package/dist/internal/advance-stage/hook.js +40 -0
- package/dist/internal/advance-stage/parsers.d.ts +54 -0
- package/dist/internal/advance-stage/parsers.js +307 -0
- package/dist/internal/advance-stage/review-loop.d.ts +7 -0
- package/dist/internal/advance-stage/review-loop.js +170 -0
- package/dist/internal/advance-stage/rewind.d.ts +14 -0
- package/dist/internal/advance-stage/rewind.js +108 -0
- package/dist/internal/advance-stage/start-flow.d.ts +11 -0
- package/dist/internal/advance-stage/start-flow.js +136 -0
- package/dist/internal/advance-stage/verify.d.ts +29 -0
- package/dist/internal/advance-stage/verify.js +225 -0
- package/dist/internal/advance-stage.js +21 -1470
- package/dist/internal/compound-readiness.d.ts +1 -1
- package/dist/internal/compound-readiness.js +2 -2
- package/dist/internal/early-loop-status.d.ts +7 -0
- package/dist/internal/early-loop-status.js +90 -0
- package/dist/internal/runtime-integrity.d.ts +7 -0
- package/dist/internal/runtime-integrity.js +288 -0
- package/dist/internal/tdd-red-evidence.js +1 -1
- package/dist/knowledge-store.d.ts +3 -8
- package/dist/knowledge-store.js +16 -29
- package/dist/managed-resources.js +24 -2
- package/dist/policy.js +5 -7
- package/dist/run-archive.d.ts +1 -1
- package/dist/run-archive.js +16 -16
- package/dist/run-persistence.js +112 -12
- package/dist/tdd-cycle.d.ts +3 -3
- package/dist/tdd-cycle.js +1 -1
- package/dist/types.d.ts +18 -10
- package/package.json +1 -1
- package/dist/content/finish-command.d.ts +0 -2
- package/dist/content/finish-command.js +0 -26
- package/dist/content/ideate-command.d.ts +0 -8
- package/dist/content/ideate-frames.d.ts +0 -31
- package/dist/content/ideate-ranking.d.ts +0 -25
- package/dist/content/next-command.d.ts +0 -20
- package/dist/content/next-command.js +0 -298
- package/dist/content/seed-shelf.d.ts +0 -36
- package/dist/content/seed-shelf.js +0 -301
- package/dist/content/stage-common-guidance.d.ts +0 -1
- package/dist/content/stage-common-guidance.js +0 -106
- package/dist/doctor-registry.d.ts +0 -10
- package/dist/doctor-registry.js +0 -186
- package/dist/doctor.d.ts +0 -17
- package/dist/doctor.js +0 -2206
- package/dist/internal/hook-manifest.d.ts +0 -16
- package/dist/internal/hook-manifest.js +0 -77
package/dist/delegation.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export interface DelegationTokenUsage {
|
|
|
34
34
|
output: number;
|
|
35
35
|
model: string;
|
|
36
36
|
}
|
|
37
|
+
export type DelegationWaiverAcceptedBy = "user-flag";
|
|
37
38
|
export type DelegationEntry = {
|
|
38
39
|
stage: string;
|
|
39
40
|
agent: string;
|
|
@@ -56,6 +57,7 @@ export type DelegationEntry = {
|
|
|
56
57
|
*/
|
|
57
58
|
taskId?: string;
|
|
58
59
|
waiverReason?: string;
|
|
60
|
+
acceptedBy?: DelegationWaiverAcceptedBy;
|
|
59
61
|
ts?: string;
|
|
60
62
|
/**
|
|
61
63
|
* Run id the entry belongs to. Older ledgers written before 0.5.17 may omit this;
|
package/dist/delegation.js
CHANGED
|
@@ -198,6 +198,7 @@ function isDelegationEntry(value) {
|
|
|
198
198
|
(o.endTs === undefined || typeof o.endTs === "string") &&
|
|
199
199
|
(o.taskId === undefined || typeof o.taskId === "string") &&
|
|
200
200
|
(o.waiverReason === undefined || typeof o.waiverReason === "string") &&
|
|
201
|
+
(o.acceptedBy === undefined || o.acceptedBy === "user-flag") &&
|
|
201
202
|
waiverOk &&
|
|
202
203
|
(o.runId === undefined || typeof o.runId === "string") &&
|
|
203
204
|
(o.fulfillmentMode === undefined ||
|
|
@@ -269,7 +270,7 @@ function parseLedger(raw, runId) {
|
|
|
269
270
|
// and entry, the entry has no fulfillmentMode, and there is no
|
|
270
271
|
// dispatch-surface or dispatch-id evidence on the row. We honor
|
|
271
272
|
// that by tagging fulfillmentMode = "legacy-inferred" so callers
|
|
272
|
-
// (stage-complete,
|
|
273
|
+
// (stage-complete, sync/runtime checks) can require an explicit `--rerecord`
|
|
273
274
|
// before the row counts as proof-era.
|
|
274
275
|
const ledgerHasNoVersion = ledgerSchemaVersion === undefined || ledgerSchemaVersion === 1;
|
|
275
276
|
const entryHasNoVersion = item.schemaVersion === undefined || item.schemaVersion === 1;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
export declare const EARLY_LOOP_STAGES: readonly ["brainstorm", "scope", "design"];
|
|
2
|
+
export type EarlyLoopStage = (typeof EARLY_LOOP_STAGES)[number];
|
|
3
|
+
export type EarlyLoopConcernSeverity = "critical" | "important" | "suggestion";
|
|
4
|
+
export interface EarlyLoopConcern {
|
|
5
|
+
id: string;
|
|
6
|
+
severity: EarlyLoopConcernSeverity;
|
|
7
|
+
locator: string;
|
|
8
|
+
summary: string;
|
|
9
|
+
firstSeenIteration: number;
|
|
10
|
+
lastSeenIteration: number;
|
|
11
|
+
resolvedAtIteration?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface EarlyLoopStatus {
|
|
14
|
+
schemaVersion: 1;
|
|
15
|
+
stage: EarlyLoopStage;
|
|
16
|
+
runId: string;
|
|
17
|
+
iteration: number;
|
|
18
|
+
maxIterations: number;
|
|
19
|
+
openConcerns: EarlyLoopConcern[];
|
|
20
|
+
resolvedConcerns: EarlyLoopConcern[];
|
|
21
|
+
lastSeenConcernIds: string[];
|
|
22
|
+
convergenceTripped: boolean;
|
|
23
|
+
escalationReason?: string;
|
|
24
|
+
lastUpdatedAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface EarlyLoopLogConcern {
|
|
27
|
+
id: string;
|
|
28
|
+
severity: EarlyLoopConcernSeverity;
|
|
29
|
+
locator: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
}
|
|
32
|
+
export interface EarlyLoopLogEntry {
|
|
33
|
+
ts: string;
|
|
34
|
+
runId: string;
|
|
35
|
+
stage: string;
|
|
36
|
+
iteration?: number;
|
|
37
|
+
concerns: EarlyLoopLogConcern[];
|
|
38
|
+
resolvedConcernIds: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface EarlyLoopParseIssue {
|
|
41
|
+
lineNumber: number;
|
|
42
|
+
reason: string;
|
|
43
|
+
rawLine: string;
|
|
44
|
+
}
|
|
45
|
+
export interface ParseEarlyLoopLogOptions {
|
|
46
|
+
issues?: EarlyLoopParseIssue[];
|
|
47
|
+
strict?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface DeriveEarlyLoopStatusOptions {
|
|
50
|
+
stage: EarlyLoopStage;
|
|
51
|
+
runId: string;
|
|
52
|
+
maxIterations?: number;
|
|
53
|
+
now?: Date;
|
|
54
|
+
}
|
|
55
|
+
export interface ComputeEarlyLoopStatusOptions {
|
|
56
|
+
maxIterations?: number;
|
|
57
|
+
now?: Date;
|
|
58
|
+
parseIssues?: EarlyLoopParseIssue[];
|
|
59
|
+
strictParse?: boolean;
|
|
60
|
+
}
|
|
61
|
+
export declare function isEarlyLoopStage(value: unknown): value is EarlyLoopStage;
|
|
62
|
+
export declare function normalizeEarlyLoopMaxIterations(value: number | undefined): number;
|
|
63
|
+
export declare function parseEarlyLoopLog(text: string, options?: ParseEarlyLoopLogOptions): EarlyLoopLogEntry[];
|
|
64
|
+
export declare function deriveEarlyLoopStatus(entries: EarlyLoopLogEntry[], options: DeriveEarlyLoopStatusOptions): EarlyLoopStatus;
|
|
65
|
+
export declare function computeEarlyLoopStatus(stage: EarlyLoopStage, runId: string, concernsLogPath: string, options?: ComputeEarlyLoopStatusOptions): Promise<EarlyLoopStatus>;
|
|
66
|
+
export declare function formatEarlyLoopStatusLine(status: EarlyLoopStatus): string;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { DEFAULT_EARLY_LOOP_MAX_ITERATIONS } from "./config.js";
|
|
3
|
+
export const EARLY_LOOP_STAGES = ["brainstorm", "scope", "design"];
|
|
4
|
+
const CONCERN_ID_PREFIX = "C-";
|
|
5
|
+
function severityWeight(severity) {
|
|
6
|
+
if (severity === "critical")
|
|
7
|
+
return 3;
|
|
8
|
+
if (severity === "important")
|
|
9
|
+
return 2;
|
|
10
|
+
return 1;
|
|
11
|
+
}
|
|
12
|
+
function normalizeSeverity(value) {
|
|
13
|
+
if (value === "critical" || value === "important" || value === "suggestion") {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
return "important";
|
|
17
|
+
}
|
|
18
|
+
function normalizeText(value, fallback) {
|
|
19
|
+
if (typeof value !== "string")
|
|
20
|
+
return fallback;
|
|
21
|
+
const trimmed = value.trim();
|
|
22
|
+
return trimmed.length > 0 ? trimmed : fallback;
|
|
23
|
+
}
|
|
24
|
+
function stableConcernFallbackId(locator, summary) {
|
|
25
|
+
const seed = `${locator}::${summary}`.trim().toLowerCase();
|
|
26
|
+
let hash = 0;
|
|
27
|
+
for (let index = 0; index < seed.length; index += 1) {
|
|
28
|
+
hash = (Math.imul(31, hash) + seed.charCodeAt(index)) >>> 0;
|
|
29
|
+
}
|
|
30
|
+
return `${CONCERN_ID_PREFIX}${hash.toString(16).padStart(8, "0")}`;
|
|
31
|
+
}
|
|
32
|
+
function normalizeConcernId(id, locator, summary) {
|
|
33
|
+
if (typeof id === "string") {
|
|
34
|
+
const trimmed = id.trim();
|
|
35
|
+
if (trimmed.length > 0) {
|
|
36
|
+
return trimmed;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return stableConcernFallbackId(locator, summary);
|
|
40
|
+
}
|
|
41
|
+
function normalizeConcerns(value) {
|
|
42
|
+
if (!Array.isArray(value))
|
|
43
|
+
return [];
|
|
44
|
+
const concerns = [];
|
|
45
|
+
for (const row of value) {
|
|
46
|
+
if (!row || typeof row !== "object" || Array.isArray(row))
|
|
47
|
+
continue;
|
|
48
|
+
const typed = row;
|
|
49
|
+
const locator = normalizeText(typed.locator, "unknown-location");
|
|
50
|
+
const summary = normalizeText(typed.summary, "missing-summary");
|
|
51
|
+
concerns.push({
|
|
52
|
+
id: normalizeConcernId(typed.id, locator, summary),
|
|
53
|
+
severity: normalizeSeverity(typed.severity),
|
|
54
|
+
locator,
|
|
55
|
+
summary
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return concerns;
|
|
59
|
+
}
|
|
60
|
+
function normalizeResolvedConcernIds(value) {
|
|
61
|
+
if (!Array.isArray(value))
|
|
62
|
+
return [];
|
|
63
|
+
return value
|
|
64
|
+
.filter((entry) => typeof entry === "string")
|
|
65
|
+
.map((entry) => entry.trim())
|
|
66
|
+
.filter((entry) => entry.length > 0);
|
|
67
|
+
}
|
|
68
|
+
export function isEarlyLoopStage(value) {
|
|
69
|
+
return typeof value === "string" && EARLY_LOOP_STAGES.includes(value);
|
|
70
|
+
}
|
|
71
|
+
export function normalizeEarlyLoopMaxIterations(value) {
|
|
72
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 1) {
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
return DEFAULT_EARLY_LOOP_MAX_ITERATIONS;
|
|
76
|
+
}
|
|
77
|
+
export function parseEarlyLoopLog(text, options = {}) {
|
|
78
|
+
const strict = options.strict === true;
|
|
79
|
+
const issues = options.issues;
|
|
80
|
+
const normalized = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
81
|
+
const lines = normalized.split(/\r?\n/u);
|
|
82
|
+
const entries = [];
|
|
83
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
84
|
+
const raw = lines[index] ?? "";
|
|
85
|
+
const line = raw.trim();
|
|
86
|
+
if (line.length === 0)
|
|
87
|
+
continue;
|
|
88
|
+
const lineNumber = index + 1;
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(line);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
issues?.push({
|
|
95
|
+
lineNumber,
|
|
96
|
+
reason: `json-parse-failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
97
|
+
rawLine: raw
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const runId = normalizeText(parsed.runId, "");
|
|
102
|
+
const stage = normalizeText(parsed.stage, "");
|
|
103
|
+
const concerns = normalizeConcerns(parsed.concerns);
|
|
104
|
+
const resolvedConcernIds = normalizeResolvedConcernIds(parsed.resolvedConcernIds);
|
|
105
|
+
const iteration = typeof parsed.iteration === "number" &&
|
|
106
|
+
Number.isInteger(parsed.iteration) &&
|
|
107
|
+
parsed.iteration >= 1
|
|
108
|
+
? parsed.iteration
|
|
109
|
+
: undefined;
|
|
110
|
+
if (strict) {
|
|
111
|
+
const missing = [];
|
|
112
|
+
if (runId.length === 0)
|
|
113
|
+
missing.push("runId");
|
|
114
|
+
if (stage.length === 0)
|
|
115
|
+
missing.push("stage");
|
|
116
|
+
if (concerns.length === 0 && resolvedConcernIds.length === 0) {
|
|
117
|
+
missing.push("concerns/resolvedConcernIds");
|
|
118
|
+
}
|
|
119
|
+
if (missing.length > 0) {
|
|
120
|
+
issues?.push({
|
|
121
|
+
lineNumber,
|
|
122
|
+
reason: `missing-required-fields: ${missing.join(",")}`,
|
|
123
|
+
rawLine: raw
|
|
124
|
+
});
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
entries.push({
|
|
129
|
+
ts: normalizeText(parsed.ts, ""),
|
|
130
|
+
runId: runId.length > 0 ? runId : "active",
|
|
131
|
+
stage: stage.length > 0 ? stage : "brainstorm",
|
|
132
|
+
iteration,
|
|
133
|
+
concerns,
|
|
134
|
+
resolvedConcernIds
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
return entries;
|
|
138
|
+
}
|
|
139
|
+
function sortConcerns(a, b) {
|
|
140
|
+
const severityDiff = severityWeight(b.severity) - severityWeight(a.severity);
|
|
141
|
+
if (severityDiff !== 0)
|
|
142
|
+
return severityDiff;
|
|
143
|
+
if (a.firstSeenIteration !== b.firstSeenIteration) {
|
|
144
|
+
return a.firstSeenIteration - b.firstSeenIteration;
|
|
145
|
+
}
|
|
146
|
+
if (a.lastSeenIteration !== b.lastSeenIteration) {
|
|
147
|
+
return a.lastSeenIteration - b.lastSeenIteration;
|
|
148
|
+
}
|
|
149
|
+
return a.id.localeCompare(b.id, "en");
|
|
150
|
+
}
|
|
151
|
+
export function deriveEarlyLoopStatus(entries, options) {
|
|
152
|
+
const maxIterations = normalizeEarlyLoopMaxIterations(options.maxIterations);
|
|
153
|
+
const concerns = new Map();
|
|
154
|
+
const filtered = entries.filter((entry) => entry.runId === options.runId && entry.stage === options.stage);
|
|
155
|
+
let previousConcernSnapshotKey = "";
|
|
156
|
+
let sameConcernStreak = 0;
|
|
157
|
+
let convergenceTripped = false;
|
|
158
|
+
let escalationReason;
|
|
159
|
+
let currentIteration = 0;
|
|
160
|
+
let lastSeenConcernIds = [];
|
|
161
|
+
for (const entry of filtered) {
|
|
162
|
+
currentIteration += 1;
|
|
163
|
+
const iteration = entry.iteration ?? currentIteration;
|
|
164
|
+
const seenThisIteration = new Set();
|
|
165
|
+
for (const concern of entry.concerns) {
|
|
166
|
+
seenThisIteration.add(concern.id);
|
|
167
|
+
const existing = concerns.get(concern.id);
|
|
168
|
+
if (!existing) {
|
|
169
|
+
concerns.set(concern.id, {
|
|
170
|
+
id: concern.id,
|
|
171
|
+
severity: concern.severity,
|
|
172
|
+
locator: concern.locator,
|
|
173
|
+
summary: concern.summary,
|
|
174
|
+
firstSeenIteration: iteration,
|
|
175
|
+
lastSeenIteration: iteration
|
|
176
|
+
});
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
existing.lastSeenIteration = iteration;
|
|
180
|
+
existing.locator = concern.locator;
|
|
181
|
+
existing.summary = concern.summary;
|
|
182
|
+
if (severityWeight(concern.severity) >= severityWeight(existing.severity)) {
|
|
183
|
+
existing.severity = concern.severity;
|
|
184
|
+
}
|
|
185
|
+
delete existing.resolvedAtIteration;
|
|
186
|
+
}
|
|
187
|
+
for (const concernId of entry.resolvedConcernIds) {
|
|
188
|
+
const existing = concerns.get(concernId);
|
|
189
|
+
if (!existing)
|
|
190
|
+
continue;
|
|
191
|
+
if (seenThisIteration.has(concernId))
|
|
192
|
+
continue;
|
|
193
|
+
if (existing.resolvedAtIteration === undefined) {
|
|
194
|
+
existing.resolvedAtIteration = iteration;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const concern of concerns.values()) {
|
|
198
|
+
if (concern.resolvedAtIteration !== undefined)
|
|
199
|
+
continue;
|
|
200
|
+
if (seenThisIteration.has(concern.id))
|
|
201
|
+
continue;
|
|
202
|
+
concern.resolvedAtIteration = iteration;
|
|
203
|
+
}
|
|
204
|
+
const openConcernIds = Array.from(concerns.values())
|
|
205
|
+
.filter((concern) => concern.resolvedAtIteration === undefined)
|
|
206
|
+
.map((concern) => concern.id)
|
|
207
|
+
.sort((a, b) => a.localeCompare(b, "en"));
|
|
208
|
+
lastSeenConcernIds = openConcernIds;
|
|
209
|
+
const snapshotKey = openConcernIds.join("|");
|
|
210
|
+
if (snapshotKey.length > 0 && snapshotKey === previousConcernSnapshotKey) {
|
|
211
|
+
sameConcernStreak += 1;
|
|
212
|
+
if (!convergenceTripped && sameConcernStreak >= 2) {
|
|
213
|
+
convergenceTripped = true;
|
|
214
|
+
escalationReason = `same concerns ${sameConcernStreak} iterations in a row`;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
sameConcernStreak = snapshotKey.length > 0 ? 1 : 0;
|
|
219
|
+
}
|
|
220
|
+
previousConcernSnapshotKey = snapshotKey;
|
|
221
|
+
}
|
|
222
|
+
const openConcerns = Array.from(concerns.values())
|
|
223
|
+
.filter((concern) => concern.resolvedAtIteration === undefined)
|
|
224
|
+
.sort(sortConcerns);
|
|
225
|
+
const resolvedConcerns = Array.from(concerns.values())
|
|
226
|
+
.filter((concern) => concern.resolvedAtIteration !== undefined)
|
|
227
|
+
.sort((a, b) => {
|
|
228
|
+
if (a.resolvedAtIteration !== b.resolvedAtIteration) {
|
|
229
|
+
return a.resolvedAtIteration - b.resolvedAtIteration;
|
|
230
|
+
}
|
|
231
|
+
return sortConcerns(a, b);
|
|
232
|
+
});
|
|
233
|
+
if (!convergenceTripped && openConcerns.length > 0 && currentIteration >= maxIterations) {
|
|
234
|
+
convergenceTripped = true;
|
|
235
|
+
escalationReason = `max iterations ${maxIterations} reached with ${openConcerns.length} open concern(s)`;
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
schemaVersion: 1,
|
|
239
|
+
stage: options.stage,
|
|
240
|
+
runId: options.runId,
|
|
241
|
+
iteration: currentIteration,
|
|
242
|
+
maxIterations,
|
|
243
|
+
openConcerns,
|
|
244
|
+
resolvedConcerns,
|
|
245
|
+
lastSeenConcernIds,
|
|
246
|
+
convergenceTripped,
|
|
247
|
+
...(escalationReason ? { escalationReason } : {}),
|
|
248
|
+
lastUpdatedAt: (options.now ?? new Date()).toISOString()
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
export async function computeEarlyLoopStatus(stage, runId, concernsLogPath, options = {}) {
|
|
252
|
+
let raw = "";
|
|
253
|
+
try {
|
|
254
|
+
raw = await fs.readFile(concernsLogPath, "utf8");
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
if (error.code !== "ENOENT") {
|
|
258
|
+
throw error;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
const parsed = parseEarlyLoopLog(raw, {
|
|
262
|
+
issues: options.parseIssues,
|
|
263
|
+
strict: options.strictParse
|
|
264
|
+
});
|
|
265
|
+
return deriveEarlyLoopStatus(parsed, {
|
|
266
|
+
stage,
|
|
267
|
+
runId,
|
|
268
|
+
maxIterations: options.maxIterations,
|
|
269
|
+
now: options.now
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
export function formatEarlyLoopStatusLine(status) {
|
|
273
|
+
const convergence = status.convergenceTripped ? "tripped" : "clear";
|
|
274
|
+
return `Early Loop: stage=${status.stage}, iter=${status.iteration}/${status.maxIterations}, open=${status.openConcerns.length}, convergence=${convergence}`;
|
|
275
|
+
}
|
package/dist/flow-state.d.ts
CHANGED
|
@@ -33,7 +33,7 @@ export interface RetroState {
|
|
|
33
33
|
* Ship closeout substate machine.
|
|
34
34
|
*
|
|
35
35
|
* After ship completes, cclaw auto-chains retro → compound → archive.
|
|
36
|
-
* Each step is interruptible: `/cc
|
|
36
|
+
* Each step is interruptible: `/cc` reads `shipSubstate` and resumes
|
|
37
37
|
* from the correct step even across sessions.
|
|
38
38
|
*
|
|
39
39
|
* - `idle` — ship not complete, or closeout not yet started.
|
package/dist/flow-state.js
CHANGED
|
@@ -6,7 +6,7 @@ export const FLOW_STATE_SCHEMA_VERSION = 1;
|
|
|
6
6
|
* Ship closeout substate machine.
|
|
7
7
|
*
|
|
8
8
|
* After ship completes, cclaw auto-chains retro → compound → archive.
|
|
9
|
-
* Each step is interruptible: `/cc
|
|
9
|
+
* Each step is interruptible: `/cc` reads `shipSubstate` and resumes
|
|
10
10
|
* from the correct step even across sessions.
|
|
11
11
|
*
|
|
12
12
|
* - `idle` — ship not complete, or closeout not yet started.
|
package/dist/gate-evidence.d.ts
CHANGED
|
@@ -30,6 +30,12 @@ export interface CompletedStagesClosureResult {
|
|
|
30
30
|
}>;
|
|
31
31
|
}
|
|
32
32
|
export declare const RECONCILIATION_NOTICES_REL_PATH = ".cclaw/state/reconciliation-notices.json";
|
|
33
|
+
export type ReconciliationNoticeKind = "gate_demotion" | "closeout_substate_demotion";
|
|
34
|
+
export interface CloseoutSubstateDemotionPayload {
|
|
35
|
+
previous: string;
|
|
36
|
+
next: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
}
|
|
33
39
|
export interface ReconciliationNotice {
|
|
34
40
|
id: string;
|
|
35
41
|
runId: string;
|
|
@@ -37,6 +43,8 @@ export interface ReconciliationNotice {
|
|
|
37
43
|
gateId: string;
|
|
38
44
|
reason: string;
|
|
39
45
|
demotedAt: string;
|
|
46
|
+
kind?: ReconciliationNoticeKind;
|
|
47
|
+
payload?: CloseoutSubstateDemotionPayload;
|
|
40
48
|
}
|
|
41
49
|
export interface ReconciliationNoticesPayload {
|
|
42
50
|
schemaVersion: number;
|
package/dist/gate-evidence.js
CHANGED
|
@@ -2,10 +2,12 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { checkReviewSecurityNoChangeAttestation, checkReviewVerdictConsistency, extractMarkdownSectionBody, lintArtifact, validateReviewArmy } from "./artifact-linter.js";
|
|
4
4
|
import { resolveArtifactPath } from "./artifact-paths.js";
|
|
5
|
+
import { readConfig } from "./config.js";
|
|
5
6
|
import { RUNTIME_ROOT } from "./constants.js";
|
|
6
7
|
import { stageSchema } from "./content/stage-schema.js";
|
|
7
8
|
import { readDelegationLedger } from "./delegation.js";
|
|
8
9
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
10
|
+
import { computeEarlyLoopStatus, isEarlyLoopStage, normalizeEarlyLoopMaxIterations } from "./early-loop.js";
|
|
9
11
|
import { detectPublicApiChanges } from "./internal/detect-public-api-changes.js";
|
|
10
12
|
import { readFlowState, writeFlowState } from "./runs.js";
|
|
11
13
|
import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
|
|
@@ -119,6 +121,92 @@ async function verifyDiscoveredCommandEvidence(projectRoot, stage, gateId, flowS
|
|
|
119
121
|
return null;
|
|
120
122
|
return `${stage} verification gate blocked (${gateId}): guard evidence must cite one discovered real test command: ${commands.join(", ")}.`;
|
|
121
123
|
}
|
|
124
|
+
function toEarlyLoopGateSnapshot(value) {
|
|
125
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
126
|
+
return null;
|
|
127
|
+
const typed = value;
|
|
128
|
+
const stage = typeof typed.stage === "string" ? typed.stage : "";
|
|
129
|
+
const runId = typeof typed.runId === "string" ? typed.runId : "active";
|
|
130
|
+
const iteration = typeof typed.iteration === "number" && Number.isInteger(typed.iteration) && typed.iteration >= 0
|
|
131
|
+
? typed.iteration
|
|
132
|
+
: 0;
|
|
133
|
+
const maxIterations = normalizeEarlyLoopMaxIterations(typeof typed.maxIterations === "number" ? typed.maxIterations : undefined);
|
|
134
|
+
const openConcernIds = Array.isArray(typed.openConcerns)
|
|
135
|
+
? typed.openConcerns
|
|
136
|
+
.flatMap((concern) => {
|
|
137
|
+
if (!concern || typeof concern !== "object" || Array.isArray(concern))
|
|
138
|
+
return [];
|
|
139
|
+
const id = concern.id;
|
|
140
|
+
return typeof id === "string" && id.trim().length > 0 ? [id.trim()] : [];
|
|
141
|
+
})
|
|
142
|
+
.sort((a, b) => a.localeCompare(b, "en"))
|
|
143
|
+
: [];
|
|
144
|
+
if (stage.length === 0)
|
|
145
|
+
return null;
|
|
146
|
+
return {
|
|
147
|
+
stage,
|
|
148
|
+
runId,
|
|
149
|
+
iteration,
|
|
150
|
+
maxIterations,
|
|
151
|
+
openConcernIds,
|
|
152
|
+
openConcernCount: openConcernIds.length,
|
|
153
|
+
convergenceTripped: typed.convergenceTripped === true,
|
|
154
|
+
escalationReason: typeof typed.escalationReason === "string" && typed.escalationReason.trim().length > 0
|
|
155
|
+
? typed.escalationReason.trim()
|
|
156
|
+
: undefined
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
async function readEarlyLoopGateSnapshot(projectRoot, flowState) {
|
|
160
|
+
if (!isEarlyLoopStage(flowState.currentStage)) {
|
|
161
|
+
return { snapshot: null };
|
|
162
|
+
}
|
|
163
|
+
const stateDir = path.join(projectRoot, RUNTIME_ROOT, "state");
|
|
164
|
+
const statusPath = path.join(stateDir, "early-loop.json");
|
|
165
|
+
let onDisk = null;
|
|
166
|
+
if (await exists(statusPath)) {
|
|
167
|
+
try {
|
|
168
|
+
onDisk = toEarlyLoopGateSnapshot(JSON.parse(await fs.readFile(statusPath, "utf8")));
|
|
169
|
+
}
|
|
170
|
+
catch (error) {
|
|
171
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
172
|
+
return {
|
|
173
|
+
snapshot: null,
|
|
174
|
+
issue: `early loop gate blocked (early_loop_open_concerns): unable to parse ${statusPath} (${reason}). ` +
|
|
175
|
+
"Rebuild status with `cclaw internal early-loop-status --write`."
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (onDisk &&
|
|
180
|
+
onDisk.stage === flowState.currentStage &&
|
|
181
|
+
onDisk.runId === flowState.activeRunId) {
|
|
182
|
+
return { snapshot: onDisk };
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const config = await readConfig(projectRoot);
|
|
186
|
+
const computed = await computeEarlyLoopStatus(flowState.currentStage, flowState.activeRunId, path.join(stateDir, "early-loop-log.jsonl"), {
|
|
187
|
+
maxIterations: config.earlyLoop?.maxIterations
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
snapshot: {
|
|
191
|
+
stage: computed.stage,
|
|
192
|
+
runId: computed.runId,
|
|
193
|
+
iteration: computed.iteration,
|
|
194
|
+
maxIterations: computed.maxIterations,
|
|
195
|
+
openConcernIds: computed.openConcerns.map((concern) => concern.id),
|
|
196
|
+
openConcernCount: computed.openConcerns.length,
|
|
197
|
+
convergenceTripped: computed.convergenceTripped,
|
|
198
|
+
escalationReason: computed.escalationReason
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
204
|
+
return {
|
|
205
|
+
snapshot: null,
|
|
206
|
+
issue: `early loop gate blocked (early_loop_open_concerns): unable to compute status from early-loop-log.jsonl (${reason}).`
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
122
210
|
const RECONCILIATION_NOTICES_FILE = "reconciliation-notices.json";
|
|
123
211
|
const RECONCILIATION_NOTICES_SCHEMA_VERSION = 1;
|
|
124
212
|
const DESIGN_RESEARCH_REQUIRED_SECTIONS = [
|
|
@@ -156,13 +244,31 @@ function sanitizeReconciliationNotice(raw) {
|
|
|
156
244
|
typeof typed.demotedAt !== "string") {
|
|
157
245
|
return null;
|
|
158
246
|
}
|
|
247
|
+
const kind = typed.kind === "closeout_substate_demotion"
|
|
248
|
+
? "closeout_substate_demotion"
|
|
249
|
+
: "gate_demotion";
|
|
250
|
+
let payload;
|
|
251
|
+
if (kind === "closeout_substate_demotion" && typed.payload && typeof typed.payload === "object" && !Array.isArray(typed.payload)) {
|
|
252
|
+
const payloadTyped = typed.payload;
|
|
253
|
+
if (typeof payloadTyped.previous === "string" &&
|
|
254
|
+
typeof payloadTyped.next === "string" &&
|
|
255
|
+
typeof payloadTyped.reason === "string") {
|
|
256
|
+
payload = {
|
|
257
|
+
previous: payloadTyped.previous,
|
|
258
|
+
next: payloadTyped.next,
|
|
259
|
+
reason: payloadTyped.reason
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}
|
|
159
263
|
return {
|
|
160
264
|
id: typed.id,
|
|
161
265
|
runId: typed.runId,
|
|
162
266
|
stage: typed.stage,
|
|
163
267
|
gateId: typed.gateId,
|
|
164
268
|
reason: typed.reason,
|
|
165
|
-
demotedAt: typed.demotedAt
|
|
269
|
+
demotedAt: typed.demotedAt,
|
|
270
|
+
kind,
|
|
271
|
+
payload
|
|
166
272
|
};
|
|
167
273
|
}
|
|
168
274
|
export async function readReconciliationNotices(projectRoot) {
|
|
@@ -211,6 +317,9 @@ export function classifyReconciliationNotices(flowState, notices) {
|
|
|
211
317
|
staleRun.push(notice);
|
|
212
318
|
continue;
|
|
213
319
|
}
|
|
320
|
+
if (notice.kind === "closeout_substate_demotion") {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
214
323
|
const stageCatalog = flowState.stageGateCatalog[notice.stage];
|
|
215
324
|
const blocked = stageCatalog.blocked.includes(notice.gateId);
|
|
216
325
|
if (!blocked) {
|
|
@@ -239,6 +348,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
239
348
|
const recommendedSet = new Set(recommended);
|
|
240
349
|
const allowedSet = new Set([...required, ...recommended]);
|
|
241
350
|
const issues = [];
|
|
351
|
+
const softNotices = [];
|
|
242
352
|
const catalogRequired = unique(catalog.required);
|
|
243
353
|
const catalogRecommended = unique(catalog.recommended ?? []);
|
|
244
354
|
const catalogConditional = unique(catalog.conditional ?? []);
|
|
@@ -439,8 +549,33 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
439
549
|
}
|
|
440
550
|
}
|
|
441
551
|
}
|
|
552
|
+
if (isEarlyLoopStage(stage)) {
|
|
553
|
+
const { snapshot, issue } = await readEarlyLoopGateSnapshot(projectRoot, flowState);
|
|
554
|
+
if (issue) {
|
|
555
|
+
issues.push(issue);
|
|
556
|
+
}
|
|
557
|
+
else if (snapshot && snapshot.openConcernCount > 0) {
|
|
558
|
+
const concernTail = snapshot.openConcernIds.length > 3
|
|
559
|
+
? `, +${snapshot.openConcernIds.length - 3} more`
|
|
560
|
+
: "";
|
|
561
|
+
const concernSample = snapshot.openConcernIds.slice(0, 3).join(", ");
|
|
562
|
+
if (snapshot.convergenceTripped) {
|
|
563
|
+
const reason = snapshot.escalationReason ?? "convergence guard tripped";
|
|
564
|
+
softNotices.push(`early loop escalation notice (early_loop_open_concerns): ${reason}; ` +
|
|
565
|
+
`open concerns remain (${concernSample}${concernTail}). Request explicit human override before advancing.`);
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
issues.push(`early loop gate blocked (early_loop_open_concerns): ` +
|
|
569
|
+
`${snapshot.openConcernCount} open concern(s) remain after iteration ` +
|
|
570
|
+
`${snapshot.iteration}/${snapshot.maxIterations} (${concernSample}${concernTail}).`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
442
574
|
const missingRequired = required.filter((gateId) => !passedSet.has(gateId));
|
|
443
|
-
const missingRecommended =
|
|
575
|
+
const missingRecommended = [
|
|
576
|
+
...recommended.filter((gateId) => !passedSet.has(gateId)),
|
|
577
|
+
...softNotices
|
|
578
|
+
];
|
|
444
579
|
const missingTriggeredConditional = [];
|
|
445
580
|
const blockingBlocked = catalog.blocked.filter((gateId) => requiredSet.has(gateId));
|
|
446
581
|
const complete = missingRequired.length === 0 && blockingBlocked.length === 0;
|
|
@@ -617,9 +752,9 @@ export async function reconcileAndWriteCurrentStageGateCatalog(projectRoot) {
|
|
|
617
752
|
noticesChanged = true;
|
|
618
753
|
}
|
|
619
754
|
if (reconciliation.demotedGateIds.length > 0) {
|
|
620
|
-
const existing = new Set(noticesPayload.notices.map((notice) => `${notice.runId}:${notice.stage}:${notice.gateId}`));
|
|
755
|
+
const existing = new Set(noticesPayload.notices.map((notice) => `${notice.runId}:${notice.stage}:${notice.gateId}:${notice.kind ?? "gate_demotion"}`));
|
|
621
756
|
for (const gateId of reconciliation.demotedGateIds) {
|
|
622
|
-
const dedupeKey = `${effectiveState.activeRunId}:${reconciliation.stage}:${gateId}`;
|
|
757
|
+
const dedupeKey = `${effectiveState.activeRunId}:${reconciliation.stage}:${gateId}:gate_demotion`;
|
|
623
758
|
if (existing.has(dedupeKey)) {
|
|
624
759
|
continue;
|
|
625
760
|
}
|
|
@@ -630,7 +765,8 @@ export async function reconcileAndWriteCurrentStageGateCatalog(projectRoot) {
|
|
|
630
765
|
stage: reconciliation.stage,
|
|
631
766
|
gateId,
|
|
632
767
|
reason: "demoted from passed to blocked during gate reconciliation (missing evidence)",
|
|
633
|
-
demotedAt: ts
|
|
768
|
+
demotedAt: ts,
|
|
769
|
+
kind: "gate_demotion"
|
|
634
770
|
});
|
|
635
771
|
existing.add(dedupeKey);
|
|
636
772
|
noticesChanged = true;
|
|
@@ -32,7 +32,7 @@ export type SubagentFallback =
|
|
|
32
32
|
* directories under a skills root (Codex CLI ≥0.89, Jan 2026). cclaw
|
|
33
33
|
* writes `<commandDir>/<skillName>/SKILL.md` and the agent invokes it
|
|
34
34
|
* either via `/use <skillName>` or via automatic description matching
|
|
35
|
-
* when the user's text mentions `/cc`, `/cc-
|
|
35
|
+
* when the user's text mentions `/cc`, `/cc-idea`, or `/cc-cancel`.
|
|
36
36
|
*/
|
|
37
37
|
export type ShimKind = "command" | "skill";
|
|
38
38
|
export interface HarnessAdapter {
|
|
@@ -47,7 +47,7 @@ export interface HarnessAdapter {
|
|
|
47
47
|
* Root directory where cclaw writes `/cc*` entry points.
|
|
48
48
|
*
|
|
49
49
|
* - For `shimKind: "command"` this is the directory containing flat
|
|
50
|
-
* markdown files (`<commandDir>/cc.md`, `<commandDir>/cc-
|
|
50
|
+
* markdown files (`<commandDir>/cc.md`, `<commandDir>/cc-idea.md`, …).
|
|
51
51
|
* - For `shimKind: "skill"` this is the skills root that contains
|
|
52
52
|
* per-skill subdirectories (`<commandDir>/<skillName>/SKILL.md`).
|
|
53
53
|
*/
|