feat: prioritize user config, do not merge default tools

Changed loadClaudeCliTools() to only load tools explicitly defined
in user config. Previously, DEFAULT_TOOLS_CONFIG.tools was spread
before user tools, causing all default tools to be loaded even if
not present in user config.

User config now has complete control over which tools are loaded.
This commit is contained in:
catlog22
2026-01-22 23:37:42 +08:00
parent ac62bf70db
commit 2fb1d1243c
17 changed files with 79 additions and 79 deletions

View File

@@ -41,8 +41,8 @@ PROJECT CONTEXT:
## Execution Steps
1. **Read all progress**
- Load worker outputs from `.loop/{loopId}.workers/`
- Read progress files from `.loop/{loopId}.progress/`
- Load worker outputs from `.workflow/.loop/{loopId}.workers/`
- Read progress files from `.workflow/.loop/{loopId}.progress/`
- Consolidate findings
2. **Verify completeness**

View File

@@ -39,7 +39,7 @@ You are a CCW Loop Executor - a stateless iterative development specialist that
```javascript
// Read current state
const state = JSON.parse(Read('.loop/{loopId}.json'))
const state = JSON.parse(Read('.workflow/.loop/{loopId}.json'))
// Check control signals
if (state.status === 'paused') {
@@ -110,7 +110,7 @@ NEXT_ACTION_NEEDED: {action_name} | WAITING_INPUT | COMPLETED | PAUSED
```javascript
function updateState(loopId, skillStateUpdates) {
const state = JSON.parse(Read(`.loop/${loopId}.json`))
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
state.updated_at = getUtc8ISOString()
state.skill_state = {
...state.skill_state,
@@ -118,7 +118,7 @@ function updateState(loopId, skillStateUpdates) {
last_action: currentAction,
completed_actions: [...state.skill_state.completed_actions, currentAction]
}
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
}
```
@@ -136,7 +136,7 @@ function updateState(loopId, skillStateUpdates) {
5. Update state with skill_state
**Output**:
- `.loop/{loopId}.progress/develop.md` (initialized)
- `.workflow/.loop/{loopId}.progress/develop.md` (initialized)
- State: skill_state populated with tasks
### DEVELOP Action

View File

@@ -91,7 +91,7 @@ Spawn multiple workers simultaneously, batch wait for results.
## Session Structure
```
.loop/
.workflow/.loop/
+-- {loopId}.json # Master state
+-- {loopId}.workers/ # Worker outputs
| +-- init.output.json
@@ -184,7 +184,7 @@ Coordinator adapts to mode:
### 3. State Management
Unified state at `.loop/{loopId}.json`:
Unified state at `.workflow/.loop/{loopId}.json`:
- **API compatible**: Works with CCW API
- **Extension fields**: Skill-specific data in `skill_state`
- **Worker outputs**: Structured JSON for each action

View File

@@ -61,7 +61,7 @@ Coordinator -> spawn [develop, debug, validate] in parallel -> wait({ ids: all }
## Session Structure
```
.loop/
.workflow/.loop/
+-- {loopId}.json # Master state
+-- {loopId}.workers/ # Worker outputs
| +-- develop.output.json
@@ -242,9 +242,9 @@ function buildWorkerPrompt(action, loopId, state) {
- **Loop ID**: ${loopId}
- **Action**: ${action}
- **State File**: .loop/${loopId}.json
- **Output File**: .loop/${loopId}.workers/${action}.output.json
- **Progress File**: .loop/${loopId}.progress/${action}.md
- **State File**: .workflow/.loop/${loopId}.json
- **Output File**: .workflow/.loop/${loopId}.workers/${action}.output.json
- **Progress File**: .workflow/.loop/${loopId}.progress/${action}.md
## CURRENT STATE

View File

@@ -14,7 +14,7 @@ Read state -> Select mode -> Spawn workers -> Wait results -> Merge -> Update st
```javascript
function readState(loopId) {
const stateFile = `.loop/${loopId}.json`
const stateFile = `.workflow/.loop/${loopId}.json`
return fs.existsSync(stateFile)
? JSON.parse(Read(stateFile))
: null
@@ -252,6 +252,6 @@ function parseWorkerResult(output) {
## Best Practices
1. **Worker 生命周期**: spawn → wait → close不保留 worker
2. **结果持久化**: Worker 输出写入 `.loop/{loopId}.workers/`
2. **结果持久化**: Worker 输出写入 `.workflow/.loop/{loopId}.workers/`
3. **状态同步**: 每次 worker 完成后更新 state
4. **超时处理**: send_input 请求收敛,再超时则跳过

View File

@@ -56,7 +56,7 @@
## Worker Output Structure
Each worker writes to `.loop/{loopId}.workers/{action}.output.json`:
Each worker writes to `.workflow/.loop/{loopId}.workers/{action}.output.json`:
```json
{
@@ -81,7 +81,7 @@ Each worker writes to `.loop/{loopId}.workers/{action}.output.json`:
## Progress File Structure
Human-readable progress in `.loop/{loopId}.progress/{action}.md`:
Human-readable progress in `.workflow/.loop/{loopId}.progress/{action}.md`:
```markdown
# Develop Progress
@@ -165,7 +165,7 @@ When `mode === 'parallel'`:
## Directory Structure
```
.loop/
.workflow/.loop/
+-- loop-b-20260122-abc123.json # Master state
+-- loop-b-20260122-abc123.workers/
| +-- init.output.json

View File

@@ -68,7 +68,7 @@ Files are in `.codex/skills/ccw-loop/`:
```
1. Parse arguments (task or --loop-id)
2. Create/read state from .loop/{loopId}.json
2. Create/read state from .workflow/.loop/{loopId}.json
3. spawn_agent with ccw-loop-executor role
4. Main loop:
a. wait() for agent output
@@ -84,7 +84,7 @@ Files are in `.codex/skills/ccw-loop/`:
## Session Files
```
.loop/
.workflow/.loop/
+-- {loopId}.json # Master state (API + Skill)
+-- {loopId}.progress/
+-- develop.md # Development timeline
@@ -157,7 +157,7 @@ spawn_agent({
Works with CCW Dashboard Loop Monitor:
- Dashboard creates loop via API
- API triggers this skill with `--loop-id`
- Skill reads/writes `.loop/{loopId}.json`
- Skill reads/writes `.workflow/.loop/{loopId}.json`
- Dashboard polls state for real-time updates
### Control Signals

View File

@@ -27,8 +27,8 @@ Stateless iterative development loop using Codex subagent pattern. Supports deve
+-------------------------------------------------------------+
| loop-v2-routes.ts (Control Plane) |
| |
| State: .loop/{loopId}.json (MASTER) |
| Tasks: .loop/{loopId}.tasks.jsonl |
| State: .workflow/.loop/{loopId}.json (MASTER) |
| Tasks: .workflow/.loop/{loopId}.tasks.jsonl |
| |
| /start -> Trigger ccw-loop skill with --loop-id |
| /pause -> Set status='paused' (skill checks before action) |
@@ -42,8 +42,8 @@ Stateless iterative development loop using Codex subagent pattern. Supports deve
| |
| Codex Pattern: spawn_agent -> wait -> send_input -> close |
| |
| Reads/Writes: .loop/{loopId}.json (unified state) |
| Writes: .loop/{loopId}.progress/* (progress files) |
| Reads/Writes: .workflow/.loop/{loopId}.json (unified state) |
| Writes: .workflow/.loop/{loopId}.progress/* (progress files) |
| |
| BEFORE each action: |
| -> Check status: paused/stopped -> exit gracefully |
@@ -55,9 +55,9 @@ Stateless iterative development loop using Codex subagent pattern. Supports deve
## Key Design Principles (Codex Adaptation)
1. **Unified State**: API and Skill share `.loop/{loopId}.json` state file
1. **Unified State**: API and Skill share `.workflow/.loop/{loopId}.json` state file
2. **Control Signals**: Skill checks status field before each action (paused/stopped)
3. **File-Driven**: All progress documented in `.loop/{loopId}.progress/`
3. **File-Driven**: All progress documented in `.workflow/.loop/{loopId}.progress/`
4. **Resumable**: Continue any loop with `--loop-id`
5. **Dual Trigger**: Supports API trigger (`--loop-id`) and direct call (task description)
6. **Single Agent Deep Interaction**: Use send_input for multi-phase execution instead of multiple agents
@@ -100,7 +100,7 @@ Develop -> Debug -> Validate -> (if issues) -> Develop -> ...
## Session Structure (Unified Location)
```
.loop/
.workflow/.loop/
+-- {loopId}.json # Master state file (API + Skill shared)
+-- {loopId}.tasks.jsonl # Task list (API managed)
+-- {loopId}.progress/ # Skill progress files
@@ -129,8 +129,8 @@ const loopId = args['--loop-id'] || (() => {
return `loop-v2-${timestamp}-${random}`
})()
const loopFile = `.loop/${loopId}.json`
const progressDir = `.loop/${loopId}.progress`
const loopFile = `.workflow/.loop/${loopId}.json`
const progressDir = `.workflow/.loop/${loopId}.progress`
// Create progress directory
mkdir -p "${progressDir}"
@@ -144,14 +144,14 @@ mkdir -p "${progressDir}"
// Step 1: Read or create initial state
let state = null
if (existingLoopId) {
state = JSON.parse(Read(`.loop/${loopId}.json`))
state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
if (!state) {
console.error(`Loop not found: ${loopId}`)
return
}
} else {
state = createInitialState(loopId, taskDescription)
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
}
// Step 2: Create orchestrator agent (single agent handles all phases)
@@ -169,7 +169,7 @@ const agent = spawn_agent({
## LOOP CONTEXT
- **Loop ID**: ${loopId}
- **State File**: .loop/${loopId}.json
- **State File**: .workflow/.loop/${loopId}.json
- **Progress Dir**: ${progressDir}
- **Mode**: ${mode} // 'interactive' or 'auto'
@@ -186,7 +186,7 @@ ${taskDescription}
You are executing CCW Loop orchestrator. Your job:
1. **Check Control Signals**
- Read .loop/${loopId}.json
- Read .workflow/.loop/${loopId}.json
- If status === 'paused' -> Output "PAUSED" and stop
- If status === 'failed' -> Output "STOPPED" and stop
- If status === 'running' -> Continue
@@ -200,7 +200,7 @@ You are executing CCW Loop orchestrator. Your job:
3. **Execute Action**
- Follow action instructions from ~/.codex/skills/ccw-loop/phases/actions/
- Update progress files in ${progressDir}/
- Update state in .loop/${loopId}.json
- Update state in .workflow/.loop/${loopId}.json
4. **Output Format**
\`\`\`
@@ -283,10 +283,10 @@ Continue with: ${actionResult.next_action}
}
// Update iteration count in state
const currentState = JSON.parse(Read(`.loop/${loopId}.json`))
const currentState = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
currentState.current_iteration = iteration
currentState.updated_at = getUtc8ISOString()
Write(`.loop/${loopId}.json`, JSON.stringify(currentState, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(currentState, null, 2))
}
// Step 4: Cleanup

View File

@@ -20,7 +20,7 @@ Complete CCW Loop session and generate summary report.
### Step 1: Verify Control Signals
```javascript
const state = JSON.parse(Read(`.loop/${loopId}.json`))
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
if (state.status !== 'running') {
return {
@@ -174,7 +174,7 @@ state.updated_at = timestamp
state.skill_state.last_action = 'COMPLETE'
state.skill_state.summary = stats
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
```
## Output Format
@@ -190,8 +190,8 @@ ACTION_RESULT:
}
FILES_UPDATED:
- .loop/{loopId}.json: Status set to completed
- .loop/{loopId}.progress/summary.md: Summary report generated
- .workflow/.loop/{loopId}.json: Status set to completed
- .workflow/.loop/{loopId}.progress/summary.md: Summary report generated
NEXT_ACTION_NEEDED: COMPLETED
```

View File

@@ -233,7 +233,7 @@ if (confirmedHypothesis) {
state.skill_state.last_action = 'DEBUG'
state.updated_at = timestamp
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
```
## Output Format
@@ -249,8 +249,8 @@ ACTION_RESULT:
}
FILES_UPDATED:
- .loop/{loopId}.progress/debug.md: Understanding updated
- .loop/{loopId}.progress/hypotheses.json: Hypotheses updated
- .workflow/.loop/{loopId}.progress/debug.md: Understanding updated
- .workflow/.loop/{loopId}.progress/hypotheses.json: Hypotheses updated
- [Source files]: Instrumentation added
NEXT_ACTION_NEEDED: {DEBUG | VALIDATE | DEVELOP | MENU}

View File

@@ -20,7 +20,7 @@ Execute development task and record progress to develop.md.
### Step 1: Verify Control Signals
```javascript
const state = JSON.parse(Read(`.loop/${loopId}.json`))
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
if (state.status !== 'running') {
return {
@@ -133,7 +133,7 @@ state.skill_state.last_action = 'DEVELOP'
state.skill_state.completed_actions.push('DEVELOP')
state.updated_at = timestamp
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
```
## Output Format
@@ -149,9 +149,9 @@ ACTION_RESULT:
}
FILES_UPDATED:
- .loop/{loopId}.json: Task status updated
- .loop/{loopId}.progress/develop.md: Progress entry added
- .loop/{loopId}.progress/changes.log: Change entry added
- .workflow/.loop/{loopId}.json: Task status updated
- .workflow/.loop/{loopId}.progress/develop.md: Progress entry added
- .workflow/.loop/{loopId}.progress/changes.log: Change entry added
NEXT_ACTION_NEEDED: {DEVELOP | DEBUG | VALIDATE | MENU}
```

View File

@@ -19,7 +19,7 @@ Initialize CCW Loop session, create directory structure and initial state.
### Step 1: Verify Control Signals
```javascript
const state = JSON.parse(Read(`.loop/${loopId}.json`))
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
if (state.status !== 'running') {
return {
@@ -34,7 +34,7 @@ if (state.status !== 'running') {
### Step 2: Create Directory Structure
```javascript
const progressDir = `.loop/${loopId}.progress`
const progressDir = `.workflow/.loop/${loopId}.progress`
// Directories created by orchestrator, verify they exist
// mkdir -p ${progressDir}
@@ -131,7 +131,7 @@ const skillState = {
state.skill_state = skillState
state.updated_at = getUtc8ISOString()
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
```
## Output Format
@@ -143,8 +143,8 @@ ACTION_RESULT:
- message: Session initialized with {N} development tasks
FILES_UPDATED:
- .loop/{loopId}.json: skill_state initialized
- .loop/{loopId}.progress/develop.md: Progress document created
- .workflow/.loop/{loopId}.json: skill_state initialized
- .workflow/.loop/{loopId}.progress/develop.md: Progress document created
NEXT_ACTION_NEEDED: {DEVELOP (auto) | MENU (interactive)}
```

View File

@@ -20,7 +20,7 @@ Display interactive action menu for user selection.
### Step 1: Verify Control Signals
```javascript
const state = JSON.parse(Read(`.loop/${loopId}.json`))
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
if (state.status !== 'running') {
return {
@@ -172,7 +172,7 @@ If user selects "exit":
// Save current state
state.status = 'user_exit'
state.updated_at = getUtc8ISOString()
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
return {
action: 'MENU',

View File

@@ -21,7 +21,7 @@ Run tests and verify implementation, record results to validate.md.
### Step 1: Verify Control Signals
```javascript
const state = JSON.parse(Read(`.loop/${loopId}.json`))
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
if (state.status !== 'running') {
return {
@@ -174,7 +174,7 @@ state.skill_state.validate.last_run_at = timestamp
state.skill_state.last_action = 'VALIDATE'
state.updated_at = timestamp
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
```
## Output Format
@@ -191,9 +191,9 @@ ACTION_RESULT:
}
FILES_UPDATED:
- .loop/{loopId}.progress/validate.md: Validation report created
- .loop/{loopId}.progress/test-results.json: Test results saved
- .loop/{loopId}.progress/coverage.json: Coverage data saved (if available)
- .workflow/.loop/{loopId}.progress/validate.md: Validation report created
- .workflow/.loop/{loopId}.progress/test-results.json: Test results saved
- .workflow/.loop/{loopId}.progress/coverage.json: Coverage data saved (if available)
NEXT_ACTION_NEEDED: {COMPLETE | DEBUG | DEVELOP | MENU}
```

View File

@@ -45,7 +45,7 @@ const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOSt
* @param loopId - Loop ID (e.g., "loop-v2-20260122-abc123")
*/
function readLoopState(loopId) {
const stateFile = `.loop/${loopId}.json`
const stateFile = `.workflow/.loop/${loopId}.json`
if (!fs.existsSync(stateFile)) {
return null
@@ -63,7 +63,7 @@ function readLoopState(loopId) {
* Create new loop state (only for direct calls, API triggers have existing state)
*/
function createLoopState(loopId, taskDescription) {
const stateFile = `.loop/${loopId}.json`
const stateFile = `.workflow/.loop/${loopId}.json`
const now = getUtc8ISOString()
const state = {
@@ -83,7 +83,7 @@ function createLoopState(loopId, taskDescription) {
// Ensure directories exist
mkdir -p ".loop"
mkdir -p ".loop/${loopId}.progress"
mkdir -p ".workflow/.loop/${loopId}.progress"
Write(stateFile, JSON.stringify(state, null, 2))
return state
@@ -137,7 +137,7 @@ async function runOrchestrator(options = {}) {
return { status: 'error', message: 'Missing loopId or task' }
}
const progressDir = `.loop/${loopId}.progress`
const progressDir = `.workflow/.loop/${loopId}.progress`
// 2. Create executor agent (single agent for entire loop)
const agent = spawn_agent({
@@ -154,7 +154,7 @@ async function runOrchestrator(options = {}) {
## LOOP CONTEXT
- **Loop ID**: ${loopId}
- **State File**: .loop/${loopId}.json
- **State File**: .workflow/.loop/${loopId}.json
- **Progress Dir**: ${progressDir}
- **Mode**: ${mode}
@@ -213,7 +213,7 @@ Execution timeout reached. Please:
state = readLoopState(loopId)
state.current_iteration = iteration
state.updated_at = getUtc8ISOString()
Write(`.loop/${loopId}.json`, JSON.stringify(state, null, 2))
Write(`.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
// Handle different outcomes
switch (actionResult.next_action) {

View File

@@ -4,7 +4,7 @@ CCW Loop state structure definition for Codex subagent pattern.
## State File
**Location**: `.loop/{loopId}.json` (unified location, API + Skill shared)
**Location**: `.workflow/.loop/{loopId}.json` (unified location, API + Skill shared)
## Structure Definition
@@ -208,7 +208,7 @@ Agent checks control signals at start of every action:
* @returns { continue: boolean, action: 'pause_exit' | 'stop_exit' | 'continue' }
*/
function checkControlSignals(loopId) {
const state = JSON.parse(Read(`.loop/${loopId}.json`))
const state = JSON.parse(Read(`.workflow/.loop/${loopId}.json`))
switch (state.status) {
case 'paused':
@@ -338,17 +338,17 @@ State-to-file mapping:
| State Field | Sync File | Sync Timing |
|-------------|-----------|-------------|
| Entire LoopState | `.loop/{loopId}.json` | Every state change (master) |
| `skill_state.develop` | `.loop/{loopId}.progress/develop.md` | After each dev operation |
| `skill_state.debug` | `.loop/{loopId}.progress/debug.md` | After each debug operation |
| `skill_state.validate` | `.loop/{loopId}.progress/validate.md` | After each validation |
| Code changes log | `.loop/{loopId}.progress/changes.log` | Each file modification (NDJSON) |
| Debug log | `.loop/{loopId}.progress/debug.log` | Each debug log (NDJSON) |
| Entire LoopState | `.workflow/.loop/{loopId}.json` | Every state change (master) |
| `skill_state.develop` | `.workflow/.loop/{loopId}.progress/develop.md` | After each dev operation |
| `skill_state.debug` | `.workflow/.loop/{loopId}.progress/debug.md` | After each debug operation |
| `skill_state.validate` | `.workflow/.loop/{loopId}.progress/validate.md` | After each validation |
| Code changes log | `.workflow/.loop/{loopId}.progress/changes.log` | Each file modification (NDJSON) |
| Debug log | `.workflow/.loop/{loopId}.progress/debug.log` | Each debug log (NDJSON) |
### File Structure
```
.loop/
.workflow/.loop/
+-- loop-v2-20260122-abc123.json # Master state file (API + Skill)
+-- loop-v2-20260122-abc123.tasks.jsonl # Task list (API managed)
+-- loop-v2-20260122-abc123.progress/ # Skill progress files
@@ -366,7 +366,7 @@ If master state file corrupted, rebuild skill_state from progress files:
```javascript
function rebuildSkillStateFromProgress(loopId) {
const progressDir = `.loop/${loopId}.progress`
const progressDir = `.workflow/.loop/${loopId}.progress`
// Parse progress files to rebuild state
const skill_state = {
@@ -381,7 +381,7 @@ function rebuildSkillStateFromProgress(loopId) {
## Codex Pattern Notes
1. **Agent reads state**: Agent reads `.loop/{loopId}.json` at action start
1. **Agent reads state**: Agent reads `.workflow/.loop/{loopId}.json` at action start
2. **Agent writes state**: Agent updates state after action completion
3. **Orchestrator tracks iterations**: Main loop tracks `current_iteration`
4. **Single agent context**: All state updates in same agent conversation via send_input

View File

@@ -439,9 +439,9 @@ export function loadClaudeCliTools(projectDir: string): ClaudeCliToolsConfig & {
const migrated = migrateConfig(parsed, projectDir);
const needsSave = migrated.version !== parsed.version;
// Merge tools with defaults and ensure required fields exist
// Load user-configured tools only (defaults NOT merged)
const mergedTools: Record<string, ClaudeCliTool> = {};
for (const [key, tool] of Object.entries({ ...DEFAULT_TOOLS_CONFIG.tools, ...(migrated.tools || {}) })) {
for (const [key, tool] of Object.entries(migrated.tools || {})) {
mergedTools[key] = {
...ensureToolTags(tool),
type: tool.type ?? 'builtin',