okstra 0.116.0 → 0.118.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/README.md +115 -104
- package/docs/architecture/storage-model.md +300 -0
- package/docs/architecture.md +802 -0
- package/docs/cli.md +658 -0
- package/docs/container.md +124 -0
- package/docs/contributor-change-matrix.md +5 -4
- package/docs/follow-ups/2026-07-10-final-report-option-3.md +51 -0
- package/docs/performance-improvement-plan-v2.md +374 -0
- package/docs/project-structure-overview.md +28 -10
- package/package.json +10 -5
- package/runtime/BUILD.json +2 -2
- package/runtime/python/okstra_ctl/recap.py +80 -3
- package/runtime/python/okstra_ctl/report_views.py +23 -8
- package/runtime/skills/okstra-inspect/SKILL.md +37 -2
- package/runtime/skills/okstra-pr-gen/SKILL.md +108 -0
- package/src/cli-registry.mjs +10 -0
- package/src/commands/inspect/recap.mjs +6 -1
- package/src/commands/pr/default.md +21 -0
- package/src/commands/pr/pr.mjs +261 -0
- package/src/lib/skill-catalog.mjs +1 -0
- package/README.kr.md +0 -231
- package/docs/kr/architecture/storage-model.md +0 -297
- package/docs/kr/architecture.md +0 -794
- package/docs/kr/cli.md +0 -657
- package/docs/kr/container.md +0 -122
- package/docs/kr/follow-ups/2026-07-10-final-report-option-3.md +0 -51
- package/docs/kr/performance-improvement-plan-v2.md +0 -374
- package/docs/kr/performance-improvement-plan.md +0 -147
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Okstra — 폴더 구조 및 기능 요약
|
|
2
2
|
|
|
3
|
-
> 현재 저장소 기준의 living map입니다. 빠른 사용법은 [`README.
|
|
3
|
+
> 현재 저장소 기준의 living map입니다. 빠른 사용법은 [`README.md`](../README.md), 내부 실행 계약은 [`docs/architecture.md`](architecture.md), 저장 구조는 [`docs/architecture/storage-model.md`](architecture/storage-model.md), CLI 세부 옵션은 [`docs/cli.md`](cli.md)를 함께 보세요.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -54,19 +54,21 @@ okstra/
|
|
|
54
54
|
│ ├── okstra_vendor/ vendored Jinja2 / MarkupSafe + graphify / networkx (okstra-graphify skill)
|
|
55
55
|
│ ├── lib/okstra/ Bash helpers for okstra.sh
|
|
56
56
|
│ └── lib/okstra-ctl/ Bash control-center subcommands
|
|
57
|
-
├── skills/ Claude Code skills (
|
|
57
|
+
├── skills/ Claude Code skills (11)
|
|
58
58
|
├── agents/ lead SKILL.md + worker agent specs
|
|
59
59
|
├── prompts/ launch template, phase profiles, wizard prompt JSON
|
|
60
60
|
├── schemas/ JSON schema for final-report data.json
|
|
61
61
|
├── templates/ report, setup, PR, project-doc templates/assets
|
|
62
62
|
├── validators/ run / brief / schedule / view validators
|
|
63
63
|
├── tools/build.mjs source → runtime sync
|
|
64
|
+
├── tools/docs_publication/ public-manual publication pipeline (build-time only; not shipped)
|
|
65
|
+
├── config/docs-publication.json publication inventory: every tracked Markdown path + classification
|
|
64
66
|
├── runtime/ generated install payload; do not edit directly
|
|
65
67
|
├── tests/ pytest unit suite
|
|
66
68
|
├── tests-e2e/ Bash end-to-end scenarios
|
|
67
69
|
├── docs/ architecture, CLI, plans/specs
|
|
68
70
|
├── package.json npm metadata and published file list
|
|
69
|
-
├── README.md
|
|
71
|
+
├── README.md public user manual
|
|
70
72
|
├── CHANGES.md / CHANGELOG.md human-authored and generated changelogs
|
|
71
73
|
└── RELEASING.md release process
|
|
72
74
|
```
|
|
@@ -84,9 +86,13 @@ okstra/
|
|
|
84
86
|
- `bin/`
|
|
85
87
|
- `src/`
|
|
86
88
|
- `runtime/`
|
|
87
|
-
- `docs
|
|
89
|
+
- `docs/architecture.md`
|
|
90
|
+
- `docs/architecture/`
|
|
91
|
+
- `docs/cli.md`
|
|
92
|
+
- `docs/container.md`
|
|
93
|
+
- `docs/performance-improvement-plan-v2.md`
|
|
94
|
+
- selected contributor, follow-up, AI, and task-process documentation paths
|
|
88
95
|
- `README.md`
|
|
89
|
-
- `README.kr.md`
|
|
90
96
|
|
|
91
97
|
So `runtime/` is not the only npm-published content. It is the install payload copied into `~/.okstra`.
|
|
92
98
|
|
|
@@ -142,7 +148,7 @@ Runtime/install asset changes follow this checklist:
|
|
|
142
148
|
|
|
143
149
|
`bin/okstra` is a thin dynamic-import router. Every command module exports `run(args) -> Promise<number>` or a named install/uninstall runner.
|
|
144
150
|
|
|
145
|
-
`src/` is layered: the dispatch table (`src/cli-registry.mjs`) stays at the root, shared infrastructure with no command export lives under `src/lib/`, and command modules are grouped by domain under `src/commands/{lifecycle,execute,inspect,report,memory}/`.
|
|
151
|
+
`src/` is layered: the dispatch table (`src/cli-registry.mjs`) stays at the root, shared infrastructure with no command export lives under `src/lib/`, and command modules are grouped by domain under `src/commands/{lifecycle,execute,inspect,report,memory,pr}/`.
|
|
146
152
|
|
|
147
153
|
| Command | Module | Role |
|
|
148
154
|
|---|---|---|
|
|
@@ -175,6 +181,7 @@ Runtime/install asset changes follow this checklist:
|
|
|
175
181
|
| `token-usage` | `src/commands/execute/token-usage.mjs` | Wrap installed Python token usage CLI |
|
|
176
182
|
| `spawn-followups`, `error-log` | `src/commands/execute/*.mjs` | Follow-up task bundle creation and run error-log append helpers |
|
|
177
183
|
| `memory` | `src/commands/memory/memory.mjs` | Store/find global conversation memory under `~/.okstra/memory-book` |
|
|
184
|
+
| `pr` | `src/commands/pr/pr.mjs` | `okstra pr <template\|branches\|gen>` — PR body template store under `~/.okstra/template/pr/` (bundled fallback `src/commands/pr/default.md`), base-branch recommendation, and the `gen` JSON bundle (template + `<base>..HEAD` commits + `<base>...HEAD` diffstat) backing the okstra-pr-gen skill. Git-only; no project registration required |
|
|
178
185
|
| `recap` | `src/commands/inspect/recap.mjs` | `okstra recap <assemble\|record>` Node wrapper backing the okstra-inspect `recap` facet |
|
|
179
186
|
| `rollup` | `src/commands/inspect/rollup.mjs` | `okstra rollup` thin shim into `scripts/okstra_ctl/rollup.py` — read-only cross-task roll-up backing the okstra-rollup skill |
|
|
180
187
|
| `container` | `src/commands/inspect/container.mjs` | `bin okstra container` thin shim into `scripts/okstra_ctl/container.py` for the okstra-container-build skill |
|
|
@@ -211,6 +218,8 @@ Important modules:
|
|
|
211
218
|
| `implementation_stage.py` | `implementation` 단일-stage run 오케스트레이션 — Stage Lifecycle Snapshot 읽기 → 가용 Stage Map entry 선택 → 격리 stage worktree provision → 선택 stage 를 run context 로 발행 (`run.py` 에서 추출) |
|
|
212
219
|
| `stage_targets.py` | Stage readiness/verification 정책 SSOT — Stage Lifecycle Snapshot(`consumers.jsonl` ledger + carry sidecar backfill + active registry reservation)으로 어떤 stage 가 실행 가능한지, 어느 commit 에서 분기하는지, final-verification 이 무엇을 검사하는지 결정. `order_stage_closure` 는 wizard 멀티선택 stage 집합의 의존성 closure 를 Kahn 위상정렬해 `chain-stages` 무인 연쇄 순서를 산출 |
|
|
213
220
|
| `stage_reconcile.py` | stage prepare 흐름 공용 best-effort git reconciliation (`git_reconcile.auto_reconcile` 위임; advisory — 실패는 stderr 보고만, dependency gate 가 권위 판정 유지) |
|
|
221
|
+
| `incremental_scope.py` | `implementation-planning` clarification 재실행의 증분 재검증 판정 (결정적 순수 함수) — 직전 run data.json 의 `implementationPlanning.stageMap` 에서 의존성 그래프를 읽고, base-ref SHA 불변 + 영향 stage 의 `downstream_stage_closure` 가 전체 stage 의 과반 이하일 때만 `mode="incremental"` 을 반환. CLI: `okstra incremental-scope` |
|
|
222
|
+
| `incremental_carry.py` | 증분 재실행의 carry 병합 — 직전 run 의 plan-item verdict 중 이번 run 이 재검증하지 않은 항목을 `carriedForwardFromSeq` 태그와 함께 현재 data.json 에 병합. `schemaVersion` drift 시 `CarryError` 로 비영 종료해 full 폴백을 유도. CLI: `okstra incremental-carry` |
|
|
214
223
|
| `run_context.py` | Per-task mutex, run context and run-input persistence; `consumers_mutex` helper for atomic `consumers.jsonl` writes |
|
|
215
224
|
| `path_hints.py` | Compact path-hint persistence + legacy context hydration — `run-context` / `active-run-context` 를 schemaVersion `2.0` 의 `identity` + `pathHints` compact schema 로 저장하고, host-side reader 가 읽는 순간 기존 flat path key(`RUN_MANIFEST_RELATIVE_PATH`, `TEAM_STATE_PATH` 등)를 메모리에서 hydrate |
|
|
216
225
|
| `consumers.py` | Append-only `consumers.jsonl` writer + reader — records which `implementation` runs consumed which `implementation-planning` stage |
|
|
@@ -291,6 +300,11 @@ Token/cost accounting:
|
|
|
291
300
|
|
|
292
301
|
`schemas/final-report-v1.0.schema.json` is the final-report data.json contract. The report-writer worker writes `final-report-<task-type>-<seq>.data.json`; the renderer produces Markdown from that JSON.
|
|
293
302
|
|
|
303
|
+
Optional (v1.0 하위호환) 최상위 키:
|
|
304
|
+
|
|
305
|
+
- `readerSummary` — 사람이 먼저 읽는 요약 블록. 존재하면 5개 필드(`decision`, `humanActionRequired`, `blockingItems`, `safeToSkip`, `recommendedCommand`)가 모두 필수. 없는 기존 data.json 도 그대로 렌더됩니다(HTML dashboard 는 `verdictCard` 로 폴백).
|
|
306
|
+
- `implementationPlanning.incrementalDecision` — 증분 재검증 판정 (`mode`, `reverifyStages`, `carryStages`, `reason`). `mode == "incremental"` 이면 렌더러가 `### 0.1 Incremental Re-Verification Scope` 감사 블록을 emit 하고 `validators/validate-run.py` 가 그 부재를 차단합니다.
|
|
307
|
+
|
|
294
308
|
### 4.9 `validators/`
|
|
295
309
|
|
|
296
310
|
| File | Role |
|
|
@@ -306,7 +320,7 @@ Token/cost accounting:
|
|
|
306
320
|
|
|
307
321
|
### 4.10 `skills/`
|
|
308
322
|
|
|
309
|
-
|
|
323
|
+
11 user-facing skills (the only skills `okstra install` copies). The list SSOT is `USER_SKILL_NAMES` in `src/lib/skill-catalog.mjs`; the Claude plugin manifest and the installer both derive from it:
|
|
310
324
|
|
|
311
325
|
| Skill | User-invocable | Role |
|
|
312
326
|
|---|---:|---|
|
|
@@ -317,6 +331,8 @@ Token/cost accounting:
|
|
|
317
331
|
| `okstra-rollup` | yes | Cross-task roll-up — aggregate runs/time/errors across a task-group (or whole project) and synthesize a digest from the report files |
|
|
318
332
|
| `okstra-schedule` | yes | Generate task-group schedule |
|
|
319
333
|
| `okstra-container-build` | yes | Non-linear deploy tool — deploy a verified task's code as a docker compose group and watch each container (sub-commands `up` / `status` / `logs` / `stop-watcher` / `down`) |
|
|
334
|
+
| `okstra-graphify` | yes | Knowledge graph over the project's own `.okstra/` memory — final reports, `decisions/*.md`, `glossary.md`; scope restricted to `.okstra/`, output under `.okstra/graph/` (sub-commands `build` / `query` / `path` / `explain` / `mcp` / `wiki`) |
|
|
335
|
+
| `okstra-pr-gen` | yes | Register PR body templates under `~/.okstra/template/pr/` and generate a PR description from a branch diff (drives `okstra pr template` / `branches` / `gen`). **Global skill** — needs a Git repo, not `<PROJECT_ROOT>/.okstra/project.json` |
|
|
320
336
|
| `okstra-manager` | yes | Coordinate cross-project okstra tasks through manager-owned plans, assignments, sync snapshots, status, and child launch context packets |
|
|
321
337
|
| `okstra-setup` | yes | Install/check runtime and register project |
|
|
322
338
|
|
|
@@ -481,11 +497,13 @@ Final report artifacts live under `runs/<task-type>/reports/`. Human responses f
|
|
|
481
497
|
|
|
482
498
|
When changing code, keep these docs in sync:
|
|
483
499
|
|
|
484
|
-
- New Node command: update `bin/okstra` usage, `README
|
|
500
|
+
- New Node command: update `bin/okstra` usage, `README.md`, this file, and `docs/cli.md`.
|
|
485
501
|
- New runtime source copied to users: update `tools/build.mjs`, install/uninstall manifests if applicable, and this file.
|
|
486
|
-
- New skill/agent: update `README
|
|
502
|
+
- New skill/agent: update `README.md`, this file, install/uninstall fallback lists, and `CHANGES.md`.
|
|
487
503
|
- New report field/section: update schema, template, report-writer worker, validator tests, this file's report model if user-visible.
|
|
488
|
-
- New phase/profile behavior: update `prompts/profiles/*`, `docs/
|
|
504
|
+
- New phase/profile behavior: update `prompts/profiles/*`, `docs/architecture.md`, `docs/cli.md`, and `README.md` if user-facing.
|
|
505
|
+
|
|
506
|
+
`README.md`, `docs/cli.md`, `docs/architecture.md`, `docs/architecture/storage-model.md`, `docs/container.md`, and `docs/performance-improvement-plan-v2.md` are `translated` outputs in `config/docs-publication.json` — do **not** hand-edit them. Author the Korean source under the maintainer's Git-ignored `.project-docs/ko-source/`, then run the publication pipeline (`python3 -m tools.docs_publication prepare|validate|apply|record`), which keeps the source/output hashes in `.project-docs/doc-publishing/state.json` in step. Files classified `pending` — including this one, `CHANGES.md`, and `CHANGELOG.md` — are edited directly.
|
|
489
507
|
|
|
490
508
|
---
|
|
491
509
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "okstra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.118.0",
|
|
4
4
|
"description": "Multi-agent cross-verification orchestrator runtime + Claude Code skills.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "devonshin",
|
|
@@ -16,14 +16,18 @@
|
|
|
16
16
|
"bin/",
|
|
17
17
|
"src/",
|
|
18
18
|
"runtime/",
|
|
19
|
+
"docs/architecture.md",
|
|
20
|
+
"docs/architecture/",
|
|
21
|
+
"docs/cli.md",
|
|
22
|
+
"docs/container.md",
|
|
19
23
|
"docs/contributor-change-matrix.md",
|
|
24
|
+
"docs/follow-ups/2026-07-10-final-report-option-3.md",
|
|
20
25
|
"docs/for-ai/",
|
|
21
|
-
"docs/
|
|
26
|
+
"docs/performance-improvement-plan-v2.md",
|
|
22
27
|
"docs/pr-template-usage.md",
|
|
23
28
|
"docs/project-structure-overview.md",
|
|
24
29
|
"docs/task-process/",
|
|
25
|
-
"README.md"
|
|
26
|
-
"README.kr.md"
|
|
30
|
+
"README.md"
|
|
27
31
|
],
|
|
28
32
|
"engines": {
|
|
29
33
|
"node": ">=18"
|
|
@@ -31,9 +35,10 @@
|
|
|
31
35
|
"scripts": {
|
|
32
36
|
"build": "node tools/build.mjs",
|
|
33
37
|
"prepack": "node tools/build.mjs",
|
|
38
|
+
"docs:validate": "python3 -m tools.docs_publication validate --repo .",
|
|
34
39
|
"test:js": "node --test tests-js/*.test.mjs",
|
|
35
40
|
"test:py": "python3 -m pytest tests/",
|
|
36
41
|
"test:workflow": "bash validators/validate-workflow.sh",
|
|
37
|
-
"check": "npm run build && npm run test:js && npm run test:py && npm run test:workflow"
|
|
42
|
+
"check": "npm run docs:validate && npm run build && npm run test:js && npm run test:py && npm run test:workflow"
|
|
38
43
|
}
|
|
39
44
|
}
|
package/runtime/BUILD.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
"""Read-side recap for a task: assemble run-to-run phase transitions
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
"""Read-side recap for a task: assemble run-to-run phase transitions, append a
|
|
2
|
+
recap Q&A log, and write agent-authored notes. Writes only under
|
|
3
|
+
<task-root>/recap/recap-log.jsonl and <task-root>/notes/; never touches
|
|
4
|
+
task-manifest / catalog / timeline.
|
|
4
5
|
"""
|
|
5
6
|
from __future__ import annotations
|
|
6
7
|
|
|
@@ -10,9 +11,12 @@ import json
|
|
|
10
11
|
import sys
|
|
11
12
|
from pathlib import Path
|
|
12
13
|
|
|
14
|
+
from okstra_ctl.ids import slugify_task_segment
|
|
13
15
|
from okstra_ctl.run_context import dir_flock
|
|
14
16
|
from okstra_ctl.task_target import resolve_task_root, project_rel
|
|
15
17
|
|
|
18
|
+
NOTE_KINDS = ("verification-evidence", "decision-draft", "analysis-note")
|
|
19
|
+
|
|
16
20
|
|
|
17
21
|
def _load_timeline(task_root: Path) -> list[dict]:
|
|
18
22
|
path = task_root / "history" / "timeline.json"
|
|
@@ -91,6 +95,56 @@ def append_recap_entry(task_root, project_root, *, kind, mode, question,
|
|
|
91
95
|
return {"logPath": project_rel(log_path, project_root), "entry": entry}
|
|
92
96
|
|
|
93
97
|
|
|
98
|
+
def _note_path(notes_dir: Path, slug: str, date: str) -> Path:
|
|
99
|
+
# 같은 날 같은 slug 로 노트를 여러 개 남기면 덮어쓰지 않도록 -2, -3 을 붙인다.
|
|
100
|
+
base = notes_dir / f"{slug}-{date}.md"
|
|
101
|
+
if not base.exists():
|
|
102
|
+
return base
|
|
103
|
+
seq = 2
|
|
104
|
+
while (candidate := notes_dir / f"{slug}-{date}-{seq}.md").exists():
|
|
105
|
+
seq += 1
|
|
106
|
+
return candidate
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _note_frontmatter(*, task_key, kind, created_at, purpose, scope_note) -> str:
|
|
110
|
+
return (
|
|
111
|
+
"---\n"
|
|
112
|
+
f"task-key: {task_key}\n"
|
|
113
|
+
f"kind: {kind}\n"
|
|
114
|
+
"author: agent\n"
|
|
115
|
+
f"created-at: {created_at}\n"
|
|
116
|
+
f"purpose: {purpose}\n"
|
|
117
|
+
f"scope-note: {scope_note}\n"
|
|
118
|
+
"---\n\n"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def write_note(task_root, project_root, *, kind, slug, purpose, scope_note,
|
|
123
|
+
body, now) -> dict:
|
|
124
|
+
notes_dir = task_root / "notes"
|
|
125
|
+
notes_dir.mkdir(parents=True, exist_ok=True)
|
|
126
|
+
created_at = now.date().isoformat()
|
|
127
|
+
slug_seg = slugify_task_segment(slug)
|
|
128
|
+
if not slug_seg:
|
|
129
|
+
raise ValueError("slug must contain at least one alphanumeric character")
|
|
130
|
+
path = _note_path(notes_dir, slug_seg, created_at)
|
|
131
|
+
task_key = _task_key(task_root)
|
|
132
|
+
frontmatter = _note_frontmatter(
|
|
133
|
+
task_key=task_key, kind=kind, created_at=created_at,
|
|
134
|
+
purpose=purpose, scope_note=scope_note)
|
|
135
|
+
path.write_text(frontmatter + body.rstrip("\n") + "\n", encoding="utf-8")
|
|
136
|
+
note_rel = project_rel(path, project_root)
|
|
137
|
+
return {
|
|
138
|
+
"notePath": note_rel,
|
|
139
|
+
"taskKey": task_key,
|
|
140
|
+
"kind": kind,
|
|
141
|
+
"createdAt": created_at,
|
|
142
|
+
# 노트는 inert — 다음 run 이 자동으로 읽지 않는다. 이 인자를 그대로
|
|
143
|
+
# `okstra run ...` 에 붙여야만 instruction-set 에 주입된다.
|
|
144
|
+
"clarificationResponseArg": f"--clarification-response {note_rel}",
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
94
148
|
def _add_common(sp) -> None:
|
|
95
149
|
# skill 이 쓰는 `<verb> <target> --project-root <root>` 형태를 받으려면
|
|
96
150
|
# 공통 옵션을 부모가 아닌 각 서브파서에 등록해야 한다.
|
|
@@ -115,12 +169,35 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
115
169
|
p_rec.add_argument("--answer", required=True)
|
|
116
170
|
p_rec.add_argument("--citation", action="append", default=[])
|
|
117
171
|
|
|
172
|
+
p_note = sub.add_parser(
|
|
173
|
+
"note", help="write an agent-authored note into <task-root>/notes/")
|
|
174
|
+
p_note.add_argument("target", help="task root path or task-key")
|
|
175
|
+
_add_common(p_note)
|
|
176
|
+
p_note.add_argument("--kind", choices=NOTE_KINDS, required=True)
|
|
177
|
+
p_note.add_argument("--slug", required=True,
|
|
178
|
+
help="short topic slug for the filename")
|
|
179
|
+
p_note.add_argument("--purpose", required=True,
|
|
180
|
+
help="one line — what it is and which run/decision it feeds")
|
|
181
|
+
p_note.add_argument("--scope-note", required=True, dest="scope_note",
|
|
182
|
+
help="one line — what it is NOT (e.g. not a user decision)")
|
|
183
|
+
body_src = p_note.add_mutually_exclusive_group(required=True)
|
|
184
|
+
body_src.add_argument("--body", help="inline markdown body")
|
|
185
|
+
body_src.add_argument("--body-file", dest="body_file",
|
|
186
|
+
help="path to a file holding the markdown body")
|
|
187
|
+
|
|
118
188
|
args = parser.parse_args(argv)
|
|
119
189
|
task_root, project_root = resolve_task_root(
|
|
120
190
|
args.target, args.project_root, args.cwd)
|
|
121
191
|
|
|
122
192
|
if args.command == "assemble":
|
|
123
193
|
result = assemble_recap(task_root, project_root)
|
|
194
|
+
elif args.command == "note":
|
|
195
|
+
body = (Path(args.body_file).read_text(encoding="utf-8")
|
|
196
|
+
if args.body_file else args.body)
|
|
197
|
+
result = write_note(
|
|
198
|
+
task_root, project_root, kind=args.kind, slug=args.slug,
|
|
199
|
+
purpose=args.purpose, scope_note=args.scope_note, body=body,
|
|
200
|
+
now=dt.datetime.now(dt.timezone.utc))
|
|
124
201
|
else:
|
|
125
202
|
result = append_recap_entry(
|
|
126
203
|
task_root, project_root, kind=args.kind, mode=args.mode,
|
|
@@ -599,6 +599,10 @@ _ALTERNATIVES_CUE = "Alternatives:"
|
|
|
599
599
|
_PICK_ONE_ANNOTATION = re.compile(r"\s*\([^()]*택\s*\d+\s*\)\s*$")
|
|
600
600
|
|
|
601
601
|
|
|
602
|
+
def _lettered_option(letter: str, text: str) -> str:
|
|
603
|
+
return f"({letter}) {text}"
|
|
604
|
+
|
|
605
|
+
|
|
602
606
|
def _parse_expected_form_options(expected_form: str) -> list[tuple[str, str]]:
|
|
603
607
|
"""Parse the ``Expected form`` contract format
|
|
604
608
|
(``Recommended: <answer> — <rationale>; Alternatives: <options>``,
|
|
@@ -620,17 +624,24 @@ def _parse_expected_form_options(expected_form: str) -> list[tuple[str, str]]:
|
|
|
620
624
|
# The rationale follows " — "; the option keeps only the answer part.
|
|
621
625
|
answer = rec_body.split(" — ", 1)[0].strip(" .,;—-")
|
|
622
626
|
options: list[tuple[str, str]] = []
|
|
627
|
+
next_letter_idx = 0
|
|
623
628
|
if answer:
|
|
624
|
-
|
|
629
|
+
letter = _ENUM_LETTERS[next_letter_idx]
|
|
630
|
+
options.append(("recommended", _lettered_option(letter, answer)))
|
|
631
|
+
next_letter_idx += 1
|
|
625
632
|
enum_alts = _parse_enum_options(alt_body)
|
|
626
633
|
if enum_alts:
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
634
|
+
for _original_letter, text in enum_alts:
|
|
635
|
+
if next_letter_idx >= len(_ENUM_LETTERS):
|
|
636
|
+
break
|
|
637
|
+
letter = _ENUM_LETTERS[next_letter_idx]
|
|
638
|
+
options.append((letter, _lettered_option(letter, text)))
|
|
639
|
+
next_letter_idx += 1
|
|
630
640
|
else:
|
|
631
641
|
alt_text = alt_body.strip(" .,;—-")
|
|
632
|
-
if alt_text:
|
|
633
|
-
|
|
642
|
+
if alt_text and next_letter_idx < len(_ENUM_LETTERS):
|
|
643
|
+
letter = _ENUM_LETTERS[next_letter_idx]
|
|
644
|
+
options.append((letter, _lettered_option(letter, alt_text)))
|
|
634
645
|
return options
|
|
635
646
|
|
|
636
647
|
|
|
@@ -651,15 +662,19 @@ def _match_option_value(
|
|
|
651
662
|
cv = (current_value or "").strip()
|
|
652
663
|
if not cv:
|
|
653
664
|
return None
|
|
665
|
+
normalized_current = _OPTION_LABEL_PREFIX.sub("", cv).strip()
|
|
654
666
|
bodies = [
|
|
655
667
|
(value, _OPTION_LABEL_PREFIX.sub("", label).strip())
|
|
656
668
|
for value, label in opts
|
|
657
669
|
]
|
|
658
670
|
for value, body in bodies:
|
|
659
|
-
if body and (cv == body or cv == value):
|
|
671
|
+
if body and (normalized_current == body or cv == body or cv == value):
|
|
660
672
|
return value
|
|
661
673
|
for value, body in bodies:
|
|
662
|
-
if
|
|
674
|
+
if (
|
|
675
|
+
len(body) >= _MIN_OPTION_PREFIX_LEN
|
|
676
|
+
and (normalized_current.startswith(body) or cv.startswith(body))
|
|
677
|
+
):
|
|
663
678
|
return value
|
|
664
679
|
return None
|
|
665
680
|
|
|
@@ -18,7 +18,7 @@ Single read-side entry point for okstra runtime inspection plus the one status m
|
|
|
18
18
|
| `cost` | Estimate file/read context cost for a task bundle. |
|
|
19
19
|
| `errors` | Aggregate okstra-run error logs for a task into a timestamped markdown report; print a summary. |
|
|
20
20
|
| `error-zip` | Collect cross-project okstra error logs into an anonymized zip (report + raw) and summarize clusters. |
|
|
21
|
-
| `recap` | Summarize a task's run-to-run phase transitions, then answer free-form questions over its `.okstra` artifacts. Appends each summary/Q&A to `recap/recap-log.jsonl
|
|
21
|
+
| `recap` | Summarize a task's run-to-run phase transitions, then answer free-form questions over its `.okstra` artifacts. Appends each summary/Q&A to `recap/recap-log.jsonl`, and writes agent-authored notes to `notes/` for feeding into later runs. |
|
|
22
22
|
|
|
23
23
|
## Step 0: Preflight (shared)
|
|
24
24
|
|
|
@@ -690,7 +690,7 @@ stdout JSON 을 파싱해 보고:
|
|
|
690
690
|
|
|
691
691
|
Trigger phrases: "okstra recap", "recap", "작업 요약", "이 task 요약", "전후 요약", "이 작업 설명해줘", "task 질문".
|
|
692
692
|
|
|
693
|
-
task-id 하나에 쌓인 `.okstra` 산출물 위에서 (a) 처리 전/후 요약을 내고, (b) 그 작업에 대한 자유 질문에 답한다. 기본은 `.okstra/` 서브트리만 읽는다(artifact 모드). 사용자가 명시적으로 코드 변경까지 봐 달라고 요청할 때만 code 모드로 확장한다. 이 sub-command 는 `recap/recap-log.jsonl`
|
|
693
|
+
task-id 하나에 쌓인 `.okstra` 산출물 위에서 (a) 처리 전/후 요약을 내고, (b) 그 작업에 대한 자유 질문에 답한다. 기본은 `.okstra/` 서브트리만 읽는다(artifact 모드). 사용자가 명시적으로 코드 변경까지 봐 달라고 요청할 때만 code 모드로 확장한다. 이 sub-command 는 `recap/recap-log.jsonl` append 와 `notes/` 노트 작성(recap.5)만 수행하고 `task-manifest.json` / catalog / timeline 은 절대 mutate 하지 않는다.
|
|
694
694
|
|
|
695
695
|
### recap.1 — Resolve target
|
|
696
696
|
|
|
@@ -748,6 +748,41 @@ okstra recap record <resolved-target> --project-root <projectRoot> \
|
|
|
748
748
|
<이후 자유 질문을 받습니다. 코드 변경까지 보려면 "diff 까지 봐줘" 라고 요청하세요.>
|
|
749
749
|
```
|
|
750
750
|
|
|
751
|
+
### recap.5 — Agent 저작 노트 (`notes/`)
|
|
752
|
+
|
|
753
|
+
recap 중에 네가 **okstra task 를 위해** 만든 산출물 — 검증 증거, 설계·decision 초안, 분석 노트 — 이 앞으로의 run 에 근거로 쓰일 수 있고, 그 내용이 **렌더된 report 도 아니고 사용자 결정도 아닐 때**만 `notes/` 에 남긴다.
|
|
754
|
+
|
|
755
|
+
**소유권으로 라우팅한다.** 지금 쓰려는 것이 아래 어디에 해당하는지 먼저 판별한다:
|
|
756
|
+
|
|
757
|
+
| 내용 | 레인(경로) | 저작 주체 |
|
|
758
|
+
|---|---|---|
|
|
759
|
+
| final report 본문 | `runs/<type>/reports/*.md` (`*.data.json` 에서 렌더) | okstra 렌더러 — 절대 수기 편집 금지 |
|
|
760
|
+
| clarification 에 대한 사용자 답변 | `runs/<type>/user-responses/*.md` (`created-by: user`) | 사용자만 |
|
|
761
|
+
| 확정된 ADR | `.okstra/decisions/NNNN-*.md` | 승격·관리됨 |
|
|
762
|
+
| **Agent 증거 / 설계 초안 / 분석** | **`.okstra/tasks/<group>/<id>/notes/`** | 너(AI) |
|
|
763
|
+
|
|
764
|
+
렌더 산출물도, 사용자 결정도 아닌 **너 자신의 근거·초안**이면 `notes/` 로 간다. 노트는 손으로 찍지 말고 CLI 로 쓴다 — 경로·frontmatter·날짜를 코드가 보장하고, 다음 run 에 넘길 인자를 출력해 준다:
|
|
765
|
+
|
|
766
|
+
```bash
|
|
767
|
+
okstra recap note <resolved-target> --project-root <projectRoot> \
|
|
768
|
+
--kind <verification-evidence|decision-draft|analysis-note> \
|
|
769
|
+
--slug <short-topic-slug> \
|
|
770
|
+
--purpose "<한 줄 — 무엇이며 어느 run/decision 에 투입되는지>" \
|
|
771
|
+
--scope-note "<한 줄 — 무엇이 아닌지, 예: C-00x 에 대한 사용자 결정이 아님>" \
|
|
772
|
+
--body-file <노트 본문 마크다운 경로>
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
본문은 먼저 scratchpad 에 마크다운으로 쓴 뒤 `--body-file` 로 넘긴다(짧으면 `--body "<md>"` 인라인도 가능). CLI 는 `.okstra/tasks/<group>/<id>/notes/<slug>-<YYYY-MM-DD>.md` 를 생성하고 stdout JSON 으로 `notePath` 와 `clarificationResponseArg` 를 돌려준다.
|
|
776
|
+
|
|
777
|
+
**self-check 규칙 (validator 가 없으니 스스로 지킨다):**
|
|
778
|
+
|
|
779
|
+
1. **렌더된 report 를 수기 편집하지 않는다** (`runs/*/reports/` 아래 `*.md` / `*.data.json` / `*.html`). `data.json` 에서 재생성되므로 편집은 덮어써지고 SSOT 와 어긋난다. 발견 내용은 `notes/` 에 쓴다.
|
|
780
|
+
2. **`user-responses/` 파일을 작성하거나 `created-by: user` 를 달지 않는다.** 그건 사용자의 결정이며, 대신 쓰면 사용자가 내리지 않은 결정을 위조하는 것이다. 네 증거가 특정 답을 뒷받침하면 `notes/` 에 그렇게 적고 결정은 사용자에게 맡긴다.
|
|
781
|
+
3. **okstra 관리 디렉토리에 쓰지 않는다** (`runs/`, `instruction-set/`, `history/`, `recap/`, `.okstra/decisions/`). `notes/` 만이 agent 소유 레인이다. `recap/recap-log.jsonl` 은 예외적으로 쓰되 손으로 편집하지 말고 `okstra recap record` CLI 로만 남긴다.
|
|
782
|
+
4. **`notes/` 는 okstra 에 inert 하다** — 어떤 run 도 자동으로 읽지 않는다. 작성 후 CLI 가 출력한 `clarificationResponseArg`(예: `--clarification-response <notePath>`)를 사용자에게 그대로 알려, 다음 run 을 그 인자와 함께 실행해야만 반영된다는 것을 전한다.
|
|
783
|
+
|
|
784
|
+
**Guardrail:** `.okstra/` 는 gitignored 다 — `notes/` 는 로컬 scratch 로 취급하고 절대 `git add` 하지 않는다. 새 노트 생성은 되돌리기 쉬우니(파일 삭제), 생성물이나 사용자 소유 파일을 편집하는 것보다 항상 이쪽을 택한다.
|
|
785
|
+
|
|
751
786
|
---
|
|
752
787
|
|
|
753
788
|
## Output Rules (shared)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: okstra-pr-gen
|
|
3
|
+
description: Use when the user wants to register a PR body template or generate a PR description from a branch diff using a stored (or default) template. Manages templates under ~/.okstra/template/pr and drives okstra pr {template,branches,gen}. Trigger words include "okstra pr", "register PR template", "generate PR body", "generate PR message", "generate PR", "pr gen", and "register template".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# okstra-pr-gen
|
|
7
|
+
|
|
8
|
+
Register PR body templates and generate PR descriptions. Templates live in the
|
|
9
|
+
user home at `~/.okstra/template/pr/`. This skill is **global** — it does not
|
|
10
|
+
require `<PROJECT_ROOT>/.okstra/project.json`. PR generation additionally
|
|
11
|
+
requires the current directory to be a git repository.
|
|
12
|
+
|
|
13
|
+
## Step 0: Check CLI availability
|
|
14
|
+
|
|
15
|
+
Run as a separate Bash tool call with a literal leading token:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
okstra pr --help
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
If `okstra` is not on PATH, tell the user:
|
|
22
|
+
|
|
23
|
+
`okstra not installed — run npx okstra@latest install once, then retry this skill.`
|
|
24
|
+
|
|
25
|
+
**Bash invocation rule:** every Bash command begins with the literal token
|
|
26
|
+
`okstra` and passes literal argument values. Do not introduce shell variables,
|
|
27
|
+
`$(...)`, leading `VAR=`, or wrap in `if`/`eval`/`||`/`&&` — that defeats the
|
|
28
|
+
`Bash(okstra:*)` permission match. Paste literal values emitted by a prior call.
|
|
29
|
+
|
|
30
|
+
## Step 1: Pick the mode (always first)
|
|
31
|
+
|
|
32
|
+
Present a 3-option picker (`AskUserQuestion`):
|
|
33
|
+
|
|
34
|
+
1. `Generate PR` — generate a PR body from a branch diff.
|
|
35
|
+
2. `Register template` — save a new PR body template.
|
|
36
|
+
3. `Enter manually` — always the last option (okstra picker convention).
|
|
37
|
+
|
|
38
|
+
## Mode A — Generate PR
|
|
39
|
+
|
|
40
|
+
### A1. Pick a template
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
okstra pr template list --json
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- If `templates` is non-empty, present a picker: 1–2 recommended templates from
|
|
47
|
+
the list, plus `Default template` (the bundled default), plus `Enter manually`
|
|
48
|
+
last.
|
|
49
|
+
- If empty, tell the user the bundled default template will be used.
|
|
50
|
+
|
|
51
|
+
Carry the chosen template name as `<template>` (`default` for the bundled one).
|
|
52
|
+
|
|
53
|
+
### A2. Pick the base branch
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
okstra pr branches --json
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Present a 3-option base picker from `recommended` (top entries) plus `Enter manually`
|
|
60
|
+
last. Carry the choice as `<base>`.
|
|
61
|
+
|
|
62
|
+
### A3. Build the generation bundle and fill the template
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
okstra pr gen --base <base> --template <template> --json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Parse `{base, currentBranch, templateName, template, commits, diffStat}`. Then
|
|
69
|
+
read the full diff honestly — it is the source of truth:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
git diff <base>...HEAD
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
For a large diff, read it in sections. Fill the `template` placeholders from the
|
|
76
|
+
diff and commits: describe only what actually changed. Mark checklist boxes
|
|
77
|
+
`[x]` only when the diff supports them (tests touched → tests box, docs touched →
|
|
78
|
+
docs box). If `commits`/`diffStat` are empty, tell the user there is nothing to
|
|
79
|
+
describe and stop. **Never** append AI trailers/footers.
|
|
80
|
+
|
|
81
|
+
### A4. Output and offer to create the PR
|
|
82
|
+
|
|
83
|
+
Print the filled PR body to chat as a single fenced markdown block. Then ask the
|
|
84
|
+
user whether to open a real PR. Only on an explicit yes:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
gh pr create --base <base> --title "<title>" --body-file <path-to-body>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Write the body to a temp file first and pass `--body-file`. If `gh` is missing
|
|
91
|
+
or not authenticated (`gh auth status` fails), leave the text in chat and tell
|
|
92
|
+
the user to create the PR manually. Do not push or create the PR without the
|
|
93
|
+
user's explicit confirmation.
|
|
94
|
+
|
|
95
|
+
## Mode B — Register template
|
|
96
|
+
|
|
97
|
+
1. Ask for a template name (`AskUserQuestion`, free text). It must match
|
|
98
|
+
`^[A-Za-z0-9._-]+$`; re-ask on invalid input.
|
|
99
|
+
2. Ask for the template body — either pasted text or an absolute file path.
|
|
100
|
+
3. Save it:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
okstra pr template add --name <name> --file <abs-path>
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
or, for pasted text, `--content "<body>"`. Add `--yes` only if the user
|
|
107
|
+
confirmed overwriting an existing template of the same name. Report the
|
|
108
|
+
saved path (`saved: ...`) back to the user.
|
package/src/cli-registry.mjs
CHANGED
|
@@ -329,6 +329,16 @@ export const COMMAND_REGISTRY = [
|
|
|
329
329
|
"~/.okstra/memory-book.",
|
|
330
330
|
],
|
|
331
331
|
},
|
|
332
|
+
{
|
|
333
|
+
name: "pr",
|
|
334
|
+
module: "./commands/pr/pr.mjs",
|
|
335
|
+
export: "run",
|
|
336
|
+
category: "introspection",
|
|
337
|
+
summary: [
|
|
338
|
+
"Manage PR body templates and prepare PR generation",
|
|
339
|
+
"bundles under ~/.okstra/template/pr.",
|
|
340
|
+
],
|
|
341
|
+
},
|
|
332
342
|
];
|
|
333
343
|
|
|
334
344
|
export const COMMANDS = new Map(
|
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { runPythonModule } from "../../lib/python-helper.mjs";
|
|
2
2
|
|
|
3
|
-
const USAGE = `okstra recap — assemble a task's run-to-run phase recap,
|
|
3
|
+
const USAGE = `okstra recap — assemble a task's run-to-run phase recap, append a recap log entry, or write an agent note
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
6
6
|
okstra recap assemble <task-root|task-key> [--project-root <dir>] [--cwd <dir>]
|
|
7
7
|
okstra recap record <task-root|task-key> --kind <summary|qa> --mode <artifact|code> \\
|
|
8
8
|
[--question <text>] --answer <text> [--citation <path:line> ...]
|
|
9
|
+
okstra recap note <task-root|task-key> \\
|
|
10
|
+
--kind <verification-evidence|decision-draft|analysis-note> --slug <topic> \\
|
|
11
|
+
--purpose <text> --scope-note <text> (--body <markdown> | --body-file <path>)
|
|
9
12
|
|
|
10
13
|
\`assemble\` is read-only and prints a JSON summary of phase transitions across runs.
|
|
11
14
|
\`record\` appends one line to <task-root>/recap/recap-log.jsonl; it never mutates
|
|
12
15
|
other task artifacts.
|
|
16
|
+
\`note\` writes an agent-authored markdown note to <task-root>/notes/ and prints its
|
|
17
|
+
path plus the \`--clarification-response\` argument for feeding it into a later run.
|
|
13
18
|
`;
|
|
14
19
|
|
|
15
20
|
export async function run(args) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## **Please check if the PR fulfills these requirements**
|
|
2
|
+
- [ ] Commits have a single intent
|
|
3
|
+
- [ ] Tests for the changes have been added (for bug fixes / features)
|
|
4
|
+
- [ ] I reviewed my own code
|
|
5
|
+
- [ ] I tested the changes (if not, explain why in the "Other information" section)
|
|
6
|
+
- [ ] Docs have been added / updated
|
|
7
|
+
|
|
8
|
+
## **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
|
|
9
|
+
<pr-kind>
|
|
10
|
+
|
|
11
|
+
## **What is the current behavior?** (You can also link to an open issue here)
|
|
12
|
+
<current-behavior>
|
|
13
|
+
|
|
14
|
+
## **What is the new behavior (if this is a feature change)?**
|
|
15
|
+
<new-behavior>
|
|
16
|
+
|
|
17
|
+
## **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?)
|
|
18
|
+
<breaking-change>
|
|
19
|
+
|
|
20
|
+
## **Other information**:
|
|
21
|
+
<other-information>
|