okstra 0.138.0 → 0.139.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.
Files changed (28) hide show
  1. package/README.md +1 -0
  2. package/docs/architecture.md +4 -4
  3. package/docs/cli.md +4 -3
  4. package/docs/for-ai/README.md +2 -0
  5. package/docs/for-ai/skills/okstra-code-review.md +57 -0
  6. package/docs/project-structure-overview.md +5 -0
  7. package/package.json +1 -1
  8. package/runtime/BUILD.json +2 -2
  9. package/runtime/prompts/coding-preflight/architectures/hexagonal.md +34 -0
  10. package/runtime/prompts/coding-preflight/clean-code.md +54 -0
  11. package/runtime/prompts/coding-preflight/languages/javascript-typescript.md +11 -0
  12. package/runtime/prompts/lead/okstra-lead-contract.md +1 -1
  13. package/runtime/prompts/profiles/_implementation-diff-review.md +8 -3
  14. package/runtime/prompts/profiles/_implementation-self-check.md +2 -1
  15. package/runtime/prompts/profiles/_implementation-verifier.md +12 -3
  16. package/runtime/prompts/profiles/implementation-planning.md +1 -0
  17. package/runtime/prompts/profiles/release-handoff.md +2 -1
  18. package/runtime/python/okstra_ctl/code_review_paths.py +64 -0
  19. package/runtime/python/okstra_ctl/code_review_target.py +152 -0
  20. package/runtime/python/okstra_ctl/worktree.py +3 -2
  21. package/runtime/python/okstra_project/__init__.py +2 -0
  22. package/runtime/python/okstra_project/state.py +142 -0
  23. package/runtime/skills/okstra-code-review/SKILL.md +180 -0
  24. package/runtime/skills/okstra-code-review/references/census-rules.md +100 -0
  25. package/runtime/skills/okstra-code-review/references/review-calibration.md +86 -0
  26. package/src/cli-registry.mjs +7 -0
  27. package/src/commands/inspect/code-review.mjs +35 -0
  28. package/src/lib/skill-catalog.mjs +1 -0
@@ -0,0 +1,86 @@
1
+ # Review calibration — verdicts, severity, and the report
2
+
3
+ Read this before verdicting. It defines the shape of an answer, not the rules — the rules are in the coding-preflight pack paths your brief listed, and you read those directly.
4
+
5
+ ## One verdict per cell
6
+
7
+ Return verdicts in census order, one per cell. A clean cell is a single line — do not pad it with prose:
8
+
9
+ ```
10
+ - cell: src/orders/pricing.py × structural — verdict: clean
11
+ ```
12
+
13
+ A cell covers its whole rule group, so it can carry several findings from several rules; repeat the cell once per finding and name the violated rule in each:
14
+
15
+ ```
16
+ - cell: src/orders/pricing.py × structural
17
+ verdict: finding
18
+ rule: DRY
19
+ line: 42
20
+ severity: must-fix
21
+ snippet: `discount = subtotal * 0.15 if tier == "gold" else 0`
22
+ note: The same tier→rate table is already in `domain/tiers.py:18`; a rate change now has two homes. Call the existing lookup instead of re-expressing it here.
23
+ ```
24
+
25
+ Fields: `cell` (`target × axis`, exactly as the census wrote it — the axis is the rule group, and there is never one cell per individual rule), `verdict` (`clean` | `finding`), `rule` (the specific rule this finding violates, spelled as its pack spells it; omitted on `clean`), `line` (a line **this diff changed**), `severity`, `snippet` (the quoted changed line), `note` (1–2 sentences: what is wrong plus a concrete fix).
26
+
27
+ There is no length budget. Dropping a real finding to stay brief is the failure this review exists to prevent, and a missing cell is re-dispatched as unfinished work, never read as a clean.
28
+
29
+ ## Severity and points
30
+
31
+ | Severity | Meaning | Points |
32
+ |---|---|---|
33
+ | `must-fix` | a defect, or a rule violation that will cost real work later — wrong behavior, a swallowed error, a duplicated decision, a self-mocked test that proves nothing | 3 |
34
+ | `should-fix` | a clear rule violation with no immediate breakage — a name that misleads, a function that fails the plain-English test, business logic in the wrong layer | 2 |
35
+ | `nit` | real but small; the fix is cheap and obviously better | 1 |
36
+
37
+ Grade the defect, not your confidence. If you are not confident, the verdict is `clean` — see the hedge test below.
38
+
39
+ ## When `clean` is the right verdict
40
+
41
+ `clean` is a result, not a concession. Return it when:
42
+
43
+ - **The defect sits only on lines this diff did not change.** Findings anchor on changed lines; pre-existing warts are not this change's problem.
44
+ - **The rule does not apply to this cell.** The census over-includes on purpose, so cells where a rule turns out to be irrelevant are expected — that is exactly what a `clean` verdict records.
45
+ - **The change is mechanical for this rule.** A signature-only or type-annotation-only edit does not reopen a function's body-level rules; a function the diff neither added nor renamed does not reopen its name.
46
+
47
+ **The hedge test breaks a borderline `must-fix` or `should-fix`.** At those two severities the finding is one you would defend without qualifiers. If your draft note reaches for a softener that questions whether the point is worth raising at all — "marginal", "arguably", "if you are touching it anyway" — you are looking at a `nit` or at nothing: downgrade it or verdict `clean`, but never ship a hedged `should-fix`.
48
+
49
+ The test does not empty the `nit` tier. A `nit` says the defect is small because that is the tier's definition, not because the reviewer is hedging — it still names a concrete defect on a changed line and a concrete fix.
50
+
51
+ ## Every finding carries its fix
52
+
53
+ A finding without a fix is a complaint. Specifically:
54
+
55
+ - **Readability findings** carry a pseudocode sketch — 4 to 6 lines that convey the intended reading experience, not a full refactor. If the sketch does not make a reader think "yes, that reads like prose", rewrite the sketch.
56
+ - **Naming findings** carry a concrete alternative name, and one clause saying what the new name encodes that the old one did not.
57
+ - **Structural findings** name the destination: which layer, module, or existing symbol the code should move to or call.
58
+
59
+ ## Test files
60
+
61
+ Loose typing in test files (`any`, dynamic casts, untyped fixtures) is a **typing** carve-out only — never flag it on typing grounds alone. It stops being a carve-out the moment the cast is the vehicle for another violation: reaching into privates, or stubbing the unit under test. The self-mock rules apply through the cast exactly as they do without it — what is judged is the behavior, not the cast.
62
+
63
+ ## The report
64
+
65
+ Merged by the orchestrator, written to `reviewPath`. Sections, in this order, empty ones omitted except `Coverage` and `Score`:
66
+
67
+ ```
68
+ ## Coverage
69
+ ## Must-fix
70
+ ## Should-fix
71
+ ## Nits
72
+ ## Score
73
+ ```
74
+
75
+ - **Coverage** — one or two lines: N changed files → S `structural` / F `semantic` / T `state-and-tests` / G `general` cells, all verdicted; M files excluded with reasons; the applied packs, and any pack that was unavailable. The four counts are cell counts at the census's granularity — one cell per target per axis.
76
+ - **Findings** — each one opens with `` `path/to/file.py:42` `` + the verdict's `rule` name + severity + points, then the snippet as a blockquote, then the 1–2 sentence note with its fix.
77
+ - **Score** — every finding gets a row, and the table is emitted even when there are none, with a single total row reading 0:
78
+
79
+ ```
80
+ | # | Location | Rule | Severity | Points |
81
+ |---|---|---|---|---|
82
+ | 1 | `src/orders/pricing.py:42` | DRY | must-fix | 3 |
83
+ | | | | **Total** | **3** |
84
+ ```
85
+
86
+ The report's prose is written in Korean. Paths, identifiers, rule names, and quoted code stay verbatim.
@@ -145,6 +145,13 @@ export const COMMAND_REGISTRY = [
145
145
  category: "introspection",
146
146
  summary: ["Resolve a bare task-id to candidate task-keys from the catalog"],
147
147
  },
148
+ {
149
+ name: "code-review",
150
+ module: "./commands/inspect/code-review.mjs",
151
+ export: "run",
152
+ category: "introspection",
153
+ summary: ["Resolve a code review's diff range and result file path"],
154
+ },
148
155
  {
149
156
  name: "set-work-status",
150
157
  module: "./commands/inspect/set-work-status.mjs",
@@ -0,0 +1,35 @@
1
+ import { runPythonModule } from "../../lib/python-helper.mjs";
2
+
3
+ const USAGE = `okstra code-review — resolve a code review's diff range and result path
4
+
5
+ Usage:
6
+ okstra code-review target --task-key <k> --stage <N> [--project-root <dir>] [--cwd <dir>] --json
7
+ okstra code-review target --branch <name> [--base <ref>] [--date <YYYY-MM-DD>] [--project-root <dir>] --json
8
+
9
+ Output: JSON { ok, mode, worktreePath, branch, baseCommit, headCommit,
10
+ reviewPath, round }. Stage mode reads the base commit from the stage
11
+ registry row; branch mode falls back to the merge-base with the default
12
+ branch. An empty worktreePath means the review reads the branch ref
13
+ instead of a checked-out tree. This is read-only — it never creates
14
+ directories or files.
15
+ `;
16
+
17
+ export async function run(args) {
18
+ if (args.includes("--help") || args.includes("-h")) {
19
+ process.stdout.write(USAGE);
20
+ return 0;
21
+ }
22
+ const [subcommand, ...rest] = args;
23
+ if (subcommand !== "target") {
24
+ process.stderr.write(
25
+ `error: unknown subcommand '${subcommand ?? ""}'\n\n${USAGE}`,
26
+ );
27
+ return 2;
28
+ }
29
+ const result = await runPythonModule({
30
+ module: "okstra_ctl.code_review_target",
31
+ args: rest,
32
+ stdio: "inherit-stdout",
33
+ });
34
+ return result.code;
35
+ }
@@ -17,6 +17,7 @@ export const USER_SKILL_NAMES = Object.freeze([
17
17
  "okstra-container-build",
18
18
  "okstra-pr-gen",
19
19
  "okstra-user-response",
20
+ "okstra-code-review",
20
21
  ]);
21
22
 
22
23
  // Names okstra used to install as skills before they were renamed or moved