cclaw-cli 0.5.17 → 0.6.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.
@@ -1,7 +1,28 @@
1
1
  import { COMMAND_FILE_ORDER } from "./constants.js";
2
2
  import { buildTransitionRules, orderedStageSchemas, stageGateIds } from "./content/stage-schema.js";
3
+ import { FLOW_STAGES, FLOW_TRACKS, TRACK_STAGES } from "./types.js";
3
4
  export const TRANSITION_RULES = buildTransitionRules();
4
- export function createInitialFlowState(activeRunId = "active") {
5
+ export function isFlowTrack(value) {
6
+ return typeof value === "string" && FLOW_TRACKS.includes(value);
7
+ }
8
+ export function trackStages(track) {
9
+ return [...TRACK_STAGES[track]];
10
+ }
11
+ export function skippedStagesForTrack(track) {
12
+ const inTrack = new Set(TRACK_STAGES[track]);
13
+ return FLOW_STAGES.filter((stage) => !inTrack.has(stage));
14
+ }
15
+ export function firstStageForTrack(track) {
16
+ const stages = TRACK_STAGES[track];
17
+ return stages[0] ?? "brainstorm";
18
+ }
19
+ export function createInitialFlowState(activeRunIdOrOptions = "active", maybeTrack) {
20
+ const options = typeof activeRunIdOrOptions === "string"
21
+ ? { activeRunId: activeRunIdOrOptions, track: maybeTrack }
22
+ : activeRunIdOrOptions;
23
+ const activeRunId = options.activeRunId ?? "active";
24
+ const track = options.track ?? "standard";
25
+ const skippedStages = skippedStagesForTrack(track);
5
26
  const stageGateCatalog = {};
6
27
  for (const schema of orderedStageSchemas()) {
7
28
  stageGateCatalog[schema.stage] = {
@@ -12,10 +33,12 @@ export function createInitialFlowState(activeRunId = "active") {
12
33
  }
13
34
  return {
14
35
  activeRunId,
15
- currentStage: "brainstorm",
36
+ currentStage: firstStageForTrack(track),
16
37
  completedStages: [],
17
38
  guardEvidence: {},
18
- stageGateCatalog
39
+ stageGateCatalog,
40
+ track,
41
+ skippedStages
19
42
  };
20
43
  }
21
44
  export function canTransition(from, to) {
@@ -25,17 +48,33 @@ export function getTransitionGuards(from, to) {
25
48
  const match = TRANSITION_RULES.find((rule) => rule.from === from && rule.to === to);
26
49
  return match ? [...match.guards] : [];
27
50
  }
28
- export function nextStage(stage) {
29
- const index = COMMAND_FILE_ORDER.indexOf(stage);
30
- if (index < 0 || index === COMMAND_FILE_ORDER.length - 1) {
51
+ export function nextStage(stage, track = "standard") {
52
+ const ordered = TRACK_STAGES[track];
53
+ const index = ordered.indexOf(stage);
54
+ if (index < 0) {
55
+ const fallback = COMMAND_FILE_ORDER.indexOf(stage);
56
+ if (fallback < 0 || fallback === COMMAND_FILE_ORDER.length - 1) {
57
+ return null;
58
+ }
59
+ return COMMAND_FILE_ORDER[fallback + 1];
60
+ }
61
+ if (index === ordered.length - 1) {
31
62
  return null;
32
63
  }
33
- return COMMAND_FILE_ORDER[index + 1];
64
+ return ordered[index + 1];
34
65
  }
35
- export function previousStage(stage) {
36
- const index = COMMAND_FILE_ORDER.indexOf(stage);
37
- if (index <= 0) {
66
+ export function previousStage(stage, track = "standard") {
67
+ const ordered = TRACK_STAGES[track];
68
+ const index = ordered.indexOf(stage);
69
+ if (index === 0) {
38
70
  return null;
39
71
  }
40
- return COMMAND_FILE_ORDER[index - 1];
72
+ if (index < 0) {
73
+ const fallback = COMMAND_FILE_ORDER.indexOf(stage);
74
+ if (fallback <= 0) {
75
+ return null;
76
+ }
77
+ return COMMAND_FILE_ORDER[fallback - 1];
78
+ }
79
+ return ordered[index - 1];
41
80
  }
@@ -124,6 +124,7 @@ export async function syncHarnessShims(projectRoot, harnesses) {
124
124
  await writeFileSafe(path.join(commandDir, "cc.md"), utilityShimContent(harness, "cc", "flow-start", "start.md"));
125
125
  await writeFileSafe(path.join(commandDir, "cc-next.md"), utilityShimContent(harness, "next", "flow-next-step", "next.md"));
126
126
  await writeFileSafe(path.join(commandDir, "cc-learn.md"), utilityShimContent(harness, "learn", "learnings", "learn.md"));
127
+ await writeFileSafe(path.join(commandDir, "cc-status.md"), utilityShimContent(harness, "status", "flow-status", "status.md"));
127
128
  }
128
129
  await syncAgentFiles(projectRoot);
129
130
  await syncAgentsMd(projectRoot);
package/dist/install.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import type { HarnessId } from "./types.js";
1
+ import type { FlowTrack, HarnessId } from "./types.js";
2
2
  export interface InitOptions {
3
3
  projectRoot: string;
4
4
  harnesses?: HarnessId[];
5
+ track?: FlowTrack;
5
6
  }
6
7
  export declare function initCclaw(options: InitOptions): Promise<void>;
7
8
  export declare function syncCclaw(projectRoot: string): Promise<void>;
package/dist/install.js CHANGED
@@ -9,9 +9,10 @@ import { contextModeFiles, createInitialContextModeState } from "./content/conte
9
9
  import { learnSkillMarkdown, learnCommandContract } from "./content/learnings.js";
10
10
  import { nextCommandContract, nextCommandSkillMarkdown } from "./content/next-command.js";
11
11
  import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
12
+ import { statusCommandContract, statusCommandSkillMarkdown } from "./content/status-command.js";
12
13
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
13
14
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
14
- import { sessionStartScript, stopCheckpointScript, opencodePluginJs, claudeHooksJson, cursorHooksJson, codexHooksJson } from "./content/hooks.js";
15
+ import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, cursorHooksJson, codexHooksJson } from "./content/hooks.js";
15
16
  import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
16
17
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
17
18
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
@@ -173,6 +174,7 @@ async function writeSkills(projectRoot) {
173
174
  await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
174
175
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-next-step", "SKILL.md"), nextCommandSkillMarkdown());
175
176
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
177
+ await writeFileSafe(runtimePath(projectRoot, "skills", "flow-status", "SKILL.md"), statusCommandSkillMarkdown());
176
178
  await writeFileSafe(runtimePath(projectRoot, "skills", "subagent-dev", "SKILL.md"), subagentDrivenDevSkill());
177
179
  await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
178
180
  await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
@@ -186,6 +188,7 @@ async function writeUtilityCommands(projectRoot) {
186
188
  await writeFileSafe(runtimePath(projectRoot, "commands", "learn.md"), learnCommandContract());
187
189
  await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
188
190
  await writeFileSafe(runtimePath(projectRoot, "commands", "start.md"), startCommandContract());
191
+ await writeFileSafe(runtimePath(projectRoot, "commands", "status.md"), statusCommandContract());
189
192
  }
190
193
  function toObject(value) {
191
194
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -482,6 +485,7 @@ async function writeHooks(projectRoot, config) {
482
485
  await ensureDir(hooksDir);
483
486
  await writeFileSafe(path.join(hooksDir, "session-start.sh"), sessionStartScript());
484
487
  await writeFileSafe(path.join(hooksDir, "stop-checkpoint.sh"), stopCheckpointScript());
488
+ await writeFileSafe(path.join(hooksDir, "pre-compact.sh"), preCompactScript());
485
489
  await writeFileSafe(path.join(hooksDir, "prompt-guard.sh"), promptGuardScript({
486
490
  strictMode: config.promptGuardMode === "strict"
487
491
  }));
@@ -493,6 +497,7 @@ async function writeHooks(projectRoot, config) {
493
497
  for (const script of [
494
498
  "session-start.sh",
495
499
  "stop-checkpoint.sh",
500
+ "pre-compact.sh",
496
501
  "prompt-guard.sh",
497
502
  "workflow-guard.sh",
498
503
  "context-monitor.sh",
@@ -542,6 +547,101 @@ async function ensureKnowledgeStore(projectRoot) {
542
547
  await writeFileSafe(storePath, "# Project Knowledge\n\n");
543
548
  }
544
549
  }
550
+ async function ensureCustomSkillsScaffold(projectRoot) {
551
+ const customDir = runtimePath(projectRoot, "custom-skills");
552
+ await ensureDir(customDir);
553
+ const readmePath = path.join(customDir, "README.md");
554
+ if (!(await exists(readmePath))) {
555
+ await writeFileSafe(readmePath, CUSTOM_SKILLS_README);
556
+ }
557
+ const examplePath = path.join(customDir, "example", "SKILL.md");
558
+ if (!(await exists(examplePath))) {
559
+ await writeFileSafe(examplePath, CUSTOM_SKILLS_EXAMPLE);
560
+ }
561
+ }
562
+ const CUSTOM_SKILLS_README = `# Custom Skills (sync-safe)
563
+
564
+ This directory is **never overwritten** by \`cclaw sync\` or \`cclaw upgrade\`. Use it
565
+ to add project-specific skills that complement the managed skills under
566
+ \`.cclaw/skills/\`.
567
+
568
+ ## When to add a custom skill
569
+
570
+ - A repeatable lens specific to **this project** (e.g. "billing-domain", "kafka-message-contracts").
571
+ - A team convention you want every agent session to load.
572
+ - A domain checklist that does not generalize to other projects.
573
+
574
+ If the skill is general (security, performance, accessibility, etc.) prefer
575
+ contributing it upstream instead — the managed skills receive maintenance.
576
+
577
+ ## File format
578
+
579
+ Each skill lives at \`.cclaw/custom-skills/<folder>/SKILL.md\` with frontmatter:
580
+
581
+ \`\`\`markdown
582
+ ---
583
+ name: <kebab-case-skill-name>
584
+ description: "One sentence describing when this skill applies. Triggers semantic routing."
585
+ ---
586
+
587
+ # <Skill title>
588
+
589
+ ## When to use
590
+ - ...
591
+
592
+ ## HARD-GATE (optional)
593
+ A non-skippable rule, if any. Phrase it as a refusal, not a recommendation.
594
+
595
+ ## Algorithm / checklist
596
+ 1. ...
597
+ 2. ...
598
+
599
+ ## Anti-patterns
600
+ - ...
601
+ \`\`\`
602
+
603
+ ## Routing
604
+
605
+ Custom skills are surfaced via the \`using-cclaw\` meta-skill at session start.
606
+ Mention the skill name in your prompt or let the agent semantic-route to it
607
+ based on the description.
608
+
609
+ ## Removing or replacing
610
+
611
+ Custom skills are user-owned. Delete or edit them at any time — \`cclaw sync\`
612
+ will not touch them.
613
+ `;
614
+ const CUSTOM_SKILLS_EXAMPLE = `---
615
+ name: example-custom-skill
616
+ 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."
617
+ ---
618
+
619
+ # Example Custom Skill
620
+
621
+ This is a placeholder. Use it as a starting template, then delete or rename
622
+ the \`example/\` folder.
623
+
624
+ ## When to use
625
+
626
+ - A real, repeatable situation in **this** project that needs a consistent lens.
627
+
628
+ ## HARD-GATE (optional)
629
+
630
+ Drop this section if no hard rule applies. Keep it crisp:
631
+
632
+ > Do not <X> while <Y>.
633
+
634
+ ## Algorithm
635
+
636
+ 1. Step one — observable, file:line evidence required.
637
+ 2. Step two — produce a named artifact, not a vibe.
638
+ 3. Step three — escalate / hand off / record knowledge entry.
639
+
640
+ ## Anti-patterns
641
+
642
+ - Treating this skill as advisory when the situation matches the trigger.
643
+ - Loading this skill when the situation clearly does not match (context bloat).
644
+ `;
545
645
  async function ensureSessionStateFiles(projectRoot) {
546
646
  const stateDir = runtimePath(projectRoot, "state");
547
647
  await ensureDir(stateDir);
@@ -623,12 +723,12 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
623
723
  await removeManagedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
624
724
  }
625
725
  }
626
- async function writeState(projectRoot, forceReset = false) {
726
+ async function writeState(projectRoot, config, forceReset = false) {
627
727
  const statePath = runtimePath(projectRoot, "state", "flow-state.json");
628
728
  if (!forceReset && (await exists(statePath))) {
629
729
  return;
630
730
  }
631
- const state = createInitialFlowState();
731
+ const state = createInitialFlowState("active", config.defaultTrack ?? "standard");
632
732
  await writeFileSafe(statePath, `${JSON.stringify(state, null, 2)}\n`);
633
733
  }
634
734
  async function writeAdapterManifest(projectRoot, harnesses) {
@@ -740,11 +840,12 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
740
840
  await writeContextModes(projectRoot);
741
841
  await writeArtifactTemplates(projectRoot);
742
842
  await writeRulebook(projectRoot);
743
- await writeState(projectRoot, forceStateReset);
843
+ await writeState(projectRoot, config, forceStateReset);
744
844
  await ensureRunSystem(projectRoot, { createIfMissing: false });
745
845
  await ensureSessionStateFiles(projectRoot);
746
846
  await writeAdapterManifest(projectRoot, harnesses);
747
847
  await ensureKnowledgeStore(projectRoot);
848
+ await ensureCustomSkillsScaffold(projectRoot);
748
849
  await writeHooks(projectRoot, config);
749
850
  await syncDisabledHarnessArtifacts(projectRoot, harnesses);
750
851
  await syncManagedGitHooks(projectRoot, config);
@@ -753,7 +854,7 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
753
854
  await ensureGitignore(projectRoot);
754
855
  }
755
856
  export async function initCclaw(options) {
756
- const config = createDefaultConfig(options.harnesses);
857
+ const config = createDefaultConfig(options.harnesses, options.track);
757
858
  await writeConfig(options.projectRoot, config);
758
859
  await materializeRuntime(options.projectRoot, config, true);
759
860
  }
@@ -828,7 +929,7 @@ function stripManagedHookCommands(value) {
828
929
  }
829
930
  function isManagedRuntimeHookCommand(command) {
830
931
  const normalized = command.trim().replace(/\s+/gu, " ");
831
- return /(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/(?:session-start|stop-checkpoint|prompt-guard|workflow-guard|context-monitor)\.sh(?:\s|$)/u.test(normalized);
932
+ return /(^|\s)(?:bash\s+)?(?:\.\/)?\.cclaw\/hooks\/(?:session-start|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor)\.sh(?:\s|$)/u.test(normalized);
832
933
  }
833
934
  async function removeManagedHookEntries(hookFilePath) {
834
935
  if (!(await exists(hookFilePath)))
package/dist/runs.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { COMMAND_FILE_ORDER, RUNTIME_ROOT } from "./constants.js";
4
- import { canTransition, createInitialFlowState } from "./flow-state.js";
4
+ import { canTransition, createInitialFlowState, isFlowTrack, skippedStagesForTrack } from "./flow-state.js";
5
5
  import { ensureDir, exists, withDirectoryLock, writeFileSafe } from "./fs-utils.js";
6
6
  export class InvalidStageTransitionError extends Error {
7
7
  from;
@@ -156,8 +156,27 @@ function sanitizeStageGateCatalog(value, fallback) {
156
156
  }
157
157
  return next;
158
158
  }
159
+ function coerceTrack(value) {
160
+ return isFlowTrack(value) ? value : "standard";
161
+ }
162
+ function sanitizeSkippedStages(value, track) {
163
+ const trackDefault = skippedStagesForTrack(track);
164
+ if (!Array.isArray(value)) {
165
+ return trackDefault;
166
+ }
167
+ const seen = new Set();
168
+ const out = [];
169
+ for (const raw of value) {
170
+ if (isFlowStage(raw) && !seen.has(raw)) {
171
+ seen.add(raw);
172
+ out.push(raw);
173
+ }
174
+ }
175
+ return out.length > 0 ? out : trackDefault;
176
+ }
159
177
  function coerceFlowState(parsed) {
160
- const next = createInitialFlowState();
178
+ const track = coerceTrack(parsed.track);
179
+ const next = createInitialFlowState("active", track);
161
180
  const activeRunIdRaw = parsed.activeRunId;
162
181
  const activeRunId = typeof activeRunIdRaw === "string" && activeRunIdRaw.trim().length > 0
163
182
  ? activeRunIdRaw.trim()
@@ -167,7 +186,9 @@ function coerceFlowState(parsed) {
167
186
  currentStage: isFlowStage(parsed.currentStage) ? parsed.currentStage : next.currentStage,
168
187
  completedStages: sanitizeCompletedStages(parsed.completedStages),
169
188
  guardEvidence: sanitizeGuardEvidence(parsed.guardEvidence),
170
- stageGateCatalog: sanitizeStageGateCatalog(parsed.stageGateCatalog, next.stageGateCatalog)
189
+ stageGateCatalog: sanitizeStageGateCatalog(parsed.stageGateCatalog, next.stageGateCatalog),
190
+ track,
191
+ skippedStages: sanitizeSkippedStages(parsed.skippedStages, track)
171
192
  };
172
193
  }
173
194
  function toArchiveDate(date = new Date()) {
package/dist/types.d.ts CHANGED
@@ -1,5 +1,16 @@
1
1
  export declare const FLOW_STAGES: readonly ["brainstorm", "scope", "design", "spec", "plan", "tdd", "review", "ship"];
2
2
  export type FlowStage = (typeof FLOW_STAGES)[number];
3
+ export declare const FLOW_TRACKS: readonly ["quick", "standard"];
4
+ export type FlowTrack = (typeof FLOW_TRACKS)[number];
5
+ /**
6
+ * Ordered stages that make up each flow track.
7
+ *
8
+ * - `standard` runs the full 8-stage pipeline (default — same as before tracks existed).
9
+ * - `quick` skips the upstream product stages (brainstorm/scope/design/plan) for
10
+ * small bug fixes or single-purpose changes where the spec is already known.
11
+ * It still keeps the non-negotiable safety gates: spec → tdd → review → ship.
12
+ */
13
+ export declare const TRACK_STAGES: Record<FlowTrack, readonly FlowStage[]>;
3
14
  export declare const HARNESS_IDS: readonly ["claude", "cursor", "opencode", "codex"];
4
15
  export type HarnessId = (typeof HARNESS_IDS)[number];
5
16
  export interface VibyConfig {
@@ -12,6 +23,8 @@ export interface VibyConfig {
12
23
  promptGuardMode?: "advisory" | "strict";
13
24
  /** When true, cclaw installs managed git pre-commit/pre-push wrappers. */
14
25
  gitHookGuards?: boolean;
26
+ /** Default flow track for new runs (quick = shortened path, standard = full pipeline). */
27
+ defaultTrack?: FlowTrack;
15
28
  }
16
29
  export interface TransitionRule {
17
30
  from: FlowStage;
package/dist/types.js CHANGED
@@ -8,4 +8,17 @@ export const FLOW_STAGES = [
8
8
  "review",
9
9
  "ship"
10
10
  ];
11
+ export const FLOW_TRACKS = ["quick", "standard"];
12
+ /**
13
+ * Ordered stages that make up each flow track.
14
+ *
15
+ * - `standard` runs the full 8-stage pipeline (default — same as before tracks existed).
16
+ * - `quick` skips the upstream product stages (brainstorm/scope/design/plan) for
17
+ * small bug fixes or single-purpose changes where the spec is already known.
18
+ * It still keeps the non-negotiable safety gates: spec → tdd → review → ship.
19
+ */
20
+ export const TRACK_STAGES = {
21
+ standard: FLOW_STAGES,
22
+ quick: ["spec", "tdd", "review", "ship"]
23
+ };
11
24
  export const HARNESS_IDS = ["claude", "cursor", "opencode", "codex"];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.5.17",
3
+ "version": "0.6.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {