cclaw-cli 0.1.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.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/dist/cli.d.ts +10 -0
  4. package/dist/cli.js +101 -0
  5. package/dist/config.d.ts +5 -0
  6. package/dist/config.js +70 -0
  7. package/dist/constants.d.ts +12 -0
  8. package/dist/constants.js +50 -0
  9. package/dist/content/agents.d.ts +39 -0
  10. package/dist/content/agents.js +244 -0
  11. package/dist/content/autoplan.d.ts +7 -0
  12. package/dist/content/autoplan.js +297 -0
  13. package/dist/content/contracts.d.ts +2 -0
  14. package/dist/content/contracts.js +50 -0
  15. package/dist/content/examples.d.ts +2 -0
  16. package/dist/content/examples.js +327 -0
  17. package/dist/content/hooks.d.ts +16 -0
  18. package/dist/content/hooks.js +753 -0
  19. package/dist/content/learnings.d.ts +5 -0
  20. package/dist/content/learnings.js +265 -0
  21. package/dist/content/meta-skill.d.ts +10 -0
  22. package/dist/content/meta-skill.js +137 -0
  23. package/dist/content/observe.d.ts +21 -0
  24. package/dist/content/observe.js +1110 -0
  25. package/dist/content/session-hooks.d.ts +7 -0
  26. package/dist/content/session-hooks.js +137 -0
  27. package/dist/content/skills.d.ts +3 -0
  28. package/dist/content/skills.js +257 -0
  29. package/dist/content/stage-schema.d.ts +78 -0
  30. package/dist/content/stage-schema.js +1453 -0
  31. package/dist/content/subagents.d.ts +13 -0
  32. package/dist/content/subagents.js +616 -0
  33. package/dist/content/templates.d.ts +3 -0
  34. package/dist/content/templates.js +272 -0
  35. package/dist/content/utility-skills.d.ts +12 -0
  36. package/dist/content/utility-skills.js +467 -0
  37. package/dist/doctor.d.ts +7 -0
  38. package/dist/doctor.js +610 -0
  39. package/dist/flow-state.d.ts +19 -0
  40. package/dist/flow-state.js +41 -0
  41. package/dist/fs-utils.d.ts +5 -0
  42. package/dist/fs-utils.js +28 -0
  43. package/dist/gitignore.d.ts +3 -0
  44. package/dist/gitignore.js +43 -0
  45. package/dist/harness-adapters.d.ts +12 -0
  46. package/dist/harness-adapters.js +175 -0
  47. package/dist/install.d.ts +9 -0
  48. package/dist/install.js +562 -0
  49. package/dist/learnings-summarizer.d.ts +25 -0
  50. package/dist/learnings-summarizer.js +201 -0
  51. package/dist/logger.d.ts +3 -0
  52. package/dist/logger.js +6 -0
  53. package/dist/policy.d.ts +6 -0
  54. package/dist/policy.js +179 -0
  55. package/dist/runs.d.ts +18 -0
  56. package/dist/runs.js +446 -0
  57. package/dist/types.d.ts +19 -0
  58. package/dist/types.js +12 -0
  59. package/package.json +47 -0
