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

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 (126) 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/fs-intent.d.ts +34 -0
  14. package/lib/index.d.ts +12 -11
  15. package/lib/index.js +1008 -4507
  16. package/lib/init-templates.d.ts +41 -0
  17. package/lib/live-route.d.ts +69 -0
  18. package/lib/phase-rules.d.ts +34 -0
  19. package/lib/policy.d.ts +1 -1
  20. package/lib/runtime.d.ts +24 -17
  21. package/lib/snapshot.d.ts +11 -0
  22. package/package.json +4 -3
  23. package/preset/recursive/agent.cordis.yml +7 -4
  24. package/preset/recursive/preset.yml +1 -1
  25. package/references/agents-block.md +95 -0
  26. package/references/artifact-template.md +2574 -0
  27. package/references/bodies/claude.md +7 -0
  28. package/references/bodies/codex-agents.md +95 -0
  29. package/references/bodies/copilot.md +7 -0
  30. package/references/bodies/cursorrules.md +7 -0
  31. package/references/bodies/decisions.md +5 -0
  32. package/references/bodies/delegated-verification.md +49 -0
  33. package/references/bodies/memory-router.md +45 -0
  34. package/references/bodies/phase8-skill-memory.md +48 -0
  35. package/references/bodies/plans-bridge.md +43 -0
  36. package/references/bodies/recursive-agents-router.md +64 -0
  37. package/references/bodies/skill-discovery.md +51 -0
  38. package/references/bodies/skill-memory-router.md +42 -0
  39. package/references/bodies/state.md +5 -0
  40. package/references/bootstrap/RECURSIVE.md +2362 -0
  41. package/references/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
  42. package/references/scripts/lint-recursive-run.ps1 +25 -0
  43. package/references/scripts/lint-recursive-run.py +2870 -0
  44. package/references/scripts/recursive-closeout.ps1 +38 -0
  45. package/references/scripts/recursive-closeout.py +541 -0
  46. package/references/scripts/recursive-init.ps1 +515 -0
  47. package/references/scripts/recursive-init.py +356 -0
  48. package/references/scripts/recursive-lock.ps1 +26 -0
  49. package/references/scripts/recursive-lock.py +302 -0
  50. package/references/scripts/recursive-review-bundle.ps1 +58 -0
  51. package/references/scripts/recursive-review-bundle.py +503 -0
  52. package/references/scripts/recursive-router-cli-configure.ps1 +2 -0
  53. package/references/scripts/recursive-router-cli-configure.py +9 -0
  54. package/references/scripts/recursive-router-cli-init.ps1 +2 -0
  55. package/references/scripts/recursive-router-cli-init.py +9 -0
  56. package/references/scripts/recursive-router-cli-invoke.ps1 +2 -0
  57. package/references/scripts/recursive-router-cli-invoke.py +9 -0
  58. package/references/scripts/recursive-router-cli-probe.ps1 +2 -0
  59. package/references/scripts/recursive-router-cli-probe.py +9 -0
  60. package/references/scripts/recursive-router-cli-resolve.ps1 +2 -0
  61. package/references/scripts/recursive-router-cli-resolve.py +9 -0
  62. package/references/scripts/recursive-router-cli-validate.ps1 +2 -0
  63. package/references/scripts/recursive-router-cli-validate.py +9 -0
  64. package/references/scripts/recursive-router-configure.ps1 +27 -0
  65. package/references/scripts/recursive-router-configure.py +74 -0
  66. package/references/scripts/recursive-router-init.ps1 +17 -0
  67. package/references/scripts/recursive-router-init.py +29 -0
  68. package/references/scripts/recursive-router-invoke.ps1 +47 -0
  69. package/references/scripts/recursive-router-invoke.py +103 -0
  70. package/references/scripts/recursive-router-probe.ps1 +25 -0
  71. package/references/scripts/recursive-router-probe.py +44 -0
  72. package/references/scripts/recursive-router-resolve.ps1 +26 -0
  73. package/references/scripts/recursive-router-resolve.py +46 -0
  74. package/references/scripts/recursive-router-validate.ps1 +17 -0
  75. package/references/scripts/recursive-router-validate.py +27 -0
  76. package/references/scripts/recursive-status.ps1 +23 -0
  77. package/references/scripts/recursive-status.py +2124 -0
  78. package/references/scripts/recursive-subagent-action.ps1 +98 -0
  79. package/references/scripts/recursive-subagent-action.py +197 -0
  80. package/references/scripts/recursive-training-extract.ps1 +23 -0
  81. package/references/scripts/recursive-training-extract.py +99 -0
  82. package/references/scripts/recursive-training-grpo.ps1 +52 -0
  83. package/references/scripts/recursive-training-grpo.py +1341 -0
  84. package/references/scripts/recursive-training-loader.ps1 +78 -0
  85. package/references/scripts/recursive-training-loader.py +558 -0
  86. package/references/scripts/recursive-training-mcp.ps1 +29 -0
  87. package/references/scripts/recursive-training-mcp.py +261 -0
  88. package/references/scripts/recursive-training-phase8-trigger.ps1 +55 -0
  89. package/references/scripts/recursive-training-phase8-trigger.py +137 -0
  90. package/references/scripts/recursive-training-sync.ps1 +29 -0
  91. package/references/scripts/recursive-training-sync.py +183 -0
  92. package/references/scripts/recursive_phase_rules.py +367 -0
  93. package/references/scripts/recursive_router_cli_lib.py +2 -0
  94. package/references/scripts/recursive_router_lib.py +2282 -0
  95. package/references/scripts/verify-locks.ps1 +25 -0
  96. package/references/scripts/verify-locks.py +353 -0
  97. package/scripts/__pycache__/lint-recursive-run.cpython-314.pyc +0 -0
  98. package/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
  99. package/scripts/install-preset.cmd +7 -0
  100. package/scripts/install-preset.js +101 -0
  101. package/scripts/lint-recursive-run.py +7 -5
  102. package/scripts/test-recursive-mode-smoke.ts +29 -28
  103. package/src/bootstrap.ts +253 -39
  104. package/src/client/apply-guard.ts +23 -0
  105. package/src/client/board.tsx +0 -0
  106. package/src/client/contract.ts +72 -24
  107. package/src/client/host-api.ts +90 -0
  108. package/src/client/index.ts +22 -11
  109. package/src/client/inspector.tsx +7 -9
  110. package/src/client/slots.ts +70 -26
  111. package/src/client/strip.tsx +9 -9
  112. package/src/client/use-live.ts +52 -0
  113. package/src/fs-intent.ts +84 -0
  114. package/src/index.ts +100 -155
  115. package/src/init-templates.ts +276 -0
  116. package/src/live-route.ts +163 -0
  117. package/src/phase-rules.ts +262 -0
  118. package/src/policy.ts +29 -5
  119. package/src/runtime.ts +129 -77
  120. package/src/snapshot.ts +104 -0
  121. package/lib/client/node.d.ts +0 -71
  122. package/lib/events.d.ts +0 -173
  123. package/lib/projection.d.ts +0 -29
  124. package/src/client/node.ts +0 -156
  125. package/src/events.ts +0 -173
  126. package/src/projection.ts +0 -237
