@brunosps00/dev-workflow 2.5.0 → 2.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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +101 -0
- package/README.md +8 -5
- package/lib/doctor.js +63 -11
- package/lib/hooks.js +112 -27
- package/lib/init.js +27 -11
- package/lib/migrate-skills.js +189 -39
- package/lib/removed-bundled-skills.js +4 -2
- package/lib/skill-registry.js +12 -2
- package/lib/uninstall.js +63 -7
- package/package.json +1 -1
- package/scaffold/en/agent-instructions.md +1 -1
- package/scaffold/en/commands/dw-brainstorm.md +2 -2
- package/scaffold/en/commands/dw-bugfix.md +1 -1
- package/scaffold/en/commands/dw-commit.md +1 -1
- package/scaffold/en/commands/dw-dockerize.md +2 -2
- package/scaffold/en/commands/dw-functional-doc.md +1 -1
- package/scaffold/en/commands/dw-harness-audit.md +1 -1
- package/scaffold/en/commands/dw-intel.md +1 -1
- package/scaffold/en/commands/dw-learn.md +1 -1
- package/scaffold/en/commands/dw-new-project.md +3 -3
- package/scaffold/en/commands/dw-plan.md +2 -2
- package/scaffold/en/commands/dw-qa.md +6 -5
- package/scaffold/en/commands/dw-redesign-ui.md +3 -3
- package/scaffold/en/commands/dw-review.md +2 -2
- package/scaffold/en/commands/dw-skill-health.md +24 -1
- package/scaffold/en/commands/dw-update.md +28 -11
- package/scaffold/en/references/invariants.md +7 -2
- package/scaffold/en/templates/project-onepager.md +1 -1
- package/scaffold/pt-br/agent-instructions.md +1 -1
- package/scaffold/pt-br/commands/dw-brainstorm.md +2 -2
- package/scaffold/pt-br/commands/dw-bugfix.md +1 -1
- package/scaffold/pt-br/commands/dw-commit.md +1 -1
- package/scaffold/pt-br/commands/dw-dockerize.md +2 -2
- package/scaffold/pt-br/commands/dw-functional-doc.md +1 -1
- package/scaffold/pt-br/commands/dw-harness-audit.md +1 -1
- package/scaffold/pt-br/commands/dw-intel.md +1 -1
- package/scaffold/pt-br/commands/dw-learn.md +1 -1
- package/scaffold/pt-br/commands/dw-new-project.md +3 -3
- package/scaffold/pt-br/commands/dw-plan.md +2 -2
- package/scaffold/pt-br/commands/dw-qa.md +6 -5
- package/scaffold/pt-br/commands/dw-redesign-ui.md +3 -3
- package/scaffold/pt-br/commands/dw-review.md +2 -2
- package/scaffold/pt-br/commands/dw-skill-health.md +24 -1
- package/scaffold/pt-br/commands/dw-update.md +28 -11
- package/scaffold/pt-br/references/invariants.md +8 -2
- package/scaffold/pt-br/templates/project-onepager.md +1 -1
- package/scaffold/scripts/hooks/git-guardrails.mjs +3 -2
- package/scaffold/scripts/hooks/session-cost.mjs +62 -21
- package/scaffold/scripts/hooks/statusline.mjs +14 -11
- package/scaffold/skills/docker-compose-recipes/references/compose-composition.md +1 -1
- package/scaffold/skills/dw-llm-eval/SKILL.md +3 -1
- package/scaffold/skills/dw-llm-eval/references/integration-with-dev-workflow-commands-detail.md +2 -2
- package/scaffold/skills/dw-llm-eval/references/reference-dataset.md +57 -3
- package/scaffold/skills/dw-ui-discipline/SKILL.md +8 -5
- package/scaffold/skills/dw-ui-discipline/references/accessibility-floor.md +20 -8
- package/scaffold/skills/dw-ui-discipline/references/content-stress.md +246 -0
- package/scaffold/skills/dw-ui-discipline/references/motion.md +205 -35
- package/scaffold/skills/dw-ui-discipline/references/responsive.md +44 -2
|
@@ -24,15 +24,16 @@ Antes de sobrescrever arquivos gerenciados, crie um snapshot:
|
|
|
24
24
|
```bash
|
|
25
25
|
SNAPSHOT_DIR=".dw/.backup/$(date -u +%Y%m%dT%H%M%SZ)"
|
|
26
26
|
mkdir -p "$SNAPSHOT_DIR"
|
|
27
|
-
cp -r .dw/commands .dw/templates .dw/references .dw/scripts "$SNAPSHOT_DIR/" 2>/dev/null
|
|
27
|
+
cp -r .dw/commands .dw/templates .dw/references .dw/scripts .dw/skills "$SNAPSHOT_DIR/" 2>/dev/null
|
|
28
28
|
# agents/skills (bundled) tambem fazem parte do update
|
|
29
29
|
[ -d .agents/skills ] && cp -r .agents/skills "$SNAPSHOT_DIR/agents-skills" 2>/dev/null
|
|
30
30
|
# .claude/settings.json agora e mutado pelo update (MCPs + hooks + statusLine)
|
|
31
31
|
[ -f .claude/settings.json ] && mkdir -p "$SNAPSHOT_DIR/claude" && cp .claude/settings.json "$SNAPSHOT_DIR/claude/settings.json" 2>/dev/null
|
|
32
|
+
[ -f .dw/install-state.json ] && cp .dw/install-state.json "$SNAPSHOT_DIR/install-state.json" 2>/dev/null
|
|
32
33
|
echo "Snapshot salvo em $SNAPSHOT_DIR"
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
Manter apenas os 3 snapshots mais recentes (remover os mais antigos) para evitar acumulo.
|
|
36
|
+
Manter apenas os 3 snapshots mais recentes em `.dw/.backup/` (remover os mais antigos) para evitar acumulo. `.dw/.migrated/` não é snapshot: nunca remova.
|
|
36
37
|
|
|
37
38
|
### 1. Registrar Versão Atual (Obrigatório)
|
|
38
39
|
|
|
@@ -86,7 +87,13 @@ O comando `update` sobrescreve arquivos gerenciados e PRESERVA:
|
|
|
86
87
|
|
|
87
88
|
O comando `update` também roda o passo de migração GSD automaticamente — se o projeto tem `.planning/` legado (de uso prévio do GSD), o conteúdo é migrado para `.dw/intel/`, `.dw/spec/active-session.md`, `.dw/spec/quick/`, etc., e `.planning/` é renomeado para `.planning.gsd-archive-<DATA>/` para inspeção. Os arquivos `.claude/commands/gsd/`, `.claude/agents/gsd-*.md`, `.claude/hooks/gsd-*.js` e `.claude/gsd-file-manifest.json` são removidos durante a migração.
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
O CLI nunca apaga o que aposenta: move para `.dw/.migrated/<timestamp>/`, mantendo o caminho relativo, e imprime o destino. Isso acontece só depois que as skills novas estão em `.dw/skills/`, então um update interrompido deixa as cópias antigas no lugar. O que ele aposenta, e como decide:
|
|
91
|
+
|
|
92
|
+
- **Cópias legadas de skills bundled** em `.agents/skills/` (`[moved]`): só quando a cópia é do dev-workflow. Skill homônima que não é sai como `[kept]` e fica intocada.
|
|
93
|
+
- **Skills removidas em releases anteriores** (`[v<N>]`) e **fontes de comandos removidos** (`[legacy]`): casadas por nome, então uma skill homônima de outra origem também é movida — é o move que torna isso recuperável.
|
|
94
|
+
- **Wrappers `dw-*` órfãos** (`[orphan]`): `dw-` é o namespace deste pacote; uma skill do usuário chamada `dw-*` é movida, não perdida.
|
|
95
|
+
|
|
96
|
+
Se o update falhar (erro de rede, permissão, pacote indisponível): reporte o erro ao usuário e PARE. NÃO tente workarounds manuais como copiar arquivos. Passe ao usuário o caminho do snapshot do passo 0 e as duas saídas: rodar o update de novo depois de resolver a causa (ele é idempotente), ou `/dw-update --rollback`.
|
|
90
97
|
|
|
91
98
|
### 4. Capturar Nova Versão
|
|
92
99
|
|
|
@@ -96,7 +103,7 @@ node -e 'const s = JSON.parse(require("node:fs").readFileSync(".dw/install-state
|
|
|
96
103
|
|
|
97
104
|
### Checks de compatibilidade do upgrade
|
|
98
105
|
|
|
99
|
-
Leia o `.dw/commands/dw-update.md` atualizado após o CLI terminar. Use `.dw/install-state.json` e o output `Installed version` do CLI para a diferença de versões: um pacote executado por `npx` não precisa ser resolvível pelo `node_modules` do consumidor. Estado novo ausente ou inválido é falha de verificação; não declare sucesso do update.
|
|
106
|
+
Leia o `.dw/commands/dw-update.md` atualizado após o CLI terminar. Use `.dw/install-state.json` e o output `Installed version` do CLI para a diferença de versões: um pacote executado por `npx` não precisa ser resolvível pelo `node_modules` do consumidor. Estado novo ausente ou inválido é falha de verificação; não declare sucesso do update. A versão lida no passo 4 tem de ser igual ao lado direito da linha `Installed version: <antiga> -> <nova>` do CLI; se essa linha faltar, ou as versões diferirem, o CLI não terminou e o update falhou.
|
|
100
107
|
|
|
101
108
|
Em um upgrade de versão anterior à 2.3.0 (ou versão legada desconhecida):
|
|
102
109
|
|
|
@@ -116,6 +123,7 @@ Apresente ao usuário:
|
|
|
116
123
|
- Resumo do que o output do `update` mostrou (arquivos copiados, wrappers gerados, MCPs configurados)
|
|
117
124
|
- As `Post-update agent actions` impressas pelo CLI
|
|
118
125
|
- Quaisquer avisos ou erros
|
|
126
|
+
- Toda linha `[moved]`, `[v<N>]`, `[orphan]`, `[legacy]` ou `[kept]` do `Skill migration`, o caminho `.dw/.migrated/<timestamp>/` e todo arquivo marcado `was not installed by dev-workflow` (adição do próprio time: ofereça trazê-lo de volta numa skill do time, nunca em `.dw/skills/`, que o update sobrescreve)
|
|
119
127
|
|
|
120
128
|
### 6. Executar Ações de Agente Pós-Update (Obrigatório)
|
|
121
129
|
|
|
@@ -144,19 +152,28 @@ Se invocado com `--rollback`:
|
|
|
144
152
|
1. Listar snapshots em `.dw/.backup/`
|
|
145
153
|
2. Se nenhum existir: PARAR e reportar "Nenhum snapshot disponível"
|
|
146
154
|
3. Se mais de um existir: perguntar ao usuário qual restaurar (padrão: mais recente)
|
|
147
|
-
4. Confirmar com o usuário: "Restaurar snapshot `<path>`? Isso
|
|
155
|
+
4. Confirmar com o usuário: "Restaurar snapshot `<path>`? Isso SUBSTITUI `.dw/commands/`, `.dw/templates/`, `.dw/references/`, `.dw/scripts/` e `.dw/skills/` pelas cópias do snapshot (quando ele as tem), copia o `.agents/skills/` do snapshot por cima do atual sem remover outras entradas, e restaura `.claude/settings.json` e `.dw/install-state.json`. `.dw/templates/overrides/` nunca é tocado. Prosseguir? [s/N]"
|
|
148
156
|
5. Somente após `s`: copiar de volta
|
|
149
157
|
|
|
150
158
|
```bash
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
[ -d "$SNAPSHOT_DIR
|
|
159
|
+
SNAPSHOT_DIR=".dw/.backup/<timestamp-escolhido>" # o snapshot confirmado no passo 4
|
|
160
|
+
[ -d "$SNAPSHOT_DIR" ] || { echo "Snapshot não encontrado: $SNAPSHOT_DIR"; exit 1; }
|
|
161
|
+
# Pastas gerenciadas de .dw são substituídas, para não sobrar arquivo da release mais nova.
|
|
162
|
+
for d in commands references scripts skills; do
|
|
163
|
+
[ -d "$SNAPSHOT_DIR/$d" ] && rm -rf ".dw/$d" && cp -r "$SNAPSHOT_DIR/$d" .dw/
|
|
164
|
+
done
|
|
165
|
+
# .dw/templates/overrides/ é do dono: restaure os templates gerenciados em volta dele, nunca o remova.
|
|
166
|
+
if [ -d "$SNAPSHOT_DIR/templates" ]; then
|
|
167
|
+
find .dw/templates -mindepth 1 -maxdepth 1 ! -name overrides -exec rm -rf {} +
|
|
168
|
+
for f in "$SNAPSHOT_DIR/templates"/*; do [ "$(basename "$f")" = overrides ] || cp -r "$f" .dw/templates/; done
|
|
169
|
+
fi
|
|
170
|
+
# .agents/skills é compartilhada com as skills do próprio usuário: mescla, nunca substitui.
|
|
171
|
+
[ -d "$SNAPSHOT_DIR/agents-skills" ] && mkdir -p .agents/skills && cp -r "$SNAPSHOT_DIR/agents-skills/." .agents/skills/ 2>/dev/null
|
|
156
172
|
[ -f "$SNAPSHOT_DIR/claude/settings.json" ] && cp "$SNAPSHOT_DIR/claude/settings.json" .claude/settings.json 2>/dev/null
|
|
173
|
+
[ -f "$SNAPSHOT_DIR/install-state.json" ] && cp "$SNAPSHOT_DIR/install-state.json" .dw/install-state.json 2>/dev/null
|
|
157
174
|
```
|
|
158
175
|
|
|
159
|
-
6. Reportar: snapshot restaurado, versão
|
|
176
|
+
6. Reportar: snapshot restaurado, e a versão do `.dw/install-state.json` restaurado (ou "unknown" quando o snapshot é anterior a ele). Snapshot de antes da 2.7.0 não tem `skills/`: `.dw/skills/` fica como estava, o que é inofensivo (comandos antigos leem `.agents/skills/`), e o próximo update aposenta de novo as cópias restauradas em `.agents/skills/`.
|
|
160
177
|
|
|
161
178
|
## Opções Avançadas
|
|
162
179
|
|
|
@@ -49,8 +49,9 @@ funcionando. Um agente que não consegue destruir trabalho não pode ser convenc
|
|
|
49
49
|
|
|
50
50
|
**Como isso é verificado:** em parte pelo `.dw/scripts/hooks/git-guardrails.mjs`, um hook `PreToolUse` que
|
|
51
51
|
nega esses padrões no Bash. O hook é **uma implementação, não a fonte da regra**, e tem dois limites
|
|
52
|
-
conhecidos: só cobre Bash no Claude Code
|
|
53
|
-
|
|
52
|
+
conhecidos: só cobre Bash no Claude Code e no Codex (o Codex só o roda depois que o projeto e o hook são
|
|
53
|
+
marcados como confiáveis), e **falha aberto** — erro de parse ou exceção em runtime deixa o comando passar.
|
|
54
|
+
Então a regra obriga mesmo onde o hook não roda.
|
|
54
55
|
|
|
55
56
|
Duas operações ficam de fora do hook de propósito e vivem só aqui: `rebase` e `commit --amend`. As duas são
|
|
56
57
|
rotina em trabalho não pushado, e uma linha de comando não distingue pushado de não pushado. Negá-las
|
|
@@ -80,6 +81,11 @@ Este piso é estreito de propósito. Algo genuinamente destrutivo que não estej
|
|
|
80
81
|
isso — aplique o mesmo critério: se a ação destrói trabalho sem segunda cópia, reescreve história
|
|
81
82
|
compartilhada ou move uma credencial, pare e pergunte. Uma confirmação a mais custa uma mensagem.
|
|
82
83
|
|
|
84
|
+
O mesmo vale para as pastas de skills que os agentes compartilham — `.agents/skills/`, `.claude/skills/`,
|
|
85
|
+
`.codex/skills/`: elas guardam também skills do usuário e de outras ferramentas. Fora do `update`/`uninstall`
|
|
86
|
+
do próprio CLI, liste o que removeria e pergunte por entrada; mova o aprovado para `.dw/.migrated/<timestamp>/`,
|
|
87
|
+
nunca `rm`.
|
|
88
|
+
|
|
83
89
|
## Atribuição
|
|
84
90
|
|
|
85
91
|
O modelo de duas camadas — um piso que regra de projeto só pode apertar, nunca afrouxar — foi adaptado do
|
|
@@ -80,7 +80,7 @@ services: []
|
|
|
80
80
|
│ └── api/ (criado por `pnpm dlx @nestjs/cli` ou scaffold escolhido)
|
|
81
81
|
├── packages/
|
|
82
82
|
│ └── shared/ (criado pelo /dw-new-project)
|
|
83
|
-
├── docker-compose.dev.yml (composto a partir de .
|
|
83
|
+
├── docker-compose.dev.yml (composto a partir de .dw/skills/docker-compose-recipes/)
|
|
84
84
|
├── .env.example (consolidado dos servicos selecionados)
|
|
85
85
|
├── .gitignore (por stack)
|
|
86
86
|
├── .dockerignore (por stack)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* dev-workflow git guardrails —
|
|
3
|
+
* dev-workflow git guardrails — PreToolUse hook (matcher: Bash) for Claude Code
|
|
4
|
+
* (.claude/settings.json) and Codex (.codex/hooks.json); both send the same payload.
|
|
4
5
|
*
|
|
5
6
|
* Blocks irreversible git commands (force push, hard reset, clean -f, branch
|
|
6
7
|
* deletion, remote branch deletion). Everything else is allowed.
|
|
@@ -76,7 +77,7 @@ export function evaluatePayload(input) {
|
|
|
76
77
|
permissionDecision: 'deny',
|
|
77
78
|
permissionDecisionReason:
|
|
78
79
|
`Blocked by dev-workflow git guardrails: ${rule.why}. ` +
|
|
79
|
-
`If this is intentional, run it yourself outside the agent, or adjust .claude/settings.json hooks.`,
|
|
80
|
+
`If this is intentional, run it yourself outside the agent, or adjust the hook in .claude/settings.json or .codex/hooks.json.`,
|
|
80
81
|
},
|
|
81
82
|
};
|
|
82
83
|
}
|
|
@@ -12,9 +12,12 @@
|
|
|
12
12
|
* USD is a best-effort ESTIMATE from a local price table — tokens are exact.
|
|
13
13
|
*
|
|
14
14
|
* The same pass also counts which skills the session's tool calls referenced,
|
|
15
|
-
* emitted as `skills` on the row
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* emitted as `skills` on the row, and which dev-workflow commands it loaded
|
|
16
|
+
* (a read of `.dw/commands/<name>.md`, which every command wrapper performs, or a
|
|
17
|
+
* Skill-tool call naming a command), emitted as `commands`. `/dw-skill-health`
|
|
18
|
+
* reads both as usage evidence. An absent key means the row predates that
|
|
19
|
+
* instrumentation; `{}` means the session was observed and nothing fired —
|
|
20
|
+
* those are different facts.
|
|
18
21
|
*
|
|
19
22
|
* Contract: reads the SessionEnd payload as JSON on stdin. Fails SAFE — any
|
|
20
23
|
* error exits 0 without writing, so a hook bug never disrupts the session.
|
|
@@ -70,7 +73,7 @@ function knownSkills() {
|
|
|
70
73
|
return false;
|
|
71
74
|
}
|
|
72
75
|
};
|
|
73
|
-
for (const base of ['.agents/skills', '.claude/skills']) {
|
|
76
|
+
for (const base of ['.dw/skills', '.agents/skills', '.claude/skills']) {
|
|
74
77
|
const root = join(PROJECT_ROOT, base);
|
|
75
78
|
if (!isDir(root)) continue;
|
|
76
79
|
let top;
|
|
@@ -103,8 +106,29 @@ function knownSkills() {
|
|
|
103
106
|
}
|
|
104
107
|
|
|
105
108
|
// Matches a skills/ path inside a tool call's input, capturing at most two
|
|
106
|
-
// segments so a grouped `group/skill` is preserved.
|
|
107
|
-
|
|
109
|
+
// segments so a grouped `group/skill` is preserved. `.dw/skills/` is where the
|
|
110
|
+
// bundled skills live; `.agents/` and `.claude/` hold wrappers, external and
|
|
111
|
+
// legacy copies.
|
|
112
|
+
const SKILL_PATH = /(?:\.dw|\.agents|\.claude)\/skills\/([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)?)\//g;
|
|
113
|
+
|
|
114
|
+
// Matches the canonical command file every wrapper tells the agent to read.
|
|
115
|
+
const COMMAND_PATH = /\.dw\/commands\/([A-Za-z0-9._-]+)\.md/g;
|
|
116
|
+
|
|
117
|
+
// Commands this project has installed: the names usage counting may emit under
|
|
118
|
+
// `commands`. Their wrappers also sit under `.claude/skills/` and
|
|
119
|
+
// `.agents/skills/`, so knownSkills() sees the same names; a command name is
|
|
120
|
+
// always counted as a command, never as a skill.
|
|
121
|
+
function knownCommands() {
|
|
122
|
+
const names = new Set();
|
|
123
|
+
try {
|
|
124
|
+
for (const file of readdirSync(join(DW_DIR, 'commands'))) {
|
|
125
|
+
if (file.endsWith('.md')) names.add(file.slice(0, -3));
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
/* no installed commands — nothing to count */
|
|
129
|
+
}
|
|
130
|
+
return names;
|
|
131
|
+
}
|
|
108
132
|
|
|
109
133
|
// Count skill references in ONE assistant message.
|
|
110
134
|
//
|
|
@@ -113,17 +137,22 @@ const SKILL_PATH = /(?:\.agents|\.claude)\/skills\/([A-Za-z0-9._-]+(?:\/[A-Za-z0
|
|
|
113
137
|
// `tool_result` that returned the CONTENTS of a SKILL.md (those cite other
|
|
114
138
|
// skills) and a user prompt that merely names one — both would report a skill as
|
|
115
139
|
// fired when it never was. What this measures is loading, not obedience.
|
|
116
|
-
function countSkillUses(msg, known, out) {
|
|
140
|
+
function countSkillUses(msg, known, out, commands = new Set(), commandOut = {}) {
|
|
117
141
|
const content = msg && msg.content;
|
|
118
142
|
if (!Array.isArray(content)) return;
|
|
119
143
|
for (const block of content) {
|
|
120
144
|
if (!block || block.type !== 'tool_use' || !block.input) continue;
|
|
121
|
-
// One tool call is one reference per skill, however many times
|
|
122
|
-
// names
|
|
123
|
-
// is one load, not three.
|
|
145
|
+
// One tool call is one reference per skill or command, however many times
|
|
146
|
+
// its input names it. A grep over three files under the same skill
|
|
147
|
+
// directory is one load, not three.
|
|
124
148
|
const seen = new Set();
|
|
125
|
-
|
|
126
|
-
|
|
149
|
+
const seenCommands = new Set();
|
|
150
|
+
const note = (name) => {
|
|
151
|
+
if (commands.has(name)) seenCommands.add(name);
|
|
152
|
+
else if (known.has(name)) seen.add(name);
|
|
153
|
+
};
|
|
154
|
+
if (block.name === 'Skill' && typeof block.input.skill === 'string') {
|
|
155
|
+
note(block.input.skill);
|
|
127
156
|
}
|
|
128
157
|
let serialized;
|
|
129
158
|
try {
|
|
@@ -136,24 +165,29 @@ function countSkillUses(msg, known, out) {
|
|
|
136
165
|
let match;
|
|
137
166
|
while ((match = SKILL_PATH.exec(serialized)) !== null) {
|
|
138
167
|
const full = match[1];
|
|
139
|
-
|
|
140
|
-
|
|
168
|
+
note(known.has(full) ? full : full.split('/')[0]);
|
|
169
|
+
}
|
|
170
|
+
COMMAND_PATH.lastIndex = 0;
|
|
171
|
+
while ((match = COMMAND_PATH.exec(serialized)) !== null) {
|
|
172
|
+
if (commands.has(match[1])) seenCommands.add(match[1]);
|
|
141
173
|
}
|
|
142
174
|
}
|
|
143
175
|
for (const name of seen) out[name] = (out[name] || 0) + 1;
|
|
176
|
+
for (const name of seenCommands) commandOut[name] = (commandOut[name] || 0) + 1;
|
|
144
177
|
}
|
|
145
178
|
}
|
|
146
179
|
|
|
147
180
|
// Sum per-turn usage from the transcript, grouped by model, and count which
|
|
148
181
|
// skills the session's tool calls referenced.
|
|
149
|
-
function tallyTranscript(transcriptPath, known) {
|
|
182
|
+
function tallyTranscript(transcriptPath, known, commands = new Set()) {
|
|
150
183
|
const byModel = {};
|
|
151
184
|
const skills = {};
|
|
185
|
+
const commandUses = {};
|
|
152
186
|
let raw;
|
|
153
187
|
try {
|
|
154
188
|
raw = readFileSync(transcriptPath, 'utf-8');
|
|
155
189
|
} catch {
|
|
156
|
-
return { byModel, skills };
|
|
190
|
+
return { byModel, skills, commands: commandUses };
|
|
157
191
|
}
|
|
158
192
|
for (const line of raw.split('\n')) {
|
|
159
193
|
if (!line.trim()) continue;
|
|
@@ -166,7 +200,7 @@ function tallyTranscript(transcriptPath, known) {
|
|
|
166
200
|
if (obj.type !== 'assistant') continue;
|
|
167
201
|
const msg = obj && obj.message;
|
|
168
202
|
try {
|
|
169
|
-
countSkillUses(msg, known, skills);
|
|
203
|
+
countSkillUses(msg, known, skills, commands, commandUses);
|
|
170
204
|
} catch {
|
|
171
205
|
/* usage evidence is best-effort — never let it break cost tracking */
|
|
172
206
|
}
|
|
@@ -179,7 +213,7 @@ function tallyTranscript(transcriptPath, known) {
|
|
|
179
213
|
m.cache_write += u.cache_creation_input_tokens || 0;
|
|
180
214
|
m.cache_read += u.cache_read_input_tokens || 0;
|
|
181
215
|
}
|
|
182
|
-
return { byModel, skills };
|
|
216
|
+
return { byModel, skills, commands: commandUses };
|
|
183
217
|
}
|
|
184
218
|
|
|
185
219
|
async function main() {
|
|
@@ -198,7 +232,13 @@ async function main() {
|
|
|
198
232
|
} catch {
|
|
199
233
|
known = new Set();
|
|
200
234
|
}
|
|
201
|
-
|
|
235
|
+
let installedCommands;
|
|
236
|
+
try {
|
|
237
|
+
installedCommands = knownCommands();
|
|
238
|
+
} catch {
|
|
239
|
+
installedCommands = new Set();
|
|
240
|
+
}
|
|
241
|
+
const { byModel, skills, commands } = tallyTranscript(transcriptPath, known, installedCommands);
|
|
202
242
|
const modelNames = Object.keys(byModel);
|
|
203
243
|
if (modelNames.length === 0) return process.exit(0); // nothing to record
|
|
204
244
|
|
|
@@ -223,9 +263,10 @@ async function main() {
|
|
|
223
263
|
total_usd: Number(totalUsd.toFixed(4)),
|
|
224
264
|
total_tokens: totalTokens,
|
|
225
265
|
models,
|
|
226
|
-
// Always emitted, even empty: `{}` records an observed session with no
|
|
227
|
-
// reference, while an absent key marks a row written before
|
|
266
|
+
// Always emitted, even empty: `{}` records an observed session with no
|
|
267
|
+
// reference, while an absent key marks a row written before that existed.
|
|
228
268
|
skills,
|
|
269
|
+
commands,
|
|
229
270
|
};
|
|
230
271
|
|
|
231
272
|
try {
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
* dev-workflow statusline — Claude Code statusLine command.
|
|
4
4
|
*
|
|
5
5
|
* Prints one line: git branch · dev-workflow active spec (if any) · minimalism
|
|
6
|
-
* mode. Reads the session payload (JSON on stdin)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* mode. Reads the session payload (JSON on stdin): the branch comes from the
|
|
7
|
+
* current dir, the .dw/ state from the project dir, since the shell may sit in a
|
|
8
|
+
* subdirectory. Falls back to $CLAUDE_PROJECT_DIR, then process.cwd(). Fails
|
|
9
|
+
* SAFE — any error prints a minimal line so the statusline never breaks the
|
|
10
|
+
* session.
|
|
9
11
|
*
|
|
10
12
|
* Minimalism mode is read from .dw/minimalism.json (see the dw-minimalism skill).
|
|
11
13
|
*/
|
|
@@ -93,23 +95,24 @@ function activeSpec(cwd) {
|
|
|
93
95
|
|
|
94
96
|
async function main() {
|
|
95
97
|
let cwd = process.cwd();
|
|
98
|
+
let root = process.env.CLAUDE_PROJECT_DIR || '';
|
|
96
99
|
try {
|
|
97
100
|
const payload = JSON.parse(await readStdin());
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
cwd;
|
|
101
|
+
const workspace = (payload && payload.workspace) || {};
|
|
102
|
+
cwd = workspace.current_dir || (payload && payload.cwd) || cwd;
|
|
103
|
+
root = workspace.project_dir || root;
|
|
102
104
|
} catch {
|
|
103
|
-
/* use process.cwd() */
|
|
105
|
+
/* use $CLAUDE_PROJECT_DIR / process.cwd() */
|
|
104
106
|
}
|
|
107
|
+
root = root || cwd;
|
|
105
108
|
|
|
106
109
|
const parts = [];
|
|
107
110
|
const branch = gitBranch(cwd);
|
|
108
111
|
if (branch) parts.push(`⎇ ${branch}`);
|
|
109
|
-
const spec = activeSpec(
|
|
112
|
+
const spec = activeSpec(root);
|
|
110
113
|
if (spec) parts.push(spec);
|
|
111
|
-
parts.push(`min:${minimalismMode(
|
|
112
|
-
const cost = costToday(
|
|
114
|
+
parts.push(`min:${minimalismMode(root)}`);
|
|
115
|
+
const cost = costToday(root);
|
|
113
116
|
if (cost) parts.push(cost);
|
|
114
117
|
|
|
115
118
|
process.stdout.write(`dw · ${parts.join(' · ')}`);
|
|
@@ -37,7 +37,7 @@ This file describes how `/dw-new-project` and `/dw-dockerize` merge the standalo
|
|
|
37
37
|
|
|
38
38
|
```yaml
|
|
39
39
|
# Generated by /dw-new-project on YYYY-MM-DD
|
|
40
|
-
# Edit freely. Recipes live in .
|
|
40
|
+
# Edit freely. Recipes live in .dw/skills/docker-compose-recipes/
|
|
41
41
|
|
|
42
42
|
services:
|
|
43
43
|
# --- storage tier ---
|
|
@@ -52,7 +52,9 @@ Structure:
|
|
|
52
52
|
└── runs/<YYYY-MM-DD>.jsonl # results of each eval run
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
|
|
55
|
+
Eval-first: run a baseline on the current code before the change; new-behavior cases must fail there. Compare runs case by case: a case that passed and now fails blocks, whatever the average says. Never tune against the `holdout` split.
|
|
56
|
+
|
|
57
|
+
See `references/reference-dataset.md` for case design, baseline, holdout, per-case drift and when to expand the set.
|
|
56
58
|
|
|
57
59
|
## Consistency (run-to-run stability)
|
|
58
60
|
|
package/scaffold/skills/dw-llm-eval/references/integration-with-dev-workflow-commands-detail.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
## Integration with dev-workflow commands
|
|
2
2
|
|
|
3
|
-
- `/dw-plan tasks`: when the PRD has an AI feature requirement, an eval-plan subtask is mandatory. The task references this skill's oracle ladder.
|
|
3
|
+
- `/dw-plan tasks`: when the PRD has an AI feature requirement, an eval-plan subtask is mandatory, ordered before the implementation subtasks: behavior spec, cases, baseline run (`references/reference-dataset.md` "Baseline before the change"). The task references this skill's oracle ladder.
|
|
4
4
|
- `/dw-review --code-only`: AI feature PRs require a reference dataset + ≥2 oracle rungs (lower rungs FIRST). The constitution gate also applies — if the project has principles about AI feature reliability, they're enforced here.
|
|
5
|
-
- `/dw-qa --ai`: new mode (when this skill is bundled) — runs the reference dataset against the current implementation, logs to `QA/logs/ai/<feature>-<date>.jsonl`, computes precision@k / faithfulness / outcome accuracy per the feature type.
|
|
5
|
+
- `/dw-qa --ai`: new mode (when this skill is bundled) — runs the reference dataset against the current implementation, logs to `QA/logs/ai/<feature>-<date>.jsonl`, computes precision@k / faithfulness / outcome accuracy per the feature type, and lists per-case flips against the prior run.
|
|
6
6
|
- `/dw-bugfix` when the bug is an AI failure mode (hallucination, tool misuse, classification error): adds the failing case to the reference dataset BEFORE fixing — the case is now a regression test forever.
|
|
@@ -37,6 +37,7 @@ Each case is one line in `cases.jsonl`:
|
|
|
37
37
|
```json
|
|
38
38
|
{
|
|
39
39
|
"id": "case-001",
|
|
40
|
+
"split": "dev",
|
|
40
41
|
"input": {
|
|
41
42
|
"user_message": "I want to cancel my subscription",
|
|
42
43
|
"user_context": { "tier": "premium", "tenure_months": 18 }
|
|
@@ -60,6 +61,7 @@ Each case is one line in `cases.jsonl`:
|
|
|
60
61
|
Fields:
|
|
61
62
|
|
|
62
63
|
- **`id`** — stable identifier (you'll reference it in regression reports).
|
|
64
|
+
- **`split`** — `dev` (you tune against it) or `holdout` (you never do). See "Held-out split".
|
|
63
65
|
- **`input`** — what the system receives. Match the production input shape exactly.
|
|
64
66
|
- **`expected`** — for rungs 1-3, the deterministic expected output or state change. For rung 4, the rubric-target (what a 5/5 answer would do).
|
|
65
67
|
- **`rubric_criteria`** — which rubric dimensions this case exercises.
|
|
@@ -129,18 +131,70 @@ Process:
|
|
|
129
131
|
4. **Categorize** — difficulty, tags, rubric criteria.
|
|
130
132
|
5. **Promote** to `cases.jsonl` after review.
|
|
131
133
|
|
|
134
|
+
## Baseline before the change
|
|
135
|
+
|
|
136
|
+
> Eval-first, from LangChain's [eval-driven development](https://www.langchain.com/blog/what-is-eval-driven-development)
|
|
137
|
+
> post (2026-11-09): the eval is written and run before the change it measures.
|
|
138
|
+
|
|
139
|
+
When a task adds or changes an AI behavior, the eval exists before the implementation:
|
|
140
|
+
|
|
141
|
+
1. **Spec the behavior, one at a time.** Four lines, definite enough to score pass/fail: when it should
|
|
142
|
+
happen, what the system does, what it must not do, and which existing behaviors must keep working.
|
|
143
|
+
"The answer is helpful" is not a spec.
|
|
144
|
+
2. **Add the cases** for it: the new behavior, its must-not boundary (the near miss that should NOT
|
|
145
|
+
trigger it), and the existing cases that cover the keep-working list.
|
|
146
|
+
3. **Run the baseline** on the current code, before any change, and save it as
|
|
147
|
+
`runs/<YYYY-MM-DD>-baseline.jsonl`.
|
|
148
|
+
4. **Read the baseline.** New-behavior cases must fail there. A new case that already passes does not test
|
|
149
|
+
the change: rewrite it or drop it. Existing cases that pass are the regression set for this change.
|
|
150
|
+
5. Implement, re-run, compare case by case (see "Detecting drift").
|
|
151
|
+
|
|
152
|
+
Each line of a run file is one case:
|
|
153
|
+
|
|
154
|
+
```json
|
|
155
|
+
{"id": "case-001", "split": "dev", "pass": true, "score": 4, "commit": "a1b2c3d", "dataset_version": "2.3"}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`pass` is required (the verdict against the case's oracle); `score` only when a rung-4 judge produced one.
|
|
159
|
+
|
|
160
|
+
## Held-out split
|
|
161
|
+
|
|
162
|
+
Iterating until every case passes overfits the system to the dataset: the prompt learns the cases, not the
|
|
163
|
+
behavior. Mark about 20% of cases `"split": "holdout"`, spread across difficulty and tags, and:
|
|
164
|
+
|
|
165
|
+
- iterate against `dev` only — never read a holdout failure to decide the next prompt tweak;
|
|
166
|
+
- run `holdout` before merge to main and before deploy;
|
|
167
|
+
- if `dev` improves and `holdout` stays flat or drops, report overfitting instead of the improvement.
|
|
168
|
+
|
|
169
|
+
A holdout case that was used to tune is no longer held out: move it to `dev` and add a fresh one.
|
|
170
|
+
|
|
132
171
|
## Sampling for regression runs
|
|
133
172
|
|
|
134
173
|
You don't need to re-run the entire dataset every time. Smart sampling:
|
|
135
174
|
|
|
136
|
-
- **PR-time:** random sample of 30% + all "high difficulty" cases + any case added in the last 30 days. Fast feedback.
|
|
137
|
-
- **Pre-merge to main:** full dataset.
|
|
175
|
+
- **PR-time:** `dev` split only — random sample of 30% + all "high difficulty" cases + any case added in the last 30 days. Fast feedback.
|
|
176
|
+
- **Pre-merge to main:** full dataset, `holdout` included.
|
|
138
177
|
- **Nightly:** full dataset + judge re-calibration check.
|
|
139
178
|
- **Pre-deploy:** full dataset + manual eyeball on 10 random outputs.
|
|
140
179
|
|
|
141
180
|
## Detecting drift
|
|
142
181
|
|
|
143
|
-
After each run, compare against the prior run on the SAME cases
|
|
182
|
+
After each run, compare against the prior run on the SAME cases — **case by case first, averages
|
|
183
|
+
second.** An average can rise while a case that used to pass breaks: the change fixed five things and
|
|
184
|
+
overwrote one behavior someone depends on.
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
Run 2026-05-08 vs 2026-05-01 (dataset 2.3, 24 cases):
|
|
188
|
+
flipped pass → fail: case-007 (dev), case-019 (holdout)
|
|
189
|
+
flipped fail → pass: case-021, case-022, case-023
|
|
190
|
+
unchanged: 19
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The gate: **any case that passed in the prior run and fails now blocks**, whatever the average says, until
|
|
194
|
+
it is fixed or accepted in writing (PR or techspec: case id, why the old behavior no longer holds). A
|
|
195
|
+
fail → pass flip on a case nobody was working on is also worth a look: it may be judge drift, not progress.
|
|
196
|
+
|
|
197
|
+
Then the metric deltas:
|
|
144
198
|
|
|
145
199
|
```
|
|
146
200
|
Run 2026-05-08 vs 2026-05-01:
|
|
@@ -33,12 +33,13 @@ For the four grounding questions, read `references/the-four-grounding-questions-
|
|
|
33
33
|
| Any UI work (the full version of the grounding) | `references/hard-gate.md` |
|
|
34
34
|
| Reviewing or proposing a design | `references/visual-slop.md` (14 patterns + specific anti-default values) |
|
|
35
35
|
| Designing state coverage | `references/state-matrix.md` |
|
|
36
|
+
| Stress-testing with worst-case data (long names, `1 members`, 1,000 rows, 320px, 200% zoom) | `references/content-stress.md` |
|
|
36
37
|
| Interactive widget (button, form, modal, anything clickable) | `references/accessibility-floor.md` |
|
|
37
38
|
| No design authority exists in the project | `references/curated-defaults.md` (palettes / fonts / scales) |
|
|
38
39
|
| Choosing or reviewing a palette / dark mode | `references/color-oklch.md` (OKLCH, tinted neutrals, contrast) |
|
|
39
40
|
| Fonts, sizing, hierarchy, line length | `references/type-scale.md` (modular scale, pairing, measure) |
|
|
40
|
-
| Adding
|
|
41
|
-
| Cross-device /
|
|
41
|
+
| Adding, reviewing or removing animation | `references/motion.md` (frequency gate, easing, performance, removal, review triggers) |
|
|
42
|
+
| Cross-device / touch / mobile web | `references/responsive.md` (mobile-first, container queries, mobile fixes) |
|
|
42
43
|
| Any user-facing copy (labels, errors, empty states) | `references/ux-writing.md` |
|
|
43
44
|
|
|
44
45
|
## The 14 visual-slop patterns (full catalog in `references/visual-slop.md`)
|
|
@@ -74,7 +75,8 @@ Before any interactive widget ships:
|
|
|
74
75
|
- [ ] No keyboard traps.
|
|
75
76
|
- [ ] Touch targets ≥24×24 CSS pixels (≥44×44 recommended on mobile).
|
|
76
77
|
- [ ] Heading hierarchy is semantic (no skipped levels).
|
|
77
|
-
- [ ] `prefers-reduced-motion` honored.
|
|
78
|
+
- [ ] `prefers-reduced-motion` honored (gentler, not zero).
|
|
79
|
+
- [ ] Zoom never disabled (no `user-scalable=no` / `maximum-scale=1`); content survives 200% zoom.
|
|
78
80
|
|
|
79
81
|
Full verification recipes in `references/accessibility-floor.md`. `/dw-review --code-only` rejects the verdict if any interactive widget ships without these.
|
|
80
82
|
|
|
@@ -90,7 +92,8 @@ In all bend cases, document the bend in the PR (one line). "I skipped the state
|
|
|
90
92
|
|
|
91
93
|
- `/dw-redesign-ui` runs the grounding end-to-end. Steps 4 (propose) and 7 (validate) consult this skill explicitly.
|
|
92
94
|
- `/dw-plan techspec` UI sections must answer the 4 grounding questions and reference the state matrix.
|
|
93
|
-
- `/dw-review --code-only` checks UI diffs against the 14 visual-slop patterns
|
|
95
|
+
- `/dw-review --code-only` checks UI diffs against the 14 visual-slop patterns, the accessibility floor, `motion.md` §9 triggers and `content-stress.md` static signals.
|
|
96
|
+
- `/dw-qa` (UI mode) runs the content-stress pass.
|
|
94
97
|
- `/dw-functional-doc` records the surface-job and scene sentences in the overview for each screen.
|
|
95
98
|
|
|
96
99
|
## Why this approach
|
|
@@ -108,5 +111,5 @@ When invoked directly or by a harness, return or merge this block:
|
|
|
108
111
|
- **Evidence:** design source read, four grounding answers, states checked, screenshots, and accessibility checks.
|
|
109
112
|
- **Artifacts:** UI proposal, design notes, state matrix, screenshot path, or QA finding.
|
|
110
113
|
- **Decisions:** palette/type/component choices, state handling, and accepted deviations.
|
|
111
|
-
- **Risks:** training-data defaults, missing states, contrast/focus failures, unreadable hierarchy, or mobile overflow.
|
|
114
|
+
- **Risks:** training-data defaults, missing states, contrast/focus failures, unreadable hierarchy, worst-case data breaks, high-frequency motion, or mobile overflow.
|
|
112
115
|
- **Next Step:** exact design/code/QA action or missing input to gather.
|
|
@@ -152,24 +152,34 @@ Screen readers switch pronunciation based on this.
|
|
|
152
152
|
|
|
153
153
|
### 10. Reduced motion respect
|
|
154
154
|
|
|
155
|
-
Honor `prefers-reduced-motion
|
|
155
|
+
Honor `prefers-reduced-motion`. It means fewer and gentler animations, not zero: drop movement (slide,
|
|
156
|
+
scale, parallax), keep the opacity/color changes that show a state change.
|
|
156
157
|
|
|
157
158
|
```css
|
|
158
159
|
@media (prefers-reduced-motion: reduce) {
|
|
159
|
-
|
|
160
|
-
animation-duration: 0.01ms !important;
|
|
161
|
-
transition-duration: 0.01ms !important;
|
|
162
|
-
}
|
|
160
|
+
.drawer { transition: opacity 200ms ease; transform: none; }
|
|
163
161
|
}
|
|
164
162
|
```
|
|
165
163
|
|
|
166
164
|
Or in Tailwind:
|
|
167
165
|
```html
|
|
168
|
-
<div className="transition-
|
|
166
|
+
<div className="transition-transform motion-reduce:transition-none">
|
|
169
167
|
```
|
|
170
168
|
|
|
169
|
+
A global `animation-duration: 0.01ms !important` reset is an acceptable project-wide fallback, as long as
|
|
170
|
+
every state change stays visible. Details in `motion.md` §7.
|
|
171
|
+
|
|
171
172
|
Vestibular disorders trigger on parallax, large slide-ins, infinite scroll animations.
|
|
172
173
|
|
|
174
|
+
### 11. Zoom is never disabled
|
|
175
|
+
|
|
176
|
+
`user-scalable=no` and `maximum-scale=1` in the viewport meta block pinch-zoom for low-vision users (WCAG
|
|
177
|
+
1.4.4 Resize Text). They are usually added to stop iOS zooming into inputs; fix the cause instead —
|
|
178
|
+
inputs at `font-size: 16px` or more (`responsive.md`, "Mobile platform fixes").
|
|
179
|
+
|
|
180
|
+
Content must also survive **200% browser zoom** without clipping or overlapping: fixed heights on text
|
|
181
|
+
boxes are the usual culprit (`content-stress.md`, Step 4).
|
|
182
|
+
|
|
173
183
|
## Verification recipes
|
|
174
184
|
|
|
175
185
|
### Automated (CI)
|
|
@@ -195,8 +205,9 @@ Run on every PR. Block merge on violations.
|
|
|
195
205
|
1. Open the changed page.
|
|
196
206
|
2. Tab through with keyboard only. Verify focus visible at every stop.
|
|
197
207
|
3. Run axe DevTools, fix violations.
|
|
198
|
-
4. Toggle prefers-reduced-motion in DevTools rendering panel. Verify
|
|
199
|
-
5.
|
|
208
|
+
4. Toggle prefers-reduced-motion in DevTools rendering panel. Verify movement stops and state changes stay visible.
|
|
209
|
+
5. Zoom the page to 200%. Verify nothing clips, overlaps or becomes unreachable.
|
|
210
|
+
6. Test with screen reader (VoiceOver on Mac, NVDA on Windows) on at least one critical flow.
|
|
200
211
|
|
|
201
212
|
### Spot checks
|
|
202
213
|
|
|
@@ -221,5 +232,6 @@ LLM-produced UI commonly fails on:
|
|
|
221
232
|
- `outline: none` with no replacement focus state.
|
|
222
233
|
- Modals that trap focus only on success path, not on error.
|
|
223
234
|
- Auto-playing carousels with no pause control.
|
|
235
|
+
- `user-scalable=no` / `maximum-scale=1` in the viewport meta.
|
|
224
236
|
|
|
225
237
|
`/dw-review --code-only` runs axe-style checks on the diff for these.
|