feat: add MCP server for semantic code search with FastMCP integration

This commit is contained in:
catlog22
2026-03-17 23:03:20 +08:00
parent ef2c5a58e1
commit ad9d3f94e0
80 changed files with 3427 additions and 21329 deletions

View File

@@ -0,0 +1,92 @@
# Phase 1: Load & Bind
## Objective
Locate and load the workflow template, collect any missing context variables from the user, bind all `{variable}` references.
## Workflow
### Step 1.1 — Resolve Template Path
Parse `$ARGUMENTS` for template identifier:
**Path resolution order**:
1. Absolute path: use as-is
2. Relative path (starts with `.`): resolve from cwd
3. Slug only (e.g. `feature-tdd-review`): look up in `.workflow/templates/index.json` → get path
4. Partial slug match: scan index for closest match → confirm with user
If not found:
- Show available templates from index
- AskUserQuestion: "Which template to run?"
### Step 1.2 — Parse --context Arguments
Extract `--context key=value` pairs from `$ARGUMENTS`.
Examples:
```
--context goal="Implement user auth" --context scope="src/auth"
--context goal='Fix login bug' scope=src/auth
```
Build `bound_context = { goal: "...", scope: "..." }`.
### Step 1.3 — Load Template
Read template JSON from resolved path.
Validate:
- `template_id`, `nodes`, `edges`, `context_schema` all present
- `nodes` array non-empty
### Step 1.4 — Collect Missing Required Variables
For each variable in `context_schema` where `required: true`:
- If not in `bound_context`: collect via AskUserQuestion
- If has `default` value: use default if not provided
```
AskUserQuestion({
questions: [{
question: "Provide values for required workflow inputs:",
header: "Workflow: <template.name>",
// one question per missing required variable
}]
})
```
For optional variables not provided: use `default` value or leave as empty string.
### Step 1.5 — Bind Variables
Apply substitution throughout all `args_template` strings:
- Replace `{variable_name}` with `bound_context[variable_name]`
- Leave `{N-001.session_id}` and `{prev_*}` references unresolved — these are runtime-resolved in Phase 3
Write bound context to memory for Phase 3 use.
### Step 1.6 — Dry Run Output (if --dry-run)
Print execution plan and exit:
```
Workflow: <template.name>
Context:
goal = "<value>"
scope = "<value>"
Execution Plan:
[1] N-001 [skill] workflow-lite-plan "<goal>"
[2] CP-01 [checkpoint] After Plan auto-continue
[3] N-002 [skill] workflow-execute --resume-session {N-001.session_id}
[4] CP-02 [checkpoint] Before Tests pause-for-user
[5] N-003 [skill] workflow-test-fix --session {N-002.session_id}
To execute: Skill(skill="wf-player", args="<slug> --context goal='...'")
```
## Success Criteria
- Template loaded and validated
- All required context variables bound
- bound_context{} available for Phase 2

View File

@@ -0,0 +1,110 @@
# Phase 2: Instantiate — Init Session State
## Objective
Create the WFR session directory, initialize `session-state.json` with all nodes marked pending, compute topological execution order.
## Workflow
### Step 2.1 — Generate Session ID
```
session_id = "WFR-<template-slug>-<YYYYMMDD>-<HHmmss>"
session_dir = ".workflow/sessions/<session_id>/"
```
Create session directory.
### Step 2.2 — Topological Sort
Run topological sort on `template.nodes` + `template.edges`:
```
function topoSort(nodes, edges):
build adjacency list from edges
Kahn's algorithm (BFS from nodes with no incoming edges)
return ordered node IDs
```
**Parallel group handling**: Nodes in the same `parallel_group` can execute concurrently. In topological order, keep them adjacent and mark them as a parallel batch.
Store `execution_plan`:
```json
[
{ "batch": 1, "nodes": ["N-001"], "parallel": false },
{ "batch": 2, "nodes": ["CP-01"], "parallel": false },
{ "batch": 3, "nodes": ["N-002a", "N-002b"], "parallel": true },
{ "batch": 4, "nodes": ["N-003"], "parallel": false }
]
```
### Step 2.3 — Init Node States
For each node in template:
```json
{
"N-001": {
"status": "pending",
"started_at": null,
"completed_at": null,
"session_id": null,
"output_path": null,
"artifacts": [],
"error": null
}
}
```
Checkpoint nodes:
```json
{
"CP-01": {
"status": "pending",
"saved_at": null,
"snapshot_path": null
}
}
```
### Step 2.4 — Write session-state.json
See `specs/state-schema.md` for full schema. Write to `<session_dir>/session-state.json`:
```json
{
"session_id": "<WFR-id>",
"template_id": "<template.template_id>",
"template_path": "<path to template>",
"template_name": "<template.name>",
"status": "running",
"context": { /* bound_context from Phase 1 */ },
"execution_plan": [ /* batches */ ],
"current_batch": 1,
"current_node": "N-001",
"last_checkpoint": null,
"node_states": { /* all nodes as pending */ },
"created_at": "<ISO>",
"updated_at": "<ISO>"
}
```
### Step 2.5 — Show Execution Start Banner
```
[wf-player] ============================================
[wf-player] Starting: <template.name>
[wf-player] Session: <session_id>
[wf-player] Context: goal="<value>"
[wf-player]
[wf-player] Plan: <N> nodes, <C> checkpoints
[wf-player] N-001 [skill] workflow-lite-plan
[wf-player] CP-01 [checkpoint] After Plan
[wf-player] N-002 [skill] workflow-execute
[wf-player] ============================================
```
## Success Criteria
- `<session_dir>/session-state.json` written
- `execution_plan` has valid topological order
- Status = "running"

