cclaw-cli 0.51.27 → 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.
Files changed (43) hide show
  1. package/dist/artifact-linter.js +73 -16
  2. package/dist/cli.d.ts +17 -1
  3. package/dist/cli.js +185 -49
  4. package/dist/codex-feature-flag.d.ts +1 -1
  5. package/dist/codex-feature-flag.js +1 -1
  6. package/dist/config.js +3 -0
  7. package/dist/content/cancel-command.d.ts +2 -0
  8. package/dist/content/cancel-command.js +25 -0
  9. package/dist/content/finish-command.d.ts +2 -0
  10. package/dist/content/finish-command.js +26 -0
  11. package/dist/content/harness-doc.js +1 -1
  12. package/dist/content/hooks.js +32 -9
  13. package/dist/content/ideate-command.js +12 -7
  14. package/dist/content/next-command.js +17 -13
  15. package/dist/content/node-hooks.js +22 -6
  16. package/dist/content/opencode-plugin.js +1 -1
  17. package/dist/content/stages/review.js +1 -1
  18. package/dist/content/stages/tdd.js +1 -1
  19. package/dist/content/start-command.js +6 -5
  20. package/dist/content/state-contracts.js +1 -1
  21. package/dist/content/status-command.js +4 -3
  22. package/dist/content/track-render-context.d.ts +1 -0
  23. package/dist/content/track-render-context.js +2 -0
  24. package/dist/doctor-registry.d.ts +2 -0
  25. package/dist/doctor-registry.js +37 -10
  26. package/dist/doctor.d.ts +2 -1
  27. package/dist/doctor.js +183 -2
  28. package/dist/fs-utils.js +6 -0
  29. package/dist/harness-adapters.js +29 -5
  30. package/dist/install.d.ts +4 -1
  31. package/dist/install.js +37 -4
  32. package/dist/internal/advance-stage.js +6 -6
  33. package/dist/managed-resources.d.ts +53 -0
  34. package/dist/managed-resources.js +289 -0
  35. package/dist/run-archive.d.ts +8 -0
  36. package/dist/run-archive.js +19 -5
  37. package/dist/runs.d.ts +1 -1
  38. package/dist/runs.js +1 -1
  39. package/dist/tdd-cycle.js +10 -10
  40. package/dist/tdd-verification-evidence.js +4 -4
  41. package/dist/track-heuristics.d.ts +2 -0
  42. package/dist/track-heuristics.js +11 -3
  43. package/package.json +1 -1
@@ -1,5 +1,6 @@
1
1
  function ref(fileName) {
2
- return `docs/${fileName}`;
2
+ const anchor = fileName.replace(/\.md$/u, "").replace(/[^a-z0-9]+/giu, "-").toLowerCase();
3
+ return `README.md#${anchor}`;
3
4
  }
4
5
  const RULES = [
5
6
  {
@@ -8,6 +9,7 @@ const RULES = [
8
9
  severity: "info",
9
10
  summary: "Gate reconciliation status update.",
10
11
  fix: "No action required unless subsequent gate checks fail.",
12
+ actionGroup: "informational",
11
13
  docRef: ref("config.md")
12
14
  }
13
15
  },
@@ -17,6 +19,7 @@ const RULES = [
17
19
  severity: "warning",
18
20
  summary: "Advisory signal; runtime can continue with caution.",
19
21
  fix: "Address when possible to prevent future drift or degraded behavior.",
22
+ actionGroup: "informational",
20
23
  docRef: "README.md"
21
24
  }
22
25
  },
@@ -26,6 +29,7 @@ const RULES = [
26
29
  severity: "warning",
27
30
  summary: "Stage skill quality guardrail check.",
28
31
  fix: "Tune generated stage skill content and re-run `cclaw sync`.",
32
+ actionGroup: "sync",
29
33
  docRef: "README.md"
30
34
  }
31
35
  },
@@ -35,6 +39,7 @@ const RULES = [
35
39
  severity: "error",
36
40
  summary: "Required runtime tooling availability check.",
37
41
  fix: "Install the missing required tool and re-run `cclaw doctor`.",
42
+ actionGroup: "user-action",
38
43
  docRef: "README.md"
39
44
  }
40
45
  },
@@ -43,16 +48,28 @@ const RULES = [
43
48
  metadata: {
44
49
  severity: "error",
45
50
  summary: "Generated runtime surface presence check.",
46
- fix: "Run `cclaw sync` to regenerate runtime files, then re-run doctor.",
51
+ fix: "Run `cclaw sync` to safely regenerate generated runtime files, then re-run doctor.",
52
+ actionGroup: "sync",
47
53
  docRef: "README.md"
48
54
  }
49
55
  },
56
+ {
57
+ test: /^managed_resources:/,
58
+ metadata: {
59
+ severity: "error",
60
+ summary: "Managed generated resource manifest integrity check.",
61
+ fix: "Run `cclaw sync` to refresh managed generated files; inspect upgrade backup paths before discarding local edits.",
62
+ actionGroup: "sync",
63
+ docRef: ref("config.md")
64
+ }
65
+ },
50
66
  {
51
67
  test: /^(hook:|hooks:|lifecycle:|git_hooks:)/,
52
68
  metadata: {
53
69
  severity: "error",
54
70
  summary: "Hook wiring and lifecycle integration check.",
55
- fix: "Repair hook/plugin wiring (usually via `cclaw sync`) and validate harness config.",
71
+ fix: "Run `cclaw sync` to regenerate hook/plugin wiring; if the check still fails, validate harness config and permissions.",
72
+ actionGroup: "sync",
56
73
  docRef: ref("harnesses.md")
57
74
  }
58
75
  },
