cclaw-cli 1.0.0 → 3.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 (52) hide show
  1. package/dist/artifact-linter/brainstorm.js +15 -1
  2. package/dist/artifact-linter/design.js +14 -0
  3. package/dist/artifact-linter/scope.js +14 -0
  4. package/dist/artifact-linter/shared.d.ts +1 -0
  5. package/dist/artifact-linter/shared.js +32 -0
  6. package/dist/artifact-linter.js +13 -5
  7. package/dist/cli.js +2 -9
  8. package/dist/config.d.ts +11 -67
  9. package/dist/config.js +59 -649
  10. package/dist/content/hook-events.js +1 -5
  11. package/dist/content/hook-manifest.d.ts +6 -4
  12. package/dist/content/hook-manifest.js +16 -65
  13. package/dist/content/hooks.js +54 -14
  14. package/dist/content/meta-skill.js +4 -3
  15. package/dist/content/node-hooks.d.ts +0 -26
  16. package/dist/content/node-hooks.js +459 -157
  17. package/dist/content/observe.js +5 -4
  18. package/dist/content/opencode-plugin.js +1 -78
  19. package/dist/content/skills-elicitation.d.ts +1 -0
  20. package/dist/content/skills-elicitation.js +123 -0
  21. package/dist/content/skills.js +6 -4
  22. package/dist/content/stages/brainstorm.js +7 -3
  23. package/dist/content/stages/design.js +6 -2
  24. package/dist/content/stages/plan.js +2 -2
  25. package/dist/content/stages/scope.js +9 -5
  26. package/dist/content/stages/tdd.js +11 -11
  27. package/dist/content/start-command.js +4 -4
  28. package/dist/content/templates.js +21 -0
  29. package/dist/flow-state.d.ts +7 -0
  30. package/dist/flow-state.js +1 -0
  31. package/dist/gate-evidence.js +1 -5
  32. package/dist/hook-schema.js +3 -0
  33. package/dist/hook-schemas/claude-hooks.v1.json +2 -5
  34. package/dist/hook-schemas/codex-hooks.v1.json +1 -4
  35. package/dist/hook-schemas/cursor-hooks.v1.json +1 -3
  36. package/dist/install.d.ts +2 -7
  37. package/dist/install.js +32 -123
  38. package/dist/internal/advance-stage/advance.js +22 -1
  39. package/dist/internal/advance-stage/parsers.d.ts +1 -0
  40. package/dist/internal/advance-stage/parsers.js +6 -0
  41. package/dist/internal/compound-readiness.js +1 -16
  42. package/dist/internal/early-loop-status.js +1 -3
  43. package/dist/internal/runtime-integrity.js +0 -20
  44. package/dist/policy.js +6 -9
  45. package/dist/run-persistence.d.ts +1 -1
  46. package/dist/run-persistence.js +29 -2
  47. package/dist/runtime/run-hook.mjs +459 -265
  48. package/dist/tdd-verification-evidence.js +6 -18
  49. package/dist/track-heuristics.d.ts +7 -1
  50. package/dist/track-heuristics.js +12 -0
  51. package/dist/types.d.ts +0 -56
  52. package/package.json +1 -1
