okstra 0.199.0 → 0.199.1

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": "okstra",
3
- "version": "0.199.0",
3
+ "version": "0.199.1",
4
4
  "description": "Host-aware multi-provider cross-verification orchestrator runtime and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "devonshin",
@@ -1,5 +1,5 @@
1
1
  {
2
- "package": "0.199.0",
3
- "builtAt": "2026-09-11T19:30:28.119Z",
2
+ "package": "0.199.1",
3
+ "builtAt": "2026-09-11T21:21:59.712Z",
4
4
  "repoRoot": "/home/runner/work/okstra/okstra"
5
5
  }
@@ -54,3 +54,10 @@ re-plan it without a RED step. Check `okstra stage-map <task-key>` first: when
54
54
  what is left. That command refuses unless the commit exists and the stage's
55
55
  conformance gate permits progress, so it cannot close a stage the run validator
56
56
  would have blocked.
57
+
58
+ If `okstra stage-map` cannot read the prior plan during this planning run,
59
+ follow the analysis packet's Stage Ledger recovery guidance. A notation error
60
+ in an existing `dependsOn` cell is work for the new plan, not a reason to ask
61
+ the user to edit JSON. Preserve the prior report, existing stage numbers and
62
+ completed work; do not close or execute a stage from an unreadable map.
63
+ An ambiguous dependency or stage identity remains a blocker.
@@ -69,7 +69,7 @@ roles:
69
69
  - Every stage number in the ledger is taken. A new stage takes the next number after the highest one listed; numbers are never reused or reordered. **Not yet machine-enforced** — the validator for this rule lands with the plan-amendment feature.
70
70
  - The ledger answers two questions from two sources, and the block names both. `sourcePlan` is the plan the completed stages were actually built against; `latestPlan` is the plan the `stages` list came from and is therefore the numbering authority. When they differ, the completed work followed the former and the highest taken number comes from the latter.
71
71
  - A `planDivergence` entry means one of two things: the two plans disagree about a stage that is already `done` — the same number naming different work, or a completed stage the latest plan no longer declares — or the plan the completed stages were built against could not be read at all, so that comparison never ran. Both block the same way: do not pick one of the two plans yourself; register it as a `Blocks=approval` clarification row and assign no new stage number until it is resolved. Completed stages built against an *earlier* plan are not a divergence — that is the normal shape of an amended plan and the ledger folds it silently.
72
- - The block is absent ONLY on a task's first planning run. Its absence then means there is no prior plan, not that no stage is done. When the ledger could not be read, the packet says so under the same heading with a reason instead of going silent in that state, assign no new stage number and report the reason as a blocker.
72
+ - The block is absent ONLY on a task's first planning run. Its absence then means there is no prior plan, not that no stage is done. When the ledger could not be read, the packet names the source and reason. Continue planning to repair unambiguous dependency notation in the new report, retaining every stage number and title and every completed stage body. Record the original and corrected values with their source. Do not overwrite the prior report or select an older plan. Assign no new stage number until the corrected Stage Map validates; unresolved dependency meaning remains a blocker. The preparation behavior is covered by `tests/run/test_stage_ledger_prepare.py`; preservation of author intent remains a review guideline.
73
73
  - **Prior Planning Run (read before re-deriving anything):** on a re-run the packet carries a `## Prior Planning Run` block with the previous planning run's clarification rows, its Stage Map one line per stage, the findings its convergence left `contested` / `worker-unique`, and the path to the report itself. It is context, not an approved plan — the previous run did not reach approval. Use it so a stage you are not changing keeps the wording it already had and a settled question is not re-opened under a new `P-*` number; where the brief, the directive, or a clarification answer disagrees with it, they win. The block is absent on a first planning run and on a clarification re-run, which receives the same rows through `## Clarification Carry-In Extract` instead. **Enforced:** `okstra_ctl.prior_planning.build_prior_planning_summary` assembles it and `okstra_ctl.analysis_packet._prior_planning_block` renders it; nothing checks that a worker read it.
74
74
  - Primary focus areas:
75
75
  - requirement gaps
@@ -538,10 +538,19 @@ def _stage_ledger_unavailable_block(notice: str) -> list[str]:
538
538
  "",
539
539
  f"- Reason: {notice.strip()}",
540
540
  "",
541
+ "Continue this planning run by reading that report and the implementation",
542
+ "carry records. Repair an unambiguous dependency notation error in the",
543
+ "new planning report, preserving every existing stage number and title",
544
+ "and the bodies of completed stages. Record each original and corrected",
545
+ "value with its source path. Do not overwrite the prior report or fall",
546
+ "back to an older plan. If the intended dependency cannot be established",
547
+ "from those records, report that uncertainty as a blocker; do not guess.",
548
+ "Validate the corrected Stage Map before treating its dependencies as facts.",
549
+ "",
541
550
  "Do not assign a number to any new stage in this state. A number taken",
542
551
  "by a plan this run could not read would collide with the completed",
543
552
  "work under it, and that collision stays silent until integration.",
544
- "Report this as a blocker instead.",
553
+ "Until the corrected map validates, keep new stage numbering blocked.",
545
554
  "",
546
555
  ]
547
556
 
@@ -159,15 +159,17 @@ def _stage_map_table_lines(text: str, source_plan_path: str) -> list[str]:
159
159
  ]
160
160
 
161
161
 
