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.
Files changed (55) hide show
  1. package/README.md +4 -3
  2. package/dist/cli.d.ts +6 -0
  3. package/dist/cli.js +297 -9
  4. package/dist/config.js +83 -3
  5. package/dist/content/core-agents.d.ts +44 -0
  6. package/dist/content/core-agents.js +225 -0
  7. package/dist/content/doctor-references.d.ts +2 -0
  8. package/dist/content/doctor-references.js +144 -0
  9. package/dist/content/examples.js +1 -1
  10. package/dist/content/harnesses-doc.d.ts +1 -0
  11. package/dist/content/harnesses-doc.js +95 -0
  12. package/dist/content/hook-events.d.ts +4 -0
  13. package/dist/content/hook-events.js +42 -0
  14. package/dist/content/hooks.js +81 -11
  15. package/dist/content/meta-skill.d.ts +0 -8
  16. package/dist/content/meta-skill.js +51 -341
  17. package/dist/content/next-command.js +2 -1
  18. package/dist/content/protocols.d.ts +7 -0
  19. package/dist/content/protocols.js +123 -0
  20. package/dist/content/research-playbooks.d.ts +8 -0
  21. package/dist/content/research-playbooks.js +135 -0
  22. package/dist/content/skills.js +202 -312
  23. package/dist/content/stage-common-guidance.d.ts +2 -0
  24. package/dist/content/stage-common-guidance.js +71 -0
  25. package/dist/content/stage-schema.d.ts +11 -1
  26. package/dist/content/stage-schema.js +155 -52
  27. package/dist/content/start-command.js +19 -13
  28. package/dist/content/subagents.d.ts +1 -1
  29. package/dist/content/subagents.js +23 -38
  30. package/dist/content/templates.d.ts +1 -1
  31. package/dist/content/templates.js +49 -11
  32. package/dist/delegation.d.ts +1 -0
  33. package/dist/delegation.js +27 -1
  34. package/dist/doctor-registry.d.ts +8 -0
  35. package/dist/doctor-registry.js +127 -0
  36. package/dist/doctor.d.ts +5 -0
  37. package/dist/doctor.js +133 -27
  38. package/dist/flow-state.d.ts +4 -0
  39. package/dist/flow-state.js +4 -1
  40. package/dist/gate-evidence.d.ts +9 -1
  41. package/dist/gate-evidence.js +121 -17
  42. package/dist/harness-adapters.d.ts +7 -0
  43. package/dist/harness-adapters.js +53 -9
  44. package/dist/init-detect.d.ts +2 -0
  45. package/dist/init-detect.js +45 -0
  46. package/dist/install.js +73 -1
  47. package/dist/policy.js +21 -13
  48. package/dist/runs.js +21 -4
  49. package/dist/track-heuristics.d.ts +12 -0
  50. package/dist/track-heuristics.js +144 -0
  51. package/dist/types.d.ts +26 -3
  52. package/dist/types.js +6 -3
  53. package/package.json +2 -1
  54. package/dist/content/agents.d.ts +0 -48
  55. package/dist/content/agents.js +0 -411
@@ -0,0 +1,45 @@
1
+ import path from "node:path";
2
+ import { exists } from "./fs-utils.js";
3
+ export async function detectHarnesses(projectRoot) {
4
+ const detected = [];
5
+ const claudeHints = [
6
+ path.join(projectRoot, ".claude"),
7
+ path.join(projectRoot, "CLAUDE.md")
8
+ ];
9
+ if (await anyExists(claudeHints)) {
10
+ detected.push("claude");
11
+ }
12
+ const cursorHints = [
13
+ path.join(projectRoot, ".cursor"),
14
+ path.join(projectRoot, ".cursor/rules")
15
+ ];
16
+ if (await anyExists(cursorHints)) {
17
+ detected.push("cursor");
18
+ }
19
+ const opencodeHints = [
20
+ path.join(projectRoot, ".opencode"),
21
+ path.join(projectRoot, "opencode.json"),
22
+ path.join(projectRoot, "opencode.jsonc"),
23
+ path.join(projectRoot, ".opencode/opencode.json"),
24
+ path.join(projectRoot, ".opencode/opencode.jsonc")
25
+ ];
26
+ if (await anyExists(opencodeHints)) {
27
+ detected.push("opencode");
28
+ }
29
+ const codexHints = [
30
+ path.join(projectRoot, ".codex"),
31
+ path.join(projectRoot, ".codex/hooks.json")
32
+ ];
33
+ if (await anyExists(codexHints)) {
34
+ detected.push("codex");
35
+ }
36
+ return detected;
37
+ }
38
+ async function anyExists(paths) {
39
+ for (const candidate of paths) {
40
+ if (await exists(candidate)) {
41
+ return true;
42
+ }
43
+ }
44
+ return false;
45
+ }
package/dist/install.js CHANGED
@@ -15,15 +15,21 @@ import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
15
15
  import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, cursorHooksJson, codexHooksJson } from "./content/hooks.js";