@@ -76,6 +76,13 @@ export const ARTIFACT_TEMPLATES = {
76
76
  |---|---|---|---|
77
77
  | 1 | | | |
78
78
 
79
+ ## Q&A Log
80
+ | Turn | Question | User answer (1-line) | Decision impact |
81
+ |---|---|---|---|
82
+ | 1 | | | |
83
+
84
+ > Append-only by turn. Add one row after each user answer; do not rewrite prior rows.
85
+
79
86
  ## Approach Tier
80
87
  - Tier: lite | standard | deep
81
88
  - Why this tier:
@@ -192,6 +199,13 @@ ${MARKDOWN_CODE_FENCE}
192
199
  - Open questions:
193
200
  - Drift from upstream (or \`None\`):
194
201
 
202
+ ## Q&A Log
203
+ | Turn | Question | User answer (1-line) | Decision impact |
204
+ |---|---|---|---|
205
+ | 1 | | | |
206
+
207
+ > Append-only by turn. Add one row after each user answer; do not rewrite prior rows.
208
+
195
209
  ## Pre-Scope System Audit
196
210
  | Check | Command | Findings |
197
211
  |---|---|---|
@@ -427,6 +441,13 @@ ${MARKDOWN_CODE_FENCE}
427
441
  - Open questions:
428
442
  - Drift from upstream (or \`None\`):
429
443
 
444
+ ## Q&A Log
445
+ | Turn | Question | User answer (1-line) | Decision impact |
446
+ |---|---|---|---|
447
+ | 1 | | | |
448
+
449
+ > Append-only by turn. Add one row after each user answer; do not rewrite prior rows.
450
+
430
451
  ## Codebase Investigation
431
452
  | File | Current responsibility | Patterns discovered | Existing fit / reuse candidate |
432
453
  |---|---|---|---|
@@ -82,11 +82,18 @@ export interface FlowState {
82
82
  staleStages: Partial<Record<FlowStage, StaleStageMarker>>;
83
83
  /** Chronological rewind operations for the active run. */
84
84
  rewinds: RewindRecord[];
85
+ /** Optional per-stage interaction hints carried from prior stage transitions. */
86
+ interactionHints?: Partial<Record<FlowStage, StageInteractionHint>>;
85
87
  /** Mandatory retrospective gate status before archive. */
86
88
  retro: RetroState;
87
89
  /** Ship → post_ship_review → archive substate for resumable closeout. */
88
90
  closeout: CloseoutState;
89
91
  }
92
+ export interface StageInteractionHint {
93
+ skipQuestions?: boolean;
94
+ sourceStage?: FlowStage;
95
+ recordedAt?: string;
96
+ }
90
97
  export interface InitialFlowStateOptions {
91
98
  activeRunId?: string;
92
99
  track?: FlowTrack;
@@ -90,6 +90,7 @@ export function createInitialFlowState(activeRunIdOrOptions = {}, maybeTrack) {
90
90
  skippedStages,
91
91
  staleStages: {},
92
92
  rewinds: [],
93
+ interactionHints: {},
93
94
  retro: {
94
95
  required: false,
95
96
  completedAt: undefined,
@@ -2,7 +2,6 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { checkReviewSecurityNoChangeAttestation, checkReviewVerdictConsistency, extractMarkdownSectionBody, lintArtifact, validateReviewArmy } from "./artifact-linter.js";
4
4
  import { resolveArtifactPath } from "./artifact-paths.js";
5
- import { readConfig } from "./config.js";
6
5
  import { RUNTIME_ROOT } from "./constants.js";
7
6
  import { stageSchema } from "./content/stage-schema.js";
8
7
  import { readDelegationLedger } from "./delegation.js";
@@ -179,10 +178,7 @@ async function readEarlyLoopGateSnapshot(projectRoot, flowState) {
179
178
  return { snapshot: onDisk };
180
179
  }
181
180
  try {
182
- const config = await readConfig(projectRoot);
183
- const computed = await computeEarlyLoopStatus(flowState.currentStage, flowState.activeRunId, path.join(stateDir, "early-loop-log.jsonl"), {
184
- maxIterations: config.earlyLoop?.maxIterations
185
- });
181
+ const computed = await computeEarlyLoopStatus(flowState.currentStage, flowState.activeRunId, path.join(stateDir, "early-loop-log.jsonl"));
186
182
  return {
187
183
  snapshot: {
188
184
  stage: computed.stage,
@@ -68,6 +68,9 @@ function validateClaudeLikeEvent(eventName, eventEntries, errors) {
68
68
  if (hook.timeout !== undefined && !isPositiveNumber(hook.timeout)) {
69
69
  errors.push(`hooks.${eventName}[${index}].hooks[${hookIndex}].timeout must be a positive number when present`);
70
70
  }
71
+ if (hook.statusMessage !== undefined && !isNonEmptyString(hook.statusMessage)) {
72
+ errors.push(`hooks.${eventName}[${index}].hooks[${hookIndex}].statusMessage must be a non-empty string when present`);
73
+ }
71
74
  }
72
75
  }
73
76
  }
@@ -2,12 +2,9 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "cclaw://hooks/claude/v1",
4
4
  "harness": "claude",
5
- "schemaVersion": 1,
5
+ "schemaVersion": 2,
6
6
  "requiredEvents": [
7
7
  "SessionStart",
8
- "PreToolUse",
9
- "PostToolUse",
10
- "Stop",
11
- "PreCompact"
8
+ "Stop"
12
9
  ]
13
10
  }
@@ -2,12 +2,9 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "cclaw://hooks/codex/v1",
4
4
  "harness": "codex",
5
- "schemaVersion": 1,
5
+ "schemaVersion": 2,
6
6
  "requiredEvents": [
7
7
  "SessionStart",
8
- "UserPromptSubmit",
9
- "PreToolUse",
10
- "PostToolUse",
11
8
  "Stop"
12
9
  ]
13
10
  }
@@ -2,14 +2,12 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "cclaw://hooks/cursor/v1",
4
4
  "harness": "cursor",
5
- "schemaVersion": 1,
5
+ "schemaVersion": 2,
6
6
  "requiredEvents": [
7
7
  "sessionStart",
8
8
  "sessionResume",
9
9
  "sessionClear",
10
10
  "sessionCompact",
11
- "preToolUse",
12
- "postToolUse",
13
11
  "stop"
14
12
  ]
15
13
  }
package/dist/install.d.ts CHANGED
@@ -11,13 +11,8 @@ export declare function initCclaw(options: InitOptions): Promise<void>;
11
11
  export declare function syncCclaw(projectRoot: string, options?: SyncOptions): Promise<void>;
12
12
  /**
13
13
  * Refresh generated files in `.cclaw/` without touching user-authored
14
- * artifacts, state, or custom config keys. Only the `version` + `flowVersion`
15
- * stamps are rewritten so the on-disk config reflects the installed CLI.
16
- *
17
- * Shape preservation: if the user previously hand-authored advanced keys
18
- * (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
19
- * the yaml. If their existing config is minimal, the upgrade keeps it
20
- * minimal — advanced knobs are never silently added.
14
+ * artifacts or state. Config remains harness-only with managed version
15
+ * stamps.
21
16
  */
22
17
  export declare function upgradeCclaw(projectRoot: string): Promise<void>;
23
18
  export declare function uninstallCclaw(projectRoot: string): Promise<void>;
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,7 +12,7 @@ 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";
@@ -20,7 +20,8 @@ import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildR
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
- import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
23
+ import { adaptiveElicitationSkillMarkdown } from "./content/skills-elicitation.js";
24
+ import { LANGUAGE_RULE_PACK_DIR, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
24
25
  import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
25
26
  import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
26
27
  import { CCLAW_AGENTS } from "./content/core-agents.js";
@@ -406,55 +407,13 @@ async function removeManagedGitHookRelays(projectRoot) {
406
407
  }
407
408
  }
408
409
  async function syncManagedGitHooks(projectRoot, config) {
409
- const hooksDir = await resolveGitHooksDir(projectRoot);
410
- if (!hooksDir) {
411
- return;
412
- }
413
- if (config.gitHookGuards !== true) {
414
- await removeManagedGitHookRelays(projectRoot);
415
- try {
416
- await fs.rm(path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR), { recursive: true, force: true });
417
- }
418
- catch {
419
- // best-effort cleanup
420
- }
421
- return;
422
- }
423
- const runtimeGitHooksDir = path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR);
424
- await ensureDir(runtimeGitHooksDir);
425
- for (const hookName of ["pre-commit", "pre-push"]) {
426
- const runtimePathForHook = path.join(runtimeGitHooksDir, `${hookName}.mjs`);
427
- await writeFileSafe(runtimePathForHook, managedGitRuntimeScript(hookName));
428
- try {
429
- await fs.chmod(runtimePathForHook, 0o755);
430
- }
431
- catch {
432
- // best effort on constrained filesystems
433
- }
410
+ void config;
411
+ await removeManagedGitHookRelays(projectRoot);
412
+ try {
413
+ await fs.rm(path.join(projectRoot, GIT_HOOK_RUNTIME_REL_DIR), { recursive: true, force: true });
434
414
  }
435
- await ensureDir(hooksDir);
436
- for (const hookName of ["pre-commit", "pre-push"]) {
437
- const hookPath = path.join(hooksDir, hookName);
438
- let canWriteRelay = true;
439
- if (await exists(hookPath)) {
440
- try {
441
- const existing = await fs.readFile(hookPath, "utf8");
442
- canWriteRelay = existing.includes(GIT_HOOK_MANAGED_MARKER);
443
- }
444
- catch {
445
- canWriteRelay = false;
446
- }
447
- }
448
- if (!canWriteRelay) {
449
- continue;
450
- }
451
- await writeFileSafe(hookPath, managedGitRelayHook(hookName));
452
- try {
453
- await fs.chmod(hookPath, 0o755);
454
- }
455
- catch {
456
- // best effort on constrained filesystems
457
- }
415
+ catch {
416
+ // best-effort cleanup
458
417
  }
459
418
  }
460
419
  async function ensureStructure(projectRoot) {
@@ -474,7 +433,8 @@ async function writeWavePlansScaffold(projectRoot) {
474
433
  await writeFileSafe(runtimePath(projectRoot, "wave-plans", ".gitkeep"), "");
475
434
  }
476
435
  async function writeSkills(projectRoot, config) {
477
- const skillTrack = config?.defaultTrack ?? "standard";
436
+ void config;
437
+ const skillTrack = "standard";
478
438
  for (const stage of FLOW_STAGES) {
479
439
  const folder = stageSkillFolder(stage);
480
440
  await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage, skillTrack));
@@ -490,6 +450,7 @@ async function writeSkills(projectRoot, config) {
490
450
  await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
491
451
  await writeFileSafe(runtimePath(projectRoot, "skills", "iron-laws", "SKILL.md"), ironLawsSkillMarkdown());
492
452
  await writeFileSafe(runtimePath(projectRoot, "skills", "executing-waves", "SKILL.md"), executingWavesSkillMarkdown());
453
+ await writeFileSafe(runtimePath(projectRoot, "skills", "adaptive-elicitation", "SKILL.md"), adaptiveElicitationSkillMarkdown());
493
454
  await writeFileSafe(runtimePath(projectRoot, "skills", META_SKILL_NAME, "SKILL.md"), usingCclawSkillMarkdown());
494
455
  // In-thread research procedures (no YAML frontmatter, not delegated personas).
495
456
  for (const [fileName, markdown] of Object.entries(RESEARCH_PLAYBOOKS)) {
@@ -501,42 +462,8 @@ async function writeSkills(projectRoot, config) {
501
462
  for (const [folderName, markdown] of Object.entries(SUBAGENT_CONTEXT_SKILLS)) {
502
463
  await writeFileSafe(runtimePath(projectRoot, "skills", folderName, "SKILL.md"), markdown);
503
464
  }
504
- // Language rule packs live under .cclaw/rules/lang/<pack>.md. They are opt-in:
505
- // only the packs listed in config.languageRulePacks are materialised. Any
506
- // legacy per-language skill folders from v0.7.0 (.cclaw/skills/language-*)
507
- // are cleaned up below so the new rules/lang layout is the only truth.
508
- const enabledPacks = config?.languageRulePacks ?? [];
509
- const enabledPackFileNames = new Set();
510
- for (const pack of enabledPacks) {
511
- const fileName = LANGUAGE_RULE_PACK_FILES[pack];
512
- const generator = LANGUAGE_RULE_PACK_GENERATORS[pack];
513
- if (!fileName || !generator)
514
- continue;
515
- enabledPackFileNames.add(fileName);
516
- await writeFileSafe(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR, fileName), generator());
517
- }
518
- // Strict idempotence: once a pack is removed from config, its generated
519
- // file under .cclaw/rules/lang/ must disappear on the next sync. Without
520
- // this loop the directory accumulates a superset of every pack ever
521
- // enabled, which silently keeps stale guidance alive.
522
- const langDir = runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR);
523
- if (await exists(langDir)) {
524
- const knownPackFileNames = new Set(Object.values(LANGUAGE_RULE_PACK_FILES));
525
- let entries = [];
526
- try {
527
- entries = await fs.readdir(langDir);
528
- }
529
- catch {
530
- entries = [];
531
- }
532
- for (const entry of entries) {
533
- if (!knownPackFileNames.has(entry))
534
- continue;
535
- if (enabledPackFileNames.has(entry))
536
- continue;
537
- await fs.rm(path.join(langDir, entry), { force: true });
538
- }
539
- }
465
+ // Wave 21: language packs are no longer materialized from config.
466
+ await fs.rm(runtimePath(projectRoot, ...LANGUAGE_RULE_PACK_DIR), { recursive: true, force: true });
540
467
  for (const legacyFolder of LEGACY_LANGUAGE_RULE_PACK_FOLDERS) {
541
468
  const legacyPath = runtimePath(projectRoot, "skills", legacyFolder);
542
469
  if (await exists(legacyPath)) {
@@ -934,22 +861,10 @@ async function writeHooks(projectRoot, config) {
934
861
  const stateDir = runtimePath(projectRoot, "state");
935
862
  await ensureDir(hooksDir);
936
863
  await ensureDir(stateDir);
937
- const effectiveStrictness = config.strictness ?? "advisory";
938
- await writeFileSafe(runtimePath(projectRoot, "state", "iron-laws.json"), `${JSON.stringify(ironLawRuntimeDocument({
939
- mode: effectiveStrictness,
940
- strictLaws: config.ironLaws?.strictLaws
941
- }), null, 2)}\n`);
942
864
  await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
943
865
  await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
944
866
  await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
945
- const hookRuntimeOptions = {
946
- strictness: effectiveStrictness,
947
- tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
948
- tddProductionPathPatterns: config.tdd?.productionPathPatterns,
949
- compoundRecurrenceThreshold: config.compound?.recurrenceThreshold,
950
- earlyLoopEnabled: config.earlyLoop?.enabled,
951
- earlyLoopMaxIterations: config.earlyLoop?.maxIterations
952
- };
867
+ const hookRuntimeOptions = {};
953
868
  const bundledHookRuntime = await readBundledRunHookRuntimeScript(hookRuntimeOptions);
954
869
  await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), bundledHookRuntime ?? nodeHookRuntimeScript(hookRuntimeOptions));
955
870
  await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
@@ -1080,11 +995,17 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
1080
995
  }
1081
996
  }
1082
997
  async function writeState(projectRoot, config, forceReset = false) {
998
+ void config;
999
+ // Fresh init no longer materializes flow-state.json. The first managed
1000
+ // `/cc <idea>` start-flow call creates the state file.
1001
+ if (!forceReset) {
1002
+ return;
1003
+ }
1083
1004
  const statePath = runtimePath(projectRoot, "state", "flow-state.json");
1084
- if (!forceReset && (await exists(statePath))) {
1005
+ if (await exists(statePath)) {
1085
1006
  return;
1086
1007
  }
1087
- const state = createInitialFlowState({ track: config.defaultTrack ?? "standard" });
1008
+ const state = createInitialFlowState({ track: "standard" });
1088
1009
  await writeFileSafe(statePath, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
1089
1010
  }
1090
1011
  async function cleanLegacyArtifacts(projectRoot) {
@@ -1282,19 +1203,12 @@ export async function initCclaw(options) {
1282
1203
  if (options.harnesses !== undefined && options.harnesses.length === 0) {
1283
1204
  throw new Error("Select at least one harness.");
1284
1205
  }
1285
- const baseConfig = createDefaultConfig(options.harnesses, options.track);
1286
- // Best-effort auto-detect: a Node project gets `typescript`, a Go module
1287
- // gets `go`, etc. Skipped entirely when the project root has no manifests.
1288
- const detectedPacks = await detectLanguageRulePacks(options.projectRoot);
1289
- const config = {
1290
- ...baseConfig,
1291
- languageRulePacks: detectedPacks
1292
- };
1293
- // Write a minimal `config.yaml` — advanced knobs live in docs/config.md
1294
- // and only appear in the on-disk file when the user sets them explicitly
1295
- // or a non-default value was detected (e.g. languageRulePacks).
1206
+ const config = createDefaultConfig(options.harnesses, options.track);
1207
+ // Wave 21: config is always minimal and harness-only.
1296
1208
  await writeConfig(options.projectRoot, config, { mode: "minimal" });
1297
- await materializeRuntime(options.projectRoot, config, true, "init");
1209
+ // Init should scaffold runtime surfaces but leave flow-state creation to the
1210
+ // first managed start-flow invocation.
1211
+ await materializeRuntime(options.projectRoot, config, false, "init");
1298
1212
  }
1299
1213
  export async function syncCclaw(projectRoot, options = {}) {
1300
1214
  if (options.harnesses !== undefined && options.harnesses.length === 0) {
@@ -1330,13 +1244,8 @@ export async function syncCclaw(projectRoot, options = {}) {
1330
1244
  }
1331
1245
  /**
1332
1246
  * Refresh generated files in `.cclaw/` without touching user-authored
1333
- * artifacts, state, or custom config keys. Only the `version` + `flowVersion`
1334
- * stamps are rewritten so the on-disk config reflects the installed CLI.
1335
- *
1336
- * Shape preservation: if the user previously hand-authored advanced keys
1337
- * (e.g. `tdd`, `compound`, `trackHeuristics`, `sliceReview`), those stay in
1338
- * the yaml. If their existing config is minimal, the upgrade keeps it
1339
- * minimal — advanced knobs are never silently added.
1247
+ * artifacts or state. Config remains harness-only with managed version
1248
+ * stamps.
1340
1249
  */
1341
1250
  export async function upgradeCclaw(projectRoot) {
1342
1251
  const configExists = await exists(configPath(projectRoot));
@@ -1418,7 +1327,7 @@ function isManagedRuntimeHookCommand(command) {
1418
1327
  // (e.g. `node .cclaw\hooks\run-hook.mjs ...`) still round-trip through
1419
1328
  // sync without being duplicated alongside freshly generated entries.
1420
1329
  const normalized = command.trim().replace(/\s+/gu, " ").replace(/\\/gu, "/");
1421
- if (/(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.(?:mjs|cmd)(?:"|')?\s+(?:session-start|stop-handoff|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor|verify-current-state)(?:\s|$)/u.test(normalized)) {
1330
+ if (/(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.(?:mjs|cmd)(?:"|')?\s+(?:session-start|stop-handoff|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|pre-tool-pipeline|prompt-pipeline|context-monitor|verify-current-state)(?:\s|$)/u.test(normalized)) {
1422
1331
  return true;
1423
1332
  }
1424
1333
  // Codex UserPromptSubmit non-blocking state nudge.
@@ -40,6 +40,23 @@ function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGu
40
40
  }
41
41
  return natural;
42
42
  }
43
+ function nextInteractionHints(flowState, args, successor) {
44
+ const hints = { ...(flowState.interactionHints ?? {}) };
45
+ delete hints[args.stage];
46
+ if (successor) {
47
+ if (args.skipQuestions) {
48
+ hints[successor] = {
49
+ skipQuestions: true,
50
+ sourceStage: args.stage,
51
+ recordedAt: new Date().toISOString()
52
+ };
53
+ }
54
+ else {
55
+ delete hints[successor];
56
+ }
57
+ }
58
+ return hints;
59
+ }
43
60
  export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage, track, selectedGateIds, evidenceByGate) {
44
61
  const gateId = AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage];
45
62
  if (!gateId)
@@ -243,6 +260,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
243
260
  mode: "mandatory",
244
261
  status: "waived",
245
262
  waiverReason,
263
+ runId: flowState.activeRunId,
246
264
  fulfillmentMode: "role-switch",
247
265
  ts: new Date().toISOString()
248
266
  });
@@ -452,10 +470,12 @@ export async function runAdvanceStage(projectRoot, args, io) {
452
470
  : flowState.completedStages.includes(args.stage)
453
471
  ? [...flowState.completedStages]
454
472
  : [...flowState.completedStages, args.stage];
473
+ const interactionHints = nextInteractionHints(flowState, args, successor);
455
474
  const finalState = {
456
475
  ...candidateState,
457
476
  completedStages,
458
- currentStage: successor ?? args.stage
477
+ currentStage: successor ?? args.stage,
478
+ interactionHints
459
479
  };
460
480
  await writeFlowState(projectRoot, finalState);
461
481
  if (!args.quiet) {
@@ -466,6 +486,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
466
486
  nextStage: successor,
467
487
  currentStage: finalState.currentStage,
468
488
  completedStages: finalState.completedStages,
489
+ skipQuestionsHint: args.skipQuestions,
469
490
  learnings: {
470
491
  parsed: learningsHarvest.parsedEntries,
471
492
  appended: learningsHarvest.appendedEntries,
@@ -8,6 +8,7 @@ export interface AdvanceStageArgs {
8
8
  waiverReason?: string;
9
9
  acceptProactiveWaiver: boolean;
10
10
  acceptProactiveWaiverReason?: string;
11
+ skipQuestions: boolean;
11
12
  quiet: boolean;
12
13
  json: boolean;
13
14
  }
@@ -12,6 +12,7 @@ export function parseAdvanceStageArgs(tokens) {
12
12
  let waiverReason;
13
13
  let acceptProactiveWaiver = false;
14
14
  let acceptProactiveWaiverReason;
15
+ let skipQuestions = false;
15
16
  let quiet = false;
16
17
  let json = false;
17
18
  for (let i = 0; i < flagTokens.length; i += 1) {
@@ -80,6 +81,10 @@ export function parseAdvanceStageArgs(tokens) {
80
81
  acceptProactiveWaiver = true;
81
82
  continue;
82
83
  }
84
+ if (token === "--skip-questions") {
85
+ skipQuestions = true;
86
+ continue;
87
+ }
83
88
  if (token === "--accept-proactive-waiver-reason") {
84
89
  if (!nextToken || nextToken.startsWith("--")) {
85
90
  throw new Error("--accept-proactive-waiver-reason requires a text value.");
@@ -102,6 +107,7 @@ export function parseAdvanceStageArgs(tokens) {
102
107
  waiverReason,
103
108
  acceptProactiveWaiver,
104
109
  acceptProactiveWaiverReason,
110
+ skipQuestions,
105
111
  quiet,
106
112
  json
107
113
  };
@@ -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")) {
@@ -42,5 +42,5 @@ export declare function flowStateLockPathFor(projectRoot: string): string;
42
42
  interface EnsureRunSystemOptions {
43
43
  createIfMissing?: boolean;
44
44
  }
45
- export declare function ensureRunSystem(projectRoot: string, _options?: EnsureRunSystemOptions): Promise<FlowState>;
45
+ export declare function ensureRunSystem(projectRoot: string, options?: EnsureRunSystemOptions): Promise<FlowState>;
46
46
  export {};