okstra 0.111.0 → 0.113.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.kr.md +2 -2
- package/README.md +2 -2
- package/bin/okstra +7 -1
- package/docs/for-ai/README.md +2 -2
- package/docs/for-ai/skills/okstra-brief.md +2 -3
- package/docs/for-ai/skills/okstra-container-build.md +2 -3
- package/docs/for-ai/skills/okstra-inspect.md +5 -12
- package/docs/for-ai/skills/okstra-rollup.md +3 -4
- package/docs/for-ai/skills/okstra-run.md +3 -5
- package/docs/for-ai/skills/okstra-schedule.md +2 -7
- package/docs/for-ai/skills/okstra-setup.md +1 -1
- package/docs/kr/architecture/storage-model.md +1 -2
- package/docs/kr/architecture.md +2 -2
- package/docs/kr/cli.md +4 -2
- package/docs/project-structure-overview.md +5 -3
- package/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/agents/workers/antigravity-worker.md +1 -1
- package/runtime/agents/workers/claude-worker.md +1 -1
- package/runtime/agents/workers/codex-worker.md +1 -1
- package/runtime/prompts/coding-preflight/overview.md +3 -1
- package/runtime/prompts/launch.template.md +1 -5
- package/runtime/prompts/lead/context-loader.md +2 -4
- package/runtime/prompts/lead/convergence.md +11 -240
- package/runtime/prompts/lead/okstra-lead-contract.md +70 -34
- package/runtime/prompts/lead/plan-body-verification.md +240 -0
- package/runtime/prompts/lead/report-writer.md +7 -7
- package/runtime/prompts/lead/team-contract.md +15 -17
- package/runtime/prompts/profiles/_common-contract.md +1 -37
- package/runtime/prompts/profiles/_implementation-executor.md +2 -2
- package/runtime/prompts/profiles/_implementation-self-check.md +1 -0
- package/runtime/prompts/profiles/_implementation-verifier.md +2 -2
- package/runtime/prompts/profiles/final-verification.md +6 -5
- package/runtime/prompts/profiles/implementation-planning.md +6 -4
- package/runtime/prompts/profiles/implementation.md +1 -1
- package/runtime/prompts/profiles/improvement-discovery.md +1 -0
- package/runtime/prompts/profiles/requirements-discovery.md +1 -0
- package/runtime/python/okstra_ctl/path_hints.py +1 -0
- package/runtime/python/okstra_ctl/paths.py +3 -0
- package/runtime/python/okstra_ctl/render.py +8 -0
- package/runtime/python/okstra_ctl/set_work_status.py +147 -0
- package/runtime/python/okstra_ctl/team_reconcile.py +1 -1
- package/runtime/schemas/final-report-v1.0.schema.json +6 -4
- package/runtime/skills/okstra-brief/SKILL.md +32 -176
- package/runtime/skills/okstra-brief/references/reporter-confirmations.md +71 -0
- package/runtime/skills/okstra-brief/references/tracker-recursion.md +90 -0
- package/runtime/skills/okstra-container-build/SKILL.md +7 -20
- package/runtime/skills/okstra-graphify/SKILL.md +8 -8
- package/runtime/skills/okstra-inspect/SKILL.md +27 -43
- package/runtime/skills/okstra-rollup/SKILL.md +6 -6
- package/runtime/skills/okstra-run/SKILL.md +7 -16
- package/runtime/skills/okstra-schedule/SKILL.md +64 -419
- package/runtime/skills/okstra-setup/SKILL.md +25 -223
- package/runtime/skills/okstra-setup/references/project-config.md +188 -0
- package/runtime/templates/reports/final-report.template.md +24 -1
- package/runtime/templates/reports/improvement-discovery-input.template.md +3 -2
- package/runtime/templates/reports/schedule.template.md +2 -2
- package/runtime/templates/worker-prompt-preamble.md +5 -1
- package/runtime/validators/validate-run.py +8 -7
- package/src/cli-registry.mjs +14 -0
- package/src/commands/inspect/set-work-status.mjs +31 -0
- package/src/commands/lifecycle/check-project.mjs +68 -56
- package/src/commands/lifecycle/install.mjs +1 -0
- package/src/commands/lifecycle/preflight.mjs +82 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""task-manifest.json 의 workStatus 를 갱신하는 단일 CLI 진입점.
|
|
2
|
+
|
|
3
|
+
okstra-inspect status.4 가 Edit 도구로 하던 수동 JSON 편집(키 순서·개행 보존
|
|
4
|
+
규칙을 프롬프트로 강제)을 CLI 로 수렴시킨다. 직렬화는 render._write_json 과
|
|
5
|
+
동일한 json.dumps(indent=2, ensure_ascii=False) + "\n" 이므로 재렌더와 byte
|
|
6
|
+
규칙이 일치한다. discovery/task-catalog.json 은 여기서 재생성하지 않는다 —
|
|
7
|
+
다음 run 렌더가 manifest 를 재투영할 때까지 stale 할 수 있다.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import sys
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from okstra_ctl.ids import slugify_task_segment
|
|
18
|
+
from okstra_project import (
|
|
19
|
+
ResolverError,
|
|
20
|
+
StateError,
|
|
21
|
+
resolve_project_root,
|
|
22
|
+
resolve_task_reference,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
ALLOWED_WORK_STATUSES = ("todo", "in-progress", "blocked", "done")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _emit(payload: dict) -> None:
|
|
29
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _manifest_path(project_root: Path, entry: dict) -> Path:
|
|
33
|
+
rel = entry.get("taskManifestPath") or ""
|
|
34
|
+
if rel:
|
|
35
|
+
candidate = Path(rel)
|
|
36
|
+
return candidate if candidate.is_absolute() else project_root / candidate
|
|
37
|
+
group_seg = entry.get("taskGroupPathSegment") or slugify_task_segment(
|
|
38
|
+
entry.get("taskGroup", "")
|
|
39
|
+
)
|
|
40
|
+
id_seg = entry.get("taskIdPathSegment") or slugify_task_segment(
|
|
41
|
+
entry.get("taskId", "")
|
|
42
|
+
)
|
|
43
|
+
return project_root / ".okstra" / "tasks" / group_seg / id_seg / "task-manifest.json"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def main(argv: list[str] | None = None) -> int:
|
|
47
|
+
parser = argparse.ArgumentParser(
|
|
48
|
+
description="Set a task's user-managed workStatus in task-manifest.json."
|
|
49
|
+
)
|
|
50
|
+
parser.add_argument(
|
|
51
|
+
"token",
|
|
52
|
+
help="full task-key (<project-id>:<task-group>:<task-id>) or bare task-id",
|
|
53
|
+
)
|
|
54
|
+
parser.add_argument("status", choices=ALLOWED_WORK_STATUSES)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
"--note",
|
|
57
|
+
default=None,
|
|
58
|
+
help="set workStatusNote; omit to leave any existing note untouched",
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
"--task-group", default="", help="scope a bare task-id match to this task-group"
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument("--project-root", default="", help="project root for catalog lookup")
|
|
64
|
+
parser.add_argument("--cwd", default=".", help="cwd for project root resolution")
|
|
65
|
+
parser.add_argument("--json", action="store_true", help="emit JSON (always on)")
|
|
66
|
+
args = parser.parse_args(argv)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
project_root = resolve_project_root(explicit_root=args.project_root, cwd=args.cwd)
|
|
70
|
+
except ResolverError as exc:
|
|
71
|
+
_emit({"ok": False, "stage": "resolve", "reason": str(exc)})
|
|
72
|
+
return 2
|
|
73
|
+
|
|
74
|
+
token = args.token
|
|
75
|
+
task_group = args.task_group
|
|
76
|
+
if token.count(":") == 2:
|
|
77
|
+
_, key_group, key_id = token.split(":")
|
|
78
|
+
token = key_id
|
|
79
|
+
task_group = key_group or task_group
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
matches = resolve_task_reference(
|
|
83
|
+
project_root, token, task_group=task_group or None
|
|
84
|
+
)
|
|
85
|
+
except StateError as exc:
|
|
86
|
+
_emit({"ok": False, "stage": "catalog", "reason": str(exc)})
|
|
87
|
+
return 2
|
|
88
|
+
|
|
89
|
+
if not matches:
|
|
90
|
+
_emit({"ok": False, "stage": "not-found", "token": args.token})
|
|
91
|
+
return 1
|
|
92
|
+
if len(matches) > 1:
|
|
93
|
+
_emit({"ok": False, "stage": "ambiguous", "matches": matches})
|
|
94
|
+
return 2
|
|
95
|
+
|
|
96
|
+
entry = matches[0]
|
|
97
|
+
manifest_path = _manifest_path(project_root, entry)
|
|
98
|
+
if not manifest_path.exists():
|
|
99
|
+
_emit(
|
|
100
|
+
{
|
|
101
|
+
"ok": False,
|
|
102
|
+
"stage": "manifest-missing",
|
|
103
|
+
"taskKey": entry.get("taskKey", ""),
|
|
104
|
+
"taskManifestPath": str(manifest_path),
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
return 1
|
|
108
|
+
try:
|
|
109
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
110
|
+
except ValueError as exc:
|
|
111
|
+
_emit(
|
|
112
|
+
{
|
|
113
|
+
"ok": False,
|
|
114
|
+
"stage": "manifest-invalid",
|
|
115
|
+
"taskManifestPath": str(manifest_path),
|
|
116
|
+
"reason": str(exc),
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
return 1
|
|
120
|
+
|
|
121
|
+
previous = manifest.get("workStatus", "")
|
|
122
|
+
manifest["workStatus"] = args.status
|
|
123
|
+
manifest["workStatusUpdatedAt"] = datetime.now(timezone.utc).strftime(
|
|
124
|
+
"%Y-%m-%dT%H:%M:%SZ"
|
|
125
|
+
)
|
|
126
|
+
if args.note is not None:
|
|
127
|
+
manifest["workStatusNote"] = args.note
|
|
128
|
+
manifest_path.write_text(
|
|
129
|
+
json.dumps(manifest, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
_emit(
|
|
133
|
+
{
|
|
134
|
+
"ok": True,
|
|
135
|
+
"taskKey": entry.get("taskKey", ""),
|
|
136
|
+
"previousWorkStatus": previous,
|
|
137
|
+
"workStatus": args.status,
|
|
138
|
+
"workStatusUpdatedAt": manifest["workStatusUpdatedAt"],
|
|
139
|
+
"workStatusNote": manifest.get("workStatusNote", ""),
|
|
140
|
+
"taskManifestPath": str(manifest_path),
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
return 0
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if __name__ == "__main__":
|
|
147
|
+
raise SystemExit(main(sys.argv[1:]))
|
|
@@ -15,7 +15,7 @@ recorded pane — those are left for graceful shutdown.
|
|
|
15
15
|
CC v2.1.178 removed ``TeamDelete``: the implicit team ends with the session and
|
|
16
16
|
run-end teardown only dismisses teammates via ``shutdown_request``. Reconcile
|
|
17
17
|
keeps the roster honest so a dismissed-but-dead member does not linger as a
|
|
18
|
-
live pill — see
|
|
18
|
+
live pill — see okstra-lead-contract.md "Run-scoped pane & teammate lifecycle".
|
|
19
19
|
|
|
20
20
|
Roster resolution (``--project-root``) exists because ``team-state.lead.sessionId``
|
|
21
21
|
is a phase-3 snapshot: Claude Code re-issues the session id across resume /
|
|
@@ -696,7 +696,7 @@
|
|
|
696
696
|
"properties": {
|
|
697
697
|
"target": { "type": "string" },
|
|
698
698
|
"performed": { "type": "string" },
|
|
699
|
-
"result": { "
|
|
699
|
+
"result": { "enum": ["pass", "fail", "blocked"] },
|
|
700
700
|
"observed": { "type": "string" }
|
|
701
701
|
}
|
|
702
702
|
}
|
|
@@ -1691,16 +1691,18 @@
|
|
|
1691
1691
|
"number": { "type": "integer", "minimum": 1 },
|
|
1692
1692
|
"tier": { "enum": [1, 2] },
|
|
1693
1693
|
"command": { "type": "string", "minLength": 1 },
|
|
1694
|
-
"status": { "enum": ["executed", "rejected", "not-configured"] },
|
|
1694
|
+
"status": { "enum": ["executed", "rejected", "not-configured", "env-unavailable"] },
|
|
1695
1695
|
"exitCode": { "type": ["integer", "null"] },
|
|
1696
|
-
"
|
|
1696
|
+
"statusReason": { "type": ["string", "null"] },
|
|
1697
1697
|
"outputTail": { "type": "string" }
|
|
1698
1698
|
},
|
|
1699
1699
|
"allOf": [
|
|
1700
1700
|
{ "if": { "properties": { "status": { "const": "executed" } } },
|
|
1701
1701
|
"then": { "properties": { "exitCode": { "type": "integer" } } } },
|
|
1702
1702
|
{ "if": { "properties": { "status": { "const": "rejected" } } },
|
|
1703
|
-
"then": { "required": ["
|
|
1703
|
+
"then": { "required": ["statusReason"] } },
|
|
1704
|
+
{ "if": { "properties": { "status": { "const": "env-unavailable" } } },
|
|
1705
|
+
"then": { "required": ["statusReason"] } }
|
|
1704
1706
|
]
|
|
1705
1707
|
},
|
|
1706
1708
|
|
|
@@ -117,26 +117,15 @@ as source material, and never writes them. Decision files (when raised by
|
|
|
117
117
|
via `implementation`):
|
|
118
118
|
`<PROJECT_ROOT>/.okstra/decisions/<NNNN>-<slug>.md`.
|
|
119
119
|
|
|
120
|
-
## Step 0:
|
|
120
|
+
## Step 0: Preflight
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
Every Bash command in this skill is a **separate Bash tool call** starting with the literal token `okstra` (never wrapped in `if`/`eval`/`export`/`$(...)`/`VAR=...`/`||`/`&&`/`npx` — a non-literal leading token defeats the `Bash(okstra:*)` permission match).
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
|
-
okstra
|
|
125
|
+
okstra preflight --runtime claude-code --json
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
okstra check-project --json
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Parse the JSON from stdout:
|
|
135
|
-
|
|
136
|
-
- `ok: true` → use `projectRoot` from the JSON output. Carry it as a literal string into the steps below.
|
|
137
|
-
- `ok: false` → tell the user: "this project has no okstra setup. Run `/okstra-setup` first." Then stop.
|
|
138
|
-
|
|
139
|
-
> If the `okstra` binary is not on `PATH` at all, the commands above will not run. In that case tell the user verbatim: "okstra not installed — run `/okstra-setup` first." Do **not** try to invoke `npx -y okstra@latest ...` from this skill — `npx` is not on the literal-token allow-list this skill targets and will force a confirmation prompt on every subsequent call.
|
|
128
|
+
Parse the stdout JSON. `ok: true` → carry `projectRoot` as a literal string into the steps below. `ok: false` (or `okstra` not on `PATH` at all) → tell the user: "okstra not set up — run `/okstra-setup` first." Then stop. If the call fails with `unknown command: preflight`, the `okstra` binary on PATH predates this skill — tell the user to update it (`npm i -g okstra@latest`), then stop (`/okstra-setup` does not update the binary). Do **not** try `npx -y okstra@latest ...` as a fallback.
|
|
140
129
|
|
|
141
130
|
## Step 1: Choose input source
|
|
142
131
|
|
|
@@ -234,86 +223,15 @@ Order of operations:
|
|
|
234
223
|
preserved)`).
|
|
235
224
|
|
|
236
225
|
6. **Sub-ticket / child-issue / sub-task handling — recursive**:
|
|
237
|
-
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
Capture each as a child candidate.
|
|
247
|
-
2. **tracked / sub-issue GraphQL** — for GitHub Projects v2
|
|
248
|
-
"tracked issues" / sub-issue relations, the REST `gh issue view`
|
|
249
|
-
response does NOT carry them. Query GraphQL explicitly, e.g.
|
|
250
|
-
`gh api graphql -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){issue(number:$n){trackedIssues(first:50){nodes{number repository{nameWithOwner}}} subIssues:trackedInIssues(first:50){nodes{number repository{nameWithOwner}}}}}}' -f o=<owner> -f r=<repo> -F n=<num>`.
|
|
251
|
-
If the GraphQL request fails (auth scope missing, feature
|
|
252
|
-
unavailable on this repo, network error), **do NOT silently
|
|
253
|
-
continue with full recursion**. Disable the "Generate the full
|
|
254
|
-
tree recursively" option for this branch, default to
|
|
255
|
-
`Parent only; list children in Related Artifacts and Related Task
|
|
256
|
-
Graph`, and surface the GraphQL failure to the user in one line so
|
|
257
|
-
they can decide whether to paste child bodies manually.
|
|
258
|
-
- Notion: child pages / sub-pages
|
|
259
|
-
- If there is **one or more** child, ask **once at the top parent**:
|
|
260
|
-
- `AskUserQuestion` (single-select)
|
|
261
|
-
- **Label**: `"This ticket has sub-tickets. How should the child tree be handled?"`
|
|
262
|
-
- **Options**:
|
|
263
|
-
1. `Full tree` (recommended) — walk children, grandchildren, …
|
|
264
|
-
via BFS/DFS and emit one brief per node.
|
|
265
|
-
2. `Parent only` — single brief. Children are not fetched; their
|
|
266
|
-
keys/URLs are recorded under Related Artifacts and as `parent-of`
|
|
267
|
-
rows in Related Task Graph.
|
|
268
|
-
3. `Selected` — multi-select the direct children; for each chosen
|
|
269
|
-
child, apply option-1 policy recursively to that branch.
|
|
270
|
-
- For options 1 / 3, **recurse into Step 1b sub-steps 3–5** for every
|
|
271
|
-
descendant. No depth limit. Maintain a visited set of
|
|
272
|
-
`<tracker>:<ticket-id>` to prevent cycles; on revisit, do not emit a
|
|
273
|
-
new brief — only add a link back to the existing brief.
|
|
274
|
-
- **Visited-set across re-runs (rebuild from disk)** — the in-memory
|
|
275
|
-
visited set is per-run and disappears between invocations. When this
|
|
276
|
-
skill runs again over a tree that already has briefs on disk, it MUST
|
|
277
|
-
reseed the visited set by scanning
|
|
278
|
-
`<PROJECT_ROOT>/.okstra/briefs/<task-group>/` recursively
|
|
279
|
-
and reading each brief's frontmatter `ticket-id` + `source-type`.
|
|
280
|
-
Reseed precedence:
|
|
281
|
-
1. On-disk frontmatter (`ticket-id` ≠ "") populates visited entries
|
|
282
|
-
as `<source-type>:<ticket-id>` — these win.
|
|
283
|
-
2. Step 2c collision policy (`Skip` / `Append timestamp` /
|
|
284
|
-
`Overwrite`) applies to any node whose path is already taken.
|
|
285
|
-
3. The fresh in-memory walk fills any gaps for tickets not yet on
|
|
286
|
-
disk.
|
|
287
|
-
A ticket present on disk is NOT refetched unless the user explicitly
|
|
288
|
-
answers `Overwrite` for that path.
|
|
289
|
-
- Each descendant's brief file at depth N is created under
|
|
290
|
-
`<task-group>/<sub/ nested N times>/<ticket-id>-<file-title>.md` per
|
|
291
|
-
Step 2b.
|
|
292
|
-
- Each node's `Related Artifacts` should list:
|
|
293
|
-
- The parent brief's relative path (`../<ticket-id>-<file-title>.md`)
|
|
294
|
-
- All direct children briefs' relative paths
|
|
295
|
-
(`sub/<ticket-id>-<file-title>.md`)
|
|
296
|
-
bidirectionally. (Non-direct ancestors/descendants are tracked via the
|
|
297
|
-
frontmatter `parent-id` chain.)
|
|
298
|
-
- Each generated brief's `Related Task Graph` should contain the same full
|
|
299
|
-
split topology, not only the current node's direct edges, so downstream
|
|
300
|
-
phases can start from any child brief without losing order or dependency
|
|
301
|
-
context. Use one row per source-backed edge with these columns:
|
|
302
|
-
`From | Relation | To | Direction | Source | Impact`.
|
|
303
|
-
- `From` / `To` use task key, brief id, tracker id, or URL. The edge
|
|
304
|
-
direction is `From` → `To`.
|
|
305
|
-
- `Relation` is one of `parent-of`, `child-of`, `depends-on`, `blocks`,
|
|
306
|
-
`blocked-by`, `follow-up-of`, `split-from`, `duplicates`, `related-to`.
|
|
307
|
-
- `Direction` is `directed` for parentage/ordering relations and
|
|
308
|
-
`undirected` for `duplicates` / `related-to`.
|
|
309
|
-
- `Source` names the evidence for the edge: tracker linked issue,
|
|
310
|
-
markdown task-list checkbox, reporter statement, manual split, or prior
|
|
311
|
-
okstra task.
|
|
312
|
-
- `Impact` states what the next phase must preserve, such as "run after
|
|
313
|
-
API contract" or "avoid duplicate implementation".
|
|
314
|
-
Do not invent graph edges from filename similarity or topic overlap; if a
|
|
315
|
-
relation is unclear, use `related-to` + `undirected` only when the source
|
|
316
|
-
explicitly says the items are related.
|
|
226
|
+
- If the fetched ticket response shows **any** child reference (Linear
|
|
227
|
+
`children`/`subIssues`, Jira `subtasks`/`issuelinks`, GitHub task-list
|
|
228
|
+
checkboxes or tracked/sub-issues, Notion child pages), **read
|
|
229
|
+
`references/tracker-recursion.md` next to this SKILL.md and follow it**
|
|
230
|
+
before writing any brief — it defines per-tracker child discovery
|
|
231
|
+
(including the GitHub GraphQL query and its failure handling), the
|
|
232
|
+
Full tree / Parent only / Selected picker, the cross-run visited-set
|
|
233
|
+
reseed from on-disk brief frontmatter, and the Related Artifacts /
|
|
234
|
+
Related Task Graph topology every emitted brief must carry.
|
|
317
235
|
- If there are no children, proceed with a single brief (depth 0).
|
|
318
236
|
|
|
319
237
|
### 1c. `Link URL`
|
|
@@ -584,13 +502,11 @@ project's domain.
|
|
|
584
502
|
|
|
585
503
|
## Step 4: Fill in missing fields (NO full interview)
|
|
586
504
|
|
|
587
|
-
> **Verbatim-source rule
|
|
588
|
-
>
|
|
589
|
-
>
|
|
590
|
-
>
|
|
591
|
-
>
|
|
592
|
-
> Questions` only.** brief is a pre-discovery artifact, not a PRD —
|
|
593
|
-
> decisions belong to later phases.
|
|
505
|
+
> **Verbatim-source rule** (the first non-negotiable at the top of this
|
|
506
|
+
> file): never modify Source Material by a single byte. **Anything learned
|
|
507
|
+
> by grilling in this step lands in `Augmentation` or `Open Questions`
|
|
508
|
+
> only.** brief is a pre-discovery artifact, not a PRD — decisions belong
|
|
509
|
+
> to later phases.
|
|
594
510
|
|
|
595
511
|
Inspect the synthesized brief draft. For each REQUIRED section below that is
|
|
596
512
|
empty or trivially thin **after** reading Source Material verbatim, ask **at
|
|
@@ -903,72 +819,16 @@ If the list is non-empty, run **one** `AskUserQuestion`:
|
|
|
903
819
|
`implementation-planning`); see each phase profile's "Brief
|
|
904
820
|
consumption" addendum.
|
|
905
821
|
|
|
906
|
-
### 6.5c.
|
|
907
|
-
|
|
908
|
-
For each pending row, formulate one question. Because the
|
|
909
|
-
`AskUserQuestion` tool caps options at 4 per call and questions per call
|
|
910
|
-
also at 4, split into batches of ≤ 4 questions.
|
|
911
|
-
|
|
912
|
-
**Cap — 12 questions per Step 6.5 run.** When the pending list
|
|
913
|
-
exceeds 12 rows, sort by priority and ask only the top 12 this round:
|
|
914
|
-
|
|
915
|
-
1. `conversion-block:` rows first (translation failure — highest
|
|
916
|
-
downstream risk).
|
|
917
|
-
2. `intent-check:` rows next, in the order they appear in `Open
|
|
918
|
-
Questions`.
|
|
919
|
-
|
|
920
|
-
Any rows beyond the cap are left unanswered in this run; set
|
|
921
|
-
`reporter-confirmations: partial` in the frontmatter and tell the user
|
|
922
|
-
which rows remain. The next `okstra-brief` re-run (or the downstream
|
|
923
|
-
phase that consumes `Blocks=next-phase`) handles the remainder.
|
|
924
|
-
|
|
925
|
-
Each question carries:
|
|
926
|
-
|
|
927
|
-
- The reporter-facing phrasing (plain language, no jargon).
|
|
928
|
-
- A recommended answer drawn from the brief's `intent-inference` /
|
|
929
|
-
`conversion-block:` content, marked `(Recommended)`.
|
|
930
|
-
- 2–3 alternative options when the answer space is naturally enumerable;
|
|
931
|
-
otherwise a free-text fallback.
|
|
932
|
-
|
|
933
|
-
### 6.5d. Record verbatim
|
|
934
|
-
|
|
935
|
-
Create a new top-level section `## Reporter Confirmations` in the brief
|
|
936
|
-
(if not present) with one subsection per answered row:
|
|
937
|
-
|
|
938
|
-
```markdown
|
|
939
|
-
## Reporter Confirmations
|
|
940
|
-
|
|
941
|
-
### RC-1 — <intent-check: or conversion-block: row id / topic>
|
|
942
|
-
- asked: <YYYY-MM-DD HH:MM>
|
|
943
|
-
- linked-row: `<exact Open Questions row text>`
|
|
944
|
-
- answer (verbatim):
|
|
945
|
-
|
|
946
|
-
> <reporter's answer, byte-for-byte>
|
|
947
|
-
```
|
|
948
|
-
|
|
949
|
-
Mark the linked `Open Questions` row by appending `[CONFIRMED <YYYY-MM-DD> → RC-N]` to the end of the row's line. Do NOT delete the row — the
|
|
950
|
-
`CONFIRMED` marker keeps the audit trail intact.
|
|
951
|
-
|
|
952
|
-
For every `intent-inference` augmentation whose paired `intent-check:`
|
|
953
|
-
was confirmed, add one new line directly below the `> augmented:
|
|
954
|
-
intent-inference …` blockquote:
|
|
955
|
-
|
|
956
|
-
```
|
|
957
|
-
> confirmed-by-reporter: RC-N — <one-line summary of confirmation>
|
|
958
|
-
```
|
|
959
|
-
|
|
960
|
-
This converts the inference from "unverified hypothesis" to "verified
|
|
961
|
-
hypothesis" without rewriting the original augmentation.
|
|
962
|
-
|
|
963
|
-
### 6.5e. Frontmatter state
|
|
964
|
-
|
|
965
|
-
Update `reporter-confirmations` in the brief frontmatter:
|
|
822
|
+
### 6.5c–e. Collect, record, and set frontmatter state
|
|
966
823
|
|
|
967
|
-
|
|
968
|
-
-
|
|
969
|
-
|
|
970
|
-
-
|
|
971
|
-
|
|
824
|
+
When 6.5b answered "Yes — collect now", **read
|
|
825
|
+
`references/reporter-confirmations.md` next to this SKILL.md and follow it**:
|
|
826
|
+
it defines the ≤ 4-question batching, the 12-question cap with
|
|
827
|
+
`conversion-block:`-first priority, the `## Reporter Confirmations` RC-N
|
|
828
|
+
record shape with `[CONFIRMED <date> → RC-N]` row markers and
|
|
829
|
+
`> confirmed-by-reporter:` blockquote lines, and the final
|
|
830
|
+
`reporter-confirmations` frontmatter value (`complete` / `partial` /
|
|
831
|
+
`skipped`).
|
|
972
832
|
|
|
973
833
|
Step 6.5 ends here. The brief now has either zero pending reporter-only
|
|
974
834
|
rows (`complete` / `partial` with remainder in the same file) or an
|
|
@@ -1038,11 +898,11 @@ started.
|
|
|
1038
898
|
stays under `<PROJECT_ROOT>/.okstra/` — briefs in `.okstra/briefs/`, glossary
|
|
1039
899
|
in `.okstra/glossary.md` (Step 4.5). Paths outside that subtree are read-only
|
|
1040
900
|
source material, and only when the reporter cited them.
|
|
1041
|
-
- **Verbatim source
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
901
|
+
- **Verbatim source** (the two non-negotiables at the top of this file
|
|
902
|
+
govern): no line budget overrides them — if source content is large,
|
|
903
|
+
preserve it exactly as separate `Source Material` entries; if a tool
|
|
904
|
+
returns a truncated body, ask the user for the missing content or emit a
|
|
905
|
+
`conversion-block:` row. Never silently excerpt or summarize.
|
|
1046
906
|
- **No template author-guidance in the artifact body.** The Step 5
|
|
1047
907
|
template carries author guidance in HTML comments (`<!-- ... -->`);
|
|
1048
908
|
**strip every such comment when writing the brief** — the final artifact
|
|
@@ -1061,10 +921,6 @@ started.
|
|
|
1061
921
|
paste.
|
|
1062
922
|
- Never fabricate content for empty sections; use `_(none)_`.
|
|
1063
923
|
- Echo each `AskUserQuestion` outcome on one short line.
|
|
1064
|
-
- No line budget overrides the verbatim-source rule. If source content is
|
|
1065
|
-
large, preserve it exactly as separate `Source Material` entries; if a
|
|
1066
|
-
tool only returns a truncated body, ask the user for the missing content
|
|
1067
|
-
or emit a `conversion-block:` row. Never silently excerpt or summarize.
|
|
1068
924
|
|
|
1069
925
|
## Failure Modes
|
|
1070
926
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Reporter batch confirmation — collection procedure (Step 6.5c–e)
|
|
2
|
+
|
|
3
|
+
Read this only when Step 6.5b answered "Yes — collect now" for a non-empty
|
|
4
|
+
pending list of `intent-check:` / `conversion-block:` rows.
|
|
5
|
+
|
|
6
|
+
## 6.5c. Batch the questions
|
|
7
|
+
|
|
8
|
+
For each pending row, formulate one question. Because the `AskUserQuestion`
|
|
9
|
+
tool caps options at 4 per call and questions per call also at 4, split into
|
|
10
|
+
batches of ≤ 4 questions.
|
|
11
|
+
|
|
12
|
+
**Cap — 12 questions per Step 6.5 run.** When the pending list exceeds 12
|
|
13
|
+
rows, sort by priority and ask only the top 12 this round:
|
|
14
|
+
|
|
15
|
+
1. `conversion-block:` rows first (translation failure — highest downstream
|
|
16
|
+
risk).
|
|
17
|
+
2. `intent-check:` rows next, in the order they appear in `Open Questions`.
|
|
18
|
+
|
|
19
|
+
Any rows beyond the cap are left unanswered in this run; set
|
|
20
|
+
`reporter-confirmations: partial` in the frontmatter and tell the user which
|
|
21
|
+
rows remain. The next `okstra-brief` re-run (or the downstream phase that
|
|
22
|
+
consumes `Blocks=next-phase`) handles the remainder.
|
|
23
|
+
|
|
24
|
+
Each question carries:
|
|
25
|
+
|
|
26
|
+
- The reporter-facing phrasing (plain language, no jargon).
|
|
27
|
+
- A recommended answer drawn from the brief's `intent-inference` /
|
|
28
|
+
`conversion-block:` content, marked `(Recommended)`.
|
|
29
|
+
- 2–3 alternative options when the answer space is naturally enumerable;
|
|
30
|
+
otherwise a free-text fallback.
|
|
31
|
+
|
|
32
|
+
## 6.5d. Record verbatim
|
|
33
|
+
|
|
34
|
+
Create a new top-level section `## Reporter Confirmations` in the brief (if
|
|
35
|
+
not present) with one subsection per answered row:
|
|
36
|
+
|
|
37
|
+
```markdown
|
|
38
|
+
## Reporter Confirmations
|
|
39
|
+
|
|
40
|
+
### RC-1 — <intent-check: or conversion-block: row id / topic>
|
|
41
|
+
- asked: <YYYY-MM-DD HH:MM>
|
|
42
|
+
- linked-row: `<exact Open Questions row text>`
|
|
43
|
+
- answer (verbatim):
|
|
44
|
+
|
|
45
|
+
> <reporter's answer, byte-for-byte>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Mark the linked `Open Questions` row by appending
|
|
49
|
+
`[CONFIRMED <YYYY-MM-DD> → RC-N]` to the end of the row's line. Do NOT delete
|
|
50
|
+
the row — the `CONFIRMED` marker keeps the audit trail intact.
|
|
51
|
+
|
|
52
|
+
For every `intent-inference` augmentation whose paired `intent-check:` was
|
|
53
|
+
confirmed, add one new line directly below the `> augmented: intent-inference …`
|
|
54
|
+
blockquote:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
> confirmed-by-reporter: RC-N — <one-line summary of confirmation>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This converts the inference from "unverified hypothesis" to "verified
|
|
61
|
+
hypothesis" without rewriting the original augmentation.
|
|
62
|
+
|
|
63
|
+
## 6.5e. Frontmatter state
|
|
64
|
+
|
|
65
|
+
Update `reporter-confirmations` in the brief frontmatter:
|
|
66
|
+
|
|
67
|
+
- `complete` — every pending row was answered.
|
|
68
|
+
- `partial` — some rows answered, some skipped within this run.
|
|
69
|
+
- `skipped` — user picked option 2 in 6.5b.
|
|
70
|
+
- `pending` — should never appear at this point; if it does, treat the brief
|
|
71
|
+
as not yet handed off and refuse to proceed.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Sub-ticket / child-issue recursion (tracker sources)
|
|
2
|
+
|
|
3
|
+
Read this only when a fetched tracker ticket shows one or more child references
|
|
4
|
+
(okstra-brief Step 1b sub-step 6). It defines how the child tree is discovered,
|
|
5
|
+
which briefs are emitted, and how re-runs stay idempotent.
|
|
6
|
+
|
|
7
|
+
## 1. Discover child references per tracker
|
|
8
|
+
|
|
9
|
+
- Linear: `children` / `subIssues` field.
|
|
10
|
+
- Jira: `subtasks` (or `issuelinks` with the `is parent of` relation).
|
|
11
|
+
- GitHub: `gh issue view --json title,body,comments,labels,state` only returns
|
|
12
|
+
the issue body and comments — child relations are NOT in that JSON. Resolve
|
|
13
|
+
children via two paths:
|
|
14
|
+
1. **task-list parsing** — scan the fetched body and comments for markdown
|
|
15
|
+
task-list checkboxes referencing other issues (`- [ ] #NNN`,
|
|
16
|
+
`- [x] owner/repo#NNN`, `- [ ] https://github.com/...`). Capture each as a
|
|
17
|
+
child candidate.
|
|
18
|
+
2. **tracked / sub-issue GraphQL** — for GitHub Projects v2 "tracked issues" /
|
|
19
|
+
sub-issue relations, query GraphQL explicitly, e.g.
|
|
20
|
+
`gh api graphql -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){issue(number:$n){trackedIssues(first:50){nodes{number repository{nameWithOwner}}} subIssues:trackedInIssues(first:50){nodes{number repository{nameWithOwner}}}}}}' -f o=<owner> -f r=<repo> -F n=<num>`.
|
|
21
|
+
If the GraphQL request fails (auth scope missing, feature unavailable,
|
|
22
|
+
network error), do NOT silently continue with full recursion: disable the
|
|
23
|
+
"Full tree" option for this branch, default to `Parent only`, and surface
|
|
24
|
+
the GraphQL failure to the user in one line so they can decide whether to
|
|
25
|
+
paste child bodies manually.
|
|
26
|
+
- Notion: child pages / sub-pages.
|
|
27
|
+
|
|
28
|
+
## 2. Ask once at the top parent
|
|
29
|
+
|
|
30
|
+
If there is one or more child, `AskUserQuestion` (single-select):
|
|
31
|
+
|
|
32
|
+
- **Label**: `"This ticket has sub-tickets. How should the child tree be handled?"`
|
|
33
|
+
- **Options**:
|
|
34
|
+
1. `Full tree` (recommended) — walk children, grandchildren, … via BFS/DFS
|
|
35
|
+
and emit one brief per node.
|
|
36
|
+
2. `Parent only` — single brief. Children are not fetched; their keys/URLs are
|
|
37
|
+
recorded under Related Artifacts and as `parent-of` rows in Related Task
|
|
38
|
+
Graph.
|
|
39
|
+
3. `Selected` — multi-select the direct children; for each chosen child,
|
|
40
|
+
apply option-1 policy recursively to that branch.
|
|
41
|
+
|
|
42
|
+
For options 1 / 3, recurse into Step 1b sub-steps 3–5 for every descendant. No
|
|
43
|
+
depth limit. Maintain a visited set of `<tracker>:<ticket-id>` to prevent
|
|
44
|
+
cycles; on revisit, do not emit a new brief — only add a link back to the
|
|
45
|
+
existing brief.
|
|
46
|
+
|
|
47
|
+
## 3. Visited-set across re-runs (rebuild from disk)
|
|
48
|
+
|
|
49
|
+
The in-memory visited set is per-run. When this skill runs again over a tree
|
|
50
|
+
that already has briefs on disk, reseed the visited set by scanning
|
|
51
|
+
`<PROJECT_ROOT>/.okstra/briefs/<task-group>/` recursively and reading each
|
|
52
|
+
brief's frontmatter `ticket-id` + `source-type`. Reseed precedence:
|
|
53
|
+
|
|
54
|
+
1. On-disk frontmatter (`ticket-id` ≠ "") populates visited entries as
|
|
55
|
+
`<source-type>:<ticket-id>` — these win.
|
|
56
|
+
2. Step 2c collision policy (`Skip` / `Append timestamp` / `Overwrite`) applies
|
|
57
|
+
to any node whose path is already taken.
|
|
58
|
+
3. The fresh in-memory walk fills any gaps for tickets not yet on disk.
|
|
59
|
+
|
|
60
|
+
A ticket present on disk is NOT refetched unless the user explicitly answers
|
|
61
|
+
`Overwrite` for that path.
|
|
62
|
+
|
|
63
|
+
## 4. Per-node output
|
|
64
|
+
|
|
65
|
+
- Each descendant's brief file at depth N is created under
|
|
66
|
+
`<task-group>/<sub/ nested N times>/<ticket-id>-<file-title>.md` per Step 2b.
|
|
67
|
+
- Each node's `Related Artifacts` lists, bidirectionally:
|
|
68
|
+
- the parent brief's relative path (`../<ticket-id>-<file-title>.md`)
|
|
69
|
+
- all direct children briefs' relative paths
|
|
70
|
+
(`sub/<ticket-id>-<file-title>.md`)
|
|
71
|
+
(Non-direct ancestors/descendants are tracked via the frontmatter
|
|
72
|
+
`parent-id` chain.)
|
|
73
|
+
- Each generated brief's `Related Task Graph` contains the same **full split
|
|
74
|
+
topology**, not only the current node's direct edges, so downstream phases
|
|
75
|
+
can start from any child brief without losing order or dependency context.
|
|
76
|
+
One row per source-backed edge with columns
|
|
77
|
+
`From | Relation | To | Direction | Source | Impact`:
|
|
78
|
+
- `From` / `To` use task key, brief id, tracker id, or URL; edge direction is
|
|
79
|
+
`From` → `To`.
|
|
80
|
+
- `Relation` is one of `parent-of`, `child-of`, `depends-on`, `blocks`,
|
|
81
|
+
`blocked-by`, `follow-up-of`, `split-from`, `duplicates`, `related-to`.
|
|
82
|
+
- `Direction` is `directed` for parentage/ordering relations, `undirected`
|
|
83
|
+
for `duplicates` / `related-to`.
|
|
84
|
+
- `Source` names the evidence for the edge: tracker linked issue, markdown
|
|
85
|
+
task-list checkbox, reporter statement, manual split, or prior okstra task.
|
|
86
|
+
- `Impact` states what the next phase must preserve (e.g. "run after API
|
|
87
|
+
contract", "avoid duplicate implementation").
|
|
88
|
+
Do not invent graph edges from filename similarity or topic overlap; if a
|
|
89
|
+
relation is unclear, use `related-to` + `undirected` only when the source
|
|
90
|
+
explicitly says the items are related.
|