cclaw-cli 0.5.4 → 0.5.6

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/README.md CHANGED
@@ -27,10 +27,10 @@ sequenceDiagram
27
27
  participant U as User
28
28
  participant H as Harness
29
29
  participant V as cclaw Hooks + Skills
30
- participant S as State + Learnings
30
+ participant S as State + Knowledge
31
31
  U->>H: /cc <idea>
32
32
  H->>V: Load stage contract + HARD-GATE
33
- V->>S: Read context (state/learnings)
33
+ V->>S: Read context (state/knowledge)
34
34
  V-->>H: Structured execution guidance
35
35
  H->>S: Write artifacts + checkpoint
36
36
  S-->>U: Next stage is explicit
@@ -42,8 +42,8 @@ sequenceDiagram
42
42
  - **Installer-first architecture:** generates files and hooks; does not run a hidden control plane.
43
43
  - **Hard-gated quality:** each stage has non-skippable constraints that reduce AI drift.
44
44
  - **Cross-harness parity:** same behavior model across Claude Code, Cursor, Codex, OpenCode.
45
- - **Compounding context:** flow state + learnings get rehydrated on new sessions automatically.
46
- - **Run-scoped execution:** each flow works against an active run with explicit handoff state.
45
+ - **Compounding context:** flow state + project knowledge get rehydrated on new sessions automatically.
46
+ - **Incremental delivery:** active artifacts stay in one place; `cclaw archive` snapshots completed features into dated run folders.
47
47
 
48
48
  ## Compared To Top References
49
49
 
@@ -73,6 +73,7 @@ Core installer lifecycle:
73
73
  ```bash
74
74
  npx cclaw-cli sync
75
75
  npx cclaw-cli doctor
76
+ npx cclaw-cli archive --name <feature-name>
76
77
  npx cclaw-cli upgrade
77
78
  npx cclaw-cli uninstall
78
79
  ```
@@ -113,14 +114,10 @@ Required repository secret:
113
114
  ├── commands/
114
115
  ├── hooks/
115
116
  ├── templates/
116
- ├── artifacts/ # active mirror for current run (fallback path)
117
+ ├── artifacts/ # active feature artifacts
117
118
  ├── state/
118
- ├── runs/
119
- └── <activeRunId>/
120
- │ ├── artifacts/ # canonical run artifacts
121
- │ ├── run.json
122
- │ └── handoff.md
123
- └── learnings.jsonl
119
+ ├── knowledge.md # append-only rule/pattern/lesson log
120
+ └── runs/ # archived feature snapshots (YYYY-MM-DD-feature-name)
124
121
  ```
125
122
 
126
123
  ## License
@@ -3,20 +3,10 @@ import path from "node:path";
3
3
  import { RUNTIME_ROOT } from "./constants.js";
4
4
  import { exists } from "./fs-utils.js";
5
5
  import { orderedStageSchemas, stageSchema } from "./content/stage-schema.js";
6
- import { readFlowState } from "./runs.js";
7
6
  async function resolveArtifactPath(projectRoot, fileName) {
8
- const fallbackRelPath = path.join(RUNTIME_ROOT, "artifacts", fileName);
9
- const fallbackAbsPath = path.join(projectRoot, fallbackRelPath);
10
- const { activeRunId } = await readFlowState(projectRoot);
11
- const runId = activeRunId.trim();
12
- if (runId.length > 0) {
13
- const canonicalRelPath = path.join(RUNTIME_ROOT, "runs", runId, "artifacts", fileName);
14
- const canonicalAbsPath = path.join(projectRoot, canonicalRelPath);
15
- if (await exists(canonicalAbsPath)) {
16
- return { absPath: canonicalAbsPath, relPath: canonicalRelPath };
17
- }
18
- }
19
- return { absPath: fallbackAbsPath, relPath: fallbackRelPath };
7
+ const relPath = path.join(RUNTIME_ROOT, "artifacts", fileName);
8
+ const absPath = path.join(projectRoot, relPath);
9
+ return { absPath, relPath };
20
10
  }
21
11
  function normalizeHeadingTitle(title) {
22
12
  return title.trim().replace(/\s+/g, " ");
package/dist/cli.d.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import type { HarnessId } from "./types.js";
3
- type CommandName = "init" | "sync" | "doctor" | "upgrade" | "uninstall";
3
+ type CommandName = "init" | "sync" | "doctor" | "upgrade" | "uninstall" | "archive";
4
4
  interface ParsedArgs {
5
5
  command?: CommandName;
6
6
  harnesses?: HarnessId[];
7
7
  reconcileGates?: boolean;
8
+ archiveName?: string;
8
9
  }
9
10
  declare function parseHarnesses(raw: string): HarnessId[];
10
11
  declare function parseArgs(argv: string[]): ParsedArgs;
package/dist/cli.js CHANGED
@@ -7,7 +7,8 @@ import { HARNESS_IDS } from "./types.js";
7
7
  import { doctorChecks, doctorSucceeded } from "./doctor.js";
8
8
  import { initCclaw, syncCclaw, uninstallCclaw, upgradeCclaw } from "./install.js";
9
9
  import { error, info } from "./logger.js";
10
- const INSTALLER_COMMANDS = ["init", "sync", "doctor", "upgrade", "uninstall"];
10
+ import { archiveRun } from "./runs.js";
11
+ const INSTALLER_COMMANDS = ["init", "sync", "doctor", "upgrade", "uninstall", "archive"];
11
12
  function usage() {
12
13
  return `cclaw - installer-first flow toolkit
