cclaw-cli 0.51.22 → 0.51.24
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/README.md +14 -13
- package/dist/content/core-agents.d.ts +18 -2
- package/dist/content/core-agents.js +59 -13
- package/dist/content/examples.js +15 -7
- package/dist/content/hook-manifest.js +1 -4
- package/dist/content/learnings.js +5 -2
- package/dist/content/meta-skill.d.ts +1 -0
- package/dist/content/meta-skill.js +10 -1
- package/dist/content/node-hooks.js +1 -1
- package/dist/content/seed-shelf.js +73 -8
- package/dist/content/skills.js +14 -10
- package/dist/content/stage-command.d.ts +2 -0
- package/dist/content/stage-command.js +17 -0
- package/dist/content/stage-schema.js +50 -6
- package/dist/content/stages/brainstorm.js +20 -15
- package/dist/content/stages/design.js +16 -16
- package/dist/content/stages/review.js +20 -11
- package/dist/content/stages/schema-types.d.ts +1 -1
- package/dist/content/stages/scope.js +16 -11
- package/dist/content/stages/tdd.js +10 -3
- package/dist/content/subagents.js +73 -7
- package/dist/content/templates.js +127 -31
- package/dist/content/track-render-context.js +7 -0
- package/dist/delegation.d.ts +2 -2
- package/dist/delegation.js +16 -9
- package/dist/doctor-registry.js +1 -1
- package/dist/doctor.js +195 -33
- package/dist/flow-state.d.ts +1 -0
- package/dist/flow-state.js +1 -0
- package/dist/harness-adapters.d.ts +14 -11
- package/dist/harness-adapters.js +153 -17
- package/dist/install.js +101 -5
- package/dist/knowledge-store.js +30 -6
- package/dist/run-archive.js +11 -0
- package/dist/run-persistence.js +14 -7
- package/package.json +1 -1
package/dist/install.js
CHANGED
|
@@ -6,6 +6,7 @@ import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./cons
|
|
|
6
6
|
import { writeConfig, createDefaultConfig, readConfig, configPath, detectLanguageRulePacks, detectAdvancedKeys } from "./config.js";
|
|
7
7
|
import { learnSkillMarkdown } from "./content/learnings.js";
|
|
8
8
|
import { nextCommandContract, nextCommandSkillMarkdown } from "./content/next-command.js";
|
|
9
|
+
import { stageCommandShimMarkdown } from "./content/stage-command.js";
|
|
9
10
|
import { ideateCommandContract, ideateCommandSkillMarkdown } from "./content/ideate-command.js";
|
|
10
11
|
import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
|
|
11
12
|
import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
|
|
@@ -190,15 +191,84 @@ function resolveRepoRoot() {
|
|
|
190
191
|
return process.cwd();
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
function isZeroSha(value) {
|
|
195
|
+
return /^0{40,64}$/u.test(value);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function readStdin() {
|
|
199
|
+
try {
|
|
200
|
+
return fs.readFileSync(0, "utf8");
|
|
201
|
+
} catch {
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function uniqueLines(chunks) {
|
|
207
|
+
return [...new Set(chunks
|
|
208
|
+
.join("\n")
|
|
209
|
+
.split(/\r?\n/gu)
|
|
210
|
+
.map((line) => line.trim())
|
|
211
|
+
.filter((line) => line.length > 0))].join("\n");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function diffNames(root, range) {
|
|
215
|
+
const result = runGit(["diff", "--name-only", range], root);
|
|
216
|
+
return result.status === 0 ? result.stdout : "";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function changedFilesFromUnpushedCommits(root, localSha = "HEAD") {
|
|
220
|
+
const revList = runGit(["rev-list", "--reverse", localSha, "--not", "--remotes"], root);
|
|
221
|
+
if (revList.status !== 0 || revList.stdout.trim().length === 0) {
|
|
222
|
+
return "";
|
|
223
|
+
}
|
|
224
|
+
const chunks = [];
|
|
225
|
+
for (const commit of revList.stdout.split(/\r?\n/gu).map((line) => line.trim()).filter(Boolean)) {
|
|
226
|
+
const diffTree = runGit(["diff-tree", "--no-commit-id", "--name-only", "-r", "--root", commit], root);
|
|
227
|
+
if (diffTree.status === 0) chunks.push(diffTree.stdout);
|
|
228
|
+
}
|
|
229
|
+
return uniqueLines(chunks);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function changedFilesFromPrePushStdin(root, stdin) {
|
|
233
|
+
const chunks = [];
|
|
234
|
+
for (const rawLine of stdin.split(/\r?\n/gu)) {
|
|
235
|
+
const parts = rawLine.trim().split(/\s+/u);
|
|
236
|
+
if (parts.length < 4) continue;
|
|
237
|
+
const [localRef, localSha, remoteRef, remoteSha] = parts;
|
|
238
|
+
void localRef;
|
|
239
|
+
void remoteRef;
|
|
240
|
+
if (!localSha || isZeroSha(localSha)) continue;
|
|
241
|
+
if (remoteSha && !isZeroSha(remoteSha)) {
|
|
242
|
+
chunks.push(diffNames(root, remoteSha + ".." + localSha));
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const upstream = runGit(["rev-parse", "--verify", "--quiet", "@{upstream}"], root);
|
|
246
|
+
if (upstream.status === 0 && upstream.stdout.trim().length > 0) {
|
|
247
|
+
chunks.push(diffNames(root, upstream.stdout.trim() + ".." + localSha));
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
chunks.push(changedFilesFromUnpushedCommits(root, localSha));
|
|
251
|
+
}
|
|
252
|
+
return uniqueLines(chunks);
|
|
253
|
+
}
|
|
254
|
+
|
|
193
255
|
function resolveChangedFiles(root) {
|
|
194
256
|
if (HOOK_NAME === "pre-commit") {
|
|
195
257
|
const result = runGit(["diff", "--cached", "--name-only"], root);
|
|
196
258
|
return result.status === 0 ? result.stdout : "";
|
|
197
259
|
}
|
|
198
|
-
const
|
|
260
|
+
const stdinChanged = changedFilesFromPrePushStdin(root, readStdin());
|
|
261
|
+
if (stdinChanged.length > 0) {
|
|
262
|
+
return stdinChanged;
|
|
263
|
+
}
|
|
264
|
+
const upstreamResult = runGit(["diff", "--name-only", "@{upstream}..HEAD"], root);
|
|
199
265
|
if (upstreamResult.status === 0) {
|
|
200
266
|
return upstreamResult.stdout;
|
|
201
267
|
}
|
|
268
|
+
const unpushed = changedFilesFromUnpushedCommits(root);
|
|
269
|
+
if (unpushed.length > 0) {
|
|
270
|
+
return unpushed;
|
|
271
|
+
}
|
|
202
272
|
const fallback = runGit(["diff", "--name-only", "HEAD~1...HEAD"], root);
|
|
203
273
|
return fallback.status === 0 ? fallback.stdout : "";
|
|
204
274
|
}
|
|
@@ -443,6 +513,9 @@ async function writeEntryCommands(projectRoot) {
|
|
|
443
513
|
await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
|
|
444
514
|
await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
|
|
445
515
|
await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
|
|
516
|
+
for (const stage of FLOW_STAGES) {
|
|
517
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", `${stage}.md`), stageCommandShimMarkdown(stage));
|
|
518
|
+
}
|
|
446
519
|
}
|
|
447
520
|
function toObject(value) {
|
|
448
521
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -547,11 +620,20 @@ function mergeOpenCodePluginConfig(existingDoc, pluginRelPath) {
|
|
|
547
620
|
if (!normalized.has(pluginRelPath)) {
|
|
548
621
|
pluginsRaw.push(pluginRelPath);
|
|
549
622
|
}
|
|
550
|
-
const
|
|
623
|
+
const permission = toObject(root.permission) ?? {};
|
|
624
|
+
const permissionChanged = permission.question !== "allow";
|
|
625
|
+
const changed = !normalized.has(pluginRelPath) ||
|
|
626
|
+
!Array.isArray(root.plugin) ||
|
|
627
|
+
permissionChanged ||
|
|
628
|
+
!toObject(root.permission);
|
|
551
629
|
return {
|
|
552
630
|
merged: {
|
|
553
631
|
...root,
|
|
554
|
-
plugin: pluginsRaw
|
|
632
|
+
plugin: pluginsRaw,
|
|
633
|
+
permission: {
|
|
634
|
+
...permission,
|
|
635
|
+
question: "allow"
|
|
636
|
+
}
|
|
555
637
|
},
|
|
556
638
|
changed
|
|
557
639
|
};
|
|
@@ -933,7 +1015,6 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
933
1015
|
await removeBestEffort(legacyPlugin);
|
|
934
1016
|
}
|
|
935
1017
|
for (const legacyRuntimeFile of [
|
|
936
|
-
...FLOW_STAGES.map((stage) => runtimePath(projectRoot, "commands", `${stage}.md`)),
|
|
937
1018
|
...DEPRECATED_COMMAND_FILES.map((file) => runtimePath(projectRoot, "commands", file)),
|
|
938
1019
|
...DEPRECATED_SKILL_FILES.map((segments) => runtimePath(projectRoot, "skills", ...segments)),
|
|
939
1020
|
...DEPRECATED_STATE_FILES.map((file) => runtimePath(projectRoot, "state", file)),
|
|
@@ -1248,7 +1329,7 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1248
1329
|
try {
|
|
1249
1330
|
const entries = await fs.readdir(codexSkillsRoot);
|
|
1250
1331
|
for (const entry of entries) {
|
|
1251
|
-
if (/^(?:cclaw-)?cc(?:-(?:next|view|ops|ideate))?$/u.test(entry)) {
|
|
1332
|
+
if (/^(?:cclaw-)?cc(?:-(?:next|view|ops|ideate|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
|
|
1252
1333
|
await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
|
|
1253
1334
|
}
|
|
1254
1335
|
}
|
|
@@ -1258,6 +1339,19 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1258
1339
|
}
|
|
1259
1340
|
await removeIfEmpty(codexSkillsRoot);
|
|
1260
1341
|
await removeIfEmpty(path.join(projectRoot, ".agents"));
|
|
1342
|
+
const managedAgentNames = [
|
|
1343
|
+
"planner",
|
|
1344
|
+
"product-manager",
|
|
1345
|
+
"critic",
|
|
1346
|
+
"reviewer",
|
|
1347
|
+
"security-reviewer",
|
|
1348
|
+
"test-author",
|
|
1349
|
+
"doc-updater"
|
|
1350
|
+
];
|
|
1351
|
+
for (const agentName of managedAgentNames) {
|
|
1352
|
+
await removeBestEffort(path.join(projectRoot, ".opencode/agents", `${agentName}.md`));
|
|
1353
|
+
await removeBestEffort(path.join(projectRoot, ".codex/agents", `${agentName}.toml`));
|
|
1354
|
+
}
|
|
1261
1355
|
for (const pluginPath of [
|
|
1262
1356
|
path.join(projectRoot, ".opencode/plugins/viby-plugin.mjs"),
|
|
1263
1357
|
path.join(projectRoot, ".opencode/plugins/opencode-plugin.mjs"),
|
|
@@ -1284,8 +1378,10 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1284
1378
|
".cursor/rules",
|
|
1285
1379
|
".cursor/commands",
|
|
1286
1380
|
".cursor",
|
|
1381
|
+
".codex/agents",
|
|
1287
1382
|
".codex/commands",
|
|
1288
1383
|
".codex",
|
|
1384
|
+
".opencode/agents",
|
|
1289
1385
|
".opencode/plugins",
|
|
1290
1386
|
".opencode/commands",
|
|
1291
1387
|
".opencode"
|
package/dist/knowledge-store.js
CHANGED
|
@@ -505,18 +505,40 @@ export async function appendKnowledge(projectRoot, seeds, defaults = {}) {
|
|
|
505
505
|
appendedEntries
|
|
506
506
|
};
|
|
507
507
|
}
|
|
508
|
+
const SHORT_TECHNICAL_TOKEN_SET = new Set(["ci", "db", "ui", "qa", "ux"]);
|
|
508
509
|
function tokenizeText(value) {
|
|
509
510
|
if (!value)
|
|
510
511
|
return [];
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
512
|
+
const tokens = [];
|
|
513
|
+
const matches = value.matchAll(/[A-Za-z0-9]+/gu);
|
|
514
|
+
for (const match of matches) {
|
|
515
|
+
const raw = match[0] ?? "";
|
|
516
|
+
const normalized = raw.toLowerCase();
|
|
517
|
+
if (normalized.length >= 3) {
|
|
518
|
+
tokens.push(normalized);
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
if (/^[A-Z]{2}$/u.test(raw) || SHORT_TECHNICAL_TOKEN_SET.has(normalized)) {
|
|
522
|
+
tokens.push(normalized);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return tokens;
|
|
516
526
|
}
|
|
517
527
|
function uniqueTokens(values) {
|
|
518
528
|
return [...new Set(values)];
|
|
519
529
|
}
|
|
530
|
+
function supersededTriggerSet(entries) {
|
|
531
|
+
const superseded = new Set();
|
|
532
|
+
for (const entry of entries) {
|
|
533
|
+
for (const trigger of entry.supersedes ?? []) {
|
|
534
|
+
superseded.add(normalizeText(trigger));
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return superseded;
|
|
538
|
+
}
|
|
539
|
+
function isSupersededLearning(entry, supersededTriggers) {
|
|
540
|
+
return entry.superseded_by !== undefined || supersededTriggers.has(normalizeText(entry.trigger));
|
|
541
|
+
}
|
|
520
542
|
function pathTokens(paths) {
|
|
521
543
|
if (!Array.isArray(paths) || paths.length === 0)
|
|
522
544
|
return [];
|
|
@@ -538,7 +560,9 @@ export async function selectRelevantLearnings(projectRoot, options = {}) {
|
|
|
538
560
|
const limit = typeof options.limit === "number" && Number.isFinite(options.limit) && options.limit > 0
|
|
539
561
|
? Math.floor(options.limit)
|
|
540
562
|
: 8;
|
|
541
|
-
const
|
|
563
|
+
const staleTriggers = supersededTriggerSet(entries);
|
|
564
|
+
const activeEntries = entries.filter((entry) => !isSupersededLearning(entry, staleTriggers));
|
|
565
|
+
const ranked = activeEntries.map((entry, index) => {
|
|
542
566
|
let score = 0;
|
|
543
567
|
let stageScore = 0;
|
|
544
568
|
if (stage) {
|
package/dist/run-archive.js
CHANGED
|
@@ -35,6 +35,13 @@ function stateDirPath(projectRoot) {
|
|
|
35
35
|
function archiveLockPath(projectRoot) {
|
|
36
36
|
return path.join(projectRoot, RUNTIME_ROOT, "state", ".archive.lock");
|
|
37
37
|
}
|
|
38
|
+
function compoundCloseoutComplete(state) {
|
|
39
|
+
return (state.closeout.compoundCompletedAt !== undefined ||
|
|
40
|
+
state.closeout.compoundPromoted > 0 ||
|
|
41
|
+
(state.closeout.compoundSkipped === true &&
|
|
42
|
+
typeof state.closeout.compoundSkipReason === "string" &&
|
|
43
|
+
state.closeout.compoundSkipReason.trim().length > 0));
|
|
44
|
+
}
|
|
38
45
|
async function snapshotStateDirectory(projectRoot, destinationRoot) {
|
|
39
46
|
const sourceDir = stateDirPath(projectRoot);
|
|
40
47
|
if (!(await exists(sourceDir))) {
|
|
@@ -209,6 +216,10 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
209
216
|
sourceState.closeout.retroSkipReason.trim().length > 0;
|
|
210
217
|
const readyForArchive = sourceState.closeout.shipSubstate === "ready_to_archive";
|
|
211
218
|
const inShipCloseout = sourceState.currentStage === "ship";
|
|
219
|
+
if (readyForArchive && !compoundCloseoutComplete(sourceState)) {
|
|
220
|
+
throw new Error("Archive blocked: compound closeout is incomplete. " +
|
|
221
|
+
"Promote compound guidance or skip compound review with an explicit reason before archiving.");
|
|
222
|
+
}
|
|
212
223
|
if (inShipCloseout && skipRetro) {
|
|
213
224
|
throw new Error("Archive blocked: --skip-retro is not allowed while current stage is ship. " +
|
|
214
225
|
"Complete closeout to ready_to_archive via /cc-next.");
|
package/dist/run-persistence.js
CHANGED
|
@@ -255,21 +255,27 @@ function sanitizeCloseoutState(value) {
|
|
|
255
255
|
? true
|
|
256
256
|
: undefined;
|
|
257
257
|
const compoundCompletedAt = typeof typed.compoundCompletedAt === "string" ? typed.compoundCompletedAt : undefined;
|
|
258
|
-
const
|
|
258
|
+
const compoundSkipReason = typeof typed.compoundSkipReason === "string"
|
|
259
|
+
? typed.compoundSkipReason.trim() || undefined
|
|
260
|
+
: undefined;
|
|
261
|
+
const compoundSkipped = typed.compoundSkipped === true && compoundSkipReason !== undefined
|
|
262
|
+
? true
|
|
263
|
+
: undefined;
|
|
259
264
|
const promotedRaw = typed.compoundPromoted;
|
|
260
265
|
const compoundPromoted = typeof promotedRaw === "number" && Number.isFinite(promotedRaw) && promotedRaw >= 0
|
|
261
266
|
? Math.floor(promotedRaw)
|
|
262
267
|
: 0;
|
|
263
|
-
// Demote shipSubstate when its
|
|
264
|
-
// hand-edited flow-state could claim `ready_to_archive`
|
|
265
|
-
//
|
|
266
|
-
// proceed and skip the gate. Compound completion is not independently
|
|
267
|
-
// tracked in all flows (some runs rely on knowledge.jsonl + the retro
|
|
268
|
-
// window), so we only demote when the retro leg is missing outright.
|
|
268
|
+
// Demote shipSubstate when its closeout invariants are violated on disk. A
|
|
269
|
+
// hand-edited flow-state could claim `ready_to_archive` without completing
|
|
270
|
+
// the compound leg, which would let `archive` skip durable closeout proof.
|
|
269
271
|
const retroDone = retroAcceptedAt !== undefined || retroSkipped === true;
|
|
272
|
+
const compoundDone = compoundCompletedAt !== undefined || compoundPromoted > 0 || compoundSkipped === true;
|
|
270
273
|
if (!retroDone && (shipSubstate === "ready_to_archive" || shipSubstate === "compound_review")) {
|
|
271
274
|
shipSubstate = "retro_review";
|
|
272
275
|
}
|
|
276
|
+
else if (shipSubstate === "ready_to_archive" && !compoundDone) {
|
|
277
|
+
shipSubstate = "compound_review";
|
|
278
|
+
}
|
|
273
279
|
return {
|
|
274
280
|
shipSubstate,
|
|
275
281
|
retroDraftedAt,
|
|
@@ -278,6 +284,7 @@ function sanitizeCloseoutState(value) {
|
|
|
278
284
|
retroSkipReason,
|
|
279
285
|
compoundCompletedAt,
|
|
280
286
|
compoundSkipped,
|
|
287
|
+
compoundSkipReason,
|
|
281
288
|
compoundPromoted
|
|
282
289
|
};
|
|
283
290
|
}
|