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
package/dist/runs.js ADDED
@@ -0,0 +1,446 @@
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 { ARTIFACT_TEMPLATES } from "./content/templates.js";
5
+ import { createInitialFlowState } from "./flow-state.js";
6
+ import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
7
+ const FLOW_STATE_REL_PATH = `${RUNTIME_ROOT}/state/flow-state.json`;
8
+ const RUNS_DIR_REL_PATH = `${RUNTIME_ROOT}/runs`;
9
+ const ACTIVE_ARTIFACTS_REL_PATH = `${RUNTIME_ROOT}/artifacts`;
10
+ const RUN_META_FILE = "run.json";
11
+ const RUN_HANDOFF_FILE = "00-handoff.md";
12
+ const FLOW_STAGE_SET = new Set(COMMAND_FILE_ORDER);
13
+ function flowStatePath(projectRoot) {
14
+ return path.join(projectRoot, FLOW_STATE_REL_PATH);
15
+ }
16
+ function runsRoot(projectRoot) {
17
+ return path.join(projectRoot, RUNS_DIR_REL_PATH);
18
+ }
19
+ function activeArtifactsPath(projectRoot) {
20
+ return path.join(projectRoot, ACTIVE_ARTIFACTS_REL_PATH);
21
+ }
22
+ function runRoot(projectRoot, runId) {
23
+ return path.join(runsRoot(projectRoot), requireSafeRunId(runId));
24
+ }
25
+ function runArtifactsPath(projectRoot, runId) {
26
+ return path.join(runRoot(projectRoot, runId), "artifacts");
27
+ }
28
+ function runMetaPath(projectRoot, runId) {
29
+ return path.join(runRoot(projectRoot, runId), RUN_META_FILE);
30
+ }
31
+ function runHandoffPath(projectRoot, runId) {
32
+ return path.join(runRoot(projectRoot, runId), RUN_HANDOFF_FILE);
33
+ }
34
+ function nowIso() {
35
+ return new Date().toISOString();
36
+ }
37
+ function pad2(value) {
38
+ return value.toString().padStart(2, "0");
39
+ }
40
+ function buildRunId(date = new Date()) {
41
+ const yyyy = date.getUTCFullYear();
42
+ const mm = pad2(date.getUTCMonth() + 1);
43
+ const dd = pad2(date.getUTCDate());
44
+ const hh = pad2(date.getUTCHours());
45
+ const min = pad2(date.getUTCMinutes());
46
+ const ss = pad2(date.getUTCSeconds());
47
+ const random = Math.random().toString(36).slice(2, 6);
48
+ return `run-${yyyy}${mm}${dd}-${hh}${min}${ss}-${random}`;
49
+ }
50
+ function normalizeTitle(title) {
51
+ const trimmed = (title ?? "").trim();
52
+ if (trimmed.length === 0) {
53
+ return "New feature run";
54
+ }
55
+ return trimmed;
56
+ }
57
+ function isSafeRunId(value) {
58
+ return /^[A-Za-z0-9_-]{1,128}$/u.test(value);
59
+ }
60
+ function sanitizeRunId(value) {
61
+ if (typeof value !== "string")
62
+ return undefined;
63
+ const trimmed = value.trim();
64
+ return isSafeRunId(trimmed) ? trimmed : undefined;
65
+ }
66
+ function requireSafeRunId(runId) {
67
+ const safe = sanitizeRunId(runId);
68
+ if (!safe) {
69
+ throw new Error(`Invalid run id "${runId}"`);
70
+ }
71
+ return safe;
72
+ }
73
+ function snapshotState(state) {
74
+ return {
75
+ currentStage: state.currentStage,
76
+ completedStages: [...state.completedStages],
77
+ guardEvidence: { ...state.guardEvidence },
78
+ stageGateCatalog: JSON.parse(JSON.stringify(state.stageGateCatalog))
79
+ };
80
+ }
81
+ function isFlowStage(value) {
82
+ return typeof value === "string" && FLOW_STAGE_SET.has(value);
83
+ }
84
+ function sanitizeStringArray(value) {
85
+ if (!Array.isArray(value)) {
86
+ return [];
87
+ }
88
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0);
89
+ }
90
+ function sanitizeCompletedStages(value) {
91
+ if (!Array.isArray(value)) {
92
+ return [];
93
+ }
94
+ const unique = new Set();
95
+ const stages = [];
96
+ for (const item of value) {
97
+ if (isFlowStage(item) && !unique.has(item)) {
98
+ unique.add(item);
99
+ stages.push(item);
100
+ }
101
+ }
102
+ return stages;
103
+ }
104
+ function sanitizeGuardEvidence(value) {
105
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
106
+ return {};
107
+ }
108
+ const next = {};
109
+ for (const [key, raw] of Object.entries(value)) {
110
+ if (typeof raw === "string") {
111
+ next[key] = raw;
112
+ }
113
+ }
114
+ return next;
115
+ }
116
+ function sanitizeStageGateCatalog(value, fallback) {
117
+ const next = {};
118
+ for (const stage of COMMAND_FILE_ORDER) {
119
+ const base = fallback[stage];
120
+ next[stage] = {
121
+ required: [...base.required],
122
+ passed: [...base.passed],
123
+ blocked: [...base.blocked]
124
+ };
125
+ }
126
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
127
+ return next;
128
+ }
129
+ const rawCatalog = value;
130
+ for (const stage of COMMAND_FILE_ORDER) {
131
+ const rawStage = rawCatalog[stage];
132
+ if (!rawStage || typeof rawStage !== "object" || Array.isArray(rawStage)) {
133
+ continue;
134
+ }
135
+ const typed = rawStage;
136
+ const allowedGateIds = new Set(next[stage].required);
137
+ next[stage] = {
138
+ required: [...next[stage].required],
139
+ passed: sanitizeStringArray(typed.passed).filter((gate) => allowedGateIds.has(gate)),
140
+ blocked: sanitizeStringArray(typed.blocked).filter((gate) => allowedGateIds.has(gate))
141
+ };
142
+ }
143
+ return next;
144
+ }
145
+ function coerceFlowState(parsed, activeRunIdOverride) {
146
+ const overrideTrim = sanitizeRunId(activeRunIdOverride);
147
+ const parsedActiveRun = sanitizeRunId(parsed.activeRunId);
148
+ const seedRunId = overrideTrim ?? parsedActiveRun;
149
+ const next = createInitialFlowState(seedRunId);
150
+ return {
151
+ activeRunId: overrideTrim ?? parsedActiveRun ?? next.activeRunId,
152
+ currentStage: isFlowStage(parsed.currentStage) ? parsed.currentStage : next.currentStage,
153
+ completedStages: sanitizeCompletedStages(parsed.completedStages),
154
+ guardEvidence: sanitizeGuardEvidence(parsed.guardEvidence),
155
+ stageGateCatalog: sanitizeStageGateCatalog(parsed.stageGateCatalog, next.stageGateCatalog)
156
+ };
157
+ }
158
+ function createdAtFromRunId(runId) {
159
+ const match = /^run-(\d{4})(\d{2})(\d{2})-(\d{2})(\d{2})(\d{2})-[a-z0-9]+$/iu.exec(runId);
160
+ if (!match) {
161
+ return null;
162
+ }
163
+ const [, year, month, day, hour, minute, second] = match;
164
+ const date = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day), Number(hour), Number(minute), Number(second)));
165
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
166
+ }
167
+ async function readJsonFile(filePath) {
168
+ if (!(await exists(filePath)))
169
+ return null;
170
+ try {
171
+ return JSON.parse(await fs.readFile(filePath, "utf8"));
172
+ }
173
+ catch {
174
+ return null;
175
+ }
176
+ }
177
+ async function listImmediateFiles(dirPath) {
178
+ if (!(await exists(dirPath)))
179
+ return [];
180
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
181
+ return entries.filter((entry) => entry.isFile()).map((entry) => entry.name).sort();
182
+ }
183
+ async function clearImmediateFiles(dirPath) {
184
+ if (!(await exists(dirPath)))
185
+ return;
186
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
187
+ for (const entry of entries) {
188
+ if (entry.isFile()) {
189
+ await fs.rm(path.join(dirPath, entry.name), { force: true });
190
+ }
191
+ }
192
+ }
193
+ async function copyImmediateFiles(fromDir, toDir) {
194
+ await ensureDir(toDir);
195
+ const fileNames = await listImmediateFiles(fromDir);
196
+ for (const fileName of fileNames) {
197
+ const sourcePath = path.join(fromDir, fileName);
198
+ const targetPath = path.join(toDir, fileName);
199
+ await fs.copyFile(sourcePath, targetPath);
200
+ }
201
+ }
202
+ function handoffMarkdown(runMeta, state) {
203
+ return `# Run Handoff
204
+
205
+ ## Run
206
+ - ID: ${runMeta.id}
207
+ - Title: ${runMeta.title}
208
+ - Created: ${runMeta.createdAt}
209
+ - Archived: ${runMeta.archivedAt ?? "active"}
210
+
211
+ ## Flow Snapshot
212
+ - Active stage: ${state.currentStage}
213
+ - Completed stages: ${state.completedStages.join(", ") || "(none)"}
214
+ - Active run ID in flow-state: ${state.activeRunId}
215
+
216
+ ## Paths
217
+ - Active artifacts: \`${RUNTIME_ROOT}/artifacts/\`
218
+ - Canonical run artifacts: \`${RUNTIME_ROOT}/runs/${runMeta.id}/artifacts/\`
219
+
220
+ ## Resume
221
+ 1. Continue with the stage command for \`${state.currentStage}\`
222
+ 2. If needed, sync artifacts from \`${RUNTIME_ROOT}/runs/${runMeta.id}/artifacts/\`
223
+ `;
224
+ }
225
+ export async function readFlowState(projectRoot) {
226
+ const statePath = flowStatePath(projectRoot);
227
+ const parsed = await readJsonFile(statePath);
228
+ if (!parsed || typeof parsed !== "object") {
229
+ return createInitialFlowState();
230
+ }
231
+ return coerceFlowState(parsed);
232
+ }
233
+ export async function writeFlowState(projectRoot, state) {
234
+ const safe = coerceFlowState({ ...state }, state.activeRunId);
235
+ await writeFileSafe(flowStatePath(projectRoot), `${JSON.stringify(safe, null, 2)}\n`);
236
+ }
237
+ export async function listRuns(projectRoot) {
238
+ const root = runsRoot(projectRoot);
239
+ if (!(await exists(root)))
240
+ return [];
241
+ const dirs = await fs.readdir(root, { withFileTypes: true });
242
+ const metas = [];
243
+ for (const dir of dirs) {
244
+ if (!dir.isDirectory())
245
+ continue;
246
+ const runId = dir.name;
247
+ if (!isSafeRunId(runId))
248
+ continue;
249
+ const meta = await readJsonFile(runMetaPath(projectRoot, runId));
250
+ if (meta && typeof meta.id === "string" && meta.id === runId) {
251
+ metas.push(meta);
252
+ continue;
253
+ }
254
+ let fallbackCreatedAt = createdAtFromRunId(runId);
255
+ if (!fallbackCreatedAt) {
256
+ try {
257
+ const stat = await fs.stat(path.join(root, runId));
258
+ fallbackCreatedAt = stat.birthtime?.toISOString?.() ?? stat.mtime.toISOString();
259
+ }
260
+ catch {
261
+ fallbackCreatedAt = null;
262
+ }
263
+ }
264
+ metas.push({
265
+ id: runId,
266
+ title: runId,
267
+ createdAt: fallbackCreatedAt ?? nowIso()
268
+ });
269
+ }
270
+ return metas.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
271
+ }
272
+ async function ensureRunMetadata(projectRoot, meta) {
273
+ await writeFileSafe(runMetaPath(projectRoot, meta.id), `${JSON.stringify(meta, null, 2)}\n`);
274
+ }
275
+ async function persistRunStateSnapshot(projectRoot, runId, state) {
276
+ const meta = await readJsonFile(runMetaPath(projectRoot, runId));
277
+ if (!meta)
278
+ return;
279
+ const safeState = coerceFlowState({ ...state }, state.activeRunId);
280
+ await ensureRunMetadata(projectRoot, {
281
+ ...meta,
282
+ stateSnapshot: snapshotState(safeState)
283
+ });
284
+ }
285
+ async function seedArtifactsFromTemplates(targetDir) {
286
+ await ensureDir(targetDir);
287
+ for (const [fileName, content] of Object.entries(ARTIFACT_TEMPLATES)) {
288
+ await writeFileSafe(path.join(targetDir, fileName), content);
289
+ }
290
+ }
291
+ async function syncActiveArtifactsToRun(projectRoot, runId) {
292
+ const fromDir = activeArtifactsPath(projectRoot);
293
+ const toDir = runArtifactsPath(projectRoot, runId);
294
+ await ensureDir(toDir);
295
+ await clearImmediateFiles(toDir);
296
+ await copyImmediateFiles(fromDir, toDir);
297
+ }
298
+ async function loadRunArtifactsToActive(projectRoot, runId) {
299
+ const fromDir = runArtifactsPath(projectRoot, runId);
300
+ const toDir = activeArtifactsPath(projectRoot);
301
+ await ensureDir(toDir);
302
+ await clearImmediateFiles(toDir);
303
+ await copyImmediateFiles(fromDir, toDir);
304
+ }
305
+ async function createRun(projectRoot, options) {
306
+ const runId = buildRunId();
307
+ const meta = {
308
+ id: runId,
309
+ title: normalizeTitle(options?.title),
310
+ createdAt: nowIso()
311
+ };
312
+ await ensureDir(runRoot(projectRoot, runId));
313
+ await ensureRunMetadata(projectRoot, meta);
314
+ const runArtifactsDir = runArtifactsPath(projectRoot, runId);
315
+ if (options?.seedFromActiveArtifacts && (await exists(activeArtifactsPath(projectRoot)))) {
316
+ await ensureDir(runArtifactsDir);
317
+ await copyImmediateFiles(activeArtifactsPath(projectRoot), runArtifactsDir);
318
+ }
319
+ else {
320
+ await seedArtifactsFromTemplates(runArtifactsDir);
321
+ }
322
+ return meta;
323
+ }
324
+ async function ensureRunHandoff(projectRoot, runId) {
325
+ const state = await readFlowState(projectRoot);
326
+ const meta = await readJsonFile(runMetaPath(projectRoot, runId));
327
+ if (!meta)
328
+ return;
329
+ await writeFileSafe(runHandoffPath(projectRoot, runId), handoffMarkdown(meta, state));
330
+ }
331
+ export async function ensureRunSystem(projectRoot) {
332
+ await ensureDir(runsRoot(projectRoot));
333
+ await ensureDir(activeArtifactsPath(projectRoot));
334
+ let state = await readFlowState(projectRoot);
335
+ let activeRunId = state.activeRunId;
336
+ const activeRunExists = activeRunId.trim().length > 0 && (await exists(runArtifactsPath(projectRoot, activeRunId)));
337
+ if (!activeRunExists) {
338
+ const activeHasArtifacts = (await listImmediateFiles(activeArtifactsPath(projectRoot))).length > 0;
339
+ const initialRun = await createRun(projectRoot, {
340
+ title: activeHasArtifacts ? "Migrated active run" : "Initial feature run",
341
+ seedFromActiveArtifacts: activeHasArtifacts
342
+ });
343
+ activeRunId = initialRun.id;
344
+ state = { ...state, activeRunId };
345
+ await writeFlowState(projectRoot, state);
346
+ }
347
+ const runArtifactsDir = runArtifactsPath(projectRoot, activeRunId);
348
+ if ((await listImmediateFiles(runArtifactsDir)).length === 0) {
349
+ await seedArtifactsFromTemplates(runArtifactsDir);
350
+ }
351
+ if ((await listImmediateFiles(activeArtifactsPath(projectRoot))).length === 0) {
352
+ await loadRunArtifactsToActive(projectRoot, activeRunId);
353
+ }
354
+ else {
355
+ await syncActiveArtifactsToRun(projectRoot, activeRunId);
356
+ }
357
+ await persistRunStateSnapshot(projectRoot, activeRunId, state);
358
+ await ensureRunHandoff(projectRoot, activeRunId);
359
+ return state;
360
+ }
361
+ export async function startNewFeatureRun(projectRoot, title) {
362
+ await ensureRunSystem(projectRoot);
363
+ const state = await readFlowState(projectRoot);
364
+ await syncActiveArtifactsToRun(projectRoot, state.activeRunId);
365
+ await persistRunStateSnapshot(projectRoot, state.activeRunId, state);
366
+ await ensureRunHandoff(projectRoot, state.activeRunId);
367
+ const nextRun = await createRun(projectRoot, {
368
+ title,
369
+ seedFromActiveArtifacts: false
370
+ });
371
+ const nextState = {
372
+ ...createInitialFlowState(nextRun.id),
373
+ activeRunId: nextRun.id
374
+ };
375
+ await writeFlowState(projectRoot, nextState);
376
+ await persistRunStateSnapshot(projectRoot, nextRun.id, nextState);
377
+ await loadRunArtifactsToActive(projectRoot, nextRun.id);
378
+ await ensureRunHandoff(projectRoot, nextRun.id);
379
+ return nextRun;
380
+ }
381
+ export async function resumeRun(projectRoot, runId) {
382
+ await ensureRunSystem(projectRoot);
383
+ const safeRunId = requireSafeRunId(runId);
384
+ const targetMeta = await readJsonFile(runMetaPath(projectRoot, safeRunId));
385
+ if (!targetMeta) {
386
+ throw new Error(`Run "${safeRunId}" not found under ${RUNTIME_ROOT}/runs/`);
387
+ }
388
+ const state = await readFlowState(projectRoot);
389
+ await syncActiveArtifactsToRun(projectRoot, state.activeRunId);
390
+ await persistRunStateSnapshot(projectRoot, state.activeRunId, state);
391
+ await ensureRunHandoff(projectRoot, state.activeRunId);
392
+ const nextState = targetMeta.stateSnapshot
393
+ ? coerceFlowState({
394
+ ...createInitialFlowState(safeRunId),
395
+ ...targetMeta.stateSnapshot,
396
+ activeRunId: safeRunId
397
+ }, safeRunId)
398
+ : coerceFlowState({
399
+ ...createInitialFlowState(safeRunId),
400
+ activeRunId: safeRunId
401
+ }, safeRunId);
402
+ await writeFlowState(projectRoot, nextState);
403
+ await persistRunStateSnapshot(projectRoot, safeRunId, nextState);
404
+ await loadRunArtifactsToActive(projectRoot, safeRunId);
405
+ await ensureRunHandoff(projectRoot, safeRunId);
406
+ return targetMeta;
407
+ }
408
+ export async function archiveRun(projectRoot, runId) {
409
+ await ensureRunSystem(projectRoot);
410
+ const state = await readFlowState(projectRoot);
411
+ const targetRunId = runId ? requireSafeRunId(runId) : state.activeRunId;
412
+ const targetMeta = await readJsonFile(runMetaPath(projectRoot, targetRunId));
413
+ if (!targetMeta) {
414
+ throw new Error(`Run "${targetRunId}" not found under ${RUNTIME_ROOT}/runs/`);
415
+ }
416
+ if (targetRunId === state.activeRunId) {
417
+ await syncActiveArtifactsToRun(projectRoot, targetRunId);
418
+ await persistRunStateSnapshot(projectRoot, targetRunId, state);
419
+ }
420
+ const archivedMeta = {
421
+ ...targetMeta,
422
+ archivedAt: nowIso()
423
+ };
424
+ await ensureRunMetadata(projectRoot, archivedMeta);
425
+ await ensureRunHandoff(projectRoot, targetRunId);
426
+ if (targetRunId !== state.activeRunId) {
427
+ const activeMeta = await readJsonFile(runMetaPath(projectRoot, state.activeRunId));
428
+ if (!activeMeta) {
429
+ throw new Error(`Active run "${state.activeRunId}" is missing metadata`);
430
+ }
431
+ return { archived: archivedMeta, active: activeMeta };
432
+ }
433
+ const nextRun = await createRun(projectRoot, {
434
+ title: "Post-archive run",
435
+ seedFromActiveArtifacts: false
436
+ });
437
+ const nextState = {
438
+ ...createInitialFlowState(nextRun.id),
439
+ activeRunId: nextRun.id
440
+ };
441
+ await writeFlowState(projectRoot, nextState);
442
+ await persistRunStateSnapshot(projectRoot, nextRun.id, nextState);
443
+ await loadRunArtifactsToActive(projectRoot, nextRun.id);
444
+ await ensureRunHandoff(projectRoot, nextRun.id);
445
+ return { archived: archivedMeta, active: nextRun };
446
+ }
@@ -0,0 +1,19 @@
1
+ export declare const FLOW_STAGES: readonly ["brainstorm", "scope", "design", "spec", "plan", "test", "build", "review", "ship"];
2
+ export type FlowStage = (typeof FLOW_STAGES)[number];
3
+ export declare const HARNESS_IDS: readonly ["claude", "cursor", "opencode", "codex"];
4
+ export type HarnessId = (typeof HARNESS_IDS)[number];
5
+ export interface CclawConfig {
6
+ version: string;
7
+ flowVersion: string;
8
+ harnesses: HarnessId[];
9
+ }
10
+ export interface TransitionRule {
11
+ from: FlowStage;
12
+ to: FlowStage;
13
+ guards: string[];
14
+ }
15
+ export interface CliContext {
16
+ cwd: string;
17
+ stdout: NodeJS.WriteStream;
18
+ stderr: NodeJS.WriteStream;
19
+ }
package/dist/types.js ADDED
@@ -0,0 +1,12 @@
1
+ export const FLOW_STAGES = [
2
+ "brainstorm",
3
+ "scope",
4
+ "design",
5
+ "spec",
6
+ "plan",
7
+ "test",
8
+ "build",
9
+ "review",
10
+ "ship"
11
+ ];
12
+ export const HARNESS_IDS = ["claude", "cursor", "opencode", "codex"];
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "cclaw-cli",
3
+ "version": "0.1.0",
4
+ "description": "Installer-first flow toolkit for coding agents",
5
+ "type": "module",
6
+ "bin": {
7
+ "cclaw": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "scripts": {
18
+ "clean:dist": "node -e \"import('node:fs/promises').then((fs) => fs.rm('dist', { recursive: true, force: true }))\"",
19
+ "build": "npm run clean:dist && tsc -p tsconfig.json",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
22
+ "smoke:runtime": "npm run build && node scripts/smoke-init.mjs",
23
+ "lint:hooks": "npm run build && node scripts/lint-generated-hooks.mjs",
24
+ "build:plugin-manifests": "npm run build && node scripts/build-plugin-manifests.mjs",
25
+ "release:check": "npm run build && npm run test && node scripts/lint-generated-hooks.mjs && node scripts/build-plugin-manifests.mjs && npm pack --dry-run && node scripts/smoke-init.mjs",
26
+ "release:bundle": "npm run release:check && npm pack"
27
+ },
28
+ "keywords": [
29
+ "cli",
30
+ "agent",
31
+ "workflow",
32
+ "tdd",
33
+ "npx"
34
+ ],
35
+ "license": "MIT",
36
+ "engines": {
37
+ "node": ">=20.0.0"
38
+ },
39
+ "dependencies": {
40
+ "yaml": "^2.8.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^24.7.2",
44
+ "typescript": "^5.9.3",
45
+ "vitest": "^3.2.4"
46
+ }
47
+ }