13
14
 
@@ -15,6 +16,7 @@ Usage:
15
16
  cclaw init [--harnesses=claude,cursor,opencode,codex]
16
17
  cclaw sync
17
18
  cclaw doctor [--reconcile-gates]
19
+ cclaw archive [--name=feature-name]
18
20
  cclaw upgrade
19
21
  cclaw uninstall
20
22
  `;
@@ -43,6 +45,10 @@ function parseArgs(argv) {
43
45
  }
44
46
  if (flag === "--reconcile-gates") {
45
47
  parsed.reconcileGates = true;
48
+ continue;
49
+ }
50
+ if (flag.startsWith("--name=")) {
51
+ parsed.archiveName = flag.replace("--name=", "").trim();
46
52
  }
47
53
  }
48
54
  return parsed;
@@ -80,6 +86,11 @@ async function runCommand(parsed, ctx) {
80
86
  info(ctx, "Upgraded .cclaw runtime and regenerated generated files");
81
87
  return 0;
82
88
  }
89
+ if (command === "archive") {
90
+ const archived = await archiveRun(ctx.cwd, parsed.archiveName);
91
+ info(ctx, `Archived active artifacts to ${archived.archivePath}. Flow state reset to brainstorm.`);
92
+ return 0;
93
+ }
83
94
  await uninstallCclaw(ctx.cwd);
84
95
  info(ctx, "Removed .cclaw runtime and generated shim files");
85
96
  return 0;
package/dist/config.js CHANGED
@@ -12,8 +12,6 @@ const ALLOWED_CONFIG_KEYS = new Set([
12
12
  "flowVersion",
13
13
  "harnesses",
14
14
  "autoAdvance",
15
- "globalLearnings",
16
- "globalLearningsPath",
17
15
  "promptGuardMode",
18
16
  "gitHookGuards"
19
17
  ]);
@@ -37,7 +35,6 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES) {
37
35
  flowVersion: FLOW_VERSION,
38
36
  harnesses,
39
37
  autoAdvance: false,
40
- globalLearnings: false,
41
38
  promptGuardMode: "advisory",
42
39
  gitHookGuards: false
43
40
  };
@@ -79,20 +76,6 @@ export async function readConfig(projectRoot) {
79
76
  : DEFAULT_HARNESSES;
80
77
  const autoAdvanceRaw = parsed.autoAdvance;
81
78
  const autoAdvance = typeof autoAdvanceRaw === "boolean" ? autoAdvanceRaw : false;
82
- const globalLearningsRaw = parsed.globalLearnings;
83
- if (Object.prototype.hasOwnProperty.call(parsed, "globalLearnings") &&
84
- typeof globalLearningsRaw !== "boolean") {
85
- throw configValidationError(fullPath, `"globalLearnings" must be a boolean`);
86
- }
87
- const globalLearnings = typeof globalLearningsRaw === "boolean" ? globalLearningsRaw : false;
88
- const globalLearningsPathRaw = parsed.globalLearningsPath;
89
- if (Object.prototype.hasOwnProperty.call(parsed, "globalLearningsPath") &&
90
- typeof globalLearningsPathRaw !== "string") {
91
- throw configValidationError(fullPath, `"globalLearningsPath" must be a string`);
92
- }
93
- const globalLearningsPath = typeof globalLearningsPathRaw === "string" && globalLearningsPathRaw.trim().length > 0
94
- ? globalLearningsPathRaw.trim()
95
- : undefined;
96
79
  const promptGuardModeRaw = parsed.promptGuardMode;
97
80
  if (Object.prototype.hasOwnProperty.call(parsed, "promptGuardMode") &&
98
81
  promptGuardModeRaw !== "advisory" &&
@@ -111,8 +94,6 @@ export async function readConfig(projectRoot) {
111
94
  flowVersion: parsed.flowVersion ?? FLOW_VERSION,
112
95
  harnesses,
113
96
  autoAdvance,
114
- globalLearnings,
115
- globalLearningsPath,
116
97
  promptGuardMode,
117
98
  gitHookGuards
118
99
  };
@@ -6,13 +6,7 @@ export function commandContract(stage) {
6
6
  const reads = schema.crossStageTrace.readsFrom;
7
7
  const readsLine = reads.length > 0 ? reads.join(", ") : "(first stage)";
8
8
  const hydrationLines = reads.length > 0
9
- ? reads
10
- .map((readPath) => {
11
- const parts = readPath.split("/");
12
- const fileName = parts[parts.length - 1] ?? readPath;
13
- return `- Canonical: \`.cclaw/runs/<activeRunId>/artifacts/${fileName}\` (fallback: \`${readPath}\`)`;
14
- })
15
- .join("\n")
9
+ ? reads.map((readPath) => `- \`${readPath}\``).join("\n")
16
10
  : "- (first stage — no upstream artifacts)";
