super-engineer-workflow 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.6
4
+
5
+ - 修复计划生成失败或中断后重复 `/se:apply` 会创建多个空 session/output 目录的问题。
6
+
3
7
  ## 0.1.5
4
8
 
5
9
  - 修复重复执行 `/se:plan` 会创建多个 session 的问题:计划阶段复用当前 session,交付中重复 plan 会被拒绝。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "super-engineer-workflow",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Super Engineer workflow skill and CLI for demand-driven AI delivery with OpenSpec bridge support.",
5
5
  "license": "MIT",
6
6
  "author": "Gary-Coding",
@@ -24,6 +24,7 @@ def main() -> None:
24
24
  os.environ["USERPROFILE"] = str(home)
25
25
  test_templates_cli(root)
26
26
  test_openspec_state_and_bridge(root)
27
+ test_incomplete_plan_session_is_reused(root)
27
28
  test_todo_auto_session_and_verify_compaction(root)
28
29
  print("e2e_test=ok")
29
30
 
@@ -323,6 +324,84 @@ def test_todo_auto_session_and_verify_compaction(root: Path) -> None:
323
324
  raise AssertionError("notification should be marked skipped when no provider is configured")
324
325
 
325
326
 
327
+ def test_incomplete_plan_session_is_reused(root: Path) -> None:
328
+ workspace = root / "incomplete-plan-workspace"
329
+ code = root / "broken-code" / "not-a-project"
330
+ demand_dir = workspace / "superengineer" / "10-broken"
331
+ demand_dir.mkdir(parents=True)
332
+ code.mkdir(parents=True)
333
+ (workspace / "docs").mkdir(parents=True)
334
+ (workspace / "workspace.yml").write_text(
335
+ "\n".join(
336
+ [
337
+ "version: 1",
338
+ "mode: auto",
339
+ "workflow_source: todo",
340
+ "vars:",
341
+ " demand_name: 10-broken",
342
+ "todo_file: superengineer/${demand_name}/todo.md",
343
+ "reference_files: []",
344
+ "code_path: ../broken-code/not-a-project",
345
+ "output_dir: superengineer/${demand_name}/output",
346
+ "",
347
+ ]
348
+ ),
349
+ encoding="utf-8",
350
+ )
351
+ (demand_dir / "todo.md").write_text(
352
+ "# 限制条件\n"
353
+ "- 修改的服务是 broken-service\n\n"
354
+ "# 待办事项\n\n"
355
+ "- [ ] 增加一个测试接口\n",
356
+ encoding="utf-8",
357
+ )
358
+
359
+ first_apply = run(
360
+ [
361
+ sys.executable,
362
+ str(RUN_WORKFLOW),
363
+ "route-se",
364
+ "--workspace",
365
+ str(workspace),
366
+ "--command-text",
367
+ "/se:apply",
368
+ ],
369
+ check=False,
370
+ )
371
+ if first_apply.returncode == 0 or "未找到可识别的项目目录" not in first_apply.output:
372
+ raise AssertionError("first /se:apply should fail before plan is generated")
373
+ current = read_json(workspace / ".super-engineer" / "current-session.json")
374
+ first_session_id = current["session_id"]
375
+ sessions_dir = workspace / ".super-engineer" / "sessions"
376
+ output_dir = workspace / "superengineer" / "10-broken" / "output"
377
+ if len(list(sessions_dir.iterdir())) != 1:
378
+ raise AssertionError("first failed /se:apply should create exactly one reusable data session")
379
+ if output_dir.exists() and list(output_dir.iterdir()):
380
+ raise AssertionError("failed plan should not create an empty output report session")
381
+
382
+ second_apply = run(
383
+ [
384
+ sys.executable,
385
+ str(RUN_WORKFLOW),
386
+ "route-se",
387
+ "--workspace",
388
+ str(workspace),
389
+ "--command-text",
390
+ "/se:apply",
391
+ ],
392
+ check=False,
393
+ )
394
+ if second_apply.returncode == 0 or "session_action=reused_incomplete" not in second_apply.output:
395
+ raise AssertionError("second /se:apply should reuse the incomplete planning session")
396
+ current = read_json(workspace / ".super-engineer" / "current-session.json")
397
+ if current["session_id"] != first_session_id:
398
+ raise AssertionError("incomplete planning session should remain current")
399
+ if len(list(sessions_dir.iterdir())) != 1:
400
+ raise AssertionError("repeated failed /se:apply should not create another data session")
401
+ if output_dir.exists() and list(output_dir.iterdir()):
402
+ raise AssertionError("repeated failed /se:apply should not create output report sessions")
403
+
404
+
326
405
  def run(command: list[str], check: bool = True, env: dict[str, str] | None = None):
