claude-code-workflow 7.2.29 → 7.2.30
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/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json +2 -2
- package/.ccw/workflows/cli-templates/schemas/task-schema.json +14 -7
- package/.claude/agents/action-planning-agent.md +7 -4
- package/.claude/agents/cli-explore-agent.md +77 -63
- package/.claude/agents/cli-lite-planning-agent.md +11 -10
- package/.claude/agents/issue-plan-agent.md +421 -426
- package/.claude/commands/workflow/spec/setup.md +1 -1
- package/.claude/skills/ccw-chain/SKILL.md +119 -0
- package/.claude/skills/ccw-chain/chains/ccw-cycle.json +21 -0
- package/.claude/skills/ccw-chain/chains/ccw-exploration.json +47 -0
- package/.claude/skills/ccw-chain/chains/ccw-issue.json +33 -0
- package/.claude/skills/ccw-chain/chains/ccw-lightweight.json +57 -0
- package/.claude/skills/ccw-chain/chains/ccw-main.json +52 -0
- package/.claude/skills/ccw-chain/chains/ccw-standard.json +39 -0
- package/.claude/skills/ccw-chain/chains/ccw-team.json +10 -0
- package/.claude/skills/ccw-chain/chains/ccw-with-file.json +31 -0
- package/.claude/skills/ccw-chain/phases/analyze-with-file.md +788 -0
- package/.claude/skills/ccw-chain/phases/brainstorm/SKILL.md +408 -0
- package/.claude/skills/ccw-chain/phases/brainstorm/phases/01-mode-routing.md +207 -0
- package/.claude/skills/ccw-chain/phases/brainstorm/phases/02-artifacts.md +567 -0
- package/.claude/skills/ccw-chain/phases/brainstorm/phases/03-role-analysis.md +748 -0
- package/.claude/skills/ccw-chain/phases/brainstorm/phases/04-synthesis.md +827 -0
- package/.claude/skills/ccw-chain/phases/brainstorm-with-file.md +482 -0
- package/.claude/skills/ccw-chain/phases/collaborative-plan-with-file.md +639 -0
- package/.claude/skills/ccw-chain/phases/debug-with-file.md +656 -0
- package/.claude/skills/ccw-chain/phases/integration-test-cycle.md +936 -0
- package/.claude/skills/ccw-chain/phases/issue-convert-to-plan.md +720 -0
- package/.claude/skills/ccw-chain/phases/issue-discover.md +483 -0
- package/.claude/skills/ccw-chain/phases/issue-execute.md +629 -0
- package/.claude/skills/ccw-chain/phases/issue-from-brainstorm.md +382 -0
- package/.claude/skills/ccw-chain/phases/issue-plan.md +343 -0
- package/.claude/skills/ccw-chain/phases/issue-queue.md +464 -0
- package/.claude/skills/ccw-chain/phases/refactor-cycle.md +852 -0
- package/.claude/skills/ccw-chain/phases/review-cycle/SKILL.md +132 -0
- package/.claude/skills/ccw-chain/phases/review-cycle/phases/review-fix.md +760 -0
- package/.claude/skills/ccw-chain/phases/review-cycle/phases/review-module.md +764 -0
- package/.claude/skills/ccw-chain/phases/review-cycle/phases/review-session.md +775 -0
- package/.claude/skills/ccw-chain/phases/roadmap-with-file.md +544 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/SKILL.md +338 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/phases/01-5-requirement-clarification.md +404 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/phases/01-discovery.md +257 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/phases/02-product-brief.md +274 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/phases/03-requirements.md +184 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/phases/04-architecture.md +248 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/phases/05-epics-stories.md +178 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/phases/06-5-auto-fix.md +144 -0
- package/.claude/skills/ccw-chain/phases/spec-generator/phases/06-readiness-check.md +480 -0
- package/.claude/skills/ccw-chain/phases/team-planex.md +123 -0
- package/.claude/skills/ccw-chain/phases/ui-design-explore-auto.md +678 -0
- package/.claude/skills/ccw-chain/phases/unified-execute-with-file.md +870 -0
- package/.claude/skills/ccw-chain/phases/workflow-execute/SKILL.md +625 -0
- package/.claude/skills/ccw-chain/phases/workflow-execute/phases/06-review.md +215 -0
- package/.claude/skills/ccw-chain/phases/workflow-lite-plan.md +616 -0
- package/.claude/skills/ccw-chain/phases/workflow-multi-cli-plan.md +424 -0
- package/.claude/skills/ccw-chain/phases/workflow-plan/SKILL.md +466 -0
- package/.claude/skills/ccw-chain/phases/workflow-plan/phases/01-session-discovery.md +99 -0
- package/.claude/skills/ccw-chain/phases/workflow-plan/phases/02-context-gathering.md +338 -0
- package/.claude/skills/ccw-chain/phases/workflow-plan/phases/03-conflict-resolution.md +422 -0
- package/.claude/skills/ccw-chain/phases/workflow-plan/phases/04-task-generation.md +440 -0
- package/.claude/skills/ccw-chain/phases/workflow-plan/phases/05-plan-verify.md +395 -0
- package/.claude/skills/ccw-chain/phases/workflow-plan/phases/06-replan.md +594 -0
- package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/SKILL.md +527 -0
- package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/01-session-discovery.md +57 -0
- package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/02-context-gathering.md +407 -0
- package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/03-test-coverage-analysis.md +172 -0
- package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/04-conflict-resolution.md +426 -0
- package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/05-tdd-task-generation.md +473 -0
- package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/06-tdd-structure-validation.md +189 -0
- package/.claude/skills/ccw-chain/phases/workflow-tdd-plan/phases/07-tdd-verify.md +635 -0
- package/.claude/skills/ccw-chain/phases/workflow-test-fix/SKILL.md +482 -0
- package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/01-session-start.md +60 -0
- package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/02-test-context-gather.md +493 -0
- package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/03-test-concept-enhanced.md +150 -0
- package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/04-test-task-generate.md +346 -0
- package/.claude/skills/ccw-chain/phases/workflow-test-fix/phases/05-test-cycle-execute.md +538 -0
- package/.claude/skills/ccw-chain/specs/auto-mode.md +47 -0
- package/.claude/skills/ccw-chain/specs/intent-patterns.md +60 -0
- package/.claude/skills/chain-loader/SKILL.md +78 -0
- package/.claude/skills/chain-loader/phases/01-analyze-skill.md +53 -0
- package/.claude/skills/chain-loader/phases/02-design-graph.md +73 -0
- package/.claude/skills/chain-loader/phases/03-generate-validate.md +75 -0
- package/.claude/skills/chain-loader/specs/chain-schema.md +99 -0
- package/.claude/skills/chain-loader/specs/design-patterns.md +99 -0
- package/.claude/skills/chain-loader/templates/chain-json.md +63 -0
- package/.claude/skills/review-cycle/phases/review-module.md +764 -764
- package/.claude/skills/review-cycle/phases/review-session.md +775 -775
- package/.claude/skills/workflow-multi-cli-plan/SKILL.md +2 -2
- package/.claude/skills/workflow-plan/phases/03-conflict-resolution.md +422 -422
- package/.claude/skills/workflow-plan/phases/05-plan-verify.md +395 -395
- package/.claude/skills/workflow-tdd-plan/phases/02-context-gathering.md +407 -407
- package/.claude/skills/workflow-tdd-plan/phases/04-conflict-resolution.md +426 -426
- package/.claude/skills/workflow-test-fix/phases/02-test-context-gather.md +493 -493
- package/README.md +14 -0
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.js +0 -23
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
- package/ccw/dist/tools/chain-loader.d.ts +10 -0
- package/ccw/dist/tools/chain-loader.d.ts.map +1 -0
- package/ccw/dist/tools/chain-loader.js +642 -0
- package/ccw/dist/tools/chain-loader.js.map +1 -0
- package/ccw/dist/tools/index.d.ts.map +1 -1
- package/ccw/dist/tools/index.js +2 -0
- package/ccw/dist/tools/index.js.map +1 -1
- package/ccw/dist/tools/json-builder.js +20 -0
- package/ccw/dist/tools/json-builder.js.map +1 -1
- package/ccw/dist/types/chain-types.d.ts +72 -0
- package/ccw/dist/types/chain-types.d.ts.map +1 -0
- package/ccw/dist/types/chain-types.js +5 -0
- package/ccw/dist/types/chain-types.js.map +1 -0
- package/ccw/scripts/prepublish-clean.mjs +0 -1
- package/package.json +1 -3
- package/ccw-litellm/README.md +0 -180
- package/ccw-litellm/pyproject.toml +0 -35
- package/ccw-litellm/src/ccw_litellm/__init__.py +0 -47
- package/ccw-litellm/src/ccw_litellm/cli.py +0 -108
- package/ccw-litellm/src/ccw_litellm/clients/__init__.py +0 -12
- package/ccw-litellm/src/ccw_litellm/clients/litellm_embedder.py +0 -270
- package/ccw-litellm/src/ccw_litellm/clients/litellm_llm.py +0 -198
- package/ccw-litellm/src/ccw_litellm/config/__init__.py +0 -22
- package/ccw-litellm/src/ccw_litellm/config/loader.py +0 -343
- package/ccw-litellm/src/ccw_litellm/config/models.py +0 -162
- package/ccw-litellm/src/ccw_litellm/interfaces/__init__.py +0 -14
- package/ccw-litellm/src/ccw_litellm/interfaces/embedder.py +0 -52
- package/ccw-litellm/src/ccw_litellm/interfaces/llm.py +0 -45
|
@@ -0,0 +1,642 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain Loader Tool - Progressive skill chain execution with step-by-step
|
|
3
|
+
* content delivery and LLM-driven decision routing.
|
|
4
|
+
*
|
|
5
|
+
* Commands: list, start, next, done, status, content, complete
|
|
6
|
+
*/
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, } from 'fs';
|
|
9
|
+
import { resolve, join, dirname } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
// ─── Params ──────────────────────────────────────────────────
|
|
12
|
+
const ParamsSchema = z.object({
|
|
13
|
+
cmd: z.enum(['list', 'start', 'next', 'done', 'status', 'content', 'complete']),
|
|
14
|
+
skill: z.string().optional(),
|
|
15
|
+
chain: z.string().optional(),
|
|
16
|
+
session_id: z.string().optional(),
|
|
17
|
+
choice: z.number().optional(),
|
|
18
|
+
});
|
|
19
|
+
// ─── Tool Schema ─────────────────────────────────────────────
|
|
20
|
+
export const schema = {
|
|
21
|
+
name: 'chain_loader',
|
|
22
|
+
description: `Progressive skill chain loader. Auto-detects skill from cwd (cd to skill dir first) or pass skill explicitly.
|
|
23
|
+
list: List chains. Params: skill? (string, auto-detect from cwd)
|
|
24
|
+
start: Start chain session. Params: chain (string), skill? (string, auto-detect from cwd)
|
|
25
|
+
next: Read current node (idempotent if active, advance if completed). Params: session_id (string)
|
|
26
|
+
done: Complete current node and advance. Params: session_id (string), choice? (number, 1-based for decisions)
|
|
27
|
+
status: Query session state. Params: session_id (string)
|
|
28
|
+
content: Get all loaded content. Params: session_id (string)
|
|
29
|
+
complete: Mark session completed. Params: session_id (string)`,
|
|
30
|
+
inputSchema: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
properties: {
|
|
33
|
+
cmd: { type: 'string', description: 'Command: list|start|next|done|status|content|complete' },
|
|
34
|
+
skill: { type: 'string', description: 'Skill name (optional, auto-detected from cwd if omitted)' },
|
|
35
|
+
chain: { type: 'string', description: 'Chain name (for start)' },
|
|
36
|
+
session_id: { type: 'string', description: 'Session ID (for next/done/status/content/complete)' },
|
|
37
|
+
choice: { type: 'number', description: 'Decision choice index (1-based, for done on decision nodes)' },
|
|
38
|
+
},
|
|
39
|
+
required: ['cmd'],
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
// ─── Constants ───────────────────────────────────────────────
|
|
43
|
+
const SESSIONS_BASE = '.workflow/.chain-sessions';
|
|
44
|
+
// ─── Handler ─────────────────────────────────────────────────
|
|
45
|
+
export async function handler(params) {
|
|
46
|
+
const parsed = ParamsSchema.safeParse(params);
|
|
47
|
+
if (!parsed.success) {
|
|
48
|
+
return { success: false, error: `Invalid params: ${parsed.error.message}` };
|
|
49
|
+
}
|
|
50
|
+
const p = parsed.data;
|
|
51
|
+
try {
|
|
52
|
+
switch (p.cmd) {
|
|
53
|
+
case 'list': return cmdList(p);
|
|
54
|
+
case 'start': return cmdStart(p);
|
|
55
|
+
case 'next': return cmdNext(p);
|
|
56
|
+
case 'done': return cmdDone(p);
|
|
57
|
+
case 'status': return cmdStatus(p);
|
|
58
|
+
case 'content': return cmdContent(p);
|
|
59
|
+
case 'complete': return cmdComplete(p);
|
|
60
|
+
default:
|
|
61
|
+
return { success: false, error: `Unknown command: ${p.cmd}` };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
return { success: false, error: err.message };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ─── list ────────────────────────────────────────────────────
|
|
69
|
+
function cmdList(p) {
|
|
70
|
+
// If no skill given, try cwd auto-detect first; fall back to scanning all
|
|
71
|
+
const cwdSkill = !p.skill ? detectSkillFromCwd() : null;
|
|
72
|
+
const skillDirs = cwdSkill ? [cwdSkill] : discoverSkillDirs(p.skill);
|
|
73
|
+
const results = [];
|
|
74
|
+
for (const { skillName, skillPath } of skillDirs) {
|
|
75
|
+
const chainsDir = join(skillPath, 'chains');
|
|
76
|
+
if (!existsSync(chainsDir))
|
|
77
|
+
continue;
|
|
78
|
+
const files = readdirSync(chainsDir).filter(f => f.endsWith('.json'));
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
try {
|
|
81
|
+
const chain = loadChainJson(join(chainsDir, file));
|
|
82
|
+
results.push({
|
|
83
|
+
skill: skillName,
|
|
84
|
+
chain_id: chain.chain_id,
|
|
85
|
+
name: chain.name,
|
|
86
|
+
description: chain.description,
|
|
87
|
+
node_count: Object.keys(chain.nodes).length,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
catch { /* skip invalid chain files */ }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return { success: true, result: { chains: results, total: results.length } };
|
|
94
|
+
}
|
|
95
|
+
// ─── start ───────────────────────────────────────────────────
|
|
96
|
+
function cmdStart(p) {
|
|
97
|
+
if (!p.chain)
|
|
98
|
+
return { success: false, error: 'chain is required for start' };
|
|
99
|
+
// Resolve skill: explicit param → cwd auto-detect → error
|
|
100
|
+
const resolved = resolveSkill(p.skill);
|
|
101
|
+
if (!resolved) {
|
|
102
|
+
return { success: false, error: p.skill
|
|
103
|
+
? `Skill not found: ${p.skill}`
|
|
104
|
+
: 'Cannot auto-detect skill from cwd. Pass skill explicitly or cd to a skill directory (must contain chains/ dir).' };
|
|
105
|
+
}
|
|
106
|
+
const { skillName, skillPath } = resolved;
|
|
107
|
+
const chainPath = join(skillPath, 'chains', `${p.chain}.json`);
|
|
108
|
+
if (!existsSync(chainPath)) {
|
|
109
|
+
return { success: false, error: `Chain not found: ${p.chain} in skill ${skillName}` };
|
|
110
|
+
}
|
|
111
|
+
const chain = loadChainJson(chainPath);
|
|
112
|
+
if (!chain.nodes[chain.entry]) {
|
|
113
|
+
return { success: false, error: `Entry node "${chain.entry}" not found in chain` };
|
|
114
|
+
}
|
|
115
|
+
// Generate session ID
|
|
116
|
+
const now = new Date();
|
|
117
|
+
const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '');
|
|
118
|
+
const sessionId = `CL-${skillName}-${timeStr}`;
|
|
119
|
+
// Create session
|
|
120
|
+
const session = {
|
|
121
|
+
session_id: sessionId,
|
|
122
|
+
skill_name: skillName,
|
|
123
|
+
skill_path: skillPath,
|
|
124
|
+
status: 'active',
|
|
125
|
+
current_chain: chain.chain_id,
|
|
126
|
+
current_node: chain.entry,
|
|
127
|
+
node_status: 'active',
|
|
128
|
+
chain_stack: [],
|
|
129
|
+
history: [],
|
|
130
|
+
loaded_content: [],
|
|
131
|
+
started_at: now.toISOString(),
|
|
132
|
+
updated_at: now.toISOString(),
|
|
133
|
+
};
|
|
134
|
+
// Load entry node content
|
|
135
|
+
const entryNode = chain.nodes[chain.entry];
|
|
136
|
+
const nodeContent = loadNodeContent(entryNode, skillPath);
|
|
137
|
+
// Record in history
|
|
138
|
+
session.history.push({
|
|
139
|
+
node_id: chain.entry,
|
|
140
|
+
chain_id: chain.chain_id,
|
|
141
|
+
node_type: entryNode.type,
|
|
142
|
+
node_status: 'active',
|
|
143
|
+
timestamp: now.toISOString(),
|
|
144
|
+
});
|
|
145
|
+
// Add to loaded content
|
|
146
|
+
if (nodeContent) {
|
|
147
|
+
session.loaded_content.push({
|
|
148
|
+
node_id: chain.entry,
|
|
149
|
+
chain_id: chain.chain_id,
|
|
150
|
+
content: nodeContent,
|
|
151
|
+
loaded_at: now.toISOString(),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
saveSession(session);
|
|
155
|
+
return {
|
|
156
|
+
success: true,
|
|
157
|
+
result: {
|
|
158
|
+
session_id: sessionId,
|
|
159
|
+
current_chain: chain.chain_id,
|
|
160
|
+
current_node: chain.entry,
|
|
161
|
+
node_status: 'active',
|
|
162
|
+
...formatNodeOutput(entryNode, nodeContent),
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// ─── next ────────────────────────────────────────────────────
|
|
167
|
+
function cmdNext(p) {
|
|
168
|
+
if (!p.session_id)
|
|
169
|
+
return { success: false, error: 'session_id is required for next' };
|
|
170
|
+
const session = loadSession(p.session_id);
|
|
171
|
+
if (!session)
|
|
172
|
+
return { success: false, error: `Session not found: ${p.session_id}` };
|
|
173
|
+
if (session.status === 'completed') {
|
|
174
|
+
return { success: true, result: { status: 'completed', message: 'Session already completed.' } };
|
|
175
|
+
}
|
|
176
|
+
// If current node is active → return current content (idempotent)
|
|
177
|
+
if (session.node_status === 'active') {
|
|
178
|
+
const chain = loadChainFromSession(session);
|
|
179
|
+
const node = chain.nodes[session.current_node];
|
|
180
|
+
const content = getLastLoadedContent(session, session.current_node);
|
|
181
|
+
return {
|
|
182
|
+
success: true,
|
|
183
|
+
result: {
|
|
184
|
+
session_id: session.session_id,
|
|
185
|
+
current_chain: session.current_chain,
|
|
186
|
+
current_node: session.current_node,
|
|
187
|
+
node_status: 'active',
|
|
188
|
+
...formatNodeOutput(node, content),
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
// If current node is completed → auto-advance (same as done without choice)
|
|
193
|
+
return advanceToNext(session, undefined);
|
|
194
|
+
}
|
|
195
|
+
// ─── done ────────────────────────────────────────────────────
|
|
196
|
+
function cmdDone(p) {
|
|
197
|
+
if (!p.session_id)
|
|
198
|
+
return { success: false, error: 'session_id is required for done' };
|
|
199
|
+
const session = loadSession(p.session_id);
|
|
200
|
+
if (!session)
|
|
201
|
+
return { success: false, error: `Session not found: ${p.session_id}` };
|
|
202
|
+
if (session.status === 'completed') {
|
|
203
|
+
return { success: true, result: { status: 'completed', message: 'Session already completed.' } };
|
|
204
|
+
}
|
|
205
|
+
// Mark current node as completed
|
|
206
|
+
session.node_status = 'completed';
|
|
207
|
+
session.history.push({
|
|
208
|
+
node_id: session.current_node,
|
|
209
|
+
chain_id: session.current_chain,
|
|
210
|
+
node_type: getNodeType(session),
|
|
211
|
+
node_status: 'completed',
|
|
212
|
+
timestamp: new Date().toISOString(),
|
|
213
|
+
choice: p.choice,
|
|
214
|
+
});
|
|
215
|
+
return advanceToNext(session, p.choice);
|
|
216
|
+
}
|
|
217
|
+
// ─── status ──────────────────────────────────────────────────
|
|
218
|
+
function cmdStatus(p) {
|
|
219
|
+
if (!p.session_id)
|
|
220
|
+
return { success: false, error: 'session_id is required for status' };
|
|
221
|
+
const session = loadSession(p.session_id);
|
|
222
|
+
if (!session)
|
|
223
|
+
return { success: false, error: `Session not found: ${p.session_id}` };
|
|
224
|
+
return {
|
|
225
|
+
success: true,
|
|
226
|
+
result: {
|
|
227
|
+
session_id: session.session_id,
|
|
228
|
+
skill_name: session.skill_name,
|
|
229
|
+
status: session.status,
|
|
230
|
+
current_chain: session.current_chain,
|
|
231
|
+
current_node: session.current_node,
|
|
232
|
+
node_status: session.node_status,
|
|
233
|
+
chain_stack_depth: session.chain_stack.length,
|
|
234
|
+
history_length: session.history.length,
|
|
235
|
+
loaded_count: session.loaded_content.length,
|
|
236
|
+
started_at: session.started_at,
|
|
237
|
+
updated_at: session.updated_at,
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
// ─── content ─────────────────────────────────────────────────
|
|
242
|
+
function cmdContent(p) {
|
|
243
|
+
if (!p.session_id)
|
|
244
|
+
return { success: false, error: 'session_id is required for content' };
|
|
245
|
+
const session = loadSession(p.session_id);
|
|
246
|
+
if (!session)
|
|
247
|
+
return { success: false, error: `Session not found: ${p.session_id}` };
|
|
248
|
+
return {
|
|
249
|
+
success: true,
|
|
250
|
+
result: {
|
|
251
|
+
session_id: session.session_id,
|
|
252
|
+
entries: session.loaded_content,
|
|
253
|
+
total: session.loaded_content.length,
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
// ─── complete ────────────────────────────────────────────────
|
|
258
|
+
function cmdComplete(p) {
|
|
259
|
+
if (!p.session_id)
|
|
260
|
+
return { success: false, error: 'session_id is required for complete' };
|
|
261
|
+
const session = loadSession(p.session_id);
|
|
262
|
+
if (!session)
|
|
263
|
+
return { success: false, error: `Session not found: ${p.session_id}` };
|
|
264
|
+
session.status = 'completed';
|
|
265
|
+
session.updated_at = new Date().toISOString();
|
|
266
|
+
saveSession(session);
|
|
267
|
+
const stepCount = session.history.filter(h => h.node_type === 'step' && h.node_status === 'completed').length;
|
|
268
|
+
const decisionCount = session.history.filter(h => h.node_type === 'decision' && h.node_status === 'completed').length;
|
|
269
|
+
return {
|
|
270
|
+
success: true,
|
|
271
|
+
result: {
|
|
272
|
+
status: 'completed',
|
|
273
|
+
session_id: session.session_id,
|
|
274
|
+
summary: `Chain completed. ${stepCount} steps, ${decisionCount} decisions.`,
|
|
275
|
+
loaded_content_count: session.loaded_content.length,
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
// ─── Core Logic ──────────────────────────────────────────────
|
|
280
|
+
function advanceToNext(session, choice) {
|
|
281
|
+
const chain = loadChainFromSession(session);
|
|
282
|
+
const currentNode = chain.nodes[session.current_node];
|
|
283
|
+
if (!currentNode) {
|
|
284
|
+
return { success: false, error: `Node "${session.current_node}" not found in chain "${session.current_chain}"` };
|
|
285
|
+
}
|
|
286
|
+
// Determine next node ID
|
|
287
|
+
let nextNodeId = null;
|
|
288
|
+
if (currentNode.type === 'step') {
|
|
289
|
+
nextNodeId = currentNode.next;
|
|
290
|
+
}
|
|
291
|
+
else if (currentNode.type === 'decision') {
|
|
292
|
+
if (choice !== undefined && choice >= 1 && choice <= currentNode.choices.length) {
|
|
293
|
+
nextNodeId = currentNode.choices[choice - 1].next;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
nextNodeId = currentNode.default;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else if (currentNode.type === 'delegate') {
|
|
300
|
+
// Push current chain onto stack and switch to sub-chain
|
|
301
|
+
const subChainId = currentNode.chain;
|
|
302
|
+
const subChainPath = join(session.skill_path, 'chains', `${subChainId}.json`);
|
|
303
|
+
if (!existsSync(subChainPath)) {
|
|
304
|
+
return { success: false, error: `Delegate chain not found: ${subChainId}` };
|
|
305
|
+
}
|
|
306
|
+
const subChain = loadChainJson(subChainPath);
|
|
307
|
+
// Push frame
|
|
308
|
+
session.chain_stack.push({
|
|
309
|
+
chain_id: session.current_chain,
|
|
310
|
+
return_node: currentNode.next || null,
|
|
311
|
+
});
|
|
312
|
+
// Switch to sub-chain
|
|
313
|
+
session.current_chain = subChain.chain_id;
|
|
314
|
+
session.current_node = subChain.entry;
|
|
315
|
+
session.node_status = 'active';
|
|
316
|
+
session.updated_at = new Date().toISOString();
|
|
317
|
+
const entryNode = subChain.nodes[subChain.entry];
|
|
318
|
+
const content = loadNodeContent(entryNode, session.skill_path);
|
|
319
|
+
session.history.push({
|
|
320
|
+
node_id: subChain.entry,
|
|
321
|
+
chain_id: subChain.chain_id,
|
|
322
|
+
node_type: entryNode.type,
|
|
323
|
+
node_status: 'active',
|
|
324
|
+
timestamp: new Date().toISOString(),
|
|
325
|
+
});
|
|
326
|
+
if (content) {
|
|
327
|
+
session.loaded_content.push({
|
|
328
|
+
node_id: subChain.entry,
|
|
329
|
+
chain_id: subChain.chain_id,
|
|
330
|
+
content,
|
|
331
|
+
loaded_at: new Date().toISOString(),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
saveSession(session);
|
|
335
|
+
return {
|
|
336
|
+
success: true,
|
|
337
|
+
result: {
|
|
338
|
+
session_id: session.session_id,
|
|
339
|
+
current_chain: session.current_chain,
|
|
340
|
+
current_node: session.current_node,
|
|
341
|
+
node_status: 'active',
|
|
342
|
+
delegate_depth: session.chain_stack.length,
|
|
343
|
+
...formatNodeOutput(entryNode, content),
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
// Handle cross-chain routing (→chain-name)
|
|
348
|
+
if (nextNodeId && nextNodeId.startsWith('→')) {
|
|
349
|
+
const targetChainId = nextNodeId.slice(1);
|
|
350
|
+
const targetChainPath = join(session.skill_path, 'chains', `${targetChainId}.json`);
|
|
351
|
+
if (!existsSync(targetChainPath)) {
|
|
352
|
+
return { success: false, error: `Cross-chain target not found: ${targetChainId}` };
|
|
353
|
+
}
|
|
354
|
+
const targetChain = loadChainJson(targetChainPath);
|
|
355
|
+
session.current_chain = targetChain.chain_id;
|
|
356
|
+
session.current_node = targetChain.entry;
|
|
357
|
+
session.node_status = 'active';
|
|
358
|
+
session.updated_at = new Date().toISOString();
|
|
359
|
+
const entryNode = targetChain.nodes[targetChain.entry];
|
|
360
|
+
const content = loadNodeContent(entryNode, session.skill_path);
|
|
361
|
+
session.history.push({
|
|
362
|
+
node_id: targetChain.entry,
|
|
363
|
+
chain_id: targetChain.chain_id,
|
|
364
|
+
node_type: entryNode.type,
|
|
365
|
+
node_status: 'active',
|
|
366
|
+
timestamp: new Date().toISOString(),
|
|
367
|
+
});
|
|
368
|
+
if (content) {
|
|
369
|
+
session.loaded_content.push({
|
|
370
|
+
node_id: targetChain.entry,
|
|
371
|
+
chain_id: targetChain.chain_id,
|
|
372
|
+
content,
|
|
373
|
+
loaded_at: new Date().toISOString(),
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
saveSession(session);
|
|
377
|
+
return {
|
|
378
|
+
success: true,
|
|
379
|
+
result: {
|
|
380
|
+
session_id: session.session_id,
|
|
381
|
+
current_chain: session.current_chain,
|
|
382
|
+
current_node: session.current_node,
|
|
383
|
+
node_status: 'active',
|
|
384
|
+
routed_from: chain.chain_id,
|
|
385
|
+
...formatNodeOutput(entryNode, content),
|
|
386
|
+
},
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
// next is null → check chain stack for return
|
|
390
|
+
if (nextNodeId === null || nextNodeId === undefined) {
|
|
391
|
+
if (session.chain_stack.length > 0) {
|
|
392
|
+
const frame = session.chain_stack.pop();
|
|
393
|
+
if (frame.return_node) {
|
|
394
|
+
// Return to parent chain's return node
|
|
395
|
+
const parentChainPath = join(session.skill_path, 'chains', `${frame.chain_id}.json`);
|
|
396
|
+
const parentChain = loadChainJson(parentChainPath);
|
|
397
|
+
session.current_chain = frame.chain_id;
|
|
398
|
+
session.current_node = frame.return_node;
|
|
399
|
+
session.node_status = 'active';
|
|
400
|
+
session.updated_at = new Date().toISOString();
|
|
401
|
+
const returnNode = parentChain.nodes[frame.return_node];
|
|
402
|
+
const content = loadNodeContent(returnNode, session.skill_path);
|
|
403
|
+
session.history.push({
|
|
404
|
+
node_id: frame.return_node,
|
|
405
|
+
chain_id: frame.chain_id,
|
|
406
|
+
node_type: returnNode.type,
|
|
407
|
+
node_status: 'active',
|
|
408
|
+
timestamp: new Date().toISOString(),
|
|
409
|
+
});
|
|
410
|
+
if (content) {
|
|
411
|
+
session.loaded_content.push({
|
|
412
|
+
node_id: frame.return_node,
|
|
413
|
+
chain_id: frame.chain_id,
|
|
414
|
+
content,
|
|
415
|
+
loaded_at: new Date().toISOString(),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
saveSession(session);
|
|
419
|
+
return {
|
|
420
|
+
success: true,
|
|
421
|
+
result: {
|
|
422
|
+
session_id: session.session_id,
|
|
423
|
+
current_chain: session.current_chain,
|
|
424
|
+
current_node: session.current_node,
|
|
425
|
+
node_status: 'active',
|
|
426
|
+
returned_from_delegate: true,
|
|
427
|
+
...formatNodeOutput(returnNode, content),
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// No more nodes, no more stack → session completed
|
|
433
|
+
session.status = 'completed';
|
|
434
|
+
session.updated_at = new Date().toISOString();
|
|
435
|
+
saveSession(session);
|
|
436
|
+
const stepCount = session.history.filter(h => h.node_type === 'step' && h.node_status === 'completed').length;
|
|
437
|
+
const decisionCount = session.history.filter(h => h.node_type === 'decision' && h.node_status === 'completed').length;
|
|
438
|
+
return {
|
|
439
|
+
success: true,
|
|
440
|
+
result: {
|
|
441
|
+
status: 'completed',
|
|
442
|
+
session_id: session.session_id,
|
|
443
|
+
summary: `Chain ${session.current_chain} completed. ${stepCount} steps, ${decisionCount} decisions.`,
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
// Advance to next node in current chain
|
|
448
|
+
const nextNode = chain.nodes[nextNodeId];
|
|
449
|
+
if (!nextNode) {
|
|
450
|
+
return { success: false, error: `Next node "${nextNodeId}" not found in chain "${session.current_chain}"` };
|
|
451
|
+
}
|
|
452
|
+
session.current_node = nextNodeId;
|
|
453
|
+
session.node_status = 'active';
|
|
454
|
+
session.updated_at = new Date().toISOString();
|
|
455
|
+
const content = loadNodeContent(nextNode, session.skill_path);
|
|
456
|
+
session.history.push({
|
|
457
|
+
node_id: nextNodeId,
|
|
458
|
+
chain_id: session.current_chain,
|
|
459
|
+
node_type: nextNode.type,
|
|
460
|
+
node_status: 'active',
|
|
461
|
+
timestamp: new Date().toISOString(),
|
|
462
|
+
});
|
|
463
|
+
if (content) {
|
|
464
|
+
session.loaded_content.push({
|
|
465
|
+
node_id: nextNodeId,
|
|
466
|
+
chain_id: session.current_chain,
|
|
467
|
+
content,
|
|
468
|
+
loaded_at: new Date().toISOString(),
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
saveSession(session);
|
|
472
|
+
return {
|
|
473
|
+
success: true,
|
|
474
|
+
result: {
|
|
475
|
+
session_id: session.session_id,
|
|
476
|
+
current_chain: session.current_chain,
|
|
477
|
+
current_node: nextNodeId,
|
|
478
|
+
node_status: 'active',
|
|
479
|
+
...formatNodeOutput(nextNode, content),
|
|
480
|
+
},
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
// ─── Node Helpers ────────────────────────────────────────────
|
|
484
|
+
function loadNodeContent(node, skillPath) {
|
|
485
|
+
if (node.type === 'step') {
|
|
486
|
+
if (node.content_ref) {
|
|
487
|
+
// Resolve @path relative to skill directory
|
|
488
|
+
const refPath = node.content_ref.replace(/^@/, '');
|
|
489
|
+
const fullPath = resolve(skillPath, refPath);
|
|
490
|
+
if (existsSync(fullPath)) {
|
|
491
|
+
return readFileSync(fullPath, 'utf-8');
|
|
492
|
+
}
|
|
493
|
+
return `[Content not found: ${fullPath}]`;
|
|
494
|
+
}
|
|
495
|
+
if (node.content_inline) {
|
|
496
|
+
return node.content_inline;
|
|
497
|
+
}
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
if (node.type === 'decision') {
|
|
501
|
+
// Return prompt as content
|
|
502
|
+
return node.prompt;
|
|
503
|
+
}
|
|
504
|
+
if (node.type === 'delegate') {
|
|
505
|
+
return `[Delegate to chain: ${node.chain}]`;
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
function formatNodeOutput(node, content) {
|
|
510
|
+
const output = {
|
|
511
|
+
type: node.type,
|
|
512
|
+
name: node.name,
|
|
513
|
+
};
|
|
514
|
+
if (node.type === 'step') {
|
|
515
|
+
output.content = content;
|
|
516
|
+
}
|
|
517
|
+
else if (node.type === 'decision') {
|
|
518
|
+
output.prompt = node.prompt;
|
|
519
|
+
output.choices = node.choices.map((c, i) => ({
|
|
520
|
+
index: i + 1,
|
|
521
|
+
label: c.label,
|
|
522
|
+
description: c.description,
|
|
523
|
+
next: c.next,
|
|
524
|
+
}));
|
|
525
|
+
output.default = node.default;
|
|
526
|
+
}
|
|
527
|
+
else if (node.type === 'delegate') {
|
|
528
|
+
output.chain = node.chain;
|
|
529
|
+
output.content = content;
|
|
530
|
+
}
|
|
531
|
+
return output;
|
|
532
|
+
}
|
|
533
|
+
function getNodeType(session) {
|
|
534
|
+
try {
|
|
535
|
+
const chain = loadChainFromSession(session);
|
|
536
|
+
const node = chain.nodes[session.current_node];
|
|
537
|
+
return node?.type || 'step';
|
|
538
|
+
}
|
|
539
|
+
catch {
|
|
540
|
+
return 'step';
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
function getLastLoadedContent(session, nodeId) {
|
|
544
|
+
for (let i = session.loaded_content.length - 1; i >= 0; i--) {
|
|
545
|
+
if (session.loaded_content[i].node_id === nodeId) {
|
|
546
|
+
return session.loaded_content[i].content;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
// ─── Chain/Session I/O ───────────────────────────────────────
|
|
552
|
+
function loadChainJson(filePath) {
|
|
553
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
554
|
+
return JSON.parse(raw);
|
|
555
|
+
}
|
|
556
|
+
function loadChainFromSession(session) {
|
|
557
|
+
const chainPath = join(session.skill_path, 'chains', `${session.current_chain}.json`);
|
|
558
|
+
if (!existsSync(chainPath)) {
|
|
559
|
+
throw new Error(`Chain file not found: ${chainPath}`);
|
|
560
|
+
}
|
|
561
|
+
return loadChainJson(chainPath);
|
|
562
|
+
}
|
|
563
|
+
function getSessionDir(sessionId) {
|
|
564
|
+
return resolve(process.cwd(), SESSIONS_BASE, sessionId);
|
|
565
|
+
}
|
|
566
|
+
function saveSession(session) {
|
|
567
|
+
const dir = getSessionDir(session.session_id);
|
|
568
|
+
if (!existsSync(dir)) {
|
|
569
|
+
mkdirSync(dir, { recursive: true });
|
|
570
|
+
}
|
|
571
|
+
writeFileSync(join(dir, 'state.json'), JSON.stringify(session, null, 2), 'utf-8');
|
|
572
|
+
}
|
|
573
|
+
function loadSession(sessionId) {
|
|
574
|
+
const statePath = join(getSessionDir(sessionId), 'state.json');
|
|
575
|
+
if (!existsSync(statePath))
|
|
576
|
+
return null;
|
|
577
|
+
return JSON.parse(readFileSync(statePath, 'utf-8'));
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Auto-detect skill from cwd.
|
|
581
|
+
* Checks if cwd (or a parent up to 3 levels) has a chains/ subdirectory.
|
|
582
|
+
* e.g. cwd = .claude/skills/investigate → detect "investigate"
|
|
583
|
+
*/
|
|
584
|
+
function detectSkillFromCwd() {
|
|
585
|
+
let dir = process.cwd();
|
|
586
|
+
for (let i = 0; i < 4; i++) {
|
|
587
|
+
if (existsSync(join(dir, 'chains'))) {
|
|
588
|
+
const name = dir.replace(/[\\/]+$/, '').split(/[\\/]/).pop();
|
|
589
|
+
return { skillName: name, skillPath: dir };
|
|
590
|
+
}
|
|
591
|
+
const parent = dirname(dir);
|
|
592
|
+
if (parent === dir)
|
|
593
|
+
break;
|
|
594
|
+
dir = parent;
|
|
595
|
+
}
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Resolve skill: explicit name → cwd auto-detect → null
|
|
600
|
+
*/
|
|
601
|
+
function resolveSkill(skillName) {
|
|
602
|
+
if (skillName) {
|
|
603
|
+
const path = findSkillPath(skillName);
|
|
604
|
+
return path ? { skillName, skillPath: path } : null;
|
|
605
|
+
}
|
|
606
|
+
return detectSkillFromCwd();
|
|
607
|
+
}
|
|
608
|
+
function discoverSkillDirs(filterSkill) {
|
|
609
|
+
const results = [];
|
|
610
|
+
const searchLocations = [
|
|
611
|
+
join(process.cwd(), '.claude', 'skills'),
|
|
612
|
+
join(homedir(), '.claude', 'skills'),
|
|
613
|
+
];
|
|
614
|
+
for (const base of searchLocations) {
|
|
615
|
+
if (!existsSync(base))
|
|
616
|
+
continue;
|
|
617
|
+
const entries = readdirSync(base, { withFileTypes: true });
|
|
618
|
+
for (const entry of entries) {
|
|
619
|
+
if (!entry.isDirectory())
|
|
620
|
+
continue;
|
|
621
|
+
if (filterSkill && entry.name !== filterSkill)
|
|
622
|
+
continue;
|
|
623
|
+
const chainsDir = join(base, entry.name, 'chains');
|
|
624
|
+
if (existsSync(chainsDir)) {
|
|
625
|
+
results.push({ skillName: entry.name, skillPath: join(base, entry.name) });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
return results;
|
|
630
|
+
}
|
|
631
|
+
function findSkillPath(skillName) {
|
|
632
|
+
const searchLocations = [
|
|
633
|
+
join(process.cwd(), '.claude', 'skills', skillName),
|
|
634
|
+
join(homedir(), '.claude', 'skills', skillName),
|
|
635
|
+
];
|
|
636
|
+
for (const path of searchLocations) {
|
|
637
|
+
if (existsSync(path))
|
|
638
|
+
return path;
|
|
639
|
+
}
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
//# sourceMappingURL=chain-loader.js.map
|