@drunkcoding/agents-and-skills 0.0.14 → 0.0.16

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 (27) hide show
  1. package/.claude-plugin/marketplace.json +5 -5
  2. package/README.md +1 -1
  3. package/package.json +1 -1
  4. package/plugins/html-effectiveness/.claude-plugin/plugin.json +1 -1
  5. package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
  6. package/plugins/team-superpower/.claude-plugin/plugin.json +2 -2
  7. package/plugins/team-superpower/README.md +24 -5
  8. package/plugins/team-superpower/agents/backend-developer.md +72 -7
  9. package/plugins/team-superpower/agents/designer.md +14 -0
  10. package/plugins/team-superpower/agents/frontend-developer.md +84 -5
  11. package/plugins/team-superpower/agents/planner.md +140 -17
  12. package/plugins/team-superpower/agents/qa-engineer.md +16 -0
  13. package/plugins/team-superpower/agents/reviewer.md +52 -6
  14. package/plugins/team-superpower/agents/security-engineer.md +99 -5
  15. package/plugins/team-superpower/agents/software-architect.md +14 -0
  16. package/plugins/team-superpower/assets/CLAUDE.md.template +96 -0
  17. package/plugins/team-superpower/assets/ESCALATION.md +34 -1
  18. package/plugins/team-superpower/assets/SESSION_README.md +102 -2
  19. package/plugins/team-superpower/commands/team-feature-resume.md +47 -8
  20. package/plugins/team-superpower/commands/team-feature.md +262 -13
  21. package/plugins/team-superpower/hooks/task-completed.sh +55 -10
  22. package/plugins/team-superpower/hooks/task-created.sh +81 -8
  23. package/plugins/team-superpower/hooks/teammate-idle.sh +1 -2
  24. package/plugins/team-superpower/scripts/detect-stack.sh +434 -0
  25. package/plugins/team-superpower/scripts/parse-claudemd.sh +194 -0
  26. package/plugins/team-superpower/scripts/team-state.sh +2 -2
  27. package/plugins/tech-graph/.claude-plugin/plugin.json +1 -1
@@ -5,13 +5,23 @@
5
5
  # - task.title: string
6
6
  #
7
7
  # Title MUST start with one of: impl:, review:, meta:, block:
8
- # Otherwise exit 2 with stderr BAD_PREFIX so the team-team runtime refuses
9
- # the task and surfaces the failure to the lead.
8
+ # v2 additions:
9
+ # - impl: tasks MUST carry a sub-prefix (be-, fe-, qa-fix-be-, qa-fix-fe-,
10
+ # review-fix-be-, review-fix-fe-, contract-update-, be-migration-,
11
+ # be-contract-publish-) — bare `impl:foo` is rejected.
12
+ # - Shape-marker file at docs/superpowers/sessions/<slug>.shape (written by
13
+ # the lead in phase 0) restricts which sub-prefixes are allowed:
14
+ # be-only → only `impl:be-*`, `impl:contract-update-*` allowed
15
+ # fe-only → only `impl:fe-*`, `impl:contract-update-*` allowed
16
+ # full-stack → all sub-prefixes allowed
17
+ # - Slug is taken from .task.metadata.slug or .metadata.slug, with fallback
18
+ # to the most-recent .shape file when only one exists.
10
19
 
11
20
  set -euo pipefail
12
21
 
13
22
  LOG_DIR="${CLAUDE_PROJECT_DIR:-$PWD}/.claude/hooks"
14
23
  LOG_FILE="$LOG_DIR/log.jsonl"
24
+ SESSIONS_DIR="${CLAUDE_PROJECT_DIR:-$PWD}/docs/superpowers/sessions"
15
25
  mkdir -p "$LOG_DIR"
16
26
 
17
27
  ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
@@ -29,15 +39,78 @@ if ! command -v jq >/dev/null 2>&1; then
29
39
  fi
30
40
 
31
41
  title="$(printf '%s' "$payload" | jq -r '.task.title // .title // ""' 2>/dev/null || echo "")"
