cclaw-cli 0.15.1 → 0.18.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.
@@ -188,6 +188,66 @@ function lineContainsVagueAdjective(text) {
188
188
  }
189
189
  return null;
190
190
  }
191
+ const FRONTMATTER_REQUIRED_KEYS = [
192
+ "stage",
193
+ "schema_version",
194
+ "version",
195
+ "feature",
196
+ "locked_decisions",
197
+ "inputs_hash"
198
+ ];
199
+ const PLACEHOLDER_PATTERNS = [
200
+ { label: "TODO", regex: /\bTODO\b/iu },
201
+ { label: "TBD", regex: /\bTBD\b/iu },
202
+ { label: "FIXME", regex: /\bFIXME\b/iu },
203
+ { label: "<fill-in>", regex: /<fill-in>/iu },
204
+ { label: "<your-*-here>", regex: /<your-[^>]*-here>/iu },
205
+ { label: "xxx", regex: /\bxxx\b/iu },
206
+ { label: "ellipsis", regex: /\.{3}/u }
207
+ ];
208
+ const SCOPE_REDUCTION_PATTERNS = [
209
+ { label: "v1", regex: /\bv1\b/iu },
210
+ { label: "for now", regex: /\bfor now\b/iu },
211
+ { label: "later", regex: /\blater\b/iu },
212
+ { label: "temporary", regex: /\btemporary\b/iu },
213
+ { label: "placeholder", regex: /\bplaceholder\b/iu },
214
+ { label: "mock for now", regex: /\bmock for now\b/iu },
215
+ { label: "hardcoded for now", regex: /\bhardcoded for now\b/iu },
216
+ { label: "will improve later", regex: /\bwill improve later\b/iu }
217
+ ];
218
+ function parseFrontmatter(markdown) {
219
+ const lines = markdown.split(/\r?\n/);
220
+ if (lines[0]?.trim() !== "---") {
221
+ return { hasFrontmatter: false, values: {} };
222
+ }
223
+ const endIndex = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
224
+ if (endIndex < 0) {
225
+ return { hasFrontmatter: false, values: {} };
226
+ }
227
+ const values = {};
228
+ for (const line of lines.slice(1, endIndex)) {
229
+ const match = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/u.exec(line.trim());
230
+ if (!match)
231
+ continue;
232
+ const key = match[1];
233
+ const value = match[2].trim();
234
+ values[key] = value;
235
+ }
236
+ return { hasFrontmatter: true, values };
237
+ }
238
+ function extractDecisionIds(text) {
239
+ const ids = text.match(/\bD-\d+\b/gu) ?? [];
240
+ return [...new Set(ids)];
241
+ }
242
+ function collectPatternHits(text, patterns) {
243
+ const hits = [];
244
+ for (const pattern of patterns) {
245
+ if (pattern.regex.test(text)) {
246
+ hits.push(pattern.label);
247
+ }
248
+ }
249
+ return hits;
250
+ }
191
251
  function validateSectionBody(sectionBody, rule, sectionName) {
192
252
  const bodyLines = sectionBody.split(/\r?\n/).map((line) => line.trim());
193
253
  const meaningful = meaningfulLineCount(sectionBody);
@@ -336,6 +396,37 @@ export async function lintArtifact(projectRoot, stage) {
336
396
  }
337
397
  const raw = await fs.readFile(absFile, "utf8");
338
398
  const sections = extractH2Sections(raw);
399
+ const parsedFrontmatter = parseFrontmatter(raw);
400
+ const frontmatterMissingKeys = FRONTMATTER_REQUIRED_KEYS.filter((key) => {
401
+ const value = parsedFrontmatter.values[key];
402
+ return typeof value !== "string" || value.trim().length === 0;
403
+ });
404
+ const frontmatterStage = parsedFrontmatter.values.stage?.replace(/^['"]|['"]$/gu, "");
405
+ const frontmatterSchemaVersion = parsedFrontmatter.values.schema_version?.replace(/^['"]|['"]$/gu, "");
406
+ const frontmatterInputsHash = parsedFrontmatter.values.inputs_hash?.replace(/^['"]|['"]$/gu, "");
407
+ const frontmatterValid = parsedFrontmatter.hasFrontmatter &&
408
+ frontmatterMissingKeys.length === 0 &&
409
+ frontmatterStage === stage &&
410
+ frontmatterSchemaVersion === "1" &&
411
+ /^sha256:(?:pending|[a-f0-9]{64})$/iu.test(frontmatterInputsHash ?? "");
412
+ const requireFrontmatter = parsedFrontmatter.hasFrontmatter;
413
+ findings.push({
414
+ section: "Frontmatter",
415
+ required: requireFrontmatter,
416
+ rule: "Artifact must include frontmatter keys (stage, schema_version=1, version, feature, locked_decisions, inputs_hash=sha256:pending|sha256:<64hex>).",
417
+ found: parsedFrontmatter.hasFrontmatter ? frontmatterValid : true,
418
+ details: !parsedFrontmatter.hasFrontmatter
419
+ ? "Legacy artifact without YAML frontmatter (allowed for backward compatibility)."
420
+ : frontmatterMissingKeys.length > 0
421
+ ? `Frontmatter missing required key(s): ${frontmatterMissingKeys.join(", ")}.`
422
+ : frontmatterStage !== stage
423
+ ? `Frontmatter stage must be "${stage}" (found "${frontmatterStage ?? "(missing)"}").`
424
+ : frontmatterSchemaVersion !== "1"
425
+ ? `Frontmatter schema_version must be "1" (found "${frontmatterSchemaVersion ?? "(missing)"}").`
426
+ : !/^sha256:(?:pending|[a-f0-9]{64})$/iu.test(frontmatterInputsHash ?? "")
427
+ ? "Frontmatter inputs_hash must be sha256:pending or sha256:<64 hex chars>."
428
+ : "Frontmatter integrity checks passed."
429
+ });
339
430
  const isTrivialOverride = schema.trivialOverrideSections &&
340
431
  schema.trivialOverrideSections.length > 0 &&
341
432
  /trivial.change|mini.design|escape.hatch/iu.test(raw);
@@ -362,6 +453,69 @@ export async function lintArtifact(projectRoot, stage) {
362
453
  : validation.details
363
454
  });
364
455
  }
456
+ if (stage === "plan") {
457
+ const strictPlanGuards = parsedFrontmatter.hasFrontmatter ||
458
+ headingPresent(sections, "No-Placeholder Scan") ||
459
+ headingPresent(sections, "No Scope Reduction Language Scan") ||
460
+ headingPresent(sections, "Locked Decision Coverage");
461
+ const taskListBody = sectionBodyByName(sections, "Task List") ?? raw;
462
+ const placeholderHits = collectPatternHits(taskListBody, PLACEHOLDER_PATTERNS);
463
+ findings.push({
464
+ section: "No Placeholder Enforcement",
465
+ required: strictPlanGuards,
466
+ rule: "Task List must not contain placeholders (TODO/TBD/FIXME/<fill-in>/<your-*-here>/xxx/ellipsis).",
467
+ found: placeholderHits.length === 0,
468
+ details: placeholderHits.length === 0
469
+ ? "No placeholder tokens detected in Task List."
470
+ : `Detected placeholder token(s) in Task List: ${placeholderHits.join(", ")}.`
471
+ });
472
+ const scopePath = path.join(projectRoot, RUNTIME_ROOT, "artifacts", "02-scope.md");
473
+ const scopeRaw = (await exists(scopePath)) ? await fs.readFile(scopePath, "utf8") : "";
474
+ const scopeDecisionIds = extractDecisionIds(scopeRaw);
475
+ const missingDecisionRefs = scopeDecisionIds.filter((id) => !raw.includes(id));
476
+ findings.push({
477
+ section: "Locked Decision Traceability",
478
+ required: strictPlanGuards && scopeDecisionIds.length > 0,
479
+ rule: "Every locked decision ID (D-XX) in scope must be referenced in plan.",
480
+ found: missingDecisionRefs.length === 0,
481
+ details: scopeDecisionIds.length === 0
482
+ ? "No D-XX IDs found in scope artifact; traceability check skipped."
483
+ : missingDecisionRefs.length === 0
484
+ ? `All ${scopeDecisionIds.length} scope decision IDs are referenced in plan.`
485
+ : `Missing scope decision reference(s) in plan: ${missingDecisionRefs.join(", ")}.`
486
+ });
487
+ const reductionHits = collectPatternHits(taskListBody, SCOPE_REDUCTION_PATTERNS);
488
+ findings.push({
489
+ section: "No Scope Reduction Language",
490
+ required: strictPlanGuards && scopeDecisionIds.length > 0,
491
+ rule: "Task List must not include scope-reduction language when locked decisions exist.",
492
+ found: reductionHits.length === 0,
493
+ details: scopeDecisionIds.length === 0
494
+ ? "No locked decisions found in scope artifact; scope-reduction scan is advisory."
495
+ : reductionHits.length === 0
496
+ ? "No scope-reduction phrases detected in Task List."
497
+ : `Detected scope-reduction phrase(s) in Task List: ${reductionHits.join(", ")}.`
498
+ });
499
+ }
500
+ if (stage === "scope") {
501
+ const strictScopeGuards = parsedFrontmatter.hasFrontmatter ||
502
+ headingPresent(sections, "Locked Decisions (D-XX)");
503
+ const scopeSections = [
504
+ sectionBodyByName(sections, "In Scope / Out of Scope") ?? "",
505
+ sectionBodyByName(sections, "Scope Summary") ?? "",
506
+ sectionBodyByName(sections, "Locked Decisions (D-XX)") ?? ""
507
+ ].join("\n");
508
+ const reductionHits = collectPatternHits(scopeSections, SCOPE_REDUCTION_PATTERNS);
509
+ findings.push({
510
+ section: "No Scope Reduction Language",
511
+ required: strictScopeGuards,
512
+ rule: "Scope boundary sections must not use reduction placeholders (`v1`, `for now`, `later`, `temporary`, `placeholder`).",
513
+ found: reductionHits.length === 0,
514
+ details: reductionHits.length === 0
515
+ ? "No scope-reduction phrases detected in scope boundary sections."
516
+ : `Detected scope-reduction phrase(s): ${reductionHits.join(", ")}.`
517
+ });
518
+ }
365
519
  const passed = findings.every((f) => !f.required || f.found);
366
520
  return { stage, file: relFile, passed, findings };
367
521
  }
package/dist/cli.js CHANGED
@@ -122,7 +122,8 @@ function buildInitSurfacePreview(harnesses) {
122
122
  ".cclaw/rules/**",
123
123
  ".cclaw/adapters/*.md",
124
124
  ".cclaw/custom-skills/README.md",
125
- ".cclaw/features/**",
125
+ ".cclaw/worktrees/**",
126
+ ".cclaw/features/** (legacy snapshots, read-only migration)",
126
127
  ".cclaw/runs/**",
127
128
  ".cclaw/artifacts/**",
128
129
  ".cclaw/knowledge.jsonl",
@@ -4,9 +4,9 @@ export declare const RUNTIME_ROOT = ".cclaw";
4
4
  export declare const CCLAW_VERSION = "0.1.1";
5
5
  export declare const FLOW_VERSION = "1.0.0";
6
6
  export declare const DEFAULT_HARNESSES: HarnessId[];
7
- export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/features", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills"];
7
+ export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/worktrees", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills"];
8
8
  export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".codex/commands/cc-*.md", ".codex/commands/cc.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".codex/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
9
9
  export declare const COMMAND_FILE_ORDER: FlowStage[];
10
- export declare const UTILITY_COMMANDS: readonly ["learn", "next", "view", "status", "tree", "diff", "ops", "feature", "tdd-log", "retro", "archive", "rewind", "rewind-ack"];
10
+ export declare const UTILITY_COMMANDS: readonly ["learn", "next", "view", "status", "tree", "diff", "ops", "feature", "tdd-log", "retro", "archive", "rewind"];
11
11
  export declare const SUBAGENT_SKILL_FOLDERS: readonly ["subagent-dev", "parallel-dispatch"];
12
12
  export type UtilityCommand = (typeof UTILITY_COMMANDS)[number];
package/dist/constants.js CHANGED
@@ -15,7 +15,7 @@ export const REQUIRED_DIRS = [
15
15
  `${RUNTIME_ROOT}/contexts`,
16
16
  `${RUNTIME_ROOT}/templates`,
17
17
  `${RUNTIME_ROOT}/artifacts`,
18
- `${RUNTIME_ROOT}/features`,
18
+ `${RUNTIME_ROOT}/worktrees`,
19
19
  `${RUNTIME_ROOT}/state`,
20
20
  `${RUNTIME_ROOT}/runs`,
21
21
  `${RUNTIME_ROOT}/rules`,
@@ -63,8 +63,7 @@ export const UTILITY_COMMANDS = [
63
63
  "tdd-log",
64
64
  "retro",
65
65
  "archive",
66
- "rewind",
67
- "rewind-ack"
66
+ "rewind"
68
67
  ];
69
68
  export const SUBAGENT_SKILL_FOLDERS = [
70
69
  "subagent-dev",
@@ -34,7 +34,7 @@ ${schema.hardGate}
34
34
  2. Resolve active artifact root: \`.cclaw/artifacts/\`.
35
35
  3. Load required upstream artifacts for this stage:
36
36
  ${hydrationLines}
37
- 4. Stream \`.cclaw/knowledge.jsonl\` and apply relevant JSON-line entries (strict schema: type, trigger, action, confidence, domain, stage, created, project).
37
+ 4. Stream \`.cclaw/knowledge.jsonl\` and apply relevant JSON-line entries (strict schema: type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project).
38
38
  5. Write stage output to ${writeStepPaths}.
39
39
  6. Do NOT copy artifacts into \`.cclaw/runs/\`; archival is handled by \`/cc-ops archive\` (agent-facing wrapper over archive runtime).
40
40
 
@@ -11,7 +11,7 @@ Reference docs for \`cclaw doctor\` checks.
11
11
  - \`hooks-and-lifecycle.md\` - hook wiring and harness lifecycle integration
12
12
  - \`harness-and-routing.md\` - harness shims, AGENTS/CLAUDE routing blocks, cursor rule
13
13
  - \`state-and-gates.md\` - flow-state integrity and gate evidence contracts
14
- - \`delegation-and-preamble.md\` - mandatory delegations and preamble budget controls
14
+ - \`delegation-and-preamble.md\` - mandatory delegations and lightweight announce discipline
15
15
  - \`traceability.md\` - spec/plan/tdd trace matrix expectations
16
16
  - \`tooling-capabilities.md\` - local runtime prerequisites (bash/node/python/jq)
17
17
  - \`config-and-policy.md\` - config schema, rules policy, and validation references
@@ -84,18 +84,19 @@ Reference docs for \`cclaw doctor\` checks.
84
84
  - mandatory delegations for the current stage must be completed or waived
85
85
  - waivers should include an explicit reason
86
86
  - stale entries from previous runs are ignored by current-run checks
87
+ - delegation entries use span-compatible fields (\`spanId\`, \`startTs\`, \`endTs\`, \`retryCount\`, \`evidenceRefs\`)
87
88
 
88
- ## Preamble budget contract
89
+ ## Announce discipline contract
89
90
 
90
- - preamble events are logged to \`.cclaw/state/preamble-log.jsonl\`
91
- - repeated entries inside cooldown windows indicate noisy preamble behavior
92
- - in TDD wave mode, emit once per wave unless plan materially changes
91
+ - no dedicated preamble runtime log is required
92
+ - substantial turns should still start with a concise announce (stage + goal + next action)
93
+ - do not spam repeated announces when intent did not change
93
94
 
94
95
  ## Typical fixes
95
96
 
96
97
  1. Append missing delegation records with \`completed\` or \`waived\` status.
97
98
  2. Record harness-limitation waivers when native delegation is unavailable.
98
- 3. Reduce repeated preamble emissions and keep logs structured (\`ts/stage/trigger/hash\`).
99
+ 3. Keep announces concise and only refresh when plan/risk materially changes.
99
100
  `,
100
101
  "traceability.md": `# Traceability
101
102
 
@@ -1,66 +1,71 @@
1
1
  import { RUNTIME_ROOT } from "../constants.js";
2
- const FEATURE_SKILL_FOLDER = "feature-workspaces";
3
- const FEATURE_SKILL_NAME = "feature-workspaces";
2
+ const FEATURE_SKILL_FOLDER = "using-git-worktrees";
3
+ const FEATURE_SKILL_NAME = "using-git-worktrees";
4
4
  function activeFeaturePath() {
5
5
  return `${RUNTIME_ROOT}/state/active-feature.json`;
6
6
  }
7
- function featuresRoot() {
8
- return `${RUNTIME_ROOT}/features`;
7
+ function worktreeRegistryPath() {
8
+ return `${RUNTIME_ROOT}/state/worktrees.json`;
9
9
  }
10
- function runtimeArtifactsPath() {
11
- return `${RUNTIME_ROOT}/artifacts`;
10
+ function managedWorktreesRoot() {
11
+ return `${RUNTIME_ROOT}/worktrees`;
12
12
  }
13
- function runtimeStatePath() {
14
- return `${RUNTIME_ROOT}/state`;
13
+ function legacyFeaturesRoot() {
14
+ return `${RUNTIME_ROOT}/features`;
15
15
  }
16
16
  export function featureCommandContract() {
17
17
  return `# /cc-ops feature
18
18
 
19
19
  ## Purpose
20
20
 
21
- Manage multi-feature workspaces without flow-state/artifact collisions.
22
-
23
- The active runtime remains:
24
- - \`${runtimeArtifactsPath()}\` (active artifacts)
25
- - \`${runtimeStatePath()}\` (active state)
21
+ Manage parallel feature execution using git worktrees (git-native isolation).
26
22
 
27
- Feature snapshots live under \`${featuresRoot()}/<feature-id>/\`.
23
+ Runtime state/artifacts are **never** copied between features anymore. Isolation is branch/worktree-level.
28
24
 
29
25
  ## HARD-GATE
30
26
 
31
- - Never overwrite another feature snapshot silently.
32
- - Before switching feature, snapshot the current active runtime first.
33
- - Keep \`${activeFeaturePath()}\` as the single source of "current feature".
27
+ - Do not mutate feature context by copying \`${RUNTIME_ROOT}/artifacts\` or \`${RUNTIME_ROOT}/state\` between feature IDs.
28
+ - Use \`git worktree add\` for new feature execution paths.
29
+ - Keep \`${activeFeaturePath()}\` + \`${worktreeRegistryPath()}\` as the feature routing source of truth.
30
+ - Treat \`${legacyFeaturesRoot()}/\` as read-only migration data.
34
31
 
35
32
  ## Subcommands
36
33
 
37
34
  ### \`/cc-ops feature status\`
38
- Show active feature id and snapshot location.
35
+ Show:
36
+ - active feature id from \`${activeFeaturePath()}\`
37
+ - resolved worktree entry from \`${worktreeRegistryPath()}\`
38
+ - active workspace path
39
39
 
40
40
  ### \`/cc-ops feature list\`
41
- List all feature ids in \`${featuresRoot()}/\` (directory names).
41
+ List registered feature worktrees from \`${worktreeRegistryPath()}\` and mark active entry.
42
42
 
43
43
  ### \`/cc-ops feature new <feature-id>\`
44
- Create \`${featuresRoot()}/<feature-id>/artifacts\` and \`${featuresRoot()}/<feature-id>/state\`.
44
+ 1. Validate \`feature-id\` (lowercase slug, letters/numbers/dashes).
45
+ 2. Create worktree under \`${managedWorktreesRoot()}/<feature-id>\`.
46
+ 3. Create/switch branch using \`git worktree add\` (prefer \`feature/<feature-id>\` naming).
47
+ 4. Register entry in \`${worktreeRegistryPath()}\`.
45
48
 
46
- Optional flag:
47
- - \`--clone-active\`: clone current active runtime into the new feature snapshot.
49
+ Optional flags:
50
+ - \`--clone-active\`: seed from active branch HEAD (default behavior).
51
+ - \`--switch\`: mark new feature as active after registration.
48
52
 
49
53
  ### \`/cc-ops feature switch <feature-id>\`
50
- 1. Snapshot current active runtime into \`${featuresRoot()}/<active>/\`.
51
- 2. Restore target snapshot from \`${featuresRoot()}/<feature-id>/\` into active runtime:
52
- - \`${runtimeArtifactsPath()}\`
53
- - \`${runtimeStatePath()}\` (preserve \`active-feature.json\`)
54
- 3. Update \`${activeFeaturePath()}\` with \`activeFeature=<feature-id>\`.
54
+ 1. Validate that \`<feature-id>\` exists in \`${worktreeRegistryPath()}\`.
55
+ 2. Update \`${activeFeaturePath()}\`.
56
+ 3. Print target worktree path and instruct the operator/agent to continue from that workspace root.
57
+
58
+ ## Migration note
55
59
 
56
- If the target snapshot is empty, initialize runtime as a fresh flow.
60
+ Legacy snapshot folders under \`${legacyFeaturesRoot()}/\` are supported as read-only references during migration and should not be used for new execution.
57
61
 
58
62
  ## Output
59
63
 
60
64
  Always print:
61
65
  - active feature before
62
66
  - active feature after
63
- - whether snapshot/restore changed files
67
+ - target workspace path
68
+ - workspace source (\`git-worktree\` | \`workspace\` | \`legacy-snapshot\`)
64
69
 
65
70
  ## Primary skill
66
71
 
@@ -70,51 +75,49 @@ Always print:
70
75
  export function featureCommandSkillMarkdown() {
71
76
  return `---
72
77
  name: ${FEATURE_SKILL_NAME}
73
- description: "Manage cclaw multi-feature workspaces (status/list/new/switch) while preserving active flow runtime."
78
+ description: "Manage cclaw feature isolation using git worktrees (status/list/new/switch)."
74
79
  ---
75
80
 
76
- # /cc-ops feature — Feature Workspace Manager
81
+ # /cc-ops feature — Git Worktree Manager
77
82
 
78
83
  ## HARD-GATE
79
84
 
80
- Do not switch feature by editing only \`active-feature.json\`. A valid switch must snapshot current runtime and restore target runtime.
85
+ Do not implement feature switching by copying runtime files between feature IDs. Use git worktrees and registry updates only.
81
86
 
82
87
  ## Paths
83
88
 
84
89
  - Active pointer: \`${activeFeaturePath()}\`
85
- - Feature snapshots: \`${featuresRoot()}/<feature-id>/\`
86
- - Active runtime artifacts: \`${runtimeArtifactsPath()}\`
87
- - Active runtime state: \`${runtimeStatePath()}\`
90
+ - Worktree registry: \`${worktreeRegistryPath()}\`
91
+ - Managed worktree root: \`${managedWorktreesRoot()}\`
92
+ - Legacy snapshots (read-only): \`${legacyFeaturesRoot()}\`
88
93
 
89
94
  ## Protocol
90
95
 
91
96
  ### status
92
97
  1. Read \`${activeFeaturePath()}\`.
93
- 2. Print active feature id and its snapshot folder.
98
+ 2. Resolve active entry in \`${worktreeRegistryPath()}\`.
99
+ 3. Print active id + workspace path + source.
94
100
 
95
101
  ### list
96
- 1. Enumerate directories in \`${featuresRoot()}/\`.
102
+ 1. Enumerate entries in \`${worktreeRegistryPath()}\`.
97
103
  2. Mark the active one.
104
+ 3. Highlight any \`legacy-snapshot\` entries as migration-only.
98
105
 
99
- ### new <feature-id> [--clone-active]
100
- 1. Validate \`feature-id\` (lowercase slug, letters/numbers/dashes).
101
- 2. Create snapshot dirs:
102
- - \`${featuresRoot()}/<feature-id>/artifacts\`
103
- - \`${featuresRoot()}/<feature-id>/state\`
104
- 3. If \`--clone-active\`: copy active runtime artifacts/state into the new snapshot.
105
- 4. Do not change active feature unless the user explicitly requests switch.
106
+ ### new <feature-id> [--clone-active] [--switch]
107
+ 1. Validate \`feature-id\` and ensure not already registered.
108
+ 2. Run \`git worktree add\` to create \`${managedWorktreesRoot()}/<feature-id>\`.
109
+ 3. Register entry in \`${worktreeRegistryPath()}\` with branch + path + source.
110
+ 4. If \`--switch\`, update \`${activeFeaturePath()}\`.
106
111
 
107
112
  ### switch <feature-id>
108
- 1. Read current active feature id.
109
- 2. Snapshot current runtime into current feature snapshot.
110
- 3. Restore target snapshot into active runtime.
111
- 4. Update \`${activeFeaturePath()}\`.
112
- 5. Report stage/run after restore (\`flow-state.json\`).
113
+ 1. Validate target exists in \`${worktreeRegistryPath()}\`.
114
+ 2. Update \`${activeFeaturePath()}\`.
115
+ 3. Report target path and require continuation from that workspace root.
113
116
 
114
117
  ## Safety checks
115
118
 
116
119
  - If target feature does not exist: block and suggest \`/cc-ops feature new <id>\`.
117
- - If snapshot copy fails: abort switch, keep current active feature unchanged.
118
- - Preserve global pointer file \`active-feature.json\` when restoring state.
120
+ - If \`git worktree add\` fails: do not write partial registry updates.
121
+ - If active feature maps to \`legacy-snapshot\`, report read-only migration warning.
119
122
  `;
120
123
  }
@@ -75,12 +75,12 @@ Read-only subcommands:
75
75
  - \`/cc-view diff\` - before/after flow-state diff map
76
76
 
77
77
  Operations subcommands:
78
- - \`/cc-ops feature ...\` - multi-feature workspace management
78
+ - \`/cc-ops feature ...\` - git-worktree feature isolation and routing
79
79
  - \`/cc-ops tdd-log ...\` - explicit RED/GREEN/REFACTOR evidence log
80
80
  - \`/cc-ops retro\` - mandatory retrospective gate before archive
81
81
  - \`/cc-ops archive\` - archive active run from harness flow
82
82
  - \`/cc-ops rewind ...\` - rewind flow and invalidate downstream stages
83
- - \`/cc-ops rewind-ack ...\` - clear stale stage markers after redo
83
+ - \`/cc-ops rewind --ack ...\` - clear stale stage markers after redo
84
84
 
85
85
  Stage order remains canonical:
86
86
  \`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship\`
@@ -457,7 +457,7 @@ if [ -n "$ROUTING_MISSING" ]; then
457
457
  fi
458
458
 
459
459
  # --- Build context message ---
460
- CTX="cclaw loaded. Flow: stage=$STAGE ($COMPLETED/8 completed, run=$ACTIVE_RUN, feature=$ACTIVE_FEATURE). Active artifacts: ${RUNTIME_ROOT}/artifacts/. Feature snapshots: ${RUNTIME_ROOT}/features/$ACTIVE_FEATURE/. Learnings: $LEARNINGS_COUNT entries."
460
+ CTX="cclaw loaded. Flow: stage=$STAGE ($COMPLETED/8 completed, run=$ACTIVE_RUN, feature=$ACTIVE_FEATURE). Active artifacts: ${RUNTIME_ROOT}/artifacts/. Feature registry: ${RUNTIME_ROOT}/state/worktrees.json (managed roots: ${RUNTIME_ROOT}/worktrees/). Learnings: $LEARNINGS_COUNT entries."
461
461
  if [ -n "$VERSION_NOTE" ]; then
462
462
  CTX="$CTX
463
463
  $VERSION_NOTE"
@@ -496,7 +496,7 @@ To disable suggestions persistently set ${RUNTIME_ROOT}/state/suggestion-memory.
496
496
  fi
497
497
  if [ -n "$STALE_STAGES" ]; then
498
498
  CTX="$CTX
499
- Stale stages pending acknowledgement: $STALE_STAGES (use /cc-ops rewind-ack <stage> after redo)."
499
+ Stale stages pending acknowledgement: $STALE_STAGES (use /cc-ops rewind --ack <stage> after redo)."
500
500
  fi
501
501
  if [ -n "$KNOWLEDGE_DIGEST" ]; then
502
502
  CTX="$CTX
@@ -2,6 +2,6 @@
2
2
  * Canonical JSONL field order (matches the reference spec).
3
3
  * Exported for tests and any programmatic writer that wants the exact shape.
4
4
  */
5
- export declare const KNOWLEDGE_JSONL_FIELDS: readonly ["type", "trigger", "action", "confidence", "domain", "stage", "created", "project"];
5
+ export declare const KNOWLEDGE_JSONL_FIELDS: readonly ["type", "trigger", "action", "confidence", "domain", "stage", "origin_stage", "origin_feature", "frequency", "universality", "maturity", "created", "first_seen_ts", "last_seen_ts", "project"];
6
6
  export declare function learnSkillMarkdown(): string;
7
7
  export declare function learnCommandContract(): string;
@@ -20,7 +20,14 @@ export const KNOWLEDGE_JSONL_FIELDS = [
20
20
  "confidence",
21
21
  "domain",
22
22
  "stage",
23
+ "origin_stage",
24
+ "origin_feature",
25
+ "frequency",
26
+ "universality",
27
+ "maturity",
23
28
  "created",
29
+ "first_seen_ts",
30
+ "last_seen_ts",
24
31
  "project"
25
32
  ];
26
33
  export function learnSkillMarkdown() {
@@ -51,10 +58,10 @@ Do not invent alternate stores (no markdown mirror, no SQLite, no per-stage file
51
58
  ## Entry format — strict JSONL schema
52
59
 
53
60
  Exactly one JSON object per line. Fields must appear in the order:
54
- \`type, trigger, action, confidence, domain, stage, created, project\`.
61
+ \`type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project\`.
55
62
 
56
63
  \`\`\`json
57
- {"type":"pattern","trigger":"when reviewing external payloads","action":"parse through zod before touching service layer","confidence":"high","domain":"api","stage":"review","created":"2026-04-14T12:00:00Z","project":"cclaw"}
64
+ {"type":"pattern","trigger":"when reviewing external payloads","action":"parse through zod before touching service layer","confidence":"high","domain":"api","stage":"review","origin_stage":"review","origin_feature":"payload-hardening","frequency":1,"universality":"project","maturity":"raw","created":"2026-04-14T12:00:00Z","first_seen_ts":"2026-04-14T12:00:00Z","last_seen_ts":"2026-04-14T12:00:00Z","project":"cclaw"}
58
65
  \`\`\`
59
66
 
60
67
  | field | type | required | notes |
@@ -65,7 +72,14 @@ Exactly one JSON object per line. Fields must appear in the order:
65
72
  | \`confidence\` | \`"high" \\| "medium" \\| "low"\` | yes | Write \`medium\` when unsure; do not omit. |
66
73
  | \`domain\` | string \\| null | yes | Free-form taxonomy (\`api\`, \`infra\`, \`ui\`, \`security\`, \`testing\`, …). Use \`null\` when cross-cutting. |
67
74
  | \`stage\` | \`FlowStage\` \\| null | yes | One of brainstorm / scope / design / spec / plan / tdd / review / ship, or \`null\` when cross-stage. |
75
+ | \`origin_stage\` | \`FlowStage\` \\| null | yes | Stage where this learning was first observed. |
76
+ | \`origin_feature\` | string \\| null | yes | Feature/worktree label where it was observed first. |
77
+ | \`frequency\` | integer >= 1 | yes | Number of times this same trigger/action pair has been observed. |
78
+ | \`universality\` | \`"project" \\| "personal" \\| "universal"\` | yes | Scope of applicability. |
79
+ | \`maturity\` | \`"raw" \\| "lifted-to-rule" \\| "lifted-to-enforcement"\` | yes | Lifecycle state of the learning. |
68
80
  | \`created\` | ISO 8601 UTC string | yes | \`date -u +%Y-%m-%dT%H:%M:%SZ\`. |
81
+ | \`first_seen_ts\` | ISO 8601 UTC string | yes | First observed timestamp (usually equals \`created\`). |
82
+ | \`last_seen_ts\` | ISO 8601 UTC string | yes | Last re-confirmed timestamp. |
69
83
  | \`project\` | string \\| null | yes | Repo or scope name. Use \`null\` when the entry crosses projects. |
70
84
 
71
85
  Rules:
@@ -96,10 +110,13 @@ Rules:
96
110
  - Return the matched lines pretty-printed (do not mutate the file).
97
111
 
98
112
  ### \`/cc-learn add\`
99
- - Ask for required fields in order: \`type\`, \`trigger\`, \`action\`, \`confidence\`, \`domain\`, \`stage\`, \`project\`.
113
+ - Ask for required user-facing fields in order: \`type\`, \`trigger\`, \`action\`, \`confidence\`, \`domain\`, \`stage\`, \`universality\`, \`project\`.
100
114
  - \`confidence\` must be one of \`high\`, \`medium\`, \`low\`. Default to \`medium\` if the user declines to set it.
101
115
  - \`domain\`, \`stage\`, and \`project\` may be explicitly \`null\`.
102
- - \`created\` is set automatically to the current UTC ISO timestamp.
116
+ - \`origin_stage\` defaults to \`stage\`; \`origin_feature\` defaults to active feature (or \`null\` if unknown).
117
+ - \`frequency\` starts at \`1\`.
118
+ - \`maturity\` starts at \`raw\`.
119
+ - \`created\`, \`first_seen_ts\`, and \`last_seen_ts\` are set automatically to current UTC ISO timestamp.
103
120
  - Append exactly one JSON line to \`${KNOWLEDGE_PATH}\` with the field order from the schema table above.
104
121
  - Re-read the file tail to confirm the new line is valid JSON and parses back to the same object.
105
122
 
@@ -130,7 +147,7 @@ Do not edit source code from this command. Only operate on \`${KNOWLEDGE_PATH}\`
130
147
  |---|---|---|
131
148
  | (default) | — | Show recent knowledge entries (tail of JSONL, pretty-printed). |
132
149
  | \`search\` | \`<query>\` | Stream-filter the JSONL for matching \`trigger\`, \`action\`, \`domain\`, \`project\`. |
133
- | \`add\` | — | Append one JSON line (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) with the strict 8-field schema. |
150
+ | \`add\` | — | Append one JSON line (\`rule\` / \`pattern\` / \`lesson\` / \`compound\`) with the strict 15-field schema. |
134
151
  | \`curate\` | — | Hand off to the **knowledge-curation** skill: read-only audit + soft-archive plan when the file exceeds the curation threshold. |
135
152
  `;
136
153
  }
@@ -28,7 +28,7 @@ Task arrives
28
28
  ├─ Resume existing flow? -> /cc or /cc-next
29
29
  ├─ Knowledge operation? -> /cc-learn
30
30
  ├─ Read-only workspace view? -> /cc-view [status|tree|diff]
31
- └─ Workspace operation? -> /cc-ops [feature|tdd-log|retro|archive|rewind|rewind-ack]
31
+ └─ Workspace operation? -> /cc-ops [feature|tdd-log|retro|archive|rewind]
32
32
  \`\`\`
33
33
 
34
34
  ## Task classification
@@ -72,7 +72,7 @@ Do not inline these protocols in stage skills; cite by path:
72
72
 
73
73
  - Decision protocol: \`${DECISION_PROTOCOL_REL_PATH}\`
74
74
  - Completion/resume protocol: \`${COMPLETION_PROTOCOL_REL_PATH}\`
75
- - Engineering ethos + preamble rules: \`${ETHOS_PROTOCOL_REL_PATH}\`
75
+ - Engineering ethos + announce discipline: \`${ETHOS_PROTOCOL_REL_PATH}\`
76
76
 
77
77
  ## Knowledge guidance
78
78
 
@@ -39,7 +39,7 @@ This is the only progression command the user needs to drive the entire flow. St
39
39
 
40
40
  1. Read **\`${flowPath}\`**. If missing → **BLOCKED** (state missing).
41
41
  2. Parse JSON. Capture \`currentStage\` and \`stageGateCatalog[currentStage]\`.
42
- 3. If \`staleStages[currentStage]\` exists, do not advance automatically. Re-run the stage artifact work, then clear the marker with \`/cc-ops rewind-ack <currentStage>\`.
42
+ 3. If \`staleStages[currentStage]\` exists, do not advance automatically. Re-run the stage artifact work, then clear the marker with \`/cc-ops rewind --ack <currentStage>\`.
43
43
  4. Let \`G\` = \`requiredGates\` for **\`currentStage\`** from the stage schema.
44
44
  5. Let \`catalog\` = \`stageGateCatalog[currentStage]\` from flow state.
45
45
  6. **Satisfied** for gate id \`g\`: \`g\` in \`catalog.passed\` and \`g\` not in \`catalog.blocked\`.
@@ -124,7 +124,7 @@ Do **not** mark gates satisfied from memory alone. Cite **artifact evidence** (p
124
124
 
125
125
  1. Open **\`${flowPath}\`**.
126
126
  2. Record \`currentStage\` and \`stageGateCatalog[currentStage]\`.
127
- 3. If \`staleStages[currentStage]\` exists, re-run the stage and clear marker via \`/cc-ops rewind-ack <currentStage>\` before advancing.
127
+ 3. If \`staleStages[currentStage]\` exists, re-run the stage and clear marker via \`/cc-ops rewind --ack <currentStage>\` before advancing.
128
128
  4. If the file is missing or invalid JSON → **BLOCKED** (report and stop).
129
129
 
130
130
  ### Step 2: Evaluate gates
@@ -819,7 +819,8 @@ if command -v jq >/dev/null 2>&1; then
819
819
  --arg phase "$PHASE" \\
820
820
  --arg stage "$STAGE" \\
821
821
  --arg runId "$ACTIVE_RUN" \\
822
- '{ts:$ts,event:$event,tool:$tool,phase:$phase,stage:$stage,runId:$runId}' 2>/dev/null || echo "")
822
+ --arg schemaVersion "1" \\
823
+ '{ts:$ts,event:$event,tool:$tool,phase:$phase,stage:$stage,runId:$runId,schemaVersion:($schemaVersion|tonumber)}' 2>/dev/null || echo "")
823
824
  else
824
825
  EVENT_JSON=$(printf '{"ts":"%s","event":"%s","tool":"%s","phase":"%s","stage":"%s","runId":"%s","data":"%s"}' \\
825
826
  "$(escape_json "$TS")" \\
@@ -829,7 +830,7 @@ else
829
830
  "$(escape_json "$STAGE")" \\
830
831
  "$(escape_json "$ACTIVE_RUN")" \\
831
832
  "$(escape_json "$PAYLOAD")")
832
- ACTIVITY_JSON=$(printf '{"ts":"%s","event":"%s","tool":"%s","phase":"%s","stage":"%s","runId":"%s"}' \\
833
+ ACTIVITY_JSON=$(printf '{"ts":"%s","event":"%s","tool":"%s","phase":"%s","stage":"%s","runId":"%s","schemaVersion":1}' \\
833
834
  "$(escape_json "$TS")" \\
834
835
  "$(escape_json "$EVENT")" \\
835
836
  "$(escape_json "$TOOL")" \\
@@ -14,7 +14,6 @@ Subcommands:
14
14
  - \`retro\` -> \`/cc-ops retro\`
15
15
  - \`archive\` -> \`/cc-ops archive\`
16
16
  - \`rewind\` -> \`/cc-ops rewind\`
17
- - \`rewind-ack\` -> \`/cc-ops rewind-ack\`
18
17
 
19
18
  ## HARD-GATE
20
19
 
@@ -30,7 +29,6 @@ Subcommands:
30
29
  - \`retro\` -> \`${RUNTIME_ROOT}/commands/retro.md\`
31
30
  - \`archive\` -> \`${RUNTIME_ROOT}/commands/archive.md\`
32
31
  - \`rewind\` -> \`${RUNTIME_ROOT}/commands/rewind.md\`
33
- - \`rewind-ack\` -> \`${RUNTIME_ROOT}/commands/rewind-ack.md\`
34
32
  3. Unknown subcommand -> print supported values and stop.
35
33
 
36
34
  ## Primary skill
@@ -52,7 +50,7 @@ This wrapper only dispatches. It must not apply state mutations itself.
52
50
 
53
51
  ## Protocol
54
52
 
55
- 1. Require a subcommand (\`feature|tdd-log|retro|archive|rewind|rewind-ack\`).
53
+ 1. Require a subcommand (\`feature|tdd-log|retro|archive|rewind\`).
56
54
  2. Route to the matching command contract + skill pair.
57
55
  3. Preserve pass-through args after the subcommand (e.g. \`/cc-ops rewind design\`).
58
56
  4. Echo which subcommand was dispatched for auditability.