@tw93/waza 3.27.0 → 3.28.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.
@@ -85,6 +85,12 @@ CLI_CORE_BUCKETS = (
85
85
  )
86
86
 
87
87
 
88
+ # The file-walk helpers below are deliberately duplicated in
89
+ # skills/health/scripts/check_maintainability.py. Both scripts ship
90
+ # standalone (see packaging.allowlist) and run inside an arbitrary target
91
+ # project, so they import only stdlib. Do not hoist them into a shared
92
+ # scripts/ module: it is dev-only, not on the ship allowlist, and would
93
+ # couple a standalone tool to the install layout.
88
94
  def is_excluded(path: Path, root: Path) -> bool:
89
95
  try:
90
96
  parts = path.relative_to(root).parts
@@ -158,23 +164,18 @@ def status(label: str) -> None:
158
164
 
159
165
  def block_hotspots(files: list[Path], root: Path) -> None:
160
166
  header("FILE SIZE HOTSPOTS")
167
+ sized = ((p, line_count(p)) for p in files if p.suffix in SOURCE_EXTS)
161
168
  big = sorted(
162
- ((p, line_count(p)) for p in files
163
- if p.suffix in SOURCE_EXTS and line_count(p) >= HOTSPOT_LINES),
169
+ (item for item in sized if item[1] >= HOTSPOT_LINES),
164
170
  key=lambda x: -x[1],
165
171
  )[:10]
166
172
  if not big:
167
- print("(no source files >= 800 lines)")
173
+ print(f"(no source files >= {HOTSPOT_LINES} lines)")
168
174
  status("PASS")
169
175
  return
170
176
  for path, n in big:
171
177
  print(f" {n:>5} {rel(path, root)}")
172
- if any(n >= HOTSPOT_FAIL for _, n in big):
173
- status("FAIL")
174
- elif len(big) > 3:
175
- status("WARN")
176
- else:
177
- status("WARN")
178
+ status("FAIL" if any(n >= HOTSPOT_FAIL for _, n in big) else "WARN")
178
179
 
179
180
 
180
181
  def block_heredoc(files: list[Path], root: Path) -> None:
@@ -22,6 +22,8 @@ If it could have been generated by a default prompt, it is not good enough.
22
22
 
23
23
  **Chinese gut-feel complaints**: when the user says "很傻", "很怪", "突兀", "不协调", "不和谐" about a visual, treat it as an aesthetic rejection, not a debugging symptom. Route to Screenshot Iteration Mode, not to `/hunt`.
24
24
 
25
+ **Document & print typography → Kami.** When the deliverable is a shippable document rather than a product UI surface (report, slide deck, resume, long-form or print-oriented page, paged PDF), do not hand-roll an over-designed document layout here. Suggest the user run it through Kami (`tw93/Kami`), a document design system with a fixed constraint language and templates, and let Kami draft the detailed plan. Screen 排版 (app surfaces, components, web pages) stays in this skill.
26
+
25
27
  ## Durable Context Preflight
26
28
 
27
29
  See [rules/durable-context.md](../../rules/durable-context.md) for when to read durable context, the read-order budget, and the memory-type mapping.
@@ -123,7 +125,7 @@ Summarize the direction as three lines before writing any code:
123
125
 
