Skip to content

Commit 4df3c92

Browse files
authored
Merge pull request #45 from closedloop-ai/symphony/pln-202
PLN-202: Add multi-repo support for planning and exploration
2 parents 9496ccb + 6ae5d73 commit 4df3c92

39 files changed

Lines changed: 1615 additions & 314 deletions

CHANGELOG.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,26 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
1414
- Updated agent output path references from `.claude/runs/` to `.closedloop-ai/runs/` in `agent-prompt-generator`
1515
- Updated bootstrap configuration documentation in `agent-bootstrap.md` to reference `.closedloop-ai/` state directory
1616

17+
### code v1.9.0
18+
19+
#### Added
20+
- Multi-repo planning and exploration support via new `--add-dir` flag in `run-loop.sh`, exposing `CLOSEDLOOP_ADD_DIRS` and `CLOSEDLOOP_REPO_MAP` env vars to downstream agents
21+
- `pre-explorer` agent produces per-repo code maps (`code-map-{name}.json`) when secondary repos are supplied
22+
- `plan-draft-writer` agent emits multi-repo plans with a `## Repositories` table and `@{repo}:path` task prefixes
23+
- `repositories` map field added to the plan root schema in `plan-schema.json` for multi-repo plan traceability, keyed by repo short-name with `path` and `isPrimary` metadata
24+
- Tier 0 explicit-directory discovery and dedup helpers in `discover-repos.sh`, with structured JSON output and a `local: true` marker on `--add-dir` peers
25+
- Tests for `discover-repos.sh` and `setup-closedloop.sh` (`test_discover_repos.py`, `test_setup_closedloop.py`) plus new multi-repo cases in `test_validate_plan.py`
26+
27+
#### Fixed
28+
- `run-loop.sh` now scans the full per-iteration stream for the `<promise>` completion marker instead of only inspecting the final `type==result` record, preventing missed completion signals when the orchestrator emits the promise in an intermediate message followed by additional tool_use or wrap-up output
29+
- `discover-repos.sh` now filters add-dirs that are ancestors of the workdir and deduplicates repo entries to prevent duplicate discovery results
30+
31+
#### Changed
32+
- Consolidated Tier 0 `discover-repos.sh` tests into a single scenario-driven harness, replacing the prior fragmented per-case test files
33+
- Migrated workdir internal state directory from `.closedloop/` to `.closedloop-ai/` across hooks, setup scripts, and loop state management
34+
- Established `CLOSEDLOOP_STATE_DIR` constant as single source of truth for state directory name across shell scripts
35+
- Added `Skill` to `plan-evaluator` agent's allowed tools to enable `code:plan-validate` skill execution
36+
1737
### code v1.6.0
1838

1939
#### Changed
@@ -45,6 +65,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4565
- Domain critic pass injection in fast-path reviewer via `{DOMAIN_CRITIC_PASS}` placeholder, enabling domain expert review within single-agent fast-path runs
4666
- Replaced shared prompt reasoning checklist with structured `PREMISE / EVIDENCE / GUARD CHECK / SEVERITY CHECK` analysis framework
4767

68+
### judges v1.5.1
69+
70+
#### Changed
71+
- Migrated perf-substep state paths from `.closedloop/` to `.closedloop-ai/` in `run-judges` skill telemetry instrumentation
72+
4873
### judges v1.5.0
4974

5075
#### Changed
@@ -55,6 +80,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5580
#### Changed
5681
- Version bump to align with cross-plugin `.closedloop-ai/` directory migration
5782

83+
### self-learning v1.1.1
84+
85+
#### Changed
86+
- Established `CLOSEDLOOP_STATE_DIR` constant as single source of truth for state directory name in `bootstrap-learnings.sh`, `compute_success_rates.py`, and `write_merged_patterns.py`
87+
5888
### self-learning v1.1.0
5989

6090
#### Changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ Always use `plugin-name:skill-name` format (e.g., `self-learning:learning-qualit
4444

4545
### Closed Loop (run-loop.sh)
4646

