cclaw-cli 0.51.28 → 0.51.29
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 +17 -1
- package/dist/cli.js +185 -49
- package/dist/codex-feature-flag.d.ts +1 -1
- package/dist/codex-feature-flag.js +1 -1
- package/dist/config.js +3 -0
- package/dist/content/cancel-command.d.ts +2 -0
- package/dist/content/cancel-command.js +25 -0
- package/dist/content/finish-command.d.ts +2 -0
- package/dist/content/finish-command.js +26 -0
- package/dist/content/harness-doc.js +1 -1
- package/dist/content/hooks.js +32 -9
- package/dist/content/ideate-command.js +12 -7
- package/dist/content/next-command.js +17 -13
- package/dist/content/node-hooks.js +22 -6
- package/dist/content/opencode-plugin.js +1 -1
- package/dist/content/stages/review.js +1 -1
- package/dist/content/stages/tdd.js +1 -1
- package/dist/content/start-command.js +6 -5
- package/dist/content/status-command.js +4 -3
- package/dist/content/track-render-context.d.ts +1 -0
- package/dist/content/track-render-context.js +2 -0
- package/dist/doctor-registry.d.ts +2 -0
- package/dist/doctor-registry.js +37 -10
- package/dist/doctor.d.ts +2 -1
- package/dist/doctor.js +183 -2
- package/dist/fs-utils.js +6 -0
- package/dist/harness-adapters.js +29 -5
- package/dist/install.d.ts +4 -1
- package/dist/install.js +37 -4
- package/dist/internal/advance-stage.js +6 -6
- package/dist/managed-resources.d.ts +53 -0
- package/dist/managed-resources.js +289 -0
- package/dist/run-archive.d.ts +8 -0
- package/dist/run-archive.js +19 -5
- package/dist/runs.d.ts +1 -1
- package/dist/runs.js +1 -1
- package/dist/tdd-cycle.js +10 -10
- package/dist/tdd-verification-evidence.js +4 -4
- package/dist/track-heuristics.d.ts +2 -0
- package/dist/track-heuristics.js +11 -3
- package/package.json +1 -1
package/dist/doctor.js
CHANGED
|
@@ -3,10 +3,11 @@ import path from "node:path";
|
|
|
3
3
|
import { execFile } from "node:child_process";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
-
import { REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
|
|
6
|
+
import { CCLAW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
|
|
7
7
|
import { CCLAW_AGENTS } from "./content/core-agents.js";
|
|
8
8
|
import { detectAdvancedKeys, InvalidConfigError, readConfig } from "./config.js";
|
|
9
9
|
import { exists } from "./fs-utils.js";
|
|
10
|
+
import { hashManagedResourceContent, isManagedGeneratedPath, MANAGED_RESOURCE_MANIFEST_REL_PATH, readManagedResourceManifest, validateManagedResourceManifest } from "./managed-resources.js";
|
|
10
11
|
import { gitignoreHasRequiredPatterns } from "./gitignore.js";
|
|
11
12
|
import { HARNESS_ADAPTERS, CCLAW_MARKER_START, CCLAW_MARKER_END, harnessShimFileNames, harnessShimSkillNames } from "./harness-adapters.js";
|
|
12
13
|
import { policyChecks } from "./policy.js";
|
|
@@ -21,6 +22,7 @@ import { stageSkillFolder } from "./content/skills.js";
|
|
|
21
22
|
import { stageCommandShimMarkdown } from "./content/stage-command.js";
|
|
22
23
|
import { doctorCheckMetadata } from "./doctor-registry.js";
|
|
23
24
|
import { resolveTrackFromPrompt } from "./track-heuristics.js";
|
|
25
|
+
import { detectHarnesses } from "./init-detect.js";
|
|
24
26
|
import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "./codex-feature-flag.js";
|
|
25
27
|
import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
|
|
26
28
|
import { validateHookDocument } from "./hook-schema.js";
|
|
@@ -92,8 +94,48 @@ function extractGeneratedCliEntrypoints(scriptContent) {
|
|
|
92
94
|
// malformed generated constant; treat below as missing/unusable
|
|
93
95
|
}
|
|
94
96
|
}
|
|
97
|
+
for (const match of scriptContent.matchAll(/const\s+CCLAW_CLI_ARGS_PREFIX\s*=\s*(\[(?:\\.|[^\]])*\]);/gu)) {
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(match[1] ?? "[]");
|
|
100
|
+
if (Array.isArray(parsed)) {
|
|
101
|
+
for (const item of parsed) {
|
|
102
|
+
if (typeof item === "string" && item.trim().length > 0 && !item.startsWith("-")) {
|
|
103
|
+
paths.push(item);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// malformed generated constant; treat below as missing/unusable
|
|
110
|
+
}
|
|
111
|
+
}
|
|
95
112
|
return paths;
|
|
96
113
|
}
|
|
114
|
+
async function walkGeneratedCandidates(projectRoot, relDir, candidates) {
|
|
115
|
+
const fullDir = path.join(projectRoot, relDir);
|
|
116
|
+
if (!(await exists(fullDir)))
|
|
117
|
+
return;
|
|
118
|
+
let entries = [];
|
|
119
|
+
try {
|
|
120
|
+
entries = await fs.readdir(fullDir, { withFileTypes: true });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
for (const entry of entries) {
|
|
126
|
+
const rel = path.join(relDir, entry.name).replace(/\\/gu, "/");
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
await walkGeneratedCandidates(projectRoot, rel, candidates);
|
|
129
|
+
}
|
|
130
|
+
else if (entry.isFile() && isManagedGeneratedPath(rel)) {
|
|
131
|
+
candidates.push(rel);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function formatManagedValidationIssue(issue) {
|
|
136
|
+
const subject = issue.path ?? (issue.index !== undefined ? `resources[${issue.index}]` : "manifest");
|
|
137
|
+
return `${subject} ${issue.field}: ${issue.message}`;
|
|
138
|
+
}
|
|
97
139
|
async function generatedCliEntrypointsOk(projectRoot) {
|
|
98
140
|
const hookScripts = ["stage-complete.mjs", "start-flow.mjs", "run-hook.mjs"];
|
|
99
141
|
const problems = [];
|
|
@@ -690,8 +732,140 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
690
732
|
ok: await gitignoreHasRequiredPatterns(projectRoot),
|
|
691
733
|
details: ".gitignore must include cclaw ignore block"
|
|
692
734
|
});
|
|
735
|
+
const managedManifestPath = path.join(projectRoot, MANAGED_RESOURCE_MANIFEST_REL_PATH);
|
|
736
|
+
let rawManagedManifest = null;
|
|
737
|
+
let managedManifestParseError = null;
|
|
738
|
+
if (await exists(managedManifestPath)) {
|
|
739
|
+
try {
|
|
740
|
+
rawManagedManifest = JSON.parse(await fs.readFile(managedManifestPath, "utf8"));
|
|
741
|
+
}
|
|
742
|
+
catch (error) {
|
|
743
|
+
managedManifestParseError = error instanceof Error ? error.message : String(error);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const managedManifestValidationIssues = rawManagedManifest === null
|
|
747
|
+
? []
|
|
748
|
+
: validateManagedResourceManifest(rawManagedManifest);
|
|
749
|
+
const managedManifest = await readManagedResourceManifest(projectRoot).catch(() => null);
|
|
750
|
+
checks.push({
|
|
751
|
+
name: "managed_resources:manifest_exists",
|
|
752
|
+
ok: managedManifest !== null,
|
|
753
|
+
details: managedManifest
|
|
754
|
+
? `${MANAGED_RESOURCE_MANIFEST_REL_PATH} tracks ${managedManifest.resources.length} managed generated file(s)`
|
|
755
|
+
: `${MANAGED_RESOURCE_MANIFEST_REL_PATH} missing; run cclaw sync to establish generated file ownership`
|
|
756
|
+
});
|
|
757
|
+
checks.push({
|
|
758
|
+
name: "managed_resources:manifest_valid",
|
|
759
|
+
ok: managedManifestParseError === null && managedManifestValidationIssues.length === 0,
|
|
760
|
+
details: managedManifestParseError
|
|
761
|
+
? `${MANAGED_RESOURCE_MANIFEST_REL_PATH} is unreadable JSON (${managedManifestParseError})`
|
|
762
|
+
: managedManifestValidationIssues.length === 0
|
|
763
|
+
? `${MANAGED_RESOURCE_MANIFEST_REL_PATH} metadata is structurally valid`
|
|
764
|
+
: `malformed managed resource metadata: ${managedManifestValidationIssues.slice(0, 12).map(formatManagedValidationIssue).join("; ")}`
|
|
765
|
+
});
|
|
766
|
+
if (managedManifest) {
|
|
767
|
+
checks.push({
|
|
768
|
+
name: "managed_resources:manifest_package_version",
|
|
769
|
+
ok: managedManifest.packageVersion === CCLAW_VERSION,
|
|
770
|
+
details: managedManifest.packageVersion === CCLAW_VERSION
|
|
771
|
+
? `${MANAGED_RESOURCE_MANIFEST_REL_PATH} packageVersion matches cclaw ${CCLAW_VERSION}`
|
|
772
|
+
: `${MANAGED_RESOURCE_MANIFEST_REL_PATH} packageVersion ${managedManifest.packageVersion} is stale; current cclaw is ${CCLAW_VERSION}. Run cclaw upgrade.`
|
|
773
|
+
});
|
|
774
|
+
const rawResources = toObject(rawManagedManifest)?.resources;
|
|
775
|
+
const stalePackageEntries = Array.isArray(rawResources)
|
|
776
|
+
? rawResources.flatMap((entry, index) => {
|
|
777
|
+
const obj = toObject(entry);
|
|
778
|
+
if (!obj)
|
|
779
|
+
return [];
|
|
780
|
+
const entryPath = typeof obj.path === "string" ? obj.path : `resources[${index}]`;
|
|
781
|
+
return typeof obj.packageVersion === "string" && obj.packageVersion !== CCLAW_VERSION
|
|
782
|
+
? [`${entryPath} (${obj.packageVersion})`]
|
|
783
|
+
: [];
|
|
784
|
+
})
|
|
785
|
+
: [];
|
|
786
|
+
const stale = [];
|
|
787
|
+
const missing = [];
|
|
788
|
+
for (const entry of managedManifest.resources) {
|
|
789
|
+
const filePath = path.join(projectRoot, entry.path);
|
|
790
|
+
if (!(await exists(filePath))) {
|
|
791
|
+
missing.push(entry.path);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
const currentHash = hashManagedResourceContent(await fs.readFile(filePath));
|
|
795
|
+
if (currentHash !== entry.sha256) {
|
|
796
|
+
stale.push(entry.path);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
checks.push({
|
|
800
|
+
name: "managed_resources:entry_package_versions",
|
|
801
|
+
ok: stalePackageEntries.length === 0,
|
|
802
|
+
details: stalePackageEntries.length === 0
|
|
803
|
+
? `all manifest entries match cclaw ${CCLAW_VERSION}`
|
|
804
|
+
: `manifest entries have stale packageVersion; run cclaw upgrade: ${stalePackageEntries.slice(0, 12).join(", ")}`
|
|
805
|
+
});
|
|
806
|
+
checks.push({
|
|
807
|
+
name: "managed_resources:user_modified",
|
|
808
|
+
ok: stale.length === 0,
|
|
809
|
+
details: stale.length === 0
|
|
810
|
+
? "all manifest-tracked managed files match recorded hashes"
|
|
811
|
+
: `manifest-tracked managed files have user modifications: ${stale.slice(0, 12).join(", ")}`
|
|
812
|
+
});
|
|
813
|
+
checks.push({
|
|
814
|
+
name: "managed_resources:stale_entries",
|
|
815
|
+
ok: missing.length === 0,
|
|
816
|
+
details: missing.length === 0
|
|
817
|
+
? "all manifest entries still exist"
|
|
818
|
+
: `manifest entries point to missing files: ${missing.slice(0, 12).join(", ")}`
|
|
819
|
+
});
|
|
820
|
+
const manifestPaths = new Set(managedManifest.resources.map((entry) => entry.path));
|
|
821
|
+
const candidates = [];
|
|
822
|
+
for (const relDir of [
|
|
823
|
+
`${RUNTIME_ROOT}/commands`,
|
|
824
|
+
`${RUNTIME_ROOT}/skills`,
|
|
825
|
+
`${RUNTIME_ROOT}/templates`,
|
|
826
|
+
`${RUNTIME_ROOT}/rules`,
|
|
827
|
+
`${RUNTIME_ROOT}/agents`,
|
|
828
|
+
`${RUNTIME_ROOT}/hooks`,
|
|
829
|
+
".claude/commands",
|
|
830
|
+
".cursor/commands",
|
|
831
|
+
".opencode/commands",
|
|
832
|
+
".opencode/agents",
|
|
833
|
+
".codex/agents",
|
|
834
|
+
".agents/skills",
|
|
835
|
+
".claude/hooks",
|
|
836
|
+
".cursor",
|
|
837
|
+
".codex",
|
|
838
|
+
".opencode/plugins"
|
|
839
|
+
]) {
|
|
840
|
+
await walkGeneratedCandidates(projectRoot, relDir, candidates);
|
|
841
|
+
}
|
|
842
|
+
for (const rel of ["AGENTS.md", "CLAUDE.md"]) {
|
|
843
|
+
if ((await exists(path.join(projectRoot, rel))) && isManagedGeneratedPath(rel)) {
|
|
844
|
+
candidates.push(rel);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const orphaned = [...new Set(candidates)].filter((rel) => !manifestPaths.has(rel)).sort();
|
|
848
|
+
checks.push({
|
|
849
|
+
name: "managed_resources:orphaned_generated_files",
|
|
850
|
+
ok: orphaned.length === 0,
|
|
851
|
+
details: orphaned.length === 0
|
|
852
|
+
? "no orphaned generated files detected across known cclaw surfaces"
|
|
853
|
+
: `warning: generated-looking files are not tracked in manifest: ${orphaned.slice(0, 12).join(", ")}`
|
|
854
|
+
});
|
|
855
|
+
}
|
|
693
856
|
let configuredHarnesses = [];
|
|
694
857
|
let parsedConfig = null;
|
|
858
|
+
const configFileExists = await exists(path.join(projectRoot, RUNTIME_ROOT, "config.yaml"));
|
|
859
|
+
if (!configFileExists) {
|
|
860
|
+
const detectedHarnesses = await detectHarnesses(projectRoot).catch(() => []);
|
|
861
|
+
checks.push({
|
|
862
|
+
name: "config:present",
|
|
863
|
+
ok: detectedHarnesses.length === 0,
|
|
864
|
+
details: detectedHarnesses.length > 0
|
|
865
|
+
? `${RUNTIME_ROOT}/config.yaml is missing but harness markers were detected (${detectedHarnesses.join(", ")}). Run cclaw sync --harnesses=${detectedHarnesses.join(",")} or cclaw sync --interactive.`
|
|
866
|
+
: `${RUNTIME_ROOT}/config.yaml missing and no harness markers were detected; run cclaw init or cclaw sync when ready.`
|
|
867
|
+
});
|
|
868
|
+
}
|
|
695
869
|
try {
|
|
696
870
|
const config = await readConfig(projectRoot);
|
|
697
871
|
parsedConfig = config;
|
|
@@ -811,6 +985,8 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
811
985
|
const hasCcNext = content.includes("/cc-next");
|
|
812
986
|
const hasCcIdeate = content.includes("/cc-ideate");
|
|
813
987
|
const hasCcView = content.includes("/cc-view");
|
|
988
|
+
const hasCcFinish = content.includes("/cc-finish");
|
|
989
|
+
const hasCcCancel = content.includes("/cc-cancel");
|
|
814
990
|
const hasVerification = content.includes("Verification Discipline");
|
|
815
991
|
const hasMinimalMarker = content.includes("intentionally minimal for cross-project use");
|
|
816
992
|
const hasMetaSkillPointer = content.includes(".cclaw/skills/using-cclaw/SKILL.md");
|
|
@@ -819,6 +995,8 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
819
995
|
&& hasCcNext
|
|
820
996
|
&& hasCcIdeate
|
|
821
997
|
&& hasCcView
|
|
998
|
+
&& hasCcFinish
|
|
999
|
+
&& hasCcCancel
|
|
822
1000
|
&& hasVerification
|
|
823
1001
|
&& hasMinimalMarker
|
|
824
1002
|
&& hasMetaSkillPointer;
|
|
@@ -828,7 +1006,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
828
1006
|
ok: agentsBlockOk,
|
|
829
1007
|
details: `${agentsFile} must contain the managed cclaw marker block with routing, verification, and minimal detail pointer`
|
|
830
1008
|
});
|
|
831
|
-
for (const cmd of ["start", "next", "ideate", "view"]) {
|
|
1009
|
+
for (const cmd of ["start", "next", "ideate", "view", "finish", "cancel"]) {
|
|
832
1010
|
const cmdPath = path.join(projectRoot, RUNTIME_ROOT, "commands", `${cmd}.md`);
|
|
833
1011
|
checks.push({
|
|
834
1012
|
name: `utility_command:${cmd}`,
|
|
@@ -854,6 +1032,8 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
854
1032
|
["learnings", "learnings"],
|
|
855
1033
|
["flow-ideate", "flow-ideate"],
|
|
856
1034
|
["flow-view", "flow-view"],
|
|
1035
|
+
["flow-finish", "flow-finish"],
|
|
1036
|
+
["flow-cancel", "flow-cancel"],
|
|
857
1037
|
["subagent-dev", "sdd"],
|
|
858
1038
|
["parallel-dispatch", "parallel-agents"],
|
|
859
1039
|
["session", "session"],
|
|
@@ -2016,6 +2196,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
2016
2196
|
severity: check.severity ?? metadata.severity,
|
|
2017
2197
|
summary: check.summary ?? metadata.summary,
|
|
2018
2198
|
fix: check.fix ?? metadata.fix,
|
|
2199
|
+
actionGroup: check.actionGroup ?? metadata.actionGroup,
|
|
2019
2200
|
docRef: check.docRef ?? metadata.docRef
|
|
2020
2201
|
};
|
|
2021
2202
|
});
|
package/dist/fs-utils.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { getActiveManagedResourceSession } from "./managed-resources.js";
|
|
3
4
|
export async function ensureDir(dirPath) {
|
|
4
5
|
await fs.mkdir(dirPath, { recursive: true });
|
|
5
6
|
}
|
|
@@ -83,6 +84,11 @@ export async function withDirectoryLock(lockPath, fn, options = {}) {
|
|
|
83
84
|
}
|
|
84
85
|
}
|
|
85
86
|
export async function writeFileSafe(filePath, content, options = {}) {
|
|
87
|
+
const managedSession = getActiveManagedResourceSession();
|
|
88
|
+
if (managedSession?.shouldManage(filePath)) {
|
|
89
|
+
await managedSession.writeFileSafe(filePath, content, options);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
86
92
|
await ensureDir(path.dirname(filePath));
|
|
87
93
|
const tempPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
88
94
|
const targetMode = options.mode;
|
package/dist/harness-adapters.js
CHANGED
|
@@ -35,6 +35,20 @@ const UTILITY_SHIMS = [
|
|
|
35
35
|
command: "view",
|
|
36
36
|
skillFolder: "flow-view",
|
|
37
37
|
commandFile: "view.md"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
fileName: "cc-finish.md",
|
|
41
|
+
skillName: "cc-finish",
|
|
42
|
+
command: "finish",
|
|
43
|
+
skillFolder: "flow-finish",
|
|
44
|
+
commandFile: "finish.md"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
fileName: "cc-cancel.md",
|
|
48
|
+
skillName: "cc-cancel",
|
|
49
|
+
command: "cancel",
|
|
50
|
+
skillFolder: "flow-cancel",
|
|
51
|
+
commandFile: "cancel.md"
|
|
38
52
|
}
|
|
39
53
|
];
|
|
40
54
|
/** Skill-kind shim name for the root `/cc` entry point. */
|
|
@@ -179,9 +193,9 @@ export function harnessDispatchSurface(harnessId) {
|
|
|
179
193
|
case "cursor":
|
|
180
194
|
return "Use Cursor Subagent/Task with a generic subagent_type (explore for read-only mapping, generalPurpose for broader work, shell/browser-use when specifically needed) and paste the cclaw role prompt; record fulfillmentMode: \"generic-dispatch\" with evidenceRefs.";
|
|
181
195
|
case "opencode":
|
|
182
|
-
return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
|
|
196
|
+
return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; if agents or plugin registration are missing, run `cclaw sync` and check opencode.json(.c) plugin registration with `cclaw doctor --explain`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
|
|
183
197
|
case "codex":
|
|
184
|
-
return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
|
|
198
|
+
return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; if hooks are inert, set `[features] codex_hooks = true` in ~/.codex/config.toml or rerun init/sync repair, then `cclaw doctor --explain`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
|
|
185
199
|
}
|
|
186
200
|
}
|
|
187
201
|
/**
|
|
@@ -362,13 +376,15 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
|
|
|
362
376
|
| \`/cc-next\` | **Progression.** Advances to the next stage when current is complete. |
|
|
363
377
|
| \`/cc-ideate\` | **Ideate mode.** Generates a ranked repo-improvement backlog before implementation. |
|
|
364
378
|
| \`/cc-view\` | **Read-only router.** Unified entry for status/tree/diff views. |
|
|
379
|
+
| \`/cc-finish\` | **Successful closeout.** Archives a completed run after strict ship closeout gates. |
|
|
380
|
+
| \`/cc-cancel\` | **Non-completion closeout.** Archives a cancelled/abandoned run with a required reason. |
|
|
365
381
|
|
|
366
382
|
Knowledge capture and curation run automatically as part of stage completion
|
|
367
383
|
protocols via the internal \`learnings\` skill — no user-facing command.
|
|
368
384
|
Reusable entries land in \`.cclaw/knowledge.jsonl\` as strict JSONL with
|
|
369
385
|
\`type\`, \`trigger\`, \`action\`, and \`origin_run\` metadata.
|
|
370
386
|
|
|
371
|
-
**Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship, then closeout: retro > compound > archive.
|
|
387
|
+
**Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship, then closeout: retro > compound > archive. Use \`/cc-finish\` for completed runs and \`/cc-cancel\` for cancelled/abandoned runs.
|
|
372
388
|
\`/cc-next\` loads the right stage skill automatically and also drives post-ship closeout. Gates must pass before handoff.
|
|
373
389
|
|
|
374
390
|
### Verification Discipline
|
|
@@ -391,8 +407,8 @@ If the same approach fails three times in a row (same command, same finding, sam
|
|
|
391
407
|
|
|
392
408
|
OpenAI Codex CLI has **no native \`/cc\` slash command** (custom prompts
|
|
393
409
|
were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-next\`,
|
|
394
|
-
\`/cc-ideate\`, \`/cc-view
|
|
395
|
-
Codex they map onto skills cclaw installs at
|
|
410
|
+
\`/cc-ideate\`, \`/cc-view\`, \`/cc-finish\`, and \`/cc-cancel\`
|
|
411
|
+
tokens above describe intent — in Codex they map onto skills cclaw installs at
|
|
396
412
|
\`.agents/skills/cc*/SKILL.md\`. Activate one of two ways:
|
|
397
413
|
|
|
398
414
|
- Type \`/use cc\` (or \`cc-next\`, etc.) at Codex's prompt.
|
|
@@ -474,6 +490,10 @@ function utilityShimBehavior(command) {
|
|
|
474
490
|
return "This is an ideation command, not a flow stage. It may write ideation artifacts/seeds but does not advance flow state.";
|
|
475
491
|
case "view":
|
|
476
492
|
return "This is a read-only view command, not a flow stage. It never mutates flow state.";
|
|
493
|
+
case "finish":
|
|
494
|
+
return "This is a successful closeout utility, not a flow stage. It archives a completed run after ship closeout gates pass and records completed disposition semantics.";
|
|
495
|
+
case "cancel":
|
|
496
|
+
return "This is a non-completion closeout utility, not a flow stage. It requires a reason and archives cancelled or abandoned work without presenting it as completed.";
|
|
477
497
|
default:
|
|
478
498
|
return "This is a utility command, not a flow stage.";
|
|
479
499
|
}
|
|
@@ -555,6 +575,10 @@ function codexSkillDescription(command) {
|
|
|
555
575
|
return `Read-only repo-improvement ideate mode for cclaw. Use when the user types \`/cc-ideate\` or asks to "ideate", "scan the repo for TODOs/tech debt", "generate a backlog", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
|
|
556
576
|
case "view":
|
|
557
577
|
return `Read-only router for cclaw flow views. Use when the user types \`/cc-view\`, \`/cc-view status\`, \`/cc-view tree\`, \`/cc-view diff\`, or asks to "show cclaw status", "show the flow tree", "diff flow state", or wants a snapshot without mutation.`;
|
|
578
|
+
case "finish":
|
|
579
|
+
return `Finish a completed cclaw run. Use when the user types \`/cc-finish\` or asks to finish, complete, close out, or archive a successful run. Runs cclaw archive with completed disposition after strict ship closeout gates.`;
|
|
580
|
+
case "cancel":
|
|
581
|
+
return `Cancel or abandon the active cclaw run. Use when the user types \`/cc-cancel\` or asks to cancel, abandon, stop, discard, or reset an unfinished run. Requires a reason and archives with cancelled/abandoned disposition.`;
|
|
558
582
|
default:
|
|
559
583
|
return `Generated cclaw skill for ${command}.`;
|
|
560
584
|
}
|
package/dist/install.d.ts
CHANGED
|
@@ -4,8 +4,11 @@ export interface InitOptions {
|
|
|
4
4
|
harnesses?: HarnessId[];
|
|
5
5
|
track?: FlowTrack;
|
|
6
6
|
}
|
|
7
|
+
export interface SyncOptions {
|
|
8
|
+
harnesses?: HarnessId[];
|
|
9
|
+
}
|
|
7
10
|
export declare function initCclaw(options: InitOptions): Promise<void>;
|
|
8
|
-
export declare function syncCclaw(projectRoot: string): Promise<void>;
|
|
11
|
+
export declare function syncCclaw(projectRoot: string, options?: SyncOptions): Promise<void>;
|
|
9
12
|
/**
|
|
10
13
|
* Refresh generated files in `.cclaw/` without touching user-authored
|
|
11
14
|
* artifacts, state, or custom config keys. Only the `version` + `flowVersion`
|
package/dist/install.js
CHANGED
|
@@ -10,6 +10,8 @@ import { stageCommandShimMarkdown } from "./content/stage-command.js";
|
|
|
10
10
|
import { ideateCommandContract, ideateCommandSkillMarkdown } from "./content/ideate-command.js";
|
|
11
11
|
import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
|
|
12
12
|
import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
|
|
13
|
+
import { finishCommandContract, finishCommandSkillMarkdown } from "./content/finish-command.js";
|
|
14
|
+
import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
|
|
13
15
|
import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
|
|
14
16
|
import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
|
|
15
17
|
import { ironLawRuntimeDocument, ironLawsSkillMarkdown } from "./content/iron-laws.js";
|
|
@@ -26,6 +28,7 @@ import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
|
|
|
26
28
|
import { CCLAW_AGENTS } from "./content/core-agents.js";
|
|
27
29
|
import { createInitialFlowState } from "./flow-state.js";
|
|
28
30
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
31
|
+
import { ManagedResourceSession, setActiveManagedResourceSession } from "./managed-resources.js";
|
|
29
32
|
import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
|
|
30
33
|
import { HARNESS_ADAPTERS, harnessShimFileNames, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
|
|
31
34
|
import { validateHookDocument } from "./hook-schema.js";
|
|
@@ -451,6 +454,8 @@ async function writeSkills(projectRoot, config) {
|
|
|
451
454
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-ideate", "SKILL.md"), ideateCommandSkillMarkdown());
|
|
452
455
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-start", "SKILL.md"), startCommandSkillMarkdown());
|
|
453
456
|
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-view", "SKILL.md"), viewCommandSkillMarkdown());
|
|
457
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-finish", "SKILL.md"), finishCommandSkillMarkdown());
|
|
458
|
+
await writeFileSafe(runtimePath(projectRoot, "skills", "flow-cancel", "SKILL.md"), cancelCommandSkillMarkdown());
|
|
454
459
|
await writeFileSafe(runtimePath(projectRoot, "skills", "subagent-dev", "SKILL.md"), subagentDrivenDevSkill());
|
|
455
460
|
await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
|
|
456
461
|
await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
|
|
@@ -514,6 +519,8 @@ async function writeEntryCommands(projectRoot) {
|
|
|
514
519
|
await writeFileSafe(runtimePath(projectRoot, "commands", "next.md"), nextCommandContract());
|
|
515
520
|
await writeFileSafe(runtimePath(projectRoot, "commands", "ideate.md"), ideateCommandContract());
|
|
516
521
|
await writeFileSafe(runtimePath(projectRoot, "commands", "view.md"), viewCommandContract());
|
|
522
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", "finish.md"), finishCommandContract());
|
|
523
|
+
await writeFileSafe(runtimePath(projectRoot, "commands", "cancel.md"), cancelCommandContract());
|
|
517
524
|
for (const stage of FLOW_STAGES) {
|
|
518
525
|
await writeFileSafe(runtimePath(projectRoot, "commands", `${stage}.md`), stageCommandShimMarkdown(stage));
|
|
519
526
|
}
|
|
@@ -1085,6 +1092,8 @@ async function cleanStaleFiles(projectRoot) {
|
|
|
1085
1092
|
}
|
|
1086
1093
|
async function materializeRuntime(projectRoot, config, forceStateReset, operation = "sync") {
|
|
1087
1094
|
const sentinelPath = await writeInitSentinel(projectRoot, operation);
|
|
1095
|
+
const managedSession = await ManagedResourceSession.create({ projectRoot, operation });
|
|
1096
|
+
setActiveManagedResourceSession(managedSession);
|
|
1088
1097
|
try {
|
|
1089
1098
|
const harnesses = config.harnesses;
|
|
1090
1099
|
await ensureStructure(projectRoot);
|
|
@@ -1105,14 +1114,21 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
|
|
|
1105
1114
|
await syncHarnessShims(projectRoot, harnesses);
|
|
1106
1115
|
await writeCursorWorkflowRule(projectRoot, harnesses);
|
|
1107
1116
|
await ensureGitignore(projectRoot);
|
|
1117
|
+
await managedSession.commit();
|
|
1108
1118
|
await fs.unlink(sentinelPath).catch(() => undefined);
|
|
1109
1119
|
}
|
|
1110
1120
|
catch (error) {
|
|
1111
1121
|
// Leave the sentinel in place so doctor can surface the interrupted run.
|
|
1112
1122
|
throw error;
|
|
1113
1123
|
}
|
|
1124
|
+
finally {
|
|
1125
|
+
setActiveManagedResourceSession(null);
|
|
1126
|
+
}
|
|
1114
1127
|
}
|
|
1115
1128
|
export async function initCclaw(options) {
|
|
1129
|
+
if (options.harnesses !== undefined && options.harnesses.length === 0) {
|
|
1130
|
+
throw new Error("Select at least one harness.");
|
|
1131
|
+
}
|
|
1116
1132
|
const baseConfig = createDefaultConfig(options.harnesses, options.track);
|
|
1117
1133
|
// Best-effort auto-detect: a Node project gets `typescript`, a Go module
|
|
1118
1134
|
// gets `go`, etc. Skipped entirely when the project root has no manifests.
|
|
@@ -1127,7 +1143,10 @@ export async function initCclaw(options) {
|
|
|
1127
1143
|
await writeConfig(options.projectRoot, config, { mode: "minimal" });
|
|
1128
1144
|
await materializeRuntime(options.projectRoot, config, true, "init");
|
|
1129
1145
|
}
|
|
1130
|
-
export async function syncCclaw(projectRoot) {
|
|
1146
|
+
export async function syncCclaw(projectRoot, options = {}) {
|
|
1147
|
+
if (options.harnesses !== undefined && options.harnesses.length === 0) {
|
|
1148
|
+
throw new Error("Select at least one harness.");
|
|
1149
|
+
}
|
|
1131
1150
|
const configExists = await exists(configPath(projectRoot));
|
|
1132
1151
|
let config = await readConfig(projectRoot);
|
|
1133
1152
|
if (!configExists) {
|
|
@@ -1138,11 +1157,21 @@ export async function syncCclaw(projectRoot) {
|
|
|
1138
1157
|
// Fall back to the previous default (config.harnesses) if no markers
|
|
1139
1158
|
// are found so brand-new projects still bootstrap cleanly.
|
|
1140
1159
|
const detected = await detectHarnesses(projectRoot);
|
|
1141
|
-
const harnesses = detected.length > 0 ? detected : config.harnesses;
|
|
1160
|
+
const harnesses = options.harnesses ?? (detected.length > 0 ? detected : config.harnesses);
|
|
1142
1161
|
const defaultConfig = createDefaultConfig(harnesses);
|
|
1143
1162
|
await writeConfig(projectRoot, defaultConfig);
|
|
1144
1163
|
config = defaultConfig;
|
|
1145
1164
|
}
|
|
1165
|
+
else if (options.harnesses !== undefined) {
|
|
1166
|
+
config = {
|
|
1167
|
+
...config,
|
|
1168
|
+
harnesses: options.harnesses
|
|
1169
|
+
};
|
|
1170
|
+
await writeConfig(projectRoot, config, {
|
|
1171
|
+
mode: "minimal",
|
|
1172
|
+
advancedKeysPresent: await detectAdvancedKeys(projectRoot)
|
|
1173
|
+
});
|
|
1174
|
+
}
|
|
1146
1175
|
await materializeRuntime(projectRoot, config, false, "sync");
|
|
1147
1176
|
}
|
|
1148
1177
|
/**
|
|
@@ -1156,8 +1185,12 @@ export async function syncCclaw(projectRoot) {
|
|
|
1156
1185
|
* minimal — advanced knobs are never silently added.
|
|
1157
1186
|
*/
|
|
1158
1187
|
export async function upgradeCclaw(projectRoot) {
|
|
1188
|
+
const configExists = await exists(configPath(projectRoot));
|
|
1159
1189
|
const advancedKeysPresent = await detectAdvancedKeys(projectRoot);
|
|
1160
|
-
const
|
|
1190
|
+
const detectedHarnesses = configExists ? [] : await detectHarnesses(projectRoot);
|
|
1191
|
+
const existing = configExists
|
|
1192
|
+
? await readConfig(projectRoot)
|
|
1193
|
+
: createDefaultConfig(detectedHarnesses.length > 0 ? detectedHarnesses : undefined);
|
|
1161
1194
|
const upgraded = {
|
|
1162
1195
|
...existing,
|
|
1163
1196
|
version: CCLAW_VERSION,
|
|
@@ -1332,7 +1365,7 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1332
1365
|
try {
|
|
1333
1366
|
const entries = await fs.readdir(codexSkillsRoot);
|
|
1334
1367
|
for (const entry of entries) {
|
|
1335
|
-
if (/^(?:cclaw-)?cc(?:-(?:next|view|ops|ideate|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
|
|
1368
|
+
if (/^(?:cclaw-)?cc(?:-(?:next|view|finish|cancel|ops|ideate|brainstorm|scope|design|spec|plan|tdd|review|ship))?$/u.test(entry)) {
|
|
1336
1369
|
await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
|
|
1337
1370
|
}
|
|
1338
1371
|
}
|
|
@@ -919,13 +919,13 @@ async function runAdvanceStage(projectRoot, args, io) {
|
|
|
919
919
|
});
|
|
920
920
|
const nextActions = [];
|
|
921
921
|
if (validation.delegation.missing.length > 0) {
|
|
922
|
-
nextActions.push(`
|
|
922
|
+
nextActions.push(`Run mandatory delegation(s) for stage "${args.stage}": ${validation.delegation.missing.join(", ")}. These roles are required by the stage schema before advance. If dispatch is impossible, use the waiver fallback only with a user-visible reason: \`node .cclaw/hooks/stage-complete.mjs ${args.stage} --waive-delegation=${validation.delegation.missing.join(",")} --waiver-reason="<why safe>"\`.`);
|
|
923
923
|
}
|
|
924
924
|
if (validation.delegation.missingEvidence.length > 0) {
|
|
925
|
-
nextActions.push(`Role-switch fallback completion needs --evidence-ref or escalate to a real isolated dispatch surface.`);
|
|
925
|
+
nextActions.push(`Role-switch fallback completion needs artifact evidenceRefs naming what the role proved; rerun completion with --evidence-ref=<artifact#anchor> or escalate to a real isolated dispatch surface.`);
|
|
926
926
|
}
|
|
927
927
|
if (validation.delegation.missingDispatchProof.length > 0) {
|
|
928
|
-
nextActions.push(`Isolated completion(s) ${dispatchProofDetails.join(", ") || validation.delegation.missingDispatchProof.join(", ")} lack
|
|
928
|
+
nextActions.push(`Isolated completion(s) ${dispatchProofDetails.join(", ") || validation.delegation.missingDispatchProof.join(", ")} lack event-log dispatch proof. The ledger says completed, but .cclaw/state/delegation-events.jsonl must show scheduled -> launched -> acknowledged -> completed with --span-id, --dispatch-id, --dispatch-surface, --agent-definition-path, ackTs, and completedTs before advancing.`);
|
|
929
929
|
}
|
|
930
930
|
if (validation.delegation.legacyInferredCompletions.length > 0) {
|
|
931
931
|
nextActions.push(`Pre-v3 ledger entries found: ${validation.delegation.legacyInferredCompletions.join(", ")}. Run \`node .cclaw/hooks/delegation-record.mjs --rerecord --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path>\` to upgrade the row to dispatch-proof shape.`);
|
|
@@ -962,15 +962,15 @@ async function runAdvanceStage(projectRoot, args, io) {
|
|
|
962
962
|
io.stderr.write(`cclaw internal advance-stage: validation failed for stage "${args.stage}".\n`);
|
|
963
963
|
if (validation.delegation.missing.length > 0) {
|
|
964
964
|
io.stderr.write(`- missing delegations: ${validation.delegation.missing.join(", ")}\n`);
|
|
965
|
-
io.stderr.write(` next action:
|
|
965
|
+
io.stderr.write(` next action: run the named agent(s) for this stage, or rerun with --waive-delegation=${validation.delegation.missing.join(",")} --waiver-reason="<why safe>" only when the user accepts the safety trade-off.\n`);
|
|
966
966
|
}
|
|
967
967
|
if (validation.delegation.missingEvidence.length > 0) {
|
|
968
968
|
io.stderr.write(`- role-switch evidence missing: ${validation.delegation.missingEvidence.join(", ")}\n`);
|
|
969
|
-
io.stderr.write(` next action: include --evidence-ref=<artifact#anchor> when emitting the completed event, or escalate to a true isolated dispatch surface.\n`);
|
|
969
|
+
io.stderr.write(` next action: include --evidence-ref=<artifact#anchor> when emitting the completed event so the artifact shows what was reviewed/proved, or escalate to a true isolated dispatch surface.\n`);
|
|
970
970
|
}
|
|
971
971
|
if (validation.delegation.missingDispatchProof.length > 0) {
|
|
972
972
|
io.stderr.write(`- isolated completion lacks dispatch proof: ${dispatchProofDetails.join(", ") || validation.delegation.missingDispatchProof.join(", ")}\n`);
|
|
973
|
-
io.stderr.write(` next action:
|
|
973
|
+
io.stderr.write(` next action: repair the event log proof by emitting scheduled -> launched -> acknowledged -> completed with --span-id, --dispatch-id, --dispatch-surface, --agent-definition-path, ackTs, and completedTs before advancing.\n`);
|
|
974
974
|
}
|
|
975
975
|
if (validation.delegation.legacyInferredCompletions.length > 0) {
|
|
976
976
|
io.stderr.write(`- legacy-inferred completions need rerecord: ${validation.delegation.legacyInferredCompletions.join(", ")}\n`);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type HarnessId } from "./types.js";
|
|
2
|
+
import type { WriteFileSafeOptions } from "./fs-utils.js";
|
|
3
|
+
export declare const MANAGED_RESOURCE_MANIFEST_REL_PATH = ".cclaw/state/managed-resources.json";
|
|
4
|
+
export interface ManagedResourceEntry {
|
|
5
|
+
path: string;
|
|
6
|
+
sha256: string;
|
|
7
|
+
owner: "cclaw";
|
|
8
|
+
harness?: HarnessId | "core";
|
|
9
|
+
packageVersion: string;
|
|
10
|
+
prunable: boolean;
|
|
11
|
+
safeToOverwrite: boolean;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
lastBackupPath?: string;
|
|
14
|
+
previousSha256?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ManagedResourceManifest {
|
|
17
|
+
version: 1;
|
|
18
|
+
generatedAt: string;
|
|
19
|
+
packageVersion: string;
|
|
20
|
+
resources: ManagedResourceEntry[];
|
|
21
|
+
}
|
|
22
|
+
interface ManagedResourceSessionOptions {
|
|
23
|
+
projectRoot: string;
|
|
24
|
+
operation: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ManagedResourceValidationIssue {
|
|
27
|
+
index?: number;
|
|
28
|
+
path?: string;
|
|
29
|
+
field: string;
|
|
30
|
+
message: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function isManagedGeneratedPath(relPath: string): boolean;
|
|
33
|
+
export declare function validateManagedResourceEntry(value: unknown, index?: number): ManagedResourceValidationIssue[];
|
|
34
|
+
export declare function validateManagedResourceManifest(value: unknown): ManagedResourceValidationIssue[];
|
|
35
|
+
export declare function isValidManagedResourceEntry(value: unknown): value is ManagedResourceEntry;
|
|
36
|
+
export declare function readManagedResourceManifest(projectRoot: string): Promise<ManagedResourceManifest | null>;
|
|
37
|
+
export declare class ManagedResourceSession {
|
|
38
|
+
private readonly projectRoot;
|
|
39
|
+
private readonly operation;
|
|
40
|
+
private readonly timestamp;
|
|
41
|
+
private readonly previous;
|
|
42
|
+
private readonly touched;
|
|
43
|
+
private constructor();
|
|
44
|
+
static create(options: ManagedResourceSessionOptions): Promise<ManagedResourceSession>;
|
|
45
|
+
shouldManage(filePath: string): boolean;
|
|
46
|
+
writeFileSafe(filePath: string, content: string, options?: WriteFileSafeOptions): Promise<void>;
|
|
47
|
+
commit(): Promise<ManagedResourceManifest>;
|
|
48
|
+
}
|
|
49
|
+
export declare function getActiveManagedResourceSession(): ManagedResourceSession | null;
|
|
50
|
+
export declare function setActiveManagedResourceSession(session: ManagedResourceSession | null): void;
|
|
51
|
+
export declare function isManagedResourcePath(projectRoot: string, filePath: string): boolean;
|
|
52
|
+
export declare function hashManagedResourceContent(content: string | Buffer): string;
|
|
53
|
+
export {};
|