okstra 0.86.0 → 0.87.0

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 (101) hide show
  1. package/docs/kr/architecture.md +27 -27
  2. package/docs/kr/cli.md +21 -21
  3. package/docs/kr/performance-improvement-plan-v2.md +4 -4
  4. package/docs/kr/performance-improvement-plan.md +3 -3
  5. package/docs/project-structure-overview.md +6 -6
  6. package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +724 -0
  7. package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +123 -0
  8. package/docs/task-process/error-analysis.md +1 -1
  9. package/docs/task-process/final-verification.md +4 -4
  10. package/docs/task-process/implementation-planning.md +2 -2
  11. package/docs/task-process/implementation.md +3 -3
  12. package/docs/task-process/requirements-discovery.md +2 -2
  13. package/package.json +1 -1
  14. package/runtime/BUILD.json +2 -2
  15. package/runtime/agents/workers/{gemini-worker.md → antigravity-worker.md} +56 -56
  16. package/runtime/agents/workers/claude-worker.md +2 -2
  17. package/runtime/agents/workers/codex-worker.md +1 -1
  18. package/runtime/agents/workers/report-writer-worker.md +2 -2
  19. package/runtime/bin/lib/okstra/cli.sh +3 -3
  20. package/runtime/bin/lib/okstra/globals.sh +8 -8
  21. package/runtime/bin/lib/okstra/interactive.sh +1 -1
  22. package/runtime/bin/lib/okstra/usage.sh +7 -7
  23. package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +5 -3
  24. package/runtime/bin/lib/okstra-ctl/prepare.sh +4 -2
  25. package/runtime/bin/{okstra-gemini-exec.sh → okstra-antigravity-exec.sh} +67 -60
  26. package/runtime/bin/okstra-error-log.py +2 -1
  27. package/runtime/bin/okstra-token-usage.py +3 -4
  28. package/runtime/bin/okstra-trace-cleanup.sh +4 -4
  29. package/runtime/bin/okstra-wrapper-status.py +3 -3
  30. package/runtime/bin/okstra.sh +1 -1
  31. package/runtime/prompts/launch.template.md +3 -3
  32. package/runtime/prompts/lead/context-loader.md +1 -1
  33. package/runtime/prompts/lead/convergence.md +8 -8
  34. package/runtime/prompts/lead/okstra-lead-contract.md +15 -15
  35. package/runtime/prompts/lead/report-writer.md +6 -6
  36. package/runtime/prompts/lead/team-contract.md +31 -31
  37. package/runtime/prompts/profiles/_coding-conventions-preflight.md +2 -2
  38. package/runtime/prompts/profiles/_common-contract.md +7 -7
  39. package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
  40. package/runtime/prompts/profiles/_implementation-executor.md +6 -6
  41. package/runtime/prompts/profiles/_implementation-verifier.md +4 -4
  42. package/runtime/prompts/profiles/_stage-discipline.md +1 -1
  43. package/runtime/prompts/profiles/error-analysis.md +1 -1
  44. package/runtime/prompts/profiles/final-verification.md +1 -1
  45. package/runtime/prompts/profiles/forbidden-actions.json +2 -2
  46. package/runtime/prompts/profiles/implementation-planning.md +1 -1
  47. package/runtime/prompts/profiles/implementation.md +3 -3
  48. package/runtime/prompts/profiles/improvement-discovery.md +3 -3
  49. package/runtime/prompts/profiles/requirements-discovery.md +1 -1
  50. package/runtime/prompts/wizard/prompts.ko.json +9 -5
  51. package/runtime/python/okstra_ctl/__init__.py +2 -0
  52. package/runtime/python/okstra_ctl/analysis_packet.py +1 -1
  53. package/runtime/python/okstra_ctl/clarification_items.py +17 -7
  54. package/runtime/python/okstra_ctl/codex_dispatch.py +2 -2
  55. package/runtime/python/okstra_ctl/context_cost.py +10 -44
  56. package/runtime/python/okstra_ctl/doctor.py +1 -1
  57. package/runtime/python/okstra_ctl/error_report.py +185 -0
  58. package/runtime/python/okstra_ctl/improvement_lenses.py +1 -1
  59. package/runtime/python/okstra_ctl/models.py +10 -10
  60. package/runtime/python/okstra_ctl/paths.py +9 -9
  61. package/runtime/python/okstra_ctl/reconcile.py +74 -11
  62. package/runtime/python/okstra_ctl/render.py +21 -21
  63. package/runtime/python/okstra_ctl/run.py +16 -16
  64. package/runtime/python/okstra_ctl/run_context.py +1 -1
  65. package/runtime/python/okstra_ctl/seeding.py +1 -1
  66. package/runtime/python/okstra_ctl/task_target.py +44 -0
  67. package/runtime/python/okstra_ctl/team.py +1 -1
  68. package/runtime/python/okstra_ctl/wizard.py +55 -30
  69. package/runtime/python/okstra_ctl/workers.py +3 -3
  70. package/runtime/python/okstra_ctl/workflow.py +1 -1
  71. package/runtime/python/okstra_ctl/worktree.py +1 -1
  72. package/runtime/python/okstra_token_usage/__init__.py +2 -3
  73. package/runtime/python/okstra_token_usage/antigravity.py +26 -0
  74. package/runtime/python/okstra_token_usage/blocks.py +4 -0
  75. package/runtime/python/okstra_token_usage/collect.py +20 -15
  76. package/runtime/python/okstra_token_usage/paths.py +0 -1
  77. package/runtime/python/okstra_token_usage/pricing.py +6 -3
  78. package/runtime/schemas/final-report-v1.0.schema.json +1 -1
  79. package/runtime/skills/okstra-brief/SKILL.md +1 -1
  80. package/runtime/skills/okstra-inspect/SKILL.md +85 -21
  81. package/runtime/skills/okstra-run/SKILL.md +3 -3
  82. package/runtime/skills/okstra-setup/SKILL.md +2 -2
  83. package/runtime/templates/prd/brief.template.md +2 -2
  84. package/runtime/templates/reports/i18n/en.json +1 -1
  85. package/runtime/templates/reports/i18n/ko.json +1 -1
  86. package/runtime/templates/reports/settings.template.json +2 -2
  87. package/runtime/templates/reports/task-brief.template.md +2 -2
  88. package/runtime/templates/worker-prompt-preamble.md +7 -7
  89. package/runtime/validators/forbidden_actions.py +1 -1
  90. package/runtime/validators/lib/fixtures.sh +3 -3
  91. package/runtime/validators/lib/validate-assets.sh +1 -1
  92. package/runtime/validators/validate-run.py +8 -8
  93. package/src/cli-registry.mjs +7 -0
  94. package/src/codex-dispatch.mjs +3 -3
  95. package/src/codex-run.mjs +2 -2
  96. package/src/doctor.mjs +1 -1
  97. package/src/error-report.mjs +27 -0
  98. package/src/install.mjs +1 -1
  99. package/src/render-bundle.mjs +1 -1
  100. package/src/uninstall.mjs +2 -2
  101. package/runtime/python/okstra_token_usage/gemini.py +0 -82
