cclaw-cli 2.0.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/dist/artifact-linter/brainstorm.js +13 -2
  2. package/dist/artifact-linter/design.js +14 -3
  3. package/dist/artifact-linter/scope.js +20 -33
  4. package/dist/artifact-linter/shared.d.ts +48 -7
  5. package/dist/artifact-linter/shared.js +130 -55
  6. package/dist/artifact-linter.d.ts +11 -1
  7. package/dist/artifact-linter.js +30 -16
  8. package/dist/cli.js +2 -9
  9. package/dist/config.d.ts +11 -67
  10. package/dist/config.js +59 -649
  11. package/dist/content/examples.js +8 -0
  12. package/dist/content/hook-events.js +0 -3
  13. package/dist/content/hook-manifest.d.ts +5 -2
  14. package/dist/content/hook-manifest.js +18 -64
  15. package/dist/content/hooks.js +2 -1
  16. package/dist/content/node-hooks.d.ts +0 -26
  17. package/dist/content/node-hooks.js +237 -105
  18. package/dist/content/observe.js +2 -1
  19. package/dist/content/opencode-plugin.js +1 -72
  20. package/dist/content/review-prompts.js +3 -3
  21. package/dist/content/skills-elicitation.js +58 -20
  22. package/dist/content/skills.js +19 -6
  23. package/dist/content/stage-schema.js +36 -18
  24. package/dist/content/stages/brainstorm.js +3 -3
  25. package/dist/content/stages/design.js +4 -4
  26. package/dist/content/stages/plan.js +3 -3
  27. package/dist/content/stages/schema-types.d.ts +9 -0
  28. package/dist/content/stages/scope.js +8 -8
  29. package/dist/content/stages/tdd.js +11 -11
  30. package/dist/content/templates.d.ts +8 -1
  31. package/dist/content/templates.js +80 -18
  32. package/dist/gate-evidence.d.ts +25 -1
  33. package/dist/gate-evidence.js +35 -8
  34. package/dist/harness-adapters.js +8 -0
  35. package/dist/hook-schema.js +3 -0
  36. package/dist/hook-schemas/claude-hooks.v1.json +0 -2
  37. package/dist/hook-schemas/codex-hooks.v1.json +0 -3
  38. package/dist/hook-schemas/cursor-hooks.v1.json +0 -2
  39. package/dist/install.d.ts +2 -7
  40. package/dist/install.js +42 -131
  41. package/dist/internal/advance-stage/advance.d.ts +1 -0
  42. package/dist/internal/advance-stage/advance.js +5 -2
  43. package/dist/internal/compound-readiness.js +1 -16
  44. package/dist/internal/early-loop-status.js +1 -3
  45. package/dist/internal/runtime-integrity.js +0 -20
  46. package/dist/policy.js +6 -9
  47. package/dist/runtime/run-hook.mjs +237 -213
  48. package/dist/tdd-verification-evidence.js +6 -18
  49. package/dist/types.d.ts +0 -56
  50. package/package.json +1 -1
package/dist/install.js CHANGED
@@ -3,7 +3,7 @@ import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { promisify } from "node:util";
5
5
  import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
6
- import { writeConfig, createDefaultConfig, readConfig, configPath, detectLanguageRulePacks, detectAdvancedKeys } from "./config.js";
6
+ import { writeConfig, createDefaultConfig, readConfig, configPath, detectAdvancedKeys } from "./config.js";
7
7
  import { learnSkillMarkdown } from "./content/learnings.js";
8
8
  import { stageCommandShimMarkdown } from "./content/stage-command.js";
9
9
  import { ideaCommandContract, ideaCommandSkillMarkdown } from "./content/idea.js";
@@ -12,16 +12,16 @@ import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-co
12
12
  import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
13
13
  import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
14
14
  import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
15
- import { ironLawRuntimeDocument, ironLawsSkillMarkdown } from "./content/iron-laws.js";
15
+ import { ironLawsSkillMarkdown } from "./content/iron-laws.js";
16
16
  import { stageCompleteScript, startFlowScript, cancelRunScript, runHookCmdScript, delegationRecordScript, opencodePluginJs, claudeHooksJson, codexHooksJson, cursorHooksJson } from "./content/hooks.js";
17
17
  import { nodeHookRuntimeScript } from "./content/node-hooks.js";
18
18
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
19
- import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
19
+ import { ARTIFACT_TEMPLATES, CURSOR_GUIDELINES_RULE_MDC, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
20
20
  import { STATE_CONTRACTS } from "./content/state-contracts.js";
21
21
  import { REVIEW_PROMPTS } from "./content/review-prompts.js";
22
22
  import { stageSkillFolder, stageSkillMarkdown, executingWavesSkillMarkdown } from "./content/skills.js";
23
23
  import { adaptiveElicitationSkillMarkdown } from "./content/skills-elicitation.js";
24
- import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
24
+ import { LANGUAGE_RULE_PACK_DIR, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
25
25
  import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
26
26
  import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
27
27
  import { CCLAW_AGENTS } from "./content/core-agents.js";
@@ -37,6 +37,7 @@ import { CorruptFlowStateError, ensureRunSystem } from "./runs.js";
37
37
  import { FLOW_STAGES } from "./types.js";
38
38
  const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
39
39
  const CURSOR_RULE_REL_PATH = ".cursor/rules/cclaw-workflow.mdc";
40
+ const CURSOR_GUIDELINES_REL_PATH = ".cursor/rules/cclaw-guidelines.mdc";
40
41
  const GIT_HOOK_MANAGED_MARKER = "cclaw-managed-git-hook";
41
42
  const GIT_HOOK_RUNTIME_REL_DIR = `${RUNTIME_ROOT}/hooks/git`;
42
43
  const INIT_SENTINEL_FILE = ".init-in-progress";
@@ -407,55 +408,13 @@ async function removeManagedGitHookRelays(projectRoot) {
407
408
  }
408
409
  }
409
410
  async function syncManagedGitHooks(projectRoot, config) {
410
- const hooksDir = await resolveGitHooksDir(projectRoot);
411
- if (!hooksDir) {
412
- return;
413
- }
414
- if (config.gitHookGuards !== true) {
415
- await removeManagedGitHookRelays(projectRoot);
416
- try {
417
- await fs.rm(path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR), { recursive: true, force: true });
418
- }
419
- catch {
420
- // best-effort cleanup
421
- }
422
- return;
423
- }
424
- const runtimeGitHooksDir = path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR);
425
- await ensureDir(runtimeGitHooksDir);
426
- for (const hookName of ["pre-commit", "pre-push"]) {
427
- const runtimePathForHook = path.join(runtimeGitHooksDir, `${hookName}.mjs`);
428
- await writeFileSafe(runtimePathForHook, managedGitRuntimeScript(hookName));
429
- try {
430
- await fs.chmod(runtimePathForHook, 0o755);
431
- }
432
- catch {
433
- // best effort on constrained filesystems
434
- }
411
+ void config;
412
+ await removeManagedGitHookRelays(projectRoot);
413
+ try {
414
+ await fs.rm(path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR), { recursive: true, force: true });
435
415
  }
436
- await ensureDir(hooksDir);
437
- for (const hookName of ["pre-commit", "pre-push"]) {
438
- const hookPath = path.join(hooksDir, hookName);
439
- let canWriteRelay = true;
440
- if (await exists(hookPath)) {
441
- try {
442
- const existing = await fs.readFile(hookPath, "utf8");
443
- canWriteRelay = existing.includes(GIT_HOOK_MANAGED_MARKER);
444
- }
445
- catch {
446
- canWriteRelay = false;
447
- }
448
- }
449
- if (!canWriteRelay) {
450
- continue;
451
- }
452
- await writeFileSafe(hookPath, managedGitRelayHook(hookName));
453
- try {
454
- await fs.chmod(hookPath, 0o755);
455
- }
456
- catch {
457
- // best effort on constrained filesystems
458
- }
416
+ catch {
417
+ // best-effort cleanup
459
418
  }
460
419
  }
