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 +0 -4
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +0 -3
- package/dist/content/contexts.d.ts +11 -2
- package/dist/content/contexts.js +17 -58
- package/dist/content/meta-skill.js +0 -3
- package/dist/content/opencode-plugin.js +126 -2
- package/dist/content/utility-skills.js +7 -6
- package/dist/doctor.js +0 -64
- package/dist/install.js +1 -163
- package/dist/policy.js +0 -4
- package/package.json +1 -1
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/**",
|
package/dist/constants.d.ts
CHANGED
|
@@ -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/
|
|
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
|
-
|
|
3
|
-
|
|
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;
|
package/dist/content/contexts.js
CHANGED
|
@@ -1,65 +1,24 @@
|
|
|
1
1
|
export const DEFAULT_CONTEXT_MODE = "default";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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:
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
- \`
|
|
571
|
-
- \`
|
|
572
|
-
- \`
|
|
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.
|
|
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 {
|
|
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" },
|