@xenonbyte/req-2-plan 0.5.1 → 0.5.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xenonbyte/req-2-plan",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Requirement-to-PLAN workflow CLI and agent integration installer.",
5
5
  "bin": {
6
6
  "r2p": "bin/r2p.js"
@@ -49,9 +49,13 @@ from tools.workflow_cli.gates import (
49
49
  check_execution_complete,
50
50
  )
51
51
  from tools.workflow_cli.output import (
52
+ COMPACT_DETAIL_LIMIT,
53
+ COMPACT_FILE_LIST_LIMIT,
54
+ compact_human_list,
52
55
  format_success,
53
56
  format_error,
54
57
  format_gate_result,
58
+ is_json_mode,
55
59
  print_and_exit,
56
60
  EXIT_OK,
57
61
  EXIT_CLI_ERR,
@@ -95,6 +99,53 @@ def _load_run(work_id: str, base_path: Path | None = None):
95
99
  )
96
100
 
97
101
 
102
+ def _write_recovery_list(run_dir: Path, work_id: str, filename: str, items: list[str]) -> str | None:
103
+ logs_dir = run_dir / "logs"
104
+ if logs_dir.is_symlink():
105
+ return None
106
+ try:
107
+ logs_dir.mkdir(parents=True, exist_ok=True)
108
+ recovery_path = logs_dir / filename
109
+ atomic_write_text(recovery_path, "\n".join(items) + "\n")
110
+ except OSError:
111
+ return None
112
+ return f".req-to-plan/{work_id}/logs/{filename}"
113
+
114
+
115
+ def _human_list_payload(
116
+ *,
117
+ run_dir: Path,
118
+ work_id: str,
119
+ label: str,
120
+ items: list,
121
+ limit: int,
122
+ recovery_filename: str,
123
+ recovery_items: list[str] | None = None,
124
+ ) -> dict:
125
+ if is_json_mode() or len(items) <= limit:
126
+ return {label: items}
127
+
128
+ recovery_path = _write_recovery_list(
129
+ run_dir,
130
+ work_id,
131
+ recovery_filename,
132
+ recovery_items if recovery_items is not None else [str(item) for item in items],
133
+ )
134
+ if recovery_path is None:
135
+ return {label: items}
136
+
137
+ payload = compact_human_list(
138
+ label=label,
139
+ items=items,
140
+ limit=limit,
141
+ recovery_path=recovery_path,
142
+ )
143
+ payload[f"{label}_summary"] = (
144
+ f"{payload[f'{label}_shown']} shown, {payload[f'{label}_total']} total"
145
+ )
146
+ return payload
147
+
148
+
98
149
  def _validate_work_id(raw: str) -> WorkId:
99
150
  """Parse WorkId or exit with CLI error."""
100
151
  try:
@@ -334,6 +385,15 @@ def _cmd_run_start(args):
334
385
  def _cmd_run_resume(args):
335
386
  record, mgr, run_dir = _load_run(args.work_id, args.base_path)
336
387
  rc = record.resume_context
388
+ reread_targets = list(rc.required_reread_targets)
389
+ reread_payload = _human_list_payload(
390
+ run_dir=run_dir,
391
+ work_id=str(record.work_id),
392
+ label="required_reread_targets",
393
+ items=reread_targets,
394
+ limit=COMPACT_FILE_LIST_LIMIT,
395
+ recovery_filename="run-resume-reread-targets.txt",
396
+ )
337
397
  print_and_exit(
338
398
  format_success(
339
399
  {
@@ -344,6 +404,7 @@ def _cmd_run_resume(args):
344
404
  "next_operation": rc.next_allowed_operation,
345
405
  "active_item": rc.active_item,
346
406
  "resume_reason": rc.resume_reason,
407
+ **reread_payload,
347
408
  },
348
409
  message="Resume context",
349
410
  ),
@@ -1223,6 +1284,22 @@ def _cmd_status_run(args):
1223
1284
  for s in record.stale_artifacts
1224
1285
  ]
1225
1286
  outstanding_stale = [aa.stage.value for aa in record.active_artifacts if aa.status == "stale"]
1287
+ approved_checkpoints = [cp.stage.value for cp in record.approved_checkpoints]
1288
+ approved_payload = _human_list_payload(
1289
+ run_dir=run_dir,
1290
+ work_id=str(record.work_id),
1291
+ label="approved_checkpoints",
1292
+ items=approved_checkpoints,
1293
+ limit=COMPACT_DETAIL_LIMIT,
1294
+ recovery_filename="status-run-approved-checkpoints.txt",
1295
+ recovery_items=[
1296
+ (
1297
+ f"{cp.stage.value}\t{cp.artifact}\tv{cp.version}\t"
1298
+ f"{cp.approved_at}\t{cp.downstream_authorization}\t{cp.bundle_id or ''}"
1299
+ )
1300
+ for cp in record.approved_checkpoints
1301
+ ],
1302
+ )
1226
1303
 
1227
1304
  print_and_exit(
1228
1305
  format_success(
@@ -1237,7 +1314,7 @@ def _cmd_status_run(args):
1237
1314
  "open_routes_detail": open_routes_detail,
1238
1315
  "stale_artifacts": stale_artifacts,
1239
1316
  "outstanding_stale": outstanding_stale,
1240
- "approved_checkpoints": [cp.stage.value for cp in record.approved_checkpoints],
1317
+ **approved_payload,
1241
1318
  },
1242
1319
  message="Run status",
1243
1320
  ),
@@ -11,12 +11,38 @@ EXIT_REVIEW_REQ = 5 # forced subagent review required
11
11
  EXIT_CONFLICT = 6 # state conflict (run already closed, etc.)
12
12
  EXIT_NOT_FOUND = 7 # resource not found (run.md, artifact, etc.)
13
13
 
14
+ # Opt-in compact display limits. Default formatters remain uncapped.
15
+ COMPACT_DETAIL_LIMIT = 10
16
+ COMPACT_FILE_LIST_LIMIT = 15
17
+
14
18
 
15
19
  def is_json_mode() -> bool:
16
20
  """Check if JSON output mode is enabled via R2P_JSON environment variable."""
17
21
  return os.environ.get("R2P_JSON", "0") == "1"
18
22
 
19
23
 
24
+ def compact_human_list(
25
+ *,
26
+ label: str,
27
+ items: list,
28
+ limit: int,
29
+ recovery_path: str | None = None,
30
+ ) -> dict:
31
+ """Build an opt-in compact list payload without touching the filesystem."""
32
+ if limit < 0:
33
+ raise ValueError("limit must be non-negative")
34
+
35
+ visible_items = list(items[:limit])
36
+ result = {
37
+ label: visible_items,
38
+ f"{label}_shown": len(visible_items),
39
+ f"{label}_total": len(items),
40
+ }
41
+ if recovery_path:
42
+ result[f"{label}_full_list"] = recovery_path
43
+ return result
44
+
45
+
20
46
  def format_success(data: dict, message: str = "") -> str:
21
47
  """Format a success response."""
22
48
  if is_json_mode():
@@ -1 +1 @@
1
- R2P_VERSION = "0.5.1"
1
+ R2P_VERSION = "0.5.2"
@@ -14,7 +14,7 @@ from pathlib import Path
14
14
 
15
15
  from tools.workflow_cli.atomic import atomic_write_text
16
16
 
17
- _WORKSPACE_GITIGNORE_LINES = ("/archive", "/.workflow-active")
17
+ _WORKSPACE_GITIGNORE_LINES = ("/archive", "/.workflow-active", "/*/logs/")
18
18
 
19
19
 
20
20
  def ensure_workspace_gitignore(base_path: Path) -> None: