ll-skills 2.0.2 → 3.1.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.
Files changed (125) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +42 -20
  3. package/agents/ll-executor.md +2 -1
  4. package/agents/ll-verifier.md +1 -0
  5. package/assets/preamble.md +27 -39
  6. package/bin/install.js +4 -1
  7. package/hooks/ll-precompact.js +29 -1
  8. package/hooks/ll-skills-check-update.js +6 -6
  9. package/hooks/ll-state.js +30 -2
  10. package/package.json +3 -2
  11. package/scripts/evals/README.md +81 -0
  12. package/scripts/evals/cases/auto-dry-run/assert.sh +35 -0
  13. package/scripts/evals/cases/auto-dry-run/case.json +8 -0
  14. package/scripts/evals/cases/auto-dry-run/prompt.txt +1 -0
  15. package/scripts/evals/cases/auto-empty-repo/assert.sh +25 -0
  16. package/scripts/evals/cases/auto-empty-repo/case.json +8 -0
  17. package/scripts/evals/cases/auto-empty-repo/fixture/.gitkeep +0 -0
  18. package/scripts/evals/cases/auto-empty-repo/prompt.txt +1 -0
  19. package/scripts/evals/cases/decide-final-round/assert.sh +45 -0
  20. package/scripts/evals/cases/decide-final-round/case.json +8 -0
  21. package/scripts/evals/cases/decide-final-round/fixture/README.md +3 -0
  22. package/scripts/evals/cases/decide-final-round/prompt.txt +1 -0
  23. package/scripts/evals/cases/executor-block/assert.sh +33 -0
  24. package/scripts/evals/cases/executor-block/case.json +8 -0
  25. package/scripts/evals/cases/executor-block/prompt.txt +14 -0
  26. package/scripts/evals/cases/goal-autonomous/assert.sh +35 -0
  27. package/scripts/evals/cases/goal-autonomous/case.json +8 -0
  28. package/scripts/evals/cases/goal-autonomous/fixture/PLAN.md +42 -0
  29. package/scripts/evals/cases/goal-autonomous/fixture/PROGRESS.md +20 -0
  30. package/scripts/evals/cases/goal-autonomous/fixture/ROADMAP.md +29 -0
  31. package/scripts/evals/cases/goal-autonomous/fixture/package.json +8 -0
  32. package/scripts/evals/cases/goal-autonomous/fixture/src/money.js +6 -0
  33. package/scripts/evals/cases/goal-autonomous/fixture/test/reconcile.test.js +8 -0
  34. package/scripts/evals/cases/goal-autonomous/prompt.txt +1 -0
  35. package/scripts/evals/cases/implement-review-gate/assert.sh +37 -0
  36. package/scripts/evals/cases/implement-review-gate/case.json +8 -0
  37. package/scripts/evals/cases/implement-review-gate/prompt.txt +1 -0
  38. package/scripts/evals/cases/implement-stops-at-next/assert.sh +121 -0
  39. package/scripts/evals/cases/implement-stops-at-next/case.json +9 -0
  40. package/scripts/evals/cases/implement-stops-at-next/prompt.txt +1 -0
  41. package/scripts/evals/cases/preamble-no-ritual/assert.sh +17 -0
  42. package/scripts/evals/cases/preamble-no-ritual/case.json +8 -0
  43. package/scripts/evals/cases/preamble-no-ritual/fixture/README.md +3 -0
  44. package/scripts/evals/cases/preamble-no-ritual/fixture/src/a.ts +3 -0
  45. package/scripts/evals/cases/preamble-no-ritual/prompt.txt +1 -0
  46. package/scripts/evals/cases/router-no-skill/assert.sh +33 -0
  47. package/scripts/evals/cases/router-no-skill/case.json +8 -0
  48. package/scripts/evals/cases/router-no-skill/fixture/PLAN.md +21 -0
  49. package/scripts/evals/cases/router-no-skill/fixture/README.md +7 -0
  50. package/scripts/evals/cases/router-no-skill/fixture/decisions/README.md +3 -0
  51. package/scripts/evals/cases/router-no-skill/prompt.txt +1 -0
  52. package/scripts/evals/cases/router-small/assert.sh +21 -0
  53. package/scripts/evals/cases/router-small/case.json +8 -0
  54. package/scripts/evals/cases/router-small/fixture/README.md +17 -0
  55. package/scripts/evals/cases/router-small/prompt.txt +1 -0
  56. package/scripts/evals/cases/scout-no-plan/assert.sh +41 -0
  57. package/scripts/evals/cases/scout-no-plan/case.json +8 -0
  58. package/scripts/evals/cases/scout-no-plan/prompt.txt +8 -0
  59. package/scripts/evals/cases/verifier-weakened-test/assert.sh +19 -0
  60. package/scripts/evals/cases/verifier-weakened-test/case.json +8 -0
  61. package/scripts/evals/cases/verifier-weakened-test/prompt.txt +13 -0
  62. package/scripts/evals/cases/verifier-weakened-test/setup.sh +19 -0
  63. package/scripts/evals/fixtures/manual-contract/out.json +29 -0
  64. package/scripts/evals/fixtures/manual-contract/out.txt +5 -0
  65. package/scripts/evals/fixtures/manual-contract/with-skill.json +46 -0
  66. package/scripts/evals/fixtures/router-no-skill/fail.txt +5 -0
  67. package/scripts/evals/fixtures/router-no-skill/out.json +27 -0
  68. package/scripts/evals/fixtures/router-no-skill/pass.txt +4 -0
  69. package/scripts/evals/lib/assert.sh +107 -0
  70. package/scripts/evals/lib/extract.js +73 -0
  71. package/scripts/evals/run.sh +369 -0
  72. package/scripts/fixtures/auto-closed/PLAN.md +5 -0
  73. package/scripts/fixtures/auto-closed/PROGRESS.md +20 -0
  74. package/scripts/fixtures/auto-closed/ROADMAP.md +6 -0
  75. package/scripts/fixtures/auto-closed/docs/DELIVERY.md +3 -0
  76. package/scripts/fixtures/auto-decisions/decisions/DEC-0001-taken-alone.md +13 -0
  77. package/scripts/fixtures/auto-decisions/decisions/DEC-0002-owner.md +13 -0
  78. package/scripts/fixtures/auto-noroadmap/PLAN.md +20 -0
  79. package/scripts/fixtures/auto-noroadmap/PROGRESS.md +11 -0
  80. package/scripts/fixtures/auto-verify-next/PLAN.md +5 -0
  81. package/scripts/fixtures/auto-verify-next/PROGRESS.md +18 -0
  82. package/scripts/fixtures/auto-verify-next/ROADMAP.md +5 -0
  83. package/scripts/fixtures/auto-verify-next/phases/01/PLAN.md +6 -0
  84. package/scripts/fixtures/evals-auto/auto-dry-run/pass.txt +18 -0
  85. package/scripts/fixtures/evals-auto/auto-empty-repo/pass.txt +2 -0
  86. package/scripts/fixtures/evals-auto/goal-autonomous/pass.txt +29 -0
  87. package/scripts/fixtures/lint-bad/folded-description/SKILL.md +13 -0
  88. package/scripts/fixtures/lint-bad/jargon-in-questions/SKILL.md +36 -0
  89. package/scripts/fixtures/lint-bad/model-invocation-false/SKILL.md +10 -0
  90. package/scripts/fixtures/next-bad/skills/ll-bad/SKILL.md +30 -0
  91. package/scripts/fixtures/next-good/skills/ll-good/SKILL.md +26 -0
  92. package/scripts/fixtures/project/BACKLOG.md +6 -5
  93. package/scripts/fixtures/project/PROGRESS.md +4 -0
  94. package/scripts/lint-contract.cjs +495 -0
  95. package/scripts/lint-prompts.sh +443 -0
  96. package/scripts/ll-tools.js +497 -450
  97. package/scripts/smoke-test.sh +481 -4
  98. package/skills/ll-auto/SKILL.md +74 -0
  99. package/skills/ll-auto/references/run.md +75 -0
  100. package/skills/ll-auto/references/stages.md +66 -0
  101. package/skills/ll-auto/scripts/ll-auto.js +345 -0
  102. package/skills/ll-brainstorm/SKILL.md +14 -12
  103. package/skills/ll-brainstorm/references/decision-policy.md +22 -12
  104. package/skills/ll-close/SKILL.md +8 -7
  105. package/skills/ll-close/references/delivery.md +3 -1
  106. package/skills/ll-decide/SKILL.md +21 -19
  107. package/skills/ll-decide/references/decision-policy.md +22 -12
  108. package/skills/ll-decide/references/decision-room.md +5 -3
  109. package/skills/ll-decide/references/interview.md +29 -13
  110. package/skills/ll-decide/references/plan-skeleton.md +14 -14
  111. package/skills/ll-decide/references/premise-gate.md +34 -20
  112. package/skills/ll-goal/SKILL.md +22 -4
  113. package/skills/ll-goal/references/goal-template.md +57 -0
  114. package/skills/ll-implement/SKILL.md +25 -22
  115. package/skills/ll-implement/references/briefs.md +2 -1
  116. package/skills/ll-implement/references/decision-policy.md +22 -12
  117. package/skills/ll-implement/references/phase-conversation.md +20 -12
  118. package/skills/ll-implement/references/phase-plan.md +23 -0
  119. package/skills/ll-oncall/SKILL.md +3 -2
  120. package/skills/ll-refine/SKILL.md +3 -2
  121. package/skills/ll-research/SKILL.md +3 -2
  122. package/skills/ll-resume/SKILL.md +9 -4
  123. package/skills/ll-update/SKILL.md +6 -1
  124. package/skills/ll-verify/SKILL.md +2 -1
  125. package/skills/ll-verify/references/verifier-briefs.md +3 -0
