okstra 0.85.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.
- package/docs/kr/architecture.md +27 -27
- package/docs/kr/cli.md +21 -21
- package/docs/kr/performance-improvement-plan-v2.md +4 -4
- package/docs/kr/performance-improvement-plan.md +3 -3
- package/docs/project-structure-overview.md +6 -6
- package/docs/superpowers/plans/2026-06-17-okstra-error-report.md +724 -0
- package/docs/superpowers/specs/2026-06-17-okstra-error-report-design.md +123 -0
- package/docs/task-process/error-analysis.md +1 -1
- package/docs/task-process/final-verification.md +4 -4
- package/docs/task-process/implementation-planning.md +2 -2
- package/docs/task-process/implementation.md +3 -3
- package/docs/task-process/requirements-discovery.md +2 -2
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/{gemini-worker.md → antigravity-worker.md} +56 -56
- package/runtime/agents/workers/claude-worker.md +2 -2
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/agents/workers/report-writer-worker.md +2 -2
- package/runtime/bin/lib/okstra/cli.sh +3 -3
- package/runtime/bin/lib/okstra/globals.sh +8 -8
- package/runtime/bin/lib/okstra/interactive.sh +1 -1
- package/runtime/bin/lib/okstra/usage.sh +7 -7
- package/runtime/bin/lib/okstra-ctl/cmd-reconcile.sh +5 -3
- package/runtime/bin/lib/okstra-ctl/prepare.sh +4 -2
- package/runtime/bin/{okstra-gemini-exec.sh → okstra-antigravity-exec.sh} +67 -60
- package/runtime/bin/okstra-error-log.py +2 -1
- package/runtime/bin/okstra-token-usage.py +3 -4
- package/runtime/bin/okstra-trace-cleanup.sh +4 -4
- package/runtime/bin/okstra-wrapper-status.py +3 -3
- package/runtime/bin/okstra.sh +1 -1
- package/runtime/prompts/launch.template.md +3 -3
- package/runtime/prompts/lead/context-loader.md +1 -1
- package/runtime/prompts/lead/convergence.md +8 -8
- package/runtime/prompts/lead/okstra-lead-contract.md +15 -15
- package/runtime/prompts/lead/report-writer.md +6 -6
- package/runtime/prompts/lead/team-contract.md +31 -31
- package/runtime/prompts/profiles/_coding-conventions-preflight.md +2 -2
- package/runtime/prompts/profiles/_common-contract.md +7 -7
- package/runtime/prompts/profiles/_implementation-deliverable.md +1 -1
- package/runtime/prompts/profiles/_implementation-executor.md +6 -6
- package/runtime/prompts/profiles/_implementation-verifier.md +4 -4
- package/runtime/prompts/profiles/_stage-discipline.md +1 -1
- package/runtime/prompts/profiles/error-analysis.md +1 -1
- package/runtime/prompts/profiles/final-verification.md +1 -1
- package/runtime/prompts/profiles/forbidden-actions.json +2 -2
- package/runtime/prompts/profiles/implementation-planning.md +1 -1
- package/runtime/prompts/profiles/implementation.md +3 -3
- package/runtime/prompts/profiles/improvement-discovery.md +3 -3
- package/runtime/prompts/profiles/requirements-discovery.md +1 -1
- package/runtime/prompts/wizard/prompts.ko.json +9 -5
- package/runtime/python/okstra_ctl/__init__.py +2 -0
- package/runtime/python/okstra_ctl/analysis_packet.py +1 -1
- package/runtime/python/okstra_ctl/clarification_items.py +17 -7
- package/runtime/python/okstra_ctl/codex_dispatch.py +2 -2
- package/runtime/python/okstra_ctl/context_cost.py +10 -44
- package/runtime/python/okstra_ctl/doctor.py +1 -1
- package/runtime/python/okstra_ctl/error_report.py +185 -0
- package/runtime/python/okstra_ctl/improvement_lenses.py +1 -1
- package/runtime/python/okstra_ctl/models.py +10 -10
- package/runtime/python/okstra_ctl/paths.py +9 -9
- package/runtime/python/okstra_ctl/reconcile.py +74 -11
- package/runtime/python/okstra_ctl/render.py +21 -21
- package/runtime/python/okstra_ctl/run.py +16 -16
- package/runtime/python/okstra_ctl/run_context.py +1 -1
- package/runtime/python/okstra_ctl/seeding.py +1 -1
- package/runtime/python/okstra_ctl/task_target.py +44 -0
- package/runtime/python/okstra_ctl/team.py +1 -1
- package/runtime/python/okstra_ctl/wizard.py +55 -30
- package/runtime/python/okstra_ctl/workers.py +3 -3
- package/runtime/python/okstra_ctl/workflow.py +1 -1
- package/runtime/python/okstra_ctl/worktree.py +1 -1
- package/runtime/python/okstra_token_usage/__init__.py +2 -3
- package/runtime/python/okstra_token_usage/antigravity.py +26 -0
- package/runtime/python/okstra_token_usage/blocks.py +4 -0
- package/runtime/python/okstra_token_usage/collect.py +20 -15
- package/runtime/python/okstra_token_usage/paths.py +0 -1
- package/runtime/python/okstra_token_usage/pricing.py +6 -3
- package/runtime/schemas/final-report-v1.0.schema.json +1 -1
- package/runtime/skills/okstra-brief/SKILL.md +1 -1
- package/runtime/skills/okstra-inspect/SKILL.md +85 -21
- package/runtime/skills/okstra-run/SKILL.md +3 -3
- package/runtime/skills/okstra-setup/SKILL.md +2 -2
- package/runtime/templates/prd/brief.template.md +2 -2
- package/runtime/templates/reports/i18n/en.json +1 -1
- package/runtime/templates/reports/i18n/ko.json +1 -1
- package/runtime/templates/reports/settings.template.json +2 -2
- package/runtime/templates/reports/task-brief.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +7 -7
- package/runtime/validators/forbidden_actions.py +1 -1
- package/runtime/validators/lib/fixtures.sh +3 -3
- package/runtime/validators/lib/validate-assets.sh +1 -1
- package/runtime/validators/validate-run.py +8 -8
- package/src/cli-registry.mjs +7 -0
- package/src/codex-dispatch.mjs +3 -3
- package/src/codex-run.mjs +2 -2
- package/src/doctor.mjs +1 -1
- package/src/error-report.mjs +27 -0
- package/src/install.mjs +1 -1
- package/src/render-bundle.mjs +1 -1
- package/src/uninstall.mjs +2 -2
- 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
|
-
|
|
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-
|
|
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
|
-
"
|
|
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", "
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
|
2449
|
-
t = _p(state.workspace_root, "
|
|
2450
|
-
return _model_pick(
|
|
2451
|
-
|
|
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
|
|
2455
|
-
state.
|
|
2456
|
-
return f"
|
|
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", "
|
|
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(
|
|
2973
|
+
Step(S_ANTIGRAVITY_MODEL,
|
|
2949
2974
|
applies=lambda s: (s.use_defaults is False
|
|
2950
2975
|
and s.task_type != "implementation"
|
|
2951
|
-
and "
|
|
2952
|
-
and
|
|
2953
|
-
build=
|
|
2954
|
-
owns=("
|
|
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,
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
3343
|
-
lines.append(f"
|
|
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", "
|
|
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 만 추출한다. (` -
|
|
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 로 사용 (
|
|
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 (`
|
|
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 /
|
|
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 .
|
|
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
|
-
|
|
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 .
|
|
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,
|
|
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", "
|
|
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 == "
|
|
294
|
-
matches =
|
|
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 =
|
|
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 =
|
|
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", "
|
|
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/
|
|
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/
|
|
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", "
|
|
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 =
|
|
560
|
-
cli_totals =
|
|
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 =
|
|
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,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-
|
|
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
|
|
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
|
|
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", "
|
|
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 /
|
|
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
|