@r1z4x/tezgah 1.11.0 → 1.14.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 +507 -1
- package/MANIFEST +40 -0
- package/NOTICE +152 -0
- package/README.de.md +41 -20
- package/README.es.md +39 -19
- package/README.fr.md +41 -20
- package/README.md +18 -10
- package/README.tr.md +39 -21
- package/bin/codegen +9 -2
- package/bin/consult +6 -0
- package/bin/tezgah-adhd +3 -0
- package/bin/tezgah-agents +3 -0
- package/bin/tezgah-capture +3 -0
- package/bin/tezgah-design +329 -54
- package/bin/tezgah-docs +158 -42
- package/bin/tezgah-dsh +1 -1
- package/bin/tezgah-gate +7 -2
- package/bin/tezgah-import-ai-research +16 -4
- package/bin/tezgah-index +3 -0
- package/bin/tezgah-lessons +13 -4
- package/bin/tezgah-mcp +4 -0
- package/bin/tezgah-migrate +3 -0
- package/bin/tezgah-ocr-review +2 -1
- package/bin/tezgah-optimize +4 -1
- package/bin/tezgah-pony +3 -0
- package/bin/tezgah-research +51 -10
- package/bin/tezgah-rollback +3 -0
- package/bin/tezgah-route +1 -1
- package/bin/tezgah-setup +1741 -95
- package/bin/tezgah-status +11 -1
- package/bin/tezgah-task +58 -17
- package/bin/tezgah.js +10 -3
- package/commands/adhd.md +1 -1
- package/docs/README.md +19 -8
- package/docs/architecture.md +2 -2
- package/docs/citations-baseline.json +11 -17
- package/docs/clarity-baseline.json +12 -6
- package/docs/contract.md +83 -34
- package/docs/environment.md +59 -24
- package/docs/evidence.md +128 -25
- package/docs/gate.md +77 -23
- package/docs/glossary.md +1 -1
- package/docs/hosts.md +343 -48
- package/docs/layers.md +2 -2
- package/docs/models.md +6 -0
- package/docs/operations.md +83 -29
- package/docs/research.md +37 -11
- package/docs/skills.md +31 -8
- package/docs/status-line.md +39 -23
- package/docs/testing.md +50 -17
- package/docs/vision.md +6 -2
- package/hooks/hooks.json +1 -1
- package/hooks/lang/en.json +16 -2
- package/hooks/lang/tr.json +14 -2
- package/hooks/projects-posttooluse.py +11 -3
- package/hooks/projects-stop.py +3 -2
- package/hooks/tezgah_agents.py +27 -3
- package/hooks/tezgah_attest.py +79 -10
- package/hooks/tezgah_context.py +61 -31
- package/hooks/tezgah_gate.py +480 -126
- package/hooks/tezgah_integrity.py +577 -133
- package/hooks/tezgah_lessons.py +5 -2
- package/hooks/tezgah_paths.py +60 -8
- package/hooks/tezgah_policy.py +48 -18
- package/hooks/tezgah_replay.py +8 -2
- package/hooks/tezgah_research.py +167 -24
- package/hooks/tezgah_skill_pick.py +1 -1
- package/hooks/tezgah_snapshot.py +6 -3
- package/hooks/tezgah_stack.py +236 -1
- package/hooks/tezgah_stopspec.py +2 -1
- package/hooks/tezgah_task.py +2 -1
- package/hosts/amp/hook.py +217 -0
- package/hosts/amp/tezgah-plugin.ts.in +396 -0
- package/hosts/antigravity/hook.py +255 -0
- package/hosts/codex/hook.py +3 -2
- package/hosts/commandcode/hook.py +239 -0
- package/hosts/copilot/hook.py +284 -0
- package/hosts/cursor/hook.py +4 -2
- package/hosts/droid/hook.py +259 -0
- package/hosts/dsh/hooks.json +1 -1
- package/hosts/gemini/hook.py +213 -0
- package/hosts/goose/hook.py +145 -0
- package/hosts/omp/hook.py +5 -2
- package/hosts/omp/tezgah-hook.ts.in +157 -42
- package/hosts/opencode/plugins/tezgah.js +209 -63
- package/hosts/qwen/hook.py +206 -0
- package/output-styles/tezgah.md +11 -6
- package/package.json +6 -3
- package/packaging/build.sh +4 -4
- package/packaging/install.sh +11 -0
- package/packaging/upgrade.sh +3 -0
- package/skills/ai-research/LICENSE +21 -0
- package/skills/design-contract/SKILL.md +27 -6
- package/skills/design-library/LICENSE +21 -0
- package/skills/harness/SKILL.md +2 -0
- package/skills/i-have-adhd/SKILL.md +10 -1
- package/skills/plan-add/SKILL.md +5 -1
- package/skills/plan-add/render_table.py +7 -0
- package/skills/pm-frameworks/LICENSE +21 -0
- package/skills/research/SKILL.md +22 -9
- package/skills/rl-env/LICENSE +201 -0
- package/skills/tezgah-contract/SKILL.md +21 -8
- package/statusline.py +20 -9
- package/third_party/open-code-review/LICENSE +201 -0
- package/third_party/open-code-review/README.md +15 -0
- package/third_party/open-code-review/prompts/main_task_system.md +26 -0
- package/third_party/open-code-review/prompts/review_filter_task_system.md +13 -0
- package/workflows/graph-map.js +14 -5
- package/workflows/graph-review.js +14 -2
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Qwen Code lifecycle hook. Reads Qwen's JSON on stdin and answers in Qwen's own
|
|
3
|
+
output shape, onto the shared tezgah core (hooks/).
|
|
4
|
+
|
|
5
|
+
Qwen Code 0.21.13 fires SessionStart, UserPromptSubmit, PreToolUse, PostToolUse,
|
|
6
|
+
PostToolUseFailure, SubagentStart, SubagentStop and Stop here
|
|
7
|
+
(`bin/tezgah-setup::QWEN_HOOK_EVENTS`); anything else is ignored. Inert outside
|
|
8
|
+
the roots.
|
|
9
|
+
|
|
10
|
+
The always-on core is the managed block in `~/.qwen/QWEN.md`, which Qwen loads
|
|
11
|
+
into every session, so SessionStart repeats it only when that block is missing.
|
|
12
|
+
PreToolUse translates Qwen's runtime tool ids into the gate's vocabulary and
|
|
13
|
+
denies with `hookSpecificOutput.permissionDecision` on exit 0 - never exit 2,
|
|
14
|
+
whose stderr channel carries no reason the ledger and the probe can read. Qwen
|
|
15
|
+
fires PostToolUse for a successful call and PostToolUseFailure for a failed one
|
|
16
|
+
(a shell exit Qwen counts as an error, `isShellExitError`), so the event is the
|
|
17
|
+
call's outcome. Stop blocks a done/tested claim with no successful check behind
|
|
18
|
+
it, judged on `last_assistant_message`, the same rule Claude's Stop hook enforces
|
|
19
|
+
(hooks/tezgah_integrity.py). The status line is `tezgah-statusline --qwen`, Qwen's
|
|
20
|
+
`ui.statusLine` command, so no event here carries it.
|
|
21
|
+
"""
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import sys
|
|
25
|
+
|
|
26
|
+
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))
|
|
27
|
+
sys.path.insert(0, os.path.join(ROOT, "hooks"))
|
|
28
|
+
from tezgah_guard import attest_session, import_failed, safe # noqa: E402
|
|
29
|
+
try:
|
|
30
|
+
from tezgah_context import command_text, context_for, record, shell_kind # noqa: E402
|
|
31
|
+
from tezgah_gate import cross_project_note, decision, graph_advice # noqa: E402
|
|
32
|
+
from tezgah_integrity import ( # noqa: E402
|
|
33
|
+
SUBAGENT_CHANNEL, bind_session, note, note_tool, ran_nothing, report_bytes,
|
|
34
|
+
stop_reason, untrusted_source)
|
|
35
|
+
from tezgah_paths import HOST_DIRS, off, root_for # noqa: E402
|
|
36
|
+
from tezgah_taste import write_note # noqa: E402
|
|
37
|
+
from tezgah_untrusted import marks # noqa: E402
|
|
38
|
+
except Exception as exc:
|
|
39
|
+
import_failed(exc)
|
|
40
|
+
|
|
41
|
+
# Qwen's runtime tool ids (the `ToolNames` table in the 0.21.13 bundle) -> the
|
|
42
|
+
# shared gate's vocabulary. `replace` is the edit tool's id in older builds; the
|
|
43
|
+
# 0.21.13 bundle has none, and an unknown id passes the gate untouched. MCP tools
|
|
44
|
+
# arrive as `mcp__<server>__<tool>`, the shared shape, and pass through.
|
|
45
|
+
# `monitor` runs a shell command too (Qwen's headless deny list pairs it with
|
|
46
|
+
# SHELL), and `notebook_edit` writes an .ipynb (review of plan 138).
|
|
47
|
+
GATE_TOOLS = {
|
|
48
|
+
"run_shell_command": "Bash",
|
|
49
|
+
"monitor": "Bash",
|
|
50
|
+
"write_file": "Write",
|
|
51
|
+
"edit": "Edit",
|
|
52
|
+
"replace": "Edit",
|
|
53
|
+
"notebook_edit": "NotebookEdit",
|
|
54
|
+
"agent": "Task",
|
|
55
|
+
"grep_search": "Grep",
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def gate_name(name):
|
|
60
|
+
"""The tool name the gate denies under and the ledger row records.
|
|
61
|
+
|
|
62
|
+
`call_id` hashes the name it is handed, so both halves map through here: a
|
|
63
|
+
PostToolUse row under the raw id would never match the PreToolUse call and
|
|
64
|
+
the loop guard would never deny (`hosts/codex/hook.py::gate_name`)."""
|
|
65
|
+
return GATE_TOOLS.get(name, name)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def classify(payload):
|
|
69
|
+
"""The used-tool kind for a tool payload, or None."""
|
|
70
|
+
name = payload.get("tool_name", "") or ""
|
|
71
|
+
if name.startswith("mcp__") and "codegraph" in name:
|
|
72
|
+
return "graph"
|
|
73
|
+
if name == "agent":
|
|
74
|
+
return "orch"
|
|
75
|
+
if name == "run_shell_command":
|
|
76
|
+
return shell_kind(command_text(payload.get("tool_input")))
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def subagent_id(payload):
|
|
81
|
+
"""The subagent id Qwen adds to a hook input run inside a subagent, else None."""
|
|
82
|
+
aid = payload.get("agent_id")
|
|
83
|
+
return aid if isinstance(aid, str) and aid else None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def rules_carry_core():
|
|
87
|
+
"""True when `QWEN.md` in Qwen's home holds tezgah-setup's managed block."""
|
|
88
|
+
try:
|
|
89
|
+
with open(os.path.join(HOST_DIRS["qwen"], "QWEN.md"), encoding="utf-8") as fh:
|
|
90
|
+
return "<!-- tezgah:start" in fh.read()
|
|
91
|
+
except OSError:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def result_size(result):
|
|
96
|
+
"""The result's top-level length, the Claude-family writer's measure; a value
|
|
97
|
+
with no length counts as non-empty and only a missing result is None."""
|
|
98
|
+
if isinstance(result, (str, bytes, bytearray, list, tuple, dict, set, frozenset)):
|
|
99
|
+
return len(result)
|
|
100
|
+
return 1 if result is not None else None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def inp_of(payload):
|
|
104
|
+
inp = payload.get("tool_input")
|
|
105
|
+
return inp if isinstance(inp, dict) else {}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def post_tool(payload, cwd, session_id, failed):
|
|
109
|
+
"""The ledger row for one finished call, and the notice for the model."""
|
|
110
|
+
tool = gate_name(payload.get("tool_name", ""))
|
|
111
|
+
inp = inp_of(payload)
|
|
112
|
+
# a `monitor` PostToolUse fires when the watch starts ("Monitor started"),
|
|
113
|
+
# long before its command ends: it records a run, never a pass (review of
|
|
114
|
+
# plan 138, round 2); a failed start still arrives as PostToolUseFailure
|
|
115
|
+
if payload.get("tool_name") == "monitor" and failed is False:
|
|
116
|
+
failed = None
|
|
117
|
+
safe(session_id, record, session_id, classify(payload))
|
|
118
|
+
if not root_for(cwd):
|
|
119
|
+
return None
|
|
120
|
+
result = payload.get("tool_response")
|
|
121
|
+
# read before this call's row lands: `source` is the taint's own mark
|
|
122
|
+
got = safe(session_id, marks, tool, inp, session_id, result)
|
|
123
|
+
source, notice = got or (None, None)
|
|
124
|
+
safe(session_id, note_tool, session_id, tool, inp, failed=failed,
|
|
125
|
+
interrupted=bool(failed and payload.get("is_interrupt") is True),
|
|
126
|
+
source=source, cwd=cwd,
|
|
127
|
+
out_bytes=(report_bytes(result)
|
|
128
|
+
if untrusted_source(tool, inp) == SUBAGENT_CHANNEL
|
|
129
|
+
else result_size(result)),
|
|
130
|
+
empty_run=ran_nothing(result), agent=subagent_id(payload))
|
|
131
|
+
advice = safe(session_id, graph_advice, tool, inp, cwd, "qwen")
|
|
132
|
+
taste = cross = None
|
|
133
|
+
if not failed:
|
|
134
|
+
taste = safe(session_id, write_note, session_id, inp, cwd, tool)
|
|
135
|
+
cross = safe(session_id, cross_project_note, session_id, tool, inp, cwd)
|
|
136
|
+
return "\n".join(p for p in (notice, advice, taste, cross) if p)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def context(event, text):
|
|
140
|
+
return {"hookSpecificOutput": {"hookEventName": event,
|
|
141
|
+
"additionalContext": text}} if text else {}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main():
|
|
145
|
+
try:
|
|
146
|
+
payload = json.load(sys.stdin)
|
|
147
|
+
except Exception:
|
|
148
|
+
return
|
|
149
|
+
if not isinstance(payload, dict):
|
|
150
|
+
return
|
|
151
|
+
event = payload.get("hook_event_name") or ""
|
|
152
|
+
cwd = payload.get("cwd") or os.getcwd()
|
|
153
|
+
session_id = payload.get("session_id")
|
|
154
|
+
safe(session_id, bind_session, session_id, payload.get("parent")) # the switch latch
|
|
155
|
+
out = {}
|
|
156
|
+
if event == "PreToolUse":
|
|
157
|
+
reason = safe(session_id, decision, gate_name(payload.get("tool_name", "")),
|
|
158
|
+
inp_of(payload), cwd, session_id, agent=subagent_id(payload))
|
|
159
|
+
if reason:
|
|
160
|
+
out = {"hookSpecificOutput": {
|
|
161
|
+
"hookEventName": "PreToolUse",
|
|
162
|
+
"permissionDecision": "deny",
|
|
163
|
+
"permissionDecisionReason": reason,
|
|
164
|
+
}}
|
|
165
|
+
elif event in ("PostToolUse", "PostToolUseFailure"):
|
|
166
|
+
out = context(event, post_tool(payload, cwd, session_id,
|
|
167
|
+
event == "PostToolUseFailure"))
|
|
168
|
+
elif event == "SessionStart":
|
|
169
|
+
safe(session_id, attest_session, "qwen", session_id, cwd)
|
|
170
|
+
out = context(event, safe(session_id, context_for, "session_start", cwd,
|
|
171
|
+
payload, with_core=not rules_carry_core()))
|
|
172
|
+
elif event == "UserPromptSubmit":
|
|
173
|
+
out = context(event, safe(session_id, context_for, "user_prompt", cwd, payload))
|
|
174
|
+
elif event == "SubagentStart":
|
|
175
|
+
safe(session_id, record, session_id, "orch")
|
|
176
|
+
kind = payload.get("agent_type")
|
|
177
|
+
if subagent_id(payload) and isinstance(kind, str) and root_for(cwd):
|
|
178
|
+
safe(session_id, note, session_id, "subagent_start", kind,
|
|
179
|
+
agent=subagent_id(payload))
|
|
180
|
+
# the delegate is a fresh context: Qwen documents additionalContext here
|
|
181
|
+
# as the subagent's initial context
|
|
182
|
+
out = context(event, safe(session_id, context_for, "subagent_start", cwd,
|
|
183
|
+
payload))
|
|
184
|
+
elif event == "SubagentStop":
|
|
185
|
+
# a subagent's end is judged record-only (ADR 011): one `subagent_end`
|
|
186
|
+
# row from its last message, never a block
|
|
187
|
+
safe(session_id, record, session_id, "orch")
|
|
188
|
+
if not off("verify-off") and root_for(cwd):
|
|
189
|
+
safe(session_id, stop_reason, payload.get("last_assistant_message") or "",
|
|
190
|
+
session_id, cwd=cwd, subagent=True)
|
|
191
|
+
elif event == "Stop":
|
|
192
|
+
# Qwen's Stop carries `last_assistant_message` and `stop_hook_active`,
|
|
193
|
+
# and takes {"decision": "block", "reason": ...}. The reply after a block
|
|
194
|
+
# is judged again and refused at most once more (tezgah_integrity).
|
|
195
|
+
if not off("verify-off") and root_for(cwd):
|
|
196
|
+
reason = safe(session_id, stop_reason,
|
|
197
|
+
payload.get("last_assistant_message"), session_id,
|
|
198
|
+
cwd=cwd, after_block=bool(payload.get("stop_hook_active")),
|
|
199
|
+
mode=payload.get("permission_mode"))
|
|
200
|
+
if reason:
|
|
201
|
+
out = {"decision": "block", "reason": reason}
|
|
202
|
+
if out:
|
|
203
|
+
print(json.dumps(out))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
main()
|
package/output-styles/tezgah.md
CHANGED
|
@@ -38,7 +38,7 @@ first; write what was cut only when you consciously cut something the user asked
|
|
|
38
38
|
for, in at most three lines. In force from the first turn; levels
|
|
39
39
|
lite|full|ultra are set with `~/.config/tezgah/bin/tezgah-pony` and ride the per-turn reminder when
|
|
40
40
|
not `full`; read the full `ponytail` skill from the router for the level table
|
|
41
|
-
and examples. Off: "stop ponytail".
|
|
41
|
+
and examples. Off: "stop ponytail", `ponytail-auto.off` or `.no-ponytail`.
|
|
42
42
|
|
|
43
43
|
**Output shape: ADHD-friendly.** The answer or the next action is on the first
|
|
44
44
|
line - a command, path, snippet or the decision itself, never context or the
|
|
@@ -47,7 +47,10 @@ bounded action per step, and while it is in flight its position is restated in
|
|
|
47
47
|
one line - the todo list is that source, never re-narrate the plan. End with the
|
|
48
48
|
approval questions the Ask policy (below) leaves, or with nothing when none is
|
|
49
49
|
left; a step you can take yourself is taken, never handed
|
|
50
|
-
over
|
|
50
|
+
over, and in the host's plan mode, where nothing can run, the plan is the
|
|
51
|
+
deliverable: its approval is the question that turn asks and nothing is
|
|
52
|
+
reported as done or tested.
|
|
53
|
+
Finish the issue in hand before raising a second one; an error states
|
|
51
54
|
location, cause and fix with no drama, naming a cause only when the evidence
|
|
52
55
|
identifies it (else what is known and what would confirm it); after a change say
|
|
53
56
|
what now works. A list shows at most five items, ranked, the rest kept in
|
|
@@ -112,15 +115,17 @@ lines: item - blocker - evidence - what unblocks it. No blockers: no heading.
|
|
|
112
115
|
Work and process - the order, "shall I go on", a check, a plan, a consult
|
|
113
116
|
member, a spend within `spend_cap`, a local merge of the session's branch,
|
|
114
117
|
removing its worktree - are taken and stated in one line, never asked; a taste
|
|
115
|
-
or intent fork goes in the batch with a default taken this turn, but
|
|
116
|
-
|
|
118
|
+
or intent fork goes in the batch with a default taken this turn, but the
|
|
119
|
+
never-default items below wait for the answer. An outward step
|
|
117
120
|
on the user's own remote (push, PR, release, their package) runs on a grant
|
|
118
121
|
("push without asking", "I approve", "bana sormadan pushla", "onay veriyorum"), else is asked once. Never on a
|
|
119
122
|
default - ask and wait for an answer naming action and target: a force push,
|
|
120
123
|
deleting what the session did not make, production, a credential, a third
|
|
121
|
-
party, a spend over the cap
|
|
124
|
+
party, a spend over the cap, building a plan this turn wrote, picking a
|
|
125
|
+
recommended variant, a way around a tezgah refusal, another session's work. The batch: one "Approval questions"/"Onay için sorular" heading, waiting
|
|
122
126
|
items first, each numbered item with "Recommended:"/"Öneri:" its default; an ask tool
|
|
123
|
-
sets a recommended option.
|
|
127
|
+
sets a recommended option. No fresh subagent, no review verdict: say so and
|
|
128
|
+
ask. Contest a finding only with a file:line; never lower a severity.
|
|
124
129
|
**Say what a number was measured on.** A figure produced by a fixture - a temp
|
|
125
130
|
HOME, a generated repository, a stand-in, a hand-written sample - is evidence
|
|
126
131
|
about the code path, never a property of the running system, and it is reported
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@r1z4x/tezgah",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "One shared working contract for every AI coding assistant you run: Claude Code, Codex, Cursor, opencode, dsh and
|
|
3
|
+
"version": "1.14.0",
|
|
4
|
+
"description": "One shared working contract for every AI coding assistant you run, across fourteen hosts: omp, Claude Code, Codex, Cursor, opencode, dsh, Qwen Code, Factory Droid, Command Code, GitHub Copilot CLI, Gemini CLI, Antigravity CLI, Goose and Amp.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -23,7 +23,10 @@
|
|
|
23
23
|
"MANIFEST",
|
|
24
24
|
"CHANGELOG.md",
|
|
25
25
|
"LICENSE",
|
|
26
|
-
"
|
|
26
|
+
"NOTICE",
|
|
27
|
+
"third_party/",
|
|
28
|
+
"packaging/",
|
|
29
|
+
"!**/__pycache__"
|
|
27
30
|
],
|
|
28
31
|
"engines": {
|
|
29
32
|
"node": ">=18"
|
package/packaging/build.sh
CHANGED
|
@@ -79,12 +79,12 @@ def version():
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
def changelog_version():
|
|
82
|
-
"""The newest
|
|
82
|
+
"""The newest `## [x.y.z]` heading, as `RELEASE` in hooks/tezgah_context.py."""
|
|
83
83
|
try:
|
|
84
84
|
with open(os.path.join(ROOT, "CHANGELOG.md")) as fh:
|
|
85
|
-
for
|
|
86
|
-
if
|
|
87
|
-
return
|
|
85
|
+
for v in (ln[4:].split("]")[0] for ln in fh if ln.startswith("## [")):
|
|
86
|
+
if v.count(".") == 2 and all(p.isdigit() for p in v.split(".")):
|
|
87
|
+
return v
|
|
88
88
|
except OSError:
|
|
89
89
|
pass
|
|
90
90
|
return ""
|
package/packaging/install.sh
CHANGED
|
@@ -26,6 +26,17 @@ while [ $# -gt 0 ]; do case $1 in
|
|
|
26
26
|
*) echo "tezgah: unknown option $1" >&2; exit 2 ;;
|
|
27
27
|
esac; done
|
|
28
28
|
|
|
29
|
+
# Python 3.10+ is the floor (README; CI tests nothing older): refused here, by
|
|
30
|
+
# the version found, before anything is fetched.
|
|
31
|
+
py=$(command -v python3 2>/dev/null) || { echo "tezgah: needs Python 3.10 or newer, and no python3 is on PATH" >&2; exit 1; }
|
|
32
|
+
pyver=$("$py" --version 2>&1 | sed -n 's/^Python \([0-9][0-9]*\.[0-9][0-9.]*\).*/\1/p')
|
|
33
|
+
pymajor=0; pyminor=0
|
|
34
|
+
if [ -n "$pyver" ]; then pymajor=${pyver%%.*}; pyminor=${pyver#*.}; pyminor=${pyminor%%.*}; fi
|
|
35
|
+
if [ "$pymajor" -lt 3 ] || { [ "$pymajor" -eq 3 ] && [ "$pyminor" -lt 10 ]; }; then
|
|
36
|
+
echo "tezgah: needs Python 3.10 or newer; $py is ${pyver:-of unknown version}" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
29
40
|
# The default upgrade.sh resolves: the installer is reached through the tree it picks.
|
|
30
41
|
PREFIX=${PREFIX_ARG:-${TEZGAH_PREFIX:-${XDG_DATA_HOME:-$HOME/.local/share}/tezgah}}
|
|
31
42
|
finish() { [ -z "$work" ] || rm -rf "$work"; }
|
package/packaging/upgrade.sh
CHANGED
|
@@ -41,6 +41,9 @@ die() {
|
|
|
41
41
|
exit 1
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
# First: a missing curl would otherwise read as a fetch failure blaming a token.
|
|
45
|
+
for t in curl tar; do command -v "$t" >/dev/null 2>&1 || die "$t is required (README: Install)"; done
|
|
46
|
+
|
|
44
47
|
# A private repository answers 404 to `releases/download/...` from a session without
|
|
45
48
|
# a token (measured 2026-10-17, with the token as URL userinfo and as a header
|
|
46
49
|
# alike), while the REST asset endpoint returns the blob for `Accept:
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Claude AI Research Skills Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -33,8 +33,8 @@ file, so it costs nothing until it is used.
|
|
|
33
33
|
the machine half and the only part `check` reads, and the header is what a person
|
|
34
34
|
opens. Field names and prose stay English.
|
|
35
35
|
|
|
36
|
-
- `source` - where the floor came from: `css-custom-properties`, `tailwind-config
|
|
37
|
-
or `theme-
|
|
36
|
+
- `source` - where the floor came from: `css-custom-properties`, `tailwind-config`,
|
|
37
|
+
`theme-json` or `theme-module` when it was read out of the repository, `derived` when it could
|
|
38
38
|
not be and someone chose it. An empty `source` is refused: a floor nobody can
|
|
39
39
|
trace is not a floor.
|
|
40
40
|
- `tokens.colors` - the colour roles, as role name to hex. A measured colour that
|
|
@@ -160,18 +160,25 @@ Read the repository's own token source. In this order, and nothing else:
|
|
|
160
160
|
role, named by the property (the `color-`/`c-` prefix and a trailing `-color`
|
|
161
161
|
are dropped); one named `space`, `spacing`, `unit`, `rhythm` and the like
|
|
162
162
|
states the unit; `font-size`/`text-size`/`fs` properties are the type scale.
|
|
163
|
-
2. **A Tailwind config** - `tailwind.config.{js,cjs,mjs,ts,json}
|
|
164
|
-
`spacing` and `fontSize` blocks.
|
|
165
|
-
|
|
163
|
+
2. **A Tailwind config** - every `tailwind.config.{js,cjs,mjs,ts,json}` in the tree (a monorepo
|
|
164
|
+
has one per app), its `colors`, `spacing` and `fontSize` blocks. A nested colour scale is
|
|
165
|
+
flattened to the class name Tailwind generates (`brand: {500: ...}` is `brand-500`, `DEFAULT`
|
|
166
|
+
is `brand`); a name two configs give different values keeps both (`ink@apps/b/...`). `unit`/
|
|
167
|
+
`base` in `spacing` states a config's unit, otherwise the greatest common divisor of its
|
|
168
|
+
scale does, and the floor's unit is the one every config's unit is a multiple of.
|
|
166
169
|
3. **A tokens/theme JSON** - `theme.json`, `tokens.json` or `design-tokens.json`,
|
|
167
170
|
its `colors`/`palette`, `spacing` and `fontSize` keys, found at any depth.
|
|
171
|
+
4. **A JS/TS theme module** - `theme`, `tokens`, `colors`, `colours` or `palette` `.ts/.js/.mjs`,
|
|
172
|
+
the floor a React Native or Expo app keeps in code: every top-level `const` object's colour
|
|
173
|
+
literals (`palette-ink`, `light-text`), the `spacing` object's numbers (unitless is px/dp), and
|
|
174
|
+
every `fontSize:` number.
|
|
168
175
|
|
|
169
176
|
```sh
|
|
170
177
|
~/.config/tezgah/bin/tezgah-design derive --repo . # writes .tezgah/design-contract.md
|
|
171
178
|
~/.config/tezgah/bin/tezgah-design derive --repo . --json # the same, machine-readable
|
|
172
179
|
```
|
|
173
180
|
|
|
174
|
-
`derive` writes nothing and exits 2 when the repository has none of the
|
|
181
|
+
`derive` writes nothing and exits 2 when the repository has none of the four, and it never
|
|
175
182
|
overwrites a contract that already exists - that file carries the component inventory someone
|
|
176
183
|
filled in, so a re-derive over changed tokens is a hand edit or a deliberate removal first. It
|
|
177
184
|
does not invent a palette, a type scale or a unit either: a repository with no design system gets
|
|
@@ -185,6 +192,20 @@ the artifact says so in its header: the `component-inventory` rule is inert unti
|
|
|
185
192
|
`{name, kind}` per component. A measurement names a component; only a person knows which ones the
|
|
186
193
|
repository owns, so list them in the turn that writes a new one.
|
|
187
194
|
|
|
195
|
+
`derive --suggest` is the starting list, never the inventory: it prints one candidate per
|
|
196
|
+
component file (a `.tsx/.jsx/.vue/.svelte/.astro/.swift/.kt/.dart` file under a
|
|
197
|
+
`components`/`widgets`/`ui` directory; tests, stories, mocks, a `.kt`/`.swift` file under `theme/` and Swift `+`
|
|
198
|
+
extensions aside; `Button.ios.tsx` is `Button`), minus the names the contract already lists. `kind`
|
|
199
|
+
is filled only where the name's last word says it, matched whole (`IconButton` is `interactive`,
|
|
200
|
+
`StatCard` is `data`, `FeedbackForm` is `null`), and `null` elsewhere. It writes nothing, exits 2
|
|
201
|
+
when there is no candidate, and exits 2 naming the contract when it cannot read it. Copy the rows
|
|
202
|
+
the repository owns into `components`, check every guessed kind and set every `null` one:
|
|
203
|
+
|
|
204
|
+
```sh
|
|
205
|
+
~/.config/tezgah/bin/tezgah-design derive --suggest --repo . # table + pasteable rows
|
|
206
|
+
~/.config/tezgah/bin/tezgah-design derive --suggest --repo . --json # {ok, candidates: [{name, kind, path}]}
|
|
207
|
+
```
|
|
208
|
+
|
|
188
209
|
## The check
|
|
189
210
|
|
|
190
211
|
```sh
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MC Dean
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/skills/harness/SKILL.md
CHANGED
|
@@ -66,6 +66,8 @@ is reported as such, and an empty list beside it is not evidence of absence.
|
|
|
66
66
|
|
|
67
67
|
`graph-review` splits findings into `confirmed` (no refuter refuted it), `refuted` (do not report as bugs),
|
|
68
68
|
and `unverified` (hit the cap, or every refuter failed - label them as such). Never promote a refuted or unverified finding.
|
|
69
|
+
Before the cap, findings with the same file, line and title merge into one. The merge keeps the higher severity
|
|
70
|
+
and lists every raising lens in `dimensions`; `caps.deduplicated` counts the merges.
|
|
69
71
|
|
|
70
72
|
`graph-review` is one review round, not a loop: inside a plan it is round one or round two of the
|
|
71
73
|
plan's single review (two rounds at most, `~/.config/tezgah/bin/tezgah-task review` refuses a third), never one run per
|
|
@@ -102,7 +102,10 @@ If something is left that only the reader can do - a decision, a credential or
|
|
|
102
102
|
grant, consent for a step that cannot be undone - name that ONE step, small
|
|
103
103
|
enough to start now. Several such decisions go in one numbered approval batch,
|
|
104
104
|
each with a recommended default, and the work proceeds on the defaults (the
|
|
105
|
-
tezgah contract's Ask policy)
|
|
105
|
+
tezgah contract's Ask policy) - except a class-F item (choosing a variant or
|
|
106
|
+
option a research, analysis or review recommends, a way around a tezgah
|
|
107
|
+
refusal, deciding about another session's work), which waits for the answer.
|
|
108
|
+
A step you can take yourself is not a next step:
|
|
106
109
|
take it, and never end on asking whether to. When nothing is left, end on what
|
|
107
110
|
now works.
|
|
108
111
|
|
|
@@ -110,6 +113,12 @@ Bad: "Hope that helps. Let me know if you want to dig deeper."
|
|
|
110
113
|
Bad: "Next: run `npm test` and paste the first failing line." (you can run it)
|
|
111
114
|
Good: "Blockers: the push to `main` - IRREVERSIBLE - `git push origin main` waits - your yes." (tr: "Engeller", GERİ ALINAMAZ)
|
|
112
115
|
|
|
116
|
+
In the host's plan mode nothing can run. There the plan is the deliverable:
|
|
117
|
+
the approval is the one step that is the reader's, every other step waits for
|
|
118
|
+
it, and nothing is reported as done or tested, because nothing ran. The Stop
|
|
119
|
+
rule reads the mode from the host and holds both ends of that
|
|
120
|
+
(`hooks/tezgah_integrity.py::read_only_turn`).
|
|
121
|
+
|
|
113
122
|
### 5. Suppress tangents
|
|
114
123
|
|
|
115
124
|
A second issue waits until the first is finished, then arrives as its own
|
package/skills/plan-add/SKILL.md
CHANGED
|
@@ -97,7 +97,11 @@ Input: `$ARGUMENTS` (free text describing the work). Run these steps in order.
|
|
|
97
97
|
is refused while the tree is dirty, because the phase starts by recording
|
|
98
98
|
`checkpoint:` - the commit the work returns to - and uncommitted work has none
|
|
99
99
|
to record. Make the checkpoint commit it prints, then run the phase move
|
|
100
|
-
again and the sha is written.
|
|
100
|
+
again and the sha is written. Before `implementation`, spawn one
|
|
101
|
+
tezgah-reviewer in plan-readiness mode over the plan file. Fix each major
|
|
102
|
+
finding in the plan, or ask the owner when only the owner can decide, then
|
|
103
|
+
move the phase. A plan of one small task may skip this review; say so in
|
|
104
|
+
one line. Move the phase yourself as the work reaches it
|
|
101
105
|
(`~/.config/tezgah/bin/tezgah-task phase implementation`, then `verification`); when a write is
|
|
102
106
|
refused for its path and the plan's work genuinely reaches that file, widen
|
|
103
107
|
the scope with `~/.config/tezgah/bin/tezgah-task allow <glob> ... --why "<reason>"`, which records the reason. Never retype the record:
|
|
@@ -7,6 +7,13 @@ Usage: python3 render_table.py [--pr-info 'NNN=text' ...] (extra text appended
|
|
|
7
7
|
when an item still under .tezgah/plans/open names no command either)
|
|
8
8
|
Creates .tezgah/plans/README.md, or appends the marker block to it, when either is missing."""
|
|
9
9
|
import glob, os, re, sys
|
|
10
|
+
if sys.argv[1:2] in (["-h"], ["--help"]):
|
|
11
|
+
print(__doc__)
|
|
12
|
+
sys.exit(0)
|
|
13
|
+
_bad = [a for a in sys.argv[1:] if a.startswith("-") and a not in ("--pr-info", "--acceptance", "--strict")]
|
|
14
|
+
if _bad:
|
|
15
|
+
print("render_table: unknown flag %s\n%s" % (_bad[0], __doc__.split("\n")[1]), file=sys.stderr)
|
|
16
|
+
sys.exit(2)
|
|
10
17
|
# The one reader of a plan file lives in the module that owns the record, so the
|
|
11
18
|
# table and the acceptance report cannot disagree about what a plan says. Three
|
|
12
19
|
# dirnames up from skills/plan-add/render_table.py is the tree that holds hooks/,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pawel Huryn
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/skills/research/SKILL.md
CHANGED
|
@@ -653,18 +653,27 @@ opened under):
|
|
|
653
653
|
| `criteria.json` | `question`, `baseline_variant` (V0, the status quo or current design) and `criteria[]`, each with `id` (`K1`), `name`, `kind` (`measured` or `judged`), `direction` (`max`, `min` or `pass`), optional `threshold` and `guardrail` |
|
|
654
654
|
| `variants.jsonl` | one row per variant: `id`, `title`, `artifact` (a path in the line or the repository, `git:<branch>` or `orx:<expId>`), `status` (`produced` or `dropped` with `drop_reason`), and `line` when it is an older line's deliverable |
|
|
655
655
|
| `comparison.jsonl` | one row per cell: `variant`, `criterion`, `value` with its `source`, or `not_checked` with the reason; `rater` on a judged cell. Append with `~/.config/tezgah/bin/tezgah-research compare <slug> <id>` (one JSON object on stdin), which refuses what `check` refuses |
|
|
656
|
-
| `decision.md` | `
|
|
656
|
+
| `decision.md` | `recommended: V2` (the line's pick), one line per rejected id naming the criterion id that ruled it out, and `flip:` - the result that would reverse the choice; `chosen:` and `owner:` only once the owner picks |
|
|
657
657
|
|
|
658
658
|
`check` refuses: fewer produced variants than the deliverable takes; two produced
|
|
659
659
|
files with the same content (a renamed copy is not an alternative); `criteria.json`
|
|
660
660
|
not committed before the first `comparison.jsonl` commit; a comparison row changed
|
|
661
661
|
after it was committed; a missing cell once `decision.md` exists or the line is
|
|
662
|
-
delivering; a `chosen` that is not a produced
|
|
663
|
-
criterion, no `flip:`; and a
|
|
664
|
-
numeric criterion. A judged cell with fewer
|
|
665
|
-
refuses). A design or plan (and an analysis on
|
|
666
|
-
`decisions/` warns while the line runs, is refused
|
|
667
|
-
the line open.
|
|
662
|
+
delivering; a pick (`chosen:`, else `recommended:`) that is not a produced
|
|
663
|
+
variant, a rejected variant with no criterion, no `flip:`; and a picked variant
|
|
664
|
+
another one beats or ties on every numeric criterion. A judged cell with fewer
|
|
665
|
+
than two raters warns (`--strict` refuses). A design or plan (and an analysis on
|
|
666
|
+
a line before rules 5) with no `decisions/` warns while the line runs, is refused
|
|
667
|
+
once it delivers, and keeps the line open.
|
|
668
|
+
|
|
669
|
+
The line recommends; the owner chooses. Choosing a variant or option a research,
|
|
670
|
+
analysis or review recommends is the owner's call, so a rules-9 line writes
|
|
671
|
+
`recommended:` and never `chosen:` itself - a `chosen:` with no `owner:` line
|
|
672
|
+
fails `check`. Report the recommendation and wait for the answer; building it
|
|
673
|
+
waits too. When the owner answers, record their words:
|
|
674
|
+
`~/.config/tezgah/bin/tezgah-research choose <slug> <decision> <variant> --ack
|
|
675
|
+
"<the owner's words>"` writes `chosen:` and `owner:`, on an open or a done line.
|
|
676
|
+
`conclude` names each recommendation still waiting for the owner.
|
|
668
677
|
|
|
669
678
|
When the variants are code or config, they are sibling orx nodes under one head
|
|
670
679
|
node (orx cardinal rule 4: fan within a round, then descend onto the winner), and
|
|
@@ -681,8 +690,12 @@ into `state.json` `closed` and `log.md`, seals its experiments' file hashes
|
|
|
681
690
|
way, and an edit after the seal fails `check`. The no-slug `check` reads a done
|
|
682
691
|
line by its seal alone and counts it on one line; its other errors show in
|
|
683
692
|
`check <slug>` and `check --all-lines`. `init --allow-open` is refused
|
|
684
|
-
while an open line has `check` errors
|
|
685
|
-
`
|
|
693
|
+
while an open line has `check` errors, other than a criterion whose verdict is
|
|
694
|
+
still `pending` (a line in flight). A line another session is running is not this
|
|
695
|
+
session's to fix or close: when a refusal names one, stop and ask the owner -
|
|
696
|
+
never a way around a tezgah refusal (another folder, a hidden dir). A hidden dir
|
|
697
|
+
under `research/` is not a line: `check` and `status` fail it. A line moves to
|
|
698
|
+
another checkout with `import`, never as a copy: a copy loses the order proof.
|
|
686
699
|
|
|
687
700
|
Finally, a `## Patterns` bullet names what it generalises from - a claim id, a
|
|
688
701
|
`literature/` note or a run - because a pattern is the one finding a reader carries
|