461
420
  async function ensureStructure(projectRoot) {
@@ -475,7 +434,8 @@ async function writeWavePlansScaffold(projectRoot) {
475
434
  await writeFileSafe(runtimePath(projectRoot, "wave-plans", ".gitkeep"), "");
476
435
  }
477
436
  async function writeSkills(projectRoot, config) {
478
- const skillTrack = config?.defaultTrack ?? "standard";
437
+ void config;
438
+ const skillTrack = "standard";
479
439
  for (const stage of FLOW_STAGES) {
480
440
  const folder = stageSkillFolder(stage);
481
441
  await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage, skillTrack));
@@ -503,42 +463,8 @@ async function writeSkills(projectRoot, config) {
503
463
  for (const [folderName, markdown] of Object.entries(SUBAGENT_CONTEXT_SKILLS)) {
504
464
  await writeFileSafe(runtimePath(projectRoot, "skills", folderName, "SKILL.md"), markdown);
505
465
  }
506
- // Language rule packs live under .cclaw/rules/lang/<pack>.md. They are opt-in:
507
- // only the packs listed in config.languageRulePacks are materialised. Any
508
- // legacy per-language skill folders from v0.7.0 (.cclaw/skills/language-*)
509
- // are cleaned up below so the new rules/lang layout is the only truth.
510
- const enabledPacks = config?.languageRulePacks ?? [];
511
- const enabledPackFileNames = new Set();
512
- for (const pack of enabledPacks) {
513
- const fileName = LANGUAGE_RULE_PACK_FILES[pack];
514
- const generator = LANGUAGE_RULE_PACK_GENERATORS[pack];
515
- if (!fileName || !generator)
516
- continue;
517
- enabledPackFileNames.add(fileName);
518
- await writeFileSafe(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR, fileName), generator());
519
- }
520
- // Strict idempotence: once a pack is removed from config, its generated
521
- // file under .cclaw/rules/lang/ must disappear on the next sync. Without
522
- // this loop the directory accumulates a superset of every pack ever
523
- // enabled, which silently keeps stale guidance alive.
524
- const langDir = runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR);
525
- if (await exists(langDir)) {
526
- const knownPackFileNames = new Set(Object.values(LANGUAGE_RULE_PACK_FILES));
527
- let entries = [];
528
- try {
529
- entries = await fs.readdir(langDir);
530
- }
531
- catch {
532
- entries = [];
533
- }
534
- for (const entry of entries) {
535
- if (!knownPackFileNames.has(entry))
536
- continue;
537
- if (enabledPackFileNames.has(entry))
538
- continue;
539
- await fs.rm(path.join(langDir, entry), { force: true });
540
- }
541
- }
466
+ // Wave 21: language packs are no longer materialized from config.
467
+ await fs.rm(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR), { recursive: true, force: true });
542
468
  for (const legacyFolder of LEGACY_LANGUAGE_RULE_PACK_FOLDERS) {
543
469
  const legacyPath = runtimePath(projectRoot, "skills", legacyFolder);
544
470
  if (await exists(legacyPath)) {
@@ -936,22 +862,10 @@ async function writeHooks(projectRoot, config) {
936
862
  const stateDir = runtimePath(projectRoot, "state");
937
863
  await ensureDir(hooksDir);
938
864
  await ensureDir(stateDir);
939
- const effectiveStrictness = config.strictness ?? "advisory";
940
- await writeFileSafe(runtimePath(projectRoot, "state", "iron-laws.json"), `${JSON.stringify(ironLawRuntimeDocument({
941
- mode: effectiveStrictness,
942
- strictLaws: config.ironLaws?.strictLaws
943
- }), null, 2)}\n`);
944
865
  await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
945
866
  await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
946
867
  await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
947
- const hookRuntimeOptions = {
948
- strictness: effectiveStrictness,
949
- tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
950
- tddProductionPathPatterns: config.tdd?.productionPathPatterns,
951
- compoundRecurrenceThreshold: config.compound?.recurrenceThreshold,
952
- earlyLoopEnabled: config.earlyLoop?.enabled,
953
- earlyLoopMaxIterations: config.earlyLoop?.maxIterations
954
- };
868
+ const hookRuntimeOptions = {};
955
869
  const bundledHookRuntime = await readBundledRunHookRuntimeScript(hookRuntimeOptions);
956
870
  await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), bundledHookRuntime ?? nodeHookRuntimeScript(hookRuntimeOptions));
957
871
  await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
@@ -1029,17 +943,22 @@ async function writeRulebook(projectRoot) {
1029
943
  }
1030
944
  async function writeCursorWorkflowRule(projectRoot, harnesses) {
1031
945
  const rulePath = path.join(projectRoot, CURSOR_RULE_REL_PATH);
946
+ const guidelinesPath = path.join(projectRoot, CURSOR_GUIDELINES_REL_PATH);
1032
947
  if (!harnesses.includes("cursor")) {
1033
- try {
1034
- await fs.rm(rulePath, { force: true });
1035
- }
1036
- catch {
1037
- // best-effort cleanup
948
+ for (const target of [rulePath, guidelinesPath]) {
949
+ try {
950
+ await fs.rm(target, { force: true });
951
+ }
952
+ catch {
953
+ // best-effort cleanup
954
+ }
1038
955
  }
1039
956
  return;
1040
957
  }
1041
958
  await ensureDir(path.dirname(rulePath));
1042
959
  await writeFileSafe(rulePath, CURSOR_WORKFLOW_RULE_MDC);
960
+ await ensureDir(path.dirname(guidelinesPath));
961
+ await writeFileSafe(guidelinesPath, CURSOR_GUIDELINES_RULE_MDC);
1043
962
  }
1044
963
  async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
1045
964
  const enabled = new Set(harnesses);
@@ -1082,6 +1001,7 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
1082
1001
  }
1083
1002
  }