124
126
  For production or multi-page UIs, expand the thesis into the 9-section DESIGN.md scaffold in `references/design-reference.md` (theme, palette, typography, components, layout, depth, do/don't, responsive, prompt guide). For a single component, the three lines are sufficient.
125
127
 
126
- ## Non-Negotiable Constraints
128
+ ## Hard Rules
127
129
 
128
130
  `references/design-reference.md` is already loaded during direction lock. It owns the full rules: typography, OKLCH color, motion timings, layout defaults, CSS-pattern bans, accessibility baseline, and complexity matching. Apply them. Do not restate them here.
129
131
 
@@ -143,6 +145,7 @@ Give at least 3 variations across genuinely different dimensions (density, typog
143
145
  | Fixed visual polish by redesigning the whole surface | Locate the concrete visual delta first, then make the smallest material, opacity, geometry, or typography change that addresses it. |
144
146
  | Added a setting or louder control to solve UI noise | Remove the misleading affordance or choose a quiet default first |
145
147
  | English looked fine, localized text overflowed | Test long words and localized strings before handoff, especially inside buttons, tabs, nav, and compact cards. |
148
+ | Relied on `…` truncation to fit text in a fixed-width slot | Guarantee fit instead: compact the format, cap to whole segments, or hard-trim with no glyph. Metric and label footers must never tail-truncate into an ellipsis. |
146
149
 
147
150
  ## Aesthetic Review
148
151
 
@@ -123,6 +123,13 @@ When extending an existing interface, first spend time understanding its visual
123
123
 
124
124
  If swapping in different content would make the new component look out of place, the vocabulary was not matched closely enough.
125
125
 
126
+ ### Responsive & Screen Verification
127
+ - Verify the rendered surface, not a type check or CSS-balance read. Several regressions (early wraps, orphaned separator dots, table overflow) are invisible in source and only show in the render. Screenshot at phone (375px, plus 320px for buttons) and desktop (1280px), in every shipped locale.
128
+ - Line widows: eliminate 1-2 word last lines by trimming the copy so the block rebalances, not by adding a `max-width` cap (a cap narrower than its container wraps early and leaves empty space on the right, which reads as a premature break). Detect objectively: flag any text block whose last line is under ~13% of its widest line; eyeballing misses them, and nested `<code>` hides them from greps.
129
+ - Mobile CTA resting state: natural width, left-aligned to the surrounding text edge, height unchanged. Centering reads as floating; full-width `flex: 1` reads heavy; dropping button height to relieve a "too full" feel treats a width problem as a height one.
130
+ - Spacing is a system, not a per-gap value. Run section spacing as one responsive ladder; when a page reads too airy or too tight, scale the whole set by a single factor across all breakpoints rather than tuning one gap. Asymmetry that survives tuning is structural.
131
+ - Long-form and documentation surfaces stay light: a borderless prev/next text pager (not bordered cards), a sidebar active state as a thin rail rather than a filled block, and build-time zero-runtime-JS code highlighting (bake static spans, plain code stays the source) over a shipped highlighter.
132
+
126
133
  ## Data Visualization Surfaces
127
134
 
128
135
  For dashboards, analytics views, chart-heavy interfaces, or number-dense displays, load `references/design-data-viz.md`. It owns dashboard defaults, chart selection, number alignment, and product-benchmark extraction.
@@ -140,6 +147,16 @@ Reject: Inter, DM Sans, DM Serif Display, DM Serif Text, Outfit, Plus Jakarta Sa
140
147
  3. Reject all three.
141
148
  4. Pick a typeface from a named foundry (Klim, Commercial Type, Colophon, Grilli Type, OH no Type, Village, etc.) or an open-source option with a clear personality that matches the brand words. Be able to explain why that specific typeface in one sentence.
142
149
 
150
+ ## CJK & Multilingual Type
151
+
152
+ When the interface mixes Chinese, Japanese, or Korean with Latin, Latin-only type rules silently break the CJK text. Apply these before handoff:
153
+
154
+ - **Latin face first, system CJK face after** in the stack, so each script renders with correct glyphs: `font-family: -apple-system, "SF Pro Text", "PingFang SC", "Noto Sans SC", sans-serif;`. Latin runs use the Latin face; Han characters fall through to the CJK face.
155
+ - **Give CJK body text more line-height than Latin**: roughly 1.7–1.8 for reading. Dense Hanzi needs more vertical room than the 1.4–1.5 that suits Latin body copy.
156
+ - **Tag runs with `lang="zh"` / `lang="ja"` / `lang="en"`** so the browser picks the right font and line-breaking. Mixed-language paragraphs break badly without it.
157
+ - **Serif reading modes need an explicit CJK serif fallback.** Most Latin "reading serif" webfonts carry no CJK glyphs, so a serif toggle silently drops Chinese back to a sans and looks broken. Pair them: `"Newsreader", "Songti SC", "Noto Serif SC", serif`.
158
+ - **Do not apply negative letter-spacing to CJK runs.** The display-type tracking rule above is Latin-only; tightening tracking on Hanzi cramps the glyphs and reads as a rendering bug. Scope tracking to `lang="en"` runs.
159
+
143
160
  ## Color System: OKLCH Rules
144
161
 
145
162
  - Use OKLCH instead of HSL. OKLCH is perceptually uniform: equal numeric changes produce equal perceived changes across the spectrum.
@@ -40,13 +40,11 @@ For `/health`, audit expectations are `decision`, `preference`, and `principle`
40
40
 
41
41
  Pick one. Apply only that tier's requirements.
42
42
 
43
-
44
- | Tier | Signal | What's expected |
45
- | ------------ | --------------------------------------- | ---------------------------------------------- |
46
- | **Simple** | <500 files, 1 contributor, no CI | CLAUDE.md only; 0-1 skills; hooks optional |
47
- | **Standard** | 500-5K files, small team or CI | CLAUDE.md + 1-2 rules; 2-4 skills; basic hooks |
48
- | **Complex** | >5K files, multi-contributor, active CI | Full six-layer setup required |
49
-
43
+ | Tier | Signal | What's expected |
44
+ |---|---|---|
45
+ | **Simple** | <500 files, 1 contributor, no CI | CLAUDE.md only; 0-1 skills; hooks optional |
46
+ | **Standard** | 500-5K files, small team or CI | CLAUDE.md + 1-2 rules; 2-4 skills; basic hooks |
47
+ | **Complex** | >5K files, multi-contributor, active CI | Full six-layer setup required |
50
48
 
51
49
  ## Step 1: Collect data
52
50
 
@@ -86,7 +84,11 @@ The collector includes both runtime-specific and agent-agnostic surfaces:
86
84
 
87
85
  Test every MCP server: call one harmless tool per server. Record `live=yes/no` with error detail. Respect `enabled: false` (skip without flagging). For API keys, only check if the env var is set (`echo $VAR | head -c 5`), never print full keys.
88
86
 
89
- ## Security Baseline Checks
87
+ ## Step 1c: Safety and security checks
88
+
89
+ These run after collection and before the Step 2 analysis. The first two apply to every audit; the third only to projects with long-running or autonomous agents.
90
+
91
+ ### Security Baseline Checks
90
92
 
91
93
  Run these on every audit, regardless of tier. They are the floor, not the ceiling.
92
94
 
@@ -94,15 +96,15 @@ Run these on every audit, regardless of tier. They are the floor, not the ceilin
94
96
 
95
97
  **Environment override surface.** Treat the following as attack surface, report when set in tracked files or shipped settings without a justification comment: API base-URL overrides (redirect all traffic to a third party), auto-trust flags for project-local MCP servers, wildcard tool allowlists (`allowedTools: ["*"]`), and permission-skip flags (`--dangerously-skip-permissions` or equivalents). Print file:line and the key name only; never print secrets.
96
98
 
97
- ## Memory and Skill Supply Chain
99
+ ### Memory and Skill Supply Chain
98
100
 
99
101
  Treat agent memory and third-party skills as supply-chain artifacts. They run with the user's privileges.
100
102
 
101
103
  **Memory hygiene.** Audit the project's long-term agent memory store for secrets, tokens, or credentials (Critical), and for entries written by untrusted runs (subagent invoked on attacker-controlled input, /loop iteration over external content); recommend rotation after such runs. For high-risk one-off runs (untrusted PDFs, uncontrolled scraping, third-party scripts), recommend disabling memory persistence for that session entirely.
102
104
 
103
- **Skill supply chain.** Third-party skills, plugins, and MCP servers run with the user's privileges. For each one not authored in this repo, check: source pinned to a release tag (not `main` or a branch), hook handlers do not write to credential directories, MCP servers have explicit user consent (not auto-trusted by wildcard). Report unpinned sources or unreviewed hook handlers as Structural, not Critical, unless an active exploit signal is present.
105
+ **Skill supply chain.** Third-party skills, plugins, and MCP servers run with the user's privileges. For each one not authored in this repo, check: source pinned to a release tag or revision (not `main`, a branch, or a remote git marketplace left tracking its latest head), hook handlers do not write to credential directories, MCP servers have explicit user consent (not auto-trusted by wildcard). Report unpinned sources or unreviewed hook handlers as Structural, not Critical, unless an active exploit signal is present.
104
106
 
105
- ## Long-Running Agent Stop Conditions
107
+ ### Long-Running Agent Stop Conditions
106
108
 
107
109
  For projects that use `/loop`, autonomous agents, or any long-running agent flow, the project must define explicit stop conditions. An agent that never stops is a budget and safety incident waiting to happen.
108
110
 
@@ -113,7 +115,7 @@ Audit for these four hard stop signals; flag the absence of each as a Structural
113
115
  3. **Cost or token budget exceeded.** Project should declare a per-run budget (tokens, API spend, wall-clock minutes). Loop exits when the budget is hit, not when work is done.
114
116
  4. **External blockers.** Merge conflict on the target branch, dependency lock the agent cannot resolve, missing credential, network unreachable. Any of these halt the loop and ask the user, not retry forever.
115
117
 
116
- The stop conditions should live in tracked project docs (`AGENTS.md`, the loop's launch script, or a dedicated config), not only in the agent's prompt. Prompts are forgettable; tracked config is enforceable. Recommend hooks (PostToolUse on the relevant tools) over prompt instructions when the project supports them: a hook physically cannot be skipped, a prompt instruction can.
118
+ The stop conditions should live in tracked project docs (`AGENTS.md`, the loop's launch script, or a dedicated config), not only in the agent's prompt. Prompts are forgettable; tracked config is enforceable. Recommend hooks (PostToolUse on the relevant tools) over prompt instructions when the project supports them: a hook physically cannot be skipped, a prompt instruction can. Confirm the host's hook coverage before recommending one: some agents only fire PostToolUse for a subset of tools (for example, a runtime may match shell/Bash only), so a fixup that must run after file edits belongs on a Stop or session-end hook there instead.
117
119
 
118
120
  ## Step 2: Analyze
119
121
 
@@ -167,7 +169,19 @@ bash skills/health/scripts/check-agent-context.sh . summary
167
169
 
168
170
  **AI-maintainability gaps.** Use `AI MAINTAINABILITY SUMMARY` in summary mode and `AI MAINTAINABILITY DETAIL` in deep mode. Report `FAIL` when the project has no executable verification command, no agent instruction surface for a non-trivial repo, or broken doc references. Report `WARN` when instructions exist but lack a project map, verification guidance, boundary/non-goal language, when TODO/HACK markers are concentrated, when large source hotspots lack ownership/boundary and verification guidance, or when durable docs contain raw one-off review reports, scorecards, dated line references, or diagnostic dumps instead of stable invariants. Treat missing `docs/`, `specs/`, `.specify/`, `HANDOFF.md`, `CHANGELOG`, issue templates, and PR templates as informational unless project complexity makes them necessary for handoff. The action for stale reports is to extract stable rules into public instructions, rules, references, or verifier scripts, then remove or archive the transient report.
169
171
 
170
- **Conversation-derived guidance.** When a health audit reads recent agent conversations, do not recommend copying the conversation or a scorecard into docs. Recommend a candidate-matrix pass instead: repeated failure, durable invariant, target public layer, verifier if deterministic, and redaction risk. If the lesson cannot be stated without local paths, issue numbers, customer details, or one-machine state, keep it out of public guidance and leave it as private context.
172
+ **Conversation-derived guidance.** When a health audit reads recent agent conversations, do not recommend copying the conversation or a scorecard into docs. Recommend a candidate-matrix pass instead:
173
+
174
+ | Field | Question |
175
+ |---|---|
176
+ | Repeated failure | Did this recur across fixes, releases, agents, or user reports? |
177
+ | Durable invariant | Can the lesson be stated as a stable rule, not a dated incident summary? |
178
+ | Target layer | Should it live in project instructions, a Waza skill, a global rule, or private memory? |
179
+ | Verifier | Is there a deterministic command, script, artifact check, or runtime smoke that can enforce it? |
180
+ | Redaction risk | Does the lesson require local paths, issue numbers, customer details, machine state, secrets, or unpublished release facts? |
181
+
182
+ Layering rule: project-specific commands, app names, artifact names, and release rituals stay in the project; reusable workflows such as cancelled-release review gates or native-freeze evidence ladders belong in Waza skills; universal honesty and verification rules belong in global CLAUDE/AGENTS; private user preferences and one-machine facts stay in memory. If the lesson cannot pass the redaction-risk field, keep it out of public guidance.
183
+
184
+ **Concentrated fix chains.** Run `git log --oneline --since='2 weeks ago' | grep -i fix` and group by area (the prefix before `:` or `(`). When the same area has 3+ fix commits in a short window, it signals a missing structural invariant: each fix is a guess at a rule that was never written down. Report a Structural `WARN` with the area name, fix count, and recommend adding an explicit rule to `AGENTS.md` / `CLAUDE.md` / project rules that captures the invariant those fixes were converging toward. A concentrated fix chain that touches the same file 4+ times is a stronger signal than scattered fixes across different files.
171
185
 
172
186
  **Hotspot ownership gaps.** In deep mode, read `HOTSPOT OWNERSHIP SURFACE`. If a largest source file exceeds the hotspot threshold and `AGENTS.md` / `CLAUDE.md` / shared instruction files do not name who owns the hotspot, what boundary should stay stable, and which verification command covers it, report a Structural `WARN`. Do not treat documented large files as code rot by size alone; some modules are intentionally large.
173
187
 
@@ -231,15 +245,14 @@ If no issues: `All relevant checks passed. Nothing to fix.`
231
245
 
232
246
  ## Gotchas
233
247
 
234
-
235
- | What happened | Rule |
236
- | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
237
- | Missed the local override | Always read `settings.local.json` too; it shadows the committed file |
238
- | Subagent timeout reported as MCP failure | MCP failures come from the live probe, not data collection |
239
- | Reported issues in wrong language | Honor CLAUDE.md Communication rule first |
240
- | Flagged intentionally noisy hook as broken | Ask before calling a hook "broken" |
248
+ | What happened | Rule |
249
+ |---|---|
250
+ | Missed the local override | Always read `settings.local.json` too; it shadows the committed file |
251
+ | Subagent timeout reported as MCP failure | MCP failures come from the live probe, not data collection |
252
+ | Reported issues in wrong language | Honor CLAUDE.md Communication rule first |
253
+ | Flagged intentionally noisy hook as broken | Ask before calling a hook "broken" |
241
254
  | Hook seemed not to fire, but it did -- a later UI element rendered above it | Hook firing order is not visual order. Before re-editing the hook config: (a) confirm with `--debug` or by piping output, (b) check whether a diff dialog, permission prompt, or other UI element rendered on top and pushed the hook output offscreen, (c) only then suspect the hook itself. |
242
- | `/health` burned too much quota on first run | Stay in summary mode first. Full conversation extracts and inspector subagents are deep-audit tools, not the default path for Standard projects. |
243
- | Treated missing specs/docs as a failure | Decision artifacts are optional by default. Escalate missing docs/specs only when the tier, active handoff risk, or user request makes them necessary. |
244
- | Treated an ignored AGENTS/CLAUDE file as durable project truth | Report whether the rule is tracked and distributed. Local overlays can inform the audit, but durable fixes belong in public repo docs or shipped skill/rule files. |
245
- | Treated a review scorecard as maintainability documentation | Scorecards are snapshots. Extract the invariant and verification path, then remove or archive the report instead of calling the score itself a durable rule. |
255
+ | `/health` burned too much quota on first run | Stay in summary mode first. Full conversation extracts and inspector subagents are deep-audit tools, not the default path for Standard projects. |
256
+ | Treated missing specs/docs as a failure | Decision artifacts are optional by default. Escalate missing docs/specs only when the tier, active handoff risk, or user request makes them necessary. |
257
+ | Treated an ignored AGENTS/CLAUDE file as durable project truth | Report whether the rule is tracked and distributed. Local overlays can inform the audit, but durable fixes belong in public repo docs or shipped skill/rule files. |
258
+ | Treated a review scorecard as maintainability documentation | Scorecards are snapshots. Extract the invariant and verification path, then remove or archive the report instead of calling the score itself a durable rule. |
@@ -179,7 +179,7 @@ def parse_codex_config(
179
179
  if "=" not in line:
180
180
  continue
181
181
  key, value = [part.strip() for part in line.split("=", 1)]
182
- if section == "features" and value.lower() == "true":
182
+ if section == "features" and value.split("#", 1)[0].strip().strip('"').lower() == "true":
183
183
  features.append(key)
184
184
  elif section.startswith('projects."') and key == "trust_level":
185
185
  project = section[len('projects."'): -1]
@@ -66,6 +66,12 @@ VERIFICATION_WORD_RE = re.compile(
66
66
  )
67
67
 
68
68
 
69
+ # The file-walk helpers below are deliberately duplicated in
70
+ # skills/check/scripts/audit_signals.py. Both scripts ship standalone
71
+ # (see packaging.allowlist) and run inside an arbitrary target project, so
72
+ # they import only stdlib. Do not hoist them into a shared scripts/
73
+ # module: it is dev-only, not on the ship allowlist, and would couple a
74
+ # standalone tool to the install layout.
69
75
  def rel(path: Path, root: Path) -> str:
70
76
  try:
71
77
  return path.resolve().relative_to(root).as_posix()
@@ -8,7 +8,7 @@
8
8
  # python3 not on PATH -> MCP/hooks/allowedTools sections print "(unavailable)"; do not flag those areas
9
9
  # settings.local.json absent -> hooks, MCP, allowedTools all show "(unavailable)"; normal for global-settings-only projects
10
10
  # MEMORY.md path -> built via sed on pwd; unusual chars produce wrong project key; verify manually if (none) seems wrong
11
- # Conversation scope -> only 2 most recent .jsonl files sampled; fewer than 2 = [LOW CONFIDENCE]
11
+ # Conversation scope -> 2 most recent PREVIOUS .jsonl sampled (live session skipped); fewer than 2 = [LOW CONFIDENCE]
12
12
  # MCP token estimate -> assumes ~25 tools/server, ~200 tokens/tool; treat as directional, not precise
13
13
  # Tier misclassification -> .next/, __pycache__, .turbo/ can inflate file count; recheck manually if tier feels wrong
14
14
  set -euo pipefail
@@ -293,9 +293,10 @@ sample_jsonl_prefix() {
293
293
  ' "$file"
294
294
  }
295
295
 
296
- extract_messages_from_file() {
297
- local file="$1"
298
- sample_jsonl_prefix "$file" | jq -r '
296
+ # Shared jq filter: collapse one transcript record to a single trimmed text
297
+ # line, dropping meta and tool-result noise. Defined once and prepended to both
298
+ # extract_* programs below so the flattening logic lives in exactly one place.
299
+ JQ_FLATTEN='
299
300
  def flatten:
300
301
  if (.isMeta // false) or (.toolUseResult? != null) then
301
302
  empty
@@ -311,6 +312,11 @@ extract_messages_from_file() {
311
312
  | sub("^ "; "")
312
313
  | sub(" $"; "")
313
314
  end;
315
+ '
316
+
317
+ extract_messages_from_file() {
318
+ local file="$1"
319
+ sample_jsonl_prefix "$file" | jq -r "$JQ_FLATTEN"'
314
320
  (.type // .role // "") as $kind
315
321
  | (flatten) as $text
316
322
  | if ($text | length) == 0 then
@@ -329,22 +335,7 @@ extract_messages_from_file() {
329
335
 
330
336
  extract_signals_from_file() {
331
337
  local file="$1"
332
- sample_jsonl_prefix "$file" | jq -r '
333
- def flatten:
334
- if (.isMeta // false) or (.toolUseResult? != null) then
335
- empty
336
- else
337
- (.message.content // .content // .text // "")
338
- | if type == "array" then
339
- [ .[] | if type == "object" and .type == "text" then .text elif type == "string" then . else empty end ] | join(" ")
340
- elif type == "string" then .
341
- else empty
342
- end
343
- | gsub("[\\r\\n]+"; " ")
344
- | gsub(" +"; " ")
345
- | sub("^ "; "")
346
- | sub(" $"; "")
347
- end;
338
+ sample_jsonl_prefix "$file" | jq -r "$JQ_FLATTEN"'
348
339
  def is_correction:
349
340
  test("(?i)(\\bdon'\''t\\b|\\bdo not\\b|\\bplease don'\''t\\b|\\binstead\\b|\\bnext time\\b|\\bremember\\b|\\buse\\b.*\\binstead\\b|\\bnot\\b.*\\bbut\\b)")
350
341
  or test("(不要再|请不要|不要|别再|下次|记得|改成|改为|而不是|别用|去掉|统一成)");
@@ -46,7 +46,7 @@ For `/hunt`, diagnostic constraints are `decision`, `preference`, and `principle
46
46
  - **System/tooling symptoms need a lower-layer baseline.** Before blaming the visible app, generated file, or top-level feature, measure the raw lower layer first: OS capture versus post-processing, runtime service versus UI, compiler/toolchain versus test assertion, network/API versus client handling. Retire hypotheses that the baseline disproves instead of circling them.
47
47
  - **Pay attention to deflection.** When someone says "that part doesn't matter," treat it as a signal. The area someone avoids examining is often where the problem lives.
48
48
  - **Visual/rendering bugs: static analysis first.** Trace paint layers, stacking contexts, and layer order in DevTools before adding console.log or visual debug overlays. Logs cannot capture what the compositor does. Only add instrumentation after static analysis fails.
49
- - **Behavioral / lifecycle bugs: log-debug first after one failed fix.** Window lifecycle, event delivery, navigation, focus, timer, and async-state bugs do *not* yield to static reading alone. After static analysis plus one failed hypothesis, stop guessing and add a runtime probe (Swift `#if DEBUG NSLog("[App.stage] state=...")`, JS `console.log`, Rust `eprintln!` / `tracing::debug!`) at the suspect boundary. Read its output before changing code again. "Looks reasonable but unverified" twice in a row is the hard-stop signal. Distinguish from the visual-rendering rule above: a compositor bug needs DevTools; a "the function was never called" / "the object was already destroyed" bug needs a log.
49
+ - **Behavioral / lifecycle / async bugs: instrument first, not after failure.** Window lifecycle, event delivery, navigation, focus, timer, state-machine, and async-ordering bugs almost never yield to static reading alone. Do not wait for a failed fix to add logs. The moment your hypothesis involves "this callback fires before/after that one", "this state should be X when Y runs", or "this object should still be alive here", **add the log immediately as part of forming the hypothesis**, before writing any fix. A hypothesis without runtime evidence is a guess; two guesses in a row is the hard-stop signal. Distinguish from visual-rendering bugs (compositor behavior needs DevTools, not logs) and pure-logic bugs (wrong formula, off-by-one) where static analysis is sufficient.
50
50
  - **Tuning magic numbers past round three: stop, unify.** When a spacing / sizing / threshold value has been adjusted three times and still looks wrong, the bug is structural, not numeric. Replace the N independent values with one named token (`Spacing.s4`, `--gap-content`, etc.) and verify the asymmetry was hiding a missing constraint. Asymmetry that survives tuning is structural; more tuning will not converge.
51
51
  - **Fix the cause, not the symptom.** If the fix touches more than 5 files, pause and confirm scope with the user.
52
52
 
@@ -102,7 +102,7 @@ If the blast surfaces unrelated bugs, list them but do not fix in this PR unless
102
102
 
103
103
  ## Confirm or Discard
104
104
 
105
- Add one targeted instrument: a log line, a failing assertion, or the smallest test that would fail if the hypothesis is correct. Run it. If the evidence contradicts the hypothesis, discard it completely and re-orient with what was just learned. Do not preserve a hypothesis the evidence disproves.
105
+ The instrument-first rule lives in Hard Rules (behavioral/async bugs) above; this is what to do with its result. Run the one probe that would fail if the hypothesis were wrong, then read it. If the evidence contradicts the hypothesis, discard it completely and re-orient on what the probe just showed. Do not stack a fix onto a disproven hypothesis, and do not keep one just because the code "looks like" the cause.
106
106
 
107
107
  ## Runtime Evidence Ladder
108
108
 
@@ -118,6 +118,26 @@ Compile-only is not enough for UI, native-app, visual, rendering, or generated-a
118
118
 
119
119
  For recurring classes of failures, load `references/failure-patterns.md` before adding a second fix.
120
120
 
121
+ ## Native App Freeze Mode
122
+
123
+ Activate when a desktop or mobile native app reports beachball, not responding, tab-switch freeze, first-open lag, idle wake stall, overlay lockup, or a screenshot shows a frozen app.
124
+
125
+ Evidence to collect before changing code:
126
+
127
+ 1. Exact user path and version: first launch versus warm launch, the tab or window transition, idle duration, permissions, display count, and any setting that makes the freeze disappear.
128
+ 2. Runtime capture while frozen: `sample <process>`, recent app logs, CPU and memory footprint, thread count, and whether the main thread is blocked, spinning, or allocating.
129
+ 3. First-frame surface: view body work, first `.task`, synchronous icon or metadata lookup, filesystem scans, URL parent walks, notification callbacks, and app/window wake handlers.
130
+ 4. Blast search after the fix: grep the same API shape across the repo, especially path parent walks, synchronous icon loading, metadata reads in render paths, and callbacks that run on the main thread.
131
+
132
+ Common native freeze traps:
133
+
134
+ - Launch, terminate, permission, audio, display, or workspace notifications doing path walks, icon lookup, filesystem scans, or process enumeration on the main thread.
135
+ - First paint hydrating a full app list, directory tree, media thumbnail set, or system status table before showing an interactive shell.
136
+ - An input-lock or full-screen overlay without a guaranteed teardown path for Escape, app deactivation, permission denial, process termination, and window close.
137
+ - Timer or sampler work that survives hidden windows, long idle periods, sleep/wake, or app reactivation.
138
+
139
+ Compile-only and source-only checks are insufficient for this mode. The outcome must include the runtime capture, the root-cause frame or state transition, the focused regression guard, and any sibling matches that were fixed or explicitly left safe.
140
+
121
141
  ## Targeted Logging
122
142
 
123
143
  Use logs as a scalpel, not as noise. Before adding a log, write the question it answers:
@@ -118,3 +118,12 @@ Checks:
118
118
  - Read the tool's man page for cold-start semantics. `top -l 2`, `iostat -d 2`, `vm_stat 1 2`, etc. all share this shape.
119
119
  - Slice the output to the latest sample (`.suffix(perSampleSize)` on parsed lines, or look for the second instance of the header row).
120
120
  - When in doubt, raise `-l` to 3 and confirm sample 2 and 3 agree; sample 1 stays zero.
121
+
122
+ ## Aggregation Key Variant
123
+
124
+ Signals: a count, log roll-up, event tally, or per-category breakdown is short by some entries; the missing items share a trait (a system-derived path, a localized string, a prefixed command name); the base-form key matches but a derived variant (`<base>-system`, a suffix, a prefix) is silently dropped.
125
+
126
+ Checks:
127
+ - Before adding a category, grep every write site that produces this class of key and enumerate the real variants, not just the base form.
128
+ - Match with `hasPrefix` / a regex / an explicit variant list rather than exact equality on the base key.
129
+ - Add a fixture row for each known variant so a future key shape that escapes the matcher fails the test instead of the aggregate.
@@ -35,12 +35,15 @@ PROXY="${2:-}"
35
35
 
36
36
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
37
37
 
38
+ LOCAL_ERR="$(mktemp)"
39
+ trap 'rm -f "$LOCAL_ERR"' EXIT
40
+
38
41
  # shellcheck disable=SC2329,SC2317 # called indirectly via _with_retry / _try_once
39
42
  _curl() {
40
43
  if [ -n "$PROXY" ]; then
41
- https_proxy="$PROXY" http_proxy="$PROXY" curl -sfL "$@"
44
+ https_proxy="$PROXY" http_proxy="$PROXY" curl -sfL --connect-timeout 10 --max-time 30 "$@"
42
45
  else
43
- curl -sfL "$@"
46
+ curl -sfL --connect-timeout 10 --max-time 30 "$@"
44
47
  fi
45
48
  }
46
49
 
@@ -70,14 +73,12 @@ _with_retry() {
70
73
  }
71
74
 
72
75
  # Tier 1: local extractor. Always tried first.
73
- if OUT=$(python3 "$SCRIPT_DIR/fetch_local.py" "$URL" 2>/tmp/fetch-local.err); then
74
- cat /tmp/fetch-local.err >&2 2>/dev/null || true
76
+ if OUT=$(python3 "$SCRIPT_DIR/fetch_local.py" "$URL" 2>"$LOCAL_ERR"); then
77
+ cat "$LOCAL_ERR" >&2 2>/dev/null || true
75
78
  echo "$OUT"
76
- rm -f /tmp/fetch-local.err
77
79
  exit 0
78
80
  fi
79
- cat /tmp/fetch-local.err >&2 2>/dev/null || true
80
- rm -f /tmp/fetch-local.err
81
+ cat "$LOCAL_ERR" >&2 2>/dev/null || true
81
82
 
82
83
  # Without --use-proxy, stop here. URL never leaves the machine.
83
84
  if [ "$USE_PROXY" -eq 0 ]; then
@@ -31,6 +31,7 @@ except ImportError:
31
31
  sys.exit(1)
32
32
 
33
33
  API = "https://open.feishu.cn/open-apis"
34
+ TIMEOUT = 20
34
35
 
35
36
 
36
37
  def yaml_string(value):
@@ -43,7 +44,8 @@ def get_token():
43
44
  if not app_id or not app_secret:
44
45
  return None, "FEISHU_APP_ID or FEISHU_APP_SECRET not set"
45
46
  resp = requests.post(f"{API}/auth/v3/tenant_access_token/internal",
46
- json={"app_id": app_id, "app_secret": app_secret})
47
+ json={"app_id": app_id, "app_secret": app_secret},
48
+ timeout=TIMEOUT)
47
49
  d = resp.json()
48
50
  if d.get("code") != 0:
49
51
  return None, f"Auth failed: {d.get('msg', resp.text)}"
@@ -69,7 +71,8 @@ def parse_url(url):
69
71
  def resolve_wiki(token, wiki_token):
70
72
  resp = requests.get(f"{API}/wiki/v2/spaces/get_node",
71
73
  headers={"Authorization": f"Bearer {token}"},
72
- params={"token": wiki_token})
74
+ params={"token": wiki_token},
75
+ timeout=TIMEOUT)
73
76
  d = resp.json()
74
77
  if d.get("code") == 0:
75
78
  node = d["data"]["node"]
@@ -85,7 +88,8 @@ def get_blocks(token, doc_id):
85
88
  params["page_token"] = page_token
86
89
  resp = requests.get(f"{API}/docx/v1/documents/{doc_id}/blocks",
87
90
  headers={"Authorization": f"Bearer {token}"},
88
- params=params)
91
+ params=params,
92
+ timeout=TIMEOUT)
89
93
  d = resp.json()
90
94
  if d.get("code") != 0:
91
95
  return None, f"Blocks fetch failed: {d.get('msg', resp.text)}"
@@ -141,7 +145,7 @@ def blocks_to_md(blocks):
141
145
  key = f"heading{level}"
142
146
  data = block.get(key) or block.get("heading", {})
143
147
  text = extract_text(data.get("elements", []))
144
- lines.append(f"{'#' * level} {text}")
148
+ lines.append(f"{'#' * min(level, 6)} {text}")
145
149
  elif bt == 10:
146
150
  text = extract_text(block.get("bullet", {}).get("elements", []))
147
151
  lines.append(f"- {text}")
@@ -203,8 +207,9 @@ def fetch_feishu(url):
203
207
  doc_id, doc_type = real_id, real_type or "docx"
204
208
 
205
209
  info_resp = requests.get(f"{API}/docx/v1/documents/{doc_id}",
206
- headers={"Authorization": f"Bearer {token}"})
207
- doc_info = info_resp.json().get("data", {}).get("document", {})
210
+ headers={"Authorization": f"Bearer {token}"},
211
+ timeout=TIMEOUT)
212
+ doc_info = (info_resp.json().get("data") or {}).get("document") or {}
208
213
  title = doc_info.get("title", "")
209
214
 
210
215
  blocks, err = get_blocks(token, doc_id)
@@ -20,6 +20,14 @@ Give opinions directly. Take a position and state what evidence would change it.
20
20
  - Evidence: current repo state, project docs, live external docs when relevant, prior decisions, constraints, and explicit user preferences.
21
21
  - Output: one recommended direction or a handoff plan with assumptions and verification steps.
22
22
 
23
+ ## Durable Context Preflight
24
+
25
+ See [rules/durable-context.md](../../rules/durable-context.md) for when to read durable context, the read-order budget, and the memory-type mapping (planning constraints, reusable patterns, facts that need re-verification against current state).
26
+
27
+ For `/think`, planning constraints are `decision`, `preference`, and `principle` entries; current repo state, live docs, logs, tests, and remote state override memory. Lock durable decisions and preferences before asking questions. Do not ask the user to restate an intent that the durable context already establishes unless it is risky, stale, or contradicted by current state.
28
+
29
+ Before outputting any plan, scan the project's `AGENTS.md`, `CLAUDE.md`, `.claude/rules/*.md`, and any local agent-memory summary if the user pointed at one. If the proposed plan contradicts a "hard rule", "never X", "must Y", or "prefer Z" stated in those files, surface the contradiction in the plan output (one sentence: which rule, which step contradicts it, recommended resolution). Do not silently override the rule. If the rule blocks the plan, stop and ask before continuing.
30
+
23
31
  ## Lightweight Mode
24
32
 
25
33
  Activate when the user wants to fix something rather than build something, the problem is already defined, and the only open question is "how to fix it."
@@ -50,6 +58,22 @@ Do not use a build-plan template here. Do not list options. Give one verdict.
50
58
 
51
59
  Distinction from Lightweight Mode: Lightweight answers "how to fix it" (method). Evaluation answers "should it exist" (value judgment).
52
60
 
61
+ ## Triage Mode
62
+
63
+ Activate when the user forwards a bundle of asks: an issue with multiple requests, a batch of screenshots, a user saying "看看这几个需求", or any input containing 3+ distinct items that could each be accepted or rejected independently.
64
+
65
+ Do not treat the bundle as a to-do list. Classify each item first:
66
+
67
+ | Bucket | Meaning | Action |
68
+ |--------|---------|--------|
69
+ | **Bug** | Broken behavior with evidence | Fix |
70
+ | **Already works** | The feature exists but the reporter missed it | Point to the existing affordance |
71
+ | **Accepted improvement** | Genuine gap, low-risk, aligns with product direction | Implement |
72
+ | **Cosmetic / preference** | Subjective, no functional impact | Note it, do not implement unless the maintainer agrees |
73
+ | **Out of scope** | Conflicts with product boundary or adds unjustified complexity | Decline with one sentence |
74
+
75
+ Output the classification table first. Wait for the user to confirm the accepted subset before implementing anything. "Already works" misidentified as missing is the most common waste; grep for the existing affordance before classifying an item as a gap.
76
+
53
77
  **Negative-user feedback is not automatic scope.** When a user evaluation is triggered by a refund customer, a churn report, or a "competitor X is more intuitive" comparison, do not convert the complaint into a rework plan by default. First check whether the current behavior is intentional product differentiation, not an oversight: read the project's own AGENTS.md / CLAUDE.md / product notes for phrases like "review-first", "verifiability over speed", "evidence-driven", "explicit confirmation". If the behavior the user criticized is named there as a deliberate choice, the verdict is **Keep**, with one sentence on why the differentiation matters, and a note that the maintainer can override. Do not write a "fix the friction" plan that quietly removes the differentiator. The signal-to-respect ratio for refund / competitor-comparison feedback on a deliberately-designed surface is low.
54
78
 
55
79
  ## Before Reading Any Code
@@ -58,14 +82,6 @@ Distinction from Lightweight Mode: Lightweight answers "how to fix it" (method).
58
82
  - If the project tracks prior decisions (ADRs, design docs, issue threads), skim the ones matching the problem before proposing. Skip if none exist.
59
83
  - If the plan involves a default value, env var, or config field, open the project's actual config file (e.g. `app.config.json`, `tauri.conf.json`, `package.json`, `.env`) and lift the live value. Never quote a default from memory or docs.
60
84
 
61
- ## Durable Context Preflight
62
-
63
- See [rules/durable-context.md](../../rules/durable-context.md) for when to read durable context, the read-order budget, and the memory-type mapping (planning constraints, reusable patterns, facts that need re-verification against current state).
64
-
65
- For `/think`, planning constraints are `decision`, `preference`, and `principle` entries; current repo state, live docs, logs, tests, and remote state override memory. Lock durable decisions and preferences before asking questions. Do not ask the user to restate an intent that the durable context already establishes unless it is risky, stale, or contradicted by current state.
66
-
67
- Before outputting any plan, scan the project's `AGENTS.md`, `CLAUDE.md`, `.claude/rules/*.md`, and any local agent-memory summary if the user pointed at one. If the proposed plan contradicts a "hard rule", "never X", "must Y", or "prefer Z" stated in those files, surface the contradiction in the plan output (one sentence: which rule, which step contradicts it, recommended resolution). Do not silently override the rule. If the rule blocks the plan, stop and ask before continuing.
68
-
69
85
  ## Check for Official Solutions First
70
86
 
71
87
  Before proposing custom implementations, search for framework built-ins, official patterns, and ecosystem standards. Use Context7 MCP tools to query latest docs when available. If an official solution exists, it is the default recommendation unless you can articulate why it is insufficient for this specific case.