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

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 (85) hide show
  1. package/lib/bootstrap.d.ts +6 -3
  2. package/lib/client/board.d.ts +4 -1
  3. package/lib/client/open-state.d.ts +20 -0
  4. package/lib/client/slots.d.ts +7 -1
  5. package/lib/client.js +135 -47
  6. package/lib/index.js +3391 -906
  7. package/lib/runtime.d.ts +12 -5
  8. package/lib/ts-lint.d.ts +205 -0
  9. package/package.json +1 -1
  10. package/src/bootstrap.ts +338 -331
  11. package/src/client/board.tsx +0 -0
  12. package/src/client/open-state.ts +46 -0
  13. package/src/client/slots.ts +41 -20
  14. package/src/commands.ts +11 -0
  15. package/src/index.ts +238 -228
  16. package/src/runtime.ts +19 -23
  17. package/src/ts-lint.ts +2110 -0
  18. package/references/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
  19. package/references/scripts/lint-recursive-run.ps1 +0 -25
  20. package/references/scripts/lint-recursive-run.py +0 -2870
  21. package/references/scripts/recursive-closeout.ps1 +0 -38
  22. package/references/scripts/recursive-closeout.py +0 -541
  23. package/references/scripts/recursive-init.ps1 +0 -515
  24. package/references/scripts/recursive-init.py +0 -356
  25. package/references/scripts/recursive-lock.ps1 +0 -26
  26. package/references/scripts/recursive-lock.py +0 -302
  27. package/references/scripts/recursive-review-bundle.ps1 +0 -58
  28. package/references/scripts/recursive-review-bundle.py +0 -503
  29. package/references/scripts/recursive-router-cli-configure.ps1 +0 -2
  30. package/references/scripts/recursive-router-cli-configure.py +0 -9
  31. package/references/scripts/recursive-router-cli-init.ps1 +0 -2
  32. package/references/scripts/recursive-router-cli-init.py +0 -9
  33. package/references/scripts/recursive-router-cli-invoke.ps1 +0 -2
  34. package/references/scripts/recursive-router-cli-invoke.py +0 -9
  35. package/references/scripts/recursive-router-cli-probe.ps1 +0 -2
  36. package/references/scripts/recursive-router-cli-probe.py +0 -9
  37. package/references/scripts/recursive-router-cli-resolve.ps1 +0 -2
  38. package/references/scripts/recursive-router-cli-resolve.py +0 -9
  39. package/references/scripts/recursive-router-cli-validate.ps1 +0 -2
  40. package/references/scripts/recursive-router-cli-validate.py +0 -9
  41. package/references/scripts/recursive-router-configure.ps1 +0 -27
  42. package/references/scripts/recursive-router-configure.py +0 -74
  43. package/references/scripts/recursive-router-init.ps1 +0 -17
  44. package/references/scripts/recursive-router-init.py +0 -29
  45. package/references/scripts/recursive-router-invoke.ps1 +0 -47
  46. package/references/scripts/recursive-router-invoke.py +0 -103
  47. package/references/scripts/recursive-router-probe.ps1 +0 -25
  48. package/references/scripts/recursive-router-probe.py +0 -44
  49. package/references/scripts/recursive-router-resolve.ps1 +0 -26
  50. package/references/scripts/recursive-router-resolve.py +0 -46
  51. package/references/scripts/recursive-router-validate.ps1 +0 -17
  52. package/references/scripts/recursive-router-validate.py +0 -27
  53. package/references/scripts/recursive-status.ps1 +0 -23
  54. package/references/scripts/recursive-status.py +0 -2124
  55. package/references/scripts/recursive-subagent-action.ps1 +0 -98
  56. package/references/scripts/recursive-subagent-action.py +0 -197
  57. package/references/scripts/recursive-training-extract.ps1 +0 -23
  58. package/references/scripts/recursive-training-extract.py +0 -99
  59. package/references/scripts/recursive-training-grpo.ps1 +0 -52
  60. package/references/scripts/recursive-training-grpo.py +0 -1341
  61. package/references/scripts/recursive-training-loader.ps1 +0 -78
  62. package/references/scripts/recursive-training-loader.py +0 -558
  63. package/references/scripts/recursive-training-mcp.ps1 +0 -29
  64. package/references/scripts/recursive-training-mcp.py +0 -261
  65. package/references/scripts/recursive-training-phase8-trigger.ps1 +0 -55
  66. package/references/scripts/recursive-training-phase8-trigger.py +0 -137
  67. package/references/scripts/recursive-training-sync.ps1 +0 -29
  68. package/references/scripts/recursive-training-sync.py +0 -183
  69. package/references/scripts/recursive_phase_rules.py +0 -367
  70. package/references/scripts/recursive_router_cli_lib.py +0 -2
  71. package/references/scripts/recursive_router_lib.py +0 -2282
  72. package/references/scripts/verify-locks.ps1 +0 -25
  73. package/references/scripts/verify-locks.py +0 -353
  74. package/scripts/__pycache__/lint-recursive-run.cpython-314.pyc +0 -0
  75. package/scripts/__pycache__/recursive_phase_rules.cpython-314.pyc +0 -0
  76. package/scripts/install-recursive-mode.ps1 +0 -956
  77. package/scripts/install-recursive-mode.py +0 -750
  78. package/scripts/lint-recursive-run.py +0 -2870
  79. package/scripts/recursive-closeout.py +0 -541
  80. package/scripts/recursive-init.py +0 -356
  81. package/scripts/recursive-lock.py +0 -302
  82. package/scripts/recursive-status.py +0 -2124
  83. package/scripts/recursive_phase_rules.py +0 -367
  84. package/scripts/recursive_router_lib.py +0 -2282
  85. package/scripts/verify-locks.py +0 -353