@@ -0,0 +1,98 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [Parameter(Mandatory = $true)][string]$RunId,
4
+ [Parameter(Mandatory = $true)][string]$SubagentId,
5
+ [Parameter(Mandatory = $true)][string]$Phase,
6
+ [Parameter(Mandatory = $true)][string]$Purpose,
7
+ [Parameter(Mandatory = $true)][string]$ExecutionMode,
8
+ [string]$ArtifactPath = "",
9
+ [string]$RepoRoot = (Get-Location).Path,
10
+ [string[]]$UpstreamArtifact = @(),
11
+ [string[]]$Addendum = @(),
12
+ [string]$ReviewBundle = "",
13
+ [string]$DiffBasis = "",
14
+ [string[]]$CodeRef = @(),
15
+ [string[]]$MemoryRef = @(),
16
+ [string[]]$AuditQuestion = @(),
17
+ [string[]]$ActionTaken = @(),
18
+ [string[]]$CreatedFile = @(),
19
+ [string[]]$ModifiedFile = @(),
20
+ [string[]]$ReviewedFile = @(),
21
+ [string[]]$UntouchedFile = @(),
22
+ [string[]]$ArtifactRead = @(),
23
+ [string[]]$ArtifactUpdated = @(),
24
+ [string[]]$EvidenceUsed = @(),
25
+ [string[]]$Finding = @(),
26
+ [string[]]$VerificationPath = @(),
27
+ [string[]]$VerificationItem = @(),
28
+ [string]$RouterUsed = "",
29
+ [string]$RoutedRole = "",
30
+ [string]$RoutedCli = "",
31
+ [string]$RoutedModel = "",
32
+ [string]$RoutingConfigPath = "",
33
+ [string]$RoutingDiscoveryPath = "",
34
+ [string]$RoutingResolutionBasis = "",
35
+ [string]$RoutingFallbackReason = "",
36
+ [string]$CliProbeSummary = "",
37
+ [string]$PromptBundlePath = "",
38
+ [string]$InvocationExitCode = "",
39
+ [string[]]$OutputCapturePath = @(),
40
+ [string]$OutputName = ""
41
+ )
42
+
43
+ Set-StrictMode -Version Latest
44
+ $ErrorActionPreference = "Stop"
45
+
46
+ $python = if ($env:PYTHON) { $env:PYTHON } elseif (Get-Command python -ErrorAction SilentlyContinue) { "python" } else { $null }
47
+ if (-not $python) {
48
+ Write-Host "[FAIL] Python executable not found in PATH."
49
+ exit 1
50
+ }
51
+
52
+ $scriptPath = Join-Path $PSScriptRoot "recursive-subagent-action.py"
53
+ $argsList = @(
54
+ $scriptPath,
55
+ "--repo-root", $RepoRoot,
56
+ "--run-id", $RunId,
57
+ "--subagent-id", $SubagentId,
58
+ "--phase", $Phase,
59
+ "--purpose", $Purpose,
60
+ "--execution-mode", $ExecutionMode
61
+ )
62
+
63
+ if ($ArtifactPath) { $argsList += @("--artifact-path", $ArtifactPath) }
64
+ if ($ReviewBundle) { $argsList += @("--review-bundle", $ReviewBundle) }
65
+ if ($DiffBasis) { $argsList += @("--diff-basis", $DiffBasis) }
66
+ if ($OutputName) { $argsList += @("--output-name", $OutputName) }
67
+ if ($RouterUsed) { $argsList += @("--router-used", $RouterUsed) }
68
+ if ($RoutedRole) { $argsList += @("--routed-role", $RoutedRole) }
69
+ if ($RoutedCli) { $argsList += @("--routed-cli", $RoutedCli) }
70
+ if ($RoutedModel) { $argsList += @("--routed-model", $RoutedModel) }
71
+ if ($RoutingConfigPath) { $argsList += @("--routing-config-path", $RoutingConfigPath) }
72
+ if ($RoutingDiscoveryPath) { $argsList += @("--routing-discovery-path", $RoutingDiscoveryPath) }
73
+ if ($RoutingResolutionBasis) { $argsList += @("--routing-resolution-basis", $RoutingResolutionBasis) }
74
+ if ($RoutingFallbackReason) { $argsList += @("--routing-fallback-reason", $RoutingFallbackReason) }
75
+ if ($CliProbeSummary) { $argsList += @("--cli-probe-summary", $CliProbeSummary) }
76
+ if ($PromptBundlePath) { $argsList += @("--prompt-bundle-path", $PromptBundlePath) }
77
+ if ($InvocationExitCode) { $argsList += @("--invocation-exit-code", $InvocationExitCode) }
78
+
79
+ foreach ($value in @($UpstreamArtifact)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--upstream-artifact", $piece.Trim()) } } }
80
+ foreach ($value in @($Addendum)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--addendum", $piece.Trim()) } } }
81
+ foreach ($value in @($CodeRef)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--code-ref", $piece.Trim()) } } }
82
+ foreach ($value in @($MemoryRef)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--memory-ref", $piece.Trim()) } } }
83
+ foreach ($value in @($AuditQuestion)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--audit-question", $piece.Trim()) } } }
84
+ foreach ($value in @($ActionTaken)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--action-taken", $piece.Trim()) } } }
85
+ foreach ($value in @($CreatedFile)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--created-file", $piece.Trim()) } } }
86
+ foreach ($value in @($ModifiedFile)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--modified-file", $piece.Trim()) } } }
87
+ foreach ($value in @($ReviewedFile)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--reviewed-file", $piece.Trim()) } } }
88
+ foreach ($value in @($UntouchedFile)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--untouched-file", $piece.Trim()) } } }
89
+ foreach ($value in @($ArtifactRead)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--artifact-read", $piece.Trim()) } } }
90
+ foreach ($value in @($ArtifactUpdated)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--artifact-updated", $piece.Trim()) } } }
91
+ foreach ($value in @($EvidenceUsed)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--evidence-used", $piece.Trim()) } } }
92
+ foreach ($value in @($Finding)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--finding", $piece.Trim()) } } }
93
+ foreach ($value in @($VerificationPath)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--verification-path", $piece.Trim()) } } }
94
+ foreach ($value in @($VerificationItem)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--verification-item", $piece.Trim()) } } }
95
+ foreach ($value in @($OutputCapturePath)) { foreach ($piece in ($value -split ",")) { if ($piece.Trim()) { $argsList += @("--output-capture-path", $piece.Trim()) } } }
96
+
97
+ & $python @argsList
98
+ exit $LASTEXITCODE
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Generate a recursive-mode subagent action record scaffold.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import hashlib
10
+ import re
11
+ from datetime import datetime, timezone
12
+ from pathlib import Path
13
+
14
+
15
+ def normalize_repo_path(raw_path: str) -> str:
16
+ return raw_path.replace("\\", "/").strip().lstrip("/")
17
+
18
+
19
+ def slugify(value: str) -> str:
20
+ lowered = value.strip().lower()
21
+ slug = re.sub(r"[^a-z0-9]+", "-", lowered).strip("-")
22
+ return slug or "action"
23
+
24
+
25
+ def render_path_list(title: str, values: list[str]) -> list[str]:
26
+ lines = [title]
27
+ if not values:
28
+ lines.append("- none")
29
+ return lines
30
+ lines.extend(f"- `{value}`" for value in values)
31
+ return lines
32
+
33
+
34
+ def render_text_list(title: str, values: list[str]) -> list[str]:
35
+ lines = [title]
36
+ if not values:
37
+ lines.append("- none")
38
+ return lines
39
+ lines.extend(f"- {value}" for value in values)
40
+ return lines
41
+
42
+
43
+ def content_sha256(content: str) -> str:
44
+ normalized = content.replace("\r\n", "\n").replace("\r", "\n")
45
+ return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
46
+
47
+
48
+ def main() -> int:
49
+ parser = argparse.ArgumentParser(description="Generate a recursive-mode subagent action record scaffold.")
50
+ parser.add_argument("--repo-root", default=".", help="Repository root path.")
51
+ parser.add_argument("--run-id", required=True, help="Run ID under .recursive/run/.")
52
+ parser.add_argument("--subagent-id", required=True, help="Subagent identifier.")
53
+ parser.add_argument("--phase", required=True, help="Phase name for the action record.")
54
+ parser.add_argument("--purpose", required=True, help="Invocation purpose.")
55
+ parser.add_argument("--execution-mode", required=True, help="Execution mode, e.g. review, audit, implementer.")
56
+ parser.add_argument("--artifact-path", default="", help="Repo-relative current artifact path.")
57
+ parser.add_argument("--upstream-artifact", action="append", default=[], help="Repo-relative upstream artifact path.")
58
+ parser.add_argument("--addendum", action="append", default=[], help="Repo-relative addendum path.")
59
+ parser.add_argument("--review-bundle", default="", help="Repo-relative review bundle path if used.")
60
+ parser.add_argument("--diff-basis", default="", help="Explicit diff basis summary if not inferred.")
61
+ parser.add_argument("--code-ref", action="append", default=[], help="Repo-relative code reference path.")
62
+ parser.add_argument("--memory-ref", action="append", default=[], help="Repo-relative memory doc path.")
63
+ parser.add_argument("--audit-question", action="append", default=[], help="Audit/task question passed to the subagent.")
64
+ parser.add_argument("--action-taken", action="append", default=[], help="Concrete delegated action taken by the subagent.")
65
+ parser.add_argument("--created-file", action="append", default=[], help="Repo-relative created file path.")
66
+ parser.add_argument("--modified-file", action="append", default=[], help="Repo-relative modified file path.")
67
+ parser.add_argument("--reviewed-file", action="append", default=[], help="Repo-relative reviewed file path.")
68
+ parser.add_argument("--untouched-file", action="append", default=[], help="Repo-relative relevant but untouched file path.")
69
+ parser.add_argument("--artifact-read", action="append", default=[], help="Repo-relative recursive artifact read by the subagent.")
70
+ parser.add_argument("--artifact-updated", action="append", default=[], help="Repo-relative recursive artifact updated by the subagent.")
71
+ parser.add_argument("--evidence-used", action="append", default=[], help="Repo-relative evidence path used by the subagent.")
72
+ parser.add_argument("--finding", action="append", default=[], help="Claimed finding or unresolved point.")
73
+ parser.add_argument("--verification-path", action="append", default=[], help="Repo-relative file or artifact path the controller should inspect first.")
74
+ parser.add_argument("--verification-item", action="append", default=[], help="Main-agent verification handoff item.")
75
+ parser.add_argument("--router-used", default="", help="Router identifier used for delegated dispatch, e.g. recursive-router.")
76
+ parser.add_argument("--routed-role", default="", help="Canonical routed role resolved for this action.")
77
+ parser.add_argument("--routed-cli", default="", help="Resolved external CLI id if any.")
78
+ parser.add_argument("--routed-model", default="", help="Resolved model id if any.")
79
+ parser.add_argument("--routing-config-path", default="", help="Repo-relative routing policy path.")
80
+ parser.add_argument("--routing-discovery-path", default="", help="Repo-relative routing discovery path.")
81
+ parser.add_argument("--routing-resolution-basis", default="", help="Short explanation of how the route was resolved.")
82
+ parser.add_argument("--routing-fallback-reason", default="", help="Why execution fell back from routed delegation, if applicable.")
83
+ parser.add_argument("--cli-probe-summary", default="", help="Compact CLI probe summary for the action record.")
84
+ parser.add_argument("--prompt-bundle-path", default="", help="Repo-relative routed prompt bundle path if used.")
85
+ parser.add_argument("--invocation-exit-code", default="", help="External CLI invocation exit code if applicable.")
86
+ parser.add_argument("--output-capture-path", action="append", default=[], help="Repo-relative captured output path from the routed CLI.")
87
+ parser.add_argument("--output-name", default="", help="Optional action record filename under subagents/.")
88
+ args = parser.parse_args()
89
+
90
+ repo_root = Path(args.repo_root).resolve()
91
+ run_dir = repo_root / ".recursive" / "run" / args.run_id.strip()
92
+ if not run_dir.exists():
93
+ print(f"[FAIL] Run directory not found: {run_dir}")
94
+ return 1
95
+
96
+ subagents_dir = run_dir / "subagents"
97
+ subagents_dir.mkdir(parents=True, exist_ok=True)
98
+
99
+ bundle_path = normalize_repo_path(args.review_bundle) if args.review_bundle.strip() else ""
100
+ artifact_path = normalize_repo_path(args.artifact_path) if args.artifact_path.strip() else ""
101
+ artifact_hash = ""
102
+ if artifact_path and (repo_root / artifact_path).exists():
103
+ artifact_hash = content_sha256((repo_root / artifact_path).read_text(encoding="utf-8"))
104
+
105
+ diff_basis = args.diff_basis.strip() or "See /.recursive/run/<run-id>/00-worktree.md for the normalized diff basis used."
106
+ timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
107
+ output_name = args.output_name.strip()
108
+ if not output_name:
109
+ output_name = f"{timestamp.replace(':', '').replace('-', '')}-{slugify(args.subagent_id)}-action.md"
110
+ if not output_name.lower().endswith(".md"):
111
+ output_name = f"{output_name}.md"
112
+ output_path = subagents_dir / output_name
113
+ output_rel = normalize_repo_path(str(output_path.relative_to(repo_root)))
114
+
115
+ upstream_artifacts = sorted(set(normalize_repo_path(value) for value in args.upstream_artifact if value.strip()))
116
+ addenda = sorted(set(normalize_repo_path(value) for value in args.addendum if value.strip()))
117
+ code_refs = sorted(set(normalize_repo_path(value) for value in args.code_ref if value.strip()))
118
+ memory_refs = sorted(set(normalize_repo_path(value) for value in args.memory_ref if value.strip()))
119
+ created_files = sorted(set(normalize_repo_path(value) for value in args.created_file if value.strip()))
120
+ modified_files = sorted(set(normalize_repo_path(value) for value in args.modified_file if value.strip()))
121
+ reviewed_files = sorted(set(normalize_repo_path(value) for value in args.reviewed_file if value.strip()))
122
+ untouched_files = sorted(set(normalize_repo_path(value) for value in args.untouched_file if value.strip()))
123
+ artifacts_read = sorted(set(normalize_repo_path(value) for value in args.artifact_read if value.strip()))
124
+ artifacts_updated = sorted(set(normalize_repo_path(value) for value in args.artifact_updated if value.strip()))
125
+ evidence_used = sorted(set(normalize_repo_path(value) for value in args.evidence_used if value.strip()))
126
+ actions_taken = [value.strip() for value in args.action_taken if value.strip()]
127
+ verification_paths = sorted(set(normalize_repo_path(value) for value in args.verification_path if value.strip()))
128
+ output_capture_paths = sorted(set(normalize_repo_path(value) for value in args.output_capture_path if value.strip()))
129
+ routing_config_path = normalize_repo_path(args.routing_config_path) if args.routing_config_path.strip() else ""
130
+ routing_discovery_path = normalize_repo_path(args.routing_discovery_path) if args.routing_discovery_path.strip() else ""
131
+ prompt_bundle_path = normalize_repo_path(args.prompt_bundle_path) if args.prompt_bundle_path.strip() else ""
132
+
133
+ lines: list[str] = [
134
+ "# Subagent Action Record",
135
+ "",
136
+ "## Metadata",
137
+ f"- Subagent ID: `{args.subagent_id.strip()}`",
138
+ f"- Run ID: `{args.run_id.strip()}`",
139
+ f"- Phase: `{args.phase.strip()}`",
140
+ f"- Purpose: `{args.purpose.strip()}`",
141
+ f"- Execution Mode: `{args.execution_mode.strip()}`",
142
+ f"- Timestamp: `{timestamp}`",
143
+ f"- Action Record Path: `/{output_rel}`",
144
+ "",
145
+ "## Inputs Provided",
146
+ f"- Current Artifact: `{('/' + artifact_path) if artifact_path else 'none'}`",
147
+ f"- Artifact Content Hash: `{artifact_hash or 'UNKNOWN'}`",
148
+ ]
149
+ lines.extend(render_path_list("- Upstream Artifacts:", [f"/{value}" for value in upstream_artifacts]))
150
+ lines.extend(render_path_list("- Addenda:", [f"/{value}" for value in addenda]))
151
+ lines.append(f"- Review Bundle: `{('/' + bundle_path) if bundle_path else 'none'}`")
152
+ lines.append(f"- Diff Basis: `{diff_basis}`")
153
+ lines.extend(render_path_list("- Code Refs:", [f"/{value}" for value in code_refs]))
154
+ lines.extend(render_path_list("- Memory Refs:", [f"/{value}" for value in memory_refs]))
155
+ lines.extend(render_text_list("- Audit / Task Questions:", args.audit_question))
156
+ lines.append("")
157
+ lines.append("## Routing")
158
+ lines.append(f"- Router Used: `{args.router_used.strip() or 'none'}`")
159
+ lines.append(f"- Routed Role: `{args.routed_role.strip() or 'none'}`")
160
+ lines.append(f"- Routed CLI: `{args.routed_cli.strip() or 'none'}`")
161
+ lines.append(f"- Routed Model: `{args.routed_model.strip() or 'none'}`")
162
+ lines.append(f"- Routing Config Path: `{('/' + routing_config_path) if routing_config_path else 'none'}`")
163
+ lines.append(f"- Routing Discovery Path: `{('/' + routing_discovery_path) if routing_discovery_path else 'none'}`")
164
+ lines.append(f"- Routing Resolution Basis: `{args.routing_resolution_basis.strip() or 'none'}`")
165
+ lines.append(f"- Routing Fallback Reason: `{args.routing_fallback_reason.strip() or 'none'}`")
166
+ lines.append(f"- CLI Probe Summary: `{args.cli_probe_summary.strip() or 'none'}`")
167
+ lines.append(f"- Prompt Bundle Path: `{('/' + prompt_bundle_path) if prompt_bundle_path else 'none'}`")
168
+ lines.append(f"- Invocation Exit Code: `{args.invocation_exit_code.strip() or 'none'}`")
169
+ lines.extend(render_path_list("- Output Capture Paths:", [f"/{value}" for value in output_capture_paths]))
170
+ lines.append("")
171
+ lines.extend(render_text_list("## Claimed Actions Taken", actions_taken))
172
+ lines.append("")
173
+ lines.append("## Claimed File Impact")
174
+ lines.extend(render_path_list("### Created", [f"/{value}" for value in created_files]))
175
+ lines.extend(render_path_list("### Modified", [f"/{value}" for value in modified_files]))
176
+ lines.extend(render_path_list("### Reviewed", [f"/{value}" for value in reviewed_files]))
177
+ lines.extend(render_path_list("### Relevant but Untouched", [f"/{value}" for value in untouched_files]))
178
+ lines.append("")
179
+ lines.append("## Claimed Artifact Impact")
180
+ lines.extend(render_path_list("### Read", [f"/{value}" for value in artifacts_read]))
181
+ lines.extend(render_path_list("### Updated", [f"/{value}" for value in artifacts_updated]))
182
+ lines.extend(render_path_list("### Evidence Used", [f"/{value}" for value in evidence_used]))
183
+ lines.append("")
184
+ lines.extend(render_text_list("## Claimed Findings", args.finding))
185
+ lines.append("")
186
+ lines.append("## Verification Handoff")
187
+ lines.extend(render_path_list("- Inspect first:", [f"/{value}" if value.startswith(".recursive/") else value for value in verification_paths]))
188
+ lines.extend(render_text_list("- Notes:", args.verification_item))
189
+ lines.append("")
190
+
191
+ output_path.write_text("\n".join(lines), encoding="utf-8", newline="\n")
192
+ print(f"[OK] Wrote subagent action record: /{output_rel}")
193
+ return 0
194
+
195
+
196
+ if __name__ == "__main__":
197
+ raise SystemExit(main())
@@ -0,0 +1,23 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [Parameter(Mandatory = $true)][string]$RepoRoot,
4
+ [Parameter(Mandatory = $true)][string]$PromptFile,
5
+ [Parameter()][string]$ResponseFile = ""
6
+ )
7
+
8
+ Set-StrictMode -Version Latest
9
+ $ErrorActionPreference = "Stop"
10
+
11
+ $python = if ($env:PYTHON) { $env:PYTHON } elseif (Get-Command python -ErrorAction SilentlyContinue) { "python" } else { $null }
12
+ if (-not $python) {
13
+ Write-Host "[FAIL] Python executable not found in PATH."
14
+ exit 1
15
+ }
16
+
17
+ $scriptPath = Join-Path $PSScriptRoot "recursive-training-extract.py"
18
+ $argsList = @("--repo-root", $RepoRoot, "--prompt-file", $PromptFile)
19
+ if ($ResponseFile) {
20
+ $argsList += @("--response-file", $ResponseFile)
21
+ }
22
+ & $python $scriptPath @argsList
23
+ exit $LASTEXITCODE
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Companion extractor for recursive-training prompt evaluation.
4
+
5
+ The training scripts build extraction prompts and hand them to this script.
6
+ Supported evaluation modes (first match wins):
7
+
8
+ 1. --response-file PATH — print the file contents (agent/offline JSON)
9
+ 2. Sibling <prompt-file>.response.json — same as response-file when present
10
+ 3. RECURSIVE_TRAINING_EXTRACTOR_CMD — shell command; `{prompt_file}` and
11
+ `{repo_root}` are substituted; stdout is the extraction result
12
+ 4. Otherwise exit 2 (unavailable)
13
+
14
+ Exit codes:
15
+ 0: extraction text printed to stdout
16
+ 1: invalid arguments / missing prompt
17
+ 2: no extractor wired
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ import os
24
+ import shlex
25
+ import subprocess
26
+ import sys
27
+ from pathlib import Path
28
+
29
+
30
+ def _run_external_command(command: str, repo_root: Path, prompt_path: Path) -> int:
31
+ rendered = command.format(prompt_file=str(prompt_path), repo_root=str(repo_root))
32
+ # Windows-friendly: run via shell when the command is a single string template
33
+ result = subprocess.run(
34
+ rendered if os.name == "nt" else shlex.split(rendered),
35
+ check=False,
36
+ capture_output=True,
37
+ text=True,
38
+ shell=(os.name == "nt"),
39
+ )
40
+ if result.returncode != 0:
41
+ err = (result.stderr or result.stdout or "extractor command failed").strip()
42
+ print(err, file=sys.stderr)
43
+ return result.returncode if result.returncode != 2 else 1
44
+ sys.stdout.write(result.stdout)
45
+ if result.stdout and not result.stdout.endswith("\n"):
46
+ sys.stdout.write("\n")
47
+ return 0
48
+
49
+
50
+ def main() -> int:
51
+ parser = argparse.ArgumentParser(description="Companion extractor for recursive-training")
52
+ parser.add_argument("--repo-root", required=True, help="Repository root path")
53
+ parser.add_argument("--prompt-file", required=True, help="Path to the prompt file to evaluate")
54
+ parser.add_argument(
55
+ "--response-file",
56
+ default="",
57
+ help="Optional precomputed JSON/text response to emit instead of calling a model",
58
+ )
59
+ args = parser.parse_args()
60
+
61
+ repo_root = Path(args.repo_root).resolve()
62
+ prompt_path = Path(args.prompt_file).resolve()
63
+ if not prompt_path.exists():
64
+ print("ERROR: Prompt file not found for recursive-training extraction.", file=sys.stderr)
65
+ return 1
66
+
67
+ response_path = Path(args.response_file).resolve() if args.response_file.strip() else None
68
+ if response_path is None:
69
+ sibling = prompt_path.with_suffix(prompt_path.suffix + ".response.json")
70
+ if not sibling.exists():
71
+ sibling = Path(str(prompt_path) + ".response.json")
72
+ if sibling.exists():
73
+ response_path = sibling
74
+
75
+ if response_path is not None:
76
+ if not response_path.exists():
77
+ print(f"ERROR: Response file not found: {response_path}", file=sys.stderr)
78
+ return 1
79
+ payload = response_path.read_text(encoding="utf-8")
80
+ sys.stdout.write(payload)
81
+ if payload and not payload.endswith("\n"):
82
+ sys.stdout.write("\n")
83
+ return 0
84
+
85
+ extractor_cmd = os.environ.get("RECURSIVE_TRAINING_EXTRACTOR_CMD", "").strip()
86
+ if extractor_cmd:
87
+ return _run_external_command(extractor_cmd, repo_root, prompt_path)
88
+
89
+ print(
90
+ "Training extractor is not available for recursive-training in this environment.\n"
91
+ "Wire one of: --response-file, <prompt>.response.json, or "
92
+ "RECURSIVE_TRAINING_EXTRACTOR_CMD.",
93
+ file=sys.stderr,
94
+ )
95
+ return 2
96
+
97
+
98
+ if __name__ == "__main__":
99
+ raise SystemExit(main())
@@ -0,0 +1,52 @@
1
+ #Requires -Version 5.1
2
+ <#
3
+ .SYNOPSIS
4
+ Repository-local Training-Free GRPO for recursive-mode runs (PowerShell wrapper).
5
+
6
+ .DESCRIPTION
7
+ Thin wrapper that calls recursive-training-grpo.py with the same arguments.
8
+ Extraction is delegated to recursive-training-extract.py (response-file or
9
+ RECURSIVE_TRAINING_EXTRACTOR_CMD). This wrapper does not select an LLM provider.
10
+ #>
11
+
12
+ [CmdletBinding()]
13
+ param(
14
+ [Parameter(Mandatory = $true)]
15
+ [string]$RepoRoot,
16
+
17
+ [Parameter()]
18
+ [switch]$Incremental,
19
+
20
+ [Parameter()]
21
+ [string]$RunId,
22
+
23
+ [Parameter()]
24
+ [int]$WinnerOnlyThreshold = 0
25
+ )
26
+
27
+ $python = Get-Command python -ErrorAction SilentlyContinue
28
+ if (-not $python) {
29
+ $python = Get-Command python3 -ErrorAction SilentlyContinue
30
+ }
31
+
32
+ if (-not $python) {
33
+ Write-Error "Python is required but not found. Please install Python 3.10+."
34
+ exit 1
35
+ }
36
+
37
+ $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
38
+ $pyScript = Join-Path $scriptDir "recursive-training-grpo.py"
39
+
40
+ $argsList = @("--repo-root", $RepoRoot)
41
+ if ($Incremental) {
42
+ $argsList += "--incremental"
43
+ }
44
+ if ($RunId) {
45
+ $argsList += @("--run-id", $RunId)
46
+ }
47
+ if ($WinnerOnlyThreshold -gt 0) {
48
+ $argsList += @("--winner-only-threshold", "$WinnerOnlyThreshold")
49
+ }
50
+
51
+ & $python.Source $pyScript @argsList
52
+ exit $LASTEXITCODE