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.
- package/dist/artifact-linter/brainstorm.js +15 -1
- package/dist/artifact-linter/design.js +14 -0
- package/dist/artifact-linter/scope.js +14 -0
- package/dist/artifact-linter/shared.d.ts +1 -0
- package/dist/artifact-linter/shared.js +32 -0
- package/dist/artifact-linter.js +13 -5
- package/dist/cli.js +2 -9
- package/dist/config.d.ts +11 -67
- package/dist/config.js +59 -649
- package/dist/content/hook-events.js +1 -5
- package/dist/content/hook-manifest.d.ts +6 -4
- package/dist/content/hook-manifest.js +16 -65
- package/dist/content/hooks.js +54 -14
- package/dist/content/meta-skill.js +4 -3
- package/dist/content/node-hooks.d.ts +0 -26
- package/dist/content/node-hooks.js +459 -157
- package/dist/content/observe.js +5 -4
- package/dist/content/opencode-plugin.js +1 -78
- package/dist/content/skills-elicitation.d.ts +1 -0
- package/dist/content/skills-elicitation.js +123 -0
- package/dist/content/skills.js +6 -4
- package/dist/content/stages/brainstorm.js +7 -3
- package/dist/content/stages/design.js +6 -2
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/scope.js +9 -5
- package/dist/content/stages/tdd.js +11 -11
- package/dist/content/start-command.js +4 -4
- package/dist/content/templates.js +21 -0
- package/dist/flow-state.d.ts +7 -0
- package/dist/flow-state.js +1 -0
- package/dist/gate-evidence.js +1 -5
- package/dist/hook-schema.js +3 -0
- package/dist/hook-schemas/claude-hooks.v1.json +2 -5
- package/dist/hook-schemas/codex-hooks.v1.json +1 -4
- package/dist/hook-schemas/cursor-hooks.v1.json +1 -3
- package/dist/install.d.ts +2 -7
- package/dist/install.js +32 -123
- package/dist/internal/advance-stage/advance.js +22 -1
- package/dist/internal/advance-stage/parsers.d.ts +1 -0
- package/dist/internal/advance-stage/parsers.js +6 -0
- package/dist/internal/compound-readiness.js +1 -16
- package/dist/internal/early-loop-status.js +1 -3
- package/dist/internal/runtime-integrity.js +0 -20
- package/dist/policy.js +6 -9
- package/dist/run-persistence.d.ts +1 -1
- package/dist/run-persistence.js +29 -2
- package/dist/runtime/run-hook.mjs +459 -265
- package/dist/tdd-verification-evidence.js +6 -18
- package/dist/track-heuristics.d.ts +7 -1
- package/dist/track-heuristics.js +12 -0
- package/dist/types.d.ts +0 -56
- 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
|
|---|---|---|---|
|
package/dist/flow-state.d.ts
CHANGED
|
@@ -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;
|
package/dist/flow-state.js
CHANGED
package/dist/gate-evidence.js
CHANGED
|
@@ -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
|
|
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,
|
package/dist/hook-schema.js
CHANGED
|
@@ -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":
|
|
5
|
+
"schemaVersion": 2,
|
|
6
6
|
"requiredEvents": [
|
|
7
7
|
"SessionStart",
|
|
8
|
-
"
|
|
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":
|
|
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":
|
|
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
|
|
15
|
-
* stamps
|
|
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,
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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
|
-
|
|
436
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
505
|
-
|
|
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 (
|
|
1005
|
+
if (await exists(statePath)) {
|
|
1085
1006
|
return;
|
|
1086
1007
|
}
|
|
1087
|
-
const state = createInitialFlowState({ track:
|
|
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
|
|
1286
|
-
//
|
|
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
|
-
|
|
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
|
|
1334
|
-
* stamps
|
|
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,
|
|
@@ -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
|
-
|
|
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")
|
|
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: "
|
|
100
|
-
{ file: runtimeFile("hooks/run-hook.mjs"), needle: "
|
|
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: "
|
|
112
|
-
name: "hooks:opencode:
|
|
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: "
|
|
117
|
-
name: "hooks:opencode:
|
|
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,
|
|
45
|
+
export declare function ensureRunSystem(projectRoot: string, options?: EnsureRunSystemOptions): Promise<FlowState>;
|
|
46
46
|
export {};
|