View File

@@ -0,0 +1,211 @@
# Phase 3: Execute Loop
## Objective
Execute each node batch in topological order. Use the correct mechanism per node type. Save state after every checkpoint. Support CLI serial-blocking with hook callback resume.
## Pre-execution: Runtime Reference Resolution
Before executing each node, resolve any `{N-xxx.field}` and `{prev_*}` references in `args_template`:
```
function resolveArgs(args_template, node_id, session_state):
for each {ref} in args_template:
if ref matches {variable}:
replace with session_state.context[variable]
if ref matches {N-001.session_id}:
replace with session_state.node_states["N-001"].session_id
if ref matches {N-001.output_path}:
replace with session_state.node_states["N-001"].output_path
if ref matches {prev_session_id}:
find previous non-checkpoint node -> replace with its session_id
if ref matches {prev_output}:
find previous non-checkpoint node -> replace with its output_text
if ref matches {prev_output_path}:
find previous non-checkpoint node -> replace with its output_path
return resolved_args
```
## Node Execution by Type
Read `specs/node-executor.md` for full details. Summary:
### skill node
```
resolved_args = resolveArgs(node.args_template, ...)
mark node status = "running", write session-state.json
result = Skill(skill=node.executor, args=resolved_args)
extract from result: session_id, output_path, artifacts[]
update node_states[node.id]:
status = "completed"
session_id = extracted_session_id
output_path = extracted_output_path
artifacts = extracted_artifacts
completed_at = now()
write session-state.json
advance to next node
```
### command node
Same as skill node but executor is a namespaced command:
```
Skill(skill=node.executor, args=resolved_args)
```
### cli node — CRITICAL: serial blocking
```
resolved_args = resolveArgs(node.args_template, ...)
mark node status = "running", write session-state.json
Bash({
command: `ccw cli -p "${resolved_args}" --tool ${node.cli_tool} --mode ${node.cli_mode} --rule ${node.cli_rule}`,
run_in_background: true
})
write session-state.json // persist "running" state
STOP — wait for hook callback
```
**Hook callback resumes here**:
```
// Called when ccw cli completes
load session-state.json
find node with status "running"
extract result: exec_id, output_path, cli_output
update node_states[node.id]:
status = "completed"
output_path = extracted_output_path
completed_at = now()
write session-state.json
advance to next node
```
### agent node
```
resolved_args = resolveArgs(node.args_template, ...)
mark node status = "running", write session-state.json
result = Agent({
subagent_type: node.executor,
prompt: resolved_args,
run_in_background: node.run_in_background ?? false,
description: node.name
})
update node_states[node.id]:
status = "completed"
output_path = result.output_path or session_dir + "/artifacts/" + node.id + ".md"
completed_at = now()
write session-state.json
advance to next node
```
**Parallel agent nodes** (same parallel_group):
```
// Launch all agents in parallel
for each node in parallel_batch:
mark node status = "running"
Agent({
subagent_type: node.executor,
prompt: resolveArgs(node.args_template, ...),
run_in_background: true, // parallel
description: node.name
})
// Wait for all to complete (Agent with run_in_background=false blocks — use team-coordinate pattern)
// team-coordinate: spawn as team-workers with callbacks if complex
// For simple parallel: use multiple Agent calls synchronously or use team-coordinate's spawn-and-stop
```
### checkpoint node
```
// Save snapshot
snapshot = {
session_id: session_state.session_id,
checkpoint_id: node.id,
checkpoint_name: node.name,
saved_at: now(),
node_states_snapshot: session_state.node_states,
last_completed_node: previous_node_id
}
write session-state.json (last_checkpoint = node.id)
write <session_dir>/checkpoints/<node.id>.json
if node.auto_continue == false:
// Pause for user
AskUserQuestion({
questions: [{
question: node.description + "\n\nReview checkpoint state and confirm to continue.",
header: "Checkpoint: " + node.name,
options: [
{ label: "Continue", description: "Proceed to next node" },
{ label: "Pause", description: "Save state and exit (resume later)" },
{ label: "Abort", description: "Stop execution" }
]
}]
})
on "Pause":
session_state.status = "paused"
write session-state.json
output "Session paused. Resume with: Skill(skill='wf-player', args='--resume <session_id>')"
EXIT
on "Abort":
session_state.status = "aborted"
write session-state.json
EXIT
// auto_continue or user chose Continue
mark checkpoint status = "completed"
write session-state.json
advance to next node
```
## Progress Display
After each node completes, print progress:
```
[wf-player] [2/5] CP-01 checkpoint saved ✓
[wf-player] [3/5] N-002 workflow-execute ... running
```
## Error Handling
On node failure (exception or skill returning error state):
```
on_fail = node.on_fail || "abort"
if on_fail == "skip":
mark node status = "skipped"
log warning
advance to next node
if on_fail == "retry":
retry once
if still fails: fall through to abort
if on_fail == "abort":
AskUserQuestion:
- Retry
- Skip this node
- Abort workflow
handle choice accordingly
```
## Loop Termination
After last node in execution_plan completes:
- All node_states should be "completed" or "skipped"
- Proceed to Phase 4 (Complete)

View File

@@ -0,0 +1,93 @@
# Phase 4: Complete — Archive + Summary
## Objective
Mark session complete, output execution summary with artifact paths, offer archive/keep options.
## Workflow
### Step 4.1 — Mark Session Complete
Load `session-state.json`.
Set:
```json
{
"status": "completed",
"completed_at": "<ISO timestamp>"
}
```
Write back to `session-state.json`.
### Step 4.2 — Collect Artifacts
Aggregate all artifacts from node_states:
```javascript
const artifacts = Object.values(node_states)
.filter(s => s.artifacts && s.artifacts.length > 0)
.flatMap(s => s.artifacts.map(a => ({ node: s.node_id, path: a })));
const outputPaths = Object.values(node_states)
.filter(s => s.output_path)
.map(s => ({ node: s.node_id, path: s.output_path }));
```
### Step 4.3 — Execution Summary
```
[wf-player] ============================================
[wf-player] COMPLETE: <template_name>
[wf-player]
[wf-player] Session: <session_id>
[wf-player] Context: goal="<value>"
[wf-player]
[wf-player] Nodes: <completed>/<total> completed
[wf-player] N-001 workflow-lite-plan ✓ (WFS-plan-xxx)
[wf-player] CP-01 After Plan ✓ (checkpoint saved)
[wf-player] N-002 workflow-execute ✓ (WFS-exec-xxx)
[wf-player] CP-02 Before Tests ✓ (checkpoint saved)
[wf-player] N-003 workflow-test-fix ✓ (WFS-test-xxx)
[wf-player]
[wf-player] Artifacts:
[wf-player] - IMPL_PLAN.md (N-001)
[wf-player] - src/auth/index.ts (N-002)
[wf-player] - test/auth.test.ts (N-003)
[wf-player]
[wf-player] Session dir: .workflow/sessions/<session_id>/
[wf-player] ============================================
```
### Step 4.4 — Completion Action
```
AskUserQuestion({
questions: [{
question: "Workflow complete. What would you like to do?",
header: "Completion Action",
options: [
{ label: "Archive session", description: "Move session to .workflow/sessions/archive/" },
{ label: "Keep session", description: "Leave session active for follow-up" },
{ label: "Run again", description: "Re-run template with same or new context" },
{ label: "Nothing", description: "Done" }
]
}]
})
```
**Archive**:
- Move `<session_dir>` to `.workflow/sessions/archive/<session_id>/`
- Update `session-state.json` status = "archived"
**Keep**: No action, session stays at `.workflow/sessions/<session_id>/`
**Run again**:
- AskUserQuestion: "Same context or new?" → new context → re-enter Phase 1
**Nothing**: Output final artifact paths list, done.
## Success Criteria
- session-state.json status = "completed" or "archived"
- All artifact paths listed in console output
- User presented completion action options