cclaw-cli 0.6.0 → 0.7.1

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/install.js CHANGED
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import { COMMAND_FILE_ORDER, REQUIRED_DIRS, RUNTIME_ROOT, UTILITY_COMMANDS } from "./constants.js";
6
- import { writeConfig, createDefaultConfig, readConfig, configPath } from "./config.js";
6
+ import { writeConfig, createDefaultConfig, createProfileConfig, readConfig, configPath } from "./config.js";
7
7
  import { commandContract } from "./content/contracts.js";
8
8
  import { contextModeFiles, createInitialContextModeState } from "./content/contexts.js";
9
9
  import { learnSkillMarkdown, learnCommandContract } from "./content/learnings.js";
@@ -17,7 +17,7 @@ import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./
17
17
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
18
18
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
19
19
  import { stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
20
- import { UTILITY_SKILL_FOLDERS, UTILITY_SKILL_MAP } from "./content/utility-skills.js";
20
+ import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS, UTILITY_SKILL_MAP } from "./content/utility-skills.js";
21
21
  import { createInitialFlowState } from "./flow-state.js";
22
22
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
23
23
  import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
@@ -165,7 +165,7 @@ async function writeArtifactTemplates(projectRoot) {
165
165
  await writeFileSafe(runtimePath(projectRoot, "templates", fileName), content);
166
166
  }
167
167
  }
168
- async function writeSkills(projectRoot) {
168
+ async function writeSkills(projectRoot, config) {
169
169
  for (const stage of COMMAND_FILE_ORDER) {
170
170
  const folder = stageSkillFolder(stage);
171
171
  await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage));
@@ -183,6 +183,24 @@ async function writeSkills(projectRoot) {
183
183
  const generator = UTILITY_SKILL_MAP[folder];
184
184
  await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), generator());
185
185
  }
186
+ // Language rule packs live under .cclaw/rules/lang/<pack>.md. They are opt-in:
187
+ // only the packs listed in config.languageRulePacks are materialised. Any
188
+ // legacy per-language skill folders from v0.7.0 (.cclaw/skills/language-*)
189
+ // are cleaned up below so the new rules/lang layout is the only truth.
190
+ const enabledPacks = config?.languageRulePacks ?? [];
191
+ for (const pack of enabledPacks) {
192
+ const fileName = LANGUAGE_RULE_PACK_FILES[pack];
193
+ const generator = LANGUAGE_RULE_PACK_GENERATORS[pack];
194
+ if (!fileName || !generator)
195
+ continue;
196
+ await writeFileSafe(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR, fileName), generator());
197
+ }
198
+ for (const legacyFolder of LEGACY_LANGUAGE_RULE_PACK_FOLDERS) {
199
+ const legacyPath = runtimePath(projectRoot, "skills", legacyFolder);
200
+ if (await exists(legacyPath)) {
201
+ await fs.rm(legacyPath, { recursive: true, force: true });
202
+ }
203
+ }
186
204
  }
187
205
  async function writeUtilityCommands(projectRoot) {
188
206
  await writeFileSafe(runtimePath(projectRoot, "commands", "learn.md"), learnCommandContract());
@@ -542,9 +560,13 @@ async function writeHooks(projectRoot, config) {
542
560
  }
543
561
  }
544
562
  async function ensureKnowledgeStore(projectRoot) {
545
- const storePath = runtimePath(projectRoot, "knowledge.md");
563
+ const storePath = runtimePath(projectRoot, "knowledge.jsonl");
546
564
  if (!(await exists(storePath))) {
547
- await writeFileSafe(storePath, "# Project Knowledge\n\n");
565
+ await writeFileSafe(storePath, "");
566
+ }
567
+ const legacyMdPath = runtimePath(projectRoot, "knowledge.md");
568
+ if (await exists(legacyMdPath)) {
569
+ await fs.rm(legacyMdPath, { force: true });
548
570
  }
549
571
  }
