cclaw-cli 0.15.0 → 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.
- package/dist/artifact-linter.js +154 -0
- package/dist/cli.js +2 -1
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +2 -3
- package/dist/content/contracts.js +1 -1
- package/dist/content/doctor-references.js +7 -6
- package/dist/content/feature-command.js +54 -51
- package/dist/content/harnesses-doc.js +2 -2
- package/dist/content/hooks.js +2 -2
- package/dist/content/learnings.d.ts +1 -1
- package/dist/content/learnings.js +22 -5
- package/dist/content/meta-skill.js +2 -2
- package/dist/content/next-command.js +2 -2
- package/dist/content/observe.js +3 -2
- package/dist/content/ops-command.js +1 -3
- package/dist/content/protocols.js +6 -34
- package/dist/content/rewind-command.d.ts +0 -1
- package/dist/content/rewind-command.js +19 -33
- package/dist/content/skills.js +2 -3
- package/dist/content/stage-schema.d.ts +2 -92
- package/dist/content/stage-schema.js +10 -1379
- package/dist/content/stages/brainstorm.d.ts +2 -0
- package/dist/content/stages/brainstorm.js +136 -0
- package/dist/content/stages/design.d.ts +2 -0
- package/dist/content/stages/design.js +215 -0
- package/dist/content/stages/index.d.ts +8 -0
- package/dist/content/stages/index.js +11 -0
- package/dist/content/stages/plan.d.ts +2 -0
- package/dist/content/stages/plan.js +157 -0
- package/dist/content/stages/review.d.ts +2 -0
- package/dist/content/stages/review.js +197 -0
- package/dist/content/stages/schema-types.d.ts +94 -0
- package/dist/content/stages/schema-types.js +1 -0
- package/dist/content/stages/scope.d.ts +2 -0
- package/dist/content/stages/scope.js +194 -0
- package/dist/content/stages/ship.d.ts +2 -0
- package/dist/content/stages/ship.js +142 -0
- package/dist/content/stages/spec.d.ts +2 -0
- package/dist/content/stages/spec.js +136 -0
- package/dist/content/stages/tdd.d.ts +2 -0
- package/dist/content/stages/tdd.js +185 -0
- package/dist/content/templates.js +105 -9
- package/dist/content/utility-skills.js +1 -1
- package/dist/delegation.d.ts +33 -3
- package/dist/delegation.js +56 -3
- package/dist/doctor.js +147 -88
- package/dist/feature-system.d.ts +22 -5
- package/dist/feature-system.js +267 -126
- package/dist/install.js +4 -8
- package/dist/policy.js +3 -4
- package/package.json +1 -1
package/dist/artifact-linter.js
CHANGED
|
@@ -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/
|
|
125
|
+
".cclaw/worktrees/**",
|
|
126
|
+
".cclaw/features/** (legacy snapshots, read-only migration)",
|
|
126
127
|
".cclaw/runs/**",
|
|
127
128
|
".cclaw/artifacts/**",
|
|
128
129
|
".cclaw/knowledge.jsonl",
|
package/dist/constants.d.ts
CHANGED
|
@@ -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/
|
|
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"
|
|
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}/
|
|
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
|
|
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
|
-
##
|
|
89
|
+
## Announce discipline contract
|
|
89
90
|
|
|
90
|
-
- preamble
|
|
91
|
-
-
|
|
92
|
-
-
|
|
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.
|
|
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 = "
|
|
3
|
-
const FEATURE_SKILL_NAME = "
|
|
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
|
|
8
|
-
return `${RUNTIME_ROOT}/
|
|
7
|
+
function worktreeRegistryPath() {
|
|
8
|
+
return `${RUNTIME_ROOT}/state/worktrees.json`;
|
|
9
9
|
}
|
|
10
|
-
function
|
|
11
|
-
return `${RUNTIME_ROOT}/
|
|
10
|
+
function managedWorktreesRoot() {
|
|
11
|
+
return `${RUNTIME_ROOT}/worktrees`;
|
|
12
12
|
}
|
|
13
|
-
function
|
|
14
|
-
return `${RUNTIME_ROOT}/
|
|
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
|
|
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
|
-
|
|
23
|
+
Runtime state/artifacts are **never** copied between features anymore. Isolation is branch/worktree-level.
|
|
28
24
|
|
|
29
25
|
## HARD-GATE
|
|
30
26
|
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
- Keep \`${activeFeaturePath()}\` as the
|
|
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
|
|
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
|
|
41
|
+
List registered feature worktrees from \`${worktreeRegistryPath()}\` and mark active entry.
|
|
42
42
|
|
|
43
43
|
### \`/cc-ops feature new <feature-id>\`
|
|
44
|
-
|
|
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
|
|
47
|
-
- \`--clone-active\`:
|
|
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.
|
|
51
|
-
2.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
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
|
|
78
|
+
description: "Manage cclaw feature isolation using git worktrees (status/list/new/switch)."
|
|
74
79
|
---
|
|
75
80
|
|
|
76
|
-
# /cc-ops feature —
|
|
81
|
+
# /cc-ops feature — Git Worktree Manager
|
|
77
82
|
|
|
78
83
|
## HARD-GATE
|
|
79
84
|
|
|
80
|
-
Do not
|
|
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
|
-
-
|
|
86
|
-
-
|
|
87
|
-
-
|
|
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.
|
|
98
|
+
2. Resolve active entry in \`${worktreeRegistryPath()}\`.
|
|
99
|
+
3. Print active id + workspace path + source.
|
|
94
100
|
|
|
95
101
|
### list
|
|
96
|
-
1. Enumerate
|
|
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\`
|
|
101
|
-
2.
|
|
102
|
-
|
|
103
|
-
|
|
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.
|
|
109
|
-
2.
|
|
110
|
-
3.
|
|
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
|
|
118
|
-
-
|
|
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 ...\` -
|
|
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
|
|
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\`
|
package/dist/content/hooks.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
- \`
|
|
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
|
|
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
|
|
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 +
|
|
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
|
|
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
|
|
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
|
package/dist/content/observe.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|