cclaw-cli 0.5.17 → 0.7.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/cli.d.ts +6 -2
- package/dist/cli.js +45 -5
- package/dist/config.d.ts +12 -2
- package/dist/config.js +79 -5
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +3 -2
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +145 -0
- package/dist/content/learnings.js +91 -18
- package/dist/content/meta-skill.js +52 -3
- package/dist/content/next-command.js +8 -0
- package/dist/content/observe.js +18 -0
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/stage-schema.d.ts +18 -1
- package/dist/content/stage-schema.js +36 -10
- package/dist/content/start-command.js +30 -7
- package/dist/content/status-command.d.ts +9 -0
- package/dist/content/status-command.js +154 -0
- package/dist/content/templates.js +41 -5
- package/dist/content/utility-skills.d.ts +16 -2
- package/dist/content/utility-skills.js +721 -3
- package/dist/delegation.d.ts +6 -1
- package/dist/delegation.js +3 -2
- package/dist/doctor.js +38 -1
- package/dist/flow-state.d.ts +16 -4
- package/dist/flow-state.js +50 -11
- package/dist/harness-adapters.js +1 -0
- package/dist/install.d.ts +4 -1
- package/dist/install.js +174 -10
- package/dist/policy.js +2 -1
- package/dist/runs.d.ts +15 -0
- package/dist/runs.js +59 -4
- package/dist/types.d.ts +42 -0
- package/dist/types.js +33 -0
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import type { HarnessId } from "./types.js";
|
|
2
|
+
import type { FlowTrack, HarnessId, InitProfile } from "./types.js";
|
|
3
3
|
type CommandName = "init" | "sync" | "doctor" | "upgrade" | "uninstall" | "archive";
|
|
4
4
|
interface ParsedArgs {
|
|
5
5
|
command?: CommandName;
|
|
6
6
|
harnesses?: HarnessId[];
|
|
7
|
+
track?: FlowTrack;
|
|
8
|
+
profile?: InitProfile;
|
|
7
9
|
reconcileGates?: boolean;
|
|
8
10
|
archiveName?: string;
|
|
9
11
|
showHelp?: boolean;
|
|
@@ -11,5 +13,7 @@ interface ParsedArgs {
|
|
|
11
13
|
}
|
|
12
14
|
export declare function usage(): string;
|
|
13
15
|
declare function parseHarnesses(raw: string): HarnessId[];
|
|
16
|
+
declare function parseTrack(raw: string): FlowTrack;
|
|
17
|
+
declare function parseProfile(raw: string): InitProfile;
|
|
14
18
|
declare function parseArgs(argv: string[]): ParsedArgs;
|
|
15
|
-
export { parseArgs, parseHarnesses };
|
|
19
|
+
export { parseArgs, parseHarnesses, parseTrack, parseProfile };
|
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,12 @@ import { readFileSync, realpathSync } from "node:fs";
|
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { HARNESS_IDS } from "./types.js";
|
|
6
|
+
import { FLOW_TRACKS, HARNESS_IDS, INIT_PROFILES } from "./types.js";
|
|
7
7
|
import { doctorChecks, doctorSucceeded } from "./doctor.js";
|
|
8
8
|
import { initCclaw, syncCclaw, uninstallCclaw, upgradeCclaw } from "./install.js";
|
|
9
9
|
import { error, info } from "./logger.js";
|
|
10
10
|
import { archiveRun } from "./runs.js";
|
|
11
|
+
import { RUNTIME_ROOT } from "./constants.js";
|
|
11
12
|
const INSTALLER_COMMANDS = ["init", "sync", "doctor", "upgrade", "uninstall", "archive"];
|
|
12
13
|
export function usage() {
|
|
13
14
|
return `cclaw - installer-first flow toolkit
|
|
@@ -19,7 +20,9 @@ Usage:
|
|
|
19
20
|
|
|
20
21
|
Commands:
|
|
21
22
|
init Bootstrap .cclaw runtime, state, and harness shims in this project.
|
|
22
|
-
Flags: --
|
|
23
|
+
Flags: --profile=<id> Pre-fill defaults. One of: minimal | standard | full. Default: standard.
|
|
24
|
+
--harnesses=<list> Comma list of harnesses (claude,cursor,opencode,codex). Overrides the profile default.
|
|
25
|
+
--track=<id> Flow track for new runs (standard | quick). Overrides the profile default.
|
|
23
26
|
sync Regenerate harness shim files from the current .cclaw config (non-destructive).
|
|
24
27
|
doctor Run health checks against the local .cclaw runtime. Exit code 2 on failure.
|
|
25
28
|
Flags: --reconcile-gates Recompute current-stage gate evidence before checks.
|
|
@@ -77,6 +80,20 @@ function parseHarnesses(raw) {
|
|
|
77
80
|
}
|
|
78
81
|
return requested;
|
|
79
82
|
}
|
|
83
|
+
function parseTrack(raw) {
|
|
84
|
+
const trimmed = raw.trim();
|
|
85
|
+
if (!FLOW_TRACKS.includes(trimmed)) {
|
|
86
|
+
throw new Error(`Unknown track: ${trimmed}. Supported: ${FLOW_TRACKS.join(", ")}`);
|
|
87
|
+
}
|
|
88
|
+
return trimmed;
|
|
89
|
+
}
|
|
90
|
+
function parseProfile(raw) {
|
|
91
|
+
const trimmed = raw.trim();
|
|
92
|
+
if (!INIT_PROFILES.includes(trimmed)) {
|
|
93
|
+
throw new Error(`Unknown profile: ${trimmed}. Supported: ${INIT_PROFILES.join(", ")}`);
|
|
94
|
+
}
|
|
95
|
+
return trimmed;
|
|
96
|
+
}
|
|
80
97
|
function parseArgs(argv) {
|
|
81
98
|
const parsed = {};
|
|
82
99
|
const helpFlag = argv.find((arg) => arg === "--help" || arg === "-h");
|
|
@@ -96,6 +113,14 @@ function parseArgs(argv) {
|
|
|
96
113
|
parsed.harnesses = parseHarnesses(flag.replace("--harnesses=", ""));
|
|
97
114
|
continue;
|
|
98
115
|
}
|
|
116
|
+
if (flag.startsWith("--track=")) {
|
|
117
|
+
parsed.track = parseTrack(flag.replace("--track=", ""));
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (flag.startsWith("--profile=")) {
|
|
121
|
+
parsed.profile = parseProfile(flag.replace("--profile=", ""));
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
99
124
|
if (flag === "--reconcile-gates") {
|
|
100
125
|
parsed.reconcileGates = true;
|
|
101
126
|
continue;
|
|
@@ -123,9 +148,14 @@ async function runCommand(parsed, ctx) {
|
|
|
123
148
|
if (command === "init") {
|
|
124
149
|
await initCclaw({
|
|
125
150
|
projectRoot: ctx.cwd,
|
|
126
|
-
harnesses: parsed.harnesses
|
|
151
|
+
harnesses: parsed.harnesses,
|
|
152
|
+
track: parsed.track,
|
|
153
|
+
profile: parsed.profile
|
|
127
154
|
});
|
|
128
|
-
|
|
155
|
+
const profileNote = parsed.profile ? ` profile=${parsed.profile}` : "";
|
|
156
|
+
const trackNote = parsed.track ? ` track=${parsed.track}` : "";
|
|
157
|
+
const suffix = profileNote || trackNote ? ` (${(profileNote + trackNote).trim()})` : "";
|
|
158
|
+
info(ctx, `Initialized .cclaw runtime and generated harness shims${suffix}`);
|
|
129
159
|
return 0;
|
|
130
160
|
}
|
|
131
161
|
if (command === "sync") {
|
|
@@ -153,6 +183,16 @@ async function runCommand(parsed, ctx) {
|
|
|
153
183
|
? ` Snapshotted ${archived.snapshottedStateFiles.length} state file(s) under ${archived.archivePath}/state and wrote archive-manifest.json.`
|
|
154
184
|
: "";
|
|
155
185
|
info(ctx, `Archived active artifacts to ${archived.archivePath}. Flow state reset to brainstorm.${snapshotSummary}`);
|
|
186
|
+
const k = archived.knowledge;
|
|
187
|
+
if (k.overThreshold) {
|
|
188
|
+
info(ctx, `Knowledge curation recommended: ${k.knowledgePath} now has ${k.activeEntryCount} active entries (soft threshold ${k.softThreshold}). Run \`/cc-learn curate\` to plan a soft-archive of stale/duplicate entries to ${RUNTIME_ROOT}/knowledge.archive.md.`);
|
|
189
|
+
}
|
|
190
|
+
else if (k.activeEntryCount > 0) {
|
|
191
|
+
info(ctx, `Knowledge: ${k.activeEntryCount}/${k.softThreshold} active entries. Run \`/cc-learn curate\` if you want a sweep before the next run.`);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
info(ctx, `Knowledge: 0 active entries in ${k.knowledgePath}. Capture lessons from this run with \`/cc-learn add\` before they fade.`);
|
|
195
|
+
}
|
|
156
196
|
return 0;
|
|
157
197
|
}
|
|
158
198
|
await uninstallCclaw(ctx.cwd);
|
|
@@ -190,4 +230,4 @@ function isDirectExecution() {
|
|
|
190
230
|
if (isDirectExecution()) {
|
|
191
231
|
void main();
|
|
192
232
|
}
|
|
193
|
-
export { parseArgs, parseHarnesses };
|
|
233
|
+
export { parseArgs, parseHarnesses, parseTrack, parseProfile };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import type { HarnessId, VibyConfig } from "./types.js";
|
|
1
|
+
import type { FlowTrack, HarnessId, InitProfile, LanguageRulePack, VibyConfig } from "./types.js";
|
|
2
2
|
export declare function configPath(projectRoot: string): string;
|
|
3
|
-
export declare function createDefaultConfig(harnesses?: HarnessId[]): VibyConfig;
|
|
3
|
+
export declare function createDefaultConfig(harnesses?: HarnessId[], defaultTrack?: FlowTrack): VibyConfig;
|
|
4
|
+
/**
|
|
5
|
+
* Build a VibyConfig for a named init profile. Profile defaults are applied
|
|
6
|
+
* first, then any explicit overrides (CLI flags) win. This keeps the profile
|
|
7
|
+
* contract deterministic and testable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createProfileConfig(profile: InitProfile, overrides?: {
|
|
10
|
+
harnesses?: HarnessId[];
|
|
11
|
+
defaultTrack?: FlowTrack;
|
|
12
|
+
languageRulePacks?: LanguageRulePack[];
|
|
13
|
+
}): VibyConfig;
|
|
4
14
|
export declare function readConfig(projectRoot: string): Promise<VibyConfig>;
|
|
5
15
|
export declare function writeConfig(projectRoot: string, config: VibyConfig): Promise<void>;
|
package/dist/config.js
CHANGED
|
@@ -3,17 +3,23 @@ import path from "node:path";
|
|
|
3
3
|
import { parse, stringify } from "yaml";
|
|
4
4
|
import { CCLAW_VERSION, DEFAULT_HARNESSES, FLOW_VERSION, RUNTIME_ROOT } from "./constants.js";
|
|
5
5
|
import { exists, writeFileSafe } from "./fs-utils.js";
|
|
6
|
-
import { HARNESS_IDS } from "./types.js";
|
|
6
|
+
import { FLOW_TRACKS, HARNESS_IDS, LANGUAGE_RULE_PACKS } from "./types.js";
|
|
7
7
|
const CONFIG_PATH = `${RUNTIME_ROOT}/config.yaml`;
|
|
8
8
|
const HARNESS_ID_SET = new Set(HARNESS_IDS);
|
|
9
|
+
const FLOW_TRACK_SET = new Set(FLOW_TRACKS);
|
|
10
|
+
const LANGUAGE_RULE_PACK_SET = new Set(LANGUAGE_RULE_PACKS);
|
|
9
11
|
const SUPPORTED_HARNESSES_TEXT = HARNESS_IDS.join(", ");
|
|
12
|
+
const SUPPORTED_TRACKS_TEXT = FLOW_TRACKS.join(", ");
|
|
13
|
+
const SUPPORTED_LANGUAGE_RULE_PACKS_TEXT = LANGUAGE_RULE_PACKS.join(", ");
|
|
10
14
|
const ALLOWED_CONFIG_KEYS = new Set([
|
|
11
15
|
"version",
|
|
12
16
|
"flowVersion",
|
|
13
17
|
"harnesses",
|
|
14
18
|
"autoAdvance",
|
|
15
19
|
"promptGuardMode",
|
|
16
|
-
"gitHookGuards"
|
|
20
|
+
"gitHookGuards",
|
|
21
|
+
"defaultTrack",
|
|
22
|
+
"languageRulePacks"
|
|
17
23
|
]);
|
|
18
24
|
function configFixExample() {
|
|
19
25
|
return `harnesses:
|
|
@@ -23,22 +29,66 @@ function configFixExample() {
|
|
|
23
29
|
function configValidationError(configFilePath, reason) {
|
|
24
30
|
return new Error(`Invalid cclaw config at ${configFilePath}: ${reason}\n` +
|
|
25
31
|
`Supported harnesses: ${SUPPORTED_HARNESSES_TEXT}\n` +
|
|
32
|
+
`Supported tracks: ${SUPPORTED_TRACKS_TEXT}\n` +
|
|
33
|
+
`Supported languageRulePacks: ${SUPPORTED_LANGUAGE_RULE_PACKS_TEXT}\n` +
|
|
26
34
|
`Example config:\n${configFixExample()}\n` +
|
|
27
35
|
`After fixing, run: cclaw sync`);
|
|
28
36
|
}
|
|
29
37
|
export function configPath(projectRoot) {
|
|
30
38
|
return path.join(projectRoot, CONFIG_PATH);
|
|
31
39
|
}
|
|
32
|
-
export function createDefaultConfig(harnesses = DEFAULT_HARNESSES) {
|
|
40
|
+
export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack = "standard") {
|
|
33
41
|
return {
|
|
34
42
|
version: CCLAW_VERSION,
|
|
35
43
|
flowVersion: FLOW_VERSION,
|
|
36
44
|
harnesses,
|
|
37
45
|
autoAdvance: false,
|
|
38
46
|
promptGuardMode: "advisory",
|
|
39
|
-
gitHookGuards: false
|
|
47
|
+
gitHookGuards: false,
|
|
48
|
+
defaultTrack,
|
|
49
|
+
languageRulePacks: []
|
|
40
50
|
};
|
|
41
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Build a VibyConfig for a named init profile. Profile defaults are applied
|
|
54
|
+
* first, then any explicit overrides (CLI flags) win. This keeps the profile
|
|
55
|
+
* contract deterministic and testable.
|
|
56
|
+
*/
|
|
57
|
+
export function createProfileConfig(profile, overrides = {}) {
|
|
58
|
+
const base = createDefaultConfig();
|
|
59
|
+
switch (profile) {
|
|
60
|
+
case "minimal":
|
|
61
|
+
return {
|
|
62
|
+
...base,
|
|
63
|
+
harnesses: overrides.harnesses ?? ["claude"],
|
|
64
|
+
autoAdvance: false,
|
|
65
|
+
promptGuardMode: "advisory",
|
|
66
|
+
gitHookGuards: false,
|
|
67
|
+
defaultTrack: overrides.defaultTrack ?? "quick",
|
|
68
|
+
languageRulePacks: overrides.languageRulePacks ?? []
|
|
69
|
+
};
|
|
70
|
+
case "standard":
|
|
71
|
+
return {
|
|
72
|
+
...base,
|
|
73
|
+
harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
|
|
74
|
+
autoAdvance: false,
|
|
75
|
+
promptGuardMode: "advisory",
|
|
76
|
+
gitHookGuards: false,
|
|
77
|
+
defaultTrack: overrides.defaultTrack ?? "standard",
|
|
78
|
+
languageRulePacks: overrides.languageRulePacks ?? []
|
|
79
|
+
};
|
|
80
|
+
case "full":
|
|
81
|
+
return {
|
|
82
|
+
...base,
|
|
83
|
+
harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
|
|
84
|
+
autoAdvance: false,
|
|
85
|
+
promptGuardMode: "strict",
|
|
86
|
+
gitHookGuards: true,
|
|
87
|
+
defaultTrack: overrides.defaultTrack ?? "standard",
|
|
88
|
+
languageRulePacks: overrides.languageRulePacks ?? [...LANGUAGE_RULE_PACKS]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
42
92
|
export async function readConfig(projectRoot) {
|
|
43
93
|
const fullPath = configPath(projectRoot);
|
|
44
94
|
if (!(await exists(fullPath))) {
|
|
@@ -89,13 +139,37 @@ export async function readConfig(projectRoot) {
|
|
|
89
139
|
throw configValidationError(fullPath, `"gitHookGuards" must be a boolean`);
|
|
90
140
|
}
|
|
91
141
|
const gitHookGuards = typeof gitHookGuardsRaw === "boolean" ? gitHookGuardsRaw : false;
|
|
142
|
+
const defaultTrackRaw = parsed.defaultTrack;
|
|
143
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "defaultTrack") &&
|
|
144
|
+
(typeof defaultTrackRaw !== "string" || !FLOW_TRACK_SET.has(defaultTrackRaw))) {
|
|
145
|
+
throw configValidationError(fullPath, `"defaultTrack" must be one of: ${SUPPORTED_TRACKS_TEXT}`);
|
|
146
|
+
}
|
|
147
|
+
const defaultTrack = typeof defaultTrackRaw === "string" && FLOW_TRACK_SET.has(defaultTrackRaw)
|
|
148
|
+
? defaultTrackRaw
|
|
149
|
+
: "standard";
|
|
150
|
+
const languageRulePacksRaw = parsed.languageRulePacks;
|
|
151
|
+
const hasLanguageRulePacksField = Object.prototype.hasOwnProperty.call(parsed, "languageRulePacks");
|
|
152
|
+
if (hasLanguageRulePacksField && !Array.isArray(languageRulePacksRaw)) {
|
|
153
|
+
throw configValidationError(fullPath, `"languageRulePacks" must be an array`);
|
|
154
|
+
}
|
|
155
|
+
const rawPacks = (languageRulePacksRaw ?? []);
|
|
156
|
+
const invalidPacks = rawPacks.filter((pack) => typeof pack !== "string" || !LANGUAGE_RULE_PACK_SET.has(pack));
|
|
157
|
+
if (invalidPacks.length > 0) {
|
|
158
|
+
const formatted = invalidPacks
|
|
159
|
+
.map((item) => (typeof item === "string" ? item : JSON.stringify(item)))
|
|
160
|
+
.join(", ");
|
|
161
|
+
throw configValidationError(fullPath, `unknown languageRulePacks id(s): ${formatted}`);
|
|
162
|
+
}
|
|
163
|
+
const languageRulePacks = [...new Set(rawPacks)];
|
|
92
164
|
return {
|
|
93
165
|
version: parsed.version ?? CCLAW_VERSION,
|
|
94
166
|
flowVersion: parsed.flowVersion ?? FLOW_VERSION,
|
|
95
167
|
harnesses,
|
|
96
168
|
autoAdvance,
|
|
97
169
|
promptGuardMode,
|
|
98
|
-
gitHookGuards
|
|
170
|
+
gitHookGuards,
|
|
171
|
+
defaultTrack,
|
|
172
|
+
languageRulePacks
|
|
99
173
|
};
|
|
100
174
|
}
|
|
101
175
|
export async function writeConfig(projectRoot, config) {
|
package/dist/constants.d.ts
CHANGED
|
@@ -4,9 +4,9 @@ export declare const RUNTIME_ROOT = ".cclaw";
|
|
|
4
4
|
export declare const CCLAW_VERSION = "0.1.1";
|
|
5
5
|
export declare const FLOW_VERSION = "1.0.0";
|
|
6
6
|
export declare const DEFAULT_HARNESSES: HarnessId[];
|
|
7
|
-
export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks"];
|
|
7
|
+
export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills"];
|
|
8
8
|
export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".codex/commands/cc-*.md", ".codex/commands/cc.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
|
|
9
9
|
export declare const COMMAND_FILE_ORDER: FlowStage[];
|
|
10
|
-
export declare const UTILITY_COMMANDS: readonly ["learn", "next"];
|
|
10
|
+
export declare const UTILITY_COMMANDS: readonly ["learn", "next", "status"];
|
|
11
11
|
export declare const SUBAGENT_SKILL_FOLDERS: readonly ["subagent-dev", "parallel-dispatch"];
|
|
12
12
|
export type UtilityCommand = (typeof UTILITY_COMMANDS)[number];
|
package/dist/constants.js
CHANGED
|
@@ -20,7 +20,8 @@ export const REQUIRED_DIRS = [
|
|
|
20
20
|
`${RUNTIME_ROOT}/rules`,
|
|
21
21
|
`${RUNTIME_ROOT}/adapters`,
|
|
22
22
|
`${RUNTIME_ROOT}/agents`,
|
|
23
|
-
`${RUNTIME_ROOT}/hooks
|
|
23
|
+
`${RUNTIME_ROOT}/hooks`,
|
|
24
|
+
`${RUNTIME_ROOT}/custom-skills`
|
|
24
25
|
];
|
|
25
26
|
export const REQUIRED_GITIGNORE_PATTERNS = [
|
|
26
27
|
"# cclaw generated artifacts",
|
|
@@ -49,7 +50,7 @@ export const COMMAND_FILE_ORDER = [
|
|
|
49
50
|
"review",
|
|
50
51
|
"ship"
|
|
51
52
|
];
|
|
52
|
-
export const UTILITY_COMMANDS = ["learn", "next"];
|
|
53
|
+
export const UTILITY_COMMANDS = ["learn", "next", "status"];
|
|
53
54
|
export const SUBAGENT_SKILL_FOLDERS = [
|
|
54
55
|
"subagent-dev",
|
|
55
56
|
"parallel-dispatch"
|
package/dist/content/hooks.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface HookRuntimeOptions {
|
|
|
11
11
|
export declare const RUNTIME_SHELL_DETECT_ROOT = "HARNESS=\"codex\"\nif [ -n \"${CLAUDE_PROJECT_DIR:-}\" ]; then\n HARNESS=\"claude\"\nelif [ -n \"${CURSOR_PROJECT_DIR:-}\" ] || [ -n \"${CURSOR_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"cursor\"\nelif [ -n \"${OPENCODE_PROJECT_DIR:-}\" ] || [ -n \"${OPENCODE_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"opencode\"\nfi\n\nROOT=\"\"\nfor candidate in \"${CCLAW_PROJECT_ROOT:-}\" \"${CLAUDE_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_ROOT:-}\" \"${OPENCODE_PROJECT_DIR:-}\" \"${OPENCODE_PROJECT_ROOT:-}\" \"${PWD:-}\"; do\n if [ -n \"$candidate\" ] && [ -d \"$candidate/.cclaw\" ]; then\n ROOT=\"$candidate\"\n break\n fi\ndone\nif [ -z \"$ROOT\" ]; then\n ROOT=\"${CCLAW_PROJECT_ROOT:-${CLAUDE_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-${CURSOR_PROJECT_ROOT:-${OPENCODE_PROJECT_DIR:-${OPENCODE_PROJECT_ROOT:-${PWD}}}}}}}\"\nfi";
|
|
12
12
|
export declare function sessionStartScript(_options?: HookRuntimeOptions): string;
|
|
13
13
|
export declare function stopCheckpointScript(): string;
|
|
14
|
+
export declare function preCompactScript(): string;
|
|
14
15
|
export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
|
|
15
16
|
export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
|
|
16
17
|
export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
|
package/dist/content/hooks.js
CHANGED
|
@@ -616,6 +616,151 @@ case "$HARNESS" in
|
|
|
616
616
|
esac
|
|
617
617
|
`;
|
|
618
618
|
}
|
|
619
|
+
export function preCompactScript() {
|
|
620
|
+
return `#!/usr/bin/env bash
|
|
621
|
+
# cclaw pre-compact hook — generated by cclaw sync
|
|
622
|
+
# Persists a session digest before the harness compacts/clears context, so the
|
|
623
|
+
# next session-start hook can restore the most important state without the agent
|
|
624
|
+
# having to re-derive it from scratch.
|
|
625
|
+
set -uo pipefail
|
|
626
|
+
|
|
627
|
+
${DETECT_ROOT}
|
|
628
|
+
|
|
629
|
+
INPUT=$(cat 2>/dev/null || echo '{}')
|
|
630
|
+
|
|
631
|
+
STATE_DIR="$ROOT/${RUNTIME_ROOT}/state"
|
|
632
|
+
STATE_FILE="$STATE_DIR/flow-state.json"
|
|
633
|
+
DELEGATION_FILE="$STATE_DIR/delegation-log.json"
|
|
634
|
+
KNOWLEDGE_FILE="$ROOT/${RUNTIME_ROOT}/knowledge.md"
|
|
635
|
+
DIGEST_FILE="$STATE_DIR/session-digest.md"
|
|
636
|
+
DIGEST_TMP="$STATE_DIR/session-digest.md.tmp.$$"
|
|
637
|
+
|
|
638
|
+
mkdir -p "$STATE_DIR" 2>/dev/null || true
|
|
639
|
+
|
|
640
|
+
cleanup_digest_tmp() {
|
|
641
|
+
rm -f "$DIGEST_TMP" 2>/dev/null || true
|
|
642
|
+
}
|
|
643
|
+
trap cleanup_digest_tmp EXIT INT TERM
|
|
644
|
+
|
|
645
|
+
STAGE="none"
|
|
646
|
+
TRACK="standard"
|
|
647
|
+
COMPLETED="0"
|
|
648
|
+
SKIPPED=""
|
|
649
|
+
ACTIVE_RUN="none"
|
|
650
|
+
PASSED_GATES=""
|
|
651
|
+
BLOCKED_GATES=""
|
|
652
|
+
|
|
653
|
+
if [ -f "$STATE_FILE" ]; then
|
|
654
|
+
if command -v jq >/dev/null 2>&1; then
|
|
655
|
+
STAGE=$(jq -r '.currentStage // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
|
|
656
|
+
TRACK=$(jq -r '.track // "standard"' "$STATE_FILE" 2>/dev/null || echo "standard")
|
|
657
|
+
COMPLETED=$(jq -r '(.completedStages // []) | length' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
658
|
+
SKIPPED=$(jq -r '(.skippedStages // []) | join(",")' "$STATE_FILE" 2>/dev/null || echo "")
|
|
659
|
+
ACTIVE_RUN=$(jq -r '.activeRunId // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
|
|
660
|
+
PASSED_GATES=$(jq -r --arg stage "$STAGE" '(.stageGates[$stage].passed // []) | join(",")' "$STATE_FILE" 2>/dev/null || echo "")
|
|
661
|
+
BLOCKED_GATES=$(jq -r --arg stage "$STAGE" '(.stageGates[$stage].blocked // []) | join(",")' "$STATE_FILE" 2>/dev/null || echo "")
|
|
662
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
663
|
+
OUTPUT=$(python3 - "$STATE_FILE" <<'PY'
|
|
664
|
+
import json, sys
|
|
665
|
+
try:
|
|
666
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
667
|
+
data = json.load(fh)
|
|
668
|
+
except Exception:
|
|
669
|
+
data = {}
|
|
670
|
+
stage = data.get("currentStage") or "none"
|
|
671
|
+
track = data.get("track") or "standard"
|
|
672
|
+
completed = data.get("completedStages") or []
|
|
673
|
+
skipped = data.get("skippedStages") or []
|
|
674
|
+
run = data.get("activeRunId") or "none"
|
|
675
|
+
gates = (data.get("stageGates") or {}).get(stage) or {}
|
|
676
|
+
passed = gates.get("passed") or []
|
|
677
|
+
blocked = gates.get("blocked") or []
|
|
678
|
+
print(stage)
|
|
679
|
+
print(track)
|
|
680
|
+
print(len(completed) if isinstance(completed, list) else 0)
|
|
681
|
+
print(",".join(skipped) if isinstance(skipped, list) else "")
|
|
682
|
+
print(run)
|
|
683
|
+
print(",".join(passed) if isinstance(passed, list) else "")
|
|
684
|
+
print(",".join(blocked) if isinstance(blocked, list) else "")
|
|
685
|
+
PY
|
|
686
|
+
)
|
|
687
|
+
{
|
|
688
|
+
IFS= read -r STAGE
|
|
689
|
+
IFS= read -r TRACK
|
|
690
|
+
IFS= read -r COMPLETED
|
|
691
|
+
IFS= read -r SKIPPED
|
|
692
|
+
IFS= read -r ACTIVE_RUN
|
|
693
|
+
IFS= read -r PASSED_GATES
|
|
694
|
+
IFS= read -r BLOCKED_GATES
|
|
695
|
+
} <<EOF
|
|
696
|
+
$OUTPUT
|
|
697
|
+
EOF
|
|
698
|
+
fi
|
|
699
|
+
fi
|
|
700
|
+
|
|
701
|
+
DELEGATION_PENDING=""
|
|
702
|
+
if [ -f "$DELEGATION_FILE" ] && command -v jq >/dev/null 2>&1; then
|
|
703
|
+
DELEGATION_PENDING=$(jq -r --arg stage "$STAGE" '
|
|
704
|
+
(.entries // [])
|
|
705
|
+
| map(select((.stage // "") == $stage and (.status // "") != "completed" and (.status // "") != "waived"))
|
|
706
|
+
| map(.agent // "unknown")
|
|
707
|
+
| unique
|
|
708
|
+
| join(",")
|
|
709
|
+
' "$DELEGATION_FILE" 2>/dev/null || echo "")
|
|
710
|
+
fi
|
|
711
|
+
|
|
712
|
+
KNOWLEDGE_TAIL=""
|
|
713
|
+
if [ -f "$KNOWLEDGE_FILE" ] && [ -s "$KNOWLEDGE_FILE" ]; then
|
|
714
|
+
KNOWLEDGE_TAIL=$(tail -n 12 "$KNOWLEDGE_FILE" 2>/dev/null || echo "")
|
|
715
|
+
fi
|
|
716
|
+
|
|
717
|
+
GIT_HEAD=""
|
|
718
|
+
GIT_BRANCH=""
|
|
719
|
+
GIT_DIRTY="unknown"
|
|
720
|
+
if command -v git >/dev/null 2>&1 && git -C "$ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
721
|
+
GIT_HEAD=$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo "")
|
|
722
|
+
GIT_BRANCH=$(git -C "$ROOT" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
723
|
+
if [ -n "$(git -C "$ROOT" status --porcelain 2>/dev/null)" ]; then
|
|
724
|
+
GIT_DIRTY="dirty"
|
|
725
|
+
else
|
|
726
|
+
GIT_DIRTY="clean"
|
|
727
|
+
fi
|
|
728
|
+
fi
|
|
729
|
+
|
|
730
|
+
TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")
|
|
731
|
+
|
|
732
|
+
{
|
|
733
|
+
printf '# Session Digest\n'
|
|
734
|
+
printf '_Generated by pre-compact hook at %s_\n\n' "$TS"
|
|
735
|
+
printf '## Flow snapshot\n'
|
|
736
|
+
printf '- track: %s\n' "$TRACK"
|
|
737
|
+
printf '- current stage: %s\n' "$STAGE"
|
|
738
|
+
printf '- completed: %s stages\n' "$COMPLETED"
|
|
739
|
+
printf '- skipped: %s\n' "\${SKIPPED:-(none)}"
|
|
740
|
+
printf '- run: %s\n\n' "$ACTIVE_RUN"
|
|
741
|
+
printf '## Gates (current stage)\n'
|
|
742
|
+
printf '- passed: %s\n' "\${PASSED_GATES:-(none)}"
|
|
743
|
+
printf '- blocked: %s\n\n' "\${BLOCKED_GATES:-(none)}"
|
|
744
|
+
printf '## Outstanding delegations\n'
|
|
745
|
+
printf '- pending: %s\n\n' "\${DELEGATION_PENDING:-(none)}"
|
|
746
|
+
printf '## Git\n'
|
|
747
|
+
printf '- branch: %s\n' "\${GIT_BRANCH:-(unknown)}"
|
|
748
|
+
printf '- head: %s\n' "\${GIT_HEAD:-(unknown)}"
|
|
749
|
+
printf '- worktree: %s\n\n' "$GIT_DIRTY"
|
|
750
|
+
if [ -n "$KNOWLEDGE_TAIL" ]; then
|
|
751
|
+
printf '## Knowledge tail\n'
|
|
752
|
+
printf '%s\n' "$KNOWLEDGE_TAIL"
|
|
753
|
+
fi
|
|
754
|
+
} > "$DIGEST_TMP" 2>/dev/null || true
|
|
755
|
+
|
|
756
|
+
if [ -s "$DIGEST_TMP" ]; then
|
|
757
|
+
mv "$DIGEST_TMP" "$DIGEST_FILE" 2>/dev/null || rm -f "$DIGEST_TMP" 2>/dev/null || true
|
|
758
|
+
fi
|
|
759
|
+
|
|
760
|
+
trap - EXIT INT TERM
|
|
761
|
+
exit 0
|
|
762
|
+
`;
|
|
763
|
+
}
|
|
619
764
|
// ---------------------------------------------------------------------------
|
|
620
765
|
// hooks.json generators are defined in observe.ts (shared across harnesses).
|
|
621
766
|
// ---------------------------------------------------------------------------
|