cclaw-cli 0.48.34 → 0.49.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.js CHANGED
@@ -143,14 +143,10 @@ function buildInitSurfacePreview(harnesses) {
143
143
  ".cclaw/config.yaml",
144
144
  ".cclaw/commands/*.md",
145
145
  ".cclaw/skills/*/SKILL.md",
146
- ".cclaw/contexts/*.md",
147
146
  ".cclaw/templates/*",
148
147
  ".cclaw/agents/*.md",
149
148
  ".cclaw/hooks/*",
150
149
  ".cclaw/rules/**",
151
- ".cclaw/adapters/*.md",
152
- ".cclaw/custom-skills/README.md",
153
- ".cclaw/worktrees/**",
154
150
  ".cclaw/features/** (legacy snapshots, read-only migration)",
155
151
  ".cclaw/runs/**",
156
152
  ".cclaw/artifacts/**",
@@ -19,7 +19,7 @@ export declare const DEFAULT_HARNESSES: HarnessId[];
19
19
  export declare const EVALS_ROOT = ".cclaw/evals";
20
20
  export declare const EVALS_CONFIG_PATH = ".cclaw/evals/config.yaml";
21
21
  export declare const EVALS_DIRS: readonly [".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
22
- export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/worktrees", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills", ".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
22
+ export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/agents", ".cclaw/hooks", ".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
23
23
  export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", "# cclaw evals: user-owned, track in git", "!.cclaw/evals/", "!.cclaw/evals/config.yaml", "!.cclaw/evals/corpus/", "!.cclaw/evals/corpus/**", "!.cclaw/evals/rubrics/", "!.cclaw/evals/rubrics/**", "!.cclaw/evals/baselines/", "!.cclaw/evals/baselines/**", ".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"];
24
24
  /**
25
25
  * Canonical stage -> skill folder mapping.
package/dist/constants.js CHANGED
@@ -74,14 +74,11 @@ export const REQUIRED_DIRS = [
74
74
  `${RUNTIME_ROOT}/contexts`,
75
75
  `${RUNTIME_ROOT}/templates`,
76
76
  `${RUNTIME_ROOT}/artifacts`,
77
- `${RUNTIME_ROOT}/worktrees`,
78
77
  `${RUNTIME_ROOT}/state`,
79
78
  `${RUNTIME_ROOT}/runs`,
80
79
  `${RUNTIME_ROOT}/rules`,
81
- `${RUNTIME_ROOT}/adapters`,
82
80
  `${RUNTIME_ROOT}/agents`,
83
81
  `${RUNTIME_ROOT}/hooks`,
84
- `${RUNTIME_ROOT}/custom-skills`,
85
82
  ...EVALS_DIRS
86
83
  ];
87
84
  export const REQUIRED_GITIGNORE_PATTERNS = [
@@ -1,6 +1,15 @@
1
1
  export declare const DEFAULT_CONTEXT_MODE = "default";
2
- export declare const CONTEXT_MODES: Record<string, string>;
3
- export declare function contextModeFiles(): Record<string, string>;
2
+ /**
3
+ * Valid context mode identifiers used by the session hooks and the
4
+ * `context-engineering` skill. Mode bodies no longer live as separate
5
+ * `.cclaw/contexts/<mode>.md` files — the guidance was merged into the
6
+ * `context-engineering` skill. This list only exists so `doctor` can
7
+ * validate that `state/context-mode.json#activeMode` references a known
8
+ * mode name.
9
+ */
10
+ export declare const AVAILABLE_CONTEXT_MODES: readonly ["default", "execution", "review", "incident"];
11
+ /** Legacy alias: kept so existing imports keep typechecking. */
12
+ export declare const CONTEXT_MODES: Record<string, true>;
4
13
  export interface ContextModeState {
5
14
  activeMode: string;
6
15
  updatedAt: string;
@@ -1,65 +1,24 @@
1
1
  export const DEFAULT_CONTEXT_MODE = "default";
2
- export const CONTEXT_MODES = {
3
- default: `# Context Mode: default
4
-
5
- Use for most day-to-day feature work.
6
-
7
- ## Focus
8
- - Follow the active cclaw stage strictly.
9
- - Keep changes within the current task blast radius.
10
- - Prefer incremental progress with frequent verification.
11
-
12
- ## Decision posture
13
- - Escalate only meaningful trade-offs.
14
- - Ask for user confirmation only at explicit stage gates.
15
- `,
16
- execution: `# Context Mode: execution
17
-
18
- Use when plan/spec are approved and the goal is high-throughput delivery.
19
-
20
- ## Focus
21
- - Prioritize deterministic implementation flow (RED -> GREEN -> REFACTOR).
22
- - Minimize conversational overhead; keep updates concise and evidence-first.
23
- - Batch machine-only checks through subagent dispatch where supported.
24
-
25
- ## Decision posture
26
- - Avoid reopening settled design debates unless a blocker appears.
27
- - Stop immediately on failing quality gates or unresolved critical findings.
28
- `,
29
- review: `# Context Mode: review
30
-
31
- Use for deep validation, risk discovery, and merge readiness.
32
-
33
- ## Focus
34
- - Bias toward finding concrete defects, regressions, and evidence gaps.
35
- - Cross-check spec, plan, tests, and implementation alignment.
36
- - Treat unsupported claims as unverified until backed by command output.
37
-
38
- ## Decision posture
39
- - Classify findings by severity and expected blast radius.
40
- - Block ship decisions when critical issues remain unresolved.
41
- `,
42
- incident: `# Context Mode: incident
43
-
44
- Use for production failures, emergency regressions, or urgent stabilization.
45
-
46
- ## Focus
47
- - Reproduce first, then isolate, then fix.
48
- - Favor smallest safe change with rollback clarity.
49
- - Preserve timeline and evidence for post-incident learning.
50
-
51
- ## Decision posture
52
- - Prefer containment over optimization.
53
- - Require explicit evidence for declaring recovery complete.
54
- `
55
- };
56
- export function contextModeFiles() {
57
- return { ...CONTEXT_MODES };
58
- }
2
+ /**
3
+ * Valid context mode identifiers used by the session hooks and the
4
+ * `context-engineering` skill. Mode bodies no longer live as separate
5
+ * `.cclaw/contexts/<mode>.md` files the guidance was merged into the
6
+ * `context-engineering` skill. This list only exists so `doctor` can
7
+ * validate that `state/context-mode.json#activeMode` references a known
8
+ * mode name.
9
+ */
10
+ export const AVAILABLE_CONTEXT_MODES = [
11
+ "default",
12
+ "execution",
13
+ "review",
14
+ "incident"
15
+ ];
16
+ /** Legacy alias: kept so existing imports keep typechecking. */
17
+ export const CONTEXT_MODES = Object.fromEntries(AVAILABLE_CONTEXT_MODES.map((mode) => [mode, true]));
59
18
  export function createInitialContextModeState(nowIso = new Date().toISOString()) {
60
19
  return {
61
20
  activeMode: DEFAULT_CONTEXT_MODE,
62
21
  updatedAt: nowIso,
63
- availableModes: Object.keys(CONTEXT_MODES)
22
+ availableModes: [...AVAILABLE_CONTEXT_MODES]
64
23
  };
65
24
  }
@@ -106,9 +106,6 @@ Load utility skills only when triggered by the current task:
106
106
  - iron-laws as policy arbitration when instructions conflict
107
107
  - language rule packs from \`.cclaw/config.yaml\` when enabled
108
108
 
109
- Custom project skills under \`.cclaw/custom-skills/\` are opt-in supplements,
110
- never mandatory delegations.
111
-
112
109
  ## Protocol references
113
110
 
114
111
  Do not inline these protocols in stage skills; cite by path:
@@ -12,6 +12,7 @@ export default function cclawPlugin(ctx) {
12
12
  const stateDir = join(runtimeDir, "state");
13
13
  const logsDir = join(runtimeDir, "logs");
14
14
  const pluginLogPath = join(logsDir, "opencode-plugin.log");
15
+ const configPath = join(runtimeDir, "config.yaml");
15
16
  const flowStatePath = join(stateDir, "flow-state.json");
16
17
  const checkpointPath = join(stateDir, "checkpoint.json");
17
18
  const activityPath = join(stateDir, "stage-activity.jsonl");
@@ -306,6 +307,41 @@ export default function cclawPlugin(ctx) {
306
307
  lastHookStderr.set(hookName, trimmed);
307
308
  }
308
309
 
310
+ /**
311
+ * A hook process can exit non-zero for two very different reasons:
312
+ * (a) the guard legitimately decided to refuse an operation
313
+ * (strict-mode refusal — this is the *only* case we ever want
314
+ * to surface as a block to the user), or
315
+ * (b) the hook infrastructure itself failed — the runtime crashed,
316
+ * a child binary was missing, stderr is a chunk of yargs help
317
+ * from some unrelated process, timeout, etc.
318
+ *
319
+ * Treating (b) as a block is what produces the "guard blocked
320
+ * tool.execute.before" error on a user who did nothing wrong. The
321
+ * heuristic below trims guaranteed-infra signals so only cleanly
322
+ * structured guard output is eligible for a real strict-mode block.
323
+ */
324
+ const INFRA_NOISE_PATTERNS = [
325
+ /^\\s*(Usage|Options|Commands|Examples|Positionals|Aliases):/im,
326
+ /^\\s*--[a-z][a-z0-9-]*\\b.*\\[(string|boolean|number|array)\\]/im,
327
+ /\\bcommand (not found|failed)\\b/i,
328
+ /\\bno such file or directory\\b/i,
329
+ /\\bCannot find module\\b/i,
330
+ /\\bThrowsCompletion\\b/,
331
+ /\\b(Reference|Syntax|Type|Range)Error\\b/,
332
+ /^\\s*at [^\\n]+\\([^)]*:\\d+:\\d+\\)/im,
333
+ /^\\s*node:internal\\b/im
334
+ ];
335
+ function looksLikeInfrastructureFailure(stderr) {
336
+ if (typeof stderr !== "string") return true;
337
+ const trimmed = stderr.trim();
338
+ if (trimmed.length === 0) return true;
339
+ for (const pattern of INFRA_NOISE_PATTERNS) {
340
+ if (pattern.test(trimmed)) return true;
341
+ }
342
+ return false;
343
+ }
344
+
309
345
  async function runHookScript(hookName, payload = {}) {
310
346
  const { spawn } = await import("node:child_process");
311
347
  const hookRuntimePath = join(root, "${RUNTIME_ROOT}/hooks/run-hook.mjs");
@@ -406,14 +442,42 @@ export default function cclawPlugin(ctx) {
406
442
  * still runs through guards.
407
443
  */
408
444
  const SAFE_READONLY_TOOLS = new Set([
445
+ // Filesystem / search reads — no state mutation possible.
409
446
  "read",
410
447
  "glob",
411
448
  "grep",
412
449
  "list",
413
450
  "ls",
414
451
  "view",
452
+ "find",
453
+ // Network reads — no local state mutation.
415
454
  "webfetch",
416
- "websearch"
455
+ "websearch",
456
+ // User-facing question / ask tools: they only ask the human for
457
+ // input and cannot touch the filesystem or execute code. Blocking
458
+ // them strands the plugin mid-decision (see OpenCode's \`question\`).
459
+ "question",
460
+ "ask",
461
+ "askuser",
462
+ "askquestion",
463
+ "ask_question",
464
+ "ask_user",
465
+ "ask_user_question",
466
+ "askuserquestion",
467
+ "request_user_input",
468
+ "requestuserinput",
469
+ "prompt",
470
+ // Thinking / scratchpad tools — pure reasoning with no side effects.
471
+ "think",
472
+ "thinking",
473
+ // Todo bookkeeping tools — they write only inside the harness's own
474
+ // session state, not project files, and blocking them breaks agent
475
+ // planning without protecting anything.
476
+ "todo",
477
+ "todoread",
478
+ "todowrite",
479
+ "todo_read",
480
+ "todo_write"
417
481
  ]);
418
482
 
419
483
  function isSafeReadOnlyTool(payload) {
@@ -435,6 +499,42 @@ export default function cclawPlugin(ctx) {
435
499
  return false;
436
500
  }
437
501
 
502
+ /**
503
+ * Strictness derived from (in order of precedence): CCLAW_STRICTNESS
504
+ * env override, \`.cclaw/config.yaml\` key \`strictness\`, or the
505
+ * library default of "advisory". The plugin only ever *blocks* tool
506
+ * execution when strictness resolves to "strict"; in advisory mode
507
+ * guard failures are logged and the tool call proceeds. This mirrors
508
+ * the Ralph-loop / hook-runtime semantics of
509
+ * \`DEFAULT_STRICTNESS = advisory\`, so the plugin can no longer
510
+ * accidentally be the stricter half of a mismatched pair.
511
+ */
512
+ function readConfigStrictness() {
513
+ try {
514
+ if (!existsSync(configPath)) return "";
515
+ const { readFileSync } = require("node:fs");
516
+ const raw = readFileSync(configPath, "utf8");
517
+ if (typeof raw !== "string" || raw.length === 0) return "";
518
+ const match = raw.match(/^\\s*strictness\\s*:\\s*([A-Za-z0-9_-]+)/m);
519
+ return match && typeof match[1] === "string" ? match[1].trim().toLowerCase() : "";
520
+ } catch {
521
+ return "";
522
+ }
523
+ }
524
+
525
+ function resolveStrictness() {
526
+ const envRaw = typeof process.env.CCLAW_STRICTNESS === "string"
527
+ ? process.env.CCLAW_STRICTNESS.trim().toLowerCase()
528
+ : "";
529
+ if (envRaw === "strict") return "strict";
530
+ if (envRaw === "advisory" || envRaw === "off" || envRaw === "disabled" || envRaw === "none") {
531
+ return "advisory";
532
+ }
533
+ const fileRaw = readConfigStrictness();
534
+ if (fileRaw === "strict") return "strict";
535
+ return "advisory";
536
+ }
537
+
438
538
  /**
439
539
  * cclaw considers itself "active" in a project when both the state
440
540
  * file and the hook runtime script exist. If either is missing the
@@ -618,11 +718,35 @@ export default function cclawPlugin(ctx) {
618
718
  const failed = !promptOk ? "prompt-guard" : "workflow-guard";
619
719
  const rawDetail = lastHookStderr.get(failed) || "";
620
720
  const detail = rawDetail.length > 0 ? rawDetail.slice(-400) : "(no stderr captured)";
721
+ if (looksLikeInfrastructureFailure(rawDetail)) {
722
+ // Never let a broken hook runtime or misrouted child-process
723
+ // stderr (yargs help, Node crash, ENOENT, timeout) masquerade
724
+ // as a policy block. Log the infra hit and let the user keep
725
+ // working regardless of strictness.
726
+ logToFile(
727
+ "infra: " + failed + " non-zero exit with non-guard stderr — treated as infrastructure failure, tool allowed. " +
728
+ "stderr=" + detail.replace(/\\s+/g, " ").slice(0, 300)
729
+ );
730
+ return;
731
+ }
732
+ const strictness = resolveStrictness();
733
+ if (strictness !== "strict") {
734
+ // Advisory mode (the default) — every guard refusal is a hint,
735
+ // not a hard stop. Users report the "failure" as a log line
736
+ // and keep working. Only \`strictness: strict\` in config.yaml
737
+ // or CCLAW_STRICTNESS=strict upgrades this to a thrown block.
738
+ logToFile(
739
+ "advisory: " + failed + " flagged tool.execute.before (strictness=" +
740
+ strictness + "). detail=" + detail.replace(/\\s+/g, " ").slice(0, 300)
741
+ );
742
+ return;
743
+ }
621
744
  throw new Error(
622
745
  "cclaw " + failed + " blocked tool.execute.before.\\n" +
623
746
  "Reason: " + detail + "\\n" +
624
747
  "Diagnose: run \`cclaw doctor\` in project root.\\n" +
625
- "Bypass (temporary): export CCLAW_DISABLE=1 before starting OpenCode."
748
+ "Bypass (temporary): export CCLAW_DISABLE=1 before starting OpenCode,\\n" +
749
+ "or set \`strictness: advisory\` in .cclaw/config.yaml."
626
750
  );
627
751
  }
628
752
  },
@@ -565,11 +565,12 @@ Do not keep stale or oversized context loaded when task intent changes. Context
565
565
 
566
566
  ## Context Modes
567
567
 
568
- Modes are stored in \`.cclaw/contexts/\`:
569
- - \`default\` — balanced execution
570
- - \`execution\` — fast plan/tdd throughput
571
- - \`review\` — defect/risk discovery
572
- - \`incident\` — stabilization and recovery
568
+ Four intentional postures tracked via \`.cclaw/state/context-mode.json\`:
569
+
570
+ - \`default\` — balanced execution. Follow active stage, keep changes scoped, escalate real trade-offs only.
571
+ - \`execution\` — high-throughput delivery after spec/plan lock. RED→GREEN→REFACTOR. Evidence-first, minimal chatter.
572
+ - \`review\` — deep validation. Bias toward concrete defects, unsupported claims treated as unverified.
573
+ - \`incident\` — stabilization. Reproduce, isolate, fix smallest safe change first. Preserve evidence.
573
574
 
574
575
  ## Mode Switching Protocol
575
576
 
@@ -578,7 +579,7 @@ Modes are stored in \`.cclaw/contexts/\`:
578
579
  - \`activeMode\`: target mode id
579
580
  - \`updatedAt\`: current ISO timestamp
580
581
  3. Announce mode change in-session with one-line reason.
581
- 4. Continue using the corresponding \`.cclaw/contexts/<mode>.md\` guidance.
582
+ 4. Keep applying the posture above until the next explicit mode switch.
582
583
 
583
584
  ## Payload Hygiene Rules
584
585
 
package/dist/doctor.js CHANGED
@@ -1241,62 +1241,6 @@ export async function doctorChecks(projectRoot, options = {}) {
1241
1241
  ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "state", "harness-gaps.json")),
1242
1242
  details: `${RUNTIME_ROOT}/state/harness-gaps.json must exist for tiered harness capability tracking`
1243
1243
  });
1244
- const adapterManifestPath = path.join(projectRoot, RUNTIME_ROOT, "adapters", "manifest.json");
1245
- const adapterManifestExists = await exists(adapterManifestPath);
1246
- checks.push({
1247
- name: "state:adapter_manifest_exists",
1248
- ok: adapterManifestExists,
1249
- details: `${RUNTIME_ROOT}/adapters/manifest.json must exist for harness adapter provenance`
1250
- });
1251
- if (adapterManifestExists) {
1252
- let harnessesOk = false;
1253
- let harnessesDetails = "";
1254
- let sourcesOk = false;
1255
- let sourcesDetails = "";
1256
- try {
1257
- const parsed = JSON.parse(await fs.readFile(adapterManifestPath, "utf8"));
1258
- const manifestHarnesses = Array.isArray(parsed.harnesses)
1259
- ? parsed.harnesses.filter((entry) => typeof entry === "string")
1260
- : [];
1261
- const expectedHarnesses = configuredHarnesses.length > 0
1262
- ? [...new Set(configuredHarnesses)].sort()
1263
- : null;
1264
- const actualHarnesses = [...new Set(manifestHarnesses)].sort();
1265
- harnessesOk = expectedHarnesses
1266
- ? actualHarnesses.length === expectedHarnesses.length &&
1267
- actualHarnesses.every((harness, index) => harness === expectedHarnesses[index])
1268
- : actualHarnesses.length > 0;
1269
- harnessesDetails = expectedHarnesses
1270
- ? harnessesOk
1271
- ? `adapter manifest harnesses match config.yaml: ${actualHarnesses.join(", ")}`
1272
- : `adapter manifest harnesses [${actualHarnesses.join(", ")}] do not match config.yaml [${expectedHarnesses.join(", ")}]`
1273
- : harnessesOk
1274
- ? `adapter manifest declares harnesses: ${actualHarnesses.join(", ")}`
1275
- : "adapter manifest must declare at least one harness";
1276
- const commandSource = typeof parsed.commandSource === "string" ? parsed.commandSource.trim() : "";
1277
- const skillSource = typeof parsed.skillSource === "string" ? parsed.skillSource.trim() : "";
1278
- sourcesOk = commandSource.length > 0 && skillSource.length > 0;
1279
- sourcesDetails = sourcesOk
1280
- ? `adapter manifest source globs are set (commandSource=${commandSource}; skillSource=${skillSource})`
1281
- : "adapter manifest must include non-empty commandSource and skillSource";
1282
- }
1283
- catch {
1284
- harnessesOk = false;
1285
- harnessesDetails = "adapter manifest must be valid JSON with a harnesses array";
1286
- sourcesOk = false;
1287
- sourcesDetails = "adapter manifest must be valid JSON with source globs";
1288
- }
1289
- checks.push({
1290
- name: "state:adapter_manifest_harnesses",
1291
- ok: harnessesOk,
1292
- details: harnessesDetails
1293
- });
1294
- checks.push({
1295
- name: "state:adapter_manifest_sources",
1296
- ok: sourcesOk,
1297
- details: sourcesDetails
1298
- });
1299
- }
1300
1244
  const contextModeStatePath = path.join(projectRoot, RUNTIME_ROOT, "state", "context-mode.json");
1301
1245
  checks.push({
1302
1246
  name: "state:context_mode_exists",
@@ -1319,14 +1263,6 @@ export async function doctorChecks(projectRoot, options = {}) {
1319
1263
  details: `${RUNTIME_ROOT}/state/context-mode.json must reference one of: ${Object.keys(CONTEXT_MODES).join(", ")} (default=${DEFAULT_CONTEXT_MODE})`
1320
1264
  });
1321
1265
  }
1322
- for (const mode of Object.keys(CONTEXT_MODES)) {
1323
- const modePath = path.join(projectRoot, RUNTIME_ROOT, "contexts", `${mode}.md`);
1324
- checks.push({
1325
- name: `contexts:mode:${mode}`,
1326
- ok: await exists(modePath),
1327
- details: modePath
1328
- });
1329
- }
1330
1266
  await ensureFeatureSystem(projectRoot, { repair: false });
1331
1267
  const activeFeature = await readActiveFeature(projectRoot, { repair: false });
1332
1268
  let flowState = createInitialFlowState();
package/dist/install.js CHANGED
@@ -5,7 +5,7 @@ import { promisify } from "node:util";
5
5
  import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
6
6
  import { writeConfig, createDefaultConfig, readConfig, configPath, detectLanguageRulePacks, detectAdvancedKeys } from "./config.js";
7
7
  import { stageCommandContract } from "./content/contracts.js";
8
- import { contextModeFiles, createInitialContextModeState } from "./content/contexts.js";
8
+ import { createInitialContextModeState } from "./content/contexts.js";
9
9
  import { learnSkillMarkdown, learnCommandContract } from "./content/learnings.js";
10
10
  import { nextCommandContract, nextCommandSkillMarkdown } from "./content/next-command.js";
11
11
  import { ideateCommandContract, ideateCommandSkillMarkdown } from "./content/ideate-command.js";
@@ -799,151 +799,6 @@ async function ensureKnowledgeStore(projectRoot) {
799
799
  await fs.rm(legacyMdPath, { force: true });
800
800
  }
801
801
  }
802
- async function ensureCustomSkillsScaffold(projectRoot) {
803
- const customDir = runtimePath(projectRoot, "custom-skills");
804
- await ensureDir(customDir);
805
- const readmePath = path.join(customDir, "README.md");
806
- if (!(await exists(readmePath))) {
807
- await writeFileSafe(readmePath, CUSTOM_SKILLS_README);
808
- }
809
- const examplePath = path.join(customDir, "example", "SKILL.md");
810
- if (!(await exists(examplePath))) {
811
- await writeFileSafe(examplePath, CUSTOM_SKILLS_EXAMPLE);
812
- }
813
- }
814
- const CUSTOM_SKILLS_README = `# Custom Skills (sync-safe)
815
-
816
- This directory is **never overwritten** by \`cclaw sync\` or \`cclaw upgrade\`. Use it
817
- to add project-specific skills that complement the managed skills under
818
- \`.cclaw/skills/\`.
819
-
820
- ## When to add a custom skill
821
-
822
- - A repeatable lens specific to **this project** (e.g. "billing-domain", "kafka-message-contracts").
823
- - A team convention you want every agent session to load.
824
- - A domain checklist that does not generalize to other projects.
825
-
826
- If the skill is general (security, performance, accessibility, etc.) prefer
827
- contributing it upstream instead — the managed skills receive maintenance.
828
-
829
- ## File format — public API (stable contract)
830
-
831
- Each skill lives at \`.cclaw/custom-skills/<folder>/SKILL.md\`. The format is a
832
- **stable public API**: \`cclaw sync\` and \`cclaw upgrade\` will not rewrite
833
- custom skills, and the fields below are guaranteed to be respected by the
834
- meta-skill router and the stage hooks.
835
-
836
- ### Frontmatter (YAML, required)
837
-
838
- \`\`\`yaml
839
- ---
840
- # Required fields
841
- name: <kebab-case-skill-name>
842
- description: >
843
- One sentence (≤180 chars) that triggers semantic routing. Include the
844
- concrete situation and the expected action
845
- (e.g. "Audit Kafka topic contracts when a producer or consumer signature changes").
846
-
847
- # Optional fields (omit when not applicable)
848
- stages: [design, spec, tdd, review] # flow stages this skill applies to
849
- triggers:
850
- - "kafka topic"
851
- - "producer.schema"
852
- - "consumer.schema"
853
- hardGate: false # true => skill body MUST include a ## HARD-GATE section
854
- owners: ["@team-messaging"] # informational routing hint, not enforced
855
- version: 0.1.0 # semver; bump when hardGate or algorithm changes
856
- ---
857
- \`\`\`
858
-
859
- ### Field contract
860
-
861
- | Field | Type | Required | Meaning |
862
- |---|---|---|---|
863
- | \`name\` | string (kebab-case) | yes | Unique id used by the router and by \`/cc-view status\` diagnostics. |
864
- | \`description\` | string ≤180 chars (single line OR YAML \`>\` folded) | yes | Drives semantic routing. Include trigger + action. |
865
- | \`stages\` | array of flow stages | no | When present, the meta-skill only surfaces this skill during those stages. Omit for "any stage". |
866
- | \`triggers\` | array of strings | no | Extra literal substrings that route to this skill when found in the user prompt or the active artifact. |
867
- | \`hardGate\` | boolean | no | When \`true\`, the body MUST include a \`## HARD-GATE\` section; the agent treats the rule as non-skippable. |
868
- | \`owners\` | array of strings | no | Informational only — surfaced to the user, never enforced. |
869
- | \`version\` | semver string | no | Bump when you change the HARD-GATE or algorithm so reviewers can spot changes. |
870
-
871
- ### Body sections (markdown, recommended order)
872
-
873
- \`\`\`markdown
874
- # <Skill title>
875
-
876
- ## Overview
877
- One-paragraph summary; context for when this skill is loaded.
878
-
879
- ## When to use
880
- - Bullet list of situations where this skill adds value.
881
-
882
- ## When NOT to use
883
- - Situations where loading this skill is context bloat or wrong.
884
-
885
- ## HARD-GATE (REQUIRED when frontmatter hardGate: true)
886
- Phrase it as a refusal:
887
- > Do not <X> while <Y>.
888
-
889
- ## Algorithm / checklist
890
- 1. Concrete, observable steps with evidence (file:line, artifact, or knowledge entry).
891
-
892
- ## Output protocol
893
- Where the artifact / chat output lives and what shape it takes.
894
-
895
- ## Anti-patterns
896
- - Common failure modes to reject.
897
- \`\`\`
898
-
899
- ### Stage association semantics
900
-
901
- - \`stages: []\` or missing → skill is available at any stage. The meta-skill still only surfaces it when \`description\` or \`triggers\` match the prompt.
902
- - \`stages: [review]\` → skill is offered only during the review stage.
903
- - Custom skills **never** become mandatory delegations. They are opt-in lenses. If you need a mandatory dispatch, add a proper managed specialist under \`.cclaw/skills/\` instead.
904
-
905
- ## Routing
906
-
907
- Custom skills are surfaced via the \`using-cclaw\` meta-skill at session start.
908
- Mention the skill name in your prompt or let the agent semantic-route to it
909
- based on the description + triggers + stages frontmatter.
910
-
911
- ## Versioning & removal
912
-
913
- Custom skills are user-owned. Bump \`version\` when you change the HARD-GATE or
914
- algorithm; delete or edit them at any time — \`cclaw sync\` will not touch them.
915
- `;
916
- const CUSTOM_SKILLS_EXAMPLE = `---
917
- name: example-custom-skill
918
- description: "Replace this with a one-sentence description that triggers when the skill should be used. Delete or rename this folder when you add a real skill."
919
- ---
920
-
921
- # Example Custom Skill
922
-
923
- This is a placeholder. Use it as a starting template, then delete or rename
924
- the \`example/\` folder.
925
-
926
- ## When to use
927
-
928
- - A real, repeatable situation in **this** project that needs a consistent lens.
929
-
930
- ## HARD-GATE (optional)
931
-
932
- Drop this section if no hard rule applies. Keep it crisp:
933
-
934
- > Do not <X> while <Y>.
935
-
936
- ## Algorithm
937
-
938
- 1. Step one — observable, file:line evidence required.
939
- 2. Step two — produce a named artifact, not a vibe.
940
- 3. Step three — escalate / hand off / record knowledge entry.
941
-
942
- ## Anti-patterns
943
-
944
- - Treating this skill as advisory when the situation matches the trigger.
945
- - Loading this skill when the situation clearly does not match (context bloat).
946
- `;
947
802
  async function ensureSessionStateFiles(projectRoot) {
948
803
  const stateDir = runtimePath(projectRoot, "state");
949
804
  await ensureDir(stateDir);
@@ -1020,11 +875,6 @@ async function writeRulebook(projectRoot) {
1020
875
  await writeFileSafe(runtimePath(projectRoot, "rules", "RULES.md"), RULEBOOK_MARKDOWN);
1021
876
  await writeFileSafe(runtimePath(projectRoot, "rules", "rules.json"), `${JSON.stringify(buildRulesJson(), null, 2)}\n`);
1022
877
  }
1023
- async function writeContextModes(projectRoot) {
1024
- for (const [mode, content] of Object.entries(contextModeFiles())) {
1025
- await writeFileSafe(runtimePath(projectRoot, "contexts", `${mode}.md`), content);
1026
- }
1027
- }
1028
878
  async function writeCursorWorkflowRule(projectRoot, harnesses) {
1029
879
  const rulePath = path.join(projectRoot, CURSOR_RULE_REL_PATH);
1030
880
  if (!harnesses.includes("cursor")) {
@@ -1073,15 +923,6 @@ async function writeState(projectRoot, config, forceReset = false) {
1073
923
  const state = createInitialFlowState({ track: config.defaultTrack ?? "standard" });
1074
924
  await writeFileSafe(statePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
1075
925
  }
1076
- async function writeAdapterManifest(projectRoot, harnesses) {
1077
- const manifest = {
1078
- generatedAt: new Date().toISOString(),
1079
- harnesses,
1080
- commandSource: `${RUNTIME_ROOT}/commands/*.md`,
1081
- skillSource: `${RUNTIME_ROOT}/skills/*/SKILL.md`
1082
- };
1083
- await writeFileSafe(runtimePath(projectRoot, "adapters", "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
1084
- }
1085
926
  async function writeHarnessGapsState(projectRoot, harnesses) {
1086
927
  const report = harnesses.map((harness) => {
1087
928
  const capabilities = HARNESS_ADAPTERS[harness].capabilities;
@@ -1304,7 +1145,6 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
1304
1145
  writeCommandContracts(projectRoot, config.defaultTrack ?? "standard"),
1305
1146
  writeUtilityCommands(projectRoot, config),
1306
1147
  writeSkills(projectRoot, config),
1307
- writeContextModes(projectRoot),
1308
1148
  writeArtifactTemplates(projectRoot),
1309
1149
  writeEvalScaffold(projectRoot),
1310
1150
  writeRulebook(projectRoot)
@@ -1312,10 +1152,8 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
1312
1152
  await writeState(projectRoot, config, forceStateReset);
1313
1153
  await ensureRunSystem(projectRoot, { createIfMissing: false });
1314
1154
  await ensureSessionStateFiles(projectRoot);
1315
- await writeAdapterManifest(projectRoot, harnesses);
1316
1155
  await writeHarnessGapsState(projectRoot, harnesses);
1317
1156
  await ensureKnowledgeStore(projectRoot);
1318
- await ensureCustomSkillsScaffold(projectRoot);
1319
1157
  await writeHooks(projectRoot, config);
1320
1158
  await syncDisabledHarnessArtifacts(projectRoot, harnesses);
1321
1159
  await syncManagedGitHooks(projectRoot, config);
package/dist/policy.js CHANGED
@@ -181,10 +181,6 @@ export async function policyChecks(projectRoot, options = {}) {
181
181
  { file: runtimeFile("skills/frontend-accessibility/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:frontend_accessibility:hard_gate" },
182
182
  { file: runtimeFile("skills/frontend-accessibility/SKILL.md"), needle: "## Checklist", name: "utility_skill:frontend_accessibility:checklist" },
183
183
  { file: runtimeFile("skills/frontend-accessibility/SKILL.md"), needle: "## Anti-Patterns", name: "utility_skill:frontend_accessibility:anti_patterns" },
184
- { file: runtimeFile("contexts/default.md"), needle: "Context Mode: default", name: "context_mode:default" },
185
- { file: runtimeFile("contexts/execution.md"), needle: "Context Mode: execution", name: "context_mode:execution" },
186
- { file: runtimeFile("contexts/review.md"), needle: "Context Mode: review", name: "context_mode:review" },
187
- { file: runtimeFile("contexts/incident.md"), needle: "Context Mode: incident", name: "context_mode:incident" },
188
184
  { file: runtimeFile("hooks/run-hook.mjs"), needle: "activeRunId", name: "hooks:session_start:active_run" },
189
185
  { file: runtimeFile("hooks/run-hook.mjs"), needle: "checkpoint.json", name: "hooks:session_start:checkpoint_ref" },
190
186
  { file: runtimeFile("hooks/run-hook.mjs"), needle: "stage-activity.jsonl", name: "hooks:session_start:activity_ref" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.48.34",
3
+ "version": "0.49.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {