cclaw-cli 1.0.0 → 2.0.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/dist/artifact-linter/brainstorm.js +15 -1
- package/dist/artifact-linter/design.js +14 -0
- package/dist/artifact-linter/scope.js +14 -0
- package/dist/artifact-linter/shared.d.ts +1 -0
- package/dist/artifact-linter/shared.js +32 -0
- package/dist/artifact-linter.js +11 -1
- package/dist/content/hook-events.js +1 -2
- package/dist/content/hook-manifest.d.ts +3 -4
- package/dist/content/hook-manifest.js +22 -25
- package/dist/content/hooks.js +54 -14
- package/dist/content/meta-skill.js +4 -3
- package/dist/content/node-hooks.js +259 -89
- package/dist/content/observe.js +3 -3
- package/dist/content/opencode-plugin.js +0 -6
- package/dist/content/skills-elicitation.d.ts +1 -0
- package/dist/content/skills-elicitation.js +123 -0
- package/dist/content/skills.js +6 -4
- package/dist/content/stages/brainstorm.js +7 -3
- package/dist/content/stages/design.js +4 -0
- package/dist/content/stages/scope.js +6 -2
- package/dist/content/start-command.js +4 -4
- package/dist/content/templates.js +21 -0
- package/dist/flow-state.d.ts +7 -0
- package/dist/flow-state.js +1 -0
- package/dist/hook-schemas/claude-hooks.v1.json +2 -3
- package/dist/hook-schemas/codex-hooks.v1.json +1 -1
- package/dist/hook-schemas/cursor-hooks.v1.json +1 -1
- package/dist/install.js +12 -3
- package/dist/internal/advance-stage/advance.js +22 -1
- package/dist/internal/advance-stage/parsers.d.ts +1 -0
- package/dist/internal/advance-stage/parsers.js +6 -0
- package/dist/run-persistence.d.ts +1 -1
- package/dist/run-persistence.js +29 -2
- package/dist/runtime/run-hook.mjs +259 -89
- package/dist/track-heuristics.d.ts +7 -1
- package/dist/track-heuristics.js +12 -0
- package/package.json +1 -1
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { checkCriticPredictionsContract, sectionBodyByName, validateApproachesTaxonomy, headingLineIndex, meaningfulLineCount, parseShortCircuitStatus, validateCalibratedSelfReview, markdownFieldRegex } from "./shared.js";
|
|
3
|
+
import { checkCriticPredictionsContract, sectionBodyByName, validateApproachesTaxonomy, headingLineIndex, meaningfulLineCount, getMarkdownTableRows, parseShortCircuitStatus, validateCalibratedSelfReview, markdownFieldRegex } from "./shared.js";
|
|
4
4
|
export async function lintBrainstormStage(ctx) {
|
|
5
5
|
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
|
|
6
|
+
const qaLogBody = sectionBodyByName(sections, "Q&A Log");
|
|
7
|
+
const qaLogRows = qaLogBody ? getMarkdownTableRows(qaLogBody) : [];
|
|
8
|
+
const qaLogOk = qaLogBody !== null && qaLogRows.length > 0;
|
|
9
|
+
findings.push({
|
|
10
|
+
section: "qa_log_missing",
|
|
11
|
+
required: false,
|
|
12
|
+
rule: "[P3] qa_log_missing — Q&A Log empty — confirm you actually had a dialogue with the user, not a draft from memory.",
|
|
13
|
+
found: qaLogOk,
|
|
14
|
+
details: qaLogOk
|
|
15
|
+
? `Q&A Log contains ${qaLogRows.length} data row(s).`
|
|
16
|
+
: qaLogBody === null
|
|
17
|
+
? "Missing `## Q&A Log` section."
|
|
18
|
+
: "Q&A Log is present but has zero data rows."
|
|
19
|
+
});
|
|
6
20
|
// Brainstorm Iron Law: "NO ARTIFACT IS COMPLETE WITHOUT AN EXPLICITLY
|
|
7
21
|
// APPROVED DIRECTION — SILENCE IS NOT APPROVAL." Previously this was
|
|
8
22
|
// prose-only — nothing failed when the Selected Direction section
|
|
@@ -204,6 +204,20 @@ async function runStaleDiagramAudit(projectRoot, artifactPath, artifactRaw, code
|
|
|
204
204
|
}
|
|
205
205
|
export async function lintDesignStage(ctx) {
|
|
206
206
|
const { projectRoot, track, raw, absFile, sections, findings, parsedFrontmatter, brainstormShortCircuitBody, brainstormShortCircuitActivated, staleDiagramAuditEnabled, isTrivialOverride } = ctx;
|
|
207
|
+
const qaLogBody = sectionBodyByName(sections, "Q&A Log");
|
|
208
|
+
const qaLogRows = qaLogBody ? getMarkdownTableRows(qaLogBody) : [];
|
|
209
|
+
const qaLogOk = qaLogBody !== null && qaLogRows.length > 0;
|
|
210
|
+
findings.push({
|
|
211
|
+
section: "qa_log_missing",
|
|
212
|
+
required: false,
|
|
213
|
+
rule: "[P3] qa_log_missing — Q&A Log empty — confirm you actually had a dialogue with the user, not a draft from memory.",
|
|
214
|
+
found: qaLogOk,
|
|
215
|
+
details: qaLogOk
|
|
216
|
+
? `Q&A Log contains ${qaLogRows.length} data row(s).`
|
|
217
|
+
: qaLogBody === null
|
|
218
|
+
? "Missing `## Q&A Log` section."
|
|
219
|
+
: "Q&A Log is present but has zero data rows."
|
|
220
|
+
});
|
|
207
221
|
const criticPredictions = checkCriticPredictionsContract(sections);
|
|
208
222
|
if (criticPredictions !== null) {
|
|
209
223
|
findings.push({
|
|
@@ -12,6 +12,20 @@ export async function lintScopeStage(ctx) {
|
|
|
12
12
|
sectionBodyByName(sections, "Scope Summary") ?? "",
|
|
13
13
|
lockedDecisionsBody
|
|
14
14
|
].join("\n");
|
|
15
|
+
const qaLogBody = sectionBodyByName(sections, "Q&A Log");
|
|
16
|
+
const qaLogRows = qaLogBody ? getMarkdownTableRows(qaLogBody) : [];
|
|
17
|
+
const qaLogOk = qaLogBody !== null && qaLogRows.length > 0;
|
|
18
|
+
findings.push({
|
|
19
|
+
section: "qa_log_missing",
|
|
20
|
+
required: false,
|
|
21
|
+
rule: "[P3] qa_log_missing — Q&A Log empty — confirm you actually had a dialogue with the user, not a draft from memory.",
|
|
22
|
+
found: qaLogOk,
|
|
23
|
+
details: qaLogOk
|
|
24
|
+
? `Q&A Log contains ${qaLogRows.length} data row(s).`
|
|
25
|
+
: qaLogBody === null
|
|
26
|
+
? "Missing `## Q&A Log` section."
|
|
27
|
+
: "Q&A Log is present but has zero data rows."
|
|
28
|
+
});
|
|
15
29
|
const strategistRequired = selectedScopeMode === "SCOPE EXPANSION" || selectedScopeMode === "SELECTIVE EXPANSION";
|
|
16
30
|
if (strategistRequired) {
|
|
17
31
|
const delegationLedger = await readDelegationLedger(projectRoot);
|
|
@@ -26,6 +26,7 @@ export type H2SectionMap = Map<string, string>;
|
|
|
26
26
|
* into multiple passes.
|
|
27
27
|
*/
|
|
28
28
|
export declare function extractH2Sections(markdown: string): H2SectionMap;
|
|
29
|
+
export declare function duplicateH2Headings(markdown: string): string[];
|
|
29
30
|
export declare function headingPresent(sections: H2SectionMap, section: string): boolean;
|
|
30
31
|
export declare function sectionBodyByName(sections: H2SectionMap, section: string): string | null;
|
|
31
32
|
export declare function sectionBodyByAnyName(sections: H2SectionMap, sectionNames: string[]): string | null;
|
|
@@ -57,6 +57,38 @@ export function extractH2Sections(markdown) {
|
|
|
57
57
|
flush();
|
|
58
58
|
return sections;
|
|
59
59
|
}
|
|
60
|
+
export function duplicateH2Headings(markdown) {
|
|
61
|
+
const lines = markdown.split(/\r?\n/);
|
|
62
|
+
let fenced = null;
|
|
63
|
+
const counts = new Map();
|
|
64
|
+
const displayHeading = new Map();
|
|
65
|
+
for (const line of lines) {
|
|
66
|
+
const fenceMatch = /^(```|~~~)/u.exec(line);
|
|
67
|
+
if (fenceMatch) {
|
|
68
|
+
if (fenced === null) {
|
|
69
|
+
fenced = fenceMatch[1] ?? null;
|
|
70
|
+
}
|
|
71
|
+
else if (line.startsWith(fenced)) {
|
|
72
|
+
fenced = null;
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (fenced !== null)
|
|
77
|
+
continue;
|
|
78
|
+
const match = /^##\s+(.+)$/u.exec(line);
|
|
79
|
+
if (!match)
|
|
80
|
+
continue;
|
|
81
|
+
const heading = normalizeHeadingTitle(match[1] ?? "");
|
|
82
|
+
const key = heading.toLowerCase();
|
|
83
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
84
|
+
if (!displayHeading.has(key)) {
|
|
85
|
+
displayHeading.set(key, heading);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return [...counts.entries()]
|
|
89
|
+
.filter(([, count]) => count > 1)
|
|
90
|
+
.map(([key]) => displayHeading.get(key) ?? key);
|
|
91
|
+
}
|
|
60
92
|
export function headingPresent(sections, section) {
|
|
61
93
|
const want = normalizeHeadingTitle(section).toLowerCase();
|
|
62
94
|
for (const h of sections.keys()) {
|
package/dist/artifact-linter.js
CHANGED
|
@@ -3,7 +3,7 @@ import { resolveArtifactPath as resolveStageArtifactPath } from "./artifact-path
|
|
|
3
3
|
import { readConfig } from "./config.js";
|
|
4
4
|
import { exists } from "./fs-utils.js";
|
|
5
5
|
import { stageSchema } from "./content/stage-schema.js";
|
|
6
|
-
import { extractH2Sections, extractLockedDecisionAnchors, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody } from "./artifact-linter/shared.js";
|
|
6
|
+
import { duplicateH2Headings, extractH2Sections, extractLockedDecisionAnchors, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody } from "./artifact-linter/shared.js";
|
|
7
7
|
import { lintBrainstormStage } from "./artifact-linter/brainstorm.js";
|
|
8
8
|
import { lintDesignStage } from "./artifact-linter/design.js";
|
|
9
9
|
import { lintPlanStage } from "./artifact-linter/plan.js";
|
|
@@ -48,6 +48,16 @@ export async function lintArtifact(projectRoot, stage, track = "standard") {
|
|
|
48
48
|
}
|
|
49
49
|
const raw = await fs.readFile(absFile, "utf8");
|
|
50
50
|
const sections = extractH2Sections(raw);
|
|
51
|
+
const duplicateHeadings = duplicateH2Headings(raw);
|
|
52
|
+
if (duplicateHeadings.length > 0) {
|
|
53
|
+
findings.push({
|
|
54
|
+
section: "duplicate_h2_heading",
|
|
55
|
+
required: false,
|
|
56
|
+
rule: "[P3] keep each `##` heading unique within an artifact; append updates to the existing section instead of cloning headings.",
|
|
57
|
+
found: false,
|
|
58
|
+
details: `Duplicate H2 heading(s): ${duplicateHeadings.join(", ")}. Merge edits into the existing heading to avoid split contracts.`
|
|
59
|
+
});
|
|
60
|
+
}
|
|
51
61
|
const projectConfig = await readConfig(projectRoot);
|
|
52
62
|
const parsedFrontmatter = parseFrontmatter(raw);
|
|
53
63
|
const frontmatterMissingKeys = FRONTMATTER_REQUIRED_KEYS.filter((key) => {
|
|
@@ -11,8 +11,7 @@ const OPENCODE_SEMANTIC_COVERAGE = {
|
|
|
11
11
|
pre_tool_prompt_guard: "plugin tool.execute.before -> prompt-guard",
|
|
12
12
|
pre_tool_workflow_guard: "plugin tool.execute.before -> workflow-guard",
|
|
13
13
|
post_tool_context_monitor: "plugin tool.execute.after -> context-monitor",
|
|
14
|
-
stop_handoff: "plugin session.idle -> stop-handoff"
|
|
15
|
-
precompact_compat: "plugin session.compacted -> pre-compact"
|
|
14
|
+
stop_handoff: "plugin session.idle -> stop-handoff"
|
|
16
15
|
};
|
|
17
16
|
/**
|
|
18
17
|
* Public semantic coverage map derived from `HOOK_MANIFEST` for
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
export declare const HOOK_MANIFEST_HARNESSES: readonly ["claude", "cursor", "codex"];
|
|
23
23
|
export type HookManifestHarness = (typeof HOOK_MANIFEST_HARNESSES)[number];
|
|
24
|
-
export declare const HOOK_HANDLERS: readonly ["session-start", "prompt-guard", "workflow-guard", "
|
|
24
|
+
export declare const HOOK_HANDLERS: readonly ["session-start", "prompt-guard", "workflow-guard", "pre-tool-pipeline", "prompt-pipeline", "context-monitor", "stop-handoff", "verify-current-state"];
|
|
25
25
|
export type HookHandlerId = (typeof HOOK_HANDLERS)[number];
|
|
26
26
|
export interface HookBinding {
|
|
27
27
|
/**
|
|
@@ -35,8 +35,7 @@ export interface HookBinding {
|
|
|
35
35
|
* Within a single (harness, event) group, entries are sorted by
|
|
36
36
|
* `priority` ASC, ties broken by manifest-declaration order. Use
|
|
37
37
|
* this to express "this handler must run BEFORE/AFTER that handler
|
|
38
|
-
* on the same event"
|
|
39
|
-
* on cursor `sessionCompact`). Default `0`.
|
|
38
|
+
* on the same event". Default `0`.
|
|
40
39
|
*/
|
|
41
40
|
priority?: number;
|
|
42
41
|
}
|
|
@@ -50,7 +49,7 @@ export interface HookHandlerSpec {
|
|
|
50
49
|
semantic: HookSemanticEvent | null;
|
|
51
50
|
bindings: Partial<Record<HookManifestHarness, HookBinding[]>>;
|
|
52
51
|
}
|
|
53
|
-
export declare const HOOK_SEMANTIC_EVENTS: readonly ["session_rehydrate", "pre_tool_prompt_guard", "pre_tool_workflow_guard", "post_tool_context_monitor", "stop_handoff", "
|
|
52
|
+
export declare const HOOK_SEMANTIC_EVENTS: readonly ["session_rehydrate", "pre_tool_prompt_guard", "pre_tool_workflow_guard", "post_tool_context_monitor", "stop_handoff", "strict_state_verify"];
|
|
54
53
|
export type HookSemanticEvent = (typeof HOOK_SEMANTIC_EVENTS)[number];
|
|
55
54
|
export declare const HOOK_MANIFEST: readonly HookHandlerSpec[];
|
|
56
55
|
export interface EventGroup {
|
|
@@ -24,9 +24,10 @@ export const HOOK_HANDLERS = [
|
|
|
24
24
|
"session-start",
|
|
25
25
|
"prompt-guard",
|
|
26
26
|
"workflow-guard",
|
|
27
|
+
"pre-tool-pipeline",
|
|
28
|
+
"prompt-pipeline",
|
|
27
29
|
"context-monitor",
|
|
28
30
|
"stop-handoff",
|
|
29
|
-
"pre-compact",
|
|
30
31
|
"verify-current-state"
|
|
31
32
|
];
|
|
32
33
|
export const HOOK_SEMANTIC_EVENTS = [
|
|
@@ -35,7 +36,6 @@ export const HOOK_SEMANTIC_EVENTS = [
|
|
|
35
36
|
"pre_tool_workflow_guard",
|
|
36
37
|
"post_tool_context_monitor",
|
|
37
38
|
"stop_handoff",
|
|
38
|
-
"precompact_compat",
|
|
39
39
|
"strict_state_verify"
|
|
40
40
|
];
|
|
41
41
|
export const HOOK_MANIFEST = [
|
|
@@ -59,12 +59,7 @@ export const HOOK_MANIFEST = [
|
|
|
59
59
|
description: "Stage-aware prompt gate (iron-laws + strictness).",
|
|
60
60
|
semantic: "pre_tool_prompt_guard",
|
|
61
61
|
bindings: {
|
|
62
|
-
claude: [{ event: "PreToolUse", matcher: "*" }]
|
|
63
|
-
cursor: [{ event: "preToolUse", matcher: "*" }],
|
|
64
|
-
codex: [
|
|
65
|
-
{ event: "UserPromptSubmit" },
|
|
66
|
-
{ event: "PreToolUse", matcher: "Bash|bash" }
|
|
67
|
-
]
|
|
62
|
+
claude: [{ event: "PreToolUse", matcher: "*" }]
|
|
68
63
|
}
|
|
69
64
|
},
|
|
70
65
|
{
|
|
@@ -72,11 +67,26 @@ export const HOOK_MANIFEST = [
|
|
|
72
67
|
description: "TDD and workflow gate on Write/Edit/Bash style tool invocations.",
|
|
73
68
|
semantic: "pre_tool_workflow_guard",
|
|
74
69
|
bindings: {
|
|
75
|
-
claude: [{ event: "PreToolUse", matcher: "Write|Edit|MultiEdit|NotebookEdit|Bash" }]
|
|
70
|
+
claude: [{ event: "PreToolUse", matcher: "Write|Edit|MultiEdit|NotebookEdit|Bash" }]
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
handler: "pre-tool-pipeline",
|
|
75
|
+
description: "In-process pre-tool pipeline for harnesses that would otherwise spawn prompt/workflow guards separately.",
|
|
76
|
+
semantic: null,
|
|
77
|
+
bindings: {
|
|
76
78
|
cursor: [{ event: "preToolUse", matcher: "*" }],
|
|
77
79
|
codex: [{ event: "PreToolUse", matcher: "Bash|bash" }]
|
|
78
80
|
}
|
|
79
81
|
},
|
|
82
|
+
{
|
|
83
|
+
handler: "prompt-pipeline",
|
|
84
|
+
description: "In-process prompt pipeline for Codex UserPromptSubmit (prompt-guard + verify-current-state).",
|
|
85
|
+
semantic: "strict_state_verify",
|
|
86
|
+
bindings: {
|
|
87
|
+
codex: [{ event: "UserPromptSubmit" }]
|
|
88
|
+
}
|
|
89
|
+
},
|
|
80
90
|
{
|
|
81
91
|
handler: "context-monitor",
|
|
82
92
|
description: "Post-tool context usage + stage signal monitor.",
|
|
@@ -97,24 +107,11 @@ export const HOOK_MANIFEST = [
|
|
|
97
107
|
codex: [{ event: "Stop", timeout: 10 }]
|
|
98
108
|
}
|
|
99
109
|
},
|
|
100
|
-
{
|
|
101
|
-
handler: "pre-compact",
|
|
102
|
-
description: "No-op compatibility hook for harness pre-compact events; session-start rehydrates from flow-state, artifacts, and knowledge.",
|
|
103
|
-
semantic: "precompact_compat",
|
|
104
|
-
bindings: {
|
|
105
|
-
claude: [{ event: "PreCompact", matcher: "manual|auto", timeout: 10 }],
|
|
106
|
-
// Keep this before session-start on cursor `sessionCompact` so the
|
|
107
|
-
// compatibility handler runs before rehydration.
|
|
108
|
-
cursor: [{ event: "sessionCompact", priority: -10 }]
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
110
|
{
|
|
112
111
|
handler: "verify-current-state",
|
|
113
|
-
description: "Supplementary
|
|
114
|
-
semantic:
|
|
115
|
-
bindings: {
|
|
116
|
-
codex: [{ event: "UserPromptSubmit" }]
|
|
117
|
-
}
|
|
112
|
+
description: "Supplementary strict-mode guard callable from in-process pipelines to assert live state matches flow.",
|
|
113
|
+
semantic: null,
|
|
114
|
+
bindings: {}
|
|
118
115
|
}
|
|
119
116
|
];
|
|
120
117
|
/** Sanity: every harness in HOOK_MANIFEST_HARNESSES must be a HarnessId. */
|
package/dist/content/hooks.js
CHANGED
|
@@ -5,6 +5,15 @@ import { RUNTIME_ROOT } from "../constants.js";
|
|
|
5
5
|
import { DELEGATION_DISPATCH_SURFACES, DELEGATION_DISPATCH_SURFACE_PATH_PREFIXES } from "../delegation.js";
|
|
6
6
|
function resolveCliRuntimeForGeneratedHook() {
|
|
7
7
|
const here = fileURLToPath(import.meta.url);
|
|
8
|
+
// Vitest runs init/sync from src/ and expects helpers to execute the same
|
|
9
|
+
// source runtime, even when a stale dist/ exists in the repository.
|
|
10
|
+
if (process.env.VITEST === "true") {
|
|
11
|
+
const sourceCli = path.resolve(path.dirname(here), "..", "cli.ts");
|
|
12
|
+
const viteNode = path.resolve(path.dirname(here), "..", "..", "node_modules", "vite-node", "vite-node.mjs");
|
|
13
|
+
if (existsSync(sourceCli) && existsSync(viteNode)) {
|
|
14
|
+
return { entrypoint: viteNode, argsPrefix: ["--script", sourceCli] };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
8
17
|
const candidates = [
|
|
9
18
|
path.resolve(path.dirname(here), "..", "cli.js"),
|
|
10
19
|
path.resolve(path.dirname(here), "..", "..", "dist", "cli.js")
|
|
@@ -15,15 +24,6 @@ function resolveCliRuntimeForGeneratedHook() {
|
|
|
15
24
|
if (existsSync(candidate))
|
|
16
25
|
return { entrypoint: candidate, argsPrefix: [] };
|
|
17
26
|
}
|
|
18
|
-
// Vitest exercises init/sync directly from src/ without a compiled dist/.
|
|
19
|
-
// Route that dev-only shape through vite-node so hooks still prove a local runtime.
|
|
20
|
-
if (process.env.VITEST === "true") {
|
|
21
|
-
const sourceCli = path.resolve(path.dirname(here), "..", "cli.ts");
|
|
22
|
-
const viteNode = path.resolve(path.dirname(here), "..", "..", "node_modules", "vite-node", "vite-node.mjs");
|
|
23
|
-
if (existsSync(sourceCli) && existsSync(viteNode)) {
|
|
24
|
-
return { entrypoint: viteNode, argsPrefix: ["--script", sourceCli] };
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
27
|
return { entrypoint: null, argsPrefix: [] };
|
|
28
28
|
}
|
|
29
29
|
function internalHelperScript(helperName, internalSubcommand, usage, options) {
|
|
@@ -42,6 +42,7 @@ const INTERNAL_SUBCOMMAND = ${JSON.stringify(internalSubcommand)};
|
|
|
42
42
|
const USAGE = ${JSON.stringify(usage)};
|
|
43
43
|
const POSITIONAL_ARG_NAME = ${JSON.stringify(options?.positionalArgName ?? null)};
|
|
44
44
|
const POSITIONAL_ARG_REQUIRED = ${JSON.stringify(options?.positionalArgRequired === true)};
|
|
45
|
+
const DEFAULT_QUIET_ENV_VAR = ${JSON.stringify(options?.defaultQuietEnvVar ?? null)};
|
|
45
46
|
|
|
46
47
|
async function detectRoot() {
|
|
47
48
|
const candidates = [
|
|
@@ -88,6 +89,19 @@ async function main() {
|
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
if (DEFAULT_QUIET_ENV_VAR !== null) {
|
|
93
|
+
const envRaw = process.env[DEFAULT_QUIET_ENV_VAR];
|
|
94
|
+
if (typeof envRaw !== "string" || envRaw.trim().length === 0) {
|
|
95
|
+
process.env[DEFAULT_QUIET_ENV_VAR] = "1";
|
|
96
|
+
}
|
|
97
|
+
const quietRaw = (process.env[DEFAULT_QUIET_ENV_VAR] ?? "").trim().toLowerCase();
|
|
98
|
+
const quietEnabled = !/^(0|false|no|off)$/u.test(quietRaw);
|
|
99
|
+
const alreadyQuiet = flags.includes("--quiet");
|
|
100
|
+
if (quietEnabled && !alreadyQuiet) {
|
|
101
|
+
flags = [...flags, "--quiet"];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
const root = await detectRoot();
|
|
92
106
|
const runtimePath = path.join(root, RUNTIME_ROOT);
|
|
93
107
|
try {
|
|
@@ -171,13 +185,13 @@ void main();
|
|
|
171
185
|
`;
|
|
172
186
|
}
|
|
173
187
|
export function startFlowScript() {
|
|
174
|
-
return internalHelperScript("start-flow", "start-flow", "Usage: node " + RUNTIME_ROOT + "/hooks/start-flow.mjs --track=<standard|medium|quick> [--class=...] [--prompt=...] [--stack=...] [--reason=...] [--reclassify] [--force-reset]");
|
|
188
|
+
return internalHelperScript("start-flow", "start-flow", "Usage: node " + RUNTIME_ROOT + "/hooks/start-flow.mjs --track=<standard|medium|quick> [--class=...] [--prompt=...] [--stack=...] [--reason=...] [--reclassify] [--force-reset]", { defaultQuietEnvVar: "CCLAW_START_FLOW_QUIET" });
|
|
175
189
|
}
|
|
176
190
|
export function cancelRunScript() {
|
|
177
191
|
return internalHelperScript("cancel-run", "cancel-run", "Usage: node " + RUNTIME_ROOT + "/hooks/cancel-run.mjs --reason=<text> [--disposition=<cancelled|abandoned>] [--name=<slug>]");
|
|
178
192
|
}
|
|
179
193
|
export function stageCompleteScript() {
|
|
180
|
-
return internalHelperScript("stage-complete", "advance-stage", "Usage: node " + RUNTIME_ROOT + "/hooks/stage-complete.mjs <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...] [--accept-proactive-waiver] [--accept-proactive-waiver-reason=...] [--json]", {
|
|
194
|
+
return internalHelperScript("stage-complete", "advance-stage", "Usage: node " + RUNTIME_ROOT + "/hooks/stage-complete.mjs <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...] [--accept-proactive-waiver] [--accept-proactive-waiver-reason=...] [--skip-questions] [--json]", {
|
|
181
195
|
positionalArgName: "stage",
|
|
182
196
|
positionalArgRequired: true
|
|
183
197
|
});
|
|
@@ -280,7 +294,7 @@ function usage() {
|
|
|
280
294
|
process.stderr.write([
|
|
281
295
|
"Usage:",
|
|
282
296
|
" node .cclaw/hooks/delegation-record.mjs --stage=<stage> --agent=<agent> --mode=<mandatory|proactive> --status=<scheduled|launched|acknowledged|completed|failed|waived|stale> --span-id=<id> [--dispatch-id=<id>] [--worker-run-id=<id>] [--dispatch-surface=<surface>] [--agent-definition-path=<path>] [--ack-ts=<iso>] [--launched-ts=<iso>] [--completed-ts=<iso>] [--evidence-ref=<ref>] [--waiver-reason=<text>] [--json]",
|
|
283
|
-
" node .cclaw/hooks/delegation-record.mjs --rerecord --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path> [--ack-ts=<iso>] [--completed-ts=<iso>] [--json]",
|
|
297
|
+
" node .cclaw/hooks/delegation-record.mjs --rerecord --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path> [--ack-ts=<iso>] [--completed-ts=<iso>] [--evidence-ref=<ref>] [--json]",
|
|
284
298
|
"",
|
|
285
299
|
"Allowed --dispatch-surface values:",
|
|
286
300
|
" " + VALID_DISPATCH_SURFACES.join(", "),
|
|
@@ -322,6 +336,18 @@ async function pathExists(filePath) {
|
|
|
322
336
|
}
|
|
323
337
|
}
|
|
324
338
|
|
|
339
|
+
function normalizeEvidenceRefs(args) {
|
|
340
|
+
if (Array.isArray(args["evidence-refs"])) {
|
|
341
|
+
return args["evidence-refs"]
|
|
342
|
+
.filter((ref) => typeof ref === "string" && ref.trim().length > 0)
|
|
343
|
+
.map((ref) => ref.trim());
|
|
344
|
+
}
|
|
345
|
+
if (typeof args["evidence-ref"] === "string" && args["evidence-ref"].trim().length > 0) {
|
|
346
|
+
return [args["evidence-ref"].trim()];
|
|
347
|
+
}
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
|
|
325
351
|
function buildRow(args, status, runId, now) {
|
|
326
352
|
const fulfillmentMode = args["dispatch-surface"] === "role-switch"
|
|
327
353
|
? "role-switch"
|
|
@@ -340,7 +366,7 @@ function buildRow(args, status, runId, now) {
|
|
|
340
366
|
agentDefinitionPath: args["agent-definition-path"],
|
|
341
367
|
fulfillmentMode,
|
|
342
368
|
waiverReason: args["waiver-reason"],
|
|
343
|
-
evidenceRefs: args
|
|
369
|
+
evidenceRefs: normalizeEvidenceRefs(args),
|
|
344
370
|
runId,
|
|
345
371
|
startTs: now,
|
|
346
372
|
ts: now,
|
|
@@ -420,6 +446,18 @@ async function runRerecord(args, json) {
|
|
|
420
446
|
emitProblems(["no legacy ledger entry found for --span-id=" + args["span-id"]], json, 1);
|
|
421
447
|
return;
|
|
422
448
|
}
|
|
449
|
+
const explicitEvidenceRef =
|
|
450
|
+
typeof args["evidence-ref"] === "string" && args["evidence-ref"].trim().length > 0
|
|
451
|
+
? args["evidence-ref"].trim()
|
|
452
|
+
: "";
|
|
453
|
+
const legacyEvidenceRefs = Array.isArray(legacyEntry.evidenceRefs)
|
|
454
|
+
? legacyEntry.evidenceRefs
|
|
455
|
+
.filter((ref) => typeof ref === "string" && ref.trim().length > 0)
|
|
456
|
+
.map((ref) => ref.trim())
|
|
457
|
+
: [];
|
|
458
|
+
const mergedEvidenceRefs = explicitEvidenceRef.length > 0
|
|
459
|
+
? [explicitEvidenceRef]
|
|
460
|
+
: legacyEvidenceRefs;
|
|
423
461
|
if (args["dispatch-surface"] !== "role-switch") {
|
|
424
462
|
if (!dispatchSurfaceMatchesPath(args["dispatch-surface"], args["agent-definition-path"])) {
|
|
425
463
|
const allowedPrefixes = SURFACE_PATH_PREFIXES[args["dispatch-surface"]];
|
|
@@ -445,7 +483,9 @@ async function runRerecord(args, json) {
|
|
|
445
483
|
"agent-definition-path": args["agent-definition-path"],
|
|
446
484
|
"ack-ts": args["ack-ts"] || legacyEntry.ackTs || now,
|
|
447
485
|
"completed-ts": args["completed-ts"] || legacyEntry.completedTs || now,
|
|
448
|
-
"launched-ts": args["launched-ts"] || legacyEntry.launchedTs || now
|
|
486
|
+
"launched-ts": args["launched-ts"] || legacyEntry.launchedTs || now,
|
|
487
|
+
"evidence-ref": explicitEvidenceRef.length > 0 ? explicitEvidenceRef : undefined,
|
|
488
|
+
"evidence-refs": mergedEvidenceRefs
|
|
449
489
|
};
|
|
450
490
|
const status = "completed";
|
|
451
491
|
const clean = Object.fromEntries(Object.entries(buildRow(merged, status, runId, now)).filter(([, value]) => value !== undefined));
|
|
@@ -44,9 +44,10 @@ Substantive vs. non-substantive:
|
|
|
44
44
|
- **Non-substantive** (skill load optional): one-line acknowledgement,
|
|
45
45
|
clarifying a typo, confirming a prior answer, pure conversation.
|
|
46
46
|
|
|
47
|
-
If
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
If \`.cclaw/state/flow-state.json\` is missing, treat it as a normal fresh-init
|
|
48
|
+
state and route through \`/cc <idea>\` to start the first tracked run.
|
|
49
|
+
If the file exists but is corrupt/unreadable, stop and route through \`/cc\`
|
|
50
|
+
before any substantive response.
|
|
50
51
|
|
|
51
52
|
## Red Flags (stop and re-route)
|
|
52
53
|
|