cclaw-cli 0.15.1 → 0.21.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 +4 -3
- package/dist/content/compound-command.d.ts +2 -0
- package/dist/content/compound-command.js +72 -0
- 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 +5 -3
- package/dist/content/hooks.js +2 -2
- package/dist/content/ideate-command.d.ts +2 -0
- package/dist/content/ideate-command.js +73 -0
- package/dist/content/learnings.d.ts +1 -1
- package/dist/content/learnings.js +22 -5
- package/dist/content/meta-skill.js +6 -3
- package/dist/content/next-command.js +5 -5
- package/dist/content/observe.js +3 -2
- package/dist/content/ops-command.js +4 -4
- package/dist/content/protocols.js +27 -38
- package/dist/content/retro-command.js +2 -1
- package/dist/content/rewind-command.d.ts +0 -1
- package/dist/content/rewind-command.js +19 -33
- package/dist/content/skills.js +14 -8
- package/dist/content/stage-schema.js +3 -38
- package/dist/content/stages/plan.js +16 -5
- package/dist/content/stages/review.js +20 -0
- package/dist/content/stages/scope.js +9 -3
- package/dist/content/stages/ship.js +1 -0
- package/dist/content/stages/tdd.js +5 -4
- package/dist/content/templates.js +105 -9
- package/dist/content/utility-skills.d.ts +3 -1
- package/dist/content/utility-skills.js +91 -1
- package/dist/delegation.d.ts +33 -3
- package/dist/delegation.js +56 -3
- package/dist/doctor.js +269 -88
- package/dist/feature-system.d.ts +22 -5
- package/dist/feature-system.js +267 -126
- package/dist/harness-adapters.js +17 -1
- package/dist/install.js +10 -8
- package/dist/policy.js +13 -4
- package/package.json +1 -1
package/dist/feature-system.js
CHANGED
|
@@ -1,36 +1,43 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { execFile } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
3
5
|
import { RUNTIME_ROOT } from "./constants.js";
|
|
4
6
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
5
|
-
const
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
|
+
const WORKTREES_DIR_REL_PATH = `${RUNTIME_ROOT}/worktrees`;
|
|
9
|
+
const LEGACY_FEATURES_DIR_REL_PATH = `${RUNTIME_ROOT}/features`;
|
|
6
10
|
const ACTIVE_FEATURE_META_REL_PATH = `${RUNTIME_ROOT}/state/active-feature.json`;
|
|
11
|
+
const WORKTREE_REGISTRY_REL_PATH = `${RUNTIME_ROOT}/state/worktrees.json`;
|
|
7
12
|
const DEFAULT_FEATURE_ID = "default";
|
|
13
|
+
const WORKTREE_REGISTRY_SCHEMA_VERSION = 1;
|
|
8
14
|
const FEATURE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/u;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
".flow-state.lock",
|
|
12
|
-
".delegation.lock"
|
|
13
|
-
]);
|
|
14
|
-
function featuresRoot(projectRoot) {
|
|
15
|
-
return path.join(projectRoot, FEATURES_DIR_REL_PATH);
|
|
15
|
+
function worktreesRoot(projectRoot) {
|
|
16
|
+
return path.join(projectRoot, WORKTREES_DIR_REL_PATH);
|
|
16
17
|
}
|
|
17
|
-
function
|
|
18
|
-
return path.join(projectRoot,
|
|
19
|
-
}
|
|
20
|
-
function runtimeStateRoot(projectRoot) {
|
|
21
|
-
return path.join(projectRoot, RUNTIME_ROOT, "state");
|
|
18
|
+
function legacyFeaturesRoot(projectRoot) {
|
|
19
|
+
return path.join(projectRoot, LEGACY_FEATURES_DIR_REL_PATH);
|
|
22
20
|
}
|
|
23
21
|
export function activeFeatureMetaPath(projectRoot) {
|
|
24
22
|
return path.join(projectRoot, ACTIVE_FEATURE_META_REL_PATH);
|
|
25
23
|
}
|
|
24
|
+
export function worktreeRegistryPath(projectRoot) {
|
|
25
|
+
return path.join(projectRoot, WORKTREE_REGISTRY_REL_PATH);
|
|
26
|
+
}
|
|
26
27
|
export function featureRootPath(projectRoot, featureId) {
|
|
27
|
-
return path.join(
|
|
28
|
+
return path.join(worktreesRoot(projectRoot), normalizedFeatureId(featureId));
|
|
28
29
|
}
|
|
29
30
|
export function featureArtifactsPath(projectRoot, featureId) {
|
|
30
|
-
return path.join(featureRootPath(projectRoot, featureId), "artifacts");
|
|
31
|
+
return path.join(featureRootPath(projectRoot, featureId), RUNTIME_ROOT, "artifacts");
|
|
31
32
|
}
|
|
32
33
|
export function featureStatePath(projectRoot, featureId) {
|
|
33
|
-
return path.join(featureRootPath(projectRoot, featureId), "state");
|
|
34
|
+
return path.join(featureRootPath(projectRoot, featureId), RUNTIME_ROOT, "state");
|
|
35
|
+
}
|
|
36
|
+
export function resolveFeatureWorkspacePath(projectRoot, entry) {
|
|
37
|
+
if (entry.path === ".") {
|
|
38
|
+
return projectRoot;
|
|
39
|
+
}
|
|
40
|
+
return path.resolve(projectRoot, entry.path);
|
|
34
41
|
}
|
|
35
42
|
function normalizedFeatureId(value) {
|
|
36
43
|
const candidate = value
|
|
@@ -45,64 +52,134 @@ function normalizedFeatureId(value) {
|
|
|
45
52
|
const clipped = candidate.slice(0, 64);
|
|
46
53
|
return FEATURE_ID_PATTERN.test(clipped) ? clipped : DEFAULT_FEATURE_ID;
|
|
47
54
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
55
|
+
function toRelativePath(projectRoot, absolutePath) {
|
|
56
|
+
const rel = path.relative(projectRoot, absolutePath);
|
|
57
|
+
if (!rel || rel.trim().length === 0) {
|
|
58
|
+
return ".";
|
|
53
59
|
}
|
|
54
|
-
|
|
55
|
-
|
|
60
|
+
return rel.split(path.sep).join("/");
|
|
61
|
+
}
|
|
62
|
+
function sanitizeWorkspaceSource(value) {
|
|
63
|
+
if (value === "git-worktree" || value === "workspace" || value === "legacy-snapshot") {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
return "workspace";
|
|
67
|
+
}
|
|
68
|
+
function sanitizeRegistryEntry(raw) {
|
|
69
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
70
|
+
return null;
|
|
56
71
|
}
|
|
72
|
+
const typed = raw;
|
|
73
|
+
const featureIdRaw = typeof typed.featureId === "string" ? typed.featureId : "";
|
|
74
|
+
const featureId = normalizedFeatureId(featureIdRaw);
|
|
75
|
+
if (!FEATURE_ID_PATTERN.test(featureId)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const branch = typeof typed.branch === "string" && typed.branch.trim().length > 0
|
|
79
|
+
? typed.branch.trim()
|
|
80
|
+
: (featureId === DEFAULT_FEATURE_ID ? "workspace/default" : `workspace/${featureId}`);
|
|
81
|
+
const pathRaw = typeof typed.path === "string" ? typed.path.trim() : "";
|
|
82
|
+
const workspacePath = pathRaw.length > 0 ? pathRaw : ".";
|
|
83
|
+
const createdAt = typeof typed.createdAt === "string" && typed.createdAt.trim().length > 0
|
|
84
|
+
? typed.createdAt.trim()
|
|
85
|
+
: new Date().toISOString();
|
|
86
|
+
return {
|
|
87
|
+
featureId,
|
|
88
|
+
branch,
|
|
89
|
+
path: workspacePath,
|
|
90
|
+
source: sanitizeWorkspaceSource(typed.source),
|
|
91
|
+
createdAt
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function dedupeEntries(entries) {
|
|
95
|
+
const byId = new Map();
|
|
57
96
|
for (const entry of entries) {
|
|
58
|
-
if (
|
|
59
|
-
|
|
97
|
+
if (!byId.has(entry.featureId)) {
|
|
98
|
+
byId.set(entry.featureId, entry);
|
|
60
99
|
}
|
|
61
|
-
await fs.rm(path.join(dirPath, entry.name), { recursive: true, force: true });
|
|
62
100
|
}
|
|
101
|
+
return [...byId.values()].sort((a, b) => a.featureId.localeCompare(b.featureId));
|
|
63
102
|
}
|
|
64
|
-
async function
|
|
65
|
-
const exclude = options.exclude ?? new Set();
|
|
66
|
-
const preserveTargetEntries = options.preserveTargetEntries ?? new Set();
|
|
67
|
-
await ensureDir(targetDir);
|
|
68
|
-
await clearDirectory(targetDir, preserveTargetEntries);
|
|
69
|
-
if (!(await exists(sourceDir))) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
let entries;
|
|
103
|
+
async function runGit(projectRoot, args) {
|
|
73
104
|
try {
|
|
74
|
-
|
|
105
|
+
const { stdout, stderr } = await execFileAsync("git", args, { cwd: projectRoot });
|
|
106
|
+
return { ok: true, stdout: stdout.trim(), stderr: stderr.trim() };
|
|
75
107
|
}
|
|
76
|
-
catch {
|
|
77
|
-
|
|
108
|
+
catch (error) {
|
|
109
|
+
const err = error;
|
|
110
|
+
return {
|
|
111
|
+
ok: false,
|
|
112
|
+
stdout: typeof err.stdout === "string" ? err.stdout.trim() : "",
|
|
113
|
+
stderr: typeof err.stderr === "string" && err.stderr.trim().length > 0
|
|
114
|
+
? err.stderr.trim()
|
|
115
|
+
: (err.message ?? "git command failed")
|
|
116
|
+
};
|
|
78
117
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
118
|
+
}
|
|
119
|
+
async function isGitRepository(projectRoot) {
|
|
120
|
+
const result = await runGit(projectRoot, ["rev-parse", "--is-inside-work-tree"]);
|
|
121
|
+
return result.ok && result.stdout === "true";
|
|
122
|
+
}
|
|
123
|
+
async function currentBranch(projectRoot) {
|
|
124
|
+
const result = await runGit(projectRoot, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
125
|
+
return result.ok && result.stdout.length > 0 ? result.stdout : "HEAD";
|
|
126
|
+
}
|
|
127
|
+
async function defaultStartPoint(projectRoot) {
|
|
128
|
+
const remoteHead = await runGit(projectRoot, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"]);
|
|
129
|
+
if (remoteHead.ok && remoteHead.stdout.length > 0) {
|
|
130
|
+
return remoteHead.stdout.replace(/^origin\//u, "");
|
|
92
131
|
}
|
|
132
|
+
return currentBranch(projectRoot);
|
|
133
|
+
}
|
|
134
|
+
function buildDefaultEntry(source, branch) {
|
|
135
|
+
return {
|
|
136
|
+
featureId: DEFAULT_FEATURE_ID,
|
|
137
|
+
branch,
|
|
138
|
+
path: ".",
|
|
139
|
+
source,
|
|
140
|
+
createdAt: new Date().toISOString()
|
|
141
|
+
};
|
|
93
142
|
}
|
|
94
|
-
async function
|
|
95
|
-
|
|
96
|
-
|
|
143
|
+
async function readRegistry(projectRoot) {
|
|
144
|
+
const filePath = worktreeRegistryPath(projectRoot);
|
|
145
|
+
if (!(await exists(filePath))) {
|
|
146
|
+
return {
|
|
147
|
+
schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
|
|
148
|
+
updatedAt: new Date().toISOString(),
|
|
149
|
+
entries: []
|
|
150
|
+
};
|
|
97
151
|
}
|
|
98
152
|
try {
|
|
99
|
-
const
|
|
100
|
-
|
|
153
|
+
const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
154
|
+
const entriesRaw = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
155
|
+
const entries = dedupeEntries(entriesRaw
|
|
156
|
+
.map((entry) => sanitizeRegistryEntry(entry))
|
|
157
|
+
.filter((entry) => entry !== null));
|
|
158
|
+
const updatedAt = typeof parsed.updatedAt === "string" && parsed.updatedAt.trim().length > 0
|
|
159
|
+
? parsed.updatedAt.trim()
|
|
160
|
+
: new Date().toISOString();
|
|
161
|
+
return {
|
|
162
|
+
schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
|
|
163
|
+
updatedAt,
|
|
164
|
+
entries
|
|
165
|
+
};
|
|
101
166
|
}
|
|
102
167
|
catch {
|
|
103
|
-
return
|
|
168
|
+
return {
|
|
169
|
+
schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
|
|
170
|
+
updatedAt: new Date().toISOString(),
|
|
171
|
+
entries: []
|
|
172
|
+
};
|
|
104
173
|
}
|
|
105
174
|
}
|
|
175
|
+
async function writeRegistry(projectRoot, registry) {
|
|
176
|
+
const normalized = {
|
|
177
|
+
schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
|
|
178
|
+
updatedAt: registry.updatedAt,
|
|
179
|
+
entries: dedupeEntries(registry.entries)
|
|
180
|
+
};
|
|
181
|
+
await writeFileSafe(worktreeRegistryPath(projectRoot), `${JSON.stringify(normalized, null, 2)}\n`);
|
|
182
|
+
}
|
|
106
183
|
async function readActiveFeatureMetaInternal(projectRoot) {
|
|
107
184
|
const filePath = activeFeatureMetaPath(projectRoot);
|
|
108
185
|
if (!(await exists(filePath))) {
|
|
@@ -138,84 +215,94 @@ async function writeActiveFeatureMeta(projectRoot, meta) {
|
|
|
138
215
|
};
|
|
139
216
|
await writeFileSafe(activeFeatureMetaPath(projectRoot), `${JSON.stringify(normalized, null, 2)}\n`);
|
|
140
217
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
await ensureDir(featureArtifactsPath(projectRoot, id));
|
|
144
|
-
await ensureDir(featureStatePath(projectRoot, id));
|
|
218
|
+
function registryHasFeature(registry, featureId) {
|
|
219
|
+
return registry.entries.some((entry) => entry.featureId === featureId);
|
|
145
220
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return normalizedFeatureId(meta.activeFeature);
|
|
221
|
+
function findEntry(registry, featureId) {
|
|
222
|
+
return registry.entries.find((entry) => entry.featureId === featureId);
|
|
149
223
|
}
|
|
150
|
-
|
|
151
|
-
const root =
|
|
224
|
+
async function listLegacySnapshotIds(projectRoot) {
|
|
225
|
+
const root = legacyFeaturesRoot(projectRoot);
|
|
152
226
|
if (!(await exists(root))) {
|
|
153
227
|
return [];
|
|
154
228
|
}
|
|
155
|
-
let entries;
|
|
156
229
|
try {
|
|
157
|
-
entries = await fs.readdir(root, { withFileTypes: true });
|
|
230
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
231
|
+
return entries
|
|
232
|
+
.filter((entry) => entry.isDirectory() && FEATURE_ID_PATTERN.test(entry.name))
|
|
233
|
+
.map((entry) => entry.name)
|
|
234
|
+
.sort((a, b) => a.localeCompare(b));
|
|
158
235
|
}
|
|
159
236
|
catch {
|
|
160
237
|
return [];
|
|
161
238
|
}
|
|
162
|
-
return entries
|
|
163
|
-
.filter((entry) => entry.isDirectory() && FEATURE_ID_PATTERN.test(entry.name))
|
|
164
|
-
.map((entry) => entry.name)
|
|
165
|
-
.sort((a, b) => a.localeCompare(b));
|
|
166
239
|
}
|
|
167
|
-
|
|
168
|
-
await ensureDir(
|
|
169
|
-
await ensureDir(
|
|
170
|
-
await
|
|
171
|
-
const
|
|
172
|
-
const
|
|
173
|
-
await
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const featureStateHasData = await dirHasEntries(featureStatePath(projectRoot, activeFeature));
|
|
178
|
-
if ((runtimeArtifactsHasData || runtimeStateHasData) && !featureArtifactsHasData && !featureStateHasData) {
|
|
179
|
-
await copyDirectoryContents(runtimeArtifactsRoot(projectRoot), featureArtifactsPath(projectRoot, activeFeature));
|
|
180
|
-
await copyDirectoryContents(runtimeStateRoot(projectRoot), featureStatePath(projectRoot, activeFeature), { exclude: FEATURE_STATE_EXCLUDE_FROM_SNAPSHOT });
|
|
181
|
-
}
|
|
182
|
-
else if ((!runtimeArtifactsHasData && !runtimeStateHasData) && (featureArtifactsHasData || featureStateHasData)) {
|
|
183
|
-
await copyDirectoryContents(featureArtifactsPath(projectRoot, activeFeature), runtimeArtifactsRoot(projectRoot));
|
|
184
|
-
await copyDirectoryContents(featureStatePath(projectRoot, activeFeature), runtimeStateRoot(projectRoot), { preserveTargetEntries: new Set(["active-feature.json"]) });
|
|
240
|
+
async function ensureRegistryState(projectRoot) {
|
|
241
|
+
await ensureDir(path.join(projectRoot, RUNTIME_ROOT, "state"));
|
|
242
|
+
await ensureDir(worktreesRoot(projectRoot));
|
|
243
|
+
const gitRepo = await isGitRepository(projectRoot);
|
|
244
|
+
const source = gitRepo ? "git-worktree" : "workspace";
|
|
245
|
+
const branch = gitRepo ? await currentBranch(projectRoot) : "workspace/default";
|
|
246
|
+
const currentRegistry = await readRegistry(projectRoot);
|
|
247
|
+
const entries = [...currentRegistry.entries];
|
|
248
|
+
if (!entries.some((entry) => entry.featureId === DEFAULT_FEATURE_ID)) {
|
|
249
|
+
entries.push(buildDefaultEntry(source, branch));
|
|
185
250
|
}
|
|
186
|
-
const
|
|
187
|
-
|
|
251
|
+
const legacyFeatureIds = await listLegacySnapshotIds(projectRoot);
|
|
252
|
+
for (const legacyId of legacyFeatureIds) {
|
|
253
|
+
if (entries.some((entry) => entry.featureId === legacyId)) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
entries.push({
|
|
257
|
+
featureId: legacyId,
|
|
258
|
+
branch: `legacy/${legacyId}`,
|
|
259
|
+
path: `${LEGACY_FEATURES_DIR_REL_PATH}/${legacyId}`,
|
|
260
|
+
source: "legacy-snapshot",
|
|
261
|
+
createdAt: new Date().toISOString()
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
const registry = {
|
|
265
|
+
schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
|
|
266
|
+
updatedAt: new Date().toISOString(),
|
|
267
|
+
entries: dedupeEntries(entries)
|
|
268
|
+
};
|
|
269
|
+
await writeRegistry(projectRoot, registry);
|
|
270
|
+
const active = await readActiveFeatureMetaInternal(projectRoot);
|
|
271
|
+
const normalizedActive = registryHasFeature(registry, active.activeFeature)
|
|
272
|
+
? active.activeFeature
|
|
273
|
+
: DEFAULT_FEATURE_ID;
|
|
274
|
+
const activeMeta = {
|
|
275
|
+
activeFeature: normalizedActive,
|
|
188
276
|
updatedAt: new Date().toISOString()
|
|
189
277
|
};
|
|
190
|
-
await writeActiveFeatureMeta(projectRoot,
|
|
191
|
-
return
|
|
278
|
+
await writeActiveFeatureMeta(projectRoot, activeMeta);
|
|
279
|
+
return { registry, activeMeta };
|
|
280
|
+
}
|
|
281
|
+
export async function ensureFeatureSystem(projectRoot) {
|
|
282
|
+
const { activeMeta } = await ensureRegistryState(projectRoot);
|
|
283
|
+
return activeMeta;
|
|
284
|
+
}
|
|
285
|
+
export async function readFeatureWorktreeRegistry(projectRoot) {
|
|
286
|
+
const { registry } = await ensureRegistryState(projectRoot);
|
|
287
|
+
return registry;
|
|
288
|
+
}
|
|
289
|
+
export async function readActiveFeature(projectRoot) {
|
|
290
|
+
const meta = await ensureFeatureSystem(projectRoot);
|
|
291
|
+
return normalizedFeatureId(meta.activeFeature);
|
|
292
|
+
}
|
|
293
|
+
export async function listFeatures(projectRoot) {
|
|
294
|
+
const registry = await readFeatureWorktreeRegistry(projectRoot);
|
|
295
|
+
return registry.entries.map((entry) => entry.featureId).sort((a, b) => a.localeCompare(b));
|
|
192
296
|
}
|
|
193
297
|
export async function syncActiveFeatureSnapshot(projectRoot) {
|
|
194
|
-
|
|
195
|
-
await ensureFeatureSnapshot(projectRoot, activeFeature);
|
|
196
|
-
await copyDirectoryContents(runtimeArtifactsRoot(projectRoot), featureArtifactsPath(projectRoot, activeFeature));
|
|
197
|
-
await copyDirectoryContents(runtimeStateRoot(projectRoot), featureStatePath(projectRoot, activeFeature), {
|
|
198
|
-
exclude: FEATURE_STATE_EXCLUDE_FROM_SNAPSHOT
|
|
199
|
-
});
|
|
298
|
+
await ensureFeatureSystem(projectRoot);
|
|
200
299
|
}
|
|
201
300
|
export async function switchActiveFeature(projectRoot, featureId) {
|
|
202
|
-
await
|
|
203
|
-
const current = await readActiveFeature(projectRoot);
|
|
301
|
+
const registry = await readFeatureWorktreeRegistry(projectRoot);
|
|
204
302
|
const target = normalizedFeatureId(featureId);
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
updatedAt: new Date().toISOString()
|
|
209
|
-
};
|
|
210
|
-
await writeActiveFeatureMeta(projectRoot, unchanged);
|
|
211
|
-
return unchanged;
|
|
212
|
-
}
|
|
213
|
-
await syncActiveFeatureSnapshot(projectRoot);
|
|
214
|
-
await ensureFeatureSnapshot(projectRoot, target);
|
|
215
|
-
await copyDirectoryContents(featureArtifactsPath(projectRoot, target), runtimeArtifactsRoot(projectRoot));
|
|
216
|
-
await copyDirectoryContents(featureStatePath(projectRoot, target), runtimeStateRoot(projectRoot), {
|
|
217
|
-
preserveTargetEntries: new Set(["active-feature.json"])
|
|
218
|
-
});
|
|
303
|
+
if (!registryHasFeature(registry, target)) {
|
|
304
|
+
throw new Error(`Feature "${target}" is not registered. Create it first with /cc-ops feature new ${target}.`);
|
|
305
|
+
}
|
|
219
306
|
const nextMeta = {
|
|
220
307
|
activeFeature: target,
|
|
221
308
|
updatedAt: new Date().toISOString()
|
|
@@ -224,24 +311,78 @@ export async function switchActiveFeature(projectRoot, featureId) {
|
|
|
224
311
|
return nextMeta;
|
|
225
312
|
}
|
|
226
313
|
export async function createFeature(projectRoot, rawFeatureId, options = {}) {
|
|
227
|
-
await
|
|
314
|
+
const registry = await readFeatureWorktreeRegistry(projectRoot);
|
|
228
315
|
const featureId = normalizedFeatureId(rawFeatureId);
|
|
229
|
-
if (featureId === DEFAULT_FEATURE_ID &&
|
|
316
|
+
if (featureId === DEFAULT_FEATURE_ID &&
|
|
317
|
+
rawFeatureId.trim().length > 0 &&
|
|
318
|
+
rawFeatureId.trim().toLowerCase() !== "default") {
|
|
230
319
|
throw new Error(`Unable to create feature from "${rawFeatureId}" — use letters, numbers, and dashes.`);
|
|
231
320
|
}
|
|
232
|
-
|
|
233
|
-
if (await exists(featureDir)) {
|
|
321
|
+
if (registryHasFeature(registry, featureId)) {
|
|
234
322
|
throw new Error(`Feature "${featureId}" already exists.`);
|
|
235
323
|
}
|
|
236
|
-
await
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
324
|
+
const isGit = await isGitRepository(projectRoot);
|
|
325
|
+
let entry;
|
|
326
|
+
if (isGit) {
|
|
327
|
+
const workspacePath = featureRootPath(projectRoot, featureId);
|
|
328
|
+
if (await exists(workspacePath)) {
|
|
329
|
+
throw new Error(`Worktree path already exists: ${workspacePath}`);
|
|
330
|
+
}
|
|
331
|
+
await ensureDir(path.dirname(workspacePath));
|
|
332
|
+
const branch = `feature/${featureId}`;
|
|
333
|
+
const localBranchRef = `refs/heads/${branch}`;
|
|
334
|
+
const branchCheck = await runGit(projectRoot, ["show-ref", "--verify", "--quiet", localBranchRef]);
|
|
335
|
+
if (branchCheck.ok) {
|
|
336
|
+
const addExisting = await runGit(projectRoot, ["worktree", "add", workspacePath, branch]);
|
|
337
|
+
if (!addExisting.ok) {
|
|
338
|
+
throw new Error(`Unable to attach worktree for branch "${branch}": ${addExisting.stderr}`);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
const startPoint = options.cloneActive === false
|
|
343
|
+
? await defaultStartPoint(projectRoot)
|
|
344
|
+
: "HEAD";
|
|
345
|
+
const addNew = await runGit(projectRoot, ["worktree", "add", "-b", branch, workspacePath, startPoint]);
|
|
346
|
+
if (!addNew.ok) {
|
|
347
|
+
throw new Error(`Unable to create worktree "${featureId}" on branch "${branch}": ${addNew.stderr}`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
entry = {
|
|
351
|
+
featureId,
|
|
352
|
+
branch,
|
|
353
|
+
path: toRelativePath(projectRoot, workspacePath),
|
|
354
|
+
source: "git-worktree",
|
|
355
|
+
createdAt: new Date().toISOString()
|
|
356
|
+
};
|
|
242
357
|
}
|
|
358
|
+
else {
|
|
359
|
+
const workspacePath = featureRootPath(projectRoot, featureId);
|
|
360
|
+
await ensureDir(workspacePath);
|
|
361
|
+
entry = {
|
|
362
|
+
featureId,
|
|
363
|
+
branch: `workspace/${featureId}`,
|
|
364
|
+
path: toRelativePath(projectRoot, workspacePath),
|
|
365
|
+
source: "workspace",
|
|
366
|
+
createdAt: new Date().toISOString()
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
const nextRegistry = {
|
|
370
|
+
schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
|
|
371
|
+
updatedAt: new Date().toISOString(),
|
|
372
|
+
entries: dedupeEntries([...registry.entries, entry])
|
|
373
|
+
};
|
|
374
|
+
await writeRegistry(projectRoot, nextRegistry);
|
|
243
375
|
if (options.switchTo === true) {
|
|
244
376
|
await switchActiveFeature(projectRoot, featureId);
|
|
245
377
|
}
|
|
246
378
|
return featureId;
|
|
247
379
|
}
|
|
380
|
+
export async function activeFeatureWorkspacePath(projectRoot) {
|
|
381
|
+
const registry = await readFeatureWorktreeRegistry(projectRoot);
|
|
382
|
+
const active = await readActiveFeature(projectRoot);
|
|
383
|
+
const entry = findEntry(registry, active);
|
|
384
|
+
if (!entry) {
|
|
385
|
+
return projectRoot;
|
|
386
|
+
}
|
|
387
|
+
return resolveFeatureWorkspacePath(projectRoot, entry);
|
|
388
|
+
}
|
package/dist/harness-adapters.js
CHANGED
|
@@ -18,6 +18,12 @@ const UTILITY_SHIMS = [
|
|
|
18
18
|
skillFolder: "flow-next-step",
|
|
19
19
|
commandFile: "next.md"
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
fileName: "cc-ideate.md",
|
|
23
|
+
command: "ideate",
|
|
24
|
+
skillFolder: "flow-ideate",
|
|
25
|
+
commandFile: "ideate.md"
|
|
26
|
+
},
|
|
21
27
|
{
|
|
22
28
|
fileName: "cc-view.md",
|
|
23
29
|
command: "view",
|
|
@@ -99,6 +105,15 @@ function agentsMdBlock() {
|
|
|
99
105
|
> Auto-generated by \`cclaw sync\`. Do not edit this managed block manually.
|
|
100
106
|
> Existing project rules in this repository take precedence over cclaw defaults.
|
|
101
107
|
|
|
108
|
+
## Anti-Slop Guard
|
|
109
|
+
|
|
110
|
+
Treat quality as a hard requirement, not style preference:
|
|
111
|
+
|
|
112
|
+
1. Confirm there is a real problem statement before proposing broad changes.
|
|
113
|
+
2. Prefer one focused change over bundled unrelated edits.
|
|
114
|
+
3. Verify claims with fresh evidence in this turn.
|
|
115
|
+
4. If uncertain, escalate with options instead of fabricating certainty.
|
|
116
|
+
|
|
102
117
|
### Activation Rule
|
|
103
118
|
|
|
104
119
|
Before responding to a coding request:
|
|
@@ -133,9 +148,10 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
|
|
|
133
148
|
|---|---|
|
|
134
149
|
| \`/cc\` | **Entry point.** No args = resume current stage. With prompt = classify task and start the right flow. |
|
|
135
150
|
| \`/cc-next\` | **Progression.** Advances to the next stage when current is complete. |
|
|
151
|
+
| \`/cc-ideate\` | **Discovery mode.** Generates a ranked repo-improvement backlog before implementation. |
|
|
136
152
|
| \`/cc-view\` | **Read-only router.** Unified entry for status/tree/diff views. |
|
|
137
153
|
| \`/cc-learn\` | **Cross-cutting.** Capture or review project knowledge (append-only JSONL). |
|
|
138
|
-
| \`/cc-ops\` | **Operations router.** Unified entry for feature/tdd-log/retro/archive/rewind actions. |
|
|
154
|
+
| \`/cc-ops\` | **Operations router.** Unified entry for feature/tdd-log/retro/compound/archive/rewind actions. |
|
|
139
155
|
|
|
140
156
|
**Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship.
|
|
141
157
|
\`/cc-next\` loads the right stage skill automatically. Gates must pass before handoff.
|
package/dist/install.js
CHANGED
|
@@ -8,6 +8,7 @@ import { commandContract } from "./content/contracts.js";
|
|
|
8
8
|
import { contextModeFiles, createInitialContextModeState } from "./content/contexts.js";
|
|
9
9
|
import { learnSkillMarkdown, learnCommandContract } from "./content/learnings.js";
|
|
10
10
|
import { nextCommandContract, nextCommandSkillMarkdown } from "./content/next-command.js";
|
|
11
|
+
import { ideateCommandContract, ideateCommandSkillMarkdown } from "./content/ideate-command.js";
|
|
11
12
|
import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
|
|
12
13
|
import { statusCommandContract, statusCommandSkillMarkdown } from "./content/status-command.js";
|
|
13
14
|
import { treeCommandContract, treeCommandSkillMarkdown } from "./content/tree-command.js";
|
|
@@ -17,8 +18,9 @@ import { opsCommandContract, opsCommandSkillMarkdown } from "./content/ops-comma
|
|
|
17
18
|
import { featureCommandContract, featureCommandSkillMarkdown } from "./content/feature-command.js";
|
|
18
19
|
import { tddLogCommandContract, tddLogCommandSkillMarkdown } from "./content/tdd-log-command.js";
|
|
19
20
|
import { retroCommandContract, retroCommandSkillMarkdown } from "./content/retro-command.js";
|
|
21
|
+
import { compoundCommandContract, compoundCommandSkillMarkdown } from "./content/compound-command.js";
|
|
20
22
|
import { archiveCommandContract, archiveCommandSkillMarkdown } from "./content/archive-command.js";
|
|
21
|
-
import {
|
|
23
|
+
import { rewindCommandContract, rewindCommandSkillMarkdown } from "./content/rewind-command.js";
|
|
22
24
|
import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
|
|
23
25
|
import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
|
|
24
26
|
import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, cursorHooksJson, codexHooksJson } from "./content/hooks.js";
|
|
@@ -204,15 +206,17 @@ async function writeSkills(projectRoot, config) {
|
|
|
204
206
|
// Utility skills (not flow stages)
|
|
205
207
|
await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
|
|
206
208
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-next-step", "SKILL.md"), nextCommandSkillMarkdown());
|
|
209
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-ideate", "SKILL.md"), ideateCommandSkillMarkdown());
|
|
207
210
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
|
|
208
211
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-view", "SKILL.md"), viewCommandSkillMarkdown());
|
|
209
212
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-status", "SKILL.md"), statusCommandSkillMarkdown());
|
|
210
213
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-tree", "SKILL.md"), treeCommandSkillMarkdown());
|
|
211
214
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-diff", "SKILL.md"), diffCommandSkillMarkdown());
|
|
212
215
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-ops", "SKILL.md"), opsCommandSkillMarkdown());
|
|
213
|
-
await writeFileSafe(runtimePath(projectRoot, "skills", "
|
|
216
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "using-git-worktrees", "SKILL.md"), featureCommandSkillMarkdown());
|
|
214
217
|
await writeFileSafe(runtimePath(projectRoot, "skills", "tdd-cycle-log", "SKILL.md"), tddLogCommandSkillMarkdown());
|
|
215
218
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-retro", "SKILL.md"), retroCommandSkillMarkdown());
|
|
219
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-compound", "SKILL.md"), compoundCommandSkillMarkdown());
|
|
216
220
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-rewind", "SKILL.md"), rewindCommandSkillMarkdown());
|
|
217
221
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-archive", "SKILL.md"), archiveCommandSkillMarkdown());
|
|
218
222
|
await writeFileSafe(runtimePath(projectRoot, "skills", "subagent-dev", "SKILL.md"), subagentDrivenDevSkill());
|
|
@@ -266,6 +270,7 @@ async function writeSkills(projectRoot, config) {
|
|
|
266
270
|
async function writeUtilityCommands(projectRoot) {
|
|
267
271
|
await writeFileSafe(runtimePath(projectRoot, "commands", "learn.md"), learnCommandContract());
|
|
268
272
|
await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
|
|
273
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
|
|
269
274
|
await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
|
|
270
275
|
await writeFileSafe(runtimePath(projectRoot, "commands", "start.md"), startCommandContract());
|
|
271
276
|
await writeFileSafe(runtimePath(projectRoot, "commands", "status.md"), statusCommandContract());
|
|
@@ -275,9 +280,9 @@ async function writeUtilityCommands(projectRoot) {
|
|
|
275
280
|
await writeFileSafe(runtimePath(projectRoot, "commands", "feature.md"), featureCommandContract());
|
|
276
281
|
await writeFileSafe(runtimePath(projectRoot, "commands", "tdd-log.md"), tddLogCommandContract());
|
|
277
282
|
await writeFileSafe(runtimePath(projectRoot, "commands", "retro.md"), retroCommandContract());
|
|
283
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", "compound.md"), compoundCommandContract());
|
|
278
284
|
await writeFileSafe(runtimePath(projectRoot, "commands", "archive.md"), archiveCommandContract());
|
|
279
285
|
await writeFileSafe(runtimePath(projectRoot, "commands", "rewind.md"), rewindCommandContract());
|
|
280
|
-
await writeFileSafe(runtimePath(projectRoot, "commands", "rewind-ack.md"), rewindAcknowledgeCommandContract());
|
|
281
286
|
}
|
|
282
287
|
function toObject(value) {
|
|
283
288
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -827,10 +832,6 @@ async function ensureSessionStateFiles(projectRoot) {
|
|
|
827
832
|
if (!(await exists(knowledgeDigestPath))) {
|
|
828
833
|
await writeFileSafe(knowledgeDigestPath, "# Knowledge digest (auto-generated)\n\n(no entries yet)\n");
|
|
829
834
|
}
|
|
830
|
-
const preambleLogPath = path.join(stateDir, "preamble-log.jsonl");
|
|
831
|
-
if (!(await exists(preambleLogPath))) {
|
|
832
|
-
await writeFileSafe(preambleLogPath, "");
|
|
833
|
-
}
|
|
834
835
|
const tddCycleLogPath = path.join(stateDir, "tdd-cycle-log.jsonl");
|
|
835
836
|
if (!(await exists(tddCycleLogPath))) {
|
|
836
837
|
await writeFileSafe(tddCycleLogPath, "");
|
|
@@ -945,7 +946,8 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
945
946
|
"session-guidelines",
|
|
946
947
|
"security-review",
|
|
947
948
|
"documentation",
|
|
948
|
-
"browser-qa-testing"
|
|
949
|
+
"browser-qa-testing",
|
|
950
|
+
"feature-workspaces"
|
|
949
951
|
]) {
|
|
950
952
|
try {
|
|
951
953
|
await fs.rm(runtimePath(projectRoot, "skills", legacyFolder), {
|
package/dist/policy.js
CHANGED
|
@@ -86,10 +86,13 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
86
86
|
const utilitySkillChecks = [
|
|
87
87
|
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "strict JSONL schema", name: "utility_skill:learnings:jsonl_schema" },
|
|
88
88
|
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "knowledge.jsonl", name: "utility_skill:learnings:jsonl_store" },
|
|
89
|
-
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "type, trigger, action, confidence, domain, stage, created, project", name: "utility_skill:learnings:field_order" },
|
|
89
|
+
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "type, trigger, action, confidence, domain, stage, origin_stage, origin_feature, frequency, universality, maturity, created, first_seen_ts, last_seen_ts, project", name: "utility_skill:learnings:field_order" },
|
|
90
90
|
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "## Subcommands", name: "utility_skill:learnings:subcommands" },
|
|
91
91
|
{ file: runtimeFile("skills/learnings/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:learnings:hard_gate" },
|
|
92
92
|
{ file: runtimeFile("commands/learn.md"), needle: "## Subcommands", name: "utility_command:learn:subcommands" },
|
|
93
|
+
{ file: runtimeFile("commands/ideate.md"), needle: "## Algorithm", name: "utility_command:ideate:algorithm" },
|
|
94
|
+
{ file: runtimeFile("skills/flow-ideate/SKILL.md"), needle: "## Protocol", name: "utility_skill:ideate:protocol" },
|
|
95
|
+
{ file: runtimeFile("skills/flow-ideate/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:ideate:hard_gate" },
|
|
93
96
|
{ file: runtimeFile("commands/status.md"), needle: "bar:", name: "utility_command:status:visual_bar" },
|
|
94
97
|
{ file: runtimeFile("commands/status.md"), needle: "/cc-view tree · /cc-view diff", name: "utility_command:status:tree_diff_link" },
|
|
95
98
|
{ file: runtimeFile("commands/tree.md"), needle: "## Algorithm", name: "utility_command:tree:algorithm" },
|
|
@@ -99,16 +102,18 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
99
102
|
{ file: runtimeFile("skills/flow-diff/SKILL.md"), needle: "## Protocol", name: "utility_skill:diff:protocol" },
|
|
100
103
|
{ file: runtimeFile("skills/flow-diff/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:diff:hard_gate" },
|
|
101
104
|
{ file: runtimeFile("commands/feature.md"), needle: "## Subcommands", name: "utility_command:feature:subcommands" },
|
|
102
|
-
{ file: runtimeFile("skills/
|
|
103
|
-
{ file: runtimeFile("skills/
|
|
105
|
+
{ file: runtimeFile("skills/using-git-worktrees/SKILL.md"), needle: "## Protocol", name: "utility_skill:feature:protocol" },
|
|
106
|
+
{ file: runtimeFile("skills/using-git-worktrees/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:feature:hard_gate" },
|
|
104
107
|
{ file: runtimeFile("commands/tdd-log.md"), needle: "## Subcommands", name: "utility_command:tdd_log:subcommands" },
|
|
105
108
|
{ file: runtimeFile("skills/tdd-cycle-log/SKILL.md"), needle: "## Protocol", name: "utility_skill:tdd_log:protocol" },
|
|
106
109
|
{ file: runtimeFile("skills/tdd-cycle-log/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:tdd_log:hard_gate" },
|
|
107
110
|
{ file: runtimeFile("commands/retro.md"), needle: "## Algorithm", name: "utility_command:retro:algorithm" },
|
|
108
111
|
{ file: runtimeFile("skills/flow-retro/SKILL.md"), needle: "## Protocol", name: "utility_skill:retro:protocol" },
|
|
109
112
|
{ file: runtimeFile("skills/flow-retro/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:retro:hard_gate" },
|
|
113
|
+
{ file: runtimeFile("commands/compound.md"), needle: "## Algorithm", name: "utility_command:compound:algorithm" },
|
|
114
|
+
{ file: runtimeFile("skills/flow-compound/SKILL.md"), needle: "## Protocol", name: "utility_skill:compound:protocol" },
|
|
115
|
+
{ file: runtimeFile("skills/flow-compound/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:compound:hard_gate" },
|
|
110
116
|
{ file: runtimeFile("commands/rewind.md"), needle: "## Algorithm", name: "utility_command:rewind:algorithm" },
|
|
111
|
-
{ file: runtimeFile("commands/rewind-ack.md"), needle: "## Algorithm", name: "utility_command:rewind_ack:algorithm" },
|
|
112
117
|
{ file: runtimeFile("skills/flow-rewind/SKILL.md"), needle: "## Protocol", name: "utility_skill:rewind:protocol" },
|
|
113
118
|
{ file: runtimeFile("skills/flow-rewind/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:rewind:hard_gate" },
|
|
114
119
|
{ file: runtimeFile("skills/subagent-dev/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:sdd:hard_gate" },
|
|
@@ -157,6 +162,10 @@ export async function policyChecks(projectRoot, options = {}) {
|
|
|
157
162
|
{ file: runtimeFile("skills/executing-plans/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:executing_plans:hard_gate" },
|
|
158
163
|
{ file: runtimeFile("skills/executing-plans/SKILL.md"), needle: "## Execution Protocol", name: "utility_skill:executing_plans:protocol" },
|
|
159
164
|
{ file: runtimeFile("skills/executing-plans/SKILL.md"), needle: "## Wave Checklist", name: "utility_skill:executing_plans:waves" },
|
|
165
|
+
{ file: runtimeFile("skills/verification-before-completion/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:verification_before_completion:hard_gate" },
|
|
166
|
+
{ file: runtimeFile("skills/verification-before-completion/SKILL.md"), needle: "## Protocol", name: "utility_skill:verification_before_completion:protocol" },
|
|
167
|
+
{ file: runtimeFile("skills/finishing-a-development-branch/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:finishing_branch:hard_gate" },
|
|
168
|
+
{ file: runtimeFile("skills/finishing-a-development-branch/SKILL.md"), needle: "## Protocol", name: "utility_skill:finishing_branch:protocol" },
|
|
160
169
|
{ file: runtimeFile("skills/context-engineering/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:context_engineering:hard_gate" },
|
|
161
170
|
{ file: runtimeFile("skills/context-engineering/SKILL.md"), needle: "## Context Modes", name: "utility_skill:context_engineering:modes" },
|
|
162
171
|
{ file: runtimeFile("skills/context-engineering/SKILL.md"), needle: "## Mode Switching Protocol", name: "utility_skill:context_engineering:switch" },
|