@try-works/dsh-recursive-mode 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/cordis.patch.yml +15 -10
  2. package/lib/bootstrap.d.ts +9 -2
  3. package/lib/client/apply-guard.d.ts +14 -0
  4. package/lib/client/board.d.ts +5 -4
  5. package/lib/client/contract.d.ts +57 -24
  6. package/lib/client/host-api.d.ts +26 -0
  7. package/lib/client/index.d.ts +16 -9
  8. package/lib/client/inspector.d.ts +3 -3
  9. package/lib/client/slots.d.ts +4 -6
  10. package/lib/client/strip.d.ts +4 -3
  11. package/lib/client/use-live.d.ts +9 -0
  12. package/lib/client.js +252 -174
  13. package/lib/index.d.ts +11 -11
  14. package/lib/index.js +972 -4504
  15. package/lib/init-templates.d.ts +41 -0
  16. package/lib/live-route.d.ts +69 -0
  17. package/lib/phase-rules.d.ts +34 -0
  18. package/lib/runtime.d.ts +24 -17
  19. package/lib/snapshot.d.ts +11 -0
  20. package/package.json +4 -3
  21. package/preset/recursive/agent.cordis.yml +7 -4
  22. package/preset/recursive/preset.yml +1 -1
  23. package/references/agents-block.md +95 -0
  24. package/references/artifact-template.md +2574 -0
  25. package/references/bodies/claude.md +7 -0
  26. package/references/bodies/codex-agents.md +95 -0
  27. package/references/bodies/copilot.md +7 -0
  28. package/references/bodies/cursorrules.md +7 -0
  29. package/references/bodies/decisions.md +5 -0
  30. package/references/bodies/delegated-verification.md +49 -0
  31. package/references/bodies/memory-router.md +45 -0
  32. package/references/bodies/phase8-skill-memory.md +48 -0
  33. package/references/bodies/plans-bridge.md +43 -0
  34. package/references/bodies/recursive-agents-router.md +64 -0
  35. package/references/bodies/skill-discovery.md +51 -0
  36. package/references/bodies/skill-memory-router.md +42 -0
  37. package/references/bodies/state.md +5 -0
  38. package/references/bootstrap/RECURSIVE.md +2362 -0
  39. package/references/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
  40. package/references/scripts/lint-recursive-run.ps1 +25 -0
  41. package/references/scripts/lint-recursive-run.py +2870 -0
  42. package/references/scripts/recursive-closeout.ps1 +38 -0
  43. package/references/scripts/recursive-closeout.py +541 -0
  44. package/references/scripts/recursive-init.ps1 +515 -0
  45. package/references/scripts/recursive-init.py +356 -0
  46. package/references/scripts/recursive-lock.ps1 +26 -0
  47. package/references/scripts/recursive-lock.py +302 -0
  48. package/references/scripts/recursive-review-bundle.ps1 +58 -0
  49. package/references/scripts/recursive-review-bundle.py +503 -0
  50. package/references/scripts/recursive-router-cli-configure.ps1 +2 -0
  51. package/references/scripts/recursive-router-cli-configure.py +9 -0
  52. package/references/scripts/recursive-router-cli-init.ps1 +2 -0
  53. package/references/scripts/recursive-router-cli-init.py +9 -0
  54. package/references/scripts/recursive-router-cli-invoke.ps1 +2 -0
  55. package/references/scripts/recursive-router-cli-invoke.py +9 -0
  56. package/references/scripts/recursive-router-cli-probe.ps1 +2 -0
  57. package/references/scripts/recursive-router-cli-probe.py +9 -0
  58. package/references/scripts/recursive-router-cli-resolve.ps1 +2 -0
  59. package/references/scripts/recursive-router-cli-resolve.py +9 -0
  60. package/references/scripts/recursive-router-cli-validate.ps1 +2 -0
  61. package/references/scripts/recursive-router-cli-validate.py +9 -0
  62. package/references/scripts/recursive-router-configure.ps1 +27 -0
  63. package/references/scripts/recursive-router-configure.py +74 -0
  64. package/references/scripts/recursive-router-init.ps1 +17 -0
  65. package/references/scripts/recursive-router-init.py +29 -0
  66. package/references/scripts/recursive-router-invoke.ps1 +47 -0
  67. package/references/scripts/recursive-router-invoke.py +103 -0
  68. package/references/scripts/recursive-router-probe.ps1 +25 -0
  69. package/references/scripts/recursive-router-probe.py +44 -0
  70. package/references/scripts/recursive-router-resolve.ps1 +26 -0
  71. package/references/scripts/recursive-router-resolve.py +46 -0
  72. package/references/scripts/recursive-router-validate.ps1 +17 -0
  73. package/references/scripts/recursive-router-validate.py +27 -0
  74. package/references/scripts/recursive-status.ps1 +23 -0
  75. package/references/scripts/recursive-status.py +2124 -0
  76. package/references/scripts/recursive-subagent-action.ps1 +98 -0
  77. package/references/scripts/recursive-subagent-action.py +197 -0
  78. package/references/scripts/recursive-training-extract.ps1 +23 -0
  79. package/references/scripts/recursive-training-extract.py +99 -0
  80. package/references/scripts/recursive-training-grpo.ps1 +52 -0
  81. package/references/scripts/recursive-training-grpo.py +1341 -0
  82. package/references/scripts/recursive-training-loader.ps1 +78 -0
  83. package/references/scripts/recursive-training-loader.py +558 -0
  84. package/references/scripts/recursive-training-mcp.ps1 +29 -0
  85. package/references/scripts/recursive-training-mcp.py +261 -0
  86. package/references/scripts/recursive-training-phase8-trigger.ps1 +55 -0
  87. package/references/scripts/recursive-training-phase8-trigger.py +137 -0
  88. package/references/scripts/recursive-training-sync.ps1 +29 -0
  89. package/references/scripts/recursive-training-sync.py +183 -0
  90. package/references/scripts/recursive_phase_rules.py +367 -0
  91. package/references/scripts/recursive_router_cli_lib.py +2 -0
  92. package/references/scripts/recursive_router_lib.py +2282 -0
  93. package/references/scripts/verify-locks.ps1 +25 -0
  94. package/references/scripts/verify-locks.py +353 -0
  95. package/scripts/__pycache__/lint-recursive-run.cpython-314.pyc +0 -0
  96. package/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
  97. package/scripts/install-preset.cmd +7 -0
  98. package/scripts/install-preset.js +101 -0
  99. package/scripts/lint-recursive-run.py +7 -5
  100. package/scripts/test-recursive-mode-smoke.ts +29 -28
  101. package/src/bootstrap.ts +253 -39
  102. package/src/client/apply-guard.ts +23 -0
  103. package/src/client/board.tsx +0 -0
  104. package/src/client/contract.ts +72 -24
  105. package/src/client/host-api.ts +90 -0
  106. package/src/client/index.ts +22 -11
  107. package/src/client/inspector.tsx +7 -9
  108. package/src/client/slots.ts +70 -26
  109. package/src/client/strip.tsx +9 -9
  110. package/src/client/use-live.ts +52 -0
  111. package/src/index.ts +89 -146
  112. package/src/init-templates.ts +276 -0
  113. package/src/live-route.ts +163 -0
  114. package/src/phase-rules.ts +262 -0
  115. package/src/policy.ts +28 -4
  116. package/src/runtime.ts +129 -77
  117. package/src/snapshot.ts +104 -0
  118. package/lib/client/node.d.ts +0 -71
  119. package/lib/events.d.ts +0 -173
  120. package/lib/projection.d.ts +0 -29
  121. package/src/client/node.ts +0 -156
  122. package/src/events.ts +0 -173
  123. package/src/projection.ts +0 -237
