cclaw-cli 0.37.0 → 0.39.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 +27 -11
- package/dist/cli.js +9 -4
- package/dist/config.js +5 -25
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +4 -3
- package/dist/content/harness-playbooks.js +56 -27
- package/dist/content/harnesses-doc.js +1 -1
- package/dist/content/hook-events.js +5 -6
- package/dist/content/hooks.js +5 -3
- package/dist/content/start-command.js +2 -2
- package/dist/doctor.js +64 -33
- package/dist/harness-adapters.d.ts +25 -0
- package/dist/harness-adapters.js +184 -16
- package/dist/init-detect.js +7 -2
- package/dist/install.js +34 -8
- package/dist/track-heuristics.d.ts +8 -1
- package/dist/track-heuristics.js +14 -29
- package/dist/types.d.ts +28 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,8 +127,10 @@ Plus harness-specific shims:
|
|
|
127
127
|
- `.claude/commands/cc*.md` + `.claude/hooks/hooks.json`
|
|
128
128
|
- `.cursor/commands/cc*.md` + `.cursor/hooks.json` + `.cursor/rules/cclaw-workflow.mdc`
|
|
129
129
|
- `.opencode/commands/cc*.md` + `.opencode/plugins/cclaw-plugin.mjs`
|
|
130
|
-
- `.
|
|
131
|
-
-
|
|
130
|
+
- `.agents/skills/cclaw-cc*/SKILL.md` (Codex; activated via `/use cclaw-cc`
|
|
131
|
+
or description-based auto-matching — Codex no longer reads `.codex/commands/`
|
|
132
|
+
or `.codex/hooks.json`, and `cclaw sync` cleans those up if present)
|
|
133
|
+
- `AGENTS.md` with a managed routing block (includes a Codex-specific note)
|
|
132
134
|
|
|
133
135
|
`.cclaw/config.yaml` holds every tunable key (prompt guard strictness,
|
|
134
136
|
TDD enforcement, git-hook guards, language rule packs, track heuristics).
|
|
@@ -229,27 +231,30 @@ Each critical-path stage produces a dated artifact under
|
|
|
229
231
|
bundle into `.cclaw/runs/<YYYY-MM-DD-slug>/` and resets the active flow
|
|
230
232
|
for the next feature.
|
|
231
233
|
|
|
232
|
-
### Track heuristics are configurable
|
|
234
|
+
### Track heuristics are configurable (advisory)
|
|
233
235
|
|
|
234
236
|
Every team has its own vocabulary. Override the built-in trigger lists in
|
|
235
237
|
`.cclaw/config.yaml`:
|
|
236
238
|
|
|
237
239
|
```yaml
|
|
238
240
|
trackHeuristics:
|
|
239
|
-
priority: [standard, medium, quick]
|
|
240
241
|
fallback: standard
|
|
241
242
|
tracks:
|
|
242
243
|
quick:
|
|
243
244
|
triggers: [hotfix, rollback, prod-incident]
|
|
244
|
-
veto: [schema, migration] # never route quick even if
|
|
245
|
+
veto: [schema, migration] # never route quick even if a trigger hits
|
|
245
246
|
standard:
|
|
246
|
-
|
|
247
|
-
- "^epic:"
|
|
248
|
-
- "platform-team|core-infra"
|
|
247
|
+
triggers: [epic, platform-team, core-infra]
|
|
249
248
|
```
|
|
250
249
|
|
|
251
|
-
|
|
252
|
-
|
|
250
|
+
Honest caveat: this config is **advisory**. cclaw surfaces these lists in
|
|
251
|
+
the `/cc` skill and contract prose so the LLM applies them during
|
|
252
|
+
classification — there is no Node-level router that mechanically enforces
|
|
253
|
+
the outcome. That is why the knobs are deliberately minimal: per-track
|
|
254
|
+
`triggers` + `veto` on top of defaults, plus `fallback`. Evaluation order is
|
|
255
|
+
fixed (`standard -> medium -> quick`, narrow-to-broad); regex `patterns`
|
|
256
|
+
and a `priority` override were removed in v0.38.0 because nothing in
|
|
257
|
+
runtime consumed them.
|
|
253
258
|
|
|
254
259
|
### Mid-flow reclassification
|
|
255
260
|
|
|
@@ -352,7 +357,7 @@ closes every real gap with a documented fallback — not a silent waiver.
|
|
|
352
357
|
| Claude Code | full (named subagents) | `native` | full | `AskUserQuestion` | [`claude-playbook.md`](./src/content/harness-playbooks.ts) |
|
|
353
358
|
| Cursor | generic Task dispatcher | `generic-dispatch` | full | `AskQuestion` | `cursor-playbook.md` |
|
|
354
359
|
| OpenCode | plugin / in-session | `role-switch` | plugin | plain-text | `opencode-playbook.md` |
|
|
355
|
-
| OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) |
|
|
360
|
+
| OpenAI Codex | in-session only | `role-switch` (evidenceRefs required) | none (no hooks API) | plain-text | `codex-playbook.md` |
|
|
356
361
|
|
|
357
362
|
What the fallbacks mean:
|
|
358
363
|
|
|
@@ -375,6 +380,17 @@ What the fallbacks mean:
|
|
|
375
380
|
harness declares it. Currently unused — v0.33 removed the old
|
|
376
381
|
Codex-only auto-waiver path.
|
|
377
382
|
|
|
383
|
+
> **Codex note (v0.39+).** Codex CLI deprecated custom prompts and the
|
|
384
|
+
> `.codex/hooks.json` API, so cclaw installs Codex entry points as
|
|
385
|
+
> native **skills** under `.agents/skills/cclaw-cc*/SKILL.md`. Invoke
|
|
386
|
+
> them with `/use cclaw-cc`, `/use cclaw-cc-next`, `/use cclaw-cc-view`,
|
|
387
|
+
> `/use cclaw-cc-ops`, `/use cclaw-cc-ideate`, or just say something
|
|
388
|
+
> like *"run cc for payments refund fix"* — Codex auto-matches skills
|
|
389
|
+
> from their description. Hook-driven checks (prompt-guard, stop-save,
|
|
390
|
+
> post-tool context monitor) are substituted in the `cclaw-cc*` skill
|
|
391
|
+
> bodies as explicit agent steps; run `cclaw doctor` to see what's
|
|
392
|
+
> missing and how the playbook compensates.
|
|
393
|
+
|
|
378
394
|
The full capability matrix lives in
|
|
379
395
|
[`docs/harnesses.md`](./docs/harnesses.md). Per-harness playbooks are
|
|
380
396
|
generated into `.cclaw/references/harnesses/` on every install and
|
package/dist/cli.js
CHANGED
|
@@ -151,7 +151,12 @@ function buildInitSurfacePreview(harnesses) {
|
|
|
151
151
|
];
|
|
152
152
|
for (const harness of harnesses) {
|
|
153
153
|
const adapter = HARNESS_ADAPTERS[harness];
|
|
154
|
-
|
|
154
|
+
if (adapter.shimKind === "skill") {
|
|
155
|
+
lines.push(`${adapter.commandDir}/cclaw-cc*/SKILL.md`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
lines.push(`${adapter.commandDir}/cc*.md`);
|
|
159
|
+
}
|
|
155
160
|
if (harness === "claude") {
|
|
156
161
|
lines.push(".claude/hooks/hooks.json");
|
|
157
162
|
}
|
|
@@ -159,9 +164,9 @@ function buildInitSurfacePreview(harnesses) {
|
|
|
159
164
|
lines.push(".cursor/hooks.json");
|
|
160
165
|
lines.push(".cursor/rules/cclaw-workflow.mdc");
|
|
161
166
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
// Codex has no hooks file — it reads skills from `.agents/skills/` only
|
|
168
|
+
// (v0.39.0+). Legacy `.codex/commands/*` and `.codex/hooks.json` are
|
|
169
|
+
// auto-cleaned on sync.
|
|
165
170
|
if (harness === "opencode") {
|
|
166
171
|
lines.push(".opencode/plugins/cclaw-plugin.mjs");
|
|
167
172
|
lines.push("opencode.json(.c) plugin registration");
|
package/dist/config.js
CHANGED
|
@@ -160,17 +160,8 @@ export async function readConfig(projectRoot) {
|
|
|
160
160
|
if (fallbackRaw !== undefined && (typeof fallbackRaw !== "string" || !FLOW_TRACK_SET.has(fallbackRaw))) {
|
|
161
161
|
throw configValidationError(fullPath, `"trackHeuristics.fallback" must be one of: ${SUPPORTED_TRACKS_TEXT}`);
|
|
162
162
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (priorityRaw !== undefined) {
|
|
166
|
-
if (!Array.isArray(priorityRaw)) {
|
|
167
|
-
throw configValidationError(fullPath, `"trackHeuristics.priority" must be an array`);
|
|
168
|
-
}
|
|
169
|
-
const invalidPriority = priorityRaw.filter((value) => typeof value !== "string" || !FLOW_TRACK_SET.has(value));
|
|
170
|
-
if (invalidPriority.length > 0) {
|
|
171
|
-
throw configValidationError(fullPath, `"trackHeuristics.priority" must contain only: ${SUPPORTED_TRACKS_TEXT}`);
|
|
172
|
-
}
|
|
173
|
-
priority = [...new Set(priorityRaw)];
|
|
163
|
+
if (Object.prototype.hasOwnProperty.call(trackHeuristicsRaw, "priority")) {
|
|
164
|
+
throw configValidationError(fullPath, `"trackHeuristics.priority" is no longer supported (removed in v0.38.0). Track evaluation order is always standard -> medium -> quick. Remove the field to upgrade.`);
|
|
174
165
|
}
|
|
175
166
|
const tracksRaw = trackHeuristicsRaw.tracks;
|
|
176
167
|
let tracks = undefined;
|
|
@@ -186,30 +177,19 @@ export async function readConfig(projectRoot) {
|
|
|
186
177
|
if (!isRecord(ruleRaw)) {
|
|
187
178
|
throw configValidationError(fullPath, `"trackHeuristics.tracks.${trackName}" must be an object`);
|
|
188
179
|
}
|
|
180
|
+
if (Object.prototype.hasOwnProperty.call(ruleRaw, "patterns")) {
|
|
181
|
+
throw configValidationError(fullPath, `"trackHeuristics.tracks.${trackName}.patterns" is no longer supported (removed in v0.38.0). Regex patterns were never wired into runtime routing. Move the intent into "triggers" (substrings) or "veto".`);
|
|
182
|
+
}
|
|
189
183
|
const triggers = validateStringArray(ruleRaw.triggers, `trackHeuristics.tracks.${trackName}.triggers`, fullPath);
|
|
190
|
-
const patterns = validateStringArray(ruleRaw.patterns, `trackHeuristics.tracks.${trackName}.patterns`, fullPath);
|
|
191
184
|
const veto = validateStringArray(ruleRaw.veto, `trackHeuristics.tracks.${trackName}.veto`, fullPath);
|
|
192
|
-
if (patterns) {
|
|
193
|
-
for (const pattern of patterns) {
|
|
194
|
-
try {
|
|
195
|
-
// eslint-disable-next-line no-new
|
|
196
|
-
new RegExp(pattern, "iu");
|
|
197
|
-
}
|
|
198
|
-
catch {
|
|
199
|
-
throw configValidationError(fullPath, `"trackHeuristics.tracks.${trackName}.patterns" contains invalid regex "${pattern}"`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
185
|
tracks[trackName] = {
|
|
204
186
|
triggers,
|
|
205
|
-
patterns,
|
|
206
187
|
veto
|
|
207
188
|
};
|
|
208
189
|
}
|
|
209
190
|
}
|
|
210
191
|
trackHeuristics = {
|
|
211
192
|
fallback: fallbackRaw,
|
|
212
|
-
priority,
|
|
213
193
|
tracks
|
|
214
194
|
};
|
|
215
195
|
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare const EVALS_ROOT = ".cclaw/evals";
|
|
|
14
14
|
export declare const EVALS_CONFIG_PATH = ".cclaw/evals/config.yaml";
|
|
15
15
|
export declare const EVALS_DIRS: readonly [".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
|
|
16
16
|
export declare const REQUIRED_DIRS: readonly [".cclaw", ".cclaw/commands", ".cclaw/skills", ".cclaw/contexts", ".cclaw/templates", ".cclaw/artifacts", ".cclaw/worktrees", ".cclaw/state", ".cclaw/runs", ".cclaw/rules", ".cclaw/adapters", ".cclaw/agents", ".cclaw/hooks", ".cclaw/custom-skills", ".cclaw/evals", ".cclaw/evals/corpus", ".cclaw/evals/rubrics", ".cclaw/evals/baselines", ".cclaw/evals/reports"];
|
|
17
|
-
export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", "# cclaw evals: user-owned, track in git", "!.cclaw/evals/", "!.cclaw/evals/config.yaml", "!.cclaw/evals/corpus/", "!.cclaw/evals/corpus/**", "!.cclaw/evals/rubrics/", "!.cclaw/evals/rubrics/**", "!.cclaw/evals/baselines/", "!.cclaw/evals/baselines/**", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".
|
|
17
|
+
export declare const REQUIRED_GITIGNORE_PATTERNS: readonly ["# cclaw generated artifacts", ".cclaw/", "# cclaw evals: user-owned, track in git", "!.cclaw/evals/", "!.cclaw/evals/config.yaml", "!.cclaw/evals/corpus/", "!.cclaw/evals/corpus/**", "!.cclaw/evals/rubrics/", "!.cclaw/evals/rubrics/**", "!.cclaw/evals/baselines/", "!.cclaw/evals/baselines/**", ".claude/commands/cc-*.md", ".claude/commands/cc.md", ".cursor/commands/cc-*.md", ".cursor/commands/cc.md", ".opencode/commands/cc-*.md", ".opencode/commands/cc.md", ".agents/skills/cclaw-cc/SKILL.md", ".agents/skills/cclaw-cc-*/SKILL.md", ".claude/hooks/hooks.json", ".cursor/hooks.json", ".opencode/plugins/cclaw-plugin.mjs", ".cursor/rules/cclaw-workflow.mdc"];
|
|
18
18
|
export declare const COMMAND_FILE_ORDER: FlowStage[];
|
|
19
19
|
export declare const UTILITY_COMMANDS: readonly ["learn", "next", "ideate", "view", "status", "tree", "diff", "ops", "feature", "tdd-log", "retro", "compound", "archive", "rewind"];
|
|
20
20
|
export declare const SUBAGENT_SKILL_FOLDERS: readonly ["subagent-dev", "parallel-dispatch"];
|
package/dist/constants.js
CHANGED
|
@@ -91,11 +91,12 @@ export const REQUIRED_GITIGNORE_PATTERNS = [
|
|
|
91
91
|
".cursor/commands/cc.md",
|
|
92
92
|
".opencode/commands/cc-*.md",
|
|
93
93
|
".opencode/commands/cc.md",
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// Codex uses skill-kind shims under `.agents/skills/cclaw-cc*/` since
|
|
95
|
+
// v0.39.0; legacy `.codex/commands/*` is auto-cleaned on sync.
|
|
96
|
+
".agents/skills/cclaw-cc/SKILL.md",
|
|
97
|
+
".agents/skills/cclaw-cc-*/SKILL.md",
|
|
96
98
|
".claude/hooks/hooks.json",
|
|
97
99
|
".cursor/hooks.json",
|
|
98
|
-
".codex/hooks.json",
|
|
99
100
|
".opencode/plugins/cclaw-plugin.mjs",
|
|
100
101
|
".cursor/rules/cclaw-workflow.mdc"
|
|
101
102
|
];
|
|
@@ -192,28 +192,43 @@ has either a \`completed\` row with evidenceRefs (role-switch) or a
|
|
|
192
192
|
const CODEX_PLAYBOOK = `---
|
|
193
193
|
harness: codex
|
|
194
194
|
fallback: role-switch
|
|
195
|
-
description: "OpenAI Codex has no subagent dispatch
|
|
195
|
+
description: "OpenAI Codex has no subagent dispatch and no hooks. cclaw ships entry points as skills under .agents/skills/; mandatory delegations fall back to role-switch with evidenceRefs."
|
|
196
196
|
---
|
|
197
197
|
|
|
198
198
|
# OpenAI Codex — Parity Playbook
|
|
199
199
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
200
|
+
Codex CLI exposes **neither a custom slash-command system nor a hooks
|
|
201
|
+
API**. cclaw v0.39.0 acknowledged this and rewired the codex harness:
|
|
202
|
+
|
|
203
|
+
- **Entry points are skills.** \`/cc\`, \`/cc-next\`, \`/cc-ideate\`,
|
|
204
|
+
\`/cc-view\`, \`/cc-ops\` are generated as skills at
|
|
205
|
+
\`.agents/skills/cclaw-cc/SKILL.md\` (and \`cclaw-cc-next/\`, etc.). They
|
|
206
|
+
activate via Codex's native \`/use <skillName>\` command or
|
|
207
|
+
automatically when the user's prompt mentions any of the
|
|
208
|
+
\`/cc\`-style tokens (skill descriptions include them verbatim).
|
|
209
|
+
- **No hooks.** Everything that Claude/Cursor get from
|
|
210
|
+
\`SessionStart\` / \`PreToolUse\` / \`PostToolUse\` / \`Stop\` /
|
|
211
|
+
\`PreCompact\` must run as explicit agent steps. The session rehydration,
|
|
212
|
+
prompt-guard, workflow-guard, context-monitor, and stop-checkpoint
|
|
213
|
+
behaviors are documented in \`.cclaw/skills/using-cclaw/SKILL.md\`.
|
|
214
|
+
- **Legacy paths are dead.** \`.codex/commands/*\` and \`.codex/hooks.json\`
|
|
215
|
+
are removed on every \`cclaw sync\`. Do not restore them by hand —
|
|
216
|
+
Codex CLI never read either path.
|
|
217
|
+
|
|
218
|
+
## Fallback: role-switch
|
|
219
|
+
|
|
220
|
+
Codex has no subagent dispatch — neither named nor generic. Mandatory
|
|
221
|
+
delegations must be role-switched in-session. Silent auto-waiver was
|
|
222
|
+
disabled in v0.33 and remains off.
|
|
208
223
|
|
|
209
224
|
1. **Explicit announce.** Before performing the role, emit a single
|
|
210
225
|
message naming the role and citing \`.cclaw/agents/<agent>.md\`.
|
|
211
|
-
2. **No role interleaving.**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
3. **EvidenceRefs are mandatory.**
|
|
215
|
-
\`
|
|
216
|
-
|
|
226
|
+
2. **No role interleaving.** Close one delegation before opening
|
|
227
|
+
another; never mix, for example, reviewer and test-author work in
|
|
228
|
+
the same turn.
|
|
229
|
+
3. **EvidenceRefs are mandatory.** A \`completed\` row without
|
|
230
|
+
\`evidenceRefs\` is treated as \`missingEvidence\` by \`cclaw doctor\`
|
|
231
|
+
and blocks the stage gate.
|
|
217
232
|
|
|
218
233
|
## Stage-specific role maps
|
|
219
234
|
|
|
@@ -226,23 +241,37 @@ Identical to OpenCode. Key requirements:
|
|
|
226
241
|
| review | \`reviewer\`, \`security-reviewer\` | \`.cclaw/artifacts/07-review.md\` |
|
|
227
242
|
| ship | \`doc-updater\` | \`.cclaw/artifacts/08-ship.md\` |
|
|
228
243
|
|
|
229
|
-
##
|
|
244
|
+
## Invocation cheatsheet
|
|
230
245
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
246
|
+
- \`/use cclaw-cc\` — open the \`/cc\` skill and pick a track.
|
|
247
|
+
- \`/use cclaw-cc-next\` — advance the flow one stage.
|
|
248
|
+
- \`/use cclaw-cc-ops\` — compound / archive / rewind.
|
|
249
|
+
- Typing \`/cc …\` or \`/cc-next …\` in plain text also works: Codex
|
|
250
|
+
matches the skill descriptions (which spell out these tokens) and
|
|
251
|
+
auto-loads the right skill body.
|
|
252
|
+
- Use Codex's built-in \`/skill\` UI to enable or disable
|
|
253
|
+
cclaw skills per session.
|
|
235
254
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
255
|
+
## Hook substitution matrix
|
|
256
|
+
|
|
257
|
+
| Hook intent | Codex substitute |
|
|
258
|
+
|-------------|------------------|
|
|
259
|
+
| SessionStart rehydration | On first turn, the agent reads \`.cclaw/state/flow-state.json\` and \`.cclaw/knowledge.jsonl\` explicitly before acting. |
|
|
260
|
+
| PreToolUse prompt-guard | The \`/cc\` skill body enforces task classification before writes. |
|
|
261
|
+
| PreToolUse workflow-guard | The active stage skill enforces TDD / artifact gates before writes. |
|
|
262
|
+
| PostToolUse context-monitor | End-of-turn budget check lives in \`.cclaw/references/protocols/ethos.md\`. |
|
|
263
|
+
| Stop checkpoint | Stage-completion protocol updates \`.cclaw/state/flow-state.json\` in the same turn. |
|
|
264
|
+
| PreCompact digest | Manual \`/cc-view status\` before \`/compact\`; the user triggers this. |
|
|
240
265
|
|
|
241
266
|
## Verification
|
|
242
267
|
|
|
243
|
-
\`cclaw doctor\`
|
|
244
|
-
|
|
245
|
-
|
|
268
|
+
\`cclaw doctor\` on a codex-enabled install checks:
|
|
269
|
+
|
|
270
|
+
- \`shim:codex:cclaw-cc:present\` and \`frontmatter\` (plus the four
|
|
271
|
+
utility skills).
|
|
272
|
+
- No legacy \`.codex/commands/\` or \`.codex/hooks.json\` lingering.
|
|
273
|
+
- Every mandatory agent for the active stage has a \`completed\` row
|
|
274
|
+
with \`fulfillmentMode: "role-switch"\` and at least one \`evidenceRef\`.
|
|
246
275
|
`;
|
|
247
276
|
const PLAYBOOK_BY_HARNESS = {
|
|
248
277
|
claude: CLAUDE_PLAYBOOK,
|
|
@@ -112,7 +112,7 @@ Harness-specific additions:
|
|
|
112
112
|
- \`claude\`: \`.claude/commands/cc*.md\`, \`.claude/hooks/hooks.json\`
|
|
113
113
|
- \`cursor\`: \`.cursor/commands/cc*.md\`, \`.cursor/hooks.json\`, \`.cursor/rules/cclaw-workflow.mdc\`
|
|
114
114
|
- \`opencode\`: \`.opencode/commands/cc*.md\`, \`.opencode/plugins/cclaw-plugin.mjs\`, opencode plugin registration
|
|
115
|
-
- \`codex\`: \`.
|
|
115
|
+
- \`codex\`: \`.agents/skills/cclaw-cc/SKILL.md\`, \`.agents/skills/cclaw-cc-next/SKILL.md\`, \`.agents/skills/cclaw-cc-ideate/SKILL.md\`, \`.agents/skills/cclaw-cc-view/SKILL.md\`, \`.agents/skills/cclaw-cc-ops/SKILL.md\` (Codex CLI reads \`.agents/skills/\` on startup; \`.codex/*\` was never consumed by the CLI and is auto-cleaned on sync)
|
|
116
116
|
|
|
117
117
|
## Runtime observability
|
|
118
118
|
|
|
@@ -32,11 +32,10 @@ export const HOOK_EVENTS_BY_HARNESS = {
|
|
|
32
32
|
precompact_digest: "plugin session.cleared/session.resumed hooks"
|
|
33
33
|
},
|
|
34
34
|
codex: {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
precompact_digest: "PreCompact -> pre-compact.sh"
|
|
35
|
+
// Codex CLI has no hooks primitive. cclaw substitutes via skills
|
|
36
|
+
// under `.agents/skills/cclaw-cc*/SKILL.md` plus explicit in-turn
|
|
37
|
+
// agent steps (see codex playbook). All semantic events are
|
|
38
|
+
// intentionally unmapped here so `harness-gaps.json` exposes them
|
|
39
|
+
// honestly.
|
|
41
40
|
}
|
|
42
41
|
};
|
package/dist/content/hooks.js
CHANGED
|
@@ -1205,16 +1205,18 @@ export default function cclawPlugin(ctx) {
|
|
|
1205
1205
|
export function hooksAgentsMdBlock() {
|
|
1206
1206
|
return `### Hooks (real lifecycle integration)
|
|
1207
1207
|
|
|
1208
|
-
Cclaw generates real hook integrations
|
|
1209
|
-
|
|
1208
|
+
Cclaw generates real hook integrations for every harness that exposes a
|
|
1209
|
+
hook primitive:
|
|
1210
|
+
- **Claude/Cursor:** lifecycle rehydration + PreToolUse/PostToolUse + Stop
|
|
1210
1211
|
- **OpenCode:** session lifecycle + system transform rehydration + bootstrap parity (digest/warnings/knowledge snapshot)
|
|
1212
|
+
- **Codex:** *no hooks API exists in Codex CLI* — substitution happens via skills (\`.agents/skills/cclaw-cc*/SKILL.md\`) and explicit in-turn agent steps. See \`.cclaw/references/harnesses/codex-playbook.md\`.
|
|
1211
1213
|
|
|
1212
1214
|
| Harness | Hook file | Events |
|
|
1213
1215
|
|---------|-----------|--------|
|
|
1214
1216
|
| Claude Code | \`.claude/hooks/hooks.json\` | SessionStart(startup/resume/clear/compact), PreToolUse, PostToolUse, Stop |
|
|
1215
1217
|
| Cursor | \`.cursor/hooks.json\` | sessionStart/sessionResume/sessionClear/sessionCompact, preToolUse, postToolUse, stop |
|
|
1216
|
-
| Codex | \`.codex/hooks.json\` | SessionStart(startup/resume/clear/compact), PreToolUse, PostToolUse, Stop |
|
|
1217
1218
|
| OpenCode | \`${RUNTIME_ROOT}/hooks/opencode-plugin.mjs\` | session.created/updated/resumed/cleared/compacted/idle, tool.execute.before/after, system transform |
|
|
1219
|
+
| Codex | *none* | skill-description matching + in-turn agent steps (no hooks API) |
|
|
1218
1220
|
|
|
1219
1221
|
Hook state files:
|
|
1220
1222
|
- \`${RUNTIME_ROOT}/state/stage-activity.jsonl\`
|
|
@@ -65,7 +65,7 @@ This is the **recommended way to start** working with cclaw. Use \`/cc-next\` fo
|
|
|
65
65
|
4. Read \`${flowPath}\`.
|
|
66
66
|
5. If flow already has completed stages beyond brainstorm, warn the user that starting a new brainstorm will reset progress. Ask for confirmation before proceeding.
|
|
67
67
|
6. **Track heuristic** — classify the idea text and **recommend** a track (the user can override before any state mutation):
|
|
68
|
-
- First, load \`${RUNTIME_ROOT}/config.yaml\`. If \`trackHeuristics\` is defined, apply those per-track
|
|
68
|
+
- First, load \`${RUNTIME_ROOT}/config.yaml\`. If \`trackHeuristics\` is defined, apply those per-track vocabulary hints (\`fallback\`, \`tracks.<id>.{triggers,veto}\`) on top of the built-in defaults. Evaluation order is always \`standard -> medium -> quick\` (narrow-to-broad).
|
|
69
69
|
- **quick** (\`spec → tdd → review → ship\`) — single-purpose work where the spec is essentially already known.
|
|
70
70
|
Triggers (case-insensitive substring or close variant): \`bug\`, \`bugfix\`, \`fix\`, \`hotfix\`, \`patch\`, \`typo\`, \`regression\`, \`copy change\`, \`rename\`, \`bump\`, \`upgrade dep\`, \`config tweak\`, \`docs only\`, \`comment\`, \`lint\`, \`format\`, \`small\`, \`tiny\`, \`one-liner\`, \`revert\`.
|
|
71
71
|
- **medium** (\`brainstorm → spec → plan → tdd → review → ship\`) — additive work that fits existing architecture and still needs product framing.
|
|
@@ -141,7 +141,7 @@ Do **not** silently discard an existing flow when the user provides a prompt. If
|
|
|
141
141
|
- Ask: "Continue with reset? (A) Yes, start fresh (B) No, resume current flow"
|
|
142
142
|
- If (B) → switch to Path B behavior.
|
|
143
143
|
6. **Classify the idea** using the heuristic below and present a single track recommendation. Wait for explicit confirmation or override before mutating any state.
|
|
144
|
-
- If \`${RUNTIME_ROOT}/config.yaml\` defines \`trackHeuristics\`, apply
|
|
144
|
+
- If \`${RUNTIME_ROOT}/config.yaml\` defines \`trackHeuristics\`, apply those vocabulary hints (\`fallback\`, \`tracks.<id>.{triggers,veto}\`) on top of built-in defaults. Evaluation order is fixed: \`standard -> medium -> quick\`. (Honest note: this is advisory prose; the LLM applies it, not a Node-level router.)
|
|
145
145
|
|
|
146
146
|
**Track heuristic** (lowercase substring match against the user prompt):
|
|
147
147
|
|
package/dist/doctor.js
CHANGED
|
@@ -8,7 +8,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";
|
|
11
|
-
import { HARNESS_ADAPTERS, CCLAW_MARKER_START, CCLAW_MARKER_END, harnessShimFileNames } from "./harness-adapters.js";
|
|
11
|
+
import { HARNESS_ADAPTERS, CCLAW_MARKER_START, CCLAW_MARKER_END, harnessShimFileNames, harnessShimSkillNames } from "./harness-adapters.js";
|
|
12
12
|
import { policyChecks } from "./policy.js";
|
|
13
13
|
import { readFlowState } from "./runs.js";
|
|
14
14
|
import { skippedStagesForTrack } from "./flow-state.js";
|
|
@@ -474,13 +474,17 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
474
474
|
});
|
|
475
475
|
continue;
|
|
476
476
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
477
|
+
// For command-kind harnesses we check flat files; skill-kind (codex) is
|
|
478
|
+
// validated in the codex-specific block below (`shim:codex:<name>:*`).
|
|
479
|
+
if (adapter.shimKind === "command") {
|
|
480
|
+
for (const shim of harnessShimFileNames()) {
|
|
481
|
+
const shimPath = path.join(projectRoot, adapter.commandDir, shim);
|
|
482
|
+
checks.push({
|
|
483
|
+
name: `shim:${harness}:${shim.replace(".md", "")}`,
|
|
484
|
+
ok: await exists(shimPath),
|
|
485
|
+
details: shimPath
|
|
486
|
+
});
|
|
487
|
+
}
|
|
484
488
|
}
|
|
485
489
|
const playbookFile = path.join(projectRoot, RUNTIME_ROOT, ...HARNESS_PLAYBOOKS_DIR.split("/"), harnessPlaybookFileName(harness));
|
|
486
490
|
checks.push({
|
|
@@ -631,15 +635,16 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
631
635
|
});
|
|
632
636
|
}
|
|
633
637
|
}
|
|
634
|
-
// Hook JSON files per harness
|
|
638
|
+
// Hook JSON files per harness. Codex is absent because Codex CLI has no
|
|
639
|
+
// hooks primitive — cclaw stopped writing `.codex/hooks.json` in v0.39.0.
|
|
640
|
+
// OpenCode ships hooks through its plugin system (covered below).
|
|
635
641
|
const hookPaths = {
|
|
636
642
|
claude: ".claude/hooks/hooks.json",
|
|
637
|
-
cursor: ".cursor/hooks.json"
|
|
638
|
-
codex: ".codex/hooks.json"
|
|
643
|
+
cursor: ".cursor/hooks.json"
|
|
639
644
|
};
|
|
640
645
|
for (const harness of configuredHarnesses) {
|
|
641
646
|
const hp = hookPaths[harness];
|
|
642
|
-
if (!hp && harness !== "opencode") {
|
|
647
|
+
if (!hp && harness !== "opencode" && harness !== "codex") {
|
|
643
648
|
checks.push({
|
|
644
649
|
name: `hook:json:${harness}`,
|
|
645
650
|
ok: false,
|
|
@@ -656,7 +661,7 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
656
661
|
ok: hookOk,
|
|
657
662
|
details: fullPath
|
|
658
663
|
});
|
|
659
|
-
if (harness === "claude" || harness === "cursor"
|
|
664
|
+
if (harness === "claude" || harness === "cursor") {
|
|
660
665
|
const schema = validateHookDocument(harness, parsed);
|
|
661
666
|
checks.push({
|
|
662
667
|
name: `hook:schema:${harness}`,
|
|
@@ -757,29 +762,55 @@ export async function doctorChecks(projectRoot, options = {}) {
|
|
|
757
762
|
});
|
|
758
763
|
}
|
|
759
764
|
if (configuredHarnesses.includes("codex")) {
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
const
|
|
765
|
+
// Codex CLI has no hooks primitive and no slash-command discovery
|
|
766
|
+
// (`.codex/commands/*` was never read). cclaw ships codex shims as
|
|
767
|
+
// skills under `.agents/skills/cclaw-cc*/SKILL.md`. Every required
|
|
768
|
+
// skill must exist with the expected frontmatter `name`.
|
|
769
|
+
const skillsRoot = path.join(projectRoot, ".agents/skills");
|
|
770
|
+
for (const skillName of harnessShimSkillNames()) {
|
|
771
|
+
const skillPath = path.join(skillsRoot, skillName, "SKILL.md");
|
|
772
|
+
let ok = false;
|
|
773
|
+
let frontmatterOk = false;
|
|
774
|
+
if (await exists(skillPath)) {
|
|
775
|
+
ok = true;
|
|
776
|
+
const content = await fs.readFile(skillPath, "utf8");
|
|
777
|
+
frontmatterOk = new RegExp(`^---[\\s\\S]*?\\nname: ${skillName}\\b`, "u").test(content);
|
|
778
|
+
}
|
|
779
|
+
checks.push({
|
|
780
|
+
name: `shim:codex:${skillName}:present`,
|
|
781
|
+
ok,
|
|
782
|
+
details: skillPath
|
|
783
|
+
});
|
|
784
|
+
checks.push({
|
|
785
|
+
name: `shim:codex:${skillName}:frontmatter`,
|
|
786
|
+
ok,
|
|
787
|
+
details: frontmatterOk
|
|
788
|
+
? `${skillPath} has \`name: ${skillName}\` frontmatter`
|
|
789
|
+
: ok
|
|
790
|
+
? `${skillPath} present but \`name: ${skillName}\` frontmatter is missing`
|
|
791
|
+
: `${skillPath} absent; cannot validate frontmatter`
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
// Warn if legacy `.codex/commands/*` or `.codex/hooks.json` is still
|
|
795
|
+
// around — cclaw syncs should have removed these, but a botched
|
|
796
|
+
// upgrade or a manual restore could leave them dangling.
|
|
797
|
+
const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
|
|
798
|
+
const legacyCommandsPresent = await exists(legacyCommandsDir);
|
|
765
799
|
checks.push({
|
|
766
|
-
name: "
|
|
767
|
-
ok,
|
|
768
|
-
details:
|
|
800
|
+
name: "warning:codex:legacy_commands_dir",
|
|
801
|
+
ok: true,
|
|
802
|
+
details: legacyCommandsPresent
|
|
803
|
+
? `warning: ${legacyCommandsDir} still present; Codex never read this directory — run \`cclaw sync\` to remove it.`
|
|
804
|
+
: `no legacy ${legacyCommandsDir} detected`
|
|
769
805
|
});
|
|
770
|
-
const
|
|
771
|
-
const
|
|
772
|
-
const postCommands = collectHookCommands(hooks.PostToolUse);
|
|
773
|
-
const stopCommands = collectHookCommands(hooks.Stop);
|
|
774
|
-
const wiringOk = sessionCommands.some((cmd) => cmd.includes("session-start.sh")) &&
|
|
775
|
-
preCommands.some((cmd) => cmd.includes("prompt-guard.sh")) &&
|
|
776
|
-
preCommands.some((cmd) => cmd.includes("workflow-guard.sh")) &&
|
|
777
|
-
postCommands.some((cmd) => cmd.includes("context-monitor.sh")) &&
|
|
778
|
-
stopCommands.some((cmd) => cmd.includes("stop-checkpoint.sh"));
|
|
806
|
+
const legacyHooks = path.join(projectRoot, ".codex/hooks.json");
|
|
807
|
+
const legacyHooksPresent = await exists(legacyHooks);
|
|
779
808
|
checks.push({
|
|
780
|
-
name: "
|
|
781
|
-
ok:
|
|
782
|
-
details:
|
|
809
|
+
name: "warning:codex:legacy_hooks_json",
|
|
810
|
+
ok: true,
|
|
811
|
+
details: legacyHooksPresent
|
|
812
|
+
? `warning: ${legacyHooks} still present; Codex CLI has no hooks API — run \`cclaw sync\` to remove it.`
|
|
813
|
+
: `no legacy ${legacyHooks} detected`
|
|
783
814
|
});
|
|
784
815
|
}
|
|
785
816
|
if (configuredHarnesses.includes("opencode")) {
|
|
@@ -21,9 +21,32 @@ export type SubagentFallback =
|
|
|
21
21
|
* under `waiverReason: "harness_limitation"`.
|
|
22
22
|
*/
|
|
23
23
|
| "waiver";
|
|
24
|
+
/**
|
|
25
|
+
* How a harness discovers cclaw's `/cc*` entry points.
|
|
26
|
+
*
|
|
27
|
+
* - `command` — harness has a native custom slash-command system and reads
|
|
28
|
+
* flat markdown files from `<commandDir>/<fileName>.md` (Claude Code,
|
|
29
|
+
* Cursor, OpenCode).
|
|
30
|
+
* - `skill` — harness ignores flat commands and reads SKILL.md from
|
|
31
|
+
* directories under a skills root (Codex CLI ≥0.89, Jan 2026). cclaw
|
|
32
|
+
* writes `<commandDir>/<skillName>/SKILL.md` and the agent invokes it
|
|
33
|
+
* either via `/use <skillName>` or via automatic description matching
|
|
34
|
+
* when the user's text mentions `/cc`, `/cc-next`, etc.
|
|
35
|
+
*/
|
|
36
|
+
export type ShimKind = "command" | "skill";
|
|
24
37
|
export interface HarnessAdapter {
|
|
25
38
|
id: HarnessId;
|
|
39
|
+
/**
|
|
40
|
+
* Root directory where cclaw writes `/cc*` entry points.
|
|
41
|
+
*
|
|
42
|
+
* - For `shimKind: "command"` this is the directory containing flat
|
|
43
|
+
* markdown files (`<commandDir>/cc.md`, `<commandDir>/cc-next.md`, …).
|
|
44
|
+
* - For `shimKind: "skill"` this is the skills root that contains
|
|
45
|
+
* per-skill subdirectories (`<commandDir>/<skillName>/SKILL.md`).
|
|
46
|
+
*/
|
|
26
47
|
commandDir: string;
|
|
48
|
+
/** See {@link ShimKind}. Defaults to `"command"` if unspecified at a callsite. */
|
|
49
|
+
shimKind: ShimKind;
|
|
27
50
|
capabilities: {
|
|
28
51
|
/**
|
|
29
52
|
* Level of native subagent dispatch:
|
|
@@ -45,6 +68,8 @@ export interface HarnessAdapter {
|
|
|
45
68
|
};
|
|
46
69
|
}
|
|
47
70
|
export declare function harnessShimFileNames(): string[];
|
|
71
|
+
/** Skill folder names cclaw writes under `<commandDir>` for skill-kind harnesses. */
|
|
72
|
+
export declare function harnessShimSkillNames(): string[];
|
|
48
73
|
export declare const HARNESS_ADAPTERS: Record<HarnessId, HarnessAdapter>;
|
|
49
74
|
export type HarnessTier = "tier1" | "tier2" | "tier3";
|
|
50
75
|
export declare function harnessTier(harnessId: HarnessId): HarnessTier;
|
package/dist/harness-adapters.js
CHANGED
|
@@ -14,29 +14,35 @@ const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOUR
|
|
|
14
14
|
const UTILITY_SHIMS = [
|
|
15
15
|
{
|
|
16
16
|
fileName: "cc-next.md",
|
|
17
|
+
skillName: "cclaw-cc-next",
|
|
17
18
|
command: "next",
|
|
18
19
|
skillFolder: "flow-next-step",
|
|
19
20
|
commandFile: "next.md"
|
|
20
21
|
},
|
|
21
22
|
{
|
|
22
23
|
fileName: "cc-ideate.md",
|
|
24
|
+
skillName: "cclaw-cc-ideate",
|
|
23
25
|
command: "ideate",
|
|
24
26
|
skillFolder: "flow-ideate",
|
|
25
27
|
commandFile: "ideate.md"
|
|
26
28
|
},
|
|
27
29
|
{
|
|
28
30
|
fileName: "cc-view.md",
|
|
31
|
+
skillName: "cclaw-cc-view",
|
|
29
32
|
command: "view",
|
|
30
33
|
skillFolder: "flow-view",
|
|
31
34
|
commandFile: "view.md"
|
|
32
35
|
},
|
|
33
36
|
{
|
|
34
37
|
fileName: "cc-ops.md",
|
|
38
|
+
skillName: "cclaw-cc-ops",
|
|
35
39
|
command: "ops",
|
|
36
40
|
skillFolder: "flow-ops",
|
|
37
41
|
commandFile: "ops.md"
|
|
38
42
|
}
|
|
39
43
|
];
|
|
44
|
+
/** Skill-kind shim name for the root `/cc` entry point. */
|
|
45
|
+
const ENTRY_SHIM_SKILL_NAME = "cclaw-cc";
|
|
40
46
|
/**
|
|
41
47
|
* Shims that older cclaw versions installed as top-level slash commands but
|
|
42
48
|
* which we now treat as internal (skill-only, invoked by the agent, never
|
|
@@ -47,10 +53,15 @@ const LEGACY_HARNESS_SHIMS = ["cc-learn.md"];
|
|
|
47
53
|
export function harnessShimFileNames() {
|
|
48
54
|
return ["cc.md", ...UTILITY_SHIMS.map((shim) => shim.fileName)];
|
|
49
55
|
}
|
|
56
|
+
/** Skill folder names cclaw writes under `<commandDir>` for skill-kind harnesses. */
|
|
57
|
+
export function harnessShimSkillNames() {
|
|
58
|
+
return [ENTRY_SHIM_SKILL_NAME, ...UTILITY_SHIMS.map((shim) => shim.skillName)];
|
|
59
|
+
}
|
|
50
60
|
export const HARNESS_ADAPTERS = {
|
|
51
61
|
claude: {
|
|
52
62
|
id: "claude",
|
|
53
63
|
commandDir: ".claude/commands",
|
|
64
|
+
shimKind: "command",
|
|
54
65
|
capabilities: {
|
|
55
66
|
nativeSubagentDispatch: "full",
|
|
56
67
|
hookSurface: "full",
|
|
@@ -61,6 +72,7 @@ export const HARNESS_ADAPTERS = {
|
|
|
61
72
|
cursor: {
|
|
62
73
|
id: "cursor",
|
|
63
74
|
commandDir: ".cursor/commands",
|
|
75
|
+
shimKind: "command",
|
|
64
76
|
capabilities: {
|
|
65
77
|
// Cursor has a real Task tool with subagent_type (generalPurpose,
|
|
66
78
|
// explore, shell, browser-use, …) but no user-defined named
|
|
@@ -75,6 +87,7 @@ export const HARNESS_ADAPTERS = {
|
|
|
75
87
|
opencode: {
|
|
76
88
|
id: "opencode",
|
|
77
89
|
commandDir: ".opencode/commands",
|
|
90
|
+
shimKind: "command",
|
|
78
91
|
capabilities: {
|
|
79
92
|
nativeSubagentDispatch: "partial",
|
|
80
93
|
hookSurface: "plugin",
|
|
@@ -84,10 +97,17 @@ export const HARNESS_ADAPTERS = {
|
|
|
84
97
|
},
|
|
85
98
|
codex: {
|
|
86
99
|
id: "codex",
|
|
87
|
-
|
|
100
|
+
// Codex CLI reads skills from the universal `.agents/skills/` path
|
|
101
|
+
// (OpenAI Codex 0.89, Jan 2026; legacy `~/.codex/skills/` also
|
|
102
|
+
// supported). It has no native `.codex/commands/` slash-command
|
|
103
|
+
// discovery and no `.codex/hooks.json` primitive — v0.39.0 migrated
|
|
104
|
+
// cclaw to write skill-kind shims here and stops generating the
|
|
105
|
+
// dead `.codex/*` surfaces.
|
|
106
|
+
commandDir: ".agents/skills",
|
|
107
|
+
shimKind: "skill",
|
|
88
108
|
capabilities: {
|
|
89
109
|
nativeSubagentDispatch: "none",
|
|
90
|
-
hookSurface: "
|
|
110
|
+
hookSurface: "none",
|
|
91
111
|
structuredAsk: "plain-text",
|
|
92
112
|
subagentFallback: "role-switch"
|
|
93
113
|
}
|
|
@@ -193,6 +213,22 @@ If the same approach fails three times in a row (same command, same finding, sam
|
|
|
193
213
|
- Detailed operating procedures live in \`.cclaw/skills/using-cclaw/SKILL.md\`.
|
|
194
214
|
- Preamble budget and cooldown rules live in \`.cclaw/references/protocols/ethos.md\`.
|
|
195
215
|
- Subagent orchestration patterns: \`.cclaw/skills/subagent-dev/SKILL.md\` and \`.cclaw/skills/parallel-dispatch/SKILL.md\`.
|
|
216
|
+
|
|
217
|
+
### Codex users
|
|
218
|
+
|
|
219
|
+
OpenAI Codex CLI has **no native \`/cc\` slash command** and **no hooks API**. The
|
|
220
|
+
\`/cc\`, \`/cc-next\`, \`/cc-ideate\`, \`/cc-view\`, \`/cc-ops\` tokens above describe
|
|
221
|
+
intent — in Codex they map onto skills cclaw installs at
|
|
222
|
+
\`.agents/skills/cclaw-cc*/SKILL.md\`. Activate one of two ways:
|
|
223
|
+
|
|
224
|
+
- Type \`/use cclaw-cc\` (or \`cclaw-cc-next\`, etc.) at Codex's prompt.
|
|
225
|
+
- Type \`/cc …\` as plain text — Codex matches the skill \`description\`
|
|
226
|
+
frontmatter (which spells out the token verbatim) and loads the right
|
|
227
|
+
skill body automatically.
|
|
228
|
+
|
|
229
|
+
Legacy \`.codex/commands/*\` and \`.codex/hooks.json\` are removed on
|
|
230
|
+
\`cclaw sync\` — Codex CLI never consumed either path. See
|
|
231
|
+
\`.cclaw/references/harnesses/codex-playbook.md\` for the hook-substitution matrix.
|
|
196
232
|
${CCLAW_MARKER_END}`;
|
|
197
233
|
}
|
|
198
234
|
/** Removes the cclaw AGENTS.md block. */
|
|
@@ -266,6 +302,143 @@ Load and execute:
|
|
|
266
302
|
This is a utility command (not a flow stage). It does not advance flow state.
|
|
267
303
|
`;
|
|
268
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Frontmatter `description` that triggers the skill when the user types any
|
|
307
|
+
* of the classic cclaw slash-tokens. Codex's skill matcher runs on the skill
|
|
308
|
+
* description verbatim, so we spell out every vocabulary Codex users type
|
|
309
|
+
* instead of relying on semantics.
|
|
310
|
+
*/
|
|
311
|
+
function codexSkillDescription(command) {
|
|
312
|
+
switch (command) {
|
|
313
|
+
case "cc":
|
|
314
|
+
return `Entry point for the cclaw 8-stage workflow (brainstorm → scope → design → spec → plan → tdd → review → ship). Use whenever the user types \`/cc\`, \`/cclaw\`, or asks to "start the flow", "begin cclaw", "kick off the workflow", "classify this task", or wants to start/resume a non-trivial software change. No args = resume the active stage from \`.cclaw/state/flow-state.json\`. With a prompt = classify and pick a track (quick/medium/standard).`;
|
|
315
|
+
case "next":
|
|
316
|
+
return `Advance the cclaw flow to the next stage. Use when the user types \`/cc-next\` or asks to "move to the next stage", "continue the flow", "advance cclaw", "progress the workflow", or when the current stage skill reports completion and gates have passed.`;
|
|
317
|
+
case "ideate":
|
|
318
|
+
return `Read-only repo-improvement discovery for cclaw. Use when the user types \`/cc-ideate\` or asks to "ideate", "brainstorm improvements", "scan the repo for TODOs/tech debt", "generate a backlog", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
|
|
319
|
+
case "view":
|
|
320
|
+
return `Read-only router for cclaw flow views. Use when the user types \`/cc-view\`, \`/cc-view status\`, \`/cc-view tree\`, \`/cc-view diff\`, or asks to "show cclaw status", "show the flow tree", "diff flow state", or wants a snapshot without mutation.`;
|
|
321
|
+
case "ops":
|
|
322
|
+
return `Operations router for cclaw post-flow actions. Use when the user types \`/cc-ops\`, \`/cc-ops feature\`, \`/cc-ops tdd-log\`, \`/cc-ops retro\`, \`/cc-ops compound\`, \`/cc-ops archive\`, \`/cc-ops rewind\`, or asks to "archive the run", "run the retro", "compound knowledge", "rewind to an earlier stage", or manage feature worktrees.`;
|
|
323
|
+
default:
|
|
324
|
+
return `Generated cclaw skill for ${command}.`;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Skill body for codex-kind shims. Deliberately terse — the meat lives in
|
|
329
|
+
* `.cclaw/skills/` and `.cclaw/commands/`, and Codex's progressive-disclosure
|
|
330
|
+
* model loads skill bodies lazily, so we want a pointer plus the honest
|
|
331
|
+
* harness caveat, not a duplicated contract.
|
|
332
|
+
*/
|
|
333
|
+
function codexSkillBody(command, skillFolder, commandFile) {
|
|
334
|
+
const slashToken = command === "cc" ? "/cc" : `/cc-${command}`;
|
|
335
|
+
const title = command === "cc" ? "cclaw /cc (Codex adapter)" : `cclaw ${slashToken} (Codex adapter)`;
|
|
336
|
+
const extraContractHeading = command === "cc"
|
|
337
|
+
? "If you have not already loaded the cclaw meta-skill this session, also load `.cclaw/skills/using-cclaw/SKILL.md` — it is the routing brain for stage/utility selection."
|
|
338
|
+
: "This skill is a utility entry point, not a flow stage. Do not mutate `.cclaw/state/flow-state.json` directly.";
|
|
339
|
+
return `# ${title}
|
|
340
|
+
|
|
341
|
+
You are running inside the OpenAI Codex harness. Codex has **no native
|
|
342
|
+
\`${slashToken}\` slash command and no \`.codex/hooks.json\` primitive** — cclaw
|
|
343
|
+
ships its entry points as skills under \`.agents/skills/\` and relies on
|
|
344
|
+
\`AGENTS.md\` + skill descriptions for activation. If the user typed
|
|
345
|
+
\`${slashToken} …\` as plain text (or asked to perform its action in English),
|
|
346
|
+
follow the steps below.
|
|
347
|
+
|
|
348
|
+
## Protocol
|
|
349
|
+
|
|
350
|
+
1. Read \`.cclaw/state/flow-state.json\` first to know the active stage,
|
|
351
|
+
track, and run metadata.
|
|
352
|
+
2. Load and follow \`.cclaw/skills/${skillFolder}/SKILL.md\` as the
|
|
353
|
+
authoritative skill — its gates, artifacts, and delegations are
|
|
354
|
+
canonical.
|
|
355
|
+
3. Load \`.cclaw/commands/${commandFile}\` for the full command contract
|
|
356
|
+
(protocol, validation, post-state expectations).
|
|
357
|
+
4. ${extraContractHeading}
|
|
358
|
+
|
|
359
|
+
## Honest caveats
|
|
360
|
+
|
|
361
|
+
- Codex has no subagent dispatch primitive. Mandatory delegations
|
|
362
|
+
fall back to **role-switch** — announce the role, act in-session,
|
|
363
|
+
append a completed row with \`evidenceRefs\` to
|
|
364
|
+
\`.cclaw/state/delegation-log.json\`. Silent auto-waiver is disabled
|
|
365
|
+
(v0.33+).
|
|
366
|
+
- Codex has no hooks. Session rehydration, prompt-guard, workflow-guard,
|
|
367
|
+
context-monitor, stop-checkpoint, and pre-compact behavior all have to
|
|
368
|
+
run as explicit agent steps. Read \`.cclaw/references/harnesses/codex-playbook.md\`
|
|
369
|
+
for the substitution matrix.
|
|
370
|
+
`;
|
|
371
|
+
}
|
|
372
|
+
function codexSkillMarkdown(command, skillName, skillFolder, commandFile) {
|
|
373
|
+
const description = codexSkillDescription(command);
|
|
374
|
+
const frontmatter = [
|
|
375
|
+
"---",
|
|
376
|
+
`name: ${skillName}`,
|
|
377
|
+
`description: ${description}`,
|
|
378
|
+
"source: generated-by-cclaw",
|
|
379
|
+
"---",
|
|
380
|
+
""
|
|
381
|
+
].join("\n");
|
|
382
|
+
return `${frontmatter}${codexSkillBody(command, skillFolder, commandFile)}`;
|
|
383
|
+
}
|
|
384
|
+
async function writeCommandKindShims(commandDir, harness) {
|
|
385
|
+
await ensureDir(commandDir);
|
|
386
|
+
await writeFileSafe(path.join(commandDir, "cc.md"), utilityShimContent(harness, "cc", "flow-start", "start.md"));
|
|
387
|
+
for (const shim of UTILITY_SHIMS) {
|
|
388
|
+
await writeFileSafe(path.join(commandDir, shim.fileName), utilityShimContent(harness, shim.command, shim.skillFolder, shim.commandFile));
|
|
389
|
+
}
|
|
390
|
+
for (const legacy of LEGACY_HARNESS_SHIMS) {
|
|
391
|
+
const legacyPath = path.join(commandDir, legacy);
|
|
392
|
+
try {
|
|
393
|
+
await fs.unlink(legacyPath);
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
// fine — file may not exist (fresh install) or may be on read-only FS
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
async function writeSkillKindShims(commandDir) {
|
|
401
|
+
await ensureDir(commandDir);
|
|
402
|
+
await writeFileSafe(path.join(commandDir, ENTRY_SHIM_SKILL_NAME, "SKILL.md"), codexSkillMarkdown("cc", ENTRY_SHIM_SKILL_NAME, "flow-start", "start.md"));
|
|
403
|
+
for (const shim of UTILITY_SHIMS) {
|
|
404
|
+
await writeFileSafe(path.join(commandDir, shim.skillName, "SKILL.md"), codexSkillMarkdown(shim.command, shim.skillName, shim.skillFolder, shim.commandFile));
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Legacy codex surfaces cclaw wrote before v0.39.0 that Codex CLI never
|
|
409
|
+
* actually consumed (`.codex/commands/*.md` had no discovery, `.codex/hooks.json`
|
|
410
|
+
* had no hooks API). On every sync we proactively delete these so users
|
|
411
|
+
* upgrading from older installs see a clean `.codex/` (or no `.codex/` at all).
|
|
412
|
+
*/
|
|
413
|
+
async function cleanupLegacyCodexSurfaces(projectRoot) {
|
|
414
|
+
const legacyCommandsDir = path.join(projectRoot, ".codex/commands");
|
|
415
|
+
try {
|
|
416
|
+
await fs.rm(legacyCommandsDir, { recursive: true, force: true });
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// best-effort cleanup
|
|
420
|
+
}
|
|
421
|
+
const legacyHooksFile = path.join(projectRoot, ".codex/hooks.json");
|
|
422
|
+
try {
|
|
423
|
+
await fs.rm(legacyHooksFile, { force: true });
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// best-effort cleanup
|
|
427
|
+
}
|
|
428
|
+
// If `.codex/` is now empty we drop it entirely — codex CLI doesn't need
|
|
429
|
+
// that directory anymore. Leave it alone if the user stored their own
|
|
430
|
+
// data there.
|
|
431
|
+
try {
|
|
432
|
+
const codexDir = path.join(projectRoot, ".codex");
|
|
433
|
+
const entries = await fs.readdir(codexDir);
|
|
434
|
+
if (entries.length === 0) {
|
|
435
|
+
await fs.rmdir(codexDir);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
// directory absent or non-empty
|
|
440
|
+
}
|
|
441
|
+
}
|
|
269
442
|
async function syncAgentFiles(projectRoot) {
|
|
270
443
|
const agentsDir = path.join(projectRoot, RUNTIME_ROOT, "agents");
|
|
271
444
|
await ensureDir(agentsDir);
|
|
@@ -274,25 +447,20 @@ async function syncAgentFiles(projectRoot) {
|
|
|
274
447
|
}
|
|
275
448
|
}
|
|
276
449
|
export async function syncHarnessShims(projectRoot, harnesses) {
|
|
450
|
+
// Legacy codex cleanup is unconditional — even installs that never enabled
|
|
451
|
+
// codex but previously did will see stale `.codex/commands/*.md` and
|
|
452
|
+
// `.codex/hooks.json` get removed on upgrade.
|
|
453
|
+
await cleanupLegacyCodexSurfaces(projectRoot);
|
|
277
454
|
for (const harness of harnesses) {
|
|
278
455
|
const adapter = HARNESS_ADAPTERS[harness];
|
|
279
|
-
if (!adapter)
|
|
456
|
+
if (!adapter)
|
|
280
457
|
continue;
|
|
281
|
-
}
|
|
282
458
|
const commandDir = path.join(projectRoot, adapter.commandDir);
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
for (const shim of UTILITY_SHIMS) {
|
|
286
|
-
await writeFileSafe(path.join(commandDir, shim.fileName), utilityShimContent(harness, shim.command, shim.skillFolder, shim.commandFile));
|
|
459
|
+
if (adapter.shimKind === "skill") {
|
|
460
|
+
await writeSkillKindShims(commandDir);
|
|
287
461
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
try {
|
|
291
|
-
await fs.unlink(legacyPath);
|
|
292
|
-
}
|
|
293
|
-
catch {
|
|
294
|
-
// fine — file may not exist (fresh install) or may be on read-only FS
|
|
295
|
-
}
|
|
462
|
+
else {
|
|
463
|
+
await writeCommandKindShims(commandDir, harness);
|
|
296
464
|
}
|
|
297
465
|
}
|
|
298
466
|
await syncAgentFiles(projectRoot);
|
package/dist/init-detect.js
CHANGED
|
@@ -26,9 +26,14 @@ export async function detectHarnesses(projectRoot) {
|
|
|
26
26
|
if (await anyExists(opencodeHints)) {
|
|
27
27
|
detected.push("opencode");
|
|
28
28
|
}
|
|
29
|
+
// Codex CLI doesn't require a persistent per-project directory. We
|
|
30
|
+
// detect via `.agents/skills/` (the universal path Codex 0.89+ reads;
|
|
31
|
+
// Jan 2026) or the legacy `.codex/` marker left by pre-v0.39 cclaw.
|
|
32
|
+
// AGENTS.md is intentionally *not* a codex hint because every other
|
|
33
|
+
// harness in cclaw's list also reads AGENTS.md.
|
|
29
34
|
const codexHints = [
|
|
30
|
-
path.join(projectRoot, ".
|
|
31
|
-
path.join(projectRoot, ".codex
|
|
35
|
+
path.join(projectRoot, ".agents/skills"),
|
|
36
|
+
path.join(projectRoot, ".codex")
|
|
32
37
|
];
|
|
33
38
|
if (await anyExists(codexHints)) {
|
|
34
39
|
detected.push("codex");
|
package/dist/install.js
CHANGED
|
@@ -23,7 +23,7 @@ import { archiveCommandContract, archiveCommandSkillMarkdown } from "./content/a
|
|
|
23
23
|
import { rewindCommandContract, rewindCommandSkillMarkdown } from "./content/rewind-command.js";
|
|
24
24
|
import { subagentDrivenDevSkill, parallelAgentsSkill } from "./content/subagents.js";
|
|
25
25
|
import { sessionHooksSkillMarkdown } from "./content/session-hooks.js";
|
|
26
|
-
import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, cursorHooksJson
|
|
26
|
+
import { sessionStartScript, stopCheckpointScript, preCompactScript, opencodePluginJs, claudeHooksJson, cursorHooksJson } from "./content/hooks.js";
|
|
27
27
|
import { contextMonitorScript, promptGuardScript, workflowGuardScript } from "./content/observe.js";
|
|
28
28
|
import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.js";
|
|
29
29
|
import { decisionProtocolMarkdown, completionProtocolMarkdown, ethosProtocolMarkdown } from "./content/protocols.js";
|
|
@@ -667,11 +667,10 @@ async function writeHooks(projectRoot, config) {
|
|
|
667
667
|
await ensureDir(cursorDir);
|
|
668
668
|
await writeMergedHookJson(projectRoot, path.join(cursorDir, "hooks.json"), cursorHooksJson());
|
|
669
669
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
}
|
|
670
|
+
// Codex has no hooks primitive — v0.39.0 stopped generating
|
|
671
|
+
// `.codex/hooks.json` because Codex CLI never actually read it. Codex
|
|
672
|
+
// substitutes for hooks via explicit agent steps documented in the
|
|
673
|
+
// codex playbook.
|
|
675
674
|
// OpenCode registration is auto-managed via opencode.json/opencode.jsonc.
|
|
676
675
|
}
|
|
677
676
|
}
|
|
@@ -906,10 +905,13 @@ async function writeCursorWorkflowRule(projectRoot, harnesses) {
|
|
|
906
905
|
}
|
|
907
906
|
async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
|
|
908
907
|
const enabled = new Set(harnesses);
|
|
908
|
+
// Codex is intentionally absent — cclaw stopped generating `.codex/hooks.json`
|
|
909
|
+
// in v0.39.0 (the file was never consumed by Codex CLI). Legacy `.codex/*`
|
|
910
|
+
// files are removed unconditionally by `cleanupLegacyCodexSurfaces` during
|
|
911
|
+
// every `syncHarnessShims` pass.
|
|
909
912
|
const managedHookFiles = [
|
|
910
913
|
{ harness: "claude", hookPath: path.join(projectRoot, ".claude/hooks/hooks.json") },
|
|
911
|
-
{ harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") }
|
|
912
|
-
{ harness: "codex", hookPath: path.join(projectRoot, ".codex/hooks.json") }
|
|
914
|
+
{ harness: "cursor", hookPath: path.join(projectRoot, ".cursor/hooks.json") }
|
|
913
915
|
];
|
|
914
916
|
for (const entry of managedHookFiles) {
|
|
915
917
|
if (enabled.has(entry.harness))
|
|
@@ -1075,6 +1077,12 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
1075
1077
|
async function cleanStaleFiles(projectRoot) {
|
|
1076
1078
|
const expectedShimFiles = new Set(harnessShimFileNames());
|
|
1077
1079
|
for (const adapter of Object.values(HARNESS_ADAPTERS)) {
|
|
1080
|
+
// Skill-kind shims (Codex) live in per-skill directories, not flat
|
|
1081
|
+
// markdown files, so the regex-based stale sweep below would never
|
|
1082
|
+
// match them anyway. The legacy `.codex/commands/` cleanup happens in
|
|
1083
|
+
// `cleanupLegacyCodexSurfaces` inside syncHarnessShims().
|
|
1084
|
+
if (adapter.shimKind === "skill")
|
|
1085
|
+
continue;
|
|
1078
1086
|
const commandDir = path.join(projectRoot, adapter.commandDir);
|
|
1079
1087
|
if (!(await exists(commandDir)))
|
|
1080
1088
|
continue;
|
|
@@ -1298,6 +1306,24 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1298
1306
|
// directory not present
|
|
1299
1307
|
}
|
|
1300
1308
|
}
|
|
1309
|
+
// v0.39.0 migrated Codex shims to `.agents/skills/cclaw-cc*/SKILL.md`
|
|
1310
|
+
// (Codex CLI reads `.agents/skills/`, not `.codex/commands/`). On uninstall
|
|
1311
|
+
// we remove just the cclaw-owned skill folders, not the whole
|
|
1312
|
+
// `.agents/skills/` directory — other tools may share it.
|
|
1313
|
+
const codexSkillsRoot = path.join(projectRoot, ".agents/skills");
|
|
1314
|
+
try {
|
|
1315
|
+
const entries = await fs.readdir(codexSkillsRoot);
|
|
1316
|
+
for (const entry of entries) {
|
|
1317
|
+
if (/^cclaw-(?:cc)(?:-.*)?$/u.test(entry)) {
|
|
1318
|
+
await fs.rm(path.join(codexSkillsRoot, entry), { recursive: true, force: true });
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
catch {
|
|
1323
|
+
// directory not present
|
|
1324
|
+
}
|
|
1325
|
+
await removeIfEmpty(codexSkillsRoot);
|
|
1326
|
+
await removeIfEmpty(path.join(projectRoot, ".agents"));
|
|
1301
1327
|
for (const pluginPath of [
|
|
1302
1328
|
path.join(projectRoot, ".opencode/plugins/viby-plugin.mjs"),
|
|
1303
1329
|
path.join(projectRoot, ".opencode/plugins/opencode-plugin.mjs"),
|
|
@@ -4,9 +4,16 @@ export interface TrackResolution {
|
|
|
4
4
|
reason: string;
|
|
5
5
|
matchedTokens: string[];
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Reference implementation of the track classifier the /cc skill prose
|
|
9
|
+
* describes. Tests pin its behavior so the built-in defaults stay honest.
|
|
10
|
+
* This function is not called from cclaw runtime — `/cc` routing happens in
|
|
11
|
+
* the LLM. If you wire this in later, update README to drop the
|
|
12
|
+
* "advisory" language.
|
|
13
|
+
*/
|
|
7
14
|
export declare function resolveTrackFromPrompt(prompt: string, config: TrackHeuristicsConfig | undefined): TrackResolution;
|
|
8
15
|
export declare const TRACK_HEURISTICS_DEFAULTS: {
|
|
9
16
|
readonly fallback: "standard";
|
|
10
|
-
readonly
|
|
17
|
+
readonly evaluationOrder: readonly ("quick" | "medium" | "standard")[];
|
|
11
18
|
readonly tracks: Record<"quick" | "medium" | "standard", TrackHeuristicRule>;
|
|
12
19
|
};
|
package/dist/track-heuristics.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { FLOW_TRACKS } from "./types.js";
|
|
2
|
+
// Built-in vocabulary per track. Kept in one place so tests, docs, and the
|
|
3
|
+
// /cc skill prose can snapshot the exact same strings.
|
|
2
4
|
const DEFAULT_RULES = {
|
|
3
5
|
quick: {
|
|
4
6
|
triggers: [
|
|
@@ -48,7 +50,9 @@ const DEFAULT_RULES = {
|
|
|
48
50
|
]
|
|
49
51
|
}
|
|
50
52
|
};
|
|
51
|
-
|
|
53
|
+
// Fixed evaluation order: narrow-to-broad. Overriding this was never wired
|
|
54
|
+
// into runtime, so cclaw stopped offering the knob in v0.38.0.
|
|
55
|
+
const EVALUATION_ORDER = ["standard", "medium", "quick"];
|
|
52
56
|
const DEFAULT_FALLBACK = "standard";
|
|
53
57
|
function hasToken(promptLower, token) {
|
|
54
58
|
return promptLower.includes(token.toLowerCase());
|
|
@@ -62,17 +66,6 @@ function matchRule(promptLower, rule) {
|
|
|
62
66
|
matches.push(trigger);
|
|
63
67
|
}
|
|
64
68
|
}
|
|
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
69
|
return [...new Set(matches)];
|
|
77
70
|
}
|
|
78
71
|
function isValidTrack(value) {
|
|
@@ -89,34 +82,26 @@ function mergeRules(base, overrides) {
|
|
|
89
82
|
continue;
|
|
90
83
|
merged[track] = {
|
|
91
84
|
triggers: rule.triggers ?? merged[track].triggers,
|
|
92
|
-
patterns: rule.patterns ?? merged[track].patterns,
|
|
93
85
|
veto: rule.veto ?? merged[track].veto
|
|
94
86
|
};
|
|
95
87
|
}
|
|
96
88
|
return merged;
|
|
97
89
|
}
|
|
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
90
|
function resolveFallback(config) {
|
|
112
91
|
return config?.fallback && isValidTrack(config.fallback) ? config.fallback : DEFAULT_FALLBACK;
|
|
113
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Reference implementation of the track classifier the /cc skill prose
|
|
95
|
+
* describes. Tests pin its behavior so the built-in defaults stay honest.
|
|
96
|
+
* This function is not called from cclaw runtime — `/cc` routing happens in
|
|
97
|
+
* the LLM. If you wire this in later, update README to drop the
|
|
98
|
+
* "advisory" language.
|
|
99
|
+
*/
|
|
114
100
|
export function resolveTrackFromPrompt(prompt, config) {
|
|
115
101
|
const promptLower = prompt.toLowerCase();
|
|
116
102
|
const rules = mergeRules(DEFAULT_RULES, config);
|
|
117
|
-
const priority = resolvePriority(config);
|
|
118
103
|
const fallback = resolveFallback(config);
|
|
119
|
-
for (const track of
|
|
104
|
+
for (const track of EVALUATION_ORDER) {
|
|
120
105
|
const rule = rules[track];
|
|
121
106
|
const vetoes = rule.veto ?? [];
|
|
122
107
|
if (vetoes.some((token) => hasToken(promptLower, token))) {
|
|
@@ -139,6 +124,6 @@ export function resolveTrackFromPrompt(prompt, config) {
|
|
|
139
124
|
}
|
|
140
125
|
export const TRACK_HEURISTICS_DEFAULTS = {
|
|
141
126
|
fallback: DEFAULT_FALLBACK,
|
|
142
|
-
|
|
127
|
+
evaluationOrder: EVALUATION_ORDER,
|
|
143
128
|
tracks: DEFAULT_RULES
|
|
144
129
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -25,20 +25,38 @@ export type HarnessId = (typeof HARNESS_IDS)[number];
|
|
|
25
25
|
*/
|
|
26
26
|
export declare const LANGUAGE_RULE_PACKS: readonly ["typescript", "python", "go"];
|
|
27
27
|
export type LanguageRulePack = (typeof LANGUAGE_RULE_PACKS)[number];
|
|
28
|
+
/**
|
|
29
|
+
* Per-track vocabulary hints the LLM applies when classifying a /cc prompt.
|
|
30
|
+
*
|
|
31
|
+
* Intentionally minimal:
|
|
32
|
+
* - `triggers`: additional substrings that push a prompt toward this track.
|
|
33
|
+
* - `veto`: substrings that forbid this track even if a trigger matches.
|
|
34
|
+
*
|
|
35
|
+
* Removed in v0.38.0:
|
|
36
|
+
* - `patterns` (regex): no runtime ever consumed them; kept authors honest
|
|
37
|
+
* about what cclaw actually enforces.
|
|
38
|
+
*/
|
|
28
39
|
export interface TrackHeuristicRule {
|
|
29
40
|
triggers?: string[];
|
|
30
|
-
patterns?: string[];
|
|
31
41
|
veto?: string[];
|
|
32
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Optional prompt-to-track overrides for /cc classification.
|
|
45
|
+
*
|
|
46
|
+
* Honesty note: this config is **advisory**. cclaw surfaces these lists in
|
|
47
|
+
* the /cc skill and contract prose so the LLM can apply them when picking a
|
|
48
|
+
* track. There is no Node-level routing layer that mechanically enforces the
|
|
49
|
+
* result — which is why we only ship `triggers`, `veto`, and `fallback`, not
|
|
50
|
+
* regex patterns or priority overrides.
|
|
51
|
+
*
|
|
52
|
+
* Removed in v0.38.0:
|
|
53
|
+
* - `priority`: track evaluation order is always `standard -> medium -> quick`
|
|
54
|
+
* (narrow-to-broad matching). Overriding it was never wired.
|
|
55
|
+
*/
|
|
33
56
|
export interface TrackHeuristicsConfig {
|
|
34
|
-
/** Track used when no trigger
|
|
57
|
+
/** Track used when no trigger matches. Defaults to `standard`. */
|
|
35
58
|
fallback?: FlowTrack;
|
|
36
|
-
/**
|
|
37
|
-
* Track evaluation order. First matching track wins.
|
|
38
|
-
* Example: ["standard", "medium", "quick"].
|
|
39
|
-
*/
|
|
40
|
-
priority?: FlowTrack[];
|
|
41
|
-
/** Per-track matching rules. */
|
|
59
|
+
/** Per-track vocabulary hints. */
|
|
42
60
|
tracks?: Partial<Record<FlowTrack, TrackHeuristicRule>>;
|
|
43
61
|
}
|
|
44
62
|
/**
|
|
@@ -92,7 +110,8 @@ export interface VibyConfig {
|
|
|
92
110
|
*/
|
|
93
111
|
languageRulePacks?: LanguageRulePack[];
|
|
94
112
|
/**
|
|
95
|
-
* Optional prompt-to-track
|
|
113
|
+
* Optional prompt-to-track vocabulary overrides for /cc classification.
|
|
114
|
+
* Advisory (surfaced in the /cc skill prose), not machine-enforced.
|
|
96
115
|
* If omitted, cclaw uses built-in defaults.
|
|
97
116
|
*/
|
|
98
117
|
trackHeuristics?: TrackHeuristicsConfig;
|