agent-project-sdlc 0.1.22 → 0.1.23

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.
@@ -1,19 +1,14 @@
1
1
  #!/usr/bin/env python3
2
- from harness_utils import dump_yaml, load_lifecycle, load_phase_contracts, make_arg_parser, require, run_main
3
-
4
-
5
- RFC_INTERRUPT_SOURCES = {"SPRINTING", "REVIEWING", "TESTING", "RELEASING"}
6
-
7
-
8
- def phase_targets(phase: dict) -> list[str]:
9
- targets: list[str] = []
10
- next_phase = phase.get("next")
11
- if next_phase:
12
- targets.append(str(next_phase))
13
- for return_phase in phase.get("returns") or []:
14
- if return_phase:
15
- targets.append(str(return_phase))
16
- return list(dict.fromkeys(targets))
2
+ from harness_utils import (
3
+ dump_yaml,
4
+ find_phase_transition,
5
+ load_lifecycle,
6
+ load_phase_contract_data,
7
+ make_arg_parser,
8
+ phase_transition_targets,
9
+ require,
10
+ run_main,
11
+ )
17
12
 
18
13
 
19
14
  def main() -> None:
@@ -24,29 +19,25 @@ def main() -> None:
24
19
  args = parser.parse_args()
25
20
 
26
21
  lifecycle = load_lifecycle()
27
- phases = load_phase_contracts()
22
+ contract_data = load_phase_contract_data()
23
+ phases = contract_data["phases"]
28
24
  target = args.to
29
25
  current = lifecycle.get("current_phase")
30
26
  require(target in phases, f"Unknown target phase: {target}")
31
27
  require(current in phases, f"Current phase is not declared in phase_contracts.yaml: {current}")
32
28
 
33
- legal = set(lifecycle.get("allowed_next_phases") or [])
34
- legal.update(phase_targets(phases[current]))
35
- if target == "RFC_RECALIBRATION" and current in RFC_INTERRUPT_SOURCES:
36
- legal.add(target)
37
- if target == "BLOCKED":
38
- legal.add(target)
39
29
  suspended = lifecycle.get("suspended_phase")
40
- if current == "BLOCKED" and suspended:
41
- legal.add(suspended)
30
+ legal = set(phase_transition_targets(contract_data, str(current), str(suspended or "")))
31
+ transition = find_phase_transition(contract_data, str(current), target, str(suspended or ""))
42
32
 
43
33
  require(args.force or target in legal, f"Illegal transition {current} -> {target}. Legal: {sorted(legal)}")
44
34
 
45
- if target in {"RFC_RECALIBRATION", "BLOCKED"} and current not in {"RFC_RECALIBRATION", "BLOCKED"}:
35
+ effects = transition.get("effects") if transition else {}
36
+ if not isinstance(effects, dict):
37
+ effects = {}
38
+ if effects.get("set_suspended_phase"):
46
39
  lifecycle["suspended_phase"] = current
47
- elif current == "RFC_RECALIBRATION" and target == "SPRINTING":
48
- lifecycle["suspended_phase"] = ""
49
- elif suspended and target == suspended:
40
+ if effects.get("clear_suspended_phase"):
50
41
  lifecycle["suspended_phase"] = ""
51
42
 
52
43
  phase = phases[target]
@@ -54,9 +45,11 @@ def main() -> None:
54
45
  lifecycle["active_role"] = phase.get("role", "")
55
46
  lifecycle["active_skill"] = phase.get("skill", "")
56
47
 
57
- lifecycle["allowed_next_phases"] = phase_targets(phase)
58
- if target == "BLOCKED" and lifecycle.get("suspended_phase"):
59
- lifecycle["allowed_next_phases"] = [lifecycle["suspended_phase"]]
48
+ lifecycle["allowed_next_phases"] = phase_transition_targets(
49
+ contract_data,
50
+ target,
51
+ str(lifecycle.get("suspended_phase") or ""),
52
+ )
60
53
 
61
54
  dump_yaml(lifecycle, ".codex/state/lifecycle.yaml")
62
55
  print(f"Transitioned {current} -> {target}")