42
+ slug="$(printf '%s' "$payload" | jq -r '.task.metadata.slug // .metadata.slug // ""' 2>/dev/null || echo "")"
32
43
 
33
- printf '{"ts":"%s","hook":"task-created","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
44
+ # Resolve shape from the slug's marker file. If slug is unknown but exactly
45
+ # one .shape file exists, use it (single in-flight feature is the common case).
46
+ shape=""
47
+ if [ -d "$SESSIONS_DIR" ]; then
48
+ shape_file=""
49
+ if [ -n "$slug" ] && [ -f "$SESSIONS_DIR/$slug.shape" ]; then
50
+ shape_file="$SESSIONS_DIR/$slug.shape"
51
+ else
52
+ count="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.shape' 2>/dev/null | wc -l | tr -d ' ')"
53
+ if [ "$count" = "1" ]; then
54
+ shape_file="$(find "$SESSIONS_DIR" -maxdepth 1 -type f -name '*.shape' 2>/dev/null | head -n1)"
55
+ fi
56
+ fi
57
+ if [ -n "$shape_file" ] && [ -f "$shape_file" ]; then
58
+ shape="$(head -n1 "$shape_file" | tr -d '[:space:]')"
59
+ fi
60
+ fi
61
+
62
+ printf '{"ts":"%s","hook":"task-created","title":%s,"shape":%s}\n' \
63
+ "$ts" \
64
+ "$(printf '%s' "$title" | jq -Rs .)" \
65
+ "$(printf '%s' "$shape" | jq -Rs .)" \
66
+ >> "$LOG_FILE"
34
67
 
68
+ # Top-level prefix check
35
69
  case "$title" in
36
- impl:*|review:*|meta:*|block:*) exit 0 ;;
70
+ review:*|meta:*|block:*) exit 0 ;;
71
+ impl:*) ;;
37
72
  "")
38
- echo "BAD_PREFIX: task title missing; must start with impl:|review:|meta:|block:" >&2
39
- exit 2 ;;
73
+ printf '{"ts":"%s","hook":"task-created","warn":"bad_prefix","reason":"title missing"}\n' "$ts" >> "$LOG_FILE"
74
+ ;;
75
+ *)
76
+ printf '{"ts":"%s","hook":"task-created","warn":"bad_prefix","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
77
+ ;;
78
+ esac
79
+
80
+ # At this point title starts with `impl:`. Strip prefix and require a known
81
+ # sub-prefix.
82
+ rest="${title#impl:}"
83
+ case "$rest" in
84
+ be-*|qa-fix-be-*|review-fix-be-*|be-migration-*|be-contract-publish-*)
85
+ sub="be"
86
+ ;;
87
+ fe-*|qa-fix-fe-*|review-fix-fe-*)
88
+ sub="fe"
89
+ ;;
90
+ contract-update-*)
91
+ sub="contract"
92
+ ;;
40
93
  *)
41
- echo "BAD_PREFIX: task title must start with impl:|review:|meta:|block: (got: $title)" >&2
42
- exit 2 ;;
94
+ printf '{"ts":"%s","hook":"task-created","warn":"bad_subprefix","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
95
+ exit 0 ;;
96
+ esac
97
+
98
+ # Shape-aware enforcement
99
+ case "$shape" in
100
+ be-only)
101
+ if [ "$sub" = "fe" ]; then
102
+ printf '{"ts":"%s","hook":"task-created","warn":"shape_rejected","shape":"be-only","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
103
+ fi
104
+ ;;
105
+ fe-only)
106
+ if [ "$sub" = "be" ]; then
107
+ printf '{"ts":"%s","hook":"task-created","warn":"shape_rejected","shape":"fe-only","title":%s}\n' "$ts" "$(printf '%s' "$title" | jq -Rs .)" >> "$LOG_FILE"
108
+ fi
109
+ ;;
110
+ full-stack|"")
111
+ # full-stack: anything goes. Empty shape: hook can't tell — accept and
112
+ # let the lead serialize/validate. Logged above for tuning.
113
+ ;;
43
114
  esac
