cclaw-cli 0.12.0 → 0.14.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/cli.d.ts +2 -0
- package/dist/cli.js +25 -1
- package/dist/config.js +19 -8
- package/dist/constants.d.ts +2 -2
- package/dist/constants.js +16 -1
- package/dist/content/archive-command.d.ts +2 -0
- package/dist/content/archive-command.js +98 -0
- package/dist/content/contracts.js +1 -1
- package/dist/content/diff-command.d.ts +2 -0
- package/dist/content/diff-command.js +83 -0
- package/dist/content/feature-command.d.ts +2 -0
- package/dist/content/feature-command.js +120 -0
- package/dist/content/harnesses-doc.js +11 -0
- package/dist/content/hooks.js +48 -2
- package/dist/content/learnings.d.ts +0 -2
- package/dist/content/learnings.js +4 -33
- package/dist/content/meta-skill.js +4 -2
- package/dist/content/next-command.js +18 -9
- package/dist/content/observe.d.ts +5 -1
- package/dist/content/observe.js +134 -2
- package/dist/content/ops-command.d.ts +2 -0
- package/dist/content/ops-command.js +60 -0
- package/dist/content/protocols.js +14 -2
- package/dist/content/retro-command.d.ts +2 -0
- package/dist/content/retro-command.js +77 -0
- package/dist/content/rewind-command.d.ts +3 -0
- package/dist/content/rewind-command.js +120 -0
- package/dist/content/skills.js +2 -0
- package/dist/content/stage-common-guidance.js +2 -1
- package/dist/content/status-command.js +43 -35
- package/dist/content/tdd-log-command.d.ts +2 -0
- package/dist/content/tdd-log-command.js +75 -0
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +36 -6
- package/dist/content/tree-command.d.ts +2 -0
- package/dist/content/tree-command.js +91 -0
- package/dist/content/utility-skills.js +1 -1
- package/dist/content/view-command.d.ts +2 -0
- package/dist/content/view-command.js +57 -0
- package/dist/doctor-registry.js +3 -3
- package/dist/doctor.js +149 -3
- package/dist/feature-system.d.ts +18 -0
- package/dist/feature-system.js +247 -0
- package/dist/flow-state.d.ts +25 -0
- package/dist/flow-state.js +8 -1
- package/dist/harness-adapters.js +95 -4
- package/dist/install.js +44 -2
- package/dist/policy.js +22 -0
- package/dist/runs.d.ts +33 -1
- package/dist/runs.js +365 -6
- package/dist/tdd-cycle.d.ts +22 -0
- package/dist/tdd-cycle.js +82 -0
- package/dist/types.d.ts +4 -2
- package/package.json +1 -1
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -39,6 +39,8 @@ Commands:
|
|
|
39
39
|
--quiet Print only failing checks (and totals).
|
|
40
40
|
archive Move .cclaw/artifacts into .cclaw/runs/<date>-<slug> and reset flow state.
|
|
41
41
|
Flags: --name=<feature> Feature slug (default: inferred from 00-idea.md).
|
|
42
|
+
--skip-retro Bypass mandatory retro gate (requires --retro-reason).
|
|
43
|
+
--retro-reason=<t> Reason for bypassing retro gate.
|
|
42
44
|
upgrade Refresh generated files in .cclaw without modifying user artifacts.
|
|
43
45
|
uninstall Remove .cclaw runtime and the generated harness shim files.
|
|
44
46
|
|
|
@@ -113,6 +115,17 @@ function buildInitSurfacePreview(harnesses) {
|
|
|
113
115
|
".cclaw/config.yaml",
|
|
114
116
|
".cclaw/commands/*.md",
|
|
115
117
|
".cclaw/skills/*/SKILL.md",
|
|
118
|
+
".cclaw/contexts/*.md",
|
|
119
|
+
".cclaw/templates/*",
|
|
120
|
+
".cclaw/agents/*.md",
|
|
121
|
+
".cclaw/hooks/*",
|
|
122
|
+
".cclaw/rules/**",
|
|
123
|
+
".cclaw/adapters/*.md",
|
|
124
|
+
".cclaw/custom-skills/README.md",
|
|
125
|
+
".cclaw/features/**",
|
|
126
|
+
".cclaw/runs/**",
|
|
127
|
+
".cclaw/artifacts/**",
|
|
128
|
+
".cclaw/knowledge.jsonl",
|
|
116
129
|
".cclaw/state/*.json|*.jsonl",
|
|
117
130
|
".cclaw/references/**",
|
|
118
131
|
"AGENTS.md (managed block)"
|
|
@@ -368,6 +381,14 @@ function parseArgs(argv) {
|
|
|
368
381
|
}
|
|
369
382
|
if (flag.startsWith("--name=")) {
|
|
370
383
|
parsed.archiveName = flag.replace("--name=", "").trim();
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (flag === "--skip-retro") {
|
|
387
|
+
parsed.archiveSkipRetro = true;
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (flag.startsWith("--retro-reason=")) {
|
|
391
|
+
parsed.archiveSkipRetroReason = flag.replace("--retro-reason=", "").trim();
|
|
371
392
|
}
|
|
372
393
|
}
|
|
373
394
|
return parsed;
|
|
@@ -466,7 +487,10 @@ async function runCommand(parsed, ctx) {
|
|
|
466
487
|
return 0;
|
|
467
488
|
}
|
|
468
489
|
if (command === "archive") {
|
|
469
|
-
const archived = await archiveRun(ctx.cwd, parsed.archiveName
|
|
490
|
+
const archived = await archiveRun(ctx.cwd, parsed.archiveName, {
|
|
491
|
+
skipRetro: parsed.archiveSkipRetro === true,
|
|
492
|
+
skipRetroReason: parsed.archiveSkipRetroReason
|
|
493
|
+
});
|
|
470
494
|
const snapshotSummary = archived.snapshottedStateFiles.length > 0
|
|
471
495
|
? ` Snapshotted ${archived.snapshottedStateFiles.length} state file(s) under ${archived.archivePath}/state and wrote archive-manifest.json.`
|
|
472
496
|
: "";
|
package/dist/config.js
CHANGED
|
@@ -15,8 +15,9 @@ const ALLOWED_CONFIG_KEYS = new Set([
|
|
|
15
15
|
"version",
|
|
16
16
|
"flowVersion",
|
|
17
17
|
"harnesses",
|
|
18
|
-
"autoAdvance",
|
|
19
18
|
"promptGuardMode",
|
|
19
|
+
"tddEnforcement",
|
|
20
|
+
"tddTestGlobs",
|
|
20
21
|
"gitHookGuards",
|
|
21
22
|
"defaultTrack",
|
|
22
23
|
"languageRulePacks",
|
|
@@ -58,8 +59,9 @@ export function createDefaultConfig(harnesses = DEFAULT_HARNESSES, defaultTrack
|
|
|
58
59
|
version: CCLAW_VERSION,
|
|
59
60
|
flowVersion: FLOW_VERSION,
|
|
60
61
|
harnesses,
|
|
61
|
-
autoAdvance: false,
|
|
62
62
|
promptGuardMode: "advisory",
|
|
63
|
+
tddEnforcement: "advisory",
|
|
64
|
+
tddTestGlobs: ["**/*.test.*", "**/*.spec.*", "**/test/**"],
|
|
63
65
|
gitHookGuards: false,
|
|
64
66
|
defaultTrack,
|
|
65
67
|
languageRulePacks: []
|
|
@@ -77,8 +79,8 @@ export function createProfileConfig(profile, overrides = {}) {
|
|
|
77
79
|
return {
|
|
78
80
|
...base,
|
|
79
81
|
harnesses: overrides.harnesses ?? ["claude"],
|
|
80
|
-
autoAdvance: false,
|
|
81
82
|
promptGuardMode: "advisory",
|
|
83
|
+
tddEnforcement: "advisory",
|
|
82
84
|
gitHookGuards: false,
|
|
83
85
|
defaultTrack: overrides.defaultTrack ?? "medium",
|
|
84
86
|
languageRulePacks: overrides.languageRulePacks ?? []
|
|
@@ -87,8 +89,8 @@ export function createProfileConfig(profile, overrides = {}) {
|
|
|
87
89
|
return {
|
|
88
90
|
...base,
|
|
89
91
|
harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
|
|
90
|
-
autoAdvance: false,
|
|
91
92
|
promptGuardMode: "advisory",
|
|
93
|
+
tddEnforcement: "advisory",
|
|
92
94
|
gitHookGuards: false,
|
|
93
95
|
defaultTrack: overrides.defaultTrack ?? "standard",
|
|
94
96
|
languageRulePacks: overrides.languageRulePacks ?? []
|
|
@@ -97,8 +99,8 @@ export function createProfileConfig(profile, overrides = {}) {
|
|
|
97
99
|
return {
|
|
98
100
|
...base,
|
|
99
101
|
harnesses: overrides.harnesses ?? DEFAULT_HARNESSES,
|
|
100
|
-
autoAdvance: false,
|
|
101
102
|
promptGuardMode: "strict",
|
|
103
|
+
tddEnforcement: "strict",
|
|
102
104
|
gitHookGuards: true,
|
|
103
105
|
defaultTrack: overrides.defaultTrack ?? "standard",
|
|
104
106
|
languageRulePacks: overrides.languageRulePacks ?? [...LANGUAGE_RULE_PACKS]
|
|
@@ -140,8 +142,6 @@ export async function readConfig(projectRoot) {
|
|
|
140
142
|
const harnesses = hasHarnessesField
|
|
141
143
|
? [...new Set(validatedHarnesses)]
|
|
142
144
|
: DEFAULT_HARNESSES;
|
|
143
|
-
const autoAdvanceRaw = parsed.autoAdvance;
|
|
144
|
-
const autoAdvance = typeof autoAdvanceRaw === "boolean" ? autoAdvanceRaw : false;
|
|
145
145
|
const promptGuardModeRaw = parsed.promptGuardMode;
|
|
146
146
|
if (Object.prototype.hasOwnProperty.call(parsed, "promptGuardMode") &&
|
|
147
147
|
promptGuardModeRaw !== "advisory" &&
|
|
@@ -149,6 +149,16 @@ export async function readConfig(projectRoot) {
|
|
|
149
149
|
throw configValidationError(fullPath, `"promptGuardMode" must be "advisory" or "strict"`);
|
|
150
150
|
}
|
|
151
151
|
const promptGuardMode = promptGuardModeRaw === "strict" ? "strict" : "advisory";
|
|
152
|
+
const tddEnforcementRaw = parsed.tddEnforcement;
|
|
153
|
+
if (Object.prototype.hasOwnProperty.call(parsed, "tddEnforcement") &&
|
|
154
|
+
tddEnforcementRaw !== "advisory" &&
|
|
155
|
+
tddEnforcementRaw !== "strict") {
|
|
156
|
+
throw configValidationError(fullPath, `"tddEnforcement" must be "advisory" or "strict"`);
|
|
157
|
+
}
|
|
158
|
+
const tddEnforcement = tddEnforcementRaw === "strict" ? "strict" : "advisory";
|
|
159
|
+
const tddTestGlobsRaw = parsed.tddTestGlobs;
|
|
160
|
+
const tddTestGlobs = validateStringArray(tddTestGlobsRaw, "tddTestGlobs", fullPath)
|
|
161
|
+
?? ["**/*.test.*", "**/*.spec.*", "**/test/**"];
|
|
152
162
|
const gitHookGuardsRaw = parsed.gitHookGuards;
|
|
153
163
|
if (Object.prototype.hasOwnProperty.call(parsed, "gitHookGuards") &&
|
|
154
164
|
typeof gitHookGuardsRaw !== "boolean") {
|
|
@@ -244,8 +254,9 @@ export async function readConfig(projectRoot) {
|
|
|
244
254
|
version: parsed.version ?? CCLAW_VERSION,
|
|
245
255
|
flowVersion: parsed.flowVersion ?? FLOW_VERSION,
|
|
246
256
|
harnesses,
|
|
247
|
-
autoAdvance,
|
|
248
257
|
promptGuardMode,
|
|
258
|
+
tddEnforcement,
|
|
259
|
+
tddTestGlobs,
|
|
249
260
|
gitHookGuards,
|
|
250
261
|
defaultTrack,
|
|
251
262
|
languageRulePacks,
|
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/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/features", ".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", "status"];
|
|
10
|
+
export declare const UTILITY_COMMANDS: readonly ["learn", "next", "view", "status", "tree", "diff", "ops", "feature", "tdd-log", "retro", "archive", "rewind", "rewind-ack"];
|
|
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,6 +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
19
|
`${RUNTIME_ROOT}/state`,
|
|
19
20
|
`${RUNTIME_ROOT}/runs`,
|
|
20
21
|
`${RUNTIME_ROOT}/rules`,
|
|
@@ -50,7 +51,21 @@ export const COMMAND_FILE_ORDER = [
|
|
|
50
51
|
"review",
|
|
51
52
|
"ship"
|
|
52
53
|
];
|
|
53
|
-
export const UTILITY_COMMANDS = [
|
|
54
|
+
export const UTILITY_COMMANDS = [
|
|
55
|
+
"learn",
|
|
56
|
+
"next",
|
|
57
|
+
"view",
|
|
58
|
+
"status",
|
|
59
|
+
"tree",
|
|
60
|
+
"diff",
|
|
61
|
+
"ops",
|
|
62
|
+
"feature",
|
|
63
|
+
"tdd-log",
|
|
64
|
+
"retro",
|
|
65
|
+
"archive",
|
|
66
|
+
"rewind",
|
|
67
|
+
"rewind-ack"
|
|
68
|
+
];
|
|
54
69
|
export const SUBAGENT_SKILL_FOLDERS = [
|
|
55
70
|
"subagent-dev",
|
|
56
71
|
"parallel-dispatch"
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
+
const ARCHIVE_SKILL_FOLDER = "flow-archive";
|
|
3
|
+
const ARCHIVE_SKILL_NAME = "flow-archive";
|
|
4
|
+
function flowStatePath() {
|
|
5
|
+
return `${RUNTIME_ROOT}/state/flow-state.json`;
|
|
6
|
+
}
|
|
7
|
+
function runsPath() {
|
|
8
|
+
return `${RUNTIME_ROOT}/runs`;
|
|
9
|
+
}
|
|
10
|
+
function activeArtifactsPath() {
|
|
11
|
+
return `${RUNTIME_ROOT}/artifacts`;
|
|
12
|
+
}
|
|
13
|
+
export function archiveCommandContract() {
|
|
14
|
+
return `# /cc-archive
|
|
15
|
+
|
|
16
|
+
## Purpose
|
|
17
|
+
|
|
18
|
+
Archive the active cclaw run from inside the harness flow (agent-first finish).
|
|
19
|
+
|
|
20
|
+
This command removes the user-facing CLI gap: users can stay in \`/cc-*\` flow and
|
|
21
|
+
finish with \`/cc-archive\` after ship + retro are complete.
|
|
22
|
+
|
|
23
|
+
## HARD-GATE
|
|
24
|
+
|
|
25
|
+
- Do not archive a shipped run when retro is still incomplete.
|
|
26
|
+
- Do not manually move files between \`${activeArtifactsPath()}\` and \`${runsPath()}\`.
|
|
27
|
+
- Use the archive runtime so state snapshots + manifest stay consistent.
|
|
28
|
+
|
|
29
|
+
## Inputs
|
|
30
|
+
|
|
31
|
+
\`/cc-archive [--name=<slug>] [--skip-retro --retro-reason=<text>]\`
|
|
32
|
+
|
|
33
|
+
## Algorithm
|
|
34
|
+
|
|
35
|
+
1. Read \`${flowStatePath()}\`.
|
|
36
|
+
2. If ship is complete and \`retro.completedAt\` is absent:
|
|
37
|
+
- block with explicit instruction: run \`/cc-retro\` first.
|
|
38
|
+
3. Build archive command:
|
|
39
|
+
- base: \`npx cclaw archive\`
|
|
40
|
+
- optional: \`--name=<slug>\`
|
|
41
|
+
- optional override: \`--skip-retro --retro-reason=<text>\`
|
|
42
|
+
4. Execute archive command in project root.
|
|
43
|
+
5. Surface result:
|
|
44
|
+
- archive id/path,
|
|
45
|
+
- reset stage (brainstorm/spec depending on track default),
|
|
46
|
+
- knowledge curation hint when threshold exceeded.
|
|
47
|
+
|
|
48
|
+
## Output format
|
|
49
|
+
|
|
50
|
+
\`\`\`
|
|
51
|
+
cclaw archive
|
|
52
|
+
status: archived
|
|
53
|
+
run: <archive-id>
|
|
54
|
+
path: .cclaw/runs/<archive-id>
|
|
55
|
+
next: /cc <new-idea>
|
|
56
|
+
\`\`\`
|
|
57
|
+
|
|
58
|
+
## Primary skill
|
|
59
|
+
|
|
60
|
+
**${RUNTIME_ROOT}/skills/${ARCHIVE_SKILL_FOLDER}/SKILL.md**
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
export function archiveCommandSkillMarkdown() {
|
|
64
|
+
return `---
|
|
65
|
+
name: ${ARCHIVE_SKILL_NAME}
|
|
66
|
+
description: "Archive the active cclaw run from harness flow and reset runtime safely."
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
# /cc-archive
|
|
70
|
+
|
|
71
|
+
## HARD-GATE
|
|
72
|
+
|
|
73
|
+
Never simulate archive by hand-editing runtime files. Always execute the archive
|
|
74
|
+
runtime command so state snapshots and manifest generation stay atomic.
|
|
75
|
+
|
|
76
|
+
## Protocol
|
|
77
|
+
|
|
78
|
+
1. Read \`${flowStatePath()}\`:
|
|
79
|
+
- confirm whether ship is completed,
|
|
80
|
+
- check \`retro.completedAt\` for post-ship runs.
|
|
81
|
+
2. If ship complete and retro incomplete -> stop and direct user to \`/cc-retro\`.
|
|
82
|
+
3. Build shell command:
|
|
83
|
+
- \`npx cclaw archive\`
|
|
84
|
+
- append \`--name=<slug>\` when provided
|
|
85
|
+
- append \`--skip-retro --retro-reason=<text>\` only when user explicitly requests skip
|
|
86
|
+
4. Run command from repo root.
|
|
87
|
+
5. Relay key lines from output:
|
|
88
|
+
- archive destination under \`${runsPath()}\`
|
|
89
|
+
- flow reset confirmation
|
|
90
|
+
- knowledge curation recommendation
|
|
91
|
+
|
|
92
|
+
## Validation
|
|
93
|
+
|
|
94
|
+
- \`${runsPath()}\` contains a new archive folder.
|
|
95
|
+
- \`${activeArtifactsPath()}\` is reset for the next run.
|
|
96
|
+
- \`${flowStatePath()}\` is valid JSON and points to the initial stage.
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
@@ -36,7 +36,7 @@ ${schema.hardGate}
|
|
|
36
36
|
${hydrationLines}
|
|
37
37
|
4. Stream \`.cclaw/knowledge.jsonl\` and apply relevant JSON-line entries (strict schema: type, trigger, action, confidence, domain, stage, created, project).
|
|
38
38
|
5. Write stage output to ${writeStepPaths}.
|
|
39
|
-
6. Do NOT copy artifacts into \`.cclaw/runs/\`; archival is handled
|
|
39
|
+
6. Do NOT copy artifacts into \`.cclaw/runs/\`; archival is handled by \`/cc-archive\` (agent-facing wrapper over archive runtime).
|
|
40
40
|
|
|
41
41
|
## Gates
|
|
42
42
|
${gateIds}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
+
const DIFF_SKILL_FOLDER = "flow-diff";
|
|
3
|
+
const DIFF_SKILL_NAME = "flow-diff";
|
|
4
|
+
function flowStatePath() {
|
|
5
|
+
return `${RUNTIME_ROOT}/state/flow-state.json`;
|
|
6
|
+
}
|
|
7
|
+
function snapshotPath() {
|
|
8
|
+
return `${RUNTIME_ROOT}/state/flow-state.snapshot.json`;
|
|
9
|
+
}
|
|
10
|
+
export function diffCommandContract() {
|
|
11
|
+
return `# /cc-diff
|
|
12
|
+
|
|
13
|
+
## Purpose
|
|
14
|
+
|
|
15
|
+
Show a visual before/after diff map for flow-state progression.
|
|
16
|
+
|
|
17
|
+
## HARD-GATE
|
|
18
|
+
|
|
19
|
+
- Compare against \`${snapshotPath()}\` first; do not overwrite baseline before rendering.
|
|
20
|
+
- If no snapshot exists, initialize baseline and report "baseline created" explicitly.
|
|
21
|
+
|
|
22
|
+
## Algorithm
|
|
23
|
+
|
|
24
|
+
1. Read current state from \`${flowStatePath()}\`.
|
|
25
|
+
2. Read baseline from \`${snapshotPath()}\` (if missing -> create baseline from current state and stop).
|
|
26
|
+
3. Compute deltas:
|
|
27
|
+
- stage transition (\`from -> to\`)
|
|
28
|
+
- completed stage additions/removals
|
|
29
|
+
- skipped stage additions/removals
|
|
30
|
+
- stale stage additions/removals
|
|
31
|
+
- current-stage gate \`passed\` and \`blocked\` changes
|
|
32
|
+
4. Render a compact diff map (added \`+\`, removed \`-\`, changed \`->\`).
|
|
33
|
+
5. Persist current state back to \`${snapshotPath()}\` as new baseline with \`capturedAt\`.
|
|
34
|
+
|
|
35
|
+
## Diff Map Format
|
|
36
|
+
|
|
37
|
+
\`\`\`
|
|
38
|
+
cclaw flow diff
|
|
39
|
+
stage: design -> spec
|
|
40
|
+
completed: +design
|
|
41
|
+
stale: -design
|
|
42
|
+
gates(spec): +spec_contract_complete -spec_open_questions_closed
|
|
43
|
+
blocked(spec): +spec_trace_matrix_missing
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
## Primary skill
|
|
47
|
+
|
|
48
|
+
**${RUNTIME_ROOT}/skills/${DIFF_SKILL_FOLDER}/SKILL.md**
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
export function diffCommandSkillMarkdown() {
|
|
52
|
+
return `---
|
|
53
|
+
name: ${DIFF_SKILL_NAME}
|
|
54
|
+
description: "Compare current flow-state against saved snapshot and render gate/stage deltas."
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
# /cc-diff
|
|
58
|
+
|
|
59
|
+
## HARD-GATE
|
|
60
|
+
|
|
61
|
+
Never lose baseline visibility: render deltas before writing a new snapshot.
|
|
62
|
+
|
|
63
|
+
## Protocol
|
|
64
|
+
|
|
65
|
+
1. Read \`${flowStatePath()}\`.
|
|
66
|
+
2. Read \`${snapshotPath()}\`.
|
|
67
|
+
3. If snapshot missing:
|
|
68
|
+
- write baseline snapshot from current state,
|
|
69
|
+
- print \`flow diff baseline created\`,
|
|
70
|
+
- stop.
|
|
71
|
+
4. Build deltas for stage, completed/skipped/stale sets, and current-stage gate arrays.
|
|
72
|
+
5. Print a compact diff map with explicit \`+\`, \`-\`, and \`->\` markers.
|
|
73
|
+
6. Write updated snapshot with:
|
|
74
|
+
- \`capturedAt\` (ISO)
|
|
75
|
+
- \`state\` (full current flow-state object)
|
|
76
|
+
|
|
77
|
+
## Validation
|
|
78
|
+
|
|
79
|
+
- Diff output must be deterministic for identical states ("no changes").
|
|
80
|
+
- Snapshot file stays valid JSON after every run.
|
|
81
|
+
- Do not suppress removed values; removals are first-class evidence.
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { RUNTIME_ROOT } from "../constants.js";
|
|
2
|
+
const FEATURE_SKILL_FOLDER = "feature-workspaces";
|
|
3
|
+
const FEATURE_SKILL_NAME = "feature-workspaces";
|
|
4
|
+
function activeFeaturePath() {
|
|
5
|
+
return `${RUNTIME_ROOT}/state/active-feature.json`;
|
|
6
|
+
}
|
|
7
|
+
function featuresRoot() {
|
|
8
|
+
return `${RUNTIME_ROOT}/features`;
|
|
9
|
+
}
|
|
10
|
+
function runtimeArtifactsPath() {
|
|
11
|
+
return `${RUNTIME_ROOT}/artifacts`;
|
|
12
|
+
}
|
|
13
|
+
function runtimeStatePath() {
|
|
14
|
+
return `${RUNTIME_ROOT}/state`;
|
|
15
|
+
}
|
|
16
|
+
export function featureCommandContract() {
|
|
17
|
+
return `# /cc-feature
|
|
18
|
+
|
|
19
|
+
## Purpose
|
|
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)
|
|
26
|
+
|
|
27
|
+
Feature snapshots live under \`${featuresRoot()}/<feature-id>/\`.
|
|
28
|
+
|
|
29
|
+
## HARD-GATE
|
|
30
|
+
|
|
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".
|
|
34
|
+
|
|
35
|
+
## Subcommands
|
|
36
|
+
|
|
37
|
+
### \`/cc-feature status\`
|
|
38
|
+
Show active feature id and snapshot location.
|
|
39
|
+
|
|
40
|
+
### \`/cc-feature list\`
|
|
41
|
+
List all feature ids in \`${featuresRoot()}/\` (directory names).
|
|
42
|
+
|
|
43
|
+
### \`/cc-feature new <feature-id>\`
|
|
44
|
+
Create \`${featuresRoot()}/<feature-id>/artifacts\` and \`${featuresRoot()}/<feature-id>/state\`.
|
|
45
|
+
|
|
46
|
+
Optional flag:
|
|
47
|
+
- \`--clone-active\`: clone current active runtime into the new feature snapshot.
|
|
48
|
+
|
|
49
|
+
### \`/cc-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>\`.
|
|
55
|
+
|
|
56
|
+
If the target snapshot is empty, initialize runtime as a fresh flow.
|
|
57
|
+
|
|
58
|
+
## Output
|
|
59
|
+
|
|
60
|
+
Always print:
|
|
61
|
+
- active feature before
|
|
62
|
+
- active feature after
|
|
63
|
+
- whether snapshot/restore changed files
|
|
64
|
+
|
|
65
|
+
## Primary skill
|
|
66
|
+
|
|
67
|
+
**${RUNTIME_ROOT}/skills/${FEATURE_SKILL_FOLDER}/SKILL.md**
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
export function featureCommandSkillMarkdown() {
|
|
71
|
+
return `---
|
|
72
|
+
name: ${FEATURE_SKILL_NAME}
|
|
73
|
+
description: "Manage cclaw multi-feature workspaces (status/list/new/switch) while preserving active flow runtime."
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
# /cc-feature — Feature Workspace Manager
|
|
77
|
+
|
|
78
|
+
## HARD-GATE
|
|
79
|
+
|
|
80
|
+
Do not switch feature by editing only \`active-feature.json\`. A valid switch must snapshot current runtime and restore target runtime.
|
|
81
|
+
|
|
82
|
+
## Paths
|
|
83
|
+
|
|
84
|
+
- Active pointer: \`${activeFeaturePath()}\`
|
|
85
|
+
- Feature snapshots: \`${featuresRoot()}/<feature-id>/\`
|
|
86
|
+
- Active runtime artifacts: \`${runtimeArtifactsPath()}\`
|
|
87
|
+
- Active runtime state: \`${runtimeStatePath()}\`
|
|
88
|
+
|
|
89
|
+
## Protocol
|
|
90
|
+
|
|
91
|
+
### status
|
|
92
|
+
1. Read \`${activeFeaturePath()}\`.
|
|
93
|
+
2. Print active feature id and its snapshot folder.
|
|
94
|
+
|
|
95
|
+
### list
|
|
96
|
+
1. Enumerate directories in \`${featuresRoot()}/\`.
|
|
97
|
+
2. Mark the active one.
|
|
98
|
+
|
|
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
|
+
|
|
107
|
+
### 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
|
+
|
|
114
|
+
## Safety checks
|
|
115
|
+
|
|
116
|
+
- If target feature does not exist: block and suggest \`/cc-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.
|
|
119
|
+
`;
|
|
120
|
+
}
|
|
@@ -65,7 +65,18 @@ All harnesses receive the same utility commands:
|
|
|
65
65
|
|
|
66
66
|
- \`/cc\` - flow entry and resume
|
|
67
67
|
- \`/cc-next\` - stage progression
|
|
68
|
+
- \`/cc-view\` - read-only router for status/tree/diff
|
|
68
69
|
- \`/cc-learn\` - knowledge capture/lookup
|
|
70
|
+
- \`/cc-status\` - read-only visual flow snapshot
|
|
71
|
+
- \`/cc-tree\` - deep flow tree (stages, artifacts, stale markers)
|
|
72
|
+
- \`/cc-diff\` - before/after flow-state diff map
|
|
73
|
+
- \`/cc-ops\` - operations router for feature/tdd-log/retro/archive/rewind
|
|
74
|
+
- \`/cc-feature\` - multi-feature workspace management
|
|
75
|
+
- \`/cc-tdd-log\` - explicit RED/GREEN/REFACTOR evidence log
|
|
76
|
+
- \`/cc-retro\` - mandatory retrospective gate before archive
|
|
77
|
+
- \`/cc-archive\` - archive active run from harness flow
|
|
78
|
+
- \`/cc-rewind\` - rewind flow and invalidate downstream stages
|
|
79
|
+
- \`/cc-rewind-ack\` - clear stale stage markers after redo
|
|
69
80
|
|
|
70
81
|
Stage order remains canonical:
|
|
71
82
|
\`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship\`
|
package/dist/content/hooks.js
CHANGED
|
@@ -45,6 +45,7 @@ set -euo pipefail
|
|
|
45
45
|
${DETECT_ROOT}
|
|
46
46
|
|
|
47
47
|
STATE_FILE="$ROOT/${RUNTIME_ROOT}/state/flow-state.json"
|
|
48
|
+
ACTIVE_FEATURE_FILE="$ROOT/${RUNTIME_ROOT}/state/active-feature.json"
|
|
48
49
|
CHECKPOINT_FILE="$ROOT/${RUNTIME_ROOT}/state/checkpoint.json"
|
|
49
50
|
ACTIVITY_FILE="$ROOT/${RUNTIME_ROOT}/state/stage-activity.jsonl"
|
|
50
51
|
SUGGESTION_MEMORY_FILE="$ROOT/${RUNTIME_ROOT}/state/suggestion-memory.json"
|
|
@@ -59,13 +60,16 @@ META_SKILL="$ROOT/${RUNTIME_ROOT}/skills/${META_SKILL_NAME}/SKILL.md"
|
|
|
59
60
|
STAGE="none"
|
|
60
61
|
COMPLETED="0"
|
|
61
62
|
ACTIVE_RUN="none"
|
|
63
|
+
ACTIVE_FEATURE="default"
|
|
62
64
|
ACTIVE_CONTEXT_MODE="default"
|
|
65
|
+
STALE_STAGES=""
|
|
63
66
|
CONTEXT_MODE_NOTE=""
|
|
64
67
|
if [ -f "$STATE_FILE" ]; then
|
|
65
68
|
if command -v jq >/dev/null 2>&1; then
|
|
66
69
|
STAGE=$(jq -r '.currentStage // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
|
|
67
70
|
COMPLETED=$(jq -r '(.completedStages | length) // 0' "$STATE_FILE" 2>/dev/null || echo "0")
|
|
68
71
|
ACTIVE_RUN=$(jq -r '.activeRunId // "none"' "$STATE_FILE" 2>/dev/null || echo "none")
|
|
72
|
+
STALE_STAGES=$(jq -r '(.staleStages // {} | keys | join(", "))' "$STATE_FILE" 2>/dev/null || echo "")
|
|
69
73
|
else
|
|
70
74
|
if command -v python3 >/dev/null 2>&1; then
|
|
71
75
|
STAGE=$(python3 - "$STATE_FILE" <<'PY'
|
|
@@ -115,6 +119,22 @@ except Exception:
|
|
|
115
119
|
pass
|
|
116
120
|
print(run)
|
|
117
121
|
PY
|
|
122
|
+
)
|
|
123
|
+
STALE_STAGES=$(python3 - "$STATE_FILE" <<'PY'
|
|
124
|
+
import json
|
|
125
|
+
import sys
|
|
126
|
+
value = ""
|
|
127
|
+
try:
|
|
128
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
129
|
+
data = json.load(fh)
|
|
130
|
+
stale = data.get("staleStages", {})
|
|
131
|
+
if isinstance(stale, dict):
|
|
132
|
+
keys = [k for k, v in stale.items() if isinstance(v, dict)]
|
|
133
|
+
value = ", ".join(keys)
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
print(value)
|
|
137
|
+
PY
|
|
118
138
|
)
|
|
119
139
|
else
|
|
120
140
|
STAGE=$(grep -o '"currentStage"[[:space:]]*:[[:space:]]*"[^"]*"' "$STATE_FILE" 2>/dev/null | head -1 | sed 's/.*"\\([^"]*\\)"$/\\1/' || echo "none")
|
|
@@ -129,6 +149,28 @@ PY
|
|
|
129
149
|
fi
|
|
130
150
|
fi
|
|
131
151
|
|
|
152
|
+
if [ -f "$ACTIVE_FEATURE_FILE" ]; then
|
|
153
|
+
if command -v jq >/dev/null 2>&1; then
|
|
154
|
+
ACTIVE_FEATURE=$(jq -r '.activeFeature // "default"' "$ACTIVE_FEATURE_FILE" 2>/dev/null || echo "default")
|
|
155
|
+
elif command -v python3 >/dev/null 2>&1; then
|
|
156
|
+
ACTIVE_FEATURE=$(python3 - "$ACTIVE_FEATURE_FILE" <<'PY'
|
|
157
|
+
import json
|
|
158
|
+
import sys
|
|
159
|
+
feature = "default"
|
|
160
|
+
try:
|
|
161
|
+
with open(sys.argv[1], "r", encoding="utf-8") as fh:
|
|
162
|
+
data = json.load(fh)
|
|
163
|
+
value = data.get("activeFeature")
|
|
164
|
+
if isinstance(value, str) and value:
|
|
165
|
+
feature = value
|
|
166
|
+
except Exception:
|
|
167
|
+
pass
|
|
168
|
+
print(feature)
|
|
169
|
+
PY
|
|
170
|
+
)
|
|
171
|
+
fi
|
|
172
|
+
fi
|
|
173
|
+
|
|
132
174
|
if [ -f "$CONTEXT_MODE_FILE" ]; then
|
|
133
175
|
if command -v jq >/dev/null 2>&1; then
|
|
134
176
|
ACTIVE_CONTEXT_MODE=$(jq -r '.activeMode // "default"' "$CONTEXT_MODE_FILE" 2>/dev/null || echo "default")
|
|
@@ -415,7 +457,7 @@ if [ -n "$ROUTING_MISSING" ]; then
|
|
|
415
457
|
fi
|
|
416
458
|
|
|
417
459
|
# --- Build context message ---
|
|
418
|
-
CTX="cclaw loaded. Flow: stage=$STAGE ($COMPLETED/8 completed, run=$ACTIVE_RUN). Active artifacts: ${RUNTIME_ROOT}/artifacts/. 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 snapshots: ${RUNTIME_ROOT}/features/$ACTIVE_FEATURE/. Learnings: $LEARNINGS_COUNT entries."
|
|
419
461
|
if [ -n "$VERSION_NOTE" ]; then
|
|
420
462
|
CTX="$CTX
|
|
421
463
|
$VERSION_NOTE"
|
|
@@ -452,6 +494,10 @@ if [ -n "$STAGE_SUGGESTION" ]; then
|
|
|
452
494
|
$STAGE_SUGGESTION
|
|
453
495
|
To disable suggestions persistently set ${RUNTIME_ROOT}/state/suggestion-memory.json -> enabled=false."
|
|
454
496
|
fi
|
|
497
|
+
if [ -n "$STALE_STAGES" ]; then
|
|
498
|
+
CTX="$CTX
|
|
499
|
+
Stale stages pending acknowledgement: $STALE_STAGES (use /cc-rewind-ack <stage> after redo)."
|
|
500
|
+
fi
|
|
455
501
|
if [ -n "$KNOWLEDGE_DIGEST" ]; then
|
|
456
502
|
CTX="$CTX
|
|
457
503
|
Knowledge digest (top relevant entries):
|
|
@@ -696,7 +742,7 @@ if [ "$CHECKPOINT_WRITTEN" -eq 0 ]; then
|
|
|
696
742
|
CHECKPOINT_NOTE="Checkpoint update failed. Review ${RUNTIME_ROOT}/state/checkpoint.json manually."
|
|
697
743
|
fi
|
|
698
744
|
|
|
699
|
-
RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROOT}/artifacts until cclaw archive."
|
|
745
|
+
RUN_SYNC_NOTE="Run metadata sync removed; active artifacts stay in ${RUNTIME_ROOT}/artifacts until /cc-archive (or cclaw archive runtime)."
|
|
700
746
|
|
|
701
747
|
# --- Escape for JSON ---
|
|
702
748
|
${ESCAPE_FN}
|
|
@@ -6,5 +6,3 @@ export declare const KNOWLEDGE_JSONL_FIELDS: readonly ["type", "trigger", "actio
|
|
|
6
6
|
export declare function learnSkillMarkdown(): string;
|
|
7
7
|
export declare function learnCommandContract(): string;
|
|
8
8
|
export declare function selfImprovementBlock(stageName: string): string;
|
|
9
|
-
export declare function learningsSearchPreamble(stage: string): string;
|
|
10
|
-
export declare function learningsAgentsMdBlock(): string;
|