@@ -129,6 +129,11 @@ def validate_self_test_contract_tech_plan_binding(task: dict, normalized_tech_re
129
129
  contains_any(section, ["module key test path", "模块关键测试路径"]),
130
130
  f"Draft task {task_id} tech plan Development Self-Test Contract must include Module key test path: {source}",
131
131
  )
132
+ if contract.get("graph_required") is True:
133
+ require(
134
+ contains_any(section, ["module key test graph", "module_key_test_graph", "模块关键测试图"]),
135
+ f"Draft task {task_id} tech plan Development Self-Test Contract must include Module Key Test Graph when graph_required is true: {source}",
136
+ )
132
137
  for scenario in contract.get("scenarios") or []:
133
138
  if not isinstance(scenario, dict):
134
139
  continue
@@ -1,5 +1,15 @@
1
1
  #!/usr/bin/env python3
2
- from harness_utils import load_lifecycle, load_phase_contracts, load_yaml, repo_path, require, require_paths, run_main
2
+ from harness_utils import (
3
+ load_lifecycle,
4
+ load_phase_contract_data,
5
+ load_phase_contracts,
6
+ load_yaml,
7
+ phase_transition_contract_errors,
8
+ repo_path,
9
+ require,
10
+ require_paths,
11
+ run_main,
12
+ )
3
13
 
4
14
 
5
15
  def main() -> None:
@@ -37,6 +47,7 @@ def main() -> None:
37
47
  require_paths(required_files + required_dirs)
38
48
 
39
49
  lifecycle = load_lifecycle()
50
+ phase_contract_data = load_phase_contract_data()
40
51
  phases = load_phase_contracts()
41
52
  load_yaml(".codex/pjsdlc_managed/policies/gates.yaml")
42
53
  load_yaml(".codex/pjsdlc_managed/policies/allowed_paths.yaml")
@@ -44,6 +55,8 @@ def main() -> None:
44
55
 
45
56
  current_phase = lifecycle.get("current_phase")
46
57
  require(current_phase in phases, f"Lifecycle current_phase is not declared: {current_phase}")
58
+ for error in phase_transition_contract_errors(phase_contract_data, require_transitions=True):
59
+ require(False, error)
47
60
 
48
61
  for phase_name, contract in phases.items():
49
62
  skill = contract.get("skill")
@@ -68,7 +68,7 @@ YAML_KEYWORDS = {
68
68
  "next_task_sequence",
69
69
  "tasks",
70
70
  ],
71
- "phase_contracts": ["phases"],
71
+ "phase_contracts": ["phases", "transitions"],
72
72
  }
73
73
 
74
74
 
@@ -30,6 +30,11 @@ SELF_TEST_TRIGGER_TERMS = [
30
30
  "handoff",
31
31
  "blocker",
32
32
  "module key test path",
33
+ "module key test graph",
34
+ "module_key_test_graph",
35
+ "checkpoint",
36
+ "observable exit",
37
+ "evidence refs",
33
38
  "test route",
34
39
  "test path",
35
40
  "debug path",
package/dist/lib/init.js CHANGED
@@ -53,7 +53,7 @@ async function createProjectState(projectRoot, root, report) {
53
53
  const files = [
54
54
  [
55
55
  harnessPath(root, "state", "lifecycle.yaml"),
56
- `project_name: "Project"\nversion: "v0.1"\ncurrent_phase: "SPRINTING"\nactive_role: "developer"\nactive_skill: "pjsdlc_dev_sprint"\ncurrent_milestone: "MVP"\nblocked_reason: ""\nsuspended_phase: ""\nallowed_next_phases:\n - "REVIEWING"\n`
56
+ `project_name: "Project"\nversion: "v0.1"\ncurrent_phase: "SPRINTING"\nactive_role: "developer"\nactive_skill: "pjsdlc_dev_sprint"\ncurrent_milestone: "MVP"\nblocked_reason: ""\nsuspended_phase: ""\nallowed_next_phases:\n - "REVIEWING"\n - "RFC_RECALIBRATION"\n - "BLOCKED"\n`
57
57
  ],
58
58
  [harnessPath(root, "state", "plan.yaml"), `current_task_id: ""\nnext_task_sequence: 1\ntasks: []\n`],
59
59
  [harnessPath(root, "state", "plan.draft.yaml"), `next_task_sequence: 1\ntasks: []\n`],