1084
1003
  async function writeState(projectRoot, config, forceReset = false) {
1004
+ void config;
1085
1005
  // Fresh init no longer materializes flow-state.json. The first managed
1086
1006
  // `/cc <idea>` start-flow call creates the state file.
1087
1007
  if (!forceReset) {
@@ -1091,7 +1011,7 @@ async function writeState(projectRoot, config, forceReset = false) {
1091
1011
  if (await exists(statePath)) {
1092
1012
  return;
1093
1013
  }
1094
- const state = createInitialFlowState({ track: config.defaultTrack ?? "standard" });
1014
+ const state = createInitialFlowState({ track: "standard" });
1095
1015
  await writeFileSafe(statePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
1096
1016
  }
1097
1017
  async function cleanLegacyArtifacts(projectRoot) {
@@ -1289,17 +1209,8 @@ export async function initCclaw(options) {
1289
1209
  if (options.harnesses !== undefined && options.harnesses.length === 0) {
1290
1210
  throw new Error("Select at least one harness.");
1291
1211
  }
1292
- const baseConfig = createDefaultConfig(options.harnesses, options.track);
1293
- // Best-effort auto-detect: a Node project gets `typescript`, a Go module
1294
- // gets `go`, etc. Skipped entirely when the project root has no manifests.
1295
- const detectedPacks = await detectLanguageRulePacks(options.projectRoot);
1296
- const config = {
1297
- ...baseConfig,
1298
- languageRulePacks: detectedPacks
1299
- };
1300
- // Write a minimal `config.yaml` — advanced knobs live in docs/config.md
1301
- // and only appear in the on-disk file when the user sets them explicitly
1302
- // or a non-default value was detected (e.g. languageRulePacks).
1212
+ const config = createDefaultConfig(options.harnesses, options.track);
1213
+ // Wave 21: config is always minimal and harness-only.
1303
1214
  await writeConfig(options.projectRoot, config, { mode: "minimal" });
1304
1215
  // Init should scaffold runtime surfaces but leave flow-state creation to the
1305
1216
  // first managed start-flow invocation.
@@ -1339,13 +1250,8 @@ export async function syncCclaw(projectRoot, options = {}) {
1339
1250
  }
1340
1251
  /**
1341
1252
  * Refresh generated files in `.cclaw/` without touching user-authored
1342
- * artifacts, state, or custom config keys. Only the `version` + `flowVersion`
1343
- * stamps are rewritten so the on-disk config reflects the installed CLI.
1344
- *
1345
- * Shape preservation: if the user previously hand-authored advanced keys
1346
- * (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
1347
- * the yaml. If their existing config is minimal, the upgrade keeps it
1348
- * minimal — advanced knobs are never silently added.
1253
+ * artifacts or state. Config remains harness-only with managed version
1254
+ * stamps.
1349
1255
  */
1350
1256
  export async function upgradeCclaw(projectRoot) {
1351
1257
  const configExists = await exists(configPath(projectRoot));
@@ -1564,11 +1470,16 @@ export async function uninstallCclaw(projectRoot) {
1564
1470
  }
1565
1471
  }
1566
1472
  await removeManagedOpenCodePluginConfig(projectRoot, OPENCODE_PLUGIN_REL_PATH);
1567
- try {
1568
- await fs.rm(path.join(projectRoot, CURSOR_RULE_REL_PATH), { force: true });
1569
- }
1570
- catch {
1571
- // best-effort cleanup
1473
+ for (const target of [
1474
+ path.join(projectRoot, CURSOR_RULE_REL_PATH),
1475
+ path.join(projectRoot, CURSOR_GUIDELINES_REL_PATH)
1476
+ ]) {
1477
+ try {
1478
+ await fs.rm(target, { force: true });
1479
+ }
1480
+ catch {
1481
+ // best-effort cleanup
1482
+ }
1572
1483
  }
1573
1484
  const managedDirs = [
1574
1485
  ".claude/hooks",
@@ -35,6 +35,7 @@ interface InternalValidationReport {
35
35
  export declare function hydrateReviewLoopEvidenceFromArtifact(projectRoot: string, stage: FlowStage, track: FlowState["track"], selectedGateIds: string[], evidenceByGate: Record<string, string>): Promise<void>;
36
36
  export declare function buildValidationReport(projectRoot: string, flowState: FlowState, options?: {
37
37
  allowBlockedReviewRoute?: boolean;
38
+ extraStageFlags?: string[];
38
39
  }): Promise<InternalValidationReport>;
39
40
  interface HarvestLearningsResult {
40
41
  ok: boolean;
@@ -89,7 +89,9 @@ export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage,
89
89
  }
90
90
  export async function buildValidationReport(projectRoot, flowState, options = {}) {
91
91
  const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage);
92
- const gates = await verifyCurrentStageGateEvidence(projectRoot, flowState);
92
+ const gates = await verifyCurrentStageGateEvidence(projectRoot, flowState, {
93
+ extraStageFlags: options.extraStageFlags
94
+ });
93
95
  const completedStages = verifyCompletedStagesGateClosure(flowState);
94
96
  const blockedReviewRouteComplete = options.allowBlockedReviewRoute === true
95
97
  && flowState.currentStage === "review"
@@ -332,7 +334,8 @@ export async function runAdvanceStage(projectRoot, args, io) {
332
334
  }
333
335
  };
334
336
  const validation = await buildValidationReport(projectRoot, candidateState, {
335
- allowBlockedReviewRoute: blockedReviewRoute
337
+ allowBlockedReviewRoute: blockedReviewRoute,
338
+ extraStageFlags: args.skipQuestions ? ["--skip-questions"] : undefined
336
339
  });
337
340
  if (!validation.ok) {
338
341
  const ledgerForDiag = await readDelegationLedger(projectRoot).catch(() => ({ entries: [] }));
@@ -1,7 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { RUNTIME_ROOT } from "../constants.js";
4
- import { readConfig } from "../config.js";
5
4
  import { writeFileSafe } from "../fs-utils.js";
6
5
  import { computeCompoundReadiness, readKnowledgeSafely } from "../knowledge-store.js";
7
6
  function parseArgs(tokens) {
@@ -80,21 +79,7 @@ export function formatCompoundReadinessLine(status) {
80
79
  }
81
80
  export async function runCompoundReadinessCommand(projectRoot, argv, io) {
82
81
  const args = parseArgs(argv);
83
- // Reading config is best-effort — but DO surface a stderr warning so
84
- // mis-wired / malformed config shows up in hook-errors / CI logs
85
- // instead of silently degrading to default threshold.
86
- let config = null;
87
- try {
88
- config = await readConfig(projectRoot);
89
- }
90
- catch (error) {
91
- const detail = error instanceof Error ? error.message : String(error);
92
- io.stderr.write(`[cclaw] compound-readiness: failed to read config (${detail}); falling back to default threshold\n`);
93
- }
94
- const threshold = args.threshold ??
95
- (typeof config?.compound?.recurrenceThreshold === "number"
96
- ? config.compound.recurrenceThreshold
97
- : undefined);
82
+ const threshold = args.threshold;
98
83
  const archivedRunsCount = await countArchivedRunsSafely(projectRoot);
99
84
  const { entries } = await readKnowledgeSafely(projectRoot, { lockAware: true });
100
85
  const status = computeCompoundReadiness(entries, {
@@ -1,5 +1,4 @@
1
1
  import path from "node:path";
2
- import { readConfig } from "../config.js";
3
2
  import { RUNTIME_ROOT } from "../constants.js";
4
3
  import { computeEarlyLoopStatus, formatEarlyLoopStatusLine, isEarlyLoopStage } from "../early-loop.js";
5
4
  import { writeFileSafe } from "../fs-utils.js";
@@ -66,14 +65,13 @@ function stateDir(projectRoot) {
66
65
  export async function runEarlyLoopStatusCommand(projectRoot, argv, io) {
67
66
  const args = parseArgs(argv);
68
67
  const flow = await readFlowState(projectRoot).catch(() => null);
69
- const config = await readConfig(projectRoot).catch(() => null);
70
68
  const stage = args.stage ?? flow?.currentStage;
71
69
  if (!isEarlyLoopStage(stage)) {
72
70
  io.stderr.write("cclaw internal early-loop-status: current stage is not an early-loop stage. Pass --stage=brainstorm|scope|design.\n");
73
71
  return 1;
74
72
  }
75
73
  const runId = (args.runId ?? flow?.activeRunId ?? "active").trim() || "active";
76
- const status = await computeEarlyLoopStatus(stage, runId, path.join(stateDir(projectRoot), "early-loop-log.jsonl"), { maxIterations: config?.earlyLoop?.maxIterations });
74
+ const status = await computeEarlyLoopStatus(stage, runId, path.join(stateDir(projectRoot), "early-loop-log.jsonl"));
77
75
  if (args.write) {
78
76
  const target = path.join(stateDir(projectRoot), "early-loop.json");
79
77
  await writeFileSafe(target, `${JSON.stringify(status, null, 2)}\n`);
@@ -2,7 +2,6 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { readConfig } from "../config.js";
4
4
  import { RUNTIME_ROOT } from "../constants.js";
5
- import { classifyCodexHooksFlag, codexConfigPath, readCodexConfig } from "../codex-feature-flag.js";
6
5
  import { exists } from "../fs-utils.js";
7
6
  import { HARNESS_ADAPTERS, harnessShimFileNames, harnessShimSkillNames } from "../harness-adapters.js";
8
7
  import { validateHookDocument } from "../hook-schema.js";
@@ -220,24 +219,6 @@ async function checkHarnessShims(projectRoot, harnesses) {
220
219
  }
221
220
  return findings;
222
221
  }
223
- async function checkCodexHooksFlag(harnesses) {
224
- if (!harnesses.includes("codex")) {
225
- return warningFinding("codex_hooks_flag", true, "Codex harness is not enabled.");
226
- }
227
- const configTomlPath = codexConfigPath();
228
- let existing;
229
- try {
230
- existing = await readCodexConfig(configTomlPath);
231
- }
232
- catch (error) {
233
- return warningFinding("codex_hooks_flag", false, "Could not read Codex config.toml to validate codex_hooks.", [error instanceof Error ? error.message : String(error)]);
234
- }
235
- const state = classifyCodexHooksFlag(existing);
236
- if (state === "enabled") {
237
- return warningFinding("codex_hooks_flag", true, "Codex hooks feature flag is enabled.");
238
- }
239
- return warningFinding("codex_hooks_flag", false, "Codex hooks file is present, but [features] codex_hooks is not true in Codex config.", [`configPath: ${configTomlPath}`, `state: ${state}`]);
240
- }
241
222
  function buildReport(findings) {
242
223
  const errors = findings.filter((finding) => !finding.ok && finding.severity === "error").length;
243
224
  const warnings = findings.filter((finding) => !finding.ok && finding.severity === "warning").length;
@@ -274,7 +255,6 @@ export async function runRuntimeIntegrityCommand(projectRoot, argv, io) {
274
255
  findings.push(await checkHookDocument(projectRoot, harness));
275
256
  }
276
257
  }
277
- findings.push(await checkCodexHooksFlag(harnesses));
278
258
  const report = buildReport(findings);
279
259
  if (!args.quiet) {
280
260
  if (args.json) {
package/dist/policy.js CHANGED
@@ -96,11 +96,8 @@ export async function policyChecks(projectRoot, options = {}) {
96
96
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Resume Protocol", name: "utility_skill:session:resume" },
97
97
  { file: runtimeFile("skills/brainstorm/SKILL.md"), needle: "## Shared Stage Guidance", name: "stage_skill:shared_guidance_inline" },
98
98
  { file: runtimeFile("hooks/run-hook.mjs"), needle: "activeRunId", name: "hooks:session_start:active_run" },
99
- { file: runtimeFile("hooks/run-hook.mjs"), needle: "write_to_cclaw_runtime", name: "hooks:guard:risky_write_advisory" },
100
- { file: runtimeFile("hooks/run-hook.mjs"), needle: "stage_invocation_without_recent_flow_read", name: "hooks:workflow_guard:flow_read_reason" },
101
- { file: runtimeFile("hooks/run-hook.mjs"), needle: "stage_jump_", name: "hooks:workflow_guard:stage_jump_reason" },
102
- { file: runtimeFile("hooks/run-hook.mjs"), needle: "tdd_write_without_open_red", name: "hooks:workflow_guard:tdd_red_first" },
103
- { file: runtimeFile("hooks/run-hook.mjs"), needle: "context remaining is", name: "hooks:context:threshold_warning" },
99
+ { file: runtimeFile("hooks/run-hook.mjs"), needle: "session-start", name: "hooks:session_start:wired" },
100
+ { file: runtimeFile("hooks/run-hook.mjs"), needle: "stop-handoff", name: "hooks:stop_handoff:wired" },
104
101
  { file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "activeRunId", name: "hooks:opencode:active_run" },
105
102
  { file: runtimeFile("hooks/run-hook.mjs"), needle: "Knowledge digest", name: "hooks:session_start:knowledge_digest" },
106
103
  { file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "Knowledge digest", name: "hooks:opencode:knowledge_digest" }
@@ -108,13 +105,13 @@ export async function policyChecks(projectRoot, options = {}) {
108
105
  if (activeHarnesses.has("opencode")) {
109
106
  utilitySkillChecks.push({
110
107
  file: ".opencode/plugins/cclaw-plugin.mjs",
111
- needle: "\"tool.execute.before\"",
112
- name: "hooks:opencode:deployed_tool_hook"
108
+ needle: "session-start",
109
+ name: "hooks:opencode:deployed_session_start_hook"
113
110
  });
114
111
  utilitySkillChecks.push({
115
112
  file: ".opencode/plugins/cclaw-plugin.mjs",
116
- needle: "workflow-guard",
117
- name: "hooks:opencode:deployed_workflow_guard"
113
+ needle: "stop-handoff",
114
+ name: "hooks:opencode:deployed_stop_handoff_hook"
118
115
  });
119
116
  }
120
117
  if (activeHarnesses.has("cursor")) {