cclaw-cli 7.1.1 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.d.ts CHANGED
@@ -9,6 +9,7 @@ interface ParsedArgs {
9
9
  track?: FlowTrack;
10
10
  dryRun?: boolean;
11
11
  interactive?: boolean;
12
+ syncCheck?: boolean;
12
13
  archiveName?: string;
13
14
  archiveSkipRetro?: boolean;
14
15
  archiveSkipRetroReason?: string;
package/dist/cli.js CHANGED
@@ -40,6 +40,7 @@ Commands:
40
40
  sync Reconcile generated runtime files with the current config.
41
41
  Flags: --harnesses=<list> Update configured harnesses before syncing.
42
42
  --interactive Pick harnesses from a numbered TTY menu.
43
+ --check Verify managed hook files are byte-identical to canonical generators.
43
44
  upgrade Refresh generated files in .cclaw. Preserves your config.yaml.
44
45
  archive Archive the active run and reset flow state for the next run.
45
46
  Flags: --name=<slug> Override archive folder suffix.
@@ -57,6 +58,7 @@ Examples:
57
58
  npx cclaw-cli
58
59
  npx cclaw-cli init --harnesses=claude,cursor --no-interactive
59
60
  npx cclaw-cli sync --interactive
61
+ npx cclaw-cli sync --check
60
62
  npx cclaw-cli archive --name=my-run
61
63
  npx cclaw-cli archive --disposition=cancelled --reason="deprioritized"
62
64
  npx cclaw-cli upgrade
@@ -334,6 +336,7 @@ function parseArgs(argv) {
334
336
  return flag.startsWith("--harnesses=") ||
335
337
  (parsed.command === "init" && flag.startsWith("--track=")) ||
336
338
  (parsed.command === "init" && flag.startsWith("--profile=")) ||
339
+ (parsed.command === "sync" && flag === "--check") ||
337
340
  flag === "--interactive" ||
338
341
  flag === "--no-interactive" ||
339
342
  (parsed.command === "init" && flag === "--dry-run");
@@ -374,6 +377,10 @@ function parseArgs(argv) {
374
377
  parsed.dryRun = true;
375
378
  continue;
376
379
  }
380
+ if (flag === "--check") {
381
+ parsed.syncCheck = true;
382
+ continue;
383
+ }
377
384
  if (flag.startsWith("--name=")) {
378
385
  parsed.archiveName = flag.replace("--name=", "").trim();
379
386
  continue;
@@ -446,6 +453,11 @@ async function runCommand(parsed, ctx) {
446
453
  return 0;
447
454
  }
448
455
  if (command === "sync") {
456
+ if (parsed.syncCheck === true) {
457
+ await syncCclaw(ctx.cwd, { check: true });
458
+ info(ctx, "Managed hook drift check passed (no sync required).");
459
+ return 0;
460
+ }
449
461
  const resolved = await resolveSyncInputs(parsed, ctx);
450
462
  await syncCclaw(ctx.cwd, { harnesses: resolved.harnesses });
451
463
  const harnessNote = resolved.harnesses ? ` (${resolved.harnesses.join(", ")})` : "";
package/dist/config.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- import type { CclawConfig, FlowTrack, HarnessId, LanguageRulePack, TddCommitMode } from "./types.js";
1
+ import type { CclawConfig, FlowTrack, HarnessId, LanguageRulePack, TddCommitMode, TddIsolationMode } from "./types.js";
2
2
  export declare const TDD_COMMIT_MODES: readonly ["managed-per-slice", "agent-required", "checkpoint-only", "off"];
3
3
  export declare const DEFAULT_TDD_COMMIT_MODE: TddCommitMode;
4
+ export declare const TDD_ISOLATION_MODES: readonly ["worktree", "in-place", "auto"];
5
+ export declare const DEFAULT_TDD_ISOLATION_MODE: TddIsolationMode;
6
+ export declare const DEFAULT_TDD_WORKTREE_ROOT = ".cclaw/worktrees";
4
7
  export declare const DEFAULT_TDD_TEST_PATH_PATTERNS: readonly string[];
5
8
  export declare const DEFAULT_TDD_TEST_GLOBS: readonly string[];
6
9
  export declare const DEFAULT_TDD_PRODUCTION_PATH_PATTERNS: readonly string[];
@@ -19,6 +22,8 @@ export declare class InvalidConfigError extends Error {
19
22
  export declare function configPath(projectRoot: string): string;
20
23
  export declare function createDefaultConfig(harnesses?: HarnessId[], _defaultTrack?: FlowTrack): CclawConfig;
21
24
  export declare function resolveTddCommitMode(config: Pick<CclawConfig, "tdd"> | null | undefined): TddCommitMode;
25
+ export declare function resolveTddIsolationMode(config: Pick<CclawConfig, "tdd"> | null | undefined): TddIsolationMode;
26
+ export declare function resolveTddWorktreeRoot(config: Pick<CclawConfig, "tdd"> | null | undefined): string;
22
27
  export declare function detectLanguageRulePacks(_projectRoot: string): Promise<LanguageRulePack[]>;
23
28
  export declare function readConfig(projectRoot: string, _options?: ReadConfigOptions): Promise<CclawConfig>;
24
29
  export interface WriteConfigOptions {
package/dist/config.js CHANGED
@@ -16,6 +16,10 @@ export const TDD_COMMIT_MODES = [
16
16
  ];
17
17
  const TDD_COMMIT_MODE_SET = new Set(TDD_COMMIT_MODES);
18
18
  export const DEFAULT_TDD_COMMIT_MODE = "managed-per-slice";
19
+ export const TDD_ISOLATION_MODES = ["worktree", "in-place", "auto"];
20
+ const TDD_ISOLATION_MODE_SET = new Set(TDD_ISOLATION_MODES);
21
+ export const DEFAULT_TDD_ISOLATION_MODE = "worktree";
22
+ export const DEFAULT_TDD_WORKTREE_ROOT = `${RUNTIME_ROOT}/worktrees`;
19
23
  // Kept for runtime modules that use these defaults directly.
20
24
  export const DEFAULT_TDD_TEST_PATH_PATTERNS = [
21
25
  "**/*.test.*",
@@ -40,7 +44,9 @@ function configFixExample() {
40
44
  - claude
41
45
  - cursor
42
46
  tdd:
43
- commitMode: managed-per-slice`;
47
+ commitMode: managed-per-slice
48
+ isolationMode: worktree
49
+ worktreeRoot: .cclaw/worktrees`;
44
50
  }
45
51
  function configValidationError(configFilePath, reason) {
46
52
  return new InvalidConfigError(`Invalid cclaw config at ${configFilePath}: ${reason}\n` +
@@ -60,7 +66,9 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, _defaultTrack
60
66
  flowVersion: FLOW_VERSION,
61
67
  harnesses: [...new Set(harnesses)],
62
68
  tdd: {
63
- commitMode: DEFAULT_TDD_COMMIT_MODE
69
+ commitMode: DEFAULT_TDD_COMMIT_MODE,
70
+ isolationMode: DEFAULT_TDD_ISOLATION_MODE,
71
+ worktreeRoot: DEFAULT_TDD_WORKTREE_ROOT
64
72
  }
65
73
  };
66
74
  }
@@ -71,6 +79,20 @@ export function resolveTddCommitMode(config) {
71
79
  }
72
80
  return DEFAULT_TDD_COMMIT_MODE;
73
81
  }
82
+ export function resolveTddIsolationMode(config) {
83
+ const raw = config?.tdd?.isolationMode;
84
+ if (typeof raw === "string" && TDD_ISOLATION_MODE_SET.has(raw)) {
85
+ return raw;
86
+ }
87
+ return DEFAULT_TDD_ISOLATION_MODE;
88
+ }
89
+ export function resolveTddWorktreeRoot(config) {
90
+ const raw = config?.tdd?.worktreeRoot;
91
+ if (typeof raw === "string" && raw.trim().length > 0) {
92
+ return raw.trim();
93
+ }
94
+ return DEFAULT_TDD_WORKTREE_ROOT;
95
+ }
74
96
  function assertOnlySupportedKeys(parsed, fullPath) {
75
97
  const unknownKeys = Object.keys(parsed).filter((key) => !ALLOWED_CONFIG_KEYS.has(key));
76
98
  if (unknownKeys.length === 0)
@@ -129,19 +151,37 @@ export async function readConfig(projectRoot, _options = {}) {
129
151
  : FLOW_VERSION;
130
152
  const parsedTdd = isRecord(parsed.tdd) ? parsed.tdd : {};
131
153
  const rawCommitMode = parsedTdd.commitMode;
154
+ const rawIsolationMode = parsedTdd.isolationMode;
155
+ const rawWorktreeRoot = parsedTdd.worktreeRoot;
132
156
  if (rawCommitMode !== undefined &&
133
157
  (typeof rawCommitMode !== "string" || !TDD_COMMIT_MODE_SET.has(rawCommitMode))) {
134
158
  throw configValidationError(fullPath, `"tdd.commitMode" must be one of: ${TDD_COMMIT_MODES.join(", ")}`);
135
159
  }
160
+ if (rawIsolationMode !== undefined &&
161
+ (typeof rawIsolationMode !== "string" || !TDD_ISOLATION_MODE_SET.has(rawIsolationMode))) {
162
+ throw configValidationError(fullPath, `"tdd.isolationMode" must be one of: ${TDD_ISOLATION_MODES.join(", ")}`);
163
+ }
164
+ if (rawWorktreeRoot !== undefined &&
165
+ (typeof rawWorktreeRoot !== "string" || rawWorktreeRoot.trim().length === 0)) {
166
+ throw configValidationError(fullPath, `"tdd.worktreeRoot" must be a non-empty string when provided`);
167
+ }
136
168
  const commitMode = typeof rawCommitMode === "string"
137
169
  ? rawCommitMode
138
170
  : DEFAULT_TDD_COMMIT_MODE;
171
+ const isolationMode = typeof rawIsolationMode === "string"
172
+ ? rawIsolationMode
173
+ : DEFAULT_TDD_ISOLATION_MODE;
174
+ const worktreeRoot = typeof rawWorktreeRoot === "string" && rawWorktreeRoot.trim().length > 0
175
+ ? rawWorktreeRoot.trim()
176
+ : DEFAULT_TDD_WORKTREE_ROOT;
139
177
  return {
140
178
  version,
141
179
  flowVersion,
142
180
  harnesses: normalizedHarnesses,
143
181
  tdd: {
144
- commitMode
182
+ commitMode,
183
+ isolationMode,
184
+ worktreeRoot
145
185
  }
146
186
  };
147
187
  }
@@ -151,7 +191,9 @@ export async function writeConfig(projectRoot, config, _options = {}) {
151
191
  flowVersion: config.flowVersion,
152
192
  harnesses: config.harnesses,
153
193
  tdd: {
154
- commitMode: resolveTddCommitMode(config)
194
+ commitMode: resolveTddCommitMode(config),
195
+ isolationMode: resolveTddIsolationMode(config),
196
+ worktreeRoot: resolveTddWorktreeRoot(config)
155
197
  }
156
198
  };
157
199
  await writeFileSafe(configPath(projectRoot), stringify(serialisable));
@@ -10,7 +10,7 @@ export declare const FLOW_VERSION = "1.0.0";
10
10
  export declare const SHIP_FINALIZATION_MODES: readonly ["FINALIZE_MERGE_LOCAL", "FINALIZE_OPEN_PR", "FINALIZE_KEEP_BRANCH", "FINALIZE_DISCARD_BRANCH", "FINALIZE_NO_VCS"];
11
11
  export type ShipFinalizationMode = (typeof SHIP_FINALIZATION_MODES)[number];
12
12
  export declare const DEFAULT_HARNESSES: HarnessId[];
13
- export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/templates", ".cclaw/templates/state-contracts", ".cclaw/artifacts", ".cclaw/wave-plans", ".cclaw/archive", ".cclaw/state", ".cclaw/rules", ".cclaw/agents", ".cclaw/hooks", ".cclaw/skills/review-prompts"];
13
+ export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/templates", ".cclaw/templates/state-contracts", ".cclaw/artifacts", ".cclaw/wave-plans", ".cclaw/archive", ".cclaw/worktrees", ".cclaw/state", ".cclaw/rules", ".cclaw/agents", ".cclaw/hooks", ".cclaw/skills/review-prompts"];
14
14
  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", ".agents/skills/cc/SKILL.md", ".agents/skills/cc-*/SKILL.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
15
15
  /**
16
16
  * Canonical stage -> skill folder mapping.
package/dist/constants.js CHANGED
@@ -61,6 +61,7 @@ export const REQUIRED_DIRS = [
61
61
  `${RUNTIME_ROOT}/artifacts`,
62
62
  `${RUNTIME_ROOT}/wave-plans`,
63
63
  `${RUNTIME_ROOT}/archive`,
64
+ `${RUNTIME_ROOT}/worktrees`,
64
65
  `${RUNTIME_ROOT}/state`,
65
66
  `${RUNTIME_ROOT}/rules`,
66
67
  `${RUNTIME_ROOT}/agents`,
@@ -97,7 +97,7 @@ node .cclaw/hooks/delegation-record.mjs \\
97
97
  --json
98
98
  \`\`\`
99
99
 
100
- Reuse the same \`<spanId>\` and \`<dispatchId>\` across both rows. **GREEN evidence freshness** (slice-builder): the FIRST \`--evidence-ref\` MUST (1) reference the same test the matching \`phase=red\` row cited (basename/stem substring; reject \`green_evidence_red_test_mismatch\`), (2) include a recognized passing-runner line such as \`=> N passed; 0 failed\`, \`N passed in 0.42s\`, or \`ok pkg 0.12s\` (reject \`green_evidence_passing_assertion_missing\`), AND (3) be captured AFTER \`ackTs\` of this span — \`completedTs - ackTs\` must be ≥ \`flow-state.json::tddGreenMinElapsedMs\` (default 4000ms; reject \`green_evidence_too_fresh\`). Escape clause for legitimate observational GREEN: pass BOTH \`--allow-fast-green --green-mode=observational\`. \`--ack-ts\` and \`--completed-ts\` must be monotonic on the span (\`startTs ≤ launchedTs ≤ ackTs ≤ completedTs\`); the helper rejects out-of-order writes with \`delegation_timestamp_non_monotonic\`. If the helper rejects with \`dispatch_active_span_collision\` against a stale span, surface the conflicting \`spanId\` to the parent — do NOT silently retry with \`--allow-parallel\`.`;
100
+ Reuse the same \`<spanId>\` and \`<dispatchId>\` across both rows. **GREEN evidence freshness** (slice-builder): the FIRST \`--evidence-ref\` MUST (1) reference the same test the matching \`phase=red\` row cited (basename/stem substring; reject \`green_evidence_red_test_mismatch\`), (2) include a recognized passing-runner line such as \`=> N passed; 0 failed\`, \`N passed in 0.42s\`, or \`ok pkg 0.12s\` (reject \`green_evidence_passing_assertion_missing\`), AND (3) be captured AFTER \`ackTs\` of this span — \`completedTs - ackTs\` must be ≥ \`flow-state.json::tddGreenMinElapsedMs\` (default 4000ms; reject \`green_evidence_too_fresh\`). Escape clause for legitimate observational GREEN: pass \`--green-mode=observational\`. \`--ack-ts\` and \`--completed-ts\` must be monotonic on the span (\`startTs ≤ launchedTs ≤ ackTs ≤ completedTs\`); the helper rejects out-of-order writes with \`delegation_timestamp_non_monotonic\`. If the helper rejects with \`dispatch_active_span_collision\` against a stale span, surface the conflicting \`spanId\` to the parent — do NOT silently retry with \`--allow-parallel\`.`;
101
101
  }
102
102
  function formatReturnSchema(schema) {
103
103
  const lines = [
@@ -156,6 +156,7 @@ export function sliceBuilderProtocol() {
156
156
  "### Invariants",
157
157
  "- Produce failing RED evidence (or cite the delegated RED artifact) **before** production edits.",
158
158
  "- Stay inside the slice contract: `claimedPaths`, acceptance mapping, and forbidden-change lists from the parent.",
159
+ "- When `tdd.isolationMode=worktree|auto`, run the slice inside the assigned worktree path (never in the shared repo root) so filesystem isolation enforces the claimed-path fence.",
159
160
  "- When `tdd.commitMode=managed-per-slice`, do **not** hand-edit git state for slice files (no manual `git add/commit` on claimed paths). Let `.cclaw/hooks/slice-commit.mjs` own per-slice commits.",
160
161
  "- After GREEN, refactor inline **or** record deferred refactor via the same `--refactor-outcome` mechanics the controller specifies.",
161
162
  "- Own the prose slice summary at `<artifacts-dir>/tdd-slices/S-<id>.md` yourself.",