47-
The core orchestration loop in `plugins/code/scripts/run-loop.sh`. Drives fresh-context Claude iterations — each `claude -p` invocation gets a clean context window. The orchestrator prompt at `plugins/code/prompts/prompt.md` coordinates 8 workflow phases via subagent delegation. Post-iteration, `run-loop.sh` runs an 11-step pipeline calling Python scripts from `self-learning/tools/python/`.
47+
The core orchestration loop in `plugins/code/scripts/run-loop.sh`. Drives fresh-context Claude iterations — each `claude -p` invocation gets a clean context window. The orchestrator prompt at `plugins/code/prompts/prompt.md` coordinates 8 workflow phases via subagent delegation. Post-iteration, `run-loop.sh` runs an 11-step pipeline calling Python scripts from `self-learning/tools/python/`. Multi-repo behavior lives in the agents themselves (`pre-explorer`, `plan-draft-writer`, `cross-repo-coordinator`, `cross-repo-prd-writer`), which read `CLOSEDLOOP_REPO_MAP`, `CLOSEDLOOP_ADD_DIRS`, and the `local` flag on peers — no orchestrator-level branching is required.
4848

4949
### Hooks
5050

plugins/code/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "code",
33
"description": "Code and planning framework plugin",
4-
"version": "1.8.1",
4+
"version": "1.9.0",
55
"author": {
66
"name": "ClosedLoop",
77
"email": "support@closedloop.ai"

plugins/code/README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ State is maintained in `$CLOSEDLOOP_WORKDIR/state.json` at each phase transition
6363
```
6464

6565
- `working-directory`: Path to the work directory containing the PRD (defaults to current directory)
66-
- `--prompt <name>`: Select an alternate orchestrator prompt from `prompts/` (defaults to `prompt`)
66+
- `--prompt <name>`: Select an alternate orchestrator prompt (`prompts/<name>.md`). Defaults to `prompt`.
6767
- `--prd <file>`: Explicitly specify the requirements file (auto-detected if omitted)
6868

6969
**What it does:**
@@ -276,7 +276,7 @@ Cleans up session-level artifacts: removes the session workdir mapping file, cle
276276

277277
Runs when any subagent starts. Performs three tasks:
278278

279-
1. **Loop agent state creation**: If the agent type appears in `loop-agents.json`, creates the initial state file in `{WORKDIR}/.closedloop/` if it does not already exist.
279+
1. **Loop agent state creation**: If the agent type appears in `loop-agents.json`, creates the initial state file in `{WORKDIR}/.closedloop-ai/` if it does not already exist.
280280
2. **Agent type tracking**: Writes the agent type, short name, and start timestamp to `.agent-types/{agent_id}` so the stop hook can track timing and type.
281281
3. **Learning injection**: Reads `~/.closedloop-ai/learnings/org-patterns.toon`, filters patterns matching the agent's name, sorts by category priority (mistake > convention > pattern > insight) and confidence, and injects up to 15 patterns into the agent's context via `additionalContext`. Also injects environment variables (`CLOSEDLOOP_WORKDIR`, `CLAUDE_PLUGIN_ROOT`, etc.) into every agent's context.
282282

@@ -293,7 +293,7 @@ Runs when any subagent exits. Performs:
293293

294294
Implements the validation loop for agents registered in `loop-agents.json`. When an agent exits:
295295

296-
1. Reads the loop state file (`{WORKDIR}/.closedloop/{state_file_suffix}`)
296+
1. Reads the loop state file (`{WORKDIR}/.closedloop-ai/{state_file_suffix}`)
297297
2. Checks whether the agent output contains the expected completion promise (e.g., `<promise>PLAN_VALIDATED</promise>`)
298298
3. If the promise is present, optionally runs a validation script (e.g., `validate-plan.sh`)
299299
4. If validation passes, allows the agent to exit (returns nothing)
@@ -346,7 +346,7 @@ Defines the structure of `code-map.json` produced by the `pre-explorer` agent. R
346346

347347
### `setup-closedloop.sh`
348348

349-
Initializes a ClosedLoop session. Parses arguments (`--prd`, `--max-iterations`, `--prompt`, positional workdir), auto-detects the PRD file by checking common patterns (`prd.md`, `prd.pdf`, `requirements.md`, etc.), establishes the session-to-workdir mapping, validates the prompt name, and writes `{WORKDIR}/.closedloop/config.env` with all environment variables.
349+
Initializes a ClosedLoop session. Parses arguments (`--prd`, `--max-iterations`, `--prompt`, positional workdir), auto-detects the PRD file by checking common patterns (`prd.md`, `prd.pdf`, `requirements.md`, etc.), establishes the session-to-workdir mapping, validates the prompt name, and writes `{WORKDIR}/.closedloop-ai/config.env` with all environment variables.
350350

351351
### `run-loop.sh`
352352

@@ -485,7 +485,7 @@ After a full run, the work directory will contain:
485485
log.md # Change log appended each phase
486486
perf.jsonl # Agent timing events
487487
reviews/ # Critic review files (*.review.json)
488-
.closedloop/config.env # Session environment variables
488+
.closedloop-ai/config.env # Session environment variables
489489
.learnings/ # Self-learning artifacts
490490
pending/ # Unprocessed learning JSON files
491491
outcomes.log # Pattern application outcomes

plugins/code/agents/cross-repo-coordinator.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ Extract task IDs (T-X.Y format) that depend on each capability.
8989

9090
### Step 4: Write Capability Needs
9191

92-
Write `$CLOSEDLOOP_WORKDIR/.cross-repo-needs.json`:
92+
Write `$CLOSEDLOOP_WORKDIR/.cross-repo-needs.json`. The `local` field on each need entry comes directly from the peer's `local` field in `discover-repos.sh` output (defaulting to `false` when absent — only `--add-dir` peers carry `local: true`):
9393

9494
```json
9595
{
@@ -104,6 +104,7 @@ Write `$CLOSEDLOOP_WORKDIR/.cross-repo-needs.json`:
104104
"peerName": "astoria-service",
105105
"peerType": "backend",
106106
"peerPath": "/path/to/backend",
107+
"local": false,
107108
"capabilities": [
108109
{
109110
"type": "endpoint",
@@ -116,6 +117,19 @@ Write `$CLOSEDLOOP_WORKDIR/.cross-repo-needs.json`:
116117
"neededBy": ["T-3.1"]
117118
}
118119
]
120+
},
121+
{
122+
"peerName": "astoria-shared",
123+
"peerType": "library",
124+
"peerPath": "/path/to/shared",
125+
"local": true,
126+
"capabilities": [
127+
{
128+
"type": "module",
129+
"description": "Shared auth utilities",
130+
"neededBy": ["T-1.2"]
131+
}
132+
]
119133
}
120134
]
121135
}