17
11
  const gateIds = schema.requiredGates
18
12
  .map((g) => `\`${g.id}\``)
@@ -26,16 +20,17 @@ ${schema.hardGate}
26
20
 
27
21
  ## In / Out
28
22
  - **Reads:** ${readsLine}
29
- - **Writes:** \`.cclaw/artifacts/${schema.artifactFile}\` (run snapshot under \`.cclaw/runs/<activeRunId>/artifacts/${schema.artifactFile}\` is synchronized by cclaw runtime)
23
+ - **Writes:** \`.cclaw/artifacts/${schema.artifactFile}\`
30
24
  - **Next:** \`/cc-next\` (updates flow-state and loads the next stage)
31
25
 
32
26
  ## Context Hydration (mandatory before stage work)
33
- 1. Read \`.cclaw/state/flow-state.json\` and capture \`activeRunId\`.
34
- 2. Resolve canonical artifact root: \`.cclaw/runs/<activeRunId>/artifacts/\`.
27
+ 1. Read \`.cclaw/state/flow-state.json\`.
28
+ 2. Resolve active artifact root: \`.cclaw/artifacts/\`.
35
29
  3. Load required upstream artifacts for this stage:
36
30
  ${hydrationLines}
37
- 4. If a canonical run artifact is missing, fallback to the matching file under \`.cclaw/artifacts/\` and record that fallback in the stage artifact.
38
- 5. Write stage output to \`.cclaw/artifacts/${schema.artifactFile}\`. Do NOT manually copy into run directories; cclaw sync/runtime keeps run snapshots aligned.
31
+ 4. Load \`.cclaw/knowledge.md\` and apply relevant entries.
32
+ 5. Write stage output to \`.cclaw/artifacts/${schema.artifactFile}\`.
33
+ 6. Do NOT copy artifacts into \`.cclaw/runs/\`; archival is handled only by \`cclaw archive\`.
39
34
 
40
35
  ## Gates
41
36
  ${gateIds}
@@ -1,69 +1,51 @@
1
1
  const STAGE_EXAMPLES = {
2
- brainstorm: `### Clarification sequence (Socratic)
2
+ brainstorm: `### Context
3
3
 
4
- **Q1 (PURPOSE):** "What decision will this demo help your audience make, and who exactly is that audience?"
5
- **A1:** "Internal platform team; they should see whether our release flow can be trusted."
4
+ - **Project state:** Monorepo with CI pipeline using custom release scripts. Release checks are scattered across shell scripts with no shared validation logic.
5
+ - **Relevant existing code/patterns:** \`scripts/pre-publish.sh\` does metadata checks. \`src/release/\` has partial validation helpers.
6
6
 
7
- **Q2 (SCOPE):** "Which outcomes are explicitly OUT of scope for this demo so we avoid accidental expansion?"
8
- **A2:** "No production rollout automation; only release metadata validation + publish safety checks."
7
+ ### Problem
9
8
 
10
- **Q3 (BOUNDARIES):** "When release metadata is missing or invalid, what should happen and what should NEVER happen?"
11
- **A3:** "Must block release with a clear reason; must never auto-publish anyway."
9
+ - **What we're solving:** release checks are fragile and inconsistent between CI and local runs. Invalid metadata sometimes reaches npm publish.
10
+ - **Success criteria:** invalid release preconditions are caught before publish with explicit operator feedback, in both CI and local workflows.
11
+ - **Constraints:** no new runtime dependencies; must work within existing CI pipeline structure.
12
12
 
13
- **Q4 (ENVIRONMENT):** "Where will this run in practice: local only, CI only, or both? How is it installed and invoked?"
14
- **A4:** "CI-first in GitHub Actions, but reproducible locally with npm scripts."
13
+ ### Clarifying Questions
15
14
 
16
- **Q5 (CONSTRAINTS):** "What constraints are non-negotiable (runtime deps, latency, compatibility, policy)?"
17
- **A5:** "No new runtime dependencies; checks should stay under 2 minutes; compatible with current workflow."
18
-
19
- ### One-question-per-message discipline
20
-
21
- **Bad (bundled):** "Where will this run? Which Python version? Stdlib only? Any perf limits?"
22
-
23
- **Good (single ask):** "Where will this run in practice: local only, CI only, or both?"
24
-
25
- **Then next turn:** "Which runtime version should we lock as minimum?"
26
-
27
- **Then next turn:** "Should we treat 'stdlib-only' as a hard dependency constraint?"
28
-
29
- ### Premise challenge and boundary stress-test
30
-
31
- **Premise challenge:** "If the goal is only to demonstrate filesystem I/O, why is a CLI better than a tiny script or notebook for this demo?"
32
-
33
- **Boundary stress-test:** "When a write fails midway (permissions or partial path issues), what outcome should NEVER happen?"
34
-
35
- ### Alternatives comparison
36
-
37
- | Approach | Pros | Cons | Effort | Recommendation |
38
- | --- | --- | --- | --- | --- |
39
- | REST + polling | Simple to deploy; works through strict proxies; easy to cache | Higher latency; wasted requests; battery use on mobile | Low | Good default when real-time is not required |
40
- | WebSocket | Lowest latency; bidirectional; efficient for bursts | More ops complexity; reconnect/state sync; harder through some gateways | Medium | Prefer when server must push frequently or conversationally |
41
- | SSE | One-way server push over HTTP; simpler than WebSockets for “notify only” | Unidirectional; browser connection limits; proxy buffering quirks | Low–Medium | Prefer for live dashboards and one-way event streams |
15
+ | # | Question | Answer | Decision impact |
16
+ | --- | --- | --- | --- |
17
+ | 1 | If release metadata is invalid, should we block publishing hard or only warn? | Block hard. | Validation becomes a mandatory gate — no warning-only fallback. |
18
+ | 2 | Should the validation logic live in a reusable module or stay as shell scripts? | Reusable module. | Architecture: shared TypeScript module imported by CI and local tooling, not duplicated shell scripts. |
19
+ | 3 | For v1, prioritize rapid delivery or maximum configurability? | Rapid delivery. | Minimal deterministic validation surface; defer plugin/config system to v2. |
42
20
 
43
- ### Approved Direction
21
+ ### Approaches
44
22
 
45
- We will ship **SSE for server-originated notifications** to the web client first, because the UX requires timely delivery without bidirectional chat. We will keep a **REST fallback** for environments where SSE is unreliable, and we will defer WebSockets until we have a concrete bidirectional requirement (collaborative editing).
23
+ | Approach | Architecture | Trade-offs | Recommendation |
24
+ | --- | --- | --- | --- |
25
+ | A: Reusable validation module | Shared TS module with typed validators, imported by CI scripts and local CLI. Existing \`pre-publish.sh\` calls the module. | Medium upfront effort, high reuse. Requires test coverage for the module. | **Recommended** — best balance of reuse and delivery speed. |
26
+ | B: Hardened shell scripts | Keep existing script approach, add stricter checks and error messages. | Lowest effort. Weak reuse, CI/local divergence risk grows over time. | Viable fallback if TS module is blocked. |
27
+ | C: Full release framework | New release orchestrator with plugin system, config files, rollback commands. | Maximum flexibility. High risk, delivery delay, over-engineered for current needs. | Not recommended for v1. |
46
28
 
47
- ### Open Questions
29
+ ### Selected Direction
48
30
 
49
- - Do we need guaranteed delivery semantics (at-least-once vs best-effort) for in-app banners?
50
- - What is the maximum event rate we must support per user session without degrading the UI thread?
51
- - Should mobile clients reuse the same event channel contract or expose a separate polling endpoint?
31
+ - **Approach:** A Reusable validation module
32
+ - **Rationale:** shared TS module gives consistent behavior in CI and local, avoids script duplication, and stays within the no-new-dependency constraint.
33
+ - **Approval:** approved
52
34
 
53
- ### Assumptions (explicit)
35
+ ### Design
54
36
 
55
- - Users are authenticated; anonymous traffic does not receive personalized streams.
56
- - “Notification” means **account-scoped operational messages**, not third-party ads.
57
- - The first release optimizes for **web**; native clients can lag by one sprint.
37
+ - **Architecture:** single \`release-validator\` module in \`src/release/\` exporting typed check functions. CI script and local CLI both import and run the same checks.
38
+ - **Key components:** \`validateMetadata()\`, \`validateChangelog()\`, \`validateVersion()\` each returns a typed result with error details. A \`runAll()\` orchestrator runs checks and exits non-zero on any failure.
39
+ - **Data flow:** package.json + CHANGELOG.md validator module structured result CI/CLI renders human-readable report.
58
40
 
59
- ### Constraints
41
+ ### Assumptions and Open Questions
60
42
 
61
- - No new infrastructure class (managed broker) unless latency goals are missed in a spike.
62
- - Compliance requires auditability: who saw what, when at least at coarse granularity.
43
+ - **Assumptions:** CI remains the primary execution path; existing release metadata files remain the source of truth; v1 prioritizes determinism over customization.
44
+ - **Open questions:** What exact rollback sequence for failed publish? Should status output include machine-readable JSON alongside markdown?
63
45
 
64
46
  ### Notes for the next stage
65
47
 
66
- Capture the chosen direction as a **single paragraph decision** (above) and ensure open questions are either answered in scope or explicitly deferred with an owner + date.`,
48
+ Carry the no-new-dependency constraint and hard-block behavior directly into scope in/out boundaries.`,
67
49
  scope: `### Scope contract
