@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,27 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [Parameter(Mandatory = $true)][string[]]$Set,
4
+ [string]$RepoRoot = (Get-Location).Path,
5
+ [switch]$Json,
6
+ [int]$TimeoutMs = 0
7
+ )
8
+
9
+ Set-StrictMode -Version Latest
10
+ $ErrorActionPreference = "Stop"
11
+
12
+ $python = if ($env:PYTHON) { $env:PYTHON } elseif (Get-Command python -ErrorAction SilentlyContinue) { "python" } else { $null }
13
+ if (-not $python) {
14
+ Write-Host "[FAIL] Python executable not found in PATH."
15
+ exit 1
16
+ }
17
+
18
+ $scriptPath = Join-Path $PSScriptRoot "recursive-router-configure.py"
19
+ $argsList = @($scriptPath, "--repo-root", $RepoRoot)
20
+ foreach ($assignment in $Set) {
21
+ $argsList += @("--set", $assignment)
22
+ }
23
+ if ($Json) { $argsList += "--json" }
24
+ if ($TimeoutMs -gt 0) { $argsList += @("--timeout-ms", $TimeoutMs) }
25
+
26
+ & $python @argsList
27
+ exit $LASTEXITCODE
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ from recursive_router_lib import RouterConfigError, canonicalize_router_role, configure_verified_routes, pretty_json
8
+
9
+
10
+ def parse_assignment(raw: str) -> tuple[str, str, str]:
11
+ if "=" not in raw:
12
+ raise RouterConfigError(f"Invalid --set value {raw!r}. Expected role=cli:model.")
13
+ role, target = raw.split("=", 1)
14
+ role = canonicalize_router_role(role.strip())
15
+ if ":" not in target:
16
+ raise RouterConfigError(f"Invalid --set value {raw!r}. Expected role=cli:model.")
17
+ cli_id, model = target.split(":", 1)
18
+ cli_id = cli_id.strip()
19
+ model = model.strip()
20
+ if not cli_id:
21
+ raise RouterConfigError(f"Invalid --set value {raw!r}. CLI id must be non-empty.")
22
+ if not model:
23
+ raise RouterConfigError(f"Invalid --set value {raw!r}. Model must be non-empty.")
24
+ return role, cli_id, model
25
+
26
+
27
+ def main() -> int:
28
+ parser = argparse.ArgumentParser(description="Verify recursive-router bindings before saving them.")
29
+ parser.add_argument("--repo-root", default=".", help="Repository root path.")
30
+ parser.add_argument(
31
+ "--set",
32
+ dest="assignments",
33
+ action="append",
34
+ required=True,
35
+ help="Role binding in role=cli:model form. Repeatable. Prefer stage-aligned roles; legacy aliases are accepted.",
36
+ )
37
+ parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON.")
38
+ parser.add_argument(
39
+ "--timeout-ms",
40
+ type=int,
41
+ default=50000,
42
+ help="Verification timeout in milliseconds. Defaults to 50000.",
43
+ )
44
+ args = parser.parse_args()
45
+
46
+ repo_root = Path(args.repo_root).resolve()
47
+ try:
48
+ assignments: dict[str, dict[str, str]] = {}
49
+ for raw_assignment in args.assignments:
50
+ role, cli_id, model = parse_assignment(raw_assignment)
51
+ if role in assignments:
52
+ raise RouterConfigError(f"Duplicate role assignment provided for {role}.")
53
+ assignments[role] = {"cli": cli_id, "model": model}
54
+
55
+ payload = configure_verified_routes(repo_root, assignments=assignments, timeout_ms=args.timeout_ms)
56
+ except RouterConfigError as exc:
57
+ print(f"[FAIL] {exc}")
58
+ return 1
59
+
60
+ if args.json:
61
+ print(pretty_json(payload), end="")
62
+ else:
63
+ print(f"Saved={'yes' if payload['saved'] else 'no'} Policy={payload['policy_path']}")
64
+ for role, result in payload["verification_results"].items():
65
+ print(
66
+ f"{role}: verified={'yes' if result['verified'] else 'no'} "
67
+ f"cli={result['cli']} model={result['model']} transport={result['transport']}"
68
+ )
69
+ print(f" reason: {result['reason']}")
70
+ return 0 if payload["saved"] else 1
71
+
72
+
73
+ if __name__ == "__main__":
74
+ raise SystemExit(main())
@@ -0,0 +1,17 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [string]$RepoRoot = (Get-Location).Path
4
+ )
5
+
6
+ Set-StrictMode -Version Latest
7
+ $ErrorActionPreference = "Stop"
8
+
9
+ $python = if ($env:PYTHON) { $env:PYTHON } elseif (Get-Command python -ErrorAction SilentlyContinue) { "python" } else { $null }
10
+ if (-not $python) {
11
+ Write-Host "[FAIL] Python executable not found in PATH."
12
+ exit 1
13
+ }
14
+
15
+ $scriptPath = Join-Path $PSScriptRoot "recursive-router-init.py"
16
+ & $python $scriptPath "--repo-root" $RepoRoot
17
+ exit $LASTEXITCODE
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+
6
+ from pathlib import Path
7
+
8
+ from recursive_router_lib import RouterConfigError, ensure_router_scaffold, normalize_repo_path
9
+
10
+
11
+ def main() -> int:
12
+ parser = argparse.ArgumentParser(description="Initialize recursive-router config files.")
13
+ parser.add_argument("--repo-root", default=".", help="Repository root path.")
14
+ args = parser.parse_args()
15
+
16
+ repo_root = Path(args.repo_root).resolve()
17
+ try:
18
+ policy_path, discovery_path = ensure_router_scaffold(repo_root)
19
+ except RouterConfigError as exc:
20
+ print(f"[FAIL] {exc}")
21
+ return 1
22
+
23
+ print(f"[OK] Router policy path: {normalize_repo_path(str(policy_path.relative_to(repo_root)))}")
24
+ print(f"[OK] Discovery inventory path: {normalize_repo_path(str(discovery_path.relative_to(repo_root)))}")
25
+ return 0
26
+
27
+
28
+ if __name__ == "__main__":
29
+ raise SystemExit(main())
@@ -0,0 +1,47 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [string]$RepoRoot = (Get-Location).Path,
4
+ [string]$Role,
5
+ [string]$PromptFile,
6
+ [string]$Prompt,
7
+ [string]$OutputFile,
8
+ [string]$MetadataFile,
9
+ [switch]$Json,
10
+ [switch]$NoWrite,
11
+ [int]$TimeoutMs = 0
12
+ )
13
+
14
+ Set-StrictMode -Version Latest
15
+ $ErrorActionPreference = "Stop"
16
+
17
+ $python = if ($env:PYTHON) { $env:PYTHON } elseif (Get-Command python -ErrorAction SilentlyContinue) { "python" } else { $null }
18
+ if (-not $python) {
19
+ Write-Host "[FAIL] Python executable not found in PATH."
20
+ exit 1
21
+ }
22
+
23
+ if (-not $Role) {
24
+ Write-Host "[FAIL] -Role is required."
25
+ exit 1
26
+ }
27
+ if ([string]::IsNullOrWhiteSpace($PromptFile) -and [string]::IsNullOrWhiteSpace($Prompt)) {
28
+ Write-Host "[FAIL] Provide either -PromptFile or -Prompt."
29
+ exit 1
30
+ }
31
+ if (-not [string]::IsNullOrWhiteSpace($PromptFile) -and -not [string]::IsNullOrWhiteSpace($Prompt)) {
32
+ Write-Host "[FAIL] Provide only one of -PromptFile or -Prompt."
33
+ exit 1
34
+ }
35
+
36
+ $scriptPath = Join-Path $PSScriptRoot "recursive-router-invoke.py"
37
+ $argsList = @($scriptPath, "--repo-root", $RepoRoot, "--role", $Role)
38
+ if ($PromptFile) { $argsList += @("--prompt-file", $PromptFile) }
39
+ if ($Prompt) { $argsList += @("--prompt", $Prompt) }
40
+ if ($OutputFile) { $argsList += @("--output-file", $OutputFile) }
41
+ if ($MetadataFile) { $argsList += @("--metadata-file", $MetadataFile) }
42
+ if ($Json) { $argsList += "--json" }
43
+ if ($NoWrite) { $argsList += "--no-write" }
44
+ if ($TimeoutMs -gt 0) { $argsList += @("--timeout-ms", $TimeoutMs) }
45
+
46
+ & $python @argsList
47
+ exit $LASTEXITCODE
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ from recursive_router_lib import RouterConfigError, invoke_route_binding, normalize_repo_path, pretty_json, write_json
8
+
9
+
10
+ def _normalize_prompt_path(repo_root: Path, prompt_path: Path) -> str:
11
+ try:
12
+ return normalize_repo_path(str(prompt_path.relative_to(repo_root)))
13
+ except ValueError:
14
+ return str(prompt_path)
15
+
16
+
17
+ def _is_run_subagents_path(repo_root: Path, path: Path) -> bool:
18
+ try:
19
+ relative = path.resolve().relative_to(repo_root)
20
+ except ValueError:
21
+ return False
22
+ parts = relative.parts
23
+ return len(parts) >= 4 and parts[0] == ".recursive" and parts[1] == "run" and parts[3] == "subagents"
24
+
25
+
26
+ def main() -> int:
27
+ parser = argparse.ArgumentParser(description="Invoke a recursive-router role with a real prompt bundle or inline prompt.")
28
+ parser.add_argument("--repo-root", default=".", help="Repository root path.")
29
+ parser.add_argument("--role", required=True, help="Stage-aligned routed role to invoke. Legacy aliases are accepted.")
30
+ prompt_group = parser.add_mutually_exclusive_group(required=True)
31
+ prompt_group.add_argument("--prompt-file", help="Path to a prompt bundle file to dispatch.")
32
+ prompt_group.add_argument("--prompt", help="Inline prompt text to dispatch.")
33
+ parser.add_argument("--output-file", help="Optional file path to write the routed model output text.")
34
+ parser.add_argument("--metadata-file", help="Optional file path to write the full invocation payload JSON.")
35
+ parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON.")
36
+ parser.add_argument("--no-write", action="store_true", help="Do not refresh the discovery inventory file.")
37
+ parser.add_argument("--timeout-ms", type=int, default=None, help="Override probe and invocation timeout in milliseconds.")
38
+ args = parser.parse_args()
39
+
40
+ repo_root = Path(args.repo_root).resolve()
41
+ try:
42
+ if args.output_file and _is_run_subagents_path(repo_root, Path(args.output_file)):
43
+ raise RouterConfigError(
44
+ "Raw routed output must be stored under the run evidence tree, "
45
+ "for example /.recursive/run/<run-id>/evidence/router/, not under subagents/."
46
+ )
47
+ if args.metadata_file and _is_run_subagents_path(repo_root, Path(args.metadata_file)):
48
+ raise RouterConfigError(
49
+ "Router invocation metadata must be stored under the run evidence tree, "
50
+ "for example /.recursive/run/<run-id>/evidence/router/, not under subagents/."
51
+ )
52
+ prompt_bundle_path = ""
53
+ if args.prompt_file:
54
+ prompt_path = Path(args.prompt_file).resolve()
55
+ prompt_text = prompt_path.read_text(encoding="utf-8")
56
+ prompt_bundle_path = _normalize_prompt_path(repo_root, prompt_path)
57
+ prompt_source = "file"
58
+ else:
59
+ prompt_text = args.prompt
60
+ prompt_source = "inline"
61
+
62
+ payload = invoke_route_binding(
63
+ repo_root,
64
+ role=args.role,
65
+ prompt=prompt_text,
66
+ timeout_ms=args.timeout_ms,
67
+ write_discovery=not args.no_write,
68
+ )
69
+ payload["prompt_source"] = prompt_source
70
+ payload["prompt_bundle_path"] = prompt_bundle_path
71
+ except (OSError, RouterConfigError) as exc:
72
+ print(f"[FAIL] {exc}")
73
+ return 1
74
+
75
+ if args.output_file and payload["output_text"]:
76
+ output_path = Path(args.output_file).resolve()
77
+ output_path.parent.mkdir(parents=True, exist_ok=True)
78
+ output_path.write_text(str(payload["output_text"]).rstrip() + "\n", encoding="utf-8", newline="\n")
79
+ if args.metadata_file:
80
+ metadata_path = Path(args.metadata_file).resolve()
81
+ write_json(metadata_path, payload)
82
+
83
+ if args.json:
84
+ print(pretty_json(payload), end="")
85
+ else:
86
+ print(
87
+ f"Role={payload['role']} Decision={payload['decision']['decision']} "
88
+ f"CLI={payload['cli'] or 'none'} Model={payload['model'] or 'none'} "
89
+ f"Success={'yes' if payload['success'] else 'no'}"
90
+ )
91
+ print(f"Reason: {payload['reason']}")
92
+ if payload["output_text"]:
93
+ print("")
94
+ print(payload["output_text"])
95
+ elif payload["decision"].get("prompt"):
96
+ print("")
97
+ print(payload["decision"]["prompt"])
98
+
99
+ return 0 if payload["success"] else 1
100
+
101
+
102
+ if __name__ == "__main__":
103
+ raise SystemExit(main())
@@ -0,0 +1,25 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [string]$RepoRoot = (Get-Location).Path,
4
+ [switch]$Json,
5
+ [switch]$NoWrite,
6
+ [int]$TimeoutMs = 0
7
+ )
8
+
9
+ Set-StrictMode -Version Latest
10
+ $ErrorActionPreference = "Stop"
11
+
12
+ $python = if ($env:PYTHON) { $env:PYTHON } elseif (Get-Command python -ErrorAction SilentlyContinue) { "python" } else { $null }
13
+ if (-not $python) {
14
+ Write-Host "[FAIL] Python executable not found in PATH."
15
+ exit 1
16
+ }
17
+
18
+ $scriptPath = Join-Path $PSScriptRoot "recursive-router-probe.py"
19
+ $argsList = @($scriptPath, "--repo-root", $RepoRoot)
20
+ if ($Json) { $argsList += "--json" }
21
+ if ($NoWrite) { $argsList += "--no-write" }
22
+ if ($TimeoutMs -gt 0) { $argsList += @("--timeout-ms", $TimeoutMs) }
23
+
24
+ & $python @argsList
25
+ exit $LASTEXITCODE
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ import json
6
+ from pathlib import Path
7
+
8
+ from recursive_router_lib import RouterConfigError, ensure_router_scaffold, pretty_json, probe_inventory
9
+
10
+
11
+ def main() -> int:
12
+ parser = argparse.ArgumentParser(description="Probe available routers for recursive-router.")
13
+ parser.add_argument("--repo-root", default=".", help="Repository root path.")
14
+ parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON.")
15
+ parser.add_argument("--no-write", action="store_true", help="Do not refresh the discovery inventory file.")
16
+ parser.add_argument("--timeout-ms", type=int, default=None, help="Override probe timeout in milliseconds.")
17
+ args = parser.parse_args()
18
+
19
+ repo_root = Path(args.repo_root).resolve()
20
+ try:
21
+ ensure_router_scaffold(repo_root)
22
+ inventory = probe_inventory(
23
+ repo_root,
24
+ timeout_ms=args.timeout_ms,
25
+ probe_tool="recursive-router-probe",
26
+ write_discovery=not args.no_write,
27
+ )
28
+ except RouterConfigError as exc:
29
+ print(f"[FAIL] {exc}")
30
+ return 1
31
+
32
+ if args.json:
33
+ print(pretty_json(inventory), end="")
34
+ else:
35
+ for entry in inventory["clis"]:
36
+ status = "available" if entry["available"] else "unavailable"
37
+ version = entry["version"] or "unknown version"
38
+ models = ", ".join(entry["models"]) if entry["models"] else "(no models listed)"
39
+ print(f"- {entry['id']}: {status}; version={version}; models={models}; source={entry['model_source']}")
40
+ return 0
41
+
42
+
43
+ if __name__ == "__main__":
44
+ raise SystemExit(main())
@@ -0,0 +1,26 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [Parameter(Mandatory = $true)][string]$Role,
4
+ [string]$RepoRoot = (Get-Location).Path,
5
+ [switch]$Json,
6
+ [switch]$NoWrite,
7
+ [int]$TimeoutMs = 0
8
+ )
9
+
10
+ Set-StrictMode -Version Latest
11
+ $ErrorActionPreference = "Stop"
12
+
13
+ $python = if ($env:PYTHON) { $env:PYTHON } elseif (Get-Command python -ErrorAction SilentlyContinue) { "python" } else { $null }
14
+ if (-not $python) {
15
+ Write-Host "[FAIL] Python executable not found in PATH."
16
+ exit 1
17
+ }
18
+
19
+ $scriptPath = Join-Path $PSScriptRoot "recursive-router-resolve.py"
20
+ $argsList = @($scriptPath, "--repo-root", $RepoRoot, "--role", $Role)
21
+ if ($Json) { $argsList += "--json" }
22
+ if ($NoWrite) { $argsList += "--no-write" }
23
+ if ($TimeoutMs -gt 0) { $argsList += @("--timeout-ms", $TimeoutMs) }
24
+
25
+ & $python @argsList
26
+ exit $LASTEXITCODE
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ from recursive_router_lib import RouterConfigError, pretty_json, resolve_route
8
+
9
+
10
+ def main() -> int:
11
+ parser = argparse.ArgumentParser(description="Resolve recursive-router routing for a subagent role.")
12
+ parser.add_argument("--repo-root", default=".", help="Repository root path.")
13
+ parser.add_argument("--role", required=True, help="Stage-aligned routed role to resolve. Legacy aliases are accepted.")
14
+ parser.add_argument("--json", action="store_true", help="Emit machine-readable JSON.")
15
+ parser.add_argument("--no-write", action="store_true", help="Do not refresh the discovery inventory file.")
16
+ parser.add_argument("--timeout-ms", type=int, default=None, help="Override probe timeout in milliseconds.")
17
+ args = parser.parse_args()
18
+
19
+ repo_root = Path(args.repo_root).resolve()
20
+ try:
21
+ decision = resolve_route(
22
+ repo_root,
23
+ role=args.role,
24
+ timeout_ms=args.timeout_ms,
25
+ write_discovery=not args.no_write,
26
+ )
27
+ except RouterConfigError as exc:
28
+ print(f"[FAIL] {exc}")
29
+ return 1
30
+
31
+ if args.json:
32
+ print(pretty_json(decision), end="")
33
+ else:
34
+ print(
35
+ f"Role={decision['role']} Decision={decision['decision']} "
36
+ f"CLI={decision['cli'] or 'none'} Model={decision['model'] or 'none'}"
37
+ )
38
+ print(f"Reason: {decision['reason']}")
39
+ if decision.get("prompt"):
40
+ print("")
41
+ print(decision["prompt"])
42
+ return 0
43
+
44
+
45
+ if __name__ == "__main__":
46
+ raise SystemExit(main())
@@ -0,0 +1,17 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [string]$RepoRoot = (Get-Location).Path
4
+ )
5
+
6
+ Set-StrictMode -Version Latest
7
+ $ErrorActionPreference = "Stop"
8
+
9
+ $python = if ($env:PYTHON) { $env:PYTHON } elseif (Get-Command python -ErrorAction SilentlyContinue) { "python" } else { $null }
10
+ if (-not $python) {
11
+ Write-Host "[FAIL] Python executable not found in PATH."
12
+ exit 1
13
+ }
14
+
15
+ $scriptPath = Join-Path $PSScriptRoot "recursive-router-validate.py"
16
+ & $python $scriptPath "--repo-root" $RepoRoot
17
+ exit $LASTEXITCODE
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import argparse
5
+ from pathlib import Path
6
+
7
+ from recursive_router_lib import RouterConfigError, load_policy, normalize_repo_path, router_policy_path
8
+
9
+
10
+ def main() -> int:
11
+ parser = argparse.ArgumentParser(description="Validate recursive-router routing policy.")
12
+ parser.add_argument("--repo-root", default=".", help="Repository root path.")
13
+ args = parser.parse_args()
14
+
15
+ repo_root = Path(args.repo_root).resolve()
16
+ try:
17
+ load_policy(repo_root)
18
+ except RouterConfigError as exc:
19
+ print(f"[FAIL] {exc}")
20
+ return 1
21
+
22
+ print(f"[OK] Routing policy is valid: {normalize_repo_path(str(router_policy_path(repo_root).relative_to(repo_root)))}")
23
+ return 0
24
+
25
+
26
+ if __name__ == "__main__":
27
+ raise SystemExit(main())
@@ -0,0 +1,23 @@
1
+ [CmdletBinding()]
2
+ param(
3
+ [string]$RunId = "",
4
+ [string]$RepoRoot = (Get-Location).Path,
5
+ [switch]$ShowHashes
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-status.py"
18
+ $argsList = @($scriptPath, "--repo-root", $RepoRoot)
19
+ if ($RunId) { $argsList += @("--run-id", $RunId) }
20
+ if ($ShowHashes.IsPresent) { $argsList += "--show-hashes" }
21
+
22
+ & $python @argsList
23
+ exit $LASTEXITCODE