cclaw-cli 0.49.0 → 0.51.1

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.
Files changed (183) hide show
  1. package/README.md +57 -84
  2. package/dist/artifact-linter.d.ts +4 -0
  3. package/dist/artifact-linter.js +24 -3
  4. package/dist/cli.d.ts +1 -19
  5. package/dist/cli.js +49 -491
  6. package/dist/constants.d.ts +2 -13
  7. package/dist/constants.js +1 -43
  8. package/dist/content/closeout-guidance.d.ts +14 -0
  9. package/dist/content/closeout-guidance.js +42 -0
  10. package/dist/content/core-agents.js +55 -17
  11. package/dist/content/decision-protocol.d.ts +12 -0
  12. package/dist/content/decision-protocol.js +20 -0
  13. package/dist/content/diff-command.d.ts +1 -2
  14. package/dist/content/diff-command.js +8 -94
  15. package/dist/content/examples.d.ts +4 -10
  16. package/dist/content/examples.js +10 -20
  17. package/dist/content/hook-events.js +2 -2
  18. package/dist/content/hook-inline-snippets.d.ts +5 -2
  19. package/dist/content/hook-inline-snippets.js +33 -1
  20. package/dist/content/hook-manifest.d.ts +3 -4
  21. package/dist/content/hook-manifest.js +11 -12
  22. package/dist/content/hooks.js +44 -21
  23. package/dist/content/ideate-command.d.ts +2 -0
  24. package/dist/content/ideate-command.js +34 -25
  25. package/dist/content/iron-laws.d.ts +5 -5
  26. package/dist/content/iron-laws.js +5 -5
  27. package/dist/content/language-policy.d.ts +2 -0
  28. package/dist/content/language-policy.js +13 -0
  29. package/dist/content/learnings.d.ts +3 -4
  30. package/dist/content/learnings.js +26 -50
  31. package/dist/content/meta-skill.js +33 -22
  32. package/dist/content/next-command.js +41 -38
  33. package/dist/content/node-hooks.js +17 -345
  34. package/dist/content/opencode-plugin.js +5 -103
  35. package/dist/content/research-playbooks.js +14 -14
  36. package/dist/content/review-loop.d.ts +2 -0
  37. package/dist/content/review-loop.js +8 -0
  38. package/dist/content/session-hooks.js +15 -47
  39. package/dist/content/skills.d.ts +0 -5
  40. package/dist/content/skills.js +55 -128
  41. package/dist/content/stage-common-guidance.d.ts +0 -1
  42. package/dist/content/stage-common-guidance.js +17 -14
  43. package/dist/content/stage-schema.d.ts +26 -1
  44. package/dist/content/stage-schema.js +121 -40
  45. package/dist/content/stages/_lint-metadata/index.js +9 -15
  46. package/dist/content/stages/brainstorm.js +22 -43
  47. package/dist/content/stages/design.js +37 -57
  48. package/dist/content/stages/plan.js +22 -13
  49. package/dist/content/stages/review.js +24 -27
  50. package/dist/content/stages/scope.js +34 -46
  51. package/dist/content/stages/ship.js +7 -4
  52. package/dist/content/stages/spec.js +20 -9
  53. package/dist/content/stages/tdd.js +64 -44
  54. package/dist/content/start-command.js +13 -12
  55. package/dist/content/status-command.d.ts +2 -7
  56. package/dist/content/status-command.js +19 -146
  57. package/dist/content/subagents.d.ts +0 -5
  58. package/dist/content/subagents.js +51 -28
  59. package/dist/content/templates.d.ts +1 -1
  60. package/dist/content/templates.js +126 -135
  61. package/dist/content/track-render-context.d.ts +17 -0
  62. package/dist/content/track-render-context.js +44 -0
  63. package/dist/content/tree-command.d.ts +1 -2
  64. package/dist/content/tree-command.js +4 -87
  65. package/dist/content/utility-skills.d.ts +2 -29
  66. package/dist/content/utility-skills.js +2 -1534
  67. package/dist/content/view-command.js +31 -11
  68. package/dist/delegation.d.ts +1 -1
  69. package/dist/delegation.js +5 -15
  70. package/dist/doctor-registry.js +20 -21
  71. package/dist/doctor.js +88 -344
  72. package/dist/flow-state.d.ts +3 -0
  73. package/dist/flow-state.js +2 -0
  74. package/dist/harness-adapters.d.ts +1 -1
  75. package/dist/harness-adapters.js +51 -58
  76. package/dist/install.js +128 -358
  77. package/dist/internal/advance-stage.js +3 -9
  78. package/dist/internal/compound-readiness.d.ts +1 -1
  79. package/dist/internal/compound-readiness.js +1 -1
  80. package/dist/internal/tdd-loop-status.d.ts +1 -1
  81. package/dist/internal/tdd-loop-status.js +1 -1
  82. package/dist/knowledge-store.d.ts +16 -10
  83. package/dist/knowledge-store.js +51 -15
  84. package/dist/policy.js +16 -105
  85. package/dist/run-archive.d.ts +4 -6
  86. package/dist/run-archive.js +15 -20
  87. package/dist/run-persistence.d.ts +2 -2
  88. package/dist/run-persistence.js +3 -9
  89. package/package.json +1 -2
  90. package/dist/content/archive-command.d.ts +0 -2
  91. package/dist/content/archive-command.js +0 -124
  92. package/dist/content/compound-command.d.ts +0 -5
  93. package/dist/content/compound-command.js +0 -193
  94. package/dist/content/contexts.d.ts +0 -18
  95. package/dist/content/contexts.js +0 -24
  96. package/dist/content/contracts.d.ts +0 -2
  97. package/dist/content/contracts.js +0 -51
  98. package/dist/content/doctor-references.d.ts +0 -2
  99. package/dist/content/doctor-references.js +0 -150
  100. package/dist/content/eval-scaffold.d.ts +0 -15
  101. package/dist/content/eval-scaffold.js +0 -370
  102. package/dist/content/feature-command.d.ts +0 -2
  103. package/dist/content/feature-command.js +0 -123
  104. package/dist/content/flow-map.d.ts +0 -23
  105. package/dist/content/flow-map.js +0 -134
  106. package/dist/content/harness-doc.d.ts +0 -2
  107. package/dist/content/harness-doc.js +0 -202
  108. package/dist/content/harness-playbooks.d.ts +0 -24
  109. package/dist/content/harness-playbooks.js +0 -393
  110. package/dist/content/harness-tool-refs.d.ts +0 -20
  111. package/dist/content/harness-tool-refs.js +0 -268
  112. package/dist/content/ops-command.d.ts +0 -2
  113. package/dist/content/ops-command.js +0 -71
  114. package/dist/content/protocols.d.ts +0 -7
  115. package/dist/content/protocols.js +0 -215
  116. package/dist/content/retro-command.d.ts +0 -2
  117. package/dist/content/retro-command.js +0 -165
  118. package/dist/content/rewind-command.d.ts +0 -2
  119. package/dist/content/rewind-command.js +0 -106
  120. package/dist/content/tdd-log-command.d.ts +0 -2
  121. package/dist/content/tdd-log-command.js +0 -85
  122. package/dist/eval/agents/single-shot.d.ts +0 -27
  123. package/dist/eval/agents/single-shot.js +0 -79
  124. package/dist/eval/agents/with-tools.d.ts +0 -44
  125. package/dist/eval/agents/with-tools.js +0 -261
  126. package/dist/eval/agents/workflow.d.ts +0 -31
  127. package/dist/eval/agents/workflow.js +0 -155
  128. package/dist/eval/baseline.d.ts +0 -38
  129. package/dist/eval/baseline.js +0 -282
  130. package/dist/eval/config-loader.d.ts +0 -14
  131. package/dist/eval/config-loader.js +0 -395
  132. package/dist/eval/corpus.d.ts +0 -30
  133. package/dist/eval/corpus.js +0 -330
  134. package/dist/eval/cost-guard.d.ts +0 -102
  135. package/dist/eval/cost-guard.js +0 -190
  136. package/dist/eval/diff.d.ts +0 -64
  137. package/dist/eval/diff.js +0 -323
  138. package/dist/eval/llm-client.d.ts +0 -176
  139. package/dist/eval/llm-client.js +0 -267
  140. package/dist/eval/mode.d.ts +0 -28
  141. package/dist/eval/mode.js +0 -61
  142. package/dist/eval/progress.d.ts +0 -83
  143. package/dist/eval/progress.js +0 -59
  144. package/dist/eval/report.d.ts +0 -11
  145. package/dist/eval/report.js +0 -181
  146. package/dist/eval/rubric-loader.d.ts +0 -20
  147. package/dist/eval/rubric-loader.js +0 -143
  148. package/dist/eval/runner.d.ts +0 -81
  149. package/dist/eval/runner.js +0 -746
  150. package/dist/eval/runs.d.ts +0 -41
  151. package/dist/eval/runs.js +0 -114
  152. package/dist/eval/sandbox.d.ts +0 -38
  153. package/dist/eval/sandbox.js +0 -137
  154. package/dist/eval/tools/glob.d.ts +0 -2
  155. package/dist/eval/tools/glob.js +0 -163
  156. package/dist/eval/tools/grep.d.ts +0 -2
  157. package/dist/eval/tools/grep.js +0 -152
  158. package/dist/eval/tools/index.d.ts +0 -7
  159. package/dist/eval/tools/index.js +0 -35
  160. package/dist/eval/tools/read.d.ts +0 -2
  161. package/dist/eval/tools/read.js +0 -122
  162. package/dist/eval/tools/types.d.ts +0 -49
  163. package/dist/eval/tools/types.js +0 -41
  164. package/dist/eval/tools/write.d.ts +0 -2
  165. package/dist/eval/tools/write.js +0 -92
  166. package/dist/eval/types.d.ts +0 -561
  167. package/dist/eval/types.js +0 -47
  168. package/dist/eval/verifiers/judge.d.ts +0 -40
  169. package/dist/eval/verifiers/judge.js +0 -256
  170. package/dist/eval/verifiers/rules.d.ts +0 -24
  171. package/dist/eval/verifiers/rules.js +0 -218
  172. package/dist/eval/verifiers/structural.d.ts +0 -14
  173. package/dist/eval/verifiers/structural.js +0 -171
  174. package/dist/eval/verifiers/traceability.d.ts +0 -23
  175. package/dist/eval/verifiers/traceability.js +0 -84
  176. package/dist/eval/verifiers/workflow-consistency.d.ts +0 -21
  177. package/dist/eval/verifiers/workflow-consistency.js +0 -225
  178. package/dist/eval/workflow-corpus.d.ts +0 -7
  179. package/dist/eval/workflow-corpus.js +0 -207
  180. package/dist/feature-system.d.ts +0 -42
  181. package/dist/feature-system.js +0 -432
  182. package/dist/internal/knowledge-digest.d.ts +0 -7
  183. package/dist/internal/knowledge-digest.js +0 -93
