mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-11 02:33:51 +08:00
Add coverage prettification and sorting functionality
- Introduced `prettify.css` for syntax highlighting in coverage reports. - Added `prettify.js` to handle code formatting and highlighting. - Included `sort-arrow-sprite.png` for sort indicators in the coverage table. - Implemented `sorter.js` to enable sorting and filtering of coverage summary tables. - Added a search box for filtering table rows based on user input.
This commit is contained in:
@@ -459,7 +459,7 @@ Display current understanding and exploration findings to the user.
|
|||||||
- Open questions or areas needing clarification
|
- Open questions or areas needing clarification
|
||||||
- Available action options
|
- Available action options
|
||||||
|
|
||||||
**User Feedback Options** (AskUserQuestion - single select):
|
**User Feedback Options** (ASK_USER - single select):
|
||||||
|
|
||||||
| Option | Purpose | Next Action |
|
| Option | Purpose | Next Action |
|
||||||
|--------|---------|------------|
|
|--------|---------|------------|
|
||||||
@@ -543,7 +543,7 @@ close_agent({ id: deepeningAgent })
|
|||||||
When user indicates a different focus is needed, spawn new agent with adjusted perspective.
|
When user indicates a different focus is needed, spawn new agent with adjusted perspective.
|
||||||
|
|
||||||
**Direction Adjustment Process**:
|
**Direction Adjustment Process**:
|
||||||
1. Ask user for adjusted focus area (through AskUserQuestion)
|
1. Ask user for adjusted focus area (via ASK_USER)
|
||||||
2. Spawn new agent with different dimension/perspective
|
2. Spawn new agent with different dimension/perspective
|
||||||
3. Compare new insights with prior analysis
|
3. Compare new insights with prior analysis
|
||||||
4. Identify what was missed and why
|
4. Identify what was missed and why
|
||||||
@@ -592,10 +592,11 @@ When user has specific questions, address them directly.
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Capture user questions first
|
// Capture user questions first
|
||||||
const userQuestions = await AskUserQuestion({
|
const userQuestions = ASK_USER([{
|
||||||
question: "What specific questions do you have?",
|
id: "user_questions", type: "input",
|
||||||
|
prompt: "What specific questions do you have?",
|
||||||
options: [/* predefined + custom */]
|
options: [/* predefined + custom */]
|
||||||
})
|
}]) // BLOCKS (wait for user response)
|
||||||
|
|
||||||
// Send questions to active agent
|
// Send questions to active agent
|
||||||
send_input({
|
send_input({
|
||||||
@@ -759,7 +760,7 @@ Append conclusions section and finalize the understanding document.
|
|||||||
|
|
||||||
Offer user follow-up actions based on analysis results.
|
Offer user follow-up actions based on analysis results.
|
||||||
|
|
||||||
**Available Options** (AskUserQuestion - multi-select):
|
**Available Options** (ASK_USER - multi-select):
|
||||||
|
|
||||||
| Option | Purpose | Action |
|
| Option | Purpose | Action |
|
||||||
|--------|---------|--------|
|
|--------|---------|--------|
|
||||||
|
|||||||
@@ -182,14 +182,11 @@ ${synthesis.top_ideas.map((idea, i) =>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const selection = AskUser({
|
const selection = ASK_USER([{
|
||||||
questions: [{
|
id: "idea", type: "select",
|
||||||
question: "Which idea should be developed?",
|
prompt: "Which idea should be developed?",
|
||||||
header: "Idea",
|
options: ideaOptions
|
||||||
multiSelect: false,
|
}]) // BLOCKS (wait for user response)
|
||||||
options: ideaOptions
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Parse selection
|
// Parse selection
|
||||||
if (selection.idea === "Primary Recommendation") {
|
if (selection.idea === "Primary Recommendation") {
|
||||||
@@ -292,17 +289,14 @@ Write(`${sessionFolder}/cycle-task.md`, `# Generated Task\n\n**Generated**: ${ge
|
|||||||
let shouldLaunch = isAutoMode
|
let shouldLaunch = isAutoMode
|
||||||
|
|
||||||
if (!isAutoMode) {
|
if (!isAutoMode) {
|
||||||
const confirmation = AskUser({
|
const confirmation = ASK_USER([{
|
||||||
questions: [{
|
id: "launch", type: "select",
|
||||||
question: "Launch parallel-dev-cycle with this task?",
|
prompt: "Launch parallel-dev-cycle with this task?",
|
||||||
header: "Launch",
|
options: [
|
||||||
multiSelect: false,
|
{ label: "Yes, launch cycle (Recommended)", description: "Start parallel-dev-cycle with enriched task" },
|
||||||
options: [
|
{ label: "No, just save task", description: "Save formatted task for manual use" }
|
||||||
{ label: "Yes, launch cycle (Recommended)", description: "Start parallel-dev-cycle with enriched task" },
|
]
|
||||||
{ label: "No, just save task", description: "Save formatted task for manual use" }
|
}]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
shouldLaunch = confirmation.launch.includes("Yes")
|
shouldLaunch = confirmation.launch.includes("Yes")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -412,20 +412,17 @@ async function showMenuAndGetChoice(state) {
|
|||||||
const pendingCount = ss?.pending_tasks?.length || 0
|
const pendingCount = ss?.pending_tasks?.length || 0
|
||||||
const completedCount = ss?.completed_tasks?.length || 0
|
const completedCount = ss?.completed_tasks?.length || 0
|
||||||
|
|
||||||
const response = await AskUserQuestion({
|
const response = await ASK_USER([{
|
||||||
questions: [{
|
id: "Action", type: "select",
|
||||||
question: `Select next action (completed: ${completedCount}, pending: ${pendingCount}):`,
|
prompt: `Select next action (completed: ${completedCount}, pending: ${pendingCount}):`,
|
||||||
header: "Action",
|
options: [
|
||||||
multiSelect: false,
|
{ label: "develop", description: `Continue development (${pendingCount} pending)` },
|
||||||
options: [
|
{ label: "debug", description: "Start debugging / diagnosis" },
|
||||||
{ label: "develop", description: `Continue development (${pendingCount} pending)` },
|
{ label: "validate", description: "Run tests and validation" },
|
||||||
{ label: "debug", description: "Start debugging / diagnosis" },
|
{ label: "complete", description: "Complete loop and generate summary" },
|
||||||
{ label: "validate", description: "Run tests and validation" },
|
{ label: "exit", description: "Exit and save progress" }
|
||||||
{ label: "complete", description: "Complete loop and generate summary" },
|
]
|
||||||
{ label: "exit", description: "Exit and save progress" }
|
}]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
return response["Action"]
|
return response["Action"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,20 +274,17 @@ async function displayMenuAndGetChoice(actionResult) {
|
|||||||
console.log('\n' + menuMatch[1])
|
console.log('\n' + menuMatch[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await AskUserQuestion({
|
const response = await ASK_USER([{
|
||||||
questions: [{
|
id: "Action", type: "select",
|
||||||
question: "Select next action:",
|
prompt: "Select next action:",
|
||||||
header: "Action",
|
options: [
|
||||||
multiSelect: false,
|
{ label: "develop", description: "Continue development" },
|
||||||
options: [
|
{ label: "debug", description: "Start debugging" },
|
||||||
{ label: "develop", description: "Continue development" },
|
{ label: "validate", description: "Run validation" },
|
||||||
{ label: "debug", description: "Start debugging" },
|
{ label: "complete", description: "Complete loop" },
|
||||||
{ label: "validate", description: "Run validation" },
|
{ label: "exit", description: "Exit and save" }
|
||||||
{ label: "complete", description: "Complete loop" },
|
]
|
||||||
{ label: "exit", description: "Exit and save" }
|
}]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
return { action: response["Action"] }
|
return { action: response["Action"] }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ Phase 2: Drift Discovery (Subagent)
|
|||||||
Phase 3: Confirmation
|
Phase 3: Confirmation
|
||||||
├─ Validate manifest schema
|
├─ Validate manifest schema
|
||||||
├─ Display cleanup summary by category
|
├─ Display cleanup summary by category
|
||||||
├─ AskUser: Select categories and risk level
|
├─ ASK_USER: Select categories and risk level
|
||||||
└─ Dry-run exit if --dry-run
|
└─ Dry-run exit if --dry-run
|
||||||
|
|
||||||
Phase 4: Execution
|
Phase 4: Execution
|
||||||
@@ -241,29 +241,26 @@ Manifest: ${sessionFolder}/cleanup-manifest.json
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User confirmation
|
// User confirmation
|
||||||
const selection = AskUser({
|
const selection = ASK_USER([
|
||||||
questions: [
|
{
|
||||||
{
|
id: "categories", type: "multi-select",
|
||||||
question: "Which categories to clean?",
|
prompt: "Which categories to clean?",
|
||||||
header: "Categories",
|
options: [
|
||||||
multiSelect: true,
|
{ label: "Sessions", description: `${manifest.summary.by_category.stale_sessions} stale sessions` },
|
||||||
options: [
|
{ label: "Documents", description: `${manifest.summary.by_category.drifted_documents} drifted docs` },
|
||||||
{ label: "Sessions", description: `${manifest.summary.by_category.stale_sessions} stale sessions` },
|
{ label: "Dead Code", description: `${manifest.summary.by_category.dead_code} unused files` }
|
||||||
{ label: "Documents", description: `${manifest.summary.by_category.drifted_documents} drifted docs` },
|
]
|
||||||
{ label: "Dead Code", description: `${manifest.summary.by_category.dead_code} unused files` }
|
},
|
||||||
]
|
{
|
||||||
},
|
id: "risk", type: "select",
|
||||||
{
|
prompt: "Risk level?",
|
||||||
question: "Risk level?",
|
options: [
|
||||||
header: "Risk",
|
{ label: "Low only", description: "Safest (Recommended)" },
|
||||||
options: [
|
{ label: "Low + Medium", description: "Includes likely unused" },
|
||||||
{ label: "Low only", description: "Safest (Recommended)" },
|
{ label: "All", description: "Aggressive" }
|
||||||
{ label: "Low + Medium", description: "Includes likely unused" },
|
]
|
||||||
{ label: "All", description: "Aggressive" }
|
}
|
||||||
]
|
]) // BLOCKS (wait for user response)
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Unified issue discovery and creation skill covering three entry points: manual i
|
|||||||
│ → Action selection → Route to phase → Execute → Summary │
|
│ → Action selection → Route to phase → Execute → Summary │
|
||||||
└───────────────┬─────────────────────────────────────────────────┘
|
└───────────────┬─────────────────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
├─ AskUserQuestion: Select action
|
├─ ASK_USER: Select action
|
||||||
│
|
│
|
||||||
┌───────────┼───────────┬───────────┐
|
┌───────────┼───────────┬───────────┐
|
||||||
↓ ↓ ↓ │
|
↓ ↓ ↓ │
|
||||||
@@ -38,7 +38,7 @@ Unified issue discovery and creation skill covering three entry points: manual i
|
|||||||
|
|
||||||
## Key Design Principles
|
## Key Design Principles
|
||||||
|
|
||||||
1. **Action-Driven Routing**: AskUserQuestion selects action, then load single phase
|
1. **Action-Driven Routing**: ASK_USER selects action, then load single phase
|
||||||
2. **Progressive Phase Loading**: Only read the selected phase document
|
2. **Progressive Phase Loading**: Only read the selected phase document
|
||||||
3. **CLI-First Data Access**: All issue CRUD via `ccw issue` CLI commands
|
3. **CLI-First Data Access**: All issue CRUD via `ccw issue` CLI commands
|
||||||
4. **Auto Mode Support**: `-y` flag skips action selection with auto-detection
|
4. **Auto Mode Support**: `-y` flag skips action selection with auto-detection
|
||||||
@@ -89,7 +89,7 @@ Action Selection:
|
|||||||
│ ├─ Path pattern (src/**, *.ts) → Discover (Phase 2)
|
│ ├─ Path pattern (src/**, *.ts) → Discover (Phase 2)
|
||||||
│ ├─ Short text (< 80 chars) → Create New (Phase 1)
|
│ ├─ Short text (< 80 chars) → Create New (Phase 1)
|
||||||
│ └─ Long descriptive text (≥ 80 chars) → Discover by Prompt (Phase 3)
|
│ └─ Long descriptive text (≥ 80 chars) → Discover by Prompt (Phase 3)
|
||||||
└─ Otherwise → AskUserQuestion to select action
|
└─ Otherwise → ASK_USER to select action
|
||||||
|
|
||||||
Phase Execution (load one phase):
|
Phase Execution (load one phase):
|
||||||
├─ Phase 1: Create New → phases/01-issue-new.md
|
├─ Phase 1: Create New → phases/01-issue-new.md
|
||||||
@@ -155,31 +155,29 @@ function detectAction(input, flags) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Action Selection (AskUserQuestion)
|
### Action Selection (ASK_USER)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// When action cannot be auto-detected
|
// When action cannot be auto-detected
|
||||||
const answer = AskUserQuestion({
|
const answer = ASK_USER([{
|
||||||
questions: [{
|
id: "action",
|
||||||
question: "What would you like to do?",
|
type: "select",
|
||||||
header: "Action",
|
prompt: "What would you like to do?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{
|
||||||
{
|
label: "Create New Issue (Recommended)",
|
||||||
label: "Create New Issue (Recommended)",
|
description: "Create issue from GitHub URL, text description, or structured input"
|
||||||
description: "Create issue from GitHub URL, text description, or structured input"
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Discover Issues",
|
||||||
label: "Discover Issues",
|
description: "Multi-perspective discovery: bug, security, test, quality, performance, etc."
|
||||||
description: "Multi-perspective discovery: bug, security, test, quality, performance, etc."
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Discover by Prompt",
|
||||||
label: "Discover by Prompt",
|
description: "Describe what to find — Gemini plans the exploration strategy iteratively"
|
||||||
description: "Describe what to find — Gemini plans the exploration strategy iteratively"
|
}
|
||||||
}
|
]
|
||||||
]
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Route based on selection
|
// Route based on selection
|
||||||
const actionMap = {
|
const actionMap = {
|
||||||
@@ -196,7 +194,7 @@ User Input (URL / text / path pattern / descriptive prompt)
|
|||||||
↓
|
↓
|
||||||
[Parse Flags + Auto-Detect Action]
|
[Parse Flags + Auto-Detect Action]
|
||||||
↓
|
↓
|
||||||
[Action Selection] ← AskUserQuestion (if needed)
|
[Action Selection] ← ASK_USER (if needed)
|
||||||
↓
|
↓
|
||||||
[Read Selected Phase Document]
|
[Read Selected Phase Document]
|
||||||
↓
|
↓
|
||||||
@@ -294,7 +292,7 @@ close_agent({ id: agentId })
|
|||||||
|
|
||||||
| Error | Resolution |
|
| Error | Resolution |
|
||||||
|-------|------------|
|
|-------|------------|
|
||||||
| No action detected | Show AskUserQuestion with all 3 options |
|
| No action detected | Show ASK_USER with all 3 options |
|
||||||
| Invalid action type | Show available actions, re-prompt |
|
| Invalid action type | Show available actions, re-prompt |
|
||||||
| Phase execution fails | Report error, suggest manual intervention |
|
| Phase execution fails | Report error, suggest manual intervention |
|
||||||
| No files matched (discover) | Check target pattern, verify path exists |
|
| No files matched (discover) | Check target pattern, verify path exists |
|
||||||
@@ -307,33 +305,29 @@ After successful phase execution, recommend next action:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// After Create New (issue created)
|
// After Create New (issue created)
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "next_after_create",
|
||||||
question: "Issue created. What next?",
|
type: "select",
|
||||||
header: "Next",
|
prompt: "Issue created. What next?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Plan Solution", description: "Generate solution via issue-resolve" },
|
||||||
{ label: "Plan Solution", description: "Generate solution via issue-resolve" },
|
{ label: "Create Another", description: "Create more issues" },
|
||||||
{ label: "Create Another", description: "Create more issues" },
|
{ label: "View Issues", description: "Review all issues" },
|
||||||
{ label: "View Issues", description: "Review all issues" },
|
{ label: "Done", description: "Exit workflow" }
|
||||||
{ label: "Done", description: "Exit workflow" }
|
]
|
||||||
]
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// After Discover / Discover by Prompt (discoveries generated)
|
// After Discover / Discover by Prompt (discoveries generated)
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "next_after_discover",
|
||||||
question: "Discovery complete. What next?",
|
type: "select",
|
||||||
header: "Next",
|
prompt: "Discovery complete. What next?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Export to Issues", description: "Convert discoveries to issues" },
|
||||||
{ label: "Export to Issues", description: "Convert discoveries to issues" },
|
{ label: "Plan Solutions", description: "Plan solutions for exported issues via issue-resolve" },
|
||||||
{ label: "Plan Solutions", description: "Plan solutions for exported issues via issue-resolve" },
|
{ label: "Done", description: "Exit workflow" }
|
||||||
{ label: "Done", description: "Exit workflow" }
|
]
|
||||||
]
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Related Skills & Commands
|
## Related Skills & Commands
|
||||||
|
|||||||
@@ -145,16 +145,12 @@ if (clarityScore >= 1 && clarityScore <= 2 && !issueData.affected_components?.le
|
|||||||
```javascript
|
```javascript
|
||||||
// ONLY ask questions if clarity is low
|
// ONLY ask questions if clarity is low
|
||||||
if (clarityScore < 2 && (!issueData.context || issueData.context.length < 20)) {
|
if (clarityScore < 2 && (!issueData.context || issueData.context.length < 20)) {
|
||||||
const answer = AskUserQuestion({
|
const answer = ASK_USER([{
|
||||||
questions: [{
|
id: "clarify",
|
||||||
question: 'Please describe the issue in more detail:',
|
type: "input",
|
||||||
header: 'Clarify',
|
prompt: "Please describe the issue in more detail:",
|
||||||
multiSelect: false,
|
description: "Describe what, where, and expected behavior"
|
||||||
options: [
|
}]); // BLOCKS (wait for user response)
|
||||||
{ label: 'Provide details', description: 'Describe what, where, and expected behavior' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (answer.customText) {
|
if (answer.customText) {
|
||||||
issueData.context = answer.customText;
|
issueData.context = answer.customText;
|
||||||
@@ -176,19 +172,9 @@ if (clarityScore < 2 && (!issueData.context || issueData.context.length < 20)) {
|
|||||||
let publishToGitHub = false;
|
let publishToGitHub = false;
|
||||||
|
|
||||||
if (issueData.source !== 'github') {
|
if (issueData.source !== 'github') {
|
||||||
const publishAnswer = AskUserQuestion({
|
// Yes → Create issue on GitHub and link it
|
||||||
questions: [{
|
// No → Store as local issue without GitHub sync
|
||||||
question: 'Would you like to publish this issue to GitHub?',
|
publishToGitHub = CONFIRM("Would you like to publish this issue to GitHub?"); // BLOCKS (wait for user response)
|
||||||
header: 'Publish',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes, publish to GitHub', description: 'Create issue on GitHub and link it' },
|
|
||||||
{ label: 'No, keep local only', description: 'Store as local issue without GitHub sync' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
publishToGitHub = publishAnswer.answers?.['Publish']?.includes('Yes');
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -242,14 +228,14 @@ Phase 2: Data Extraction (branched by clarity)
|
|||||||
│ Score 3 │ Score 1-2 │ Score 0 │
|
│ Score 3 │ Score 1-2 │ Score 0 │
|
||||||
│ GitHub │ Text + ACE │ Vague │
|
│ GitHub │ Text + ACE │ Vague │
|
||||||
├────────────┼─────────────────┼──────────────┤
|
├────────────┼─────────────────┼──────────────┤
|
||||||
│ gh CLI │ Parse struct │ AskQuestion │
|
│ gh CLI │ Parse struct │ ASK_USER │
|
||||||
│ → parse │ + quick hint │ (1 question) │
|
│ → parse │ + quick hint │ (1 question) │
|
||||||
│ │ (3 files max) │ → feedback │
|
│ │ (3 files max) │ → feedback │
|
||||||
└────────────┴─────────────────┴──────────────┘
|
└────────────┴─────────────────┴──────────────┘
|
||||||
|
|
||||||
Phase 3: GitHub Publishing Decision (non-GitHub only)
|
Phase 3: GitHub Publishing Decision (non-GitHub only)
|
||||||
├─ Source = github: Skip (already from GitHub)
|
├─ Source = github: Skip (already from GitHub)
|
||||||
└─ Source ≠ github: AskUserQuestion
|
└─ Source ≠ github: CONFIRM
|
||||||
├─ Yes → publishToGitHub = true
|
├─ Yes → publishToGitHub = true
|
||||||
└─ No → publishToGitHub = false
|
└─ No → publishToGitHub = false
|
||||||
|
|
||||||
|
|||||||
@@ -86,20 +86,18 @@ let selectedPerspectives = [];
|
|||||||
if (args.perspectives) {
|
if (args.perspectives) {
|
||||||
selectedPerspectives = args.perspectives.split(',').map(p => p.trim());
|
selectedPerspectives = args.perspectives.split(',').map(p => p.trim());
|
||||||
} else {
|
} else {
|
||||||
// Interactive selection via AskUserQuestion
|
// Interactive selection via ASK_USER
|
||||||
const response = AskUserQuestion({
|
const response = ASK_USER([{
|
||||||
questions: [{
|
id: "focus",
|
||||||
question: "Select primary discovery focus:",
|
type: "select",
|
||||||
header: "Focus",
|
prompt: "Select primary discovery focus:",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Bug + Test + Quality", description: "Quick scan: potential bugs, test gaps, code quality (Recommended)" },
|
||||||
{ label: "Bug + Test + Quality", description: "Quick scan: potential bugs, test gaps, code quality (Recommended)" },
|
{ label: "Security + Performance", description: "System audit: security issues, performance bottlenecks" },
|
||||||
{ label: "Security + Performance", description: "System audit: security issues, performance bottlenecks" },
|
{ label: "Maintainability + Best-practices", description: "Long-term health: coupling, tech debt, conventions" },
|
||||||
{ label: "Maintainability + Best-practices", description: "Long-term health: coupling, tech debt, conventions" },
|
{ label: "Full analysis", description: "All 8 perspectives (comprehensive, takes longer)" }
|
||||||
{ label: "Full analysis", description: "All 8 perspectives (comprehensive, takes longer)" }
|
]
|
||||||
]
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
selectedPerspectives = parseSelectedPerspectives(response);
|
selectedPerspectives = parseSelectedPerspectives(response);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -273,22 +271,20 @@ await updateDiscoveryState(outputDir, {
|
|||||||
```javascript
|
```javascript
|
||||||
const hasHighPriority = issues.some(i => i.priority === 'critical' || i.priority === 'high');
|
const hasHighPriority = issues.some(i => i.priority === 'critical' || i.priority === 'high');
|
||||||
|
|
||||||
await AskUserQuestion({
|
await ASK_USER([{
|
||||||
questions: [{
|
id: "next_step",
|
||||||
question: `Discovery complete: ${issues.length} issues generated, ${prioritizedFindings.length} total findings. What next?`,
|
type: "select",
|
||||||
header: "Next Step",
|
prompt: `Discovery complete: ${issues.length} issues generated, ${prioritizedFindings.length} total findings. What next?`,
|
||||||
multiSelect: false,
|
options: hasHighPriority ? [
|
||||||
options: hasHighPriority ? [
|
{ label: "Export to Issues (Recommended)", description: `${issues.length} high-priority issues found - export to tracker` },
|
||||||
{ label: "Export to Issues (Recommended)", description: `${issues.length} high-priority issues found - export to tracker` },
|
{ label: "Open Dashboard", description: "Review findings in ccw view before exporting" },
|
||||||
{ label: "Open Dashboard", description: "Review findings in ccw view before exporting" },
|
{ label: "Skip", description: "Complete discovery without exporting" }
|
||||||
{ label: "Skip", description: "Complete discovery without exporting" }
|
] : [
|
||||||
] : [
|
{ label: "Open Dashboard (Recommended)", description: "Review findings in ccw view to decide which to export" },
|
||||||
{ label: "Open Dashboard (Recommended)", description: "Review findings in ccw view to decide which to export" },
|
{ label: "Export to Issues", description: `Export ${issues.length} issues to tracker` },
|
||||||
{ label: "Export to Issues", description: `Export ${issues.length} issues to tracker` },
|
{ label: "Skip", description: "Complete discovery without exporting" }
|
||||||
{ label: "Skip", description: "Complete discovery without exporting" }
|
]
|
||||||
]
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response === "Export to Issues") {
|
if (response === "Export to Issues") {
|
||||||
await appendJsonl('.workflow/issues/issues.jsonl', issues);
|
await appendJsonl('.workflow/issues/issues.jsonl', issues);
|
||||||
|
|||||||
@@ -389,19 +389,17 @@ await updateDiscoveryState(outputDir, {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Prompt user for next action
|
// Prompt user for next action
|
||||||
await AskUserQuestion({
|
await ASK_USER([{
|
||||||
questions: [{
|
id: "next_step",
|
||||||
question: `Discovery complete: ${issues.length} issues from ${cumulativeFindings.length} findings across ${iteration} iterations. What next?`,
|
type: "select",
|
||||||
header: "Next Step",
|
prompt: `Discovery complete: ${issues.length} issues from ${cumulativeFindings.length} findings across ${iteration} iterations. What next?`,
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Export to Issues (Recommended)", description: `Export ${issues.length} issues for planning` },
|
||||||
{ label: "Export to Issues (Recommended)", description: `Export ${issues.length} issues for planning` },
|
{ label: "Review Details", description: "View comparison analysis and iteration details" },
|
||||||
{ label: "Review Details", description: "View comparison analysis and iteration details" },
|
{ label: "Run Deeper", description: "Continue with more iterations" },
|
||||||
{ label: "Run Deeper", description: "Continue with more iterations" },
|
{ label: "Skip", description: "Complete without exporting" }
|
||||||
{ label: "Skip", description: "Complete without exporting" }
|
]
|
||||||
]
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dimension Agent Prompt Template
|
## Dimension Agent Prompt Template
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ Unified issue resolution pipeline that orchestrates solution creation from multi
|
|||||||
│ → Source selection → Route to phase → Execute → Summary │
|
│ → Source selection → Route to phase → Execute → Summary │
|
||||||
└───────────────┬─────────────────────────────────────────────────┘
|
└───────────────┬─────────────────────────────────────────────────┘
|
||||||
│
|
│
|
||||||
├─ AskUserQuestion: Select issue source
|
├─ ASK_USER: Select issue source
|
||||||
│
|
│
|
||||||
┌───────────┼───────────┬───────────┬───────────┐
|
┌───────────┼───────────┬───────────┬───────────┐
|
||||||
↓ ↓ ↓ ↓ │
|
↓ ↓ ↓ ↓ │
|
||||||
@@ -36,7 +36,7 @@ Unified issue resolution pipeline that orchestrates solution creation from multi
|
|||||||
|
|
||||||
## Key Design Principles
|
## Key Design Principles
|
||||||
|
|
||||||
1. **Source-Driven Routing**: AskUserQuestion selects workflow, then load single phase
|
1. **Source-Driven Routing**: ASK_USER selects workflow, then load single phase
|
||||||
2. **Progressive Phase Loading**: Only read the selected phase document
|
2. **Progressive Phase Loading**: Only read the selected phase document
|
||||||
3. **CLI-First Data Access**: All issue/solution CRUD via `ccw issue` CLI commands
|
3. **CLI-First Data Access**: All issue/solution CRUD via `ccw issue` CLI commands
|
||||||
4. **Auto Mode Support**: `-y` flag skips source selection (defaults to Explore & Plan)
|
4. **Auto Mode Support**: `-y` flag skips source selection (defaults to Explore & Plan)
|
||||||
@@ -142,7 +142,7 @@ Source Selection:
|
|||||||
│ ├─ SESSION="..." → From Brainstorm
|
│ ├─ SESSION="..." → From Brainstorm
|
||||||
│ ├─ File/folder path → Convert from Artifact
|
│ ├─ File/folder path → Convert from Artifact
|
||||||
│ └─ No input or --all-pending → Explore & Plan (all pending)
|
│ └─ No input or --all-pending → Explore & Plan (all pending)
|
||||||
└─ Otherwise → AskUserQuestion to select source
|
└─ Otherwise → ASK_USER to select source
|
||||||
|
|
||||||
Phase Execution (load one phase):
|
Phase Execution (load one phase):
|
||||||
├─ Phase 1: Explore & Plan → phases/01-issue-plan.md
|
├─ Phase 1: Explore & Plan → phases/01-issue-plan.md
|
||||||
@@ -210,35 +210,33 @@ function detectSource(input, flags) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Source Selection (AskUserQuestion)
|
### Source Selection (ASK_USER)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// When source cannot be auto-detected
|
// When source cannot be auto-detected
|
||||||
const answer = AskUserQuestion({
|
const answer = ASK_USER([{
|
||||||
questions: [{
|
id: "source",
|
||||||
question: "How would you like to create/manage issue solutions?",
|
type: "select",
|
||||||
header: "Source",
|
prompt: "How would you like to create/manage issue solutions?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{
|
||||||
{
|
label: "Explore & Plan (Recommended)",
|
||||||
label: "Explore & Plan (Recommended)",
|
description: "AI explores codebase and generates solutions for issues"
|
||||||
description: "AI explores codebase and generates solutions for issues"
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Convert from Artifact",
|
||||||
label: "Convert from Artifact",
|
description: "Convert existing lite-plan, workflow session, or markdown to solution"
|
||||||
description: "Convert existing lite-plan, workflow session, or markdown to solution"
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "From Brainstorm",
|
||||||
label: "From Brainstorm",
|
description: "Convert brainstorm session ideas into issue with solution"
|
||||||
description: "Convert brainstorm session ideas into issue with solution"
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Form Execution Queue",
|
||||||
label: "Form Execution Queue",
|
description: "Order bound solutions into execution queue for /issue:execute"
|
||||||
description: "Order bound solutions into execution queue for /issue:execute"
|
}
|
||||||
}
|
]
|
||||||
]
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Route based on selection
|
// Route based on selection
|
||||||
const sourceMap = {
|
const sourceMap = {
|
||||||
@@ -256,7 +254,7 @@ User Input (issue IDs / artifact path / session ID / flags)
|
|||||||
↓
|
↓
|
||||||
[Parse Flags + Auto-Detect Source]
|
[Parse Flags + Auto-Detect Source]
|
||||||
↓
|
↓
|
||||||
[Source Selection] ← AskUserQuestion (if needed)
|
[Source Selection] ← ASK_USER (if needed)
|
||||||
↓
|
↓
|
||||||
[Read Selected Phase Document]
|
[Read Selected Phase Document]
|
||||||
↓
|
↓
|
||||||
@@ -305,7 +303,7 @@ Phase-specific sub-tasks are attached when the phase executes (see individual ph
|
|||||||
|
|
||||||
| Error | Resolution |
|
| Error | Resolution |
|
||||||
|-------|------------|
|
|-------|------------|
|
||||||
| No source detected | Show AskUserQuestion with all 4 options |
|
| No source detected | Show ASK_USER with all 4 options |
|
||||||
| Invalid source type | Show available sources, re-prompt |
|
| Invalid source type | Show available sources, re-prompt |
|
||||||
| Phase execution fails | Report error, suggest manual intervention |
|
| Phase execution fails | Report error, suggest manual intervention |
|
||||||
| No pending issues (plan) | Suggest creating issues first |
|
| No pending issues (plan) | Suggest creating issues first |
|
||||||
@@ -317,19 +315,17 @@ After successful phase execution, recommend next action:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// After Plan/Convert/Brainstorm (solutions created)
|
// After Plan/Convert/Brainstorm (solutions created)
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "next_action",
|
||||||
question: "Solutions created. What next?",
|
type: "select",
|
||||||
header: "Next",
|
prompt: "Solutions created. What next?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Form Queue", description: "Order solutions for execution (/issue:queue)" },
|
||||||
{ label: "Form Queue", description: "Order solutions for execution (/issue:queue)" },
|
{ label: "Plan More Issues", description: "Continue creating solutions" },
|
||||||
{ label: "Plan More Issues", description: "Continue creating solutions" },
|
{ label: "View Issues", description: "Review issue details" },
|
||||||
{ label: "View Issues", description: "Review issue details" },
|
{ label: "Done", description: "Exit workflow" }
|
||||||
{ label: "Done", description: "Exit workflow" }
|
]
|
||||||
]
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// After Queue (queue formed)
|
// After Queue (queue formed)
|
||||||
// → Suggest /issue:execute directly
|
// → Suggest /issue:execute directly
|
||||||
|
|||||||
@@ -249,14 +249,12 @@ for (const pending of pendingSelections) {
|
|||||||
description: sol.description || sol.approach || 'No description'
|
description: sol.description || sol.approach || 'No description'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const answer = AskUserQuestion({
|
const answer = ASK_USER([{
|
||||||
questions: [{
|
id: pending.issue_id,
|
||||||
question: `Issue ${pending.issue_id}: which solution to bind?`,
|
type: "select",
|
||||||
header: pending.issue_id,
|
prompt: `Issue ${pending.issue_id}: which solution to bind?`,
|
||||||
options: options,
|
options: options
|
||||||
multiSelect: false
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
const selected = answer[Object.keys(answer)[0]];
|
const selected = answer[Object.keys(answer)[0]];
|
||||||
if (!selected || selected === 'Other') continue;
|
if (!selected || selected === 'Other') continue;
|
||||||
|
|||||||
@@ -582,19 +582,9 @@ ${solution.tasks.map(t => `- ${t.id}: ${t.title} [${t.action}]`).join('\n')}
|
|||||||
|
|
||||||
// Confirm if not auto mode
|
// Confirm if not auto mode
|
||||||
if (!flags.yes && !flags.y) {
|
if (!flags.yes && !flags.y) {
|
||||||
const confirm = AskUserQuestion({
|
const confirmed = CONFIRM(`Create solution for issue ${issueId} with ${solution.tasks.length} tasks?`); // BLOCKS (wait for user response)
|
||||||
questions: [{
|
|
||||||
question: `Create solution for issue ${issueId} with ${solution.tasks.length} tasks?`,
|
|
||||||
header: 'Confirm',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Yes, create solution', description: 'Create and bind solution' },
|
|
||||||
{ label: 'Cancel', description: 'Abort without changes' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!confirm.answers?.['Confirm']?.includes('Yes')) {
|
if (!confirmed) {
|
||||||
console.log('Cancelled.');
|
console.log('Cancelled.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,15 +251,13 @@ agentIds.forEach((agentId, i) => {
|
|||||||
```javascript
|
```javascript
|
||||||
if (allClarifications.length > 0) {
|
if (allClarifications.length > 0) {
|
||||||
for (const clarification of allClarifications) {
|
for (const clarification of allClarifications) {
|
||||||
// Present to user via AskUserQuestion
|
// Present to user via ASK_USER
|
||||||
const answer = AskUserQuestion({
|
const answer = ASK_USER([{
|
||||||
questions: [{
|
id: clarification.conflict_id,
|
||||||
question: `[${clarification.queue_id}] ${clarification.question}`,
|
type: "select",
|
||||||
header: clarification.conflict_id,
|
prompt: `[${clarification.queue_id}] ${clarification.question}`,
|
||||||
options: clarification.options,
|
options: clarification.options
|
||||||
multiSelect: false
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Re-spawn agent with user decision (original agent already closed)
|
// Re-spawn agent with user decision (original agent already closed)
|
||||||
// Create new agent with previous context + resolution
|
// Create new agent with previous context + resolution
|
||||||
@@ -345,22 +343,20 @@ ccw issue queue list --brief
|
|||||||
|
|
||||||
**Decision:**
|
**Decision:**
|
||||||
- If `active_queue_id` is null → `ccw issue queue switch <new-queue-id>` (activate new queue)
|
- If `active_queue_id` is null → `ccw issue queue switch <new-queue-id>` (activate new queue)
|
||||||
- If active queue exists → Use **AskUserQuestion** to prompt user
|
- If active queue exists → Use **ASK_USER** to prompt user
|
||||||
|
|
||||||
**AskUserQuestion:**
|
**ASK_USER:**
|
||||||
```javascript
|
```javascript
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "queue_action",
|
||||||
question: "Active queue exists. How would you like to proceed?",
|
type: "select",
|
||||||
header: "Queue Action",
|
prompt: "Active queue exists. How would you like to proceed?",
|
||||||
options: [
|
options: [
|
||||||
{ label: "Merge into existing queue", description: "Add new items to active queue, delete new queue" },
|
{ label: "Merge into existing queue", description: "Add new items to active queue, delete new queue" },
|
||||||
{ label: "Use new queue", description: "Switch to new queue, keep existing in history" },
|
{ label: "Use new queue", description: "Switch to new queue, keep existing in history" },
|
||||||
{ label: "Cancel", description: "Delete new queue, keep existing active" }
|
{ label: "Cancel", description: "Delete new queue, keep existing active" }
|
||||||
],
|
]
|
||||||
multiSelect: false
|
}]); // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Action Commands:**
|
**Action Commands:**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Seven-phase workflow: **Context collection** → **Topic analysis** → **Role selection** → **Role questions** → **Conflict resolution** → **Final check** → **Generate specification**
|
Seven-phase workflow: **Context collection** → **Topic analysis** → **Role selection** → **Role questions** → **Conflict resolution** → **Final check** → **Generate specification**
|
||||||
|
|
||||||
All user interactions use AskUserQuestion tool (max 4 questions per call, multi-round).
|
All user interactions use ASK_USER / CONFIRM pseudo-code (implemented via AskUserQuestion tool) (max 4 questions per call, multi-round).
|
||||||
|
|
||||||
**Input**: `"GOAL: [objective] SCOPE: [boundaries] CONTEXT: [background]" [--count N]`
|
**Input**: `"GOAL: [objective] SCOPE: [boundaries] CONTEXT: [background]" [--count N]`
|
||||||
**Output**: `.workflow/active/WFS-{topic}/.brainstorming/guidance-specification.md`
|
**Output**: `.workflow/active/WFS-{topic}/.brainstorming/guidance-specification.md`
|
||||||
@@ -18,7 +18,7 @@ All user interactions use AskUserQuestion tool (max 4 questions per call, multi-
|
|||||||
|
|
||||||
### Phase Summary
|
### Phase Summary
|
||||||
|
|
||||||
| Phase | Goal | AskUserQuestion | Storage |
|
| Phase | Goal | ASK_USER | Storage |
|
||||||
|-------|------|-----------------|---------|
|
|-------|------|-----------------|---------|
|
||||||
| 0 | Context collection | - | context-package.json |
|
| 0 | Context collection | - | context-package.json |
|
||||||
| 1 | Topic analysis | 2-4 questions | intent_context |
|
| 1 | Topic analysis | 2-4 questions | intent_context |
|
||||||
@@ -28,35 +28,30 @@ All user interactions use AskUserQuestion tool (max 4 questions per call, multi-
|
|||||||
| 4.5 | Final check | progressive rounds | additional_decisions |
|
| 4.5 | Final check | progressive rounds | additional_decisions |
|
||||||
| 5 | Generate spec | - | guidance-specification.md |
|
| 5 | Generate spec | - | guidance-specification.md |
|
||||||
|
|
||||||
### AskUserQuestion Pattern
|
### ASK_USER Pattern
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Single-select (Phase 1, 3, 4)
|
// Single-select (Phase 1, 3, 4)
|
||||||
AskUserQuestion({
|
ASK_USER([
|
||||||
questions: [
|
{
|
||||||
{
|
id: "{短标签}", // max 12 chars
|
||||||
question: "{问题文本}",
|
type: "select",
|
||||||
header: "{短标签}", // max 12 chars
|
prompt: "{问题文本}",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "{选项}", description: "{说明和影响}" },
|
||||||
{ label: "{选项}", description: "{说明和影响}" },
|
{ label: "{选项}", description: "{说明和影响}" },
|
||||||
{ label: "{选项}", description: "{说明和影响}" },
|
{ label: "{选项}", description: "{说明和影响}" }
|
||||||
{ label: "{选项}", description: "{说明和影响}" }
|
]
|
||||||
]
|
}
|
||||||
}
|
// ... max 4 questions per call
|
||||||
// ... max 4 questions per call
|
]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Multi-select (Phase 2)
|
// Multi-select (Phase 2)
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "角色选择", type: "multi-select",
|
||||||
question: "请选择 {count} 个角色",
|
prompt: "请选择 {count} 个角色",
|
||||||
header: "角色选择",
|
options: [/* max 4 options per call */]
|
||||||
multiSelect: true,
|
}]) // BLOCKS (wait for user response)
|
||||||
options: [/* max 4 options per call */]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Multi-Round Execution
|
### Multi-Round Execution
|
||||||
@@ -65,7 +60,7 @@ AskUserQuestion({
|
|||||||
const BATCH_SIZE = 4;
|
const BATCH_SIZE = 4;
|
||||||
for (let i = 0; i < allQuestions.length; i += BATCH_SIZE) {
|
for (let i = 0; i < allQuestions.length; i += BATCH_SIZE) {
|
||||||
const batch = allQuestions.slice(i, i + BATCH_SIZE);
|
const batch = allQuestions.slice(i, i + BATCH_SIZE);
|
||||||
AskUserQuestion({ questions: batch });
|
ASK_USER(batch); // BLOCKS (wait for user response)
|
||||||
// Store responses before next round
|
// Store responses before next round
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -143,34 +138,30 @@ if (contextResult.timed_out) {
|
|||||||
1. Load Phase 0 context (tech_stack, modules, conflict_risk)
|
1. Load Phase 0 context (tech_stack, modules, conflict_risk)
|
||||||
2. Deep topic analysis (entities, challenges, constraints, metrics)
|
2. Deep topic analysis (entities, challenges, constraints, metrics)
|
||||||
3. Generate 2-4 context-aware probing questions
|
3. Generate 2-4 context-aware probing questions
|
||||||
4. AskUserQuestion → Store to `session.intent_context`
|
4. ASK_USER → Store to `session.intent_context`
|
||||||
|
|
||||||
**Example**:
|
**Example**:
|
||||||
```javascript
|
```javascript
|
||||||
AskUserQuestion({
|
ASK_USER([
|
||||||
questions: [
|
{
|
||||||
{
|
id: "核心挑战", type: "select",
|
||||||
question: "实时协作平台的主要技术挑战?",
|
prompt: "实时协作平台的主要技术挑战?",
|
||||||
header: "核心挑战",
|
options: [
|
||||||
multiSelect: false,
|
{ label: "实时数据同步", description: "100+用户同时在线,状态同步复杂度高" },
|
||||||
options: [
|
{ label: "可扩展性架构", description: "用户规模增长时的系统扩展能力" },
|
||||||
{ label: "实时数据同步", description: "100+用户同时在线,状态同步复杂度高" },
|
{ label: "冲突解决机制", description: "多用户同时编辑的冲突处理策略" }
|
||||||
{ label: "可扩展性架构", description: "用户规模增长时的系统扩展能力" },
|
]
|
||||||
{ label: "冲突解决机制", description: "多用户同时编辑的冲突处理策略" }
|
},
|
||||||
]
|
{
|
||||||
},
|
id: "优先级", type: "select",
|
||||||
{
|
prompt: "MVP阶段最关注的指标?",
|
||||||
question: "MVP阶段最关注的指标?",
|
options: [
|
||||||
header: "优先级",
|
{ label: "功能完整性", description: "实现所有核心功能" },
|
||||||
multiSelect: false,
|
{ label: "用户体验", description: "流畅的交互体验和响应速度" },
|
||||||
options: [
|
{ label: "系统稳定性", description: "高可用性和数据一致性" }
|
||||||
{ label: "功能完整性", description: "实现所有核心功能" },
|
]
|
||||||
{ label: "用户体验", description: "流畅的交互体验和响应速度" },
|
}
|
||||||
{ label: "系统稳定性", description: "高可用性和数据一致性" }
|
]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**⚠️ CRITICAL**: Questions MUST reference topic keywords. Generic "Project type?" violates dynamic generation.
|
**⚠️ CRITICAL**: Questions MUST reference topic keywords. Generic "Project type?" violates dynamic generation.
|
||||||
@@ -183,24 +174,21 @@ AskUserQuestion({
|
|||||||
|
|
||||||
**Steps**:
|
**Steps**:
|
||||||
1. Analyze Phase 1 keywords → Recommend count+2 roles with rationale
|
1. Analyze Phase 1 keywords → Recommend count+2 roles with rationale
|
||||||
2. AskUserQuestion (multiSelect=true) → Store to `session.selected_roles`
|
2. ASK_USER (type=multi-select) → Store to `session.selected_roles`
|
||||||
3. If count+2 > 4, split into multiple rounds
|
3. If count+2 > 4, split into multiple rounds
|
||||||
|
|
||||||
**Example**:
|
**Example**:
|
||||||
```javascript
|
```javascript
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "角色选择", type: "multi-select",
|
||||||
question: "请选择 3 个角色参与头脑风暴分析",
|
prompt: "请选择 3 个角色参与头脑风暴分析",
|
||||||
header: "角色选择",
|
options: [
|
||||||
multiSelect: true,
|
{ label: "system-architect", description: "实时同步架构设计和技术选型" },
|
||||||
options: [
|
{ label: "ui-designer", description: "协作界面用户体验和状态展示" },
|
||||||
{ label: "system-architect", description: "实时同步架构设计和技术选型" },
|
{ label: "product-manager", description: "功能优先级和MVP范围决策" },
|
||||||
{ label: "ui-designer", description: "协作界面用户体验和状态展示" },
|
{ label: "data-architect", description: "数据同步模型和存储方案设计" }
|
||||||
{ label: "product-manager", description: "功能优先级和MVP范围决策" },
|
]
|
||||||
{ label: "data-architect", description: "数据同步模型和存储方案设计" }
|
}]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**⚠️ CRITICAL**: User MUST interact. NEVER auto-select without confirmation.
|
**⚠️ CRITICAL**: User MUST interact. NEVER auto-select without confirmation.
|
||||||
@@ -213,36 +201,32 @@ AskUserQuestion({
|
|||||||
1. FOR each selected role:
|
1. FOR each selected role:
|
||||||
- Map Phase 1 challenges to role domain
|
- Map Phase 1 challenges to role domain
|
||||||
- Generate 3-4 questions (implementation depth, trade-offs, edge cases)
|
- Generate 3-4 questions (implementation depth, trade-offs, edge cases)
|
||||||
- AskUserQuestion per role → Store to `session.role_decisions[role]`
|
- ASK_USER per role → Store to `session.role_decisions[role]`
|
||||||
2. Process roles sequentially (one at a time for clarity)
|
2. Process roles sequentially (one at a time for clarity)
|
||||||
3. If role needs > 4 questions, split into multiple rounds
|
3. If role needs > 4 questions, split into multiple rounds
|
||||||
|
|
||||||
**Example** (system-architect):
|
**Example** (system-architect):
|
||||||
```javascript
|
```javascript
|
||||||
AskUserQuestion({
|
ASK_USER([
|
||||||
questions: [
|
{
|
||||||
{
|
id: "状态同步", type: "select",
|
||||||
question: "100+ 用户实时状态同步方案?",
|
prompt: "100+ 用户实时状态同步方案?",
|
||||||
header: "状态同步",
|
options: [
|
||||||
multiSelect: false,
|
{ label: "Event Sourcing", description: "完整事件历史,支持回溯,存储成本高" },
|
||||||
options: [
|
{ label: "集中式状态管理", description: "实现简单,单点瓶颈风险" },
|
||||||
{ label: "Event Sourcing", description: "完整事件历史,支持回溯,存储成本高" },
|
{ label: "CRDT", description: "去中心化,自动合并,学习曲线陡" }
|
||||||
{ label: "集中式状态管理", description: "实现简单,单点瓶颈风险" },
|
]
|
||||||
{ label: "CRDT", description: "去中心化,自动合并,学习曲线陡" }
|
},
|
||||||
]
|
{
|
||||||
},
|
id: "冲突解决", type: "select",
|
||||||
{
|
prompt: "两个用户同时编辑冲突如何解决?",
|
||||||
question: "两个用户同时编辑冲突如何解决?",
|
options: [
|
||||||
header: "冲突解决",
|
{ label: "自动合并", description: "用户无感知,可能产生意外结果" },
|
||||||
multiSelect: false,
|
{ label: "手动解决", description: "用户控制,增加交互复杂度" },
|
||||||
options: [
|
{ label: "版本控制", description: "保留历史,需要分支管理" }
|
||||||
{ label: "自动合并", description: "用户无感知,可能产生意外结果" },
|
]
|
||||||
{ label: "手动解决", description: "用户控制,增加交互复杂度" },
|
}
|
||||||
{ label: "版本控制", description: "保留历史,需要分支管理" }
|
]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 4: Conflict Resolution
|
### Phase 4: Conflict Resolution
|
||||||
@@ -255,23 +239,20 @@ AskUserQuestion({
|
|||||||
- Missing integration (e.g., "Optimistic updates" but no conflict handling)
|
- Missing integration (e.g., "Optimistic updates" but no conflict handling)
|
||||||
- Implicit dependencies (e.g., "Live cursors" but no auth defined)
|
- Implicit dependencies (e.g., "Live cursors" but no auth defined)
|
||||||
2. Generate clarification questions referencing SPECIFIC Phase 3 choices
|
2. Generate clarification questions referencing SPECIFIC Phase 3 choices
|
||||||
3. AskUserQuestion (max 4 per call, multi-round) → Store to `session.cross_role_decisions`
|
3. ASK_USER (max 4 per call, multi-round) → Store to `session.cross_role_decisions`
|
||||||
4. If NO conflicts: Skip Phase 4 (inform user: "未检测到跨角色冲突,跳过Phase 4")
|
4. If NO conflicts: Skip Phase 4 (inform user: "未检测到跨角色冲突,跳过Phase 4")
|
||||||
|
|
||||||
**Example**:
|
**Example**:
|
||||||
```javascript
|
```javascript
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "架构冲突", type: "select",
|
||||||
question: "CRDT 与 UI 回滚期望冲突,如何解决?\n背景:system-architect选择CRDT,ui-designer期望回滚UI",
|
prompt: "CRDT 与 UI 回滚期望冲突,如何解决?\n背景:system-architect选择CRDT,ui-designer期望回滚UI",
|
||||||
header: "架构冲突",
|
options: [
|
||||||
multiSelect: false,
|
{ label: "采用 CRDT", description: "保持去中心化,调整UI期望" },
|
||||||
options: [
|
{ label: "显示合并界面", description: "增加用户交互,展示冲突详情" },
|
||||||
{ label: "采用 CRDT", description: "保持去中心化,调整UI期望" },
|
{ label: "切换到 OT", description: "支持回滚,增加服务器复杂度" }
|
||||||
{ label: "显示合并界面", description: "增加用户交互,展示冲突详情" },
|
]
|
||||||
{ label: "切换到 OT", description: "支持回滚,增加服务器复杂度" }
|
}]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 4.5: Final Clarification
|
### Phase 4.5: Final Clarification
|
||||||
@@ -281,22 +262,13 @@ AskUserQuestion({
|
|||||||
**Steps**:
|
**Steps**:
|
||||||
1. Ask initial check:
|
1. Ask initial check:
|
||||||
```javascript
|
```javascript
|
||||||
AskUserQuestion({
|
CONFIRM("在生成最终规范之前,是否有前面未澄清的重点需要补充?") // BLOCKS (wait for user response)
|
||||||
questions: [{
|
// Returns: "无需补充" (前面的讨论已经足够完整) | "需要补充" (还有重要内容需要澄清)
|
||||||
question: "在生成最终规范之前,是否有前面未澄清的重点需要补充?",
|
|
||||||
header: "补充确认",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "无需补充", description: "前面的讨论已经足够完整" },
|
|
||||||
{ label: "需要补充", description: "还有重要内容需要澄清" }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
2. If "需要补充":
|
2. If "需要补充":
|
||||||
- Analyze user's additional points
|
- Analyze user's additional points
|
||||||
- Generate progressive questions (not role-bound, interconnected)
|
- Generate progressive questions (not role-bound, interconnected)
|
||||||
- AskUserQuestion (max 4 per round) → Store to `session.additional_decisions`
|
- ASK_USER (max 4 per round) → Store to `session.additional_decisions`
|
||||||
- Repeat until user confirms completion
|
- Repeat until user confirms completion
|
||||||
3. If "无需补充": Proceed to Phase 5
|
3. If "无需补充": Proceed to Phase 5
|
||||||
|
|
||||||
|
|||||||
@@ -231,17 +231,14 @@ for (let i = 0; i < questions.length; i += BATCH_SIZE) {
|
|||||||
|
|
||||||
console.log(`\n[Round ${currentRound}/${totalRounds}] ${config.title} 上下文询问\n`);
|
console.log(`\n[Round ${currentRound}/${totalRounds}] ${config.title} 上下文询问\n`);
|
||||||
|
|
||||||
AskUserQuestion({
|
ASK_USER(batch.map(q => ({
|
||||||
questions: batch.map(q => ({
|
id: q.category.substring(0, 12), type: "select",
|
||||||
question: q.question,
|
prompt: q.question,
|
||||||
header: q.category.substring(0, 12),
|
options: q.options.map(opt => ({
|
||||||
multiSelect: false,
|
label: opt.label,
|
||||||
options: q.options.map(opt => ({
|
description: opt.description
|
||||||
label: opt.label,
|
|
||||||
description: opt.description
|
|
||||||
}))
|
|
||||||
}))
|
}))
|
||||||
});
|
}))); // BLOCKS (wait for user response)
|
||||||
|
|
||||||
// Store responses before next round
|
// Store responses before next round
|
||||||
for (const answer of responses) {
|
for (const answer of responses) {
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ Six-phase workflow to eliminate ambiguities and enhance conceptual depth in role
|
|||||||
|
|
||||||
**Phase 1-2**: Session detection → File discovery → Path preparation
|
**Phase 1-2**: Session detection → File discovery → Path preparation
|
||||||
**Phase 3A**: Cross-role analysis agent → Generate recommendations
|
**Phase 3A**: Cross-role analysis agent → Generate recommendations
|
||||||
**Phase 4**: User selects enhancements → User answers clarifications (via AskUserQuestion)
|
**Phase 4**: User selects enhancements → User answers clarifications (via ASK_USER)
|
||||||
**Phase 5**: Parallel update agents (one per role)
|
**Phase 5**: Parallel update agents (one per role)
|
||||||
**Phase 6**: Context package update → Metadata update → Completion report
|
**Phase 6**: Context package update → Metadata update → Completion report
|
||||||
|
|
||||||
All user interactions use AskUserQuestion tool (max 4 questions per call, multi-round).
|
All user interactions use ASK_USER / CONFIRM pseudo-code (implemented via AskUserQuestion tool) (max 4 questions per call, multi-round).
|
||||||
|
|
||||||
**Document Flow**:
|
**Document Flow**:
|
||||||
- Input: `[role]/analysis*.md`, `guidance-specification.md`, session metadata
|
- Input: `[role]/analysis*.md`, `guidance-specification.md`, session metadata
|
||||||
@@ -25,41 +25,35 @@ All user interactions use AskUserQuestion tool (max 4 questions per call, multi-
|
|||||||
| 1 | Session detection | Main flow | session_id, brainstorm_dir |
|
| 1 | Session detection | Main flow | session_id, brainstorm_dir |
|
||||||
| 2 | File discovery | Main flow | role_analysis_paths |
|
| 2 | File discovery | Main flow | role_analysis_paths |
|
||||||
| 3A | Cross-role analysis | spawn_agent | enhancement_recommendations |
|
| 3A | Cross-role analysis | spawn_agent | enhancement_recommendations |
|
||||||
| 4 | User interaction | Main flow + AskUserQuestion | update_plan |
|
| 4 | User interaction | Main flow + ASK_USER | update_plan |
|
||||||
| 5 | Document updates | spawn_agent[] (parallel) | Updated analysis*.md |
|
| 5 | Document updates | spawn_agent[] (parallel) | Updated analysis*.md |
|
||||||
| 6 | Finalization | Main flow | context-package.json, report |
|
| 6 | Finalization | Main flow | context-package.json, report |
|
||||||
|
|
||||||
### AskUserQuestion Pattern
|
### ASK_USER Pattern
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Enhancement selection (multi-select)
|
// Enhancement selection (multi-select)
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "改进选择", type: "multi-select",
|
||||||
question: "请选择要应用的改进建议",
|
prompt: "请选择要应用的改进建议",
|
||||||
header: "改进选择",
|
options: [
|
||||||
multiSelect: true,
|
{ label: "EP-001: API Contract", description: "添加详细的请求/响应 schema 定义" },
|
||||||
options: [
|
{ label: "EP-002: User Intent", description: "明确用户需求优先级和验收标准" }
|
||||||
{ label: "EP-001: API Contract", description: "添加详细的请求/响应 schema 定义" },
|
]
|
||||||
{ label: "EP-002: User Intent", description: "明确用户需求优先级和验收标准" }
|
}]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Clarification questions (single-select, multi-round)
|
// Clarification questions (single-select, multi-round)
|
||||||
AskUserQuestion({
|
ASK_USER([
|
||||||
questions: [
|
{
|
||||||
{
|
id: "用户意图", type: "select",
|
||||||
question: "MVP 阶段的核心目标是什么?",
|
prompt: "MVP 阶段的核心目标是什么?",
|
||||||
header: "用户意图",
|
options: [
|
||||||
multiSelect: false,
|
{ label: "快速验证", description: "最小功能集,快速上线获取反馈" },
|
||||||
options: [
|
{ label: "技术壁垒", description: "完善架构,为长期发展打基础" },
|
||||||
{ label: "快速验证", description: "最小功能集,快速上线获取反馈" },
|
{ label: "功能完整", description: "覆盖所有规划功能,延迟上线" }
|
||||||
{ label: "技术壁垒", description: "完善架构,为长期发展打基础" },
|
]
|
||||||
{ label: "功能完整", description: "覆盖所有规划功能,延迟上线" }
|
}
|
||||||
]
|
]) // BLOCKS (wait for user response)
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -150,7 +144,7 @@ close_agent({ id: analysisAgentId });
|
|||||||
|
|
||||||
### Phase 4: User Interaction
|
### Phase 4: User Interaction
|
||||||
|
|
||||||
**All interactions via AskUserQuestion (Chinese questions)**
|
**All interactions via ASK_USER (Chinese questions)**
|
||||||
|
|
||||||
#### Step 1: Enhancement Selection
|
#### Step 1: Enhancement Selection
|
||||||
|
|
||||||
@@ -163,17 +157,14 @@ const selectedEnhancements = [];
|
|||||||
for (let i = 0; i < enhancements.length; i += BATCH_SIZE) {
|
for (let i = 0; i < enhancements.length; i += BATCH_SIZE) {
|
||||||
const batch = enhancements.slice(i, i + BATCH_SIZE);
|
const batch = enhancements.slice(i, i + BATCH_SIZE);
|
||||||
|
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "改进选择", type: "multi-select",
|
||||||
question: `请选择要应用的改进建议 (第${Math.floor(i/BATCH_SIZE)+1}轮)`,
|
prompt: `请选择要应用的改进建议 (第${Math.floor(i/BATCH_SIZE)+1}轮)`,
|
||||||
header: "改进选择",
|
options: batch.map(ep => ({
|
||||||
multiSelect: true,
|
label: `${ep.id}: ${ep.title}`,
|
||||||
options: batch.map(ep => ({
|
description: `影响: ${ep.affected_roles.join(', ')} | ${ep.enhancement}`
|
||||||
label: `${ep.id}: ${ep.title}`,
|
}))
|
||||||
description: `影响: ${ep.affected_roles.join(', ')} | ${ep.enhancement}`
|
}]) // BLOCKS (wait for user response)
|
||||||
}))
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
// Store selections before next round
|
// Store selections before next round
|
||||||
selectedEnhancements.push(...user_selections);
|
selectedEnhancements.push(...user_selections);
|
||||||
@@ -197,17 +188,14 @@ for (let i = 0; i < clarifications.length; i += BATCH_SIZE) {
|
|||||||
const currentRound = Math.floor(i / BATCH_SIZE) + 1;
|
const currentRound = Math.floor(i / BATCH_SIZE) + 1;
|
||||||
const totalRounds = Math.ceil(clarifications.length / BATCH_SIZE);
|
const totalRounds = Math.ceil(clarifications.length / BATCH_SIZE);
|
||||||
|
|
||||||
AskUserQuestion({
|
ASK_USER(batch.map(q => ({
|
||||||
questions: batch.map(q => ({
|
id: q.category.substring(0, 12), type: "select",
|
||||||
question: q.question,
|
prompt: q.question,
|
||||||
header: q.category.substring(0, 12),
|
options: q.options.map(opt => ({
|
||||||
multiSelect: false,
|
label: opt.label,
|
||||||
options: q.options.map(opt => ({
|
description: opt.description
|
||||||
label: opt.label,
|
|
||||||
description: opt.description
|
|
||||||
}))
|
|
||||||
}))
|
}))
|
||||||
})
|
}))) // BLOCKS (wait for user response)
|
||||||
|
|
||||||
// Store answers before next round
|
// Store answers before next round
|
||||||
batch.forEach((q, idx) => {
|
batch.forEach((q, idx) => {
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ Phase 1: Discovery
|
|||||||
└─ Decision:
|
└─ Decision:
|
||||||
├─ count=0 → ERROR: No active sessions
|
├─ count=0 → ERROR: No active sessions
|
||||||
├─ count=1 → Auto-select session → Phase 2
|
├─ count=1 → Auto-select session → Phase 2
|
||||||
└─ count>1 → AskUserQuestion (max 4 options) → Phase 2
|
└─ count>1 → ASK_USER (max 4 options) → Phase 2
|
||||||
|
|
||||||
Phase 2: Planning Document Validation
|
Phase 2: Planning Document Validation
|
||||||
├─ Check IMPL_PLAN.md exists
|
├─ Check IMPL_PLAN.md exists
|
||||||
@@ -117,7 +117,7 @@ Phase 4: Execution Strategy & Task Execution
|
|||||||
Phase 5: Completion
|
Phase 5: Completion
|
||||||
├─ Update task statuses in JSON files
|
├─ Update task statuses in JSON files
|
||||||
├─ Generate summaries
|
├─ Generate summaries
|
||||||
└─ AskUserQuestion: Choose next step
|
└─ ASK_USER: Choose next step
|
||||||
├─ "Enter Review" → workflow:review
|
├─ "Enter Review" → workflow:review
|
||||||
└─ "Complete Session" → workflow:session:complete
|
└─ "Complete Session" → workflow:session:complete
|
||||||
|
|
||||||
@@ -270,23 +270,20 @@ if (autoYes) {
|
|||||||
selectedSessionId = firstSession.id
|
selectedSessionId = firstSession.id
|
||||||
// Continue to Phase 2
|
// Continue to Phase 2
|
||||||
} else {
|
} else {
|
||||||
// Interactive mode: Use AskUserQuestion to present formatted options (max 4 options shown)
|
// Interactive mode: Use ASK_USER to present formatted options (max 4 options shown)
|
||||||
// If more than 4 sessions, show most recent 4 with "Other" option for manual input
|
// If more than 4 sessions, show most recent 4 with "Other" option for manual input
|
||||||
const sessions = getActiveSessions() // sorted by last modified
|
const sessions = getActiveSessions() // sorted by last modified
|
||||||
const displaySessions = sessions.slice(0, 4)
|
const displaySessions = sessions.slice(0, 4)
|
||||||
|
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "session", type: "select",
|
||||||
question: "Multiple active sessions detected. Select one:",
|
prompt: "Multiple active sessions detected. Select one:",
|
||||||
header: "Session",
|
options: displaySessions.map(s => ({
|
||||||
multiSelect: false,
|
label: s.id,
|
||||||
options: displaySessions.map(s => ({
|
description: `${s.project} | ${s.progress}`
|
||||||
label: s.id,
|
}))
|
||||||
description: `${s.project} | ${s.progress}`
|
// Note: User can select "Other" to manually enter session ID
|
||||||
}))
|
}]) // BLOCKS (wait for user response)
|
||||||
// Note: User can select "Other" to manually enter session ID
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -415,23 +412,20 @@ if (autoYes) {
|
|||||||
// Execute: workflow:session:complete --yes
|
// Execute: workflow:session:complete --yes
|
||||||
} else {
|
} else {
|
||||||
// Interactive mode: Ask user
|
// Interactive mode: Ask user
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "next_step", type: "select",
|
||||||
question: "All tasks completed. What would you like to do next?",
|
prompt: "All tasks completed. What would you like to do next?",
|
||||||
header: "Next Step",
|
options: [
|
||||||
multiSelect: false,
|
{
|
||||||
options: [
|
label: "Enter Review",
|
||||||
{
|
description: "Run specialized review (security/architecture/quality/action-items)"
|
||||||
label: "Enter Review",
|
},
|
||||||
description: "Run specialized review (security/architecture/quality/action-items)"
|
{
|
||||||
},
|
label: "Complete Session",
|
||||||
{
|
description: "Archive session and update manifest"
|
||||||
label: "Complete Session",
|
}
|
||||||
description: "Archive session and update manifest"
|
]
|
||||||
}
|
}]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -38,20 +38,22 @@ When `--yes` or `-y`: Auto-approve plan, skip clarifications, use default execut
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```bash
|
||||||
Skill(skill="workflow-lite-plan-execute", args="<task description>")
|
$workflow-lite-plan-execute <task description>
|
||||||
Skill(skill="workflow-lite-plan-execute", args="[FLAGS] \"<task description>\"")
|
$workflow-lite-plan-execute [FLAGS] "<task description>"
|
||||||
|
|
||||||
# Flags
|
# Flags
|
||||||
-y, --yes Skip all confirmations (auto mode)
|
-y, --yes Skip all confirmations (auto mode)
|
||||||
-e, --explore Force exploration phase
|
-e, --explore Force exploration phase
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
Skill(skill="workflow-lite-plan-execute", args="\"Implement JWT authentication\"")
|
$workflow-lite-plan-execute "Implement JWT authentication"
|
||||||
Skill(skill="workflow-lite-plan-execute", args="-y \"Add user profile page\"")
|
$workflow-lite-plan-execute -y "Add user profile page"
|
||||||
Skill(skill="workflow-lite-plan-execute", args="-e \"Refactor payment module\"")
|
$workflow-lite-plan-execute -e "Refactor payment module"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> **Implementation sketch**: 编排器内部使用 `Skill(skill="workflow-lite-plan-execute", args="...")` 接口调用,此为伪代码示意,非命令行语法。
|
||||||
|
|
||||||
## Subagent API Reference
|
## Subagent API Reference
|
||||||
|
|
||||||
### spawn_agent
|
### spawn_agent
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ Phase 2: Clarification (optional, multi-round)
|
|||||||
├─ Aggregate clarification_needs from all exploration angles
|
├─ Aggregate clarification_needs from all exploration angles
|
||||||
├─ Deduplicate similar questions
|
├─ Deduplicate similar questions
|
||||||
└─ Decision:
|
└─ Decision:
|
||||||
├─ Has clarifications → AskUserQuestion (max 4 questions per round, multiple rounds allowed)
|
├─ Has clarifications → ASK_USER (max 4 questions per round, multiple rounds allowed)
|
||||||
└─ No clarifications → Skip to Phase 3
|
└─ No clarifications → Skip to Phase 3
|
||||||
|
|
||||||
Phase 3: Planning (NO CODE EXECUTION - planning only)
|
Phase 3: Planning (NO CODE EXECUTION - planning only)
|
||||||
@@ -79,7 +79,7 @@ Phase 3: Planning (NO CODE EXECUTION - planning only)
|
|||||||
|
|
||||||
Phase 4: Confirmation & Selection
|
Phase 4: Confirmation & Selection
|
||||||
├─ Display plan summary (tasks, complexity, estimated time)
|
├─ Display plan summary (tasks, complexity, estimated time)
|
||||||
└─ AskUserQuestion:
|
└─ ASK_USER:
|
||||||
├─ Confirm: Allow / Modify / Cancel
|
├─ Confirm: Allow / Modify / Cancel
|
||||||
├─ Execution: Agent / Codex / Auto
|
├─ Execution: Agent / Codex / Auto
|
||||||
└─ Review: Gemini / Agent / Skip
|
└─ Review: Gemini / Agent / Skip
|
||||||
@@ -457,7 +457,7 @@ This log will be fully consumed by planning phase, then refined for execution.
|
|||||||
|
|
||||||
**Skip if**: No exploration or `clarification_needs` is empty across all explorations
|
**Skip if**: No exploration or `clarification_needs` is empty across all explorations
|
||||||
|
|
||||||
**⚠️ CRITICAL**: AskUserQuestion tool limits max 4 questions per call. **MUST execute multiple rounds** to exhaust all clarification needs - do NOT stop at round 1.
|
**⚠️ CRITICAL**: ASK_USER tool limits max 4 questions per call. **MUST execute multiple rounds** to exhaust all clarification needs - do NOT stop at round 1.
|
||||||
|
|
||||||
**Aggregate clarification needs from all exploration angles**:
|
**Aggregate clarification needs from all exploration angles**:
|
||||||
```javascript
|
```javascript
|
||||||
@@ -506,17 +506,16 @@ if (autoYes) {
|
|||||||
|
|
||||||
console.log(`### Clarification Round ${currentRound}/${totalRounds}`)
|
console.log(`### Clarification Round ${currentRound}/${totalRounds}`)
|
||||||
|
|
||||||
AskUserQuestion({
|
ASK_USER(batch.map(need => ({
|
||||||
questions: batch.map(need => ({
|
id: `clarify-${need.source_angle}`,
|
||||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
type: "select",
|
||||||
header: need.source_angle.substring(0, 12),
|
prompt: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||||
multiSelect: false,
|
options: need.options.map((opt, index) => ({
|
||||||
options: need.options.map((opt, index) => ({
|
label: need.recommended === index ? `${opt} ★` : opt,
|
||||||
label: need.recommended === index ? `${opt} ★` : opt,
|
description: need.recommended === index ? `Recommended` : `Use ${opt}`
|
||||||
description: need.recommended === index ? `Recommended` : `Use ${opt}`
|
})),
|
||||||
}))
|
default: need.recommended
|
||||||
}))
|
}))) // BLOCKS (wait for user response)
|
||||||
})
|
|
||||||
|
|
||||||
// Store batch responses in clarificationContext before next round
|
// Store batch responses in clarificationContext before next round
|
||||||
}
|
}
|
||||||
@@ -978,41 +977,42 @@ if (autoYes) {
|
|||||||
} else {
|
} else {
|
||||||
// Interactive mode: Ask user
|
// Interactive mode: Ask user
|
||||||
// Note: Execution "Other" option allows specifying CLI tools from ~/.claude/cli-tools.json
|
// Note: Execution "Other" option allows specifying CLI tools from ~/.claude/cli-tools.json
|
||||||
userSelection = AskUserQuestion({
|
userSelection = ASK_USER([
|
||||||
questions: [
|
{
|
||||||
{
|
id: "confirm",
|
||||||
question: `Confirm plan? (${plan.tasks.length} tasks, ${plan.complexity})`,
|
type: "select",
|
||||||
header: "Confirm",
|
prompt: `Confirm plan? (${plan.tasks.length} tasks, ${plan.complexity})`,
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Allow", description: "Proceed as-is" },
|
||||||
{ label: "Allow", description: "Proceed as-is" },
|
{ label: "Modify", description: "Adjust before execution" },
|
||||||
{ label: "Modify", description: "Adjust before execution" },
|
{ label: "Cancel", description: "Abort workflow" }
|
||||||
{ label: "Cancel", description: "Abort workflow" }
|
],
|
||||||
]
|
default: "Allow"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "Execution method:",
|
id: "execution",
|
||||||
header: "Execution",
|
type: "select",
|
||||||
multiSelect: false,
|
prompt: "Execution method:",
|
||||||
options: [
|
options: [
|
||||||
{ label: "Agent", description: "@code-developer agent" },
|
{ label: "Agent", description: "@code-developer agent" },
|
||||||
{ label: "Codex", description: "codex CLI tool" },
|
{ label: "Codex", description: "codex CLI tool" },
|
||||||
{ label: "Auto", description: `Auto: ${plan.complexity === 'Low' ? 'Agent' : 'Codex'}` }
|
{ label: "Auto", description: `Auto: ${plan.complexity === 'Low' ? 'Agent' : 'Codex'}` }
|
||||||
]
|
],
|
||||||
},
|
default: "Auto"
|
||||||
{
|
},
|
||||||
question: "Code review after execution?",
|
{
|
||||||
header: "Review",
|
id: "review",
|
||||||
multiSelect: false,
|
type: "select",
|
||||||
options: [
|
prompt: "Code review after execution?",
|
||||||
{ label: "Gemini Review", description: "Gemini CLI review" },
|
options: [
|
||||||
{ label: "Codex Review", description: "Git-aware review (prompt OR --uncommitted)" },
|
{ label: "Gemini Review", description: "Gemini CLI review" },
|
||||||
{ label: "Agent Review", description: "@code-reviewer agent" },
|
{ label: "Codex Review", description: "Git-aware review (prompt OR --uncommitted)" },
|
||||||
{ label: "Skip", description: "No review" }
|
{ label: "Agent Review", description: "@code-reviewer agent" },
|
||||||
]
|
{ label: "Skip", description: "No review" }
|
||||||
}
|
],
|
||||||
]
|
default: "Skip"
|
||||||
})
|
}
|
||||||
|
]) // BLOCKS (wait for user response)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ Flexible task execution phase supporting three input modes: in-memory plan (from
|
|||||||
**Behavior**:
|
**Behavior**:
|
||||||
- Store prompt as `originalUserInput`
|
- Store prompt as `originalUserInput`
|
||||||
- Create simple execution plan from prompt
|
- Create simple execution plan from prompt
|
||||||
- AskUserQuestion: Select execution method (Agent/Codex/Auto)
|
- ASK_USER: Select execution method (Agent/Codex/Auto)
|
||||||
- AskUserQuestion: Select code review tool (Skip/Gemini/Agent/Other)
|
- ASK_USER: Select code review tool (Skip/Gemini/Agent/Other)
|
||||||
- Proceed to execution with `originalUserInput` included
|
- Proceed to execution with `originalUserInput` included
|
||||||
|
|
||||||
**User Interaction**:
|
**User Interaction**:
|
||||||
@@ -64,31 +64,31 @@ if (autoYes) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Interactive mode: Ask user
|
// Interactive mode: Ask user
|
||||||
userSelection = AskUserQuestion({
|
userSelection = ASK_USER([
|
||||||
questions: [
|
{
|
||||||
{
|
id: "execution",
|
||||||
question: "Select execution method:",
|
type: "select",
|
||||||
header: "Execution",
|
prompt: "Select execution method:",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Agent", description: "@code-developer agent" },
|
||||||
{ label: "Agent", description: "@code-developer agent" },
|
{ label: "Codex", description: "codex CLI tool" },
|
||||||
{ label: "Codex", description: "codex CLI tool" },
|
{ label: "Auto", description: "Auto-select based on complexity" }
|
||||||
{ label: "Auto", description: "Auto-select based on complexity" }
|
],
|
||||||
]
|
default: "Auto"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "Enable code review after execution?",
|
id: "review",
|
||||||
header: "Code Review",
|
type: "select",
|
||||||
multiSelect: false,
|
prompt: "Enable code review after execution?",
|
||||||
options: [
|
options: [
|
||||||
{ label: "Skip", description: "No review" },
|
{ label: "Skip", description: "No review" },
|
||||||
{ label: "Gemini Review", description: "Gemini CLI tool" },
|
{ label: "Gemini Review", description: "Gemini CLI tool" },
|
||||||
{ label: "Codex Review", description: "Git-aware review (prompt OR --uncommitted)" },
|
{ label: "Codex Review", description: "Git-aware review (prompt OR --uncommitted)" },
|
||||||
{ label: "Agent Review", description: "Current agent review" }
|
{ label: "Agent Review", description: "Current agent review" }
|
||||||
]
|
],
|
||||||
}
|
default: "Skip"
|
||||||
]
|
}
|
||||||
})
|
]) // BLOCKS (wait for user response)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -136,8 +136,8 @@ If `isPlanJson === false`:
|
|||||||
|
|
||||||
**Step 3: User Interaction**
|
**Step 3: User Interaction**
|
||||||
|
|
||||||
- AskUserQuestion: Select execution method (Agent/Codex/Auto)
|
- ASK_USER: Select execution method (Agent/Codex/Auto)
|
||||||
- AskUserQuestion: Select code review tool
|
- ASK_USER: Select code review tool
|
||||||
- Proceed to execution with full context
|
- Proceed to execution with full context
|
||||||
|
|
||||||
## Execution Process
|
## Execution Process
|
||||||
|
|||||||
@@ -362,27 +362,25 @@ if (autoYes) {
|
|||||||
console.log(`[--yes] Auto-continuing to Phase 4: Execution`)
|
console.log(`[--yes] Auto-continuing to Phase 4: Execution`)
|
||||||
// Read phases/04-execution.md and execute Phase 4
|
// Read phases/04-execution.md and execute Phase 4
|
||||||
} else {
|
} else {
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "phase3-next-action",
|
||||||
question: "Planning complete. What would you like to do next?",
|
type: "select",
|
||||||
header: "Next Action",
|
prompt: "Planning complete. What would you like to do next?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{
|
||||||
{
|
label: "Verify Plan Quality (Recommended)",
|
||||||
label: "Verify Plan Quality (Recommended)",
|
description: "Run quality verification to catch issues before execution."
|
||||||
description: "Run quality verification to catch issues before execution."
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Start Execution",
|
||||||
label: "Start Execution",
|
description: "Begin implementing tasks immediately (Phase 4)."
|
||||||
description: "Begin implementing tasks immediately (Phase 4)."
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Review Status Only",
|
||||||
label: "Review Status Only",
|
description: "View task breakdown and session status without taking further action."
|
||||||
description: "View task breakdown and session status without taking further action."
|
}
|
||||||
}
|
]
|
||||||
]
|
}]) // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute based on user choice
|
// Execute based on user choice
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Step 3: Inline Conflict Resolution (conditional)
|
|||||||
│ └─ Gemini/Qwen CLI analysis → conflict strategies
|
│ └─ Gemini/Qwen CLI analysis → conflict strategies
|
||||||
├─ 3.4 Iterative user clarification (send_input loop, max 10 rounds)
|
├─ 3.4 Iterative user clarification (send_input loop, max 10 rounds)
|
||||||
│ ├─ Display conflict + strategy ONE BY ONE
|
│ ├─ Display conflict + strategy ONE BY ONE
|
||||||
│ ├─ AskUserQuestion for user selection
|
│ ├─ ASK_USER for user selection
|
||||||
│ └─ send_input → agent re-analysis → confirm uniqueness
|
│ └─ send_input → agent re-analysis → confirm uniqueness
|
||||||
├─ 3.5 Generate conflict-resolution.json
|
├─ 3.5 Generate conflict-resolution.json
|
||||||
└─ 3.6 Close conflict agent
|
└─ 3.6 Close conflict agent
|
||||||
@@ -419,11 +419,10 @@ FOR each conflict:
|
|||||||
selectedStrategy = conflict.strategies[conflict.recommended || 0]
|
selectedStrategy = conflict.strategies[conflict.recommended || 0]
|
||||||
clarified = true // Skip clarification loop
|
clarified = true // Skip clarification loop
|
||||||
} else {
|
} else {
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: `conflict-${conflict.id}-strategy`,
|
||||||
question: formatStrategiesForDisplay(conflict.strategies),
|
type: "select",
|
||||||
header: "策略选择",
|
prompt: formatStrategiesForDisplay(conflict.strategies),
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
options: [
|
||||||
...conflict.strategies.map((s, i) => ({
|
...conflict.strategies.map((s, i) => ({
|
||||||
label: `${s.name}${i === conflict.recommended ? ' (推荐)' : ''}`,
|
label: `${s.name}${i === conflict.recommended ? ' (推荐)' : ''}`,
|
||||||
@@ -431,8 +430,7 @@ FOR each conflict:
|
|||||||
})),
|
})),
|
||||||
{ label: "自定义修改", description: `建议: ${conflict.modification_suggestions?.slice(0,2).join('; ')}` }
|
{ label: "自定义修改", description: `建议: ${conflict.modification_suggestions?.slice(0,2).join('; ')}` }
|
||||||
]
|
]
|
||||||
}]
|
}]) // BLOCKS (wait for user response)
|
||||||
})
|
|
||||||
|
|
||||||
// 3. Handle selection
|
// 3. Handle selection
|
||||||
if (userChoice === "自定义修改") {
|
if (userChoice === "自定义修改") {
|
||||||
@@ -446,12 +444,12 @@ FOR each conflict:
|
|||||||
// 4. Clarification (if needed) - using send_input for agent re-analysis
|
// 4. Clarification (if needed) - using send_input for agent re-analysis
|
||||||
if (!autoYes && selectedStrategy.clarification_needed?.length > 0) {
|
if (!autoYes && selectedStrategy.clarification_needed?.length > 0) {
|
||||||
for (batch of chunk(selectedStrategy.clarification_needed, 4)) {
|
for (batch of chunk(selectedStrategy.clarification_needed, 4)) {
|
||||||
AskUserQuestion({
|
ASK_USER(batch.map((q, i) => ({
|
||||||
questions: batch.map((q, i) => ({
|
id: `clarify-${conflict.id}-${i+1}`,
|
||||||
question: q, header: `澄清${i+1}`, multiSelect: false,
|
type: "select",
|
||||||
options: [{ label: "详细说明", description: "提供答案" }]
|
prompt: q,
|
||||||
}))
|
options: [{ label: "详细说明", description: "提供答案" }]
|
||||||
})
|
}))) // BLOCKS (wait for user response)
|
||||||
userClarifications.push(...collectAnswers(batch))
|
userClarifications.push(...collectAnswers(batch))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -498,7 +496,7 @@ selectedStrategies = resolvedConflicts.map(r => ({
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Key Points**:
|
**Key Points**:
|
||||||
- AskUserQuestion: max 4 questions/call, batch if more
|
- ASK_USER: max 4 questions/call, batch if more
|
||||||
- Strategy options: 2-4 strategies + "自定义修改"
|
- Strategy options: 2-4 strategies + "自定义修改"
|
||||||
- Clarification loop via send_input: max 10 rounds, agent determines uniqueness_confirmed
|
- Clarification loop via send_input: max 10 rounds, agent determines uniqueness_confirmed
|
||||||
- Agent stays active throughout interaction (no close_agent until Step 3.6)
|
- Agent stays active throughout interaction (no close_agent until Step 3.6)
|
||||||
|
|||||||
@@ -92,57 +92,53 @@ if (autoYes) {
|
|||||||
|
|
||||||
**User Questions** (skipped if autoYes):
|
**User Questions** (skipped if autoYes):
|
||||||
```javascript
|
```javascript
|
||||||
if (!autoYes) AskUserQuestion({
|
if (!autoYes) ASK_USER([
|
||||||
questions: [
|
{
|
||||||
{
|
id: "materials",
|
||||||
question: "Do you have supplementary materials or guidelines to include?",
|
type: "select",
|
||||||
header: "Materials",
|
prompt: "Do you have supplementary materials or guidelines to include?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "No additional materials", description: "Use existing context only" },
|
||||||
{ label: "No additional materials", description: "Use existing context only" },
|
{ label: "Provide file paths", description: "I'll specify paths to include" },
|
||||||
{ label: "Provide file paths", description: "I'll specify paths to include" },
|
{ label: "Provide inline content", description: "I'll paste content directly" }
|
||||||
{ label: "Provide inline content", description: "I'll paste content directly" }
|
]
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
{
|
id: "execution-method",
|
||||||
question: "Select execution method for generated tasks:",
|
type: "select",
|
||||||
header: "Execution",
|
prompt: "Select execution method for generated tasks:",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Agent (Recommended)", description: "Claude agent executes tasks directly" },
|
||||||
{ label: "Agent (Recommended)", description: "Claude agent executes tasks directly" },
|
{ label: "Hybrid", description: "Agent orchestrates, calls CLI for complex steps" },
|
||||||
{ label: "Hybrid", description: "Agent orchestrates, calls CLI for complex steps" },
|
{ label: "CLI Only", description: "All execution via CLI tools (codex/gemini/qwen)" }
|
||||||
{ label: "CLI Only", description: "All execution via CLI tools (codex/gemini/qwen)" }
|
]
|
||||||
]
|
},
|
||||||
},
|
{
|
||||||
{
|
id: "cli-tool",
|
||||||
question: "If using CLI, which tool do you prefer?",
|
type: "select",
|
||||||
header: "CLI Tool",
|
prompt: "If using CLI, which tool do you prefer?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Codex (Recommended)", description: "Best for implementation tasks" },
|
||||||
{ label: "Codex (Recommended)", description: "Best for implementation tasks" },
|
{ label: "Gemini", description: "Best for analysis and large context" },
|
||||||
{ label: "Gemini", description: "Best for analysis and large context" },
|
{ label: "Qwen", description: "Alternative analysis tool" },
|
||||||
{ label: "Qwen", description: "Alternative analysis tool" },
|
{ label: "Auto", description: "Let agent decide per-task" }
|
||||||
{ label: "Auto", description: "Let agent decide per-task" }
|
]
|
||||||
]
|
}
|
||||||
}
|
]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Handle Materials Response** (skipped if autoYes):
|
**Handle Materials Response** (skipped if autoYes):
|
||||||
```javascript
|
```javascript
|
||||||
if (!autoYes && userConfig.materials === "Provide file paths") {
|
if (!autoYes && userConfig.materials === "Provide file paths") {
|
||||||
// Follow-up question for file paths
|
// Follow-up question for file paths
|
||||||
const pathsResponse = AskUserQuestion({
|
const pathsResponse = ASK_USER([{
|
||||||
questions: [{
|
id: "material-paths",
|
||||||
question: "Enter file paths to include (comma-separated or one per line):",
|
type: "input",
|
||||||
header: "Paths",
|
prompt: "Enter file paths to include (comma-separated or one per line):",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{ label: "Enter paths", description: "Provide paths in text input" }
|
||||||
{ label: "Enter paths", description: "Provide paths in text input" }
|
]
|
||||||
]
|
}]) // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
})
|
|
||||||
userConfig.supplementaryPaths = parseUserPaths(pathsResponse)
|
userConfig.supplementaryPaths = parseUserPaths(pathsResponse)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ Step 3: Task Execution Loop
|
|||||||
Step 4: Completion
|
Step 4: Completion
|
||||||
├─ Synchronize all statuses
|
├─ Synchronize all statuses
|
||||||
├─ Generate summaries
|
├─ Generate summaries
|
||||||
└─ AskUserQuestion: Review or Complete Session
|
└─ ASK_USER: Review or Complete Session
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 1: TodoWrite Generation
|
## Step 1: TodoWrite Generation
|
||||||
@@ -284,23 +284,21 @@ if (autoYes) {
|
|||||||
// Execute: workflow:session:complete --yes
|
// Execute: workflow:session:complete --yes
|
||||||
} else {
|
} else {
|
||||||
// Interactive mode: Ask user
|
// Interactive mode: Ask user
|
||||||
AskUserQuestion({
|
ASK_USER([{
|
||||||
questions: [{
|
id: "completion-next-step",
|
||||||
question: "All tasks completed. What would you like to do next?",
|
type: "select",
|
||||||
header: "Next Step",
|
prompt: "All tasks completed. What would you like to do next?",
|
||||||
multiSelect: false,
|
options: [
|
||||||
options: [
|
{
|
||||||
{
|
label: "Enter Review",
|
||||||
label: "Enter Review",
|
description: "Run specialized review (security/architecture/quality/action-items)"
|
||||||
description: "Run specialized review (security/architecture/quality/action-items)"
|
},
|
||||||
},
|
{
|
||||||
{
|
label: "Complete Session",
|
||||||
label: "Complete Session",
|
description: "Archive session and update manifest"
|
||||||
description: "Archive session and update manifest"
|
}
|
||||||
}
|
]
|
||||||
]
|
}]) // BLOCKS (wait for user response)
|
||||||
}]
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -81,12 +81,14 @@ Phase 2: Agent Execution (Document Generation)
|
|||||||
|
|
||||||
**User Questions**:
|
**User Questions**:
|
||||||
```javascript
|
```javascript
|
||||||
AskUserQuestion({
|
if (AUTO_YES) {
|
||||||
questions: [
|
// --yes/-y: skip user questions, use defaults
|
||||||
|
userConfig = { materials: "No additional materials", execution: "Agent (Recommended)", cliTool: "Auto" }
|
||||||
|
} else {
|
||||||
|
ASK_USER([
|
||||||
{
|
{
|
||||||
question: "Do you have supplementary materials or guidelines to include?",
|
id: "Materials", type: "select",
|
||||||
header: "Materials",
|
prompt: "Do you have supplementary materials or guidelines to include?",
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
options: [
|
||||||
{ label: "No additional materials", description: "Use existing context only" },
|
{ label: "No additional materials", description: "Use existing context only" },
|
||||||
{ label: "Provide file paths", description: "I'll specify paths to include" },
|
{ label: "Provide file paths", description: "I'll specify paths to include" },
|
||||||
@@ -94,9 +96,8 @@ AskUserQuestion({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "Select execution method for generated TDD tasks:",
|
id: "Execution", type: "select",
|
||||||
header: "Execution",
|
prompt: "Select execution method for generated TDD tasks:",
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
options: [
|
||||||
{ label: "Agent (Recommended)", description: "Agent executes Red-Green-Refactor cycles directly" },
|
{ label: "Agent (Recommended)", description: "Agent executes Red-Green-Refactor cycles directly" },
|
||||||
{ label: "Hybrid", description: "Agent orchestrates, calls CLI for complex steps (Red/Green phases)" },
|
{ label: "Hybrid", description: "Agent orchestrates, calls CLI for complex steps (Red/Green phases)" },
|
||||||
@@ -104,9 +105,8 @@ AskUserQuestion({
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "If using CLI, which tool do you prefer?",
|
id: "CLI Tool", type: "select",
|
||||||
header: "CLI Tool",
|
prompt: "If using CLI, which tool do you prefer?",
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
options: [
|
||||||
{ label: "Codex (Recommended)", description: "Best for TDD Red-Green-Refactor cycles" },
|
{ label: "Codex (Recommended)", description: "Best for TDD Red-Green-Refactor cycles" },
|
||||||
{ label: "Gemini", description: "Best for analysis and large context" },
|
{ label: "Gemini", description: "Best for analysis and large context" },
|
||||||
@@ -114,24 +114,21 @@ AskUserQuestion({
|
|||||||
{ label: "Auto", description: "Let agent decide per-task" }
|
{ label: "Auto", description: "Let agent decide per-task" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]) // BLOCKS (wait for user response)
|
||||||
})
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Handle Materials Response**:
|
**Handle Materials Response**:
|
||||||
```javascript
|
```javascript
|
||||||
if (userConfig.materials === "Provide file paths") {
|
if (userConfig.materials === "Provide file paths") {
|
||||||
// Follow-up question for file paths
|
// Follow-up question for file paths
|
||||||
const pathsResponse = AskUserQuestion({
|
const pathsResponse = ASK_USER([{
|
||||||
questions: [{
|
id: "Paths", type: "input",
|
||||||
question: "Enter file paths to include (comma-separated or one per line):",
|
prompt: "Enter file paths to include (comma-separated or one per line):",
|
||||||
header: "Paths",
|
options: [
|
||||||
multiSelect: false,
|
{ label: "Enter paths", description: "Provide paths in text input" }
|
||||||
options: [
|
]
|
||||||
{ label: "Enter paths", description: "Provide paths in text input" }
|
}]) // BLOCKS (wait for user response)
|
||||||
]
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
userConfig.supplementaryPaths = parseUserPaths(pathsResponse)
|
userConfig.supplementaryPaths = parseUserPaths(pathsResponse)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -244,7 +241,7 @@ const userConfig = {
|
|||||||
|
|
||||||
// User configuration from Phase 0
|
// User configuration from Phase 0
|
||||||
"user_config": {
|
"user_config": {
|
||||||
// From Phase 0 AskUserQuestion
|
// From Phase 0 ASK_USER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
224
ccw/frontend/coverage/base.css
Normal file
224
ccw/frontend/coverage/base.css
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
body, html {
|
||||||
|
margin:0; padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Helvetica Neue, Helvetica, Arial;
|
||||||
|
font-size: 14px;
|
||||||
|
color:#333;
|
||||||
|
}
|
||||||
|
.small { font-size: 12px; }
|
||||||
|
*, *:after, *:before {
|
||||||
|
-webkit-box-sizing:border-box;
|
||||||
|
-moz-box-sizing:border-box;
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
h1 { font-size: 20px; margin: 0;}
|
||||||
|
h2 { font-size: 14px; }
|
||||||
|
pre {
|
||||||
|
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
-moz-tab-size: 2;
|
||||||
|
-o-tab-size: 2;
|
||||||
|
tab-size: 2;
|
||||||
|
}
|
||||||
|
a { color:#0074D9; text-decoration:none; }
|
||||||
|
a:hover { text-decoration:underline; }
|
||||||
|
.strong { font-weight: bold; }
|
||||||
|
.space-top1 { padding: 10px 0 0 0; }
|
||||||
|
.pad2y { padding: 20px 0; }
|
||||||
|
.pad1y { padding: 10px 0; }
|
||||||
|
.pad2x { padding: 0 20px; }
|
||||||
|
.pad2 { padding: 20px; }
|
||||||
|
.pad1 { padding: 10px; }
|
||||||
|
.space-left2 { padding-left:55px; }
|
||||||
|
.space-right2 { padding-right:20px; }
|
||||||
|
.center { text-align:center; }
|
||||||
|
.clearfix { display:block; }
|
||||||
|
.clearfix:after {
|
||||||
|
content:'';
|
||||||
|
display:block;
|
||||||
|
height:0;
|
||||||
|
clear:both;
|
||||||
|
visibility:hidden;
|
||||||
|
}
|
||||||
|
.fl { float: left; }
|
||||||
|
@media only screen and (max-width:640px) {
|
||||||
|
.col3 { width:100%; max-width:100%; }
|
||||||
|
.hide-mobile { display:none!important; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.quiet {
|
||||||
|
color: #7f7f7f;
|
||||||
|
color: rgba(0,0,0,0.5);
|
||||||
|
}
|
||||||
|
.quiet a { opacity: 0.7; }
|
||||||
|
|
||||||
|
.fraction {
|
||||||
|
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
||||||
|
font-size: 10px;
|
||||||
|
color: #555;
|
||||||
|
background: #E8E8E8;
|
||||||
|
padding: 4px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.path a:link, div.path a:visited { color: #333; }
|
||||||
|
table.coverage {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 10px 0 0 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.coverage td {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
table.coverage td.line-count {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 5px 0 20px;
|
||||||
|
}
|
||||||
|
table.coverage td.line-coverage {
|
||||||
|
text-align: right;
|
||||||
|
padding-right: 10px;
|
||||||
|
min-width:20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.coverage td span.cline-any {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 5px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.missing-if-branch {
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 4px;
|
||||||
|
background: #333;
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-if-branch {
|
||||||
|
display: none;
|
||||||
|
margin-right: 10px;
|
||||||
|
position: relative;
|
||||||
|
padding: 0 4px;
|
||||||
|
background: #ccc;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
.missing-if-branch .typ, .skip-if-branch .typ {
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
||||||
|
.coverage-summary {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.coverage-summary tr { border-bottom: 1px solid #bbb; }
|
||||||
|
.keyline-all { border: 1px solid #ddd; }
|
||||||
|
.coverage-summary td, .coverage-summary th { padding: 10px; }
|
||||||
|
.coverage-summary tbody { border: 1px solid #bbb; }
|
||||||
|
.coverage-summary td { border-right: 1px solid #bbb; }
|
||||||
|
.coverage-summary td:last-child { border-right: none; }
|
||||||
|
.coverage-summary th {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: normal;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.coverage-summary th.file { border-right: none !important; }
|
||||||
|
.coverage-summary th.pct { }
|
||||||
|
.coverage-summary th.pic,
|
||||||
|
.coverage-summary th.abs,
|
||||||
|
.coverage-summary td.pct,
|
||||||
|
.coverage-summary td.abs { text-align: right; }
|
||||||
|
.coverage-summary td.file { white-space: nowrap; }
|
||||||
|
.coverage-summary td.pic { min-width: 120px !important; }
|
||||||
|
.coverage-summary tfoot td { }
|
||||||
|
|
||||||
|
.coverage-summary .sorter {
|
||||||
|
height: 10px;
|
||||||
|
width: 7px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.5em;
|
||||||
|
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
||||||
|
}
|
||||||
|
.coverage-summary .sorted .sorter {
|
||||||
|
background-position: 0 -20px;
|
||||||
|
}
|
||||||
|
.coverage-summary .sorted-desc .sorter {
|
||||||
|
background-position: 0 -10px;
|
||||||
|
}
|
||||||
|
.status-line { height: 10px; }
|
||||||
|
/* yellow */
|
||||||
|
.cbranch-no { background: yellow !important; color: #111; }
|
||||||
|
/* dark red */
|
||||||
|
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
|
||||||
|
.low .chart { border:1px solid #C21F39 }
|
||||||
|
.highlighted,
|
||||||
|
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
|
||||||
|
background: #C21F39 !important;
|
||||||
|
}
|
||||||
|
/* medium red */
|
||||||
|
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
|
||||||
|
/* light red */
|
||||||
|
.low, .cline-no { background:#FCE1E5 }
|
||||||
|
/* light green */
|
||||||
|
.high, .cline-yes { background:rgb(230,245,208) }
|
||||||
|
/* medium green */
|
||||||
|
.cstat-yes { background:rgb(161,215,106) }
|
||||||
|
/* dark green */
|
||||||
|
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
|
||||||
|
.high .chart { border:1px solid rgb(77,146,33) }
|
||||||
|
/* dark yellow (gold) */
|
||||||
|
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
|
||||||
|
.medium .chart { border:1px solid #f9cd0b; }
|
||||||
|
/* light yellow */
|
||||||
|
.medium { background: #fff4c2; }
|
||||||
|
|
||||||
|
.cstat-skip { background: #ddd; color: #111; }
|
||||||
|
.fstat-skip { background: #ddd; color: #111 !important; }
|
||||||
|
.cbranch-skip { background: #ddd !important; color: #111; }
|
||||||
|
|
||||||
|
span.cline-neutral { background: #eaeaea; }
|
||||||
|
|
||||||
|
.coverage-summary td.empty {
|
||||||
|
opacity: .5;
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
line-height: 1;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-fill, .cover-empty {
|
||||||
|
display:inline-block;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
.chart {
|
||||||
|
line-height: 0;
|
||||||
|
}
|
||||||
|
.cover-empty {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.cover-full {
|
||||||
|
border-right: none !important;
|
||||||
|
}
|
||||||
|
pre.prettyprint {
|
||||||
|
border: none !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
.com { color: #999 !important; }
|
||||||
|
.ignore-none { color: #999; font-weight: normal; }
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
min-height: 100%;
|
||||||
|
height: auto !important;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0 auto -48px;
|
||||||
|
}
|
||||||
|
.footer, .push {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
87
ccw/frontend/coverage/block-navigation.js
Normal file
87
ccw/frontend/coverage/block-navigation.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
var jumpToCode = (function init() {
|
||||||
|
// Classes of code we would like to highlight in the file view
|
||||||
|
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
|
||||||
|
|
||||||
|
// Elements to highlight in the file listing view
|
||||||
|
var fileListingElements = ['td.pct.low'];
|
||||||
|
|
||||||
|
// We don't want to select elements that are direct descendants of another match
|
||||||
|
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
|
||||||
|
|
||||||
|
// Selector that finds elements on the page to which we can jump
|
||||||
|
var selector =
|
||||||
|
fileListingElements.join(', ') +
|
||||||
|
', ' +
|
||||||
|
notSelector +
|
||||||
|
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
|
||||||
|
|
||||||
|
// The NodeList of matching elements
|
||||||
|
var missingCoverageElements = document.querySelectorAll(selector);
|
||||||
|
|
||||||
|
var currentIndex;
|
||||||
|
|
||||||
|
function toggleClass(index) {
|
||||||
|
missingCoverageElements
|
||||||
|
.item(currentIndex)
|
||||||
|
.classList.remove('highlighted');
|
||||||
|
missingCoverageElements.item(index).classList.add('highlighted');
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeCurrent(index) {
|
||||||
|
toggleClass(index);
|
||||||
|
currentIndex = index;
|
||||||
|
missingCoverageElements.item(index).scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
inline: 'center'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPrevious() {
|
||||||
|
var nextIndex = 0;
|
||||||
|
if (typeof currentIndex !== 'number' || currentIndex === 0) {
|
||||||
|
nextIndex = missingCoverageElements.length - 1;
|
||||||
|
} else if (missingCoverageElements.length > 1) {
|
||||||
|
nextIndex = currentIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeCurrent(nextIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNext() {
|
||||||
|
var nextIndex = 0;
|
||||||
|
|
||||||
|
if (
|
||||||
|
typeof currentIndex === 'number' &&
|
||||||
|
currentIndex < missingCoverageElements.length - 1
|
||||||
|
) {
|
||||||
|
nextIndex = currentIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeCurrent(nextIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function jump(event) {
|
||||||
|
if (
|
||||||
|
document.getElementById('fileSearch') === document.activeElement &&
|
||||||
|
document.activeElement != null
|
||||||
|
) {
|
||||||
|
// if we're currently focused on the search input, we don't want to navigate
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (event.which) {
|
||||||
|
case 78: // n
|
||||||
|
case 74: // j
|
||||||
|
goToNext();
|
||||||
|
break;
|
||||||
|
case 66: // b
|
||||||
|
case 75: // k
|
||||||
|
case 80: // p
|
||||||
|
goToPrevious();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
window.addEventListener('keydown', jumpToCode);
|
||||||
1553
ccw/frontend/coverage/coverage-final.json
Normal file
1553
ccw/frontend/coverage/coverage-final.json
Normal file
File diff suppressed because one or more lines are too long
BIN
ccw/frontend/coverage/favicon.png
Normal file
BIN
ccw/frontend/coverage/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 445 B |
BIN
ccw/frontend/coverage/index.html
Normal file
BIN
ccw/frontend/coverage/index.html
Normal file
Binary file not shown.
1
ccw/frontend/coverage/prettify.css
Normal file
1
ccw/frontend/coverage/prettify.css
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
|
||||||
2
ccw/frontend/coverage/prettify.js
Normal file
2
ccw/frontend/coverage/prettify.js
Normal file
File diff suppressed because one or more lines are too long
BIN
ccw/frontend/coverage/sort-arrow-sprite.png
Normal file
BIN
ccw/frontend/coverage/sort-arrow-sprite.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 138 B |
210
ccw/frontend/coverage/sorter.js
Normal file
210
ccw/frontend/coverage/sorter.js
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
/* eslint-disable */
|
||||||
|
var addSorting = (function() {
|
||||||
|
'use strict';
|
||||||
|
var cols,
|
||||||
|
currentSort = {
|
||||||
|
index: 0,
|
||||||
|
desc: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// returns the summary table element
|
||||||
|
function getTable() {
|
||||||
|
return document.querySelector('.coverage-summary');
|
||||||
|
}
|
||||||
|
// returns the thead element of the summary table
|
||||||
|
function getTableHeader() {
|
||||||
|
return getTable().querySelector('thead tr');
|
||||||
|
}
|
||||||
|
// returns the tbody element of the summary table
|
||||||
|
function getTableBody() {
|
||||||
|
return getTable().querySelector('tbody');
|
||||||
|
}
|
||||||
|
// returns the th element for nth column
|
||||||
|
function getNthColumn(n) {
|
||||||
|
return getTableHeader().querySelectorAll('th')[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFilterInput() {
|
||||||
|
const searchValue = document.getElementById('fileSearch').value;
|
||||||
|
const rows = document.getElementsByTagName('tbody')[0].children;
|
||||||
|
|
||||||
|
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
|
||||||
|
// it will be treated as a plain text search
|
||||||
|
let searchRegex;
|
||||||
|
try {
|
||||||
|
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
|
||||||
|
} catch (error) {
|
||||||
|
searchRegex = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const row = rows[i];
|
||||||
|
let isMatch = false;
|
||||||
|
|
||||||
|
if (searchRegex) {
|
||||||
|
// If a valid regex was created, use it for matching
|
||||||
|
isMatch = searchRegex.test(row.textContent);
|
||||||
|
} else {
|
||||||
|
// Otherwise, fall back to the original plain text search
|
||||||
|
isMatch = row.textContent
|
||||||
|
.toLowerCase()
|
||||||
|
.includes(searchValue.toLowerCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
row.style.display = isMatch ? '' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loads the search box
|
||||||
|
function addSearchBox() {
|
||||||
|
var template = document.getElementById('filterTemplate');
|
||||||
|
var templateClone = template.content.cloneNode(true);
|
||||||
|
templateClone.getElementById('fileSearch').oninput = onFilterInput;
|
||||||
|
template.parentElement.appendChild(templateClone);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loads all columns
|
||||||
|
function loadColumns() {
|
||||||
|
var colNodes = getTableHeader().querySelectorAll('th'),
|
||||||
|
colNode,
|
||||||
|
cols = [],
|
||||||
|
col,
|
||||||
|
i;
|
||||||
|
|
||||||
|
for (i = 0; i < colNodes.length; i += 1) {
|
||||||
|
colNode = colNodes[i];
|
||||||
|
col = {
|
||||||
|
key: colNode.getAttribute('data-col'),
|
||||||
|
sortable: !colNode.getAttribute('data-nosort'),
|
||||||
|
type: colNode.getAttribute('data-type') || 'string'
|
||||||
|
};
|
||||||
|
cols.push(col);
|
||||||
|
if (col.sortable) {
|
||||||
|
col.defaultDescSort = col.type === 'number';
|
||||||
|
colNode.innerHTML =
|
||||||
|
colNode.innerHTML + '<span class="sorter"></span>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
// attaches a data attribute to every tr element with an object
|
||||||
|
// of data values keyed by column name
|
||||||
|
function loadRowData(tableRow) {
|
||||||
|
var tableCols = tableRow.querySelectorAll('td'),
|
||||||
|
colNode,
|
||||||
|
col,
|
||||||
|
data = {},
|
||||||
|
i,
|
||||||
|
val;
|
||||||
|
for (i = 0; i < tableCols.length; i += 1) {
|
||||||
|
colNode = tableCols[i];
|
||||||
|
col = cols[i];
|
||||||
|
val = colNode.getAttribute('data-value');
|
||||||
|
if (col.type === 'number') {
|
||||||
|
val = Number(val);
|
||||||
|
}
|
||||||
|
data[col.key] = val;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
// loads all row data
|
||||||
|
function loadData() {
|
||||||
|
var rows = getTableBody().querySelectorAll('tr'),
|
||||||
|
i;
|
||||||
|
|
||||||
|
for (i = 0; i < rows.length; i += 1) {
|
||||||
|
rows[i].data = loadRowData(rows[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// sorts the table using the data for the ith column
|
||||||
|
function sortByIndex(index, desc) {
|
||||||
|
var key = cols[index].key,
|
||||||
|
sorter = function(a, b) {
|
||||||
|
a = a.data[key];
|
||||||
|
b = b.data[key];
|
||||||
|
return a < b ? -1 : a > b ? 1 : 0;
|
||||||
|
},
|
||||||
|
finalSorter = sorter,
|
||||||
|
tableBody = document.querySelector('.coverage-summary tbody'),
|
||||||
|
rowNodes = tableBody.querySelectorAll('tr'),
|
||||||
|
rows = [],
|
||||||
|
i;
|
||||||
|
|
||||||
|
if (desc) {
|
||||||
|
finalSorter = function(a, b) {
|
||||||
|
return -1 * sorter(a, b);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < rowNodes.length; i += 1) {
|
||||||
|
rows.push(rowNodes[i]);
|
||||||
|
tableBody.removeChild(rowNodes[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.sort(finalSorter);
|
||||||
|
|
||||||
|
for (i = 0; i < rows.length; i += 1) {
|
||||||
|
tableBody.appendChild(rows[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// removes sort indicators for current column being sorted
|
||||||
|
function removeSortIndicators() {
|
||||||
|
var col = getNthColumn(currentSort.index),
|
||||||
|
cls = col.className;
|
||||||
|
|
||||||
|
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
|
||||||
|
col.className = cls;
|
||||||
|
}
|
||||||
|
// adds sort indicators for current column being sorted
|
||||||
|
function addSortIndicators() {
|
||||||
|
getNthColumn(currentSort.index).className += currentSort.desc
|
||||||
|
? ' sorted-desc'
|
||||||
|
: ' sorted';
|
||||||
|
}
|
||||||
|
// adds event listeners for all sorter widgets
|
||||||
|
function enableUI() {
|
||||||
|
var i,
|
||||||
|
el,
|
||||||
|
ithSorter = function ithSorter(i) {
|
||||||
|
var col = cols[i];
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
var desc = col.defaultDescSort;
|
||||||
|
|
||||||
|
if (currentSort.index === i) {
|
||||||
|
desc = !currentSort.desc;
|
||||||
|
}
|
||||||
|
sortByIndex(i, desc);
|
||||||
|
removeSortIndicators();
|
||||||
|
currentSort.index = i;
|
||||||
|
currentSort.desc = desc;
|
||||||
|
addSortIndicators();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
for (i = 0; i < cols.length; i += 1) {
|
||||||
|
if (cols[i].sortable) {
|
||||||
|
// add the click event handler on the th so users
|
||||||
|
// dont have to click on those tiny arrows
|
||||||
|
el = getNthColumn(i).querySelector('.sorter').parentElement;
|
||||||
|
if (el.addEventListener) {
|
||||||
|
el.addEventListener('click', ithSorter(i));
|
||||||
|
} else {
|
||||||
|
el.attachEvent('onclick', ithSorter(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// adds sorting functionality to the UI
|
||||||
|
return function() {
|
||||||
|
if (!getTable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cols = loadColumns();
|
||||||
|
loadData();
|
||||||
|
addSearchBox();
|
||||||
|
addSortIndicators();
|
||||||
|
enableUI();
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
window.addEventListener('load', addSorting);
|
||||||
@@ -26,14 +26,21 @@
|
|||||||
},
|
},
|
||||||
"cliTools": {
|
"cliTools": {
|
||||||
"title": "CLI Tools",
|
"title": "CLI Tools",
|
||||||
"description": "Configure CLI tool settings",
|
"description": "Configure CLI tool settings. Current default: ",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"setDefault": "Set as Default",
|
"setDefault": "Set as Default",
|
||||||
"primaryModel": "Primary Model",
|
"primaryModel": "Primary Model",
|
||||||
"secondaryModel": "Secondary Model",
|
"secondaryModel": "Secondary Model",
|
||||||
"expand": "Expand for details"
|
"expand": "Expand for details",
|
||||||
|
"envFile": "Environment File (.env)",
|
||||||
|
"envFilePlaceholder": "e.g., ~/.gemini/.env",
|
||||||
|
"envFileHint": "Path to .env file loaded before CLI execution for API keys and environment variables",
|
||||||
|
"saveToConfig": "Save to Config",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"configSaved": "Configuration saved to ~/.claude/cli-tools.json",
|
||||||
|
"configSaveError": "Failed to save configuration"
|
||||||
},
|
},
|
||||||
"display": {
|
"display": {
|
||||||
"title": "Display Settings",
|
"title": "Display Settings",
|
||||||
|
|||||||
@@ -26,14 +26,21 @@
|
|||||||
},
|
},
|
||||||
"cliTools": {
|
"cliTools": {
|
||||||
"title": "CLI 工具",
|
"title": "CLI 工具",
|
||||||
"description": "配置 CLI 工具设置",
|
"description": "配置 CLI 工具设置,当前默认工具:",
|
||||||
"enabled": "已启用",
|
"enabled": "已启用",
|
||||||
"disabled": "已禁用",
|
"disabled": "已禁用",
|
||||||
"default": "默认",
|
"default": "默认",
|
||||||
"setDefault": "设为默认",
|
"setDefault": "设为默认",
|
||||||
"primaryModel": "主模型",
|
"primaryModel": "主模型",
|
||||||
"secondaryModel": "辅助模型",
|
"secondaryModel": "辅助模型",
|
||||||
"expand": "展开详情"
|
"expand": "展开详情",
|
||||||
|
"envFile": "环境变量文件 (.env)",
|
||||||
|
"envFilePlaceholder": "例如:~/.gemini/.env",
|
||||||
|
"envFileHint": "CLI 执行前加载的 .env 文件路径,用于设置 API Key 等环境变量",
|
||||||
|
"saveToConfig": "保存到配置文件",
|
||||||
|
"saving": "保存中...",
|
||||||
|
"configSaved": "配置已保存到 ~/.claude/cli-tools.json",
|
||||||
|
"configSaveError": "保存配置失败"
|
||||||
},
|
},
|
||||||
"display": {
|
"display": {
|
||||||
"title": "显示设置",
|
"title": "显示设置",
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ describe('EndpointsPage', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
// confirm() used for delete
|
// confirm() used for delete
|
||||||
// @ts-expect-error - test override
|
vi.stubGlobal('confirm', vi.fn(() => true));
|
||||||
global.confirm = vi.fn(() => true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should render page title', () => {
|
it('should render page title', () => {
|
||||||
@@ -128,4 +127,3 @@ describe('EndpointsPage', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
// ========================================
|
// ========================================
|
||||||
// Application settings and configuration with CLI tools management
|
// Application settings and configuration with CLI tools management
|
||||||
|
|
||||||
import { useState } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
import {
|
import {
|
||||||
Settings,
|
Settings,
|
||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
Calendar,
|
Calendar,
|
||||||
File,
|
File,
|
||||||
ArrowUpCircle,
|
ArrowUpCircle,
|
||||||
|
Save,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Card } from '@/components/ui/Card';
|
import { Card } from '@/components/ui/Card';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
@@ -53,6 +54,21 @@ import {
|
|||||||
useUpgradeCcwInstallation,
|
useUpgradeCcwInstallation,
|
||||||
} from '@/hooks/useSystemSettings';
|
} from '@/hooks/useSystemSettings';
|
||||||
|
|
||||||
|
// ========== Tool Config File Helpers ==========
|
||||||
|
|
||||||
|
/** Tools that use .env file for environment variables */
|
||||||
|
const ENV_FILE_TOOLS = new Set(['gemini', 'qwen', 'opencode']);
|
||||||
|
/** Tools that use --settings for Claude CLI settings file */
|
||||||
|
const SETTINGS_FILE_TOOLS = new Set(['claude']);
|
||||||
|
/** Tools that don't need any config file */
|
||||||
|
const NO_CONFIG_FILE_TOOLS = new Set(['codex']);
|
||||||
|
|
||||||
|
function getConfigFileType(toolId: string): 'envFile' | 'settingsFile' | 'none' {
|
||||||
|
if (ENV_FILE_TOOLS.has(toolId)) return 'envFile';
|
||||||
|
if (SETTINGS_FILE_TOOLS.has(toolId)) return 'settingsFile';
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
|
||||||
// ========== CLI Tool Card Component ==========
|
// ========== CLI Tool Card Component ==========
|
||||||
|
|
||||||
interface CliToolCardProps {
|
interface CliToolCardProps {
|
||||||
@@ -61,13 +77,16 @@ interface CliToolCardProps {
|
|||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
isExpanded: boolean;
|
isExpanded: boolean;
|
||||||
toolAvailable?: boolean;
|
toolAvailable?: boolean;
|
||||||
|
isSaving?: boolean;
|
||||||
onToggleExpand: () => void;
|
onToggleExpand: () => void;
|
||||||
onToggleEnabled: () => void;
|
onToggleEnabled: () => void;
|
||||||
onSetDefault: () => void;
|
onSetDefault: () => void;
|
||||||
onUpdateModel: (field: 'primaryModel' | 'secondaryModel', value: string) => void;
|
onUpdateModel: (field: 'primaryModel' | 'secondaryModel', value: string) => void;
|
||||||
onUpdateTags: (tags: string[]) => void;
|
onUpdateTags: (tags: string[]) => void;
|
||||||
onUpdateAvailableModels: (models: string[]) => void;
|
onUpdateAvailableModels: (models: string[]) => void;
|
||||||
|
onUpdateEnvFile: (envFile: string | undefined) => void;
|
||||||
onUpdateSettingsFile: (settingsFile: string | undefined) => void;
|
onUpdateSettingsFile: (settingsFile: string | undefined) => void;
|
||||||
|
onSaveToBackend: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function CliToolCard({
|
function CliToolCard({
|
||||||
@@ -76,13 +95,16 @@ function CliToolCard({
|
|||||||
isDefault,
|
isDefault,
|
||||||
isExpanded,
|
isExpanded,
|
||||||
toolAvailable,
|
toolAvailable,
|
||||||
|
isSaving,
|
||||||
onToggleExpand,
|
onToggleExpand,
|
||||||
onToggleEnabled,
|
onToggleEnabled,
|
||||||
onSetDefault,
|
onSetDefault,
|
||||||
onUpdateModel,
|
onUpdateModel,
|
||||||
onUpdateTags,
|
onUpdateTags,
|
||||||
onUpdateAvailableModels,
|
onUpdateAvailableModels,
|
||||||
|
onUpdateEnvFile,
|
||||||
onUpdateSettingsFile,
|
onUpdateSettingsFile,
|
||||||
|
onSaveToBackend,
|
||||||
}: CliToolCardProps) {
|
}: CliToolCardProps) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
@@ -123,6 +145,8 @@ function CliToolCard({
|
|||||||
// Predefined tags
|
// Predefined tags
|
||||||
const predefinedTags = ['分析', 'Debug', 'implementation', 'refactoring', 'testing'];
|
const predefinedTags = ['分析', 'Debug', 'implementation', 'refactoring', 'testing'];
|
||||||
|
|
||||||
|
const configFileType = getConfigFileType(toolId);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={cn('overflow-hidden', !config.enabled && 'opacity-60')}>
|
<Card className={cn('overflow-hidden', !config.enabled && 'opacity-60')}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -350,26 +374,59 @@ function CliToolCard({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Settings File */}
|
{/* Env File - for gemini/qwen/opencode */}
|
||||||
<div className="space-y-2">
|
{configFileType === 'envFile' && (
|
||||||
<label className="text-sm font-medium text-foreground">
|
<div className="space-y-2">
|
||||||
{formatMessage({ id: 'apiSettings.cliSettings.settingsFile' })}
|
<label className="text-sm font-medium text-foreground">
|
||||||
</label>
|
{formatMessage({ id: 'settings.cliTools.envFile' })}
|
||||||
<Input
|
</label>
|
||||||
value={config.settingsFile || ''}
|
<Input
|
||||||
onChange={(e) => onUpdateSettingsFile(e.target.value || undefined)}
|
value={config.envFile || ''}
|
||||||
placeholder={formatMessage({ id: 'apiSettings.cliSettings.settingsFilePlaceholder' })}
|
onChange={(e) => onUpdateEnvFile(e.target.value || undefined)}
|
||||||
/>
|
placeholder={formatMessage({ id: 'settings.cliTools.envFilePlaceholder' })}
|
||||||
<p className="text-xs text-muted-foreground">
|
/>
|
||||||
{formatMessage({ id: 'apiSettings.cliSettings.settingsFileHint' })}
|
<p className="text-xs text-muted-foreground">
|
||||||
</p>
|
{formatMessage({ id: 'settings.cliTools.envFileHint' })}
|
||||||
</div>
|
</p>
|
||||||
|
</div>
|
||||||
{!isDefault && config.enabled && (
|
|
||||||
<Button variant="outline" size="sm" onClick={onSetDefault}>
|
|
||||||
{formatMessage({ id: 'settings.cliTools.setDefault' })}
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Settings File - for claude only */}
|
||||||
|
{configFileType === 'settingsFile' && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<label className="text-sm font-medium text-foreground">
|
||||||
|
{formatMessage({ id: 'apiSettings.cliSettings.settingsFile' })}
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
value={config.settingsFile || ''}
|
||||||
|
onChange={(e) => onUpdateSettingsFile(e.target.value || undefined)}
|
||||||
|
placeholder={formatMessage({ id: 'apiSettings.cliSettings.settingsFilePlaceholder' })}
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground">
|
||||||
|
{formatMessage({ id: 'apiSettings.cliSettings.settingsFileHint' })}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Action Buttons */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{!isDefault && config.enabled && (
|
||||||
|
<Button variant="outline" size="sm" onClick={onSetDefault}>
|
||||||
|
{formatMessage({ id: 'settings.cliTools.setDefault' })}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
size="sm"
|
||||||
|
onClick={onSaveToBackend}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
<Save className="w-4 h-4 mr-1" />
|
||||||
|
{isSaving
|
||||||
|
? formatMessage({ id: 'settings.cliTools.saving' })
|
||||||
|
: formatMessage({ id: 'settings.cliTools.saveToConfig' })}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
@@ -666,13 +723,16 @@ interface CliToolsWithStatusProps {
|
|||||||
cliTools: Record<string, CliToolConfig>;
|
cliTools: Record<string, CliToolConfig>;
|
||||||
defaultCliTool: string;
|
defaultCliTool: string;
|
||||||
expandedTools: Set<string>;
|
expandedTools: Set<string>;
|
||||||
|
savingTools: Set<string>;
|
||||||
onToggleExpand: (toolId: string) => void;
|
onToggleExpand: (toolId: string) => void;
|
||||||
onToggleEnabled: (toolId: string) => void;
|
onToggleEnabled: (toolId: string) => void;
|
||||||
onSetDefault: (toolId: string) => void;
|
onSetDefault: (toolId: string) => void;
|
||||||
onUpdateModel: (toolId: string, field: 'primaryModel' | 'secondaryModel', value: string) => void;
|
onUpdateModel: (toolId: string, field: 'primaryModel' | 'secondaryModel', value: string) => void;
|
||||||
onUpdateTags: (toolId: string, tags: string[]) => void;
|
onUpdateTags: (toolId: string, tags: string[]) => void;
|
||||||
onUpdateAvailableModels: (toolId: string, models: string[]) => void;
|
onUpdateAvailableModels: (toolId: string, models: string[]) => void;
|
||||||
|
onUpdateEnvFile: (toolId: string, envFile: string | undefined) => void;
|
||||||
onUpdateSettingsFile: (toolId: string, settingsFile: string | undefined) => void;
|
onUpdateSettingsFile: (toolId: string, settingsFile: string | undefined) => void;
|
||||||
|
onSaveToBackend: (toolId: string) => void;
|
||||||
formatMessage: ReturnType<typeof useIntl>['formatMessage'];
|
formatMessage: ReturnType<typeof useIntl>['formatMessage'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,13 +740,16 @@ function CliToolsWithStatus({
|
|||||||
cliTools,
|
cliTools,
|
||||||
defaultCliTool,
|
defaultCliTool,
|
||||||
expandedTools,
|
expandedTools,
|
||||||
|
savingTools,
|
||||||
onToggleExpand,
|
onToggleExpand,
|
||||||
onToggleEnabled,
|
onToggleEnabled,
|
||||||
onSetDefault,
|
onSetDefault,
|
||||||
onUpdateModel,
|
onUpdateModel,
|
||||||
onUpdateTags,
|
onUpdateTags,
|
||||||
onUpdateAvailableModels,
|
onUpdateAvailableModels,
|
||||||
|
onUpdateEnvFile,
|
||||||
onUpdateSettingsFile,
|
onUpdateSettingsFile,
|
||||||
|
onSaveToBackend,
|
||||||
formatMessage,
|
formatMessage,
|
||||||
}: CliToolsWithStatusProps) {
|
}: CliToolsWithStatusProps) {
|
||||||
const { data: toolStatus } = useCliToolStatus();
|
const { data: toolStatus } = useCliToolStatus();
|
||||||
@@ -707,13 +770,16 @@ function CliToolsWithStatus({
|
|||||||
isDefault={toolId === defaultCliTool}
|
isDefault={toolId === defaultCliTool}
|
||||||
isExpanded={expandedTools.has(toolId)}
|
isExpanded={expandedTools.has(toolId)}
|
||||||
toolAvailable={status?.available}
|
toolAvailable={status?.available}
|
||||||
|
isSaving={savingTools.has(toolId)}
|
||||||
onToggleExpand={() => onToggleExpand(toolId)}
|
onToggleExpand={() => onToggleExpand(toolId)}
|
||||||
onToggleEnabled={() => onToggleEnabled(toolId)}
|
onToggleEnabled={() => onToggleEnabled(toolId)}
|
||||||
onSetDefault={() => onSetDefault(toolId)}
|
onSetDefault={() => onSetDefault(toolId)}
|
||||||
onUpdateModel={(field, value) => onUpdateModel(toolId, field, value)}
|
onUpdateModel={(field, value) => onUpdateModel(toolId, field, value)}
|
||||||
onUpdateTags={(tags) => onUpdateTags(toolId, tags)}
|
onUpdateTags={(tags) => onUpdateTags(toolId, tags)}
|
||||||
onUpdateAvailableModels={(models) => onUpdateAvailableModels(toolId, models)}
|
onUpdateAvailableModels={(models) => onUpdateAvailableModels(toolId, models)}
|
||||||
|
onUpdateEnvFile={(envFile) => onUpdateEnvFile(toolId, envFile)}
|
||||||
onUpdateSettingsFile={(settingsFile) => onUpdateSettingsFile(toolId, settingsFile)}
|
onUpdateSettingsFile={(settingsFile) => onUpdateSettingsFile(toolId, settingsFile)}
|
||||||
|
onSaveToBackend={() => onSaveToBackend(toolId)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
@@ -733,6 +799,7 @@ export function SettingsPage() {
|
|||||||
const { updateCliTool, setDefaultCliTool, setUserPreferences, resetUserPreferences } = useConfigStore();
|
const { updateCliTool, setDefaultCliTool, setUserPreferences, resetUserPreferences } = useConfigStore();
|
||||||
|
|
||||||
const [expandedTools, setExpandedTools] = useState<Set<string>>(new Set());
|
const [expandedTools, setExpandedTools] = useState<Set<string>>(new Set());
|
||||||
|
const [savingTools, setSavingTools] = useState<Set<string>>(new Set());
|
||||||
|
|
||||||
const toggleToolExpand = (toolId: string) => {
|
const toggleToolExpand = (toolId: string) => {
|
||||||
setExpandedTools((prev) => {
|
setExpandedTools((prev) => {
|
||||||
@@ -766,10 +833,68 @@ export function SettingsPage() {
|
|||||||
updateCliTool(toolId, { availableModels });
|
updateCliTool(toolId, { availableModels });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUpdateEnvFile = (toolId: string, envFile: string | undefined) => {
|
||||||
|
updateCliTool(toolId, { envFile });
|
||||||
|
};
|
||||||
|
|
||||||
const handleUpdateSettingsFile = (toolId: string, settingsFile: string | undefined) => {
|
const handleUpdateSettingsFile = (toolId: string, settingsFile: string | undefined) => {
|
||||||
updateCliTool(toolId, { settingsFile });
|
updateCliTool(toolId, { settingsFile });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Save tool config to backend (~/.claude/cli-tools.json)
|
||||||
|
const handleSaveToBackend = useCallback(async (toolId: string) => {
|
||||||
|
const config = cliTools[toolId];
|
||||||
|
if (!config) return;
|
||||||
|
|
||||||
|
setSavingTools((prev) => new Set(prev).add(toolId));
|
||||||
|
try {
|
||||||
|
const body: Record<string, unknown> = {
|
||||||
|
enabled: config.enabled,
|
||||||
|
primaryModel: config.primaryModel,
|
||||||
|
secondaryModel: config.secondaryModel,
|
||||||
|
tags: config.tags,
|
||||||
|
availableModels: config.availableModels,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Only include the relevant config file field
|
||||||
|
const configFileType = getConfigFileType(toolId);
|
||||||
|
if (configFileType === 'envFile') {
|
||||||
|
body.envFile = config.envFile || null;
|
||||||
|
} else if (configFileType === 'settingsFile') {
|
||||||
|
body.settingsFile = config.settingsFile || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(`/api/cli/config/${toolId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
throw new Error(`HTTP ${res.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show success notification via a brief visual indicator
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'fixed bottom-4 right-4 z-50 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-in fade-in slide-in-from-bottom-2';
|
||||||
|
toast.textContent = formatMessage({ id: 'settings.cliTools.configSaved' });
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
setTimeout(() => toast.remove(), 3000);
|
||||||
|
} catch {
|
||||||
|
const toast = document.createElement('div');
|
||||||
|
toast.className = 'fixed bottom-4 right-4 z-50 bg-red-600 text-white px-4 py-2 rounded-lg shadow-lg text-sm animate-in fade-in slide-in-from-bottom-2';
|
||||||
|
toast.textContent = formatMessage({ id: 'settings.cliTools.configSaveError' });
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
setTimeout(() => toast.remove(), 4000);
|
||||||
|
} finally {
|
||||||
|
setSavingTools((prev) => {
|
||||||
|
const next = new Set(prev);
|
||||||
|
next.delete(toolId);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [cliTools, formatMessage]);
|
||||||
|
|
||||||
const handlePreferenceChange = (key: keyof UserPreferences, value: unknown) => {
|
const handlePreferenceChange = (key: keyof UserPreferences, value: unknown) => {
|
||||||
setUserPreferences({ [key]: value });
|
setUserPreferences({ [key]: value });
|
||||||
};
|
};
|
||||||
@@ -859,13 +984,16 @@ export function SettingsPage() {
|
|||||||
cliTools={cliTools}
|
cliTools={cliTools}
|
||||||
defaultCliTool={defaultCliTool}
|
defaultCliTool={defaultCliTool}
|
||||||
expandedTools={expandedTools}
|
expandedTools={expandedTools}
|
||||||
|
savingTools={savingTools}
|
||||||
onToggleExpand={toggleToolExpand}
|
onToggleExpand={toggleToolExpand}
|
||||||
onToggleEnabled={handleToggleToolEnabled}
|
onToggleEnabled={handleToggleToolEnabled}
|
||||||
onSetDefault={handleSetDefaultTool}
|
onSetDefault={handleSetDefaultTool}
|
||||||
onUpdateModel={handleUpdateModel}
|
onUpdateModel={handleUpdateModel}
|
||||||
onUpdateTags={handleUpdateTags}
|
onUpdateTags={handleUpdateTags}
|
||||||
onUpdateAvailableModels={handleUpdateAvailableModels}
|
onUpdateAvailableModels={handleUpdateAvailableModels}
|
||||||
|
onUpdateEnvFile={handleUpdateEnvFile}
|
||||||
onUpdateSettingsFile={handleUpdateSettingsFile}
|
onUpdateSettingsFile={handleUpdateSettingsFile}
|
||||||
|
onSaveToBackend={handleSaveToBackend}
|
||||||
formatMessage={formatMessage}
|
formatMessage={formatMessage}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -321,6 +321,9 @@ export interface CliToolConfig {
|
|||||||
secondaryModel: string;
|
secondaryModel: string;
|
||||||
tags: string[];
|
tags: string[];
|
||||||
type: 'builtin' | 'cli-wrapper' | 'api-endpoint';
|
type: 'builtin' | 'cli-wrapper' | 'api-endpoint';
|
||||||
|
/** Path to .env file for environment variables (gemini/qwen/opencode) */
|
||||||
|
envFile?: string;
|
||||||
|
/** Path to Claude CLI settings.json, passed via --settings (claude only) */
|
||||||
settingsFile?: string;
|
settingsFile?: string;
|
||||||
availableModels?: string[];
|
availableModels?: string[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
|||||||
if (req.method === 'PUT') {
|
if (req.method === 'PUT') {
|
||||||
handlePostRequest(req, res, async (body: unknown) => {
|
handlePostRequest(req, res, async (body: unknown) => {
|
||||||
try {
|
try {
|
||||||
const updates = body as { enabled?: boolean; primaryModel?: string; secondaryModel?: string; availableModels?: string[]; tags?: string[]; envFile?: string | null };
|
const updates = body as { enabled?: boolean; primaryModel?: string; secondaryModel?: string; availableModels?: string[]; tags?: string[]; envFile?: string | null; settingsFile?: string | null };
|
||||||
const updated = updateToolConfig(initialPath, tool, updates);
|
const updated = updateToolConfig(initialPath, tool, updates);
|
||||||
|
|
||||||
// Broadcast config updated event
|
// Broadcast config updated event
|
||||||
|
|||||||
Reference in New Issue
Block a user