@chinhae/atw-cli 0.6.0 → 0.7.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/README.md +128 -451
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +24 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/channel/adapters/claude.d.ts +44 -2
- package/dist/commands/channel/adapters/claude.d.ts.map +1 -1
- package/dist/commands/channel/adapters/claude.js +124 -23
- package/dist/commands/channel/adapters/claude.js.map +1 -1
- package/dist/commands/channel/adapters/codex.d.ts +36 -6
- package/dist/commands/channel/adapters/codex.d.ts.map +1 -1
- package/dist/commands/channel/adapters/codex.js +150 -10
- package/dist/commands/channel/adapters/codex.js.map +1 -1
- package/dist/commands/channel/adapters/index.d.ts +33 -4
- package/dist/commands/channel/adapters/index.d.ts.map +1 -1
- package/dist/commands/channel/adapters/index.js +159 -6
- package/dist/commands/channel/adapters/index.js.map +1 -1
- package/dist/commands/channel/adapters/omp.d.ts +106 -0
- package/dist/commands/channel/adapters/omp.d.ts.map +1 -0
- package/dist/commands/channel/adapters/omp.js +405 -0
- package/dist/commands/channel/adapters/omp.js.map +1 -0
- package/dist/commands/channel/adapters/pi.d.ts +123 -0
- package/dist/commands/channel/adapters/pi.d.ts.map +1 -0
- package/dist/commands/channel/adapters/pi.js +387 -0
- package/dist/commands/channel/adapters/pi.js.map +1 -0
- package/dist/commands/channel/adapters/types.d.ts +16 -0
- package/dist/commands/channel/adapters/types.d.ts.map +1 -1
- package/dist/commands/channel/agent-loader.d.ts +1 -0
- package/dist/commands/channel/agent-loader.d.ts.map +1 -1
- package/dist/commands/channel/agent-loader.js +45 -5
- package/dist/commands/channel/agent-loader.js.map +1 -1
- package/dist/commands/channel/dev-parse-trace.d.ts +1 -1
- package/dist/commands/channel/dev-parse-trace.d.ts.map +1 -1
- package/dist/commands/channel/dev-parse-trace.js +25 -2
- package/dist/commands/channel/dev-parse-trace.js.map +1 -1
- package/dist/commands/channel/index.d.ts.map +1 -1
- package/dist/commands/channel/index.js +44 -12
- package/dist/commands/channel/index.js.map +1 -1
- package/dist/commands/channel/run.d.ts +39 -2
- package/dist/commands/channel/run.d.ts.map +1 -1
- package/dist/commands/channel/run.js +227 -68
- package/dist/commands/channel/run.js.map +1 -1
- package/dist/commands/channel/spawn.d.ts +7 -2
- package/dist/commands/channel/spawn.d.ts.map +1 -1
- package/dist/commands/channel/spawn.js +35 -1
- package/dist/commands/channel/spawn.js.map +1 -1
- package/dist/commands/channel/store/events.d.ts +2 -2
- package/dist/commands/channel/store/events.d.ts.map +1 -1
- package/dist/commands/channel/store/events.js +1 -1
- package/dist/commands/channel/store/events.js.map +1 -1
- package/dist/commands/channel/supervisor/shutdown.d.ts +2 -0
- package/dist/commands/channel/supervisor/shutdown.d.ts.map +1 -1
- package/dist/commands/channel/supervisor/shutdown.js +6 -1
- package/dist/commands/channel/supervisor/shutdown.js.map +1 -1
- package/dist/commands/channel/supervisor.d.ts +2 -2
- package/dist/commands/channel/supervisor.d.ts.map +1 -1
- package/dist/commands/channel/supervisor.js +26 -3
- package/dist/commands/channel/supervisor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +6 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/mem.d.ts +2 -1
- package/dist/commands/mem.d.ts.map +1 -1
- package/dist/commands/mem.js +162 -13
- package/dist/commands/mem.js.map +1 -1
- package/dist/commands/mode.d.ts.map +1 -1
- package/dist/commands/mode.js +5 -3
- package/dist/commands/mode.js.map +1 -1
- package/dist/commands/uninstall.d.ts +5 -3
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +40 -16
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts +34 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +209 -11
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/verify.d.ts +18 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +75 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +3 -2
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/omp.d.ts +22 -0
- package/dist/configurators/omp.d.ts.map +1 -1
- package/dist/configurators/omp.js +101 -2
- package/dist/configurators/omp.js.map +1 -1
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +39 -7
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +5 -1
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +4 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +4 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.7.0.json +1378 -0
- package/dist/templates/atw/definition-of-done.md +65 -0
- package/dist/templates/atw/gitignore.txt +7 -0
- package/dist/templates/atw/index.d.ts +11 -0
- package/dist/templates/atw/index.d.ts.map +1 -1
- package/dist/templates/atw/index.js +22 -0
- package/dist/templates/atw/index.js.map +1 -1
- package/dist/templates/atw/scripts/common/active_task.py +2 -2
- package/dist/templates/atw/scripts/common/workflow_phase.py +33 -0
- package/dist/templates/atw/scripts/cross_model_review.py +3312 -0
- package/dist/templates/atw/scripts/decision_log.py +2002 -0
- package/dist/templates/atw/scripts/floor_guard.py +642 -0
- package/dist/templates/atw/scripts/task.py +25 -6
- package/dist/templates/atw/scripts/tickets.py +480 -10
- package/dist/templates/atw/workflow-general.md +6 -1
- package/dist/templates/atw/workflow.md +346 -26
- package/dist/templates/claude/settings.json +12 -0
- package/dist/templates/codex/hooks/session-start.py +2 -2
- package/dist/templates/common/bundled-skills/atw-askme/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-askme-with-docs/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-channel/SKILL.md +5 -2
- package/dist/templates/common/bundled-skills/atw-channel/references/command-reference.md +44 -5
- package/dist/templates/common/bundled-skills/atw-channel/references/progress-debugging.md +9 -0
- package/dist/templates/common/bundled-skills/atw-channel/references/workers.md +48 -4
- package/dist/templates/common/bundled-skills/atw-code-review/SKILL.md +78 -3
- package/dist/templates/common/bundled-skills/atw-codebase-design/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-codebase-design/{DESIGN-IT-TWICE.md → design-it-twice.md} +3 -3
- package/dist/templates/common/bundled-skills/atw-cross-model-review/SKILL.md +361 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/agents/openai.yaml +5 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/four-tiers.md +42 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/log-review-questions.md +38 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/questions.md +54 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/recheck-questions.md +42 -0
- package/dist/templates/common/bundled-skills/atw-cross-model-review/references/report-format.md +22 -0
- package/dist/templates/common/bundled-skills/atw-diagnosing-bugs/SKILL.md +13 -2
- package/dist/templates/common/bundled-skills/atw-domain-modeling/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-handoff/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-implement/SKILL.md +37 -1
- package/dist/templates/common/bundled-skills/atw-implement-all/SKILL.md +219 -24
- package/dist/templates/common/bundled-skills/atw-implement-spec/SKILL.md +237 -18
- package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/atw-init-repo/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw-local.md +7 -1
- package/dist/templates/common/bundled-skills/atw-init-repo/references/issue-tracker-atw.md +7 -1
- package/dist/templates/common/bundled-skills/atw-map/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-meta/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/add-project-local-conventions.md +4 -3
- package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/change-skills-or-commands.md +21 -16
- package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/change-task-lifecycle.md +1 -1
- package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/overview.md +1 -0
- package/dist/templates/common/bundled-skills/atw-meta/references/local-architecture/task-system.md +2 -1
- package/dist/templates/common/bundled-skills/atw-meta/references/platform-files/hooks-and-settings.md +1 -0
- package/dist/templates/common/bundled-skills/atw-noob-mode/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-performance/SKILL.md +134 -0
- package/dist/templates/common/bundled-skills/atw-performance/agents/openai.yaml +3 -0
- package/dist/templates/common/bundled-skills/atw-performance/references/optimization-patterns.md +250 -0
- package/dist/templates/common/bundled-skills/atw-performance/references/performance-checklist.md +238 -0
- package/dist/templates/common/bundled-skills/atw-performance/references/performance-optimization.md +264 -0
- package/dist/templates/common/bundled-skills/atw-prototype/SKILL.md +3 -3
- package/dist/templates/common/bundled-skills/atw-prototype/{LOGIC.md → logic.md} +1 -1
- package/dist/templates/common/bundled-skills/atw-prototype/references/design-direction.md +1 -1
- package/dist/templates/common/bundled-skills/atw-prototype/references/style-explorer.md +1 -1
- package/dist/templates/common/bundled-skills/atw-prototype/{UI.md → ui.md} +4 -4
- package/dist/templates/common/bundled-skills/atw-reflect/SKILL.md +77 -0
- package/dist/templates/common/bundled-skills/atw-reflect/agents/openai.yaml +5 -0
- package/dist/templates/common/bundled-skills/atw-reflect/references/divergent-reviewer.md +38 -0
- package/dist/templates/common/bundled-skills/atw-reflect/references/judgment-reviewer.md +37 -0
- package/dist/templates/common/bundled-skills/atw-reflect/references/synthesizer.md +63 -0
- package/dist/templates/common/bundled-skills/atw-reflect/references/tooling-reviewer.md +48 -0
- package/dist/templates/common/bundled-skills/atw-security/SKILL.md +248 -0
- package/dist/templates/common/bundled-skills/atw-security/agents/openai.yaml +3 -0
- package/dist/templates/common/bundled-skills/atw-security/references/hardening-patterns.md +324 -0
- package/dist/templates/common/bundled-skills/atw-security/references/security-checklist.md +248 -0
- package/dist/templates/common/bundled-skills/atw-session-insight/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-session-insight/references/cli-quick-reference.md +11 -2
- package/dist/templates/common/bundled-skills/atw-spec/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-tdd/SKILL.md +25 -0
- package/dist/templates/common/bundled-skills/atw-tickets/SKILL.md +1 -1
- package/dist/templates/common/bundled-skills/atw-triage/SKILL.md +5 -5
- package/dist/templates/common/bundled-skills/atw-ui/SKILL.md +54 -1
- package/dist/templates/common/bundled-skills/atw-ui/references/accessibility-checklist.md +160 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/SKILL.md +58 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/agents/openai.yaml +5 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/feature-map-example/README.md +47 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/feature-map-example/create-note.md +39 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/feature-map-example/search.md +45 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/generate.md +107 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/health-check.md +100 -0
- package/dist/templates/common/bundled-skills/atw-verify-skill/references/verify-skill-template.md +99 -0
- package/dist/templates/common/bundled-skills/atw-what-happened/SKILL.md +49 -0
- package/dist/templates/common/bundled-skills/atw-what-happened/agents/openai.yaml +5 -0
- package/dist/templates/common/commands/finish-work.md +10 -1
- package/dist/templates/common/commands/start.md +3 -1
- package/dist/templates/common/skills/update-spec.md +40 -4
- package/dist/templates/copilot/hooks/session-start.py +2 -2
- package/dist/templates/dsh/DSH.md +2 -2
- package/dist/templates/markdown/spec/backend/quality-guidelines.md.txt +10 -0
- package/dist/templates/markdown/spec/frontend/quality-guidelines.md.txt +10 -0
- package/dist/templates/omp/config.yml +15 -0
- package/dist/templates/omp/extensions/atw/index.ts.txt +135 -18
- package/dist/templates/omp/index.d.ts +1 -0
- package/dist/templates/omp/index.d.ts.map +1 -1
- package/dist/templates/omp/index.js +3 -0
- package/dist/templates/omp/index.js.map +1 -1
- package/dist/templates/opencode/lib/session-utils.js +4 -2
- package/dist/templates/opencode/plugins/inject-workflow-state.js +5 -1
- package/dist/templates/pi/extensions/atw/index.ts.txt +80 -7
- package/dist/templates/shared-hooks/index.d.ts +8 -1
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +8 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-workflow-state.py +131 -14
- package/dist/templates/shared-hooks/session-start.py +4 -2
- package/dist/templates/shared-hooks/show-notice.py +135 -0
- package/dist/templates/snow/SNOW.md +1 -1
- package/dist/utils/initialized.d.ts +9 -0
- package/dist/utils/initialized.d.ts.map +1 -0
- package/dist/utils/initialized.js +24 -0
- package/dist/utils/initialized.js.map +1 -0
- package/dist/utils/managed-removal.d.ts +2 -0
- package/dist/utils/managed-removal.d.ts.map +1 -1
- package/dist/utils/managed-removal.js +23 -3
- package/dist/utils/managed-removal.js.map +1 -1
- package/dist/utils/optional-skills.d.ts +2 -0
- package/dist/utils/optional-skills.d.ts.map +1 -1
- package/dist/utils/optional-skills.js +1 -1
- package/dist/utils/optional-skills.js.map +1 -1
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +3 -0
- package/dist/utils/template-hash.js.map +1 -1
- package/dist/utils/uninstall-scrubbers.d.ts +10 -0
- package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
- package/dist/utils/uninstall-scrubbers.js +50 -0
- package/dist/utils/uninstall-scrubbers.js.map +1 -1
- package/dist/utils/verify-entries.d.ts +51 -0
- package/dist/utils/verify-entries.d.ts.map +1 -0
- package/dist/utils/verify-entries.js +399 -0
- package/dist/utils/verify-entries.js.map +1 -0
- package/package.json +2 -2
- /package/dist/templates/common/bundled-skills/atw-codebase-design/{DEEPENING.md → deepening.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-domain-modeling/{ADR-FORMAT.md → adr-format.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-domain-modeling/{GLOSSARY-FORMAT.md → glossary-format.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-improve-codebase-architecture/{HTML-REPORT.md → html-report.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-triage/{AGENT-BRIEF.md → agent-brief.md} +0 -0
- /package/dist/templates/common/bundled-skills/atw-triage/{OUT-OF-SCOPE.md → out-of-scope.md} +0 -0
|
@@ -24,10 +24,22 @@ The remainder of this file uses "skill" for the local file; the override and con
|
|
|
24
24
|
| AI should automatically know a capability | Add or modify a skill. |
|
|
25
25
|
| User wants to trigger manually with a command | Add or modify a command/prompt/workflow. |
|
|
26
26
|
| Team project conventions | Prefer `.atw/spec/` or a project-local skill — never a bundled skill directory. |
|
|
27
|
-
|
|
|
27
|
+
| Adjust how an existing ATW skill or command works | Write its skill notes: `.atw/skill-notes/<installed name>.md` for the project, `~/.atw/skill-notes/<installed name>.md` for yourself across every project. Do not edit the installed copy and do not copy it under a new name. |
|
|
28
|
+
| A whole capability ATW does not have | Create a project skill whose name does not start with `atw-`. |
|
|
28
29
|
| Contribute the change back upstream | Edit `packages/cli/src/templates/common/bundled-skills/<name>/` in the ATW CLI repo, not the deployed copy. |
|
|
29
30
|
| Change ATW flow semantics | Synchronize `.atw/workflow.md`. |
|
|
30
31
|
|
|
32
|
+
## Adjust An Existing ATW Skill: Write Its Skill Notes
|
|
33
|
+
|
|
34
|
+
Every ATW skill and command is installed once per platform and refreshed by `atw update`, so an edit to any installed copy conflicts with the next update, and a copy under a new name is never called by the flow and gets none of ATW's later improvements. Put the adjustment in the skill's notes instead; the skill itself keeps upgrading and the notes sit on top of it, one file for every platform.
|
|
35
|
+
|
|
36
|
+
- **Where**: `.atw/skill-notes/<installed name>.md` in the project — it travels with the repository and the whole team uses it; `~/.atw/skill-notes/<installed name>.md` for one person, on every project on this machine. When both exist, the project's notes win.
|
|
37
|
+
- **Name**: the installed name — the skill directory name (`atw-tdd`, `atw-brainstorm`) or, for the three commands, `atw-start`, `atw-continue`, `atw-finish-work` on every platform.
|
|
38
|
+
- **How it is read**: install puts a fixed paragraph at the top of every ATW skill and command telling the AI to read both files first. Priority, highest first: what the user says in the moment → red lines and safety rules → the notes → the skill's own defaults. A note that conflicts with the two higher layers is not followed, and the user is told.
|
|
39
|
+
- **Ownership**: the notes belong to the project or the user. `atw update`, `atw uninstall` and `atw mode` never touch `.atw/skill-notes/` or `~/.atw/skill-notes/`.
|
|
40
|
+
|
|
41
|
+
Write a note as short, direct instructions about how the skill should behave differently ("Ask before running the full suite."), not a second copy of the skill.
|
|
42
|
+
|
|
31
43
|
## Modify A Skill
|
|
32
44
|
|
|
33
45
|
A skill is usually:
|
|
@@ -58,9 +70,9 @@ The same directory shape is used by two very different ownership models:
|
|
|
58
70
|
| Dispatch | Auto-dispatched to every platform skill root by `getBundledSkillTemplates()` (`packages/cli/src/templates/common/index.ts`) on `atw init` / `atw update` | Created by the user (or another skill) and never moved |
|
|
59
71
|
| Hash tracking | Every file recorded in `.atw/.template-hashes.json`; conflict prompt on update | Not tracked |
|
|
60
72
|
| Editing locally | Allowed but will be marked "modified by user" on next update | Free editing |
|
|
61
|
-
| The right way to customize |
|
|
73
|
+
| The right way to customize | Write the skill's notes (see above); never edit the installed copy or clone it under a new name | Edit the file directly |
|
|
62
74
|
|
|
63
|
-
If the goal is "make my project's AI behave differently when
|
|
75
|
+
If the goal is "make my project's AI behave differently when an ATW skill runs," the answer is that skill's notes, not surgery on `atw-meta/` and not a renamed copy.
|
|
64
76
|
|
|
65
77
|
## Modify A Command/Prompt/Workflow
|
|
66
78
|
|
|
@@ -94,30 +106,23 @@ If a command only repeats workflow rules, prefer making it reference/read `.atw/
|
|
|
94
106
|
|
|
95
107
|
Every directory above is a deploy target for the four bundled skills. Each platform receives a full copy on `atw init` and refresh on `atw update`; nothing has to be wired by hand.
|
|
96
108
|
|
|
97
|
-
## Add A Project
|
|
109
|
+
## Add A Project Skill (Only For A Missing Capability)
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
Create a project skill only when a whole capability is missing — something no ATW skill does at all. To change how an existing ATW skill works, write its skill notes instead. Never put project-private content into a bundled skill directory, since `atw update` will overwrite it.
|
|
100
112
|
|
|
101
113
|
```text
|
|
102
|
-
.claude/skills/
|
|
114
|
+
.claude/skills/acme-deploy/
|
|
103
115
|
└── SKILL.md
|
|
104
116
|
```
|
|
105
117
|
|
|
106
118
|
For multi-platform projects, add equivalent versions in each platform skill directory, or use `.agents/skills/` on platforms that support the shared layer (Codex, Gemini CLI).
|
|
107
119
|
|
|
108
|
-
Pick a name that does **not**
|
|
109
|
-
|
|
110
|
-
- `atw-meta`
|
|
111
|
-
- `atw-spec-bootstrap`
|
|
112
|
-
- `atw-session-insight`
|
|
113
|
-
- `atw-channel`
|
|
114
|
-
|
|
115
|
-
A reused name causes `getBundledSkillTemplates()` to overwrite the project-local copy on the next update. A common convention is to prefix the project name: `acme-atw-deploy`, `acme-atw-onboarding`.
|
|
120
|
+
Pick a name that does **not** start with `atw-`: that prefix is ATW's own, and a reused name causes `getBundledSkillTemplates()` to overwrite the project copy on the next update. A common convention is to prefix the project name: `acme-deploy`, `acme-onboarding`.
|
|
116
121
|
|
|
117
122
|
## Notes
|
|
118
123
|
|
|
119
124
|
- Do not mix every platform's syntax into one file.
|
|
120
125
|
- Do not change only one platform entry point while claiming all platforms are supported.
|
|
121
126
|
- Do not hide long-term engineering conventions inside a command; write them to `.atw/spec/`.
|
|
122
|
-
- Do not hand-edit files inside `atw-meta/`, `atw-spec-bootstrap/`, `atw-session-insight/`, or `atw-channel/` under any `.{platform}/skills/` directory expecting the change to persist — they are bundled and refreshed by `atw update`. Either contribute upstream or
|
|
123
|
-
- After `atw update` reports a "modified by you" conflict on a bundled skill file, choose **keep** only if you accept maintaining the divergence by hand; otherwise accept the overwrite and re-apply the intent as
|
|
127
|
+
- Do not hand-edit files inside `atw-meta/`, `atw-spec-bootstrap/`, `atw-session-insight/`, or `atw-channel/` under any `.{platform}/skills/` directory expecting the change to persist — they are bundled and refreshed by `atw update`. Either contribute upstream or write the skill's notes.
|
|
128
|
+
- After `atw update` reports a "modified by you" conflict on a bundled skill file, choose **keep** only if you accept maintaining the divergence by hand; otherwise accept the overwrite and re-apply the intent as the skill's notes.
|
|
@@ -70,7 +70,7 @@ Active task is session-level state stored in `.atw/.runtime/sessions/`. Do not f
|
|
|
70
70
|
`cmd_create` in `.atw/scripts/common/task_store.py` calls `set_active_task` best-effort right after writing the new task directory. The behavior:
|
|
71
71
|
|
|
72
72
|
- When the calling shell carries session identity (`ATW_CONTEXT_ID` env var, or any platform-specific session env that `resolve_context_key` recognizes — see `active_task.py:_ENV_SESSION_KEYS`), the per-session pointer at `.atw/.runtime/sessions/<context_key>.json` is rewritten to point at the new task. The task's `status=discover` and `[workflow-state:discover]` fires on the very next `UserPromptSubmit`.
|
|
73
|
-
- When session identity is unavailable (raw CLI invocation outside an AI session, or a platform that doesn't propagate identity to shell), the task directory is still created and `status=discover` is still written, but the active pointer is left untouched. The user can attach the task later with `task.py
|
|
73
|
+
- When session identity is unavailable (raw CLI invocation outside an AI session, or a platform that doesn't propagate identity to shell), the task directory is still created and `status=discover` is still written, but the active pointer is left untouched. The user can attach the task later with `task.py resume <dir>` once they're back in an AI session — `resume` keeps it at `discover`, while `start` would move it on to `implement`.
|
|
74
74
|
|
|
75
75
|
This makes `[workflow-state:discover]` the live breadcrumb during the brainstorm and JSONL curation work that follows `task.py create`. The pre-R7 behavior left the breadcrumb stuck on `no_task` until `task.py start`, so the discover block was effectively dead text.
|
|
76
76
|
|
package/dist/templates/common/bundled-skills/atw-meta/references/customize-local/overview.md
CHANGED
|
@@ -11,6 +11,7 @@ This directory is for local AI working in a user project where ATW was installed
|
|
|
11
11
|
| "AI did not read context / change injected content" | `change-context-loading.md` |
|
|
12
12
|
| "A platform hook is not behaving as expected" | `change-hooks.md` |
|
|
13
13
|
| "Change implement/check/research agent behavior" | `change-agents.md` |
|
|
14
|
+
| "Make an ATW skill or command behave differently" | `change-skills-or-commands.md` (write its skill notes) |
|
|
14
15
|
| "Add a skill/command/workflow/prompt" | `change-skills-or-commands.md` |
|
|
15
16
|
| "Adjust the project spec structure" | `change-spec-structure.md` |
|
|
16
17
|
| "Add team conventions and local notes" | `add-project-local-conventions.md` |
|
package/dist/templates/common/bundled-skills/atw-meta/references/local-architecture/task-system.md
CHANGED
|
@@ -77,7 +77,7 @@ The user sees a "current task," but ATW stores active task state per session.
|
|
|
77
77
|
.atw/.runtime/sessions/<context-key>.json
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
`task.py start`
|
|
80
|
+
`task.py start` and `task.py resume` write the task path into the runtime session file for the current session. To pick up an existing task — at `implement` or `accept` too — use `resume`: it writes only that session file and changes nothing in the task. `start` is for moving a task from planning into `implement` (step 1.5); it also writes `status`, and `branch` when none is set, into `task.json`. `task.py current --source` shows the current task and where it came from. Different AI windows can point to different tasks without overwriting each other.
|
|
81
81
|
|
|
82
82
|
If the platform or shell environment has no stable session identity, `task.py start` may be unable to set the active task. The AI should read the error, inspect the platform hook/session environment, and not fall back to a shared global pointer.
|
|
83
83
|
|
|
@@ -104,6 +104,7 @@ Rules:
|
|
|
104
104
|
```bash
|
|
105
105
|
python3 ./.atw/scripts/task.py create "<title>" --description "<one-line summary>" --slug <slug>
|
|
106
106
|
python3 ./.atw/scripts/task.py start <task>
|
|
107
|
+
python3 ./.atw/scripts/task.py resume <task>
|
|
107
108
|
python3 ./.atw/scripts/task.py current --source
|
|
108
109
|
python3 ./.atw/scripts/task.py add-context <task> implement <file> <reason>
|
|
109
110
|
python3 ./.atw/scripts/task.py validate <task>
|
|
@@ -41,6 +41,7 @@ Whether these files exist in a project depends on which `atw init --<platform>`
|
|
|
41
41
|
| `inject-workflow-state.py` | Parses `[workflow-state:STATUS]` blocks in `.atw/workflow.md` and emits the body matching the current task status. Falls back to `Refer to workflow.md for current step.` when no matching block exists. |
|
|
42
42
|
| `inject-subagent-context.py` | Injects PRD, JSONL context, and related spec/research into sub-agents. |
|
|
43
43
|
| `inject-shell-session-context.py` | Lets shell commands inherit ATW session identity. |
|
|
44
|
+
| `show-notice.py` | Claude Code only (PostToolUse, `Bash`): after `cross_model_review.py afk-notice`, `prepare` or `prepare-log`, has Claude Code show the user the `notice` the script wrote to `.atw/.runtime/hook-notice.json`, word for word, without the model in between. |
|
|
44
45
|
|
|
45
46
|
Not every platform has every hook. Do not copy files from another platform just because a platform lacks a hook; first confirm whether that platform supports the corresponding event.
|
|
46
47
|
|
|
@@ -91,7 +91,7 @@ Noob mode changes how you talk to the user. It changes nothing about what the wo
|
|
|
91
91
|
|
|
92
92
|
- **Stages.** The first time a stage comes up, say in a few words what it is for — discover is "getting clear on what you want", specify is "writing it down so we both agree", slice is "cutting the work into small pieces", implement is "building it", accept is "checking it against what we agreed".
|
|
93
93
|
- **Stops ①–④.** These are the user's decisions. Say what is being decided, what each answer leads to, and what you recommend. Never present a stop as a formality.
|
|
94
|
-
- **Skills only the user can run.** When the next step is theirs to start (`/atw-spec`, `/atw-tickets`, `/atw-implement`, `/atw-implement-spec`, `/atw-implement-all`, `/atw-askme`, `/atw-map`), say what typing it will do and why it is their call, then give the exact text to type.
|
|
94
|
+
- **Skills only the user can run.** When the next step is theirs to start (`/atw-spec`, `/atw-tickets`, `/atw-implement`, `/atw-implement-spec`, `/atw-implement-all`, `/atw-askme`, `/atw-map`, `/atw-cross-model-review`, `/atw-verify-skill`, `/atw-what-happened`, `/atw-reflect`), say what typing it will do and why it is their call, then give the exact text to type.
|
|
95
95
|
- **Asking them to confirm a file.** Do not hand over `prd.md` or a ticket list and ask "confirm?". Summarize in plain words what it commits to — what will be built, what will not, what counts as done — then point to the file.
|
|
96
96
|
- **Other skills' output.** A skill with its own format keeps it. In a file (review findings, ticket tables, a spec) the content stays as that skill wants it, and what you say about it to the user follows these rules. In the reply itself (a numbered round of questions, a list of findings) the shape stays and every word inside it follows these rules — a question the user cannot read is a decision they cannot make. No skill's format outranks noob mode.
|
|
97
97
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: atw-performance
|
|
3
|
+
description: "性能优化:让本来就慢的东西变快——页面加载、接口响应、构建、批处理的速度(performance、benchmark、profiling、latency、p50、p95)。用户说「太慢了,想快一点」「优化一下性能」「达不到速度目标」时用,一眼看出慢在哪、只是小改动时也先用它,再读代码、测速和改:先量现在多慢,报任何数字前过 7 个自检问题,按「不做、别重复做、少做、晚点做、趁用户不注意时做、并行做、做得更便宜」的顺序试,一次只改一处、同样的方法再量,没明显变快就撤回,每轮记进决定日志。用户要求持续改进(跑几轮、一直优化到达标)时,建任务、问需求或提任何条件之前先读它,照它提出指标、达到多少算成的目标值、最多轮数三样各给建议值,三样都经用户同意才开始第一轮,「收益太小就停」不算目标。原来正常、改了什么之后才变慢的用 atw-diagnosing-bugs。"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ATW Performance
|
|
7
|
+
|
|
8
|
+
For making something faster that is already slow, or that misses a speed target. Two ways to use it:
|
|
9
|
+
|
|
10
|
+
- **One-off fix** (the default): one slow thing, fixed and proven — see "One-off fix".
|
|
11
|
+
- **Sustained improvement**, only when the user asks for it: iterate on one metric toward an agreed target — see "Sustained improvement".
|
|
12
|
+
|
|
13
|
+
For sustained improvement, read this whole skill before you create a task, ask about the requirements, or propose any condition for the run: the ATW flow's brainstorm or spec asks this skill's three agreements, it does not replace them.
|
|
14
|
+
|
|
15
|
+
Every number either way is vetted with the "Benchmark checklist" before you report it or act on it, and every change is kept or reverted by "Keep or revert".
|
|
16
|
+
|
|
17
|
+
It used to be fine and got slower after a change: that is a regression, and it goes to the `atw-diagnosing-bugs` skill. When you cannot tell which, measure how slow it is now, then ask the user since when it has been slow.
|
|
18
|
+
|
|
19
|
+
## Where the work happens
|
|
20
|
+
|
|
21
|
+
- Do performance work inside an ATW task (`python3 .atw/scripts/task.py current`). With no current task, ask the user whether to create one before you start; create it the way `.atw/workflow.md` says.
|
|
22
|
+
- Every attempt, kept or reverted, gets one row in the task's decision log, written by the main conversation: the hypothesis, the change, the before and after numbers, the verdict (kept or reverted), and where the raw data is. A reverted attempt leaves no trace in git, so the row is how a dead idea stays dead; read the log before each attempt.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python3 .atw/scripts/decision_log.py add --phase "<性能第 N 轮 | perf round N>" --decision "<the change>" --why "<the hypothesis>" --evidence "<where the raw data is>" --result "<before → after, runs, range; verdict: kept | reverted>"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
When the task has no `decision-log.md` yet, this skill opens it: run `python3 .atw/scripts/decision_log.py start --lang <zh|en>` (the user's language) first, then `add`.
|
|
29
|
+
- Save the raw measurements as files in the task directory (for example `perf/round-03.txt`), and point to them from the row; the row carries the summary, not the raw runs.
|
|
30
|
+
- A sub-agent returns only the numbers and where its raw data is; it writes no log row.
|
|
31
|
+
- When you are an implementer sub-agent, make the change yourself and dispatch no sub-agent; put each attempt and its numbers in your report so the main conversation logs it.
|
|
32
|
+
|
|
33
|
+
## Benchmark checklist
|
|
34
|
+
|
|
35
|
+
Use this when you produce a performance number: a change's before and after, a regression claim, a hillclimb harness, or a library or config choice. Answer each question with evidence from a run, not a guess about the code.
|
|
36
|
+
|
|
37
|
+
For a quick ballpark the user asked for, one run is enough. Still check questions 4 and 7, and say that it is one run. Skip the rest unless that run looks wrong. A choice between options is never a ballpark.
|
|
38
|
+
|
|
39
|
+
### Before you run anything
|
|
40
|
+
|
|
41
|
+
- Write down the claim you expect to make, in the words you would ship ("export is 30% faster at p50 on the 60k-row dataset"). The questions test that sentence.
|
|
42
|
+
- Read the measurement script. Note what it times, counts, and ignores.
|
|
43
|
+
- Check the load average with `uptime` (Linux and macOS) and the core count with `nproc` on Linux or `sysctl -n hw.ncpu` on macOS. If the machine is busy, find out what is running. If you cannot stop it, interleave the sides so both see the same noise, and say so in the report.
|
|
44
|
+
|
|
45
|
+
### The questions
|
|
46
|
+
|
|
47
|
+
1. **Why not double?** Name the limiter. Profile in a run you do not report, because profilers and tracers slow the work down. Use CPU per process (`top` on both; `pidstat` on Linux, `ps -o pid,%cpu,command -p <pid>` on macOS), a runtime profiler (`node --cpu-prof`, `py-spy`; `perf` on Linux, `sample <pid>` on macOS), I/O wait, and syscall counts (`strace -c` on Linux; on macOS `sudo dtruss -c`, which System Integrity Protection blocks for most system binaries), then map the hot spot to source. Watch the load generator too: if it saturates first, you measured the load generator. If a change did not move the number, the limiter explains why, so find it before you call the change useless.
|
|
48
|
+
2. **Was it tuned?** Run every side the way production runs it: release builds, production flags and env, batching and transaction settings, connection pools, caches as warm or cold as production sees them, and the same versions and data. If one side runs on defaults, you compared configurations, not implementations. A limiter that is a setting (a commit per row, a debug build, a missing index) means that side is untuned. Tune it and measure again before you pick a winner. If you cannot tune it, do not pick a winner from that run. When the user is choosing what to adopt, narrowing the claim to the code as it ships today does not fix this, because they adopt the option, not today's settings.
|
|
49
|
+
3. **Did it break limits?** Do the arithmetic. Compare bytes per second with disk and network bandwidth, operations per second times cost per operation with your cores, and the time saved with the time the changed piece took. Removing a piece that takes 10% of the run can make the run at most about 11% faster. A result past a limit measured something other than the work, such as a cache, a no-op, or a bug.
|
|
50
|
+
4. **Did it error?** Count failures and non-success responses, and check that outputs are correct, not just present. Rejections are often fast, and timeouts and retries are slow. If the script does not count errors, add the count.
|
|
51
|
+
5. **Does it reproduce?** Run each side at least 5 times, alternating (A, B, A, B, and so on) so warmup, lazy initialization, caches, and drift favor neither. Report the median and range. A gap smaller than the run-to-run variation is no measurable difference. When the call is close, use a rank-sum test or the harness's own statistics.
|
|
52
|
+
6. **Does it matter?** Next to any micro result, measure the end-to-end path a user waits on, with realistic data sizes and concurrency, and report the micro result as a share of the whole. A helper that takes 1% of a request can make the request at most 1% faster, however fast the helper gets.
|
|
53
|
+
7. **Did it even happen?** Confirm the work ran inside the timed region: the request reached the server, the rows were written, the bytes were read, and the code used the result. Lazy code (generators nobody iterates, promises nobody awaits, results the JIT can discard) and timeouts produce numbers for work that never happened.
|
|
54
|
+
|
|
55
|
+
### Report
|
|
56
|
+
|
|
57
|
+
- Lead with the verdict: faster, slower, no measurable difference, or inconclusive.
|
|
58
|
+
- Give the number with its unit, the run count, the range, and the limiter. For example, "p50 41 ms → 33 ms, median of 7 runs per side, range 32 to 35 ms after, bound by JSON parsing on one core."
|
|
59
|
+
- Call the verdict inconclusive when you claim a difference but cannot name the limiter, when a side ran untuned, or when you could not check questions 4 and 7. Name the gap.
|
|
60
|
+
- Keep a commit message to one primary number. Put the runs, range, and limiter evidence in the raw data file the log row points to.
|
|
61
|
+
|
|
62
|
+
Vet the one-off fix's baseline before you plan from it, and every number after that. Vet the sustained improvement's harness before it is frozen; the frozen harness then prints error and work counts, so each keep-or-revert checks questions 4 and 7 for free.
|
|
63
|
+
|
|
64
|
+
## Keep or revert
|
|
65
|
+
|
|
66
|
+
Decide every attempt strictly, against the baseline measured the same way:
|
|
67
|
+
|
|
68
|
+
- A change whose speedup is not past the run-to-run variation is reverted — including a simplification that came along with it. Neutral code costs maintenance and bought nothing.
|
|
69
|
+
- Slower: revert.
|
|
70
|
+
- Faster, but a test went red: revert. An optimization that wins by dropping work the product needed is a regression.
|
|
71
|
+
- Past the variation and the tests green: keep, and commit it with the before and after numbers in the message.
|
|
72
|
+
|
|
73
|
+
Undo only the edits this round made, and leave every other change in the tree alone — the user's, another task's, or an earlier kept round's. Reverse your own edits (by hand, or `git apply -R` of a patch of only your edits); never `git checkout --`, `git restore`, `git stash` or `git reset` on files or a tree that held changes before the round began.
|
|
74
|
+
|
|
75
|
+
## One-off fix
|
|
76
|
+
|
|
77
|
+
This is the default. You own the measurement story: plan, review, verify the numbers. Tie every fix to a measurement; don't read source instead of measuring.
|
|
78
|
+
|
|
79
|
+
1. Capture a baseline measurement of the slow path. Vet the baseline, and each later number, with the "Benchmark checklist".
|
|
80
|
+
2. Ground hypotheses in the measurement and the code. Don't claim a perf ceiling without running it first.
|
|
81
|
+
Try the performance mantras in order, cheapest first:
|
|
82
|
+
1. Don't do it. Stop work whose result nothing uses rather than cheapening it.
|
|
83
|
+
2. Do it, but don't do it again.
|
|
84
|
+
3. Do it less.
|
|
85
|
+
4. Do it later.
|
|
86
|
+
5. Do it when they're not looking.
|
|
87
|
+
6. Do it concurrently.
|
|
88
|
+
7. Do it cheaper.
|
|
89
|
+
|
|
90
|
+
When an earlier mantra meets the target, stop.
|
|
91
|
+
3. Plan the fix from the measurement. Fix one thing per attempt, then measure again the same way as the baseline — same command, same conditions, same data — and verify each attempt before trying the next. Several changes measured together give one number you cannot attribute.
|
|
92
|
+
4. Parse and compare the artifacts (JSON to sqlite, diff). "Inconclusive" or wrong-surface is not a pass. Flag it. Keep or revert by "Keep or revert", and log the row either way.
|
|
93
|
+
5. **Guard what you kept.** Once you stop trying attempts and at least one was kept, add one guard so the thing does not slow back down. Guard the metric the user actually feels, not every available number: the same LCP, INP, p95 latency, wall time or other number that justified the fix. One guard for the whole fix, on its final number, not one per kept attempt. When it can be a test in the project, write it as one: a budget the project's own test command checks, repeated and compared by median so normal run-to-run variation does not make it flaky. A guard that needs an external monitoring service (field or real-user monitoring, alerting): only recommend it in your reply. Do not set up the service, an account or an alert for the user. When neither fits, say so in your reply, and why. Budget examples and how to enforce a budget in CI are in `references/performance-optimization.md`, "Step 5: Guard Against Regression". When a guard fires later, the thing got slower after a change: that is a regression, and it goes to the `atw-diagnosing-bugs` skill.
|
|
94
|
+
6. Cite the measurement in the commit message. Commit the guard with the fix it guards: write it before you commit the last kept attempt and put both in that commit, so the guard fails on the baseline, passes with the fix, and reverts with it. When that attempt is already committed because a later one was reverted, commit the guard on its own right after, naming the fix's commit.
|
|
95
|
+
|
|
96
|
+
To know what to measure first, and the usual bottlenecks and their fixes, see the attached files below.
|
|
97
|
+
|
|
98
|
+
**Reply:** baseline number, post-fix number, delta, raw data path, the decision-log rows written, and the guard you added, the monitoring you recommend, or why neither fits.
|
|
99
|
+
|
|
100
|
+
## Sustained improvement
|
|
101
|
+
|
|
102
|
+
Only when the user asks for it — "keep going until it's under 200 ms", "跑几轮看看能提升多少", "一直优化到达标". Otherwise do a one-off fix. You own the metric and the experiment's integrity.
|
|
103
|
+
|
|
104
|
+
Core discipline: one change, one measurement, keep or revert. Never stack untested changes, and never claim a win from code inspection.
|
|
105
|
+
|
|
106
|
+
1. Ground the workload and architecture before choosing the metric. Read the source along the target path, name the realistic workload dimensions that can move the result (data size, history, state, concurrency), and select a case that reproduces the user's complaint. If no case reproduces it, fix the repro instead of hillclimbing.
|
|
107
|
+
2. **Hard stop before round 1: agree three things.** Propose all three to the user together, each with a suggested value, before round 1 and before any other condition you propose for the run:
|
|
108
|
+
- **the metric** to improve, and the direction that counts as better (for example, the median wall time of the full run);
|
|
109
|
+
- **the target**: a value of that metric that counts as success (for example, "under 0.5 s" or "at least 80% faster than the baseline"). A stop rule such as "stop when a round gains less than 5%" is not a target; offer it, if at all, next to the target, never instead of it;
|
|
110
|
+
- **the most rounds** to try.
|
|
111
|
+
|
|
112
|
+
Numbers the user already gave win over yours. Start round 1 only once the user has agreed to all three; while any one is missing or not agreed, ask for it and start nothing. When the ATW flow runs a brainstorm or spec first, ask them there, as this skill says. Stop as soon as any one holds: the target is met, the agreed rounds are used up, or no idea left is worth trying. Then report. Don't relax the target to meet it, and don't add rounds without asking.
|
|
113
|
+
3. Build the measurement harness, prove its sensitivity, then freeze it. Run contrasting realistic workloads and confirm the target case reproduces the symptom while easier cases separate as expected. If the harness cannot distinguish them, revise the workload or metric. Vet the harness with the "Benchmark checklist" before you freeze it, and make it print its error count and a count of the work done. Once frozen, one repeatable command emits the metric, sampled enough to clear the noise (median of N, not a single run). Once frozen, do not change the harness, the workload or the metric for the rest of the run; if it turns out wrong, stop and tell the user. Record the baseline metric and a green run of the regression gate (the tests that must keep passing) before any change.
|
|
114
|
+
4. Ground each hypothesis in the architecture model from step 1, so it names a specific mechanism ("defer X off the boot path because it blocks first paint"), not "try memoizing something". For a perf metric, order hypotheses by the performance mantras in "One-off fix" step 2. Borrow only their order, not that step's stop rule.
|
|
115
|
+
5. Loop, one hypothesis per round:
|
|
116
|
+
- Make the change with a tight scope: in the main conversation, yourself or through the implementer as usual; as an implementer, yourself (see "Where the work happens"). When several independent hypotheses are live, their changes may be prepared in parallel, each in its own worktree.
|
|
117
|
+
- Formal before-and-after runs go one at a time, with no other heavy work on the machine — no build, test suite, other benchmark or parallel attempt running alongside.
|
|
118
|
+
- Measure before and after with the frozen harness, and run the regression gate.
|
|
119
|
+
- Keep or revert by "Keep or revert". A tweak that "might help" is not kept.
|
|
120
|
+
- One commit per accepted fix, staging only the files you changed (`git add <files>`, never `-A`). Log the row either way, kept or reverted.
|
|
121
|
+
Each round ends in a check before the next begins.
|
|
122
|
+
6. Push past the first plateau. On a stall, several rejects in a row, pivot category, combine near-misses, re-read the source, or try something more radical before concluding the hill is climbed. Correctness and simplicity outrank the number.
|
|
123
|
+
7. Within the agreed rounds, don't quit while cheap untried hypotheses remain. If you are stuck, surface it instead of spinning.
|
|
124
|
+
8. Leave the accepted commits on the current branch in the order they landed; review and acceptance follow the task's ATW flow. Do not open a pull request, push or merge.
|
|
125
|
+
|
|
126
|
+
**Reply:** the metric and target, baseline to final with the percent delta, rounds run (kept vs reverted), each accepted fix on one line, the decision-log path and raw data folder, and the best idea you would try next if pushed further.
|
|
127
|
+
|
|
128
|
+
## Attached files
|
|
129
|
+
|
|
130
|
+
Open them when you need them, not up front:
|
|
131
|
+
|
|
132
|
+
- `references/performance-optimization.md` — the measure, identify, fix, verify, guard workflow; "Where to Start Measuring" (what to measure first for a slow page, a sluggish interaction or a slow backend); the frontend and backend bottleneck tables; the anti-pattern catalogue; the keep-or-revert table; budgets and regression guards.
|
|
133
|
+
- `references/optimization-patterns.md` — a worked fix for each anti-pattern.
|
|
134
|
+
- `references/performance-checklist.md` — frontend and backend checklists, TTFB diagnosis, caching strategies, measurement commands, common anti-patterns.
|
package/dist/templates/common/bundled-skills/atw-performance/references/optimization-patterns.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Optimization Patterns
|
|
2
|
+
|
|
3
|
+
Worked fixes for the anti-patterns named in Step 3 of `./performance-optimization.md` (an attached file of the `atw-performance` skill). That file states the rule and the signature to look for; this file shows one concrete implementation. Open the section you need after Step 2 has identified the bottleneck — it is not meant to be read top to bottom.
|
|
4
|
+
|
|
5
|
+
Checklists, measurement commands, TTFB diagnosis, and caching read/write strategies live in `./performance-checklist.md`.
|
|
6
|
+
|
|
7
|
+
## N+1 Queries (Backend)
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// BAD: N+1 — one query per task for the owner
|
|
11
|
+
const tasks = await db.tasks.findMany();
|
|
12
|
+
for (const task of tasks) {
|
|
13
|
+
task.owner = await db.users.findUnique({ where: { id: task.ownerId } });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// GOOD: Single query with join/include
|
|
17
|
+
const tasks = await db.tasks.findMany({
|
|
18
|
+
include: { owner: true },
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Unbounded Data Fetching
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
// BAD: Fetching all records
|
|
26
|
+
const allTasks = await db.tasks.findMany();
|
|
27
|
+
|
|
28
|
+
// GOOD: Paginated with limits
|
|
29
|
+
const tasks = await db.tasks.findMany({
|
|
30
|
+
take: 20,
|
|
31
|
+
skip: (page - 1) * 20,
|
|
32
|
+
orderBy: { createdAt: 'desc' },
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Queries That Ignore Their Index
|
|
37
|
+
|
|
38
|
+
"Add an index" is the guess. The query plan is the measurement:
|
|
39
|
+
|
|
40
|
+
```sql
|
|
41
|
+
EXPLAIN ANALYZE
|
|
42
|
+
SELECT id, title FROM tasks
|
|
43
|
+
WHERE owner_id = 42 ORDER BY created_at DESC LIMIT 20;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Three things in the output decide the fix:
|
|
47
|
+
|
|
48
|
+
| What you see | What it means |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `Seq Scan` on a large table where you expected an index | No usable index for this predicate |
|
|
51
|
+
| Estimated `rows=` off from actual by an order of magnitude | Stale statistics; the planner is choosing on bad information |
|
|
52
|
+
| A `Sort` node above the scan | The index covers the filter but not the `ORDER BY` |
|
|
53
|
+
|
|
54
|
+
Index for the **shape of the query**, not the column in isolation. In a composite index, equality columns come first, then the range or sort column:
|
|
55
|
+
|
|
56
|
+
```sql
|
|
57
|
+
CREATE INDEX idx_tasks_owner_created ON tasks (owner_id, created_at DESC);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**When an index will not help:**
|
|
61
|
+
|
|
62
|
+
| Situation | Why |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Low selectivity, querying the dominant value (a `status` column that is 95% `active`, filtered on `active`) | A sequential scan is genuinely cheaper; the planner will ignore the index. Filtering on the rare value is the opposite case, and a partial index serves it well |
|
|
65
|
+
| Leading wildcard (`LIKE '%term'`) | A B-tree cannot seek without a prefix; needs trigram or full-text |
|
|
66
|
+
| Function on the column (`WHERE lower(email) = ?`) | The plain column index is unusable; index the expression instead |
|
|
67
|
+
| Write-heavy table | Every index is a tax on every `INSERT`/`UPDATE`; measure the write cost, not just the read gain |
|
|
68
|
+
|
|
69
|
+
Re-run `EXPLAIN ANALYZE` after. An index that did not change the plan is a revert (Step 4), and it is not free: it still costs on every write.
|
|
70
|
+
|
|
71
|
+
## Connection Pool Exhaustion
|
|
72
|
+
|
|
73
|
+
The signature is distinctive: **every** endpoint slows at once, the slow time is spent waiting for a connection rather than executing, and the database reports mostly idle sessions.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// BAD: a pool per request or per module — under serverless this multiplies
|
|
77
|
+
// by instance count and exhausts the database's connection limit
|
|
78
|
+
// GOOD: one pool per process, sized against the database's ceiling
|
|
79
|
+
const pool = new Pool({
|
|
80
|
+
max: 10, // instances × max must stay under max_connections
|
|
81
|
+
idleTimeoutMillis: 30_000,
|
|
82
|
+
connectionTimeoutMillis: 5_000, // fail fast instead of queueing forever
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Bigger is not faster.** A pool larger than what the database can execute concurrently just relocates the queue from your app to the database, where it is harder to see. When instance count is unbounded (serverless, autoscaling), a proxy that multiplexes connections (pgbouncer, RDS Proxy) is the fix, not a higher `max`.
|
|
87
|
+
|
|
88
|
+
## Missing Image Optimization (Frontend)
|
|
89
|
+
|
|
90
|
+
```html
|
|
91
|
+
<!-- BAD: No dimensions, no format optimization -->
|
|
92
|
+
<img src="/hero.jpg" />
|
|
93
|
+
|
|
94
|
+
<!-- GOOD: Hero / LCP image — art direction + resolution switching, high priority -->
|
|
95
|
+
<!--
|
|
96
|
+
Two techniques combined:
|
|
97
|
+
- Art direction (media): different crop/composition per breakpoint
|
|
98
|
+
- Resolution switching (srcset + sizes): right file size per screen density
|
|
99
|
+
-->
|
|
100
|
+
<picture>
|
|
101
|
+
<!-- Mobile: portrait crop (8:10) -->
|
|
102
|
+
<source
|
|
103
|
+
media="(max-width: 767px)"
|
|
104
|
+
srcset="/hero-mobile-400.avif 400w, /hero-mobile-800.avif 800w"
|
|
105
|
+
sizes="100vw"
|
|
106
|
+
width="800"
|
|
107
|
+
height="1000"
|
|
108
|
+
type="image/avif"
|
|
109
|
+
/>
|
|
110
|
+
<source
|
|
111
|
+
media="(max-width: 767px)"
|
|
112
|
+
srcset="/hero-mobile-400.webp 400w, /hero-mobile-800.webp 800w"
|
|
113
|
+
sizes="100vw"
|
|
114
|
+
width="800"
|
|
115
|
+
height="1000"
|
|
116
|
+
type="image/webp"
|
|
117
|
+
/>
|
|
118
|
+
<!-- Desktop: landscape crop (2:1) -->
|
|
119
|
+
<source
|
|
120
|
+
srcset="/hero-800.avif 800w, /hero-1200.avif 1200w, /hero-1600.avif 1600w"
|
|
121
|
+
sizes="(max-width: 1200px) 100vw, 1200px"
|
|
122
|
+
width="1200"
|
|
123
|
+
height="600"
|
|
124
|
+
type="image/avif"
|
|
125
|
+
/>
|
|
126
|
+
<source
|
|
127
|
+
srcset="/hero-800.webp 800w, /hero-1200.webp 1200w, /hero-1600.webp 1600w"
|
|
128
|
+
sizes="(max-width: 1200px) 100vw, 1200px"
|
|
129
|
+
width="1200"
|
|
130
|
+
height="600"
|
|
131
|
+
type="image/webp"
|
|
132
|
+
/>
|
|
133
|
+
<img
|
|
134
|
+
src="/hero-desktop.jpg"
|
|
135
|
+
width="1200"
|
|
136
|
+
height="600"
|
|
137
|
+
fetchpriority="high"
|
|
138
|
+
alt="Hero image description"
|
|
139
|
+
/>
|
|
140
|
+
</picture>
|
|
141
|
+
|
|
142
|
+
<!-- GOOD: Below-the-fold image — lazy loaded + async decoding -->
|
|
143
|
+
<img
|
|
144
|
+
src="/content.webp"
|
|
145
|
+
width="800"
|
|
146
|
+
height="400"
|
|
147
|
+
loading="lazy"
|
|
148
|
+
decoding="async"
|
|
149
|
+
alt="Content image description"
|
|
150
|
+
/>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Unnecessary Re-renders (React)
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
// BAD: Creates new object on every render, causing children to re-render
|
|
157
|
+
function TaskList() {
|
|
158
|
+
return <TaskFilters options={{ sortBy: 'date', order: 'desc' }} />;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// GOOD: Stable reference
|
|
162
|
+
const DEFAULT_OPTIONS = { sortBy: 'date', order: 'desc' } as const;
|
|
163
|
+
function TaskList() {
|
|
164
|
+
return <TaskFilters options={DEFAULT_OPTIONS} />;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Use React.memo for expensive components
|
|
168
|
+
const TaskItem = React.memo(function TaskItem({ task }: Props) {
|
|
169
|
+
return <div>{/* expensive render */}</div>;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Use useMemo for expensive computations
|
|
173
|
+
function TaskStats({ tasks }: Props) {
|
|
174
|
+
const stats = useMemo(() => calculateStats(tasks), [tasks]);
|
|
175
|
+
return <div>{stats.completed} / {stats.total}</div>;
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Large Bundle Size
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Modern bundlers (Vite, webpack 5+) handle named imports with tree-shaking automatically,
|
|
183
|
+
// provided the dependency ships ESM and is marked `sideEffects: false` in package.json.
|
|
184
|
+
// Profile before changing import styles — the real gains come from splitting and lazy loading.
|
|
185
|
+
|
|
186
|
+
// GOOD: Dynamic import for heavy, rarely-used features
|
|
187
|
+
const ChartLibrary = lazy(() => import('./ChartLibrary'));
|
|
188
|
+
|
|
189
|
+
// GOOD: Route-level code splitting wrapped in Suspense
|
|
190
|
+
const SettingsPage = lazy(() => import('./pages/Settings'));
|
|
191
|
+
|
|
192
|
+
function App() {
|
|
193
|
+
return (
|
|
194
|
+
<Suspense fallback={<Spinner />}>
|
|
195
|
+
<SettingsPage />
|
|
196
|
+
</Suspense>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Missing Caching (Backend)
|
|
202
|
+
|
|
203
|
+
Cache what is expensive to produce and read far more often than it changes. Caching a query that was already fast adds a network hop, a staleness bug, and an eviction policy to maintain, in exchange for nothing.
|
|
204
|
+
|
|
205
|
+
**Pick the layer deliberately:**
|
|
206
|
+
|
|
207
|
+
| Layer | Visible to | Use when | Cost |
|
|
208
|
+
|---|---|---|---|
|
|
209
|
+
| In-process (`Map`, LRU) | One instance | Small, hot, per-instance staleness is acceptable | Each instance drifts independently; invalidation reaches only one |
|
|
210
|
+
| Shared (Redis, Memcached) | All instances | Instances must agree, or the value is expensive to recompute | A network hop, and another service to run and monitor |
|
|
211
|
+
| CDN / edge | Everyone, per URL | Responses are public and identical for a given key | Invalidation is the hard part; assume you cannot recall a bad response quickly |
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
// Cache frequently-read, rarely-changed data
|
|
215
|
+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
|
|
216
|
+
let cachedConfig: AppConfig | null = null;
|
|
217
|
+
let cacheExpiry = 0;
|
|
218
|
+
|
|
219
|
+
async function getAppConfig(): Promise<AppConfig> {
|
|
220
|
+
if (cachedConfig && Date.now() < cacheExpiry) {
|
|
221
|
+
return cachedConfig;
|
|
222
|
+
}
|
|
223
|
+
cachedConfig = await db.config.findFirst();
|
|
224
|
+
cacheExpiry = Date.now() + CACHE_TTL;
|
|
225
|
+
return cachedConfig;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// HTTP caching headers for static assets
|
|
229
|
+
app.use('/static', express.static('public', {
|
|
230
|
+
maxAge: '1y', // Cache for 1 year
|
|
231
|
+
immutable: true, // Never revalidate (use content hashing in filenames)
|
|
232
|
+
}));
|
|
233
|
+
|
|
234
|
+
// Cache-Control for API responses
|
|
235
|
+
res.set('Cache-Control', 'public, max-age=300'); // 5 minutes
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Key design decides correctness.** Every input that changes the response belongs in the key: tenant, locale, permissions, feature flags. A key that omits the viewer is how one user's data gets served to another, and that ships as a performance win.
|
|
239
|
+
|
|
240
|
+
**Choose one invalidation strategy, not three:**
|
|
241
|
+
|
|
242
|
+
| Strategy | Trade-off |
|
|
243
|
+
|---|---|
|
|
244
|
+
| TTL | Simplest. You accept staleness up to the TTL, so state the acceptable window explicitly |
|
|
245
|
+
| Event or tag based | Fresh on write, but writers now have to know the cache topology |
|
|
246
|
+
| Versioned keys (`user:42:profile:v7`) | Never invalidate, just stop reading old keys. Costs memory until eviction |
|
|
247
|
+
|
|
248
|
+
**Guard against the stampede.** A hot key expires, every concurrent request misses together, and the origin takes the full load at once, which is how a cache turns into an outage instead of preventing one. Serve stale while a single request recomputes (`stale-while-revalidate`), or coalesce concurrent misses behind one in-flight promise so N waiters cause one recompute.
|
|
249
|
+
|
|
250
|
+
**Do not cache:** anything whose staleness is a correctness bug (balances, permissions, inventory at checkout), or per-user data under a key that does not identify the user. See `./performance-checklist.md#caching-strategies` for request coalescing, write strategies, negative caching, and the cache checklist.
|