@@ -205,7 +205,7 @@ def write_run_inputs(
205
205
 
206
206
  inputs schema (모든 키 optional):
207
207
  taskBriefPath, directive, workers, leadModel, claudeModel, codexModel,
208
- geminiModel, reportWriterModel, relatedTasks, approvedPlanPath,
208
+ antigravityModel, reportWriterModel, relatedTasks, approvedPlanPath,
209
209
  clarificationResponsePath, renderOnly
210
210
  """
211
211
  run_manifests_dir = Path(run_manifests_dir)
@@ -135,7 +135,7 @@ def ensure_project_settings_symlink(*, project_root: Path) -> Optional[Path]:
135
135
 
136
136
  Claude Code 가 그 프로젝트에서 host 세션으로 실행될 때 이 파일을
137
137
  자동으로 로드하므로, okstra worker wrapper 호출(`okstra-codex-exec.sh`,
138
- `okstra-gemini-exec.sh`) 이 별도 `--settings` 인자 없이도 허용된다.
138
+ `okstra-antigravity-exec.sh`) 이 별도 `--settings` 인자 없이도 허용된다.
139
139
 
140
140
  반환값:
141
141
  - target Path: symlink 가 새로 생성되었거나 이미 올바른 위치를
@@ -0,0 +1,44 @@
1
+ """task-key → (task_root, project_root) 해석 공유 헬퍼.
2
+
3
+ read-side CLI(context-cost, error-report)가 동일한 입력 계약을 공유하기 위한
4
+ SSOT. target 은 task-root 경로 / task-key 둘 다 받는다.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ from pathlib import Path
9
+
10
+ from okstra_project import ResolverError, find_task_root, resolve_project_root
11
+
12
+
13
+ def resolve_task_root(target: str, project_root: str, cwd: str) -> tuple[Path, Path]:
14
+ target_path = Path(target).expanduser()
15
+ if target_path.exists():
16
+ task_root = target_path.resolve()
17
+ return task_root, infer_project_root(task_root)
18
+
19
+ try:
20
+ resolved_project = Path(
21
+ resolve_project_root(explicit_root=project_root, cwd=cwd)
22
+ ).resolve()
23
+ except ResolverError as exc:
24
+ raise SystemExit(f"project root resolution failed: {exc}") from exc
25
+
26
+ task_root = find_task_root(resolved_project, target)
27
+ if task_root is None:
28
+ raise SystemExit(f"no task root for task key: {target}")
29
+ return task_root.resolve(), resolved_project
30
+
31
+
32
+ def infer_project_root(task_root: Path) -> Path:
33
+ parts = task_root.parts
34
+ if ".okstra" not in parts:
35
+ return task_root
36
+ idx = parts.index(".okstra")
37
+ return Path(*parts[:idx]).resolve()
38
+
39
+
40
+ def project_rel(path: Path, project_root: Path) -> str:
41
+ try:
42
+ return str(path.resolve().relative_to(project_root.resolve()))
43
+ except ValueError:
44
+ return str(path)
@@ -20,7 +20,7 @@ from .dispatch_core import (
20
20
  _SUPPORTED_WRAPPERS = {
21
21
  "claude": "okstra-claude-exec.sh",
22
22
  "codex": "okstra-codex-exec.sh",
23
- "gemini": "okstra-gemini-exec.sh",
23
+ "antigravity": "okstra-antigravity-exec.sh",
24
24
  "report-writer": "okstra-claude-exec.sh",
25
25
  }
26
26
  _TERMINAL_STATUSES = {"completed", "timeout", "error", "not-run"}
@@ -31,7 +31,7 @@ from okstra_ctl.models import (
31
31
  UnknownModelError,
32
32
  resolve_model_metadata,
33
33
  )
34
- from okstra_ctl.clarification_items import scan_approval_gate
34
+ from okstra_ctl.clarification_items import scan_approval_gate, user_response_sidecars
35
35
  from okstra_ctl.pr_template import PrTemplateError, resolve_pr_template_path
36
36
  from okstra_ctl.run import (
37
37
  APPROVED_FRONTMATTER_PATTERN,
@@ -89,7 +89,7 @@ TASK_TYPES: list[tuple[str, str]] = [
89
89
  ]
90
90
  TASK_TYPE_VALUES = [tt for tt, _ in TASK_TYPES]
91
91
 
92
- EXECUTORS = ["claude", "codex", "gemini"]
92
+ EXECUTORS = ["claude", "codex", "antigravity"]
93
93
 
94
94
  # Task types that consume an approved plan and need a stage-scope pick:
95
95
  # implementation executes a stage, final-verification verifies a stage
@@ -108,7 +108,7 @@ BASE_REF_FREE_INPUT_TOKEN = "__free_input__"
108
108
 
109
109
  CLAUDE_MODEL_OPTIONS = ["default", "fable", "opus", "sonnet", "haiku"]
110
110
  CODEX_MODEL_OPTIONS = ["default", "gpt-5.5", "gpt-5.4", "gpt-5.4-mini"]
111
- GEMINI_MODEL_OPTIONS = ["default", "gemini-3-pro-preview", "gemini-3-flash-preview", "auto"]
111
+ ANTIGRAVITY_MODEL_OPTIONS = ["default", "gemini-3.1-pro", "gemini-3.5-flash"]
112
112
 
113
113
  # special pick value: start a brand-new task
114
114
  TASK_PICK_NEW_TOKEN = "__new__"
@@ -263,7 +263,7 @@ S_LEAD_MODEL = "lead_model"
263
263
  S_EXECUTOR_MODEL = "executor_model"
264
264
  S_CLAUDE_MODEL = "claude_model"
265
265
  S_CODEX_MODEL = "codex_model"
266
- S_GEMINI_MODEL = "gemini_model"
266
+ S_ANTIGRAVITY_MODEL = "antigravity_model"
267
267
  S_REPORT_WRITER_MODEL = "report_writer_model"
268
268
  S_DIRECTIVE_PICK = "directive_pick"
269
269
  S_DIRECTIVE = "directive"
@@ -291,7 +291,7 @@ GROUP_MAX_TABS = 4 # AskUserQuestion 의 질문(탭) 수 한도
291
291
  # *_TEXT 후속 / workers_override / pr_template_scope 는 의존성 때문에 개별 유지.
292
292
  PROMPT_GROUPS: dict[str, tuple[str, ...]] = {
293
293
  GROUP_MODELS: (S_LEAD_MODEL, S_EXECUTOR_MODEL, S_CLAUDE_MODEL,
294
- S_CODEX_MODEL, S_GEMINI_MODEL, S_REPORT_WRITER_MODEL),
294
+ S_CODEX_MODEL, S_ANTIGRAVITY_MODEL, S_REPORT_WRITER_MODEL),
295
295
  GROUP_OPTIONS: (S_DIRECTIVE_PICK, S_RELATED_TASKS_PICK,
296
296
  S_CLARIFICATION_PICK, S_PR_TEMPLATE_PICK),
297
297
  }
@@ -371,7 +371,7 @@ class WizardState:
371
371
  lead_model: str = ""
372
372
  claude_model: str = ""
373
373
  codex_model: str = ""
374
- gemini_model: str = ""
374
+ antigravity_model: str = ""
375
375
  report_writer_model: str = ""
376
376
  directive: str = ""
377
377
  directive_pending_text: bool = False
@@ -689,13 +689,13 @@ def _validate_model(provider: str, raw: str) -> str:
689
689
  def _executor_model_field(executor: str) -> str:
690
690
  return {"claude": "claude_model",
691
691
  "codex": "codex_model",
692
- "gemini": "gemini_model"}[executor]
692
+ "antigravity": "antigravity_model"}[executor]
693
693
 
694
694
 
695
695
  def _executor_model_options(executor: str) -> list[str]:
696
696
  return {"claude": CLAUDE_MODEL_OPTIONS,
697
697
  "codex": CODEX_MODEL_OPTIONS,
698
- "gemini": GEMINI_MODEL_OPTIONS}[executor]
698
+ "antigravity": ANTIGRAVITY_MODEL_OPTIONS}[executor]
699
699
 