plugins/code/agents/cross-repo-prd-writer.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,25 @@ Read `$CLOSEDLOOP_WORKDIR/.cross-repo-needs.json` for the list of needed capabil
2121

2222
Read `$CLOSEDLOOP_WORKDIR/.discovery-cache/{peer_name}.json` for each peer to see verification results.
2323

24-
### Step 2: Identify Missing Capabilities
24+
### Step 2: Filter Out Local Repos
2525

26-
For each capability in the needs file, check the discovery cache:
26+
Before evaluating capabilities, inspect each peer entry in `.cross-repo-needs.json`:
27+
- If the peer has `local: true`**exclude it entirely** from all further processing. These are add-dir repos whose tasks are already incorporated into the plan; there is nothing to build in a remote repo.
28+
- Only process peers where `local` is absent or `local: false`.
29+
30+
Track the count of excluded peers for the output summary.
31+
32+
### Step 3: Identify Missing Capabilities
33+
34+
For each **non-local** peer's capabilities, check the discovery cache:
2735
- If `exists: true` → skip (already exists in peer)
2836
- If `exists: false` → include in PRD
2937

30-
### Step 3: Generate PRDs
38+
### Step 4: Generate PRDs
39+
40+
For each non-local peer with missing capabilities, create `$CLOSEDLOOP_WORKDIR/cross-repo-prd-{peer_name}.md`.
3141

32-
For each peer with missing capabilities, create `$CLOSEDLOOP_WORKDIR/cross-repo-prd-{peer_name}.md`:
42+
**Guard:** If a capability's parent peer has `local: true`, skip PRD generation for that capability entirely — do not create or modify any PRD file for it.
3343

3444
**IMPORTANT:** Before writing any file, you MUST first attempt to Read it. This is required by Claude Code's safety system:
3545
1. Try to Read the target file path
@@ -67,7 +77,7 @@ This document describes capabilities needed from **{peer_name}** to support impl
6777
...
6878
```
6979

70-
### Step 4: Update plan.json
80+
### Step 5: Update plan.json
7181

7282
**Note:** Read `$CLOSEDLOOP_WORKDIR/plan.json` first before editing it.
7383

@@ -91,11 +101,13 @@ PRDS_GENERATED:
91101
- PRDs written: [list of files]
92102
- Missing capabilities: [count]
93103
- Existing capabilities: [count]
104+
- Local repos skipped: [count]
94105
- plan.json updated: yes/no
95106
```
96107