16
16
  import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
17
17
  import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
18
+ import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
18
19
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
19
20
  import { TDD_WAVE_WALKTHROUGH_MARKDOWN, stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
21
+ import { stageCommonGuidanceMarkdown } from "./content/stage-common-guidance.js";
20
22
  import { STAGE_EXAMPLES_REFERENCE_DIR, stageExamplesReferenceMarkdown } from "./content/examples.js";
21
23
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS, UTILITY_SKILL_FOLDERS, UTILITY_SKILL_MAP } from "./content/utility-skills.js";
24
+ import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
22
25
  import { HARNESS_TOOL_REFS_DIR, HARNESS_TOOL_REFS_INDEX_MD, harnessToolRefMarkdown } from "./content/harness-tool-refs.js";
26
+ import { DOCTOR_REFERENCE_MARKDOWN } from "./content/doctor-references.js";
27
+ import { harnessIntegrationDocMarkdown } from "./content/harnesses-doc.js";
28
+ import { HOOK_EVENTS_BY_HARNESS, HOOK_SEMANTIC_EVENTS } from "./content/hook-events.js";
23
29
  import { createInitialFlowState } from "./flow-state.js";
24
30
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
25
31
  import { ensureGitignore, removeGitignorePatterns } from "./gitignore.js";
26
- import { HARNESS_ADAPTERS, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
32
+ import { HARNESS_ADAPTERS, harnessTier, syncHarnessShims, removeCclawFromAgentsMd } from "./harness-adapters.js";
27
33
  import { validateHookDocument } from "./hook-schema.js";
28
34
  import { ensureRunSystem, readFlowState } from "./runs.js";
29
35
  const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
@@ -185,6 +191,7 @@ async function writeSkills(projectRoot, config) {
185
191
  // always-rendered TDD skill stays under the line-budget and the reference
186
192
  // is loaded on demand.
187
193
  await writeFileSafe(runtimePath(projectRoot, ...STAGE_EXAMPLES_REFERENCE_DIR.split("/"), "tdd-wave-walkthrough.md"), TDD_WAVE_WALKTHROUGH_MARKDOWN);
194
+ await writeFileSafe(runtimePath(projectRoot, ...STAGE_EXAMPLES_REFERENCE_DIR.split("/"), "common-guidance.md"), stageCommonGuidanceMarkdown());
188
195
  // Utility skills (not flow stages)
189
196
  await writeFileSafe(runtimePath(projectRoot, "skills", "learnings", "SKILL.md"), learnSkillMarkdown());
190
197
  await writeFileSafe(runtimePath(projectRoot, "skills", "flow-next-step", "SKILL.md"), nextCommandSkillMarkdown());
@@ -194,10 +201,17 @@ async function writeSkills(projectRoot, config) {
194
201
  await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
195
202
  await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
196
203
  await writeFileSafe(runtimePath(projectRoot, "skills", META_SKILL_NAME, "SKILL.md"), usingCclawSkillMarkdown());
204
+ await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "decision.md"), decisionProtocolMarkdown());
205
+ await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "completion.md"), completionProtocolMarkdown());
206
+ await writeFileSafe(runtimePath(projectRoot, "references", "protocols", "ethos.md"), ethosProtocolMarkdown());
197
207
  for (const folder of UTILITY_SKILL_FOLDERS) {
198
208
  const generator = UTILITY_SKILL_MAP[folder];
199
209
  await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), generator());
200
210
  }
211
+ // In-thread research procedures (no YAML frontmatter, not delegated personas).
212
+ for (const [fileName, markdown] of Object.entries(RESEARCH_PLAYBOOKS)) {
213
+ await writeFileSafe(runtimePath(projectRoot, "skills", "research", fileName), markdown);
214
+ }
201
215
  // Language rule packs live under .cclaw/rules/lang/<pack>.md. They are opt-in:
202
216
  // only the packs listed in config.languageRulePacks are materialised. Any
203
217
  // legacy per-language skill folders from v0.7.0 (.cclaw/skills/language-*)