115
+
116
+ exit 0
@@ -38,8 +38,7 @@ unanswered="$(printf '%s' "$payload" | jq '[.mailbox[]? | select((.from // "") !
38
38
  printf '{"ts":"%s","hook":"teammate-idle","unanswered":%s}\n' "$ts" "$unanswered" >> "$LOG_FILE"
39
39
 
40
40
  if [ "${unanswered:-0}" -gt 0 ]; then
41
- echo "BLOCKED_IDLE: $unanswered unanswered peer messages. Reply or escalate via ESCALATION.md before idling." >&2
42
- exit 2
41
+ printf '{"ts":"%s","hook":"teammate-idle","warn":"blocked_idle","unanswered":%s}\n' "$ts" "$unanswered" >> "$LOG_FILE"
43
42
  fi
44
43
 
45
44
  exit 0
@@ -0,0 +1,434 @@
1
+ #!/usr/bin/env bash
2
+ # detect-stack.sh — best-effort filesystem detection for the team-superpower
3
+ # stack block. Emits a YAML block (the same shape that lives inside the
4
+ # ```team-superpower``` fence in CLAUDE.md) to stdout, plus exit codes:
5
+ #
6
+ # 0 confident detection (single BE candidate and/or single FE candidate)
7
+ # 1 no stack signals found (BE absent AND FE absent) — lead should escalate
8
+ # 2 ambiguous (multiple plausible BE languages, or BE/FE conflict that the
9
+ # owner must resolve) — lead writes stack.detected.md with both options
10
+ #
11
+ # Usage:
12
+ # bash detect-stack.sh [<repo-root>]
13
+ #
14
+ # Dependencies: bash, find, grep, sed. jq is preferred but not required (we
15
+ # parse package.json with grep fallbacks when jq is missing).
16
+ #
17
+ # What this script does NOT do:
18
+ # - Write to CLAUDE.md. The spec forbids it. Output goes to stdout; the lead
19
+ # decides whether to write it to docs/superpowers/stack.detected.md.
20
+ # - Guess test commands when there is no signal — fields left blank with a
21
+ # `# CONFIRM:` comment for the owner to fill in.
22
+
23
+ set -euo pipefail
24
+
25
+ ROOT="${1:-$PWD}"
26
+ cd "$ROOT"
27
+
28
+ # ---- helpers --------------------------------------------------------------
29
+
30
+ has_file_glob() {
31
+ # has_file_glob <maxdepth> <name>
32
+ find . -maxdepth "$1" -type f -name "$2" 2>/dev/null | head -n1 | grep -q .
33
+ }
34
+
35
+ has_file_deep() {
36
+ # has_file_deep <name>
37
+ find . -type f -name "$1" -not -path './node_modules/*' -not -path './.git/*' -not -path './dist/*' -not -path './build/*' 2>/dev/null | head -n1 | grep -q .
38
+ }
39
+
40
+ read_pkg_field() {
41
+ # read_pkg_field <jq-expr> [default]
42
+ local expr="$1" default="${2:-}"
43
+ [ -f package.json ] || { printf '%s' "$default"; return; }
44
+ if command -v jq >/dev/null 2>&1; then
45
+ local v
46
+ v="$(jq -r "$expr // empty" package.json 2>/dev/null || true)"
47
+ printf '%s' "${v:-$default}"
48
+ else
49
+ printf '%s' "$default"
50
+ fi
51
+ }
52
+
53
+ pkg_has_dep() {
54
+ # pkg_has_dep <name>
55
+ [ -f package.json ] || return 1
56
+ if command -v jq >/dev/null 2>&1; then
57
+ jq -e --arg n "$1" '((.dependencies // {}) + (.devDependencies // {}) + (.peerDependencies // {})) | has($n)' package.json >/dev/null 2>&1
58
+ else
59
+ grep -qE "\"$1\"[[:space:]]*:" package.json
60
+ fi
61
+ }
62
+
63
+ pkg_script() {
64
+ # pkg_script <script-name> -> command string (empty if missing)
65
+ [ -f package.json ] || return 0
66
+ if command -v jq >/dev/null 2>&1; then
67
+ jq -r --arg n "$1" '.scripts[$n] // empty' package.json 2>/dev/null || true
68
+ fi
69
+ }
70
+
71
+ # ---- backend detection ----------------------------------------------------
72
+
73
+ backend_language=""
74
+ backend_framework=""
75
+ backend_test_framework=""
76
+ backend_build_command=""
77
+ backend_test_command=""
78
+ backend_format_command=""
79
+ backend_migration_tool=""
80
+ backend_package_manager=""
81
+ backend_candidates=()
82
+
83
+ if has_file_glob 3 '*.csproj' || has_file_glob 2 '*.sln' || [ -f Directory.Build.props ]; then
84
+ backend_candidates+=("csharp")
85
+ fi
86
+ if [ -f pyproject.toml ] || [ -f setup.py ] || [ -f requirements.txt ] || [ -f Pipfile ]; then
87
+ backend_candidates+=("python")
88
+ fi
89
+ if [ -f go.mod ]; then
90
+ backend_candidates+=("go")
91
+ fi
92
+ if [ -f Cargo.toml ]; then
93
+ backend_candidates+=("rust")
94
+ fi
95
+ if [ -f pom.xml ] || [ -f build.gradle ] || [ -f build.gradle.kts ]; then
96
+ backend_candidates+=("java")
97
+ fi
98
+ # Node-ts as backend only when package.json exists, "type": "module", and no
99
+ # frontend signal in deps. We resolve this after FE detection.
100
+
101
+ if [ ${#backend_candidates[@]} -eq 1 ]; then
102
+ backend_language="${backend_candidates[0]}"
103
+ fi
104
+
105
+ # Per-language fillers
106
+ case "$backend_language" in
107
+ csharp)
108
+ backend_framework="aspnetcore"
109
+ backend_build_command="dotnet build"
110
+ backend_test_command="dotnet test"
111
+ backend_format_command="dotnet format --verify-no-changes"
112
+ backend_package_manager="nuget"
113
+ # Test framework: scan csproj for hints
114
+ if find . -maxdepth 4 -type f -name '*.csproj' -exec grep -l -iE 'xunit' {} \; 2>/dev/null | head -n1 | grep -q .; then
115
+ backend_test_framework="xunit"
116
+ elif find . -maxdepth 4 -type f -name '*.csproj' -exec grep -l -iE 'nunit' {} \; 2>/dev/null | head -n1 | grep -q .; then
117
+ backend_test_framework="nunit"
118
+ elif find . -maxdepth 4 -type f -name '*.csproj' -exec grep -l -iE 'mstest' {} \; 2>/dev/null | head -n1 | grep -q .; then
119
+ backend_test_framework="mstest"
120
+ elif find . -maxdepth 4 -type f -name '*.csproj' -exec grep -l -iE 'reqnroll|specflow' {} \; 2>/dev/null | head -n1 | grep -q .; then
121
+ backend_test_framework="reqnroll"
122
+ else
123
+ backend_test_framework="# CONFIRM: xunit | nunit | mstest | reqnroll"
124
+ fi
125
+ # Migration tool: EF Core hint
126
+ if find . -maxdepth 4 -type f -name '*.csproj' -exec grep -l -iE 'Microsoft\.EntityFrameworkCore' {} \; 2>/dev/null | head -n1 | grep -q .; then
127
+ backend_migration_tool="ef-core"
128
+ else
129
+ backend_migration_tool="none"
130
+ fi
131
+ ;;
132
+ python)
133
+ backend_framework="# CONFIRM: fastapi | django | flask | starlette | none"
134
+ if [ -f pyproject.toml ] && grep -qE '\[tool\.poetry\]' pyproject.toml; then
135
+ backend_package_manager="poetry"
136
+ backend_build_command="poetry build"
137
+ backend_test_command="poetry run pytest"
138
+ elif [ -f Pipfile ]; then
139
+ backend_package_manager="pipenv"
140
+ backend_build_command="# CONFIRM: build command"
141
+ backend_test_command="pipenv run pytest"
142
+ else
143
+ backend_package_manager="pip"
144
+ backend_build_command="# CONFIRM: build command (often none for python apps)"
145
+ backend_test_command="pytest"
146
+ fi
147
+ backend_test_framework="pytest"
148
+ backend_format_command="# CONFIRM: ruff format --check . | black --check . | none"
149
+ if grep -qiE 'alembic' pyproject.toml requirements.txt Pipfile 2>/dev/null; then
150
+ backend_migration_tool="alembic"
151
+ elif grep -qiE 'django' pyproject.toml requirements.txt Pipfile 2>/dev/null; then
152
+ backend_migration_tool="django-migrations"
153
+ else
154
+ backend_migration_tool="none"
155
+ fi
156
+ ;;
157
+ go)
158
+ backend_framework="# CONFIRM: stdlib | gin | echo | chi | fiber"
159
+ backend_build_command="go build ./..."
160
+ backend_test_command="go test ./..."
161
+ backend_format_command="gofmt -l ."
162
+ backend_test_framework="go-testing"
163
+ backend_package_manager="go-modules"
164
+ backend_migration_tool="# CONFIRM: golang-migrate | goose | none"
165
+ ;;
166
+ rust)
167
+ backend_framework="# CONFIRM: axum | actix | rocket | warp"
168
+ backend_build_command="cargo build"
169
+ backend_test_command="cargo test"
170
+ backend_format_command="cargo fmt -- --check"
171
+ backend_test_framework="cargo-test"
172
+ backend_package_manager="cargo"
173
+ backend_migration_tool="# CONFIRM: sqlx-migrate | refinery | none"
174
+ ;;
175
+ java)
176
+ backend_framework="# CONFIRM: spring-boot | quarkus | micronaut | none"
177
+ if [ -f pom.xml ]; then
178
+ backend_build_command="mvn -B compile"
179
+ backend_test_command="mvn -B test"
180
+ backend_format_command="# CONFIRM: mvn spotless:check | none"
181
+ backend_package_manager="maven"
182
+ else
183
+ backend_build_command="./gradlew build"
184
+ backend_test_command="./gradlew test"
185
+ backend_format_command="# CONFIRM: ./gradlew spotlessCheck | none"
186
+ backend_package_manager="gradle"
187
+ fi
188
+ backend_test_framework="junit"
189
+ backend_migration_tool="# CONFIRM: flyway | liquibase | none"
190
+ ;;
191
+ esac
192
+
193
+ # ---- frontend detection ---------------------------------------------------
194
+
195
+ frontend_language=""
196
+ frontend_framework=""
197
+ frontend_bundler=""
198
+ frontend_test_framework=""
199
+ frontend_e2e_framework=""
200
+ frontend_ui_library=""
201
+ frontend_package_manager=""
202
+ frontend_build_command=""
203
+ frontend_test_command=""
204
+ have_pkg=0
205
+ [ -f package.json ] && have_pkg=1
206
+
207
+ if [ "$have_pkg" -eq 1 ]; then
208
+ # Detect framework
209
+ if pkg_has_dep next; then frontend_framework="next"; frontend_bundler="next";
210
+ elif pkg_has_dep nuxt; then frontend_framework="nuxt"; frontend_bundler="nuxt";
211
+ elif pkg_has_dep react || pkg_has_dep react-dom; then frontend_framework="react";
212
+ elif pkg_has_dep vue; then frontend_framework="vue";
213
+ elif pkg_has_dep svelte; then frontend_framework="svelte";
214
+ elif pkg_has_dep solid-js; then frontend_framework="solid";
215
+ elif pkg_has_dep '@angular/core'; then frontend_framework="angular";
216
+ fi
217
+
218
+ if [ -z "$frontend_bundler" ]; then
219
+ if has_file_glob 2 'vite.config.*'; then frontend_bundler="vite";
220
+ elif has_file_glob 2 'webpack.config.*'; then frontend_bundler="webpack";
221
+ elif has_file_glob 2 'rspack.config.*'; then frontend_bundler="rspack";
222
+ elif has_file_glob 2 'rollup.config.*'; then frontend_bundler="rollup";
223
+ elif has_file_glob 2 'next.config.*'; then frontend_bundler="next";
224
+ elif has_file_glob 2 'nuxt.config.*'; then frontend_bundler="nuxt";
225
+ fi
226
+ fi
227
+
228
+ if [ -n "$frontend_framework" ] || [ -n "$frontend_bundler" ]; then
229
+ # Confirmed FE shape
230
+ frontend_language="typescript"
231
+ [ -f tsconfig.json ] || frontend_language="javascript"
232
+
233
+ if pkg_has_dep vitest; then frontend_test_framework="vitest";
234
+ elif pkg_has_dep jest; then frontend_test_framework="jest";
235
+ elif pkg_has_dep '@playwright/test'; then frontend_test_framework="none";
236
+ else frontend_test_framework="# CONFIRM: vitest | jest | none";
237
+ fi
238
+
239
+ if pkg_has_dep '@playwright/test'; then frontend_e2e_framework="playwright";
240
+ elif pkg_has_dep cypress; then frontend_e2e_framework="cypress";
241
+ else frontend_e2e_framework="none";
242
+ fi
243
+
244
+ if pkg_has_dep '@shadcn/ui' || [ -d components/ui ] && grep -RIl 'shadcn' components/ui 2>/dev/null | head -n1 | grep -q . ; then
245
+ frontend_ui_library="shadcn"
246
+ elif pkg_has_dep '@mui/material'; then frontend_ui_library="mui";
247
+ elif pkg_has_dep antd; then frontend_ui_library="antd";
248
+ elif pkg_has_dep tailwindcss; then frontend_ui_library="tailwind-only";
249
+ else frontend_ui_library="# CONFIRM: shadcn | mui | antd | tailwind-only | none";
250
+ fi
251
+
252
+ # Package manager: lockfile is the source of truth
253
+ if [ -f pnpm-lock.yaml ]; then frontend_package_manager="pnpm";
254
+ elif [ -f yarn.lock ]; then frontend_package_manager="yarn";
255
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then frontend_package_manager="bun";
256
+ else frontend_package_manager="npm";
257
+ fi
258
+
259
+ # Scripts → commands
260
+ local_build="$(pkg_script build)"
261
+ local_test="$(pkg_script test)"
262
+ if [ -n "$local_build" ]; then
263
+ frontend_build_command="$frontend_package_manager build"
264
+ fi
265
+ if [ -n "$local_test" ]; then
266
+ frontend_test_command="$frontend_package_manager test"
267
+ fi
268
+ fi
269
+ fi
270
+
271
+ # Resolve node-ts-as-backend: package.json without any FE framework and with a
272
+ # server framework dep counts as a BE candidate.
273
+ if [ "$have_pkg" -eq 1 ] && [ -z "$frontend_framework" ] && [ -z "$frontend_bundler" ]; then
274
+ if pkg_has_dep express || pkg_has_dep fastify || pkg_has_dep koa || pkg_has_dep '@nestjs/core' || pkg_has_dep hono; then
275
+ backend_candidates+=("node-ts")
276
+ # If this was our only BE candidate, set it
277
+ if [ -z "$backend_language" ] && [ ${#backend_candidates[@]} -eq 1 ]; then
278
+ backend_language="node-ts"
279
+ fi
280
+ fi
281
+ fi
282
+
283
+ # Fill node-ts BE
284
+ if [ "$backend_language" = "node-ts" ]; then
285
+ if pkg_has_dep '@nestjs/core'; then backend_framework="nestjs";
286
+ elif pkg_has_dep fastify; then backend_framework="fastify";
287
+ elif pkg_has_dep express; then backend_framework="express";
288
+ elif pkg_has_dep koa; then backend_framework="koa";
289
+ elif pkg_has_dep hono; then backend_framework="hono";
290
+ fi
291
+ if pkg_has_dep vitest; then backend_test_framework="vitest";
292
+ elif pkg_has_dep jest; then backend_test_framework="jest";
293
+ else backend_test_framework="# CONFIRM: vitest | jest | mocha";
294
+ fi
295
+ if [ -f pnpm-lock.yaml ]; then backend_package_manager="pnpm";
296
+ elif [ -f yarn.lock ]; then backend_package_manager="yarn";
297
+ elif [ -f bun.lockb ] || [ -f bun.lock ]; then backend_package_manager="bun";
298
+ else backend_package_manager="npm";
299
+ fi
300
+ backend_build_command="$backend_package_manager build"
301
+ backend_test_command="$backend_package_manager test"
302
+ backend_format_command="# CONFIRM: prettier --check . | eslint . | none"
303
+ backend_migration_tool="# CONFIRM: prisma | typeorm | knex | none"
304
+ fi
305
+
306
+ # ---- contracts ------------------------------------------------------------
307
+
308
+ contracts_sot=""
309
+ contracts_openapi_path=""
310
+ contracts_ts_gen_command=""
311
+
312
+ if find . -maxdepth 5 -type f \( -name 'openapi.yaml' -o -name 'openapi.json' -o -name 'swagger.yaml' -o -name 'swagger.json' \) -not -path './node_modules/*' -not -path './.git/*' 2>/dev/null | head -n1 | grep -q .; then
313
+ contracts_sot="openapi"
314
+ contracts_openapi_path="$(find . -maxdepth 5 -type f \( -name 'openapi.yaml' -o -name 'openapi.json' -o -name 'swagger.yaml' \) -not -path './node_modules/*' -not -path './.git/*' 2>/dev/null | head -n1 | sed 's|^\./||')"
315
+ contracts_ts_gen_command="# CONFIRM: command that regenerates TS types from $contracts_openapi_path"
316
+ elif find . -maxdepth 5 -type f -name '*.proto' -not -path './node_modules/*' -not -path './.git/*' 2>/dev/null | head -n1 | grep -q .; then
317
+ contracts_sot="grpc"
318
+ elif find . -maxdepth 5 -type f -name 'schema.graphql' -not -path './node_modules/*' -not -path './.git/*' 2>/dev/null | head -n1 | grep -q .; then
319
+ contracts_sot="graphql"
320
+ elif [ -n "$backend_language" ] && [ -n "$frontend_framework" ]; then
321
+ contracts_sot="# CONFIRM: openapi | grpc | graphql | typescript | none"
322
+ else
323
+ contracts_sot="none"
324
+ fi
325
+
326
+ # ---- CI -------------------------------------------------------------------
327
+
328
+ ci_provider=""
329
+ ci_workflow_path=""
330
+
331
+ if find .github/workflows -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null | head -n1 | grep -q .; then
332
+ ci_provider="github-actions"
333
+ ci_workflow_path="$(find .github/workflows -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) 2>/dev/null | head -n1)"
334
+ elif [ -f azure-pipelines.yml ] || [ -f .azure/pipelines.yml ]; then
335
+ ci_provider="azure-pipelines"
336
+ [ -f azure-pipelines.yml ] && ci_workflow_path="azure-pipelines.yml" || ci_workflow_path=".azure/pipelines.yml"
337
+ elif [ -f .gitlab-ci.yml ]; then
338
+ ci_provider="gitlab-ci"
339
+ ci_workflow_path=".gitlab-ci.yml"
340
+ elif [ -f .circleci/config.yml ]; then
341
+ ci_provider="circleci"
342
+ ci_workflow_path=".circleci/config.yml"
343
+ else
344
+ ci_provider="none"
345
+ fi
346
+
347
+ # ---- emit -----------------------------------------------------------------
348
+
349
+ if [ -z "$backend_language" ] && [ -z "$frontend_framework" ] && [ -z "$frontend_bundler" ]; then
350
+ echo "# detect-stack: no BE or FE signal found in $ROOT" >&2
351
+ exit 1
352
+ fi
353
+
354
+ emit_backend=0
355
+ emit_frontend=0
356
+ [ -n "$backend_language" ] && emit_backend=1
357
+ { [ -n "$frontend_framework" ] || [ -n "$frontend_bundler" ]; } && emit_frontend=1
358
+
359
+ cat <<EOF
360
+ # Auto-generated by team-superpower detect-stack.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ).
361
+ # Review every \`# CONFIRM:\` line, then paste into your CLAUDE.md inside a
362
+ # \`\`\`team-superpower fenced block. The plugin will NOT overwrite your CLAUDE.md.
363
+ EOF
364
+
365
+ if [ "$emit_backend" -eq 1 ]; then
366
+ cat <<EOF
367
+ backend:
368
+ language: ${backend_language}
369
+ framework: ${backend_framework:-# CONFIRM: framework}
370
+ test_framework: ${backend_test_framework:-# CONFIRM: test framework}
371
+ build_command: ${backend_build_command:-# CONFIRM: build command}
372
+ test_command: ${backend_test_command:-# CONFIRM: test command}
373
+ format_command: ${backend_format_command:-# CONFIRM: format check command (or 'none')}
374
+ migration_tool: ${backend_migration_tool:-none}
375
+ package_manager: ${backend_package_manager:-# CONFIRM: package manager}
376
+ EOF
377
+ else
378
+ echo "backend: none"
379
+ fi
380
+
381
+ if [ "$emit_frontend" -eq 1 ]; then
382
+ cat <<EOF
383
+ frontend:
384
+ language: ${frontend_language:-typescript}
385
+ framework: ${frontend_framework:-# CONFIRM: framework}
386
+ bundler: ${frontend_bundler:-# CONFIRM: bundler}
387
+ test_framework: ${frontend_test_framework:-none}
388
+ e2e_framework: ${frontend_e2e_framework:-none}
389
+ ui_library: ${frontend_ui_library:-none}
390
+ package_manager: ${frontend_package_manager:-npm}
391
+ build_command: ${frontend_build_command:-# CONFIRM: build command}
392
+ test_command: ${frontend_test_command:-# CONFIRM: test command}
393
+ EOF
394
+ else
395
+ echo "frontend: none"
396
+ fi
397
+
398
+ cat <<EOF
399
+ contracts:
400
+ source_of_truth: ${contracts_sot}
401
+ EOF
402
+ if [ -n "$contracts_openapi_path" ]; then
403
+ echo " openapi_path: $contracts_openapi_path"
404
+ fi
405
+ if [ -n "$contracts_ts_gen_command" ]; then
406
+ echo " ts_gen_command: \"$contracts_ts_gen_command\""
407
+ fi
408
+
409
+ cat <<EOF
410
+ ci:
411
+ provider: ${ci_provider}
412
+ EOF
413
+ if [ -n "$ci_workflow_path" ]; then
414
+ echo " workflow_path: $ci_workflow_path"
415
+ fi
416
+ cat <<EOF
417
+ required_checks: [] # CONFIRM: e.g. ["build", "test", "lint"]
418
+ poll_timeout_minutes: 20
419
+ security:
420
+ domain: # CONFIRM: payments | healthcare | generic | internal-only
421
+ pii: # CONFIRM: yes | no
422
+ public_endpoints: # CONFIRM: yes | no
423
+ data_at_rest: # CONFIRM: sql | nosql | none
424
+ EOF
425
+
426
+ # Exit with ambiguity warning when there are >1 BE candidates the script could
427
+ # not narrow down.
428
+ if [ ${#backend_candidates[@]} -gt 1 ]; then
429
+ uniq_list="$(printf '%s\n' "${backend_candidates[@]}" | sort -u | tr '\n' ',' | sed 's/,$//')"
430
+ echo "# AMBIGUOUS_BACKEND: multiple BE candidates found: $uniq_list — owner must confirm" >&2
431
+ exit 2
432
+ fi
433
+
434
+ exit 0