okstra 0.83.0 → 0.84.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/package.json +1 -1
- package/runtime/BUILD.json +2 -2
- package/runtime/prompts/okstra-coding-preflight/scripts/preedit-check.sh +79 -0
- package/runtime/python/okstra_ctl/run.py +3 -1
- package/runtime/skills/okstra-brief/SKILL.md +65 -11
- package/runtime/skills/okstra-inspect/SKILL.md +1 -1
- package/runtime/skills/okstra-schedule/SKILL.md +4 -4
- package/runtime/skills/okstra-setup/SKILL.md +2 -2
- package/runtime/templates/reports/settings.template.json +14 -123
- package/runtime/validators/validate-brief.py +99 -16
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/architectures/hexagonal.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/clean-code.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/frameworks/node-server.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/languages/java.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/languages/javascript-typescript.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/languages/kotlin.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/languages/python.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/languages/rust.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/languages/sql.md +0 -0
- /package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/overview.md +0 -0
package/package.json
CHANGED
package/runtime/BUILD.json
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PreToolUse hook for okstra-coding-preflight.
|
|
3
|
+
#
|
|
4
|
+
# Inspects the target file path of a Write/Edit/MultiEdit/NotebookEdit
|
|
5
|
+
# call. If the extension matches a language covered by this skill,
|
|
6
|
+
# emits a `hookSpecificOutput.additionalContext` JSON payload that
|
|
7
|
+
# reminds the agent to invoke the skill before writing.
|
|
8
|
+
#
|
|
9
|
+
# Fires at most once per session per language (marker file).
|
|
10
|
+
# Exits 0 in every case — never blocks the tool call.
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
input="$(cat)"
|
|
15
|
+
|
|
16
|
+
tool_name="$(printf '%s' "$input" | jq -r '.tool_name // empty')"
|
|
17
|
+
case "$tool_name" in
|
|
18
|
+
Write|Edit|MultiEdit|NotebookEdit) ;;
|
|
19
|
+
*) exit 0 ;;
|
|
20
|
+
esac
|
|
21
|
+
|
|
22
|
+
file_path="$(printf '%s' "$input" \
|
|
23
|
+
| jq -r '.tool_input.file_path // .tool_input.notebook_path // empty')"
|
|
24
|
+
[[ -z "$file_path" ]] && exit 0
|
|
25
|
+
|
|
26
|
+
lang=""
|
|
27
|
+
ref=""
|
|
28
|
+
extra_hint=""
|
|
29
|
+
case "$file_path" in
|
|
30
|
+
*.java)
|
|
31
|
+
lang="Java"
|
|
32
|
+
ref="languages/java.md"
|
|
33
|
+
;;
|
|
34
|
+
*.kt|*.kts)
|
|
35
|
+
lang="Kotlin"
|
|
36
|
+
ref="languages/kotlin.md"
|
|
37
|
+
;;
|
|
38
|
+
*.ts|*.tsx|*.js|*.jsx|*.mjs|*.cjs)
|
|
39
|
+
lang="JavaScript-TypeScript"
|
|
40
|
+
ref="languages/javascript-typescript.md"
|
|
41
|
+
extra_hint=" If this file is part of a Node.js server (Express/Fastify/Nest/etc.), also read languages/nodejs.md."
|
|
42
|
+
;;
|
|
43
|
+
*.py)
|
|
44
|
+
lang="Python"
|
|
45
|
+
ref="languages/python.md"
|
|
46
|
+
;;
|
|
47
|
+
*.sql)
|
|
48
|
+
lang="SQL"
|
|
49
|
+
ref="languages/sql.md"
|
|
50
|
+
;;
|
|
51
|
+
*.rs)
|
|
52
|
+
lang="Rust"
|
|
53
|
+
ref="languages/rust.md"
|
|
54
|
+
;;
|
|
55
|
+
*)
|
|
56
|
+
exit 0
|
|
57
|
+
;;
|
|
58
|
+
esac
|
|
59
|
+
|
|
60
|
+
session_id="$(printf '%s' "$input" | jq -r '.session_id // "no-session"')"
|
|
61
|
+
marker_dir="${TMPDIR:-/tmp}/mcbsc"
|
|
62
|
+
mkdir -p "$marker_dir"
|
|
63
|
+
marker="${marker_dir}/${session_id}_${lang}"
|
|
64
|
+
|
|
65
|
+
if [[ -f "$marker" ]]; then
|
|
66
|
+
exit 0
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
: > "$marker"
|
|
70
|
+
|
|
71
|
+
skill_root="$HOME/.okstra/prompts/okstra-coding-preflight"
|
|
72
|
+
msg="[okstra-coding-preflight] About to edit a ${lang} file (${file_path}). Before writing, you MUST Read ${skill_root}/${ref} plus ${skill_root}/clean-code.md (the skill is user-invocable:false — read the files directly).${extra_hint} If the project uses ports-and-adapters (domain/ + ports/ + adapters/, *.port.* files), also Read ${skill_root}/architecture/hexagonal.md. (Fires once per session per language.)"
|
|
73
|
+
|
|
74
|
+
jq -nc \
|
|
75
|
+
--arg event "PreToolUse" \
|
|
76
|
+
--arg ctx "$msg" \
|
|
77
|
+
'{hookSpecificOutput: {hookEventName: $event, additionalContext: $ctx}}'
|
|
78
|
+
|
|
79
|
+
exit 0
|
|
@@ -79,6 +79,7 @@ from .session import (
|
|
|
79
79
|
from .pr_template import PrTemplateError, resolve_pr_template_path
|
|
80
80
|
from .workers import (
|
|
81
81
|
normalize_workers,
|
|
82
|
+
resolve_optional_workers,
|
|
82
83
|
resolve_profile_workers,
|
|
83
84
|
validate_workers_against_profile,
|
|
84
85
|
)
|
|
@@ -1402,10 +1403,11 @@ def _resolve_roster(inp: PrepareInputs, profile_file: Path) -> tuple[list[str],
|
|
|
1402
1403
|
if inp.task_type == "release-handoff":
|
|
1403
1404
|
return [], ""
|
|
1404
1405
|
profile_workers = resolve_profile_workers(profile_file)
|
|
1406
|
+
optional_workers = resolve_optional_workers(profile_file)
|
|
1405
1407
|
profile_workers_csv = ",".join(profile_workers)
|
|
1406
1408
|
workers = normalize_workers(inp.workers_override or profile_workers_csv)
|
|
1407
1409
|
if inp.workers_override.strip():
|
|
1408
|
-
validate_workers_against_profile(workers, profile_workers)
|
|
1410
|
+
validate_workers_against_profile(workers, profile_workers, optional_workers)
|
|
1409
1411
|
if not workers:
|
|
1410
1412
|
raise PrepareError(f"no workers resolved for profile: {inp.task_type}")
|
|
1411
1413
|
return workers, ",".join(workers)
|
|
@@ -145,7 +145,7 @@ If the user picked `Reporter input`, proceed to Step 1.A's sub-flow below — th
|
|
|
145
145
|
- **Label**: "Source of this brief?"
|
|
146
146
|
- **Options** (single-select):
|
|
147
147
|
1. `File` — path to an existing markdown / txt / issue-export file
|
|
148
|
-
2. `Issue tracker ticket` — Linear / Jira / GitHub Issue / Notion key or URL
|
|
148
|
+
2. `Issue tracker ticket` — Linear URL/ Jira URL/ GitHub Issue URL/ Notion key or URL
|
|
149
149
|
3. `Link URL` — general web page, blog, spec doc, design doc, etc.
|
|
150
150
|
4. `User input` — synthesize from the current conversation context, or
|
|
151
151
|
accept a short free-text from the user
|
|
@@ -678,7 +678,7 @@ Use the same template per brief file. In tracker mode producing a parent
|
|
|
678
678
|
plus N children, you write **N+1 files** (each carries only its own Source
|
|
679
679
|
Material).
|
|
680
680
|
|
|
681
|
-
|
|
681
|
+
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, and inline `<!-- author guidance -->` HTML comments. Render that file with the per-brief values substituted; leave any section's body as `_(none)_` rather than fabricating content. Preserve the template's HTML comments verbatim (they are part of the contract) but do NOT promote them to body prose.
|
|
682
682
|
|
|
683
683
|
**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). The remaining sections (Context / Desired Outcome / Constraints / Related Artifacts / Open Questions / Reporter Confirmations / Augmentation) apply to both variants.
|
|
684
684
|
|
|
@@ -710,6 +710,22 @@ Material).
|
|
|
710
710
|
frontmatter.
|
|
711
711
|
- `ticket-id` is populated only for tracker sources; leave it as an empty
|
|
712
712
|
string otherwise.
|
|
713
|
+
- `source-type` records the input origin (`file | linear | jira | github |
|
|
714
|
+
notion | url | user-input`).
|
|
715
|
+
- `task-group` echoes the Step 2a value.
|
|
716
|
+
- `created` is the brief's creation date (`YYYY-MM-DD`).
|
|
717
|
+
- `generator: okstra-brief` is a fixed value.
|
|
718
|
+
- `reporter-confirmations` is set by Step 6.5 (`complete | partial | pending |
|
|
719
|
+
skipped`).
|
|
720
|
+
- `scope` is `codebase` for the codebase-scan variant; omit it (⇒
|
|
721
|
+
`reporter-input`) for the reporter-input variant.
|
|
722
|
+
|
|
723
|
+
The required-key set (`type`, `brief-id`, `parent-id`, `ticket-id`,
|
|
724
|
+
`source-type`, `task-group`, `depth`, `created`, `generator`,
|
|
725
|
+
`reporter-confirmations`) and every consistency rule above are enforced by
|
|
726
|
+
`~/.okstra/lib/validators/validate-brief.py` — run in
|
|
727
|
+
Step 6.6. The byte-for-byte field shape remains the job of
|
|
728
|
+
`~/.okstra/templates/reports/brief.template.md`.
|
|
713
729
|
|
|
714
730
|
Echo the file path back on one line. Show the rendered brief to the user
|
|
715
731
|
inline and ask:
|
|
@@ -750,8 +766,17 @@ cannot resolve:
|
|
|
750
766
|
- `intent-check:` (auto-mirrored from `intent-inference` augmentations)
|
|
751
767
|
- `conversion-block:` (translation failed in Step 3c)
|
|
752
768
|
|
|
753
|
-
|
|
754
|
-
|
|
769
|
+
**Re-run reseed (rebuild from disk).** A prior run may have left this brief at
|
|
770
|
+
`reporter-confirmations: partial` with some rows already answered. Before
|
|
771
|
+
building the pending list, exclude every `intent-check:` / `conversion-block:`
|
|
772
|
+
row that already carries a `[CONFIRMED <date> → RC-N]` marker — those were
|
|
773
|
+
resolved in an earlier run and their answers already live under `## Reporter
|
|
774
|
+
Confirmations`. Only the unmarked rows are pending this run. This mirrors the
|
|
775
|
+
visited-set rebuild rule in Step 1b: confirmation state lives on disk, not in
|
|
776
|
+
memory.
|
|
777
|
+
|
|
778
|
+
If the resulting pending list is empty, set `reporter-confirmations: complete`
|
|
779
|
+
in the brief's frontmatter and skip the rest of this step.
|
|
755
780
|
|
|
756
781
|
### 6.5b. Ask once: collect now or later
|
|
757
782
|
|
|
@@ -839,6 +864,37 @@ rows (`complete` / `partial` with remainder in the same file) or an
|
|
|
839
864
|
explicit `skipped` signal that downstream phases consume per the
|
|
840
865
|
"Brief consumption" addendum in `_common-contract.md`.
|
|
841
866
|
|
|
867
|
+
## Step 6.6: Self-validate before hand-off
|
|
868
|
+
|
|
869
|
+
After every brief is written (Step 5) and its `reporter-confirmations` state
|
|
870
|
+
is set (Step 6.5), and **before** printing the Step 7 hand-off message, you
|
|
871
|
+
MUST validate every brief produced this run. The validator enforces the
|
|
872
|
+
labelled-handoff contract — required frontmatter keys, Augmentation labels,
|
|
873
|
+
`intent-inference ↔ intent-check:` auto-mirroring, `parent-id`/`depth`
|
|
874
|
+
consistency, `reporter-confirmations` markers, and the codebase-scan
|
|
875
|
+
`scope`/`Scan Scope`/`Priority Lenses` rules.
|
|
876
|
+
|
|
877
|
+
1. Run the validator over the briefs directory:
|
|
878
|
+
|
|
879
|
+
```bash
|
|
880
|
+
~/.okstra/lib/validators/validate-brief.sh "<PROJECT_ROOT>/.okstra/briefs" --briefs-root "<PROJECT_ROOT>/.okstra/briefs"
|
|
881
|
+
```
|
|
882
|
+
|
|
883
|
+
If you are working from a repo checkout instead of an install, the dev
|
|
884
|
+
copy is at `validators/validate-brief.sh`.
|
|
885
|
+
|
|
886
|
+
2. If it exits non-zero, **fix the offending brief(s) and re-run**. Do NOT
|
|
887
|
+
print the Step 7 message until the validator passes.
|
|
888
|
+
|
|
889
|
+
3. If neither validator copy is present, fall back to a manual checklist:
|
|
890
|
+
confirm each brief has all ten required frontmatter keys (`type`,
|
|
891
|
+
`brief-id`, `parent-id`, `ticket-id`, `source-type`, `task-group`,
|
|
892
|
+
`depth`, `created`, `generator`, `reporter-confirmations`); every
|
|
893
|
+
Augmentation entry carries one of the four labels; every `intent-inference`
|
|
894
|
+
has a paired `intent-check:` row; `parent-id` is `self` at depth 0 and a
|
|
895
|
+
real parent id below; and for `scope: codebase` briefs, `Scan Scope` is
|
|
896
|
+
non-empty and `Priority Lenses` lists 1–4 whitelist lenses.
|
|
897
|
+
|
|
842
898
|
## Step 7: Hand off
|
|
843
899
|
|
|
844
900
|
Single brief:
|
|
@@ -866,13 +922,10 @@ started.
|
|
|
866
922
|
|
|
867
923
|
## Output Rules
|
|
868
924
|
|
|
869
|
-
-
|
|
870
|
-
`<PROJECT_ROOT>/.okstra
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
- Paths outside `<PROJECT_ROOT>/.okstra/**` are read-only
|
|
874
|
-
source material only when explicitly cited by the reporter. This skill
|
|
875
|
-
never writes outside okstra's subtree.
|
|
925
|
+
- **Artifact-home** (see the "Artifact-home rule" section above): every write
|
|
926
|
+
stays under `<PROJECT_ROOT>/.okstra/` — briefs in `.okstra/briefs/`, glossary
|
|
927
|
+
in `.okstra/glossary.md` (Step 4.5). Paths outside that subtree are read-only
|
|
928
|
+
source material, and only when the reporter cited them.
|
|
876
929
|
- **Verbatim source**: never paraphrase, summarize, restructure, or reorder
|
|
877
930
|
the Source Material section. Only format conversion (ADF → MD, HTML → MD)
|
|
878
931
|
is allowed, and the conversion must be annotated in the `format:` meta.
|
|
@@ -910,3 +963,4 @@ started.
|
|
|
910
963
|
| URL fetch fails or 401 / login wall | intranet, paywall, JS-rendered page | Ask the user to paste the core body. Never guess. |
|
|
911
964
|
| Tracker body is in non-MD format (ADF / HTML) | Jira description, Notion blocks | Convert format only, preserve semantics. Annotate the conversion in the `format:` meta. |
|
|
912
965
|
| Brief comes out skeletal after synthesis | source too thin AND user picked `User input` with little context | Switch to inline input mode and ask one or two targeted questions. |
|
|
966
|
+
| `validate-brief` exits non-zero in Step 6.6 | missing frontmatter key, unlabelled augmentation, broken auto-mirroring, or codebase-scan lens out of whitelist | Read the validator's per-row message, fix the cited brief, re-run. Never print the Step 7 hand-off until it passes. |
|
|
@@ -360,7 +360,7 @@ If a run never reached Phase 7, its `team-state` lacks `durationMs`. Mark such r
|
|
|
360
360
|
3. Multiple matches → list candidates (`taskKey`, `taskType`, `updatedAt`) and ask the user to pick.
|
|
361
361
|
4. Read `historyTimelinePath` from the chosen entry.
|
|
362
362
|
|
|
363
|
-
If `task-catalog.json` is missing: `No okstra history found. Run
|
|
363
|
+
If `task-catalog.json` is missing: `No okstra history found. Run /okstra-run first.`
|
|
364
364
|
|
|
365
365
|
### time.2 — Walk runs and collect durations
|
|
366
366
|
|
|
@@ -236,7 +236,7 @@ The validator rejects any of these patterns.
|
|
|
236
236
|
|
|
237
237
|
## Section Contract (required)
|
|
238
238
|
|
|
239
|
-
The canonical template lives at
|
|
239
|
+
The canonical template lives at `~/.okstra/templates/reports/schedule.template.md`. **Read it before writing the schedule** and follow it byte-for-byte for headings.
|
|
240
240
|
|
|
241
241
|
### MUST — exact ordered heading list
|
|
242
242
|
|
|
@@ -441,14 +441,14 @@ After writing the file and before printing the completion message in Step 7, you
|
|
|
441
441
|
1. **Re-read the file** you just wrote.
|
|
442
442
|
2. **Run the validator**:
|
|
443
443
|
```bash
|
|
444
|
-
python3 validators/validate-schedule.py <output-path>
|
|
444
|
+
python3 ~/.okstra/lib/validators/validate-schedule.py <output-path>
|
|
445
445
|
```
|
|
446
446
|
3. If the validator exits non-zero, **fix the file and re-validate**. Do not print the completion message until the validator passes.
|
|
447
|
-
4. If
|
|
447
|
+
4. If `~/.okstra/lib/validators/validate-schedule.py` is not present, fall back to a manual checklist: confirm each of the 11 mandatory headings from `### MUST — exact ordered heading list` (above) appears in the file with that exact spelling, the title ends with `— Work Schedule`, and the metadata `> Generated:` block is present.
|
|
448
448
|
|
|
449
449
|
## Schedule Document Template
|
|
450
450
|
|
|
451
|
-
|
|
451
|
+
The installed template `~/.okstra/templates/reports/schedule.template.md` is the byte-for-byte SSOT for section ordering, heading spelling, table columns, and per-section HTML-comment guidance (Dependency Graph Shape A/B, Gantt rules, optional Glossary gate). Do not omit sections; if data is unavailable, render the section with `_없음_` rather than skipping it.
|
|
452
452
|
|
|
453
453
|
The two rules below complement the template — they encode COMPUTATION that the template scaffold cannot carry inline.
|
|
454
454
|
|
|
@@ -250,8 +250,8 @@ back it up as `.bak.<timestamp>` rather than overwriting silently.
|
|
|
250
250
|
## Step 4.8 (optional, opt-in): register a project PR body template
|
|
251
251
|
|
|
252
252
|
`release-handoff` fills the PR body from a template. By default it uses the
|
|
253
|
-
|
|
254
|
-
`~/.
|
|
253
|
+
installed template at
|
|
254
|
+
`~/.okstra/templates/prd/pr-body.template.md`. Most projects
|
|
255
255
|
want their own — e.g. the repo's `.github/PULL_REQUEST_TEMPLATE.md` — to
|
|
256
256
|
keep PRs consistent with what the team already merges manually.
|
|
257
257
|
|
|
@@ -14,140 +14,31 @@
|
|
|
14
14
|
"WebFetch",
|
|
15
15
|
"WebSearch",
|
|
16
16
|
"TodoWrite",
|
|
17
|
-
|
|
18
|
-
"Bash(pwd)",
|
|
19
|
-
"Bash(cd)",
|
|
20
|
-
"Bash(cd:*)",
|
|
21
|
-
|
|
22
|
-
"Bash(ls)",
|
|
23
|
-
"Bash(ls:*)",
|
|
24
|
-
"Bash(cat:*)",
|
|
25
|
-
"Bash(head:*)",
|
|
26
|
-
"Bash(tail:*)",
|
|
27
|
-
"Bash(wc:*)",
|
|
28
|
-
|
|
29
|
-
"Bash(git)",
|
|
30
|
-
"Bash(git:*)",
|
|
31
|
-
"Bash(gh)",
|
|
32
|
-
"Bash(gh:*)",
|
|
33
|
-
|
|
34
|
-
"Bash(rg:*)",
|
|
35
|
-
"Bash(grep:*)",
|
|
36
|
-
"Bash(find:*)",
|
|
37
|
-
"Bash(diff:*)",
|
|
38
|
-
|
|
39
|
-
"Bash(jq:*)",
|
|
40
|
-
"Bash(yq:*)",
|
|
41
|
-
"Bash(awk:*)",
|
|
42
|
-
"Bash(sed:*)",
|
|
43
|
-
"Bash(tr:*)",
|
|
44
|
-
"Bash(sort:*)",
|
|
45
|
-
"Bash(uniq:*)",
|
|
46
|
-
"Bash(xargs:*)",
|
|
47
|
-
|
|
48
|
-
"Bash(mkdir:*)",
|
|
49
|
-
"Bash(cp:*)",
|
|
50
|
-
"Bash(mv:*)",
|
|
51
|
-
"Bash(rm:*)",
|
|
52
|
-
"Bash(chmod:*)",
|
|
53
|
-
|
|
54
|
-
"Bash(printf:*)",
|
|
55
|
-
"Bash(echo)",
|
|
56
|
-
"Bash(echo:*)",
|
|
57
|
-
"Bash(export:*)",
|
|
58
|
-
"Bash(env)",
|
|
59
|
-
"Bash(env:*)",
|
|
60
|
-
"Bash(test:*)",
|
|
61
|
-
"Bash(true)",
|
|
62
|
-
"Bash(true:*)",
|
|
63
|
-
"Bash(false)",
|
|
64
|
-
"Bash(false:*)",
|
|
65
|
-
|
|
66
|
-
"Bash(bash:*)",
|
|
67
|
-
"Bash(sh:*)",
|
|
68
|
-
"Bash(eval:*)",
|
|
69
|
-
|
|
70
|
-
"Bash(python)",
|
|
71
|
-
"Bash(python:*)",
|
|
72
|
-
"Bash(python3)",
|
|
73
|
-
"Bash(python3:*)",
|
|
74
|
-
"Bash(pip)",
|
|
75
|
-
"Bash(pip:*)",
|
|
76
|
-
"Bash(pip3)",
|
|
77
|
-
"Bash(pip3:*)",
|
|
78
|
-
"Bash(uv:*)",
|
|
79
|
-
"Bash(poetry:*)",
|
|
80
|
-
"Bash(pytest:*)",
|
|
81
|
-
|
|
82
|
-
"Bash(node)",
|
|
83
|
-
"Bash(node:*)",
|
|
84
|
-
"Bash(npm)",
|
|
85
|
-
"Bash(npm:*)",
|
|
86
|
-
"Bash(npx)",
|
|
87
|
-
"Bash(npx:*)",
|
|
88
|
-
"Bash(pnpm)",
|
|
89
|
-
"Bash(pnpm:*)",
|
|
90
|
-
"Bash(yarn)",
|
|
91
|
-
"Bash(yarn:*)",
|
|
92
|
-
"Bash(tsc)",
|
|
93
|
-
"Bash(tsc:*)",
|
|
94
|
-
|
|
95
|
-
"Bash(cargo)",
|
|
96
|
-
"Bash(cargo:*)",
|
|
97
|
-
"Bash(rustc)",
|
|
98
|
-
"Bash(rustc:*)",
|
|
99
|
-
|
|
100
|
-
"Bash(go)",
|
|
101
|
-
"Bash(go:*)",
|
|
102
|
-
"Bash(java)",
|
|
103
|
-
"Bash(java:*)",
|
|
104
|
-
"Bash(javac)",
|
|
105
|
-
"Bash(javac:*)",
|
|
106
|
-
"Bash(mvn)",
|
|
107
|
-
"Bash(mvn:*)",
|
|
108
|
-
"Bash(gradle)",
|
|
109
|
-
"Bash(gradle:*)",
|
|
110
|
-
"Bash(./gradlew)",
|
|
111
|
-
"Bash(./gradlew:*)",
|
|
112
|
-
"Bash(make)",
|
|
113
|
-
"Bash(make:*)",
|
|
114
|
-
|
|
115
|
-
"Bash(docker)",
|
|
116
|
-
"Bash(docker:*)",
|
|
117
|
-
"Bash(docker compose)",
|
|
118
|
-
"Bash(docker compose:*)",
|
|
119
|
-
"Bash(docker-compose)",
|
|
120
|
-
"Bash(docker-compose:*)",
|
|
121
|
-
"Bash(kubectl)",
|
|
122
|
-
"Bash(kubectl:*)",
|
|
123
|
-
"Bash(helm)",
|
|
124
|
-
"Bash(helm:*)",
|
|
125
|
-
|
|
126
|
-
"Bash(curl:*)",
|
|
127
|
-
"Bash(wget:*)",
|
|
128
|
-
|
|
129
|
-
"Bash(codex)",
|
|
130
|
-
"Bash(codex:*)",
|
|
131
|
-
"Bash(codex exec:*)",
|
|
132
17
|
"Bash(okstra)",
|
|
133
18
|
"Bash(okstra:*)",
|
|
134
19
|
"Bash(npx okstra@latest:*)",
|
|
135
20
|
"Bash(npx -y okstra@latest:*)",
|
|
136
21
|
"Bash($HOME/.okstra/bin/:*)",
|
|
137
|
-
"Bash(
|
|
138
|
-
"Bash(
|
|
139
|
-
|
|
22
|
+
"Bash(codex)",
|
|
23
|
+
"Bash(codex:*)",
|
|
140
24
|
"Bash(gemini)",
|
|
141
25
|
"Bash(gemini:*)",
|
|
142
|
-
|
|
143
26
|
"Bash(claude)",
|
|
144
|
-
"Bash(claude:*)"
|
|
145
|
-
|
|
146
|
-
"mcp__test-context7__resolve-library-id",
|
|
147
|
-
"mcp__test-context7__query-docs"
|
|
27
|
+
"Bash(claude:*)"
|
|
148
28
|
]
|
|
149
29
|
},
|
|
150
30
|
"hooks": {
|
|
31
|
+
"PreToolUse": [
|
|
32
|
+
{
|
|
33
|
+
"matcher": "Write|Edit|MultiEdit|NotebookEdit",
|
|
34
|
+
"hooks": [
|
|
35
|
+
{
|
|
36
|
+
"type": "command",
|
|
37
|
+
"command": "bash \"$HOME/.okstra/prompts/okstra-coding-preflight/scripts/preedit-check.sh\""
|
|
38
|
+
}
|
|
39
|
+
]
|
|
40
|
+
}
|
|
41
|
+
],
|
|
151
42
|
"SessionEnd": [
|
|
152
43
|
{
|
|
153
44
|
"hooks": [
|
|
@@ -25,7 +25,13 @@ Checks performed per brief file:
|
|
|
25
25
|
own `brief-id`.
|
|
26
26
|
9. `reporter-confirmations` consistency: when `complete`, every
|
|
27
27
|
`intent-check:` and `conversion-block:` row in Open Questions MUST
|
|
28
|
-
carry a `[CONFIRMED YYYY-MM-DD → RC-N]` marker
|
|
28
|
+
carry a `[CONFIRMED YYYY-MM-DD → RC-N]` marker; when `partial`, at
|
|
29
|
+
least one such row MUST carry the marker (use `skipped` when nothing
|
|
30
|
+
was answered).
|
|
31
|
+
10. `scope` is one of {reporter-input, codebase} (absent ⇒ reporter-input).
|
|
32
|
+
11. codebase-scan variant (`scope: codebase`): the Scan Scope section is
|
|
33
|
+
non-empty and Priority Lenses lists 1–4 values from the lens whitelist
|
|
34
|
+
(`scripts/okstra_ctl/improvement_lenses.py` SSOT).
|
|
29
35
|
|
|
30
36
|
Exit code 0 on PASS, 1 on FAIL.
|
|
31
37
|
"""
|
|
@@ -38,6 +44,19 @@ import sys
|
|
|
38
44
|
from pathlib import Path
|
|
39
45
|
from typing import Iterable
|
|
40
46
|
|
|
47
|
+
# scripts/ (repo) or python/ (installed under ~/.okstra/lib) is not a package;
|
|
48
|
+
# insert whichever exists so okstra_ctl is importable for the lens whitelist.
|
|
49
|
+
_VALIDATORS_DIR = Path(__file__).resolve().parent
|
|
50
|
+
for _ssot_dir in (_VALIDATORS_DIR.parent / "scripts", _VALIDATORS_DIR.parent / "python"):
|
|
51
|
+
if _ssot_dir.is_dir() and str(_ssot_dir) not in sys.path:
|
|
52
|
+
sys.path.insert(0, str(_ssot_dir))
|
|
53
|
+
|
|
54
|
+
from okstra_ctl.improvement_lenses import (
|
|
55
|
+
LENSES,
|
|
56
|
+
MAX_PRIORITY_LENSES,
|
|
57
|
+
MIN_PRIORITY_LENSES,
|
|
58
|
+
)
|
|
59
|
+
|
|
41
60
|
REQUIRED_FRONTMATTER_KEYS = {
|
|
42
61
|
"type",
|
|
43
62
|
"brief-id",
|
|
@@ -68,6 +87,8 @@ AUGMENTATION_LABELS = {
|
|
|
68
87
|
|
|
69
88
|
REPORTER_CONFIRMATION_VALUES = {"complete", "partial", "pending", "skipped"}
|
|
70
89
|
|
|
90
|
+
SCOPE_VALUES = {"reporter-input", "codebase"}
|
|
91
|
+
|
|
71
92
|
|
|
72
93
|
def parse_frontmatter(text: str) -> tuple[dict[str, str], int]:
|
|
73
94
|
"""Return (frontmatter dict, line after closing `---`)."""
|
|
@@ -173,6 +194,68 @@ def parse_augmentation_label(entry: str) -> tuple[str | None, str]:
|
|
|
173
194
|
return None, stripped
|
|
174
195
|
|
|
175
196
|
|
|
197
|
+
def meaningful_bullets(text: str, heading: str) -> list[str]:
|
|
198
|
+
"""Real `- ` bullets under a heading, excluding placeholders/template scaffold."""
|
|
199
|
+
body = section_body(text, heading)
|
|
200
|
+
out: list[str] = []
|
|
201
|
+
for line in body.splitlines():
|
|
202
|
+
stripped = line.strip()
|
|
203
|
+
if not stripped.startswith("- "):
|
|
204
|
+
continue
|
|
205
|
+
content = stripped[2:].strip()
|
|
206
|
+
if is_placeholder(content) or is_template_example(content):
|
|
207
|
+
continue
|
|
208
|
+
out.append(content.strip("`"))
|
|
209
|
+
return out
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def priority_lens_values(text: str) -> list[str]:
|
|
213
|
+
"""Lens tokens from the `## Priority Lenses` bullets (`- <lens>: <rationale>`)."""
|
|
214
|
+
return [
|
|
215
|
+
bullet.split(":", 1)[0].strip()
|
|
216
|
+
for bullet in meaningful_bullets(text, "Priority Lenses")
|
|
217
|
+
]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def check_codebase_scope(text: str, errors: list[str]) -> None:
|
|
221
|
+
"""codebase-scan variant: Scan Scope non-empty, Priority Lenses ⊆ whitelist."""
|
|
222
|
+
if not meaningful_bullets(text, "Scan Scope"):
|
|
223
|
+
errors.append("scope is 'codebase' but the Scan Scope section has no entries")
|
|
224
|
+
lenses = priority_lens_values(text)
|
|
225
|
+
if not (MIN_PRIORITY_LENSES <= len(lenses) <= MAX_PRIORITY_LENSES):
|
|
226
|
+
errors.append(
|
|
227
|
+
f"Priority Lenses must list {MIN_PRIORITY_LENSES}–{MAX_PRIORITY_LENSES} "
|
|
228
|
+
f"lens(es), found {len(lenses)}"
|
|
229
|
+
)
|
|
230
|
+
unknown = [lens for lens in lenses if lens not in LENSES]
|
|
231
|
+
if unknown:
|
|
232
|
+
errors.append(
|
|
233
|
+
f"Priority Lenses contains values outside the lens whitelist: {unknown} "
|
|
234
|
+
f"(allowed: {sorted(LENSES)})"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def check_reporter_confirmations(
|
|
239
|
+
rc_status: str | None, reporter_rows: list[str], errors: list[str]
|
|
240
|
+
) -> None:
|
|
241
|
+
"""`complete` ⇒ every reporter row confirmed; `partial` ⇒ at least one."""
|
|
242
|
+
confirmed = [row for row in reporter_rows if "[CONFIRMED" in row]
|
|
243
|
+
if rc_status == "complete":
|
|
244
|
+
unconfirmed = [row for row in reporter_rows if "[CONFIRMED" not in row]
|
|
245
|
+
if unconfirmed:
|
|
246
|
+
errors.append(
|
|
247
|
+
f"reporter-confirmations is 'complete' but {len(unconfirmed)} "
|
|
248
|
+
f"intent-check:/conversion-block: row(s) lack a [CONFIRMED …] "
|
|
249
|
+
f"marker (e.g. {unconfirmed[0]!r})"
|
|
250
|
+
)
|
|
251
|
+
elif rc_status == "partial" and reporter_rows and not confirmed:
|
|
252
|
+
errors.append(
|
|
253
|
+
"reporter-confirmations is 'partial' but no intent-check:/"
|
|
254
|
+
"conversion-block: row carries a [CONFIRMED …] marker "
|
|
255
|
+
"(use 'skipped' when nothing was answered)"
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
176
259
|
def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
177
260
|
text = path.read_text(encoding="utf-8")
|
|
178
261
|
errors: list[str] = []
|
|
@@ -202,6 +285,15 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
|
202
285
|
f"{fm.get('reporter-confirmations')!r}"
|
|
203
286
|
)
|
|
204
287
|
|
|
288
|
+
scope = fm.get("scope", "reporter-input")
|
|
289
|
+
if scope not in SCOPE_VALUES:
|
|
290
|
+
errors.append(
|
|
291
|
+
f"frontmatter scope must be one of {sorted(SCOPE_VALUES)} "
|
|
292
|
+
f"(or absent ⇒ reporter-input), got {scope!r}"
|
|
293
|
+
)
|
|
294
|
+
if scope == "codebase":
|
|
295
|
+
check_codebase_scope(text, errors)
|
|
296
|
+
|
|
205
297
|
# 2. brief-id matches filename stem
|
|
206
298
|
stem = path.stem
|
|
207
299
|
if fm.get("brief-id") and fm["brief-id"] != stem:
|
|
@@ -306,21 +398,12 @@ def validate_brief(path: Path, briefs_root: Path) -> list[str]:
|
|
|
306
398
|
f"({brief_id!r})"
|
|
307
399
|
)
|
|
308
400
|
|
|
309
|
-
# 9. reporter-confirmations consistency
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if "[CONFIRMED" not in row
|
|
316
|
-
]
|
|
317
|
-
if unconfirmed:
|
|
318
|
-
sample = unconfirmed[0]
|
|
319
|
-
errors.append(
|
|
320
|
-
f"reporter-confirmations is 'complete' but {len(unconfirmed)} "
|
|
321
|
-
f"intent-check:/conversion-block: row(s) lack a [CONFIRMED …] "
|
|
322
|
-
f"marker (e.g. {sample!r})"
|
|
323
|
-
)
|
|
401
|
+
# 9. reporter-confirmations consistency (complete / partial)
|
|
402
|
+
check_reporter_confirmations(
|
|
403
|
+
fm.get("reporter-confirmations"),
|
|
404
|
+
intent_check_rows + conversion_block_rows,
|
|
405
|
+
errors,
|
|
406
|
+
)
|
|
324
407
|
|
|
325
408
|
return errors
|
|
326
409
|
|
/package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/architectures/hexagonal.md
RENAMED
|
File without changes
|
|
File without changes
|
/package/runtime/prompts/{coding-preflight → okstra-coding-preflight}/frameworks/node-server.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|