550
572
  async function ensureCustomSkillsScaffold(projectRoot) {
@@ -574,42 +596,92 @@ to add project-specific skills that complement the managed skills under
574
596
  If the skill is general (security, performance, accessibility, etc.) prefer
575
597
  contributing it upstream instead — the managed skills receive maintenance.
576
598
 
577
- ## File format
599
+ ## File format — public API (stable contract)
578
600
 
579
- Each skill lives at \`.cclaw/custom-skills/<folder>/SKILL.md\` with frontmatter:
601
+ Each skill lives at \`.cclaw/custom-skills/<folder>/SKILL.md\`. The format is a
602
+ **stable public API**: \`cclaw sync\` and \`cclaw upgrade\` will not rewrite
603
+ custom skills, and the fields below are guaranteed to be respected by the
604
+ meta-skill router and the stage hooks.
580
605
 
581
- \`\`\`markdown
606
+ ### Frontmatter (YAML, required)
607
+
608
+ \`\`\`yaml
582
609
  ---
610
+ # Required fields
583
611
  name: <kebab-case-skill-name>
584
- description: "One sentence describing when this skill applies. Triggers semantic routing."
612
+ description: >
613
+ One sentence (≤180 chars) that triggers semantic routing. Include the
614
+ concrete situation and the expected action
615
+ (e.g. "Audit Kafka topic contracts when a producer or consumer signature changes").
616
+
617
+ # Optional fields (omit when not applicable)
618
+ stages: [design, spec, tdd, review] # flow stages this skill applies to
619
+ triggers:
620
+ - "kafka topic"
621
+ - "producer.schema"
622
+ - "consumer.schema"
623
+ hardGate: false # true => skill body MUST include a ## HARD-GATE section
624
+ owners: ["@team-messaging"] # informational routing hint, not enforced
625
+ version: 0.1.0 # semver; bump when hardGate or algorithm changes
585
626
  ---
627
+ \`\`\`
628
+
629
+ ### Field contract
586
630
 
631
+ | Field | Type | Required | Meaning |
632
+ |---|---|---|---|
633
+ | \`name\` | string (kebab-case) | yes | Unique id used by the router and by \`/cc-status\` diagnostics. |
634
+ | \`description\` | string ≤180 chars (single line OR YAML \`>\` folded) | yes | Drives semantic routing. Include trigger + action. |
635
+ | \`stages\` | array of flow stages | no | When present, the meta-skill only surfaces this skill during those stages. Omit for "any stage". |
636
+ | \`triggers\` | array of strings | no | Extra literal substrings that route to this skill when found in the user prompt or the active artifact. |
637
+ | \`hardGate\` | boolean | no | When \`true\`, the body MUST include a \`## HARD-GATE\` section; the agent treats the rule as non-skippable. |
638
+ | \`owners\` | array of strings | no | Informational only — surfaced to the user, never enforced. |
639
+ | \`version\` | semver string | no | Bump when you change the HARD-GATE or algorithm so reviewers can spot changes. |
640
+
641
+ ### Body sections (markdown, recommended order)
642
+
643
+ \`\`\`markdown
587
644
  # <Skill title>
588
645
 
646
+ ## Overview
647
+ One-paragraph summary; context for when this skill is loaded.
648
+
589
649
  ## When to use
590
- - ...
650
+ - Bullet list of situations where this skill adds value.
591
651
 
592
- ## HARD-GATE (optional)
593
- A non-skippable rule, if any. Phrase it as a refusal, not a recommendation.
652
+ ## When NOT to use
653
+ - Situations where loading this skill is context bloat or wrong.
654
+
655
+ ## HARD-GATE (REQUIRED when frontmatter hardGate: true)
656
+ Phrase it as a refusal:
657
+ > Do not <X> while <Y>.
594
658
 
595
659
  ## Algorithm / checklist
596
- 1. ...
597
- 2. ...
660
+ 1. Concrete, observable steps with evidence (file:line, artifact, or knowledge entry).
661
+
662
+ ## Output protocol
663
+ Where the artifact / chat output lives and what shape it takes.
598
664
 
599
665
  ## Anti-patterns
600
- - ...
666
+ - Common failure modes to reject.
601
667
  \`\`\`
602
668
 
669
+ ### Stage association semantics
670
+
671
+ - \`stages: []\` or missing → skill is available at any stage. The meta-skill still only surfaces it when \`description\` or \`triggers\` match the prompt.
672
+ - \`stages: [review]\` → skill is offered only during the review stage.
673
+ - 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.
674
+
603
675
  ## Routing
604
676
 
605
677
  Custom skills are surfaced via the \`using-cclaw\` meta-skill at session start.
606
678
  Mention the skill name in your prompt or let the agent semantic-route to it
607
- based on the description.
679
+ based on the description + triggers + stages frontmatter.
608
680
 
609
- ## Removing or replacing
681
+ ## Versioning & removal
610
682
 
611
- Custom skills are user-owned. Delete or edit them at any time — \`cclaw sync\`
612
- will not touch them.
683
+ Custom skills are user-owned. Bump \`version\` when you change the HARD-GATE or
684
+ algorithm; delete or edit them at any time — \`cclaw sync\` will not touch them.
613
685
  `;
614
686
  const CUSTOM_SKILLS_EXAMPLE = `---
615
687
  name: example-custom-skill
@@ -836,7 +908,7 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
836
908
  await cleanStaleFiles(projectRoot);
837
909
  await writeCommandContracts(projectRoot);
838
910
  await writeUtilityCommands(projectRoot);
839
- await writeSkills(projectRoot);
911
+ await writeSkills(projectRoot, config);
840
912
  await writeContextModes(projectRoot);
841
913
  await writeArtifactTemplates(projectRoot);
842
914
  await writeRulebook(projectRoot);
@@ -854,7 +926,12 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
854
926
  await ensureGitignore(projectRoot);
855
927
  }
856
928
  export async function initCclaw(options) {
857
- const config = createDefaultConfig(options.harnesses, options.track);
929
+ const config = options.profile
930
+ ? createProfileConfig(options.profile, {
931
+ harnesses: options.harnesses,
932
+ defaultTrack: options.track
933
+ })
934
+ : createDefaultConfig(options.harnesses, options.track);
858
935
  await writeConfig(options.projectRoot, config);
859
936
  await materializeRuntime(options.projectRoot, config, true);
860
937
  }
package/dist/policy.js CHANGED
@@ -85,7 +85,9 @@ export async function policyChecks(projectRoot, options = {}) {
85
85
  // --- utility skill checks ---
86
86
  const runtimeFile = (relativePath) => `${RUNTIME_ROOT}/${relativePath}`;
87
87
  const utilitySkillChecks = [
88
- { file: runtimeFile("skills/learnings/SKILL.md"), needle: "## Entry format (append-only)", name: "utility_skill:learnings:entry_format" },
88
+ { file: runtimeFile("skills/learnings/SKILL.md"), needle: "strict JSONL schema", name: "utility_skill:learnings:jsonl_schema" },
89
+ { file: runtimeFile("skills/learnings/SKILL.md"), needle: "knowledge.jsonl", name: "utility_skill:learnings:jsonl_store" },
90
+ { file: runtimeFile("skills/learnings/SKILL.md"), needle: "type, trigger, action, confidence, domain, stage, created, project", name: "utility_skill:learnings:field_order" },
89
91
  { file: runtimeFile("skills/learnings/SKILL.md"), needle: "## Subcommands", name: "utility_skill:learnings:subcommands" },
90
92
  { file: runtimeFile("skills/learnings/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:learnings:hard_gate" },
91
93
  { file: runtimeFile("commands/learn.md"), needle: "## Subcommands", name: "utility_command:learn:subcommands" },
package/dist/runs.d.ts CHANGED
@@ -24,6 +24,13 @@ export interface ArchiveRunResult {
24
24
  featureName: string;
25
25
  resetState: FlowState;
26
26
  snapshottedStateFiles: string[];
27
+ /** Knowledge curation hint: total active entries + soft threshold (50). */
28
+ knowledge: {
29
+ activeEntryCount: number;
30
+ softThreshold: number;
31
+ overThreshold: boolean;
32
+ knowledgePath: string;
33
+ };
27
34
  }
28
35
  export interface ArchiveManifest {
29
36
  version: 1;
@@ -48,4 +55,11 @@ export declare function writeFlowState(projectRoot: string, state: FlowState, op
48
55
  export declare function ensureRunSystem(projectRoot: string, _options?: EnsureRunSystemOptions): Promise<FlowState>;
49
56
  export declare function listRuns(projectRoot: string): Promise<CclawRunMeta[]>;
50
57
  export declare function archiveRun(projectRoot: string, featureName?: string): Promise<ArchiveRunResult>;
58
+ /**
59
+ * Counts entries in the canonical JSONL knowledge store. An "active" entry is one
60
+ * non-empty line that parses as JSON with the required `type` field belonging to the
61
+ * allowed set. Malformed lines are ignored (not counted) but do not throw so that a
62
+ * hand-edited file cannot break doctor/archive flows.
63
+ */
64
+ export declare function countActiveKnowledgeEntries(text: string): number;
51
65
  export {};
package/dist/runs.js CHANGED
@@ -394,12 +394,54 @@ export async function archiveRun(projectRoot, featureName) {
394
394
  snapshottedStateFiles
395
395
  };
396
396
  await writeFileSafe(path.join(archivePath, "archive-manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
397
+ const knowledgeStats = await readKnowledgeStats(projectRoot);
397
398
  return {
398
399
  archiveId,
399
400
  archivePath,
400
401
  archivedAt,
401
402
  featureName: feature,
402
403
  resetState,
403
- snapshottedStateFiles
404
+ snapshottedStateFiles,
405
+ knowledge: knowledgeStats
404
406
  };
405
407
  }
408
+ const KNOWLEDGE_SOFT_THRESHOLD = 50;
409
+ async function readKnowledgeStats(projectRoot) {
410
+ const knowledgePath = path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl");
411
+ let activeEntryCount = 0;
412
+ if (await exists(knowledgePath)) {
413
+ const text = await fs.readFile(knowledgePath, "utf8");
414
+ activeEntryCount = countActiveKnowledgeEntries(text);
415
+ }
416
+ return {
417
+ activeEntryCount,
418
+ softThreshold: KNOWLEDGE_SOFT_THRESHOLD,
419
+ overThreshold: activeEntryCount > KNOWLEDGE_SOFT_THRESHOLD,
420
+ knowledgePath: `${RUNTIME_ROOT}/knowledge.jsonl`
421
+ };
422
+ }
423
+ /**
424
+ * Counts entries in the canonical JSONL knowledge store. An "active" entry is one
425
+ * non-empty line that parses as JSON with the required `type` field belonging to the
426
+ * allowed set. Malformed lines are ignored (not counted) but do not throw so that a
427
+ * hand-edited file cannot break doctor/archive flows.
428
+ */
429
+ export function countActiveKnowledgeEntries(text) {
430
+ const allowed = new Set(["rule", "pattern", "lesson", "compound"]);
431
+ let count = 0;
432
+ for (const raw of text.split(/\r?\n/)) {
433
+ const line = raw.trim();
434
+ if (line.length === 0)
435
+ continue;
436
+ try {
437
+ const parsed = JSON.parse(line);
438
+ if (typeof parsed.type === "string" && allowed.has(parsed.type)) {
439
+ count += 1;
440
+ }
441
+ }
442
+ catch {
443
+ // Skip malformed lines silently; curation surfaces them separately.
444
+ }
445
+ }
446
+ return count;
447
+ }
package/dist/types.d.ts CHANGED
@@ -13,6 +13,28 @@ export type FlowTrack = (typeof FLOW_TRACKS)[number];
13
13
  export declare const TRACK_STAGES: Record<FlowTrack, readonly FlowStage[]>;
14
14
  export declare const HARNESS_IDS: readonly ["claude", "cursor", "opencode", "codex"];
15
15
  export type HarnessId = (typeof HARNESS_IDS)[number];
16
+ /**
17
+ * Init profiles pre-fill `cclaw init` flags for common install shapes.
18
+ *
19
+ * - `minimal` — single-harness (claude), quick track default, no git hook guards. For solo
20
+ * contributors or bugfix-heavy repos where most work is \`quick\` scope.
21
+ * - `standard` — default harness set, standard track, no git hook guards, advisory guards.
22
+ * Matches the pre-profile default behavior.
23
+ * - `full` — default harness set, standard track, git hook guards on, strict prompt guards.
24
+ * For teams that want every safety rail on.
25
+ */
26
+ export declare const INIT_PROFILES: readonly ["minimal", "standard", "full"];
27
+ export type InitProfile = (typeof INIT_PROFILES)[number];
28
+ /**
29
+ * Opt-in language rule packs. When enabled in config, `cclaw sync` installs the
30
+ * corresponding utility skill so the meta-skill router can load language-specific
31
+ * anti-patterns, idioms, and review heuristics during review/tdd stages.
32
+ *
33
+ * Opt-in intentional: cclaw stays language-agnostic by default; rule packs are
34
+ * additive context that the user must explicitly enable.
35
+ */
36
+ export declare const LANGUAGE_RULE_PACKS: readonly ["typescript", "python", "go"];
37
+ export type LanguageRulePack = (typeof LANGUAGE_RULE_PACKS)[number];
16
38
  export interface VibyConfig {
17
39
  version: string;
18
40
  flowVersion: string;
@@ -25,6 +47,13 @@ export interface VibyConfig {
25
47
  gitHookGuards?: boolean;
26
48
  /** Default flow track for new runs (quick = shortened path, standard = full pipeline). */
27
49
  defaultTrack?: FlowTrack;
50
+ /**
51
+ * Opt-in language rule packs. Each enabled pack materializes a matching rule
52
+ * file under `.cclaw/rules/lang/<id>.md` on the next `cclaw sync`. The
53
+ * meta-skill router loads the pack during review/tdd when the diff touches
54
+ * the language in question. Disabled packs have no on-disk footprint.
55
+ */
56
+ languageRulePacks?: LanguageRulePack[];
28
57
  }
29
58
  export interface TransitionRule {
30
59
  from: FlowStage;
package/dist/types.js CHANGED
@@ -22,3 +22,23 @@ export const TRACK_STAGES = {
22
22
  quick: ["spec", "tdd", "review", "ship"]
23
23
  };
24
24
  export const HARNESS_IDS = ["claude", "cursor", "opencode", "codex"];
25
+ /**
26
+ * Init profiles pre-fill `cclaw init` flags for common install shapes.
27
+ *
28
+ * - `minimal` — single-harness (claude), quick track default, no git hook guards. For solo
29
+ * contributors or bugfix-heavy repos where most work is \`quick\` scope.
30
+ * - `standard` — default harness set, standard track, no git hook guards, advisory guards.
31
+ * Matches the pre-profile default behavior.
32
+ * - `full` — default harness set, standard track, git hook guards on, strict prompt guards.
33
+ * For teams that want every safety rail on.
34
+ */
35
+ export const INIT_PROFILES = ["minimal", "standard", "full"];
36
+ /**
37
+ * Opt-in language rule packs. When enabled in config, `cclaw sync` installs the
38
+ * corresponding utility skill so the meta-skill router can load language-specific
39
+ * anti-patterns, idioms, and review heuristics during review/tdd stages.
40
+ *
41
+ * Opt-in intentional: cclaw stays language-agnostic by default; rule packs are
42
+ * additive context that the user must explicitly enable.
43
+ */
44
+ export const LANGUAGE_RULE_PACKS = ["typescript", "python", "go"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {