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.
- package/dist/artifact-linter/brainstorm.js +13 -2
- package/dist/artifact-linter/design.js +14 -3
- package/dist/artifact-linter/scope.js +20 -33
- package/dist/artifact-linter/shared.d.ts +48 -7
- package/dist/artifact-linter/shared.js +130 -55
- package/dist/artifact-linter.d.ts +11 -1
- package/dist/artifact-linter.js +30 -16
- package/dist/cli.js +2 -9
- package/dist/config.d.ts +11 -67
- package/dist/config.js +59 -649
- package/dist/content/examples.js +8 -0
- package/dist/content/hook-events.js +0 -3
- package/dist/content/hook-manifest.d.ts +5 -2
- package/dist/content/hook-manifest.js +18 -64
- package/dist/content/hooks.js +2 -1
- package/dist/content/node-hooks.d.ts +0 -26
- package/dist/content/node-hooks.js +237 -105
- package/dist/content/observe.js +2 -1
- package/dist/content/opencode-plugin.js +1 -72
- package/dist/content/review-prompts.js +3 -3
- package/dist/content/skills-elicitation.js +58 -20
- package/dist/content/skills.js +19 -6
- package/dist/content/stage-schema.js +36 -18
- package/dist/content/stages/brainstorm.js +3 -3
- package/dist/content/stages/design.js +4 -4
- package/dist/content/stages/plan.js +3 -3
- package/dist/content/stages/schema-types.d.ts +9 -0
- package/dist/content/stages/scope.js +8 -8
- package/dist/content/stages/tdd.js +11 -11
- package/dist/content/templates.d.ts +8 -1
- package/dist/content/templates.js +80 -18
- package/dist/gate-evidence.d.ts +25 -1
- package/dist/gate-evidence.js +35 -8
- package/dist/harness-adapters.js +8 -0
- package/dist/hook-schema.js +3 -0
- package/dist/hook-schemas/claude-hooks.v1.json +0 -2
- package/dist/hook-schemas/codex-hooks.v1.json +0 -3
- package/dist/hook-schemas/cursor-hooks.v1.json +0 -2
- package/dist/install.d.ts +2 -7
- package/dist/install.js +42 -131
- package/dist/internal/advance-stage/advance.d.ts +1 -0
- package/dist/internal/advance-stage/advance.js +5 -2
- 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/runtime/run-hook.mjs +237 -213
- package/dist/tdd-verification-evidence.js +6 -18
- package/dist/types.d.ts +0 -56
- 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,
|
|
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 {
|
|
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,
|
|
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
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
507
|
-
|
|
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
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
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:
|
|
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
|
|
1293
|
-
//
|
|
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
|
|
1343
|
-
* stamps
|
|
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
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
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
|
-
|
|
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")) {
|