cclaw-cli 0.5.4 → 0.5.5

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,69 @@
1
1
  const STAGE_EXAMPLES = {
2
- brainstorm: `### Clarification sequence (Socratic)
2
+ brainstorm: `### Route selection
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
+ - **Route:** Complex Route
5
+ - **Why:** touches CI behavior, release checks, and CLI flow across multiple components.
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
+ ### Round 1 grounding
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
+ - **Grounding summary:** "We are improving release reliability for the platform team. Success means invalid release preconditions are caught before publish with explicit operator feedback."
10
+ - **User confirmation:** confirmed.
12
11
 
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."
12
+ ### Round 2 forcing questions (boundaries/constraints)
15
13
 
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."
14
+ **Q1:** "If release metadata is invalid, do we block publishing hard or only warn?"
15
+ **A1:** "Block hard."
18
16
 
19
- ### One-question-per-message discipline
17
+ **Decision impact:** release checks become mandatory gates with no warning-only fallback.
20
18
 
21
- **Bad (bundled):** "Where will this run? Which Python version? Stdlib only? Any perf limits?"
19
+ **Q2:** "Do we allow new runtime dependencies for speed, or keep runtime dependency set unchanged?"
20
+ **A2:** "Keep runtime dependencies unchanged."
22
21
 
23
- **Good (single ask):** "Where will this run in practice: local only, CI only, or both?"
22
+ **Decision impact:** implementation should reuse existing tooling and built-in capabilities.
24
23
 
25
- **Then next turn:** "Which runtime version should we lock as minimum?"
24
+ ### Grounding checkpoint after Round 2
26
25
 
27
- **Then next turn:** "Should we treat 'stdlib-only' as a hard dependency constraint?"
26
+ - **Fixed:** hard-block behavior, no new runtime dependencies.
27
+ - **Unknown:** rollback command details and status reporting format.
28
28
 
29
- ### Premise challenge and boundary stress-test
29
+ ### Round 3 forcing questions (trade-offs)
30
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?"
31
+ **Q3:** "For v1, prioritize rapid delivery or maximum configurability?"
32
+ **A3:** "Rapid delivery."
32
33
 
33
- **Boundary stress-test:** "When a write fails midway (permissions or partial path issues), what outcome should NEVER happen?"
34
+ **Decision impact:** choose a minimal deterministic validation surface; defer advanced configuration.
34
35
 
35
- ### Alternatives comparison
36
+ ### Options comparison
36
37
 
37
- | Approach | Pros | Cons | Effort | Recommendation |
38
+ | Option | Pros | Cons | Effort | Recommendation |
38
39
  | --- | --- | --- | --- | --- |
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 |
40
+ | Script-only checks | Lowest implementation cost | Weak reuse and long-term maintainability | Low | Useful fallback, not preferred |
41
+ | Reusable validation module | Reusable across CI and local runs | Slightly higher upfront design effort | Medium | **Recommended** |
42
+ | Full release framework rewrite | Highest long-term flexibility | High risk and delivery delay | High | Not recommended for v1 |
42
43
 
43
44
  ### Approved Direction
44
45
 
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).
46
+ We will implement a **reusable release validation module** used by CI and local tooling, aligned with hard-block safety and no-new-runtime-dependency constraints.
46
47
 
47
48
  ### Open Questions
48
49
 
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?
50
+ - What exact rollback command sequence should be documented for failed publish attempts?
51
+ - Should status output include machine-readable JSON in addition to markdown?
52
52
 
53
53
  ### Assumptions (explicit)
54
54
 
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.
55
+ - CI remains the primary execution path; local flow mirrors CI checks.
56
+ - Existing release metadata files remain the source of truth.
57
+ - v1 prioritizes deterministic behavior over broad customization.
58
58
 
59
59
  ### Constraints
60
60
 
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.
61
+ - No additional runtime dependencies in validation path.
62
+ - Validation overhead should remain acceptable for routine CI execution.
63
63
 
64
64
  ### Notes for the next stage
65
65
 
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.`,
66
+ Carry fixed trade-off decisions directly into scope in/out boundaries and deferred list.`,
67
67
  scope: `### Scope contract
68
68
 
69
69
  **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;