cclaw-cli 0.10.1 → 0.12.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/README.md +4 -3
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +297 -9
- package/dist/config.js +83 -3
- package/dist/content/core-agents.d.ts +44 -0
- package/dist/content/core-agents.js +225 -0
- package/dist/content/doctor-references.d.ts +2 -0
- package/dist/content/doctor-references.js +144 -0
- package/dist/content/examples.js +1 -1
- package/dist/content/harnesses-doc.d.ts +1 -0
- package/dist/content/harnesses-doc.js +95 -0
- package/dist/content/hook-events.d.ts +4 -0
- package/dist/content/hook-events.js +42 -0
- package/dist/content/hooks.js +81 -11
- package/dist/content/meta-skill.d.ts +0 -8
- package/dist/content/meta-skill.js +51 -341
- package/dist/content/next-command.js +2 -1
- package/dist/content/protocols.d.ts +7 -0
- package/dist/content/protocols.js +123 -0
- package/dist/content/research-playbooks.d.ts +8 -0
- package/dist/content/research-playbooks.js +135 -0
- package/dist/content/skills.js +202 -312
- package/dist/content/stage-common-guidance.d.ts +2 -0
- package/dist/content/stage-common-guidance.js +71 -0
- package/dist/content/stage-schema.d.ts +11 -1
- package/dist/content/stage-schema.js +155 -52
- package/dist/content/start-command.js +19 -13
- package/dist/content/subagents.d.ts +1 -1
- package/dist/content/subagents.js +23 -38
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +49 -11
- package/dist/delegation.d.ts +1 -0
- package/dist/delegation.js +27 -1
- package/dist/doctor-registry.d.ts +8 -0
- package/dist/doctor-registry.js +127 -0
- package/dist/doctor.d.ts +5 -0
- package/dist/doctor.js +133 -27
- package/dist/flow-state.d.ts +4 -0
- package/dist/flow-state.js +4 -1
- package/dist/gate-evidence.d.ts +9 -1
- package/dist/gate-evidence.js +121 -17
- package/dist/harness-adapters.d.ts +7 -0
- package/dist/harness-adapters.js +53 -9
- package/dist/init-detect.d.ts +2 -0
- package/dist/init-detect.js +45 -0
- package/dist/install.js +73 -1
- package/dist/policy.js +21 -13
- package/dist/runs.js +21 -4
- package/dist/track-heuristics.d.ts +12 -0
- package/dist/track-heuristics.js +144 -0
- package/dist/types.d.ts +26 -3
- package/dist/types.js +6 -3
- package/package.json +2 -1
- package/dist/content/agents.d.ts +0 -48
- package/dist/content/agents.js +0 -411
package/dist/doctor.js
CHANGED
|
@@ -4,7 +4,7 @@ import { execFile } from "node:child_process";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { COMMAND_FILE_ORDER, REQUIRED_DIRS, RUNTIME_ROOT } from "./constants.js";
|
|
7
|
-
import { CCLAW_AGENTS } from "./content/agents.js";
|
|
7
|
+
import { CCLAW_AGENTS } from "./content/core-agents.js";
|
|
8
8
|
import { readConfig } from "./config.js";
|
|
9
9
|
import { exists } from "./fs-utils.js";
|
|
10
10
|
import { gitignoreHasRequiredPatterns } from "./gitignore.js";
|
|
@@ -17,10 +17,13 @@ import { checkMandatoryDelegations } from "./delegation.js";
|
|
|
17
17
|
import { buildTraceMatrix } from "./trace-matrix.js";
|
|
18
18
|
import { reconcileAndWriteCurrentStageGateCatalog, verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "./gate-evidence.js";
|
|
19
19
|
import { stageSkillFolder } from "./content/skills.js";
|
|
20
|
+
import { doctorCheckMetadata } from "./doctor-registry.js";
|
|
20
21
|
import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS } from "./content/utility-skills.js";
|
|
21
22
|
import { CONTEXT_MODES, DEFAULT_CONTEXT_MODE } from "./content/contexts.js";
|
|
23
|
+
import { DOCTOR_REFERENCE_MARKDOWN } from "./content/doctor-references.js";
|
|
22
24
|
import { validateHookDocument } from "./hook-schema.js";
|
|
23
25
|
const execFileAsync = promisify(execFile);
|
|
26
|
+
const PREAMBLE_COOLDOWN_MS = 15 * 60 * 1000;
|
|
24
27
|
async function isGitRepo(projectRoot) {
|
|
25
28
|
try {
|
|
26
29
|
await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], { cwd: projectRoot });
|
|
@@ -278,8 +281,8 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
278
281
|
{ id: "iron_law", pattern: /^\*\*IRON LAW — [A-Z]+:\*\* .+$/m, label: "Iron Law punchcard (<EXTREMELY-IMPORTANT> wrapper)" },
|
|
279
282
|
{ id: "hard_gate", pattern: /^## HARD-GATE$/m, label: "## HARD-GATE" },
|
|
280
283
|
{ id: "checklist", pattern: /^## Checklist$/m, label: "## Checklist" },
|
|
281
|
-
{ id: "
|
|
282
|
-
{ id: "
|
|
284
|
+
{ id: "completion_parameters", pattern: /^## Completion Parameters$/m, label: "## Completion Parameters" },
|
|
285
|
+
{ id: "shared_guidance", pattern: /^## Shared Stage Guidance$/m, label: "## Shared Stage Guidance" },
|
|
283
286
|
{ id: "good_vs_bad", pattern: /Good vs Bad/i, label: "Good vs Bad examples" },
|
|
284
287
|
{ id: "anti_patterns", pattern: /^## Anti-Patterns & Red Flags$/m, label: "## Anti-Patterns & Red Flags" }
|
|
285
288
|
];
|
|
@@ -303,14 +306,12 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
303
306
|
const metaContent = await fs.readFile(metaSkillPath, "utf8");
|
|
304
307
|
const requiredSignals = [
|
|
305
308
|
{ id: "instruction_priority", pattern: /Instruction Priority/i, label: "Instruction Priority" },
|
|
306
|
-
{ id: "
|
|
307
|
-
{ id: "
|
|
308
|
-
{ id: "
|
|
309
|
-
{ id: "
|
|
310
|
-
{ id: "
|
|
311
|
-
{ id: "
|
|
312
|
-
{ id: "engineering_ethos", pattern: /Engineering Ethos/i, label: "Engineering Ethos" },
|
|
313
|
-
{ id: "task_classification", pattern: /Task Classification/i, label: "Task Classification" }
|
|
309
|
+
{ id: "routing_flow", pattern: /Routing flow/i, label: "Routing flow" },
|
|
310
|
+
{ id: "task_classification", pattern: /Task classification/i, label: "Task classification" },
|
|
311
|
+
{ id: "stage_map", pattern: /Stage quick map/i, label: "Stage quick map" },
|
|
312
|
+
{ id: "protocol_refs", pattern: /Protocol references/i, label: "Protocol references" },
|
|
313
|
+
{ id: "knowledge_guidance", pattern: /Knowledge guidance/i, label: "Knowledge guidance" },
|
|
314
|
+
{ id: "failure_guardrails", pattern: /Failure guardrails/i, label: "Failure guardrails" }
|
|
314
315
|
];
|
|
315
316
|
const missingMeta = requiredSignals
|
|
316
317
|
.filter((signal) => !signal.pattern.test(metaContent))
|
|
@@ -347,6 +348,20 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
347
348
|
details: refPath
|
|
348
349
|
});
|
|
349
350
|
}
|
|
351
|
+
checks.push({
|
|
352
|
+
name: "harness_ref:matrix",
|
|
353
|
+
ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "references", "harnesses.md")),
|
|
354
|
+
details: `${RUNTIME_ROOT}/references/harnesses.md`
|
|
355
|
+
});
|
|
356
|
+
const doctorRefDir = path.join(projectRoot, RUNTIME_ROOT, "references", "doctor");
|
|
357
|
+
for (const fileName of Object.keys(DOCTOR_REFERENCE_MARKDOWN)) {
|
|
358
|
+
const refPath = path.join(doctorRefDir, fileName);
|
|
359
|
+
checks.push({
|
|
360
|
+
name: `doctor_ref:${fileName.replace(/\.md$/, "")}`,
|
|
361
|
+
ok: await exists(refPath),
|
|
362
|
+
details: refPath
|
|
363
|
+
});
|
|
364
|
+
}
|
|
350
365
|
checks.push({
|
|
351
366
|
name: "gitignore:required_patterns",
|
|
352
367
|
ok: await gitignoreHasRequiredPatterns(projectRoot),
|
|
@@ -782,6 +797,11 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
782
797
|
ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "knowledge.jsonl")),
|
|
783
798
|
details: `${RUNTIME_ROOT}/knowledge.jsonl must exist`
|
|
784
799
|
});
|
|
800
|
+
checks.push({
|
|
801
|
+
name: "knowledge:digest_exists",
|
|
802
|
+
ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "state", "knowledge-digest.md")),
|
|
803
|
+
details: `${RUNTIME_ROOT}/state/knowledge-digest.md must exist`
|
|
804
|
+
});
|
|
785
805
|
// There must be NO legacy markdown knowledge store — JSONL is the only store.
|
|
786
806
|
const legacyKnowledgeMdPath = path.join(projectRoot, RUNTIME_ROOT, "knowledge.md");
|
|
787
807
|
const legacyExists = await exists(legacyKnowledgeMdPath);
|
|
@@ -807,6 +827,11 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
807
827
|
ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "state", "suggestion-memory.json")),
|
|
808
828
|
details: `${RUNTIME_ROOT}/state/suggestion-memory.json must exist for proactive suggestion memory`
|
|
809
829
|
});
|
|
830
|
+
checks.push({
|
|
831
|
+
name: "state:harness_gaps_exists",
|
|
832
|
+
ok: await exists(path.join(projectRoot, RUNTIME_ROOT, "state", "harness-gaps.json")),
|
|
833
|
+
details: `${RUNTIME_ROOT}/state/harness-gaps.json must exist for tiered harness capability tracking`
|
|
834
|
+
});
|
|
810
835
|
const contextModeStatePath = path.join(projectRoot, RUNTIME_ROOT, "state", "context-mode.json");
|
|
811
836
|
checks.push({
|
|
812
837
|
name: "state:context_mode_exists",
|
|
@@ -837,6 +862,81 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
837
862
|
details: modePath
|
|
838
863
|
});
|
|
839
864
|
}
|
|
865
|
+
const preambleLogPath = path.join(projectRoot, RUNTIME_ROOT, "state", "preamble-log.jsonl");
|
|
866
|
+
const preambleLogExists = await exists(preambleLogPath);
|
|
867
|
+
checks.push({
|
|
868
|
+
name: "state:preamble_log_exists",
|
|
869
|
+
ok: preambleLogExists,
|
|
870
|
+
details: `${RUNTIME_ROOT}/state/preamble-log.jsonl must exist for preamble budget tracking`
|
|
871
|
+
});
|
|
872
|
+
if (preambleLogExists) {
|
|
873
|
+
let duplicateHits = 0;
|
|
874
|
+
let parsedEntries = 0;
|
|
875
|
+
let malformedEntries = 0;
|
|
876
|
+
try {
|
|
877
|
+
const now = Date.now();
|
|
878
|
+
const byKey = new Map();
|
|
879
|
+
const raw = await fs.readFile(preambleLogPath, "utf8");
|
|
880
|
+
const lines = raw
|
|
881
|
+
.split("\n")
|
|
882
|
+
.map((line) => line.trim())
|
|
883
|
+
.filter((line) => line.length > 0);
|
|
884
|
+
for (const line of lines) {
|
|
885
|
+
try {
|
|
886
|
+
const parsed = JSON.parse(line);
|
|
887
|
+
const tsRaw = parsed.ts;
|
|
888
|
+
const stageRaw = parsed.stage;
|
|
889
|
+
const triggerRaw = parsed.trigger;
|
|
890
|
+
const hashRaw = parsed.hash;
|
|
891
|
+
if (typeof tsRaw !== "string" ||
|
|
892
|
+
typeof stageRaw !== "string" ||
|
|
893
|
+
typeof triggerRaw !== "string" ||
|
|
894
|
+
typeof hashRaw !== "string") {
|
|
895
|
+
malformedEntries += 1;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
const stamp = Date.parse(tsRaw);
|
|
899
|
+
if (!Number.isFinite(stamp)) {
|
|
900
|
+
malformedEntries += 1;
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (now - stamp > 24 * 60 * 60 * 1000) {
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
parsedEntries += 1;
|
|
907
|
+
const key = `${stageRaw}|${triggerRaw}|${hashRaw}`;
|
|
908
|
+
const bucket = byKey.get(key) ?? [];
|
|
909
|
+
bucket.push(stamp);
|
|
910
|
+
byKey.set(key, bucket);
|
|
911
|
+
}
|
|
912
|
+
catch {
|
|
913
|
+
malformedEntries += 1;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
for (const stamps of byKey.values()) {
|
|
917
|
+
stamps.sort((a, b) => a - b);
|
|
918
|
+
for (let i = 1; i < stamps.length; i += 1) {
|
|
919
|
+
if (stamps[i] - stamps[i - 1] < PREAMBLE_COOLDOWN_MS) {
|
|
920
|
+
duplicateHits += 1;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
catch {
|
|
926
|
+
malformedEntries += 1;
|
|
927
|
+
}
|
|
928
|
+
checks.push({
|
|
929
|
+
name: "warning:preamble:dedup",
|
|
930
|
+
ok: true,
|
|
931
|
+
details: duplicateHits > 0
|
|
932
|
+
? `warning: detected ${duplicateHits} repeated preamble emission(s) inside ${Math.floor(PREAMBLE_COOLDOWN_MS / 60000)}m cooldown window`
|
|
933
|
+
: parsedEntries > 0
|
|
934
|
+
? `preamble budget healthy (${parsedEntries} recent preamble entry/entries checked)`
|
|
935
|
+
: malformedEntries > 0
|
|
936
|
+
? `warning: preamble log exists but entries are malformed (${malformedEntries} line(s) ignored)`
|
|
937
|
+
: "preamble log is empty; no recent preamble emissions recorded"
|
|
938
|
+
});
|
|
939
|
+
}
|
|
840
940
|
let flowState = await readFlowState(projectRoot);
|
|
841
941
|
if (options.reconcileCurrentStageGates === true) {
|
|
842
942
|
const reconciliation = await reconcileAndWriteCurrentStageGateCatalog(projectRoot);
|
|
@@ -908,7 +1008,9 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
908
1008
|
name: "warning:delegation:waived",
|
|
909
1009
|
ok: true,
|
|
910
1010
|
details: delegation.waived.length > 0
|
|
911
|
-
? `warning: waived mandatory delegations for stage "${flowState.currentStage}": ${delegation.waived.join(", ")}
|
|
1011
|
+
? `warning: waived mandatory delegations for stage "${flowState.currentStage}": ${delegation.waived.join(", ")}${delegation.autoWaived.length > 0
|
|
1012
|
+
? ` (auto-waived due to harness limitation: ${delegation.autoWaived.join(", ")})`
|
|
1013
|
+
: ""}`
|
|
912
1014
|
: "no waived mandatory delegations for current stage"
|
|
913
1015
|
});
|
|
914
1016
|
checks.push({
|
|
@@ -968,9 +1070,16 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
968
1070
|
name: "gates:evidence:current_stage",
|
|
969
1071
|
ok: gateEvidence.ok,
|
|
970
1072
|
details: gateEvidence.ok
|
|
971
|
-
? `stage "${gateEvidence.stage}" gate evidence is consistent (required=${gateEvidence.requiredCount}, passed=${gateEvidence.passedCount}, blocked=${gateEvidence.blockedCount})`
|
|
1073
|
+
? `stage "${gateEvidence.stage}" gate evidence is consistent (required=${gateEvidence.requiredCount}, recommended=${gateEvidence.recommendedCount}, conditional=${gateEvidence.conditionalCount}, triggered=${gateEvidence.triggeredConditionalCount}, passed=${gateEvidence.passedCount}, blocked=${gateEvidence.blockedCount})`
|
|
972
1074
|
: gateEvidence.issues.join(" ")
|
|
973
1075
|
});
|
|
1076
|
+
checks.push({
|
|
1077
|
+
name: "warning:gates:recommended:current_stage",
|
|
1078
|
+
ok: true,
|
|
1079
|
+
details: gateEvidence.missingRecommended.length > 0
|
|
1080
|
+
? `warning: stage "${gateEvidence.stage}" has unmet recommended gates: ${gateEvidence.missingRecommended.join(", ")}`
|
|
1081
|
+
: `no unmet recommended gates for stage "${gateEvidence.stage}"`
|
|
1082
|
+
});
|
|
974
1083
|
const completedClosure = verifyCompletedStagesGateClosure(flowState);
|
|
975
1084
|
checks.push({
|
|
976
1085
|
name: "gates:closure:completed_stages",
|
|
@@ -981,18 +1090,6 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
981
1090
|
: `all ${flowState.completedStages.length} completed stages have every required gate passed`
|
|
982
1091
|
: completedClosure.issues.join(" ")
|
|
983
1092
|
});
|
|
984
|
-
// Self-improvement block in stage skills
|
|
985
|
-
for (const stage of COMMAND_FILE_ORDER) {
|
|
986
|
-
const skillPath = path.join(projectRoot, RUNTIME_ROOT, "skills", stageSkillFolder(stage), "SKILL.md");
|
|
987
|
-
if (await exists(skillPath)) {
|
|
988
|
-
const content = await fs.readFile(skillPath, "utf8");
|
|
989
|
-
checks.push({
|
|
990
|
-
name: `skill:${stage}:self_improvement`,
|
|
991
|
-
ok: content.includes("## Operational Self-Improvement"),
|
|
992
|
-
details: `${skillPath} must contain self-improvement block`
|
|
993
|
-
});
|
|
994
|
-
}
|
|
995
|
-
}
|
|
996
1093
|
const isRepo = await isGitRepo(projectRoot);
|
|
997
1094
|
checks.push({
|
|
998
1095
|
name: "git:cclaw_ignored_runtime",
|
|
@@ -1027,8 +1124,17 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
1027
1124
|
});
|
|
1028
1125
|
const policy = await policyChecks(projectRoot, { harnesses: configuredHarnesses });
|
|
1029
1126
|
checks.push(...policy);
|
|
1030
|
-
return checks
|
|
1127
|
+
return checks.map((check) => {
|
|
1128
|
+
const metadata = doctorCheckMetadata(check.name);
|
|
1129
|
+
return {
|
|
1130
|
+
...check,
|
|
1131
|
+
severity: check.severity ?? metadata.severity,
|
|
1132
|
+
summary: check.summary ?? metadata.summary,
|
|
1133
|
+
fix: check.fix ?? metadata.fix,
|
|
1134
|
+
docRef: check.docRef ?? metadata.docRef
|
|
1135
|
+
};
|
|
1136
|
+
});
|
|
1031
1137
|
}
|
|
1032
1138
|
export function doctorSucceeded(checks) {
|
|
1033
|
-
return checks.every((check) => check.ok);
|
|
1139
|
+
return checks.every((check) => check.ok || check.severity !== "error");
|
|
1034
1140
|
}
|
package/dist/flow-state.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { FlowStage, FlowTrack, TransitionRule } from "./types.js";
|
|
|
2
2
|
export declare const TRANSITION_RULES: TransitionRule[];
|
|
3
3
|
export interface StageGateState {
|
|
4
4
|
required: string[];
|
|
5
|
+
recommended: string[];
|
|
6
|
+
conditional: string[];
|
|
7
|
+
/** Conditional gates currently considered active for blocking checks. */
|
|
8
|
+
triggered: string[];
|
|
5
9
|
passed: string[];
|
|
6
10
|
blocked: string[];
|
|
7
11
|
}
|
package/dist/flow-state.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { COMMAND_FILE_ORDER } from "./constants.js";
|
|
2
|
-
import { buildTransitionRules, orderedStageSchemas, stageGateIds } from "./content/stage-schema.js";
|
|
2
|
+
import { buildTransitionRules, orderedStageSchemas, stageConditionalGateIds, stageGateIds, stageRecommendedGateIds } from "./content/stage-schema.js";
|
|
3
3
|
import { FLOW_STAGES, FLOW_TRACKS, TRACK_STAGES } from "./types.js";
|
|
4
4
|
export const TRANSITION_RULES = buildTransitionRules();
|
|
5
5
|
export function isFlowTrack(value) {
|
|
@@ -27,6 +27,9 @@ export function createInitialFlowState(activeRunIdOrOptions = "active", maybeTra
|
|
|
27
27
|
for (const schema of orderedStageSchemas()) {
|
|
28
28
|
stageGateCatalog[schema.stage] = {
|
|
29
29
|
required: stageGateIds(schema.stage),
|
|
30
|
+
recommended: stageRecommendedGateIds(schema.stage),
|
|
31
|
+
conditional: stageConditionalGateIds(schema.stage),
|
|
32
|
+
triggered: [],
|
|
30
33
|
passed: [],
|
|
31
34
|
blocked: []
|
|
32
35
|
};
|
package/dist/gate-evidence.d.ts
CHANGED
|
@@ -5,12 +5,19 @@ export interface GateEvidenceCheckResult {
|
|
|
5
5
|
stage: FlowStage;
|
|
6
6
|
issues: string[];
|
|
7
7
|
requiredCount: number;
|
|
8
|
+
recommendedCount: number;
|
|
9
|
+
conditionalCount: number;
|
|
10
|
+
triggeredConditionalCount: number;
|
|
8
11
|
passedCount: number;
|
|
9
12
|
blockedCount: number;
|
|
10
|
-
/** True only when
|
|
13
|
+
/** True only when required + triggered conditional gates are passed and unblocked. */
|
|
11
14
|
complete: boolean;
|
|
12
15
|
/** Required gate ids that are neither passed nor blocked. */
|
|
13
16
|
missingRequired: string[];
|
|
17
|
+
/** Recommended gates not yet passed (does not block). */
|
|
18
|
+
missingRecommended: string[];
|
|
19
|
+
/** Triggered conditional gates that are not yet passed. */
|
|
20
|
+
missingTriggeredConditional: string[];
|
|
14
21
|
}
|
|
15
22
|
export interface CompletedStagesClosureResult {
|
|
16
23
|
ok: boolean;
|
|
@@ -18,6 +25,7 @@ export interface CompletedStagesClosureResult {
|
|
|
18
25
|
openStages: Array<{
|
|
19
26
|
stage: FlowStage;
|
|
20
27
|
missingRequired: string[];
|
|
28
|
+
missingTriggeredConditional: string[];
|
|
21
29
|
blocked: string[];
|
|
22
30
|
}>;
|
|
23
31
|
}
|
package/dist/gate-evidence.js
CHANGED
|
@@ -36,21 +36,56 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
36
36
|
const stage = flowState.currentStage;
|
|
37
37
|
const schema = stageSchema(stage);
|
|
38
38
|
const catalog = flowState.stageGateCatalog[stage];
|
|
39
|
-
const required = schema.requiredGates
|
|
39
|
+
const required = schema.requiredGates
|
|
40
|
+
.filter((gate) => gate.tier === "required")
|
|
41
|
+
.map((gate) => gate.id);
|
|
42
|
+
const recommended = schema.requiredGates
|
|
43
|
+
.filter((gate) => gate.tier === "recommended")
|
|
44
|
+
.map((gate) => gate.id);
|
|
45
|
+
const conditional = schema.requiredGates
|
|
46
|
+
.filter((gate) => gate.tier === "conditional")
|
|
47
|
+
.map((gate) => gate.id);
|
|
40
48
|
const requiredSet = new Set(required);
|
|
49
|
+
const recommendedSet = new Set(recommended);
|
|
50
|
+
const conditionalSet = new Set(conditional);
|
|
51
|
+
const allowedSet = new Set([...required, ...recommended, ...conditional]);
|
|
41
52
|
const issues = [];
|
|
42
53
|
const catalogRequired = unique(catalog.required);
|
|
54
|
+
const catalogRecommended = unique(catalog.recommended ?? []);
|
|
55
|
+
const catalogConditional = unique(catalog.conditional ?? []);
|
|
56
|
+
const catalogTriggered = unique(catalog.triggered ?? []);
|
|
43
57
|
const missingInCatalog = required.filter((gateId) => !catalogRequired.includes(gateId));
|
|
44
58
|
const unexpectedInCatalog = catalogRequired.filter((gateId) => !requiredSet.has(gateId));
|
|
59
|
+
const missingRecommendedInCatalog = recommended.filter((gateId) => !catalogRecommended.includes(gateId));
|
|
60
|
+
const unexpectedRecommendedInCatalog = catalogRecommended.filter((gateId) => !recommendedSet.has(gateId));
|
|
61
|
+
const missingConditionalInCatalog = conditional.filter((gateId) => !catalogConditional.includes(gateId));
|
|
62
|
+
const unexpectedConditionalInCatalog = catalogConditional.filter((gateId) => !conditionalSet.has(gateId));
|
|
45
63
|
for (const gateId of missingInCatalog) {
|
|
46
64
|
issues.push(`gate "${gateId}" missing from stageGateCatalog.required for stage "${stage}".`);
|
|
47
65
|
}
|
|
48
66
|
for (const gateId of unexpectedInCatalog) {
|
|
49
67
|
issues.push(`unexpected gate "${gateId}" found in stageGateCatalog.required for stage "${stage}".`);
|
|
50
68
|
}
|
|
69
|
+
for (const gateId of missingRecommendedInCatalog) {
|
|
70
|
+
issues.push(`gate "${gateId}" missing from stageGateCatalog.recommended for stage "${stage}".`);
|
|
71
|
+
}
|
|
72
|
+
for (const gateId of unexpectedRecommendedInCatalog) {
|
|
73
|
+
issues.push(`unexpected gate "${gateId}" found in stageGateCatalog.recommended for stage "${stage}".`);
|
|
74
|
+
}
|
|
75
|
+
for (const gateId of missingConditionalInCatalog) {
|
|
76
|
+
issues.push(`gate "${gateId}" missing from stageGateCatalog.conditional for stage "${stage}".`);
|
|
77
|
+
}
|
|
78
|
+
for (const gateId of unexpectedConditionalInCatalog) {
|
|
79
|
+
issues.push(`unexpected gate "${gateId}" found in stageGateCatalog.conditional for stage "${stage}".`);
|
|
80
|
+
}
|
|
81
|
+
for (const gateId of catalogTriggered) {
|
|
82
|
+
if (!conditionalSet.has(gateId)) {
|
|
83
|
+
issues.push(`triggered gate "${gateId}" is not defined as conditional for stage "${stage}".`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
51
86
|
const blockedSet = new Set(catalog.blocked);
|
|
52
87
|
for (const gateId of catalog.passed) {
|
|
53
|
-
if (!
|
|
88
|
+
if (!allowedSet.has(gateId)) {
|
|
54
89
|
issues.push(`passed gate "${gateId}" is not defined for stage "${stage}".`);
|
|
55
90
|
continue;
|
|
56
91
|
}
|
|
@@ -63,7 +98,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
63
98
|
}
|
|
64
99
|
}
|
|
65
100
|
for (const gateId of catalog.blocked) {
|
|
66
|
-
if (!
|
|
101
|
+
if (!allowedSet.has(gateId)) {
|
|
67
102
|
issues.push(`blocked gate "${gateId}" is not defined for stage "${stage}".`);
|
|
68
103
|
}
|
|
69
104
|
}
|
|
@@ -91,14 +126,25 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
91
126
|
}
|
|
92
127
|
}
|
|
93
128
|
const passedSet = new Set(catalog.passed);
|
|
129
|
+
const triggeredConditionalSet = new Set([
|
|
130
|
+
...catalogTriggered.filter((gateId) => conditionalSet.has(gateId)),
|
|
131
|
+
...catalog.passed.filter((gateId) => conditionalSet.has(gateId)),
|
|
132
|
+
...catalog.blocked.filter((gateId) => conditionalSet.has(gateId))
|
|
133
|
+
]);
|
|
94
134
|
const missingRequired = required.filter((gateId) => !passedSet.has(gateId));
|
|
95
|
-
const
|
|
135
|
+
const missingRecommended = recommended.filter((gateId) => !passedSet.has(gateId));
|
|
136
|
+
const missingTriggeredConditional = [...triggeredConditionalSet].filter((gateId) => !passedSet.has(gateId));
|
|
137
|
+
const blockingBlocked = catalog.blocked.filter((gateId) => requiredSet.has(gateId) || triggeredConditionalSet.has(gateId));
|
|
138
|
+
const complete = missingRequired.length === 0 && missingTriggeredConditional.length === 0 && blockingBlocked.length === 0;
|
|
96
139
|
if (flowState.completedStages.includes(stage) && !complete) {
|
|
97
140
|
if (missingRequired.length > 0) {
|
|
98
141
|
issues.push(`stage "${stage}" is marked completed but required gates are not passed: ${missingRequired.join(", ")}.`);
|
|
99
142
|
}
|
|
100
|
-
if (
|
|
101
|
-
issues.push(`stage "${stage}" is marked completed but
|
|
143
|
+
if (missingTriggeredConditional.length > 0) {
|
|
144
|
+
issues.push(`stage "${stage}" is marked completed but triggered conditional gates are not passed: ${missingTriggeredConditional.join(", ")}.`);
|
|
145
|
+
}
|
|
146
|
+
if (blockingBlocked.length > 0) {
|
|
147
|
+
issues.push(`stage "${stage}" is marked completed but has blocking blocked gates: ${blockingBlocked.join(", ")}.`);
|
|
102
148
|
}
|
|
103
149
|
}
|
|
104
150
|
return {
|
|
@@ -106,10 +152,15 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
|
|
|
106
152
|
stage,
|
|
107
153
|
issues,
|
|
108
154
|
requiredCount: required.length,
|
|
155
|
+
recommendedCount: recommended.length,
|
|
156
|
+
conditionalCount: conditional.length,
|
|
157
|
+
triggeredConditionalCount: triggeredConditionalSet.size,
|
|
109
158
|
passedCount: catalog.passed.length,
|
|
110
159
|
blockedCount: catalog.blocked.length,
|
|
111
160
|
complete,
|
|
112
|
-
missingRequired
|
|
161
|
+
missingRequired,
|
|
162
|
+
missingRecommended,
|
|
163
|
+
missingTriggeredConditional
|
|
113
164
|
};
|
|
114
165
|
}
|
|
115
166
|
export function verifyCompletedStagesGateClosure(flowState) {
|
|
@@ -118,16 +169,37 @@ export function verifyCompletedStagesGateClosure(flowState) {
|
|
|
118
169
|
for (const stage of flowState.completedStages) {
|
|
119
170
|
const schema = stageSchema(stage);
|
|
120
171
|
const catalog = flowState.stageGateCatalog[stage];
|
|
121
|
-
const required = schema.requiredGates
|
|
172
|
+
const required = schema.requiredGates
|
|
173
|
+
.filter((gate) => gate.tier === "required")
|
|
174
|
+
.map((gate) => gate.id);
|
|
175
|
+
const conditional = schema.requiredGates
|
|
176
|
+
.filter((gate) => gate.tier === "conditional")
|
|
177
|
+
.map((gate) => gate.id);
|
|
178
|
+
const conditionalSet = new Set(conditional);
|
|
122
179
|
const passedSet = new Set(catalog.passed);
|
|
180
|
+
const triggeredSet = new Set([
|
|
181
|
+
...(catalog.triggered ?? []).filter((gateId) => conditionalSet.has(gateId)),
|
|
182
|
+
...catalog.passed.filter((gateId) => conditionalSet.has(gateId)),
|
|
183
|
+
...catalog.blocked.filter((gateId) => conditionalSet.has(gateId))
|
|
184
|
+
]);
|
|
123
185
|
const missingRequired = required.filter((gateId) => !passedSet.has(gateId));
|
|
124
|
-
|
|
125
|
-
|
|
186
|
+
const missingTriggeredConditional = [...triggeredSet].filter((gateId) => !passedSet.has(gateId));
|
|
187
|
+
const blockingBlocked = catalog.blocked.filter((gateId) => required.includes(gateId) || triggeredSet.has(gateId));
|
|
188
|
+
if (missingRequired.length > 0 || missingTriggeredConditional.length > 0 || blockingBlocked.length > 0) {
|
|
189
|
+
openStages.push({
|
|
190
|
+
stage,
|
|
191
|
+
missingRequired,
|
|
192
|
+
missingTriggeredConditional,
|
|
193
|
+
blocked: [...blockingBlocked]
|
|
194
|
+
});
|
|
126
195
|
if (missingRequired.length > 0) {
|
|
127
196
|
issues.push(`completed stage "${stage}" has unpassed required gates: ${missingRequired.join(", ")}.`);
|
|
128
197
|
}
|
|
129
|
-
if (
|
|
130
|
-
issues.push(`completed stage "${stage}"
|
|
198
|
+
if (missingTriggeredConditional.length > 0) {
|
|
199
|
+
issues.push(`completed stage "${stage}" has unpassed triggered conditional gates: ${missingTriggeredConditional.join(", ")}.`);
|
|
200
|
+
}
|
|
201
|
+
if (blockingBlocked.length > 0) {
|
|
202
|
+
issues.push(`completed stage "${stage}" still has blocking blocked gates: ${blockingBlocked.join(", ")}.`);
|
|
131
203
|
}
|
|
132
204
|
}
|
|
133
205
|
}
|
|
@@ -135,29 +207,55 @@ export function verifyCompletedStagesGateClosure(flowState) {
|
|
|
135
207
|
}
|
|
136
208
|
export function reconcileCurrentStageGateCatalog(flowState) {
|
|
137
209
|
const stage = flowState.currentStage;
|
|
138
|
-
const required = stageSchema(stage).requiredGates
|
|
210
|
+
const required = stageSchema(stage).requiredGates
|
|
211
|
+
.filter((gate) => gate.tier === "required")
|
|
212
|
+
.map((gate) => gate.id);
|
|
213
|
+
const recommended = stageSchema(stage).requiredGates
|
|
214
|
+
.filter((gate) => gate.tier === "recommended")
|
|
215
|
+
.map((gate) => gate.id);
|
|
216
|
+
const conditional = stageSchema(stage).requiredGates
|
|
217
|
+
.filter((gate) => gate.tier === "conditional")
|
|
218
|
+
.map((gate) => gate.id);
|
|
139
219
|
const requiredSet = new Set(required);
|
|
220
|
+
const recommendedSet = new Set(recommended);
|
|
221
|
+
const conditionalSet = new Set(conditional);
|
|
222
|
+
const allowedSet = new Set([...required, ...recommended, ...conditional]);
|
|
140
223
|
const catalog = flowState.stageGateCatalog[stage];
|
|
141
224
|
const notes = [];
|
|
142
225
|
const before = {
|
|
143
226
|
required: [...catalog.required],
|
|
227
|
+
recommended: [...catalog.recommended],
|
|
228
|
+
conditional: [...catalog.conditional],
|
|
229
|
+
triggered: [...catalog.triggered],
|
|
144
230
|
passed: [...catalog.passed],
|
|
145
231
|
blocked: [...catalog.blocked]
|
|
146
232
|
};
|
|
147
233
|
const passedSet = new Set(unique(catalog.passed).filter((gateId) => {
|
|
148
|
-
const keep =
|
|
234
|
+
const keep = allowedSet.has(gateId);
|
|
149
235
|
if (!keep) {
|
|
150
236
|
notes.push(`removed unknown passed gate "${gateId}"`);
|
|
151
237
|
}
|
|
152
238
|
return keep;
|
|
153
239
|
}));
|
|
154
240
|
const blockedSet = new Set(unique(catalog.blocked).filter((gateId) => {
|
|
155
|
-
const keep =
|
|
241
|
+
const keep = allowedSet.has(gateId);
|
|
156
242
|
if (!keep) {
|
|
157
243
|
notes.push(`removed unknown blocked gate "${gateId}"`);
|
|
158
244
|
}
|
|
159
245
|
return keep;
|
|
160
246
|
}));
|
|
247
|
+
const triggeredSet = new Set(unique(catalog.triggered).filter((gateId) => {
|
|
248
|
+
const keep = conditionalSet.has(gateId);
|
|
249
|
+
if (!keep) {
|
|
250
|
+
notes.push(`removed unknown triggered gate "${gateId}"`);
|
|
251
|
+
}
|
|
252
|
+
return keep;
|
|
253
|
+
}));
|
|
254
|
+
for (const gateId of [...passedSet, ...blockedSet]) {
|
|
255
|
+
if (conditionalSet.has(gateId)) {
|
|
256
|
+
triggeredSet.add(gateId);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
161
259
|
for (const gateId of [...passedSet]) {
|
|
162
260
|
if (!blockedSet.has(gateId))
|
|
163
261
|
continue;
|
|
@@ -180,10 +278,16 @@ export function reconcileCurrentStageGateCatalog(flowState) {
|
|
|
180
278
|
}
|
|
181
279
|
const after = {
|
|
182
280
|
required: [...required],
|
|
183
|
-
|
|
184
|
-
|
|
281
|
+
recommended: [...recommended],
|
|
282
|
+
conditional: [...conditional],
|
|
283
|
+
triggered: conditional.filter((gateId) => triggeredSet.has(gateId)),
|
|
284
|
+
passed: [...required, ...recommended, ...conditional].filter((gateId) => passedSet.has(gateId)),
|
|
285
|
+
blocked: [...required, ...recommended, ...conditional].filter((gateId) => blockedSet.has(gateId) && !passedSet.has(gateId))
|
|
185
286
|
};
|
|
186
287
|
const changed = !sameStringArray(before.required, after.required) ||
|
|
288
|
+
!sameStringArray(before.recommended, after.recommended) ||
|
|
289
|
+
!sameStringArray(before.conditional, after.conditional) ||
|
|
290
|
+
!sameStringArray(before.triggered, after.triggered) ||
|
|
187
291
|
!sameStringArray(before.passed, after.passed) ||
|
|
188
292
|
!sameStringArray(before.blocked, after.blocked);
|
|
189
293
|
const nextState = changed
|
|
@@ -4,8 +4,15 @@ export declare const CCLAW_MARKER_END = "<!-- cclaw-end -->";
|
|
|
4
4
|
export interface HarnessAdapter {
|
|
5
5
|
id: HarnessId;
|
|
6
6
|
commandDir: string;
|
|
7
|
+
capabilities: {
|
|
8
|
+
nativeSubagentDispatch: "full" | "partial" | "none";
|
|
9
|
+
hookSurface: "full" | "plugin" | "limited" | "none";
|
|
10
|
+
structuredAsk: "AskUserQuestion" | "AskQuestion" | "plain-text";
|
|
11
|
+
};
|
|
7
12
|
}
|
|
8
13
|
export declare const HARNESS_ADAPTERS: Record<HarnessId, HarnessAdapter>;
|
|
14
|
+
export type HarnessTier = "tier1" | "tier2" | "tier3";
|
|
15
|
+
export declare function harnessTier(harnessId: HarnessId): HarnessTier;
|
|
9
16
|
/** Removes the cclaw AGENTS.md block. */
|
|
10
17
|
export declare function stripCclawBlock(content: string): string;
|
|
11
18
|
export declare function removeCclawFromAgentsMd(projectRoot: string): Promise<void>;
|
package/dist/harness-adapters.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 { CCLAW_AGENTS, agentMarkdown } from "./content/agents.js";
|
|
4
|
+
import { CCLAW_AGENTS, agentMarkdown } from "./content/core-agents.js";
|
|
5
5
|
import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
|
|
6
6
|
export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
|
|
7
7
|
export const CCLAW_MARKER_END = "<!-- cclaw-end -->";
|
|
@@ -12,11 +12,57 @@ const RUNTIME_AGENTS_BLOCK_SOURCE = `${escapeRegExp(CCLAW_MARKER_START)}[\\s\\S]
|
|
|
12
12
|
const RUNTIME_AGENTS_BLOCK_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "u");
|
|
13
13
|
const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "gu");
|
|
14
14
|
export const HARNESS_ADAPTERS = {
|
|
15
|
-
claude: {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
claude: {
|
|
16
|
+
id: "claude",
|
|
17
|
+
commandDir: ".claude/commands",
|
|
18
|
+
capabilities: {
|
|
19
|
+
nativeSubagentDispatch: "full",
|
|
20
|
+
hookSurface: "full",
|
|
21
|
+
structuredAsk: "AskUserQuestion"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
cursor: {
|
|
25
|
+
id: "cursor",
|
|
26
|
+
commandDir: ".cursor/commands",
|
|
27
|
+
capabilities: {
|
|
28
|
+
nativeSubagentDispatch: "partial",
|
|
29
|
+
hookSurface: "full",
|
|
30
|
+
structuredAsk: "AskQuestion"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
opencode: {
|
|
34
|
+
id: "opencode",
|
|
35
|
+
commandDir: ".opencode/commands",
|
|
36
|
+
capabilities: {
|
|
37
|
+
nativeSubagentDispatch: "partial",
|
|
38
|
+
hookSurface: "plugin",
|
|
39
|
+
structuredAsk: "plain-text"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
codex: {
|
|
43
|
+
id: "codex",
|
|
44
|
+
commandDir: ".codex/commands",
|
|
45
|
+
capabilities: {
|
|
46
|
+
nativeSubagentDispatch: "none",
|
|
47
|
+
hookSurface: "full",
|
|
48
|
+
structuredAsk: "plain-text"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
19
51
|
};
|
|
52
|
+
export function harnessTier(harnessId) {
|
|
53
|
+
const capabilities = HARNESS_ADAPTERS[harnessId].capabilities;
|
|
54
|
+
if (capabilities.nativeSubagentDispatch === "full" &&
|
|
55
|
+
capabilities.structuredAsk !== "plain-text" &&
|
|
56
|
+
capabilities.hookSurface === "full") {
|
|
57
|
+
return "tier1";
|
|
58
|
+
}
|
|
59
|
+
if (capabilities.hookSurface === "full" ||
|
|
60
|
+
capabilities.hookSurface === "plugin" ||
|
|
61
|
+
capabilities.nativeSubagentDispatch === "partial") {
|
|
62
|
+
return "tier2";
|
|
63
|
+
}
|
|
64
|
+
return "tier3";
|
|
65
|
+
}
|
|
20
66
|
function agentsMdBlock() {
|
|
21
67
|
return `${CCLAW_MARKER_START}
|
|
22
68
|
## Cclaw — Workflow Adapter
|
|
@@ -63,10 +109,6 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
|
|
|
63
109
|
**Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship.
|
|
64
110
|
\`/cc-next\` loads the right stage skill automatically. Gates must pass before handoff.
|
|
65
111
|
|
|
66
|
-
### Invocation Preamble (non-trivial turns)
|
|
67
|
-
|
|
68
|
-
Before starting substantive work, emit a one-paragraph preamble: **Stage**, **Goal**, **Plan** (next 1–3 actions), **Guardrails**. Skip for pure questions, trivial edits, and dispatched subagent invocations.
|
|
69
|
-
|
|
70
112
|
### Verification Discipline
|
|
71
113
|
|
|
72
114
|
No completion claims without fresh evidence. No "Done" / "All good" / "Tests pass" without running the command in this message. Failed tool calls are diagnostic data, not instructions.
|
|
@@ -78,7 +120,9 @@ If the same approach fails three times in a row (same command, same finding, sam
|
|
|
78
120
|
### Detail Level
|
|
79
121
|
|
|
80
122
|
- This managed AGENTS block is intentionally minimal for cross-project use.
|
|
123
|
+
- Harness coverage is tiered: Tier1 (claude), Tier2 (cursor/opencode/codex), Tier3 (fallback/manual-only).
|
|
81
124
|
- Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
|
|
125
|
+
- Preamble budget and cooldown rules live in \`.cclaw/references/protocols/ethos.md\`.
|
|
82
126
|
- Subagent orchestration patterns: \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`.
|
|
83
127
|
${CCLAW_MARKER_END}`;
|
|
84
128
|
}
|