@@ -61,7 +78,8 @@ const RULES = [
61
78
  metadata: {
62
79
  severity: "error",
63
80
  summary: "Harness shim and routing file consistency check.",
64
- fix: "Regenerate harness adapters via `cclaw sync`; confirm enabled harness list.",
81
+ fix: "Run `cclaw sync` to regenerate harness adapters; confirm enabled harness list if it remains failing.",
82
+ actionGroup: "sync",
65
83
  docRef: ref("harnesses.md")
66
84
  }
67
85
  },
@@ -70,7 +88,8 @@ const RULES = [
70
88
  metadata: {
71
89
  severity: "error",
72
90
  summary: "Flow state and gate evidence consistency check.",
73
- fix: "Repair flow-state artifacts and gate evidence, then run `cclaw doctor --reconcile-gates`.",
91
+ fix: "Repair the named stage artifacts/gate evidence, then run `cclaw doctor --reconcile-gates --explain` to refresh derived gate status only.",
92
+ actionGroup: "stage-work",
74
93
  docRef: ref("config.md")
75
94
  }
76
95
  },
@@ -79,7 +98,8 @@ const RULES = [
79
98
  metadata: {
80
99
  severity: "error",
81
100
  summary: "Knowledge and artifact runtime integrity check.",
82
- fix: "Restore missing runtime files under `.cclaw/` or re-run `cclaw sync`.",
101
+ fix: "Restore the missing `.cclaw/` runtime file or run `cclaw sync` when it is generated surface drift.",
102
+ actionGroup: "sync",
83
103
  docRef: "README.md"
84
104
  }
85
105
  },
@@ -88,7 +108,8 @@ const RULES = [
88
108
  metadata: {
89
109
  severity: "error",
90
110
  summary: "Routing skill and protocol integrity check.",
91
- fix: "Regenerate runtime skills via `cclaw sync`, then re-run doctor.",
111
+ fix: "Run `cclaw sync` to regenerate runtime skills, then re-run doctor.",
112
+ actionGroup: "sync",
92
113
  docRef: ref("harnesses.md")
93
114
  }
94
115
  },
@@ -103,7 +124,8 @@ const RULES = [
103
124
  metadata: {
104
125
  severity: "warning",
105
126
  summary: "Reference/overview doc integrity (non-blocking).",
106
- fix: "Run `cclaw sync` to regenerate the reference doc from the canonical source.",
127
+ fix: "Run `cclaw sync` to regenerate the reference surface from the canonical source.",
128
+ actionGroup: "sync",
107
129
  docRef: ref("harnesses.md")
108
130
  }
109
131
  },
@@ -113,6 +135,7 @@ const RULES = [
113
135
  severity: "info",
114
136
  summary: "Harness reality label for dispatch/proof support.",
115
137
  fix: "No action required; use this label to interpret native/generic/role-switch proof requirements.",
138
+ actionGroup: "informational",
116
139
  docRef: ref("harnesses.md")
117
140
  }
118
141
  },
@@ -121,7 +144,8 @@ const RULES = [
121
144
  metadata: {
122
145
  severity: "error",
123
146
  summary: "Mandatory delegation completion check.",
124
- fix: "Complete or explicitly waive missing mandatory delegations in delegation log.",
147
+ fix: "Run the named mandatory agent, record dispatch proof/evidenceRefs, or explicitly waive it with a user-visible rationale.",
148
+ actionGroup: "user-action",
125
149
  docRef: ref("harnesses.md")
126
150
  }
127
151
  },
@@ -130,7 +154,8 @@ const RULES = [
130
154
  metadata: {
131
155
  severity: "error",
132
156
  summary: "Cross-artifact traceability integrity check.",
133
- fix: "Restore criterion/task/test ID mappings across spec, plan, and tdd artifacts.",
157
+ fix: "Repair criterion/task/test ID mappings across spec, plan, and TDD artifacts, then re-run doctor.",
158
+ actionGroup: "stage-work",
134
159
  docRef: "README.md"
135
160
  }
136
161
  },
@@ -140,6 +165,7 @@ const RULES = [
140
165
  severity: "error",
141
166
  summary: "Config or policy schema consistency check.",
142
167
  fix: "Fix config/rules drift, then run `cclaw sync` and re-run doctor.",
168
+ actionGroup: "user-action",
143
169
  docRef: ref("config.md")
144
170
  }
145
171
  }
@@ -154,6 +180,7 @@ export function doctorCheckMetadata(checkName) {
154
180
  severity: "warning",
155
181
  summary: "Unclassified doctor check.",
156
182
  fix: "Report this check name to cclaw maintainers so doctor-registry can classify it explicitly.",
183
+ actionGroup: "informational",
157
184
  docRef: "README.md"
158
185
  };
159
186
  }
package/dist/doctor.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { DoctorSeverity } from "./doctor-registry.js";
1
+ import type { DoctorActionGroup, DoctorSeverity } from "./doctor-registry.js";
2
2
  export interface DoctorCheck {
3
3
  name: string;
4
4
  ok: boolean;
@@ -6,6 +6,7 @@ export interface DoctorCheck {
6
6
  severity: DoctorSeverity;
7
7
  summary: string;
8
8
  fix: string;
9
+ actionGroup: DoctorActionGroup;
9
10
  docRef?: string;
10
11
  }
11
12
  export interface DoctorOptions {
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;
@@ -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\` tokens above describe intent — in
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`