cclaw-cli 0.51.19 → 0.51.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/artifact-linter.js +89 -6
- package/dist/config.d.ts +8 -1
- package/dist/config.js +9 -6
- package/dist/content/examples.js +1 -0
- package/dist/content/hook-events.js +1 -5
- package/dist/content/hook-manifest.d.ts +2 -4
- package/dist/content/hook-manifest.js +4 -3
- package/dist/content/meta-skill.js +7 -9
- package/dist/content/next-command.js +2 -2
- package/dist/content/node-hooks.js +15 -16
- package/dist/content/observe.js +2 -4
- package/dist/content/opencode-plugin.js +5 -6
- package/dist/content/review-loop.js +15 -5
- package/dist/content/review-prompts.js +1 -1
- package/dist/content/skills.js +3 -2
- package/dist/content/stage-schema.d.ts +0 -1
- package/dist/content/stage-schema.js +2 -5
- package/dist/content/stages/brainstorm.js +3 -3
- package/dist/content/stages/design.js +18 -17
- package/dist/content/stages/plan.js +2 -1
- package/dist/content/stages/review.js +10 -10
- package/dist/content/stages/scope.js +13 -13
- package/dist/content/stages/spec.js +7 -5
- package/dist/content/stages/tdd.js +2 -2
- package/dist/content/start-command.d.ts +4 -3
- package/dist/content/start-command.js +21 -17
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +49 -29
- package/dist/content/view-command.js +3 -1
- package/dist/delegation.d.ts +0 -1
- package/dist/delegation.js +29 -11
- package/dist/doctor.js +148 -24
- package/dist/gate-evidence.js +19 -7
- package/dist/harness-adapters.js +1 -5
- package/dist/install.js +111 -24
- package/dist/internal/advance-stage.js +90 -11
- package/dist/knowledge-store.d.ts +4 -1
- package/dist/knowledge-store.js +24 -14
- package/dist/retro-gate.d.ts +1 -0
- package/dist/retro-gate.js +9 -9
- package/dist/run-archive.js +19 -1
- package/dist/run-persistence.js +12 -5
- package/dist/tdd-cycle.js +6 -3
- package/package.json +1 -1
package/dist/artifact-linter.js
CHANGED
|
@@ -485,15 +485,39 @@ const SCOPE_MODE_SHORT_TOKEN_REGEX = /\b(?:hold(?:[\s_-]?scope)?|selective(?:[\s
|
|
|
485
485
|
// not the wording of the rationale.
|
|
486
486
|
const NEXT_STAGE_HANDOFF_REGEX = /(?:`(?:design|spec)`|\bdesign\b|\bspec\b|next[-\s_]stage|next stage|handoff|hand[-\s]off)/iu;
|
|
487
487
|
function hasCanonicalScopeMode(body) {
|
|
488
|
-
|
|
489
|
-
|
|
488
|
+
// Strict: a Mode: / Selected mode: line that picks exactly ONE canonical mode
|
|
489
|
+
// is the strongest signal. The template scaffolding contains all four mode
|
|
490
|
+
// tokens inside an instructional `(one of ...)` placeholder; we ignore that
|
|
491
|
+
// line so authors who never replace the scaffolding still fail validation.
|
|
490
492
|
for (const match of body.matchAll(new RegExp(SCOPE_MODE_LINE_REGEX, "giu"))) {
|
|
491
|
-
const
|
|
492
|
-
|
|
493
|
+
const raw = (match[1] ?? "").trim();
|
|
494
|
+
const sanitized = raw.replace(/\(.*?\)/gu, "").trim();
|
|
495
|
+
if (sanitized.length === 0)
|
|
496
|
+
continue;
|
|
497
|
+
if (countCanonicalModeMentions(sanitized) === 1)
|
|
498
|
+
return true;
|
|
499
|
+
if (countCanonicalModeMentions(sanitized) === 0 && SCOPE_MODE_SHORT_TOKEN_REGEX.test(sanitized))
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
// Fallback: any line outside an instructional `(one of ...)` placeholder
|
|
503
|
+
// names exactly one mode. Block lines that list multiple modes (the
|
|
504
|
+
// unfilled template) or are wrapped in an instructional parenthetical.
|
|
505
|
+
for (const rawLine of body.split(/\r?\n/u)) {
|
|
506
|
+
const line = rawLine.trim();
|
|
507
|
+
if (line.length === 0)
|
|
508
|
+
continue;
|
|
509
|
+
if (/\(\s*one\s+of\b/iu.test(line))
|
|
510
|
+
continue;
|
|
511
|
+
const sanitized = line.replace(/\(.*?\)/gu, "");
|
|
512
|
+
if (countCanonicalModeMentions(sanitized) === 1)
|
|
493
513
|
return true;
|
|
494
514
|
}
|
|
495
515
|
return false;
|
|
496
516
|
}
|
|
517
|
+
function countCanonicalModeMentions(text) {
|
|
518
|
+
const matches = text.match(new RegExp(SCOPE_MODE_FULL_REGEX, "giu"));
|
|
519
|
+
return matches ? matches.length : 0;
|
|
520
|
+
}
|
|
497
521
|
function validatePremiseChallenge(sectionBody) {
|
|
498
522
|
// gstack-style premise challenge requires a real Q/A structure (table or
|
|
499
523
|
// list), not free-form prose. The validation is *structural* only — we do
|
|
@@ -1051,10 +1075,12 @@ function validateTddGreenEvidence(sectionBody) {
|
|
|
1051
1075
|
};
|
|
1052
1076
|
}
|
|
1053
1077
|
function validateVerificationLadder(sectionBody) {
|
|
1054
|
-
|
|
1078
|
+
const hasTextLine = /highest tier reached/iu.test(sectionBody);
|
|
1079
|
+
const hasCanonicalTable = hasVerificationLadderTableRow(sectionBody);
|
|
1080
|
+
if (!hasTextLine && !hasCanonicalTable) {
|
|
1055
1081
|
return {
|
|
1056
1082
|
ok: false,
|
|
1057
|
-
details: "Verification Ladder must include a 'Highest tier reached' line."
|
|
1083
|
+
details: "Verification Ladder must include either a 'Highest tier reached' line or a canonical table row (Slice | Tier reached | Evidence) with non-empty tier and evidence."
|
|
1058
1084
|
};
|
|
1059
1085
|
}
|
|
1060
1086
|
if (!/\b(static|command|behavioral|human)\b/iu.test(sectionBody)) {
|
|
@@ -1074,6 +1100,49 @@ function validateVerificationLadder(sectionBody) {
|
|
|
1074
1100
|
details: "Verification Ladder includes tier + evidence fields."
|
|
1075
1101
|
};
|
|
1076
1102
|
}
|
|
1103
|
+
function hasVerificationLadderTableRow(sectionBody) {
|
|
1104
|
+
const lines = sectionBody.split(/\r?\n/u);
|
|
1105
|
+
let sawHeader = false;
|
|
1106
|
+
let sawSeparator = false;
|
|
1107
|
+
for (const line of lines) {
|
|
1108
|
+
const trimmed = line.trim();
|
|
1109
|
+
if (!trimmed.startsWith("|")) {
|
|
1110
|
+
sawHeader = false;
|
|
1111
|
+
sawSeparator = false;
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
const cells = trimmed
|
|
1115
|
+
.replace(/^\|/u, "")
|
|
1116
|
+
.replace(/\|$/u, "")
|
|
1117
|
+
.split("|")
|
|
1118
|
+
.map((cell) => cell.trim());
|
|
1119
|
+
if (!sawHeader) {
|
|
1120
|
+
const lowered = cells.map((cell) => cell.toLowerCase());
|
|
1121
|
+
const hasTierColumn = lowered.some((cell) => /tier(?:\s+reached)?/u.test(cell));
|
|
1122
|
+
const hasEvidenceColumn = lowered.some((cell) => cell.includes("evidence"));
|
|
1123
|
+
if (hasTierColumn && hasEvidenceColumn) {
|
|
1124
|
+
sawHeader = true;
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
continue;
|
|
1128
|
+
}
|
|
1129
|
+
if (!sawSeparator) {
|
|
1130
|
+
if (cells.every((cell) => /^[:\-\s]+$/u.test(cell))) {
|
|
1131
|
+
sawSeparator = true;
|
|
1132
|
+
continue;
|
|
1133
|
+
}
|
|
1134
|
+
sawHeader = false;
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
if (cells.length >= 2 && cells.some((cell) => /\b(static|command|behavioral|human)\b/iu.test(cell))) {
|
|
1138
|
+
const evidenceCellHasContent = cells.some((cell) => cell.length > 0 && !/^\s*$/u.test(cell) && !/^[:\-\s]+$/u.test(cell));
|
|
1139
|
+
if (evidenceCellHasContent) {
|
|
1140
|
+
return true;
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1077
1146
|
const LEARNING_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
|
|
1078
1147
|
const LEARNING_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
|
|
1079
1148
|
const LEARNING_SEVERITY_SET = new Set(["critical", "important", "suggestion"]);
|
|
@@ -1786,6 +1855,20 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
|
|
|
1786
1855
|
? "Selected Direction is traceable to prior user reaction."
|
|
1787
1856
|
: "Selected Direction is not traceable to user reaction. Add `## Approach Reaction` before it, or mention the user's reaction/concerns in the rationale."
|
|
1788
1857
|
});
|
|
1858
|
+
// Track-aware handoff: standard track goes to `scope`; medium track
|
|
1859
|
+
// goes directly to `spec`; the quick track skips brainstorm entirely.
|
|
1860
|
+
// We accept either canonical successor token plus a generic
|
|
1861
|
+
// `next-stage` / `handoff` phrase to preserve i18n flexibility.
|
|
1862
|
+
const handoffTrace = /(?:`(?:scope|spec)`|\bscope\b|\bspec\b|next[-\s_]stage|next stage|\bhandoff\b|hand[-\s]off)/iu.test(directionBody);
|
|
1863
|
+
findings.push({
|
|
1864
|
+
section: "Direction Next-Stage Handoff",
|
|
1865
|
+
required: true,
|
|
1866
|
+
rule: "Selected Direction must record the track-aware next-stage handoff (mention `scope` for standard, `spec` for medium, or include a `Next-stage handoff:` line).",
|
|
1867
|
+
found: handoffTrace,
|
|
1868
|
+
details: handoffTrace
|
|
1869
|
+
? "Selected Direction names the next-stage handoff."
|
|
1870
|
+
: "Selected Direction is missing a next-stage handoff token. Mention `scope` (standard) or `spec` (medium), or add a `Next-stage handoff:` line so downstream stages can trace the contract."
|
|
1871
|
+
});
|
|
1789
1872
|
}
|
|
1790
1873
|
}
|
|
1791
1874
|
const shortCircuitBody = brainstormShortCircuitBody;
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import type { CclawConfig, FlowTrack, HarnessId, LanguageRulePack } from "./types.js";
|
|
2
|
+
export interface ConfigWarningState {
|
|
3
|
+
emitted: Set<string>;
|
|
4
|
+
}
|
|
5
|
+
export interface ReadConfigOptions {
|
|
6
|
+
warningState?: ConfigWarningState;
|
|
7
|
+
}
|
|
8
|
+
export declare function createConfigWarningState(): ConfigWarningState;
|
|
2
9
|
export declare class InvalidConfigError extends Error {
|
|
3
10
|
constructor(message: string);
|
|
4
11
|
}
|
|
@@ -34,7 +41,7 @@ export declare function createDefaultConfig(harnesses?: HarnessId[], defaultTrac
|
|
|
34
41
|
* never surprise a user who intentionally cleared the list.
|
|
35
42
|
*/
|
|
36
43
|
export declare function detectLanguageRulePacks(projectRoot: string): Promise<LanguageRulePack[]>;
|
|
37
|
-
export declare function readConfig(projectRoot: string): Promise<CclawConfig>;
|
|
44
|
+
export declare function readConfig(projectRoot: string, options?: ReadConfigOptions): Promise<CclawConfig>;
|
|
38
45
|
/**
|
|
39
46
|
* Fields that live on the populated runtime `CclawConfig` but are considered
|
|
40
47
|
* "advanced" — we keep them in the in-memory object so downstream callers
|
package/dist/config.js
CHANGED
|
@@ -56,13 +56,15 @@ const MINIMAL_CONFIG_KEYS = [
|
|
|
56
56
|
];
|
|
57
57
|
const DEFAULT_SLICE_REVIEW_THRESHOLD = 5;
|
|
58
58
|
const DEFAULT_SLICE_REVIEW_TRACKS = ["standard"];
|
|
59
|
-
|
|
60
|
-
|
|
59
|
+
export function createConfigWarningState() {
|
|
60
|
+
return { emitted: new Set() };
|
|
61
|
+
}
|
|
62
|
+
function emitConfigWarningOnce(warningState, code, message) {
|
|
61
63
|
const key = `${code}:${message}`;
|
|
62
|
-
if (
|
|
64
|
+
if (warningState.emitted.has(key)) {
|
|
63
65
|
return;
|
|
64
66
|
}
|
|
65
|
-
|
|
67
|
+
warningState.emitted.add(key);
|
|
66
68
|
process.emitWarning(message, { code });
|
|
67
69
|
}
|
|
68
70
|
function sameStringArray(a, b) {
|
|
@@ -196,7 +198,8 @@ export async function detectLanguageRulePacks(projectRoot) {
|
|
|
196
198
|
}
|
|
197
199
|
return [...new Set(detected)];
|
|
198
200
|
}
|
|
199
|
-
export async function readConfig(projectRoot) {
|
|
201
|
+
export async function readConfig(projectRoot, options = {}) {
|
|
202
|
+
const warningState = options.warningState ?? createConfigWarningState();
|
|
200
203
|
const fullPath = configPath(projectRoot);
|
|
201
204
|
if (!(await exists(fullPath))) {
|
|
202
205
|
return createDefaultConfig();
|
|
@@ -269,7 +272,7 @@ export async function readConfig(projectRoot) {
|
|
|
269
272
|
if (tddTestGlobsRaw !== undefined &&
|
|
270
273
|
explicitTddTestPathPatterns !== undefined &&
|
|
271
274
|
!sameStringArray(tddTestGlobs, explicitTddTestPathPatterns)) {
|
|
272
|
-
emitConfigWarningOnce("CCLAW_CONFIG_DEPRECATED_TDD_TEST_GLOBS", `[cclaw] Both "tddTestGlobs" (deprecated) and "tdd.testPathPatterns" are set in ${fullPath}. ` +
|
|
275
|
+
emitConfigWarningOnce(warningState, "CCLAW_CONFIG_DEPRECATED_TDD_TEST_GLOBS", `[cclaw] Both "tddTestGlobs" (deprecated) and "tdd.testPathPatterns" are set in ${fullPath}. ` +
|
|
273
276
|
`Using "tdd.testPathPatterns".`);
|
|
274
277
|
}
|
|
275
278
|
const resolvedTddTestPathPatterns = [
|
package/dist/content/examples.js
CHANGED
|
@@ -48,6 +48,7 @@ const STAGE_EXAMPLES = {
|
|
|
48
48
|
- **Approach:** A — Reusable validation module
|
|
49
49
|
- **Rationale:** based on user reaction favoring fast delivery and lower complexity, shared TS module gives consistent behavior in CI/local, avoids script duplication, and stays within the no-new-dependency constraint.
|
|
50
50
|
- **Approval:** approved
|
|
51
|
+
- **Next-stage handoff:** \`scope\` — carry the locked stack constraints and the validator module boundary forward.
|
|
51
52
|
|
|
52
53
|
## Design
|
|
53
54
|
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { semanticEventCoverage } from "./hook-manifest.js";
|
|
2
2
|
export { HOOK_SEMANTIC_EVENTS } from "./hook-manifest.js";
|
|
3
|
-
function isManifestHarness(value) {
|
|
4
|
-
return HOOK_MANIFEST_HARNESSES.includes(value);
|
|
5
|
-
}
|
|
6
3
|
/**
|
|
7
4
|
* OpenCode is covered by the inline plugin (`opencode-plugin.ts`), not
|
|
8
5
|
* by the generated `run-hook.mjs` dispatcher. We keep its semantic
|
|
@@ -28,4 +25,3 @@ export const HOOK_EVENTS_BY_HARNESS = Object.freeze({
|
|
|
28
25
|
codex: semanticEventCoverage("codex"),
|
|
29
26
|
opencode: OPENCODE_SEMANTIC_COVERAGE
|
|
30
27
|
});
|
|
31
|
-
void isManifestHarness;
|
|
@@ -45,14 +45,12 @@ export interface HookHandlerSpec {
|
|
|
45
45
|
description: string;
|
|
46
46
|
/**
|
|
47
47
|
* Semantic event id used by `HOOK_EVENTS_BY_HARNESS` / docs.
|
|
48
|
-
* `null` means this handler contributes no semantic coverage row
|
|
49
|
-
* (e.g. `verify-current-state` on codex is a supplementary guard,
|
|
50
|
-
* not a top-level semantic event).
|
|
48
|
+
* `null` means this handler contributes no semantic coverage row.
|
|
51
49
|
*/
|
|
52
50
|
semantic: HookSemanticEvent | null;
|
|
53
51
|
bindings: Partial<Record<HookManifestHarness, HookBinding[]>>;
|
|
54
52
|
}
|
|
55
|
-
export declare const HOOK_SEMANTIC_EVENTS: readonly ["session_rehydrate", "pre_tool_prompt_guard", "pre_tool_workflow_guard", "post_tool_context_monitor", "stop_handoff", "precompact_compat"];
|
|
53
|
+
export declare const HOOK_SEMANTIC_EVENTS: readonly ["session_rehydrate", "pre_tool_prompt_guard", "pre_tool_workflow_guard", "post_tool_context_monitor", "stop_handoff", "precompact_compat", "strict_state_verify"];
|
|
56
54
|
export type HookSemanticEvent = (typeof HOOK_SEMANTIC_EVENTS)[number];
|
|
57
55
|
export declare const HOOK_MANIFEST: readonly HookHandlerSpec[];
|
|
58
56
|
export interface EventGroup {
|
|
@@ -35,7 +35,8 @@ export const HOOK_SEMANTIC_EVENTS = [
|
|
|
35
35
|
"pre_tool_workflow_guard",
|
|
36
36
|
"post_tool_context_monitor",
|
|
37
37
|
"stop_handoff",
|
|
38
|
-
"precompact_compat"
|
|
38
|
+
"precompact_compat",
|
|
39
|
+
"strict_state_verify"
|
|
39
40
|
];
|
|
40
41
|
export const HOOK_MANIFEST = [
|
|
41
42
|
{
|
|
@@ -112,8 +113,8 @@ export const HOOK_MANIFEST = [
|
|
|
112
113
|
},
|
|
113
114
|
{
|
|
114
115
|
handler: "verify-current-state",
|
|
115
|
-
description: "Supplementary
|
|
116
|
-
semantic:
|
|
116
|
+
description: "Supplementary Codex strict-mode guard that runs on UserPromptSubmit to assert the live state matches the flow.",
|
|
117
|
+
semantic: "strict_state_verify",
|
|
117
118
|
bindings: {
|
|
118
119
|
codex: [{ event: "UserPromptSubmit" }]
|
|
119
120
|
}
|
|
@@ -14,7 +14,7 @@ description: "Routing brain for cclaw. Decide whether to start/resume a stage, a
|
|
|
14
14
|
1. User message in current turn.
|
|
15
15
|
2. Active stage skill and command contract.
|
|
16
16
|
3. This routing file.
|
|
17
|
-
4.
|
|
17
|
+
4. Generated cclaw helper skills, research playbooks, and enabled rule packs.
|
|
18
18
|
5. Default model behavior.
|
|
19
19
|
|
|
20
20
|
If the user explicitly overrides a stage rule, record it in the artifact.
|
|
@@ -25,9 +25,7 @@ ${conversationLanguagePolicyMarkdown()}
|
|
|
25
25
|
If \`.cclaw/state/flow-state.json\` exists and \`currentStage\` is set,
|
|
26
26
|
load the matching stage SKILL before producing **substantive** work
|
|
27
27
|
(artifact edits, code, structured clarifying questions). Do not improvise
|
|
28
|
-
from memory.
|
|
29
|
-
triggers it (security, performance, debugging, docs, finishing-a-branch,
|
|
30
|
-
verification-before-completion).
|
|
28
|
+
from memory. Load only generated helper surfaces that actually exist in this install: \`subagent-dev\`, \`parallel-dispatch\`, \`session\`, \`iron-laws\`, research playbooks, review prompts, or enabled language rule packs under \`.cclaw/rules/lang/\`. Do not invent helper-skill names beyond those generated surfaces.
|
|
31
29
|
|
|
32
30
|
Substantive vs. non-substantive:
|
|
33
31
|
|
|
@@ -70,7 +68,7 @@ Task arrives
|
|
|
70
68
|
| Class | Route |
|
|
71
69
|
|---|---|
|
|
72
70
|
| non-trivial software work | \`/cc <idea>\` |
|
|
73
|
-
| trivial software fix | \`/cc <idea>\` (quick
|
|
71
|
+
| trivial software fix | \`/cc <idea>\` (quick track) |
|
|
74
72
|
| bugfix with clear repro | \`/cc <idea>\` and enforce RED-first in tdd |
|
|
75
73
|
| pure question / conversation | answer directly |
|
|
76
74
|
| non-software work | answer directly |
|
|
@@ -115,10 +113,10 @@ Use the current stage skill plus \`.cclaw/state/flow-state.json\` for orientatio
|
|
|
115
113
|
|
|
116
114
|
Use built-in judgment only when triggered by the current task:
|
|
117
115
|
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
- iron-laws as policy arbitration when instructions conflict
|
|
116
|
+
- generated subagent context skills for mandatory review/delegation contracts
|
|
117
|
+
- research playbooks and review prompts when a stage explicitly calls for them
|
|
118
|
+
- inline verification and ship/finalization sections in the active stage skill
|
|
119
|
+
- \`iron-laws\` as policy arbitration when instructions conflict
|
|
122
120
|
- language rule packs from \`.cclaw/config.yaml\` when enabled
|
|
123
121
|
|
|
124
122
|
## Protocol Behavior
|
|
@@ -112,7 +112,7 @@ ${ralphLoopContractSnippet()}
|
|
|
112
112
|
- If \`track === "quick"\`, the critical path is **spec → tdd → review → ship**. When advancing, skip any stage listed in \`skippedStages\` — i.e. after the current stage completes, pick the next stage that is NOT in \`skippedStages\`.
|
|
113
113
|
- If \`track === "medium"\`, the critical path is **brainstorm → spec → plan → tdd → review → ship**. Scope and design are intentionally skipped unless the run is reclassified to standard.
|
|
114
114
|
- If \`track === "standard"\`, advance through all 8 stages in their natural order.
|
|
115
|
-
- Never reintroduce a skipped stage mid-run. If
|
|
115
|
+
- Never manually reintroduce a skipped stage mid-run. If evidence shows the track was wrong, stop and use the managed start-flow helper with \`--reclassify\`; only that managed reclassification may add upstream stages back into the active track.
|
|
116
116
|
|
|
117
117
|
## Resume Semantics
|
|
118
118
|
|
|
@@ -130,7 +130,7 @@ When orchestrated by another skill/subagent, emit exactly one JSON envelope and
|
|
|
130
130
|
no narrative text:
|
|
131
131
|
|
|
132
132
|
\`\`\`json
|
|
133
|
-
{"version":"1","kind":"gate-result","stage":"
|
|
133
|
+
{"version":"1","kind":"gate-result","stage":"<currentStage>","payload":{"command":"/cc-next","decision":"resume_or_advance","nextStage":"<nextStage>"},"emittedAt":"<ISO-8601>"}
|
|
134
134
|
\`\`\`
|
|
135
135
|
|
|
136
136
|
Validate envelopes with:
|
|
@@ -265,15 +265,6 @@ async function writeJsonFile(filePath, value) {
|
|
|
265
265
|
});
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
async function fileExists(filePath) {
|
|
269
|
-
try {
|
|
270
|
-
await fs.stat(filePath);
|
|
271
|
-
return true;
|
|
272
|
-
} catch {
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
268
|
async function readTextFile(filePath, fallback = "") {
|
|
278
269
|
try {
|
|
279
270
|
return await fs.readFile(filePath, "utf8");
|
|
@@ -625,7 +616,7 @@ function isCodeLikePath(rawPath) {
|
|
|
625
616
|
}
|
|
626
617
|
|
|
627
618
|
function isMutatingTool(toolLower) {
|
|
628
|
-
return /^(write|edit|multiedit|multi_edit|delete|applypatch|apply_patch)$/u.test(toolLower);
|
|
619
|
+
return /^(write|edit|multiedit|multi_edit|delete|applypatch|apply_patch|notebookedit|notebook_edit)$/u.test(toolLower);
|
|
629
620
|
}
|
|
630
621
|
|
|
631
622
|
function isExecutionOrMutatingTool(toolLower) {
|
|
@@ -871,8 +862,6 @@ async function buildKnowledgeDigest(root, currentStage, prereadRaw) {
|
|
|
871
862
|
const action = typeof row.action === "string" ? row.action : "action";
|
|
872
863
|
return "- [" + confidence + " • " + stage + " • " + domain + "] " + trigger + " -> " + action;
|
|
873
864
|
});
|
|
874
|
-
const body =
|
|
875
|
-
relevant.length > 0 ? relevant.join("\\n") : "(no matching entries for current stage)";
|
|
876
865
|
return {
|
|
877
866
|
digestLines: relevant,
|
|
878
867
|
learningsCount
|
|
@@ -1128,12 +1117,21 @@ async function handleStopHandoff(runtime) {
|
|
|
1128
1117
|
return 1;
|
|
1129
1118
|
}
|
|
1130
1119
|
|
|
1120
|
+
const closeoutObj = toObject(state.raw.closeout) || {};
|
|
1121
|
+
const shipSubstate = typeof closeoutObj.shipSubstate === "string" ? closeoutObj.shipSubstate : "idle";
|
|
1122
|
+
const closeoutContext =
|
|
1123
|
+
state.currentStage === "ship" || shipSubstate !== "idle"
|
|
1124
|
+
? " closeout.shipSubstate=" + shipSubstate + "; closeout chain=retro -> compound -> archive; continue closeout with /cc-next."
|
|
1125
|
+
: "";
|
|
1126
|
+
|
|
1131
1127
|
const message =
|
|
1132
1128
|
"Cclaw: session ending (stage=" +
|
|
1133
1129
|
state.currentStage +
|
|
1134
1130
|
", run=" +
|
|
1135
1131
|
state.activeRunId +
|
|
1136
|
-
").
|
|
1132
|
+
")." +
|
|
1133
|
+
closeoutContext +
|
|
1134
|
+
" Active artifacts stay in " +
|
|
1137
1135
|
RUNTIME_ROOT +
|
|
1138
1136
|
"/artifacts until archive. Before stopping: (1) confirm flow-state reflects reality, (2) ensure artifact changes match current intent, (3) if you discovered a non-obvious rule/pattern during stage work, add it to the current artifact ## Learnings section so stage-complete can harvest it, (4) commit or revert pending changes.";
|
|
1139
1137
|
|
|
@@ -1164,7 +1162,7 @@ async function handlePromptGuard(runtime) {
|
|
|
1164
1162
|
const payloadLower = toLower(payloadText);
|
|
1165
1163
|
const reasons = [];
|
|
1166
1164
|
|
|
1167
|
-
if (/^(write|edit|multiedit|multi_edit|delete|applypatch|runcommand|shell|terminal|execcommand)$/u.test(toolLower)) {
|
|
1165
|
+
if (/^(write|edit|multiedit|multi_edit|delete|applypatch|notebookedit|runcommand|shell|terminal|execcommand)$/u.test(toolLower)) {
|
|
1168
1166
|
// Artifacts, runs, and knowledge writes are part of normal stage flow.
|
|
1169
1167
|
// Guard only managed internals that should be mutated via installer/CLI.
|
|
1170
1168
|
if (/\\.cclaw\\/(state|hooks|skills|commands|agents)/u.test(payloadLower)) {
|
|
@@ -1661,9 +1659,10 @@ async function handleVerifyCurrentState(runtime) {
|
|
|
1661
1659
|
function normalizeHookName(rawName) {
|
|
1662
1660
|
const value = normalizeText(rawName).toLowerCase();
|
|
1663
1661
|
if (value === "session-start") return "session-start";
|
|
1664
|
-
if (value === "stop-handoff") return "stop-handoff";
|
|
1662
|
+
if (value === "stop-handoff" || value === "stop") return "stop-handoff";
|
|
1665
1663
|
if (value === "stop-checkpoint") return "stop-handoff";
|
|
1666
|
-
if (value === "pre-compact") return "pre-compact";
|
|
1664
|
+
if (value === "pre-compact" || value === "precompact") return "pre-compact";
|
|
1665
|
+
if (value === "session-rehydrate") return "session-start";
|
|
1667
1666
|
if (value === "prompt-guard") return "prompt-guard";
|
|
1668
1667
|
if (value === "workflow-guard") return "workflow-guard";
|
|
1669
1668
|
if (value === "context-monitor") return "context-monitor";
|
package/dist/content/observe.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { groupBindingsByEvent } from "./hook-manifest.js";
|
|
3
3
|
function hookDispatcherCommand(hookName) {
|
|
4
4
|
// Dispatch through the polyglot .cmd wrapper so Windows harnesses can run
|
|
5
5
|
// hooks even when command execution happens under CMD-style shells.
|
|
@@ -78,9 +78,7 @@ export function codexHooksJsonWithObservation() {
|
|
|
78
78
|
* manifest without importing the private generator helpers.
|
|
79
79
|
*/
|
|
80
80
|
export function hookManifestSnapshot() {
|
|
81
|
-
return (
|
|
82
|
-
? ["claude", "cursor", "codex"]
|
|
83
|
-
: ["claude", "cursor", "codex"]).map((harness) => ({
|
|
81
|
+
return ["claude", "cursor", "codex"].map((harness) => ({
|
|
84
82
|
harness,
|
|
85
83
|
events: groupBindingsByEvent(harness)
|
|
86
84
|
}));
|
|
@@ -463,11 +463,10 @@ export default function cclawPlugin(ctx) {
|
|
|
463
463
|
* \`DEFAULT_STRICTNESS = advisory\`, so the plugin can no longer
|
|
464
464
|
* accidentally be the stricter half of a mismatched pair.
|
|
465
465
|
*/
|
|
466
|
-
function readConfigStrictness() {
|
|
466
|
+
async function readConfigStrictness() {
|
|
467
467
|
try {
|
|
468
468
|
if (!existsSync(configPath)) return "";
|
|
469
|
-
const
|
|
470
|
-
const raw = readFileSync(configPath, "utf8");
|
|
469
|
+
const raw = await readFileText(configPath);
|
|
471
470
|
if (typeof raw !== "string" || raw.length === 0) return "";
|
|
472
471
|
const match = raw.match(/^\\s*strictness\\s*:\\s*([A-Za-z0-9_-]+)/m);
|
|
473
472
|
return match && typeof match[1] === "string" ? match[1].trim().toLowerCase() : "";
|
|
@@ -476,7 +475,7 @@ export default function cclawPlugin(ctx) {
|
|
|
476
475
|
}
|
|
477
476
|
}
|
|
478
477
|
|
|
479
|
-
function resolveStrictness() {
|
|
478
|
+
async function resolveStrictness() {
|
|
480
479
|
const envRaw = typeof process.env.CCLAW_STRICTNESS === "string"
|
|
481
480
|
? process.env.CCLAW_STRICTNESS.trim().toLowerCase()
|
|
482
481
|
: "";
|
|
@@ -484,7 +483,7 @@ export default function cclawPlugin(ctx) {
|
|
|
484
483
|
if (envRaw === "advisory" || envRaw === "off" || envRaw === "disabled" || envRaw === "none") {
|
|
485
484
|
return "advisory";
|
|
486
485
|
}
|
|
487
|
-
const fileRaw = readConfigStrictness();
|
|
486
|
+
const fileRaw = await readConfigStrictness();
|
|
488
487
|
if (fileRaw === "strict") return "strict";
|
|
489
488
|
return "advisory";
|
|
490
489
|
}
|
|
@@ -683,7 +682,7 @@ export default function cclawPlugin(ctx) {
|
|
|
683
682
|
);
|
|
684
683
|
return;
|
|
685
684
|
}
|
|
686
|
-
const strictness = resolveStrictness();
|
|
685
|
+
const strictness = await resolveStrictness();
|
|
687
686
|
if (strictness !== "strict") {
|
|
688
687
|
// Advisory mode (the default) — every guard refusal is a hint,
|
|
689
688
|
// not a hard stop. Users report the "failure" as a log line
|
|
@@ -456,6 +456,9 @@ export function buildReviewLoopEnvelope(args) {
|
|
|
456
456
|
function formatScore(value) {
|
|
457
457
|
return clampScore(value).toFixed(3);
|
|
458
458
|
}
|
|
459
|
+
function reviewLoopHeading(stage) {
|
|
460
|
+
return stage === "scope" ? "Scope Outside Voice Loop" : "Design Outside Voice Loop";
|
|
461
|
+
}
|
|
459
462
|
function finalEnvelopeScore(envelope) {
|
|
460
463
|
if (envelope.iterations.length === 0)
|
|
461
464
|
return 0;
|
|
@@ -486,7 +489,8 @@ export function renderReviewLoopSummarySection(envelope) {
|
|
|
486
489
|
})
|
|
487
490
|
.join("\n")
|
|
488
491
|
: "| 0 | 0.000 | 0 |";
|
|
489
|
-
|
|
492
|
+
const heading = reviewLoopHeading(envelope.stage);
|
|
493
|
+
return `## ${heading}
|
|
490
494
|
| Iteration | Quality Score | Findings |
|
|
491
495
|
|---|---|---|
|
|
492
496
|
${rows}
|
|
@@ -498,9 +502,14 @@ ${rows}
|
|
|
498
502
|
export function upsertReviewLoopSummary(markdown, envelope) {
|
|
499
503
|
const withHeader = upsertReviewLoopHeader(markdown, envelope);
|
|
500
504
|
const section = renderReviewLoopSummarySection(envelope);
|
|
501
|
-
const
|
|
502
|
-
const match =
|
|
503
|
-
|
|
505
|
+
const headingCandidates = [reviewLoopHeading(envelope.stage), "Spec Review Loop"];
|
|
506
|
+
const match = headingCandidates
|
|
507
|
+
.map((heading) => {
|
|
508
|
+
const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
509
|
+
return new RegExp(`^##\\s+${escapedHeading}\\s*$`, "m").exec(withHeader);
|
|
510
|
+
})
|
|
511
|
+
.find((candidate) => candidate !== null && candidate.index >= 0);
|
|
512
|
+
if (!match) {
|
|
504
513
|
const needsBreak = withHeader.endsWith("\n") ? "" : "\n";
|
|
505
514
|
return `${withHeader}${needsBreak}\n${section}\n`;
|
|
506
515
|
}
|
|
@@ -613,7 +622,8 @@ function parseHeaderMeta(markdown) {
|
|
|
613
622
|
};
|
|
614
623
|
}
|
|
615
624
|
export function extractReviewLoopEnvelopeFromArtifact(markdown, stage, artifactPath) {
|
|
616
|
-
const sectionBody = extractH2Section(markdown,
|
|
625
|
+
const sectionBody = extractH2Section(markdown, reviewLoopHeading(stage))
|
|
626
|
+
?? extractH2Section(markdown, "Spec Review Loop");
|
|
617
627
|
if (!sectionBody)
|
|
618
628
|
return null;
|
|
619
629
|
const iterations = parseIterationsTable(sectionBody);
|
|
@@ -57,7 +57,7 @@ value. Do not nitpick wording.
|
|
|
57
57
|
|
|
58
58
|
## Output
|
|
59
59
|
|
|
60
|
-
Record in \`## Outside Voice Findings\` or
|
|
60
|
+
Record in \`## Outside Voice Findings\` or the stage-specific outside voice loop section:
|
|
61
61
|
|
|
62
62
|
\`\`\`markdown
|
|
63
63
|
| ID | Dimension | Finding | Disposition | Rationale |
|
package/dist/content/skills.js
CHANGED
|
@@ -168,7 +168,7 @@ Apply concise turn announces: one announce per batch boundary (or when risk/plan
|
|
|
168
168
|
changes materially), then execute tasks without repetitive boilerplate.
|
|
169
169
|
|
|
170
170
|
Detailed walkthrough:
|
|
171
|
-
Use the
|
|
171
|
+
Use the active track's upstream artifact for ordering: plan slices on standard/medium, or spec acceptance items / bug reproduction slices on quick. Keep RED -> GREEN -> REFACTOR evidence in the TDD artifact.
|
|
172
172
|
`;
|
|
173
173
|
}
|
|
174
174
|
function crossStageTraceBlock(trace) {
|
|
@@ -386,6 +386,7 @@ ${philosophy.purpose}
|
|
|
386
386
|
|
|
387
387
|
## Complexity Tier
|
|
388
388
|
- Active tier: \`${schema.complexityTier}\`
|
|
389
|
+
- Scale-to-complexity rule: execute required gates and artifact sections, but keep optional/deep sections compact unless risk, novelty, or configuration triggers them. Do not mechanically expand lightweight work into a strategy workshop.
|
|
389
390
|
- Mandatory delegations at this tier: ${mandatoryDelegationSummary}
|
|
390
391
|
- Track render context: \`${trackContext.track}\` (${trackContext.usesPlanTerminology ? "plan-first wording" : "acceptance-first wording"})
|
|
391
392
|
|
|
@@ -452,7 +453,7 @@ ${reviewLens.outputs.map((item) => `- ${item}`).join("\n")}
|
|
|
452
453
|
${reviewSectionsBlock(reviewLens.reviewSections)}
|
|
453
454
|
|
|
454
455
|
## Shared Stage Guidance
|
|
455
|
-
-
|
|
456
|
+
- At STOP/closeout points, offer the shared handoff choices only when a user decision is needed.
|
|
456
457
|
- Carry upstream decisions forward explicitly; record drift instead of silently changing direction.
|
|
457
458
|
- Before closeout, fill \`## Learnings\` with \`- None this stage.\` or 1-3 strict JSON bullets.
|
|
458
459
|
- Keep decisions explicit: context, options, chosen option, rationale, risk, and rollback.
|
|
@@ -47,7 +47,6 @@ export declare function stageSchema(stage: FlowStage, track?: FlowTrack): StageS
|
|
|
47
47
|
export declare function orderedStageSchemas(track?: FlowTrack): StageSchema[];
|
|
48
48
|
export declare function stageGateIds(stage: FlowStage, track?: FlowTrack): string[];
|
|
49
49
|
export declare function stageRecommendedGateIds(stage: FlowStage, track?: FlowTrack): string[];
|
|
50
|
-
export declare function nextCclawCommand(stage: FlowStage): string;
|
|
51
50
|
export declare function buildTransitionRules(): TransitionRule[];
|
|
52
51
|
export declare function stagePolicyNeedles(stage: FlowStage, track?: FlowTrack): string[];
|
|
53
52
|
export declare function stageTrackRenderContext(track?: FlowTrack): import("./track-render-context.js").TrackRenderContext;
|
|
@@ -227,6 +227,7 @@ const REQUIRED_GATE_IDS = {
|
|
|
227
227
|
review: (track) => [
|
|
228
228
|
"review_layer1_spec_compliance",
|
|
229
229
|
"review_layer2_security",
|
|
230
|
+
"review_layer_coverage_complete",
|
|
230
231
|
"review_criticals_resolved",
|
|
231
232
|
"review_army_json_valid",
|
|
232
233
|
...(track === "quick" ? [] : ["review_trace_matrix_clean"])
|
|
@@ -258,7 +259,7 @@ const REQUIRED_ARTIFACT_SECTIONS = {
|
|
|
258
259
|
"Deployment & Rollout",
|
|
259
260
|
"Completion Dashboard"
|
|
260
261
|
],
|
|
261
|
-
spec: ["Acceptance Criteria", "Edge Cases", "Assumptions Before Finalization", "
|
|
262
|
+
spec: ["Acceptance Criteria", "Edge Cases", "Assumptions Before Finalization", "Acceptance Mapping", "Approval"],
|
|
262
263
|
plan: ["Task List", "Dependency Batches", "Acceptance Mapping", "Execution Posture", "WAIT_FOR_CONFIRM"],
|
|
263
264
|
tdd: ["Test Discovery", "System-Wide Impact Check", "RED Evidence", "GREEN Evidence", "REFACTOR Notes", "Traceability", "Verification Ladder"],
|
|
264
265
|
review: ["Layer 1 Verdict", "Review Findings Contract", "Severity Summary", "Final Verdict"],
|
|
@@ -577,10 +578,6 @@ export function stageRecommendedGateIds(stage, track = "standard") {
|
|
|
577
578
|
.filter((gate) => gate.tier === "recommended")
|
|
578
579
|
.map((gate) => gate.id);
|
|
579
580
|
}
|
|
580
|
-
export function nextCclawCommand(stage) {
|
|
581
|
-
const next = stageSchema(stage).next;
|
|
582
|
-
return next === "done" ? "none" : `/cc-${next}`;
|
|
583
|
-
}
|
|
584
581
|
export function buildTransitionRules() {
|
|
585
582
|
const rules = [];
|
|
586
583
|
const seen = new Set();
|
|
@@ -40,10 +40,10 @@ export const BRAINSTORM = {
|
|
|
40
40
|
"**Classify depth and scope** — pick Lightweight / Standard / Deep; decompose independent subsystems before deeper work.",
|
|
41
41
|
"**Premise check (one pass)** — answer the three gstack-style questions in the artifact body: *Right problem? Direct path? What if we do nothing?* Take a position; do not hedge.",
|
|
42
42
|
"**Reframe with How Might We** — write a single `How Might We …?` line that names the user, the desired outcome, and the constraint. This is the altitude check before approaches.",
|
|
43
|
-
"**Sharpening questions (3-5)** — capture decision-changing question/answer pairs in the `Sharpening Questions` table with the actual decision impact;
|
|
43
|
+
"**Sharpening questions (3-5)** — capture decision-changing question/answer pairs in the `Sharpening Questions` table with the actual decision impact; only non-critical preference/default assumptions may continue. STOP and ask on scope, architecture, security, data loss, public API, migration, auth/pricing, or user-approval uncertainty.",
|
|
44
44
|
"**Use compact discovery for simple apps** — for concrete low-risk asks (todo app, landing page, local widget), do one context pass, compare one baseline and one challenger, then ask for one explicit approval; do not drag the user through a full workshop.",
|
|
45
45
|
"**Short-circuit concrete asks** — for unambiguous implementation-only requests, write a compact brainstorm stub (context, problem, approved intent, constraints, assumptions) and ask for one explicit approval.",
|
|
46
|
-
"**Ask only decision-changing questions** — one at a time; if answers would not change approach, state the assumption and continue.",
|
|
46
|
+
"**Ask only decision-changing questions** — one at a time; if answers would not change approach and are non-critical preference/default assumptions, state the assumption and continue; STOP on scope, architecture, security, data loss, public API, migration, auth/pricing, or user approval uncertainty.",
|
|
47
47
|
"**Compare 2-3 distinct approaches with stable Role/Upside columns** — Role values are `baseline` | `challenger` | `wild-card`; Upside is `low` | `modest` | `high` | `higher`; include real trade-offs and reuse notes; include exactly one challenger with explicit `high` or `higher` upside.",
|
|
48
48
|
"**Collect reaction before recommending** — ask which option feels closest and what concern remains, then recommend based on that reaction.",
|
|
49
49
|
"**Write the `Not Doing` list** — name 3-5 things this brainstorm explicitly is not committing to (vs. deferred). This protects scope from silent enlargement and the next stage from rework.",
|
|
@@ -55,7 +55,7 @@ export const BRAINSTORM = {
|
|
|
55
55
|
"Start from observed project context; if the idea is vague, first narrow the project type with **one** structured question, then keep going.",
|
|
56
56
|
"Lead with the premise check (right problem / direct path / what if nothing) and the `How Might We` reframing before approaches; both go in the artifact, not just the chat.",
|
|
57
57
|
"Ask at most one question per turn, only when decision-changing; if using a structured question tool, send exactly one question object, not a multi-question form.",
|
|
58
|
-
"
|
|
58
|
+
"Only non-critical preference/default assumptions may continue inline. STOP and ask when uncertainty affects scope, architecture, security, data loss, public API, migration, auth/pricing, or user approval.",
|
|
59
59
|
"For simple greenfield web apps, present a compact A/B choice with one recommended path and one higher-upside challenger; keep the artifact concise but structurally complete (Context, Premise, How Might We, Sharpening Questions, Approaches, Reaction, Selected Direction, Not Doing).",
|
|
60
60
|
"Show approaches before the recommendation; include a higher-upside challenger and gather reaction first.",
|
|
61
61
|
"Self-review before approval: re-read the artifact, fix contradictions/placeholders/weak trade-offs, then ask for approval. Do not ask for approval on a draft you have not re-read.",
|