@ethlete/agent-rules 0.1.0-next.2 → 0.1.0-next.21
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 +275 -0
- package/README.md +350 -29
- package/THIRD-PARTY-LICENSES.md +35 -0
- package/content/git-hooks/post-checkout.sh +10 -0
- package/content/git-hooks/pre-push.sh +5 -0
- package/content/hooks/context-warning.py +766 -108
- package/content/hooks/subagent-model-policy.py +193 -0
- package/content/output-styles/ste-clarity.md +131 -0
- package/content/rules/app-styling.md +59 -0
- package/content/rules/comments.md +53 -20
- package/content/rules/lint-and-format.md +6 -5
- package/content/rules/nx-layout.md +47 -0
- package/content/rules/reactive-state.md +1 -1
- package/content/rules/styling.md +9 -7
- package/content/rules/subagent-models.md +29 -0
- package/content/skills/angular-patterns/SKILL.md +30 -7
- package/content/skills/api-source/SKILL.md +130 -0
- package/content/skills/app-testing/SKILL.md +141 -0
- package/content/skills/codex-subagent/SKILL.md +90 -0
- package/content/skills/codex-subagent/codex-agent.mjs +229 -0
- package/content/skills/design-exploration/SKILL.md +205 -0
- package/content/skills/design-exploration/check-story.mjs +139 -0
- package/content/skills/design-exploration/shoot-template.mjs +61 -0
- package/content/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/content/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/content/skills/domain-modeling/SKILL.md +80 -0
- package/content/skills/figma-export/SKILL.md +191 -0
- package/content/skills/figma-export/dump-figma-layers.py +83 -0
- package/content/skills/figma-export/dump-figma-svg.py +235 -0
- package/content/skills/figma-export/measure-template.mjs +87 -0
- package/content/skills/git-commit/SKILL.md +6 -7
- package/content/skills/git-flow/SKILL.md +87 -0
- package/content/skills/grill-with-docs/SKILL.md +13 -0
- package/content/skills/grilling/SKILL.md +34 -0
- package/content/skills/handoff/SKILL.md +74 -4
- package/content/skills/query/SKILL.md +165 -64
- package/content/skills/rxjs-signals/SKILL.md +31 -4
- package/content/skills/sdk-docs/SKILL.md +50 -14
- package/content/skills/sdk-local-build/SKILL.md +30 -10
- package/content/skills/sdk-local-build/sdk-local-baseline.mjs +64 -0
- package/content/skills/sdk-source/SKILL.md +49 -21
- package/content/skills/sdk-update/SKILL.md +96 -0
- package/content/skills/story-styling/SKILL.md +10 -8
- package/content/skills/styleguide/SKILL.md +12 -7
- package/content/skills/styleguide/assets.md +54 -0
- package/content/skills/styleguide/changesets.md +17 -0
- package/content/skills/styleguide/file-structure.md +73 -0
- package/content/skills/styleguide/lint-rule-lookup.md +51 -0
- package/content/skills/styleguide/storybook-structure.md +25 -0
- package/content/skills/theming/SKILL.md +41 -28
- package/content/skills/timetrack/SKILL.md +397 -0
- package/content/skills/verify-in-app/SKILL.md +107 -0
- package/migrations/app-styling-utilities.md +63 -0
- package/migrations/list-state-query-form.md +20 -0
- package/migrations/nx-layout.md +29 -0
- package/migrations/sdk-components-over-hand-built-ui.md +27 -0
- package/migrations/search-query-field.md +52 -0
- package/migrations.json +48 -0
- package/package.json +16 -2
- package/src/index.js +63 -28
- package/src/index.js.map +1 -1
- package/src/lib/cli-args.d.ts +9 -0
- package/src/lib/cli-args.js +48 -0
- package/src/lib/cli-args.js.map +1 -0
- package/src/lib/commitlint.d.ts +10 -0
- package/src/lib/commitlint.js +51 -0
- package/src/lib/commitlint.js.map +1 -0
- package/src/lib/config.d.ts +38 -4
- package/src/lib/config.js +84 -8
- package/src/lib/config.js.map +1 -1
- package/src/lib/filter.d.ts +2 -0
- package/src/lib/filter.js +4 -4
- package/src/lib/filter.js.map +1 -1
- package/src/lib/frontmatter.d.ts +2 -0
- package/src/lib/frontmatter.js +33 -5
- package/src/lib/frontmatter.js.map +1 -1
- package/src/lib/git-flow/build.d.ts +35 -0
- package/src/lib/git-flow/build.js +24 -0
- package/src/lib/git-flow/build.js.map +1 -0
- package/src/lib/git-flow/config.d.ts +59 -0
- package/src/lib/git-flow/config.js +50 -0
- package/src/lib/git-flow/config.js.map +1 -0
- package/src/lib/git-flow/index.d.ts +6 -0
- package/src/lib/git-flow/index.js +10 -0
- package/src/lib/git-flow/index.js.map +1 -0
- package/src/lib/git-flow/parse.d.ts +49 -0
- package/src/lib/git-flow/parse.js +300 -0
- package/src/lib/git-flow/parse.js.map +1 -0
- package/src/lib/git-flow/rename.d.ts +24 -0
- package/src/lib/git-flow/rename.js +70 -0
- package/src/lib/git-flow/rename.js.map +1 -0
- package/src/lib/git-flow/start.d.ts +49 -0
- package/src/lib/git-flow/start.js +57 -0
- package/src/lib/git-flow/start.js.map +1 -0
- package/src/lib/git-flow/validate.d.ts +34 -0
- package/src/lib/git-flow/validate.js +72 -0
- package/src/lib/git-flow/validate.js.map +1 -0
- package/src/lib/git-flow-command.d.ts +4 -0
- package/src/lib/git-flow-command.js +157 -0
- package/src/lib/git-flow-command.js.map +1 -0
- package/src/lib/git-flow-repair.d.ts +17 -0
- package/src/lib/git-flow-repair.js +157 -0
- package/src/lib/git-flow-repair.js.map +1 -0
- package/src/lib/git-flow-start.d.ts +20 -0
- package/src/lib/git-flow-start.js +132 -0
- package/src/lib/git-flow-start.js.map +1 -0
- package/src/lib/git.d.ts +64 -0
- package/src/lib/git.js +131 -0
- package/src/lib/git.js.map +1 -0
- package/src/lib/gitlab.d.ts +35 -0
- package/src/lib/gitlab.js +108 -0
- package/src/lib/gitlab.js.map +1 -0
- package/src/lib/index.d.ts +2 -0
- package/src/lib/index.js +2 -0
- package/src/lib/index.js.map +1 -1
- package/src/lib/migrate.js +1 -1
- package/src/lib/migrate.js.map +1 -1
- package/src/lib/output-style-command.d.ts +3 -0
- package/src/lib/output-style-command.js +69 -0
- package/src/lib/output-style-command.js.map +1 -0
- package/src/lib/output-style.d.ts +38 -0
- package/src/lib/output-style.js +134 -0
- package/src/lib/output-style.js.map +1 -0
- package/src/lib/owned-paths.js +27 -3
- package/src/lib/owned-paths.js.map +1 -1
- package/src/lib/package-runner.d.ts +3 -0
- package/src/lib/package-runner.js +69 -0
- package/src/lib/package-runner.js.map +1 -0
- package/src/lib/plain-text.d.ts +9 -0
- package/src/lib/plain-text.js +22 -0
- package/src/lib/plain-text.js.map +1 -0
- package/src/lib/plan.d.ts +6 -6
- package/src/lib/plan.js +147 -40
- package/src/lib/plan.js.map +1 -1
- package/src/lib/prompt.d.ts +8 -0
- package/src/lib/prompt.js +27 -0
- package/src/lib/prompt.js.map +1 -0
- package/src/lib/render.d.ts +24 -2
- package/src/lib/render.js +39 -10
- package/src/lib/render.js.map +1 -1
- package/src/lib/sync.d.ts +0 -1
- package/src/lib/sync.js +23 -15
- package/src/lib/sync.js.map +1 -1
- package/src/lib/targets/agents-skills.js +1 -1
- package/src/lib/targets/agents-skills.js.map +1 -1
- package/src/lib/targets/claude-hooks.d.ts +1 -23
- package/src/lib/targets/claude-hooks.js +17 -85
- package/src/lib/targets/claude-hooks.js.map +1 -1
- package/src/lib/targets/claude.js +1 -2
- package/src/lib/targets/claude.js.map +1 -1
- package/src/lib/targets/codex-hooks.d.ts +12 -0
- package/src/lib/targets/codex-hooks.js +34 -0
- package/src/lib/targets/codex-hooks.js.map +1 -0
- package/src/lib/targets/codex.js +4 -4
- package/src/lib/targets/codex.js.map +1 -1
- package/src/lib/targets/copilot.js +3 -3
- package/src/lib/targets/copilot.js.map +1 -1
- package/src/lib/targets/cursor.js +2 -2
- package/src/lib/targets/cursor.js.map +1 -1
- package/src/lib/targets/git-hooks.d.ts +24 -0
- package/src/lib/targets/git-hooks.js +71 -0
- package/src/lib/targets/git-hooks.js.map +1 -0
- package/src/lib/targets/hooks-shared.d.ts +58 -0
- package/src/lib/targets/hooks-shared.js +128 -0
- package/src/lib/targets/hooks-shared.js.map +1 -0
- package/src/lib/targets/shared.d.ts +1 -9
- package/src/lib/targets/shared.js +4 -8
- package/src/lib/targets/shared.js.map +1 -1
- package/src/lib/timetrack-command.d.ts +11 -0
- package/src/lib/timetrack-command.js +1055 -0
- package/src/lib/timetrack-command.js.map +1 -0
- package/src/lib/timetrack-snapshot.d.ts +55 -0
- package/src/lib/timetrack-snapshot.js +120 -0
- package/src/lib/timetrack-snapshot.js.map +1 -0
- package/src/lib/timetrack.d.ts +561 -0
- package/src/lib/timetrack.js +271 -0
- package/src/lib/timetrack.js.map +1 -0
- package/content/skills/styleguide/STYLEGUIDE.md +0 -520
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""PreToolUse hook: make a subagent's model an explicit choice.
|
|
3
|
+
|
|
4
|
+
A `Task` call that passes no `model` runs the subagent on the parent session's
|
|
5
|
+
model. A session led by an expensive model therefore spends that model on every
|
|
6
|
+
grep and every file read it delegates, which burns a usage limit in minutes for
|
|
7
|
+
work a small model does just as well.
|
|
8
|
+
|
|
9
|
+
So this hook gates the tool call rather than only advising:
|
|
10
|
+
|
|
11
|
+
* No `model` at all - denied, with the model table returned to the agent. The
|
|
12
|
+
agent then repeats the call with a model, so the cost of the mistake is one
|
|
13
|
+
tool call, not a whole subagent run.
|
|
14
|
+
* `model: fable` - the user is asked. Fable is the right pick for planning,
|
|
15
|
+
design, cross-cutting review and leading other subagents, and the wrong pick
|
|
16
|
+
for the twentieth mechanical lookup; only the user knows which this is.
|
|
17
|
+
* Any other model - allowed silently.
|
|
18
|
+
|
|
19
|
+
Two cases are left alone, because the tool's `model` would change nothing:
|
|
20
|
+
|
|
21
|
+
* `subagent_type: fork`, which always inherits the parent model.
|
|
22
|
+
* An agent type whose own definition under `.claude/agents/` sets a model.
|
|
23
|
+
|
|
24
|
+
Can be disabled per machine via a gitignored ethlete-agents.config.local.json at
|
|
25
|
+
the repo root: {"disableHooks": true} or {"disableHooks": ["subagent-model-policy"]}.
|
|
26
|
+
|
|
27
|
+
Fail-safe: any error exits 0 with no output - the hook must never block work
|
|
28
|
+
because it could not read its own input.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import sys
|
|
34
|
+
|
|
35
|
+
HOOK_NAME = "subagent-model-policy"
|
|
36
|
+
LOCAL_CONFIG_FILE = "ethlete-agents.config.local.json"
|
|
37
|
+
|
|
38
|
+
# The tool that spawns a subagent, under both names it has carried.
|
|
39
|
+
SUBAGENT_TOOLS = ("Task", "Agent")
|
|
40
|
+
|
|
41
|
+
POLICY = """A subagent's model is a choice, not an inheritance. This call passes no `model`, so the subagent \
|
|
42
|
+
would run on the model leading this session - which is how one expensive model ends up doing every small job. \
|
|
43
|
+
Call the tool again with `model` set:
|
|
44
|
+
|
|
45
|
+
- `haiku` - lookups and small, fully specified jobs: grep, find, read and summarize files or a CI log, run a command and report, a mechanical edit in named files.
|
|
46
|
+
- `sonnet` - scoped work with a clear target: a fix in a named file, a spec, a docs page, a lint cleanup, \
|
|
47
|
+
research that needs reasoning. Sonnet 5.5 is close to opus here and costs less.
|
|
48
|
+
- `opus` - hard work: a bug with no known cause, a change across many files, a review of a diff, and any task \
|
|
49
|
+
sonnet did not finish.
|
|
50
|
+
- `fable` - judgment-heavy work: planning, design, cross-cutting review, leading other subagents. The most \
|
|
51
|
+
expensive of the four, so choosing it asks the user first.
|
|
52
|
+
|
|
53
|
+
Effort follows the prompt, not a parameter: scope the prompt to one question, name what "done" is, and say \
|
|
54
|
+
"keep it brief" for a lookup. An agent type defined under `.claude/agents/` carries its own model and reasoning \
|
|
55
|
+
effort, so a call that names one needs no `model` of its own."""
|
|
56
|
+
|
|
57
|
+
FABLE_REASON = """This subagent would run on fable, the most expensive model. Approve it where the task is \
|
|
58
|
+
judgment-heavy - planning, design, cross-cutting review, or leading other subagents. Deny it for implementation \
|
|
59
|
+
(`model: "sonnet"` or `"opus"`) or for a lookup or a small, fully specified job (`model: "haiku"`), then repeat the call with that model."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def agent_name(argv):
|
|
63
|
+
"""The --agent value, defaulting to claude — older registrations pass no flag."""
|
|
64
|
+
for index, arg in enumerate(argv):
|
|
65
|
+
if arg == "--agent" and index + 1 < len(argv):
|
|
66
|
+
return argv[index + 1]
|
|
67
|
+
if arg.startswith("--agent="):
|
|
68
|
+
return arg.split("=", 1)[1]
|
|
69
|
+
return "claude"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def repo_root(data):
|
|
73
|
+
"""Repo root: the env var the agent sets, else the script's own location, else the cwd.
|
|
74
|
+
|
|
75
|
+
The script always lives at <root>/.<agent>/hooks/ethlete/subagent-model-policy.py, so
|
|
76
|
+
walking four levels up works for any agent that has no project-dir variable of its own.
|
|
77
|
+
"""
|
|
78
|
+
for variable in ("CLAUDE_PROJECT_DIR", "CODEX_PROJECT_DIR"):
|
|
79
|
+
value = os.environ.get(variable)
|
|
80
|
+
if value:
|
|
81
|
+
return value
|
|
82
|
+
here = os.path.abspath(__file__)
|
|
83
|
+
derived = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(here))))
|
|
84
|
+
if os.path.isfile(os.path.join(derived, LOCAL_CONFIG_FILE)):
|
|
85
|
+
return derived
|
|
86
|
+
cwd = data.get("cwd")
|
|
87
|
+
return cwd if isinstance(cwd, str) and cwd else derived
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def load_local_config(root):
|
|
91
|
+
"""Parsed ethlete-agents.config.local.json at the repo root, or {} if missing/unreadable."""
|
|
92
|
+
if not root:
|
|
93
|
+
return {}
|
|
94
|
+
try:
|
|
95
|
+
with open(os.path.join(root, LOCAL_CONFIG_FILE), encoding="utf-8") as f:
|
|
96
|
+
config = json.load(f)
|
|
97
|
+
except (OSError, ValueError, AttributeError):
|
|
98
|
+
return {}
|
|
99
|
+
return config if isinstance(config, dict) else {}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def disabled_locally(config):
|
|
103
|
+
"""True when the local config disables this hook (or all hooks) on this machine."""
|
|
104
|
+
disabled = config.get("disableHooks")
|
|
105
|
+
return disabled is True or (isinstance(disabled, list) and HOOK_NAME in disabled)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def definition_sets_model(root, subagent_type):
|
|
109
|
+
"""True when a project or user agent definition of that name declares its own model."""
|
|
110
|
+
if not subagent_type:
|
|
111
|
+
return False
|
|
112
|
+
file_name = f"{subagent_type.split(':')[-1]}.md".lower()
|
|
113
|
+
candidates = []
|
|
114
|
+
for directory in (
|
|
115
|
+
os.path.join(root or ".", ".claude", "agents"),
|
|
116
|
+
os.path.join(os.path.expanduser("~"), ".claude", "agents"),
|
|
117
|
+
):
|
|
118
|
+
try:
|
|
119
|
+
entries = os.listdir(directory)
|
|
120
|
+
except OSError:
|
|
121
|
+
continue
|
|
122
|
+
candidates += [os.path.join(directory, entry) for entry in entries if entry.lower() == file_name]
|
|
123
|
+
for path in candidates:
|
|
124
|
+
try:
|
|
125
|
+
with open(path, encoding="utf-8") as f:
|
|
126
|
+
head = f.read(4096)
|
|
127
|
+
except OSError:
|
|
128
|
+
continue
|
|
129
|
+
for line in head.split("\n"):
|
|
130
|
+
key, separator, value = line.partition(":")
|
|
131
|
+
if separator and key.strip() == "model" and value.strip():
|
|
132
|
+
return True
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def decision_for(tool_input, root):
|
|
137
|
+
"""The (decision, reason) this call needs, or None when it may run untouched."""
|
|
138
|
+
subagent_type = str(tool_input.get("subagent_type") or "").strip().lower()
|
|
139
|
+
model = str(tool_input.get("model") or "").strip().lower()
|
|
140
|
+
|
|
141
|
+
if "fable" in model:
|
|
142
|
+
return "ask", FABLE_REASON
|
|
143
|
+
if model:
|
|
144
|
+
return None
|
|
145
|
+
if subagent_type == "fork" or definition_sets_model(root, subagent_type):
|
|
146
|
+
return None
|
|
147
|
+
return "deny", POLICY
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def main():
|
|
151
|
+
if agent_name(sys.argv[1:]) != "claude":
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
data = json.load(sys.stdin)
|
|
155
|
+
|
|
156
|
+
if data.get("tool_name") not in SUBAGENT_TOOLS:
|
|
157
|
+
return
|
|
158
|
+
|
|
159
|
+
tool_input = data.get("tool_input")
|
|
160
|
+
|
|
161
|
+
if not isinstance(tool_input, dict):
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
root = repo_root(data)
|
|
165
|
+
|
|
166
|
+
if disabled_locally(load_local_config(root)):
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
outcome = decision_for(tool_input, root)
|
|
170
|
+
|
|
171
|
+
if not outcome:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
decision, reason = outcome
|
|
175
|
+
print(
|
|
176
|
+
json.dumps(
|
|
177
|
+
{
|
|
178
|
+
"hookSpecificOutput": {
|
|
179
|
+
"hookEventName": "PreToolUse",
|
|
180
|
+
"permissionDecision": decision,
|
|
181
|
+
"permissionDecisionReason": reason,
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
if __name__ == "__main__":
|
|
189
|
+
try:
|
|
190
|
+
main()
|
|
191
|
+
except Exception:
|
|
192
|
+
pass
|
|
193
|
+
sys.exit(0)
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ste-clarity
|
|
3
|
+
description: Write all prose in ASD-STE100 Simplified Technical English — approved words, simple verb forms, active voice, one idea per sentence.
|
|
4
|
+
keep-coding-instructions: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# ASD-STE100 output style
|
|
8
|
+
|
|
9
|
+
Write every sentence you show the user in Simplified Technical English, as specified
|
|
10
|
+
by ASD-STE100. Clarity comes first. Never sacrifice technical accuracy for brevity.
|
|
11
|
+
|
|
12
|
+
## Scope
|
|
13
|
+
|
|
14
|
+
Apply STE to your own prose: answers, explanations, plans, summaries, commit
|
|
15
|
+
messages, and pull request text.
|
|
16
|
+
|
|
17
|
+
Do not apply STE to:
|
|
18
|
+
|
|
19
|
+
- Code, identifiers, file paths, and command names.
|
|
20
|
+
- Quoted material: error messages, log lines, command output, and text from files.
|
|
21
|
+
Copy these exactly. Never rewrite a quote to make it simpler.
|
|
22
|
+
- Text the user asks you to write in a different style.
|
|
23
|
+
|
|
24
|
+
Markdown is still available. Use code blocks, and use `file.ts:42` references.
|
|
25
|
+
|
|
26
|
+
## Words
|
|
27
|
+
|
|
28
|
+
1. Use the most common, shortest word that has one clear meaning.
|
|
29
|
+
2. Use one word for one meaning. Do not use synonyms for the same thing. If you
|
|
30
|
+
write "flag" for a variable, do not later call it a "switch" or an "option".
|
|
31
|
+
3. Use one meaning for one word. Do not use "close" for both a state and an action
|
|
32
|
+
if this can confuse the reader.
|
|
33
|
+
4. Technical names are permitted. Use the exact name from the code or the domain.
|
|
34
|
+
5. Do not use slang, idioms, or figures of speech. "The build blew up" is wrong.
|
|
35
|
+
"The build failed" is correct.
|
|
36
|
+
6. Do not use "etc." Complete the list, or write "for example" and give two items.
|
|
37
|
+
7. Do not use a noun as a verb. Write "make a request", not "request it", when
|
|
38
|
+
"request" is a name in the code.
|
|
39
|
+
|
|
40
|
+
Prefer the word on the right:
|
|
41
|
+
|
|
42
|
+
| Not approved | Use |
|
|
43
|
+
| ------------------------ | ----------------- |
|
|
44
|
+
| utilize, employ | use |
|
|
45
|
+
| initiate, commence | start |
|
|
46
|
+
| terminate | stop, end |
|
|
47
|
+
| perform, execute | do, run |
|
|
48
|
+
| modify | change |
|
|
49
|
+
| indicate | show |
|
|
50
|
+
| ascertain, determine | find, find out |
|
|
51
|
+
| assist, aid | help |
|
|
52
|
+
| attempt | try |
|
|
53
|
+
| require | need |
|
|
54
|
+
| sufficient | enough |
|
|
55
|
+
| additional | more |
|
|
56
|
+
| numerous, multiple | many |
|
|
57
|
+
| approximately | about |
|
|
58
|
+
| currently, presently | now |
|
|
59
|
+
| prior to | before |
|
|
60
|
+
| subsequent to, following | after |
|
|
61
|
+
| in order to | to |
|
|
62
|
+
| due to the fact that | because |
|
|
63
|
+
| via | with, by, through |
|
|
64
|
+
| such as | for example |
|
|
65
|
+
| in the event that | if |
|
|
66
|
+
| at this point in time | now |
|
|
67
|
+
|
|
68
|
+
This table is a guide, not the full ASD-STE100 Dictionary. The Dictionary holds about
|
|
69
|
+
900 approved words, each with one approved part of speech and one approved meaning.
|
|
70
|
+
When you do not know if a word is approved, choose the shortest common word that has
|
|
71
|
+
one clear meaning.
|
|
72
|
+
|
|
73
|
+
## Verbs
|
|
74
|
+
|
|
75
|
+
1. Use only these verb forms: the infinitive, the imperative, the simple present, the
|
|
76
|
+
simple past, the simple future, and the past participle as an adjective.
|
|
77
|
+
2. Do not use complex tenses. Write "I changed the config", not "I have changed the
|
|
78
|
+
config".
|
|
79
|
+
3. Do not use "-ing" forms, unless the form is part of a technical name, for example
|
|
80
|
+
"a logging module".
|
|
81
|
+
4. Use the active voice. Write "the parser drops the field", not "the field is dropped
|
|
82
|
+
by the parser". Use the passive voice only when the actor is unknown or does not
|
|
83
|
+
matter.
|
|
84
|
+
5. Do not leave out a verb to make a sentence shorter.
|
|
85
|
+
|
|
86
|
+
## Noun phrases
|
|
87
|
+
|
|
88
|
+
1. Do not build a cluster of more than three nouns. Break up "user account balance
|
|
89
|
+
sync failure" as "a failure in the sync of the user account balance".
|
|
90
|
+
2. Add articles ("a", "the") where they help the reader.
|
|
91
|
+
|
|
92
|
+
## Sentences
|
|
93
|
+
|
|
94
|
+
1. Instructions: 20 words maximum.
|
|
95
|
+
2. Descriptions: 25 words maximum.
|
|
96
|
+
3. One idea per sentence. One instruction per sentence.
|
|
97
|
+
4. A paragraph holds 6 sentences maximum, and covers one topic.
|
|
98
|
+
5. Put a condition at the start of the sentence, before the instruction. Write "If the
|
|
99
|
+
sync fails, restart the server".
|
|
100
|
+
6. Use connecting words such as "however", "therefore", and "then" to show the link
|
|
101
|
+
between sentences.
|
|
102
|
+
7. Use a vertical list when the content has more than three parallel parts. Do not use
|
|
103
|
+
a list to give structure to a single idea.
|
|
104
|
+
|
|
105
|
+
## Procedures
|
|
106
|
+
|
|
107
|
+
1. Use the imperative for each step. Write "Open the file", not "You should open the
|
|
108
|
+
file" or "We can open the file".
|
|
109
|
+
2. Number the steps in the order the user must do them.
|
|
110
|
+
3. Give the result after the action, if the result is not obvious.
|
|
111
|
+
|
|
112
|
+
## Warnings
|
|
113
|
+
|
|
114
|
+
1. Put a warning or a caution before the step it applies to, never after.
|
|
115
|
+
2. Start the warning with a clear command. Write "Do not run this on the production
|
|
116
|
+
database. The command deletes all rows."
|
|
117
|
+
3. State the consequence in a separate, simple sentence.
|
|
118
|
+
|
|
119
|
+
## Punctuation
|
|
120
|
+
|
|
121
|
+
1. Do not use a slash to mean "and" or "or". Write "the date or the payee".
|
|
122
|
+
2. Do not use parentheses for information the reader needs. Put it in its own sentence.
|
|
123
|
+
3. Do not use a dash to join two thoughts. Use two sentences.
|
|
124
|
+
4. Use a hyphen only to make a compound word clear.
|
|
125
|
+
|
|
126
|
+
## When STE and accuracy conflict
|
|
127
|
+
|
|
128
|
+
Accuracy wins. If a simple word would hide a real distinction, use the exact term and
|
|
129
|
+
define it once in a short sentence. Never simplify a claim about what the code does.
|
|
130
|
+
Keep the difference between what you verified and what you assume: write "I ran the
|
|
131
|
+
tests and they passed" or "I did not run the tests", not a sentence that blurs the two.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: app-styling
|
|
3
|
+
description: App components are styled with Tailwind utilities in their templates; app CSS stays out of @layer components; the SDK needs a 10px rem root; hardcoded colours are never primary values.
|
|
4
|
+
kind: rule
|
|
5
|
+
scope: consumer
|
|
6
|
+
requires: ['@ethlete/core']
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Styling
|
|
10
|
+
|
|
11
|
+
Every component in this workspace (views, pages, shells, shared UI in `libs/`) is laid out
|
|
12
|
+
and styled with **Tailwind utility classes in its template**, including the colour utilities
|
|
13
|
+
the app's theme provides: the generated SDK theme utilities (`bg-et-surface-bg`,
|
|
14
|
+
`border-et-surface-border`, `text-et-<theme>`) where the app generates them, or the palette its
|
|
15
|
+
Tailwind config defines. Do not
|
|
16
|
+
invent a BEM class system, and do not write a `.css` file for layout, spacing or
|
|
17
|
+
typography a utility expresses. Every app component sets `encapsulation: ViewEncapsulation.None`,
|
|
18
|
+
as the `require-view-encapsulation-none` lint rule requires. Its CSS is then global, so give the
|
|
19
|
+
component a host class (`host: { class: 'app-player-card' }`) and scope every selector under it,
|
|
20
|
+
the way SDK components scope theirs under their `et-` classes. Never write a bare element or
|
|
21
|
+
unprefixed class selector in a component stylesheet.
|
|
22
|
+
|
|
23
|
+
In an existing repo, apply this to the component you are editing, not to the rest of the app. A
|
|
24
|
+
legacy component that relies on emulated encapsulation keeps it until its CSS is scoped under a
|
|
25
|
+
host class: without that scope, `ViewEncapsulation.None` leaks its CSS into every view.
|
|
26
|
+
|
|
27
|
+
Write CSS only for what utilities cannot express. With Tailwind 4, keep that CSS **unlayered**
|
|
28
|
+
or in `@layer utilities` — never in `@layer components`, and that includes the global
|
|
29
|
+
stylesheet. SDK component styles are injected into `@layer components` at runtime, after your
|
|
30
|
+
stylesheet, so an app rule in the same layer ties on layer and loses on source order. With
|
|
31
|
+
Tailwind 3, `@layer` is a Tailwind directive that emits unlayered CSS, so app CSS already beats
|
|
32
|
+
the SDK's layered styles.
|
|
33
|
+
|
|
34
|
+
To change an SDK component's look, set its `--et-*` tokens first (documented per
|
|
35
|
+
component), then use a utility class or unlayered CSS on the element — see "Overriding
|
|
36
|
+
component styles" in the docs site's components overview.
|
|
37
|
+
|
|
38
|
+
The SDK sizes everything in `rem` on a **10px root**: the app's global stylesheet must set
|
|
39
|
+
`html { font-size: 62.5%; }`. Without it every SDK control renders 1.6× too large. With it,
|
|
40
|
+
`1rem` is `10px`, so the app's own `rem` values stay easy to read.
|
|
41
|
+
|
|
42
|
+
The 10px root also shrinks every rem-based Tailwind scale to 62.5% of its nominal size (1.6×
|
|
43
|
+
smaller): `p-4` is 10px instead of 16px, and `max-w-3xl` is 480px instead of 768px. Keep the
|
|
44
|
+
spacing scale on its 4px step: with Tailwind 4, set `--spacing: 0.4rem` in the app's `@theme`,
|
|
45
|
+
as the SDK's own Storybook does; with Tailwind 3, redefine `theme.spacing` in
|
|
46
|
+
`tailwind.config`. In an app that already uses utilities without it, the change rescales every
|
|
47
|
+
view: tell the user rather than make it. Redefine any other rem scale the app uses (text,
|
|
48
|
+
container, radius) the same way, or use px where a size matters. Breakpoints are not affected:
|
|
49
|
+
a media query's `rem` ignores the root font size.
|
|
50
|
+
|
|
51
|
+
**Never use a hardcoded colour as the primary value.** Backgrounds, text, borders and
|
|
52
|
+
interaction states resolve from the app's theme: the SDK surface and colour tokens
|
|
53
|
+
(`--et-surface-*-solid`, `--et-theme-color-*`) and their generated utilities, or the palette
|
|
54
|
+
the app's Tailwind config defines. Never write an arbitrary colour value such as
|
|
55
|
+
`bg-[#1a1a1a]` in a template. A static fallback inside `var(--token, <fallback>)` is
|
|
56
|
+
permitted, but not required.
|
|
57
|
+
|
|
58
|
+
Theme **names** (`brand`, `danger`, `dark-elevated`, …) are registered by this app; the SDK
|
|
59
|
+
ships none. Semantic colours resolve by theme `type` (e.g. `injectSemanticColorTheme('error')`).
|
|
@@ -1,31 +1,64 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: comments
|
|
3
|
-
description:
|
|
3
|
+
description: Comments are an allowlist of four cases. Everything else gets deleted before the change is done.
|
|
4
4
|
kind: rule
|
|
5
5
|
scope: both
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
## Comments:
|
|
8
|
+
## Comments: almost none, and never for the reviewer of your change
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
**Write no comment unless it fits one of the four cases below.** This is an allowlist, not a
|
|
11
|
+
set of tips. Anything outside it gets **deleted** before you call the change done — not
|
|
12
|
+
softened, not shortened. Code that needs prose to be understood needs a better name, a
|
|
13
|
+
smaller function, or a type; fix that instead of narrating it.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
### The only comments allowed
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- **Restating the code.** `// increment the counter` above `counter++`.
|
|
17
|
+
1. **An ordering or timing constraint** a reasonable edit would break. Say what breaks.
|
|
18
|
+
2. **An invariant the types cannot express**, that a caller or a future edit could violate.
|
|
19
|
+
3. **A workaround**, naming its concrete cause (browser bug, upstream issue, framework
|
|
20
|
+
limitation) and linking it where a link exists, so the next reader can tell when it may go.
|
|
21
|
+
4. **Public API JSDoc** — what it does and how to call it, on something a lib actually
|
|
22
|
+
exports. One or two sentences. Not internals, not history, not why it is shaped that way.
|
|
23
|
+
In an application nothing is public API unless it lives in a shared lib other projects
|
|
24
|
+
import, so this case rarely applies to app code.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
use it — not why it is shaped that way).
|
|
26
|
+
Nothing else qualifies. Not "this is subtle", not "worth noting", not a heading over a group
|
|
27
|
+
of members, not a summary of the function underneath it.
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
### The test each one still has to pass
|
|
30
|
+
|
|
31
|
+
Delete it unless **both** are true:
|
|
32
|
+
|
|
33
|
+
- a competent reader who never sees your diff would be **surprised** without it, and
|
|
34
|
+
- a future edit could **break something** that this sentence is the only warning about.
|
|
35
|
+
|
|
36
|
+
Unsure counts as no. A missing comment costs a minute of reading; a stale one misleads for
|
|
37
|
+
years.
|
|
38
|
+
|
|
39
|
+
### Always delete
|
|
40
|
+
|
|
41
|
+
- **Restating the code** — `// increment the counter` over `counter++`; a JSDoc on `size` that
|
|
42
|
+
says nothing beyond "the size of the button".
|
|
43
|
+
- **Section headers and dividers** — `// --- Inputs ---`, `// Helpers`, `// Public API`.
|
|
44
|
+
- **Rationale for a mechanical choice** — `Record<Size, X>` with literal keys, a `@__PURE__`
|
|
45
|
+
annotation, a factory instead of a literal, a helper moved into its own file. The type, the
|
|
46
|
+
annotation and the import already say what happens.
|
|
47
|
+
- **Migration narration** — "moved here from X", "used to be a tuple", "so Y no longer pulls
|
|
48
|
+
Z", "renamed for clarity", "see ADR-0012". Git and the ADR index know; the next reader does
|
|
49
|
+
not care.
|
|
50
|
+
- **The same explanation at every call site.** Explain a pattern once where it is defined (the
|
|
51
|
+
helper's JSDoc, the lint rule's message, the guide) and let every use site stay silent.
|
|
52
|
+
- **Commented-out code.**
|
|
53
|
+
- **`TODO`/`FIXME` without an issue link.** Fix it now or leave nothing.
|
|
54
|
+
- **Hedging and meta** — "note that", "for clarity", "just in case", "this is cleaner", "we
|
|
55
|
+
could also…".
|
|
56
|
+
|
|
57
|
+
### Before you call the change done
|
|
58
|
+
|
|
59
|
+
Re-read every comment in your diff and cut the ones that are not one of the four. Then fix or
|
|
60
|
+
delete any existing comment your change made wrong — one describing behaviour that no longer
|
|
61
|
+
exists is worse than none.
|
|
62
|
+
|
|
63
|
+
Two signals you have already over-commented: you wrote the word "because", or the diff adds
|
|
64
|
+
more than a handful of comments. Both mean go back and cut.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lint-and-format
|
|
3
|
-
description: Run lint with --fix before fixing anything by hand, and format every edited file.
|
|
3
|
+
description: Run lint with --fix before fixing anything by hand, and format every edited source file Prettier supports.
|
|
4
4
|
kind: rule
|
|
5
5
|
scope: both
|
|
6
6
|
vars: [lintCommand, lintFixCommand, formatCommand]
|
|
@@ -12,14 +12,15 @@ Run lint with `--fix` — most styleguide rules in `@ethlete/eslint-plugin` ship
|
|
|
12
12
|
so let them do the work before correcting anything by hand:
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
{%lintFixCommand%} # auto-fixes first (case, ordering, $ suffix
|
|
15
|
+
{%lintFixCommand%} # auto-fixes first (case, ordering, metadata, …); rename to the $ suffix by hand
|
|
16
16
|
{%lintCommand%} # then re-run to see what needs a manual fix
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
For
|
|
20
|
-
|
|
19
|
+
For judgment calls lint cannot enforce, load the repository's focused guidance for the
|
|
20
|
+
code you are changing.
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Format every edited Prettier-supported source file before wrapping up. Do not send binary,
|
|
23
|
+
generated, or unsupported files to this command:
|
|
23
24
|
|
|
24
25
|
```bash
|
|
25
26
|
{%formatCommand%}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nx-layout
|
|
3
|
+
description: Nx workspace layout - thin apps, feature code in libs grouped by kind, path-mirroring aliases, scope tags.
|
|
4
|
+
kind: rule
|
|
5
|
+
scope: consumer
|
|
6
|
+
requires: ['nx']
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Nx workspace layout
|
|
10
|
+
|
|
11
|
+
Apps are thin shells. All feature code lives in libs, grouped by kind, not by Nx type:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
apps/<app>/src/app/ app.component.ts, app.config.ts, app.routes.ts only
|
|
15
|
+
libs/
|
|
16
|
+
domain/<app>/<feature>/ views, components, services, <feature>.routes.ts
|
|
17
|
+
domain/<app>/shared/ code shared by one app's features (optional)
|
|
18
|
+
queries/ query clients and creators, one folder per backend
|
|
19
|
+
types/ API models and shared types
|
|
20
|
+
uikit/ app-agnostic presentational components
|
|
21
|
+
theme/ surface and colour themes
|
|
22
|
+
env/ environment.ts, environment.production.ts
|
|
23
|
+
assets/ fonts, icons, images
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- `app.routes.ts` only lazy-loads domain libs:
|
|
27
|
+
`loadChildren: () => import('@acme/domain/admin/users').then((m) => m.USERS_ROUTES)`.
|
|
28
|
+
Each feature lib exports its own `<FEATURE>_ROUTES`.
|
|
29
|
+
- A small app may use one lib per app (`libs/domain/<app>`) with a folder per feature.
|
|
30
|
+
Never put features, queries, shared UI or theme in the app project.
|
|
31
|
+
- Do not add Nx `feature/ui/data-access/util` folders or `type:*` tags.
|
|
32
|
+
- Every lib has `src/index.ts` and explicit `build` (`@nx/angular:ng-packagr-lite`), `lint`
|
|
33
|
+
and `test` targets in `project.json`. Keep `plugins: []` in `nx.json`.
|
|
34
|
+
- Alias = path with slashes: `@acme/domain/admin/users`, `@acme/queries`, `@acme/types`,
|
|
35
|
+
`@acme/uikit`, `@acme/env`. Project name = path with dashes (`domain-admin-users`).
|
|
36
|
+
Component `prefix` = the repo abbreviation.
|
|
37
|
+
- Tag every project with one `scope:*`: `scope:<app>` for an app and its domain libs,
|
|
38
|
+
`scope:core` for env and types, plus `scope:queries`, `scope:uikit`, `scope:theme`.
|
|
39
|
+
`@nx/enforce-module-boundaries` with `enforceBuildableLibDependency: true` lets
|
|
40
|
+
`scope:<app>` depend on itself and the shared scopes, never on another app.
|
|
41
|
+
- A backend in the same workspace is an app in `apps/` with its libs in
|
|
42
|
+
`libs/domain/<name>-api`. Ban `@angular/*` in backend scopes and `@nestjs/*` in frontend
|
|
43
|
+
scopes with `bannedExternalImports`.
|
|
44
|
+
- Environment config lives only in `libs/env`, never hardcoded in a query client.
|
|
45
|
+
|
|
46
|
+
This is the layout for new workspaces and new code. Do not restructure an existing
|
|
47
|
+
workspace that deviates from it unless the user asks.
|
|
@@ -12,4 +12,4 @@ scope: both
|
|
|
12
12
|
- **Bridge, don't copy.** Cross the boundary with `toSignal()` / `toObservable()`, never by
|
|
13
13
|
`.subscribe()`-ing and assigning the value somewhere.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
For subscriptions and effects, load the repository's focused reactive-state guidance.
|
package/content/rules/styling.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: styling
|
|
3
|
-
description: Component CSS is plain CSS in @layer components, and
|
|
3
|
+
description: Component CSS is plain CSS in @layer components, and hardcoded colours are never primary values.
|
|
4
4
|
kind: rule
|
|
5
|
-
scope:
|
|
5
|
+
scope: sdk
|
|
6
6
|
requires: ['@ethlete/core']
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -26,10 +26,12 @@ utility can win.
|
|
|
26
26
|
rule, so source order decides. Leave interaction states (`:hover`, `:focus-visible`,
|
|
27
27
|
`:active`) bare so they escalate and win.
|
|
28
28
|
|
|
29
|
-
**Never
|
|
30
|
-
from the surface and colour theming tokens
|
|
31
|
-
|
|
29
|
+
**Never use a hardcoded colour as the primary value.** Backgrounds, text, borders and
|
|
30
|
+
interaction states resolve from the surface and colour theming tokens
|
|
31
|
+
(`--et-surface-*-solid`, `--et-theme-color-*`). A static fallback inside
|
|
32
|
+
`var(--token, <fallback>)` is permitted, but not required.
|
|
32
33
|
|
|
33
34
|
Theme **names** (`brand`, `danger`, `dark-elevated`, …) are registered by the application;
|
|
34
|
-
the SDK ships none. Never hardcode
|
|
35
|
-
|
|
35
|
+
the SDK ships none. Never hardcode them as an SDK-defined union or reusable API contract.
|
|
36
|
+
If an app-specific example names one, label it as belonging to that app. Semantic colours
|
|
37
|
+
resolve by theme `type` (e.g. `injectSemanticColorTheme('error')`).
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: subagent-models
|
|
3
|
+
description: A subagent's model is an explicit choice on every call - haiku for lookups and small specified jobs, sonnet for scoped work, opus for hard work, fable for judgment.
|
|
4
|
+
kind: rule
|
|
5
|
+
scope: both
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Delegating: name the subagent's model
|
|
9
|
+
|
|
10
|
+
A subagent spawned without a `model` runs on the model leading the session, so an expensive
|
|
11
|
+
model ends up doing every small job it delegates. **Set `model` on every call**, matched to the
|
|
12
|
+
task:
|
|
13
|
+
|
|
14
|
+
| Model | The work it fits |
|
|
15
|
+
| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| `haiku` | Lookups and small, fully specified jobs: grep, find, read and summarize files or a CI log, run a command and report, a mechanical edit in named files. Haiku 5.5 does these well. |
|
|
17
|
+
| `sonnet` | Scoped work with a clear target: a fix in a named file, a spec, a docs page, a lint cleanup, research that needs reasoning. Sonnet 5.5 is close to opus here and costs less. |
|
|
18
|
+
| `opus` | Hard work: a bug with no known cause, a change across many files, a review of a diff, and any task sonnet did not finish. |
|
|
19
|
+
| `fable` | Judgment-heavy work: planning, design, cross-cutting review, leading other subagents. The most expensive of the four, so ask the user before picking it. |
|
|
20
|
+
|
|
21
|
+
Effort follows the prompt, not a parameter: scope the prompt to one question, name what "done"
|
|
22
|
+
means, and say "keep it brief" for a lookup. Two calls need no `model` of their own - a named
|
|
23
|
+
agent type carries the model and reasoning effort its own definition sets, and a fork always
|
|
24
|
+
inherits the parent's model.
|
|
25
|
+
|
|
26
|
+
A subagent does not have to be a Claude one. Delegate to a Codex agent where another model
|
|
27
|
+
family would answer better - a second opinion, a review of your own diff, a bug hunt your own
|
|
28
|
+
reading failed - and whenever the Claude usage limit is close, because a Codex run does not draw
|
|
29
|
+
on it. Read {%skill:codex-subagent%} first.
|