327
406
  result = subprocess.run(
328
407
  command,
@@ -652,17 +652,13 @@ def create_session(config: dict[str, Any]) -> dict[str, Any]:
652
652
  },
653
653
  )
654
654
  Path(session_meta["data_dir"]).mkdir(parents=True, exist_ok=True)
655
- Path(session_meta["report_dir"]).mkdir(parents=True, exist_ok=True)
656
655
  write_managed_json(config, current_session_file(config), session_meta)
657
656
  return session_meta
658
657
 
659
658
 
660
659
  def current_session_meta(config: dict[str, Any]) -> dict[str, Any]:
661
660
  session_meta = read_json(current_session_file(config), {})
662
- normalized = _normalize_session_meta(config, session_meta)
663
- Path(normalized["data_dir"]).mkdir(parents=True, exist_ok=True)
664
- Path(normalized["report_dir"]).mkdir(parents=True, exist_ok=True)
665
- return normalized
661
+ return _normalize_session_meta(config, session_meta)
666
662
 
667
663
 
668
664
  def current_session_status(config: dict[str, Any], session_meta: dict[str, Any] | None = None) -> dict[str, Any]:
@@ -678,16 +674,26 @@ def active_session_for_plan(config: dict[str, Any]) -> dict[str, Any] | None:
678
674
  session_meta = current_session_meta(config)
679
675
  except FileNotFoundError:
680
676
  return None
681
- if not data_artifact_path(config, "plan.json", session_meta).exists():
682
- return None
677
+ data_dir = Path(str(session_meta["data_dir"]))
678
+ has_plan = data_artifact_path(config, "plan.json", session_meta).exists()
683
679
  status = current_session_status(config, session_meta)
684
680
  status_phase = str(status.get("phase", "") or "").strip()
685
681
  if status_phase in ("done", "archived", "blocked"):
686
682
  return None
683
+ if not has_plan and data_dir.exists():
684
+ return {
685
+ "session": session_meta,
686
+ "status": status,
687
+ "phase": status_phase or "draft",
688
+ "incomplete": True,
689
+ }
690
+ if not has_plan:
691
+ return None
687
692
  return {
688
693
  "session": session_meta,
689
694
  "status": status,
690
695
  "phase": status_phase or "plan",
696
+ "incomplete": False,
691
697
  }
692
698
 
693
699
 
@@ -697,6 +703,8 @@ def ensure_plan_can_run(config: dict[str, Any]) -> dict[str, Any] | None:
697
703
  return None
698
704
  phase = str(active.get("phase", "")).strip()
699
705
  session = active.get("session", {})
706
+ if active.get("incomplete"):
707
+ return active
700
708
  if phase in ("plan", "wait_confirm_plan"):
701
709
  return active
702
710
  raise RuntimeError(
@@ -294,16 +294,24 @@ def command_plan(workspace: Path | None) -> None:
294
294
  raise SystemExit(str(error))
295
295
  if active:
296
296
  session = active.get("session", {})
297
- print("session_action=reused")
298
- print(f"session_id={session.get('session_id', '')}")
299
- print(f"phase={active.get('phase', '')}")
300
- print("next_action=当前计划已存在,继续执行 /se:apply。")
301
- return
302
- command_init(workspace)
303
- config = load_workspace_config(workspace)
304
- session_meta = create_session(config)
305
- print("session_action=created")
306
- print(f"session_id={session_meta.get('session_id', '')}")
297
+ session_meta = session
298
+ if active.get("incomplete"):
299
+ print("session_action=reused_incomplete")
300
+ print(f"session_id={session_meta.get('session_id', '')}")
301
+ print(f"phase={active.get('phase', '')}")
302
+ print("next_action=复用未完成计划会话,继续生成 discovery/plan。")
303
+ else:
304
+ print("session_action=reused")
305
+ print(f"session_id={session_meta.get('session_id', '')}")
306
+ print(f"phase={active.get('phase', '')}")
307
+ print("next_action=当前计划已存在,继续执行 /se:apply。")
308
+ return
309
+ else:
310
+ command_init(workspace)
311
+ config = load_workspace_config(workspace)
312
+ session_meta = create_session(config)
313
+ print("session_action=created")
314
+ print(f"session_id={session_meta.get('session_id', '')}")
307
315
  command_discover(workspace)
308
316
  args = ["--workspace", str(workspace)] if workspace else []
309
317
  run_python("generate-smart-plan.py", args)