@@ -1,10 +1,29 @@
1
1
  #!/usr/bin/env bash
2
2
  # Smoke test do ll-skills: gate estático, hooks, helper, instalador, preâmbulo,
3
3
  # poda de skills antigas e uninstall. Tudo num CLAUDE_CONFIG_DIR isolado.
4
+ # Uso: smoke-test.sh [--only <seção>] (sem argumento roda tudo)
5
+ # As seções são os blocos numerados (1, 2, 3, 4, 4b, 4c, 4d, 5..10), lint-orquestrador,
6
+ # goal-autonomo, evals-auto, lint-scratch e no-talk; os blocos numerados montam estado uns para
7
+ # os outros, então --only puxa o pré-requisito de quem não roda sozinho (ver prereq abaixo).
4
8
  set -euo pipefail
5
9
  cd "$(dirname "$0")/.."
6
10
  ROOT="$PWD"
7
11
 
12
+ ONLY=""
13
+ while [ $# -gt 0 ]; do
14
+ case "$1" in
15
+ --only) ONLY="${2:-}"; shift 2 ;;
16
+ *) echo "uso: smoke-test.sh [--only <seção>]" >&2; exit 2 ;;
17
+ esac
18
+ done
19
+ # Pré-requisitos: a seção 4 usa o $EMPTY que a 2 monta e o $FIX que a 3 monta, a 6 lê o
20
+ # CLAUDE_CONFIG_DIR e o log que a 5 monta, e a 8 desinstala o que a 5 instalou e o preâmbulo que a 6
21
+ # escreve. --only puxa essas seções antes da pedida.
22
+ prereq() { case "$1" in 4) echo "2 3" ;; 6) echo "5" ;; 8) echo "5 6" ;; *) echo "" ;; esac; }
23
+ RUN=""
24
+ [ -z "$ONLY" ] || RUN=" $(prereq "$ONLY") $ONLY "
25
+ section() { [ -z "$ONLY" ] || case "$RUN" in *" $1 "*) return 0 ;; *) return 1 ;; esac; }
26
+
8
27
  TMP="$(mktemp -d)"
9
28
  trap 'rm -rf "$TMP"' EXIT
10
29
 
@@ -12,15 +31,17 @@ N=0
12
31
  check() { N=$((N + 1)); if ! eval "$2"; then echo "FALHOU: $1"; exit 1; fi; }
13
32
 
14
33
  HELPER="node $ROOT/scripts/ll-tools.js"
34
+ AUTO="node $ROOT/skills/ll-auto/scripts/ll-auto.js"
15
35
  region() { awk '/^\/\/ <ll-shared:state>$/,/^\/\/ <\/ll-shared:state>$/' "$1"; }
16
36
 
17
37
  # ---------------------------------------------------------------------------
18
38
  # 1. gate estático
19
39
  # ---------------------------------------------------------------------------
40
+ if section 1; then
20
41
  for f in bin/*.js hooks/*.js scripts/*.js; do node --check "$f"; done
21
42
 
22
- check "helper ≤700 linhas" '[ "$(wc -l < scripts/ll-tools.js)" -le 700 ]'
23
- check "helper ≤32768 bytes" '[ "$(wc -c < scripts/ll-tools.js)" -le 32768 ]'
43
+ check "helper ≤760 linhas" '[ "$(wc -l < scripts/ll-tools.js)" -le 760 ]'
44
+ check "helper ≤36000 bytes" '[ "$(wc -c < scripts/ll-tools.js)" -le 36000 ]'
24
45
 
25
46
  npm pack --dry-run --json > "$TMP/pack.json" 2>/dev/null
26
47
  check "tarball inclui o helper" 'grep -q "scripts/ll-tools.js" "$TMP/pack.json"'
@@ -30,10 +51,12 @@ region scripts/ll-tools.js > "$TMP/shared-tools.txt"
30
51
  region hooks/ll-state.js > "$TMP/shared-hook.txt"
31
52
  check "região ll-shared:state existe" '[ -s "$TMP/shared-tools.txt" ] && [ -s "$TMP/shared-hook.txt" ]'
32
53
  check "região ll-shared:state idêntica" 'cmp -s "$TMP/shared-tools.txt" "$TMP/shared-hook.txt"'
54
+ fi
33
55
 
34
56
  # ---------------------------------------------------------------------------
35
57
  # 2. hooks silenciosos fora de um projeto
36
58
  # ---------------------------------------------------------------------------
59
+ if section 2; then
37
60
  EMPTY="$TMP/empty"
38
61
  mkdir -p "$EMPTY"
39
62
  cp -R scripts/fixtures/empty/. "$EMPTY/"
@@ -56,10 +79,12 @@ for h in ll-state.js ll-precompact.js; do
56
79
  check "$h <300 ms no vazio" '[ "$(cat "$TMP/hook.ms")" -lt 300 ]'
57
80
  done
58
81
  check "hooks não criaram arquivos no vazio" '[ "$(find "$EMPTY" | wc -l)" -eq "$BEFORE_FILES" ]'
82
+ fi
59
83
 
60
84
  # ---------------------------------------------------------------------------
61
85
  # 3. hooks no fixture
62
86
  # ---------------------------------------------------------------------------
87
+ if section 3; then
63
88
  FIX="$TMP/fix"
64
89
  bash scripts/fixtures/git-history.sh "$FIX" > /dev/null
65
90
 
@@ -88,10 +113,12 @@ check "precompact avisa a sessão" 'grep -q "compaction marked in PROGRESS.md
88
113
  check "precompact inseriu 1 linha" '[ "$(grep -c "^- \[compaction" "$FIX/PROGRESS.md")" -eq "$((C0 + 1))" ]'
89
114
  check "linha nova fica acima do epílogo" \
90
115
  'grep -n "^## Epilogue" "$FIX/PROGRESS.md" | head -1 | cut -d: -f1 | xargs -I{} sh -c "sed -n \"\$(({} - 2))p\" \"$FIX/PROGRESS.md\"" | grep -q "^- \[compaction .* manual "'
116
+ fi
91
117
 
92
118
  # ---------------------------------------------------------------------------
93
- # 4. os 12 comandos do helper
119
+ # 4. os 13 comandos do helper
94
120
  # ---------------------------------------------------------------------------
121
+ if section 4; then
95
122
  cd "$FIX"
96
123
  $HELPER waves phases/07/PLAN.md --json > "$TMP/waves.json"
97
124
  check "waves: 4 ondas" 'grep -q "\"wave\":4" "$TMP/waves.json"'
@@ -103,14 +130,24 @@ $HELPER tdd-gate M1 --json > "$TMP/tdd1.json"
103
130
  check "tdd-gate M1 passa" 'grep -q "\"tdd\":\"pass\"" "$TMP/tdd1.json"'
104
131
  check "tdd-gate M1 ignora M10" '! grep -q "M10" "$TMP/tdd1.json"'
105
132
  check "tdd-gate M2 reprova" '$HELPER tdd-gate M2 --json | grep -q "\"tdd\":\"fail\""'
133
+ check "tdd-gate acha o id entre posicionais" '$HELPER tdd-gate phases/07/PLAN.md M1 --json | grep -q "\"tdd\":\"pass\""'
106
134
 
107
135
  check "spot-check M1 passa" '$HELPER spot-check M1 --files src/a.ts,test/a.test.ts --json | grep -q "\"verdict\":\"pass\""'
108
136
  check "state: fase 07, 1/3" '$HELPER state --json | grep -q "\"total\":3,\"passed\":1"'
109
137
  check "ledger: 1 FRESH 1 STALE 1 UNKNOWN" '$HELPER ledger --json | grep -q "\"FRESH\":1,\"STALE\":1,\"UNKNOWN\":1"'
110
138
  check "epilogue 07 aponta a fase 8" '$HELPER epilogue 07 --json | grep -q "\"next_command\":\"ll-implement 8\""'
111
139
  check "phase-stats: 5 dias com trabalho" '$HELPER phase-stats --json | grep -q "\"days_with_work\":5"'
140
+ check "phase-stats: fase 07 com questions/owner_prompts" \
141
+ '$HELPER phase-stats --json | grep -q "\"phase\":\"07\"" && $HELPER phase-stats --json | grep -q "\"questions\":3" && $HELPER phase-stats --json | grep -q "\"owner_prompts\":2"'
142
+ check "phase-stats: targets presente" '$HELPER phase-stats --json | grep -q "\"targets\""'
143
+ mkdir -p "$TMP/ep" && awk '/^## Epilogue — phase 07/{print "## Epilogue — phase 06 — 2026-09-08"; print "passed: M1 · left: none"; print ""} {print}' PROGRESS.md | sed 's/· amendments 1 · verification:/· amendments 1 (M3) · verification:/' > "$TMP/ep/PROGRESS.md"
144
+ $HELPER phase-stats --cwd "$TMP/ep" --json > "$TMP/ep.json"
145
+ check "phase-stats: epílogo sem linha de contagem não rouba a da fase seguinte" 'grep -q "\"phase\":\"06\",\"count_line\":false" "$TMP/ep.json"'
146
+ check "phase-stats: parêntese após amendments é aceito" 'grep -q "\"amendments\":1,\"verdict\":\"APPROVED\",\"verification\":\"phases/07/VERIFICATION.md\"" "$TMP/ep.json"'
147
+ check "epilogue 07 humano traz targets" '$HELPER epilogue 07 | grep -q "targets:"'
112
148
  check "plan-lint reprova o PLAN" '$HELPER plan-lint phases/07/PLAN.md --json | grep -q "\"verdict\":\"fail\""'
113
149
  check "plan-lint acusa acceptance-missing em M6" '$HELPER plan-lint phases/07/PLAN.md --json | grep -q "acceptance-missing"'
150
+ check "plan-lint aceita o número da fase" '$HELPER plan-lint 7 --json | grep -q "\"verdict\":\"fail\"" && $HELPER waves 07 --json | grep -q "\"waves\""'
114
151
 
115
152
  cp PROGRESS.md "$TMP/progress-before"
116
153
  $HELPER passes M2 true --json > "$TMP/passes.json"
@@ -118,6 +155,9 @@ check "passes M2 true grava" 'grep -q "\"passes\":true" "$TMP/passes.js
118
155
  check "passes muda exatamente 2 linhas" \
119
156
  '[ "$(diff "$TMP/progress-before" PROGRESS.md | grep -c "^[<>]")" -eq 2 ]'
120
157
  check "passes M9 cria a linha" '$HELPER passes M9 true --json | grep -q "\"created\":true"'
158
+ mkdir -p "$TMP/emptymap" && sed 's/^milestones:$/milestones: {}/' "$TMP/progress-before" | awk '/^milestones: \{\}$/{print; skip=1; next} skip && /^ (M[0-9]+|G-[0-9]+):/{next} {skip=0; print}' > "$TMP/emptymap/PROGRESS.md"
159
+ check "passes com milestones: {} cria a entrada" \
160
+ '$HELPER passes M1 true --cwd "$TMP/emptymap" --json | grep -q "\"created\":true" && grep -q "^milestones:$" "$TMP/emptymap/PROGRESS.md" && grep -q "^ M1: {" "$TMP/emptymap/PROGRESS.md"'
121
161
 
122
162
  check "dec-reserve 2 → 0042/0043" '$HELPER dec-reserve 2 --json | grep -q "\"DEC-0042\",\"DEC-0043\""'
123
163
  check "dec-reserve 2 → 0044/0045" '$HELPER dec-reserve 2 --json | grep -q "\"DEC-0044\",\"DEC-0045\""'
@@ -130,6 +170,20 @@ touch "$FIXDECF/decisions/DEC-X-001-a.md" "$FIXDECF/decisions/DEC-X-002-b.md"
130
170
  check "dec-reserve sem --prefix detecta DEC-X → 003" 'grep -q "\"ids\":\[\"DEC-X-003\"\]" "$TMP/dec-x.json"'
131
171
  check "dec-reserve sem --prefix retorna prefix DEC-X" 'grep -q "\"prefix\":\"DEC-X\"" "$TMP/dec-x.json"'
132
172
 
173
+ BSW="$TMP/bsw"
174
+ cp -R "$FIX" "$BSW"
175
+ $HELPER board-switch 08 --milestones M1,M2 --cwd "$BSW" --json > "$TMP/bsw.json"
176
+ check "board-switch 08 troca a fase do placar" \
177
+ 'grep -q "\"phase\":\"08\"" "$TMP/bsw.json" && grep -q "\"from\":\"07\"" "$TMP/bsw.json" && grep -q "^phase: 08$" "$BSW/PROGRESS.md"'
178
+ check "board-switch semeia 2 marcos e apaga os antigos" \
179
+ '[ "$(grep -c "^ \(M[0-9]*\|G-[0-9]*\): {" "$BSW/PROGRESS.md")" -eq 2 ] && grep -q "^ M2: { passes: false" "$BSW/PROGRESS.md" && ! grep -q "a1b2c3d" "$BSW/PROGRESS.md"'
180
+ check "board-switch na mesma fase só semeia o que falta" \
181
+ '$HELPER board-switch 08 --milestones M1,M2,M3 --cwd "$BSW" --json | grep -q "\"seeded\":\[\"M3\"\]" && [ "$(grep -c "^ M[0-9]*: {" "$BSW/PROGRESS.md")" -eq 3 ]'
182
+ check "passes --phase 07 recusa o placar da 08" \
183
+ '$HELPER passes M1 true --commit abc1234 --phase 07 --cwd "$BSW" > "$TMP/pw.out" 2>/dev/null; [ $? -eq 1 ] && grep -q "\"ok\":false" "$TMP/pw.out" && grep -q "board is phase 08, not 07" "$TMP/pw.out"'
184
+ check "passes --phase 08 grava no placar da 08" \
185
+ '$HELPER passes M1 true --commit abc1234 --phase 08 --cwd "$BSW" --json | grep -q "\"passes\":true"'
186
+
133
187
  check "heartbeat escreve no PROGRESS" '$HELPER heartbeat "smoke test ran" --json | grep -q "\"ok\":true"'
134
188
  check "heartbeat acima do epílogo" 'grep -q "smoke test ran" PROGRESS.md'
135
189
 
@@ -137,6 +191,15 @@ $HELPER backlog-reconcile --run --json > "$TMP/backlog.json"
137
191
  check "backlog-reconcile fecha só B-014" 'grep -q "\"closed\":\[\"B-014\"\]" "$TMP/backlog.json"'
138
192
  check "B-014 virou CLOSED no arquivo" 'grep "B-014" BACKLOG.md | grep -q "CLOSED"'
139
193
  check "B-015 continua OPEN" 'grep "B-015" BACKLOG.md | grep -q "OPEN"'
194
+ check "backlog-reconcile acusa B-017 unparsable" \
195
+ '$HELPER backlog-reconcile --json > "$TMP/bk017.json"; grep -q "\"id\":\"B-017\",\"state\":\"OPEN\"" "$TMP/bk017.json" && grep -q "\"unparsable-condition\"" "$TMP/bk017.json"'
196
+ check "epilogue 07 humano lista o unparsable" '$HELPER epilogue 07 | grep -q "unparsable: B-017"'
197
+ check "epilogue 07 json traz unparsable" '$HELPER epilogue 07 --json | grep -q "\"unparsable\":\[\"B-017\"\]"'
198
+ check "phase-stats --since do dia do último commit é inclusivo" \
199
+ '$HELPER phase-stats --since 2026-09-09 --json | grep -q "\"commits\":1"'
200
+ mkdir -p "$TMP/oldw" && sed "s/owner decisions open/band-1 open/" PROGRESS.md > "$TMP/oldw/PROGRESS.md"
201
+ check "phase-stats aceita a redação antiga da linha de contagem" \
202
+ '$HELPER phase-stats --cwd "$TMP/oldw" --json | grep -q "\"band1_open\":0"'
140
203
 
141
204
  # comandos de leitura fora de um projeto: ok:false e exit 0
142
205
  cd "$EMPTY"
@@ -145,10 +208,12 @@ for cmd in waves tdd-gate spot-check state ledger backlog-reconcile epilogue pha
145
208
  "$HELPER $cmd M1 > \"$TMP/r.out\" 2>/dev/null; [ \$? -eq 0 ] && grep -q '\"ok\":false' \"$TMP/r.out\""
146
209
  done
147
210
  cd "$ROOT"
211
+ fi
148
212
 
149
213
  # ---------------------------------------------------------------------------
150
214
  # 4b. nested state root (PROGRESS.md/phases/ under docs/state/, sources at git top)
151
215
  # ---------------------------------------------------------------------------
216
+ if section 4b; then
152
217
  NEST="$TMP/nest"
153
218
  bash scripts/fixtures/git-history.sh "$NEST" > /dev/null
154
219
  mkdir -p "$NEST/docs/state"
@@ -166,6 +231,19 @@ check "nested state: git_top aponta ao topo do git" \
166
231
  check "nested ledger: FRESH/STALE/UNKNOWN iguais ao topo" \
167
232
  '$HELPER ledger --cwd "$NEST/docs/state" --json | grep -q "\"FRESH\":1,\"STALE\":1,\"UNKNOWN\":1"'
168
233
 
234
+ printf '{"cwd":"%s","source":"compact"}' "$NEST" | node hooks/ll-state.js > "$TMP/nest-hook.json"
235
+ check "nested hook ll-state acha o PROGRESS em docs/state" 'grep -q "post-compaction" "$TMP/nest-hook.json" && grep -q "milestones [0-9]/3" "$TMP/nest-hook.json"'
236
+ printf '{"cwd":"%s","trigger":"auto"}' "$NEST" | node hooks/ll-precompact.js > "$TMP/nest-pre.out"
237
+ check "nested hook precompact carimba docs/state/PROGRESS.md" 'grep -q "compaction marked" "$TMP/nest-pre.out" && grep -q "^- \[compaction " "$NEST/docs/state/PROGRESS.md"'
238
+ mkdir -p "$NEST/docs/other" && printf '# other progress\n\nno state block here\n' > "$NEST/docs/other/PROGRESS.md" && touch "$NEST/docs/other/PROGRESS.md"
239
+ printf '{"cwd":"%s","source":"compact"}' "$NEST" | node hooks/ll-state.js > "$TMP/nest-hook2.json"
240
+ check "nested hook: entre vários PROGRESS.md escolhe o que tem bloco ll-state" 'grep -q "milestones [0-9]/3" "$TMP/nest-hook2.json"'
241
+ rm -rf "$NEST/docs/other"
242
+ mkdir -p "$NEST/docs/fixtures/project" && cp "$NEST/docs/state/PROGRESS.md" "$NEST/docs/fixtures/project/PROGRESS.md" && mv "$NEST/docs/state" "$TMP/state-aside"
243
+ check "hook ignora PROGRESS.md dentro de fixtures/" '[ -z "$(printf "{\"cwd\":\"%s\",\"source\":\"startup\"}" "$NEST" | node hooks/ll-state.js)" ]'
244
+ mv "$TMP/state-aside" "$NEST/docs/state"
245
+ rm -rf "$NEST/docs/fixtures"
246
+
169
247
  $HELPER backlog-reconcile --run --cwd "$NEST/docs/state" --json > "$TMP/nest-backlog.json"
170
248
  check "nested backlog-reconcile fecha B-014 (comando roda no topo do git)" \
171
249
  'grep -q "\"closed\":\[\"B-014\"\]" "$TMP/nest-backlog.json"'
@@ -181,10 +259,96 @@ check "state do topo reporta git_top correto" \
181
259
  $HELPER spot-check M1 --files src/a.ts,test/a.test.ts --cwd "$NEST" --json > "$TMP/nest-top-spot.json"
182
260
  check "spot-check do topo acha a raiz deslocada" \
183
261
  'grep -q "\"verdict\":\"pass\"" "$TMP/nest-top-spot.json"'
262
+ fi
263
+
264
+ # ---------------------------------------------------------------------------
265
+ # 4c. helper ll-auto: detect
266
+ # ---------------------------------------------------------------------------
267
+ if section 4c; then
268
+ # ids e status exatos de um detect --json: renomear um status ou perder um estágio reprova
269
+ auto_ids() { node -e 'const o=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));console.log(o.stages.map((s)=>s.id).join(","))' "$1"; }
270
+ auto_status_set() { node -e 'const o=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));console.log([...new Set(o.stages.map((s)=>s.status))].sort().join(","))' "$1"; }
271
+ check "ll-auto detect: fixture project → decide done, 07 half" \
272
+ '$AUTO detect --cwd "$ROOT/scripts/fixtures/project" --json > "$TMP/auto-project.json" \
273
+ && grep -q "\"id\":\"decide\",\"status\":\"done\"" "$TMP/auto-project.json" \
274
+ && grep -q "\"id\":\"phase-07\",\"status\":\"half\"" "$TMP/auto-project.json"'
275
+ check "ll-auto detect: fixture empty → os 4 estágios exatos, todos todo" \
276
+ '$AUTO detect --cwd "$ROOT/scripts/fixtures/empty" --json > "$TMP/auto-empty.json" \
277
+ && [ "$(auto_ids "$TMP/auto-empty.json")" = "research,brainstorm,decide,close" ] \
278
+ && [ "$(auto_status_set "$TMP/auto-empty.json")" = "todo" ]'
279
+ check "ll-auto detect: sem ROADMAP.md as fases saem da tabela do §8 do PLAN" \
280
+ '$AUTO detect --cwd "$ROOT/scripts/fixtures/auto-noroadmap" --json > "$TMP/auto-noroadmap.json" \
281
+ && [ "$(auto_ids "$TMP/auto-noroadmap.json")" = "research,brainstorm,decide,phase-01,phase-02,verify-01,verify-02,close" ] \
282
+ && grep -q "\"id\":\"phase-01\",\"status\":\"done\"" "$TMP/auto-noroadmap.json" \
283
+ && grep -q "\"id\":\"phase-02\",\"status\":\"todo\"" "$TMP/auto-noroadmap.json"'
284
+ check "ll-auto detect: DELIVERY.md + epílogo da última fase → close done" \
285
+ '$AUTO detect --cwd "$ROOT/scripts/fixtures/auto-closed" --json > "$TMP/auto-closed.json" \
286
+ && grep -q "\"id\":\"close\",\"status\":\"done\",\"evidence\":\"docs/DELIVERY.md + epilogue for phase 02\"" "$TMP/auto-closed.json" \
287
+ && grep -q "\"id\":\"phase-02\",\"status\":\"done\"" "$TMP/auto-closed.json" \
288
+ && [ "$(auto_status_set "$TMP/auto-closed.json")" = "done,todo" ]'
289
+ check "ll-auto detect: diretório inexistente → ok:false e exit 0" \
290
+ '$AUTO detect --cwd /nonexistent --json > "$TMP/auto-nodir.json" 2>/dev/null; [ $? -eq 0 ] \
291
+ && grep -q "\"ok\":false" "$TMP/auto-nodir.json" && grep -q "not a directory" "$TMP/auto-nodir.json"'
292
+ check "ll-auto detect --json é JSON válido" \
293
+ '$AUTO detect --cwd "$ROOT/scripts/fixtures/project" --json \
294
+ | node -e "JSON.parse(require(\"fs\").readFileSync(0,\"utf8\"))"'
295
+ fi
296
+
297
+ # ---------------------------------------------------------------------------
298
+ # 4d. helper ll-auto: roteiro, next-cmd, report, auto-md
299
+ # ---------------------------------------------------------------------------
300
+ if section 4d; then
301
+ check "ll-auto roteiro: --verify all → 07, verify-07, 08, verify-08, close" \
302
+ '$AUTO roteiro --cwd "$ROOT/scripts/fixtures/project" --flags "--verify all" --json > "$TMP/auto-verify-all.json" \
303
+ && grep -q "\"stage\":\"phase-07\".*\"stage\":\"verify-07\".*\"stage\":\"phase-08\".*\"stage\":\"verify-08\".*\"stage\":\"close\"" "$TMP/auto-verify-all.json" \
304
+ && ! grep -qE "\"stage\":\"(research|brainstorm|decide|phase-05|phase-06)\"" "$TMP/auto-verify-all.json"'
305
+ check "ll-auto roteiro: --only 8 corta o close e as outras fases" \
306
+ '$AUTO roteiro --cwd "$ROOT/scripts/fixtures/project" --flags "--only 8" --json > "$TMP/auto-only8.json" \
307
+ && grep -q "\"stage\":\"phase-08\",\"command\":\"ll-implement 08 --no-talk\"" "$TMP/auto-only8.json" \
308
+ && ! grep -qE "\"stage\":\"(phase-07|close)\"" "$TMP/auto-only8.json"'
309
+ check "ll-auto roteiro: repo vazio sem objetivo pede o objetivo" \
310
+ '$AUTO roteiro --cwd "$ROOT/scripts/fixtures/empty" --flags "" --json > "$TMP/auto-vazio.json" \
311
+ && grep -q "\"needs_objective\":true" "$TMP/auto-vazio.json" \
312
+ && grep -q "\"roteiro\":\[\]" "$TMP/auto-vazio.json"'
313
+ check "ll-auto roteiro: repo vazio com objetivo → research, brainstorm, decide, close" \
314
+ '$AUTO roteiro --cwd "$ROOT/scripts/fixtures/empty" --objective "um objetivo" \
315
+ --flags "--research --brainstorm" --json > "$TMP/auto-vazio2.json" \
316
+ && grep -q "\"stage\":\"research\".*\"stage\":\"brainstorm\".*\"stage\":\"decide\".*\"stage\":\"close\"" "$TMP/auto-vazio2.json" \
317
+ && grep -q "\"needs_objective\":false" "$TMP/auto-vazio2.json"'
318
+ check "ll-auto roteiro: --interactive --redo phase-05 --pause-at 8" \
319
+ '$AUTO roteiro --cwd "$ROOT/scripts/fixtures/project" \
320
+ --flags "--interactive --redo phase-05 --pause-at 8" --json > "$TMP/auto-redo.json" \
321
+ && grep -q "\"stage\":\"phase-05\",\"command\":\"ll-implement 05\",\"status\":\"done\"" "$TMP/auto-redo.json" \
322
+ && ! grep -q "ll-implement 05 --no-talk" "$TMP/auto-redo.json" \
323
+ && grep -q "\"stage\":\"phase-08\",\"command\":\"ll-implement 08\",\"status\":\"todo\",\"pause_after\":true" "$TMP/auto-redo.json"'
324
+ check "ll-auto roteiro: epílogo que pede ll-verify 01 insere verify-01 sem --verify all" \
325
+ '$AUTO roteiro --cwd "$ROOT/scripts/fixtures/auto-verify-next" --flags "" --json > "$TMP/auto-vnext.json" \
326
+ && grep -q "\"stage\":\"phase-01\",\"command\":\"ll-implement 01 --no-talk\".*\"stage\":\"verify-01\",\"command\":\"ll-verify 01\"" "$TMP/auto-vnext.json" \
327
+ && $AUTO roteiro --cwd "$ROOT/scripts/fixtures/project" --flags "" --json > "$TMP/auto-noverify.json" \
328
+ && ! grep -q "\"stage\":\"verify-" "$TMP/auto-noverify.json"'
329
+ check "ll-auto next-cmd: o epílogo da fixture aponta ll-implement 8" \
330
+ '[ "$($AUTO next-cmd "$ROOT/scripts/fixtures/project/PROGRESS.md")" = "ll-implement 8" ]'
331
+ check "ll-auto next-cmd: arquivo sem ▶ Next → saída vazia, exit 0" \
332
+ '[ -z "$($AUTO next-cmd "$ROOT/scripts/fixtures/project/ROADMAP.md")" ]'
333
+ check "ll-auto report: só o DEC com a marca de decidido sozinho" \
334
+ '$AUTO report --cwd "$ROOT/scripts/fixtures/auto-decisions" --json > "$TMP/auto-report.json" \
335
+ && grep -q "DEC-0001-taken-alone.md" "$TMP/auto-report.json" \
336
+ && ! grep -q "DEC-0002-owner.md" "$TMP/auto-report.json"'
337
+ check "ll-auto auto-md: as cinco seções, o objetivo e as flags" \
338
+ '$AUTO auto-md --cwd "$ROOT/scripts/fixtures/project" --objective "um objetivo" --flags "--only 8" \
339
+ > "$TMP/auto-md.txt" \
340
+ && grep -q "^## Objective" "$TMP/auto-md.txt" && grep -q "^## Flags" "$TMP/auto-md.txt" \
341
+ && grep -q "^## Roteiro" "$TMP/auto-md.txt" && grep -q "^## Decisions taken alone" "$TMP/auto-md.txt" \
342
+ && grep -q "^## Log" "$TMP/auto-md.txt" \
343
+ && grep -q "^um objetivo$" "$TMP/auto-md.txt" && grep -q -e "^--only 8$" "$TMP/auto-md.txt" \
344
+ && grep -q "^| # | stage | command | status | evidence |$" "$TMP/auto-md.txt" \
345
+ && grep -q "^| 1 | phase-08 | ll-implement 08 --no-talk | todo |" "$TMP/auto-md.txt"'
346
+ fi
184
347
 
185
348
  # ---------------------------------------------------------------------------
186
349
  # 5. instalador
187
350
  # ---------------------------------------------------------------------------
351
+ if section 5; then
188
352
  export CLAUDE_CONFIG_DIR="$TMP/cfg" XDG_CACHE_HOME="$TMP/cache"
189
353
  mkdir -p "$CLAUDE_CONFIG_DIR"
190
354
 
@@ -208,7 +372,7 @@ echo '{"version":"0.0.1","files":{"skills/ll-antiga/SKILL.md":"a","skills/alheia
208
372
 
209
373
  node bin/install.js < /dev/null > "$TMP/install1.log"
210
374
 
211
- check "11 skills ll-* instaladas" '[ "$(ls "$CLAUDE_CONFIG_DIR/skills" | grep -c "^ll-")" -eq 11 ]'
375
+ check "12 skills ll-* instaladas" '[ "$(ls "$CLAUDE_CONFIG_DIR/skills" | grep -c "^ll-")" -eq 12 ]'
212
376
  for s in ll-brainstorm ll-research ll-decide ll-goal ll-implement ll-verify ll-close ll-resume ll-refine ll-oncall ll-update; do
213
377
  check "skill $s instalada" '[ -f "$CLAUDE_CONFIG_DIR/skills/'"$s"'/SKILL.md" ]'
214
378
  done
@@ -240,6 +404,11 @@ for s in ll-implement ll-verify ll-close; do
240
404
  '[ "$(sha256sum "$CLAUDE_CONFIG_DIR/skills/'"$s"'/scripts/ll-tools.js" | cut -d" " -f1)" = "$SHA_SRC" ]'
241
405
  done
242
406
 
407
+ SHA_AUTO_SRC="$(sha256sum skills/ll-auto/scripts/ll-auto.js | cut -d" " -f1)"
408
+ check "ll-auto.js instalado executável" '[ -x "$CLAUDE_CONFIG_DIR/skills/ll-auto/scripts/ll-auto.js" ]'
409
+ check "ll-auto.js instalado com sha idêntico" \
410
+ '[ "$(sha256sum "$CLAUDE_CONFIG_DIR/skills/ll-auto/scripts/ll-auto.js" | cut -d" " -f1)" = "$SHA_AUTO_SRC" ]'
411
+
243
412
  cp "$CLAUDE_CONFIG_DIR/settings.json" "$TMP/s1.json"
244
413
  node bin/install.js < /dev/null > /dev/null
245
414
  check "segunda instalação sem diff no settings" 'cmp -s "$CLAUDE_CONFIG_DIR/settings.json" "$TMP/s1.json"'
@@ -260,10 +429,12 @@ mkdir -p "$XDG_CACHE_HOME/ll-skills"
260
429
  V="$(cat "$CLAUDE_CONFIG_DIR/ll-skills/VERSION")"
261
430
  printf '{"checked":%s,"installed":"%s","latest":"99.0.0","update_available":true,"method":"npm"}' "$(date +%s)" "$V" > "$XDG_CACHE_HOME/ll-skills/update-check.json"
262
431
  check "leitor avisa versão nova" 'node "$CLAUDE_CONFIG_DIR/hooks/ll-skills-check-update.js" | grep -q "/ll-update"'
432
+ fi
263
433
 
264
434
  # ---------------------------------------------------------------------------
265
435
  # 6. preâmbulo
266
436
  # ---------------------------------------------------------------------------
437
+ if section 6; then
267
438
  check "não-TTY sem --yes não escreve CLAUDE.md" '[ ! -e "$CLAUDE_CONFIG_DIR/CLAUDE.md" ]'
268
439
  check "não-TTY imprime o diff e a dica --yes" 'grep -q -- "--yes" "$TMP/install1.log"'
269
440
 
@@ -284,10 +455,12 @@ sed -i 's/^## Skills$/## Skills MEXIDO A MAO/' "$CMD"
284
455
  node bin/install.js --yes < /dev/null > /dev/null
285
456
  check "corpo alterado à mão é restaurado" 'cmp -s "$CMD" "$TMP/claudemd-1"'
286
457
  check "backup .ll-skills.bak existe" '[ -f "$CMD.ll-skills.bak" ]'
458
+ fi
287
459
 
288
460
  # ---------------------------------------------------------------------------
289
461
  # 7. poda das skills 1.x (com e sem manifesto)
290
462
  # ---------------------------------------------------------------------------
463
+ if section 7; then
291
464
  LEGACY_SKILLS="ll-atualizar ll-decidir-antes ll-desarmar ll-orquestrar ll-pesquisar ll-pesquisar-mercado ll-verificar-entrega ll-voltar-do-futuro"
292
465
  CFG_L="$TMP/cfg-legacy"
293
466
  seed_legacy() {
@@ -318,10 +491,12 @@ assert_pruned "com manifesto"
318
491
  seed_legacy # agora o manifesto atual (2.x) não lista nada disso: só KNOWN_LEGACY resolve
319
492
  CLAUDE_CONFIG_DIR="$CFG_L" node bin/install.js --no-preamble < /dev/null > /dev/null
320
493
  assert_pruned "sem manifesto"
494
+ fi
321
495
 
322
496
  # ---------------------------------------------------------------------------
323
497
  # 8. uninstall
324
498
  # ---------------------------------------------------------------------------
499
+ if section 8; then
325
500
  node bin/install.js --uninstall < /dev/null > /dev/null
326
501
  check "uninstall removeu skills" '[ -z "$(ls "$CLAUDE_CONFIG_DIR/skills" | grep "^ll-" || true)" ]'
327
502
  check "uninstall removeu as cópias do helper" '[ ! -e "$CLAUDE_CONFIG_DIR/skills/ll-implement" ]'
@@ -331,10 +506,312 @@ check "uninstall removeu o check-update" '! grep -q "ll-skills-check-update" "$C
331
506
  check "uninstall preservou alheios" 'grep -q "echo alheio" "$CLAUDE_CONFIG_DIR/settings.json" && [ -f "$CLAUDE_CONFIG_DIR/skills/alheia/SKILL.md" ]'
332
507
  check "uninstall removeu o preâmbulo" '! grep -q "ll-skills:preamble" "$CMD"'
333
508
  check "uninstall preservou as sentinelas" 'grep -q "SENTINEL-TOP" "$CMD" && grep -q "SENTINEL-BOTTOM" "$CMD"'
509
+ fi
510
+
511
+ # ---------------------------------------------------------------------------
512
+ # 9. lint dos prompts (uma checagem por regra de scripts/lint-prompts.sh)
513
+ # ---------------------------------------------------------------------------
514
+ if section 9; then
515
+ lint() { # lint <n>: roda uma regra e só imprime a saída quando ela falha
516
+ bash "$ROOT/scripts/lint-prompts.sh" --rule "$1" > "$TMP/lint-$1.txt" 2>&1 \
517
+ || { cat "$TMP/lint-$1.txt"; return 1; }
518
+ }
519
+
520
+ check "lint 1: frontmatter das skills" 'lint 1'
521
+ check "lint 2: frontmatter dos agentes" 'lint 2'
522
+ check "lint 3: tetos de linhas" 'lint 3'
523
+ printf '[{"unpackedSize":999999999}]' > "$TMP/pack-list-big.json"
524
+ check "lint 3: honra LL_PACK_JSON na forma lista (npm ≤ 11) e reprova no teto sintético" \
525
+ '! LL_PACK_JSON="$TMP/pack-list-big.json" bash "$ROOT/scripts/lint-prompts.sh" --rule 3 > "$TMP/lint-pack-list.out" 2>&1; \
526
+ grep -q "unpackedSize 999999999 bytes, ceiling 921600" "$TMP/lint-pack-list.out"'
527
+ printf '{"ll-skills":{"unpackedSize":999999999}}' > "$TMP/pack-object-big.json"
528
+ check "lint 3: honra LL_PACK_JSON na forma objeto (npm 12) e reprova no teto sintético" \
529
+ '! LL_PACK_JSON="$TMP/pack-object-big.json" bash "$ROOT/scripts/lint-prompts.sh" --rule 3 > "$TMP/lint-pack-object.out" 2>&1; \
530
+ grep -q "unpackedSize 999999999 bytes, ceiling 921600" "$TMP/lint-pack-object.out"'
531
+ check "lint 4: forma das SKILL.md" 'lint 4'
532
+ check "lint 5: strings proibidas" 'lint 5'
533
+ check "lint 6: cópias idênticas" 'lint 6'
534
+ check "lint 7: idioma" 'lint 7'
535
+ check "lint 8: lista privada" 'lint 8'
536
+ check "lint 9: perguntas sem jargão" 'lint 9 && grep -q "^ok 9 plain questions" "$TMP/lint-9.txt"'
537
+ fi
334
538
 
539
+ # 10. lint de contrato entre as peças (uma checagem por regra de scripts/lint-contract.cjs)
540
+ if section 10; then
541
+ contract() { node "$ROOT/scripts/lint-contract.cjs" --rule "$1" >/dev/null 2>&1; }
542
+ check "contrato 1: references citadas existem e são citadas" 'contract 1'
543
+ check "contrato 2: comandos do helper definidos e citados" 'contract 2'
544
+ check "contrato 3: agentes, modelos e campos do brief" 'contract 3'
545
+ check "contrato 4: vocabulário de veredito e estados" 'contract 4'
546
+ check "contrato 5: arquivos de estado nas tabelas de entregáveis" 'contract 5'
547
+ check "contrato 6: alvos do ▶ Next existem" 'contract 6'
548
+ check "contrato 7: instalador × pacote" 'contract 7'
549
+
550
+ # regra 6 contra as fixtures da gramática do ▶ Next (bad falha, good passa, raiz sem skills falha)
551
+ contract_root() { node "$ROOT/scripts/lint-contract.cjs" --rule 6 --root "$1" >/dev/null 2>&1; }
552
+ contract_fails() { node "$ROOT/scripts/lint-contract.cjs" --rule 6 --root "$1" 2>/dev/null | grep -c "^FAIL 6 " || true; }
553
+ check "contrato 6: next-bad falha" '! contract_root scripts/fixtures/next-bad'
554
+ # a contagem é fixa: afrouxar uma das formas rejeitadas reprova aqui, não só o exit
555
+ check "contrato 6: next-bad com 6 FAIL" '[ "$(contract_fails scripts/fixtures/next-bad)" -eq 6 ]'
556
+ check "contrato 6: duas skills diferentes num ▶ Next fora de parêntese reprovam" \
557
+ 'node "$ROOT/scripts/lint-contract.cjs" --rule 6 --root scripts/fixtures/next-bad > "$TMP/next-bad.out" 2>/dev/null; \
558
+ grep -q "lists alternatives outside a parenthetical —ll-bad or ll-nope" "$TMP/next-bad.out"'
559
+ check "contrato 6: next-good passa" 'contract_root scripts/fixtures/next-good'
560
+ check "contrato 6: raiz sem skills falha" '! contract_root scripts/fixtures/empty'
335
561
 
336
562
  # --- optional private word list (never shipped): LL_FORBIDDEN_FILE=<path> enables the check
337
563
  if [ -n "${LL_FORBIDDEN_FILE:-}" ] && [ -f "$LL_FORBIDDEN_FILE" ]; then
338
564
  check "no private terms in tracked files" '! git ls-files | grep -v "^\.gitignore$" | xargs grep -n -i -w -E -f "$LL_FORBIDDEN_FILE" 2>/dev/null | grep -q .'
339
565
  fi
566
+ fi
567
+
568
+ # ---------------------------------------------------------------------------
569
+ # lint-orquestrador. as exceções de ll-auto valem só para ll-auto
570
+ # ---------------------------------------------------------------------------
571
+ if section lint-orquestrador; then
572
+ cd "$ROOT"
573
+ SCR="$TMP/lint-scratch"
574
+ mkdir -p "$SCR"
575
+ git ls-files -z | xargs -0 cp --parents -t "$SCR"
576
+ git -C "$SCR" init -q
577
+ git -C "$SCR" add -A
578
+ mkdir -p "$SCR/skills/ll-fake"
579
+ cat > "$SCR/skills/ll-fake/SKILL.md" <<'FAKE'
580
+ ---
581
+ name: ll-fake
582
+ description: Pretends to be an orchestrator so the lint can prove the ll-auto exceptions do not leak to any other skill on the tree.
583
+ argument-hint: "[--flag]"
584
+ disable-model-invocation: true
585
+ allowed-tools: Bash(${CLAUDE_SKILL_DIR}/scripts/ll-auto.js *)
586
+ ---
587
+
588
+ # Fake
589
+
590
+ ## Deliverables
591
+
592
+ | File | Role | Mutability |
593
+ |---|---|---|
594
+ | `docs/FAKE.md` | nothing | never |
595
+
596
+ ## Flow
597
+
598
+ /ll-research x
599
+
600
+ ## Completion criterion
601
+
602
+ ▶ Next — /clear, then ll-resume
603
+ FAKE
604
+ git -C "$SCR" add -A
605
+ lint_scr() { bash "$SCR/scripts/lint-prompts.sh" --rule "$1" > "$TMP/scr-rule$1.out" 2>&1; }
606
+ lint_real() { bash "$ROOT/scripts/lint-prompts.sh" --rule "$1" >/dev/null 2>&1; }
607
+
608
+ check "orquestrador: skills/ll-auto/SKILL.md existe" '[ -f "$ROOT/skills/ll-auto/SKILL.md" ]'
609
+ check "orquestrador: regra 1 reprova o allowed-tools do ll-auto em outra skill" \
610
+ '! lint_scr 1 && grep -q "^FAIL 1 .*skills/ll-fake/SKILL.md: allowed-tools" "$TMP/scr-rule1.out"'
611
+ check "orquestrador: regra 5 reprova a linha /ll- em outra skill" \
612
+ '! lint_scr 5 && grep -q "^FAIL 5 .*skills/ll-fake/SKILL.md: line .* invokes a skill as a command" "$TMP/scr-rule5.out"'
613
+ check "orquestrador: regras 1 e 5 passam na árvore real (com ll-auto)" \
614
+ 'lint_real 1 && lint_real 5'
615
+ fi
616
+
617
+ # ---------------------------------------------------------------------------
618
+ # goal-autonomo. o modo --autonomous do ll-goal: hint, template e exemplo
619
+ # ---------------------------------------------------------------------------
620
+ if section goal-autonomo; then
621
+ cd "$ROOT"
622
+ GOAL_SKILL="skills/ll-goal/SKILL.md"
623
+ GOAL_TPL="skills/ll-goal/references/goal-template.md"
624
+ # primeiro bloco cercado sob "## Autonomous example", sem as cercas
625
+ goal_example() { awk '/^## Autonomous example$/{f=1;next} f&&/^```/{if(b){exit}b=1;next} f&&b' "$GOAL_TPL"; }
626
+
627
+ check "ll-goal argument-hint aceita --autonomous" \
628
+ 'sed -n "/^argument-hint:/p" "$GOAL_SKILL" | grep -q -- "--autonomous"'
629
+ check "goal-template cita ll-auto --auto-decision" \
630
+ '[ "$(grep -c "ll-auto --auto-decision" "$GOAL_TPL")" -ge 1 ]'
631
+ check "exemplo autônomo entre 500 e 4000 bytes" \
632
+ 'n=$(goal_example | wc -c); [ "$n" -ge 500 ] && [ "$n" -le 4000 ]'
633
+ check "goal-template ≤ 150 linhas" \
634
+ '[ "$(wc -l < "$GOAL_TPL")" -le 150 ]'
635
+ fi
636
+
637
+ # ---------------------------------------------------------------------------
638
+ # evals-auto. os asserts dos casos autônomos provados offline, sem nenhuma chamada paga
639
+ # ---------------------------------------------------------------------------
640
+ if section evals-auto; then
641
+ cd "$ROOT"
642
+ EV="$TMP/evals-auto"
643
+ mkdir -p "$EV"
644
+ # a resposta errada: não carrega nenhuma linha que os asserts exigem
645
+ printf 'I ran nothing and wrote nothing.\n' > "$EV/wrong.txt"
646
+
647
+ # a captura limpa: um evento assistant de texto (para o no_tool_use varrer algo) e o result.
648
+ CAP_CLEAN='[{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Rodei o dry run e nao escrevi nada."}]}},{"type":"result","subtype":"success","is_error":false,"result":""}]'
649
+ # a mesma captura com uma chamada de skill por tool_use: o assert tem de reprovar.
650
+ CAP_SKILL='[{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"t1","name":"Skill","input":{"command":"ll-implement"}}]}},{"type":"result","subtype":"success","is_error":false,"result":""}]'
651
+
652
+ # uma árvore de trabalho limpa por chamada (os asserts leem `git status --porcelain` dela)
653
+ auto_assert() { # auto_assert <caso> <arquivo de resposta> [captura]
654
+ local w="$EV/$1"
655
+ rm -rf "$w"; mkdir -p "$w"
656
+ git -C "$w" init -q -b main
657
+ printf '%s' "${3:-$CAP_CLEAN}" > "$w/out.json"
658
+ bash "$ROOT/scripts/evals/cases/$1/assert.sh" "$w" "$w/out.json" "$2" > "$EV/$1.log" 2>&1
659
+ }
660
+
661
+ check "a captura offline tem ao menos um evento assistant" \
662
+ 'node -e "const a=JSON.parse(process.argv[1]);process.exit(a.filter(e=>e.type===\"assistant\").length?0:1)" "$CAP_CLEAN"'
663
+
664
+ check "assert auto-dry-run aceita a resposta boa" \
665
+ 'auto_assert auto-dry-run "$ROOT/scripts/fixtures/evals-auto/auto-dry-run/pass.txt"'
666
+ check "assert auto-dry-run rejeita resposta errada" \
667
+ '! auto_assert auto-dry-run "$EV/wrong.txt"'
668
+ check "assert auto-empty-repo aceita a resposta boa" \
669
+ 'auto_assert auto-empty-repo "$ROOT/scripts/fixtures/evals-auto/auto-empty-repo/pass.txt"'
670
+ check "assert auto-empty-repo rejeita resposta errada" \
671
+ '! auto_assert auto-empty-repo "$EV/wrong.txt"'
672
+ # o no_tool_use não é vazio: uma captura com tool_use Skill reprova o mesmo assert
673
+ check "assert auto-dry-run rejeita captura com tool_use Skill" \
674
+ '! auto_assert auto-dry-run "$ROOT/scripts/fixtures/evals-auto/auto-dry-run/pass.txt" "$CAP_SKILL"'
675
+ check "assert auto-empty-repo rejeita captura com tool_use Skill" \
676
+ '! auto_assert auto-empty-repo "$ROOT/scripts/fixtures/evals-auto/auto-empty-repo/pass.txt" "$CAP_SKILL"'
677
+
678
+ # goal-autonomous: o assert espera docs/GOAL.md já no lugar (um `ll-goal --autonomous` real
679
+ # escreve e comita o arquivo antes de imprimir o texto colável), não só o out.json do harness.
680
+ goal_autonomous_assert() { # goal_autonomous_assert <arquivo de resposta> [captura]
681
+ local w="$EV/goal-autonomous"
682
+ rm -rf "$w"; mkdir -p "$w/docs"
683
+ git -C "$w" init -q -b main
684
+ printf 'mode: autonomous\nphase: all\n' > "$w/docs/GOAL.md"
685
+ git -C "$w" add -A && git -C "$w" -c user.email=t@t -c user.name=t commit -q -m "docs: GOAL.md"
686
+ printf '%s' "${2:-$CAP_CLEAN}" > "$w/out.json"
687
+ bash "$ROOT/scripts/evals/cases/goal-autonomous/assert.sh" "$w" "$w/out.json" "$1" > "$EV/goal-autonomous.log" 2>&1
688
+ }
689
+ check "assert goal-autonomous aceita a resposta boa" \
690
+ 'goal_autonomous_assert "$ROOT/scripts/fixtures/evals-auto/goal-autonomous/pass.txt"'
691
+ check "assert goal-autonomous rejeita resposta vazia" \
692
+ '! goal_autonomous_assert "$EV/wrong.txt"'
693
+ echo "evals-auto: goal-autonomous pair asserted"
694
+
695
+ # router-no-skill: par offline (captura limpa + resposta boa/ruim), nenhuma chamada paga.
696
+ # Skill só por comando explícito: um pedido comum é atendido na hora, mesmo com estado ll no repo.
697
+ rns_assert() { # rns_assert <arquivo de resposta> [captura inline]
698
+ local w="$EV/router-no-skill"
699
+ rm -rf "$w"; mkdir -p "$w"
700
+ git -C "$w" init -q -b main
701
+ local cap="$ROOT/scripts/evals/fixtures/router-no-skill/out.json"
702
+ if [ $# -ge 2 ]; then printf '%s' "$2" > "$w/out.json"; cap="$w/out.json"; fi
703
+ bash "$ROOT/scripts/evals/cases/router-no-skill/assert.sh" "$w" "$cap" "$1" \
704
+ > "$EV/router-no-skill.log" 2>&1
705
+ }
706
+ check "assert router-no-skill aceita a pesquisa entregue na hora" \
707
+ 'rns_assert "$ROOT/scripts/evals/fixtures/router-no-skill/pass.txt"'
708
+ check "assert router-no-skill rejeita a resposta que só manda colar o comando" \
709
+ '! rns_assert "$ROOT/scripts/evals/fixtures/router-no-skill/fail.txt"'
710
+ check "assert router-no-skill rejeita captura com tool_use Skill" \
711
+ '! rns_assert "$ROOT/scripts/evals/fixtures/router-no-skill/pass.txt" "$CAP_SKILL"'
712
+
713
+ # implement-stops-at-next: a linha de onda antes do epílogo e o helper chamado, nunca lido
714
+ CAP_ONDA='[{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"onda 1/2 — M1, M2 rodando (opus, sonnet)"}]}},{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"▶ Next — /clear, then /ll-implement 8"}]}},{"type":"result","subtype":"success","is_error":false,"result":""}]'
715
+ CAP_LE_HELPER='[{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"onda 1/2 — M1, M2 rodando (opus, sonnet)"}]}},{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","id":"t1","name":"Bash","input":{"command":"head -40 scripts/ll-tools.js"}}]}},{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"▶ Next — /clear, then /ll-implement 8"}]}},{"type":"result","subtype":"success","is_error":false,"result":""}]'
716
+ printf 'onda 1/2 — M1, M2 rodando (opus, sonnet)\nonda 1/2 — M1 ok (smoke test OK)\n## Epilogue — phase 07 — 2026-09-11\n▶ Next — `/clear`, then `/ll-implement 8`\n' > "$EV/onda.txt"
717
+ printf '## Epilogue — phase 07 — 2026-09-11\n▶ Next — `/clear`, then `/ll-implement 8`\n' > "$EV/sem-onda.txt"
718
+ isn_assert() { # isn_assert <arquivo de resposta> [captura]
719
+ local w="$EV/implement-stops-at-next"
720
+ rm -rf "$w"; mkdir -p "$w"
721
+ printf '%s' "${2:-$CAP_ONDA}" > "$w/out.json"
722
+ bash "$ROOT/scripts/evals/cases/implement-stops-at-next/assert.sh" "$w" "$w/out.json" "$1" \
723
+ > "$EV/implement-stops-at-next.log" 2>&1
724
+ }
725
+ check "assert implement-stops-at-next aceita onda antes do epílogo" \
726
+ 'isn_assert "$EV/onda.txt"'
727
+ CAP_SEM_ONDA='[{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"scout rodando (sonnet)"}]}},{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"▶ Next — /clear, then /ll-implement 8"}]}},{"type":"result","subtype":"success","is_error":false,"result":""}]'
728
+ check "assert implement-stops-at-next rejeita captura sem linha de onda" \
729
+ '! isn_assert "$EV/sem-onda.txt" "$CAP_SEM_ONDA"'
730
+ check "assert implement-stops-at-next rejeita captura que lê o helper com head" \
731
+ '! isn_assert "$EV/onda.txt" "$CAP_LE_HELPER"'
732
+
733
+ # decide-final-round: contagem em palavras simples e o caminho das opções antes da 1ª pergunta
734
+ printf 'docs/decide/OPTIONS.html — as opções lado a lado\nquestions asked 2 / assumptions 3 / owner decisions open 1\n**Pergunta 1/2 — limite de título (impacto MÉDIO · desfazer: barato)**\n' > "$EV/round-ok.txt"
735
+ printf 'questions asked 2 / assumptions 3 / owner decisions open 1\n**Pergunta 1/2 — limite de título (impacto MÉDIO · desfazer: barato)**\ndocs/decide/OPTIONS.html — as opções lado a lado\n' > "$EV/round-tarde.txt"
736
+ printf 'questions asked 2 / assumptions 3 / band-1 open 1\n**Pergunta 1/2 — limite de título**\n' > "$EV/round-jargao.txt"
737
+ dfr_assert() { # dfr_assert <arquivo de resposta>
738
+ local w="$EV/decide-final-round"
739
+ rm -rf "$w"; mkdir -p "$w"
740
+ git -C "$w" init -q -b main
741
+ printf '%s' "$CAP_CLEAN" > "$w/out.json"
742
+ bash "$ROOT/scripts/evals/cases/decide-final-round/assert.sh" "$w" "$w/out.json" "$1" \
743
+ > "$EV/decide-final-round.log" 2>&1
744
+ }
745
+ check "assert decide-final-round aceita a rodada com o caminho antes da pergunta" \
746
+ 'dfr_assert "$EV/round-ok.txt"'
747
+ check "assert decide-final-round rejeita o caminho depois da primeira pergunta" \
748
+ '! dfr_assert "$EV/round-tarde.txt"'
749
+ check "assert decide-final-round rejeita a contagem com o rótulo antigo" \
750
+ '! dfr_assert "$EV/round-jargao.txt"'
751
+ fi
752
+
753
+ # ---------------------------------------------------------------------------
754
+ # lint-scratch. a regra 1 do lint-prompts contra SKILL.md ruins, numa árvore de rascunho
755
+ # ---------------------------------------------------------------------------
756
+ if section lint-scratch; then
757
+ cd "$ROOT"
758
+ LSCR="$TMP/lint-bad-scratch"
759
+ mkdir -p "$LSCR"
760
+ git ls-files -z | xargs -0 cp --parents -t "$LSCR"
761
+ git -C "$LSCR" init -q
762
+ mkdir -p "$LSCR/skills/ll-fake"
763
+ # lint_bad <caso> <trecho esperado no FAIL>: planta a fixture como skills/ll-fake e roda a regra 1
764
+ lint_bad() {
765
+ cp "$ROOT/scripts/fixtures/lint-bad/$1/SKILL.md" "$LSCR/skills/ll-fake/SKILL.md"
766
+ git -C "$LSCR" add -A
767
+ bash "$LSCR/scripts/lint-prompts.sh" --rule 1 > "$TMP/lint-bad-$1.out" 2>&1 && return 1
768
+ grep -q "^FAIL 1 .*skills/ll-fake/SKILL.md: $2" "$TMP/lint-bad-$1.out"
769
+ }
770
+ check "lint 1: descrição dobrada (>) em mais de uma linha reprova" \
771
+ 'lint_bad folded-description "description is not one line"'
772
+ check "lint 1: disable-model-invocation: false reprova" \
773
+ 'lint_bad model-invocation-false "disable-model-invocation: true missing"'
774
+ # lint_bad9 <caso> <trecho esperado no FAIL>: a mesma fixture contra a regra 9
775
+ lint_bad9() {
776
+ cp "$ROOT/scripts/fixtures/lint-bad/$1/SKILL.md" "$LSCR/skills/ll-fake/SKILL.md"
777
+ git -C "$LSCR" add -A
778
+ bash "$LSCR/scripts/lint-prompts.sh" --rule 9 > "$TMP/lint-bad9-$1.out" 2>&1 && return 1
779
+ grep -q "^FAIL 9 .*skills/ll-fake/SKILL.md: line [0-9]*: .*$2" "$TMP/lint-bad9-$1.out"
780
+ }
781
+ check "lint 9: id de decisão no cabeçalho da pergunta reprova" \
782
+ 'lint_bad9 jargon-in-questions "\[DEC-"'
783
+ check "lint 9: a linha de contagem com o rótulo antigo reprova" \
784
+ 'lint_bad9 jargon-in-questions "band-1 open"'
785
+ check "lint 9: id de pergunta [PG- no cabeçalho reprova" \
786
+ 'lint_bad9 jargon-in-questions "\[PG-"'
787
+ check "lint 9: a palavra banda 1 na prosa reprova" \
788
+ 'lint_bad9 jargon-in-questions "banda 1"'
789
+ # sem a fixture a mesma árvore passa: o FAIL vem da SKILL.md ruim, não da cópia
790
+ rm -rf "$LSCR/skills/ll-fake"
791
+ git -C "$LSCR" add -A
792
+ check "lint 1: a árvore de rascunho sem a fixture passa" \
793
+ 'bash "$LSCR/scripts/lint-prompts.sh" --rule 1 > "$TMP/lint-bad-clean.out" 2>&1'
794
+ check "lint 9: a árvore de rascunho sem a fixture passa" \
795
+ 'bash "$LSCR/scripts/lint-prompts.sh" --rule 9 > "$TMP/lint-bad9-clean.out" 2>&1'
796
+ fi
797
+
798
+ # ---------------------------------------------------------------------------
799
+ # no-talk. o modo silencioso continua declarado no argument-hint de quem o implementa
800
+ # ---------------------------------------------------------------------------
801
+ if section no-talk; then
802
+ cd "$ROOT"
803
+ hint_has_no_talk() { # hint_has_no_talk <SKILL.md>
804
+ sed -n '/^argument-hint:/p' "$1" > "$TMP/hint.txt"
805
+ grep -q -- "--no-talk" "$TMP/hint.txt"
806
+ }
807
+ for s in ll-decide ll-close; do
808
+ check "argument-hint de $s declara --no-talk" 'hint_has_no_talk "skills/'"$s"'/SKILL.md"'
809
+ done
810
+ # a checagem não é vazia: sem a flag na linha, ela reprova
811
+ NT="$TMP/no-talk"
812
+ mkdir -p "$NT"
813
+ sed 's/ \[--no-talk\]//' skills/ll-decide/SKILL.md > "$NT/SKILL.md"
814
+ check "a checagem reprova quando --no-talk some do hint" '! hint_has_no_talk "$NT/SKILL.md"'
815
+ fi
816
+
340
817
  echo "smoke test OK — $N checks"