okstra 0.98.1 → 0.98.2
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/for-ai/README.md +55 -0
- package/docs/for-ai/skills/okstra-brief.md +240 -0
- package/docs/for-ai/skills/okstra-container.md +161 -0
- package/docs/for-ai/skills/okstra-inspect.md +260 -0
- package/docs/for-ai/skills/okstra-memory.md +127 -0
- package/docs/for-ai/skills/okstra-run.md +229 -0
- package/docs/for-ai/skills/okstra-schedule.md +321 -0
- package/docs/for-ai/skills/okstra-setup.md +140 -0
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/skills/okstra-brief/SKILL.md +24 -13
- package/runtime/validators/validate-brief.py +36 -2
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# okstra-schedule AI Manual
|
|
2
|
+
|
|
3
|
+
## 원천
|
|
4
|
+
|
|
5
|
+
- 스킬 원문: [`skills/okstra-schedule/SKILL.md`](../../../skills/okstra-schedule/SKILL.md)
|
|
6
|
+
- schedule 템플릿: [`templates/reports/schedule.template.md`](../../../templates/reports/schedule.template.md)
|
|
7
|
+
- schedule validator: [`validators/validate-schedule.py`](../../../validators/validate-schedule.py)
|
|
8
|
+
- workStatus inference 참조: [`skills/okstra-inspect/SKILL.md`](../../../skills/okstra-inspect/SKILL.md)
|
|
9
|
+
|
|
10
|
+
## 목적
|
|
11
|
+
|
|
12
|
+
`okstra-schedule`은 task-group 안의 non-done task들을 모아 클라이언트용 작업 일정 Markdown을 만든다. 실행 task를 시작하는 스킬이 아니며, 단일 task 분석도 아니다.
|
|
13
|
+
|
|
14
|
+
출력 위치:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
<PROJECT_ROOT>/.okstra/tasks/<task-group-segment>/schedule/<task-group-segment>-plan-<YYYY-MM-DD_HH-MM-SS>.md
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 사용 조건
|
|
21
|
+
|
|
22
|
+
사용한다:
|
|
23
|
+
|
|
24
|
+
- 사용자가 task-group 전체의 "일정", "schedule", "작업 계획표"를 요청한다.
|
|
25
|
+
- `.okstra/discovery/task-catalog.json`에 해당 task-group이 있고, `done`이 아닌 task가 하나 이상 있다.
|
|
26
|
+
|
|
27
|
+
사용하지 않는다:
|
|
28
|
+
|
|
29
|
+
- 단일 task 상태/분석: `okstra-inspect status`
|
|
30
|
+
- 실제 phase 실행: `okstra-run`
|
|
31
|
+
- 이미 완료된 task-group: 파일을 만들지 않고 모든 task가 done이라고 말한다.
|
|
32
|
+
|
|
33
|
+
## Preflight
|
|
34
|
+
|
|
35
|
+
각각 별도 Bash 호출:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
okstra ensure-installed --runtime claude-code
|
|
39
|
+
okstra check-project --json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
runtime이나 project setup이 없으면 `/okstra-setup` 안내 후 멈춘다.
|
|
43
|
+
|
|
44
|
+
## model gate
|
|
45
|
+
|
|
46
|
+
스킬 frontmatter는 `model: opus`다. 활성 모델이 Opus-class 또는 Fable-class가 아니면 사용자에게 전환을 권한다. 사용자가 lower model로 진행하겠다고 명시하면 진행하되, 생성 schedule 상단에 reduced-quality warning을 넣는다.
|
|
47
|
+
|
|
48
|
+
## task-group 해석
|
|
49
|
+
|
|
50
|
+
1. `.okstra/discovery/task-catalog.json`을 읽는다.
|
|
51
|
+
2. 사용자 입력 task-group을 lowercase하고 `[a-z0-9]` 외 문자를 제거한다.
|
|
52
|
+
3. catalog entry의 `taskGroupPathSegment`에도 같은 변환을 적용해 비교한다.
|
|
53
|
+
4. raw `taskGroup` fallback은 쓰지 않는다.
|
|
54
|
+
5. 매칭된 각 task의 `task-manifest.json`을 직접 읽는다. catalog는 stale일 수 있고 manifest가 authoritative다.
|
|
55
|
+
|
|
56
|
+
## workStatus filter
|
|
57
|
+
|
|
58
|
+
각 manifest에서 workStatus를 확인한다. 없거나 비어 있으면 `okstra-inspect`의 `status.4` inference table을 적용한다.
|
|
59
|
+
|
|
60
|
+
제외:
|
|
61
|
+
|
|
62
|
+
- explicit `done`
|
|
63
|
+
- inferred `done`
|
|
64
|
+
|
|
65
|
+
포함:
|
|
66
|
+
|
|
67
|
+
- `todo`
|
|
68
|
+
- `in-progress`
|
|
69
|
+
- `blocked`
|
|
70
|
+
- `phase-done`
|
|
71
|
+
- 그 밖의 non-done inferred/display 상태
|
|
72
|
+
|
|
73
|
+
필터 후 0개면 schedule file을 만들지 않는다.
|
|
74
|
+
|
|
75
|
+
## per-task data extraction
|
|
76
|
+
|
|
77
|
+
manifest에서 읽는다:
|
|
78
|
+
|
|
79
|
+
- `taskId`, `taskGroup`, `taskKey`
|
|
80
|
+
- `workCategory`
|
|
81
|
+
- `workflow.currentPhase`
|
|
82
|
+
- `workflow.currentPhaseState`
|
|
83
|
+
- `taskType`
|
|
84
|
+
- `workStatus`
|
|
85
|
+
- `latestReportPath`
|
|
86
|
+
|
|
87
|
+
report에서 파싱한다:
|
|
88
|
+
|
|
89
|
+
- Title / Problem statement
|
|
90
|
+
- Solution / Architecture
|
|
91
|
+
- Work Breakdown
|
|
92
|
+
- Verification Commands
|
|
93
|
+
- Rollback strategy
|
|
94
|
+
- Effort, Risk, Priority, Scope, Repos
|
|
95
|
+
|
|
96
|
+
report가 없거나 manifest pointer가 stale이면 fallback:
|
|
97
|
+
|
|
98
|
+
1. `taskRootPath`를 기준으로 `runs/<taskType>/reports/final-report-*.md`에서 mtime이 최신인 파일을 찾는다.
|
|
99
|
+
2. 없으면 `runs/*/reports/final-report-*.md`도 본다.
|
|
100
|
+
3. 찾으면 parse하고 schedule task block에 fallback note를 넣는다.
|
|
101
|
+
4. 끝까지 없으면 `[NEEDS-OKSTRA-RUN]`으로 표시하고 manifest metadata만 사용한다.
|
|
102
|
+
|
|
103
|
+
특정 section parse 실패는 `[PARSE-ERROR: <section>]`으로 표시하고 계속한다.
|
|
104
|
+
|
|
105
|
+
## client-facing audience rule
|
|
106
|
+
|
|
107
|
+
schedule은 클라이언트용 작업 계획이다. 내부 report의 approval, blocking decision, 사용자 확인 필요 항목을 schedule에 노출하지 않는다.
|
|
108
|
+
|
|
109
|
+
출력에서 제거한다:
|
|
110
|
+
|
|
111
|
+
- permission/authority 확인 step
|
|
112
|
+
- approval waiting buffer
|
|
113
|
+
- stakeholder coordination
|
|
114
|
+
- 법무/조직 sign-off
|
|
115
|
+
- `Consolidated User Decision Checklist`
|
|
116
|
+
- `#### 사용자 확인 필요 항목`
|
|
117
|
+
- `Done`, `Ready?`, `Blocking Decisions` column
|
|
118
|
+
- checkbox list/cell
|
|
119
|
+
|
|
120
|
+
effort와 Gantt는 engineering duration만 반영한다.
|
|
121
|
+
|
|
122
|
+
## phase classification
|
|
123
|
+
|
|
124
|
+
기본 mapping:
|
|
125
|
+
|
|
126
|
+
| workCategory | phase |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `bugfix` | High/Med-High risk이면 Phase 1, 아니면 Phase 2 |
|
|
129
|
+
| `feature` | Phase 2 |
|
|
130
|
+
| `improvement` | Phase 2 |
|
|
131
|
+
| `refactor` | Phase 3 |
|
|
132
|
+
| `ops` | Phase 3 |
|
|
133
|
+
| `docs` / `doc` | Phase 2 |
|
|
134
|
+
| `unknown` 또는 미정의 | Phase 2, rationale note 추가 |
|
|
135
|
+
|
|
136
|
+
priority override:
|
|
137
|
+
|
|
138
|
+
- `P0`: Phase 1
|
|
139
|
+
- `P1`, `P2`: Phase 2
|
|
140
|
+
- `P3`: Phase 3
|
|
141
|
+
|
|
142
|
+
ambiguous하면 가장 가까운 phase에 두고 rationale을 phase section 상단에 쓴다. 이 rationale은 현재 validator gap으로 문서에서 책임진다.
|
|
143
|
+
|
|
144
|
+
## section contract
|
|
145
|
+
|
|
146
|
+
템플릿의 heading 순서와 spelling을 따른다. `validate-schedule.py`가 section order, title suffix, metadata, field labels, enum, Gantt axis 등을 검사한다.
|
|
147
|
+
|
|
148
|
+
top-level 계약 sections:
|
|
149
|
+
|
|
150
|
+
1. `## At a Glance`
|
|
151
|
+
2. `## Executive Summary`
|
|
152
|
+
3. `## Task Dependency Graph`
|
|
153
|
+
4. `## Phase 1: Critical Fixes`
|
|
154
|
+
5. `## Phase 2: Enhancements`
|
|
155
|
+
6. `## Phase 3: Architecture`
|
|
156
|
+
7. `## Execution Priority Matrix`
|
|
157
|
+
8. `## Cross-Task Dependencies & Shared Concerns`
|
|
158
|
+
9. `## Risk Mitigation Strategy`
|
|
159
|
+
10. `## Recommended Immediate Actions`
|
|
160
|
+
|
|
161
|
+
optional sections:
|
|
162
|
+
|
|
163
|
+
- `## Gantt Chart`: `Task Dependency Graph`와 `Phase 1` 사이.
|
|
164
|
+
- `## Glossary`: 마지막 section. opaque code가 본문에 남을 때만 사용.
|
|
165
|
+
|
|
166
|
+
phase에 task가 없어도 heading을 유지하고 `_없음_`을 쓴다.
|
|
167
|
+
|
|
168
|
+
## top header
|
|
169
|
+
|
|
170
|
+
형태:
|
|
171
|
+
|
|
172
|
+
```markdown
|
|
173
|
+
# <Title> — Work Schedule
|
|
174
|
+
|
|
175
|
+
> Generated: <YYYY-MM-DD HH:MM> | Project: <project-id> | Task Group: <task-group>
|
|
176
|
+
> Source: okstra <mode> (<N> tasks included, <M> done excluded)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
title suffix `— Work Schedule`은 validator가 검사한다.
|
|
180
|
+
|
|
181
|
+
## At a Glance
|
|
182
|
+
|
|
183
|
+
다음 totals line이 정확히 있어야 한다.
|
|
184
|
+
|
|
185
|
+
```markdown
|
|
186
|
+
**총 <N>개 task / 예상 소요: <X.X> ~ <Y.Y> days (Effort 합산)**
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Effort-to-day mapping은 template의 `### Effort Sizing 기준` table이 SSOT다. lower/upper 합산으로 total day range를 만든다.
|
|
190
|
+
|
|
191
|
+
enum 값:
|
|
192
|
+
|
|
193
|
+
| Field | 값 |
|
|
194
|
+
|---|---|
|
|
195
|
+
| Effort | `S`, `M`, `L`, `XL`, `XXL` |
|
|
196
|
+
| Priority | `P0`, `P1`, `P2`, `P3` |
|
|
197
|
+
| Risk | `Very Low`, `Low`, `Medium`, `Med-High`, `High` |
|
|
198
|
+
| Phase | `1`, `2`, `3` |
|
|
199
|
+
|
|
200
|
+
`Med-High`가 canonical이다.
|
|
201
|
+
|
|
202
|
+
## per-task block
|
|
203
|
+
|
|
204
|
+
모든 task block은 `| Item | Detail |` table을 가진다. field row 순서:
|
|
205
|
+
|
|
206
|
+
1. `**Category**`
|
|
207
|
+
2. `**Priority**`
|
|
208
|
+
3. `**Effort**`
|
|
209
|
+
4. `**Status**`
|
|
210
|
+
5. `**Risk**`
|
|
211
|
+
6. `**Scope**`
|
|
212
|
+
7. `**Repo**`
|
|
213
|
+
|
|
214
|
+
그 뒤 subsection 순서:
|
|
215
|
+
|
|
216
|
+
1. `**Problem**:`
|
|
217
|
+
2. `**Solution**:`
|
|
218
|
+
3. `**Work Breakdown**:`
|
|
219
|
+
4. `**Verification Commands**:`
|
|
220
|
+
5. `**Rollback**:`
|
|
221
|
+
|
|
222
|
+
`[NEEDS-OKSTRA-RUN]` 또는 `[PARSE-ERROR: <section>]` task는 가능한 field만 채우되 marker를 task heading 바로 아래 둔다.
|
|
223
|
+
|
|
224
|
+
## Task Dependency Graph
|
|
225
|
+
|
|
226
|
+
dependency가 없으면 literal:
|
|
227
|
+
|
|
228
|
+
```markdown
|
|
229
|
+
_의존 정보 없음_
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
dependency가 있으면 plain fenced block을 사용한다. language tag를 붙이지 않는다.
|
|
233
|
+
|
|
234
|
+
````
|
|
235
|
+
```
|
|
236
|
+
DEV-1 -> DEV-2, DEV-3
|
|
237
|
+
DEV-2 -> DEV-4
|
|
238
|
+
```
|
|
239
|
+
````
|
|
240
|
+
|
|
241
|
+
arrow는 ASCII `->`만 사용한다.
|
|
242
|
+
|
|
243
|
+
## Gantt Chart
|
|
244
|
+
|
|
245
|
+
기본은 렌더한다. day signal이 하나라도 있으면 rough estimate라도 chart를 만든다.
|
|
246
|
+
|
|
247
|
+
렌더 조건 예:
|
|
248
|
+
|
|
249
|
+
- 2개 이상 task에 effort sizing이 있다.
|
|
250
|
+
- 1개 task라도 effort range가 있다.
|
|
251
|
+
- source report에 Part/Phase/Step decomposition이 있다.
|
|
252
|
+
- total effort가 3 days 이상이다.
|
|
253
|
+
|
|
254
|
+
skip은 모든 task가 XXL이고 decomposition이 없거나, 모든 task에 effort와 decomposition이 없을 때만 한다. skip하면 `Gantt Chart` 위치에 다음 blockquote를 둔다.
|
|
255
|
+
|
|
256
|
+
```markdown
|
|
257
|
+
> _Gantt Chart 생략: <actual data를 근거로 한 이유>._
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Gantt는 plain fenced ASCII다. Mermaid, PlantUML, Graphviz, date axis를 쓰지 않는다.
|
|
261
|
+
|
|
262
|
+
````
|
|
263
|
+
```
|
|
264
|
+
Day: 1 5 10 15 20
|
|
265
|
+
| | | | |
|
|
266
|
+
Phase 1
|
|
267
|
+
DEV-1 (M) ██████ ! crit
|
|
268
|
+
Phase 2
|
|
269
|
+
DEV-2 (L) ██████░░ est
|
|
270
|
+
```
|
|
271
|
+
````
|
|
272
|
+
|
|
273
|
+
axis는 relative day count만 사용한다. calendar date, weekday, "오늘 + N"을 넣지 않는다.
|
|
274
|
+
|
|
275
|
+
## opaque code 처리
|
|
276
|
+
|
|
277
|
+
내부 report code(`FC-5`, `UC-3`, `M2` 등)는 그대로 노출하지 않는다.
|
|
278
|
+
|
|
279
|
+
선택 A: 짧은 설명으로 inline replacement.
|
|
280
|
+
|
|
281
|
+
선택 B: 반복되는 code가 많으면 마지막 `## Glossary`에 모든 code를 해석한다.
|
|
282
|
+
|
|
283
|
+
decision-item letter code(`A1`, `B2`, `C3`, `D4`)는 schedule에 넣지 않는다.
|
|
284
|
+
|
|
285
|
+
## validation
|
|
286
|
+
|
|
287
|
+
작성 후 완료 메시지 전에 다시 읽고 validator를 실행한다.
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
python3 ~/.okstra/lib/validators/validate-schedule.py <output-path>
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
설치본 validator가 없으면 repo validator를 쓴다.
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
python3 validators/validate-schedule.py <output-path>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
실패하면 파일을 고치고 재검증한다. validator가 없을 때만 manual checklist로 대체한다.
|
|
300
|
+
|
|
301
|
+
## 완료 메시지
|
|
302
|
+
|
|
303
|
+
```text
|
|
304
|
+
Schedule 생성 완료: <relative-path>
|
|
305
|
+
- 포함 task: N개
|
|
306
|
+
- 제외(done) task: M개
|
|
307
|
+
- 예상 소요: X.X ~ Y.Y days (Effort 합산)
|
|
308
|
+
- 모드: lightweight
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## 금지 패턴
|
|
312
|
+
|
|
313
|
+
- heading을 한국어/프랑스어 등으로 번역하기.
|
|
314
|
+
- `## Cumulative Timeline` 추가.
|
|
315
|
+
- `## Effort-to-Day 매핑` 같은 extra top-level section 추가.
|
|
316
|
+
- Gantt를 mermaid로 렌더하기.
|
|
317
|
+
- Gantt axis에 calendar date를 넣기.
|
|
318
|
+
- checkbox, Done, Ready, Blocking Decisions column 만들기.
|
|
319
|
+
- internal approval/blocker/user-decision 항목을 클라이언트 schedule에 노출하기.
|
|
320
|
+
- report가 없다고 task를 제외하기. `[NEEDS-OKSTRA-RUN]`으로 포함한다.
|
|
321
|
+
- timestamp collision 시 기존 파일을 덮어쓰기. `-2`, `-3` suffix를 붙인다.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# okstra-setup AI Manual
|
|
2
|
+
|
|
3
|
+
## 원천
|
|
4
|
+
|
|
5
|
+
- 스킬 원문: [`skills/okstra-setup/SKILL.md`](../../../skills/okstra-setup/SKILL.md)
|
|
6
|
+
- CLI registry: [`src/cli-registry.mjs`](../../../src/cli-registry.mjs)
|
|
7
|
+
- project 등록 구현: `src/commands/lifecycle/setup.mjs`
|
|
8
|
+
- install/ensure-installed 구현: `src/commands/lifecycle/install.mjs`
|
|
9
|
+
|
|
10
|
+
## 목적
|
|
11
|
+
|
|
12
|
+
`okstra-setup`은 두 가지를 처리한다.
|
|
13
|
+
|
|
14
|
+
1. 머신 단위 runtime 설치: `~/.okstra/`, `~/.claude/skills/`, `~/.claude/agents/`
|
|
15
|
+
2. 프로젝트 단위 등록: `<PROJECT_ROOT>/.okstra/project.json`
|
|
16
|
+
|
|
17
|
+
일상적인 task 실행 스킬이 아니다. task가 이미 준비된 상태면 `okstra-run` 또는 `okstra-inspect`로 라우팅한다.
|
|
18
|
+
|
|
19
|
+
## 사용 조건
|
|
20
|
+
|
|
21
|
+
사용한다:
|
|
22
|
+
|
|
23
|
+
- 사용자가 "okstra setup", "setup okstra", "initialize okstra", "okstra init", "처음 설정"을 요청한다.
|
|
24
|
+
- `~/.okstra/version`이 없거나 오래된 것으로 보인다.
|
|
25
|
+
- 현재 프로젝트에 `.okstra/project.json`이 없다.
|
|
26
|
+
|
|
27
|
+
사용하지 않는다:
|
|
28
|
+
|
|
29
|
+
- task run을 시작하려는 경우. 이때는 `okstra-run`.
|
|
30
|
+
- status/history/report를 보려는 경우. 이때는 `okstra-inspect`.
|
|
31
|
+
- 이미 `.okstra/project.json`이 있고 day-to-day 사용만 필요한 경우.
|
|
32
|
+
|
|
33
|
+
## 실행 전 확인
|
|
34
|
+
|
|
35
|
+
사용자에게 Node 18+, Python 3.10+가 필요하다고 알린다. 현재 working directory가 okstra 메타데이터를 둘 프로젝트 안인지 불명확하면, 프로젝트 루트를 먼저 확인한다.
|
|
36
|
+
|
|
37
|
+
설치 명령:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx -y okstra@latest install --runtime claude-code
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
이 명령은 idempotent로 취급한다. 이미 설치되어 있어도 다시 실행해 runtime, skill, agent payload를 현재 package 버전과 맞춘다. 실패하면 stderr를 그대로 사용자에게 보여준다. legacy `okstra-install.sh`로 우회하지 않는다.
|
|
44
|
+
|
|
45
|
+
## 명령 호출 규칙
|
|
46
|
+
|
|
47
|
+
`okstra install` 이후에는 후속 명령이 모두 `okstra` literal token으로 시작해야 한다.
|
|
48
|
+
|
|
49
|
+
허용되는 형태:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
okstra check-project --json
|
|
53
|
+
okstra setup --yes --project-root /abs/project --project-id my-project
|
|
54
|
+
okstra doctor --runtime claude-code
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
피해야 하는 형태:
|
|
58
|
+
|
|
59
|
+
- `export PYTHONPATH=...`
|
|
60
|
+
- `eval "$(okstra paths --shell)"`
|
|
61
|
+
- `$PROJECT_ROOT` 같은 shell 변수
|
|
62
|
+
- `$(...)` command substitution
|
|
63
|
+
- `if`, `&&`, `||`로 감싼 okstra 호출
|
|
64
|
+
|
|
65
|
+
`okstra <subcmd>`가 자체적으로 Python path를 부트스트랩하므로 `okstra paths --shell`은 이 스킬에서 쓰지 않는다. 경로가 필요하면 `okstra paths --json`을 별도 호출로 실행하고 JSON 값을 읽는다.
|
|
66
|
+
|
|
67
|
+
## 프로젝트 루트 해석
|
|
68
|
+
|
|
69
|
+
먼저 실행:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
okstra check-project --json
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
결과 처리:
|
|
76
|
+
|
|
77
|
+
- `ok: true`: 이미 등록된 프로젝트다. `projectRoot`, `projectJsonPath`, `projectId`를 사용자에게 보여주고 유지 여부를 확인한다.
|
|
78
|
+
- `ok: false`, `stage: "resolve"`: 사용자에게 절대 project root를 받아 `okstra check-project --cwd /abs/path --json`을 다시 실행한다.
|
|
79
|
+
- `ok: false`, `stage: "project_json_missing"`: 정상 생성 경로로 진행한다.
|
|
80
|
+
- 그 외 실패 stage: JSON의 `reason`을 그대로 보여주고, 원문이 지시하는 recovery를 따른다.
|
|
81
|
+
|
|
82
|
+
## project.json 생성 또는 유지
|
|
83
|
+
|
|
84
|
+
`<PROJECT_ROOT>/.okstra/project.json`이 있으면 `projectId`와 `projectRoot`를 보여주고 유지/덮어쓰기 여부를 확인한다. 기본은 유지다. 기존 `projectId`를 바꾸려면 파일 삭제가 필요하므로 자동으로 덮어쓰지 않는다.
|
|
85
|
+
|
|
86
|
+
파일이 없으면 project id를 물어본다. 답변은 비어 있지 않고 alphanumeric 문자를 하나 이상 포함해야 한다. 빈 값으로 `okstra setup --yes`를 호출하면 실패하므로 다시 묻는다.
|
|
87
|
+
|
|
88
|
+
생성 명령:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
okstra setup --yes --project-root /abs/project --project-id my-project
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 선택 설정
|
|
95
|
+
|
|
96
|
+
원문상 Step 4.5-4.9는 사용자가 명시적으로 원할 때만 수행하는 선택 설정이다. 기본값으로 충분하면 건너뛰고 doctor로 간다.
|
|
97
|
+
|
|
98
|
+
선택 설정:
|
|
99
|
+
|
|
100
|
+
- `worktreeSyncDirs`: task worktree에 symlink할 프로젝트 상대 디렉터리 목록. 기본은 `.project-docs`, `.scratch`, `graphify-out`, `.claude`.
|
|
101
|
+
- `qaCommands`: implementation verifier가 실행할 check-only lint/format/typecheck/test 명령.
|
|
102
|
+
- `qaEnv`: Tier 3 conformance script가 사용할 replica/test DB, local app URL, env file, surface patterns.
|
|
103
|
+
- PR body template: `okstra config set pr-template-path "<path>" --scope project|global`
|
|
104
|
+
- final report language: `okstra config set report-language <en|ko|auto> --scope project`
|
|
105
|
+
|
|
106
|
+
`qaCommands.cmd`는 mutation을 암시하는 token을 담으면 verifier가 거부한다. deny-list의 실제 권위는 `scripts/okstra_ctl/qa_commands.py`다.
|
|
107
|
+
|
|
108
|
+
## 자동 Claude 설정 symlink
|
|
109
|
+
|
|
110
|
+
`okstra setup`은 `<PROJECT_ROOT>/.claude/settings.local.json`을 `~/.okstra/templates/settings.local.json` symlink로 provision한다. 기존 regular file이 있으면 `.bak.<timestamp>`로 백업한 뒤 symlink를 둔다. 실패 메시지가 나오면 사용자가 기존 project-specific rule을 수동 병합해야 한다.
|
|
111
|
+
|
|
112
|
+
## 검증
|
|
113
|
+
|
|
114
|
+
마지막에 실행:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
okstra doctor --runtime claude-code
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
모든 check가 OK면 setup 완료로 보고한다. 실패가 있으면 출력 내용을 그대로 보여주고, 재설치/스킵 여부는 사용자가 결정하게 한다.
|
|
121
|
+
|
|
122
|
+
## 완료 메시지
|
|
123
|
+
|
|
124
|
+
짧게 다음 정보를 포함한다.
|
|
125
|
+
|
|
126
|
+
- runtime 위치: `~/.okstra`
|
|
127
|
+
- project metadata: `<PROJECT_ROOT>/.okstra/project.json`
|
|
128
|
+
- `projectId`
|
|
129
|
+
- 다음 단계: `/okstra-run`
|
|
130
|
+
|
|
131
|
+
## 흔한 실패 처리
|
|
132
|
+
|
|
133
|
+
| 증상 | 처리 |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `command not found: npx` | Node 18+ 설치 안내 |
|
|
136
|
+
| `--project-id is required` | project id를 다시 물어보고 non-empty 값으로 재실행 |
|
|
137
|
+
| `projectId mismatch` | 어떤 id가 canonical인지 사용자에게 확인. 자동 삭제하지 않음 |
|
|
138
|
+
| `.okstra/` 쓰기 EACCES | ownership/writability 문제를 설명 |
|
|
139
|
+
| `.claude/settings.local.json` symlink warning | 백업 파일과 symlink 상태를 사용자에게 보여주고 수동 병합 안내 |
|
|
140
|
+
|
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -393,9 +393,12 @@ Step 0):
|
|
|
393
393
|
okstra task-list
|
|
394
394
|
```
|
|
395
395
|
|
|
396
|
-
Parse the JSON `tasks[]` array
|
|
397
|
-
|
|
398
|
-
|
|
396
|
+
Parse the JSON `tasks[]` array. The `taskGroup` field is a **raw display
|
|
397
|
+
name, not a slug** — `slugify` each value (lowercase + non-alphanumeric →
|
|
398
|
+
`-`) and **dedupe by slug** so case/format variants collapse (e.g.
|
|
399
|
+
`uploadFont` and `uploadfont` are one group, not two). Order the distinct
|
|
400
|
+
slugs by their most-recent `updatedAt`. Take the **2 most-recent** distinct
|
|
401
|
+
slugs as recommendations and show them in slug form.
|
|
399
402
|
|
|
400
403
|
`AskUserQuestion` (single-select):
|
|
401
404
|
|
|
@@ -413,8 +416,14 @@ picker and ask one free-text question:
|
|
|
413
416
|
`"Task group (e.g. backend-api, INV-1234, refactor)"` → `task_group`.
|
|
414
417
|
|
|
415
418
|
Slug rule: same as `okstra-run` Step 3 — slugify, must have at least one
|
|
416
|
-
alphanumeric character.
|
|
417
|
-
|
|
419
|
+
alphanumeric character. **Always slugify the chosen value**, whether it came
|
|
420
|
+
from an existing-group selection or `직접 입력` — the `taskGroup` returned by
|
|
421
|
+
`okstra task-list` is a raw display name, not a slug, so picking one verbatim
|
|
422
|
+
would create a non-standard directory (e.g. `briefs/uploadFont/`). The brief's
|
|
423
|
+
on-disk directory segment (`briefs/<task-group>/`) and the frontmatter
|
|
424
|
+
`task-group` value are both the slugified form, and `validate-brief.py`
|
|
425
|
+
(Step 6.6) rejects any brief whose directory segment differs from
|
|
426
|
+
`slugify(frontmatter task-group)`.
|
|
418
427
|
|
|
419
428
|
If `task_group` was already collected by Step 1b sub-step 0 (tracker
|
|
420
429
|
preflight), reuse that value silently — do NOT ask again.
|
|
@@ -752,7 +761,7 @@ Use the same template per brief file. In tracker mode producing a parent
|
|
|
752
761
|
plus N children, you write **N+1 files** (each carries only its own Source
|
|
753
762
|
Material).
|
|
754
763
|
|
|
755
|
-
The installed template `~/.okstra/templates/reports/brief.template.md` is the byte-for-byte SSOT for the brief shape — frontmatter keys, top-header blockquote, section ordering
|
|
764
|
+
The installed template `~/.okstra/templates/reports/brief.template.md` is the byte-for-byte SSOT for the brief shape — frontmatter keys, top-header blockquote, and section ordering. The template's inline `<!-- author guidance -->` HTML comments are fill-in scaffolding for the author, NOT part of the artifact: render the file with per-brief values substituted and **strip every `<!-- ... -->` comment** so the final brief carries none. Leave any section's body as `_(none)_` rather than fabricating content.
|
|
756
765
|
|
|
757
766
|
**Variant note:** The template file is the canonical reporter-input shape. For the codebase-scan variant (`scope: codebase` in frontmatter), OMIT the `## Source Material` and `## Problem / Symptom` headings entirely — do NOT include them with `_(none)_` bodies. Instead, KEEP the `## Scan Scope` and `## Priority Lenses` sections (already present in the template file, marked with HTML comments). For the error-feedback variant (`scope` omitted ⇒ `reporter-input`), use the reporter-input shape: KEEP `## Source Material` (the chosen cluster's anonymized error records) and `## Problem / Symptom`, and OMIT the codebase-only `## Scan Scope` / `## Priority Lenses` sections. The remaining sections (Context / Desired Outcome / Constraints / Related Artifacts / Open Questions / Reporter Confirmations / Augmentation) apply to all variants.
|
|
758
767
|
|
|
@@ -1010,15 +1019,17 @@ started.
|
|
|
1010
1019
|
Augmentation / interpretation goes only into the `Augmentation` section
|
|
1011
1020
|
or a `> augmented:` blockquote inside the required section.
|
|
1012
1021
|
- **No template author-guidance in the artifact body.** The Step 5
|
|
1013
|
-
template
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1022
|
+
template carries author guidance in HTML comments (`<!-- ... -->`);
|
|
1023
|
+
**strip every such comment when writing the brief** — the final artifact
|
|
1024
|
+
contains zero `<!-- ... -->` blocks. (The validator now ignores comment
|
|
1025
|
+
regions, but a clean artifact is the contract, and it is what the
|
|
1026
|
+
passing reference briefs look like.) Section headings stay clean — never
|
|
1027
|
+
copy parenthetical reviewer notes onto the heading itself (e.g. emit
|
|
1017
1028
|
`## Source Material`, not `## Source Material (verbatim — do not modify)`).
|
|
1018
1029
|
Placeholder prose inside angle brackets (`<Background / scope / why
|
|
1019
|
-
now…>`) is
|
|
1020
|
-
actual content;
|
|
1021
|
-
them.
|
|
1030
|
+
now…>`) is NOT a comment — it is intentional scaffolding meant to be
|
|
1031
|
+
replaced with the reporter's actual content; replace those placeholders,
|
|
1032
|
+
do not leave them in.
|
|
1022
1033
|
- If the tracker MCP is not connected, do not guess — ask the user to paste
|
|
1023
1034
|
the body or skip the source.
|
|
1024
1035
|
- If a URL fetch fails or hits an auth wall, do not guess — ask for a
|
|
@@ -6,7 +6,8 @@ Checks performed per brief file:
|
|
|
6
6
|
1. YAML frontmatter exists on line 1 with required keys.
|
|
7
7
|
2. brief-id matches the filename stem.
|
|
8
8
|
3. depth equals the number of `sub/` segments in the path (relative to the
|
|
9
|
-
`briefs/` root)
|
|
9
|
+
`briefs/` root), and the task-group directory segment equals the
|
|
10
|
+
slugified frontmatter `task-group` (lowercase, non-alphanumeric → `-`).
|
|
10
11
|
4. Every Open Questions row starts with one of the five signal prefixes
|
|
11
12
|
(general | terminology | intent-check | conversion-block | adr-candidate).
|
|
12
13
|
`adr-candidate:` targets okstra-internal
|
|
@@ -56,6 +57,7 @@ from okstra_ctl.improvement_lenses import (
|
|
|
56
57
|
MAX_PRIORITY_LENSES,
|
|
57
58
|
MIN_PRIORITY_LENSES,
|
|
58
59
|
)
|
|
60
|
+
from okstra_ctl.ids import slugify_task_segment
|
|
59
61
|
|
|
60
62
|
REQUIRED_FRONTMATTER_KEYS = {
|
|
61
63
|
"type",
|
|
@@ -89,6 +91,23 @@ REPORTER_CONFIRMATION_VALUES = {"complete", "partial", "pending", "skipped"}
|
|
|
89
91
|
|
|
90
92
|
SCOPE_VALUES = {"reporter-input", "codebase"}
|
|
91
93
|
|
|
94
|
+
_HTML_COMMENT_RE = re.compile(r"<!--.*?-->", re.DOTALL)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def strip_html_comments(text: str) -> str:
|
|
98
|
+
"""Remove `<!-- ... -->` blocks before parsing.
|
|
99
|
+
|
|
100
|
+
The brief template carries author-guidance comments whose example rows
|
|
101
|
+
(`intent-check: <…>`, `conversion-block: <…>`, `terminology-mapping: <…>`)
|
|
102
|
+
are NOT real entries. Without this, every `- ` collector below
|
|
103
|
+
(`open_questions_rows`, `augmentation_entries`, `meaningful_bullets`) would
|
|
104
|
+
count them as data and raise false `reporter-confirmations` / `terminology`
|
|
105
|
+
failures. Comments are never brief content, so stripping them once here is
|
|
106
|
+
the single root fix. Frontmatter uses `#` comments only, so this is a no-op
|
|
107
|
+
for the YAML block.
|
|
108
|
+
"""
|
|
109
|
+
return _HTML_COMMENT_RE.sub("", text)
|
|
110
|
+
|
|
92
111
|
|
|
93
112
|
def strip_yaml_comment(line: str) -> str:
|
|
94
113
|
"""Drop a YAML inline comment, keeping `#` that is part of a value.
|
|
@@ -268,7 +287,7 @@ def check_reporter_confirmations(
|
|
|
268
287
|
|
|
269
288
|
|
|
270
289
|
def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
271
|
-
text = path.read_text(encoding="utf-8")
|
|
290
|
+
text = strip_html_comments(path.read_text(encoding="utf-8"))
|
|
272
291
|
errors: list[str] = []
|
|
273
292
|
|
|
274
293
|
# 1. frontmatter
|
|
@@ -315,8 +334,10 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
|
315
334
|
# 3. depth equals path's `sub/` nesting depth
|
|
316
335
|
try:
|
|
317
336
|
rel = path.relative_to(briefs_root)
|
|
337
|
+
under_root = True
|
|
318
338
|
except ValueError:
|
|
319
339
|
rel = path
|
|
340
|
+
under_root = False
|
|
320
341
|
# path components after the task-group dir: any number of `sub` segments + filename
|
|
321
342
|
parts = list(rel.parts)
|
|
322
343
|
if len(parts) >= 2:
|
|
@@ -332,6 +353,19 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
|
332
353
|
f"frontmatter says depth={fm.get('depth')!r}"
|
|
333
354
|
)
|
|
334
355
|
|
|
356
|
+
# 3b. task-group directory segment must equal the slugified frontmatter value.
|
|
357
|
+
# Catches case/format drift (e.g. dir `uploadFont` vs slug `uploadfont`) that
|
|
358
|
+
# case-insensitive filesystems hide but case-sensitive CI breaks on.
|
|
359
|
+
if under_root and len(parts) >= 2:
|
|
360
|
+
expected_segment = slugify_task_segment(fm.get("task-group", ""))
|
|
361
|
+
if expected_segment and parts[0] != expected_segment:
|
|
362
|
+
errors.append(
|
|
363
|
+
f"task-group directory segment {parts[0]!r} does not match the "
|
|
364
|
+
f"slugified frontmatter task-group {expected_segment!r} "
|
|
365
|
+
f"(frontmatter task-group={fm.get('task-group')!r}); rename the "
|
|
366
|
+
f"directory or fix the frontmatter so both use the slug"
|
|
367
|
+
)
|
|
368
|
+
|
|
335
369
|
# 4. Open Questions prefixes
|
|
336
370
|
oq_rows = open_questions_rows(text)
|
|
337
371
|
intent_check_rows: list[str] = []
|