97108
If no missing capabilities:
98109
```
99110
NO_PRDS_NEEDED:
100111
- All capabilities exist in peer repos
112+
- Local repos skipped: [count]
101113
```

plugins/code/agents/plan-draft-writer.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,14 @@ The `content` field contains the full markdown plan following this structure:
9292
## Summary
9393
[2-3 sentences describing what will be implemented]
9494

95+
## Repositories
96+
(Only present when CLOSEDLOOP_ADD_DIRS is non-empty)
97+
98+
| Repo | Path | Primary |
99+
|------|------|---------|
100+
| primary | /path/to/primary | Yes |
101+
| secondary-name | /path/to/secondary | No |
102+
95103
## Acceptance Criteria
96104

97105
| ID | Criterion | Source |
@@ -140,6 +148,7 @@ The `content` field contains the full markdown plan following this structure:
140148

141149
7. **Visual References** (if attachments exist) - Embed images using `![description](attachments/filename.png)` relative path syntax
142150
Optional: **Architecture Diagrams** using `engineering:mermaid-visualizer` skill.
151+
Optional: **Repositories** (only when `CLOSEDLOOP_ADD_DIRS` is non-empty) - Table of all repos, placed after Summary. See `## Multi-Repository Plans`.
143152

144153
## JSON Field Sync
145154

@@ -179,6 +188,87 @@ Optional: **Architecture Diagrams** using `engineering:mermaid-visualizer` skill
179188
Manual tasks do NOT block the automated loop from completing. They are reported at the end for the human to perform.
180189
</critical_constraint>
181190

191+
## Multi-Repository Plans
192+
193+
**Skip this entire section if `CLOSEDLOOP_ADD_DIRS` is empty or unset.**
194+
195+
When `CLOSEDLOOP_ADD_DIRS` is non-empty, the plan spans multiple repositories. Follow these steps:
196+
197+
### Step M1: Parse Repository Map
198+
199+
Read the `CLOSEDLOOP_REPO_MAP` environment variable (pipe-separated `name=path` entries) to get the list of secondary repos. The primary repo is the main project codebase. Example:
200+
201+
```
202+
CLOSEDLOOP_REPO_MAP="frontend=/workspace/ui|backend=/workspace/api"
203+
```
204+
205+
Parse each entry as `{name}={path}`.
206+
207+
### Step M2: Read Per-Repo Code Maps
208+
209+
For each `name=path` entry in `CLOSEDLOOP_REPO_MAP`, read the pre-computed code map if it exists:
210+
211+
```bash
212+
cat $CLOSEDLOOP_WORKDIR/code-map-{name}.json 2>/dev/null
213+
```
214+
215+
These files are produced by the pre-explorer agent. Each contains the relevant files and patterns for that repository. Use this information to understand what files in each secondary repo are affected by the plan.
216+
217+
### Step M3: Use `@{repo-name}:path` Prefix for File References
218+
219+
When writing task descriptions that reference files in secondary repos, prefix them with `@{repo-name}:`:
220+
221+
- **Primary repo** (no prefix): `src/components/LoginForm.tsx`
222+
- **Secondary repo** (with prefix): `@frontend:src/components/LoginForm.tsx`
223+
- **Another secondary repo**: `@backend:api/routes/auth.py`
224+
225+
This convention makes cross-repo task scope unambiguous. Apply it consistently in all task descriptions, acceptance criteria references, and the Repositories section.
226+
227+
### Step M4: Add `## Repositories` Section to Plan Markdown
228+
229+
Include a `## Repositories` section in the markdown `content` field, placed immediately after `## Summary`:
230+
231+
```markdown
232+
## Repositories
233+
234+
| Repo | Path | Primary |
235+
|------|------|---------|
236+
| primary | /absolute/path/to/primary/repo | Yes |
237+
| frontend | /workspace/ui | No |
238+
| backend | /workspace/api | No |
239+
```
240+
241+
- The primary repo name is derived from the base directory name of the project (or `primary` if ambiguous).
242+
- Each secondary repo appears as a row with `No` in the Primary column.
243+
- Use the absolute path as it appears in `CLOSEDLOOP_REPO_MAP`.
244+
245+
### Step M5: Populate `repositories` Field in plan.json
246+
247+
Add an optional `repositories` field to plan.json as an object map keyed by repo short name:
248+
249+
```json
250+
{
251+
"repositories": {
252+
"primary": {
253+
"path": "/absolute/path/to/primary/repo",
254+
"isPrimary": true
255+
},
256+
"frontend": {
257+
"path": "/workspace/ui",
258+
"isPrimary": false
259+
},
260+
"backend": {
261+
"path": "/workspace/api",
262+
"isPrimary": false
263+
}
264+
}
265+
}
266+
```
267+
268+
Fields per entry:
269+
- `path`: Absolute filesystem path to the repository root
270+
- `isPrimary`: `true` only for the primary repo
271+
182272
## Process
183273