68
50
 
69
51
  **Mode selected:** SELECTIVE EXPANSION
@@ -1,20 +1,18 @@
1
1
  /**
2
2
  * Hook generators for all supported harnesses.
3
3
  *
4
- * SessionStart: injects using-cclaw + flow state + learnings + checkpoint/activity summary.
4
+ * SessionStart: injects using-cclaw + flow state + knowledge + checkpoint/activity summary.
5
5
  * Stop: writes checkpoint.json and reminds about flow consistency.
6
- * PreToolUse/PostToolUse and summarize chain are generated in observe.ts.
6
+ * Harness hook JSON wiring is generated in observe.ts.
7
7
  */
8
8
  export interface HookRuntimeOptions {
9
- globalLearningsEnabled?: boolean;
10
- globalLearningsPath?: string;
11
9
  }
12
10
  /** Shared bash preamble for generated hook scripts. */
13
11
  export declare const RUNTIME_SHELL_DETECT_ROOT = "HARNESS=\"codex\"\nif [ -n \"${CLAUDE_PROJECT_DIR:-}\" ]; then\n HARNESS=\"claude\"\nelif [ -n \"${CURSOR_PROJECT_DIR:-}\" ] || [ -n \"${CURSOR_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"cursor\"\nelif [ -n \"${OPENCODE_PROJECT_DIR:-}\" ] || [ -n \"${OPENCODE_PROJECT_ROOT:-}\" ]; then\n HARNESS=\"opencode\"\nfi\n\nROOT=\"\"\nfor candidate in \"${CCLAW_PROJECT_ROOT:-}\" \"${CLAUDE_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_DIR:-}\" \"${CURSOR_PROJECT_ROOT:-}\" \"${OPENCODE_PROJECT_DIR:-}\" \"${OPENCODE_PROJECT_ROOT:-}\" \"${PWD:-}\"; do\n if [ -n \"$candidate\" ] && [ -d \"$candidate/.cclaw\" ]; then\n ROOT=\"$candidate\"\n break\n fi\ndone\nif [ -z \"$ROOT\" ]; then\n ROOT=\"${CCLAW_PROJECT_ROOT:-${CLAUDE_PROJECT_DIR:-${CURSOR_PROJECT_DIR:-${CURSOR_PROJECT_ROOT:-${OPENCODE_PROJECT_DIR:-${OPENCODE_PROJECT_ROOT:-${PWD}}}}}}}\"\nfi";
14
- export declare function sessionStartScript(options?: HookRuntimeOptions): string;
12
+ export declare function sessionStartScript(_options?: HookRuntimeOptions): string;
15
13
  export declare function stopCheckpointScript(): string;
16
14
  export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
17
15
  export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
18
16
  export { codexHooksJsonWithObservation as codexHooksJson } from "./observe.js";
19
- export declare function opencodePluginJs(options?: HookRuntimeOptions): string;
17
+ export declare function opencodePluginJs(_options?: HookRuntimeOptions): string;
20
18
  export declare function hooksAgentsMdBlock(): string;