@r1z4x/tezgah 1.0.0 → 1.1.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/CHANGELOG.md +71 -1
- package/MANIFEST +8 -0
- package/bin/tezgah-agents +10 -1
- package/bin/tezgah-docs +1 -1
- package/bin/tezgah-doctor +11 -0
- package/bin/tezgah-research +31 -14
- package/bin/tezgah-setup +154 -29
- package/bin/tezgah-skill +59 -0
- package/bin/tezgah-skill.py +59 -0
- package/bin/tezgah-status +62 -1
- package/bin/tezgah-triage +5 -3
- package/docs/README.md +1 -0
- package/docs/architecture.md +5 -2
- package/docs/citations-baseline.json +1 -1
- package/docs/clarity-baseline.json +9 -9
- package/docs/contract.md +68 -26
- package/docs/environment.md +6 -3
- package/docs/evidence.md +7 -0
- package/docs/gate.md +13 -7
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +48 -1
- package/docs/index.json +29 -1
- package/docs/judge.md +61 -55
- package/docs/models.md +14 -8
- package/docs/operations.md +10 -5
- package/docs/orca.md +126 -0
- package/docs/research.md +38 -3
- package/docs/skills.md +74 -0
- package/docs/status-line.md +3 -1
- package/docs/testing.md +33 -21
- package/docs/vision.md +12 -8
- package/hooks/projects-pretooluse.py +17 -5
- package/hooks/tezgah_agents.py +135 -13
- package/hooks/tezgah_components.py +3 -0
- package/hooks/tezgah_context.py +65 -22
- package/hooks/tezgah_gate.py +133 -17
- package/hooks/tezgah_integrity.py +60 -13
- package/hooks/tezgah_judge.py +239 -56
- package/hooks/tezgah_models.py +41 -5
- package/hooks/tezgah_orca.py +144 -0
- package/hooks/tezgah_paths.py +40 -6
- package/hooks/tezgah_policy.py +109 -35
- package/hooks/tezgah_research.py +104 -8
- package/hooks/tezgah_skill_pick.py +459 -8
- package/hooks/tezgah_stack.py +382 -0
- package/hooks/tezgah_stopspec.py +2 -2
- package/hosts/codex/hook.py +7 -1
- package/hosts/cursor/hook.py +7 -4
- package/hosts/omp/hook.py +12 -6
- package/hosts/omp/tezgah-hook.ts.in +20 -7
- package/hosts/opencode/plugins/tezgah.js +18 -9
- package/output-styles/tezgah.md +34 -19
- package/package.json +1 -1
- package/skills/i-have-adhd/SKILL.md +2 -2
- package/skills/research/SKILL.md +60 -17
- package/skills/tezgah-contract/SKILL.md +55 -13
package/hooks/tezgah_policy.py
CHANGED
|
@@ -273,13 +273,25 @@ discovery, review, impact analysis (research goes to OpenResearch - see the
|
|
|
273
273
|
Research section - with a host subagent only as the fallback when `orx` is
|
|
274
274
|
absent). Subtasks with no data dependency
|
|
275
275
|
between them MUST be spawned in ONE message so they run in parallel; dependent
|
|
276
|
-
ones run sequentially, each briefed with the previous result.
|
|
276
|
+
ones run sequentially, each briefed with the previous result. Fanning out is the
|
|
277
|
+
default, not a favour the user has to ask for: two or more independent items -
|
|
278
|
+
a list, several files or areas, todo entries that share nothing - go to one
|
|
279
|
+
subagent each, and every writing slice gets its own branch and checkout (see
|
|
280
|
+
below), never the main checkout. The
|
|
281
|
+
orchestrator is the main thread itself: it does not wait idle on a slice while
|
|
282
|
+
independent work remains. Fan out only when
|
|
277
283
|
the subtasks share no mutable file and no interface: if two of them would edit
|
|
278
284
|
the same file, or one's answer decides the other's, keep them in one context or
|
|
279
285
|
sequence them. The shared artifact is the coordination channel, not chatter -
|
|
280
286
|
naming a lead coordinates nothing by itself. If the work
|
|
281
287
|
cannot be split - one file, one bounded change, a strictly serial chain - do it
|
|
282
288
|
directly. Never spawn a subagent whose briefing is bigger than the work.
|
|
289
|
+
A slice that needs its own checkout: inside Orca (the session-start `Orca:`
|
|
290
|
+
line) create it with `orca worktree create --name <slug> --parent-worktree
|
|
291
|
+
active --json` and track it with `orca worktree ps --json` or `tezgah-status
|
|
292
|
+
--orca` - a raw `git worktree add` gets no card, terminal tracking or agent
|
|
293
|
+
status in Orca; outside Orca, `git worktree add`. Either way the brief names
|
|
294
|
+
absolute paths under that checkout.
|
|
283
295
|
Routing: `{ROUTE_BIN} "<brief>"` names the worker - tezgah-cheap, -standard or
|
|
284
296
|
-frontier (overrides for stored data, credentials, security first; then a Jev
|
|
285
297
|
judgement; the static phase table with no key). A worker that answers
|
|
@@ -400,12 +412,30 @@ experiments/variants, or producing a research artifact (report, figure, dataset)
|
|
|
400
412
|
It is NOT "where is X defined" or "who calls Y" - that is code discovery and
|
|
401
413
|
stays on the codegraph index.
|
|
402
414
|
|
|
415
|
+
**Lock the line to the question asked.** Restate the user's question verbatim as
|
|
416
|
+
the line's scope (`--ask`) and name what is in it and what is out (`--in`,
|
|
417
|
+
`--out`: state.json `scope`). Every finding answers an in-scope item - a claim's
|
|
418
|
+
`trace` names its `S<n>`, and `{RESEARCH_BIN} check` refuses one that names none -
|
|
419
|
+
and a finding that answers nothing asked is dropped, not reported as a bonus. Do
|
|
420
|
+
not widen the question, add sub-questions or chase an adjacent topic; a tangent
|
|
421
|
+
worth having is one line under Open questions for the user to pick. The question
|
|
422
|
+
picks the method, not the tooling: a qualitative, conceptual or reference question
|
|
423
|
+
is a source review (`--method qualitative`: no metric, no experiment), and a
|
|
424
|
+
metric, a locked evaluation and the experiment tree belong only to a question
|
|
425
|
+
whose answer is a measurement (`--method measured`). A plain factual or reference
|
|
426
|
+
question gets no line at all: answer it in the reply from primary sources. Depth
|
|
427
|
+
is the primary source read, not more breadth - the spec, the paper, the code, the
|
|
428
|
+
vendor's own documentation - each finding citing the source it rests on and
|
|
429
|
+
stating its confidence (high, medium or low, and why); a summary of summaries is
|
|
430
|
+
not an answer.
|
|
431
|
+
|
|
403
432
|
When the task is research and the OpenResearch CLI (`{ORX_BIN}`) is installed,
|
|
404
433
|
drive it through that CLI instead of ad-hoc local scripting. Load the operating
|
|
405
434
|
manual first: the `orx` skill if the host has it, otherwise run
|
|
406
|
-
`{ORX_BIN} skill` from the shell; then
|
|
407
|
-
|
|
408
|
-
`{ORX_BIN} skill evidence
|
|
435
|
+
`{ORX_BIN} skill` from the shell; then the module the method needs -
|
|
436
|
+
`{ORX_BIN} skill lit-review` for sources, `{ORX_BIN} skill experiment-tree` and
|
|
437
|
+
`{ORX_BIN} skill evidence` for a measured line. On a measured line its cardinal
|
|
438
|
+
rules are not style preferences - they
|
|
409
439
|
are what keeps results comparable, and breaking one silently invalidates the run:
|
|
410
440
|
never edit a node once a run has answered it (branch a child instead); the run
|
|
411
441
|
command and environment are a fixed contract identical on every node; vary the
|
|
@@ -420,7 +450,8 @@ afterwards - and `{RESEARCH_BIN} check` refuses a claim that claims a wider scop
|
|
|
420
450
|
than the rows it rests on.
|
|
421
451
|
|
|
422
452
|
If the CLI is not installed, say the research tooling is unavailable and do not
|
|
423
|
-
improvise its protocol; fall back to a host subagent and say so
|
|
453
|
+
improvise its protocol; fall back to a host subagent and say so, briefed with the
|
|
454
|
+
question verbatim, its in- and out-of-scope items and its method. Kill switch:
|
|
424
455
|
`research-off`.
|
|
425
456
|
|
|
426
457
|
Keep the line auditable in the repository, not in your head:
|
|
@@ -437,11 +468,13 @@ into a refusal. A report owes an independent review (`reviewer` is not the
|
|
|
437
468
|
construct and conclusion validity threats, whatever `phase` says. `state.json`
|
|
438
469
|
`deliverable` names what the line delivers and lists the ask's items, each
|
|
439
470
|
answered in the report, a decision or a claim `trace`, or written `A<n> not
|
|
440
|
-
delivered: <reason>`. A design
|
|
471
|
+
delivered: <reason>`. A design or plan the user will choose between is produced
|
|
472
|
+
as >= 3 variants in
|
|
441
473
|
`decisions/<id>/` - `criteria.json` committed before any cell, `variants.jsonl`
|
|
442
474
|
(V0 the status quo), cells appended by `{RESEARCH_BIN} compare`, `decision.md`
|
|
443
475
|
naming the chosen id, each rejected id with the criterion that ruled it out, and a
|
|
444
|
-
`flip:` condition; a new version of a
|
|
476
|
+
`flip:` condition (an analysis answers its question once); a new version of a
|
|
477
|
+
line is opened `init --supersedes <old>` and
|
|
445
478
|
carries the old one as a variant. Code or config variants are sibling orx nodes
|
|
446
479
|
under one head node. Literature is found with `{ORX_BIN} discover` and read with
|
|
447
480
|
`{ORX_BIN} paper`; an agent's summary is indexed as `agent-report` and never alone
|
|
@@ -602,7 +635,7 @@ prefers shell tools; that preference does NOT cover definitions, callers, or
|
|
|
602
635
|
blast radius - those go to the graph. The graph answers from a parsed call
|
|
603
636
|
graph, so it beats text search on renames, dynamic dispatch and cross-file
|
|
604
637
|
callers. On Claude a PreToolUse hook denies the Explore subagent here and nudges
|
|
605
|
-
the first
|
|
638
|
+
the first code-symbol search per session toward the graph. The MCP tool's
|
|
606
639
|
schema is deferred: on the FIRST code-discovery step of the session load it -
|
|
607
640
|
ToolSearch("select:mcp__codegraph__codegraph_explore,mcp__plugin_tezgah_codegraph__codegraph_explore")
|
|
608
641
|
(the plain name on a checkout's own server, the `plugin_tezgah_` one on a plugin
|
|
@@ -645,27 +678,40 @@ it - no apology theater, no self-justifying phrasing.
|
|
|
645
678
|
**Ponytail (minimal code).** Laziest solution that works: YAGNI -> reuse an
|
|
646
679
|
existing helper -> stdlib -> native platform feature -> installed dependency ->
|
|
647
680
|
one line -> minimum code. No unrequested abstractions, no scaffolding "for
|
|
648
|
-
later", shortest working diff. Trace the
|
|
649
|
-
simplify away validation, error handling,
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
681
|
+
later", deletion over addition, fewest files, shortest working diff. Trace the
|
|
682
|
+
problem fully before climbing; never simplify away validation, error handling,
|
|
683
|
+
security, accessibility or anything requested, and build the full version
|
|
684
|
+
without re-arguing when the user insists. Bug fix = root cause where all
|
|
685
|
+
callers route through. A deliberate corner cut gets a `ponytail:` comment
|
|
686
|
+
naming the ceiling. Non-trivial logic leaves ONE runnable check (an assert
|
|
687
|
+
self-check or one small test); a one-liner needs none. A complex ask ships the
|
|
688
|
+
lazy version and names the fuller one in the same reply instead of stalling.
|
|
689
|
+
Reply: code first, then at most three lines - what was skipped, when to add it.
|
|
690
|
+
In force from the first turn; levels lite|full|ultra are set with `tezgah-pony`
|
|
691
|
+
and ride the per-turn reminder when not `full`; read the full `ponytail` skill
|
|
692
|
+
from the router for the level table and examples. Off: "stop ponytail".
|
|
655
693
|
|
|
656
694
|
**Output shape: ADHD-friendly.** The answer or the next action is on the first
|
|
657
|
-
line
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
695
|
+
line - a command, path, snippet or the decision itself, never context or the
|
|
696
|
+
question restated - prose after it. Multi-step work is a numbered list, one
|
|
697
|
+
bounded action per step, and while it is in flight its position is restated in
|
|
698
|
+
one line - the todo list is that source, never re-narrate the plan. End with one
|
|
699
|
+
concrete next step. Finish the issue in hand before raising a second one; an
|
|
700
|
+
error states location, cause and fix with no drama, naming a cause only when the
|
|
701
|
+
evidence identifies it (else what is known and what would confirm it); after a
|
|
702
|
+
change say what now works. A list shows at most five items, ranked, the rest
|
|
703
|
+
kept in reserve rather than dropped - presentation only, never the analysis. An
|
|
663
704
|
estimate is in concrete units and marked as an estimate, never presented as a
|
|
664
705
|
measurement. No preamble, no recap, no closer, and a question the reader raises
|
|
665
|
-
mid-work is answered rather than deferred as the second issue.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
the
|
|
706
|
+
mid-work is answered rather than deferred as the second issue. Break the shape
|
|
707
|
+
only where it would delete the answer: an explanation runs as long as it needs,
|
|
708
|
+
with headers; an options question gets two to four ranked options,
|
|
709
|
+
recommendation first; after three "still broken" turns, name the suspect
|
|
710
|
+
assumption and ask one diagnostic question. Before sending, delete an opening
|
|
711
|
+
sentence that announces, a closing one that recaps or asks "anything else?", any
|
|
712
|
+
by-the-way sidebar and any hedge that carries no real uncertainty. In force from
|
|
713
|
+
the first answer; read the full `i-have-adhd` skill from the router for the
|
|
714
|
+
examples. Off: `tezgah-adhd off`, or the repo's `.no-adhd`.
|
|
669
715
|
|
|
670
716
|
**Deliver the whole ask; never the shortcut.** The request defines the
|
|
671
717
|
deliverable: every named item is in scope until the user says otherwise, and the
|
|
@@ -756,19 +802,30 @@ Off: `consult-off`.
|
|
|
756
802
|
|
|
757
803
|
**Research: route it to OpenResearch.** When a task is research - a literature
|
|
758
804
|
or reference review, forming and testing hypotheses, running or comparing
|
|
759
|
-
experiments, producing a research artifact -
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
805
|
+
experiments, producing a research artifact - lock it to the question asked:
|
|
806
|
+
restate the user's question verbatim as the scope, name what is in and out of it
|
|
807
|
+
(`init --ask --in --out`), and drop every finding that does not answer an
|
|
808
|
+
in-scope item; never widen it or chase an adjacent topic. The question picks the
|
|
809
|
+
method: a qualitative or reference question is a source review (`--method
|
|
810
|
+
qualitative`, no metric, no experiment); metrics and the experiment tree only
|
|
811
|
+
when the answer is a measurement (`--method measured`). A plain factual or
|
|
812
|
+
reference question gets no line and no orx experiment: answer it in the reply.
|
|
813
|
+
Depth means primary sources read and cited, each finding with its confidence,
|
|
814
|
+
not a thin summary. Drive the work through the OpenResearch CLI (`{ORX_BIN}`)
|
|
815
|
+
and load its manual first (`{ORX_BIN} skill`; `lit-review` for sources,
|
|
816
|
+
`experiment-tree` for a measured line) instead of improvising the protocol.
|
|
817
|
+
Plain code discovery stays on the code graph, not OpenResearch.
|
|
818
|
+
At most one line is open at a time:
|
|
763
819
|
while one is under way - not `concluded`, or carrying a live proposal, an unrun
|
|
764
820
|
experiment, a missing report or review, an unlabelled review finding - no new line
|
|
765
821
|
opens until it is closed or `{RESEARCH_BIN} close <slug> --limit "<reason>"` records
|
|
766
822
|
what is left. `{RESEARCH_BIN} init` refuses on that rule and names each open line;
|
|
767
823
|
`--allow-open "<reason>"` passes it (never beside a line `check` fails) and logs why.
|
|
768
|
-
A
|
|
769
|
-
first (`decisions/`);
|
|
824
|
+
A design or plan the user will choose between is >= 3 variants compared under
|
|
825
|
+
criteria committed first (`decisions/`); an analysis answers its question once.{OPEN_LINES}
|
|
770
826
|
If the CLI is not installed, say the research tooling is unavailable and fall
|
|
771
|
-
back to a host subagent
|
|
827
|
+
back to a host subagent briefed with the question verbatim, its in/out scope and
|
|
828
|
+
its method. Off: `research-off`.
|
|
772
829
|
|
|
773
830
|
**Product analysis: five axes, one evidence class per finding.** A product
|
|
774
831
|
question - "analyse this product", "why is retention falling", "which feature
|
|
@@ -826,7 +883,9 @@ Never install, upgrade, restart or kill anything for tezgah, and never open an
|
|
|
826
883
|
issue for one of its tools mid-session. Name a missing capability in one line,
|
|
827
884
|
use the documented fallback (grep/find, or the second opinion skipped), and carry
|
|
828
885
|
on with the task in hand. Tezgah maintenance is in scope when the user asks for
|
|
829
|
-
it, or when the repo IS the tezgah checkout.
|
|
886
|
+
it, or when the repo IS the tezgah checkout. Before working a topic an
|
|
887
|
+
installed skill covers, run `tezgah-skill <words>` and read only the range it
|
|
888
|
+
returns.
|
|
830
889
|
|
|
831
890
|
**Workspace: `.tezgah/` only.** Every per-project tezgah artifact - plans,
|
|
832
891
|
research lines, analysis, lessons - lives under `<repo>/.tezgah/`, which the
|
|
@@ -835,6 +894,21 @@ project's `.gitignore` excludes and which keeps its own private git repository
|
|
|
835
894
|
project root and never `git add -f` anything under `.tezgah/`. Off:
|
|
836
895
|
`workspace-off`.
|
|
837
896
|
|
|
897
|
+
**Parallel by default: fan out independent items.** A request with two or
|
|
898
|
+
more independent items - a numbered or bulleted list, several files or areas, a
|
|
899
|
+
todo list whose entries share no file and no interface - is delegated without
|
|
900
|
+
being asked: one subagent per item, all spawned in ONE message, while the main
|
|
901
|
+
thread decomposes, integrates, verifies and reports. Every writing slice gets
|
|
902
|
+
its own branch and checkout - inside Orca `orca worktree create --name <slug>
|
|
903
|
+
--parent-worktree active --json`, else `git worktree add -b <branch> <absolute
|
|
904
|
+
path>`; its brief names every read, edit and shell `cwd` as an absolute path
|
|
905
|
+
under that checkout, never the main checkout, whose `git status --short` must
|
|
906
|
+
not grow. Read-only slices need no worktree. Pick each agent by task class from
|
|
907
|
+
the specialists line; `{ROUTE_BIN}` names the tier for code work. Serialize
|
|
908
|
+
only a real dependency - one slice's output is another's input, or two slices
|
|
909
|
+
edit one file - and say which. Never wait idle on a spawned slice while
|
|
910
|
+
independent work remains. Off: `orchestrate-off`.
|
|
911
|
+
|
|
838
912
|
**Kill switches:** each one removes its own rule from this text, not just the
|
|
839
913
|
status mark. `~/.config/tezgah/`: `exec-mode.off`, `orchestrate-off`,
|
|
840
914
|
`consult-off`, `research-off`, `ponytail-auto.off`, `adhd-off`, `spec-off`,
|
|
@@ -852,13 +926,13 @@ The ponytail intensity level is not a switch: `tezgah-pony lite|full|ultra`.
|
|
|
852
926
|
# time (hooks/tezgah_context.classify_prompt), because a session that never asks
|
|
853
927
|
# a structural or research question should not carry their text. Keys match the
|
|
854
928
|
# CORE_RULES labels in hooks/tezgah_context.py.
|
|
855
|
-
CONDITIONAL_KEYS = ("spec", "consult", "research", "product", "graph")
|
|
929
|
+
CONDITIONAL_KEYS = ("spec", "consult", "research", "product", "graph", "fanout")
|
|
856
930
|
|
|
857
931
|
# The always-on replacement for the conditional paragraphs: one line each so a
|
|
858
932
|
# host without a per-turn hook still knows the rule exists and where the full
|
|
859
933
|
# text lives.
|
|
860
934
|
POINTERS = """
|
|
861
|
-
**On-demand rules (armed when the task class matches; full text in the `tezgah-contract` skill).** Spec-first for an underspecified or quality-only ask. A second opinion before a call that is hard to reverse or that one model would answer with unearned confidence. OpenResearch routing for research. Product analysis is a five-axis evidence task - value, usability (the running app, not the source), feasibility (a cited `path:line`), competition, and keep/fix/cut/bet triage - with one named evidence class per finding. The code graph for "who calls X" and "what breaks if Z changes". The design floor a UI turn is judged against is `.tezgah/design-contract.md`: `tezgah-design derive` writes it from the repository's own tokens, `tezgah-design check` measures a change against it, and the `design-contract` skill owns its shape.
|
|
935
|
+
**On-demand rules (armed when the task class matches; full text in the `tezgah-contract` skill).** Spec-first for an underspecified or quality-only ask. A second opinion before a call that is hard to reverse or that one model would answer with unearned confidence. OpenResearch routing for research. Product analysis is a five-axis evidence task - value, usability (the running app, not the source), feasibility (a cited `path:line`), competition, and keep/fix/cut/bet triage - with one named evidence class per finding. The code graph for "who calls X" and "what breaks if Z changes". Two or more independent items fan out to parallel subagents in one message, one git worktree per writing slice. The design floor a UI turn is judged against is `.tezgah/design-contract.md`: `tezgah-design derive` writes it from the repository's own tokens, `tezgah-design check` measures a change against it, and the `design-contract` skill owns its shape.
|
|
862
936
|
"""
|
|
863
937
|
|
|
864
938
|
# The compact per-turn form, inside the <harness-reminder> envelope the hosts
|
package/hooks/tezgah_research.py
CHANGED
|
@@ -136,12 +136,20 @@ SOURCE_CLASSES = ("formal", "grey", "agent-report")
|
|
|
136
136
|
# those warns on it instead of refusing - its rows were written before the rule
|
|
137
137
|
# existed. Each later set is gated on its own number, so a bump never demotes an
|
|
138
138
|
# older set: 2 the standards rules, 3 the ask contract (keyed on the `ask` field),
|
|
139
|
-
# 4 the protocol sections (`_check_protocol_sections`)
|
|
139
|
+
# 4 the protocol sections (`_check_protocol_sections`), 5 the scope lock
|
|
140
|
+
# (`scope_problems`, `trace_problem`, the method-gated evaluation and the analysis
|
|
141
|
+
# that owes no variants). A `rules` above `RULES` is
|
|
140
142
|
# a line a later tezgah wrote, and every reader and writer here refuses it by name
|
|
141
143
|
# (`newer_rules`) rather than judging it by rules it does not know.
|
|
142
144
|
STANDARDS_RULES = 2
|
|
143
145
|
PROTOCOL_RULES = 4
|
|
144
|
-
|
|
146
|
+
SCOPE_RULES = 5
|
|
147
|
+
RULES = SCOPE_RULES
|
|
148
|
+
# How a rules-5 line answers its question. `qualitative` is a source or
|
|
149
|
+
# reference review and owes no metric; `measured` runs experiments and locks
|
|
150
|
+
# one. The question picks it, not the tooling: a qualitative ask answered with a
|
|
151
|
+
# metric table is the metric-first drift this field exists against.
|
|
152
|
+
METHODS = ("qualitative", "measured")
|
|
145
153
|
|
|
146
154
|
# What a line delivers, and how many compared variants that takes. `finding` is a
|
|
147
155
|
# line whose deliverable is its claims; the other four are an artifact a reader
|
|
@@ -925,6 +933,7 @@ def _check_state(base, errors, warnings, strict):
|
|
|
925
933
|
errors.append("state.json direction %r is not one of %s"
|
|
926
934
|
% (state.get("direction"), ", ".join(DIRECTIONS)))
|
|
927
935
|
_check_evaluation(state, errors, warnings, strict)
|
|
936
|
+
_check_scope(base, state, errors, warnings, strict)
|
|
928
937
|
_check_hypotheses(state, errors)
|
|
929
938
|
_check_sessions(state, errors)
|
|
930
939
|
_check_success(state, errors, warnings, strict)
|
|
@@ -1012,6 +1021,8 @@ def _check_evaluation(state, errors, warnings, strict):
|
|
|
1012
1021
|
is written where a session reads it (`skills/research/SKILL.md`,
|
|
1013
1022
|
`docs/research.md`); what it cannot catch is a tolerance its own author sets
|
|
1014
1023
|
so wide that every candidate passes it."""
|
|
1024
|
+
if _qualitative(state):
|
|
1025
|
+
return
|
|
1015
1026
|
phase = state.get("phase")
|
|
1016
1027
|
locked = phase != "bootstrap"
|
|
1017
1028
|
evaluation = state.get("evaluation")
|
|
@@ -1045,6 +1056,77 @@ def _check_evaluation(state, errors, warnings, strict):
|
|
|
1045
1056
|
"move: fill it, or omit the two-gate pair" % (counter,))
|
|
1046
1057
|
|
|
1047
1058
|
|
|
1059
|
+
# --- scope lock (rules 5) ------------------------------------------------------
|
|
1060
|
+
# A line answers the question the user asked, by the method that question takes.
|
|
1061
|
+
# `scope.in` lists what the answer covers (S1, S2, ... in order) and `scope.out`
|
|
1062
|
+
# what it deliberately leaves alone; every claim's `trace` names an in-scope item,
|
|
1063
|
+
# so a finding about something nobody asked is refused instead of padding the
|
|
1064
|
+
# report. Owner's report, 2026-10-07: research turned every question into
|
|
1065
|
+
# metrics, grew past the ask, and wandered into unrelated topics.
|
|
1066
|
+
|
|
1067
|
+
def _qualitative(state):
|
|
1068
|
+
return _rules(state) >= SCOPE_RULES and state.get("method") == "qualitative"
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def _scope_items(state):
|
|
1072
|
+
scope = state.get("scope")
|
|
1073
|
+
items = scope.get("in") if isinstance(scope, dict) else None
|
|
1074
|
+
return [i for i in items if isinstance(i, str) and i.strip()] \
|
|
1075
|
+
if isinstance(items, list) else []
|
|
1076
|
+
|
|
1077
|
+
|
|
1078
|
+
def scope_problems(state):
|
|
1079
|
+
"""What a rules-5 line is missing to be locked to its question; [] otherwise."""
|
|
1080
|
+
if _rules(state) < SCOPE_RULES:
|
|
1081
|
+
return []
|
|
1082
|
+
problems = []
|
|
1083
|
+
scope = state.get("scope") if isinstance(state.get("scope"), dict) else {}
|
|
1084
|
+
for side, what in (("in", "what the answer covers"),
|
|
1085
|
+
("out", "what it deliberately leaves alone")):
|
|
1086
|
+
items = scope.get(side)
|
|
1087
|
+
if not isinstance(items, list) or not [
|
|
1088
|
+
i for i in items if isinstance(i, str) and i.strip()]:
|
|
1089
|
+
problems.append("state.json scope.%s names nothing: list %s, from the "
|
|
1090
|
+
"user's own question" % (side, what))
|
|
1091
|
+
if state.get("method") not in METHODS:
|
|
1092
|
+
problems.append("state.json method %r is not one of %s: the question picks "
|
|
1093
|
+
"it - a source or reference question is qualitative and "
|
|
1094
|
+
"owes no metric" % (state.get("method"), ", ".join(METHODS)))
|
|
1095
|
+
return problems
|
|
1096
|
+
|
|
1097
|
+
|
|
1098
|
+
def trace_problem(cid, claim, state):
|
|
1099
|
+
"""The refusal for a rules-5 claim that answers no in-scope item, or None.
|
|
1100
|
+
The write path (`claim_problems`) and the checker (`_check_claims`) both
|
|
1101
|
+
call it, so they cannot disagree about what is on topic."""
|
|
1102
|
+
if _rules(state) < SCOPE_RULES:
|
|
1103
|
+
return None
|
|
1104
|
+
items = _scope_items(state)
|
|
1105
|
+
trace = claim.get("trace")
|
|
1106
|
+
for entry in trace if isinstance(trace, list) else [trace]:
|
|
1107
|
+
if not isinstance(entry, str):
|
|
1108
|
+
continue
|
|
1109
|
+
entry = entry.strip()
|
|
1110
|
+
if any(entry in ("S%d" % n, item.strip()) for n, item in enumerate(items, 1)):
|
|
1111
|
+
return None
|
|
1112
|
+
return ("claim %s traces to no in-scope item: its `trace` names S<n> or the "
|
|
1113
|
+
"text of a state.json scope.in entry - a finding that answers nothing "
|
|
1114
|
+
"the user asked is dropped, not reported" % cid)
|
|
1115
|
+
|
|
1116
|
+
|
|
1117
|
+
def _check_scope(base, state, errors, warnings, strict):
|
|
1118
|
+
for problem in scope_problems(state):
|
|
1119
|
+
_soft(errors, warnings, strict or state.get("phase") != "bootstrap", problem)
|
|
1120
|
+
if _qualitative(state):
|
|
1121
|
+
ran = [h for h in _experiment_names(base)
|
|
1122
|
+
if os.path.isfile(os.path.join(base, "experiments", h, "results.jsonl"))]
|
|
1123
|
+
if ran:
|
|
1124
|
+
errors.append("state.json method is qualitative and experiment(s) %s "
|
|
1125
|
+
"recorded results: a measured question is `method: "
|
|
1126
|
+
"measured` with its evaluation locked first"
|
|
1127
|
+
% ", ".join(ran))
|
|
1128
|
+
|
|
1129
|
+
|
|
1048
1130
|
def _check_hypotheses(state, errors):
|
|
1049
1131
|
"""Every hypothesis states something.
|
|
1050
1132
|
|
|
@@ -1563,7 +1645,8 @@ def _check_claims(base, errors, warnings, roots=(), strict=False):
|
|
|
1563
1645
|
superseded.update(_supersedes(give)[0])
|
|
1564
1646
|
# the token-savings rule is a `PROTOCOL_RULES` rule, so an older line keeps
|
|
1565
1647
|
# the rules it was opened under
|
|
1566
|
-
|
|
1648
|
+
line_state = _state(base)
|
|
1649
|
+
sections = _rules(line_state) >= PROTOCOL_RULES
|
|
1567
1650
|
parsed = []
|
|
1568
1651
|
for n, raw in enumerate(rows, 1):
|
|
1569
1652
|
raw = raw.strip()
|
|
@@ -1599,6 +1682,9 @@ def _check_claims(base, errors, warnings, roots=(), strict=False):
|
|
|
1599
1682
|
errors.append("claim %s status %r is not one of %s"
|
|
1600
1683
|
% (cid, claim.get("status"), ", ".join(STATUSES)))
|
|
1601
1684
|
_check_claim_scope(cid, claim, base, repo, errors, warnings, strict)
|
|
1685
|
+
problem = trace_problem(cid, claim, line_state)
|
|
1686
|
+
if problem:
|
|
1687
|
+
errors.append(problem)
|
|
1602
1688
|
# A superseded claim is the historical row: the line has corrected it in
|
|
1603
1689
|
# the row that supersedes it, and that row is the one whose numbers are
|
|
1604
1690
|
# held to their proof here. Warning on the historical statement too would
|
|
@@ -1907,6 +1993,9 @@ def claim_problems(claim, base, repo, held=None):
|
|
|
1907
1993
|
known = _claim_ids(base) if held is None else held
|
|
1908
1994
|
problems.extend("claim %s supersedes %s, and no claim in this line carries "
|
|
1909
1995
|
"that id" % (cid, ref) for ref in ids if ref not in known)
|
|
1996
|
+
problem = trace_problem(cid, claim, _state(base))
|
|
1997
|
+
if problem:
|
|
1998
|
+
problems.append(problem)
|
|
1910
1999
|
# The scope rules the checker decides, so the write path cannot record a claim
|
|
1911
2000
|
# `check` would refuse. The warn class (a claim over fixture rows that declares
|
|
1912
2001
|
# nothing) is deliberately not repeated: a writer stricter than the checker
|
|
@@ -2848,7 +2937,7 @@ def _check_results_append_only(repo, base, state, errors, warnings, strict):
|
|
|
2848
2937
|
# 2006), and serial versions fixate on the first option (Dow et al. 2010); the
|
|
2849
2938
|
# layer used to hold exactly that, v3 -> v4 -> v5 of one plan never compared.
|
|
2850
2939
|
|
|
2851
|
-
def deliverable_problems(deliverable):
|
|
2940
|
+
def deliverable_problems(deliverable, rules=RULES):
|
|
2852
2941
|
"""(problems, variants needed) for `state.json`'s deliverable declaration."""
|
|
2853
2942
|
if not isinstance(deliverable, dict):
|
|
2854
2943
|
return (["state.json deliverable is a %s, not the object naming what the line "
|
|
@@ -2862,7 +2951,11 @@ def deliverable_problems(deliverable):
|
|
|
2862
2951
|
if ask is not None and (not isinstance(ask, list) or not all(
|
|
2863
2952
|
isinstance(item, str) and item.strip() for item in ask)):
|
|
2864
2953
|
problems.append("state.json deliverable ask is not a list of the ask's items")
|
|
2865
|
-
|
|
2954
|
+
# rules 5: an analysis answers its question once; variants are for a design,
|
|
2955
|
+
# plan or code the user will choose between, not a way to triple the answer
|
|
2956
|
+
kinds = VARIANT_KINDS if rules < SCOPE_RULES else tuple(
|
|
2957
|
+
k for k in VARIANT_KINDS if k != "analysis")
|
|
2958
|
+
default = {"code": 2}.get(kind, 3) if kind in kinds else 0
|
|
2866
2959
|
need = deliverable.get("min_variants", default)
|
|
2867
2960
|
if not isinstance(need, int) or isinstance(need, bool) or need < 0:
|
|
2868
2961
|
problems.append("state.json deliverable min_variants %r is not a count"
|
|
@@ -3250,7 +3343,7 @@ def _check_decisions(repo, slug, base, state, errors, warnings, strict, git):
|
|
|
3250
3343
|
"line delivers a design, plan, analysis or code that is owed "
|
|
3251
3344
|
"compared variants, or only findings")
|
|
3252
3345
|
else:
|
|
3253
|
-
problems, need = deliverable_problems(deliverable)
|
|
3346
|
+
problems, need = deliverable_problems(deliverable, _rules(state))
|
|
3254
3347
|
errors.extend(problems)
|
|
3255
3348
|
decisions = _decision_dirs(base)
|
|
3256
3349
|
if need > 1 and not decisions:
|
|
@@ -3910,7 +4003,7 @@ def _variants_owed(base, state):
|
|
|
3910
4003
|
deliverable = state.get("deliverable")
|
|
3911
4004
|
if not isinstance(deliverable, dict):
|
|
3912
4005
|
return None
|
|
3913
|
-
_problems, need = deliverable_problems(deliverable)
|
|
4006
|
+
_problems, need = deliverable_problems(deliverable, _rules(state))
|
|
3914
4007
|
if need < 2:
|
|
3915
4008
|
return None
|
|
3916
4009
|
for _name, ddir in _decision_dirs(base):
|
|
@@ -4688,7 +4781,8 @@ def verdict_problems(state, rows):
|
|
|
4688
4781
|
return problems
|
|
4689
4782
|
|
|
4690
4783
|
|
|
4691
|
-
def init(repo, slug, question="", created="", supersedes=None, ask="", tier=DEFAULT_TIER
|
|
4784
|
+
def init(repo, slug, question="", created="", supersedes=None, ask="", tier=DEFAULT_TIER,
|
|
4785
|
+
method="", scope_in=(), scope_out=()):
|
|
4692
4786
|
"""Scaffold a research line. Returns the paths created (never overwrites).
|
|
4693
4787
|
|
|
4694
4788
|
Refuses a slug `slugs()` cannot list - see `valid_slug` - so no caller can
|
|
@@ -4702,6 +4796,8 @@ def init(repo, slug, question="", created="", supersedes=None, ask="", tier=DEFA
|
|
|
4702
4796
|
path = os.path.join(base, sub)
|
|
4703
4797
|
os.makedirs(path, exist_ok=True)
|
|
4704
4798
|
state = dict(STATE_TEMPLATE)
|
|
4799
|
+
state["method"] = method
|
|
4800
|
+
state["scope"] = {"in": list(scope_in), "out": list(scope_out)}
|
|
4705
4801
|
state["question"] = question
|
|
4706
4802
|
state["created"] = created
|
|
4707
4803
|
# every new line is opened under the newest rule set (`RULES` in the
|