@@ -70,9 +70,9 @@ Unified planning command using **issue-plan-agent** that combines exploration an
7070```
7171Phase 1: Issue Loading
7272 ├─ Parse input (single, comma-separated, or --all-pending)
73- ├─ Load issues from .workflow/issues/issues.jsonl
73+ ├─ Fetch issue metadata (ID, title, tags)
7474 ├─ Validate issues exist (create if needed)
75- └─ Group into batches ( max 3 per batch)
75+ └─ Group by similarity (shared tags or title keywords, max 3 per batch)
7676
7777Phase 2: Unified Explore + Plan (issue-plan-agent)
7878 ├─ Launch issue-plan-agent per batch
@@ -97,41 +97,71 @@ Phase 4: Summary
9797
9898## Implementation
9999
100- ### Phase 1: Issue Loading (IDs Only )
100+ ### Phase 1: Issue Loading (ID + Title + Tags )
101101
102102``` javascript
103103const batchSize = flags .batchSize || 3 ;
104- let issueIds = [];
104+ let issues = []; // {id, title, tags}
105105
106106if (flags .allPending ) {
107- // Get pending issue IDs directly via CLI
108- const ids = Bash (` ccw issue list --status pending,registered --ids` ).trim ();
109- issueIds = ids ? ids .split (' \n ' ).filter (Boolean ) : [];
107+ // Get pending issues with metadata via CLI (JSON output)
108+ const result = Bash (` ccw issue list --status pending,registered --json` ).trim ();
109+ const parsed = result ? JSON .parse (result) : [];
110+ issues = parsed .map (i => ({ id: i .id , title: i .title || ' ' , tags: i .tags || [] }));
110111
111- if (issueIds .length === 0 ) {
112+ if (issues .length === 0 ) {
112113 console .log (' No pending issues found.' );
113114 return ;
114115 }
115- console .log (` Found ${ issueIds .length } pending issues` );
116+ console .log (` Found ${ issues .length } pending issues` );
116117} else {
117- // Parse comma-separated issue IDs
118- issueIds = userInput .includes (' ,' )
118+ // Parse comma-separated issue IDs, fetch metadata
119+ const ids = userInput .includes (' ,' )
119120 ? userInput .split (' ,' ).map (s => s .trim ())
120121 : [userInput .trim ()];
121122
122- // Create if not exists
123- for (const id of issueIds) {
123+ for (const id of ids) {
124124 Bash (` ccw issue init ${ id} --title "Issue ${ id} " 2>/dev/null || true` );
125+ const info = Bash (` ccw issue status ${ id} --json` ).trim ();
126+ const parsed = info ? JSON .parse (info) : {};
127+ issues .push ({ id, title: parsed .title || ' ' , tags: parsed .tags || [] });
125128 }
126129}
127130
128- // Group into batches
129- const batches = [];
130- for (let i = 0 ; i < issueIds .length ; i += batchSize) {
131- batches .push (issueIds .slice (i, i + batchSize));
131+ // Intelligent grouping by similarity (tags → title keywords)
132+ function groupBySimilarity (issues , maxSize ) {
133+ const batches = [];
134+ const used = new Set ();
135+
136+ for (const issue of issues) {
137+ if (used .has (issue .id )) continue ;
138+
139+ const batch = [issue];
140+ used .add (issue .id );
141+ const issueTags = new Set (issue .tags );
142+ const issueWords = new Set (issue .title .toLowerCase ().split (/ \s + / ));
143+
144+ // Find similar issues
145+ for (const other of issues) {
146+ if (used .has (other .id ) || batch .length >= maxSize) continue ;
147+
148+ // Similarity: shared tags or shared title keywords
149+ const sharedTags = other .tags .filter (t => issueTags .has (t)).length ;
150+ const otherWords = other .title .toLowerCase ().split (/ \s + / );
151+ const sharedWords = otherWords .filter (w => issueWords .has (w) && w .length > 3 ).length ;
152+
153+ if (sharedTags > 0 || sharedWords >= 2 ) {
154+ batch .push (other);
155+ used .add (other .id );
156+ }
157+ }
158+ batches .push (batch);
159+ }
160+ return batches;
132161}
133162
134- console .log (` Processing ${ issueIds .length } issues in ${ batches .length } batch(es)` );
163+ const batches = groupBySimilarity (issues, batchSize);
164+ console .log (` Processing ${ issues .length } issues in ${ batches .length } batch(es) (grouped by similarity)` );
135165
136166TodoWrite ({
137167 todos: batches .map ((_ , i ) => ({
@@ -151,11 +181,16 @@ const pendingSelections = []; // Collect multi-solution issues for user selecti
151181for (const [batchIndex , batch ] of batches .entries ()) {
152182 updateTodo (` Plan batch ${ batchIndex + 1 } ` , ' in_progress' );
153183
184+ // Build issue list with metadata for agent context
185+ const issueList = batch .map (i => ` - ${ i .id } : ${ i .title }${ i .tags .length ? ` [${ i .tags .join (' , ' )} ]` : ' ' } ` ).join (' \n ' );
186+
154187 // Build minimal prompt - agent handles exploration, planning, and binding
155188 const issuePrompt = `
156189## Plan Issues
157190
158- **Issue IDs**: ${ batch .join (' , ' )}
191+ **Issues** (grouped by similarity):
192+ ${ issueList}
193+
159194**Project Root**: ${ process .cwd ()}
160195
161196### Steps
@@ -181,10 +216,11 @@ for (const [batchIndex, batch] of batches.entries()) {
181216` ;
182217
183218 // Launch issue-plan-agent - agent writes solutions directly
219+ const batchIds = batch .map (i => i .id );
184220 const result = Task (
185221 subagent_type= " issue-plan-agent" ,
186222 run_in_background= false ,
187- description= ` Explore & plan ${ batch .length } issues` ,
223+ description= ` Explore & plan ${ batch .length } issues: ${ batchIds . join ( ' , ' ) } ` ,
188224 prompt= issuePrompt
189225 );
190226
@@ -244,7 +280,7 @@ const plannedIds = Bash(`ccw issue list --status planned --ids`).trim();
244280const plannedCount = plannedIds ? plannedIds .split (' \n ' ).length : 0 ;
245281
246282console .log (`
247- ## Done: ${ issueIds .length } issues → ${ plannedCount} planned
283+ ## Done: ${ issues .length } issues → ${ plannedCount} planned
248284
249285Next: \` /issue:queue\` → \` /issue:execute\`
250286` );
0 commit comments