162
- def _parse_depends_on(
162
+ def parse_stage_dependencies(
163
163
  value: str, row_number: int, source_plan_path: str,
164
164
  ) -> tuple[int, ...]:
165
+ """계획 작성 검증과 실행 시 읽기가 같은 의존성 문법을 사용한다."""
166
+ value = value.strip()
165
167
  if value in {"", "(none)"}:
166
168
  return ()
167
169
  dependencies: list[int] = []
168
170
  for token in value.split(","):
169
171
  normalized = token.strip()
170
- if not normalized.isdigit() or int(normalized) < 1:
172
+ if not re.fullmatch(r"[1-9][0-9]*", normalized):
171
173
  raise StageMapError(
172
174
  "stage_map",
173
175
  f"Stage Map row {row_number} has invalid depends-on token "
@@ -215,7 +217,7 @@ def _parse_stage_map_row(
215
217
  return StageMapStage(
216
218
  stage_number,
217
219
  cells[1],
218
- _parse_depends_on(cells[2].strip(), row_number, source_plan_path),
220
+ parse_stage_dependencies(cells[2], row_number, source_plan_path),
219
221
  step_count,
220
222
  cells[4],
221
223
  )
@@ -337,7 +339,7 @@ def _parse_data_stage_map_row(
337
339
  return StageMapStage(
338
340
  stage_number,
339
341
  title,
340
- _parse_depends_on(depends_on.strip(), row_number, source_plan_path),
342
+ parse_stage_dependencies(depends_on, row_number, source_plan_path),
341
343
  step_count,
342
344
  exit_summary,
343
345
  )
@@ -7741,6 +7741,7 @@
7741
7741
  "dependsOn": {
7742
7742
  "type": "string",
7743
7743
  "minLength": 1,
7744
+ "pattern": "^\\s*(?:\\(none\\)|[1-9][0-9]*(?:\\s*,\\s*[1-9][0-9]*)*)\\s*$",
7744
7745
  "description": "Literal Stage Map depends-on cell: `(none)` or a comma-separated stage number list."
7745
7746
  },
7746
7747
  "stepCount": {
@@ -7839,6 +7839,7 @@
7839
7839
  "dependsOn": {
7840
7840
  "type": "string",
7841
7841
  "minLength": 1,
7842
+ "pattern": "^\\s*(?:\\(none\\)|[1-9][0-9]*(?:\\s*,\\s*[1-9][0-9]*)*)\\s*$",
7842
7843
  "description": "Literal Stage Map depends-on cell: `(none)` or a comma-separated stage number list."
7843
7844
  },
7844
7845
  "stepCount": {
@@ -354,6 +354,13 @@ re-plan it without a RED step. Check `okstra stage-map <task-key>` first: when
354
354
  what is left. That command refuses unless the commit exists and the stage's
355
355
  conformance gate permits progress, so it cannot close a stage the run validator
356
356
  would have blocked.
357
+
358
+ If `okstra stage-map` cannot read the prior plan during this planning run,
359
+ follow the analysis packet's Stage Ledger recovery guidance. A notation error
360
+ in an existing `dependsOn` cell is work for the new plan, not a reason to ask
361
+ the user to edit JSON. Preserve the prior report, existing stage numbers and
362
+ completed work; do not close or execute a stage from an unreadable map.
363
+ An ambiguous dependency or stage identity remains a blocker.
357
364
  <!-- END FRAGMENT: host-orchestration-implementation-planning -->
358
365
 
359
366
  <!-- BEGIN FRAGMENT: host-orchestration-implementation -->
@@ -30,6 +30,7 @@ from okstra_ctl.stage_map import ( # noqa: E402
30
30
  STAGE_MAP_HEADING,
31
31
  StageMapError,
32
32
  StageMapStage,
33
+ parse_stage_dependencies,
33
34
  parse_stage_map_text,
34
35
  schema_v2_report,
35
36
  )
@@ -79,17 +80,6 @@ def _check_stage_map_present(text: str) -> List[ValidationError]:
79
80
  return []
80
81
 
81
82
 
82
- def _parse_depends_on_cell(raw: str) -> List[int] | None:
83
- """Stage numbers from schema-v2 `stageMap[].dependsOn`."""
84
- value = raw.strip()
85
- if value in ("(none)", ""):
86
- return []
87
- try:
88
- return [int(x.strip()) for x in value.split(",") if x.strip()]
89
- except ValueError:
90
- return None
91
-
92
-
93
83
  def _stage_numbers_monotonic(
94
84
  stages: List[StageMapStage],
95
85
  ) -> List[ValidationError]:
@@ -495,11 +485,16 @@ def _data_stage_metas(
495
485
  stage_map: List[dict],
496
486
  ) -> Tuple[List[StageMapStage], List[ValidationError]]:
497
487
  rows = []
498
- for row in stage_map:
488
+ errors = []
489
+ for row_number, row in enumerate(stage_map, start=1):
499
490
  if not isinstance(row, dict) or not isinstance(row.get("stage"), int):
500
491
  continue
501
- depends = _parse_depends_on_cell(str(row.get("dependsOn") or ""))
502
- if depends is None:
492
+ try:
493
+ depends = parse_stage_dependencies(
494
+ str(row.get("dependsOn") or ""), row_number, ""
495
+ )
496
+ except StageMapError as exc:
497
+ errors.append(ValidationError("S2", row["stage"], exc.reason))
503
498
  continue
504
499
  rows.append(StageMapStage(
505
500
  row["stage"],
@@ -508,7 +503,7 @@ def _data_stage_metas(
508
503
  row.get("stepCount") if isinstance(row.get("stepCount"), int) else -1,
509
504
  str(row.get("exitContractSummary") or ""),
510
505
  ))
511
- return rows, _stage_numbers_monotonic(rows)
506
+ return rows, errors or _stage_numbers_monotonic(rows)
512
507
 
513
508
 
514
509
  def _check_data_slice_tdd(