@@ -1,432 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { execFile } from "node:child_process";
4
- import { promisify } from "node:util";
5
- import { RUNTIME_ROOT } from "./constants.js";
6
- import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
7
- const execFileAsync = promisify(execFile);
8
- const WORKTREES_DIR_REL_PATH = `${RUNTIME_ROOT}/worktrees`;
9
- const LEGACY_FEATURES_DIR_REL_PATH = `${RUNTIME_ROOT}/features`;
10
- const ACTIVE_FEATURE_META_REL_PATH = `${RUNTIME_ROOT}/state/active-feature.json`;
11
- const WORKTREE_REGISTRY_REL_PATH = `${RUNTIME_ROOT}/state/worktrees.json`;
12
- const DEFAULT_FEATURE_ID = "default";
13
- const WORKTREE_REGISTRY_SCHEMA_VERSION = 1;
14
- const FEATURE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/u;
15
- function worktreesRoot(projectRoot) {
16
- return path.join(projectRoot, WORKTREES_DIR_REL_PATH);
17
- }
18
- function legacyFeaturesRoot(projectRoot) {
19
- return path.join(projectRoot, LEGACY_FEATURES_DIR_REL_PATH);
20
- }
21
- export function activeFeatureMetaPath(projectRoot) {
22
- return path.join(projectRoot, ACTIVE_FEATURE_META_REL_PATH);
23
- }
24
- export function worktreeRegistryPath(projectRoot) {
25
- return path.join(projectRoot, WORKTREE_REGISTRY_REL_PATH);
26
- }
27
- export function featureRootPath(projectRoot, featureId) {
28
- return path.join(worktreesRoot(projectRoot), normalizedFeatureId(featureId));
29
- }
30
- export function featureArtifactsPath(projectRoot, featureId) {
31
- return path.join(featureRootPath(projectRoot, featureId), RUNTIME_ROOT, "artifacts");
32
- }
33
- export function featureStatePath(projectRoot, featureId) {
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);
41
- }
42
- function normalizedFeatureId(value) {
43
- const candidate = value
44
- .trim()
45
- .toLowerCase()
46
- .replace(/[^a-z0-9]+/gu, "-")
47
- .replace(/^-+/u, "")
48
- .replace(/-+$/u, "");
49
- if (!candidate) {
50
- return DEFAULT_FEATURE_ID;
51
- }
52
- const clipped = candidate.slice(0, 64);
53
- return FEATURE_ID_PATTERN.test(clipped) ? clipped : DEFAULT_FEATURE_ID;
54
- }
55
- function toRelativePath(projectRoot, absolutePath) {
56
- const rel = path.relative(projectRoot, absolutePath);
57
- if (!rel || rel.trim().length === 0) {
58
- return ".";
59
- }
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;
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();
96
- for (const entry of entries) {
97
- if (!byId.has(entry.featureId)) {
98
- byId.set(entry.featureId, entry);
99
- }
100
- }
101
- return [...byId.values()].sort((a, b) => a.featureId.localeCompare(b.featureId));
102
- }
103
- async function runGit(projectRoot, args) {
104
- try {
105
- const { stdout, stderr } = await execFileAsync("git", args, { cwd: projectRoot });
106
- return { ok: true, stdout: stdout.trim(), stderr: stderr.trim() };
107
- }
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
- };
117
- }
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, "");
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
- };
142
- }
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
- };
151
- }
152
- try {
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
- };
166
- }
167
- catch {
168
- return {
169
- schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
170
- updatedAt: new Date().toISOString(),
171
- entries: []
172
- };
173
- }
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`, { mode: 0o600 });
182
- }
183
- async function readActiveFeatureMetaInternal(projectRoot) {
184
- const filePath = activeFeatureMetaPath(projectRoot);
185
- if (!(await exists(filePath))) {
186
- return {
187
- activeFeature: DEFAULT_FEATURE_ID,
188
- updatedAt: new Date().toISOString()
189
- };
190
- }
191
- try {
192
- const parsed = JSON.parse(await fs.readFile(filePath, "utf8"));
193
- const activeFeatureRaw = typeof parsed.activeFeature === "string"
194
- ? parsed.activeFeature
195
- : DEFAULT_FEATURE_ID;
196
- const updatedAtRaw = typeof parsed.updatedAt === "string"
197
- ? parsed.updatedAt
198
- : new Date().toISOString();
199
- return {
200
- activeFeature: normalizedFeatureId(activeFeatureRaw),
201
- updatedAt: updatedAtRaw
202
- };
203
- }
204
- catch {
205
- return {
206
- activeFeature: DEFAULT_FEATURE_ID,
207
- updatedAt: new Date().toISOString()
208
- };
209
- }
210
- }
211
- async function writeActiveFeatureMeta(projectRoot, meta) {
212
- const normalized = {
213
- activeFeature: normalizedFeatureId(meta.activeFeature),
214
- updatedAt: meta.updatedAt
215
- };
216
- await writeFileSafe(activeFeatureMetaPath(projectRoot), `${JSON.stringify(normalized, null, 2)}\n`, { mode: 0o600 });
217
- }
218
- function registryHasFeature(registry, featureId) {
219
- return registry.entries.some((entry) => entry.featureId === featureId);
220
- }
221
- function findEntry(registry, featureId) {
222
- return registry.entries.find((entry) => entry.featureId === featureId);
223
- }
224
- async function listLegacySnapshotIds(projectRoot) {
225
- const root = legacyFeaturesRoot(projectRoot);
226
- if (!(await exists(root))) {
227
- return [];
228
- }
229
- try {
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));
235
- }
236
- catch {
237
- return [];
238
- }
239
- }
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));
250
- }
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,
276
- updatedAt: new Date().toISOString()
277
- };
278
- await writeActiveFeatureMeta(projectRoot, activeMeta);
279
- return { registry, activeMeta };
280
- }
281
- async function readRegistryStateReadonly(projectRoot) {
282
- const currentRegistry = await readRegistry(projectRoot);
283
- const entries = [...currentRegistry.entries];
284
- const gitRepo = await isGitRepository(projectRoot);
285
- const source = gitRepo ? "git-worktree" : "workspace";
286
- const branch = gitRepo ? await currentBranch(projectRoot) : "workspace/default";
287
- if (!entries.some((entry) => entry.featureId === DEFAULT_FEATURE_ID)) {
288
- entries.push(buildDefaultEntry(source, branch));
289
- }
290
- const legacyFeatureIds = await listLegacySnapshotIds(projectRoot);
291
- for (const legacyId of legacyFeatureIds) {
292
- if (entries.some((entry) => entry.featureId === legacyId)) {
293
- continue;
294
- }
295
- entries.push({
296
- featureId: legacyId,
297
- branch: `legacy/${legacyId}`,
298
- path: `${LEGACY_FEATURES_DIR_REL_PATH}/${legacyId}`,
299
- source: "legacy-snapshot",
300
- createdAt: new Date().toISOString()
301
- });
302
- }
303
- const registry = {
304
- schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
305
- updatedAt: currentRegistry.updatedAt,
306
- entries: dedupeEntries(entries)
307
- };
308
- const active = await readActiveFeatureMetaInternal(projectRoot);
309
- return {
310
- registry,
311
- activeMeta: {
312
- activeFeature: registryHasFeature(registry, active.activeFeature)
313
- ? active.activeFeature
314
- : DEFAULT_FEATURE_ID,
315
- updatedAt: active.updatedAt
316
- }
317
- };
318
- }
319
- async function resolveFeatureSystemState(projectRoot, options = {}) {
320
- if (options.repair === false) {
321
- return readRegistryStateReadonly(projectRoot);
322
- }
323
- return ensureRegistryState(projectRoot);
324
- }
325
- export async function ensureFeatureSystem(projectRoot, options = {}) {
326
- const { activeMeta } = await resolveFeatureSystemState(projectRoot, options);
327
- return activeMeta;
328
- }
329
- export async function readFeatureWorktreeRegistry(projectRoot, options = {}) {
330
- const { registry } = await resolveFeatureSystemState(projectRoot, options);
331
- return registry;
332
- }
333
- export async function readActiveFeature(projectRoot, options = {}) {
334
- const meta = await ensureFeatureSystem(projectRoot, options);
335
- return normalizedFeatureId(meta.activeFeature);
336
- }
337
- export async function listFeatures(projectRoot, options = {}) {
338
- const registry = await readFeatureWorktreeRegistry(projectRoot, options);
339
- return registry.entries.map((entry) => entry.featureId).sort((a, b) => a.localeCompare(b));
340
- }
341
- export async function syncActiveFeatureSnapshot(projectRoot) {
342
- await ensureFeatureSystem(projectRoot);
343
- }
344
- export async function switchActiveFeature(projectRoot, featureId) {
345
- const registry = await readFeatureWorktreeRegistry(projectRoot);
346
- const target = normalizedFeatureId(featureId);
347
- if (!registryHasFeature(registry, target)) {
348
- throw new Error(`Feature "${target}" is not registered. Create it first with /cc-ops feature new ${target}.`);
349
- }
350
- const nextMeta = {
351
- activeFeature: target,
352
- updatedAt: new Date().toISOString()
353
- };
354
- await writeActiveFeatureMeta(projectRoot, nextMeta);
355
- return nextMeta;
356
- }
357
- export async function createFeature(projectRoot, rawFeatureId, options = {}) {
358
- const registry = await readFeatureWorktreeRegistry(projectRoot);
359
- const featureId = normalizedFeatureId(rawFeatureId);
360
- if (featureId === DEFAULT_FEATURE_ID &&
361
- rawFeatureId.trim().length > 0 &&
362
- rawFeatureId.trim().toLowerCase() !== "default") {
363
- throw new Error(`Unable to create feature from "${rawFeatureId}" — use letters, numbers, and dashes.`);
364
- }
365
- if (registryHasFeature(registry, featureId)) {
366
- throw new Error(`Feature "${featureId}" already exists.`);
367
- }
368
- const isGit = await isGitRepository(projectRoot);
369
- let entry;
370
- if (isGit) {
371
- const workspacePath = featureRootPath(projectRoot, featureId);
372
- if (await exists(workspacePath)) {
373
- throw new Error(`Worktree path already exists: ${workspacePath}`);
374
- }
375
- await ensureDir(path.dirname(workspacePath));
376
- const branch = `feature/${featureId}`;
377
- const localBranchRef = `refs/heads/${branch}`;
378
- const branchCheck = await runGit(projectRoot, ["show-ref", "--verify", "--quiet", localBranchRef]);
379
- if (branchCheck.ok) {
380
- const addExisting = await runGit(projectRoot, ["worktree", "add", workspacePath, branch]);
381
- if (!addExisting.ok) {
382
- throw new Error(`Unable to attach worktree for branch "${branch}": ${addExisting.stderr}`);
383
- }
384
- }
385
- else {
386
- const startPoint = options.cloneActive === false
387
- ? await defaultStartPoint(projectRoot)
388
- : "HEAD";
389
- const addNew = await runGit(projectRoot, ["worktree", "add", "-b", branch, workspacePath, startPoint]);
390
- if (!addNew.ok) {
391
- throw new Error(`Unable to create worktree "${featureId}" on branch "${branch}": ${addNew.stderr}`);
392
- }
393
- }
394
- entry = {
395
- featureId,
396
- branch,
397
- path: toRelativePath(projectRoot, workspacePath),
398
- source: "git-worktree",
399
- createdAt: new Date().toISOString()
400
- };
401
- }
402
- else {
403
- const workspacePath = featureRootPath(projectRoot, featureId);
404
- await ensureDir(workspacePath);
405
- entry = {
406
- featureId,
407
- branch: `workspace/${featureId}`,
408
- path: toRelativePath(projectRoot, workspacePath),
409
- source: "workspace",
410
- createdAt: new Date().toISOString()
411
- };
412
- }
413
- const nextRegistry = {
414
- schemaVersion: WORKTREE_REGISTRY_SCHEMA_VERSION,
415
- updatedAt: new Date().toISOString(),
416
- entries: dedupeEntries([...registry.entries, entry])
417
- };
418
- await writeRegistry(projectRoot, nextRegistry);
419
- if (options.switchTo === true) {
420
- await switchActiveFeature(projectRoot, featureId);
421
- }
422
- return featureId;
423
- }
424
- export async function activeFeatureWorkspacePath(projectRoot) {
425
- const registry = await readFeatureWorktreeRegistry(projectRoot);
426
- const active = await readActiveFeature(projectRoot);
427
- const entry = findEntry(registry, active);
428
- if (!entry) {
429
- return projectRoot;
430
- }
431
- return resolveFeatureWorkspacePath(projectRoot, entry);
432
- }
@@ -1,7 +0,0 @@
1
- import type { Writable } from "node:stream";
2
- interface InternalIo {
3
- stdout: Writable;
4
- stderr: Writable;
5
- }
6
- export declare function runKnowledgeDigestCommand(projectRoot: string, tokens: string[], io: InternalIo): Promise<number>;
7
- export {};
@@ -1,93 +0,0 @@
1
- import { selectRelevantLearnings } from "../knowledge-store.js";
2
- import { FLOW_STAGES } from "../types.js";
3
- function parseCsv(raw) {
4
- return raw
5
- .split(",")
6
- .map((value) => value.trim())
7
- .filter((value) => value.length > 0);
8
- }
9
- function parseKnowledgeDigestArgs(tokens) {
10
- const args = {
11
- diffFiles: [],
12
- openGates: [],
13
- limit: 8,
14
- format: "markdown"
15
- };
16
- for (const token of tokens) {
17
- if (!token.startsWith("--")) {
18
- throw new Error(`Unknown positional token for knowledge-digest: ${token}`);
19
- }
20
- if (token.startsWith("--stage=")) {
21
- const value = token.replace("--stage=", "").trim();
22
- if (!value)
23
- continue;
24
- if (!FLOW_STAGES.includes(value)) {
25
- throw new Error(`--stage must be one of: ${FLOW_STAGES.join(", ")}`);
26
- }
27
- args.stage = value;
28
- continue;
29
- }
30
- if (token.startsWith("--branch=")) {
31
- const value = token.replace("--branch=", "").trim();
32
- if (value.length > 0) {
33
- args.branch = value;
34
- }
35
- continue;
36
- }
37
- if (token.startsWith("--diff-files=")) {
38
- args.diffFiles.push(...parseCsv(token.replace("--diff-files=", "")));
39
- continue;
40
- }
41
- if (token.startsWith("--open-gates=")) {
42
- args.openGates.push(...parseCsv(token.replace("--open-gates=", "")));
43
- continue;
44
- }
45
- if (token.startsWith("--limit=")) {
46
- const raw = token.replace("--limit=", "").trim();
47
- const value = Number.parseInt(raw, 10);
48
- if (!Number.isFinite(value) || value <= 0) {
49
- throw new Error("--limit must be a positive integer.");
50
- }
51
- args.limit = value;
52
- continue;
53
- }
54
- if (token === "--json") {
55
- args.format = "json";
56
- continue;
57
- }
58
- if (token === "--markdown") {
59
- args.format = "markdown";
60
- continue;
61
- }
62
- throw new Error(`Unknown flag for knowledge-digest: ${token}`);
63
- }
64
- return args;
65
- }
66
- function markdownDigest(rows) {
67
- if (rows.length === 0) {
68
- return "(no relevant learnings)";
69
- }
70
- return rows
71
- .map((entry) => {
72
- const stage = entry.stage ?? "global";
73
- const domain = entry.domain ?? "general";
74
- return `- [${entry.confidence} | ${stage} | ${domain}] ${entry.trigger} -> ${entry.action}`;
75
- })
76
- .join("\n");
77
- }
78
- export async function runKnowledgeDigestCommand(projectRoot, tokens, io) {
79
- const args = parseKnowledgeDigestArgs(tokens);
80
- const rows = await selectRelevantLearnings(projectRoot, {
81
- stage: args.stage,
82
- branch: args.branch,
83
- diffFiles: args.diffFiles,
84
- openGates: args.openGates,
85
- limit: args.limit
86
- });
87
- if (args.format === "json") {
88
- io.stdout.write(`${JSON.stringify(rows, null, 2)}\n`);
89
- return 0;
90
- }
91
- io.stdout.write(`${markdownDigest(rows)}\n`);
92
- return 0;
93
- }