@@ -225,6 +239,11 @@ async function writeSkills(projectRoot, config) {
225
239
  for (const harness of harnessIds) {
226
240
  await writeFileSafe(runtimePath(projectRoot, ...harnessRefsDir, `${harness}.md`), harnessToolRefMarkdown(harness));
227
241
  }
242
+ const doctorRefsDir = ["references", "doctor"];
243
+ for (const [fileName, markdown] of Object.entries(DOCTOR_REFERENCE_MARKDOWN)) {
244
+ await writeFileSafe(runtimePath(projectRoot, ...doctorRefsDir, fileName), markdown);
245
+ }
246
+ await writeFileSafe(runtimePath(projectRoot, "references", "harnesses.md"), harnessIntegrationDocMarkdown());
228
247
  }
229
248
  async function writeUtilityCommands(projectRoot) {
230
249
  await writeFileSafe(runtimePath(projectRoot, "commands", "learn.md"), learnCommandContract());
@@ -773,6 +792,14 @@ async function ensureSessionStateFiles(projectRoot) {
773
792
  if (!(await exists(contextModePath))) {
774
793
  await writeFileSafe(contextModePath, `${JSON.stringify(createInitialContextModeState(), null, 2)}\n`);
775
794
  }
795
+ const knowledgeDigestPath = path.join(stateDir, "knowledge-digest.md");
796
+ if (!(await exists(knowledgeDigestPath))) {
797
+ await writeFileSafe(knowledgeDigestPath, "# Knowledge digest (auto-generated)\n\n(no entries yet)\n");
798
+ }
799
+ const preambleLogPath = path.join(stateDir, "preamble-log.jsonl");
800
+ if (!(await exists(preambleLogPath))) {
801
+ await writeFileSafe(preambleLogPath, "");
802
+ }
776
803
  }
777
804
  async function writeRulebook(projectRoot) {
778
805
  await writeFileSafe(runtimePath(projectRoot, "rules", "RULES.md"), RULEBOOK_MARKDOWN);
@@ -836,6 +863,33 @@ async function writeAdapterManifest(projectRoot, harnesses) {
836
863
  };
837
864
  await writeFileSafe(runtimePath(projectRoot, "adapters", "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`);
838
865
  }
866
+ async function writeHarnessGapsState(projectRoot, harnesses) {
867
+ const report = harnesses.map((harness) => {
868
+ const capabilities = HARNESS_ADAPTERS[harness].capabilities;
869
+ const hookMap = HOOK_EVENTS_BY_HARNESS[harness];
870
+ const missingHookEvents = HOOK_SEMANTIC_EVENTS.filter((eventName) => !hookMap[eventName]);
871
+ const missingCapabilities = [];
872
+ if (capabilities.nativeSubagentDispatch !== "full") {
873
+ missingCapabilities.push(`nativeSubagentDispatch:${capabilities.nativeSubagentDispatch}`);
874
+ }
875
+ if (capabilities.hookSurface !== "full") {
876
+ missingCapabilities.push(`hookSurface:${capabilities.hookSurface}`);
877
+ }
878
+ if (capabilities.structuredAsk === "plain-text") {
879
+ missingCapabilities.push("structuredAsk:none");
880
+ }
881
+ return {
882
+ harness,
883
+ tier: harnessTier(harness),
884
+ missingCapabilities,
885
+ missingHookEvents
886
+ };
887
+ });
888
+ await writeFileSafe(runtimePath(projectRoot, "state", "harness-gaps.json"), `${JSON.stringify({
889
+ generatedAt: new Date().toISOString(),
890
+ harnesses: report
891
+ }, null, 2)}\n`);
892
+ }
839
893
  async function cleanLegacyArtifacts(projectRoot) {
840
894
  // Remove deprecated utility skill folders from older releases.
841
895
  for (const legacyFolder of [
@@ -868,6 +922,23 @@ async function cleanLegacyArtifacts(projectRoot) {
868
922
  catch {
869
923
  // best-effort cleanup
870
924
  }
925
+ // Core-5 migration: remove deprecated generated agent personas.
926
+ for (const legacyAgentFile of [
927
+ "spec-reviewer.md",
928
+ "code-reviewer.md",
929
+ "repo-research-analyst.md",
930
+ "learnings-researcher.md",
931
+ "framework-docs-researcher.md",
932
+ "best-practices-researcher.md",
933
+ "git-history-analyzer.md"
934
+ ]) {
935
+ try {
936
+ await fs.rm(runtimePath(projectRoot, "agents", legacyAgentFile), { force: true });
937
+ }
938
+ catch {
939
+ // best-effort cleanup
940
+ }
941
+ }
871
942
  for (const legacyPlugin of [
872
943
  path.join(projectRoot, ".opencode/plugins/viby-plugin.mjs"),
873
944
  path.join(projectRoot, ".opencode/plugins/opencode-plugin.mjs"),
@@ -940,6 +1011,7 @@ async function materializeRuntime(projectRoot, config, forceStateReset) {
940
1011
  await ensureRunSystem(projectRoot, { createIfMissing: false });
941
1012
  await ensureSessionStateFiles(projectRoot);
942
1013
  await writeAdapterManifest(projectRoot, harnesses);
1014
+ await writeHarnessGapsState(projectRoot, harnesses);
943
1015
  await ensureKnowledgeStore(projectRoot);
944
1016
  await ensureCustomSkillsScaffold(projectRoot);
945
1017
  await writeHooks(projectRoot, config);
package/dist/policy.js CHANGED
@@ -40,16 +40,15 @@ export async function policyChecks(projectRoot, options = {}) {
40
40
  "## Process",
41
41
  "## Verification",
42
42
  "## Interaction Protocol",
43
- "## Common Rationalizations",
44
43
  "## Anti-Patterns & Red Flags",
45
44
  "## HARD-GATE",
46
45
  "## Checklist",
47
46
  "## Context Loading",
48
47
  "## Automatic Subagent Dispatch",
49
- "## Cognitive Patterns",
50
48
  "## Cross-Stage Traceability",
51
- "## Completion Status",
52
- "## Artifact Validation"
49
+ "## Artifact Validation",
50
+ "## Completion Parameters",
51
+ "## Shared Stage Guidance"
53
52
  ]) {
54
53
  rules.push({
55
54
  filePath: skillFile,
@@ -99,18 +98,25 @@ export async function policyChecks(projectRoot, options = {}) {
99
98
  { file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "Review Army", name: "utility_skill:parallel:review_army" },
100
99
  { file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "Reconciliation", name: "utility_skill:parallel:reconciliation" },
101
100
  { file: runtimeFile("skills/parallel-dispatch/SKILL.md"), needle: "## Model & Harness Routing Notes", name: "utility_skill:parallel:routing_notes" },
101
+ { file: runtimeFile("skills/research/repo-scan.md"), needle: "# Repo Scan Playbook", name: "utility_skill:research:repo_scan" },
102
+ { file: runtimeFile("skills/research/learnings-lookup.md"), needle: "# Learnings Lookup Playbook", name: "utility_skill:research:learnings_lookup" },
103
+ { file: runtimeFile("skills/research/framework-docs-lookup.md"), needle: "# Framework Docs Lookup Playbook", name: "utility_skill:research:framework_docs_lookup" },
104
+ { file: runtimeFile("skills/research/best-practices-lookup.md"), needle: "# Best Practices Lookup Playbook", name: "utility_skill:research:best_practices_lookup" },
105
+ { file: runtimeFile("skills/research/git-history.md"), needle: "# Git History Playbook", name: "utility_skill:research:git_history" },
102
106
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:session:hard_gate" },
103
107
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Start Protocol", name: "utility_skill:session:start" },
104
108
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Stop Protocol", name: "utility_skill:session:stop" },
105
- { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Skill Discovery Flowchart", name: "meta_skill:discovery" },
106
- { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Activation Rules", name: "meta_skill:activation" },
107
- { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Stage Quick Reference", name: "meta_skill:reference" },
108
- { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Failure Modes", name: "meta_skill:failure_modes" },
109
- { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Contextual Skills", name: "meta_skill:contextual_skills" },
110
- { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Decision Protocol", name: "meta_skill:decision_protocol" },
111
- { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Progressive Disclosure (Depth / See Also)", name: "meta_skill:progressive_disclosure" },
109
+ { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Routing flow", name: "meta_skill:routing_flow" },
110
+ { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Task classification", name: "meta_skill:task_classification" },
111
+ { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Stage quick map", name: "meta_skill:stage_quick_map" },
112
+ { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Contextual skill activation", name: "meta_skill:contextual_skills" },
113
+ { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Protocol references", name: "meta_skill:protocol_refs" },
114
+ { file: runtimeFile("skills/using-cclaw/SKILL.md"), needle: "## Failure guardrails", name: "meta_skill:failure_guardrails" },
115
+ { file: runtimeFile("references/protocols/decision.md"), needle: "# Decision Protocol", name: "protocol:decision" },
116
+ { file: runtimeFile("references/protocols/completion.md"), needle: "# Stage Completion Protocol", name: "protocol:completion" },
117
+ { file: runtimeFile("references/protocols/ethos.md"), needle: "# Engineering Ethos", name: "protocol:ethos" },
112
118
  { file: runtimeFile("skills/session/SKILL.md"), needle: "## Session Resume Protocol", name: "utility_skill:session:resume" },
113
- { file: runtimeFile("skills/brainstorming/SKILL.md"), needle: "## Progressive Disclosure", name: "stage_skill:progressive_disclosure" },
119
+ { file: runtimeFile("skills/brainstorming/SKILL.md"), needle: "common-guidance.md", name: "stage_skill:shared_guidance_reference" },
114
120
  { file: runtimeFile("skills/security/SKILL.md"), needle: "## HARD-GATE", name: "utility_skill:security:hard_gate" },
115
121
  { file: runtimeFile("skills/security/SKILL.md"), needle: "## Checklist", name: "utility_skill:security:checklist" },
116
122
  { file: runtimeFile("skills/security/SKILL.md"), needle: "## Severity Classification", name: "utility_skill:security:severity" },
@@ -153,7 +159,9 @@ export async function policyChecks(projectRoot, options = {}) {
153
159
  { file: runtimeFile("hooks/workflow-guard.sh"), needle: "stage_invocation_without_recent_flow_read", name: "hooks:workflow_guard:flow_read_reason" },
154
160
  { file: runtimeFile("hooks/workflow-guard.sh"), needle: "stage_jump_", name: "hooks:workflow_guard:stage_jump_reason" },
155
161
  { file: runtimeFile("hooks/context-monitor.sh"), needle: "remaining is", name: "hooks:context:threshold_warning" },
156
- { file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "activeRunId", name: "hooks:opencode:active_run" }
162
+ { file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "activeRunId", name: "hooks:opencode:active_run" },
163
+ { file: runtimeFile("hooks/session-start.sh"), needle: "Knowledge digest", name: "hooks:session_start:knowledge_digest" },
164
+ { file: runtimeFile("hooks/opencode-plugin.mjs"), needle: "Knowledge digest", name: "hooks:opencode:knowledge_digest" }
157
165
  ];
158
166
  if (activeHarnesses.has("opencode")) {
159
167
  utilitySkillChecks.push({
package/dist/runs.js CHANGED
@@ -128,11 +128,15 @@ function sanitizeGuardEvidence(value) {
128
128
  return next;
129
129
  }
130
130
  function sanitizeStageGateCatalog(value, fallback) {
131
+ const uniqueStrings = (items) => [...new Set(items)];
131
132
  const next = {};
132
133
  for (const stage of COMMAND_FILE_ORDER) {
133
134
  const base = fallback[stage];
134
135
  next[stage] = {
135
136
  required: [...base.required],
137
+ recommended: [...base.recommended],
138
+ conditional: [...base.conditional],
139
+ triggered: [...base.triggered],
136
140
  passed: [...base.passed],
137
141
  blocked: [...base.blocked]
138
142
  };
@@ -147,11 +151,24 @@ function sanitizeStageGateCatalog(value, fallback) {
147
151
  continue;
148
152
  }
149
153
  const typed = rawStage;
150
- const allowedGateIds = new Set(next[stage].required);
154
+ const stageState = next[stage];
155
+ const allowedGateIds = new Set([
156
+ ...stageState.required,
157
+ ...stageState.recommended,
158
+ ...stageState.conditional
159
+ ]);
160
+ const conditionalGateIds = new Set(stageState.conditional);
161
+ const passed = sanitizeStringArray(typed.passed).filter((gate) => allowedGateIds.has(gate));
162
+ const blocked = sanitizeStringArray(typed.blocked).filter((gate) => allowedGateIds.has(gate));
163
+ const triggeredFromState = sanitizeStringArray(typed.triggered).filter((gate) => conditionalGateIds.has(gate));
164
+ const touchedConditionals = [...passed, ...blocked].filter((gate) => conditionalGateIds.has(gate));
151
165
  next[stage] = {
152
- required: [...next[stage].required],
153
- passed: sanitizeStringArray(typed.passed).filter((gate) => allowedGateIds.has(gate)),
154
- blocked: sanitizeStringArray(typed.blocked).filter((gate) => allowedGateIds.has(gate))
166
+ required: [...stageState.required],
167
+ recommended: [...stageState.recommended],
168
+ conditional: [...stageState.conditional],
169
+ triggered: uniqueStrings([...triggeredFromState, ...touchedConditionals]),
170
+ passed,
171
+ blocked
155
172
  };
156
173
  }
157
174
  return next;
@@ -0,0 +1,12 @@
1
+ import type { FlowTrack, TrackHeuristicRule, TrackHeuristicsConfig } from "./types.js";
2
+ export interface TrackResolution {
3
+ track: FlowTrack;
4
+ reason: string;
5
+ matchedTokens: string[];
6
+ }
7
+ export declare function resolveTrackFromPrompt(prompt: string, config: TrackHeuristicsConfig | undefined): TrackResolution;
8
+ export declare const TRACK_HEURISTICS_DEFAULTS: {
9
+ readonly fallback: "standard";
10
+ readonly priority: ("quick" | "medium" | "standard")[];
11
+ readonly tracks: Record<"quick" | "medium" | "standard", TrackHeuristicRule>;
12
+ };
@@ -0,0 +1,144 @@
1
+ import { FLOW_TRACKS } from "./types.js";
2
+ const DEFAULT_RULES = {
3
+ quick: {
4
+ triggers: [
5
+ "bug",
6
+ "bugfix",
7
+ "fix",
8
+ "hotfix",
9
+ "patch",
10
+ "typo",
11
+ "regression",
12
+ "copy change",
13
+ "rename",
14
+ "bump",
15
+ "upgrade dep",
16
+ "config tweak",
17
+ "docs only",
18
+ "comment",
19
+ "lint",
20
+ "format",
21
+ "small",
22
+ "tiny",
23
+ "one-liner",
24
+ "revert"
25
+ ]
26
+ },
27
+ medium: {
28
+ triggers: [
29
+ "add endpoint",
30
+ "add field",
31
+ "extend existing",
32
+ "wire integration",
33
+ "small migration",
34
+ "new screen following existing pattern"
35
+ ]
36
+ },
37
+ standard: {
38
+ triggers: [
39
+ "new feature",
40
+ "refactor",
41
+ "migration",
42
+ "platform",
43
+ "architecture",
44
+ "schema",
45
+ "integrate",
46
+ "workflow",
47
+ "onboarding"
48
+ ]
49
+ }
50
+ };
51
+ const DEFAULT_PRIORITY = ["standard", "medium", "quick"];
52
+ const DEFAULT_FALLBACK = "standard";
53
+ function hasToken(promptLower, token) {
54
+ return promptLower.includes(token.toLowerCase());
55
+ }
56
+ function matchRule(promptLower, rule) {
57
+ if (!rule)
58
+ return [];
59
+ const matches = [];
60
+ for (const trigger of rule.triggers ?? []) {
61
+ if (hasToken(promptLower, trigger)) {
62
+ matches.push(trigger);
63
+ }
64
+ }
65
+ for (const pattern of rule.patterns ?? []) {
66
+ try {
67
+ const regex = new RegExp(pattern, "iu");
68
+ if (regex.test(promptLower)) {
69
+ matches.push(`/${pattern}/`);
70
+ }
71
+ }
72
+ catch {
73
+ // Ignore invalid custom regex entries; config validation should catch these.
74
+ }
75
+ }
76
+ return [...new Set(matches)];
77
+ }
78
+ function isValidTrack(value) {
79
+ return FLOW_TRACKS.includes(value);
80
+ }
81
+ function mergeRules(base, overrides) {
82
+ const merged = { ...base };
83
+ const over = overrides?.tracks;
84
+ if (!over)
85
+ return merged;
86
+ for (const track of FLOW_TRACKS) {
87
+ const rule = over[track];
88
+ if (!rule)
89
+ continue;
90
+ merged[track] = {
91
+ triggers: rule.triggers ?? merged[track].triggers,
92
+ patterns: rule.patterns ?? merged[track].patterns,
93
+ veto: rule.veto ?? merged[track].veto
94
+ };
95
+ }
96
+ return merged;
97
+ }
98
+ function resolvePriority(config) {
99
+ const configured = config?.priority ?? [];
100
+ const filtered = configured.filter((track) => isValidTrack(track));
101
+ const unique = [...new Set(filtered)];
102
+ if (unique.length === 0)
103
+ return [...DEFAULT_PRIORITY];
104
+ // Ensure all tracks are still represented in deterministic order.
105
+ for (const track of FLOW_TRACKS) {
106
+ if (!unique.includes(track))
107
+ unique.push(track);
108
+ }
109
+ return unique;
110
+ }
111
+ function resolveFallback(config) {
112
+ return config?.fallback && isValidTrack(config.fallback) ? config.fallback : DEFAULT_FALLBACK;
113
+ }
114
+ export function resolveTrackFromPrompt(prompt, config) {
115
+ const promptLower = prompt.toLowerCase();
116
+ const rules = mergeRules(DEFAULT_RULES, config);
117
+ const priority = resolvePriority(config);
118
+ const fallback = resolveFallback(config);
119
+ for (const track of priority) {
120
+ const rule = rules[track];
121
+ const vetoes = rule.veto ?? [];
122
+ if (vetoes.some((token) => hasToken(promptLower, token))) {
123
+ continue;
124
+ }
125
+ const matched = matchRule(promptLower, rule);
126
+ if (matched.length > 0) {
127
+ return {
128
+ track,
129
+ reason: `matched ${track} heuristic`,
130
+ matchedTokens: matched
131
+ };
132
+ }
133
+ }
134
+ return {
135
+ track: fallback,
136
+ reason: `no explicit match, fallback=${fallback}`,
137
+ matchedTokens: []
138
+ };
139
+ }
140
+ export const TRACK_HEURISTICS_DEFAULTS = {
141
+ fallback: DEFAULT_FALLBACK,
142
+ priority: DEFAULT_PRIORITY,
143
+ tracks: DEFAULT_RULES
144
+ };
package/dist/types.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  export declare const FLOW_STAGES: readonly ["brainstorm", "scope", "design", "spec", "plan", "tdd", "review", "ship"];
2
2
  export type FlowStage = (typeof FLOW_STAGES)[number];
3
- export declare const FLOW_TRACKS: readonly ["quick", "standard"];
3
+ export declare const FLOW_TRACKS: readonly ["quick", "medium", "standard"];
4
4
  export type FlowTrack = (typeof FLOW_TRACKS)[number];
5
5
  /**
6
6
  * Ordered stages that make up each flow track.
7
7
  *
8
8
  * - `standard` runs the full 8-stage pipeline (default — same as before tracks existed).
9
+ * - `medium` keeps product framing but skips heavy scope/design lock-in:
10
+ * brainstorm -> spec -> plan -> tdd -> review -> ship.
9
11
  * - `quick` skips the upstream product stages (brainstorm/scope/design/plan) for
10
12
  * small bug fixes or single-purpose changes where the spec is already known.
11
13
  * It still keeps the non-negotiable safety gates: spec → tdd → review → ship.
@@ -16,8 +18,8 @@ export type HarnessId = (typeof HARNESS_IDS)[number];
16
18
  /**
17
19
  * Init profiles pre-fill `cclaw init` flags for common install shapes.
18
20
  *
19
- * - `minimal` — single-harness (claude), quick track default, no git hook guards. For solo
20
- * contributors or bugfix-heavy repos where most work is \`quick\` scope.
21
+ * - `minimal` — single-harness (claude), medium track default, no git hook guards. For solo
22
+ * contributors who still want brainstorm/spec/plan rigor without full scope+design overhead.
21
23
  * - `standard` — default harness set, standard track, no git hook guards, advisory guards.
22
24
  * Matches the pre-profile default behavior.
23
25
  * - `full` — default harness set, standard track, git hook guards on, strict prompt guards.
@@ -35,6 +37,22 @@ export type InitProfile = (typeof INIT_PROFILES)[number];
35
37
  */
36
38
  export declare const LANGUAGE_RULE_PACKS: readonly ["typescript", "python", "go"];
37
39
  export type LanguageRulePack = (typeof LANGUAGE_RULE_PACKS)[number];
40
+ export interface TrackHeuristicRule {
41
+ triggers?: string[];
42
+ patterns?: string[];
43
+ veto?: string[];
44
+ }
45
+ export interface TrackHeuristicsConfig {
46
+ /** Track used when no trigger/pattern matches. */
47
+ fallback?: FlowTrack;
48
+ /**
49
+ * Track evaluation order. First matching track wins.
50
+ * Example: ["standard", "medium", "quick"].
51
+ */
52
+ priority?: FlowTrack[];
53
+ /** Per-track matching rules. */
54
+ tracks?: Partial<Record<FlowTrack, TrackHeuristicRule>>;
55
+ }
38
56
  export interface VibyConfig {
39
57
  version: string;
40
58
  flowVersion: string;
@@ -54,6 +72,11 @@ export interface VibyConfig {
54
72
  * the language in question. Disabled packs have no on-disk footprint.
55
73
  */
56
74
  languageRulePacks?: LanguageRulePack[];
75
+ /**
76
+ * Optional prompt-to-track mapping overrides for /cc classification.
77
+ * If omitted, cclaw uses built-in defaults.
78
+ */
79
+ trackHeuristics?: TrackHeuristicsConfig;
57
80
  }
58
81
  export interface TransitionRule {
59
82
  from: FlowStage;
package/dist/types.js CHANGED
@@ -8,25 +8,28 @@ export const FLOW_STAGES = [
8
8
  "review",
9
9
  "ship"
10
10
  ];
11
- export const FLOW_TRACKS = ["quick", "standard"];
11
+ export const FLOW_TRACKS = ["quick", "medium", "standard"];
12
12
  /**
13
13
  * Ordered stages that make up each flow track.
14
14
  *
15
15
  * - `standard` runs the full 8-stage pipeline (default — same as before tracks existed).
16
+ * - `medium` keeps product framing but skips heavy scope/design lock-in:
17
+ * brainstorm -> spec -> plan -> tdd -> review -> ship.
16
18
  * - `quick` skips the upstream product stages (brainstorm/scope/design/plan) for
17
19
  * small bug fixes or single-purpose changes where the spec is already known.
18
20
  * It still keeps the non-negotiable safety gates: spec → tdd → review → ship.
19
21
  */
20
22
  export const TRACK_STAGES = {
21
23
  standard: FLOW_STAGES,
24
+ medium: ["brainstorm", "spec", "plan", "tdd", "review", "ship"],
22
25
  quick: ["spec", "tdd", "review", "ship"]
23
26
  };
24
27
  export const HARNESS_IDS = ["claude", "cursor", "opencode", "codex"];
25
28
  /**
26
29
  * Init profiles pre-fill `cclaw init` flags for common install shapes.
27
30
  *
28
- * - `minimal` — single-harness (claude), quick track default, no git hook guards. For solo
29
- * contributors or bugfix-heavy repos where most work is \`quick\` scope.
31
+ * - `minimal` — single-harness (claude), medium track default, no git hook guards. For solo
32
+ * contributors who still want brainstorm/spec/plan rigor without full scope+design overhead.
30
33
  * - `standard` — default harness set, standard track, no git hook guards, advisory guards.
31
34
  * Matches the pre-profile default behavior.
32
35
  * - `full` — default harness set, standard track, git hook guards on, strict prompt guards.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.10.1",
3
+ "version": "0.12.0",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,6 +23,7 @@
23
23
  "test:coverage": "vitest run --coverage",
24
24
  "smoke:runtime": "npm run build && node scripts/smoke-init.mjs",
25
25
  "lint:hooks": "npm run build && node scripts/lint-generated-hooks.mjs",
26
+ "build:harness-docs": "npm run build && node scripts/build-harness-docs.mjs",
26
27
  "build:plugin-manifests": "npm run build && node scripts/build-plugin-manifests.mjs",
27
28
  "release:check": "npm run build && npm run test && node scripts/lint-generated-hooks.mjs && node scripts/build-plugin-manifests.mjs && npm pack --dry-run && node scripts/smoke-init.mjs",
28
29
  "release:bundle": "npm run release:check && npm pack"
@@ -1,48 +0,0 @@
1
- /**
2
- * Agent persona content for Cclaw.
3
- *
4
- * Cclaw emits markdown agent definitions (`.md` with YAML frontmatter) that harnesses
5
- * use for specialist delegation. Agents are isolated context windows with constrained
6
- * tools; skills remain procedural recipes.
7
- */
8
- export interface AgentDefinition {
9
- /** Kebab-case identifier, e.g. `"spec-reviewer"`. */
10
- name: string;
11
- /** When to invoke — include PROACTIVE / MUST BE USED style guidance for harnesses. */
12
- description: string;
13
- /** Allowed tools for this agent (harness-specific names). */
14
- tools: string[];
15
- /** Model tier for routing cost/latency vs depth. */
16
- model: "fast" | "balanced" | "deep";
17
- /** How the harness should treat activation relative to flow context. */
18
- activation: "proactive" | "on-demand" | "mandatory";
19
- /** Cclaw flow stages this agent is designed to support. */
20
- relatedStages: string[];
21
- /** Markdown body rendered below the YAML frontmatter. */
22
- body: string;
23
- }
24
- /**
25
- * Canonical specialist agents Cclaw can materialize under `.cclaw/agents/`.
26
- */
27
- export declare const CCLAW_AGENTS: AgentDefinition[];
28
- /**
29
- * Render a complete Cclaw agent markdown file (YAML frontmatter + body).
30
- */
31
- export declare function agentMarkdown(agent: AgentDefinition): string;
32
- /**
33
- * Markdown table mapping Cclaw stage entry points to specialist agents.
34
- */
35
- export declare function agentRoutingTable(): string;
36
- /**
37
- * Cost tier routing: keep heavy reasoning on the \`deep\` tier (planner, a
38
- * single post-review reconciliation), push read-only research and narrow
39
- * machine-only checks to the \`fast\` tier, and default review to \`balanced\`.
40
- * This table is emitted into AGENTS.md so harness users understand why
41
- * certain specialists are automatically fan-out-able without blowing the
42
- * context budget.
43
- */
44
- export declare function agentCostTierTable(): string;
45
- /**
46
- * AGENTS.md-ready section describing Cclaw’s specialist delegation model.
47
- */
48
- export declare function agentsAgentsMdBlock(): string;