700
700
 
701
701
  # ---- Roster / profile helpers -------------------------------------------
@@ -797,6 +797,7 @@ def _p(workspace_root: str, step_id: str, **vars: str) -> dict:
797
797
  "echo_variants": raw.get("echo_variants", {}),
798
798
  "errors": raw.get("errors", {}),
799
799
  "labels": raw.get("labels", {}),
800
+ "sidecar_note": raw.get("sidecar_note", {}),
800
801
  "echo_suffixes": raw.get("echo_suffixes", {}),
801
802
  "recent_label_prefix": raw.get("recent_label_prefix", ""),
802
803
  }
@@ -1951,6 +1952,20 @@ def _suggest_latest_final_report(state: WizardState) -> str:
1951
1952
  return str(best)
1952
1953
 
1953
1954
 
1955
+ def _clarification_sidecar_note(state: WizardState, suggestion: str) -> str:
1956
+ """추천 final-report 와 함께 carry-in 될 ``user-responses/`` 사이드카 현황 문구.
1957
+
1958
+ "final-report 만 물어보니 user-responses 파일이 쓰이는지 불확실하다" 는 혼선을
1959
+ 없애기 위해, picker 단계에서 실제 첨부될 답변 파일 개수를 그 자리에서 보여준다.
1960
+ """
1961
+ p = Path(suggestion)
1962
+ if not p.is_absolute() and state.project_root:
1963
+ p = Path(state.project_root) / p
1964
+ count = len(user_response_sidecars(p))
1965
+ note = _p(state.workspace_root, "clarification_pick")["sidecar_note"]
1966
+ return note["attached"].format(count=count) if count else note["empty"]
1967
+
1968
+
1954
1969
  def _latest_run_inputs(
1955
1970
  state: WizardState, *, phase_segment: str = ""
1956
1971
  ) -> dict:
@@ -1991,6 +2006,9 @@ class _OptionalCachedPickSpec:
1991
2006
  target_attr: str # 확정값을 쓸 state 속성
1992
2007
  pending_attr: str # 직접 입력 대기 플래그 state 속성
1993
2008
  extra_clear_attrs: tuple[str, ...] = () # skip 시 추가로 비울 속성
2009
+ # 추천 옵션 라벨·echo 뒤에 덧붙일 동적 안내(예: 함께 첨부될 사이드카 개수).
2010
+ # (state, 추천 relpath) -> note. 미설정 picker 는 영향 없음.
2011
+ recommend_note: Optional[Callable[["WizardState", str], str]] = None
1994
2012
 
1995
2013
 
1996
2014
  def _pick_snippet(value: str, style: str) -> str:
@@ -2007,7 +2025,10 @@ def _build_optional_cached_pick(state: WizardState, spec: _OptionalCachedPickSpe
2007
2025
  options: list[Option] = []
2008
2026
  if suggestion:
2009
2027
  snippet = _pick_snippet(suggestion, spec.snippet_style)
2010
- options.append(_opt(spec.recommend_token, t["labels"][spec.label_key].format(snippet=snippet)))
2028
+ label = t["labels"][spec.label_key].format(snippet=snippet)
2029
+ if spec.recommend_note:
2030
+ label += spec.recommend_note(state, suggestion)
2031
+ options.append(_opt(spec.recommend_token, label))
2011
2032
  setattr(state, spec.cache_attr, suggestion)
2012
2033
  options.append(_opt(PICK_SKIP, t["options"][PICK_SKIP]))
2013
2034
  options.append(_opt(PICK_TYPE_CUSTOM, t["options"][PICK_TYPE_CUSTOM]))
@@ -2032,7 +2053,10 @@ def _submit_optional_cached_pick(
2032
2053
  cached = getattr(state, spec.cache_attr)
2033
2054
  setattr(state, spec.target_attr, cached)
2034
2055
  setattr(state, spec.pending_attr, False)
2035
- return t["echo_suffixes"][spec.echo_suffix_key].format(value=cached)
2056
+ echo = t["echo_suffixes"][spec.echo_suffix_key].format(value=cached)
2057
+ if spec.recommend_note:
2058
+ echo += spec.recommend_note(state, cached)
2059
+ return echo
2036
2060
  if value == PICK_TYPE_CUSTOM:
2037
2061
  setattr(state, spec.pending_attr, True)
2038
2062
  return None
@@ -2104,6 +2128,7 @@ _CLARIFICATION_PICK_SPEC = _OptionalCachedPickSpec(
2104
2128
  suggest=_suggest_latest_final_report, snippet_style="suffix",
2105
2129
  cache_attr="last_final_report_cached", target_attr="clarification_response_path",
2106
2130
  pending_attr="clarification_pending_text",
2131
+ recommend_note=_clarification_sidecar_note,
2107
2132
  )
2108
2133
 
2109
2134
 
@@ -2251,7 +2276,7 @@ _RESUME_REUSE_PHASES = (
2251
2276
  # YES 선택 시 prefill 로 충족되어 다시 묻지 않는 설정 단계들.
2252
2277
  _REUSE_FILLED_STEPS = (
2253
2278
  S_DEFAULTS_OR_CUSTOM, S_WORKERS_OVERRIDE, S_LEAD_MODEL, S_EXECUTOR_MODEL,
2254
- S_CLAUDE_MODEL, S_CODEX_MODEL, S_GEMINI_MODEL, S_REPORT_WRITER_MODEL,
2279
+ S_CLAUDE_MODEL, S_CODEX_MODEL, S_ANTIGRAVITY_MODEL, S_REPORT_WRITER_MODEL,
2255
2280
  S_DIRECTIVE_PICK, S_DIRECTIVE, S_RELATED_TASKS_PICK, S_RELATED_TASKS,
2256
2281
  S_CLARIFICATION_PICK, S_CLARIFICATION)
2257
2282
 
@@ -2299,7 +2324,7 @@ def _submit_reuse_previous(state: WizardState, value: str) -> Optional[str]:
2299
2324
  state.lead_model = _safe_model("claude", inputs.get("leadModel"))
2300
2325
  state.claude_model = _safe_model("claude", inputs.get("claudeModel"))
2301
2326
  state.codex_model = _safe_model("codex", inputs.get("codexModel"))
2302
- state.gemini_model = _safe_model("gemini", inputs.get("geminiModel"))
2327
+ state.antigravity_model = _safe_model("antigravity", inputs.get("antigravityModel"))
2303
2328
  state.report_writer_model = _safe_model(
2304
2329
  "claude", inputs.get("reportWriterModel"))
2305
2330
  directive = inputs.get("directive")
@@ -2445,15 +2470,15 @@ def _submit_codex_model(state: WizardState, value: str) -> Optional[str]:
2445
2470
  return f"codex-model: {state.codex_model or 'default'}"
2446
2471
 
2447
2472
 
2448
- def _build_gemini_model(state: WizardState) -> Prompt:
2449
- t = _p(state.workspace_root, "gemini_model")
2450
- return _model_pick(S_GEMINI_MODEL, t["label"],
2451
- GEMINI_MODEL_OPTIONS, t["echo_template"])
2473
+ def _build_antigravity_model(state: WizardState) -> Prompt:
2474
+ t = _p(state.workspace_root, "antigravity_model")
2475
+ return _model_pick(S_ANTIGRAVITY_MODEL, t["label"],
2476
+ ANTIGRAVITY_MODEL_OPTIONS, t["echo_template"])
2452
2477
 
2453
2478
 
2454
- def _submit_gemini_model(state: WizardState, value: str) -> Optional[str]:
2455
- state.gemini_model = _validate_model("gemini", value)
2456
- return f"gemini-model: {state.gemini_model or 'default'}"
2479
+ def _submit_antigravity_model(state: WizardState, value: str) -> Optional[str]:
2480
+ state.antigravity_model = _validate_model("antigravity", value)
2481
+ return f"antigravity-model: {state.antigravity_model or 'default'}"
2457
2482
 
2458
2483
 
2459
2484
  def _build_report_writer_model(state: WizardState) -> Prompt:
@@ -2930,7 +2955,7 @@ STEPS: list[Step] = [
2930
2955
  and s.task_type == "implementation"
2931
2956
  and S_EXECUTOR_MODEL not in s.answered),
2932
2957
  build=_build_executor_model, submit=_submit_executor_model,
2933
- owns=("claude_model", "codex_model", "gemini_model")),
2958
+ owns=("claude_model", "codex_model", "antigravity_model")),
2934
2959
  Step(S_CLAUDE_MODEL,
2935
2960
  applies=lambda s: (s.use_defaults is False
2936
2961
  and s.task_type != "implementation"
@@ -2945,13 +2970,13 @@ STEPS: list[Step] = [
2945
2970
  and S_CODEX_MODEL not in s.answered),
2946
2971
  build=_build_codex_model, submit=_submit_codex_model,
2947
2972
  owns=("codex_model",)),
2948
- Step(S_GEMINI_MODEL,
2973
+ Step(S_ANTIGRAVITY_MODEL,
2949
2974
  applies=lambda s: (s.use_defaults is False
2950
2975
  and s.task_type != "implementation"
2951
- and "gemini" in _resolved_roster(s)
2952
- and S_GEMINI_MODEL not in s.answered),
2953
- build=_build_gemini_model, submit=_submit_gemini_model,
2954
- owns=("gemini_model",)),
2976
+ and "antigravity" in _resolved_roster(s)
2977
+ and S_ANTIGRAVITY_MODEL not in s.answered),
2978
+ build=_build_antigravity_model, submit=_submit_antigravity_model,
2979
+ owns=("antigravity_model",)),
2955
2980
  Step(S_REPORT_WRITER_MODEL,
2956
2981
  applies=lambda s: (s.use_defaults is False
2957
2982
  and (s.task_type == "implementation"
@@ -3088,7 +3113,7 @@ def _ready_for_confirm(s: WizardState) -> bool:
3088
3113
  return True
3089
3114
  # customize: every customize-branch step must be answered or not-applicable.
3090
3115
  custom_ids = [S_LEAD_MODEL, S_EXECUTOR_MODEL,
3091
- S_CLAUDE_MODEL, S_CODEX_MODEL, S_GEMINI_MODEL,
3116
+ S_CLAUDE_MODEL, S_CODEX_MODEL, S_ANTIGRAVITY_MODEL,
3092
3117
  S_REPORT_WRITER_MODEL, S_DIRECTIVE, S_RELATED_TASKS,
3093
3118
  S_CLARIFICATION, S_PR_TEMPLATE, S_PR_TEMPLATE_SCOPE]
3094
3119
  for sid in custom_ids:
@@ -3130,7 +3155,7 @@ _FIELD_DEFAULTS: dict[str, Any] = {
3130
3155
  "reuse_previous": None,
3131
3156
  "use_defaults": None, "workers_override": "",
3132
3157
  "lead_model": "", "claude_model": "", "codex_model": "",
3133
- "gemini_model": "", "report_writer_model": "", "directive": "",
3158
+ "antigravity_model": "", "report_writer_model": "", "directive": "",
3134
3159
  "directive_pending_text": False,
3135
3160
  "related_tasks_raw": "", "related_tasks_pending_text": False,
3136
3161
  "clarification_response_path": "", "clarification_pending_text": False,
@@ -3299,7 +3324,7 @@ def render_args(state: WizardState) -> dict[str, str]:
3299
3324
  "lead-model": state.lead_model,
3300
3325
  "claude-model": state.claude_model,
3301
3326
  "codex-model": state.codex_model,
3302
- "gemini-model": state.gemini_model,
3327
+ "antigravity-model": state.antigravity_model,
3303
3328
  "report-writer-model": state.report_writer_model,
3304
3329
  "related-tasks": state.related_tasks_raw,
3305
3330
  "clarification-response": state.clarification_response_path,
@@ -3339,8 +3364,8 @@ def confirmation_block(state: WizardState) -> str:
3339
3364
  lines.append(f" claude-model : {state.claude_model}")
3340
3365
  if state.codex_model:
3341
3366
  lines.append(f" codex-model : {state.codex_model}")
3342
- if state.gemini_model:
3343
- lines.append(f" gemini-model : {state.gemini_model}")
3367
+ if state.antigravity_model:
3368
+ lines.append(f" antigravity-model : {state.antigravity_model}")
3344
3369
  if state.report_writer_model:
3345
3370
  lines.append(f" report-writer : {state.report_writer_model}")
3346
3371
  lines.append(f" directive : {state.directive or '(none)'}")
@@ -8,7 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  from pathlib import Path
10
10
 
11
- ALLOWED_WORKERS = ["claude", "codex", "gemini", "report-writer"]
11
+ ALLOWED_WORKERS = ["claude", "codex", "antigravity", "report-writer"]
12
12
  DEFAULT_WORKERS = ["claude", "codex", "report-writer"]
13
13
  PROFILE_BULLET_HEADERS = {
14
14
  "- Workers:",
@@ -64,7 +64,7 @@ def resolve_profile_workers(profile_path: Path) -> list[str]:
64
64
 
65
65
  def resolve_optional_workers(profile_path: Path) -> list[str]:
66
66
  r"""`- Optional workers (opt-in via \`--workers\`):` 섹션 아래 sub-bullet
67
- 들에서 worker id 만 추출한다. (` - gemini — when added …` → `gemini`)
67
+ 들에서 worker id 만 추출한다. (` - antigravity — when added …` → `antigravity`)
68
68
  Required 와 중복되는 항목은 제거. ALLOWED_WORKERS 밖 토큰도 제거.
69
69
  """
70
70
  raw = _resolve_workers_under(profile_path, PROFILE_BULLET_HEADERS_OPTIONAL)
@@ -83,7 +83,7 @@ def normalize_workers(value: str) -> list[str]:
83
83
  """CSV 입력을 정규화한다.
84
84
 
85
85
  - 공백 strip, 소문자화, 중복 제거(첫 출현 우선).
86
- - 빈 입력이면 `DEFAULT_WORKERS` 를 default 로 사용 (gemini 제외).
86
+ - 빈 입력이면 `DEFAULT_WORKERS` 를 default 로 사용 (antigravity 제외).
87
87
  - 허용 외 worker 가 포함되면 `WorkersError`.
88
88
  """
89
89
  items = [v.strip().lower() for v in (value or "").split(",") if v.strip()]
@@ -83,7 +83,7 @@ PHASE_RULES: dict[str, dict[str, str]] = {
83
83
  " - `Out-of-plan edits` block listing every file touched outside the approved plan with rationale (empty block preferred)\n"
84
84
  ' - validation evidence: actual stdout/stderr and exit code for every pre / mid / post command from the plan (no paraphrased "tests pass")\n'
85
85
  " - TDD evidence for TDD-applicable steps: failing-test output before implementation commit and passing-test output after, with framing SHAs\n"
86
- " - per-verifier sections (`Gemini verifier`, `Codex verifier`, `Claude verifier`) with independent verdict (PASS / CONCERNS / FAIL) and cited diff snippets; dissent is preserved by `Claude lead`\n"
86
+ " - per-verifier sections (`Antigravity verifier`, `Codex verifier`, `Claude verifier`) with independent verdict (PASS / CONCERNS / FAIL) and cited diff snippets; dissent is preserved by `Claude lead`\n"
87
87
  " - rollback verification (revert SHA reachable, feature flag toggle works, migration down step valid; dry-run preferred)\n"
88
88
  " - routing recommendation for `final-verification` (ready / needs new error-analysis or planning loop)"
89
89
  ),
@@ -479,7 +479,7 @@ def _link_sync_files(source_root: Path, worktree_path: Path) -> list[str]:
479
479
 
480
480
  def _seed_worktree_settings_symlink(worktree_path: Path) -> None:
481
481
  """Seed `.claude/settings.local.json` in the worker worktree so dispatched
482
- Claude / codex / gemini sessions inherit the okstra read-only / write
482
+ Claude / codex / antigravity sessions inherit the okstra read-only / write
483
483
  allowlist. Mirrors the main-project seeding done in `run.py` — needed
484
484
  because `_link_sync_dirs` skips `.claude/` whenever `git worktree add`
485
485
  already materialised the directory (e.g. tracked `.claude/handoff-*.md`).
@@ -5,12 +5,11 @@ from .blocks import na_block, usage_block
5
5
  from .claude import claude_session_totals, find_claude_team_sessions
6
6
  from .codex import codex_session_total, find_codex_session
7
7
  from .collect import collect
8
- from .gemini import find_gemini_session, gemini_session_total
8
+ from .antigravity import antigravity_session_total, find_antigravity_session
9
9
  from .jsonl_io import iter_jsonl
10
10
  from .paths import (
11
11
  CLAUDE_PROJECTS,
12
12
  CODEX_SESSIONS,
13
- GEMINI_TMP,
14
13
  claude_project_dir,
15
14
  utc_now,
16
15
  )
@@ -21,7 +20,7 @@ from .pricing import (
21
20
  claude_billable_equivalent,
22
21
  claude_cost_usd,
23
22
  codex_cost_usd,
24
- gemini_cost_usd,
23
+ antigravity_cost_usd,
25
24
  )
26
25
  from .report import (
27
26
  SubstituteRefusedError,
@@ -0,0 +1,26 @@
1
+ """Antigravity (`agy`) CLI usage collector.
2
+
3
+ okstra dispatches the antigravity-worker via `agy --print` (see
4
+ `okstra-antigravity-exec.sh`). That non-interactive print mode does not persist
5
+ a parseable transcript with per-call token counts to any known location, so
6
+ per-run token/cost usage cannot be attributed to the antigravity worker. These
7
+ collectors therefore report "unavailable" rather than fabricating numbers — the
8
+ worker's usage block degrades to `na` and `/okstra-inspect context-cost` shows
9
+ N/A for antigravity. If a future `agy` build exposes a transcript, wire its
10
+ parser here (model ids stay `gemini-*`, so `antigravity_cost_usd` already prices it).
11
+ """
12
+ from __future__ import annotations
13
+
14
+ from pathlib import Path
15
+
16
+
17
+ def antigravity_session_total(json_path: Path) -> dict:
18
+ return {"totalTokens": 0, "available": False}
19
+
20
+
21
+ def find_antigravity_session(project_root: Path, started_at: str, ended_at: str) -> Path | None:
22
+ return None
23
+
24
+
25
+ def find_antigravity_sessions(project_root: Path, started_at: str, ended_at: str) -> list[Path]:
26
+ return []
@@ -15,6 +15,8 @@ def usage_block(totals: dict, source: str, note: str | None = None) -> dict:
15
15
  "outputTokens": totals.get("outputTokens", 0) or 0,
16
16
  "toolUses": totals.get("toolUses", 0) or 0,
17
17
  "durationMs": totals.get("durationMs", 0) or 0,
18
+ "startedAt": totals.get("startedAt"),
19
+ "endedAt": totals.get("endedAt"),
18
20
  "source": source,
19
21
  "collectedAt": utc_now(),
20
22
  }
@@ -57,6 +59,8 @@ def na_block(reason: str) -> dict:
57
59
  "totalTokens": 0,
58
60
  "toolUses": 0,
59
61
  "durationMs": 0,
62
+ "startedAt": None,
63
+ "endedAt": None,
60
64
  "source": "unavailable",
61
65
  "collectedAt": utc_now(),
62
66
  "note": reason,
@@ -14,9 +14,13 @@ from .claude import (
14
14
  find_claude_team_sessions,
15
15
  )
16
16
  from .codex import codex_session_total, find_codex_session, find_codex_sessions
17
- from .gemini import find_gemini_session, find_gemini_sessions, gemini_session_total
17
+ from .antigravity import (
18
+ antigravity_session_total,
19
+ find_antigravity_session,
20
+ find_antigravity_sessions,
21
+ )
18
22
  from .paths import claude_project_dir, utc_now
19
- from .pricing import codex_cost_usd, gemini_cost_usd
23
+ from .pricing import codex_cost_usd, antigravity_cost_usd
20
24
 
21
25
 
22
26
  def match_prefixes(worker_id: str) -> list[str]:
@@ -275,7 +279,7 @@ def _codex_runtime_provider(worker: dict) -> str:
275
279
  agent = str(worker.get("agent") or "").strip()
276
280
  if worker_id == "report-writer":
277
281
  return "codex"
278
- if agent in {"codex", "gemini"}:
282
+ if agent in {"codex", "antigravity"}:
279
283
  return agent
280
284
  return ""
281
285
 
@@ -290,8 +294,8 @@ def _cli_sessions_for_windows(
290
294
  for started_at, ended_at in windows:
291
295
  if provider == "codex":
292
296
  matches = find_codex_sessions(project_root, started_at, ended_at)
293
- elif provider == "gemini":
294
- matches = find_gemini_sessions(project_root, started_at, ended_at)
297
+ elif provider == "antigravity":
298
+ matches = find_antigravity_sessions(project_root, started_at, ended_at)
295
299
  else:
296
300
  matches = []
297
301
  for path in matches:
@@ -307,7 +311,7 @@ def _cli_session_totals(provider: str, session_paths: list[Path]) -> list[dict]:
307
311
  if provider == "codex":
308
312
  total = codex_session_total(session_path)
309
313
  else:
310
- total = gemini_session_total(session_path)
314
+ total = antigravity_session_total(session_path)
311
315
  if total.get("available"):
312
316
  totals.append(total)
313
317
  return totals
@@ -328,7 +332,7 @@ def _cli_usage_block(provider: str, totals: dict, session_paths: list[Path]) ->
328
332
  totals.get("outputTokens", 0) or 0,
329
333
  )
330
334
  else:
331
- cost = gemini_cost_usd(
335
+ cost = antigravity_cost_usd(
332
336
  totals.get("model"),
333
337
  totals.get("inputTokens", 0) or 0,
334
338
  totals.get("outputTokens", 0) or 0,
@@ -403,7 +407,7 @@ def _populate_usage_summary(
403
407
  for w in workers:
404
408
  u = w.get("usage") or {}
405
409
  if (
406
- u.get("source") not in {"codex-cli", "gemini-cli"}
410
+ u.get("source") not in {"codex-cli", "agy-cli"}
407
411
  and u.get("model")
408
412
  and u.get("estimatedCostUsd") is None
409
413
  and (u.get("totalTokens") or 0) > 0
@@ -433,7 +437,7 @@ def _populate_usage_summary(
433
437
  "definitions": {
434
438
  "totalTokens": "Sum of input + output + cache_creation + cache_read tokens (raw processed volume; matches Anthropic API breakdown). Cache reads are 95%+ in long sessions.",
435
439
  "billableEquivalentTokens": "Tokens normalized to base-input-price units (cache_creation_5m x1.25, cache_creation_1h x2.0, cache_read x0.1, output x5). 5m vs 1h is split from usage.cache_creation when the API breakdown is present; otherwise all cache_creation falls into 5m.",
436
- "estimatedCostUsd": "USD cost using public list pricing for the model recorded in the session. cliWorkers covers Codex/Gemini CLI calls billed under those providers.",
440
+ "estimatedCostUsd": "USD cost using public list pricing for the model recorded in the session. cliWorkers covers Codex/Antigravity CLI calls billed under those providers.",
437
441
  },
438
442
  }
439
443
 
@@ -547,17 +551,18 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
547
551
  block["additionalSessionIds"] = [sid for sid, _, _ in matched[1:]]
548
552
  block["matchedAgentNames"] = sorted({t.get("agentName") for _, _, t in matched if t.get("agentName")})
549
553
 
550
- # For codex/gemini workers, find every CLI session that fell inside the
551
- # aggregated wrapper window.
554
+ # For codex/antigravity workers, find every CLI session that fell inside
555
+ # the aggregated wrapper window. (agy print mode leaves no transcript, so
556
+ # the antigravity path always resolves to "session not found" → na cost.)
552
557
  wrapper_start = aggregate.get("startedAt") or ""
553
558
  wrapper_end = aggregate.get("endedAt") or ""
554
- if agent in ("codex", "gemini"):
559
+ if agent in ("codex", "antigravity"):
555
560
  if agent == "codex":
556
561
  cli = find_codex_session(cwd, wrapper_start, wrapper_end)
557
562
  cli_totals = codex_session_total(cli) if cli else None
558
563
  else:
559
- cli = find_gemini_session(cwd, wrapper_start, wrapper_end)
560
- cli_totals = gemini_session_total(cli) if cli else None
564
+ cli = find_antigravity_session(cwd, wrapper_start, wrapper_end)
565
+ cli_totals = antigravity_session_total(cli) if cli else None
561
566
  if cli is None:
562
567
  block["cliNote"] = f"{agent} CLI session not found in wrapper window (fallback or never invoked)"
563
568
  else:
@@ -574,7 +579,7 @@ def collect(team_state_path: Path, project_root: Path | None = None, *,
574
579
  cli_totals.get("outputTokens", 0) or 0,
575
580
  )
576
581
  else:
577
- cost = gemini_cost_usd(
582
+ cost = antigravity_cost_usd(
578
583
  cli_totals.get("model"),
579
584
  cli_totals.get("inputTokens", 0) or 0,
580
585
  cli_totals.get("outputTokens", 0) or 0,
@@ -8,7 +8,6 @@ from pathlib import Path
8
8
  HOME = Path.home()
9
9
  CLAUDE_PROJECTS = HOME / ".claude" / "projects"
10
10
  CODEX_SESSIONS = HOME / ".codex" / "sessions"
11
- GEMINI_TMP = HOME / ".gemini" / "tmp"
12
11
 
13
12
 
14
13
  def utc_now() -> str:
@@ -8,7 +8,8 @@ provider:
8
8
  `claude-haiku-4-5-*`, `claude-3-5-sonnet-*`, `claude-3-5-haiku-*`,
9
9
  `claude-3-opus-*`, `claude-3-haiku-*`.
10
10
  * OpenAI / Codex — `gpt-5*`, `gpt-4o*`, `gpt-4*`.
11
- * Google / Gemini — `gemini-2.5-pro*`, `gemini-2.5-flash*`, `gemini-2.0-flash*`.
11
+ * Google / Gemini — `gemini-3.5-flash*`, `gemini-3.1-pro*`, `gemini-3-flash*`,
12
+ `gemini-2.5-pro*`, `gemini-2.5-flash*`, `gemini-2.0-flash*`.
12
13
 
13
14
  Insertion order is the match order, so list more specific keys first. Update
14
15
  when providers change list pricing.
@@ -112,7 +113,9 @@ GEMINI_PRICING = {
112
113
  # Both dotted (`gemini-3.1-pro`) and hyphenated (`gemini-3-1-pro`) id
113
114
  # forms appear in the wild, so include both for the new 3.x families.
114
115
 
115
- # Gemini 3 series (preview).
116
+ # Gemini 3 series.
117
+ "3.5-flash": (1.50, 9.00),
118
+ "3-5-flash": (1.50, 9.00),
116
119
  "3.1-pro": (2.00, 12.0),
117
120
  "3-1-pro": (2.00, 12.0),
118
121
  "3-flash": (0.50, 3.00),
@@ -203,7 +206,7 @@ def codex_cost_usd(model: str | None, input_t: int, cached_input_t: int, output_
203
206
  return round((fresh * pi + cached_input_t * pci + output_t * po) / 1_000_000, 4)
204
207
 
205
208
 
206
- def gemini_cost_usd(model: str | None, input_t: int, output_t: int) -> float | None:
209
+ def antigravity_cost_usd(model: str | None, input_t: int, output_t: int) -> float | None:
207
210
  p = _match_pricing(model, GEMINI_PRICING)
208
211
  if p is None:
209
212
  return None
@@ -884,7 +884,7 @@
884
884
  "required": ["agent", "role", "model", "status", "summary"],
885
885
  "additionalProperties": false,
886
886
  "properties": {
887
- "agent": { "enum": ["Claude Code", "Codex", "Gemini"] },
887
+ "agent": { "enum": ["Claude Code", "Codex", "Antigravity"] },
888
888
  "role": { "type": "string", "minLength": 1 },
889
889
  "model": { "type": "string", "minLength": 1 },
890
890
  "status": { "$ref": "#/$defs/WorkerStatus" },
@@ -462,7 +462,7 @@ on a project-internal concrete object whenever possible.
462
462
  `<PROJECT_ROOT>`) and the canonical symbol name. Record the resolution
463
463
  under `Augmentation > Domain alignment` with the `evidence-link` label,
464
464
  and link the absolute path from `Related Artifacts`. This is what makes
465
- the brief portable across downstream workers (Claude / Codex / Gemini)
465
+ the brief portable across downstream workers (Claude / Codex / Antigravity)
466
466
  that may operate in separate worktrees and need to point at the same
467
467
  object.
468
468
  - **Conversion-block signal** — when a reporter statement cannot be