@@ -0,0 +1,261 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Optional MCP (Model Context Protocol) server for repository experiences.
4
+
5
+ Provides a get_repo_experiences tool that any MCP-compatible client can call
6
+ to dynamically retrieve ReasoningBank-structured experiences relevant to the
7
+ current task, file paths, and task type.
8
+
9
+ Usage:
10
+ python recursive-training-mcp.py --repo-root /path/to/repo
11
+
12
+ Compatible clients: Claude Desktop, Cline, Cursor (with MCP), Continue.dev.
13
+ """
14
+
15
+ import argparse
16
+ import json
17
+ import re
18
+ import sys
19
+ from pathlib import Path
20
+ from typing import Dict, List
21
+
22
+
23
+ class ReasoningBankReader:
24
+ """Reads ReasoningBank-structured memory from the memory plane."""
25
+
26
+ DOMAINS_DIR = ".recursive/memory/domains"
27
+ TRAINING_DIR = ".recursive/memory/training"
28
+
29
+ def __init__(self, repo_root: str):
30
+ self.root = Path(repo_root)
31
+ self.domains_dir = self.root / self.DOMAINS_DIR
32
+ self.training_dir = self.root / self.TRAINING_DIR
33
+
34
+ def _parse_items_from_file(self, filepath: Path) -> List[dict]:
35
+ if not filepath.exists():
36
+ return []
37
+ content = filepath.read_text(encoding="utf-8")
38
+ if "Status: CURRENT" not in content and "Status: SUSPECT" not in content:
39
+ return []
40
+
41
+ items = []
42
+ pattern = re.compile(
43
+ r'### (RB-\d+):\s*(.+?)\n\n'
44
+ r'\*\*Description:\*\*\s*(.+?)\n\n'
45
+ r'\*\*Content:\*\*\s*(.+?)(?=\n\n```|\n### |\Z)',
46
+ re.DOTALL
47
+ )
48
+ for m in pattern.finditer(content):
49
+ items.append({
50
+ "rb_id": m.group(1),
51
+ "title": m.group(2).strip(),
52
+ "description": m.group(3).strip(),
53
+ "content": m.group(4).strip(),
54
+ })
55
+
56
+ # Enrich with schema metadata
57
+ schema_pattern = re.compile(
58
+ r'```yaml\n'
59
+ r'rb_id: "(RB-\d+)"\n'
60
+ r'title: "([^"]+)"\n'
61
+ r'description: "([^"]+)"\n'
62
+ r'task_type: "([^"]*)"\n'
63
+ r'subsystem: "([^"]*)"\n'
64
+ r'source_runs: (.+?)\n'
65
+ r'applies_to: (.+?)\n'
66
+ r'success_rate: ([\d.]+)\n'
67
+ r'status: (\w+)\n'
68
+ r'created_at: "([^"]+)"\n'
69
+ r'```',
70
+ re.DOTALL
71
+ )
72
+ for m in schema_pattern.finditer(content):
73
+ rb_id = m.group(1)
74
+ for item in items:
75
+ if item["rb_id"] == rb_id:
76
+ item["task_type"] = m.group(4)
77
+ item["subsystem"] = m.group(5)
78
+ item["success_rate"] = float(m.group(8))
79
+ item["status"] = m.group(9)
80
+ item["created_at"] = m.group(10)
81
+ # Parse JSON arrays
82
+ try:
83
+ sr = m.group(6)
84
+ item["source_runs"] = json.loads(sr) if sr.strip() else []
85
+ except Exception:
86
+ item["source_runs"] = []
87
+ try:
88
+ at = m.group(7)
89
+ item["applies_to"] = json.loads(at) if at.strip() else []
90
+ except Exception:
91
+ item["applies_to"] = []
92
+ break
93
+ return items
94
+
95
+ def load_all_items(self) -> List[dict]:
96
+ items = []
97
+ for d in (self.domains_dir, self.training_dir):
98
+ if not d.exists():
99
+ continue
100
+ for f in sorted(d.glob("*.md")):
101
+ items.extend(self._parse_items_from_file(f))
102
+ return [item for item in items if item.get("status") != "deprecated"]
103
+
104
+ def get_relevant(self, task_type: str = "", file_paths: List[str] = None, subsystem: str = "") -> List[dict]:
105
+ """Score and filter items by relevance. Returns sorted list with scores."""
106
+ all_items = self.load_all_items()
107
+ file_paths = file_paths or []
108
+ scored = []
109
+
110
+ for item in all_items:
111
+ score = 0
112
+ applies_to = item.get("applies_to", [])
113
+ item_task = item.get("task_type", "")
114
+ item_subsystem = item.get("subsystem", "")
115
+
116
+ # Task type match (strong signal)
117
+ if task_type and item_task and task_type.lower() in item_task.lower():
118
+ score += 5
119
+
120
+ # Subsystem match (strong signal)
121
+ if subsystem and item_subsystem and subsystem.lower() in item_subsystem.lower():
122
+ score += 4
123
+
124
+ # File path overlap (medium signal)
125
+ for f in file_paths:
126
+ parts = f.split("/")
127
+ for part in parts:
128
+ if len(part) > 2:
129
+ for tag in applies_to:
130
+ if part.lower() in tag.lower():
131
+ score += 2
132
+ break
133
+
134
+ # Content overlap (weak signal)
135
+ for f in file_paths:
136
+ content = f"{item.get('title', '')} {item.get('description', '')} {item.get('content', '')}"
137
+ if f.lower() in content.lower():
138
+ score += 1
139
+
140
+ # Success rate bonus
141
+ sr = item.get("success_rate", 0)
142
+ if isinstance(sr, (int, float)) and sr > 0.8:
143
+ score += 1
144
+
145
+ if score > 0:
146
+ item["_relevance_score"] = score
147
+ scored.append(item)
148
+
149
+ scored.sort(key=lambda x: x["_relevance_score"], reverse=True)
150
+ return scored
151
+
152
+
153
+ def main():
154
+ parser = argparse.ArgumentParser(description="MCP server for repo experiences")
155
+ parser.add_argument("--repo-root", type=str, required=True)
156
+ args = parser.parse_args()
157
+
158
+ repo_root = Path(args.repo_root).resolve()
159
+ rb = ReasoningBankReader(repo_root)
160
+
161
+ print("MCP server (ReasoningBank) starting...", file=sys.stderr)
162
+ print(f"Repo root: {repo_root}", file=sys.stderr)
163
+
164
+ while True:
165
+ try:
166
+ line = input()
167
+ msg = json.loads(line)
168
+ method = msg.get("method", "")
169
+ msg_id = msg.get("id")
170
+
171
+ if method == "initialize":
172
+ print(json.dumps({
173
+ "jsonrpc": "2.0", "id": msg_id,
174
+ "result": {
175
+ "protocolVersion": "2024-11-05",
176
+ "capabilities": {"tools": {}},
177
+ "serverInfo": {"name": "recursive-training-mcp", "version": "2.0.0"},
178
+ },
179
+ }), flush=True)
180
+
181
+ elif method == "tools/list":
182
+ print(json.dumps({
183
+ "jsonrpc": "2.0", "id": msg_id,
184
+ "result": {
185
+ "tools": [
186
+ {
187
+ "name": "get_repo_experiences",
188
+ "description": "Get ReasoningBank-structured experiences for the current repository",
189
+ "inputSchema": {
190
+ "type": "object",
191
+ "properties": {
192
+ "task_type": {
193
+ "type": "string",
194
+ "description": "Type of task (e.g., commit-remediation, add-api-endpoint)",
195
+ },
196
+ "file_paths": {
197
+ "type": "array",
198
+ "items": {"type": "string"},
199
+ "description": "List of file paths being modified",
200
+ },
201
+ "subsystem": {
202
+ "type": "string",
203
+ "description": "Target subsystem (e.g., packages/artifacts)",
204
+ },
205
+ },
206
+ },
207
+ }
208
+ ]
209
+ },
210
+ }), flush=True)
211
+
212
+ elif method == "tools/call":
213
+ params = msg.get("params", {})
214
+ tool_name = params.get("name", "")
215
+ arguments = params.get("arguments", {})
216
+
217
+ if tool_name == "get_repo_experiences":
218
+ task_type = arguments.get("task_type", "")
219
+ file_paths = arguments.get("file_paths", [])
220
+ subsystem = arguments.get("subsystem", "")
221
+ items = rb.get_relevant(task_type, file_paths, subsystem)
222
+
223
+ if items:
224
+ lines = ["# Repository Experiential Knowledge (ReasoningBank)\n"]
225
+ for item in items[:20]: # top 20
226
+ rb_id = item["rb_id"]
227
+ title = item["title"]
228
+ desc = item["description"]
229
+ content = item["content"]
230
+ score = item.get("_relevance_score", 0)
231
+ sr = item.get("success_rate")
232
+ sr_str = f" [success: {sr:.0%}]" if isinstance(sr, float) else ""
233
+ applies = ", ".join(item.get("applies_to", []))
234
+ lines.append(
235
+ f"## [{rb_id}] {title}{sr_str} (score: {score})\n"
236
+ f"{desc}\n\n{content}\n"
237
+ f"*Applies to: {applies}*\n"
238
+ )
239
+ result_text = "\n".join(lines)
240
+ else:
241
+ result_text = "No relevant experiences found for this task and file set."
242
+
243
+ print(json.dumps({
244
+ "jsonrpc": "2.0", "id": msg_id,
245
+ "result": {"content": [{"type": "text", "text": result_text}]},
246
+ }), flush=True)
247
+
248
+ else:
249
+ print(json.dumps({
250
+ "jsonrpc": "2.0", "id": msg_id,
251
+ "error": {"code": -32601, "message": f"Unknown tool: {tool_name}"},
252
+ }), flush=True)
253
+
254
+ except EOFError:
255
+ break
256
+ except Exception as e:
257
+ print(f"Error: {e}", file=sys.stderr)
258
+
259
+
260
+ if __name__ == "__main__":
261
+ main()
@@ -0,0 +1,55 @@
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Post-Phase 8 training trigger for recursive-mode.
5
+
6
+ .DESCRIPTION
7
+ Thin wrapper around recursive-training-phase8-trigger.py.
8
+ Extraction transport is owned by recursive-training-extract.py, not this wrapper.
9
+
10
+ .PARAMETER RepoRoot
11
+ Path to the git repository root.
12
+
13
+ .PARAMETER RunId
14
+ The run that just completed Phase 8.
15
+
16
+ .PARAMETER Auto
17
+ Skip user confirmation and run training immediately.
18
+
19
+ .PARAMETER GrpoArgs
20
+ Extra arguments passed through to recursive-training-grpo.py
21
+ (for example: '--winner-only-threshold 3').
22
+
23
+ .EXAMPLE
24
+ .\recursive-training-phase8-trigger.ps1 -RepoRoot . -RunId phase25 -Auto
25
+ #>
26
+ param(
27
+ [Parameter(Mandatory)]
28
+ [string]$RepoRoot,
29
+
30
+ [Parameter(Mandatory)]
31
+ [string]$RunId,
32
+
33
+ [switch]$Auto,
34
+
35
+ [string]$GrpoArgs = ""
36
+ )
37
+
38
+ $python = Get-Command python -ErrorAction SilentlyContinue
39
+ if (-not $python) {
40
+ $python = Get-Command python3 -ErrorAction SilentlyContinue
41
+ }
42
+ if (-not $python) {
43
+ Write-Error "Python is required but not found."
44
+ exit 1
45
+ }
46
+
47
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
48
+ $pyScript = Join-Path $scriptDir "recursive-training-phase8-trigger.py"
49
+
50
+ $argsList = @("--repo-root", $RepoRoot, "--run-id", $RunId)
51
+ if ($Auto) { $argsList += "--auto" }
52
+ if ($GrpoArgs) { $argsList += @("--grpo-args", $GrpoArgs) }
53
+
54
+ & $python.Source $pyScript @argsList
55
+ exit $LASTEXITCODE
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Post-Phase 8 training trigger for recursive-mode.
4
+
5
+ Run this immediately after Phase 8 (08-memory-impact.md) is locked.
6
+ Thin wrapper: checks preconditions, then delegates to recursive-training-grpo.py.
7
+
8
+ Usage (after Phase 8 lock):
9
+ python .recursive/scripts/recursive-training-phase8-trigger.py \
10
+ --repo-root . \
11
+ --run-id phase25-organizations-teams-rbac \
12
+ [--auto] \
13
+ [--grpo-args "--winner-only-threshold 3"]
14
+
15
+ Arguments:
16
+ --repo-root: Path to git repository root
17
+ --run-id: The run that just completed Phase 8
18
+ --auto: Skip the "Train now?" prompt and run immediately
19
+ --grpo-args: Extra arguments passed through to recursive-training-grpo.py
20
+
21
+ Exit codes:
22
+ 0: OK (training wrote memory items)
23
+ 1: Error (missing scripts, bridge failure, etc.)
24
+ 2: User declined (or --auto not set and user not prompted) / extractor unavailable
25
+ 3: Training ran but wrote zero items (insufficient groups / empty extraction)
26
+ """
27
+
28
+ import argparse
29
+ import shlex
30
+ import subprocess
31
+ import sys
32
+ from pathlib import Path
33
+
34
+
35
+ def count_completed_runs(runs_dir: Path) -> int:
36
+ """Count runs with a locked 08-memory-impact.md artifact."""
37
+ if not runs_dir.exists():
38
+ return 0
39
+ count = 0
40
+ for run_dir in runs_dir.iterdir():
41
+ if run_dir.is_dir():
42
+ phase8 = run_dir / "08-memory-impact.md"
43
+ if phase8.exists():
44
+ text = phase8.read_text(encoding="utf-8")
45
+ if "LockedAt" in text or "Status: LOCKED" in text:
46
+ count += 1
47
+ return count
48
+
49
+
50
+ def main() -> int:
51
+ parser = argparse.ArgumentParser(
52
+ description="Post-Phase 8 training trigger for recursive-mode"
53
+ )
54
+ parser.add_argument("--repo-root", type=str, required=True)
55
+ parser.add_argument("--run-id", type=str, required=True)
56
+ parser.add_argument("--auto", action="store_true",
57
+ help="Run training immediately without prompting")
58
+ parser.add_argument("--grpo-args", type=str, default="",
59
+ help='Extra args for recursive-training-grpo.py, e.g. "--winner-only-threshold 3"')
60
+ args = parser.parse_args()
61
+
62
+ repo_root = Path(args.repo_root).resolve()
63
+ scripts_dir = repo_root / ".recursive" / "scripts"
64
+ runs_dir = repo_root / ".recursive" / "run"
65
+ grpo_script = scripts_dir / "recursive-training-grpo.py"
66
+
67
+ # --- Check scripts exist ---
68
+ if not grpo_script.exists():
69
+ print("ERROR: recursive-training-grpo.py not found.")
70
+ print("Run: python <SKILL_DIR>/scripts/install-recursive-mode.py --repo-root .")
71
+ return 1
72
+
73
+ # --- Check enough runs ---
74
+ completed = count_completed_runs(runs_dir)
75
+ if completed < 2:
76
+ print(f"Only {completed} completed run(s). Need 2+ for training.")
77
+ print("Training skipped. Complete more runs and try again.")
78
+ return 3
79
+
80
+ # --- Prompt or auto-run ---
81
+ if not args.auto:
82
+ print(f"Run '{args.run_id}' completed Phase 8.")
83
+ print(f"{completed} completed runs available.")
84
+ print("")
85
+ print("To extract learnings, run:")
86
+ print(f" python .recursive/scripts/recursive-training-grpo.py \\")
87
+ print(f" --repo-root . --incremental --run-id {args.run_id}")
88
+ if args.grpo_args:
89
+ print(f" {args.grpo_args}")
90
+ print("")
91
+ print("Or re-run this script with --auto to skip confirmation.")
92
+ return 2
93
+
94
+ # --- Run training ---
95
+ print(f"Auto-triggering training for {args.run_id} ({completed} runs)...")
96
+
97
+ def run_grpo(extra: list[str]) -> subprocess.CompletedProcess:
98
+ cmd = [sys.executable, str(grpo_script), "--repo-root", str(repo_root), *extra]
99
+ if args.grpo_args:
100
+ cmd.extend(shlex.split(args.grpo_args))
101
+ return subprocess.run(cmd)
102
+
103
+ result = run_grpo(["--incremental", "--run-id", args.run_id])
104
+ if result.returncode == 3:
105
+ print(
106
+ "Incremental subsystem group wrote 0 items; "
107
+ "falling back to full Phase-8-locked training..."
108
+ )
109
+ result = run_grpo([])
110
+
111
+ if result.returncode == 0:
112
+ print("Training extraction updated the memory plane.")
113
+ print(
114
+ "Future runs should read .recursive/memory/MEMORY.md and use "
115
+ "recursive-training-loader.py when experiential memory is relevant."
116
+ )
117
+ print("Done.")
118
+ return 0
119
+
120
+ if result.returncode == 2:
121
+ print("ERROR: Training extractor is unavailable. Memory plane was not updated.")
122
+ print(
123
+ "Wire RECURSIVE_TRAINING_EXTRACTOR_CMD, provide a response file, "
124
+ "or run agent-operated extraction."
125
+ )
126
+ return 2
127
+
128
+ if result.returncode == 3:
129
+ print("Training ran but extracted 0 items. Memory plane was not updated.")
130
+ return 3
131
+
132
+ print("ERROR: Training extraction failed.")
133
+ return result.returncode or 1
134
+
135
+
136
+ if __name__ == "__main__":
137
+ sys.exit(main())
@@ -0,0 +1,29 @@
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Sync .recursive/memory/ to tool instruction files (PowerShell wrapper).
5
+
6
+ .DESCRIPTION
7
+ Thin wrapper that calls recursive-training-sync.py with the same arguments.
8
+ #>
9
+
10
+ [CmdletBinding()]
11
+ param(
12
+ [Parameter(Mandatory = $true)]
13
+ [string]$RepoRoot
14
+ )
15
+
16
+ $python = Get-Command python -ErrorAction SilentlyContinue
17
+ if (-not $python) {
18
+ $python = Get-Command python3 -ErrorAction SilentlyContinue
19
+ }
20
+
21
+ if (-not $python) {
22
+ Write-Error "Python is required but not found. Please install Python 3.10+."
23
+ exit 1
24
+ }
25
+
26
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
27
+ $pyScript = Join-Path $scriptDir "recursive-training-sync.py"
28
+
29
+ & $python.Source $pyScript @("--repo-root", $RepoRoot)
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Read-only startup guidance for recursive-mode experiential memory.
4
+
5
+ Reads ReasoningBank-structured memory from .recursive/memory/domains/
6
+ and .recursive/memory/training/, then prints what an agent should read or load
7
+ at the start of a run.
8
+
9
+ Usage:
10
+ python recursive-training-sync.py --repo-root .
11
+ """
12
+
13
+ import argparse
14
+ import re
15
+ from pathlib import Path
16
+ from typing import Dict, List
17
+
18
+
19
+ class ReasoningBankReader:
20
+ """Reads ReasoningBank-structured memory from the memory plane."""
21
+
22
+ MEMORY_ROOT = ".recursive/memory"
23
+ DOMAINS_DIR = ".recursive/memory/domains"
24
+ TRAINING_DIR = ".recursive/memory/training"
25
+
26
+ def __init__(self, repo_root: str):
27
+ self.root = Path(repo_root)
28
+ self.memory_root = self.root / self.MEMORY_ROOT
29
+ self.domains_dir = self.root / self.DOMAINS_DIR
30
+ self.training_dir = self.root / self.TRAINING_DIR
31
+
32
+ def _parse_items_from_file(self, filepath: Path) -> List[dict]:
33
+ """Parse ReasoningBank items from a memory markdown file."""
34
+ if not filepath.exists():
35
+ return []
36
+ content = filepath.read_text(encoding="utf-8")
37
+ if "Status: CURRENT" not in content:
38
+ return []
39
+
40
+ items = []
41
+ # Match ReasoningBank item blocks
42
+ pattern = re.compile(
43
+ r'### (RB-\d+):\s*(.+?)\n\n'
44
+ r'\*\*Description:\*\*\s*(.+?)\n\n'
45
+ r'\*\*Content:\*\*\s*(.+?)(?=\n\n```|\n### |\Z)',
46
+ re.DOTALL
47
+ )
48
+ for m in pattern.finditer(content):
49
+ items.append({
50
+ "rb_id": m.group(1),
51
+ "title": m.group(2).strip(),
52
+ "description": m.group(3).strip(),
53
+ "content": m.group(4).strip(),
54
+ })
55
+
56
+ # Also parse YAML schema blocks for metadata
57
+ schema_pattern = re.compile(
58
+ r'```yaml\n'
59
+ r'rb_id: "(RB-\d+)"\n'
60
+ r'title: "([^"]+)"\n'
61
+ r'description: "([^"]+)"\n'
62
+ r'task_type: "([^"]*)"\n'
63
+ r'subsystem: "([^"]*)"\n'
64
+ r'source_runs: (.+?)\n'
65
+ r'applies_to: (.+?)\n'
66
+ r'success_rate: ([\d.]+)\n'
67
+ r'status: (\w+)\n'
68
+ r'created_at: "([^"]+)"\n'
69
+ r'```',
70
+ re.DOTALL
71
+ )
72
+ for m in schema_pattern.finditer(content):
73
+ rb_id = m.group(1)
74
+ for item in items:
75
+ if item["rb_id"] == rb_id:
76
+ item["task_type"] = m.group(4)
77
+ item["subsystem"] = m.group(5)
78
+ item["success_rate"] = float(m.group(8))
79
+ item["status"] = m.group(9)
80
+ item["created_at"] = m.group(10)
81
+ break
82
+
83
+ return items
84
+
85
+ def load_all_items(self) -> List[dict]:
86
+ """Load all ReasoningBank items from domain + training memory."""
87
+ items = []
88
+ for d in (self.domains_dir, self.training_dir):
89
+ if not d.exists():
90
+ continue
91
+ for f in sorted(d.glob("*.md")):
92
+ items.extend(self._parse_items_from_file(f))
93
+ return items
94
+
95
+ def list_training_docs(self) -> Dict[str, dict]:
96
+ """List all training memory docs with metadata."""
97
+ docs = {}
98
+ for d in (self.domains_dir, self.training_dir):
99
+ if not d.exists():
100
+ continue
101
+ for f in sorted(d.glob("*.md")):
102
+ content = f.read_text(encoding="utf-8")
103
+ if "Status: CURRENT" not in content:
104
+ continue
105
+ # Count items
106
+ item_count = len(re.findall(r'### RB-\d+:', content))
107
+ # Extract scope from frontmatter if present
108
+ scope_match = re.search(r'Scope:\s*(.+)', content)
109
+ scope = scope_match.group(1).strip() if scope_match else f.name.replace('.md', '')
110
+ docs[f.name] = {
111
+ "path": str(f.relative_to(self.memory_root)).replace('\\', '/'),
112
+ "type": "domain" if "domains" in str(f) else "training",
113
+ "scope": scope,
114
+ "items": item_count,
115
+ }
116
+ return docs
117
+
118
+
119
+ class ExperienceSync:
120
+ def __init__(self, repo_root: str):
121
+ self.repo_root = Path(repo_root)
122
+ self.rb_reader = ReasoningBankReader(repo_root)
123
+
124
+ def build_startup_guidance(self) -> str:
125
+ """Render non-mutating startup guidance for agents."""
126
+ docs = self.rb_reader.list_training_docs()
127
+ lines = ["## Recursive Training Startup Guidance\n"]
128
+ lines.append(
129
+ "At the start of a run, read `/.recursive/memory/MEMORY.md` first.\n"
130
+ )
131
+ lines.append(
132
+ "If the task may benefit from prior experiential memory, run the loader with the current task context:\n\n"
133
+ "```bash\n"
134
+ "python .recursive/scripts/recursive-training-loader.py --repo-root . \\\n"
135
+ " --query \"your task description\" \\\n"
136
+ " --files \"path/to/file1.ts,path/to/file2.ts\"\n"
137
+ "```\n"
138
+ )
139
+ lines.append("The loader resolves relevant memory docs from the memory plane and returns the most applicable learnings.\n")
140
+
141
+ if not docs:
142
+ lines.append("No current domain or training memory docs were found under `/.recursive/memory/`.\n")
143
+ return "\n".join(lines)
144
+
145
+ # Group by type
146
+ domains = {k: v for k, v in docs.items() if v["type"] == "domain"}
147
+ training = {k: v for k, v in docs.items() if v["type"] == "training"}
148
+
149
+ if domains:
150
+ lines.append("### Domain Memory (subsystem-specific)\n")
151
+ lines.append("| Doc | Scope | Items | Description |")
152
+ lines.append("|-----|-------|-------|-------------|")
153
+ for name, info in sorted(domains.items()):
154
+ lines.append(f"| `{info['path']}` | {info['scope']} | {info['items']} items | subsystem-specific learnings |")
155
+ lines.append("")
156
+
157
+ if training:
158
+ lines.append("### Training Memory (task-type patterns)\n")
159
+ lines.append("| Doc | Scope | Items | Description |")
160
+ lines.append("|-----|-------|-------|-------------|")
161
+ for name, info in sorted(training.items()):
162
+ lines.append(f"| `{info['path']}` | {info['scope']} | {info['items']} items | cross-subsystem task patterns |")
163
+ lines.append("")
164
+
165
+ lines.append("---\n")
166
+ lines.append("This output is advisory only. The canonical memory remains under `/.recursive/memory/`.\n")
167
+ return "\n".join(lines)
168
+
169
+ def sync_all(self):
170
+ print(self.build_startup_guidance().strip())
171
+
172
+ def main():
173
+ parser = argparse.ArgumentParser(description="Print startup guidance for recursive-mode experiential memory")
174
+ parser.add_argument("--repo-root", type=str, required=True, help="Path to git repository root")
175
+ args = parser.parse_args()
176
+
177
+ sync = ExperienceSync(args.repo_root)
178
+ print("Training sync is read-only. Startup guidance:")
179
+ sync.sync_all()
180
+
181
+
182
+ if __name__ == "__main__":
183
+ main()