@@ -0,0 +1,28 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function ensureDir(dirPath) {
4
+ await fs.mkdir(dirPath, { recursive: true });
5
+ }
6
+ export async function writeFileSafe(filePath, content) {
7
+ await ensureDir(path.dirname(filePath));
8
+ const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
9
+ await fs.writeFile(tempPath, content, "utf8");
10
+ await fs.rename(tempPath, filePath);
11
+ }
12
+ export async function exists(filePath) {
13
+ try {
14
+ await fs.access(filePath);
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ export async function removeIfExists(targetPath) {
22
+ if (await exists(targetPath)) {
23
+ await fs.rm(targetPath, { recursive: true, force: true });
24
+ }
25
+ }
26
+ export function resolveProjectPath(cwd, relativePath) {
27
+ return path.resolve(cwd, relativePath);
28
+ }
@@ -0,0 +1,3 @@
1
+ export declare function ensureGitignore(projectRoot: string): Promise<void>;
2
+ export declare function removeGitignorePatterns(projectRoot: string): Promise<void>;
3
+ export declare function gitignoreHasRequiredPatterns(projectRoot: string): Promise<boolean>;
@@ -0,0 +1,43 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { REQUIRED_GITIGNORE_PATTERNS } from "./constants.js";
4
+ import { exists } from "./fs-utils.js";
5
+ export async function ensureGitignore(projectRoot) {
6
+ const gitignorePath = path.join(projectRoot, ".gitignore");
7
+ const currentContent = (await exists(gitignorePath))
8
+ ? await fs.readFile(gitignorePath, "utf8")
9
+ : "";
10
+ const lines = currentContent.split(/\r?\n/);
11
+ const normalized = new Set(lines.map((line) => line.trim()).filter(Boolean));
12
+ const missing = REQUIRED_GITIGNORE_PATTERNS.filter((pattern) => !normalized.has(pattern));
13
+ if (missing.length === 0) {
14
+ return;
15
+ }
16
+ const base = lines.join("\n").replace(/\s+$/u, "");
17
+ const suffix = `${base.length > 0 ? "\n" : ""}${missing.join("\n")}\n`;
18
+ await fs.writeFile(gitignorePath, `${base}${suffix}`, "utf8");
19
+ }
20
+ export async function removeGitignorePatterns(projectRoot) {
21
+ const gitignorePath = path.join(projectRoot, ".gitignore");
22
+ if (!(await exists(gitignorePath)))
23
+ return;
24
+ const content = await fs.readFile(gitignorePath, "utf8");
25
+ const lines = content.split(/\r?\n/);
26
+ const patternsSet = new Set(REQUIRED_GITIGNORE_PATTERNS);
27
+ const cleaned = lines.filter((line) => !patternsSet.has(line.trim()));
28
+ const result = cleaned.join("\n").replace(/\n{3,}/g, "\n\n").trim();
29
+ if (result.length === 0) {
30
+ await fs.rm(gitignorePath, { force: true });
31
+ }
32
+ else {
33
+ await fs.writeFile(gitignorePath, `${result}\n`, "utf8");
34
+ }
35
+ }
36
+ export async function gitignoreHasRequiredPatterns(projectRoot) {
37
+ const gitignorePath = path.join(projectRoot, ".gitignore");
38
+ if (!(await exists(gitignorePath))) {
39
+ return false;
40
+ }
41
+ const content = await fs.readFile(gitignorePath, "utf8");
42
+ return REQUIRED_GITIGNORE_PATTERNS.every((pattern) => content.includes(pattern));
43
+ }
@@ -0,0 +1,12 @@
1
+ import type { HarnessId } from "./types.js";
2
+ export declare const CCLAW_MARKER_START = "<!-- cclaw-start -->";
3
+ export declare const CCLAW_MARKER_END = "<!-- cclaw-end -->";
4
+ export interface HarnessAdapter {
5
+ id: HarnessId;
6
+ commandDir: string;
7
+ }
8
+ export declare const HARNESS_ADAPTERS: Record<HarnessId, HarnessAdapter>;
9
+ /** Removes the cclaw AGENTS.md block. */
10
+ export declare function stripCclawBlock(content: string): string;
11
+ export declare function removeCclawFromAgentsMd(projectRoot: string): Promise<void>;
12
+ export declare function syncHarnessShims(projectRoot: string, harnesses: HarnessId[]): Promise<void>;
@@ -0,0 +1,175 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { COMMAND_FILE_ORDER, RUNTIME_ROOT } from "./constants.js";
4
+ import { CCLAW_AGENTS, agentMarkdown, agentsAgentsMdBlock } from "./content/agents.js";
5
+ import { autoplanAgentsMdBlock } from "./content/autoplan.js";
6
+ import { learningsAgentsMdBlock } from "./content/learnings.js";
7
+ import { sessionHooksAgentsMdBlock } from "./content/session-hooks.js";
8
+ import { hooksAgentsMdBlock } from "./content/hooks.js";
9
+ import { subagentsAgentsMdBlock } from "./content/subagents.js";
10
+ import { stageSkillFolder } from "./content/skills.js";
11
+ import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
12
+ export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
13
+ export const CCLAW_MARKER_END = "<!-- cclaw-end -->";
14
+ function escapeRegExp(value) {
15
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
16
+ }
17
+ const RUNTIME_AGENTS_BLOCK_SOURCE = `${escapeRegExp(CCLAW_MARKER_START)}[\\s\\S]*?${escapeRegExp(CCLAW_MARKER_END)}`;
18
+ const RUNTIME_AGENTS_BLOCK_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "u");
19
+ const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "gu");
20
+ export const HARNESS_ADAPTERS = {
21
+ claude: { id: "claude", commandDir: ".claude/commands" },
22
+ cursor: { id: "cursor", commandDir: ".cursor/commands" },
23
+ opencode: { id: "opencode", commandDir: ".opencode/commands" },
24
+ codex: { id: "codex", commandDir: ".codex/commands" }
25
+ };
26
+ function shimFileName(stage) {
27
+ return `cc-${stage}.md`;
28
+ }
29
+ function shimContent(harness, stage) {
30
+ const skillFolder = stageSkillFolder(stage);
31
+ return `---
32
+ name: cc-${stage}
33
+ description: Generated shim for ${harness}. Runs one flow stage with command+skill context.
34
+ source: generated-by-cclaw
35
+ ---
36
+
37
+ # cclaw ${stage}
38
+
39
+ Load and execute:
40
+ 1. \`.cclaw/skills/${skillFolder}/SKILL.md\`
41
+ 2. \`.cclaw/commands/${stage}.md\`
42
+
43
+ This command is stage-scoped: do only this stage and then hand off to the next one.
44
+ Use \`.cclaw/state/flow-state.json\` for transition guards and status tracking.
45
+ Do not skip required handoff gates.
46
+ `;
47
+ }
48
+ function agentsMdBlock() {
49
+ const stageList = COMMAND_FILE_ORDER.map((s) => `| \`/cc-${s}\` | \`.cclaw/skills/${stageSkillFolder(s)}/SKILL.md\` + \`.cclaw/commands/${s}.md\` |`).join("\n");
50
+ return `${CCLAW_MARKER_START}
51
+ ## Cclaw — Structured Development Flow
52
+
53
+ > Auto-generated by \`cclaw sync\`. Do not edit manually — run \`npx cclaw sync\`.
54
+
55
+ ### Activation Rule
56
+
57
+ Before responding to a coding request:
58
+ 1. Read \`.cclaw/state/flow-state.json\` for the current stage.
59
+ 2. If a stage applies, invoke the matching \`/cc-*\` command.
60
+ 3. If no stage applies, respond normally.
61
+
62
+ ### Intent → Stage Routing
63
+
64
+ | Command | Loads |
65
+ |---|---|
66
+ ${stageList}
67
+ | \`/cc-learn\` | \`.cclaw/skills/learnings/SKILL.md\` + \`.cclaw/commands/learn.md\` |
68
+ | \`/cc-autoplan\` | \`.cclaw/skills/autoplan/SKILL.md\` + \`.cclaw/commands/autoplan.md\` |
69
+
70
+ **Stage order:** brainstorm > scope > design > spec > plan > test > build > review > ship.
71
+ One stage per invocation. Gates must pass before handoff. Artifacts in \`.cclaw/artifacts/\`.
72
+
73
+ ### Verification Discipline
74
+
75
+ No completion claims without fresh evidence. No "Done" / "All good" / "Tests pass" without running the command in this message.
76
+
77
+ ### File Map
78
+
79
+ | Path | Purpose |
80
+ |---|---|
81
+ | \`.cclaw/commands/*.md\` | Stage commands (thin orchestrators) |
82
+ | \`.cclaw/skills/*/SKILL.md\` | Full stage instructions |
83
+ | \`.cclaw/rules/\` | RULES.md + rules.json |
84
+ | \`.cclaw/state/flow-state.json\` | Flow state & gate tracking |
85
+ | \`.cclaw/artifacts/*.md\` | Stage evidence artifacts |
86
+ | \`.cclaw/agents/*.md\` | Specialist agent personas |
87
+ | \`.cclaw/learnings.jsonl\` | Project knowledge base |
88
+
89
+ ${learningsAgentsMdBlock()}
90
+ ${autoplanAgentsMdBlock()}
91
+ ${agentsAgentsMdBlock()}
92
+ ${subagentsAgentsMdBlock()}
93
+ ${sessionHooksAgentsMdBlock()}
94
+ ${hooksAgentsMdBlock()}
95
+ ${CCLAW_MARKER_END}`;
96
+ }
97
+ /** Removes the cclaw AGENTS.md block. */
98
+ export function stripCclawBlock(content) {
99
+ let updated = content.replace(RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN, "");
100
+ return updated.replace(/\n{3,}/g, "\n\n").trim();
101
+ }
102
+ async function syncAgentsMd(projectRoot) {
103
+ const agentsPath = path.join(projectRoot, "AGENTS.md");
104
+ const block = agentsMdBlock();
105
+ if (!(await exists(agentsPath))) {
106
+ await writeFileSafe(agentsPath, `# AGENTS\n\n${block}\n`);
107
+ return;
108
+ }
109
+ const content = await fs.readFile(agentsPath, "utf8");
110
+ if (RUNTIME_AGENTS_BLOCK_PATTERN.test(content)) {
111
+ const stripped = stripCclawBlock(content);
112
+ const updated = stripped.length > 0 ? `${stripped}\n\n${block}\n` : `${block}\n`;
113
+ await writeFileSafe(agentsPath, updated);
114
+ }
115
+ else {
116
+ await writeFileSafe(agentsPath, `${content.trimEnd()}\n\n${block}\n`);
117
+ }
118
+ }
119
+ export async function removeCclawFromAgentsMd(projectRoot) {
120
+ const agentsPath = path.join(projectRoot, "AGENTS.md");
121
+ if (!(await exists(agentsPath)))
122
+ return;
123
+ const content = await fs.readFile(agentsPath, "utf8");
124
+ if (!RUNTIME_AGENTS_BLOCK_PATTERN.test(content))
125
+ return;
126
+ const stripped = stripCclawBlock(content);
127
+ if (stripped.replace(/\s/g, "").length === 0) {
128
+ await fs.rm(agentsPath, { force: true });
129
+ }
130
+ else {
131
+ await writeFileSafe(agentsPath, `${stripped}\n`);
132
+ }
133
+ }
134
+ function utilityShimContent(harness, command, skillFolder, commandFile) {
135
+ return `---
136
+ name: cc-${command}
137
+ description: Generated shim for ${harness}. Utility command — not a flow stage.
138
+ source: generated-by-cclaw
139
+ ---
140
+
141
+ # cclaw ${command}
142
+
143
+ Load and execute:
144
+ 1. \`.cclaw/skills/${skillFolder}/SKILL.md\`
145
+ 2. \`.cclaw/commands/${commandFile}\`
146
+
147
+ This is a utility command (not a flow stage). It does not advance flow state.
148
+ `;
149
+ }
150
+ async function syncAgentFiles(projectRoot) {
151
+ const agentsDir = path.join(projectRoot, RUNTIME_ROOT, "agents");
152
+ await ensureDir(agentsDir);
153
+ for (const agent of CCLAW_AGENTS) {
154
+ await writeFileSafe(path.join(agentsDir, `${agent.name}.md`), agentMarkdown(agent));
155
+ }
156
+ }
157
+ export async function syncHarnessShims(projectRoot, harnesses) {
158
+ for (const harness of harnesses) {
159
+ const adapter = HARNESS_ADAPTERS[harness];
160
+ if (!adapter) {
161
+ continue;
162
+ }
163
+ const commandDir = path.join(projectRoot, adapter.commandDir);
164
+ await ensureDir(commandDir);
165
+ for (const stage of COMMAND_FILE_ORDER) {
166
+ const fullPath = path.join(commandDir, shimFileName(stage));
167
+ await writeFileSafe(fullPath, shimContent(harness, stage));
168
+ }
169
+ // Utility command shims
170
+ await writeFileSafe(path.join(commandDir, "cc-learn.md"), utilityShimContent(harness, "learn", "learnings", "learn.md"));
171
+ await writeFileSafe(path.join(commandDir, "cc-autoplan.md"), utilityShimContent(harness, "autoplan", "autoplan", "autoplan.md"));
172
+ }
173
+ await syncAgentFiles(projectRoot);
174
+ await syncAgentsMd(projectRoot);
175
+ }
@@ -0,0 +1,9 @@
1
+ import type { HarnessId } from "./types.js";
2
+ export interface InitOptions {
3
+ projectRoot: string;
4
+ harnesses?: HarnessId[];
5
+ }
6
+ export declare function initCclaw(options: InitOptions): Promise<void>;
7
+ export declare function syncCclaw(projectRoot: string): Promise<void>;
8
+ export declare function upgradeCclaw(projectRoot: string): Promise<void>;
9
+ export declare function uninstallCclaw(projectRoot: string): Promise<void>;