@@ -1,353 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Verify recursive-mode artifact lock integrity.
4
-
5
- Python equivalent to scripts/verify-locks.ps1.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import argparse
11
- import hashlib
12
- import importlib.util
13
- import re
14
- from dataclasses import dataclass
15
- from datetime import datetime, timezone
16
- from pathlib import Path
17
-
18
-
19
- def load_phase_rules_module():
20
- module_path = Path(__file__).with_name("recursive_phase_rules.py")
21
- spec = importlib.util.spec_from_file_location("recursive_phase_rules", module_path)
22
- if spec is None or spec.loader is None:
23
- raise RuntimeError(f"Unable to load phase rules module from {module_path}")
24
- module = importlib.util.module_from_spec(spec)
25
- try:
26
- spec.loader.exec_module(module)
27
- except FileNotFoundError:
28
- raise RuntimeError(f"Phase rules module not found: {module_path}")
29
- return module
30
-
31
-
32
- STATUS_RE = re.compile(r'(?m)^[ \t]*Status:\s*(?:`|")?(\w+)(?:`|")?\s*$')
33
- LOCK_HASH_RE = re.compile(r'(?m)^[ \t]*LockHash:\s*(?:`|")?([a-fA-F0-9]{64})(?:`|")?\s*$')
34
- LOCKED_AT_RE = re.compile(r'(?m)^[ \t]*LockedAt:\s*(?:`|")?([^`"\r\n]+)(?:`|")?\s*$')
35
- LOCK_HASH_LINE_RE = re.compile(r'(?m)^[ \t]*LockHash:.*(?:\n|$)')
36
- WORKFLOW_VERSION_RE = re.compile(r'(?m)^[ \t]*Workflow version:\s*(?:`|")?([^`"\r\n]+)(?:`|")?\s*$')
37
- CURRENT_WORKFLOW_PROFILE = "recursive-mode-audit-v2"
38
- STRICT_WORKFLOW_PROFILE = "recursive-mode-audit-v1"
39
- COMPAT_WORKFLOW_PROFILE = "memory-phase8"
40
- STRICT_WORKFLOW_PROFILES = {CURRENT_WORKFLOW_PROFILE, STRICT_WORKFLOW_PROFILE}
41
- AUDITED_PHASE_FILES = {
42
- "01-as-is.md",
43
- "01.5-root-cause.md",
44
- "02-to-be-plan.md",
45
- "03-implementation-summary.md",
46
- "03.5-code-review.md",
47
- "04-test-summary.md",
48
- "06-decisions-update.md",
49
- "07-state-update.md",
50
- "08-memory-impact.md",
51
- }
52
-
53
-
54
- @dataclass
55
- class LockResult:
56
- valid: bool
57
- error: str | None = None
58
- stored_hash: str | None = None
59
- actual_hash: str | None = None
60
- locked_at: str | None = None
61
- fixed: bool = False
62
- new_locked_at: str | None = None
63
- new_hash: str | None = None
64
-
65
-
66
- def write_status(status: str, message: str) -> None:
67
- print(f"[{status}] {message}")
68
-
69
-
70
- def normalize_for_lock_hash(content: str) -> str:
71
- normalized = content.replace("\r\n", "\n").replace("\r", "\n")
72
- normalized = LOCK_HASH_LINE_RE.sub("", normalized)
73
- return normalized
74
-
75
-
76
- def lock_hash_from_content(content: str) -> str:
77
- normalized = normalize_for_lock_hash(content)
78
- return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
79
-
80
-
81
- def get_workflow_profile(run_dir: Path) -> str:
82
- requirements_path = run_dir / "00-requirements.md"
83
- if requirements_path.exists():
84
- content = requirements_path.read_text(encoding="utf-8")
85
- match = WORKFLOW_VERSION_RE.search(content)
86
- if match:
87
- workflow_version = match.group(1).strip()
88
- if workflow_version == CURRENT_WORKFLOW_PROFILE:
89
- return CURRENT_WORKFLOW_PROFILE
90
- if workflow_version == STRICT_WORKFLOW_PROFILE:
91
- return STRICT_WORKFLOW_PROFILE
92
- if workflow_version == COMPAT_WORKFLOW_PROFILE:
93
- return COMPAT_WORKFLOW_PROFILE
94
-
95
- for artifact in ("06-decisions-update.md", "07-state-update.md", "08-memory-impact.md"):
96
- if (run_dir / artifact).exists():
97
- return COMPAT_WORKFLOW_PROFILE
98
-
99
- return "legacy"
100
-
101
-
102
- def test_lock_valid(artifact_path: Path, fix: bool = False) -> LockResult:
103
- if not artifact_path.exists():
104
- return LockResult(valid=False, error="File not found")
105
-
106
- content = artifact_path.read_text(encoding="utf-8")
107
-
108
- status_match = STATUS_RE.search(content)
109
- if not status_match:
110
- return LockResult(valid=False, error="No Status field found")
111
- status = status_match.group(1)
112
- if status != "LOCKED":
113
- return LockResult(valid=False, error=f"Status is '{status}', expected 'LOCKED'")
114
-
115
- hash_match = LOCK_HASH_RE.search(content)
116
- if not hash_match:
117
- return LockResult(valid=False, error="No LockHash field found")
118
- stored_hash = hash_match.group(1).lower()
119
-
120
- locked_at_match = LOCKED_AT_RE.search(content)
121
- if not locked_at_match:
122
- return LockResult(valid=False, error="No LockedAt field found")
123
- locked_at = locked_at_match.group(1)
124
-
125
- actual_hash = lock_hash_from_content(content)
126
- if stored_hash == actual_hash:
127
- return LockResult(valid=True, locked_at=locked_at, stored_hash=stored_hash)
128
-
129
- result = LockResult(
130
- valid=False,
131
- error="Hash mismatch",
132
- stored_hash=stored_hash,
133
- actual_hash=actual_hash,
134
- locked_at=locked_at,
135
- )
136
-
137
- if fix:
138
- new_locked_at = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
139
- content_with_new_locked_at = re.sub(
140
- r"(?m)^[ \t]*LockedAt:.*$",
141
- f"LockedAt: `{new_locked_at}`",
142
- content,
143
- )
144
- new_hash = lock_hash_from_content(content_with_new_locked_at)
145
- new_content = re.sub(
146
- r"(?m)^[ \t]*LockHash:.*$",
147
- f"LockHash: `{new_hash}`",
148
- content_with_new_locked_at,
149
- )
150
- artifact_path.write_text(new_content, encoding="utf-8", newline="")
151
-
152
- result.fixed = True
153
- result.new_locked_at = new_locked_at
154
- result.new_hash = new_hash
155
-
156
- return result
157
-
158
-
159
- def test_gate(content: str, name: str) -> tuple[bool, str | None]:
160
- pass_re = re.compile(rf"{name}:\s*PASS")
161
- fail_re = re.compile(rf"{name}:\s*FAIL")
162
- if pass_re.search(content):
163
- return True, None
164
- if fail_re.search(content):
165
- return False, f"{name} gate shows FAIL"
166
- return False, f"No {name} gate result found"
167
-
168
-
169
- def main() -> int:
170
- parser = argparse.ArgumentParser(description="Verify recursive-mode artifact lock integrity.")
171
- parser.add_argument("--run-id", default="", help="Run ID to verify. If omitted, scans all runs.")
172
- parser.add_argument("--repo-root", default=".", help="Repository root path.")
173
- parser.add_argument("--fix", action="store_true", help="Fix incorrect lock hashes (updates LockedAt + LockHash).")
174
- parser.add_argument("--show-hashes", action="store_true", help="Show valid lock hashes in output.")
175
- args = parser.parse_args()
176
-
177
- resolved_repo_root = Path(args.repo_root).resolve()
178
- print("\nRecursive Lock Verification")
179
- print("=====================\n")
180
- print(f"Repository: {resolved_repo_root}")
181
- print(f"Run ID: {args.run_id if args.run_id else '(scanning all runs)'}\n")
182
-
183
- run_base_dir = resolved_repo_root / ".recursive" / "run"
184
- if not run_base_dir.exists():
185
- write_status("FAIL", f"recursive run directory not found: {run_base_dir}")
186
- return 1
187
-
188
- if args.run_id:
189
- runs = [args.run_id]
190
- else:
191
- runs = sorted([d.name for d in run_base_dir.iterdir() if d.is_dir()])
192
-
193
- artifacts = [
194
- {"file": "00-requirements.md", "name": "Requirements", "optional": False},
195
- {"file": "00-worktree.md", "name": "Worktree Setup", "optional": False},
196
- {"file": "01-as-is.md", "name": "AS-IS Analysis", "optional": True},
197
- {"file": "01.5-root-cause.md", "name": "Root Cause", "optional": True},
198
- {"file": "02-to-be-plan.md", "name": "TO-BE Plan", "optional": True},
199
- {"file": "03-implementation-summary.md", "name": "Implementation", "optional": True},
200
- {"file": "03.5-code-review.md", "name": "Code Review", "optional": True},
201
- {"file": "04-test-summary.md", "name": "Test Summary", "optional": True},
202
- {"file": "05-manual-qa.md", "name": "Manual QA", "optional": True},
203
- {"file": "06-decisions-update.md", "name": "Decisions Update", "optional": False},
204
- {"file": "07-state-update.md", "name": "State Update", "optional": False},
205
- {"file": "08-memory-impact.md", "name": "Memory Impact", "optional": False},
206
- ]
207
-
208
- total_runs = 0
209
- valid_runs = 0
210
- fixed_runs = 0
211
- failed_runs = 0
212
- stale_chain_runs = 0
213
-
214
- phase_rules = load_phase_rules_module()
215
-
216
- for run in runs:
217
- total_runs += 1
218
- run_dir = run_base_dir / run
219
- workflow_profile = get_workflow_profile(run_dir)
220
- print(f"Checking run: {run}")
221
- print("-" * 50)
222
- print(f"Workflow profile: {workflow_profile}")
223
-
224
- run_valid = True
225
- run_fixed = False
226
-
227
- for artifact in artifacts:
228
- artifact_path = run_dir / artifact["file"]
229
- is_required = not artifact["optional"]
230
- if workflow_profile == "legacy" and artifact["file"] in {"06-decisions-update.md", "07-state-update.md", "08-memory-impact.md"}:
231
- is_required = False
232
- if not artifact_path.exists():
233
- if not is_required:
234
- write_status("INFO", f"{artifact['name']}: Not found (optional)")
235
- else:
236
- write_status("FAIL", f"{artifact['name']}: Missing (required)")
237
- run_valid = False
238
- continue
239
-
240
- content = artifact_path.read_text(encoding="utf-8")
241
- if not re.search(r'(?m)^[ \t]*Status:\s*(?:`|")?LOCKED(?:`|")?\s*$', content):
242
- write_status("WARN", f"{artifact['name']}: Not locked (DRAFT)")
243
- run_valid = False
244
- continue
245
-
246
- result = test_lock_valid(artifact_path, fix=args.fix)
247
- if result.valid:
248
- write_status("PASS", f"{artifact['name']}: Valid (locked at {result.locked_at})")
249
- if args.show_hashes and result.stored_hash:
250
- print(f" Hash: {result.stored_hash}")
251
- else:
252
- if result.fixed:
253
- write_status("WARN", f"{artifact['name']}: Fixed hash mismatch (was tampered)")
254
- if result.stored_hash:
255
- print(f" Old hash: {result.stored_hash}")
256
- if result.new_hash:
257
- print(f" New hash: {result.new_hash}")
258
- if result.new_locked_at:
259
- print(f" Updated at: {result.new_locked_at}")
260
- run_fixed = True
261
- else:
262
- write_status("FAIL", f"{artifact['name']}: {result.error}")
263
- if result.stored_hash and result.actual_hash:
264
- print(f" Stored: {result.stored_hash}")
265
- print(f" Actual: {result.actual_hash}")
266
- run_valid = False
267
-
268
- # Gate checks
269
- updated_content = artifact_path.read_text(encoding="utf-8")
270
- coverage_ok, coverage_reason = test_gate(updated_content, "Coverage")
271
- approval_ok, approval_reason = test_gate(updated_content, "Approval")
272
- audit_required = workflow_profile in STRICT_WORKFLOW_PROFILES and artifact["file"] in AUDITED_PHASE_FILES
273
- audit_ok = True
274
- audit_reason = None
275
- if audit_required:
276
- audit_ok, audit_reason = test_gate(updated_content, "Audit")
277
- if not coverage_ok:
278
- write_status("FAIL", f"{artifact['name']}: Coverage gate - {coverage_reason}")
279
- run_valid = False
280
- if not approval_ok:
281
- write_status("FAIL", f"{artifact['name']}: Approval gate - {approval_reason}")
282
- run_valid = False
283
- if not audit_ok:
284
- write_status("FAIL", f"{artifact['name']}: Audit gate - {audit_reason}")
285
- run_valid = False
286
-
287
- addenda_dir = run_dir / "addenda"
288
- if addenda_dir.exists():
289
- addenda_files = sorted(addenda_dir.glob("*.md"))
290
- if addenda_files:
291
- print("\nAddenda:")
292
- for addendum in addenda_files:
293
- result = test_lock_valid(addendum, fix=args.fix)
294
- if result.valid:
295
- write_status("PASS", f" {addendum.name}: Valid")
296
- elif result.fixed:
297
- write_status("WARN", f" {addendum.name}: Fixed")
298
- run_fixed = True
299
- else:
300
- write_status("FAIL", f" {addendum.name}: {result.error}")
301
- run_valid = False
302
-
303
- # Stale-chain detection: check whether any prerequisite hash in a receipt
304
- # no longer matches the current artifact content.
305
- stale_entries = phase_rules.get_all_stale_receipts(run_dir)
306
- run_stale = False
307
- if stale_entries:
308
- print("\nStale lock-chain receipts:")
309
- for entry in stale_entries:
310
- write_status("WARN", f" {entry['artifact']}: {entry['reason']}")
311
- run_stale = True
312
-
313
- print()
314
- if run_stale:
315
- stale_chain_runs += 1
316
- run_valid = False
317
- write_status("WARN", f"Run '{run}': Stale lock-chain receipts detected (re-lock in phase order)")
318
- elif run_valid and not run_fixed:
319
- valid_runs += 1
320
- write_status("PASS", f"Run '{run}': All locks valid")
321
- elif run_fixed:
322
- fixed_runs += 1
323
- write_status("WARN", f"Run '{run}': Locks fixed (tampering detected)")
324
- else:
325
- failed_runs += 1
326
- write_status("FAIL", f"Run '{run}': Lock verification failed")
327
- print()
328
-
329
- print("=====================")
330
- print("Summary")
331
- print("=====================\n")
332
- print(f"Total runs checked: {total_runs}")
333
- write_status("PASS", f"Valid runs: {valid_runs}")
334
- if stale_chain_runs > 0:
335
- write_status("WARN", f"Stale-chain runs: {stale_chain_runs}")
336
- if fixed_runs > 0:
337
- write_status("WARN", f"Fixed runs (tampered): {fixed_runs}")
338
- if failed_runs > 0:
339
- write_status("FAIL", f"Failed runs: {failed_runs}")
340
- print()
341
-
342
- if failed_runs == 0 and stale_chain_runs == 0:
343
- write_status("PASS", "All locks verified successfully")
344
- return 0
345
- if failed_runs == 0:
346
- write_status("WARN", "Lock hashes valid but stale-chain receipts detected")
347
- return 1
348
- write_status("FAIL", "Some locks failed verification")
349
- return 1
350
-
351
-
352
- if __name__ == "__main__":
353
- raise SystemExit(main())