184274
Before writing, analyze in `<analysis>` tags:
@@ -305,6 +395,7 @@ PRD mentions: "Real-time updates from Linear"
305395
| No Code | Zero code snippets, function signatures, or pseudo-code in task descriptions |
306396
| Valid JSON | Output is valid JSON with all required fields |
307397
| JSON Sync | Structured fields match markdown content exactly |
398+
| Multi-Repo (if applicable) | When `CLOSEDLOOP_ADD_DIRS` is set: `## Repositories` table present in markdown, `repositories` field in plan.json, `@{repo-name}:path` prefix used for secondary repo file references |
308399

309400
## Completion
310401

plugins/code/agents/plan-evaluator.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ You evaluate whether an implementation plan qualifies for **simple mode** (skipp
3131

3232
### Step 2: Evaluate Simple Mode
3333

34-
Apply ALL six thresholds. ALL must pass for `simple_mode = true`. Default to `false` when uncertain.
34+
Apply ALL seven thresholds. ALL must pass for `simple_mode = true`. Default to `false` when uncertain.
3535

3636
| # | Signal | Threshold | Source |
3737
|---|--------|-----------|--------|
@@ -41,12 +41,14 @@ Apply ALL six thresholds. ALL must pass for `simple_mode = true`. Default to `fa
4141
| 4 | Open questions count | <= 3 | `plan.json``openQuestions.length` |
4242
| 5 | Forbidden terms | 0 found | `plan.json``content` field: search for: database, migration, infra, auth, security, payments, concurrency |
4343
| 6 | Cross-repo keywords | 0 found | `plan.json``content` field: search for: backend, frontend, mobile, api contract, shared library |
44+
| 7 | Add-dir repos | 0 | Read CLOSEDLOOP_ADD_DIRS from environment; non-empty value forces count >= 1, failing this signal |
4445

4546
**Evaluation rules:**
4647
- Count PRD words using whitespace splitting (approximate is fine)
4748
- Forbidden term matching is case-insensitive
4849
- Cross-repo keyword matching is case-insensitive
4950
- If any threshold fails, `simple_mode = false`
51+
- Signal 7 evaluates `CLOSEDLOOP_ADD_DIRS` from the injected environment -- if non-empty (count of pipe-delimited paths >= 1) the signal fails, forcing `simple_mode = false`; if empty or unset the signal passes.
5052

5153
### Step 3: Select Critics (only if simple_mode = false)
5254

@@ -74,7 +76,8 @@ Write `$CLOSEDLOOP_WORKDIR/plan-evaluation.json`:
7476
"task_count": { "value": N, "threshold": 6, "pass": true | false },
7577
"open_questions_count": { "value": N, "threshold": 3, "pass": true | false },
7678
"forbidden_terms": { "value": ["term1", ...], "threshold": 0, "pass": true | false },
77-
"cross_repo_keywords": { "value": ["keyword1", ...], "threshold": 0, "pass": true | false }
79+
"cross_repo_keywords": { "value": ["keyword1", ...], "threshold": 0, "pass": true | false },
80+
"add_dir_repos": { "value": N, "threshold": 0, "pass": true | false }
7881
},
7982
"selected_critics": ["critic-name-1", "critic-name-2"],
8083
"evaluation_summary": "Simple mode: true/false. Reason: ..."

0 commit comments

Comments
 (0)