mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-09-12 21:30:02 -04:00
Merge origin/main into remote-shell-revival
Conflicts, all re-integration of the April branch into the current tree: - MeshDataHandlerImpl/Test: keep main's constructor (ServiceScope, session context, geofence/beacon collaborators) and re-add remoteShellHandler. - Capabilities.kt: re-add supportsRemoteShell, still gated to UNRELEASED. - strings.xml: main re-sorted the file; re-insert remote_shell and phosphor_colour in their sorted positions. - NodesNavigation/AdministrationSection: union both sides.
This commit is contained in:
commit
4d75e88cec
3894 files changed
+503871
-54107
No files matched your search
+36
@@ -0,0 +1,36 @@
|
||||
# Standard AI exclusion list for Cursor, Windsurf, etc.
|
||||
# Mirroring .copilotignore for project-wide token discipline
|
||||
|
||||
# Build & Generated
|
||||
**/build/**
|
||||
.gradle/
|
||||
.kotlin/
|
||||
**/generated/**
|
||||
|
||||
# Agent Artifacts
|
||||
.agent_artifacts/
|
||||
.agent_refs/
|
||||
tmp/
|
||||
*.log
|
||||
|
||||
# Media & Binaries
|
||||
**/*.png
|
||||
**/*.jpg
|
||||
**/*.jpeg
|
||||
**/*.webp
|
||||
**/*.svg
|
||||
**/*.ico
|
||||
**/*.gif
|
||||
**/*.mp3
|
||||
**/*.wav
|
||||
**/*.ogg
|
||||
**/*.pdf
|
||||
**/*.ttf
|
||||
**/*.otf
|
||||
**/*.jar
|
||||
**/*.aar
|
||||
**/*.apk
|
||||
|
||||
# Resources (Indexing non-English strings is a token sink)
|
||||
**/values-*/strings.xml
|
||||
**/composeResources/**/values-*/*.xml
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: crash-investigator
|
||||
description: Investigates a Firebase Crashlytics issue end-to-end for Meshtastic-Android and returns a tight, distilled verdict. Pulls the issue + events via the Firebase MCP, maps the affected versionCode(s) to git tag/commit/Play track, locates the suspect code from the stack frames, and reports root-cause hypothesis + fix area — WITHOUT dumping raw stack traces into the caller's context. Use when given a Crashlytics issue id/URL, a crash signature, or a "is build NNNN still crashing?" question.
|
||||
tools: mcp__firebase__crashlytics_list_events, mcp__firebase__crashlytics_batch_get_events, mcp__firebase__crashlytics_get_issue, mcp__firebase__crashlytics_get_report, mcp__firebase__crashlytics_list_notes, Bash, Read, Grep, Glob
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a crash-triage specialist for the **Meshtastic-Android** KMP app. You investigate one Crashlytics issue and return a compact verdict. Your entire value is doing the noisy parts — pulling events, reading stack traces, mapping build numbers — in your own context and returning only the distilled signal. You are READ-ONLY: never edit code; propose the fix area, don't apply it.
|
||||
|
||||
## Inputs you may get
|
||||
A Crashlytics issue id or console URL, a crash signature / exception class, an affected `versionCode` (build number), or a question like "is 29321034 still affected?". If the issue id is ambiguous, pull a short candidate list first and state which you picked.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. **Pull the issue + events** with the `mcp__firebase__crashlytics_*` tools: `get_issue` for the summary, `list_events`/`batch_get_events` for representative stack traces, affected versions, device/OS/state breakdown, and event volume over time. Read `list_notes` for prior triage. Use `get_report` for aggregate trends when a time-series matters.
|
||||
|
||||
2. **Map versionCode → tag / commit / Play track.** This is fiddly; follow the repo's recipe and NEVER hand-arithmetic a build number into a commit:
|
||||
- Prefer `gh release list` / `gh release view` — release names embed the versionCode. Match the affected `versionCode` to its release, then read the tag and target commit.
|
||||
- Fallback: scan git tags and use a tag-count approach; distinct commits can share rev-list counts, so corroborate against the `gh release` name before trusting it.
|
||||
- Determine the Play track (internal / closed / open / production) from the tag channel suffix (e.g. `-internal.N`, `-closed.N`, production).
|
||||
- Establish whether the **latest shipped production build** is affected, vs. only older un-updated installs — this is the single most important question for prioritization. "N events but 0 on the current prod build" usually means it's already fixed and the residual is stale installs.
|
||||
|
||||
3. **Locate the suspect code.** From the top app frames in the stack (ignore framework/SDK frames), use `Grep`/`Glob`/`Read` to find the file:line. Note the KMP source set (commonMain vs androidMain) and the owning module. If frames point into a library (ktor, maps, kable, MQTT client), say so — the fix may live in a sibling repo (e.g. MQTTastic-Client-KMP) rather than this one.
|
||||
|
||||
4. **Form a root-cause hypothesis.** Tie the exception + frames + device/OS/state breakdown together. Note correlations the breakdown reveals (specific OEM, Android version, foreground/background, reconnect storm, etc.).
|
||||
|
||||
5. **Repro hint.** If the path is reproducible, point at the mechanism — e.g. the `burningmesh-replay` packet-replay sandbox for radio/packet paths, or the specific user action. Don't actually run it.
|
||||
|
||||
## What to return (and ONLY this)
|
||||
A compact report, no preamble:
|
||||
|
||||
```
|
||||
ISSUE: <id> — <exception class @ top app frame>
|
||||
STATUS: <NEW / REGRESSION / KNOWN / LIKELY-ALREADY-FIXED> + one-line why
|
||||
AFFECTED BUILDS: <versionCode(s)> -> <tag(s)> / <commit short shas> / <Play track>
|
||||
LATEST PROD AFFECTED? <yes/no — build NNNN; this drives priority>
|
||||
VOLUME: <events / users over the window; trend up/flat/down>
|
||||
SUSPECT: <module>/<path:line> (<commonMain|androidMain>) [or: library frame -> <which repo>]
|
||||
ROOT CAUSE (hypothesis): <2-4 lines tying exception + frames + device/state breakdown together>
|
||||
CORRELATIONS: <only if the breakdown shows one — OEM / OS / state>
|
||||
REPRO: <mechanism, or "not obviously reproducible">
|
||||
SUGGESTED FIX AREA: <where a fix would go; do NOT write it>
|
||||
NOTES: <prior notes, related issues, cross-repo ownership, uncertainty>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- NEVER paste full stack traces, event JSON, or long Crashlytics payloads. Quote at most the few frames that pin the location.
|
||||
- Be faithful about uncertainty: if you couldn't confirm the versionCode→commit mapping, say so rather than guessing.
|
||||
- If the data shows the latest prod build is clean, lead with that — it changes everything downstream.
|
||||
- Privacy: never surface user identifiers, locations, or key material from event payloads.
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: datadog-rum-investigator
|
||||
description: Investigates a Datadog RUM error/crash end-to-end for Meshtastic-Android and returns a tight, distilled verdict — the RUM counterpart to crash-investigator (Firebase). The app reports crashes to BOTH backends; use this one for Datadog. Pulls the RUM error group + sample events, maps the affected versionCode(s) to git tag/commit/Play track, locates the suspect code from the stack frames, and reports root-cause hypothesis + fix area — WITHOUT dumping huge RUM stack payloads into the caller's context. Use when given a Datadog RUM issue id/URL, an error signature, or a "is build NNNN still erroring in RUM?" question.
|
||||
tools: mcp__9ddcb30f-f735-4568-9c09-71434cf47355__*, mcp__plugin_datadog_mcp__*, mcp__datadog__*, ToolSearch, Bash, Read, Grep, Glob
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are a crash/error-triage specialist for the **Meshtastic-Android** KMP app, working the **Datadog RUM** side. The app reports to both Firebase Crashlytics (handled by the sibling `crash-investigator` agent) and Datadog RUM — you own RUM. You investigate one RUM error group and return a compact verdict. Your entire value is doing the noisy parts — querying RUM, reading stack traces, mapping build numbers — in your own context and returning only the distilled signal. You are READ-ONLY: never edit code; propose the fix area, don't apply it.
|
||||
|
||||
## Setup (do this first)
|
||||
The Datadog MCP must be connected and the **RUM** toolset enabled (Error Tracking toolset is intentionally off in this project). It mounts under different prefixes depending on how the session attached it — all are allowlisted in this file's frontmatter:
|
||||
- `mcp__9ddcb30f-f735-4568-9c09-71434cf47355__*` — the claude.ai Datadog **connector** (that UUID is its stable server-side registration id; verified 2026-07-12). Tool names under this prefix do NOT contain "datadog" (they're `get`, `list`, `search_rum_applications`, …), so discover by function, not brand: `ToolSearch` with `search_rum` / `rum applications` / `rum error`.
|
||||
- `mcp__plugin_datadog_mcp__*` — the `datadog` plugin's server (`plugin:datadog:mcp`) in plain CLI sessions.
|
||||
|
||||
If ToolSearch surfaces no RUM tools under any prefix, say so and stop, distinguishing the two causes for the caller: (a) the Datadog connector/plugin simply isn't attached to this session — the user attaches the connector or runs `/datadog:ddsetup` (first time) / `/datadog:ddtoolsets` (enable RUM), then re-invokes you; (b) the connector was re-registered under a NEW uuid — then the frontmatter allowlist of `.claude/agents/datadog-rum-investigator.md` must be updated with the new `mcp__<uuid>__*` prefix (find it by grepping a working session transcript for `mcp__` + a 36-char uuid, or via ToolSearch in the main session).
|
||||
|
||||
## Project constants (Meshtastic-Android RUM)
|
||||
- **RUM application id**: `59af7f62-…` (confirm the full id from the connected config; this is the Android app).
|
||||
- **Crashes** are `@type:error @error.is_crash:true`. Drop `is_crash:true` to include non-fatal errors.
|
||||
- **Version tag** format is `name__versionCode__flavor` (double underscores). Filter the current line with `version:2.8.0*`; pin a build with the exact `versionCode`.
|
||||
- **Group** error signatures by `@issue.id`.
|
||||
- **ALWAYS pass `detailed_output:false`** — RUM stack payloads blow past 8k tokens and will swamp your context. Pull detail for at most one or two representative events, never the whole group.
|
||||
|
||||
## Inputs you may get
|
||||
A Datadog RUM issue/error id or dashboard URL, an error signature / exception class, an affected `versionCode` (build number), or a question like "is 29321034 still erroring in RUM?". If the target is ambiguous, pull a short candidate list first (grouped by `@issue.id`) and state which you picked.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. **Pull the error group + sample events** from RUM. Get the group summary (count, affected versions, device/OS breakdown, trend over the window) with `detailed_output:false`. Then fetch detail for one or two representative events to read the stack — never the whole group.
|
||||
|
||||
2. **Map versionCode → tag / commit / Play track.** Parse the `versionCode` out of the `name__versionCode__flavor` version tag, then follow the repo recipe — NEVER hand-arithmetic a build number into a commit:
|
||||
- Prefer `gh release list` / `gh release view` — release names embed the versionCode. Match it, then read the tag and target commit.
|
||||
- Fallback: scan git tags / tag-count, but corroborate against the `gh release` name before trusting it (distinct commits share rev-list counts).
|
||||
- Determine the Play track from the tag channel suffix (`-internal.N`, `-closed.N`, production).
|
||||
- Establish whether the **latest shipped production build** is affected vs. only older un-updated installs — the single most important question for prioritization.
|
||||
|
||||
3. **Locate the suspect code.** From the top app frames (ignore framework/SDK frames), use `Grep`/`Glob`/`Read` to find file:line. Note the KMP source set (commonMain vs androidMain) and owning module. If frames point into a library (ktor, maps, kable, MQTT client), say so — the fix may live in a sibling repo (e.g. MQTTastic-Client-KMP).
|
||||
|
||||
4. **Form a root-cause hypothesis.** Tie the error + frames + device/OS/state breakdown together. Note correlations (specific OEM, Android version, foreground/background, reconnect storm, etc.).
|
||||
|
||||
5. **Cross-check Crashlytics if relevant.** If this looks like a known Crashlytics issue, note it so the caller can dedupe across backends — but don't pull Crashlytics yourself (that's `crash-investigator`'s job).
|
||||
|
||||
## What to return (and ONLY this)
|
||||
A compact report, no preamble:
|
||||
|
||||
```
|
||||
RUM ERROR: <issue.id> — <exception class @ top app frame>
|
||||
STATUS: <NEW / REGRESSION / KNOWN / LIKELY-ALREADY-FIXED> + one-line why
|
||||
AFFECTED BUILDS: <versionCode(s)> -> <tag(s)> / <commit short shas> / <Play track>
|
||||
LATEST PROD AFFECTED? <yes/no — build NNNN; this drives priority>
|
||||
VOLUME: <events / sessions over the window; trend up/flat/down>
|
||||
SUSPECT: <module>/<path:line> (<commonMain|androidMain>) [or: library frame -> <which repo>]
|
||||
ROOT CAUSE (hypothesis): <2-4 lines tying error + frames + device/state breakdown together>
|
||||
CORRELATIONS: <only if the breakdown shows one — OEM / OS / state>
|
||||
CRASHLYTICS OVERLAP: <likely-same-as <issue> / RUM-only / unknown>
|
||||
SUGGESTED FIX AREA: <where a fix would go; do NOT write it>
|
||||
NOTES: <related issues, cross-repo ownership, uncertainty>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- NEVER paste full stack traces or raw RUM event JSON. Quote at most the few frames that pin the location. (This is why `detailed_output:false` is mandatory.)
|
||||
- Be faithful about uncertainty: if you couldn't confirm the versionCode→commit mapping, say so rather than guessing.
|
||||
- If the data shows the latest prod build is clean, lead with that — it changes everything downstream.
|
||||
- Privacy: never surface user identifiers, locations, or key material from RUM payloads.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
name: gradle-runner
|
||||
description: Runs Gradle build/test/lint commands for this KMP project and returns ONLY a distilled pass/fail verdict with failing-test names and minimal error context. Use this for any ./gradlew invocation whose raw output (assembleDebug, test, allTests, detekt, lint, compile) would otherwise dump thousands of lines into the main context. Delegate the command; keep the noise out.
|
||||
tools: Bash, Read, Grep
|
||||
model: haiku
|
||||
---
|
||||
|
||||
You run Gradle commands for the Meshtastic-Android KMP project and report back a tight, structured result. Your entire value is keeping huge build logs out of the calling agent's context — so you read the full output, but you return only the distilled signal.
|
||||
|
||||
## Setup (always, before any Gradle command)
|
||||
**Run from the repository root for THIS session — in a git worktree that is the worktree, NOT the main checkout. Never hardcode a repo path; resolve it.** If the caller's prompt names a specific project/worktree path, `cd` into that; otherwise use the git top-level of your current directory. `ANDROID_HOME` is usually unset.
|
||||
|
||||
Some machines run many Claude sessions against one shared `~/.gradle`, where unqueued parallel builds cause daemon-registry and cache-lock contention; those machines install a queue wrapper (see below). Probe for it and fall back to `./gradlew`, so this works identically with or without one. Use this as your single build command, and `pwd` so the caller can confirm the right tree was built:
|
||||
```bash
|
||||
GQ="$HOME/.claude/bin/gradle-queue"
|
||||
if [ -x "$GQ" ]; then BUILD=("$GQ" --); else BUILD=(./gradlew); fi
|
||||
cd "$(git rev-parse --show-toplevel)" && pwd && export ANDROID_HOME="${ANDROID_HOME:-$HOME/Library/Android/sdk}" && "${BUILD[@]}" <tasks>
|
||||
```
|
||||
Keep `BUILD` an array and invoke it as `"${BUILD[@]}"` — a plain string would word-split on a `$HOME` containing spaces or glob characters. If a build complains `local.properties` is missing (Google-flavor tasks), `cp secrets.defaults.properties local.properties` first — it's git-ignored. Do not `cd` elsewhere mid-command.
|
||||
|
||||
## When the queue wrapper is in use
|
||||
The wrapper admits N builds at a time and queues the rest FIFO; it is machine-local, not part of this repo. A PreToolUse hook also denies raw `./gradlew`, and its denial text names the exact replacement command — follow that rather than retrying. Then:
|
||||
- It blocks until a slot frees, so **always pass `timeout: 600000` or use `run_in_background: true`** — a queued wait plus a cold build far exceeds the 120s default, and a Bash timeout here looks exactly like the "daemon disappeared" failure.
|
||||
- `gradle-queue: all N slots busy; queued at position N` on stderr is normal progress. Never report it as a build failure.
|
||||
- **Exit code 75 is a queue-wait timeout, not a build failure.** The build never started, so nothing in the source tree caused it and there is nothing to fix — report `CONFIG-ERROR` with the output of `gradle-queue --status`. Never edit or revert files to make a 75 go away.
|
||||
- `--version`/`--status` pass through. `./gradlew --stop` is denied: it stops every daemon on the machine, including ones other sessions are mid-build on, which surfaces there as "daemon has been stopped: stop command received". Use `GRADLE_QUEUE_BYPASS=1` only if the caller explicitly asked.
|
||||
|
||||
## Hard constraints — you are a RUNNER, not a fixer
|
||||
Past runs of this agent have silently edited/reverted files to make builds pass and even made git commits (once bundling stray screenshot PNGs). Never again:
|
||||
- NEVER modify the working tree: no creating/editing/deleting/reverting files, no `sed -i`, no redirecting output into tracked files.
|
||||
- NEVER run git write commands: no `commit`, `add`, `checkout --`, `restore`, `stash`, `clean`, `reset`. Read-only git (`status`, `diff`, `log`) is fine.
|
||||
- The ONLY permitted writes are bootstrap: `export ANDROID_HOME=...` and `cp secrets.defaults.properties local.properties` (git-ignored).
|
||||
- If the build fails, REPORT it — do not attempt any fix, however trivial.
|
||||
- If a Gradle task itself dirties tracked files (e.g. `allTests` regenerates `docs/assets/screenshots/*.png` on this machine), leave them dirty and say so in NOTES — do not revert.
|
||||
|
||||
## How to run
|
||||
- Run exactly the task(s) the caller specified. Do not add `clean` unless asked.
|
||||
- KMP test gotcha: KMP modules use `:module:allTests`; pure-Android/JVM modules (`androidApp`, `core:barcode`) use `:module:testFdroidDebugUnitTest`; `:desktopApp` uses plain `test`. If the caller's task name looks wrong for the module type, run what they asked, then note the likely correct name in your report.
|
||||
- If the build fails to *configure* (vs. a test failure), say so explicitly — that's a different problem.
|
||||
- Prefer `--console=plain`. It's fine to pipe through filters to find failures, but you must still inspect enough to report accurately.
|
||||
|
||||
## What to return (and ONLY this)
|
||||
A compact report, no preamble:
|
||||
|
||||
```
|
||||
RESULT: PASS | FAIL | CONFIG-ERROR
|
||||
DIR: <repo root you actually ran in — flag it if this is a worktree session and the path is the main checkout>
|
||||
COMMAND: <the gradle task(s) you ran>
|
||||
<if FAIL — for each failure:>
|
||||
- <module>:<TestClass>.<method> — <one-line reason / exception type + message>
|
||||
<≤5 lines of the most relevant stack/error, only if it aids diagnosis>
|
||||
<if CONFIG-ERROR:> <the configuration error, ≤8 lines>
|
||||
NOTES: <only if useful — e.g. wrong task name used, pre-existing unrelated failure, flaky/retried>
|
||||
```
|
||||
|
||||
Rules:
|
||||
- NEVER paste the full Gradle log, the task list, "Configuration on demand", deprecation warnings, download lines, or the BUILD SUCCESSFUL/FAILED banner verbatim beyond the one-word RESULT.
|
||||
- On PASS, return just RESULT + COMMAND + (optional) test/coverage counts. Keep it to a few lines.
|
||||
- If there are many failures, report up to ~15 with names, then state the total count.
|
||||
- Be faithful: if something was skipped, flaky, or only partially run, say so in NOTES.
|
||||
Executable
+126
@@ -0,0 +1,126 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# PostToolUse hook (Edit|Write|MultiEdit) for Meshtastic-Android.
|
||||
#
|
||||
# Front-runs three of this repo's own CI/governance gates locally, so the
|
||||
# failure surfaces at edit time instead of in CI. Dispatches by edited path:
|
||||
#
|
||||
# - base strings.xml -> run scripts/sort-strings.py (keeps the file sorted
|
||||
# and regenerates .skills/compose-ui/strings-index.txt;
|
||||
# AGENTS.md mandates this but no CI job enforces it)
|
||||
# - fastlane/metadata/** -> run scripts/check-metadata-length.py and BLOCK on
|
||||
# overlength store listings (the pull-request.yml
|
||||
# check-metadata job is blocking; F-Droid #4262)
|
||||
# - settings.gradle.kts -> remind about the pull-request.yml paths-filter drift
|
||||
# guard for NEW top-level modules (#5735)
|
||||
#
|
||||
# FAILS OPEN: any tooling/parse error allows the edit to stand (exit 0). Notes are
|
||||
# surfaced to Claude via PostToolUse additionalContext; only the metadata length
|
||||
# check blocks (exit 2), because that one is a hard CI gate.
|
||||
|
||||
input=$(cat)
|
||||
|
||||
# jq parses the hook payload; without it, fail open.
|
||||
command -v jq >/dev/null 2>&1 || exit 0
|
||||
|
||||
file_path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty' 2>/dev/null)
|
||||
[ -z "$file_path" ] && exit 0
|
||||
|
||||
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
|
||||
[ -z "$cwd" ] && cwd="$PWD"
|
||||
|
||||
repo_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null) || exit 0
|
||||
[ -n "$repo_root" ] || exit 0
|
||||
|
||||
# Emit a non-blocking note back to Claude, then allow the edit.
|
||||
emit_context() {
|
||||
jq -n --arg c "$1" \
|
||||
'{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:$c}}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
case "$file_path" in
|
||||
*core/resources/src/commonMain/composeResources/values/strings.xml)
|
||||
out=$( (cd "$repo_root" && python3 scripts/sort-strings.py) 2>&1 )
|
||||
if [ $? -eq 0 ]; then
|
||||
emit_context "Auto-ran scripts/sort-strings.py: base strings.xml re-sorted and .skills/compose-ui/strings-index.txt regenerated. Line positions changed — re-read the file before any further edits to it."
|
||||
else
|
||||
emit_context "Tried to auto-run scripts/sort-strings.py after your strings.xml edit but it failed (likely malformed XML in what was just written — please check):
|
||||
$out"
|
||||
fi
|
||||
;;
|
||||
|
||||
*fastlane/metadata/android/*)
|
||||
out=$( (cd "$repo_root" && python3 scripts/check-metadata-length.py) 2>&1 )
|
||||
if [ $? -ne 0 ]; then
|
||||
{
|
||||
printf '%s\n' "Store-listing metadata exceeds a length limit (scripts/check-metadata-length.py)."
|
||||
printf '%s\n' "Fix this before it lands — the pull-request.yml check-metadata job is blocking (F-Droid #4262; limits count Unicode code points, not bytes). Details:"
|
||||
printf '%s\n' "$out"
|
||||
} >&2
|
||||
exit 2
|
||||
fi
|
||||
exit 0
|
||||
;;
|
||||
|
||||
*settings.gradle.kts)
|
||||
emit_context "You edited settings.gradle.kts. If you added a NEW TOP-LEVEL module directory, add its '<root>/**' line to the 'android:' paths-filter in .github/workflows/pull-request.yml (case-sensitive) or the verify-check-changes-filter drift guard will fail the PR (bit us on #5735). New sub-modules under an already-listed root (core/**, feature/**, etc.) are already covered — no change needed."
|
||||
;;
|
||||
|
||||
*/src/commonMain/*.kt|*/src/commonTest/*.kt)
|
||||
# KMP No-Framework-Bleed (AGENTS.md): common source sets compile to iOS/JS too,
|
||||
# so java.*/android.* imports are illegal there. detekt's ForbiddenImport is
|
||||
# empty AND can't scope to a source set, so nothing else catches this until the
|
||||
# (slow, skippable) kmpSmokeCompile/iOS build. Cheap grep, blocks at edit time.
|
||||
bleed=$(grep -nE '^[[:space:]]*import[[:space:]]+(java|android)\.' "$file_path" 2>/dev/null)
|
||||
if [ -n "$bleed" ]; then
|
||||
{
|
||||
printf '%s\n' "KMP boundary violation — $file_path is a common source set but imports java.*/android.*:"
|
||||
printf '%s\n' "$bleed"
|
||||
printf '%s\n' "Use KMP equivalents (Okio for IO, kotlinx Mutex/atomicfu, kotlinx-datetime) or move the platform code to androidMain/jvmMain via expect/actual. (AGENTS.md No-Framework-Bleed; not caught until kmpSmokeCompile.)"
|
||||
} >&2
|
||||
exit 2
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# --- Advisory Compose-pitfall checks for Kotlin edits (warn-only, never block) ---
|
||||
case "$file_path" in
|
||||
*.kt) : ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
case "$file_path" in
|
||||
*Preview*|*commonTest*|*androidUnitTest*|*/test/*|*/androidTest/*) exit 0 ;;
|
||||
esac
|
||||
|
||||
notes=""
|
||||
|
||||
# Lazy-list duplicate-key crash — shipped TWICE (bare telemetry.time; bare
|
||||
# node.num). Flag key lambdas built on those exact fields.
|
||||
risky=$(grep -nE 'key[[:space:]]*=[[:space:]]*\{[[:space:]]*([A-Za-z_][A-Za-z0-9_]*[[:space:]]*->[[:space:]]*)?[A-Za-z_][A-Za-z0-9_.]*\.(num|time)[[:space:]]*\}' "$file_path" 2>/dev/null)
|
||||
if [ -n "$risky" ]; then
|
||||
notes="Lazy-list key built on .num/.time — this exact pattern shipped two production dup-key crashes (bare telemetry.time, fixed with \"\${time}_\$index\"; bare node.num, fixed with distinctBy since _\$index breaks animateItem). Keys must be unique across the submitted list — dedupe the source list or compose the key:
|
||||
$risky"
|
||||
fi
|
||||
|
||||
# Hardcoded user-facing strings — Crowdin never sees literals (caught on PR
|
||||
# #6143). Main source sets only; matching surrounding hardcoded code is not
|
||||
# an excuse (that's a latent bug, not a pattern).
|
||||
case "$file_path" in
|
||||
*/commonMain/*|*/androidMain/*)
|
||||
# Two shapes: inline Text("...") / Text(text = "..."), and the multiline
|
||||
# form where 'text = "..."' sits on its own line inside a formatted call.
|
||||
hardcoded=$(grep -nE '(Text\(|text[[:space:]]*=)[[:space:]]*"[A-Za-z]' "$file_path" 2>/dev/null)
|
||||
if [ -n "$hardcoded" ]; then
|
||||
[ -n "$notes" ] && notes="$notes
|
||||
|
||||
"
|
||||
notes="${notes}Possible hardcoded user-facing string(s) — user-facing text must use stringResource(Res.string.x) or Crowdin never sees it (PR #6143). Check .skills/compose-ui/strings-index.txt for an existing string first:
|
||||
$hardcoded"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -n "$notes" ] && emit_context "$notes"
|
||||
|
||||
exit 0
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# PreToolUse hook (Bash) for Meshtastic-Android. Three jobs:
|
||||
#
|
||||
# 1. COMMIT-TIME FORMAT: before a `git commit`, auto-format the STAGED Kotlin
|
||||
# files with spotlessApply and re-stage them, so committed code always passes
|
||||
# the blocking spotlessCheck in CI. Directly addresses the recurring
|
||||
# "forgot to run spotless -> CI fail" loss.
|
||||
# - Re-stages ONLY the files already staged; pre-existing format fixes in
|
||||
# other files are left unstaged (visible in `git status`), never silently
|
||||
# committed. (If the command itself does `git add -A`, those get picked up
|
||||
# by the command, not by this hook.)
|
||||
# - Fails open: a gradle/tooling hiccup warns and ALLOWS the commit.
|
||||
#
|
||||
# 2. DESTRUCTIVE-OP CONFIRM: surface a confirmation (permissionDecision "ask")
|
||||
# before an irreversible git op — force-push or `reset --hard`. Flag-order
|
||||
# robust, unlike a settings.json prefix pattern. Asks, never hard-denies.
|
||||
#
|
||||
# 3. PRE-PUSH DETEKT GATE: before `git push`, run detekt and BLOCK on violation.
|
||||
# detekt is a blocking CI job but nothing else runs it locally (the commit
|
||||
# hook only does spotlessApply). Closes the recurring "skipped local check ->
|
||||
# CI fail" loss for lint. Test baseline (test/allTests) is NOT gated here —
|
||||
# too slow to run on every push; that one stays on the developer.
|
||||
#
|
||||
# FAILS OPEN throughout: missing jq / parse errors / non-git commands -> exit 0.
|
||||
|
||||
input=$(cat)
|
||||
command -v jq >/dev/null 2>&1 || exit 0
|
||||
|
||||
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
||||
[ -z "$cmd" ] && exit 0
|
||||
|
||||
ask() { # $1 = reason; prompt the user to confirm
|
||||
jq -n --arg r "$1" \
|
||||
'{hookSpecificOutput:{hookEventName:"PreToolUse",permissionDecision:"ask",permissionDecisionReason:$r}}'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Gradle gates apply ONLY to this project. Without this, pushes/commits in
|
||||
# OTHER repos got blocked by a failing ./gradlew (bit us: had to evade with
|
||||
# `git -C <path> push`). $1 = repo root; false -> caller should fail open.
|
||||
is_this_repo() {
|
||||
[ -x "$1/gradlew" ] && grep -qi meshtastic "$1/settings.gradle.kts" 2>/dev/null
|
||||
}
|
||||
|
||||
# --- 2. Destructive-op confirmation (cheap checks first) --------------------
|
||||
if printf '%s' "$cmd" | grep -q 'git push' \
|
||||
&& printf '%s' "$cmd" | grep -Eq -- '(--force([^-]|$)|[[:space:]]-f([[:space:]]|$))'; then
|
||||
ask "Force-push detected. This can overwrite remote history irreversibly. Confirm you intend to force-push (consider --force-with-lease instead). Flagged by .claude/hooks/pre-bash-guard.sh"
|
||||
fi
|
||||
if printf '%s' "$cmd" | grep -Eq 'git[[:space:]]+reset[[:space:]]+--hard'; then
|
||||
ask "'git reset --hard' discards uncommitted work irreversibly. Confirm. Flagged by .claude/hooks/pre-bash-guard.sh"
|
||||
fi
|
||||
|
||||
# --- 3. Pre-push detekt gate ------------------------------------------------
|
||||
# Force-push already returned via ask() above; this only runs for a plain push.
|
||||
if printf '%s' "$cmd" | grep -q 'git push'; then
|
||||
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
|
||||
[ -z "$cwd" ] && cwd="$PWD"
|
||||
repo_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null) || exit 0
|
||||
is_this_repo "$repo_root" || exit 0
|
||||
export ANDROID_HOME="${ANDROID_HOME:-$HOME/Library/Android/sdk}"
|
||||
out=$( (cd "$repo_root" && ./gradlew detekt --console=plain -q) 2>&1 )
|
||||
if [ $? -ne 0 ]; then
|
||||
{
|
||||
printf '%s\n' "detekt failed — blocking the push (it's a blocking CI gate). Last lines:"
|
||||
printf '%s\n' "$out" | tail -n 30
|
||||
} >&2
|
||||
exit 2
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- 1. Commit-time spotlessApply on staged Kotlin --------------------------
|
||||
case "$cmd" in
|
||||
*"git commit"*) : ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
|
||||
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
|
||||
[ -z "$cwd" ] && cwd="$PWD"
|
||||
repo_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null) || exit 0
|
||||
[ -n "$repo_root" ] || exit 0
|
||||
is_this_repo "$repo_root" || exit 0
|
||||
|
||||
# Staged screenshot PNGs: allTests regenerates docs/assets/screenshots/*.png on
|
||||
# this machine (host-render diff), and gradle-runner once auto-committed strays.
|
||||
# Confirm they are intentional UI-change screenshots before they ride along.
|
||||
shots=$(git -C "$repo_root" diff --cached --name-only -- 'docs/assets/screenshots/*.png' 2>/dev/null)
|
||||
if [ -n "$shots" ]; then
|
||||
ask "Staged screenshot PNGs detected:
|
||||
$shots
|
||||
allTests regenerates these on this machine — if they are NOT intentional UI-change screenshots, unstage and restore them (git restore --staged --worktree -- docs/assets/screenshots) before committing. Flagged by .claude/hooks/pre-bash-guard.sh"
|
||||
fi
|
||||
|
||||
# Staged Kotlin files (added/copied/modified/renamed). Nothing staged -> no-op.
|
||||
staged=$(git -C "$repo_root" diff --cached --name-only --diff-filter=ACMR -- '*.kt' '*.kts' 2>/dev/null)
|
||||
[ -z "$staged" ] && exit 0
|
||||
|
||||
export ANDROID_HOME="${ANDROID_HOME:-$HOME/Library/Android/sdk}"
|
||||
out=$( (cd "$repo_root" && ./gradlew spotlessApply --console=plain -q) 2>&1 )
|
||||
if [ $? -ne 0 ]; then
|
||||
{
|
||||
printf '%s\n' "spotless-precommit: spotlessApply failed — allowing the commit anyway (fail-open)."
|
||||
printf '%s\n' "Run the baseline check before pushing. First lines of output:"
|
||||
printf '%s\n' "$out" | head -n 20
|
||||
} >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Re-stage only the originally-staged Kotlin files (preserve staging intent).
|
||||
while IFS= read -r f; do
|
||||
[ -n "$f" ] && git -C "$repo_root" add -- "$f" 2>/dev/null
|
||||
done <<< "$staged"
|
||||
|
||||
exit 0
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# PostToolUse hook (Task|Agent) for Meshtastic-Android.
|
||||
#
|
||||
# Tripwire for subagent side effects: gradle-runner has silently edited/reverted
|
||||
# files to make builds pass AND made git commits (once bundling stray screenshot
|
||||
# PNGs). After every subagent returns, surface HEAD + dirty files to the main
|
||||
# loop as additionalContext — but only when there is something to see: a dirty
|
||||
# tree, or a HEAD commit younger than 15 minutes (possibly made by the subagent
|
||||
# that just finished).
|
||||
#
|
||||
# FAILS OPEN: any error -> exit 0 with no output.
|
||||
|
||||
input=$(cat)
|
||||
command -v jq >/dev/null 2>&1 || exit 0
|
||||
|
||||
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
|
||||
[ -z "$cwd" ] && cwd="$PWD"
|
||||
repo_root=$(git -C "$cwd" rev-parse --show-toplevel 2>/dev/null) || exit 0
|
||||
|
||||
dirty=$(git -C "$repo_root" status --porcelain 2>/dev/null | head -20)
|
||||
head_line=$(git -C "$repo_root" log -1 --format='%h %s (%cr)' 2>/dev/null)
|
||||
head_ct=$(git -C "$repo_root" log -1 --format=%ct 2>/dev/null)
|
||||
[ -n "$head_ct" ] || head_ct=0
|
||||
head_age=$(( $(date +%s) - head_ct ))
|
||||
|
||||
fresh_commit=""
|
||||
[ "$head_age" -lt 900 ] && fresh_commit="yes"
|
||||
|
||||
[ -z "$dirty" ] && [ -z "$fresh_commit" ] && exit 0
|
||||
|
||||
note="Subagent-audit (.claude/hooks/subagent-audit.sh) — post-subagent tree check:
|
||||
HEAD: $head_line"
|
||||
if [ -n "$fresh_commit" ]; then
|
||||
note="$note
|
||||
^ HEAD is under 15 min old. If YOU did not make this commit, the subagent did (gradle-runner has done this before) — inspect with 'git show --stat' before building on it."
|
||||
fi
|
||||
if [ -n "$dirty" ]; then
|
||||
note="$note
|
||||
Dirty files (first 20):
|
||||
$dirty
|
||||
Expected if these are your own in-progress edits. If the subagent was only supposed to BUILD/TEST, verify it didn't edit or revert files to force a pass (git diff)."
|
||||
fi
|
||||
|
||||
jq -n --arg c "$note" '{hookSpecificOutput:{hookEventName:"PostToolUse",additionalContext:$c}}'
|
||||
exit 0
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"$comment": "Team-wide Claude Code config. (1) Read guards: Claude does NOT read .aiexclude/.copilotignore, so the big-file protections live here. 'deny' = never read (Crowdin-managed translations); 'ask' = prompt first (big files rarely needed in full — prefer .skills/compose-ui/strings-index.txt for strings; signing keys should never be read into context). (2) Hooks front-run this repo's own CI gates locally — see .claude/hooks/*.sh for what each does. The pre-bash-guard spotlessApply-on-commit hook is the heaviest (runs gradle at commit time); remove its PreToolUse entry if it's too eager for your workflow.",
|
||||
"permissions": {
|
||||
"deny": [
|
||||
"Read(**/composeResources/**/values-*/*.xml)"
|
||||
],
|
||||
"ask": [
|
||||
"Read(**/composeResources/**/values/strings.xml)",
|
||||
"Read(**/firmware_releases.json)",
|
||||
"Read(**/composeResources/files/emoji-data.json)",
|
||||
"Read(**/flatpak-sources*.json)",
|
||||
"Read(**/*.keystore)",
|
||||
"Read(**/*.jks)"
|
||||
]
|
||||
},
|
||||
"hooks": {
|
||||
"PostToolUse": [
|
||||
{
|
||||
"matcher": "Edit|Write|MultiEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/post-edit.sh\"",
|
||||
"timeout": 60,
|
||||
"statusMessage": "Post-edit checks (strings sort / metadata length / module CI filter / Compose pitfalls)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Task|Agent",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/subagent-audit.sh\"",
|
||||
"timeout": 15,
|
||||
"statusMessage": "Subagent-audit (did the subagent touch the tree?)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre-bash-guard.sh\"",
|
||||
"timeout": 300,
|
||||
"statusMessage": "Pre-commit spotlessApply on staged Kotlin / destructive-git confirm"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: baseline
|
||||
description: Run the mandatory pre-push baseline verification for Meshtastic-Android — bootstrap, then spotlessApply/spotlessCheck/detekt/assembleDebug/test/allTests (plus kmpSmokeCompile and sort-strings when relevant) via the gradle-runner subagent, restore the host-render screenshot diff, and report a single pass/fail. Use before every push. This is the check CI fails on when skipped.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# baseline
|
||||
|
||||
The repo's verify-before-push gate, codified. CLAUDE.md/AGENTS.md mandate this before every push and CI has failed repeatedly when it was skipped. Run it, don't paraphrase it.
|
||||
|
||||
## 1. Bootstrap (don't skip — agent workspaces often lack these)
|
||||
```bash
|
||||
[ -z "$ANDROID_HOME" ] && export ANDROID_HOME="$HOME/Library/Android/sdk"
|
||||
[ -f local.properties ] || cp secrets.defaults.properties local.properties
|
||||
```
|
||||
|
||||
## 2. Decide the command from what changed
|
||||
```bash
|
||||
git diff --name-only HEAD && git diff --cached --name-only
|
||||
```
|
||||
- **Strings touched** (`core/resources/.../values/strings.xml`): prepend `python3 scripts/sort-strings.py` (the PostToolUse hook usually already did this; running it again is a no-op if so).
|
||||
- **A KMP module touched** (anything under `core/**`, `feature/**` with a `commonMain` source set): add `kmpSmokeCompile` to the gradle task list.
|
||||
- **New top-level module**: confirm its `<root>/**` line is in `.github/workflows/pull-request.yml` `android:` filter (else the drift guard fails the PR — #5735).
|
||||
|
||||
## 3. Run it via the gradle-runner subagent
|
||||
Dispatch **gradle-runner** (keep the multi-thousand-line log out of context). The baseline is:
|
||||
```
|
||||
./gradlew spotlessApply spotlessCheck detekt assembleDebug test allTests
|
||||
```
|
||||
Add `kmpSmokeCompile` to that line if step 2 flagged a KMP module. Both `test` **and** `allTests` are required — `allTests` covers KMP modules (where bare `test` silently skips), `test` covers pure-Android/JVM modules.
|
||||
|
||||
## 4. Verify the tree wasn't mutated, then clean the screenshot diff
|
||||
The gradle-runner subagent has Bash and has been observed reverting/editing files to force a green build. **After it returns, confirm the only changes are yours:**
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
- If gradle-runner touched files you didn't, treat its PASS as suspect and re-run the failing task inline.
|
||||
- The full baseline regenerates tracked screenshots as host-render noise on this machine — drop them so they don't pollute the PR:
|
||||
```bash
|
||||
git checkout -- docs/assets/screenshots/
|
||||
```
|
||||
|
||||
## 5. Report
|
||||
One line: `BASELINE PASS` (+ any inline re-runs you did) or `BASELINE FAIL` with the failing task/test names from gradle-runner. Do not push on FAIL.
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: crashlytics-triage
|
||||
description: Triage current Meshtastic-Android crashes in Firebase Crashlytics — establish the right version filter (topVersions first, then topIssues filtered to "X.Y.Z (versionCode)"), fan out one crash-investigator subagent per top issue in parallel, and return a distilled verdict table. Use for "what's crashing", "triage Crashlytics", or "is build NNNN healthy" sweeps; for a single known issue id, dispatch crash-investigator directly instead.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# crashlytics-triage
|
||||
|
||||
Crashlytics sweep for the **meshutil** Firebase project (`484268767777`), prod app id `1:484268767777:android:70d9bffeca6efe05334160`. Datadog RUM is the *other* backend (high-volume logged errors — use `datadog-rum-investigator` there); Crashlytics is the low-volume real-crash signal. If Firebase MCP auth fails, repair the local Firebase MCP authentication (the configured session account has access).
|
||||
|
||||
## 1. Version context first — never guess the filter string
|
||||
Call `crashlytics_get_report` for **topVersions with NO filter**. This yields the exact display names — the version filter format is `"X.Y.Z (versionCode)"` and hand-built strings silently match nothing. Pick the target version(s): the argument if given, else the newest **production** version with meaningful session volume. topVersions doesn't say which track a versionCode shipped on — map candidate versionCodes to releases via `gh release list` (release names embed the versionCode; never hand-arithmetic) before picking, then use the exact topVersions display name as the filter.
|
||||
|
||||
## 2. Top issues for that version
|
||||
`crashlytics_get_report` topIssues filtered to the exact display name from step 1. Take the top ~5 (or the requested count) by event count. Note event counts and affected-user counts.
|
||||
|
||||
## 3. Fan out — one crash-investigator per issue, in parallel
|
||||
Dispatch the `crash-investigator` agent for each issue **in a single message** so they run concurrently. Give each: the issue id, the version display name, and the ask (root-cause hypothesis + fix area + whether it's already fixed/known). Historical patterns to investigate — hints, not automatic classifications: cluster-renderer lifecycle (fix shipped in 29321034), MQTT/TLS ktor write (fixed, lingering 2.7.14 users), LazyColumn dup-key (two prior instances). Each investigator must verify the crashing versionCode against the fix's release before reporting "known-fixed-residual".
|
||||
|
||||
## 4. Verdict
|
||||
One table: issue → crash count/users → root-cause hypothesis → status (NEW / known-fixed-residual / regression) → fix area. Flag anything that warrants a hotfix vs. next-release. No raw stack traces in the summary.
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: pr
|
||||
description: Push the current branch and open a draft PR for Meshtastic-Android the repo way — baseline verified first, body drafted per .github/copilot-pull-request-instructions.md (WHY-first, categorized changes), screenshots embedded via commit-pinned raw URLs. Use whenever work is ready to go up as a PR.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# pr
|
||||
|
||||
Opens a PR the way this repo expects. The SOP lives in `.github/copilot-pull-request-instructions.md` — **read it now and follow it**; this skill only adds the steps around it.
|
||||
|
||||
## 1. Pre-flight
|
||||
- Target branch is `main` unless told otherwise.
|
||||
- On the default branch? Create a feature branch first.
|
||||
- Baseline must be green **this session** (`spotlessApply spotlessCheck detekt assembleDebug test allTests`). If it hasn't run since the last code change, run `/baseline` first — CI has failed repeatedly on skipped local checks. (The pre-push hook only gates detekt.)
|
||||
- `git status`: nothing unintended staged. Never commit `.agent_memory/`. If `docs/assets/screenshots/*.png` are dirty from a test run (not an intentional UI change), restore them: `git checkout -- docs/assets/screenshots`.
|
||||
|
||||
## 2. Body
|
||||
Draft per the SOP file (WHY-first summary, then changes under the 🌟 Features / 🛠️ Improvements / 🐛 Bug Fixes / 🧹 Chores categories that apply, **Testing Performed** section when tests were added/changed).
|
||||
|
||||
**Screenshots** (UI changes want them): commit the PNGs on the branch, push, then embed with commit-pinned raw URLs so they render in the PR body immediately:
|
||||
```
|
||||
https://raw.githubusercontent.com/<owner>/<repo>/<full-commit-sha>/<path/to/img.png>
|
||||
```
|
||||
Pin to the SHA that contains the image, not the branch name.
|
||||
|
||||
## 3. Push and open
|
||||
```bash
|
||||
git push -u origin HEAD
|
||||
gh pr create --draft --base <target> --title "<type>: <summary>" --body-file <(printf '%s' "$BODY")
|
||||
```
|
||||
- `--base` is the target branch chosen in pre-flight (`main` unless told otherwise) — always pass it explicitly; omitting it silently uses the repo default.
|
||||
- Draft by default; only `--ready` if explicitly asked.
|
||||
- End the body with: `🤖 Generated with [Claude Code](https://claude.com/claude-code)`
|
||||
- Report the PR URL as a markdown link.
|
||||
@@ -0,0 +1,88 @@
|
||||
---
|
||||
name: proto-bump
|
||||
description: Change the org.meshtastic:protobufs pin for Meshtastic-Android — either bump to a tagged release (mergeable) or track develop-SNAPSHOT for a preview (draft), adding/removing the transitive resolution-force hack as appropriate. Verifies with test/allTests (not just compile) via the gradle-runner subagent, audits the new field surface, and opens a PR. Use to consume new proto changes or to re-pin a SNAPSHOT draft onto a tag.
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# proto-bump
|
||||
|
||||
Changes the upstream Meshtastic protobufs Maven pin. Protobuf models are **not** generated in this repo — they come from `org.meshtastic:protobufs` (Square Wire–built KMP models), pinned in `gradle/libs.versions.toml`. A bump is a catalog edit + verification, never a hand-edit of generated proto.
|
||||
|
||||
> **Renovate already watches the catalog** for new *tagged* `org.meshtastic:protobufs` releases and opens the bump PR for you. Use this skill to **drive/finish** such a bump (verify, audit, adapt call sites), or — the part Renovate can't do — to track an unreleased `develop-SNAPSHOT`.
|
||||
|
||||
## Two modes
|
||||
|
||||
| | Mode A — tagged release | Mode B — develop-SNAPSHOT |
|
||||
|---|---|---|
|
||||
| Version | `X.Y.Z` | `develop-SNAPSHOT` |
|
||||
| Transitive force block | **absent** (remove if present) | **present** (add it) |
|
||||
| PR state | mergeable | **draft** (un-block when a tag ships) |
|
||||
| When | a release carries what you need | the change only exists on protobufs `develop` (precedent: #5790, #5834, lockdown re-port) |
|
||||
|
||||
Pick the mode from the argument / context. If the change you need isn't in any tag yet, it's Mode B.
|
||||
|
||||
## Mode A — bump to a tagged release (mergeable)
|
||||
|
||||
1. Read the current pin: `meshtastic-protobufs = "<…>"` in `gradle/libs.versions.toml`. Confirm the target is a real tag at https://github.com/meshtastic/protobufs/releases (or Maven Central), not a SNAPSHOT.
|
||||
2. Set `meshtastic-protobufs = "X.Y.Z"`.
|
||||
3. **Re-pin cleanup:** if the transitive force block (see below) is present in the root `build.gradle.kts`, **remove it** — a tagged protobufs is ordered correctly against transitive pins, so the force is unnecessary and misleading once on a release. Also re-check: is takpacket/mqtt now republished against this protobufs? If still pinning an older one, you may need to keep the force (note it in the PR).
|
||||
4. **Verify** (step "Verification" below) — including `test`/`allTests`.
|
||||
5. **Audit** the new additive surface (below).
|
||||
6. Open a normal (non-draft) PR.
|
||||
|
||||
## Mode B — track develop-SNAPSHOT (draft only)
|
||||
|
||||
1. Set `meshtastic-protobufs = "develop-SNAPSHOT"`.
|
||||
2. **Add the transitive force block** to the bottom of the root `build.gradle.kts` (exact text below). No repository change is needed — `settings.gradle.kts` already declares the Sonatype maven-snapshots repo (`snapshotsOnly()`) and JitPack (`https://jitpack.io`), which is where `develop-SNAPSHOT` resolves from.
|
||||
3. **Verify** — including `test`/`allTests` (this is the mode where skipping them bites; see below).
|
||||
4. **Audit** the new additive surface.
|
||||
5. Open the PR **as a draft**, stating the un-block condition: *"merge once protobufs `vX.Y.Z` is tagged; switch to Mode A (set the tag + remove the force block) first."*
|
||||
|
||||
## The transitive force block (why it exists, exact code)
|
||||
|
||||
`takpacket-sdk` (and the MQTT client) transitively pin a **tagged** `protobufs` (e.g. `2.7.25`). Gradle ranks `2.7.25` **above** `develop-SNAPSHOT` (a numeric component outranks the `develop` string qualifier), so the *test runtime* classpath silently downgrades to the tagged proto while the *common-metadata compile* uses the snapshot. The mismatch throws `NoSuchFieldError`/`NoSuchMethodError` on proto-generated classes **at test runtime** — and crucially `assembleDebug`/`detekt` do **not** catch it; only `test`/`allTests` do. The block forces every `org.meshtastic:protobufs*` variant to the snapshot so compile and runtime agree:
|
||||
|
||||
```kotlin
|
||||
// ─── TEMPORARY: protobufs develop-SNAPSHOT preview (PR #NNNN) ─────────────────────────────────────
|
||||
// We track the unreleased protobufs develop-SNAPSHOT. takpacket-sdk-jvm transitively pins a tagged
|
||||
// protobufs, and Gradle ranks the tag > develop-SNAPSHOT (a numeric part outranks the "develop"
|
||||
// string qualifier). That downgrades the test *runtime* classpath to the tag while the common-metadata
|
||||
// *compile* uses the snapshot, yielding NoSuchFieldError/NoSuchMethodError on the proto-generated
|
||||
// classes at test runtime (assembleDebug/detekt don't catch it; test/allTests do). Force every
|
||||
// protobufs* variant to the snapshot so compile and runtime agree. Safe while atak.proto is unchanged,
|
||||
// so takpacket's own message ABI stays compatible with the newer protobufs.
|
||||
// REMOVE once protobufs is tagged (Mode A) / takpacket + mqtt are republished against it.
|
||||
allprojects {
|
||||
configurations.all {
|
||||
resolutionStrategy.eachDependency {
|
||||
if (requested.group == "org.meshtastic" && requested.name.startsWith("protobufs")) {
|
||||
useVersion("develop-SNAPSHOT")
|
||||
because("preview #NNNN: override takpacket transitive protobufs pin")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Update `#NNNN` to the current PR. Remove this entire block when returning to a tagged release (Mode A step 3).
|
||||
|
||||
## Verification (don't skip test/allTests)
|
||||
|
||||
Dispatch the **gradle-runner** subagent (keep the heavy log out of context). The downgrade failure mode above is a *runtime* classpath problem invisible to compilation, so verification MUST exercise tests:
|
||||
|
||||
- `kmpSmokeCompile` + a broad compile of proto-consuming modules (`core:*`, `feature:*`) — catches *compile-time* breaking changes (renamed/removed fields, changed oneof shapes).
|
||||
- **`test` and `allTests`** — the only gate that catches the transitive runtime downgrade. Treat a green compile as necessary-but-not-sufficient.
|
||||
|
||||
Triage each compile/test failure: adapt this repo's call sites, or, if a break is unexpected, stop and report rather than papering over it.
|
||||
|
||||
## Audit the new additive surface (recommended)
|
||||
|
||||
New proto versions usually add fields/messages the app doesn't consume yet. Diff the new surface against current usage and list what became implementable. **Caveat from prior audits: verify each reference directly** — automated gap-lists for this repo have been wrong as often as right. Treat the list as candidates, not facts; don't implement them in this PR unless asked.
|
||||
|
||||
## PR + guardrails
|
||||
|
||||
- Write the PR per `.github/copilot-pull-request-instructions.md`: WHY-first; 🛠️ (or 🌟 if it unlocks a user-facing feature); link the upstream release notes (real URL only); "Testing Performed" = the gradle-runner run including `allTests`.
|
||||
- Never hand-edit or vendor generated proto — this repo consumes the Maven artifact only.
|
||||
- Keep the change minimal: a bump PR is a catalog edit + the force block (Mode B) + necessary call-site adaptations, not a feature.
|
||||
- Branch off `main` (the 2.8.0 line) unless told otherwise.
|
||||
- After a successful change, update the relevant memory pointer (protobufs-sdk-alignment / lora-region-preset-map) so the next session knows the new baseline.
|
||||
@@ -0,0 +1,328 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
# CodeRabbit config — see https://docs.coderabbit.ai/getting-started/yaml-configuration
|
||||
language: en-US
|
||||
|
||||
reviews:
|
||||
# chill = fewer nitpicks. CI already gates detekt/spotless/tests, and the
|
||||
# maintainers are experienced — we want CodeRabbit for substance, not lint noise.
|
||||
profile: chill
|
||||
high_level_summary: true
|
||||
poem: false
|
||||
# Don't burn reviews on WIP. This repo opens lots of draft PRs; review on "ready".
|
||||
# Skip Renovate dependency updates — CI gates dependencies; we review for substance, not every bump.
|
||||
auto_review:
|
||||
enabled: true
|
||||
drafts: false
|
||||
# Review once when the PR goes ready, then on demand via `@coderabbitai review`.
|
||||
# Re-reviewing every push turned 6-commit PRs into 8 review rounds, because each
|
||||
# fix commit reopened a full pass. Batch the fixes, push, then ask for one re-review.
|
||||
auto_incremental_review: false
|
||||
ignore_usernames:
|
||||
- renovate
|
||||
- renovate[bot]
|
||||
# Workflow-authored PRs (changelog updates, scheduled firmware/hardware/
|
||||
# translation bumps) — machine-generated content, nothing to review.
|
||||
- github-actions
|
||||
- github-actions[bot]
|
||||
ignore_title_keywords:
|
||||
- "chore: Scheduled updates"
|
||||
# Stop reviewing once a PR is closed.
|
||||
abort_on_close: true
|
||||
|
||||
path_filters:
|
||||
# Generated / huge / non-source — don't review, just noise + token burn.
|
||||
- "!**/build/**"
|
||||
- "!**/*.png"
|
||||
- "!**/*.webp"
|
||||
- "!**/firmware_releases.json"
|
||||
- "!**/emoji-data.json"
|
||||
- "!**/flatpak-sources.json"
|
||||
# Crowdin-managed translations — owned upstream, not hand-edited here.
|
||||
- "!**/values-*/strings.xml"
|
||||
# Spec Kit scaffolding — vendored tooling, not hand-maintained here.
|
||||
- "!.specify/**"
|
||||
|
||||
path_instructions:
|
||||
# Global review contract. Everything below this entry is area-specific detail;
|
||||
# this is what "a finding" means in this repo at all.
|
||||
- path: "**"
|
||||
instructions: >-
|
||||
Report problems only. Every comment must name a concrete defect with evidence in the diff.
|
||||
No praise, no style preferences, no speculative design feedback, no "consider extracting this"
|
||||
on code that works.
|
||||
|
||||
|
||||
FLAG these categories:
|
||||
|
||||
1. Bugs — logic errors, off-by-one, null dereference, a missing `await`/`join`, dropped
|
||||
cancellation, incorrect disposal order.
|
||||
|
||||
2. Security — credential or key exposure, injection, insecure defaults, PII/location/crypto-key
|
||||
material reaching a log sink or analytics payload.
|
||||
|
||||
3. Correctness — behaviour that contradicts the PR description, the linked issue, or an existing
|
||||
contract; a breaking public-API change with no justification.
|
||||
|
||||
4. Behavioural contract changes — when a type is replaced, removed, or refactored, diff the OLD
|
||||
implementation against the NEW one. Look for a removed `override`, a property that used to throw
|
||||
on invalid access and now returns a default, an exception type that changed, and call sites that
|
||||
depended on the removed type's specific behaviour.
|
||||
|
||||
5. Weakened invariants — validation quietly relaxed during a refactor. Kotlin shapes:
|
||||
`single()`/`first()` (throws) swapped for `firstOrNull()` (silently picks nothing);
|
||||
a deleted `require`/`check`/`error`; `!!` replaced by `?: <default>` so a broken state becomes a
|
||||
plausible value; an exhaustive `when` gaining an `else ->` branch that swallows new cases.
|
||||
|
||||
6. Missing error handling at system boundaries — unvalidated input from the radio, a peer, an
|
||||
MQTT broker, a deep link, or an intent extra. Do NOT flag missing null checks the Kotlin type
|
||||
system already guarantees.
|
||||
|
||||
7. Performance regressions — allocation in a hot path or a recomposition scope, N+1 database
|
||||
queries, `runBlocking`/`Thread.sleep`/`.get()` on a coroutine or UI path, blocking
|
||||
`getString()` on `Dispatchers.Default`, an unstable Compose parameter type that defeats skipping.
|
||||
|
||||
8. Concurrency — unguarded shared mutable state, a read-decide-write sequence spanning a suspend
|
||||
boundary, a mutex held across a suspending call, deadlock and lock-ordering risk.
|
||||
|
||||
9. Temporal coupling and initialisation safety — `lateinit var` paired with a separate
|
||||
`initialize()`/`start()` that a caller must remember, DI registrations that only work in one
|
||||
resolution order, any pattern where a forgotten call is a runtime crash with no compile-time
|
||||
signal.
|
||||
|
||||
10. Resource leaks — a `CoroutineScope` created and never cancelled, a `Closeable`/`AutoCloseable`
|
||||
outside `use {}`, a registered receiver/listener/callback with no matching unregister, a BLE or
|
||||
socket connection not closed on every exit path. Flag these even when the pattern was moved in
|
||||
from elsewhere.
|
||||
|
||||
11. Dead code and stale comments — a comment describing behaviour the code no longer has, an
|
||||
unused local or parameter, a materialising call whose result is never consumed.
|
||||
|
||||
12. Repository convention violations — see AGENTS.md and .skills/. Highest value: `java.*` or
|
||||
`android.*` in `commonMain`, hardcoded user-facing strings, `runCatching` in a suspend context
|
||||
instead of `safeCatching`, OkHttp instead of Ktor, hand-edits to generated or Crowdin-owned files.
|
||||
|
||||
13. Comment problems — a comment that contradicts the code, a workaround with no tracking link, a
|
||||
parser or protocol handler that omits the raw wire shape needed to read the edge cases, and
|
||||
privacy- or security-sensitive behaviour whose comment fails to explain scope and WHY. Do not ask
|
||||
for comments on obvious code.
|
||||
|
||||
|
||||
Do NOT flag: style already enforced by detekt, spotless, or .editorconfig; missing KDoc, unless a
|
||||
new public API is entirely undocumented; refactoring suggestions for code the PR did not touch;
|
||||
generated output (Wire protos, Crowdin locale files, baseline profiles, docs screenshots);
|
||||
missing tests for docs-only, comment-only, or mechanical-rename changes.
|
||||
|
||||
|
||||
Moved or extracted code counts as newly written. Review it on its merits and flag pre-existing
|
||||
defects that came along with it, labelled "pre-existing — good opportunity to fix during this
|
||||
refactor" so the author can weigh scope.
|
||||
|
||||
|
||||
One problem per comment. Cite the exact line, symbol, or condition. Give a fix direction — a
|
||||
snippet when the fix is not obvious. Never restate a finding already raised in an existing
|
||||
review thread.
|
||||
- path: "**/commonMain/**"
|
||||
instructions: >
|
||||
KMP common code. Flag any import of java.* or android.* — these break non-Android targets. Expect KMP equivalents instead (Okio, kotlinx Mutex/atomicfu, NumberFormatter.format() for floats).
|
||||
- path: "**/*.kt"
|
||||
instructions: >
|
||||
Flag leftover // ... existing code ... placeholders, and any logging of PII, location, or cryptographic keys.
|
||||
- path: "**/src/**/strings.xml"
|
||||
instructions: >
|
||||
New string resources must be alphabetically sorted (scripts/sort-strings.py). Flag out-of-order additions.
|
||||
- path: baselineprofile/
|
||||
instructions: Keep baseline profile generation tied to the `google` flavor and connected devices/emulators, and commit the generated profile output to `androidApp/src/google/generated/baselineProfiles/baseline-prof.txt`.
|
||||
- path: docs/
|
||||
instructions: Treat non-English locale folders as Crowdin-managed output; edit the English sources under `docs/en/` and register new pages through `feature/docs/` instead of hand-editing translated locale directories.
|
||||
- path: screenshot-tests/
|
||||
instructions: When updating docs screenshots, keep `docs-screenshots-manifest.txt` and `docs-screenshot-aliases.properties` in sync with the generated files, and rerun `copyDocsScreenshots` after regenerating screenshots.
|
||||
- path: docs-screenshots/
|
||||
instructions: Keep this module generate-only for documentation screenshots; do not add it to the CI validation gate that is reserved for `screenshot-tests`.
|
||||
- path: desktopApp/
|
||||
instructions: Keep desktop release ProGuard rules aligned with `androidApp/proguard-rules.pro`, and preserve the desktop-specific runtime wiring needed for `Dispatchers.Main` on JVM.
|
||||
- path: androidApp/
|
||||
instructions: Keep the Android app’s `MeshService` declaration and manifest wiring in sync with the implementation that lives in `core:service`.
|
||||
- path: core/service/
|
||||
instructions: Keep `RadioControllerImpl` composed from its sub-controllers via interface delegation; admin sends are fire-and-forget, and any config mutation must go through `editSettings { }` transactions.
|
||||
- path: feature/docs/
|
||||
instructions: Treat the Compose resources under `src/commonMain/composeResources/files/` as generated output from `/docs/en/**` and translated docs sync tasks; do not hand-edit those copied files.
|
||||
- path: feature/map/
|
||||
instructions: Route map access through the injected `CompositionLocal` provider contracts; do not depend directly on Google Maps or osmdroid from feature code.
|
||||
- path: feature/car/
|
||||
instructions: Run unit tests with `./gradlew :feature:car:testGoogleDebugUnitTest`, and keep Robolectric pinned to SDK 36 for this module.
|
||||
- path: core/database/
|
||||
instructions: >-
|
||||
Review focus: schema compatibility. A `@Database` version bump must ship a new
|
||||
`core/database/schemas/<n>.json` AND an (n-1)→n test under `androidHostTest` that inserts rows at
|
||||
the old version, migrates, and asserts row count and column values survive — not merely that the
|
||||
migration executes. A column going nullable must assert both that pre-existing values are retained
|
||||
and that the new NULL state is reachable. Flag `@Insert`+`@Update` pairs that should be `@Upsert`,
|
||||
single-row queries missing `LIMIT 1`, and N+1 patterns (a loop issuing single-row queries) that
|
||||
should be a chunked `WHERE IN`.
|
||||
- path: core/network/
|
||||
instructions: >-
|
||||
Review focus: request lifecycle. Timeouts and base URLs come from `HttpClientDefaults`; flag
|
||||
hardcoded timeouts or absolute URLs in callers. Check every failure path for cancellation
|
||||
propagation (`safeCatching`, not `runCatching`) and for a response body that is closed on error as
|
||||
well as success.
|
||||
- path: .github/workflows/
|
||||
instructions: >-
|
||||
Review focus: unintended side effects. Flag a change that widens a trigger (especially
|
||||
`pull_request_target` and anything granting write scopes to fork-authored code), a secret exposed
|
||||
to an untrusted context, a cache key that lets one job poison another, and an unpinned third-party
|
||||
action. Job-level `continue-on-error` or a removed `--fail`-style guard silently converts a broken
|
||||
gate into a green tick — flag it.
|
||||
- path: "**/*.gradle.kts"
|
||||
instructions: >-
|
||||
Review focus: build correctness over cleanliness. Several idioms here are load-bearing and look
|
||||
redundant — do not suggest removing an explicit dependency declaration, a duplicated exclusion, or
|
||||
an apparently no-op configuration block without evidence from the diff that it is dead. Flag
|
||||
changes that alter variant/flavor wiring, drop a keep rule, or make a task's inputs/outputs
|
||||
untracked (which silently disables caching and up-to-date checks).
|
||||
|
||||
# Every CodeRabbit tool is enabled by default, so this block only ever needs to
|
||||
# turn things OFF or configure them. detekt is off because CI owns it (Zero Lint
|
||||
# Tolerance gate) and duplicate comments were the noise we removed. The scanners
|
||||
# CI doesn't run — gitleaks, shellcheck, actionlint, zizmor, semgrep, trivy,
|
||||
# presidio (PII), buf (protobuf) — are already on by default; don't re-list them.
|
||||
tools:
|
||||
detekt:
|
||||
enabled: false
|
||||
# Custom AST rules mechanically enforce the recurring defect classes that prose
|
||||
# can't. See .coderabbit/ast-grep-rules/ and .skills/code-review/SKILL.md.
|
||||
# essential_rules stays on (default) — these are additive.
|
||||
ast-grep:
|
||||
rule_dirs:
|
||||
- ".coderabbit/ast-grep-rules"
|
||||
|
||||
# Auto-generated docstrings/tests/autofix are noisy for a repo with strict
|
||||
# human-authored KDoc and KMP-aware tests; leave finishing touches off.
|
||||
finishing_touches:
|
||||
docstrings:
|
||||
enabled: false
|
||||
unit_tests:
|
||||
enabled: false
|
||||
|
||||
# No KDoc-coverage mandate in this repo; the default warning-at-80% check
|
||||
# would nag every PR. PR titles are already linted by CI
|
||||
# (.github/workflows/pull-request-target.yml), so no title check here either.
|
||||
pre_merge_checks:
|
||||
docstrings:
|
||||
mode: "off"
|
||||
# Defect classes that survive review-by-prose because they are about what is
|
||||
# ABSENT from a diff — a sibling call site left unfixed, a test that would still
|
||||
# pass with the fix reverted, a regression nobody covered, a behaviour quietly
|
||||
# dropped while code moved files. A per-file reviewer never sees these; a
|
||||
# whole-PR check does. Warning, not error: all four are judgment calls and a
|
||||
# false positive must not block a merge.
|
||||
custom_checks:
|
||||
- name: "Sibling call sites and presence semantics"
|
||||
mode: "warning"
|
||||
instructions: >-
|
||||
When a diff changes how an absent value is represented — making a field nullable,
|
||||
removing a zero-guard, or adding a presence check — verify EVERY call site of that
|
||||
field was updated, not just the one the bug was reported against. Ambient temperature
|
||||
was fixed in NodeItem.kt while its sibling NodeItemCompact.kt kept the zero-guard.
|
||||
Name any unfixed sibling explicitly. Also flag a new field defaulting to 0 where 0 is
|
||||
a physically reachable value on that scale (RSSI, temperature, current, voltage,
|
||||
particulate concentration). Two exceptions, do NOT flag either: humidity, where 0 %RH
|
||||
is unreachable and the guard is intentional and tested; and the proto `rx_snr`, which
|
||||
has no presence upstream, so its 0f ambiguity cannot be fixed app-side. An app-level
|
||||
SNR field that IS nullable is still in scope.
|
||||
- name: "Tests prove the path, not the end state"
|
||||
mode: "warning"
|
||||
instructions: >-
|
||||
For each added or changed test, decide whether it would still pass if the production
|
||||
code it covers were reverted. Flag tests that seed a fake's backing store and then
|
||||
assert the value comes back, tests that assert only a collection's size rather than
|
||||
which items survived, and tests asserting emission ORDER under Dispatchers.Unconfined
|
||||
(not a stable contract). A test must assert the side effect only the intended path
|
||||
produces — a call counter, a request issued, a cache written.
|
||||
- name: "Regression coverage for changed behavior"
|
||||
mode: "warning"
|
||||
instructions: >-
|
||||
Do not stop at "tests pass" or "there are tests". For each non-trivial production change in
|
||||
the diff, work through four steps and report only the gaps.
|
||||
|
||||
|
||||
1. Changed behaviour — name the concrete code path, function, or configuration key from the
|
||||
diff whose behaviour changed.
|
||||
|
||||
2. Observable surfaces — which of these can see the change: public API, the mesh/radio
|
||||
protocol handling, persisted database rows, DataStore preferences, Compose UI state,
|
||||
navigation, notifications, the foreground service lifecycle, BLE/serial/TCP transport,
|
||||
MQTT, widgets, Android Auto, the desktop app, or R8/ProGuard-shaped release behaviour.
|
||||
|
||||
3. Regression risks — the specific ways this could break a working scenario: ordering and
|
||||
timing changes, reconnect and retry paths, process death and state restore, schema
|
||||
compatibility for rows written by an older build, cross-module call sites, flavor
|
||||
differences (google vs fdroid), and platform differences (Android vs JVM vs iOS targets).
|
||||
|
||||
4. Coverage gap — name the test that should exist and does not.
|
||||
|
||||
|
||||
A bug fix needs a test that FAILS without the fix. A test that only exercises the happy path,
|
||||
or a regenerated snapshot/golden file, does not prove a behaviour change. Flag a PR whose only
|
||||
test evidence is an updated screenshot golden, an updated Room schema JSON, or a regenerated
|
||||
baseline profile when the change is behavioural.
|
||||
|
||||
|
||||
State each finding as: impacted code path, the regression risk, the missing test shape. Be
|
||||
specific enough that the author can write the test from the comment.
|
||||
|
||||
|
||||
Do NOT ask for tests for: documentation-only or comment-only changes, mechanical renames,
|
||||
dependency version bumps, or refactors the diff shows to be behaviour-preserving. Do not
|
||||
demand a test category for a surface the change cannot reach — a `commonMain` formatting
|
||||
helper does not need a transport test.
|
||||
- name: "Moved code diffed against its original"
|
||||
mode: "warning"
|
||||
instructions: >-
|
||||
Applies when the diff deletes a type/function in one file and adds something similar
|
||||
elsewhere, or extracts code into a new file or module. Treat the moved code as newly written
|
||||
and compare the OLD implementation against the NEW one line by line.
|
||||
|
||||
|
||||
Flag any of the following that the move introduced silently:
|
||||
|
||||
- a removed `override`, or an interface member the new type no longer implements;
|
||||
|
||||
- a changed exception contract — something that threw now returns a default, or vice versa;
|
||||
|
||||
- a dropped `require`/`check`/`init` block validation, or a narrowed visibility widened;
|
||||
|
||||
- a default parameter value that changed, which alters every call site that omitted it;
|
||||
|
||||
- a nullability change on a numeric field, which is the presence-vs-sentinel-zero class;
|
||||
|
||||
- a lost `@Serializable`/`@Parcelize`/Koin annotation, or a scope change (`@Single` to
|
||||
`@Factory`) that alters instance lifetime;
|
||||
|
||||
- a coroutine scope, dispatcher, or `SharingStarted` policy that changed during the move.
|
||||
|
||||
|
||||
Then check the call sites of the removed declaration: every caller that relied on the old
|
||||
behaviour must still be correct. Name any caller the PR left on the old assumption.
|
||||
|
||||
|
||||
Pre-existing defects carried into the new location are in scope — label them "pre-existing —
|
||||
good opportunity to fix during this refactor" so the author can decide on scope. Do not flag
|
||||
a move that the diff shows to be genuinely mechanical.
|
||||
|
||||
knowledge_base:
|
||||
# Learnings are how a confirmed finding stops recurring on the next PR. Pin the
|
||||
# scope to this repo: the default `auto` already resolves to `local` for public
|
||||
# repos, but being explicit keeps it from shifting if visibility ever changes.
|
||||
learnings:
|
||||
scope: local
|
||||
# Feed CodeRabbit the same guidance human/AI contributors follow, including
|
||||
# the repo-specific .skills/ modules and Copilot path instructions it
|
||||
# wouldn't pick up by default.
|
||||
code_guidelines:
|
||||
enabled: true
|
||||
filePatterns:
|
||||
- "AGENTS.md"
|
||||
- "CLAUDE.md"
|
||||
- ".skills/**/SKILL.md"
|
||||
- ".github/copilot-instructions.md"
|
||||
- ".github/instructions/*.instructions.md"
|
||||
@@ -0,0 +1,27 @@
|
||||
# Recurring defect class A — see "Presence vs sentinel zero" in .skills/code-review/SKILL.md.
|
||||
#
|
||||
# The protobuf models are Wire-generated, so presence IS nullability: EnvironmentMetrics.temperature and friends are
|
||||
# declared `Float? = null` and there is no hasX() accessor. A `(x ?: 0f) != 0f` guard collapses "absent" and
|
||||
# "measured zero" into one state and silently discards a real reading. Use `x?.let { ... }` instead — see
|
||||
# `gatherSensors` in core/ui/.../NodeItem.kt for the reference pattern.
|
||||
#
|
||||
# Humidity is the deliberate exception: 0 %RH is not physically reachable, so relative_humidity / co2_humidity keep
|
||||
# their zero-guards and are not listed here. Same for barometric_pressure (0 hPa is a vacuum).
|
||||
id: no-float-metric-zero-sentinel
|
||||
language: kotlin
|
||||
severity: error
|
||||
message: "0 is a real reading on this scale — use `?.let { }` instead of a zero-guard."
|
||||
# Every spelling of the zero-float literal is listed: a reformat from `0f` to `0.0f` must not slip past the rule.
|
||||
rule:
|
||||
any:
|
||||
- pattern: "($X ?: 0f) != 0f"
|
||||
- pattern: "($X ?: 0f) == 0f"
|
||||
- pattern: "($X ?: 0F) != 0F"
|
||||
- pattern: "($X ?: 0F) == 0F"
|
||||
- pattern: "($X ?: 0.0f) != 0.0f"
|
||||
- pattern: "($X ?: 0.0f) == 0.0f"
|
||||
- pattern: "($X ?: 0.0F) != 0.0F"
|
||||
- pattern: "($X ?: 0.0F) == 0.0F"
|
||||
constraints:
|
||||
X:
|
||||
regex: "(temperature|voltage|current|soil_moisture)$"
|
||||
@@ -0,0 +1,20 @@
|
||||
# Recurring defect class A — see "Presence vs sentinel zero" in .skills/code-review/SKILL.md.
|
||||
#
|
||||
# 0 dBm is the STRONGEST value on the RSSI scale, so defaulting a missing reading to 0 renders an unknown signal as an
|
||||
# excellent one. Keep the value nullable end-to-end and let `MetricFormatter.rssi(null)` render an em dash.
|
||||
#
|
||||
# Scope: this rule targets DEFAULTING a live reading (`?: 0`), not comparing a stored one. A `rssi == 0` test against
|
||||
# persisted data can be legitimate migration handling — `Reaction.kt` reads pre-schema-51 rows that stored 0 where the
|
||||
# column is now nullable, so there a 0 really is indistinguishable from "no reading". Broadening this rule to `== 0`
|
||||
# would flag that documented exception and nothing else, so it deliberately stops at the `?: 0` form.
|
||||
id: no-rssi-zero-default
|
||||
language: kotlin
|
||||
severity: error
|
||||
message: "0 dBm is the strongest RSSI, not \"unknown\" — keep the value nullable."
|
||||
rule:
|
||||
any:
|
||||
- pattern: "$X ?: 0"
|
||||
- pattern: "($X ?: 0) != 0"
|
||||
constraints:
|
||||
X:
|
||||
regex: "(?i)rssi$"
|
||||
@@ -0,0 +1,19 @@
|
||||
# Class A of "Recurring Defect Classes" in .skills/code-review/SKILL.md, enforced mechanically.
|
||||
# 0 is a real reading on these scales, so a zero-guard silently drops a genuine measurement.
|
||||
# Humidity is deliberately excluded: 0 %RH is not physically reachable, and
|
||||
# EnvironmentMetricsForGraphingTest.humidity_zeroFilteredOut asserts that guard on purpose.
|
||||
id: presence-vs-sentinel-zero-float
|
||||
language: kotlin
|
||||
severity: warning
|
||||
message: >-
|
||||
Zero-guard on a zero-inclusive scale drops real readings. 0 is valid for temperature,
|
||||
current, voltage and soil moisture, so `(x ?: 0f) != 0f` hides a genuine measurement and
|
||||
conflates it with "not reported". Use a null check — `x?.let { }` — as NodeItem.kt does for
|
||||
ambient temperature. Humidity is the one legitimate exception and is excluded from this rule.
|
||||
note: >-
|
||||
See .skills/code-review/SKILL.md, "Recurring Defect Classes", class A.
|
||||
rule:
|
||||
pattern: "($FIELD ?: 0f) != 0f"
|
||||
constraints:
|
||||
FIELD:
|
||||
regex: "\\.(temperature|soil_temperature|co2_temperature|current|voltage|soil_moisture)$"
|
||||
@@ -0,0 +1,33 @@
|
||||
# Class A of "Recurring Defect Classes" in .skills/code-review/SKILL.md, enforced mechanically.
|
||||
# Presence in this repo is nullability: the models are Wire-generated, so there is no hasX()
|
||||
# accessor — an optional field is simply a nullable type. The proto `rx_rssi` only becomes `Int?`
|
||||
# in protobufs 2.7.26.138 (PR #6498, still open at time of writing); on the pinned 2.7.26.130 it
|
||||
# is `Int = 0`. So this rule currently guards APP-level nullable rssi (parameters, state, BLE
|
||||
# advertisement values) and becomes a proto regression guard once #6498 lands.
|
||||
#
|
||||
# INT LITERALS ONLY, deliberately. RSSI is an integer dBm value everywhere in this repo —
|
||||
# proto `rx_rssi: Int`, `Node.rssi: Int`, `MetricFormatter.rssi(value: Int)` — and there is no
|
||||
# Float-typed rssi declaration anywhere. Adding `?: 0f` / `takeIf { it != 0f }` variants here
|
||||
# would be dead patterns. Float sentinels on genuinely-Float metrics are the float rule's job.
|
||||
# RSSI only, deliberately NOT snr: rx_snr still has no proto presence upstream, so a 0f guard there
|
||||
# is genuinely ambiguous and unfixable app-side — flagging it would be pure noise on code nobody can
|
||||
# correct. Also excludes `replyId ?: 0` and `packet.from.takeIf { it != 0 }`, which are legitimate
|
||||
# because 0 there means "unset", not "a measurement of zero".
|
||||
id: presence-vs-sentinel-zero-signal
|
||||
language: kotlin
|
||||
severity: warning
|
||||
message: >-
|
||||
Zero-default on a signal metric conflates "not reported" with a real reading. 0 dBm is a
|
||||
valid RSSI (SX126x reports exactly 0, SX127x can go positive) and on a signal-strength scale
|
||||
it renders as the STRONGEST value — so an unknown signal displays as excellent. Keep the type
|
||||
nullable and branch on null, rendering absence explicitly rather than substituting a number.
|
||||
note: >-
|
||||
See .skills/code-review/SKILL.md, "Recurring Defect Classes", class A. rx_snr still has no
|
||||
proto presence upstream, so its 0f ambiguity cannot be fixed app-side — do not re-raise that.
|
||||
rule:
|
||||
any:
|
||||
- pattern: "$RECV.takeIf { it != 0 }"
|
||||
- pattern: "$RECV ?: 0"
|
||||
constraints:
|
||||
RECV:
|
||||
regex: "(?i)rssi$"
|
||||
+41
-14
@@ -1,27 +1,54 @@
|
||||
# Ignore build artifacts and generated files from Copilot indexing
|
||||
# Meshtastic Android - GitHub Copilot Ignore List
|
||||
# This saves context window tokens and prevents Copilot from hallucinating off of minified code.
|
||||
|
||||
# Build directories
|
||||
# ── Build & Generated ─────────────────────────────────────────────────────────
|
||||
**/build/**
|
||||
.gradle/
|
||||
.idea/
|
||||
|
||||
# Android generated files
|
||||
.kotlin/
|
||||
**/generated/**
|
||||
.cxx/
|
||||
.externalNativeBuild/
|
||||
|
||||
# Git history & worktrees
|
||||
.git/
|
||||
.worktrees/
|
||||
|
||||
# Protobuf (Prevents Copilot from suggesting raw protobuf byte buffers)
|
||||
core/proto/
|
||||
|
||||
# Environment and secrets
|
||||
# ── IDE & Environment ─────────────────────────────────────────────────────────
|
||||
.idea/
|
||||
.run/
|
||||
.claude/
|
||||
.gemini/
|
||||
.jdk
|
||||
local.properties
|
||||
secrets.properties
|
||||
*.jks
|
||||
.DS_Store
|
||||
|
||||
# Agent References (Prevents pollution of project space with external code)
|
||||
# ── Agent Artifacts (Large volumes of logs/images) ───────────────────────────
|
||||
.agent_artifacts/
|
||||
# Note: .agent_plans/ is NOT ignored to maintain implementation context.
|
||||
.agent_refs/
|
||||
tmp/
|
||||
*.log
|
||||
|
||||
# ── Binary Assets & Media ─────────────────────────────────────────────────────
|
||||
**/*.png
|
||||
**/*.jpg
|
||||
**/*.jpeg
|
||||
**/*.webp
|
||||
**/*.svg
|
||||
**/*.ico
|
||||
**/*.gif
|
||||
**/*.mp3
|
||||
**/*.wav
|
||||
**/*.ogg
|
||||
**/*.pdf
|
||||
**/*.ttf
|
||||
**/*.otf
|
||||
**/*.jar
|
||||
**/*.aar
|
||||
**/*.apk
|
||||
|
||||
# ── External & Submodules ─────────────────────────────────────────────────────
|
||||
core/proto/
|
||||
|
||||
# ── Resources ────────────────────────────────────────────────────────────────
|
||||
# Ignore translations (reduces churn and indexing tokens)
|
||||
**/values-*/strings.xml
|
||||
**/composeResources/**/values*/*.xml
|
||||
@@ -0,0 +1,13 @@
|
||||
# Mark only generated/derived XML as linguist-generated to reduce Copilot PR summary costs.
|
||||
# Hand-edited resources (layouts, string values) are intentionally excluded so they remain
|
||||
# visible in diffs and code review.
|
||||
**/composeResources/**/values-*/*.xml linguist-generated=true
|
||||
.skills/compose-ui/strings-index.txt linguist-generated=true
|
||||
|
||||
# Ensure assets are treated as binary
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.webp binary
|
||||
*.mp3 binary
|
||||
*.wav binary
|
||||
*.ogg binary
|
||||
@@ -7,9 +7,37 @@ inputs:
|
||||
jdk_distribution:
|
||||
description: 'JDK distribution (temurin or jetbrains)'
|
||||
default: 'temurin'
|
||||
install_jetbrains_jdk:
|
||||
description: 'Also install JetBrains JDK 25 for Compose Desktop toolchain resolution'
|
||||
default: 'false'
|
||||
gradle_encryption_key:
|
||||
description: 'Encryption key for Gradle remote cache'
|
||||
required: false
|
||||
develocity_access_key:
|
||||
description: 'Access key for the OSS Community Develocity Instance (Build Scan publishing and remote cache writes)'
|
||||
required: false
|
||||
job_summary_pr_comment:
|
||||
description: |
|
||||
Job summary as a PR comment: never | always | on-failure. Needs `pull-requests: write`;
|
||||
no-ops on fork PRs (read-only token).
|
||||
default: 'never'
|
||||
dependency_graph:
|
||||
description: |
|
||||
Dependency graph mode: disabled | generate | generate-and-submit | generate-and-upload |
|
||||
download-and-submit. Submit needs `contents: write`. Never combine with
|
||||
cache_configuration_cache — a CC-hit build generates NO graph.
|
||||
default: 'disabled'
|
||||
cache_configuration_cache:
|
||||
description: |
|
||||
Persist .gradle/configuration-cache. Opt-in: only pays off when config inputs are
|
||||
commit-stable (the VERSION_CODE-pinned jobs). Real-versionCode jobs would restore,
|
||||
miss, and never re-save.
|
||||
default: 'false'
|
||||
cache_key_suffix:
|
||||
description: |
|
||||
Extra CC-key discriminator for matrix legs running different task graphs (test-shards).
|
||||
Runner-only matrices don't need it — os/arch are already in the key.
|
||||
default: ''
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
@@ -20,21 +48,108 @@ runs:
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/actions/wrapper-validation@v6
|
||||
|
||||
- name: Set up JDK 21
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '21'
|
||||
java-version: '25'
|
||||
distribution: ${{ inputs.jdk_distribution }}
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Restore cached JetBrains JDK 25
|
||||
if: inputs.install_jetbrains_jdk == 'true'
|
||||
id: cache-jbr
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ runner.tool_cache }}/Java_JetBrains_jdk
|
||||
key: jbr-25-${{ runner.os }}-${{ runner.arch }}
|
||||
|
||||
- name: Set up JetBrains JDK 25 (for Compose Desktop)
|
||||
if: inputs.install_jetbrains_jdk == 'true' && steps.cache-jbr.outputs.cache-hit != 'true'
|
||||
id: setup-jbr
|
||||
continue-on-error: true
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: '25'
|
||||
distribution: 'jetbrains'
|
||||
check-latest: false
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: JBR setup skipped or failed — Gradle will auto-provision via Foojay
|
||||
if: inputs.install_jetbrains_jdk == 'true' && steps.cache-jbr.outputs.cache-hit != 'true' && steps.setup-jbr.outcome == 'failure'
|
||||
shell: bash
|
||||
run: echo "::warning::JBR setup-java failed (likely GitHub API rate limit). Gradle will auto-provision JBR via Foojay toolchain resolver."
|
||||
|
||||
# Kotlin/Native lives in ~/.konan, OUTSIDE the Gradle home that
|
||||
# setup-gradle caches — without this every job re-downloads the K/N
|
||||
# toolchain (downloadKotlinNativeDistribution) and the iOS-target compile
|
||||
# tasks it feeds miss the build cache, while the identical tasks hit
|
||||
# FROM-CACHE locally. Keyed on the version catalog: over-invalidates on
|
||||
# unrelated bumps, but konan re-downloads are exactly what it prevents.
|
||||
- name: Cache Kotlin/Native toolchain
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.konan
|
||||
key: konan-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('gradle/libs.versions.toml') }}
|
||||
|
||||
# Robolectric downloads its android-all-instrumented jars to the local Maven
|
||||
# repo, which lives outside the Gradle User Home and so isn't covered by
|
||||
# setup-gradle's caching. (This was previously attempted via
|
||||
# gradle-home-cache-includes, but those entries resolve relative to the
|
||||
# Gradle User Home — the `~/.m2/...` line expanded to the literal path
|
||||
# `<gradle-home>/~/.m2/...` and never matched anything.) Keyed on the
|
||||
# version catalog, which pins the Robolectric version.
|
||||
- name: Cache Robolectric android-all jars
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository/org/robolectric
|
||||
key: robolectric-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('gradle/libs.versions.toml') }}
|
||||
restore-keys: |
|
||||
robolectric-${{ runner.os }}-${{ runner.arch }}-
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/actions/setup-gradle@v6
|
||||
with:
|
||||
cache-read-only: ${{ inputs.cache_read_only }}
|
||||
cache-encryption-key: ${{ inputs.gradle_encryption_key }}
|
||||
cache-cleanup: on-success
|
||||
develocity-access-key: ${{ inputs.develocity_access_key }}
|
||||
# Cache cleanup is broken on Windows with setup-gradle v6's "enhanced
|
||||
# caching" provider: it deletes the whole Gradle User Home instead of
|
||||
# just unused entries (1739 files "excluded from the cache" here vs 25
|
||||
# on Linux), so every subsequent save fails path validation and the
|
||||
# Windows cache is never written. Upstream: gradle/actions#1013.
|
||||
# Skip cleanup there — a slightly larger cache beats no cache at all.
|
||||
cache-cleanup: ${{ runner.os == 'Windows' && 'never' || 'on-success' }}
|
||||
add-job-summary: always
|
||||
add-job-summary-as-pr-comment: ${{ inputs.job_summary_pr_comment }}
|
||||
dependency-graph: ${{ inputs.dependency_graph }}
|
||||
gradle-home-cache-includes: |
|
||||
caches
|
||||
notifications
|
||||
~/.m2/repository/org/robolectric
|
||||
# `caches` sweeps in caches/build-cache-1, the LOCAL build cache — which since the
|
||||
# Develocity onboarding (#6531) duplicates the remote cache at
|
||||
# community.develocity.cloud. Shipping both means paying tarball upload/download
|
||||
# for entries the remote already serves, on every one of the ~14 jobs that call
|
||||
# this action, against a 10 GB repo-wide Actions cache quota that evicts under
|
||||
# pressure. Dependency and transform caches (the expensive part) still ship.
|
||||
# Effect is measurable in the Build Scan cache-performance view; revert if remote
|
||||
# cache latency turns out to cost more than the restore it replaced.
|
||||
gradle-home-cache-excludes: |
|
||||
caches/build-cache-1
|
||||
|
||||
# CC entries live in the project dir; setup-gradle only caches the Gradle User Home —
|
||||
# Develocity measured 100% CC miss (~52s config/build) before this. Runs after Setup
|
||||
# Gradle so GRADLE_ENCRYPTION_KEY is exported. No sha in the key: unchanged build files
|
||||
# hit exactly and skip the save; restore-keys is the self-heal for stale entries.
|
||||
# The wrapper hash is its own restore-key segment: a stale same-version entry is a
|
||||
# graceful CC miss, but an entry written by a DIFFERENT Gradle version can crash
|
||||
# fingerprint deserialization outright (seen on 9.6.1 entries under 9.7.0).
|
||||
- name: Cache Gradle configuration-cache
|
||||
# Gates: opted in; key present (undecryptable otherwise — keeps keyless fork PRs off
|
||||
# these entries); not merge_group (throwaway cache scope).
|
||||
if: inputs.cache_configuration_cache == 'true' && inputs.gradle_encryption_key != '' && github.event_name != 'merge_group'
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ github.workspace }}/.gradle/configuration-cache
|
||||
key: gradle-cc-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}${{ inputs.cache_key_suffix && format('-{0}', inputs.cache_key_suffix) || '' }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('settings.gradle.kts', '**/build.gradle.kts', 'build-logic/**', 'gradle/*.gradle', 'gradle/libs.versions.toml', 'gradle.properties', 'config.properties', '.github/ci-gradle.properties') }}
|
||||
restore-keys: |
|
||||
gradle-cc-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}${{ inputs.cache_key_suffix && format('-{0}', inputs.cache_key_suffix) || '' }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}-
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
description: Generate or update the active agent governance file
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: agent-governance -->
|
||||
<!-- Config: .specify/extensions/agent-governance/ -->
|
||||
# Agent Governance Generate/Update
|
||||
|
||||
## Input
|
||||
|
||||
$ARGUMENTS
|
||||
|
||||
## Output
|
||||
|
||||
- Active agent platform governance file.
|
||||
- Managed `SPECKIT GOVERNANCE` section.
|
||||
- `.specify/memory/agent-governance.md`: internal cache.
|
||||
|
||||
## Procedure
|
||||
|
||||
1. Require `.specify/`.
|
||||
2. Resolve target:
|
||||
- `.specify/init-options.json` `context_file`
|
||||
- `.specify/integration.json` `default_integration` or `integration`
|
||||
- `AGENTS.md`
|
||||
3. Create internal cache when missing.
|
||||
4. Generate target file when missing.
|
||||
5. Update only the managed section when target exists.
|
||||
6. Use existing managed section as refresh source.
|
||||
7. Distill detected repository areas into action rules.
|
||||
- depth: 2
|
||||
- include hidden and cache directories
|
||||
8. Preserve content outside managed markers.
|
||||
9. Preserve managed markers verbatim.
|
||||
10. Run:
|
||||
|
||||
```bash
|
||||
uv run python .specify/extensions/agent-governance/scripts/refresh_agent_governance.py
|
||||
```
|
||||
|
||||
## Report
|
||||
|
||||
- target governance file
|
||||
- generated or updated
|
||||
- review target
|
||||
- internal cache status
|
||||
- captured evidence when cache is created
|
||||
@@ -0,0 +1,249 @@
|
||||
---
|
||||
description: Perform a non-destructive cross-artifact consistency and quality analysis across spec.md, plan.md, and tasks.md after task generation.
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before analysis)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_analyze` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Goal.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Goal
|
||||
|
||||
Identify inconsistencies, duplications, ambiguities, and underspecified items across the three core artifacts (`spec.md`, `plan.md`, `tasks.md`) before implementation. This command MUST run only after `/speckit.tasks` has successfully produced a complete `tasks.md`.
|
||||
|
||||
## Operating Constraints
|
||||
|
||||
**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
|
||||
|
||||
**Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this analysis scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, or tasks—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/speckit.analyze`.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### 1. Initialize Analysis Context
|
||||
|
||||
Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` once from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS. Derive absolute paths:
|
||||
|
||||
- SPEC = FEATURE_DIR/spec.md
|
||||
- PLAN = FEATURE_DIR/plan.md
|
||||
- TASKS = FEATURE_DIR/tasks.md
|
||||
|
||||
Abort with an error message if any required file is missing (instruct the user to run missing prerequisite command).
|
||||
For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
### 2. Load Artifacts (Progressive Disclosure)
|
||||
|
||||
Load only the minimal necessary context from each artifact:
|
||||
|
||||
**From spec.md:**
|
||||
|
||||
- Overview/Context
|
||||
- Functional Requirements
|
||||
- Success Criteria (measurable outcomes — e.g., performance, security, availability, user success, business impact)
|
||||
- User Stories
|
||||
- Edge Cases (if present)
|
||||
|
||||
**From plan.md:**
|
||||
|
||||
- Architecture/stack choices
|
||||
- Data Model references
|
||||
- Phases
|
||||
- Technical constraints
|
||||
|
||||
**From tasks.md:**
|
||||
|
||||
- Task IDs
|
||||
- Descriptions
|
||||
- Phase grouping
|
||||
- Parallel markers [P]
|
||||
- Referenced file paths
|
||||
|
||||
**From constitution:**
|
||||
|
||||
- Load `.specify/memory/constitution.md` for principle validation
|
||||
|
||||
### 3. Build Semantic Models
|
||||
|
||||
Create internal representations (do not include raw artifacts in output):
|
||||
|
||||
- **Requirements inventory**: For each Functional Requirement (FR-###) and Success Criterion (SC-###), record a stable key. Use the explicit FR-/SC- identifier as the primary key when present, and optionally also derive an imperative-phrase slug for readability (e.g., "User can upload file" → `user-can-upload-file`). Include only Success Criteria items that require buildable work (e.g., load-testing infrastructure, security audit tooling), and exclude post-launch outcome metrics and business KPIs (e.g., "Reduce support tickets by 50%").
|
||||
- **User story/action inventory**: Discrete user actions with acceptance criteria
|
||||
- **Task coverage mapping**: Map each task to one or more requirements or stories (inference by keyword / explicit reference patterns like IDs or key phrases)
|
||||
- **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements
|
||||
|
||||
### 4. Detection Passes (Token-Efficient Analysis)
|
||||
|
||||
Focus on high-signal findings. Limit to 50 findings total; aggregate remainder in overflow summary.
|
||||
|
||||
#### A. Duplication Detection
|
||||
|
||||
- Identify near-duplicate requirements
|
||||
- Mark lower-quality phrasing for consolidation
|
||||
|
||||
#### B. Ambiguity Detection
|
||||
|
||||
- Flag vague adjectives (fast, scalable, secure, intuitive, robust) lacking measurable criteria
|
||||
- Flag unresolved placeholders (TODO, TKTK, ???, `<placeholder>`, etc.)
|
||||
|
||||
#### C. Underspecification
|
||||
|
||||
- Requirements with verbs but missing object or measurable outcome
|
||||
- User stories missing acceptance criteria alignment
|
||||
- Tasks referencing files or components not defined in spec/plan
|
||||
|
||||
#### D. Constitution Alignment
|
||||
|
||||
- Any requirement or plan element conflicting with a MUST principle
|
||||
- Missing mandated sections or quality gates from constitution
|
||||
|
||||
#### E. Coverage Gaps
|
||||
|
||||
- Requirements with zero associated tasks
|
||||
- Tasks with no mapped requirement/story
|
||||
- Success Criteria requiring buildable work (performance, security, availability) not reflected in tasks
|
||||
|
||||
#### F. Inconsistency
|
||||
|
||||
- Terminology drift (same concept named differently across files)
|
||||
- Data entities referenced in plan but absent in spec (or vice versa)
|
||||
- Task ordering contradictions (e.g., integration tasks before foundational setup tasks without dependency note)
|
||||
- Conflicting requirements (e.g., one requires Next.js while other specifies Vue)
|
||||
|
||||
### 5. Severity Assignment
|
||||
|
||||
Use this heuristic to prioritize findings:
|
||||
|
||||
- **CRITICAL**: Violates constitution MUST, missing core spec artifact, or requirement with zero coverage that blocks baseline functionality
|
||||
- **HIGH**: Duplicate or conflicting requirement, ambiguous security/performance attribute, untestable acceptance criterion
|
||||
- **MEDIUM**: Terminology drift, missing non-functional task coverage, underspecified edge case
|
||||
- **LOW**: Style/wording improvements, minor redundancy not affecting execution order
|
||||
|
||||
### 6. Produce Compact Analysis Report
|
||||
|
||||
Output a Markdown report (no file writes) with the following structure:
|
||||
|
||||
## Specification Analysis Report
|
||||
|
||||
| ID | Category | Severity | Location(s) | Summary | Recommendation |
|
||||
|----|----------|----------|-------------|---------|----------------|
|
||||
| A1 | Duplication | HIGH | spec.md:L120-134 | Two similar requirements ... | Merge phrasing; keep clearer version |
|
||||
|
||||
(Add one row per finding; generate stable IDs prefixed by category initial.)
|
||||
|
||||
**Coverage Summary Table:**
|
||||
|
||||
| Requirement Key | Has Task? | Task IDs | Notes |
|
||||
|-----------------|-----------|----------|-------|
|
||||
|
||||
**Constitution Alignment Issues:** (if any)
|
||||
|
||||
**Unmapped Tasks:** (if any)
|
||||
|
||||
**Metrics:**
|
||||
|
||||
- Total Requirements
|
||||
- Total Tasks
|
||||
- Coverage % (requirements with >=1 task)
|
||||
- Ambiguity Count
|
||||
- Duplication Count
|
||||
- Critical Issues Count
|
||||
|
||||
### 7. Provide Next Actions
|
||||
|
||||
At end of report, output a concise Next Actions block:
|
||||
|
||||
- If CRITICAL issues exist: Recommend resolving before `/speckit.implement`
|
||||
- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions
|
||||
- Provide explicit command suggestions: e.g., "Run /speckit.specify with refinement", "Run /speckit.plan to adjust architecture", "Manually edit tasks.md to add coverage for 'performance-metrics'"
|
||||
|
||||
### 8. Offer Remediation
|
||||
|
||||
Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.)
|
||||
|
||||
### 9. Check for extension hooks
|
||||
|
||||
After reporting, check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_analyze` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Operating Principles
|
||||
|
||||
### Context Efficiency
|
||||
|
||||
- **Minimal high-signal tokens**: Focus on actionable findings, not exhaustive documentation
|
||||
- **Progressive disclosure**: Load artifacts incrementally; don't dump all content into analysis
|
||||
- **Token-efficient output**: Limit findings table to 50 rows; summarize overflow
|
||||
- **Deterministic results**: Rerunning without changes should produce consistent IDs and counts
|
||||
|
||||
### Analysis Guidelines
|
||||
|
||||
- **NEVER modify files** (this is read-only analysis)
|
||||
- **NEVER hallucinate missing sections** (if absent, report them accurately)
|
||||
- **Prioritize constitution violations** (these are always CRITICAL)
|
||||
- **Use examples over exhaustive rules** (cite specific instances, not generic patterns)
|
||||
- **Report zero issues gracefully** (emit success report with coverage statistics)
|
||||
|
||||
## Context
|
||||
|
||||
$ARGUMENTS
|
||||
@@ -0,0 +1,113 @@
|
||||
---
|
||||
description: Generate spec-kit configuration tailored to the existing codebase
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: brownfield -->
|
||||
<!-- Config: .specify/extensions/brownfield/ -->
|
||||
# Bootstrap Spec-Kit
|
||||
|
||||
Generate a customized spec-kit configuration for an existing codebase. Uses the project profile from `/speckit.brownfield.scan` (or performs a scan if none exists) to create a constitution, templates, and agent configuration that match the project's actual architecture, tech stack, and conventions.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty). The user may specify preferences (e.g., "strict TDD", "minimal constitution"), a target directory for a monorepo module, or request specific template customizations.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Verify the current directory is a git repository
|
||||
2. Verify a spec-kit project exists by checking for `.specify/` directory (run `specify init` first if missing)
|
||||
3. Check if a project profile exists from a previous scan — if not, run a scan first
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Load or generate project profile**: Check if `/speckit.brownfield.scan` has been run:
|
||||
- If a project profile exists, use it
|
||||
- If not, perform an inline scan to gather tech stack, architecture, and conventions
|
||||
- Confirm the profile with the user before proceeding
|
||||
|
||||
2. **Generate constitution**: Create `.specify/memory/constitution.md` tailored to the project:
|
||||
|
||||
The constitution **MUST** include:
|
||||
- **Project identity**: Name, purpose, primary language(s), architecture pattern
|
||||
- **Code boundaries**: Which directories contain which types of code (e.g., "frontend code lives in `client/`, backend in `server/`")
|
||||
- **Naming conventions**: File naming, variable naming, branch naming as detected
|
||||
- **Testing requirements**: Test framework, test location, coverage expectations
|
||||
- **Dependency rules**: How modules depend on each other, what imports are allowed
|
||||
- **Quality gates**: Linting, formatting, CI checks that must pass
|
||||
|
||||
The constitution **MUST NOT**:
|
||||
- Override existing project standards without user confirmation
|
||||
- Invent conventions that don't exist in the codebase
|
||||
- Include generic boilerplate unrelated to the actual project
|
||||
|
||||
3. **Customize spec template**: Modify `.specify/templates/spec-template.md` to reflect the project:
|
||||
- Add project-specific sections (e.g., "Database Migrations" for projects with ORMs)
|
||||
- Include architecture-aware requirements (e.g., "Frontend Requirements" and "API Requirements" for full-stack projects)
|
||||
- Reference actual module paths instead of generic placeholders
|
||||
|
||||
4. **Customize plan template**: Modify `.specify/templates/plan-template.md` to reflect the project:
|
||||
- Include module-aware implementation sections (e.g., separate phases for frontend/backend)
|
||||
- Reference actual test frameworks and build tools
|
||||
- Include project-specific complexity factors
|
||||
|
||||
5. **Customize tasks template**: Modify `.specify/templates/tasks-template.md` to reflect the project:
|
||||
- Task phases should map to the project's actual module structure
|
||||
- Include project-specific setup tasks (e.g., database migration, dependency install)
|
||||
- Reference actual test commands (e.g., `npm test`, `pytest`, `go test ./...`)
|
||||
|
||||
6. **Generate AGENTS.md** (if multi-module): For monorepos and multi-module projects:
|
||||
- Define agent boundaries per module
|
||||
- Specify which agent owns which directories
|
||||
- Set up inter-agent communication rules
|
||||
|
||||
7. **Present changes**: Show the user what will be created or modified:
|
||||
|
||||
```markdown
|
||||
# Bootstrap Plan
|
||||
|
||||
| File | Action | Description |
|
||||
|------|--------|-------------|
|
||||
| `.specify/memory/constitution.md` | Create | Project-specific constitution with detected conventions |
|
||||
| `.specify/templates/spec-template.md` | Modify | Add project-specific sections (Database Migrations, API Contract) |
|
||||
| `.specify/templates/plan-template.md` | Modify | Add module-aware phases (frontend, backend, shared) |
|
||||
| `.specify/templates/tasks-template.md` | Modify | Add actual test commands and build steps |
|
||||
| `AGENTS.md` | Create | Agent boundaries for frontend and backend modules |
|
||||
|
||||
Proceed with bootstrap? (confirm before writing)
|
||||
```
|
||||
|
||||
8. **Execute bootstrap**: After user confirmation, write all files.
|
||||
|
||||
9. **Report**:
|
||||
|
||||
```markdown
|
||||
# Bootstrap Complete
|
||||
|
||||
| Artifact | Status |
|
||||
|----------|--------|
|
||||
| Constitution | ✅ Created — 12 rules from detected conventions |
|
||||
| Spec template | ✅ Customized — added Database Migrations, API Contract sections |
|
||||
| Plan template | ✅ Customized — frontend/backend phase split |
|
||||
| Tasks template | ✅ Customized — actual test commands included |
|
||||
| AGENTS.md | ✅ Created — 2 agents (frontend, backend) |
|
||||
|
||||
## Next Steps
|
||||
- Review `.specify/memory/constitution.md` and adjust any rules
|
||||
- Run `/speckit.brownfield.validate` to verify configuration matches project
|
||||
- Run `/speckit.brownfield.migrate` to reverse-engineer specs for existing features
|
||||
- Start new features with `/speckit.specify` — templates are now project-aware
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- **Always confirm before writing** — show the bootstrap plan and wait for approval
|
||||
- **Never overwrite without asking** — if constitution or templates already exist, show a diff and ask
|
||||
- **Derive from reality** — every constitution rule must trace to something detected in the codebase
|
||||
- **No invented conventions** — if the project has no consistent pattern for something, say so instead of guessing
|
||||
- **Respect existing spec-kit setup** — if `.specify/` already has customizations, merge rather than replace
|
||||
- **Module-aware** — for monorepos, generate configuration that respects module boundaries
|
||||
@@ -0,0 +1,128 @@
|
||||
---
|
||||
description: Incrementally adopt SDD for existing features with reverse-engineered
|
||||
specs
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: brownfield -->
|
||||
<!-- Config: .specify/extensions/brownfield/ -->
|
||||
# Migrate Existing Features
|
||||
|
||||
Reverse-engineer spec-kit artifacts (spec.md, plan.md, tasks.md) for features that were built before spec-kit was adopted. This brings existing work into the SDD workflow so teams can track, refine, and extend features using spec-kit commands.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty). The user may specify a feature or module to migrate (e.g., "auth system", "payments module"), a branch name, or "all" to migrate everything.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Verify a spec-kit project exists by checking for `.specify/` directory
|
||||
2. Verify git is available and the project is a git repository
|
||||
3. Verify the project has existing source code to migrate (not an empty project)
|
||||
4. Verify constitution exists (recommend running `/speckit.brownfield.bootstrap` first if missing)
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Identify migration targets**: Determine what to migrate based on user input:
|
||||
|
||||
| Input | Action |
|
||||
|-------|--------|
|
||||
| Specific feature name | Locate the feature in the codebase by searching for related files, modules, or directories |
|
||||
| Specific branch name | Analyze the branch's commits and changed files to identify the feature scope |
|
||||
| Module path | Treat the entire module as a single feature to migrate |
|
||||
| `all` | List all identifiable features and let the user select which to migrate |
|
||||
| No input | Show a list of detected features and ask the user to pick one |
|
||||
|
||||
2. **Detect feature boundaries**: For each migration target, determine its scope:
|
||||
- **Files**: Which source files implement this feature
|
||||
- **Tests**: Which test files cover this feature
|
||||
- **Dependencies**: What other modules or services this feature depends on
|
||||
- **API surface**: Endpoints, functions, or interfaces exposed by this feature
|
||||
- **Database**: Migrations, models, or schema changes related to this feature
|
||||
|
||||
3. **Reverse-engineer spec.md**: Analyze the code to reconstruct what the feature does:
|
||||
- **User scenarios**: Infer from test cases, route handlers, and UI components
|
||||
- **Requirements**: Extract from code behavior, validation rules, and error handling
|
||||
- **Success criteria**: Derive from test assertions and acceptance patterns
|
||||
- **Assumptions**: Note any hardcoded values, environment dependencies, or implicit requirements
|
||||
- Mark the spec as `status: migrated` to distinguish from specs created through the normal workflow
|
||||
|
||||
4. **Reverse-engineer plan.md**: Reconstruct the implementation approach:
|
||||
- **Technical context**: Actual frameworks, libraries, and patterns used
|
||||
- **Project structure**: Where the feature's code lives in the project
|
||||
- **Complexity assessment**: Based on file count, line count, and dependency depth
|
||||
|
||||
5. **Reverse-engineer tasks.md**: Create a task list reflecting what was actually built:
|
||||
- Each major component or module becomes a task group
|
||||
- Mark all tasks as `[x]` (completed) since the feature already exists
|
||||
- Include test tasks based on actual test files found
|
||||
- Note any gaps: code without tests, features without error handling
|
||||
|
||||
6. **Create feature branch and artifacts**: For each migrated feature:
|
||||
- Create a feature directory: `specs/{feature-name}/`
|
||||
- Write `spec.md`, `plan.md`, and `tasks.md` into the feature directory
|
||||
- Do **not** create a git branch — the feature already exists on its branch or main
|
||||
|
||||
7. **Present migration plan**: Show what will be created before writing:
|
||||
|
||||
```markdown
|
||||
# Migration Plan: User Authentication
|
||||
|
||||
## Detected Scope
|
||||
| Category | Files | Lines |
|
||||
|----------|-------|-------|
|
||||
| Source | 8 files | ~420 lines |
|
||||
| Tests | 3 files | ~180 lines |
|
||||
| Migrations | 2 files | ~45 lines |
|
||||
|
||||
## Artifacts to Generate
|
||||
| File | Content |
|
||||
|------|---------|
|
||||
| `specs/user-auth/spec.md` | 4 user scenarios, 12 requirements, 6 success criteria |
|
||||
| `specs/user-auth/plan.md` | 3 implementation phases, 8 technical decisions |
|
||||
| `specs/user-auth/tasks.md` | 14 tasks (all completed), 2 gaps identified |
|
||||
|
||||
## Gaps Found
|
||||
- ⚠️ No error handling tests for expired tokens
|
||||
- ⚠️ No rate limiting on login endpoint
|
||||
|
||||
Proceed with migration?
|
||||
```
|
||||
|
||||
8. **Execute migration**: After user confirmation, write all artifacts.
|
||||
|
||||
9. **Report**:
|
||||
|
||||
```markdown
|
||||
# Migration Complete: User Authentication
|
||||
|
||||
| Artifact | Status |
|
||||
|----------|--------|
|
||||
| spec.md | ✅ Created — 4 scenarios, 12 requirements |
|
||||
| plan.md | ✅ Created — 3 phases |
|
||||
| tasks.md | ✅ Created — 14/14 tasks complete |
|
||||
|
||||
## Identified Gaps
|
||||
1. No error handling tests for expired tokens → consider `/speckit.specify` for a follow-up feature
|
||||
2. No rate limiting on login endpoint → consider `/speckit.bugfix.report` to track
|
||||
|
||||
## Next Steps
|
||||
- Review generated artifacts in `specs/user-auth/`
|
||||
- Use `/speckit.refine.update` to adjust any inaccurate specs
|
||||
- Use `/speckit.specify` for new features — they'll follow the same SDD workflow
|
||||
- Run `/speckit.brownfield.migrate` again for additional features
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- **Always confirm before writing** — show the migration plan and wait for user approval
|
||||
- **Honest assessment** — if the code is unclear or poorly documented, say so in the spec rather than inventing explanations
|
||||
- **Mark as migrated** — all migrated specs must include `status: migrated` to distinguish from fresh specs
|
||||
- **Identify gaps** — actively look for missing tests, error handling, or documentation and report them
|
||||
- **Non-destructive** — never modify existing source code, only create spec artifacts
|
||||
- **One feature at a time** — for "all" input, migrate features sequentially with confirmation between each
|
||||
- **Respect constitution** — generated artifacts must follow the project's constitution rules
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
description: Auto-discover project structure, tech stack, frameworks, and architecture
|
||||
patterns
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: brownfield -->
|
||||
<!-- Config: .specify/extensions/brownfield/ -->
|
||||
# Scan Project
|
||||
|
||||
Analyze an existing codebase to discover its technology stack, architecture patterns, module structure, and coding conventions. This produces a project profile that the bootstrap command uses to generate tailored spec-kit configuration.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty). The user may specify a subdirectory to scan (e.g., "backend/"), a focus area (e.g., "only frontend"), or request a specific depth of analysis.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Verify the current directory is a git repository
|
||||
2. Verify this is an existing project with source code (not an empty repo)
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Detect tech stack**: Identify languages, frameworks, and tools by scanning:
|
||||
|
||||
| Signal | What to Check |
|
||||
|--------|--------------|
|
||||
| **Languages** | File extensions (`.py`, `.ts`, `.go`, `.java`, `.rs`, etc.) and their relative proportions |
|
||||
| **Package managers** | `package.json`, `requirements.txt`, `pyproject.toml`, `go.mod`, `Cargo.toml`, `pom.xml`, `build.gradle` |
|
||||
| **Frameworks** | Dependencies in package files (React, Django, Spring, Express, Rails, etc.) |
|
||||
| **Build tools** | `Makefile`, `webpack.config.js`, `vite.config.ts`, `Dockerfile`, `docker-compose.yml` |
|
||||
| **CI/CD** | `.github/workflows/`, `.gitlab-ci.yml`, `.circleci/`, `Jenkinsfile` |
|
||||
| **Testing** | Test directories, test frameworks in dependencies (`jest`, `pytest`, `go test`, `JUnit`) |
|
||||
|
||||
2. **Analyze architecture**: Identify the project's structural patterns:
|
||||
|
||||
| Pattern | Indicators |
|
||||
|---------|-----------|
|
||||
| **Monolith** | Single source tree, one entry point, shared database config |
|
||||
| **Monorepo** | Multiple `package.json`/`go.mod` files, workspace config, `packages/` or `apps/` directories |
|
||||
| **Microservices** | Multiple Dockerfiles, service directories, API gateway config |
|
||||
| **Frontend + Backend** | Separate `client/`/`server/` or `frontend/`/`backend/` directories |
|
||||
| **Library/Package** | `setup.py`, `lib/` directory, published package config |
|
||||
| **MVC** | `models/`, `views/`, `controllers/` directories |
|
||||
| **Layered** | `domain/`, `application/`, `infrastructure/`, `presentation/` directories |
|
||||
|
||||
3. **Map module structure**: For monorepos and multi-module projects:
|
||||
- Identify each module/package/service and its purpose
|
||||
- Detect inter-module dependencies (imports, shared types)
|
||||
- Note module boundaries (what code belongs where)
|
||||
- Identify shared libraries or utilities
|
||||
|
||||
4. **Extract conventions**: Detect existing coding patterns:
|
||||
- **Naming**: File naming (camelCase, kebab-case, snake_case), directory naming
|
||||
- **Branching**: Existing branch names and patterns from `git branch -a`
|
||||
- **Commit style**: Recent commit message patterns from `git log --oneline -20`
|
||||
- **Testing**: Test file location (`__tests__/`, `*_test.go`, `test_*.py`), test naming
|
||||
- **Documentation**: README structure, inline docs, API docs
|
||||
|
||||
5. **Detect existing governance**: Check for files that indicate existing project standards:
|
||||
- `CONTRIBUTING.md`, `ARCHITECTURE.md`, `ADR/` (Architecture Decision Records)
|
||||
- `.editorconfig`, linter configs (`.eslintrc`, `.flake8`, `rustfmt.toml`)
|
||||
- `CLAUDE.md`, `AGENTS.md`, `.specify/` (existing spec-kit setup)
|
||||
|
||||
6. **Output project profile**:
|
||||
|
||||
```markdown
|
||||
# Project Profile
|
||||
|
||||
## Tech Stack
|
||||
| Category | Detected |
|
||||
|----------|----------|
|
||||
| **Primary language** | TypeScript (68%), Python (32%) |
|
||||
| **Frontend** | React 18, Vite, TailwindCSS |
|
||||
| **Backend** | FastAPI, SQLAlchemy, PostgreSQL |
|
||||
| **Testing** | Jest (frontend), pytest (backend) |
|
||||
| **CI/CD** | GitHub Actions |
|
||||
| **Package manager** | npm (frontend), pip (backend) |
|
||||
|
||||
## Architecture
|
||||
- **Pattern**: Frontend + Backend (separated)
|
||||
- **Frontend**: `client/` — React SPA
|
||||
- **Backend**: `server/` — FastAPI REST API
|
||||
- **Database**: PostgreSQL (via SQLAlchemy ORM)
|
||||
|
||||
## Module Map
|
||||
| Module | Path | Purpose | Dependencies |
|
||||
|--------|------|---------|-------------|
|
||||
| Frontend | `client/` | React SPA | Backend API |
|
||||
| Backend | `server/` | REST API | Database |
|
||||
| Shared | `shared/` | Type definitions | — |
|
||||
|
||||
## Conventions
|
||||
- **File naming**: kebab-case (frontend), snake_case (backend)
|
||||
- **Branch pattern**: `feat/*`, `fix/*`, `chore/*`
|
||||
- **Commit style**: Conventional Commits
|
||||
- **Test location**: `__tests__/` (frontend), `tests/` (backend)
|
||||
|
||||
## Existing Governance
|
||||
- ✅ CONTRIBUTING.md
|
||||
- ✅ .eslintrc.json
|
||||
- ❌ ARCHITECTURE.md
|
||||
- ❌ .specify/ (no spec-kit setup)
|
||||
|
||||
## Recommendations
|
||||
- Run `/speckit.brownfield.bootstrap` to generate tailored spec-kit configuration
|
||||
- Constitution should enforce: kebab-case files (frontend), snake_case (backend)
|
||||
- Feature specs should map to the frontend/backend split
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- **Read-only** — this command never modifies any files
|
||||
- **Respect .gitignore** — never scan `node_modules/`, `vendor/`, `dist/`, `.venv/`, or other ignored directories
|
||||
- **Proportional analysis** — report language percentages based on actual file counts or line counts
|
||||
- **No assumptions** — only report what is actually detected in the codebase
|
||||
- **Handle empty results** — if a category has nothing detected, say "Not detected" rather than guessing
|
||||
@@ -0,0 +1,94 @@
|
||||
---
|
||||
description: Verify bootstrap output matches actual project structure and conventions
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: brownfield -->
|
||||
<!-- Config: .specify/extensions/brownfield/ -->
|
||||
# Validate Bootstrap
|
||||
|
||||
Verify that the spec-kit configuration generated by `/speckit.brownfield.bootstrap` accurately reflects the actual project structure, conventions, and architecture. Reports mismatches and suggests corrections.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty). The user may specify a focus area (e.g., "only constitution", "only templates") or request verbose output.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Verify a spec-kit project exists by checking for `.specify/` directory
|
||||
2. Verify git is available and the project is a git repository
|
||||
3. Verify at least one bootstrap artifact exists (constitution, customized templates, or AGENTS.md)
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Validate constitution**: Check `.specify/memory/constitution.md` against the actual codebase:
|
||||
|
||||
| Check | How |
|
||||
|-------|-----|
|
||||
| **Language references** | Verify mentioned languages actually exist in the codebase |
|
||||
| **Directory references** | Verify all referenced paths (`client/`, `server/`, etc.) exist |
|
||||
| **Framework references** | Verify mentioned frameworks are in dependency files |
|
||||
| **Naming conventions** | Sample 20 files and check if naming rules match reality |
|
||||
| **Test location** | Verify test directories mentioned in constitution exist |
|
||||
| **Branch pattern** | Check if branch naming rules match actual branches in `git branch -a` |
|
||||
|
||||
2. **Validate templates**: Check customized spec/plan/tasks templates:
|
||||
|
||||
| Check | How |
|
||||
|-------|-----|
|
||||
| **Module references** | Verify template sections reference actual modules/directories |
|
||||
| **Test commands** | Verify test commands in tasks template actually work |
|
||||
| **Build commands** | Verify build commands reference real scripts from package files |
|
||||
| **Section relevance** | Flag template sections that reference non-existent project aspects |
|
||||
|
||||
3. **Validate AGENTS.md** (if exists): Check agent configuration:
|
||||
|
||||
| Check | How |
|
||||
|-------|-----|
|
||||
| **Directory ownership** | Verify each agent's directories exist |
|
||||
| **No overlaps** | Check that no directory is owned by multiple agents |
|
||||
| **No orphans** | Check that all source directories are covered by at least one agent |
|
||||
|
||||
4. **Detect drift**: Check if the project has changed since bootstrap:
|
||||
- New directories or modules added since constitution was generated
|
||||
- Dependencies added or removed since bootstrap
|
||||
- New branch patterns that don't match constitution rules
|
||||
|
||||
5. **Output validation report**:
|
||||
|
||||
```markdown
|
||||
# Validation Report
|
||||
|
||||
## Constitution
|
||||
| Rule | Status | Detail |
|
||||
|------|--------|--------|
|
||||
| Primary language: TypeScript | ✅ Pass | 68% of source files |
|
||||
| Frontend in `client/` | ✅ Pass | Directory exists, contains React code |
|
||||
| Backend in `server/` | ✅ Pass | Directory exists, contains FastAPI code |
|
||||
| Test location: `__tests__/` | ⚠️ Drift | Also found tests in `tests/` (not mentioned) |
|
||||
| Branch pattern: `feat/*` | ✅ Pass | 8/10 recent branches match |
|
||||
|
||||
## Templates
|
||||
| Template | Status | Detail |
|
||||
|----------|--------|--------|
|
||||
| Spec template | ✅ Pass | All custom sections map to real project aspects |
|
||||
| Plan template | ⚠️ Drift | References `shared/` module — directory renamed to `common/` |
|
||||
| Tasks template | ✅ Pass | Test commands verified |
|
||||
|
||||
## Summary
|
||||
- **Checks passed**: 9/11
|
||||
- **Drift detected**: 2 items
|
||||
- **Action needed**: Update plan template (`shared/` → `common/`), add `tests/` to constitution
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- **Read-only** — this command never modifies any files
|
||||
- **Evidence-based** — every pass/fail must cite specific files or directories as evidence
|
||||
- **Actionable output** — for every failure or drift, suggest the specific fix
|
||||
- **Non-blocking** — drift warnings don't mean the configuration is broken, just that it could be improved
|
||||
- **Respect .gitignore** — never scan ignored directories when validating
|
||||
@@ -0,0 +1,361 @@
|
||||
---
|
||||
description: Generate a custom checklist for the current feature based on user requirements.
|
||||
---
|
||||
|
||||
## Checklist Purpose: "Unit Tests for English"
|
||||
|
||||
**CRITICAL CONCEPT**: Checklists are **UNIT TESTS FOR REQUIREMENTS WRITING** - they validate the quality, clarity, and completeness of requirements in a given domain.
|
||||
|
||||
**NOT for verification/testing**:
|
||||
|
||||
- ❌ NOT "Verify the button clicks correctly"
|
||||
- ❌ NOT "Test error handling works"
|
||||
- ❌ NOT "Confirm the API returns 200"
|
||||
- ❌ NOT checking if code/implementation matches the spec
|
||||
|
||||
**FOR requirements quality validation**:
|
||||
|
||||
- ✅ "Are visual hierarchy requirements defined for all card types?" (completeness)
|
||||
- ✅ "Is 'prominent display' quantified with specific sizing/positioning?" (clarity)
|
||||
- ✅ "Are hover state requirements consistent across all interactive elements?" (consistency)
|
||||
- ✅ "Are accessibility requirements defined for keyboard navigation?" (coverage)
|
||||
- ✅ "Does the spec define what happens when logo image fails to load?" (edge cases)
|
||||
|
||||
**Metaphor**: If your spec is code written in English, the checklist is its unit test suite. You're testing whether the requirements are well-written, complete, unambiguous, and ready for implementation - NOT whether the implementation works.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before checklist generation)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_checklist` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Execution Steps.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Execution Steps
|
||||
|
||||
1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json` from repo root and parse JSON for FEATURE_DIR and AVAILABLE_DOCS list.
|
||||
- All file paths must be absolute.
|
||||
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Clarify intent (dynamic)**: Derive up to THREE initial contextual clarifying questions (no pre-baked catalog). They MUST:
|
||||
- Be generated from the user's phrasing + extracted signals from spec/plan/tasks
|
||||
- Only ask about information that materially changes checklist content
|
||||
- Be skipped individually if already unambiguous in `$ARGUMENTS`
|
||||
- Prefer precision over breadth
|
||||
|
||||
Generation algorithm:
|
||||
1. Extract signals: feature domain keywords (e.g., auth, latency, UX, API), risk indicators ("critical", "must", "compliance"), stakeholder hints ("QA", "review", "security team"), and explicit deliverables ("a11y", "rollback", "contracts").
|
||||
2. Cluster signals into candidate focus areas (max 4) ranked by relevance.
|
||||
3. Identify probable audience & timing (author, reviewer, QA, release) if not explicit.
|
||||
4. Detect missing dimensions: scope breadth, depth/rigor, risk emphasis, exclusion boundaries, measurable acceptance criteria.
|
||||
5. Formulate questions chosen from these archetypes:
|
||||
- Scope refinement (e.g., "Should this include integration touchpoints with X and Y or stay limited to local module correctness?")
|
||||
- Risk prioritization (e.g., "Which of these potential risk areas should receive mandatory gating checks?")
|
||||
- Depth calibration (e.g., "Is this a lightweight pre-commit sanity list or a formal release gate?")
|
||||
- Audience framing (e.g., "Will this be used by the author only or peers during PR review?")
|
||||
- Boundary exclusion (e.g., "Should we explicitly exclude performance tuning items this round?")
|
||||
- Scenario class gap (e.g., "No recovery flows detected—are rollback / partial failure paths in scope?")
|
||||
|
||||
Question formatting rules:
|
||||
- If presenting options, generate a compact table with columns: Option | Candidate | Why It Matters
|
||||
- Limit to A–E options maximum; omit table if a free-form answer is clearer
|
||||
- Never ask the user to restate what they already said
|
||||
- Avoid speculative categories (no hallucination). If uncertain, ask explicitly: "Confirm whether X belongs in scope."
|
||||
|
||||
Defaults when interaction impossible:
|
||||
- Depth: Standard
|
||||
- Audience: Reviewer (PR) if code-related; Author otherwise
|
||||
- Focus: Top 2 relevance clusters
|
||||
|
||||
Output the questions (label Q1/Q2/Q3). After answers: if ≥2 scenario classes (Alternate / Exception / Recovery / Non-Functional domain) remain unclear, you MAY ask up to TWO more targeted follow‑ups (Q4/Q5) with a one-line justification each (e.g., "Unresolved recovery path risk"). Do not exceed five total questions. Skip escalation if user explicitly declines more.
|
||||
|
||||
3. **Understand user request**: Combine `$ARGUMENTS` + clarifying answers:
|
||||
- Derive checklist theme (e.g., security, review, deploy, ux)
|
||||
- Consolidate explicit must-have items mentioned by user
|
||||
- Map focus selections to category scaffolding
|
||||
- Infer any missing context from spec/plan/tasks (do NOT hallucinate)
|
||||
|
||||
4. **Load feature context**: Read from FEATURE_DIR:
|
||||
- spec.md: Feature requirements and scope
|
||||
- plan.md (if exists): Technical details, dependencies
|
||||
- tasks.md (if exists): Implementation tasks
|
||||
|
||||
**Context Loading Strategy**:
|
||||
- Load only necessary portions relevant to active focus areas (avoid full-file dumping)
|
||||
- Prefer summarizing long sections into concise scenario/requirement bullets
|
||||
- Use progressive disclosure: add follow-on retrieval only if gaps detected
|
||||
- If source docs are large, generate interim summary items instead of embedding raw text
|
||||
|
||||
5. **Generate checklist** - Create "Unit Tests for Requirements":
|
||||
- Create `FEATURE_DIR/checklists/` directory if it doesn't exist
|
||||
- Generate unique checklist filename:
|
||||
- Use short, descriptive name based on domain (e.g., `ux.md`, `api.md`, `security.md`)
|
||||
- Format: `[domain].md`
|
||||
- File handling behavior:
|
||||
- If file does NOT exist: Create new file and number items starting from CHK001
|
||||
- If file exists: Append new items to existing file, continuing from the last CHK ID (e.g., if last item is CHK015, start new items at CHK016)
|
||||
- Never delete or replace existing checklist content - always preserve and append
|
||||
|
||||
**CORE PRINCIPLE - Test the Requirements, Not the Implementation**:
|
||||
Every checklist item MUST evaluate the REQUIREMENTS THEMSELVES for:
|
||||
- **Completeness**: Are all necessary requirements present?
|
||||
- **Clarity**: Are requirements unambiguous and specific?
|
||||
- **Consistency**: Do requirements align with each other?
|
||||
- **Measurability**: Can requirements be objectively verified?
|
||||
- **Coverage**: Are all scenarios/edge cases addressed?
|
||||
|
||||
**Category Structure** - Group items by requirement quality dimensions:
|
||||
- **Requirement Completeness** (Are all necessary requirements documented?)
|
||||
- **Requirement Clarity** (Are requirements specific and unambiguous?)
|
||||
- **Requirement Consistency** (Do requirements align without conflicts?)
|
||||
- **Acceptance Criteria Quality** (Are success criteria measurable?)
|
||||
- **Scenario Coverage** (Are all flows/cases addressed?)
|
||||
- **Edge Case Coverage** (Are boundary conditions defined?)
|
||||
- **Non-Functional Requirements** (Performance, Security, Accessibility, etc. - are they specified?)
|
||||
- **Dependencies & Assumptions** (Are they documented and validated?)
|
||||
- **Ambiguities & Conflicts** (What needs clarification?)
|
||||
|
||||
**HOW TO WRITE CHECKLIST ITEMS - "Unit Tests for English"**:
|
||||
|
||||
❌ **WRONG** (Testing implementation):
|
||||
- "Verify landing page displays 3 episode cards"
|
||||
- "Test hover states work on desktop"
|
||||
- "Confirm logo click navigates home"
|
||||
|
||||
✅ **CORRECT** (Testing requirements quality):
|
||||
- "Are the exact number and layout of featured episodes specified?" [Completeness]
|
||||
- "Is 'prominent display' quantified with specific sizing/positioning?" [Clarity]
|
||||
- "Are hover state requirements consistent across all interactive elements?" [Consistency]
|
||||
- "Are keyboard navigation requirements defined for all interactive UI?" [Coverage]
|
||||
- "Is the fallback behavior specified when logo image fails to load?" [Edge Cases]
|
||||
- "Are loading states defined for asynchronous episode data?" [Completeness]
|
||||
- "Does the spec define visual hierarchy for competing UI elements?" [Clarity]
|
||||
|
||||
**ITEM STRUCTURE**:
|
||||
Each item should follow this pattern:
|
||||
- Question format asking about requirement quality
|
||||
- Focus on what's WRITTEN (or not written) in the spec/plan
|
||||
- Include quality dimension in brackets [Completeness/Clarity/Consistency/etc.]
|
||||
- Reference spec section `[Spec §X.Y]` when checking existing requirements
|
||||
- Use `[Gap]` marker when checking for missing requirements
|
||||
|
||||
**EXAMPLES BY QUALITY DIMENSION**:
|
||||
|
||||
Completeness:
|
||||
- "Are error handling requirements defined for all API failure modes? [Gap]"
|
||||
- "Are accessibility requirements specified for all interactive elements? [Completeness]"
|
||||
- "Are mobile breakpoint requirements defined for responsive layouts? [Gap]"
|
||||
|
||||
Clarity:
|
||||
- "Is 'fast loading' quantified with specific timing thresholds? [Clarity, Spec §NFR-2]"
|
||||
- "Are 'related episodes' selection criteria explicitly defined? [Clarity, Spec §FR-5]"
|
||||
- "Is 'prominent' defined with measurable visual properties? [Ambiguity, Spec §FR-4]"
|
||||
|
||||
Consistency:
|
||||
- "Do navigation requirements align across all pages? [Consistency, Spec §FR-10]"
|
||||
- "Are card component requirements consistent between landing and detail pages? [Consistency]"
|
||||
|
||||
Coverage:
|
||||
- "Are requirements defined for zero-state scenarios (no episodes)? [Coverage, Edge Case]"
|
||||
- "Are concurrent user interaction scenarios addressed? [Coverage, Gap]"
|
||||
- "Are requirements specified for partial data loading failures? [Coverage, Exception Flow]"
|
||||
|
||||
Measurability:
|
||||
- "Are visual hierarchy requirements measurable/testable? [Acceptance Criteria, Spec §FR-1]"
|
||||
- "Can 'balanced visual weight' be objectively verified? [Measurability, Spec §FR-2]"
|
||||
|
||||
**Scenario Classification & Coverage** (Requirements Quality Focus):
|
||||
- Check if requirements exist for: Primary, Alternate, Exception/Error, Recovery, Non-Functional scenarios
|
||||
- For each scenario class, ask: "Are [scenario type] requirements complete, clear, and consistent?"
|
||||
- If scenario class missing: "Are [scenario type] requirements intentionally excluded or missing? [Gap]"
|
||||
- Include resilience/rollback when state mutation occurs: "Are rollback requirements defined for migration failures? [Gap]"
|
||||
|
||||
**Traceability Requirements**:
|
||||
- MINIMUM: ≥80% of items MUST include at least one traceability reference
|
||||
- Each item should reference: spec section `[Spec §X.Y]`, or use markers: `[Gap]`, `[Ambiguity]`, `[Conflict]`, `[Assumption]`
|
||||
- If no ID system exists: "Is a requirement & acceptance criteria ID scheme established? [Traceability]"
|
||||
|
||||
**Surface & Resolve Issues** (Requirements Quality Problems):
|
||||
Ask questions about the requirements themselves:
|
||||
- Ambiguities: "Is the term 'fast' quantified with specific metrics? [Ambiguity, Spec §NFR-1]"
|
||||
- Conflicts: "Do navigation requirements conflict between §FR-10 and §FR-10a? [Conflict]"
|
||||
- Assumptions: "Is the assumption of 'always available podcast API' validated? [Assumption]"
|
||||
- Dependencies: "Are external podcast API requirements documented? [Dependency, Gap]"
|
||||
- Missing definitions: "Is 'visual hierarchy' defined with measurable criteria? [Gap]"
|
||||
|
||||
**Content Consolidation**:
|
||||
- Soft cap: If raw candidate items > 40, prioritize by risk/impact
|
||||
- Merge near-duplicates checking the same requirement aspect
|
||||
- If >5 low-impact edge cases, create one item: "Are edge cases X, Y, Z addressed in requirements? [Coverage]"
|
||||
|
||||
**🚫 ABSOLUTELY PROHIBITED** - These make it an implementation test, not a requirements test:
|
||||
- ❌ Any item starting with "Verify", "Test", "Confirm", "Check" + implementation behavior
|
||||
- ❌ References to code execution, user actions, system behavior
|
||||
- ❌ "Displays correctly", "works properly", "functions as expected"
|
||||
- ❌ "Click", "navigate", "render", "load", "execute"
|
||||
- ❌ Test cases, test plans, QA procedures
|
||||
- ❌ Implementation details (frameworks, APIs, algorithms)
|
||||
|
||||
**✅ REQUIRED PATTERNS** - These test requirements quality:
|
||||
- ✅ "Are [requirement type] defined/specified/documented for [scenario]?"
|
||||
- ✅ "Is [vague term] quantified/clarified with specific criteria?"
|
||||
- ✅ "Are requirements consistent between [section A] and [section B]?"
|
||||
- ✅ "Can [requirement] be objectively measured/verified?"
|
||||
- ✅ "Are [edge cases/scenarios] addressed in requirements?"
|
||||
- ✅ "Does the spec define [missing aspect]?"
|
||||
|
||||
6. **Structure Reference**: Generate the checklist following the canonical template in `.specify/templates/checklist-template.md` for title, meta section, category headings, and ID formatting. If template is unavailable, use: H1 title, purpose/created meta lines, `##` category sections containing `- [ ] CHK### <requirement item>` lines with globally incrementing IDs starting at CHK001.
|
||||
|
||||
7. **Report**: Output full path to checklist file, item count, and summarize whether the run created a new file or appended to an existing one. Summarize:
|
||||
- Focus areas selected
|
||||
- Depth level
|
||||
- Actor/timing
|
||||
- Any explicit user-specified must-have items incorporated
|
||||
|
||||
**Important**: Each `/speckit.checklist` command invocation uses a short, descriptive checklist filename and either creates a new file or appends to an existing one. This allows:
|
||||
|
||||
- Multiple checklists of different types (e.g., `ux.md`, `test.md`, `security.md`)
|
||||
- Simple, memorable filenames that indicate checklist purpose
|
||||
- Easy identification and navigation in the `checklists/` folder
|
||||
|
||||
To avoid clutter, use descriptive types and clean up obsolete checklists when done.
|
||||
|
||||
## Example Checklist Types & Sample Items
|
||||
|
||||
**UX Requirements Quality:** `ux.md`
|
||||
|
||||
Sample items (testing the requirements, NOT the implementation):
|
||||
|
||||
- "Are visual hierarchy requirements defined with measurable criteria? [Clarity, Spec §FR-1]"
|
||||
- "Is the number and positioning of UI elements explicitly specified? [Completeness, Spec §FR-1]"
|
||||
- "Are interaction state requirements (hover, focus, active) consistently defined? [Consistency]"
|
||||
- "Are accessibility requirements specified for all interactive elements? [Coverage, Gap]"
|
||||
- "Is fallback behavior defined when images fail to load? [Edge Case, Gap]"
|
||||
- "Can 'prominent display' be objectively measured? [Measurability, Spec §FR-4]"
|
||||
|
||||
**API Requirements Quality:** `api.md`
|
||||
|
||||
Sample items:
|
||||
|
||||
- "Are error response formats specified for all failure scenarios? [Completeness]"
|
||||
- "Are rate limiting requirements quantified with specific thresholds? [Clarity]"
|
||||
- "Are authentication requirements consistent across all endpoints? [Consistency]"
|
||||
- "Are retry/timeout requirements defined for external dependencies? [Coverage, Gap]"
|
||||
- "Is versioning strategy documented in requirements? [Gap]"
|
||||
|
||||
**Performance Requirements Quality:** `performance.md`
|
||||
|
||||
Sample items:
|
||||
|
||||
- "Are performance requirements quantified with specific metrics? [Clarity]"
|
||||
- "Are performance targets defined for all critical user journeys? [Coverage]"
|
||||
- "Are performance requirements under different load conditions specified? [Completeness]"
|
||||
- "Can performance requirements be objectively measured? [Measurability]"
|
||||
- "Are degradation requirements defined for high-load scenarios? [Edge Case, Gap]"
|
||||
|
||||
**Security Requirements Quality:** `security.md`
|
||||
|
||||
Sample items:
|
||||
|
||||
- "Are authentication requirements specified for all protected resources? [Coverage]"
|
||||
- "Are data protection requirements defined for sensitive information? [Completeness]"
|
||||
- "Is the threat model documented and requirements aligned to it? [Traceability]"
|
||||
- "Are security requirements consistent with compliance obligations? [Consistency]"
|
||||
- "Are security failure/breach response requirements defined? [Gap, Exception Flow]"
|
||||
|
||||
## Anti-Examples: What NOT To Do
|
||||
|
||||
**❌ WRONG - These test implementation, not requirements:**
|
||||
|
||||
```markdown
|
||||
- [ ] CHK001 - Verify landing page displays 3 episode cards [Spec §FR-001]
|
||||
- [ ] CHK002 - Test hover states work correctly on desktop [Spec §FR-003]
|
||||
- [ ] CHK003 - Confirm logo click navigates to home page [Spec §FR-010]
|
||||
- [ ] CHK004 - Check that related episodes section shows 3-5 items [Spec §FR-005]
|
||||
```
|
||||
|
||||
**✅ CORRECT - These test requirements quality:**
|
||||
|
||||
```markdown
|
||||
- [ ] CHK001 - Are the number and layout of featured episodes explicitly specified? [Completeness, Spec §FR-001]
|
||||
- [ ] CHK002 - Are hover state requirements consistently defined for all interactive elements? [Consistency, Spec §FR-003]
|
||||
- [ ] CHK003 - Are navigation requirements clear for all clickable brand elements? [Clarity, Spec §FR-010]
|
||||
- [ ] CHK004 - Is the selection criteria for related episodes documented? [Gap, Spec §FR-005]
|
||||
- [ ] CHK005 - Are loading state requirements defined for asynchronous episode data? [Gap]
|
||||
- [ ] CHK006 - Can "visual hierarchy" requirements be objectively measured? [Measurability, Spec §FR-001]
|
||||
```
|
||||
|
||||
**Key Differences:**
|
||||
|
||||
- Wrong: Tests if the system works correctly
|
||||
- Correct: Tests if the requirements are written correctly
|
||||
- Wrong: Verification of behavior
|
||||
- Correct: Validation of requirement quality
|
||||
- Wrong: "Does it do X?"
|
||||
- Correct: "Is X clearly specified?"
|
||||
|
||||
## Post-Execution Checks
|
||||
|
||||
**Check for extension hooks (after checklist generation)**:
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_checklist` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
@@ -0,0 +1,247 @@
|
||||
---
|
||||
description: Identify underspecified areas in the current feature spec by asking up to 5 highly targeted clarification questions and encoding answers back into the spec.
|
||||
handoffs:
|
||||
- label: Build Technical Plan
|
||||
agent: speckit.plan
|
||||
prompt: Create a plan for the spec. I am building with...
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before clarification)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_clarify` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
Goal: Detect and reduce ambiguity or missing decision points in the active feature specification and record the clarifications directly in the spec file.
|
||||
|
||||
Note: This clarification workflow is expected to run (and be completed) BEFORE invoking `/speckit.plan`. If the user explicitly states they are skipping clarification (e.g., exploratory spike), you may proceed, but must warn that downstream rework risk increases.
|
||||
|
||||
Execution steps:
|
||||
|
||||
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root **once** (combined `--json --paths-only` mode / `-Json -PathsOnly`). Parse minimal JSON payload fields:
|
||||
- `FEATURE_DIR`
|
||||
- `FEATURE_SPEC`
|
||||
- (Optionally capture `IMPL_PLAN`, `TASKS` for future chained flows.)
|
||||
- If JSON parsing fails, abort and instruct user to re-run `/speckit.specify` or verify feature branch environment.
|
||||
- For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. Load the current spec file. Perform a structured ambiguity & coverage scan using this taxonomy. For each category, mark status: Clear / Partial / Missing. Produce an internal coverage map used for prioritization (do not output raw map unless no questions will be asked).
|
||||
|
||||
Functional Scope & Behavior:
|
||||
- Core user goals & success criteria
|
||||
- Explicit out-of-scope declarations
|
||||
- User roles / personas differentiation
|
||||
|
||||
Domain & Data Model:
|
||||
- Entities, attributes, relationships
|
||||
- Identity & uniqueness rules
|
||||
- Lifecycle/state transitions
|
||||
- Data volume / scale assumptions
|
||||
|
||||
Interaction & UX Flow:
|
||||
- Critical user journeys / sequences
|
||||
- Error/empty/loading states
|
||||
- Accessibility or localization notes
|
||||
|
||||
Non-Functional Quality Attributes:
|
||||
- Performance (latency, throughput targets)
|
||||
- Scalability (horizontal/vertical, limits)
|
||||
- Reliability & availability (uptime, recovery expectations)
|
||||
- Observability (logging, metrics, tracing signals)
|
||||
- Security & privacy (authN/Z, data protection, threat assumptions)
|
||||
- Compliance / regulatory constraints (if any)
|
||||
|
||||
Integration & External Dependencies:
|
||||
- External services/APIs and failure modes
|
||||
- Data import/export formats
|
||||
- Protocol/versioning assumptions
|
||||
|
||||
Edge Cases & Failure Handling:
|
||||
- Negative scenarios
|
||||
- Rate limiting / throttling
|
||||
- Conflict resolution (e.g., concurrent edits)
|
||||
|
||||
Constraints & Tradeoffs:
|
||||
- Technical constraints (language, storage, hosting)
|
||||
- Explicit tradeoffs or rejected alternatives
|
||||
|
||||
Terminology & Consistency:
|
||||
- Canonical glossary terms
|
||||
- Avoided synonyms / deprecated terms
|
||||
|
||||
Completion Signals:
|
||||
- Acceptance criteria testability
|
||||
- Measurable Definition of Done style indicators
|
||||
|
||||
Misc / Placeholders:
|
||||
- TODO markers / unresolved decisions
|
||||
- Ambiguous adjectives ("robust", "intuitive") lacking quantification
|
||||
|
||||
For each category with Partial or Missing status, add a candidate question opportunity unless:
|
||||
- Clarification would not materially change implementation or validation strategy
|
||||
- Information is better deferred to planning phase (note internally)
|
||||
|
||||
3. Generate (internally) a prioritized queue of candidate clarification questions (maximum 5). Do NOT output them all at once. Apply these constraints:
|
||||
- Maximum of 5 total questions across the whole session.
|
||||
- Each question must be answerable with EITHER:
|
||||
- A short multiple‑choice selection (2–5 distinct, mutually exclusive options), OR
|
||||
- A one-word / short‑phrase answer (explicitly constrain: "Answer in <=5 words").
|
||||
- Only include questions whose answers materially impact architecture, data modeling, task decomposition, test design, UX behavior, operational readiness, or compliance validation.
|
||||
- Ensure category coverage balance: attempt to cover the highest impact unresolved categories first; avoid asking two low-impact questions when a single high-impact area (e.g., security posture) is unresolved.
|
||||
- Exclude questions already answered, trivial stylistic preferences, or plan-level execution details (unless blocking correctness).
|
||||
- Favor clarifications that reduce downstream rework risk or prevent misaligned acceptance tests.
|
||||
- If more than 5 categories remain unresolved, select the top 5 by (Impact * Uncertainty) heuristic.
|
||||
|
||||
4. Sequential questioning loop (interactive):
|
||||
- Present EXACTLY ONE question at a time.
|
||||
- For multiple‑choice questions:
|
||||
- **Analyze all options** and determine the **most suitable option** based on:
|
||||
- Best practices for the project type
|
||||
- Common patterns in similar implementations
|
||||
- Risk reduction (security, performance, maintainability)
|
||||
- Alignment with any explicit project goals or constraints visible in the spec
|
||||
- Present your **recommended option prominently** at the top with clear reasoning (1-2 sentences explaining why this is the best choice).
|
||||
- Format as: `**Recommended:** Option [X] - <reasoning>`
|
||||
- Then render all options as a Markdown table:
|
||||
|
||||
| Option | Description |
|
||||
|--------|-------------|
|
||||
| A | <Option A description> |
|
||||
| B | <Option B description> |
|
||||
| C | <Option C description> (add D/E as needed up to 5) |
|
||||
| Short | Provide a different short answer (<=5 words) (Include only if free-form alternative is appropriate) |
|
||||
|
||||
- After the table, add: `You can reply with the option letter (e.g., "A"), accept the recommendation by saying "yes" or "recommended", or provide your own short answer.`
|
||||
- For short‑answer style (no meaningful discrete options):
|
||||
- Provide your **suggested answer** based on best practices and context.
|
||||
- Format as: `**Suggested:** <your proposed answer> - <brief reasoning>`
|
||||
- Then output: `Format: Short answer (<=5 words). You can accept the suggestion by saying "yes" or "suggested", or provide your own answer.`
|
||||
- After the user answers:
|
||||
- If the user replies with "yes", "recommended", or "suggested", use your previously stated recommendation/suggestion as the answer.
|
||||
- Otherwise, validate the answer maps to one option or fits the <=5 word constraint.
|
||||
- If ambiguous, ask for a quick disambiguation (count still belongs to same question; do not advance).
|
||||
- Once satisfactory, record it in working memory (do not yet write to disk) and move to the next queued question.
|
||||
- Stop asking further questions when:
|
||||
- All critical ambiguities resolved early (remaining queued items become unnecessary), OR
|
||||
- User signals completion ("done", "good", "no more"), OR
|
||||
- You reach 5 asked questions.
|
||||
- Never reveal future queued questions in advance.
|
||||
- If no valid questions exist at start, immediately report no critical ambiguities.
|
||||
|
||||
5. Integration after EACH accepted answer (incremental update approach):
|
||||
- Maintain in-memory representation of the spec (loaded once at start) plus the raw file contents.
|
||||
- For the first integrated answer in this session:
|
||||
- Ensure a `## Clarifications` section exists (create it just after the highest-level contextual/overview section per the spec template if missing).
|
||||
- Under it, create (if not present) a `### Session YYYY-MM-DD` subheading for today.
|
||||
- Append a bullet line immediately after acceptance: `- Q: <question> → A: <final answer>`.
|
||||
- Then immediately apply the clarification to the most appropriate section(s):
|
||||
- Functional ambiguity → Update or add a bullet in Functional Requirements.
|
||||
- User interaction / actor distinction → Update User Stories or Actors subsection (if present) with clarified role, constraint, or scenario.
|
||||
- Data shape / entities → Update Data Model (add fields, types, relationships) preserving ordering; note added constraints succinctly.
|
||||
- Non-functional constraint → Add/modify measurable criteria in Success Criteria > Measurable Outcomes (convert vague adjective to metric or explicit target).
|
||||
- Edge case / negative flow → Add a new bullet under Edge Cases / Error Handling (or create such subsection if template provides placeholder for it).
|
||||
- Terminology conflict → Normalize term across spec; retain original only if necessary by adding `(formerly referred to as "X")` once.
|
||||
- If the clarification invalidates an earlier ambiguous statement, replace that statement instead of duplicating; leave no obsolete contradictory text.
|
||||
- Save the spec file AFTER each integration to minimize risk of context loss (atomic overwrite).
|
||||
- Preserve formatting: do not reorder unrelated sections; keep heading hierarchy intact.
|
||||
- Keep each inserted clarification minimal and testable (avoid narrative drift).
|
||||
|
||||
6. Validation (performed after EACH write plus final pass):
|
||||
- Clarifications session contains exactly one bullet per accepted answer (no duplicates).
|
||||
- Total asked (accepted) questions ≤ 5.
|
||||
- Updated sections contain no lingering vague placeholders the new answer was meant to resolve.
|
||||
- No contradictory earlier statement remains (scan for now-invalid alternative choices removed).
|
||||
- Markdown structure valid; only allowed new headings: `## Clarifications`, `### Session YYYY-MM-DD`.
|
||||
- Terminology consistency: same canonical term used across all updated sections.
|
||||
|
||||
7. Write the updated spec back to `FEATURE_SPEC`.
|
||||
|
||||
8. Report completion (after questioning loop ends or early termination):
|
||||
- Number of questions asked & answered.
|
||||
- Path to updated spec.
|
||||
- Sections touched (list names).
|
||||
- Coverage summary table listing each taxonomy category with Status: Resolved (was Partial/Missing and addressed), Deferred (exceeds question quota or better suited for planning), Clear (already sufficient), Outstanding (still Partial/Missing but low impact).
|
||||
- If any Outstanding or Deferred remain, recommend whether to proceed to `/speckit.plan` or run `/speckit.clarify` again later post-plan.
|
||||
- Suggested next command.
|
||||
|
||||
Behavior rules:
|
||||
|
||||
- If no meaningful ambiguities found (or all potential questions would be low-impact), respond: "No critical ambiguities detected worth formal clarification." and suggest proceeding.
|
||||
- If spec file missing, instruct user to run `/speckit.specify` first (do not create a new spec here).
|
||||
- Never exceed 5 total asked questions (clarification retries for a single question do not count as new questions).
|
||||
- Avoid speculative tech stack questions unless the absence blocks functional clarity.
|
||||
- Respect user early termination signals ("stop", "done", "proceed").
|
||||
- If no questions asked due to full coverage, output a compact coverage summary (all categories Clear) then suggest advancing.
|
||||
- If quota reached with unresolved high-impact categories remaining, explicitly flag them under Deferred with rationale.
|
||||
|
||||
Context for prioritization: $ARGUMENTS
|
||||
|
||||
## Post-Execution Checks
|
||||
|
||||
**Check for extension hooks (after clarification)**:
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_clarify` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
@@ -0,0 +1,150 @@
|
||||
---
|
||||
description: Create or update the project constitution from interactive or provided principle inputs, ensuring all dependent templates stay in sync.
|
||||
handoffs:
|
||||
- label: Build Specification
|
||||
agent: speckit.specify
|
||||
prompt: Implement the feature specification based on the updated constitution. I want to build...
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before constitution update)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_constitution` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
You are updating the project constitution at `.specify/memory/constitution.md`. This file is a TEMPLATE containing placeholder tokens in square brackets (e.g. `[PROJECT_NAME]`, `[PRINCIPLE_1_NAME]`). Your job is to (a) collect/derive concrete values, (b) fill the template precisely, and (c) propagate any amendments across dependent artifacts.
|
||||
|
||||
**Note**: If `.specify/memory/constitution.md` does not exist yet, it should have been initialized from `.specify/templates/constitution-template.md` during project setup. If it's missing, copy the template first.
|
||||
|
||||
Follow this execution flow:
|
||||
|
||||
1. Load the existing constitution at `.specify/memory/constitution.md`.
|
||||
- Identify every placeholder token of the form `[ALL_CAPS_IDENTIFIER]`.
|
||||
**IMPORTANT**: The user might require less or more principles than the ones used in the template. If a number is specified, respect that - follow the general template. You will update the doc accordingly.
|
||||
|
||||
2. Collect/derive values for placeholders:
|
||||
- If user input (conversation) supplies a value, use it.
|
||||
- Otherwise infer from existing repo context (README, docs, prior constitution versions if embedded).
|
||||
- For governance dates: `RATIFICATION_DATE` is the original adoption date (if unknown ask or mark TODO), `LAST_AMENDED_DATE` is today if changes are made, otherwise keep previous.
|
||||
- `CONSTITUTION_VERSION` must increment according to semantic versioning rules:
|
||||
- MAJOR: Backward incompatible governance/principle removals or redefinitions.
|
||||
- MINOR: New principle/section added or materially expanded guidance.
|
||||
- PATCH: Clarifications, wording, typo fixes, non-semantic refinements.
|
||||
- If version bump type ambiguous, propose reasoning before finalizing.
|
||||
|
||||
3. Draft the updated constitution content:
|
||||
- Replace every placeholder with concrete text (no bracketed tokens left except intentionally retained template slots that the project has chosen not to define yet—explicitly justify any left).
|
||||
- Preserve heading hierarchy and comments can be removed once replaced unless they still add clarifying guidance.
|
||||
- Ensure each Principle section: succinct name line, paragraph (or bullet list) capturing non‑negotiable rules, explicit rationale if not obvious.
|
||||
- Ensure Governance section lists amendment procedure, versioning policy, and compliance review expectations.
|
||||
|
||||
4. Consistency propagation checklist (convert prior checklist into active validations):
|
||||
- Read `.specify/templates/plan-template.md` and ensure any "Constitution Check" or rules align with updated principles.
|
||||
- Read `.specify/templates/spec-template.md` for scope/requirements alignment—update if constitution adds/removes mandatory sections or constraints.
|
||||
- Read `.specify/templates/tasks-template.md` and ensure task categorization reflects new or removed principle-driven task types (e.g., observability, versioning, testing discipline).
|
||||
- Read each command file in `.specify/templates/commands/*.md` (including this one) to verify no outdated references (agent-specific names like CLAUDE only) remain when generic guidance is required.
|
||||
- Read any runtime guidance docs (e.g., `README.md`, `docs/quickstart.md`, or agent-specific guidance files if present). Update references to principles changed.
|
||||
|
||||
5. Produce a Sync Impact Report (prepend as an HTML comment at top of the constitution file after update):
|
||||
- Version change: old → new
|
||||
- List of modified principles (old title → new title if renamed)
|
||||
- Added sections
|
||||
- Removed sections
|
||||
- Templates requiring updates (✅ updated / ⚠ pending) with file paths
|
||||
- Follow-up TODOs if any placeholders intentionally deferred.
|
||||
|
||||
6. Validation before final output:
|
||||
- No remaining unexplained bracket tokens.
|
||||
- Version line matches report.
|
||||
- Dates ISO format YYYY-MM-DD.
|
||||
- Principles are declarative, testable, and free of vague language ("should" → replace with MUST/SHOULD rationale where appropriate).
|
||||
|
||||
7. Write the completed constitution back to `.specify/memory/constitution.md` (overwrite).
|
||||
|
||||
8. Output a final summary to the user with:
|
||||
- New version and bump rationale.
|
||||
- Any files flagged for manual follow-up.
|
||||
- Suggested commit message (e.g., `docs: amend constitution to vX.Y.Z (principle additions + governance update)`).
|
||||
|
||||
Formatting & Style Requirements:
|
||||
|
||||
- Use Markdown headings exactly as in the template (do not demote/promote levels).
|
||||
- Wrap long rationale lines to keep readability (<100 chars ideally) but do not hard enforce with awkward breaks.
|
||||
- Keep a single blank line between sections.
|
||||
- Avoid trailing whitespace.
|
||||
|
||||
If the user supplies partial updates (e.g., only one principle revision), still perform validation and version decision steps.
|
||||
|
||||
If critical info missing (e.g., ratification date truly unknown), insert `TODO(<FIELD_NAME>): explanation` and include in the Sync Impact Report under deferred items.
|
||||
|
||||
Do not create a new template; always operate on the existing `.specify/memory/constitution.md` file.
|
||||
|
||||
## Post-Execution Checks
|
||||
|
||||
**Check for extension hooks (after constitution update)**:
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_constitution` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
description: Auto-commit changes after a Spec Kit command completes
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Auto-Commit Changes
|
||||
|
||||
Automatically stage and commit all changes after a Spec Kit command completes.
|
||||
|
||||
## Behavior
|
||||
|
||||
This command is invoked as a hook after (or before) core commands. It:
|
||||
|
||||
1. Determines the event name from the hook context (e.g., if invoked as an `after_specify` hook, the event is `after_specify`; if `before_plan`, the event is `before_plan`)
|
||||
2. Checks `.specify/extensions/git/git-config.yml` for the `auto_commit` section
|
||||
3. Looks up the specific event key to see if auto-commit is enabled
|
||||
4. Falls back to `auto_commit.default` if no event-specific key exists
|
||||
5. Uses the per-command `message` if configured, otherwise a default message
|
||||
6. If enabled and there are uncommitted changes, runs `git add .` + `git commit`
|
||||
|
||||
## Execution
|
||||
|
||||
Determine the event name from the hook that triggered this command, then run the script:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/auto-commit.sh <event_name>`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/auto-commit.ps1 <event_name>`
|
||||
|
||||
Replace `<event_name>` with the actual hook event (e.g., `after_specify`, `before_plan`, `after_implement`).
|
||||
|
||||
## Configuration
|
||||
|
||||
In `.specify/extensions/git/git-config.yml`:
|
||||
|
||||
```yaml
|
||||
auto_commit:
|
||||
default: false # Global toggle — set true to enable for all commands
|
||||
after_specify:
|
||||
enabled: true # Override per-command
|
||||
message: "[Spec Kit] Add specification"
|
||||
after_plan:
|
||||
enabled: false
|
||||
message: "[Spec Kit] Add implementation plan"
|
||||
```
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
- If Git is not available or the current directory is not a repository: skips with a warning
|
||||
- If no config file exists: skips (disabled by default)
|
||||
- If no changes to commit: skips with a message
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
description: Create a feature branch with sequential or timestamp numbering
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Create Feature Branch
|
||||
|
||||
Create and switch to a new git feature branch for the given specification. This command handles **branch creation only** — the spec directory and files are created by the core `/speckit.specify` workflow.
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Environment Variable Override
|
||||
|
||||
If the user explicitly provided `GIT_BRANCH_NAME` (e.g., via environment variable, argument, or in their request), pass it through to the script by setting the `GIT_BRANCH_NAME` environment variable before invoking the script. When `GIT_BRANCH_NAME` is set:
|
||||
- The script uses the exact value as the branch name, bypassing all prefix/suffix generation
|
||||
- `--short-name`, `--number`, and `--timestamp` flags are ignored
|
||||
- `FEATURE_NUM` is extracted from the name if it starts with a numeric prefix, otherwise set to the full branch name
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Verify Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, warn the user and skip branch creation
|
||||
|
||||
## Branch Numbering Mode
|
||||
|
||||
Determine the branch numbering strategy by checking configuration in this order:
|
||||
|
||||
1. Check `.specify/extensions/git/git-config.yml` for `branch_numbering` value
|
||||
2. Check `.specify/init-options.json` for `branch_numbering` value (backward compatibility)
|
||||
3. Default to `sequential` if neither exists
|
||||
|
||||
## Execution
|
||||
|
||||
Generate a concise short name (2-4 words) for the branch:
|
||||
- Analyze the feature description and extract the most meaningful keywords
|
||||
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||
|
||||
Run the appropriate script based on your platform:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --short-name "<short-name>" "<feature description>"`
|
||||
- **Bash (timestamp)**: `.specify/extensions/git/scripts/bash/create-new-feature.sh --json --timestamp --short-name "<short-name>" "<feature description>"`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -ShortName "<short-name>" "<feature description>"`
|
||||
- **PowerShell (timestamp)**: `.specify/extensions/git/scripts/powershell/create-new-feature.ps1 -Json -Timestamp -ShortName "<short-name>" "<feature description>"`
|
||||
|
||||
**IMPORTANT**:
|
||||
- Do NOT pass `--number` — the script determines the correct next number automatically
|
||||
- Always include the JSON flag (`--json` for Bash, `-Json` for PowerShell) so the output can be parsed reliably
|
||||
- You must only ever run this script once per feature
|
||||
- The JSON output will contain `BRANCH_NAME` and `FEATURE_NUM`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the current directory is not a Git repository:
|
||||
- Branch creation is skipped with a warning: `[specify] Warning: Git repository not detected; skipped branch creation`
|
||||
- The script still outputs `BRANCH_NAME` and `FEATURE_NUM` so the caller can reference them
|
||||
|
||||
## Output
|
||||
|
||||
The script outputs JSON with:
|
||||
- `BRANCH_NAME`: The branch name (e.g., `003-user-auth` or `20260319-143022-user-auth`)
|
||||
- `FEATURE_NUM`: The numeric or timestamp prefix used
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
description: Initialize a Git repository with an initial commit
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Initialize Git Repository
|
||||
|
||||
Initialize a Git repository in the current project directory if one does not already exist.
|
||||
|
||||
## Execution
|
||||
|
||||
Run the appropriate script from the project root:
|
||||
|
||||
- **Bash**: `.specify/extensions/git/scripts/bash/initialize-repo.sh`
|
||||
- **PowerShell**: `.specify/extensions/git/scripts/powershell/initialize-repo.ps1`
|
||||
|
||||
If the extension scripts are not found, fall back to:
|
||||
- **Bash**: `git init && git add . && git commit -m "Initial commit from Specify template"`
|
||||
- **PowerShell**: `git init; git add .; git commit -m "Initial commit from Specify template"`
|
||||
|
||||
The script handles all checks internally:
|
||||
- Skips if Git is not available
|
||||
- Skips if already inside a Git repository
|
||||
- Runs `git init`, `git add .`, and `git commit` with an initial commit message
|
||||
|
||||
## Customization
|
||||
|
||||
Replace the script to add project-specific Git initialization steps:
|
||||
- Custom `.gitignore` templates
|
||||
- Default branch naming (`git config init.defaultBranch`)
|
||||
- Git LFS setup
|
||||
- Git hooks installation
|
||||
- Commit signing configuration
|
||||
- Git Flow initialization
|
||||
|
||||
## Output
|
||||
|
||||
On success:
|
||||
- `✓ Git repository initialized`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed:
|
||||
- Warn the user
|
||||
- Skip repository initialization
|
||||
- The project continues to function without Git (specs can still be created under `specs/`)
|
||||
|
||||
If Git is installed but `git init`, `git add .`, or `git commit` fails:
|
||||
- Surface the error to the user
|
||||
- Stop this command rather than continuing with a partially initialized repository
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
description: Detect Git remote URL for GitHub integration
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Detect Git Remote URL
|
||||
|
||||
Detect the Git remote URL for integration with GitHub services (e.g., issue creation).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and return empty:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; cannot determine remote URL
|
||||
```
|
||||
|
||||
## Execution
|
||||
|
||||
Run the following command to get the remote URL:
|
||||
|
||||
```bash
|
||||
git config --get remote.origin.url
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Parse the remote URL and determine:
|
||||
|
||||
1. **Repository owner**: Extract from the URL (e.g., `github` from `https://github.com/github/spec-kit.git`)
|
||||
2. **Repository name**: Extract from the URL (e.g., `spec-kit` from `https://github.com/github/spec-kit.git`)
|
||||
3. **Is GitHub**: Whether the remote points to a GitHub repository
|
||||
|
||||
Supported URL formats:
|
||||
- HTTPS: `https://github.com/<owner>/<repo>.git`
|
||||
- SSH: `git@github.com:<owner>/<repo>.git`
|
||||
|
||||
> [!CAUTION]
|
||||
> ONLY report a GitHub repository if the remote URL actually points to github.com.
|
||||
> Do NOT assume the remote is GitHub if the URL format doesn't match.
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed, the directory is not a Git repository, or no remote is configured:
|
||||
- Return an empty result
|
||||
- Do NOT error — other workflows should continue without Git remote information
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
description: Validate current branch follows feature branch naming conventions
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: git -->
|
||||
<!-- Config: .specify/extensions/git/ -->
|
||||
# Validate Feature Branch
|
||||
|
||||
Validate that the current Git branch follows the expected feature branch naming conventions.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Check if Git is available by running `git rev-parse --is-inside-work-tree 2>/dev/null`
|
||||
- If Git is not available, output a warning and skip validation:
|
||||
```
|
||||
[specify] Warning: Git repository not detected; skipped branch validation
|
||||
```
|
||||
|
||||
## Validation Rules
|
||||
|
||||
Get the current branch name:
|
||||
|
||||
```bash
|
||||
git rev-parse --abbrev-ref HEAD
|
||||
```
|
||||
|
||||
The branch name must match one of these patterns:
|
||||
|
||||
1. **Sequential**: `^[0-9]{3,}-` (e.g., `001-feature-name`, `042-fix-bug`, `1000-big-feature`)
|
||||
2. **Timestamp**: `^[0-9]{8}-[0-9]{6}-` (e.g., `20260319-143022-feature-name`)
|
||||
3. **Conventional prefix**: `^(feat|fix|chore|docs|build|ci|refactor|test|deps)/` (e.g., `feat/add-node-filter`, `fix/ble-reconnect`)
|
||||
|
||||
## Execution
|
||||
|
||||
If on a feature branch (matches either pattern):
|
||||
- Output: `✓ On feature branch: <branch-name>`
|
||||
- For sequential/timestamp branches, check if the corresponding spec directory exists under `specs/`:
|
||||
- For sequential branches, look for `specs/<prefix>-*` where prefix matches the numeric portion
|
||||
- For timestamp branches, look for `specs/<prefix>-*` where prefix matches the `YYYYMMDD-HHMMSS` portion
|
||||
- For conventional prefix branches, skip spec directory lookup (not spec-driven)
|
||||
- If spec directory exists: `✓ Spec directory found: <path>`
|
||||
- If spec directory missing: `⚠ No spec directory found for prefix <prefix>`
|
||||
|
||||
If NOT on a feature branch:
|
||||
- Output: `✗ Not on a feature branch. Current branch: <branch-name>`
|
||||
- Output: `Feature branches should be named like: 001-feature-name, feat/feature-name, or 20260319-143022-feature-name`
|
||||
|
||||
## Graceful Degradation
|
||||
|
||||
If Git is not installed or the directory is not a Git repository:
|
||||
- Check the `SPECIFY_FEATURE` environment variable as a fallback
|
||||
- If set, validate that value against the naming patterns
|
||||
- If not set, skip validation with a warning
|
||||
@@ -0,0 +1,199 @@
|
||||
---
|
||||
description: Execute the implementation plan by processing and executing all tasks defined in tasks.md
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before implementation)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_implement` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Check checklists status** (if FEATURE_DIR/checklists/ exists):
|
||||
- Scan all checklist files in the checklists/ directory
|
||||
- For each checklist, count:
|
||||
- Total items: All lines matching `- [ ]` or `- [X]` or `- [x]`
|
||||
- Completed items: Lines matching `- [X]` or `- [x]`
|
||||
- Incomplete items: Lines matching `- [ ]`
|
||||
- Create a status table:
|
||||
|
||||
```text
|
||||
| Checklist | Total | Completed | Incomplete | Status |
|
||||
|-----------|-------|-----------|------------|--------|
|
||||
| ux.md | 12 | 12 | 0 | ✓ PASS |
|
||||
| test.md | 8 | 5 | 3 | ✗ FAIL |
|
||||
| security.md | 6 | 6 | 0 | ✓ PASS |
|
||||
```
|
||||
|
||||
- Calculate overall status:
|
||||
- **PASS**: All checklists have 0 incomplete items
|
||||
- **FAIL**: One or more checklists have incomplete items
|
||||
|
||||
- **If any checklist is incomplete**:
|
||||
- Display the table with incomplete item counts
|
||||
- **STOP** and ask: "Some checklists are incomplete. Do you want to proceed with implementation anyway? (yes/no)"
|
||||
- Wait for user response before continuing
|
||||
- If user says "no" or "wait" or "stop", halt execution
|
||||
- If user says "yes" or "proceed" or "continue", proceed to step 3
|
||||
|
||||
- **If all checklists are complete**:
|
||||
- Display the table showing all checklists passed
|
||||
- Automatically proceed to step 3
|
||||
|
||||
3. Load and analyze the implementation context:
|
||||
- **REQUIRED**: Read tasks.md for the complete task list and execution plan
|
||||
- **REQUIRED**: Read plan.md for tech stack, architecture, and file structure
|
||||
- **IF EXISTS**: Read data-model.md for entities and relationships
|
||||
- **IF EXISTS**: Read contracts/ for API specifications and test requirements
|
||||
- **IF EXISTS**: Read research.md for technical decisions and constraints
|
||||
- **IF EXISTS**: Read .specify/memory/constitution.md for governance constraints
|
||||
- **IF EXISTS**: Read quickstart.md for integration scenarios
|
||||
|
||||
4. **Project Setup Verification**:
|
||||
- **REQUIRED**: Create/verify ignore files based on actual project setup:
|
||||
|
||||
**Detection & Creation Logic**:
|
||||
- Check if the following command succeeds to determine if the repository is a git repo (create/verify .gitignore if so):
|
||||
|
||||
```sh
|
||||
git rev-parse --git-dir 2>/dev/null
|
||||
```
|
||||
|
||||
- Check if Dockerfile* exists or Docker in plan.md → create/verify .dockerignore
|
||||
- Check if .eslintrc* exists → create/verify .eslintignore
|
||||
- Check if eslint.config.* exists → ensure the config's `ignores` entries cover required patterns
|
||||
- Check if .prettierrc* exists → create/verify .prettierignore
|
||||
- Check if .npmrc or package.json exists → create/verify .npmignore (if publishing)
|
||||
- Check if terraform files (*.tf) exist → create/verify .terraformignore
|
||||
- Check if .helmignore needed (helm charts present) → create/verify .helmignore
|
||||
|
||||
**If ignore file already exists**: Verify it contains essential patterns, append missing critical patterns only
|
||||
**If ignore file missing**: Create with full pattern set for detected technology
|
||||
|
||||
**Common Patterns by Technology** (from plan.md tech stack):
|
||||
- **Node.js/JavaScript/TypeScript**: `node_modules/`, `dist/`, `build/`, `*.log`, `.env*`
|
||||
- **Python**: `__pycache__/`, `*.pyc`, `.venv/`, `venv/`, `dist/`, `*.egg-info/`
|
||||
- **Java**: `target/`, `*.class`, `*.jar`, `.gradle/`, `build/`
|
||||
- **C#/.NET**: `bin/`, `obj/`, `*.user`, `*.suo`, `packages/`
|
||||
- **Go**: `*.exe`, `*.test`, `vendor/`, `*.out`
|
||||
- **Ruby**: `.bundle/`, `log/`, `tmp/`, `*.gem`, `vendor/bundle/`
|
||||
- **PHP**: `vendor/`, `*.log`, `*.cache`, `*.env`
|
||||
- **Rust**: `target/`, `debug/`, `release/`, `*.rs.bk`, `*.rlib`, `*.prof*`, `.idea/`, `*.log`, `.env*`
|
||||
- **Kotlin**: `build/`, `out/`, `.gradle/`, `.idea/`, `*.class`, `*.jar`, `*.iml`, `*.log`, `.env*`
|
||||
- **C++**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.so`, `*.a`, `*.exe`, `*.dll`, `.idea/`, `*.log`, `.env*`
|
||||
- **C**: `build/`, `bin/`, `obj/`, `out/`, `*.o`, `*.a`, `*.so`, `*.exe`, `*.dll`, `autom4te.cache/`, `config.status`, `config.log`, `.idea/`, `*.log`, `.env*`
|
||||
- **Swift**: `.build/`, `DerivedData/`, `*.swiftpm/`, `Packages/`
|
||||
- **R**: `.Rproj.user/`, `.Rhistory`, `.RData`, `.Ruserdata`, `*.Rproj`, `packrat/`, `renv/`
|
||||
- **Universal**: `.DS_Store`, `Thumbs.db`, `*.tmp`, `*.swp`, `.vscode/`, `.idea/`
|
||||
|
||||
**Tool-Specific Patterns**:
|
||||
- **Docker**: `node_modules/`, `.git/`, `Dockerfile*`, `.dockerignore`, `*.log*`, `.env*`, `coverage/`
|
||||
- **ESLint**: `node_modules/`, `dist/`, `build/`, `coverage/`, `*.min.js`
|
||||
- **Prettier**: `node_modules/`, `dist/`, `build/`, `coverage/`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`
|
||||
- **Terraform**: `.terraform/`, `*.tfstate*`, `*.tfvars`, `.terraform.lock.hcl`
|
||||
- **Kubernetes/k8s**: `*.secret.yaml`, `secrets/`, `.kube/`, `kubeconfig*`, `*.key`, `*.crt`
|
||||
|
||||
5. Parse tasks.md structure and extract:
|
||||
- **Task phases**: Setup, Tests, Core, Integration, Polish
|
||||
- **Task dependencies**: Sequential vs parallel execution rules
|
||||
- **Task details**: ID, description, file paths, parallel markers [P]
|
||||
- **Execution flow**: Order and dependency requirements
|
||||
|
||||
6. Execute implementation following the task plan:
|
||||
- **Phase-by-phase execution**: Complete each phase before moving to the next
|
||||
- **Respect dependencies**: Run sequential tasks in order, parallel tasks [P] can run together
|
||||
- **Follow TDD approach**: Execute test tasks before their corresponding implementation tasks
|
||||
- **File-based coordination**: Tasks affecting the same files must run sequentially
|
||||
- **Validation checkpoints**: Verify each phase completion before proceeding
|
||||
|
||||
7. Implementation execution rules:
|
||||
- **Setup first**: Initialize project structure, dependencies, configuration
|
||||
- **Tests before code**: If you need to write tests for contracts, entities, and integration scenarios
|
||||
- **Core development**: Implement models, services, CLI commands, endpoints
|
||||
- **Integration work**: Database connections, middleware, logging, external services
|
||||
- **Polish and validation**: Unit tests, performance optimization, documentation
|
||||
|
||||
8. Progress tracking and error handling:
|
||||
- Report progress after each completed task
|
||||
- Halt execution if any non-parallel task fails
|
||||
- For parallel tasks [P], continue with successful tasks, report failed ones
|
||||
- Provide clear error messages with context for debugging
|
||||
- Suggest next steps if implementation cannot proceed
|
||||
- **IMPORTANT** For completed tasks, make sure to mark the task off as [X] in the tasks file.
|
||||
|
||||
9. Completion validation:
|
||||
- Verify all required tasks are completed
|
||||
- Check that implemented features match the original specification
|
||||
- Validate that tests pass and coverage meets requirements
|
||||
- Confirm the implementation follows the technical plan
|
||||
- Report final status with summary of completed work
|
||||
|
||||
Note: This command assumes a complete task breakdown exists in tasks.md. If tasks are incomplete or missing, suggest running `/speckit.tasks` first to regenerate the task list.
|
||||
|
||||
10. **Check for extension hooks**: After completion validation, check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_implement` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
@@ -0,0 +1,290 @@
|
||||
---
|
||||
description: Analyze AI session patterns to suggest constitution rules or memory entries.
|
||||
handoffs:
|
||||
- label: Amend constitution
|
||||
agent: speckit.constitution
|
||||
prompt: Add the approved rules to the constitution
|
||||
- label: Optimize governance
|
||||
agent: speckit.optimize.run
|
||||
prompt: Run a full governance audit after adding new rules
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: optimize -->
|
||||
<!-- Config: .specify/extensions/optimize/ -->
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
Arguments: `--rules-only` to skip memory suggestions, `--memory-only` to skip rule suggestions, `--since <commit>` to limit analysis scope.
|
||||
|
||||
## Goal
|
||||
|
||||
Analyze the current AI session's work to identify patterns of mistakes, repetitive corrections, and governance gaps. Produce suggestions for new constitution rules or memory entries that would prevent these patterns in future sessions. Apply **nothing** without explicit user consent.
|
||||
|
||||
This command answers: "What did this AI session learn the hard way that future sessions should know from the start?"
|
||||
|
||||
**When to use**: End of an implementation session, before creating a PR/MR. Run while the session context is still fresh.
|
||||
|
||||
## Operating Constraints
|
||||
|
||||
- **Suggest-only**: NEVER add rules to the constitution or write memory files without explicit user consent. Always present proposals first.
|
||||
- **Evidence-based**: Every suggestion MUST cite specific files, diffs, or session events as evidence. No speculative rules.
|
||||
- **Spec-kit standard paths**: Use `.specify/memory/constitution.md` (follow redirects) for the constitution. Memory files go to the tool's memory system (e.g., `.claude/` for Claude Code).
|
||||
- **Minimal governance growth**: Prefer memory entries over constitution rules unless the pattern affects all team members and all AI tools. Constitution rules have a token cost paid on every future session.
|
||||
- **Deterministic proposals**: Every proposed rule MUST be concrete, MUST/SHOULD qualified, and deterministic — no vague language.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### 1. Determine Analysis Scope
|
||||
|
||||
Identify the work done in the current session:
|
||||
|
||||
1. **Git-based scope** (primary):
|
||||
- Run `git log --oneline` to find recent commits
|
||||
- If `--since <commit>` is provided, use that as the starting point
|
||||
- Otherwise, heuristically identify the session boundary: look for commits from today, or the most recent cluster of commits by the current author
|
||||
- Record the commit range as `SESSION_RANGE`
|
||||
|
||||
2. **Diff analysis**:
|
||||
- Run `git diff <SESSION_RANGE>` to get the full session diff
|
||||
- Run `git diff --stat <SESSION_RANGE>` for a file-level overview
|
||||
- Record all modified files as `SESSION_FILES`
|
||||
|
||||
3. **Session metadata**:
|
||||
- Count: commits, files modified, lines added, lines removed
|
||||
- Identify: primary language(s), directories touched, components affected
|
||||
|
||||
If no git changes are found, inform the user: "No session changes detected. This command analyzes git history to find patterns. Run it after making changes."
|
||||
|
||||
### 2. Load Current Governance
|
||||
|
||||
Load the constitution (following the standard resolution chain from `.specify/memory/constitution.md`). Parse into a flat list of rules for gap analysis.
|
||||
|
||||
Load config from `.specify/extensions/optimize/optimize-config.yml` (or defaults) for:
|
||||
- `min_corrections_to_flag` (default: 2)
|
||||
- `include_memory_suggestions` (default: true)
|
||||
- `include_rule_suggestions` (default: true)
|
||||
|
||||
### 3. Detect Mistake Patterns
|
||||
|
||||
Analyze the session's git history for evidence of repeated corrections:
|
||||
|
||||
#### 3a. Repeated Correction Patterns
|
||||
|
||||
For each file in `SESSION_FILES`, check commit history within `SESSION_RANGE`:
|
||||
|
||||
- **Same-file re-edits**: Files modified in 3+ separate commits within the session. This suggests the AI got it wrong initially and had to correct multiple times. Record the file and the nature of each change.
|
||||
|
||||
- **Revert patterns**: Look for pairs of commits where the second commit undoes part of the first (same lines modified in opposite directions). This indicates the AI made a wrong choice that was immediately corrected.
|
||||
|
||||
- **Fix-after-fix chains**: Commits with messages containing correction indicators: "fix", "actually", "oops", "correct", "should be", "typo", "missed", "forgot". Each represents a mistake the AI made.
|
||||
|
||||
- **Checkstyle/linter fix commits**: Commits that only fix style violations (detected by diffing against checkstyle or linter output). These indicate the AI didn't follow style rules the first time.
|
||||
|
||||
#### 3b. Repeated Transformation Patterns
|
||||
|
||||
Look for the same type of change applied to multiple files:
|
||||
|
||||
- **Boilerplate additions**: Same code pattern added to 3+ files (e.g., adding `this.` prefix, adding file headers, adding import ordering). If the AI had to manually apply the same transformation many times, it suggests a rule that should be automated.
|
||||
|
||||
- **Naming corrections**: Same type of rename applied multiple times (e.g., removing `Entity` suffix from non-entity classes, or adding it to entity classes). Suggests unclear naming rules.
|
||||
|
||||
- **Pattern enforcement**: Same structural change across files (e.g., converting `@Service` annotations to `@Bean` registration). Suggests the AI kept defaulting to a wrong pattern.
|
||||
|
||||
#### 3c. Constitution Violation Patterns
|
||||
|
||||
For each detected mistake pattern:
|
||||
|
||||
1. Search the constitution for a rule that should have prevented it
|
||||
2. If a rule exists:
|
||||
- The AI violated an existing rule → the rule may be ambiguous, poorly worded, or easy to miss
|
||||
- Record as: "Existing rule violated — suggest rewrite for clarity"
|
||||
3. If no rule exists:
|
||||
- The pattern represents a governance gap
|
||||
- Record as: "No existing rule — suggest new rule"
|
||||
|
||||
### 4. Detect Repetitive Task Patterns
|
||||
|
||||
Beyond mistakes, identify tasks the AI performed repeatedly that suggest missing automation or rules:
|
||||
|
||||
- **Manual enforcement**: Tasks that could be automated (e.g., repeatedly checking import order → should be enforced by a linter, repeatedly adding JavaDoc → should be caught by checkstyle)
|
||||
|
||||
- **Boilerplate generation**: Repeated creation of similar files (e.g., creating test classes with the same structure, creating DTOs with the same patterns). Suggests templates or generators would help.
|
||||
|
||||
- **Cross-file consistency**: Changes that required updating multiple files to stay consistent (e.g., adding a field to an entity requires updating the DTO, the mapper, and the test). Suggests a documentation or tooling gap.
|
||||
|
||||
### 5. Generate Proposals
|
||||
|
||||
For each detected pattern, generate a proposal. Proposals are either **constitution rules** or **memory entries**.
|
||||
|
||||
#### Constitution Rule Proposals
|
||||
|
||||
Only propose constitution rules when the pattern:
|
||||
- Affects all developers (not just one person's preference)
|
||||
- Applies across all AI tools (not tool-specific)
|
||||
- Is project-wide (not component-specific)
|
||||
- Would prevent the mistake in future sessions
|
||||
|
||||
Format for each proposed rule:
|
||||
|
||||
```markdown
|
||||
### Proposed Rule: <short title>
|
||||
|
||||
**Type**: Constitution Rule
|
||||
**Principle Placement**: <existing principle name, or "New Principle: <name>">
|
||||
**Severity**: MUST / SHOULD
|
||||
|
||||
**Rule Text**:
|
||||
> <Concrete, deterministic rule text. MUST/SHOULD qualified. No vague language.>
|
||||
|
||||
**Rationale**: <What session pattern triggered this>
|
||||
|
||||
**Evidence**:
|
||||
- `<file>:<line>` — <what happened>
|
||||
- Commit `<hash>` — <what the fix was>
|
||||
- Pattern repeated <N> times across <files>
|
||||
|
||||
**Enforcement Suggestion**: <How to automate: checkstyle rule, Gradle task, CI check, or "manual review only">
|
||||
|
||||
**Token Cost**: ~<estimated tokens this rule adds to the constitution>
|
||||
```
|
||||
|
||||
#### Memory Entry Proposals
|
||||
|
||||
Propose memory entries when the pattern:
|
||||
- Is specific to this user or project (not universal)
|
||||
- Is preference-based rather than governance-based
|
||||
- Would help the AI agent in future sessions without being a formal rule
|
||||
|
||||
Format for each proposed memory:
|
||||
|
||||
```markdown
|
||||
### Proposed Memory: <short title>
|
||||
|
||||
**Type**: Memory Entry
|
||||
**Memory Type**: feedback / user / project / reference
|
||||
**File Name**: <proposed filename, e.g., feedback_import_order.md>
|
||||
|
||||
**Content**:
|
||||
> <Proposed memory content, structured per the memory type's conventions>
|
||||
|
||||
**Rationale**: <What session pattern triggered this>
|
||||
|
||||
**Evidence**:
|
||||
- <specific examples from the session>
|
||||
```
|
||||
|
||||
### 6. Present Learning Report
|
||||
|
||||
Present all proposals to the user:
|
||||
|
||||
```markdown
|
||||
## Session Learning Report
|
||||
|
||||
**Session**: <commit range>
|
||||
**Files Modified**: <count>
|
||||
**Commits Analyzed**: <count>
|
||||
|
||||
### Session Patterns Detected
|
||||
|
||||
| # | Pattern | Occurrences | Type | Proposal |
|
||||
|---|---------|-------------|------|----------|
|
||||
| 1 | <pattern description> | X times | Mistake | Rule / Memory |
|
||||
| 2 | <pattern description> | X times | Repetitive | Rule / Memory |
|
||||
| ... | ... | ... | ... | ... |
|
||||
|
||||
### Existing Rules Violated
|
||||
|
||||
| # | Rule | Principle | Violation Count | Issue |
|
||||
|---|------|-----------|-----------------|-------|
|
||||
| 1 | <rule text> | <principle> | X | Ambiguous / Easy to miss |
|
||||
|
||||
**Suggestion**: Rewrite for clarity → <proposed rewrite>
|
||||
|
||||
### Proposed Constitution Rules (<count>)
|
||||
|
||||
[List each proposed rule per format above]
|
||||
|
||||
### Proposed Memory Entries (<count>)
|
||||
|
||||
[List each proposed memory per format above]
|
||||
|
||||
### Summary
|
||||
|
||||
- **Total patterns detected**: X
|
||||
- **Constitution rules proposed**: X (adds ~Y tokens to governance)
|
||||
- **Memory entries proposed**: X
|
||||
- **Existing rules to rewrite**: X
|
||||
|
||||
**Which proposals would you like to apply?**
|
||||
Select by number, "all rules", "all memories", or "none".
|
||||
```
|
||||
|
||||
Wait for user selection. Do NOT apply anything without explicit consent.
|
||||
|
||||
### 7. Apply Approved Proposals
|
||||
|
||||
For each user-approved proposal:
|
||||
|
||||
**Constitution rules**:
|
||||
- Do NOT directly edit the constitution
|
||||
- Hand off to `/speckit.constitution` with the specific rule text, principle placement, and rationale
|
||||
- This ensures proper version bumping and governance compliance
|
||||
|
||||
**Memory entries**:
|
||||
- Write the memory file to the appropriate memory directory
|
||||
- Update the memory index (e.g., `MEMORY.md`)
|
||||
- Confirm each write to the user
|
||||
|
||||
**Rule rewrites** (for existing rules that were violated due to ambiguity):
|
||||
- Hand off to `/speckit.optimize.run --category ai_interpretability` for a targeted rewrite
|
||||
- Or hand off to `/speckit.constitution` for manual amendment
|
||||
|
||||
### 8. Output Summary
|
||||
|
||||
```markdown
|
||||
## Session Learning Complete
|
||||
|
||||
### Applied
|
||||
- Constitution rules handed to `/speckit.constitution`: X
|
||||
- Memory entries written: X
|
||||
- Rule rewrites suggested: X
|
||||
|
||||
### Declined
|
||||
- [List of declined proposals — preserved in report for future reference]
|
||||
|
||||
### Learning Report Saved
|
||||
- Report: `.specify/optimize/learning-report-<date>.md`
|
||||
|
||||
### Recommended Follow-Up
|
||||
- Run `/speckit.constitution` to formally add approved rules
|
||||
- Run `/speckit.optimize.run` to verify the new rules don't create contradictions
|
||||
- Run `/speckit.optimize.tokens` to check token budget after additions
|
||||
```
|
||||
|
||||
### 9. Save Learning Report
|
||||
|
||||
Ask the user: "Save this learning report to `.specify/optimize/learning-report-<date>.md`?"
|
||||
|
||||
If approved, save the full report for historical reference. This enables trend analysis across sessions: "Are the same patterns recurring despite rules being added?"
|
||||
|
||||
## Operating Principles
|
||||
|
||||
### Evidence-Based Only
|
||||
Every proposal cites specific files, line numbers, commits, and pattern counts. No speculative rules based on general best practices — only rules motivated by observed session behavior.
|
||||
|
||||
### Minimal Governance Growth
|
||||
Prefer memory entries (zero token cost to future sessions) over constitution rules (permanent token cost). Only propose constitution rules when the pattern is project-wide, tool-agnostic, and would benefit all future AI sessions.
|
||||
|
||||
### Deterministic Proposals
|
||||
Every proposed rule text is concrete, MUST/SHOULD qualified, and deterministic. If the AI agent writing the proposal cannot make the rule deterministic, it should propose a memory entry instead.
|
||||
|
||||
### Suggest-Only
|
||||
The learning report is a proposal, not an action. The user reviews each suggestion individually and decides what to keep. Declined proposals are preserved in the report for future reconsideration.
|
||||
|
||||
### Session Boundary Respect
|
||||
This command only analyzes the current session's work. It does not dig into older history or make suggestions based on past sessions. For historical analysis, use `/speckit.optimize.run` which audits the full constitution.
|
||||
@@ -0,0 +1,357 @@
|
||||
---
|
||||
description: Audit and optimize governance documents for AI context efficiency.
|
||||
handoffs:
|
||||
- label: Amend constitution
|
||||
agent: speckit.constitution
|
||||
prompt: Apply the approved optimization changes to the constitution
|
||||
- label: Verify consistency
|
||||
agent: speckit.analyze
|
||||
prompt: Verify cross-artifact consistency after governance changes
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: optimize -->
|
||||
<!-- Config: .specify/extensions/optimize/ -->
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
Arguments: `--category <name>` to run a single category, `--report-only` to skip the apply step.
|
||||
|
||||
## Goal
|
||||
|
||||
Audit an existing, populated constitution for problems that are **uniquely harmful in AI-driven development**: token bloat, stale rules, ambiguity causing non-deterministic behavior, redundant governance echoes, and incoherent structure. Produce a findings report with a concrete optimization plan. Apply **only** what the user explicitly approves.
|
||||
|
||||
This command does NOT author or amend the constitution (that is `/speckit.constitution`). It audits and optimizes existing content.
|
||||
|
||||
## Operating Constraints
|
||||
|
||||
- **Suggest-only**: NEVER modify any file without explicit user consent. Always present findings and a plan first, then ask before applying.
|
||||
- **Semantic preservation**: Optimization removes redundancy, not intent. Every governance rule must survive compression — only its expression changes.
|
||||
- **Spec-kit standard paths**: Use `.specify/memory/constitution.md` as the primary constitution path. If it contains a redirect (e.g., "Read and follow the constitution in `<path>`"), follow the redirect to the actual file. Fallback discovery order: `CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`.
|
||||
- **Constitution authority**: Respect the constitution's own governance section. Version bumps follow its defined semver policy.
|
||||
- **Idempotency**: Running this command twice in succession on an optimized constitution MUST produce no new findings.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### 1. Locate and Load Constitution
|
||||
|
||||
Resolution order:
|
||||
1. Read `.specify/memory/constitution.md`
|
||||
2. If it contains a redirect pattern (e.g., `Read and follow the constitution in <path>`), follow the redirect to the actual file
|
||||
3. If `.specify/memory/constitution.md` does not exist, check fallbacks: `CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`
|
||||
4. Abort with clear error if no constitution found
|
||||
|
||||
Validate the file is a populated constitution (not a raw template with `[PLACEHOLDER]` tokens). If it is still a template, advise the user to run `/speckit.constitution` first and abort.
|
||||
|
||||
Record the resolved file path as `CONSTITUTION_PATH` for all subsequent steps.
|
||||
|
||||
### 2. Load Configuration
|
||||
|
||||
Check for project config at `.specify/extensions/optimize/optimize-config.yml`. If not found, use `defaults` from `extension.yml`. Parse:
|
||||
- Which categories are enabled
|
||||
- Threshold values
|
||||
- Target context window size
|
||||
|
||||
### 3. Parse Constitution Structure
|
||||
|
||||
Extract and catalog:
|
||||
- **Sync Impact Report** (HTML comment at top) — version, dates, template status
|
||||
- **Version History** (HTML comment) — all version entries
|
||||
- **Title** (H1 heading)
|
||||
- **Core Principles** — for each: number, name, NON-NEGOTIABLE flag, individual rules as a flat list (each bullet, MUST/SHOULD statement, or table row with normative content)
|
||||
- **Quality Gates** table
|
||||
- **Governance** section — authority, amendment process, version semantics
|
||||
- **Version footer** — current version, ratified date, last amended date
|
||||
|
||||
Store each principle's rules as a flat list for cross-comparison.
|
||||
|
||||
### 4. Discover Governance Ecosystem
|
||||
|
||||
Scan for all governance files that AI agents may load:
|
||||
- `.specify/memory/constitution.md` (and its redirect target)
|
||||
- `CLAUDE.md` (root)
|
||||
- `AGENTS.md` (root)
|
||||
- `.github/copilot-instructions.md`
|
||||
- All files in `.ai/rules/` (if directory exists)
|
||||
- All files in `.specify/memory/` (if any beyond constitution)
|
||||
|
||||
For each file found, record: path, size in characters, estimated tokens (chars ÷ `chars_per_token`).
|
||||
|
||||
### 5. Run Analysis Categories
|
||||
|
||||
Run each enabled category. If `--category <name>` was provided, run only that one.
|
||||
|
||||
---
|
||||
|
||||
#### Category 1: Token Budget Analysis
|
||||
|
||||
*Why AI-specific*: AI agents pay the full token cost of the constitution on every single invocation. A 3000-token constitution across 50 daily sessions = 150K tokens/day of governance overhead. Humans skim; AI tokenizes everything.
|
||||
|
||||
**Checks:**
|
||||
|
||||
1. **Total token estimate**: Calculate chars ÷ `chars_per_token` for the constitution and each governance file discovered in Step 4.
|
||||
|
||||
2. **Per-section token breakdown**: For each H2/H3 section in the constitution, calculate its token cost and compute a "governance density" score = (number of distinct rules in section) ÷ (estimated tokens in section). Low density = high waste.
|
||||
|
||||
3. **Version history bloat**: Detect HTML comment blocks containing version history (pattern: `<!-- ... v\d+\.\d+\.\d+ ... -->`). These are valuable for humans reviewing the file but add zero governance value for AI agents. Measure their token cost.
|
||||
|
||||
4. **Anti-pattern tax**: Detect sections containing both "WRONG" / "Anti-Pattern" / "NEVER" code blocks AND "CORRECT" / "RIGHT" / "Correct Pattern" code blocks. The anti-pattern is often inferable from the correct pattern alone. Measure the token cost of each anti-pattern block.
|
||||
|
||||
5. **Inline code duplication**: For each fenced code block in the constitution, search the repository for matching files or near-matching code. If the code exists in the repo, it can be replaced with a file reference (e.g., "See `src/.../BeanConfiguration.java`"). Use glob/grep to find matching class names, method signatures, or patterns from the code block.
|
||||
|
||||
6. **Double-governance**: For each rule, check if an equivalent enforcement exists in:
|
||||
- Checkstyle config (glob for `**/checkstyle*.xml`)
|
||||
- Build tool config (glob for `build.gradle*`, `buildSrc/**`)
|
||||
- Dependency management (glob for `**/libs.versions.toml`, `**/pom.xml`)
|
||||
- CI pipeline config (glob for `.github/workflows/*`, `.pipelines/*`, `azure-pipelines*`)
|
||||
If a tool already enforces the rule, the constitution copy is redundant — it can be compressed to a reference.
|
||||
|
||||
7. **Prose-table overlap**: Detect when the same information appears in both prose (paragraph/bullets) and a table within the same H3 section. Measure the overlap token cost.
|
||||
|
||||
**Output per finding**: Section path, token cost, issue type, suggested fix, projected savings.
|
||||
|
||||
---
|
||||
|
||||
#### Category 2: Rule Health Analysis
|
||||
|
||||
*Why AI-specific*: AI agents have no institutional memory. A rule added 6 months ago for a one-time incident is enforced with the same authority as a core architectural principle. There is no natural "forgetting" mechanism — stale rules persist forever.
|
||||
|
||||
**Checks:**
|
||||
|
||||
1. **Incident-specific rules**: Detect rules that reference specific class names, method names, or file paths (backtick-wrapped identifiers like `` `ClassName` ``, `` `methodName` ``). Cross-reference: search the codebase for the named artifact. If it exists in only one component or has been removed, the rule may be too narrow for a project-wide constitution or entirely stale.
|
||||
|
||||
2. **Superseded rules**: Within the same principle and across principles, detect rules that govern the same domain at different specificity levels. Example: "no magic numbers" (general) + "use named constants for all numeric values" (specific) — the specific one supersedes the general.
|
||||
|
||||
3. **Graduated rules**: For each rule, check if it is fully enforced by automation:
|
||||
- Parse checkstyle config for matching check names (e.g., `MagicNumberCheck` → "no magic numbers" rule)
|
||||
- Check `buildSrc/` for custom Gradle tasks (e.g., `CheckFileHeaderTask` → "file headers required")
|
||||
- Check CI pipeline for quality gates
|
||||
If a rule is 100% enforced by tooling, the constitution statement is redundant and can be compressed to: "Enforced by [tool] — see `[config path]`."
|
||||
|
||||
4. **Stale rules via git history**: Run `git log --follow -p` on the constitution file. For rules introduced in older versions (check the version history comment block), evaluate whether the context that motivated the rule still applies. Flag rules that haven't been touched in >3 versions AND reference specific artifacts.
|
||||
|
||||
**Output per finding**: Rule text, principle location, health classification (CORE / OPERATIONAL / INCIDENT-RESPONSE / GRADUATED), recommendation, evidence.
|
||||
|
||||
---
|
||||
|
||||
#### Category 3: AI Interpretability Analysis
|
||||
|
||||
*Why AI-specific*: Ambiguity in the constitution causes non-deterministic behavior — different AI sessions resolve the same ambiguity differently, leading to inconsistent codebases. Rules that require human judgment are dead code to AI agents.
|
||||
|
||||
**Checks:**
|
||||
|
||||
1. **Unenforceable rules (require human action)**: Scan for rules containing: "check with", "discuss with", "team lead approval", "manual review", "consult", "ask before", "get sign-off". These are meaningful to humans but unactionable by AI agents.
|
||||
|
||||
2. **Ambiguous quantifiers**: Scan for rules containing: "appropriate", "reasonable", "sufficient", "proper", "clean", "good", "well-structured", "meaningful", "as needed", "where possible", "when necessary". These are interpreted differently by different AI models and sessions. For each, propose a concrete, deterministic replacement.
|
||||
|
||||
3. **Missing enforcement mechanism**: For each MUST rule, check if there is a corresponding automated enforcement (checkstyle, CI, Gradle task, spec-kit command). If a rule says MUST but nothing checks compliance, it is "aspirational governance" — effective only when the AI agent happens to remember it.
|
||||
|
||||
4. **Contradiction detection**: Parse all rules into normalized assertion form. Check for:
|
||||
- **Direct contradictions**: Rule A says "MUST X" and Rule B says "MUST NOT X" or implies not-X
|
||||
- **Indirect contradictions**: Rule A requires pattern P, Rule B requires pattern Q, where P and Q are mutually exclusive in practice
|
||||
- **Scope conflicts**: Two principles claim authority over the same domain with different guidance
|
||||
For each pair, assess severity: CRITICAL (direct), HIGH (indirect), MEDIUM (scope overlap).
|
||||
|
||||
5. **Implicit context dependencies**: Scan for rules referencing: "the team's convention", "our usual approach", "as discussed", "you know", "the standard pattern" (without specifying which). These rely on context that AI agents don't carry between sessions.
|
||||
|
||||
6. **Non-deterministic choice points**: Scan for rules with: "or" / "either...or" / "when appropriate" / "use your judgment" / "consider" that leave the resolution to the AI agent without a default. Each is a source of cross-session inconsistency.
|
||||
|
||||
**Output per finding**: Rule text, location, interpretability issue type, proposed deterministic rewrite, severity.
|
||||
|
||||
**Per-rule score** (0–100): Based on specificity (25), enforceability (25), determinism (25), self-containedness (25). Report average per principle and overall.
|
||||
|
||||
---
|
||||
|
||||
#### Category 4: Semantic Compression
|
||||
|
||||
*Why AI-specific*: 10 verbose rules that could be expressed as 2 concise rules cost 5× more context tokens for identical governance. This is not about human readability — it is about information density for context-limited AI consumers.
|
||||
|
||||
**Checks:**
|
||||
|
||||
1. **Collapsible rule clusters**: Group rules by semantic domain (testing, naming, architecture, dependencies, documentation). Within each group, identify rules that share a common parent assertion. Example: "No wildcard imports", "No magic numbers", "Explicit this. prefix", "JavaDoc required" are all checkstyle-enforced quality rules that could be collapsed to a single reference: "All code MUST pass checkstyle (`config/checkstyle/checkstyle.xml`) with zero violations." Measure per-cluster token savings.
|
||||
|
||||
2. **Inline-to-reference conversion**: For each fenced code block (identified in Cat 1), if the code exists as an actual file in the repo, propose replacing the inline block with a file reference. Example: 12 lines of `BeanConfiguration` Java code → "See `src/.../BeanConfiguration.java` for the canonical pattern." Measure per-block token savings.
|
||||
|
||||
3. **Redundant examples**: For sections containing both WRONG and CORRECT code blocks, evaluate whether the anti-pattern is inferable from the correct pattern and the rule text. If yes, the anti-pattern block can be removed. Measure savings.
|
||||
|
||||
4. **Table compression**: Detect tables where most cells follow a derivable pattern. Example: A 7-line Model Types table could be 3 lines of prose. Measure savings.
|
||||
|
||||
5. **Compressed constitution draft**: If total projected savings exceed 10%, produce a full compressed draft that preserves every governance rule while minimizing tokens. Include a "governance preservation check" listing every original rule and its location in the compressed version.
|
||||
|
||||
**Output per finding**: Original section, proposed replacement, token savings, governance preservation confirmation.
|
||||
|
||||
---
|
||||
|
||||
#### Category 5: Constitution Coherence
|
||||
|
||||
*Why AI-specific*: AI agents read the constitution linearly and assign roughly equal weight to all sections. A constitution that has grown organically through many amendments tends to be structurally unbalanced — one principle with 30 rules, another with 3. Related rules scattered across principles. Missing cross-references. No clear narrative arc. A human can mentally reorganize; an AI agent cannot.
|
||||
|
||||
**Checks:**
|
||||
|
||||
1. **Principle balance**: Count rules per principle (bullets, MUST/SHOULD statements, normative table rows). Flag if the largest principle has more than `principle_balance_ratio` (default: 3×) the rules of the smallest. Report the count per principle.
|
||||
|
||||
2. **Rule scatter**: For each rule, extract its semantic domain (testing, naming, architecture, dependencies, documentation, API, security). If rules from the same domain appear in more than one principle, flag as scattered. Example: naming conventions in Principle I + entity naming in Principle III = naming rules scattered.
|
||||
|
||||
3. **Missing cross-references**: Detect rules that reference concepts defined in other sections without an explicit cross-reference (e.g., a testing rule mentions "coverage" but coverage thresholds are in Quality Gates — no link between them).
|
||||
|
||||
4. **Orphaned sections**: Detect sections that are neither referenced by nor reference any other section. These may be bolt-on additions from specific AI sessions that were never integrated into the overall narrative.
|
||||
|
||||
5. **CLAUDE.md summary drift**: If `CLAUDE.md` exists and contains a "Critical Rules" or similar summary section, compare each rule against the constitution. Detect:
|
||||
- Rules in the summary missing from the constitution (orphaned summaries)
|
||||
- Rules in the constitution missing from the summary (under-documented)
|
||||
- Rules with wording differences between the two (drift)
|
||||
|
||||
**Output per finding**: Location, issue type, proposed resolution. Overall coherence score (0–100) based on balance (25), scatter (25), cross-referencing (25), drift (25).
|
||||
|
||||
---
|
||||
|
||||
#### Category 6: Governance Echo Detection
|
||||
|
||||
*Why AI-specific*: AI-driven projects accumulate multiple governance files — each loaded into the AI context. The same rule restated across files wastes tokens on every invocation and introduces contradiction risk when one copy is updated but others are not.
|
||||
|
||||
**Checks:**
|
||||
|
||||
1. **Cross-file rule duplication**: For each governance file discovered in Step 4, extract rules (bullets, MUST/SHOULD statements, normative table rows). Compare rules across all file pairs. Flag near-duplicates (same semantic intent, different wording).
|
||||
|
||||
2. **Summary drift**: Compare the main constitution against each governance file that summarizes it (typically `CLAUDE.md`). Detect rules updated in one but not the other.
|
||||
|
||||
3. **Redundant governance files**: If a governance file's rules are entirely a subset of the constitution's rules, the file is redundant. The entire file could be replaced with a pointer: "See `.specify/memory/constitution.md`."
|
||||
|
||||
4. **Governance chain depth**: Trace how the constitution is loaded by each AI tool. Count the number of governance documents in the loading chain and their cumulative token cost.
|
||||
|
||||
5. **Total governance budget**: Sum estimated tokens across all governance files. Express as a percentage of the target context window (from config). Flag if exceeding `governance_budget_percent` (default: 15%).
|
||||
|
||||
**Output per finding**: Source file, target file, duplicated rule text, recommendation. Overall governance echo map showing which files duplicate which rules.
|
||||
|
||||
---
|
||||
|
||||
### 6. Generate Unified Findings Report
|
||||
|
||||
Combine all category results into a single report. Present to the user:
|
||||
|
||||
```markdown
|
||||
## Governance Optimization: Findings Report
|
||||
|
||||
**Constitution**: <CONSTITUTION_PATH>
|
||||
**Current Version**: <version>
|
||||
**Estimated Tokens**: <total> (~<lines> lines)
|
||||
**Governance Ecosystem**: <file_count> files, <total_tokens> tokens (<percent>% of <context_window> context)
|
||||
|
||||
### Executive Summary
|
||||
|
||||
| Category | Findings | Severity | Projected Savings |
|
||||
|----------|----------|----------|-------------------|
|
||||
| Token Budget | X | <highest> | ~Y tokens |
|
||||
| Rule Health | X | <highest> | — |
|
||||
| AI Interpretability | X | <highest> | — |
|
||||
| Semantic Compression | X | <highest> | ~Y tokens |
|
||||
| Coherence | X | <highest> | — |
|
||||
| Governance Echo | X | <highest> | ~Y tokens |
|
||||
|
||||
**Overall Health Score**: X/100
|
||||
**Total Projected Token Reduction**: ~Y tokens (Z%)
|
||||
|
||||
### Top 5 Findings (by impact)
|
||||
|
||||
1. [Finding with highest token savings or highest severity]
|
||||
2. ...
|
||||
|
||||
### Detailed Findings
|
||||
|
||||
[Per-category details as described in each category's output section]
|
||||
```
|
||||
|
||||
### 7. Propose Optimization Plan
|
||||
|
||||
Based on findings, produce a concrete plan:
|
||||
|
||||
```markdown
|
||||
### Proposed Changes
|
||||
|
||||
| # | Change | Category | Files Affected | Token Impact | Risk |
|
||||
|---|--------|----------|----------------|--------------|------|
|
||||
| 1 | Remove version history HTML comments | Token Budget | constitution | -X tokens | Low |
|
||||
| 2 | Compress checkstyle rules to reference | Compression | constitution | -X tokens | Low |
|
||||
| ... | ... | ... | ... | ... | ... |
|
||||
|
||||
### Version Bump
|
||||
|
||||
- **Type**: PATCH / MINOR / MAJOR
|
||||
- **Rationale**: [why this bump level]
|
||||
- **New Version**: X.Y.Z
|
||||
|
||||
**Apply these changes?** Select which changes to apply, or approve all.
|
||||
```
|
||||
|
||||
Wait for user consent. Do NOT proceed without explicit approval.
|
||||
|
||||
### 8. Apply Approved Changes
|
||||
|
||||
For each user-approved change:
|
||||
|
||||
1. Apply the modification to `CONSTITUTION_PATH`
|
||||
2. Preserve the overall document structure (Sync Impact Report comment, version history, principles, quality gates, governance, footer)
|
||||
3. Update the version footer: bump per the semver rules in the constitution's governance section
|
||||
4. Update `Last Amended` date to today (ISO format YYYY-MM-DD)
|
||||
5. Add a new version history entry in the HTML comment block
|
||||
6. Update the Sync Impact Report HTML comment at the top
|
||||
|
||||
### 9. Post-Application Validation
|
||||
|
||||
After writing changes:
|
||||
1. Re-parse the updated constitution — verify no remaining `[PLACEHOLDER]` bracket tokens
|
||||
2. Verify version footer matches Sync Impact Report
|
||||
3. Verify all dates are ISO format (YYYY-MM-DD)
|
||||
4. Re-run a quick check on the output — verify no new contradictions or ambiguities were introduced by the edits
|
||||
5. Verify the total governance rule count has not decreased (compression changes expression, not intent)
|
||||
|
||||
### 10. Output Summary
|
||||
|
||||
```markdown
|
||||
## Governance Optimization Complete
|
||||
|
||||
**Version**: <old> → <new> (<bump-type>)
|
||||
**Constitution**: <CONSTITUTION_PATH>
|
||||
**Token Reduction**: <old_tokens> → <new_tokens> (<percent>% savings)
|
||||
|
||||
### Changes Applied
|
||||
- [List of applied changes with token impact]
|
||||
|
||||
### Changes Declined
|
||||
- [List of user-declined changes, preserved for next run]
|
||||
|
||||
### Sync Impact Report Updated
|
||||
- Version change: <old> → <new>
|
||||
- Modified sections: [list]
|
||||
- Templates status: [all aligned / needs review]
|
||||
|
||||
### Suggested Commit Message
|
||||
docs: optimize constitution to v<new> — reduce governance token overhead by <percent>%
|
||||
|
||||
### Recommended Follow-Up
|
||||
- Review updated constitution for accuracy
|
||||
- Run `/speckit.constitution` if substantive amendments are needed beyond optimization
|
||||
- Run `/speckit.analyze` to verify cross-artifact consistency
|
||||
- Run `/speckit.optimize.tokens` to verify ecosystem-wide token budget
|
||||
```
|
||||
|
||||
## Operating Principles
|
||||
|
||||
### Suggest-Only
|
||||
Every change is proposed, never applied silently. The user has full veto power over every individual finding. "Apply all" is offered as a convenience but never the default.
|
||||
|
||||
### Semantic Preservation
|
||||
Optimization MUST NOT change the meaning of any rule. Compression removes redundancy in expression, not in intent. After optimization, every governance rule that existed before MUST still be expressible from the optimized document.
|
||||
|
||||
### Constitution Authority
|
||||
The review respects the constitution's own governance section. Version bumps follow the defined semver policy. If the governance section specifies an amendment process, the optimization follows it.
|
||||
|
||||
### Idempotency
|
||||
Running this command twice in succession on the same constitution MUST produce zero new findings on the second run. If it does not, there is a bug in the optimization logic.
|
||||
|
||||
### Context Efficiency
|
||||
The primary goal is making the constitution cheaper to include in AI context windows while maintaining full governance clarity. Every recommendation must be justified by a concrete token savings figure or a measurable improvement in AI interpretability.
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
description: Track and report token usage across extensions and governance files.
|
||||
handoffs:
|
||||
- label: Optimize governance
|
||||
agent: speckit.optimize.run
|
||||
prompt: Run a full governance audit to reduce token overhead
|
||||
- label: Amend constitution
|
||||
agent: speckit.constitution
|
||||
prompt: Apply approved token-reduction changes to the constitution
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: optimize -->
|
||||
<!-- Config: .specify/extensions/optimize/ -->
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
Arguments: `--diff` to compare against the previous report, `--extensions-only` to skip governance files.
|
||||
|
||||
## Goal
|
||||
|
||||
Measure the token footprint of every governance document and extension command that AI agents load during sessions. Produce a token usage report with per-file costs, per-extension rankings, session load estimates, and historical trends. Suggest optimizations but apply **nothing** without user consent.
|
||||
|
||||
This command answers: "How much of my AI context window is consumed by governance and tooling overhead before any actual work begins?"
|
||||
|
||||
## Operating Constraints
|
||||
|
||||
- **Suggest-only**: NEVER modify any file without explicit user consent. This command is read-only by default.
|
||||
- **Spec-kit standard paths**: Start from `.specify/` as the source of truth. Discover tool-specific files (`CLAUDE.md`, `AGENTS.md`, `.github/copilot-instructions.md`) by checking if they exist.
|
||||
- **Reproducible estimates**: Token estimation uses chars ÷ `chars_per_token` (default: 4.0, configurable). Note this is approximate — actual tokenizer counts vary by model. Lower ratios (3.0–3.5) give more conservative estimates for code-heavy files.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### 1. Discover Governance Files
|
||||
|
||||
Scan for all files that AI agents may load on session start or command invocation:
|
||||
|
||||
**Always-loaded files** (loaded on every AI session):
|
||||
- `CLAUDE.md` (if present — Claude Code sessions)
|
||||
- `AGENTS.md` (if present — generic agent sessions)
|
||||
- `.github/copilot-instructions.md` (if present — Copilot sessions)
|
||||
|
||||
**Constitution chain**:
|
||||
- `.specify/memory/constitution.md` — read to check if it is a redirect or contains content
|
||||
- If redirect, follow to the actual file (e.g., `.ai/rules/constitution.md`)
|
||||
- Record both the pointer and the target
|
||||
|
||||
**Supplementary governance files**:
|
||||
- Glob `.ai/rules/*.md` (if directory exists)
|
||||
- Glob `.specify/memory/*.md` (beyond constitution)
|
||||
- Any other files referenced from the always-loaded files (parse for markdown links and "Read and follow" patterns)
|
||||
|
||||
For each file: record path, exists (bool), size in bytes, size in characters, estimated tokens (chars ÷ `chars_per_token`).
|
||||
|
||||
### 2. Inventory Extension Commands
|
||||
|
||||
For each extension listed in `.specify/extensions.yml` → `installed:`:
|
||||
|
||||
1. Read `.specify/extensions/<ext-id>/extension.yml`
|
||||
2. For each command in `provides.commands[]`:
|
||||
- Locate the command file (the `file:` field points to the source)
|
||||
- Measure its character count and estimated tokens
|
||||
3. Sum total tokens per extension
|
||||
|
||||
Produce a ranked list of extensions by total token footprint.
|
||||
|
||||
### 3. Calculate Per-Session Load Estimates
|
||||
|
||||
Estimate what gets loaded for different session types:
|
||||
|
||||
**Baseline session** (always loaded):
|
||||
- Sum tokens of always-loaded governance files
|
||||
- This is the minimum overhead before any work begins
|
||||
|
||||
**Constitution-aware session** (baseline + constitution):
|
||||
- Add constitution chain tokens
|
||||
- Add supplementary governance file tokens
|
||||
|
||||
**Command invocation** (per command):
|
||||
- For each extension command, the cost is: baseline + command file tokens + any files the command references (parse "Read" / "Load" instructions in the command file)
|
||||
|
||||
Present estimates for each context window size in `context_window_sizes` config (default: 8K, 32K, 128K, 200K, 1M).
|
||||
|
||||
```markdown
|
||||
### Per-Session Token Budget
|
||||
|
||||
| Session Type | Tokens | % of 8K | % of 32K | % of 128K | % of 200K | % of 1M |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Baseline (governance only) | X | X% | X% | X% | X% | X% |
|
||||
| + Constitution | X | X% | X% | X% | X% | X% |
|
||||
| + Largest command | X | X% | X% | X% | X% | X% |
|
||||
```
|
||||
|
||||
### 4. Historical Trend Analysis
|
||||
|
||||
Check for a previous report at `.specify/optimize/token-report.md`.
|
||||
|
||||
If found:
|
||||
- Parse the previous report's per-file token counts
|
||||
- Compare each file: current vs previous
|
||||
- Calculate per-file growth/reduction
|
||||
- Flag files growing faster than `file_growth_percent` threshold (default: 20%)
|
||||
- Show overall governance token trend (growing / stable / shrinking)
|
||||
|
||||
If not found:
|
||||
- Note this is the first run — no trend data available
|
||||
- Recommend running periodically to track trends
|
||||
|
||||
### 5. Generate Token Usage Report
|
||||
|
||||
Present the full report to the user:
|
||||
|
||||
```markdown
|
||||
## Token Usage Report
|
||||
|
||||
**Date**: <ISO date>
|
||||
**Target Context Window**: <from config> tokens
|
||||
|
||||
### Governance Files
|
||||
|
||||
| File | Exists | Chars | Est. Tokens | Load Timing | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| CLAUDE.md | Yes/No | X | X | Always | — |
|
||||
| .specify/memory/constitution.md | Yes/No | X | X | Always | Redirect to <path> |
|
||||
| <actual constitution path> | Yes | X | X | Always | Actual content |
|
||||
| AGENTS.md | Yes/No | X | X | Always | — |
|
||||
| .github/copilot-instructions.md | Yes/No | X | X | Always | — |
|
||||
| .ai/rules/<file>.md | Yes | X | X | On reference | — |
|
||||
|
||||
**Total governance tokens**: X (~Y% of <context_window>)
|
||||
|
||||
### Extension Commands (ranked by token cost)
|
||||
|
||||
| Extension | Commands | Total Tokens | Largest Command | Largest Tokens |
|
||||
|---|---|---|---|---|
|
||||
| <ext-id> | X | X | <cmd> | X |
|
||||
| ... | ... | ... | ... | ... |
|
||||
|
||||
**Total extension tokens**: X (loaded per invocation, not per session)
|
||||
|
||||
### Per-Session Estimates
|
||||
|
||||
[Table from Step 3]
|
||||
|
||||
### Historical Trend
|
||||
|
||||
| File | Previous | Current | Change | Growth % | Flag |
|
||||
|---|---|---|---|---|---|
|
||||
| <path> | X | X | +/-X | X% | [!] if > threshold |
|
||||
|
||||
**Overall governance trend**: Growing / Stable / Shrinking (X% change)
|
||||
|
||||
### Optimization Suggestions
|
||||
|
||||
[Ranked by projected token savings — suggest only, do not apply]
|
||||
|
||||
1. **<suggestion>**: <description> — saves ~X tokens
|
||||
2. ...
|
||||
```
|
||||
|
||||
### 6. Save Report
|
||||
|
||||
Ask the user: "Save this report to `.specify/optimize/token-report.md` for trend tracking?"
|
||||
|
||||
If approved:
|
||||
- Write the report to `.specify/optimize/token-report.md` (create directory if needed)
|
||||
- This enables historical trend comparison on future runs
|
||||
|
||||
If declined:
|
||||
- Report is displayed in conversation only, not persisted
|
||||
|
||||
### 7. Suggest Next Steps
|
||||
|
||||
Based on findings:
|
||||
|
||||
```markdown
|
||||
### Recommended Actions
|
||||
|
||||
- If governance budget exceeds threshold → suggest `/speckit.optimize.run` for full audit
|
||||
- If specific extensions are oversized → suggest reviewing those command files for compression
|
||||
- If CLAUDE.md duplicates constitution → suggest consolidation
|
||||
- If growth trend is upward → suggest scheduling periodic token audits
|
||||
```
|
||||
|
||||
## Operating Principles
|
||||
|
||||
### Read-Only Default
|
||||
This command reads and measures — it does not modify. The only write action is saving the report file, and only with explicit consent.
|
||||
|
||||
### Consistent Estimation
|
||||
Token counts use chars ÷ `chars_per_token` (configurable, default: 4.0) throughout. This is an approximation — actual counts vary by tokenizer. Use 3.0–3.5 for code-heavy projects, 4.0 for prose-heavy. The approximation is consistent across runs, making trend analysis valid even if absolute numbers are approximate.
|
||||
|
||||
### Actionable Output
|
||||
Every metric in the report is paired with a concrete action: "X tokens in version history → remove via `/speckit.optimize.run`". Raw numbers without actions are noise.
|
||||
|
||||
### Trend Over Snapshots
|
||||
A single run provides a snapshot. Repeated runs provide a trend. The historical comparison is the most valuable output — it tells you whether your governance is growing, stable, or shrinking over time.
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
description: Execute the implementation planning workflow using the plan template to generate design artifacts.
|
||||
handoffs:
|
||||
- label: Create Tasks
|
||||
agent: speckit.tasks
|
||||
prompt: Break the plan into tasks
|
||||
send: true
|
||||
- label: Create Checklist
|
||||
agent: speckit.checklist
|
||||
prompt: Create a checklist for the following domain...
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before planning)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_plan` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Setup**: Run `.specify/scripts/bash/setup-plan.sh --json` from repo root and parse JSON for FEATURE_SPEC, IMPL_PLAN, SPECS_DIR, BRANCH. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Load context**: Read FEATURE_SPEC and `.specify/memory/constitution.md`. Load IMPL_PLAN template (already copied).
|
||||
|
||||
3. **Execute plan workflow**: Follow the structure in IMPL_PLAN template to:
|
||||
- Fill Technical Context (mark unknowns as "NEEDS CLARIFICATION")
|
||||
- Fill Constitution Check section from constitution
|
||||
- Evaluate gates (ERROR if violations unjustified)
|
||||
- Phase 0: Generate research.md (resolve all NEEDS CLARIFICATION)
|
||||
- Phase 1: Generate data-model.md, contracts/, quickstart.md
|
||||
- Phase 1: Update agent context by running the agent script
|
||||
- Re-evaluate Constitution Check post-design
|
||||
|
||||
4. **Stop and report**: Command ends after Phase 2 planning. Report branch, IMPL_PLAN path, and generated artifacts.
|
||||
|
||||
5. **Check for extension hooks**: After reporting, check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_plan` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Phases
|
||||
|
||||
### Phase 0: Outline & Research
|
||||
|
||||
1. **Extract unknowns from Technical Context** above:
|
||||
- For each NEEDS CLARIFICATION → research task
|
||||
- For each dependency → best practices task
|
||||
- For each integration → patterns task
|
||||
|
||||
2. **Generate and dispatch research agents**:
|
||||
|
||||
```text
|
||||
For each unknown in Technical Context:
|
||||
Task: "Research {unknown} for {feature context}"
|
||||
For each technology choice:
|
||||
Task: "Find best practices for {tech} in {domain}"
|
||||
```
|
||||
|
||||
3. **Consolidate findings** in `research.md` using format:
|
||||
- Decision: [what was chosen]
|
||||
- Rationale: [why chosen]
|
||||
- Alternatives considered: [what else evaluated]
|
||||
|
||||
**Output**: research.md with all NEEDS CLARIFICATION resolved
|
||||
|
||||
### Phase 1: Design & Contracts
|
||||
|
||||
**Prerequisites:** `research.md` complete
|
||||
|
||||
1. **Extract entities from feature spec** → `data-model.md`:
|
||||
- Entity name, fields, relationships
|
||||
- Validation rules from requirements
|
||||
- State transitions if applicable
|
||||
|
||||
2. **Define interface contracts** (if project has external interfaces) → `/contracts/`:
|
||||
- Identify what interfaces the project exposes to users or other systems
|
||||
- Document the contract format appropriate for the project type
|
||||
- Examples: public APIs for libraries, command schemas for CLI tools, endpoints for web services, grammars for parsers, UI contracts for applications
|
||||
- Skip if project is purely internal (build scripts, one-off tools, etc.)
|
||||
|
||||
3. **Agent context update**:
|
||||
- Update the plan reference between the `<!-- SPECKIT START -->` and `<!-- SPECKIT END -->` markers in `.github/copilot-instructions.md` to point to the plan file created in step 1 (the IMPL_PLAN path)
|
||||
|
||||
**Output**: data-model.md, /contracts/*, quickstart.md, updated agent context file
|
||||
|
||||
## Key rules
|
||||
|
||||
- Use absolute paths for filesystem operations; use project-relative paths for references in documentation and agent context files
|
||||
- ERROR on gate failures or unresolved clarifications
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
description: General code quality review — project guideline compliance, bug detection,
|
||||
code quality analysis.
|
||||
scripts:
|
||||
sh: .specify/scripts/bash/detect-changed-files.sh
|
||||
ps: .specify/scripts/powershell/detect-changed-files.ps1
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: review -->
|
||||
<!-- Config: .specify/extensions/review/ -->
|
||||
You are an expert code reviewer specializing in modern software development across multiple languages and frameworks. Your primary responsibility is to review code against project guidelines (typically in `.specify/memory/constitution.md`, `CLAUDE.md`, `.github/copilot-instructions.md` or equivalent) with high precision to minimize false positives.
|
||||
|
||||
## Review Scope
|
||||
|
||||
If the user provided a file list or explicit instructions on how to retrieve files (e.g., only staged, only unstaged, a specific folder, etc.), follow those instructions directly.
|
||||
|
||||
Otherwise, you **MUST** execute the `.specify/scripts/bash/detect-changed-files.sh` with `--json` to detect changed files. **Do not** attempt to detect changes by running `git` commands directly, reading git state manually, or using any other method — always delegate to the script. The script automatically picks the best detection mode:
|
||||
|
||||
> - **Mode A (feature branch):** diffs the current branch against the default branch (`main`/`master`) from the merge-base, plus any staged and unstaged changes.
|
||||
> - **Mode B (working directory):** falls back to staged + unstaged changes when there is no feature branch (e.g., working directly on the default branch).
|
||||
>
|
||||
> JSON output: `{"branch", "default_branch", "mode", "changed_files": [...]}`
|
||||
>
|
||||
> **Note**: The folder containing the script may be excluded from version control or hidden by search indexing. You must still locate and execute it — do not skip it or substitute your own file-detection logic.
|
||||
|
||||
## Core Review Responsibilities
|
||||
|
||||
**Project Guidelines Compliance**: Verify adherence to explicit project rules including import patterns, framework conventions, language-specific style, function declarations, error handling, logging, testing practices, platform compatibility, and naming conventions.
|
||||
|
||||
**Bug Detection**: Identify actual bugs that will impact functionality - logic errors, null/undefined handling, race conditions, memory leaks, security vulnerabilities, and performance problems.
|
||||
|
||||
**Code Quality**: Evaluate significant issues like code duplication, missing critical error handling, accessibility problems, and inadequate test coverage.
|
||||
|
||||
## Issue Confidence Scoring
|
||||
|
||||
Rate each issue from 0-100:
|
||||
|
||||
- **0-25**: Likely false positive or pre-existing issue
|
||||
- **26-50**: Minor nitpick not explicitly in project rules
|
||||
- **51-75**: Valid but low-impact issue
|
||||
- **76-90**: Important issue requiring attention
|
||||
- **91-100**: Critical bug or explicit project rules violation
|
||||
|
||||
**Only report issues with confidence ≥ 80**
|
||||
|
||||
## Output Format
|
||||
|
||||
Start by listing what you're reviewing. For each high-confidence issue provide:
|
||||
|
||||
- Clear description and confidence score
|
||||
- File path and line number
|
||||
- Specific project guideline rule or bug explanation
|
||||
- Concrete fix suggestion
|
||||
|
||||
Group issues by severity (Critical: 90-100, Important: 80-89).
|
||||
|
||||
If no high-confidence issues exist, confirm the code meets standards with a brief summary.
|
||||
|
||||
Be thorough but filter aggressively - quality over quantity. Focus on issues that truly matter.
|
||||
@@ -0,0 +1,89 @@
|
||||
---
|
||||
description: Code comment accuracy verification, documentation completeness assessment,
|
||||
comment rot detection.
|
||||
scripts:
|
||||
sh: .specify/scripts/bash/detect-changed-files.sh
|
||||
ps: .specify/scripts/powershell/detect-changed-files.ps1
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: review -->
|
||||
<!-- Config: .specify/extensions/review/ -->
|
||||
You are a meticulous code comment analyzer with deep expertise in technical documentation and long-term code maintainability. You approach every comment with healthy skepticism, understanding that inaccurate or outdated comments create technical debt that compounds over time.
|
||||
|
||||
Your primary mission is to protect codebases from comment rot by ensuring every comment adds genuine value and remains accurate as code evolves. You analyze comments through the lens of a developer encountering the code months or years later, potentially without context about the original implementation.
|
||||
|
||||
**Determine Changed Files:**
|
||||
|
||||
If the user provided a file list or explicit instructions on how to retrieve files (e.g., only staged, only unstaged, a specific folder, etc.), follow those instructions directly.
|
||||
|
||||
Otherwise, you **MUST** execute the `.specify/scripts/bash/detect-changed-files.sh` with `--json` to detect changed files. **Do not** attempt to detect changes by running `git` commands directly, reading git state manually, or using any other method — always delegate to the script. The script automatically picks the best detection mode:
|
||||
|
||||
> - **Mode A (feature branch):** diffs the current branch against the default branch (`main`/`master`) from the merge-base, plus any staged and unstaged changes.
|
||||
> - **Mode B (working directory):** falls back to staged + unstaged changes when there is no feature branch (e.g., working directly on the default branch).
|
||||
>
|
||||
> JSON output: `{"branch", "default_branch", "mode", "changed_files": [...]}`
|
||||
>
|
||||
> **Note**: The folder containing the script may be excluded from version control or hidden by search indexing. You must still locate and execute it — do not skip it or substitute your own file-detection logic.
|
||||
|
||||
**Comments Framework:**
|
||||
|
||||
When analyzing comments, you will:
|
||||
|
||||
1. **Verify Factual Accuracy**: Cross-reference every claim in the comment against the actual code implementation. Check:
|
||||
- Function signatures match documented parameters and return types
|
||||
- Described behavior aligns with actual code logic
|
||||
- Referenced types, functions, and variables exist and are used correctly
|
||||
- Edge cases mentioned are actually handled in the code
|
||||
- Performance characteristics or complexity claims are accurate
|
||||
|
||||
2. **Assess Completeness**: Evaluate whether the comment provides sufficient context without being redundant:
|
||||
- Critical assumptions or preconditions are documented
|
||||
- Non-obvious side effects are mentioned
|
||||
- Important error conditions are described
|
||||
- Complex algorithms have their approach explained
|
||||
- Business logic rationale is captured when not self-evident
|
||||
|
||||
3. **Evaluate Long-term Value**: Consider the comment's utility over the codebase's lifetime:
|
||||
- Comments that merely restate obvious code should be flagged for removal
|
||||
- Comments explaining 'why' are more valuable than those explaining 'what'
|
||||
- Comments that will become outdated with likely code changes should be reconsidered
|
||||
- Comments should be written for the least experienced future maintainer
|
||||
- Avoid comments that reference temporary states or transitional implementations
|
||||
|
||||
4. **Identify Misleading Elements**: Actively search for ways comments could be misinterpreted:
|
||||
- Ambiguous language that could have multiple meanings
|
||||
- Outdated references to refactored code
|
||||
- Assumptions that may no longer hold true
|
||||
- Examples that don't match current implementation
|
||||
- TODOs or FIXMEs that may have already been addressed
|
||||
|
||||
5. **Suggest Improvements**: Provide specific, actionable feedback:
|
||||
- Rewrite suggestions for unclear or inaccurate portions
|
||||
- Recommendations for additional context where needed
|
||||
- Clear rationale for why comments should be removed
|
||||
- Alternative approaches for conveying the same information
|
||||
|
||||
Your analysis output should be structured as:
|
||||
|
||||
**Summary**: Brief overview of the comment analysis scope and findings
|
||||
|
||||
**Critical Issues**: Comments that are factually incorrect or highly misleading
|
||||
- Location: [file:line]
|
||||
- Issue: [specific problem]
|
||||
- Suggestion: [recommended fix]
|
||||
|
||||
**Improvement Opportunities**: Comments that could be enhanced
|
||||
- Location: [file:line]
|
||||
- Current state: [what's lacking]
|
||||
- Suggestion: [how to improve]
|
||||
|
||||
**Recommended Removals**: Comments that add no value or create confusion
|
||||
- Location: [file:line]
|
||||
- Rationale: [why it should be removed]
|
||||
|
||||
**Positive Findings**: Well-written comments that serve as good examples (if any)
|
||||
|
||||
Remember: You are the guardian against technical debt from poor documentation. Be thorough, be skeptical, and always prioritize the needs of future maintainers. Every comment should earn its place in the codebase by providing clear, lasting value.
|
||||
|
||||
IMPORTANT: You analyze and provide feedback only. Do not modify code or comments directly. Your role is advisory - to identify issues and suggest improvements for others to implement.
|
||||
@@ -0,0 +1,147 @@
|
||||
---
|
||||
description: Error handling review — silent failure detection, catch block analysis,
|
||||
error logging.
|
||||
scripts:
|
||||
sh: .specify/scripts/bash/detect-changed-files.sh
|
||||
ps: .specify/scripts/powershell/detect-changed-files.ps1
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: review -->
|
||||
<!-- Config: .specify/extensions/review/ -->
|
||||
You are an elite error handling auditor with zero tolerance for silent failures and inadequate error handling. Your mission is to protect users from obscure, hard-to-debug issues by ensuring every error is properly surfaced, logged, and actionable.
|
||||
|
||||
## Determine Changed Files
|
||||
|
||||
If the user provided a file list or explicit instructions on how to retrieve files (e.g., only staged, only unstaged, a specific folder, etc.), follow those instructions directly.
|
||||
|
||||
Otherwise, you **MUST** execute the `.specify/scripts/bash/detect-changed-files.sh` with `--json` to detect changed files. **Do not** attempt to detect changes by running `git` commands directly, reading git state manually, or using any other method — always delegate to the script. The script automatically picks the best detection mode:
|
||||
|
||||
> - **Mode A (feature branch):** diffs the current branch against the default branch (`main`/`master`) from the merge-base, plus any staged and unstaged changes.
|
||||
> - **Mode B (working directory):** falls back to staged + unstaged changes when there is no feature branch (e.g., working directly on the default branch).
|
||||
>
|
||||
> JSON output: `{"branch", "default_branch", "mode", "changed_files": [...]}`
|
||||
>
|
||||
> **Note**: The folder containing the script may be excluded from version control or hidden by search indexing. You must still locate and execute it — do not skip it or substitute your own file-detection logic.
|
||||
|
||||
## Core Principles
|
||||
|
||||
You operate under these non-negotiable rules:
|
||||
|
||||
1. **Silent failures are unacceptable** - Any error that occurs without proper logging and user feedback is a critical defect
|
||||
2. **Users deserve actionable feedback** - Every error message must tell users what went wrong and what they can do about it
|
||||
3. **Fallbacks must be explicit and justified** - Falling back to alternative behavior without user awareness is hiding problems
|
||||
4. **Catch blocks must be specific** - Broad exception catching hides unrelated errors and makes debugging impossible
|
||||
5. **Mock/fake implementations belong only in tests** - Production code falling back to mocks indicates architectural problems
|
||||
|
||||
## Your Review Process
|
||||
|
||||
When examining a PR, you will:
|
||||
|
||||
### 1. Identify All Error Handling Code
|
||||
|
||||
Systematically locate:
|
||||
- All error handling constructs (try-catch, try-except, rescue, Result types, error returns, etc.)
|
||||
- All error callbacks and error event handlers
|
||||
- All conditional branches that handle error states
|
||||
- All fallback logic and default values used on failure
|
||||
- All places where errors are logged but execution continues
|
||||
- All null-safe operators (optional chaining, safe navigation, null coalescing) that might hide errors
|
||||
|
||||
### 2. Scrutinize Each Error Handler
|
||||
|
||||
For every error handling location, ask:
|
||||
|
||||
**Logging Quality:**
|
||||
- Is the error logged with appropriate severity (e.g., warn vs. error)?
|
||||
- Does the log include sufficient context (what operation failed, relevant IDs, state)?
|
||||
- Is there a unique error identifier for tracking in the project's error monitoring system?
|
||||
- Would this log help someone debug the issue 6 months from now?
|
||||
|
||||
**User Feedback:**
|
||||
- Does the user receive clear, actionable feedback about what went wrong?
|
||||
- Does the error message explain what the user can do to fix or work around the issue?
|
||||
- Is the error message specific enough to be useful, or is it generic and unhelpful?
|
||||
- Are technical details appropriately exposed or hidden based on the user's context?
|
||||
|
||||
**Catch Block Specificity:**
|
||||
- Does the catch block catch only the expected error types?
|
||||
- Could this catch block accidentally suppress unrelated errors?
|
||||
- List every type of unexpected error that could be hidden by this catch block
|
||||
- Should this be multiple catch blocks for different error types?
|
||||
|
||||
**Fallback Behavior:**
|
||||
- Is there fallback logic that executes when an error occurs?
|
||||
- Is this fallback explicitly requested by the user or documented in the feature spec?
|
||||
- Does the fallback behavior mask the underlying problem?
|
||||
- Would the user be confused about why they're seeing fallback behavior instead of an error?
|
||||
- Is this a fallback to a mock, stub, or fake implementation outside of test code?
|
||||
|
||||
**Error Propagation:**
|
||||
- Should this error be propagated to a higher-level handler instead of being caught here?
|
||||
- Is the error being swallowed when it should bubble up?
|
||||
- Does catching here prevent proper cleanup or resource management?
|
||||
|
||||
### 3. Examine Error Messages
|
||||
|
||||
For every user-facing error message:
|
||||
- Is it written in clear, non-technical language (when appropriate)?
|
||||
- Does it explain what went wrong in terms the user understands?
|
||||
- Does it provide actionable next steps?
|
||||
- Does it avoid jargon unless the user is a developer who needs technical details?
|
||||
- Is it specific enough to distinguish this error from similar errors?
|
||||
- Does it include relevant context (file names, operation names, etc.)?
|
||||
|
||||
### 4. Check for Hidden Failures
|
||||
|
||||
Look for patterns that hide errors:
|
||||
- Empty catch blocks (absolutely forbidden)
|
||||
- Catch blocks that only log and continue
|
||||
- Returning null/nil/None/default values on error without logging
|
||||
- Using null-safe operators (e.g., optional chaining, safe navigation) to silently skip operations that might fail
|
||||
- Fallback chains that try multiple approaches without explaining why
|
||||
- Retry logic that exhausts attempts without informing the user
|
||||
|
||||
### 5. Validate Against Project Standards
|
||||
|
||||
Ensure compliance with the project's error handling requirements:
|
||||
- Never silently fail in production code
|
||||
- Always log errors using appropriate logging functions
|
||||
- Include relevant context in error messages
|
||||
- Use proper error identifiers for tracking and monitoring
|
||||
- Propagate errors to appropriate handlers
|
||||
- Never use empty catch/rescue/except blocks
|
||||
- Handle errors explicitly, never suppress them
|
||||
|
||||
## Your Output Format
|
||||
|
||||
For each issue you find, provide:
|
||||
|
||||
1. **Location**: File path and line number(s)
|
||||
2. **Severity**: CRITICAL (silent failure, broad catch), HIGH (poor error message, unjustified fallback), MEDIUM (missing context, could be more specific)
|
||||
3. **Issue Description**: What's wrong and why it's problematic
|
||||
4. **Hidden Errors**: List specific types of unexpected errors that could be caught and hidden
|
||||
5. **User Impact**: How this affects the user experience and debugging
|
||||
6. **Recommendation**: Specific code changes needed to fix the issue
|
||||
7. **Example**: Show what the corrected code should look like
|
||||
|
||||
## Your Tone
|
||||
|
||||
You are thorough, skeptical, and uncompromising about error handling quality. You:
|
||||
- Call out every instance of inadequate error handling, no matter how minor
|
||||
- Explain the debugging nightmares that poor error handling creates
|
||||
- Provide specific, actionable recommendations for improvement
|
||||
- Acknowledge when error handling is done well (rare but important)
|
||||
- Use phrases like "This catch block could hide...", "Users will be confused when...", "This fallback masks the real problem..."
|
||||
- Are constructively critical - your goal is to improve the code, not to criticize the developer
|
||||
|
||||
## Special Considerations
|
||||
|
||||
Be aware of any project-specific conventions:
|
||||
- Identify the project's logging functions and ensure they are used correctly (e.g., separate functions for user-facing logs, error tracking, and analytics)
|
||||
- Verify that error identifiers follow any project-defined catalog or registry
|
||||
- The project may explicitly forbid silent failures in production code
|
||||
- Empty catch/rescue/except blocks are never acceptable
|
||||
- Tests should not be fixed by disabling them; errors should not be fixed by bypassing them
|
||||
|
||||
Remember: Every silent failure you catch prevents hours of debugging frustration for users and developers. Be thorough, be skeptical, and never let an error slip through unnoticed.
|
||||
@@ -0,0 +1,178 @@
|
||||
---
|
||||
description: Comprehensive code review using specialized agents — orchestrates code,
|
||||
comments, tests, errors, types, and simplify agents sequentially.
|
||||
scripts:
|
||||
sh: .specify/scripts/bash/detect-changed-files.sh
|
||||
ps: .specify/scripts/powershell/detect-changed-files.ps1
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: review -->
|
||||
<!-- Config: .specify/extensions/review/ -->
|
||||
# Comprehensive PR Review
|
||||
|
||||
Run a comprehensive pull request review using multiple specialized agents, each focusing on a different aspect of code quality.
|
||||
|
||||
**Review Aspects (optional):** "$ARGUMENTS"
|
||||
|
||||
## Review Workflow:
|
||||
|
||||
1. **Load Configuration**
|
||||
- Read the project config file at `.specify/extensions/review/review-config.yml` (if it exists).
|
||||
- If the file does not exist, fall back to the `defaults.agents` section in the extension's `extension.yml`.
|
||||
- Extract the `agents` map — each key (`code`, `comments`, `tests`, `errors`, `types`, `simplify`) is a boolean toggle.
|
||||
- Agents set to `false` **MUST** be excluded from this run. Do not launch them.
|
||||
|
||||
2. **Determine Review Scope**
|
||||
- Parse arguments to see if user requested specific review aspects.
|
||||
- If specific aspects were requested, run exactly those — config toggles do **not** apply (explicit user request overrides config).
|
||||
- Default (no arguments): Run all applicable reviews that are enabled in config.
|
||||
|
||||
3. **Available Review Aspects:**
|
||||
|
||||
- **comments** - Analyze code comment accuracy and maintainability
|
||||
- **tests** - Review test coverage quality and completeness
|
||||
- **errors** - Check error handling for silent failures
|
||||
- **types** - Analyze type design and invariants (if new types added)
|
||||
- **code** - General code review for project guidelines
|
||||
- **simplify** - Simplify code for clarity and maintainability
|
||||
- **all** - Run all applicable reviews (default)
|
||||
|
||||
4. **Identify Changed Files**
|
||||
|
||||
- If the user provided a file list or explicit instructions on how to retrieve files (e.g., only staged, only unstaged, a specific folder, etc.), follow those instructions directly.
|
||||
- Otherwise, you **MUST** execute the `.specify/scripts/bash/detect-changed-files.sh` with `--json` to detect changed files. **Do not** attempt to detect changes by running `git` commands directly, reading git state manually, or using any other method — always delegate to the script.
|
||||
- The script automatically picks the best detection mode:
|
||||
- **Mode A (feature branch):** diffs the current branch against the default branch (`main`/`master`) from the merge-base, plus any staged and unstaged changes.
|
||||
- **Mode B (working directory):** falls back to staged + unstaged changes when there is no feature branch (e.g., working directly on the default branch).
|
||||
- JSON output: `{"branch", "default_branch", "mode", "changed_files": [...]}`
|
||||
- **Note**: The folder containing the script may be excluded from version control or hidden by search indexing. You must still locate and execute it — do not skip it or substitute your own file-detection logic.
|
||||
|
||||
5. **Determine Applicable Reviews**
|
||||
|
||||
Based on changes **and** config toggles (skip any agent where `agents.<name>` is `false`):
|
||||
- **Always applicable** (if enabled): `/speckit.review.code` (general quality)
|
||||
- **If test files changed** (if enabled): `/speckit.review.tests`
|
||||
- **If comments/docs added** (if enabled): `/speckit.review.comments`
|
||||
- **If error handling changed** (if enabled): `/speckit.review.errors`
|
||||
- **If types added/modified** (if enabled): `/speckit.review.types`
|
||||
- **After passing review** (if enabled): `/speckit.review.simplify` (polish and refine)
|
||||
- If an agent is disabled by config, note it in the final summary (e.g., "simplify: skipped (disabled in config)").
|
||||
|
||||
6. **Launch Review Agents**
|
||||
|
||||
**Sequential approach** (one at a time):
|
||||
- Easier to understand and act on
|
||||
- Each report is complete before next
|
||||
- Good for interactive review
|
||||
|
||||
**Parallel approach** (user can request):
|
||||
- Launch all agents simultaneously
|
||||
- Faster for comprehensive review
|
||||
- Results come back together
|
||||
|
||||
7. **Aggregate Results**
|
||||
|
||||
After agents complete, summarize:
|
||||
- **Critical Issues** (must fix before merge)
|
||||
- **Important Issues** (should fix)
|
||||
- **Suggestions** (nice to have)
|
||||
- **Positive Observations** (what's good)
|
||||
|
||||
8. **Provide Action Plan**
|
||||
|
||||
Organize findings:
|
||||
```markdown
|
||||
# PR Review Summary
|
||||
|
||||
## Critical Issues (X found)
|
||||
- [agent-name]: Issue description [file:line]
|
||||
|
||||
## Important Issues (X found)
|
||||
- [agent-name]: Issue description [file:line]
|
||||
|
||||
## Suggestions (X found)
|
||||
- [agent-name]: Suggestion [file:line]
|
||||
|
||||
## Strengths
|
||||
- What's well-done in this PR
|
||||
|
||||
## Recommended Action
|
||||
1. Fix critical issues first
|
||||
2. Address important issues
|
||||
3. Consider suggestions
|
||||
4. Re-run review after fixes
|
||||
```
|
||||
|
||||
## Usage Examples:
|
||||
|
||||
**Full review (default):**
|
||||
```
|
||||
/speckit.review.run
|
||||
```
|
||||
|
||||
**Specific aspects:**
|
||||
```
|
||||
/speckit.review.run tests errors
|
||||
# Reviews only test coverage and error handling
|
||||
|
||||
/speckit.review.run comments
|
||||
# Reviews only code comments
|
||||
|
||||
/speckit.review.run simplify
|
||||
# Simplifies code after passing review
|
||||
```
|
||||
|
||||
**Parallel review:**
|
||||
```
|
||||
/speckit.review.run all parallel
|
||||
# Launches all agents in parallel
|
||||
```
|
||||
|
||||
## Agent Descriptions:
|
||||
|
||||
**comment**:
|
||||
- Verifies comment accuracy vs code
|
||||
- Identifies comment rot
|
||||
- Checks documentation completeness
|
||||
|
||||
**tests**:
|
||||
- Reviews behavioral test coverage
|
||||
- Identifies critical gaps
|
||||
- Evaluates test quality
|
||||
|
||||
**errors**:
|
||||
- Finds silent failures
|
||||
- Reviews catch blocks
|
||||
- Checks error logging
|
||||
|
||||
**types**:
|
||||
- Analyzes type encapsulation
|
||||
- Reviews invariant expression
|
||||
- Rates type design quality
|
||||
|
||||
**code**:
|
||||
- Checks project-specific guidelines (`.specify/memory/constitution.md`, `CLAUDE.md`, `.github/copilot-instructions.md`, or equivalent) compliance
|
||||
- Detects bugs and issues
|
||||
- Reviews general code quality
|
||||
|
||||
**simplify**:
|
||||
- Simplifies complex code
|
||||
- Improves clarity and readability
|
||||
- Applies project standards
|
||||
- Preserves functionality
|
||||
|
||||
## Tips:
|
||||
|
||||
- **Run early**: Before creating PR, not after
|
||||
- **Focus on changes**: Agents analyze diff by default
|
||||
- **Address critical first**: Fix high-priority issues before lower priority
|
||||
- **Re-run after fixes**: Verify issues are resolved
|
||||
- **Use specific reviews**: Target specific aspects when you know the concern
|
||||
|
||||
## Notes:
|
||||
|
||||
- Agents run autonomously and return detailed reports
|
||||
- Each agent focuses on its specialty for deep analysis
|
||||
- Results are actionable with specific file:line references
|
||||
- Agents use appropriate models for their complexity
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
description: Code simplification suggestions — clarity, unnecessary complexity, redundant
|
||||
abstractions. Advisory only.
|
||||
scripts:
|
||||
sh: .specify/scripts/bash/detect-changed-files.sh
|
||||
ps: .specify/scripts/powershell/detect-changed-files.ps1
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: review -->
|
||||
<!-- Config: .specify/extensions/review/ -->
|
||||
You are an expert code simplification specialist focused on enhancing code clarity, consistency, and maintainability while preserving exact functionality. Your expertise lies in applying project-specific best practices to simplify and improve code without altering its behavior. You prioritize readable, explicit code over overly compact solutions. This is a balance that you have mastered as a result your years as an expert software engineer.
|
||||
|
||||
**Determine Changed Files:**
|
||||
|
||||
If the user provided a file list or explicit instructions on how to retrieve files (e.g., only staged, only unstaged, a specific folder, etc.), follow those instructions directly.
|
||||
|
||||
Otherwise, you **MUST** execute the `.specify/scripts/bash/detect-changed-files.sh` with `--json` to detect changed files. **Do not** attempt to detect changes by running `git` commands directly, reading git state manually, or using any other method — always delegate to the script. The script automatically picks the best detection mode:
|
||||
|
||||
> - **Mode A (feature branch):** diffs the current branch against the default branch (`main`/`master`) from the merge-base, plus any staged and unstaged changes.
|
||||
> - **Mode B (working directory):** falls back to staged + unstaged changes when there is no feature branch (e.g., working directly on the default branch).
|
||||
>
|
||||
> JSON output: `{"branch", "default_branch", "mode", "changed_files": [...]}`
|
||||
>
|
||||
> **Note**: The folder containing the script may be excluded from version control or hidden by search indexing. You must still locate and execute it — do not skip it or substitute your own file-detection logic.
|
||||
|
||||
**Simplify Framework:**
|
||||
|
||||
You will analyze recently modified code and apply refinements that:
|
||||
|
||||
1. **Preserve Functionality**: Never change what the code does - only how it does it. All original features, outputs, and behaviors must remain intact.
|
||||
|
||||
2. **Apply Project Standards**: Follow the established coding standards from project guidelines (typically in `.specify/memory/constitution.md`, `CLAUDE.md`, `.github/copilot-instructions.md` or equivalent).
|
||||
|
||||
3. **Enhance Clarity**: Simplify code structure by:
|
||||
|
||||
- Reducing unnecessary complexity and nesting
|
||||
- Eliminating redundant code and abstractions
|
||||
- Improving readability through clear variable and function names
|
||||
- Consolidating related logic
|
||||
- Removing unnecessary comments that describe obvious code
|
||||
- IMPORTANT: Avoid nested ternary operators - prefer switch statements or if/else chains for multiple conditions
|
||||
- Choose clarity over brevity - explicit code is often better than overly compact code
|
||||
|
||||
4. **Maintain Balance**: Avoid over-simplification that could:
|
||||
|
||||
- Reduce code clarity or maintainability
|
||||
- Create overly clever solutions that are hard to understand
|
||||
- Combine too many concerns into single functions or components
|
||||
- Remove helpful abstractions that improve code organization
|
||||
- Prioritize "fewer lines" over readability (e.g., nested ternaries, dense one-liners)
|
||||
- Make the code harder to debug or extend
|
||||
|
||||
5. **Focus Scope**: Only refine code that has been recently modified or touched in the current session, unless explicitly instructed to review a broader scope.
|
||||
|
||||
Your refinement process:
|
||||
|
||||
1. Identify the recently modified code sections
|
||||
2. Analyze for opportunities to improve elegance and consistency
|
||||
3. Apply project-specific best practices and coding standards
|
||||
4. Ensure all functionality remains unchanged
|
||||
5. Verify the refined code is simpler and more maintainable
|
||||
6. Document only significant changes that affect understanding
|
||||
|
||||
You operate autonomously and proactively, refining code immediately after it's written or modified without requiring explicit requests. Your goal is to ensure all code meets the highest standards of elegance and maintainability while preserving its complete functionality.
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
description: Test coverage quality analysis — behavioral coverage, critical gap identification,
|
||||
test resilience evaluation.
|
||||
scripts:
|
||||
sh: .specify/scripts/bash/detect-changed-files.sh
|
||||
ps: .specify/scripts/powershell/detect-changed-files.ps1
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: review -->
|
||||
<!-- Config: .specify/extensions/review/ -->
|
||||
You are an expert test coverage analyst specializing in pull request review. Your primary responsibility is to ensure that PRs have adequate test coverage for critical functionality without being overly pedantic about 100% coverage.
|
||||
|
||||
**Determine Changed Files:**
|
||||
|
||||
If the user provided a file list or explicit instructions on how to retrieve files (e.g., only staged, only unstaged, a specific folder, etc.), follow those instructions directly.
|
||||
|
||||
Otherwise, you **MUST** execute the `.specify/scripts/bash/detect-changed-files.sh` with `--json` to detect changed files. **Do not** attempt to detect changes by running `git` commands directly, reading git state manually, or using any other method — always delegate to the script. The script automatically picks the best detection mode:
|
||||
|
||||
> - **Mode A (feature branch):** diffs the current branch against the default branch (`main`/`master`) from the merge-base, plus any staged and unstaged changes.
|
||||
> - **Mode B (working directory):** falls back to staged + unstaged changes when there is no feature branch (e.g., working directly on the default branch).
|
||||
>
|
||||
> JSON output: `{"branch", "default_branch", "mode", "changed_files": [...]}`
|
||||
>
|
||||
> **Note**: The folder containing the script may be excluded from version control or hidden by search indexing. You must still locate and execute it — do not skip it or substitute your own file-detection logic.
|
||||
|
||||
**Your Core Responsibilities:**
|
||||
|
||||
1. **Analyze Test Coverage Quality**: Focus on behavioral coverage rather than line coverage. Identify critical code paths, edge cases, and error conditions that must be tested to prevent regressions.
|
||||
|
||||
2. **Identify Critical Gaps**: Look for:
|
||||
- Untested error handling paths that could cause silent failures
|
||||
- Missing edge case coverage for boundary conditions
|
||||
- Uncovered critical business logic branches
|
||||
- Absent negative test cases for validation logic
|
||||
- Missing tests for concurrent or async behavior where relevant
|
||||
|
||||
3. **Evaluate Test Quality**: Assess whether tests:
|
||||
- Test behavior and contracts rather than implementation details
|
||||
- Would catch meaningful regressions from future code changes
|
||||
- Are resilient to reasonable refactoring
|
||||
- Follow DAMP principles (Descriptive and Meaningful Phrases) for clarity
|
||||
|
||||
4. **Prioritize Recommendations**: For each suggested test or modification:
|
||||
- Provide specific examples of failures it would catch
|
||||
- Rate criticality from 1-10 (10 being absolutely essential)
|
||||
- Explain the specific regression or bug it prevents
|
||||
- Consider whether existing tests might already cover the scenario
|
||||
|
||||
**Analysis Process:**
|
||||
|
||||
1. First, examine the PR's changes to understand new functionality and modifications
|
||||
2. Review the accompanying tests to map coverage to functionality
|
||||
3. Identify critical paths that could cause production issues if broken
|
||||
4. Check for tests that are too tightly coupled to implementation
|
||||
5. Look for missing negative cases and error scenarios
|
||||
6. Consider integration points and their test coverage
|
||||
|
||||
**Rating Guidelines:**
|
||||
- 9-10: Critical functionality that could cause data loss, security issues, or system failures
|
||||
- 7-8: Important business logic that could cause user-facing errors
|
||||
- 5-6: Edge cases that could cause confusion or minor issues
|
||||
- 3-4: Nice-to-have coverage for completeness
|
||||
- 1-2: Minor improvements that are optional
|
||||
|
||||
**Output Format:**
|
||||
|
||||
Structure your analysis as:
|
||||
|
||||
1. **Summary**: Brief overview of test coverage quality
|
||||
2. **Critical Gaps** (if any): Tests rated 8-10 that must be added
|
||||
3. **Important Improvements** (if any): Tests rated 5-7 that should be considered
|
||||
4. **Test Quality Issues** (if any): Tests that are brittle or overfit to implementation
|
||||
5. **Positive Observations**: What's well-tested and follows best practices
|
||||
|
||||
**Important Considerations:**
|
||||
|
||||
- Focus on tests that prevent real bugs, not academic completeness
|
||||
- Consider the project's testing standards from project guidelines (typically in `.specify/memory/constitution.md`, `CLAUDE.md`, `.github/copilot-instructions.md` or equivalent) if available
|
||||
- Remember that some code paths may be covered by existing integration tests
|
||||
- Avoid suggesting tests for trivial getters/setters unless they contain logic
|
||||
- Consider the cost/benefit of each suggested test
|
||||
- Be specific about what each test should verify and why it matters
|
||||
- Note when tests are testing implementation rather than behavior
|
||||
|
||||
You are thorough but pragmatic, focusing on tests that provide real value in catching bugs and preventing regressions rather than achieving metrics. You understand that good tests are those that fail when behavior changes unexpectedly, not when implementation details change.
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
description: Type design analysis — encapsulation, invariant expression, usefulness,
|
||||
and enforcement.
|
||||
scripts:
|
||||
sh: .specify/scripts/bash/detect-changed-files.sh
|
||||
ps: .specify/scripts/powershell/detect-changed-files.ps1
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: review -->
|
||||
<!-- Config: .specify/extensions/review/ -->
|
||||
You are a type design expert with extensive experience in large-scale software architecture. Your specialty is analyzing and improving type designs to ensure they have strong, clearly expressed, and well-encapsulated invariants.
|
||||
|
||||
**Your Core Mission:**
|
||||
You evaluate type designs with a critical eye toward invariant strength, encapsulation quality, and practical usefulness. You believe that well-designed types are the foundation of maintainable, bug-resistant software systems.
|
||||
|
||||
**Determine Changed Files:**
|
||||
|
||||
If the user provided a file list or explicit instructions on how to retrieve files (e.g., only staged, only unstaged, a specific folder, etc.), follow those instructions directly.
|
||||
|
||||
Otherwise, you **MUST** execute the `.specify/scripts/bash/detect-changed-files.sh` with `--json` to detect changed files. **Do not** attempt to detect changes by running `git` commands directly, reading git state manually, or using any other method — always delegate to the script. The script automatically picks the best detection mode:
|
||||
|
||||
> - **Mode A (feature branch):** diffs the current branch against the default branch (`main`/`master`) from the merge-base, plus any staged and unstaged changes.
|
||||
> - **Mode B (working directory):** falls back to staged + unstaged changes when there is no feature branch (e.g., working directly on the default branch).
|
||||
>
|
||||
> JSON output: `{"branch", "default_branch", "mode", "changed_files": [...]}`
|
||||
>
|
||||
> **Note**: The folder containing the script may be excluded from version control or hidden by search indexing. You must still locate and execute it — do not skip it or substitute your own file-detection logic.
|
||||
|
||||
**Analysis Framework:**
|
||||
|
||||
When analyzing a type, you will:
|
||||
|
||||
1. **Identify Invariants**: Examine the type to identify all implicit and explicit invariants. Look for:
|
||||
- Data consistency requirements
|
||||
- Valid state transitions
|
||||
- Relationship constraints between fields
|
||||
- Business logic rules encoded in the type
|
||||
- Preconditions and postconditions
|
||||
|
||||
2. **Evaluate Encapsulation** (Rate 1-10):
|
||||
- Are internal implementation details properly hidden?
|
||||
- Can the type's invariants be violated from outside?
|
||||
- Are there appropriate access modifiers?
|
||||
- Is the interface minimal and complete?
|
||||
|
||||
3. **Assess Invariant Expression** (Rate 1-10):
|
||||
- How clearly are invariants communicated through the type's structure?
|
||||
- Are invariants enforced at compile-time where possible?
|
||||
- Is the type self-documenting through its design?
|
||||
- Are edge cases and constraints obvious from the type definition?
|
||||
|
||||
4. **Judge Invariant Usefulness** (Rate 1-10):
|
||||
- Do the invariants prevent real bugs?
|
||||
- Are they aligned with business requirements?
|
||||
- Do they make the code easier to reason about?
|
||||
- Are they neither too restrictive nor too permissive?
|
||||
|
||||
5. **Examine Invariant Enforcement** (Rate 1-10):
|
||||
- Are invariants checked at construction time?
|
||||
- Are all mutation points guarded?
|
||||
- Is it impossible to create invalid instances?
|
||||
- Are runtime checks appropriate and comprehensive?
|
||||
|
||||
**Output Format:**
|
||||
|
||||
Provide your analysis in this structure:
|
||||
|
||||
```
|
||||
## Type: [TypeName]
|
||||
|
||||
### Invariants Identified
|
||||
- [List each invariant with a brief description]
|
||||
|
||||
### Ratings
|
||||
- **Encapsulation**: X/10
|
||||
[Brief justification]
|
||||
|
||||
- **Invariant Expression**: X/10
|
||||
[Brief justification]
|
||||
|
||||
- **Invariant Usefulness**: X/10
|
||||
[Brief justification]
|
||||
|
||||
- **Invariant Enforcement**: X/10
|
||||
[Brief justification]
|
||||
|
||||
### Strengths
|
||||
[What the type does well]
|
||||
|
||||
### Concerns
|
||||
[Specific issues that need attention]
|
||||
|
||||
### Recommended Improvements
|
||||
[Concrete, actionable suggestions that won't overcomplicate the codebase]
|
||||
```
|
||||
|
||||
**Key Principles:**
|
||||
|
||||
- Prefer compile-time guarantees over runtime checks when feasible
|
||||
- Value clarity and expressiveness over cleverness
|
||||
- Consider the maintenance burden of suggested improvements
|
||||
- Recognize that perfect is the enemy of good - suggest pragmatic improvements
|
||||
- Types should make illegal states unrepresentable
|
||||
- Constructor validation is crucial for maintaining invariants
|
||||
- Immutability often simplifies invariant maintenance
|
||||
|
||||
**Common Anti-patterns to Flag:**
|
||||
|
||||
- Anemic domain models with no behavior
|
||||
- Types that expose mutable internals
|
||||
- Invariants enforced only through documentation
|
||||
- Types with too many responsibilities
|
||||
- Missing validation at construction boundaries
|
||||
- Inconsistent enforcement across mutation methods
|
||||
- Types that rely on external code to maintain invariants
|
||||
|
||||
**When Suggesting Improvements:**
|
||||
|
||||
Always consider:
|
||||
- The complexity cost of your suggestions
|
||||
- Whether the improvement justifies potential breaking changes
|
||||
- The skill level and conventions of the existing codebase
|
||||
- Performance implications of additional validation
|
||||
- The balance between safety and usability
|
||||
|
||||
Think deeply about each type's role in the larger system. Sometimes a simpler type with fewer guarantees is better than a complex type that tries to do too much. Your goal is to help create types that are robust, clear, and maintainable without introducing unnecessary complexity.
|
||||
@@ -0,0 +1,327 @@
|
||||
---
|
||||
description: Create or update the feature specification from a natural language feature description.
|
||||
handoffs:
|
||||
- label: Build Technical Plan
|
||||
agent: speckit.plan
|
||||
prompt: Create a plan for the spec. I am building with...
|
||||
- label: Clarify Spec Requirements
|
||||
agent: speckit.clarify
|
||||
prompt: Clarify specification requirements
|
||||
send: true
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before specification)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_specify` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
The text the user typed after `/speckit.specify` in the triggering message **is** the feature description. Assume you always have it available in this conversation even if `$ARGUMENTS` appears literally below. Do not ask the user to repeat it unless they provided an empty command.
|
||||
|
||||
Given that feature description, do this:
|
||||
|
||||
1. **Generate a concise short name** (2-4 words) for the feature:
|
||||
- Analyze the feature description and extract the most meaningful keywords
|
||||
- Create a 2-4 word short name that captures the essence of the feature
|
||||
- Use action-noun format when possible (e.g., "add-user-auth", "fix-payment-bug")
|
||||
- Preserve technical terms and acronyms (OAuth2, API, JWT, etc.)
|
||||
- Keep it concise but descriptive enough to understand the feature at a glance
|
||||
- Examples:
|
||||
- "I want to add user authentication" → "user-auth"
|
||||
- "Implement OAuth2 integration for the API" → "oauth2-api-integration"
|
||||
- "Create a dashboard for analytics" → "analytics-dashboard"
|
||||
- "Fix payment processing timeout bug" → "fix-payment-timeout"
|
||||
|
||||
2. **Branch creation** (optional, via hook):
|
||||
|
||||
If a `before_specify` hook ran successfully in the Pre-Execution Checks above, it will have created/switched to a git branch and output JSON containing `BRANCH_NAME` and `FEATURE_NUM`. Note these values for reference, but the branch name does **not** dictate the spec directory name.
|
||||
|
||||
If the user explicitly provided `GIT_BRANCH_NAME`, pass it through to the hook so the branch script uses the exact value as the branch name (bypassing all prefix/suffix generation).
|
||||
|
||||
3. **Create the spec feature directory**:
|
||||
|
||||
Specs live under the default `specs/` directory unless the user explicitly provides `SPECIFY_FEATURE_DIRECTORY`.
|
||||
|
||||
**Resolution order for `SPECIFY_FEATURE_DIRECTORY`**:
|
||||
1. If the user explicitly provided `SPECIFY_FEATURE_DIRECTORY` (e.g., via environment variable, argument, or configuration), use it as-is
|
||||
2. Otherwise, auto-generate it under `specs/`:
|
||||
- Check `.specify/init-options.json` for `branch_numbering`
|
||||
- If `"timestamp"`: prefix is `YYYYMMDD-HHMMSS` (current timestamp)
|
||||
- If `"sequential"` or absent: prefix is `NNN` (next available 3-digit number after scanning existing directories in `specs/`)
|
||||
- Construct the directory name: `<prefix>-<short-name>` (e.g., `003-user-auth` or `20260319-143022-user-auth`)
|
||||
- Set `SPECIFY_FEATURE_DIRECTORY` to `specs/<directory-name>`
|
||||
|
||||
**Create the directory and spec file**:
|
||||
- `mkdir -p SPECIFY_FEATURE_DIRECTORY`
|
||||
- Copy `.specify/templates/spec-template.md` to `SPECIFY_FEATURE_DIRECTORY/spec.md` as the starting point
|
||||
- Set `SPEC_FILE` to `SPECIFY_FEATURE_DIRECTORY/spec.md`
|
||||
- Persist the resolved path to `.specify/feature.json`:
|
||||
```json
|
||||
{
|
||||
"feature_directory": "<resolved feature dir>"
|
||||
}
|
||||
```
|
||||
Write the actual resolved directory path value (for example, `specs/003-user-auth`), not the literal string `SPECIFY_FEATURE_DIRECTORY`.
|
||||
This allows downstream commands (`/speckit.plan`, `/speckit.tasks`, etc.) to locate the feature directory without relying on git branch name conventions.
|
||||
|
||||
**IMPORTANT**:
|
||||
- You must only create one feature per `/speckit.specify` invocation
|
||||
- The spec directory name and the git branch name are independent — they may be the same but that is the user's choice
|
||||
- The spec directory and file are always created by this command, never by the hook
|
||||
|
||||
4. Load `.specify/templates/spec-template.md` to understand required sections.
|
||||
|
||||
5. Follow this execution flow:
|
||||
1. Parse user description from arguments
|
||||
If empty: ERROR "No feature description provided"
|
||||
2. Extract key concepts from description
|
||||
Identify: actors, actions, data, constraints
|
||||
3. For unclear aspects:
|
||||
- Make informed guesses based on context and industry standards
|
||||
- Only mark with [NEEDS CLARIFICATION: specific question] if:
|
||||
- The choice significantly impacts feature scope or user experience
|
||||
- Multiple reasonable interpretations exist with different implications
|
||||
- No reasonable default exists
|
||||
- **LIMIT: Maximum 3 [NEEDS CLARIFICATION] markers total**
|
||||
- Prioritize clarifications by impact: scope > security/privacy > user experience > technical details
|
||||
4. Fill User Scenarios & Testing section
|
||||
If no clear user flow: ERROR "Cannot determine user scenarios"
|
||||
5. Generate Functional Requirements
|
||||
Each requirement must be testable
|
||||
Use reasonable defaults for unspecified details (document assumptions in Assumptions section)
|
||||
6. Define Success Criteria
|
||||
Create measurable, technology-agnostic outcomes
|
||||
Include both quantitative metrics (time, performance, volume) and qualitative measures (user satisfaction, task completion)
|
||||
Each criterion must be verifiable without implementation details
|
||||
7. Identify Key Entities (if data involved)
|
||||
8. Return: SUCCESS (spec ready for planning)
|
||||
|
||||
6. Write the specification to SPEC_FILE using the template structure, replacing placeholders with concrete details derived from the feature description (arguments) while preserving section order and headings.
|
||||
|
||||
7. **Specification Quality Validation**: After writing the initial spec, validate it against quality criteria:
|
||||
|
||||
a. **Create Spec Quality Checklist**: Generate a checklist file at `SPECIFY_FEATURE_DIRECTORY/checklists/requirements.md` using the checklist template structure with these validation items:
|
||||
|
||||
```markdown
|
||||
# Specification Quality Checklist: [FEATURE NAME]
|
||||
|
||||
**Purpose**: Validate specification completeness and quality before proceeding to planning
|
||||
**Created**: [DATE]
|
||||
**Feature**: [Link to spec.md]
|
||||
|
||||
## Content Quality
|
||||
|
||||
- [ ] No implementation details (languages, frameworks, APIs)
|
||||
- [ ] Focused on user value and business needs
|
||||
- [ ] Written for non-technical stakeholders
|
||||
- [ ] All mandatory sections completed
|
||||
|
||||
## Requirement Completeness
|
||||
|
||||
- [ ] No [NEEDS CLARIFICATION] markers remain
|
||||
- [ ] Requirements are testable and unambiguous
|
||||
- [ ] Success criteria are measurable
|
||||
- [ ] Success criteria are technology-agnostic (no implementation details)
|
||||
- [ ] All acceptance scenarios are defined
|
||||
- [ ] Edge cases are identified
|
||||
- [ ] Scope is clearly bounded
|
||||
- [ ] Dependencies and assumptions identified
|
||||
|
||||
## Feature Readiness
|
||||
|
||||
- [ ] All functional requirements have clear acceptance criteria
|
||||
- [ ] User scenarios cover primary flows
|
||||
- [ ] Feature meets measurable outcomes defined in Success Criteria
|
||||
- [ ] No implementation details leak into specification
|
||||
|
||||
## Notes
|
||||
|
||||
- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`
|
||||
```
|
||||
|
||||
b. **Run Validation Check**: Review the spec against each checklist item:
|
||||
- For each item, determine if it passes or fails
|
||||
- Document specific issues found (quote relevant spec sections)
|
||||
|
||||
c. **Handle Validation Results**:
|
||||
|
||||
- **If all items pass**: Mark checklist complete and proceed to step 8
|
||||
|
||||
- **If items fail (excluding [NEEDS CLARIFICATION])**:
|
||||
1. List the failing items and specific issues
|
||||
2. Update the spec to address each issue
|
||||
3. Re-run validation until all items pass (max 3 iterations)
|
||||
4. If still failing after 3 iterations, document remaining issues in checklist notes and warn user
|
||||
|
||||
- **If [NEEDS CLARIFICATION] markers remain**:
|
||||
1. Extract all [NEEDS CLARIFICATION: ...] markers from the spec
|
||||
2. **LIMIT CHECK**: If more than 3 markers exist, keep only the 3 most critical (by scope/security/UX impact) and make informed guesses for the rest
|
||||
3. For each clarification needed (max 3), present options to user in this format:
|
||||
|
||||
```markdown
|
||||
## Question [N]: [Topic]
|
||||
|
||||
**Context**: [Quote relevant spec section]
|
||||
|
||||
**What we need to know**: [Specific question from NEEDS CLARIFICATION marker]
|
||||
|
||||
**Suggested Answers**:
|
||||
|
||||
| Option | Answer | Implications |
|
||||
|--------|--------|--------------|
|
||||
| A | [First suggested answer] | [What this means for the feature] |
|
||||
| B | [Second suggested answer] | [What this means for the feature] |
|
||||
| C | [Third suggested answer] | [What this means for the feature] |
|
||||
| Custom | Provide your own answer | [Explain how to provide custom input] |
|
||||
|
||||
**Your choice**: _[Wait for user response]_
|
||||
```
|
||||
|
||||
4. **CRITICAL - Table Formatting**: Ensure markdown tables are properly formatted:
|
||||
- Use consistent spacing with pipes aligned
|
||||
- Each cell should have spaces around content: `| Content |` not `|Content|`
|
||||
- Header separator must have at least 3 dashes: `|--------|`
|
||||
- Test that the table renders correctly in markdown preview
|
||||
5. Number questions sequentially (Q1, Q2, Q3 - max 3 total)
|
||||
6. Present all questions together before waiting for responses
|
||||
7. Wait for user to respond with their choices for all questions (e.g., "Q1: A, Q2: Custom - [details], Q3: B")
|
||||
8. Update the spec by replacing each [NEEDS CLARIFICATION] marker with the user's selected or provided answer
|
||||
9. Re-run validation after all clarifications are resolved
|
||||
|
||||
d. **Update Checklist**: After each validation iteration, update the checklist file with current pass/fail status
|
||||
|
||||
8. **Report completion** to the user with:
|
||||
- `SPECIFY_FEATURE_DIRECTORY` — the feature directory path
|
||||
- `SPEC_FILE` — the spec file path
|
||||
- Checklist results summary
|
||||
- Readiness for the next phase (`/speckit.clarify` or `/speckit.plan`)
|
||||
|
||||
9. **Check for extension hooks**: After reporting completion, check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_specify` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
**NOTE:** Branch creation is handled by the `before_specify` hook (git extension). Spec directory and file creation are always handled by this core command.
|
||||
|
||||
## Quick Guidelines
|
||||
|
||||
- Focus on **WHAT** users need and **WHY**.
|
||||
- Avoid HOW to implement (no tech stack, APIs, code structure).
|
||||
- Written for business stakeholders, not developers.
|
||||
- DO NOT create any checklists that are embedded in the spec. That will be a separate command.
|
||||
|
||||
### Section Requirements
|
||||
|
||||
- **Mandatory sections**: Must be completed for every feature
|
||||
- **Optional sections**: Include only when relevant to the feature
|
||||
- When a section doesn't apply, remove it entirely (don't leave as "N/A")
|
||||
|
||||
### For AI Generation
|
||||
|
||||
When creating this spec from a user prompt:
|
||||
|
||||
1. **Make informed guesses**: Use context, industry standards, and common patterns to fill gaps
|
||||
2. **Document assumptions**: Record reasonable defaults in the Assumptions section
|
||||
3. **Limit clarifications**: Maximum 3 [NEEDS CLARIFICATION] markers - use only for critical decisions that:
|
||||
- Significantly impact feature scope or user experience
|
||||
- Have multiple reasonable interpretations with different implications
|
||||
- Lack any reasonable default
|
||||
4. **Prioritize clarifications**: scope > security/privacy > user experience > technical details
|
||||
5. **Think like a tester**: Every vague requirement should fail the "testable and unambiguous" checklist item
|
||||
6. **Common areas needing clarification** (only if no reasonable default exists):
|
||||
- Feature scope and boundaries (include/exclude specific use cases)
|
||||
- User types and permissions (if multiple conflicting interpretations possible)
|
||||
- Security/compliance requirements (when legally/financially significant)
|
||||
|
||||
**Examples of reasonable defaults** (don't ask about these):
|
||||
|
||||
- Data retention: Industry-standard practices for the domain
|
||||
- Performance targets: Standard web/mobile app expectations unless specified
|
||||
- Error handling: User-friendly messages with appropriate fallbacks
|
||||
- Authentication method: Standard session-based or OAuth2 for web apps
|
||||
- Integration patterns: Use project-appropriate patterns (REST/GraphQL for web services, function calls for libraries, CLI args for tools, etc.)
|
||||
|
||||
### Success Criteria Guidelines
|
||||
|
||||
Success criteria must be:
|
||||
|
||||
1. **Measurable**: Include specific metrics (time, percentage, count, rate)
|
||||
2. **Technology-agnostic**: No mention of frameworks, languages, databases, or tools
|
||||
3. **User-focused**: Describe outcomes from user/business perspective, not system internals
|
||||
4. **Verifiable**: Can be tested/validated without knowing implementation details
|
||||
|
||||
**Good examples**:
|
||||
|
||||
- "Users can complete checkout in under 3 minutes"
|
||||
- "System supports 10,000 concurrent users"
|
||||
- "95% of searches return results in under 1 second"
|
||||
- "Task completion rate improves by 40%"
|
||||
|
||||
**Bad examples** (implementation-focused):
|
||||
|
||||
- "API response time is under 200ms" (too technical, use "Users see results instantly")
|
||||
- "Database can handle 1000 TPS" (implementation detail, use user-facing metric)
|
||||
- "React components render efficiently" (framework-specific)
|
||||
- "Redis cache hit rate above 80%" (technology-specific)
|
||||
@@ -0,0 +1,200 @@
|
||||
---
|
||||
description: Generate an actionable, dependency-ordered tasks.md for the feature based on available design artifacts.
|
||||
handoffs:
|
||||
- label: Analyze For Consistency
|
||||
agent: speckit.analyze
|
||||
prompt: Run a project analysis for consistency
|
||||
send: true
|
||||
- label: Implement Project
|
||||
agent: speckit.implement
|
||||
prompt: Start the implementation in phases
|
||||
send: true
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before tasks generation)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_tasks` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
1. **Setup**: Run `.specify/scripts/bash/setup-tasks.sh --json` from repo root and parse FEATURE_DIR, TASKS_TEMPLATE, and AVAILABLE_DOCS list. `FEATURE_DIR` and `TASKS_TEMPLATE` must be absolute paths when provided. `AVAILABLE_DOCS` is a list of document names/relative paths available under `FEATURE_DIR` (for example `research.md` or `contracts/`). For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
2. **Load design documents**: Read from FEATURE_DIR:
|
||||
- **Required**: plan.md (tech stack, libraries, structure), spec.md (user stories with priorities)
|
||||
- **Optional**: data-model.md (entities), contracts/ (interface contracts), research.md (decisions), quickstart.md (test scenarios)
|
||||
- Note: Not all projects have all documents. Generate tasks based on what's available.
|
||||
|
||||
3. **Execute task generation workflow**:
|
||||
- Load plan.md and extract tech stack, libraries, project structure
|
||||
- Load spec.md and extract user stories with their priorities (P1, P2, P3, etc.)
|
||||
- If data-model.md exists: Extract entities and map to user stories
|
||||
- If contracts/ exists: Map interface contracts to user stories
|
||||
- If research.md exists: Extract decisions for setup tasks
|
||||
- Generate tasks organized by user story (see Task Generation Rules below)
|
||||
- Generate dependency graph showing user story completion order
|
||||
- Create parallel execution examples per user story
|
||||
- Validate task completeness (each user story has all needed tasks, independently testable)
|
||||
|
||||
4. **Generate tasks.md**: Read the tasks template from TASKS_TEMPLATE (from the JSON output above) and use it as structure. If TASKS_TEMPLATE is empty, fall back to `.specify/templates/tasks-template.md`. Fill with:
|
||||
- Correct feature name from plan.md
|
||||
- Phase 1: Setup tasks (project initialization)
|
||||
- Phase 2: Foundational tasks (blocking prerequisites for all user stories)
|
||||
- Phase 3+: One phase per user story (in priority order from spec.md)
|
||||
- Each phase includes: story goal, independent test criteria, tests (if requested), implementation tasks
|
||||
- Final Phase: Polish & cross-cutting concerns
|
||||
- All tasks must follow the strict checklist format (see Task Generation Rules below)
|
||||
- Clear file paths for each task
|
||||
- Dependencies section showing story completion order
|
||||
- Parallel execution examples per story
|
||||
- Implementation strategy section (MVP first, incremental delivery)
|
||||
|
||||
5. **Report**: Output path to generated tasks.md and summary:
|
||||
- Total task count
|
||||
- Task count per user story
|
||||
- Parallel opportunities identified
|
||||
- Independent test criteria for each story
|
||||
- Suggested MVP scope (typically just User Story 1)
|
||||
- Format validation: Confirm ALL tasks follow the checklist format (checkbox, ID, labels, file paths)
|
||||
|
||||
6. **Check for extension hooks**: After tasks.md is generated, check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_tasks` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
Context for task generation: $ARGUMENTS
|
||||
|
||||
The tasks.md should be immediately executable - each task must be specific enough that an LLM can complete it without additional context.
|
||||
|
||||
## Task Generation Rules
|
||||
|
||||
**CRITICAL**: Tasks MUST be organized by user story to enable independent implementation and testing.
|
||||
|
||||
**Tests are OPTIONAL**: Only generate test tasks if explicitly requested in the feature specification or if user requests TDD approach.
|
||||
|
||||
### Checklist Format (REQUIRED)
|
||||
|
||||
Every task MUST strictly follow this format:
|
||||
|
||||
```text
|
||||
- [ ] [TaskID] [P?] [Story?] Description with file path
|
||||
```
|
||||
|
||||
**Format Components**:
|
||||
|
||||
1. **Checkbox**: ALWAYS start with `- [ ]` (markdown checkbox)
|
||||
2. **Task ID**: Sequential number (T001, T002, T003...) in execution order
|
||||
3. **[P] marker**: Include ONLY if task is parallelizable (different files, no dependencies on incomplete tasks)
|
||||
4. **[Story] label**: REQUIRED for user story phase tasks only
|
||||
- Format: [US1], [US2], [US3], etc. (maps to user stories from spec.md)
|
||||
- Setup phase: NO story label
|
||||
- Foundational phase: NO story label
|
||||
- User Story phases: MUST have story label
|
||||
- Polish phase: NO story label
|
||||
5. **Description**: Clear action with exact file path
|
||||
|
||||
**Examples**:
|
||||
|
||||
- ✅ CORRECT: `- [ ] T001 Create project structure per implementation plan`
|
||||
- ✅ CORRECT: `- [ ] T005 [P] Implement authentication middleware in src/middleware/auth.py`
|
||||
- ✅ CORRECT: `- [ ] T012 [P] [US1] Create User model in src/models/user.py`
|
||||
- ✅ CORRECT: `- [ ] T014 [US1] Implement UserService in src/services/user_service.py`
|
||||
- ❌ WRONG: `- [ ] Create User model` (missing ID and Story label)
|
||||
- ❌ WRONG: `T001 [US1] Create model` (missing checkbox)
|
||||
- ❌ WRONG: `- [ ] [US1] Create User model` (missing Task ID)
|
||||
- ❌ WRONG: `- [ ] T001 [US1] Create model` (missing file path)
|
||||
|
||||
### Task Organization
|
||||
|
||||
1. **From User Stories (spec.md)** - PRIMARY ORGANIZATION:
|
||||
- Each user story (P1, P2, P3...) gets its own phase
|
||||
- Map all related components to their story:
|
||||
- Models needed for that story
|
||||
- Services needed for that story
|
||||
- Interfaces/UI needed for that story
|
||||
- If tests requested: Tests specific to that story
|
||||
- Mark story dependencies (most stories should be independent)
|
||||
|
||||
2. **From Contracts**:
|
||||
- Map each interface contract → to the user story it serves
|
||||
- If tests requested: Each interface contract → contract test task [P] before implementation in that story's phase
|
||||
|
||||
3. **From Data Model**:
|
||||
- Map each entity to the user story(ies) that need it
|
||||
- If entity serves multiple stories: Put in earliest story or Setup phase
|
||||
- Relationships → service layer tasks in appropriate story phase
|
||||
|
||||
4. **From Setup/Infrastructure**:
|
||||
- Shared infrastructure → Setup phase (Phase 1)
|
||||
- Foundational/blocking tasks → Foundational phase (Phase 2)
|
||||
- Story-specific setup → within that story's phase
|
||||
|
||||
### Phase Structure
|
||||
|
||||
- **Phase 1**: Setup (project initialization)
|
||||
- **Phase 2**: Foundational (blocking prerequisites - MUST complete before user stories)
|
||||
- **Phase 3+**: User Stories in priority order (P1, P2, P3...)
|
||||
- Within each story: Tests (if requested) → Models → Services → Endpoints → Integration
|
||||
- Each phase should be a complete, independently testable increment
|
||||
- **Final Phase**: Polish & Cross-Cutting Concerns
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
description: Convert existing tasks into actionable, dependency-ordered GitHub issues for the feature based on available design artifacts.
|
||||
tools: ['github/github-mcp-server/issue_write']
|
||||
---
|
||||
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Pre-Execution Checks
|
||||
|
||||
**Check for extension hooks (before tasks-to-issues conversion)**:
|
||||
- Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.before_taskstoissues` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Pre-Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Pre-Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
|
||||
Wait for the result of the hook command before proceeding to the Outline.
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
|
||||
## Outline
|
||||
|
||||
1. Run `.specify/scripts/bash/check-prerequisites.sh --json --require-tasks --include-tasks` from repo root and parse FEATURE_DIR and AVAILABLE_DOCS list. All paths must be absolute. For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
1. From the executed script, extract the path to **tasks**.
|
||||
1. Get the Git remote by running:
|
||||
|
||||
```bash
|
||||
git config --get remote.origin.url
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> ONLY PROCEED TO NEXT STEPS IF THE REMOTE IS A GITHUB URL
|
||||
|
||||
1. For each task in the list, use the GitHub MCP server to create a new issue in the repository that is representative of the Git remote.
|
||||
|
||||
> [!CAUTION]
|
||||
> UNDER NO CIRCUMSTANCES EVER CREATE ISSUES IN REPOSITORIES THAT DO NOT MATCH THE REMOTE URL
|
||||
|
||||
## Post-Execution Checks
|
||||
|
||||
**Check for extension hooks (after tasks-to-issues conversion)**:
|
||||
Check if `.specify/extensions.yml` exists in the project root.
|
||||
- If it exists, read it and look for entries under the `hooks.after_taskstoissues` key
|
||||
- If the YAML cannot be parsed or is invalid, skip hook checking silently and continue normally
|
||||
- Filter out hooks where `enabled` is explicitly `false`. Treat hooks without an `enabled` field as enabled by default.
|
||||
- For each remaining hook, do **not** attempt to interpret or evaluate hook `condition` expressions:
|
||||
- If the hook has no `condition` field, or it is null/empty, treat the hook as executable
|
||||
- If the hook defines a non-empty `condition`, skip the hook and leave condition evaluation to the HookExecutor implementation
|
||||
- For each executable hook, output the following based on its `optional` flag:
|
||||
- **Optional hook** (`optional: true`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Optional Hook**: {extension}
|
||||
Command: `/{command}`
|
||||
Description: {description}
|
||||
|
||||
Prompt: {prompt}
|
||||
To execute: `/{command}`
|
||||
```
|
||||
- **Mandatory hook** (`optional: false`):
|
||||
```
|
||||
## Extension Hooks
|
||||
|
||||
**Automatic Hook**: {extension}
|
||||
Executing: `/{command}`
|
||||
EXECUTE_COMMAND: {command}
|
||||
```
|
||||
- If no hooks are registered or `.specify/extensions.yml` does not exist, skip silently
|
||||
@@ -0,0 +1,219 @@
|
||||
---
|
||||
description: Perform a non-destructive post-implementation verification gate validating
|
||||
the implementation against spec.md, plan.md, tasks.md, and constitution.md.
|
||||
scripts:
|
||||
sh: .specify/scripts/bash/check-prerequisites.sh --json --paths-only
|
||||
ps: .specify/scripts/powershell/check-prerequisites.ps1 -Json -PathsOnly
|
||||
---
|
||||
|
||||
|
||||
<!-- Extension: verify -->
|
||||
<!-- Config: .specify/extensions/verify/ -->
|
||||
## User Input
|
||||
|
||||
```text
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
You **MUST** consider the user input before proceeding (if not empty).
|
||||
|
||||
## Goal
|
||||
|
||||
Validate the implementation against its specification artifacts (`spec.md`, `plan.md`, `tasks.md`, `constitution.md`). This command MUST run only after `/speckit.implement` has completed.
|
||||
|
||||
## Operating Constraints
|
||||
|
||||
**STRICTLY READ-ONLY**: Do **not** modify any files. Output a structured analysis report. Offer an optional remediation plan (user must explicitly approve before any follow-up editing commands would be invoked manually).
|
||||
|
||||
**Constitution Authority**: The project constitution (`.specify/memory/constitution.md`) is **non-negotiable** within this verification scope. Constitution conflicts are automatically CRITICAL and require adjustment of the spec, plan, tasks or implementation—not dilution, reinterpretation, or silent ignoring of the principle. If a principle itself needs to change, that must occur in a separate, explicit constitution update outside `/speckit.verify.run`.
|
||||
|
||||
## Execution Steps
|
||||
|
||||
### 1. Initialize Verification Context
|
||||
|
||||
Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root.
|
||||
|
||||
1. **Script succeeds** (on a feature branch): Parse JSON for FEATURE_DIR. Set `FEATURE_BRANCH = true`. Proceed to next step.
|
||||
2. **Script fails** (not on a feature branch): You MUST prompt for available features (Scan `specs/*/` to get available features). Use the **AskUserQuestion tool** to let the user select. **Do NOT guess or auto-select a change. Always let the user choose.**
|
||||
|
||||
Derive absolute paths:
|
||||
|
||||
- SPEC = FEATURE_DIR/spec.md
|
||||
- PLAN = FEATURE_DIR/plan.md
|
||||
- TASKS = FEATURE_DIR/tasks.md.
|
||||
|
||||
Abort if any required file is missing (instruct the user to run missing prerequisite command).
|
||||
For single quotes in args like "I'm Groot", use escape syntax: e.g 'I'\''m Groot' (or double-quote if possible: "I'm Groot").
|
||||
|
||||
### 2. Load Configuration
|
||||
|
||||
Run the load-config script (`.specify/extensions/verify/scripts/bash/load-config.sh` or `.specify/extensions/verify/scripts/powershell/load-config.ps1`) from the repo root. Parse the `max_findings` value from its output and store it for use in Step 6. If the script fails, abort and relay its error message to the user.
|
||||
|
||||
### 3. Load Artifacts (Progressive Disclosure)
|
||||
|
||||
Load only the minimal necessary context from each artifact:
|
||||
|
||||
**From spec.md:**
|
||||
|
||||
- User Scenarios & Testing (user stories, acceptance scenarios, priorities)
|
||||
- Edge Cases
|
||||
- Functional Requirements
|
||||
- Success Criteria / Measurable Outcomes (performance, security, availability, observability targets)
|
||||
- Assumptions
|
||||
|
||||
**From plan.md:**
|
||||
|
||||
- Architecture/stack choices
|
||||
- Technical constraints
|
||||
- Technical Context (language, dependencies, storage, testing, platform, constraints)
|
||||
- Project Structure (documentation layout and source code layout)
|
||||
|
||||
**From data-model.md (if present):**
|
||||
|
||||
- Entity names, fields, and relationships
|
||||
- Validation rules
|
||||
- State transitions
|
||||
|
||||
**From tasks.md:**
|
||||
|
||||
- Task IDs
|
||||
- Completion status
|
||||
- Descriptions
|
||||
- Phase grouping
|
||||
- Referenced file paths
|
||||
|
||||
**From constitution:**
|
||||
|
||||
- Load `.specify/memory/constitution.md` for principle validation
|
||||
|
||||
### 4. Identify Implementation Scope
|
||||
|
||||
Build the set of files to verify from tasks.md.
|
||||
|
||||
- Parse all tasks in tasks.md — both completed (`[x]`/`[X]`) and incomplete (`[ ]`)
|
||||
- Extract file paths referenced in each task description
|
||||
- Build **REVIEW_FILES** set from completed task file paths
|
||||
- Track **INCOMPLETE_TASK_FILES** from incomplete tasks (used by check C)
|
||||
|
||||
### 5. Build Semantic Models
|
||||
|
||||
Create internal representations (do not include raw artifacts in output):
|
||||
|
||||
- **Task inventory**: Each task with ID, completion status, referenced file paths, and phase grouping
|
||||
- **Implementation mapping**: Map each completed task to its referenced file paths
|
||||
- **File inventory**: All REVIEW_FILES with existence verification — flag any task-referenced file that does not exist on disk
|
||||
- **Requirements inventory**: Each functional requirement with a stable key — map to tasks and REVIEW_FILES for implementation evidence (evidence = file in REVIEW_FILES containing keyword/ID match, function signatures, or code paths that address the requirement)
|
||||
- **Spec intent references**: User stories, acceptance criteria, scenarios, edge cases, and code-verifiable success criteria from spec.md
|
||||
- **Constitution rule set**: Extract principle names and MUST/SHOULD normative statements
|
||||
|
||||
### 6. Verification Checks (Token-Efficient Analysis)
|
||||
|
||||
Focus on high-signal findings. **Limit to the configured `max_findings` value** (loaded in Step 2); aggregate remainder in overflow summary.
|
||||
|
||||
#### A. Task Completion
|
||||
|
||||
- Compare completed (`[x]`/`[X]`) vs total tasks
|
||||
- Flag majority incomplete vs minority incomplete
|
||||
|
||||
#### B. File Existence
|
||||
|
||||
- Task-referenced files that do not exist on disk
|
||||
- Tasks referencing ambiguous or unresolvable paths
|
||||
|
||||
#### C. Requirement Coverage
|
||||
|
||||
- Requirements with no implementation evidence in REVIEW_FILES
|
||||
- Requirements whose tasks are all incomplete
|
||||
|
||||
#### D. Scenario & Test Coverage
|
||||
|
||||
- Spec scenarios with no corresponding test or code path
|
||||
- Edge cases with no corresponding test, guard clause, or error-handling code path
|
||||
- No test files detected at all in REVIEW_FILES
|
||||
|
||||
#### E. Spec Intent Alignment
|
||||
|
||||
- Implementation diverging from spec intent (minor vs fundamental divergence)
|
||||
- Compare acceptance criteria against actual behaviour in REVIEW_FILES
|
||||
- Code-verifiable success criteria (performance, security, availability, observability) with no evidence of implementation support — skip business/UX metrics that require post-deployment measurement
|
||||
|
||||
#### F. Constitution Alignment
|
||||
|
||||
- Any implementation element conflicting with a constitution MUST principle
|
||||
- Missing mandated sections or quality gates from constitution
|
||||
|
||||
#### G. Design & Structure Consistency
|
||||
|
||||
- Architectural decisions or design patterns from plan.md not reflected in code
|
||||
- Planned directory/file layout deviating from actual structure
|
||||
- New code deviating from existing project conventions (naming, module structure, error handling patterns)
|
||||
- Public APIs/exports/endpoints not described in plan.md
|
||||
|
||||
### 7. Severity Assignment
|
||||
|
||||
Use this heuristic to prioritize findings:
|
||||
|
||||
- **CRITICAL**: Violates constitution MUST, majority of tasks incomplete, task-referenced files missing from disk, requirement with zero implementation
|
||||
- **HIGH**: Spec intent divergence, fundamental implementation mismatch with acceptance criteria, missing scenario/test coverage
|
||||
- **MEDIUM**: Design pattern drift, minor spec intent deviation
|
||||
- **LOW**: Structure deviations, naming inconsistencies, minor observations not affecting functionality
|
||||
|
||||
### 8. Produce Compact Verification Report
|
||||
|
||||
Output a Markdown report (no file writes) with the following structure.
|
||||
|
||||
**If `FEATURE_BRANCH = false`**, prepend: `> ⚠️ **Non-Feature-Branch Verification** from \`<BRANCH>\` against \`<FEATURE_DIR>\`. Some checks may be affected by cross-feature interference.`
|
||||
|
||||
## Verification Report
|
||||
|
||||
| ID | Category | Severity | Location(s) | Summary | Recommendation |
|
||||
|----|----------|----------|-------------|---------|----------------|
|
||||
| A1 | Task Completion | CRITICAL | tasks.md | 3 of 12 tasks incomplete | Complete tasks T05, T08, T11 |
|
||||
| B1 | File Existence | CRITICAL | src/auth.ts | Task-referenced file missing | Create file or update task reference |
|
||||
| C1 | Requirement Coverage | CRITICAL | spec.md:FR-003 | No implementation evidence | Implement FR-003 |
|
||||
|
||||
(Add one row per finding; generate stable IDs prefixed by check letter: A1, B1, C1... Reference specific files and line numbers in Location(s) where applicable.)
|
||||
|
||||
**Task Summary Table:**
|
||||
|
||||
| Task ID | Status | Referenced Files | Notes |
|
||||
|---------|--------|-----------------|-------|
|
||||
|
||||
**Constitution Alignment Issues:** (if any)
|
||||
|
||||
**Metrics:**
|
||||
|
||||
- Total Tasks (completed / total)
|
||||
- Requirement Coverage % (requirements with implementation evidence / total)
|
||||
- Files Verified
|
||||
- Critical Issues Count
|
||||
|
||||
### 9. Provide Next Actions
|
||||
|
||||
At end of report, output a concise Next Actions block:
|
||||
|
||||
- If CRITICAL issues exist: Recommend resolving before proceeding
|
||||
- If HIGH issues exist: Recommend addressing before merge; user may proceed at own risk
|
||||
- If only LOW/MEDIUM: User may proceed, but provide improvement suggestions
|
||||
- Provide explicit command suggestions: e.g., "Run `/speckit.implement` to address findings and re-run verification", "Implementation verified — ready for review or merge"
|
||||
|
||||
### 10. Offer Remediation
|
||||
|
||||
Ask the user: "Would you like me to suggest concrete remediation edits for the top N issues?" (Do NOT apply them automatically.)
|
||||
|
||||
## Operating Principles
|
||||
|
||||
### Context Efficiency
|
||||
|
||||
- **Minimal high-signal tokens**: Focus on actionable findings, not exhaustive documentation
|
||||
- **Progressive disclosure**: Load artifacts and source files incrementally; don't dump all content into analysis
|
||||
- **Token-efficient output**: Limit findings table to the configured `max_findings` value; summarize overflow
|
||||
- **Deterministic results**: Rerunning without changes should produce consistent IDs and counts
|
||||
|
||||
### Analysis Guidelines
|
||||
|
||||
- **NEVER modify files** (this is read-only analysis)
|
||||
- **NEVER hallucinate missing sections** (if absent, report them accurately)
|
||||
- **Prioritize constitution violations** (these are always CRITICAL)
|
||||
- **Use examples over exhaustive rules** (cite specific instances, not generic patterns)
|
||||
- **Report zero issues gracefully** (emit success report with coverage statistics)
|
||||
@@ -34,13 +34,11 @@ org.gradle.isolated-projects=true
|
||||
# incremental state that will never be reused.
|
||||
kotlin.incremental=false
|
||||
kotlin.code.style=official
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
|
||||
# ── KSP ──────────────────────────────────────────────────────────────
|
||||
# In CI, KSP incremental processing adds overhead without benefit (fresh
|
||||
# checkouts). Keep intermodule incremental off (no prior state).
|
||||
ksp.incremental=false
|
||||
ksp.run.in.process=true
|
||||
|
||||
# ── Android ──────────────────────────────────────────────────────────
|
||||
android.experimental.lint.analysisPerComponent=true
|
||||
|
||||
@@ -1,6 +1,45 @@
|
||||
# Meshtastic Android - GitHub Copilot Guide
|
||||
# Meshtastic Android — Copilot Instructions
|
||||
|
||||
> **Note:** The canonical instructions for all AI Agents have been deduplicated.
|
||||
> **`AGENTS.md` is the source of truth** for rules, architecture, and conventions. This file is a compact Copilot quick-reference plus Copilot-only behavior rules — it deliberately does not restate AGENTS.md. For build/test detail see `.skills/testing-ci/`; for the codebase map and bootstrap see `.skills/project-overview/`.
|
||||
|
||||
You MUST immediately read and internalize the unified instructions located at the root of the repository in `AGENTS.md`.
|
||||
After reading `AGENTS.md`, consult the `.skills/` directory for task-specific playbooks.
|
||||
## Build, Test & Lint (essentials)
|
||||
|
||||
Requires JDK 25 and `ANDROID_HOME`. Per fresh clone:
|
||||
```bash
|
||||
[ -f local.properties ] || cp secrets.defaults.properties local.properties
|
||||
```
|
||||
```bash
|
||||
./gradlew spotlessApply spotlessCheck detekt assembleDebug test allTests # full local verification (run before push)
|
||||
./gradlew :core:data:allTests # single KMP module
|
||||
./gradlew :androidApp:testFdroidDebugUnitTest # single Android-only module
|
||||
./gradlew kmpSmokeCompile # cross-platform compile check, no tests
|
||||
```
|
||||
> Both `test` AND `allTests` are needed — `allTests` covers KMP modules, `test` covers pure-Android modules.
|
||||
|
||||
**KMP vs Android-only task naming** (wrong name silently skips tests or fails resolution): KMP modules (`core:*`, `feature:*`) use `:module:allTests` and `:module:compileKotlinJvm`; Android-only modules (`androidApp`, `desktopApp`, `core:barcode`) use `:module:testFdroidDebugUnitTest` (plain `:desktopApp:test` for the JVM-only desktop module). `:module:detekt` is the lifecycle task for both — never `detektMain`/`detektDebug`. Full matrix and pitfalls: `.skills/testing-ci/`.
|
||||
|
||||
Architecture, flavors, conventions, branch naming, protos, coding rules: **see `AGENTS.md`**. Contextual `.github/instructions/` files enforce conventions scoped to specific source sets.
|
||||
|
||||
## Copilot-Only Behavior Rules
|
||||
|
||||
These are specific to the Copilot CLI environment and are not covered in AGENTS.md.
|
||||
|
||||
- **Do it right the first time.** When refactoring, implement the correct solution fully — don't defer improvements as "out of scope". Research the proper API before implementing.
|
||||
- **Preserve commit history.** Always make new commits. Never `--amend`, `rebase -i`, squash, or `--force-with-lease` unless the user explicitly requests it.
|
||||
- **Workflow-scope push limit.** The Copilot CLI OAuth token cannot push to `.github/workflows/`. If a push fails with a scope/permission error on workflow files, tell the user to push manually (`gh auth refresh -s workflow && git push`) — do not retry or work around it.
|
||||
- **No destructive git in parallel agents.** Agents sharing a worktree must never run `git reset --hard`, `git clean`, or `git checkout -- .`. Commit work before any index/working-tree operation.
|
||||
- **Audit before applying.** When porting or migrating external code, evaluate each change for relevance and correctness in our context — don't blindly copy.
|
||||
|
||||
## Context & Cost Efficiency
|
||||
|
||||
- **Compact before research phases.** Open-ended research/exploration generates high-volume tool output — compact first.
|
||||
- **Split sessions at phase boundaries.** Don't keep one session alive across multiple discrete deliverables (PR opened, spec finalized, merged). A compact summary in a fresh session is cheaper than carrying accumulated history.
|
||||
- **Don't re-inject skill context.** If a skill's context is already in the session, reference it rather than re-injecting the full payload (a brief summary suffices after compaction).
|
||||
- **PR check-ins get their own session.** Merge/CI-status polling needs only PR metadata — run it in a short-lived session, not the dev session.
|
||||
- **Compact by ~turn 12.** Later turns pay for the full accumulated history; don't let exploratory sessions run 15+ turns uncompacted.
|
||||
|
||||
<!-- SPECKIT START -->
|
||||
For additional context about technologies to be used, project structure,
|
||||
shell commands, and other important information, read the current plan at
|
||||
specs/20260711-153545-message-markdown-styling/plan.md
|
||||
<!-- SPECKIT END -->
|
||||
@@ -14,5 +14,12 @@ You are an expert open-source maintainer. Your goal is to write clear, professio
|
||||
- 🧹 **Chores** (Dependencies, formatting, docs)
|
||||
4. **Architecture Callouts:** If the diff includes moving files from `androidMain` to `commonMain`, or migrating from Android Views to Compose, highlight this as a "KMP Migration Milestone".
|
||||
5. **Testing Callouts:** If the diff includes changes to `commonTest` or mentions tests, add a section called "Testing Performed" and list the tests that were added/modified.
|
||||
6. **No "Magic" Text:** Do not invent URLs or insert fake image placeholders. Leave the HTML comment block for images intact so the user can manually add their screenshots.
|
||||
6. **Screenshots for UI changes:** If the change affects the UI (Compose composables, layouts, theming, navigation, or anything under `feature/**` / `core/ui/**`), add a **Screenshots** section when real images are available — for example generated `:screenshot-tests` reference PNGs committed in the PR, or images captured from a device/emulator. Prefer a **Before / After** table for visual changes and fixes:
|
||||
|
||||
| Before | After |
|
||||
|--------|-------|
|
||||
| <img src="<url>" width="300"/> | <img src="<url>" width="300"/> |
|
||||
|
||||
Reference committed images with a stable **commit-SHA** raw URL (`https://raw.githubusercontent.com/<owner>/<repo>/<sha>/<path>`, URL-encoding spaces as `%20`) so the links survive branch deletion, or use a GitHub-uploaded attachment. Only embed images that actually exist.
|
||||
7. **No "Magic" Text:** Never invent URLs or insert fake/placeholder images. If no real screenshot is available for a UI change, leave the template's HTML image comment block intact so the author can add one — do not fabricate.
|
||||
</instructions>
|
||||
@@ -0,0 +1,281 @@
|
||||
// Extension: speckit
|
||||
// Spec Kit SDD workflow tools for Meshtastic Android
|
||||
|
||||
import { joinSession } from "@github/copilot-sdk/extension";
|
||||
import { readdir, readFile, stat } from "node:fs/promises";
|
||||
import { join, basename } from "node:path";
|
||||
|
||||
const SPECS_DIR = join(process.cwd(), "specs");
|
||||
const SPECIFY_DIR = join(process.cwd(), ".specify");
|
||||
|
||||
async function dirExists(p) {
|
||||
try {
|
||||
return (await stat(p)).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function fileExists(p) {
|
||||
try {
|
||||
return (await stat(p)).isFile();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function discoverSpecs() {
|
||||
if (!(await dirExists(SPECS_DIR))) return [];
|
||||
const entries = await readdir(SPECS_DIR, { withFileTypes: true });
|
||||
const specs = [];
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
const specDir = join(SPECS_DIR, entry.name);
|
||||
const specFile = join(specDir, "spec.md");
|
||||
if (!(await fileExists(specFile))) continue;
|
||||
|
||||
const files = await readdir(specDir, { withFileTypes: true });
|
||||
const artifacts = [];
|
||||
for (const f of files) {
|
||||
if (f.isFile()) artifacts.push(f.name);
|
||||
if (f.isDirectory()) {
|
||||
const subFiles = await readdir(join(specDir, f.name));
|
||||
for (const sf of subFiles) artifacts.push(`${f.name}/${sf}`);
|
||||
}
|
||||
}
|
||||
|
||||
const hasSpec = artifacts.includes("spec.md");
|
||||
const hasPlan = artifacts.includes("plan.md");
|
||||
const hasTasks = artifacts.includes("tasks.md");
|
||||
|
||||
let title = entry.name;
|
||||
try {
|
||||
const content = await readFile(specFile, "utf-8");
|
||||
const match = content.match(/^#\s+(.+)/m);
|
||||
if (match) title = match[1];
|
||||
} catch { /* use dir name */ }
|
||||
|
||||
let taskStats = null;
|
||||
if (hasTasks) {
|
||||
try {
|
||||
const tasksContent = await readFile(join(specDir, "tasks.md"), "utf-8");
|
||||
const taskLines = tasksContent.match(/^[-*]\s+\[[ x]\]/gm) || [];
|
||||
const done = tasksContent.match(/^[-*]\s+\[x\]/gmi) || [];
|
||||
taskStats = { total: taskLines.length, done: done.length };
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
|
||||
specs.push({
|
||||
id: entry.name,
|
||||
title,
|
||||
artifacts,
|
||||
hasSpec,
|
||||
hasPlan,
|
||||
hasTasks,
|
||||
taskStats,
|
||||
path: specDir,
|
||||
});
|
||||
}
|
||||
return specs.sort((a, b) => a.id.localeCompare(b.id));
|
||||
}
|
||||
|
||||
const session = await joinSession({
|
||||
tools: [
|
||||
{
|
||||
name: "speckit_list",
|
||||
description:
|
||||
"List all feature specs in the specs/ directory with their artifacts and task progress. " +
|
||||
"Use this to discover which specs exist and their current state.",
|
||||
parameters: { type: "object", properties: {} },
|
||||
skipPermission: true,
|
||||
handler: async () => {
|
||||
const specs = await discoverSpecs();
|
||||
if (specs.length === 0) {
|
||||
return "No specs found in specs/ directory. Use /speckit.specify to create one.";
|
||||
}
|
||||
|
||||
const lines = ["# Feature Specs\n"];
|
||||
for (const s of specs) {
|
||||
const status = [];
|
||||
if (s.hasSpec) status.push("spec ✓");
|
||||
if (s.hasPlan) status.push("plan ✓");
|
||||
if (s.hasTasks) status.push("tasks ✓");
|
||||
|
||||
let progress = "";
|
||||
if (s.taskStats) {
|
||||
const pct = s.taskStats.total > 0
|
||||
? Math.round((s.taskStats.done / s.taskStats.total) * 100)
|
||||
: 0;
|
||||
progress = ` | ${s.taskStats.done}/${s.taskStats.total} tasks (${pct}%)`;
|
||||
}
|
||||
|
||||
lines.push(`## ${s.id}`);
|
||||
lines.push(`**${s.title}**`);
|
||||
lines.push(`Artifacts: ${status.join(", ")}${progress}`);
|
||||
lines.push(`Files: ${s.artifacts.join(", ")}`);
|
||||
lines.push(`Path: ${s.path}\n`);
|
||||
}
|
||||
return lines.join("\n");
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "speckit_load",
|
||||
description:
|
||||
"Load the primary artifacts (spec.md, plan.md, tasks.md) for a specific feature spec. " +
|
||||
"Provide the spec ID (directory name, e.g. '20260511-211823-compose-screenshot-testing') or a partial match. " +
|
||||
"Optionally load only specific artifacts.",
|
||||
parameters: {
|
||||
type: "object",
|
||||
properties: {
|
||||
spec_id: {
|
||||
type: "string",
|
||||
description:
|
||||
"The spec directory name or partial match (e.g. '001', 'mesh-discovery', 'node-list')",
|
||||
},
|
||||
artifacts: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description:
|
||||
"Which artifacts to load. Defaults to ['spec.md', 'plan.md', 'tasks.md']. " +
|
||||
"Can include any file path like 'data-model.md', 'contracts/deep-links.md', etc.",
|
||||
},
|
||||
},
|
||||
required: ["spec_id"],
|
||||
},
|
||||
skipPermission: true,
|
||||
handler: async (args) => {
|
||||
const specs = await discoverSpecs();
|
||||
const query = args.spec_id.toLowerCase();
|
||||
const match = specs.find(
|
||||
(s) =>
|
||||
s.id.toLowerCase() === query ||
|
||||
s.id.toLowerCase().includes(query),
|
||||
);
|
||||
|
||||
if (!match) {
|
||||
const available = specs.map((s) => s.id).join(", ");
|
||||
return `No spec matching '${args.spec_id}'. Available: ${available || "none"}`;
|
||||
}
|
||||
|
||||
const toLoad = args.artifacts || ["spec.md", "plan.md", "tasks.md"];
|
||||
const results = [];
|
||||
|
||||
for (const artifact of toLoad) {
|
||||
const filePath = join(match.path, artifact);
|
||||
if (await fileExists(filePath)) {
|
||||
const content = await readFile(filePath, "utf-8");
|
||||
results.push(`--- ${artifact} (${match.id}) ---\n${content}`);
|
||||
} else {
|
||||
results.push(`--- ${artifact} --- NOT FOUND`);
|
||||
}
|
||||
}
|
||||
|
||||
return results.join("\n\n");
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "speckit_constitution",
|
||||
description:
|
||||
"Display the project constitution that all specs must conform to. " +
|
||||
"The constitution defines non-negotiable principles for the Meshtastic Android project.",
|
||||
parameters: { type: "object", properties: {} },
|
||||
skipPermission: true,
|
||||
handler: async () => {
|
||||
const constitutionPath = join(SPECIFY_DIR, "memory", "constitution.md");
|
||||
if (!(await fileExists(constitutionPath))) {
|
||||
return "No constitution found at .specify/memory/constitution.md. Use /speckit.constitution to create one.";
|
||||
}
|
||||
return await readFile(constitutionPath, "utf-8");
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "speckit_status",
|
||||
description:
|
||||
"Show overall Spec Kit workflow status: specs count, constitution version, " +
|
||||
"template availability, and readiness for each workflow stage.",
|
||||
parameters: { type: "object", properties: {} },
|
||||
skipPermission: true,
|
||||
handler: async () => {
|
||||
const specs = await discoverSpecs();
|
||||
const hasConstitution = await fileExists(join(SPECIFY_DIR, "memory", "constitution.md"));
|
||||
const hasTemplates = await dirExists(join(SPECIFY_DIR, "templates"));
|
||||
const hasExtensions = await fileExists(join(SPECIFY_DIR, "extensions.yml"));
|
||||
|
||||
let constitutionVersion = "none";
|
||||
if (hasConstitution) {
|
||||
try {
|
||||
const content = await readFile(
|
||||
join(SPECIFY_DIR, "memory", "constitution.md"),
|
||||
"utf-8",
|
||||
);
|
||||
const match = content.match(/\*\*Version\*\*:\s*([\d.]+)/i) ||
|
||||
content.match(/version[:\s]+v?([\d.]+)/i);
|
||||
if (match) constitutionVersion = `v${match[1]}`;
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
|
||||
let templateList = [];
|
||||
if (hasTemplates) {
|
||||
try {
|
||||
const entries = await readdir(join(SPECIFY_DIR, "templates"));
|
||||
templateList = entries.filter((e) => e.endsWith(".md"));
|
||||
} catch { /* skip */ }
|
||||
}
|
||||
|
||||
const lines = [
|
||||
"# Spec Kit Status\n",
|
||||
`**Constitution:** ${hasConstitution ? `✓ (${constitutionVersion})` : "✗ not found"}`,
|
||||
`**Templates:** ${templateList.length > 0 ? `✓ (${templateList.join(", ")})` : "✗ none"}`,
|
||||
`**Extensions:** ${hasExtensions ? "✓ configured" : "✗ not found"}`,
|
||||
`**Specs:** ${specs.length} feature(s)\n`,
|
||||
];
|
||||
|
||||
if (specs.length > 0) {
|
||||
lines.push("| Spec | Spec.md | Plan.md | Tasks.md | Progress |");
|
||||
lines.push("|------|---------|---------|----------|----------|");
|
||||
for (const s of specs) {
|
||||
const progress = s.taskStats
|
||||
? `${s.taskStats.done}/${s.taskStats.total}`
|
||||
: "—";
|
||||
lines.push(
|
||||
`| ${s.id} | ${s.hasSpec ? "✓" : "✗"} | ${s.hasPlan ? "✓" : "✗"} | ${s.hasTasks ? "✓" : "✗"} | ${progress} |`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(
|
||||
"\n## Workflow Commands",
|
||||
"specify → clarify → plan → tasks → analyze → implement",
|
||||
"\nUse `/speckit.specify` to start a new feature, or `speckit_load` to review an existing one.",
|
||||
);
|
||||
|
||||
return lines.join("\n");
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
onSessionStart: async () => {
|
||||
const specs = await discoverSpecs();
|
||||
if (specs.length === 0) return;
|
||||
|
||||
const summary = specs
|
||||
.map((s) => {
|
||||
const progress = s.taskStats
|
||||
? ` (${s.taskStats.done}/${s.taskStats.total} tasks)`
|
||||
: "";
|
||||
return `- ${s.id}: ${s.title}${progress}`;
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
return {
|
||||
additionalContext: [
|
||||
`[Spec Kit] ${specs.length} feature spec(s) found in specs/:`,
|
||||
summary,
|
||||
"",
|
||||
"Use speckit_list, speckit_load, speckit_status, or speckit_constitution tools for spec details.",
|
||||
"Use /speckit.specify, /speckit.plan, /speckit.tasks, /speckit.analyze, /speckit.implement for workflow commands.",
|
||||
].join("\n"),
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
# Homebrew Cask template for Meshtastic Desktop.
|
||||
#
|
||||
# Source of truth for the cask published to the meshtastic/homebrew-tap tap
|
||||
# (alongside the meshtasticd formula). The `update-homebrew-cask` job in
|
||||
# promote.yml substitutes {{VERSION}} and {{SHA256}} and opens a PR against the
|
||||
# tap on every production release, so its brew test-bot CI validates the bump.
|
||||
# The download URL depends on the jpackage DMG being named
|
||||
# "Meshtastic Desktop-<version>.dmg" (GitHub rewrites the space to a dot).
|
||||
#
|
||||
# NOTE: arm64-only — CI has no Intel macOS runner, so no x86_64 DMG exists.
|
||||
# When one is added, switch to `arch arm: ..., intel: ...` with per-arch sha256.
|
||||
cask "meshtastic-desktop" do
|
||||
version "{{VERSION}}"
|
||||
sha256 "{{SHA256}}"
|
||||
|
||||
url "https://github.com/meshtastic/Meshtastic-Android/releases/download/v#{version}/Meshtastic.Desktop-#{version}.dmg",
|
||||
verified: "github.com/meshtastic/Meshtastic-Android/"
|
||||
name "Meshtastic Desktop"
|
||||
desc "Companion app for Meshtastic mesh-networking radios"
|
||||
homepage "https://meshtastic.org/"
|
||||
|
||||
livecheck do
|
||||
url :url
|
||||
strategy :github_latest
|
||||
end
|
||||
|
||||
auto_updates false
|
||||
depends_on arch: :arm64
|
||||
depends_on macos: :monterey
|
||||
|
||||
app "Meshtastic Desktop.app"
|
||||
|
||||
zap trash: "~/.meshtastic"
|
||||
end
|
||||
@@ -5,10 +5,15 @@ excludeAgent: "code-review"
|
||||
|
||||
# CI Workflow Rules
|
||||
|
||||
- Prefer explicit Gradle task paths (`app:lintFdroidDebug`) over shorthand (`lintDebug`).
|
||||
- Prefer explicit Gradle task paths (`androidApp:lintFdroidDebug`) over shorthand (`lintDebug`).
|
||||
- CI uses `.github/ci-gradle.properties` — don't assume local `gradle.properties` values.
|
||||
- CI passes `-Pci=true` to enable full processor usage via `maxParallelForks`.
|
||||
- Use `fetch-depth: 0` only where needed (spotless ratcheting, version code). Use `fetch-depth: 1` otherwise.
|
||||
- Desktop build matrix: `macos-latest`, `windows-latest`, `ubuntu-24.04`, `ubuntu-24.04-arm`.
|
||||
- Lightweight jobs (labelers, triage, stale): use `ubuntu-24.04-arm` runners.
|
||||
- Lightweight jobs (status gates, labelers, triage, stale, run-cancellers, changelog/release
|
||||
cleanup): use `ubuntu-slim`. It is container-backed and starts in seconds, but it is
|
||||
single-CPU, unprivileged, x64-only, and its 15-minute job cap is a hard platform limit — so it
|
||||
fits API/script work (`gh`, `jq`, `git`, stdlib `python3`, `github-script`) and nothing that
|
||||
needs `sudo`, `apt-get`, Docker, a mounted filesystem, or a long full-history clone.
|
||||
- Lightweight jobs that break any of those constraints: use `ubuntu-24.04-arm` runners.
|
||||
- Gradle-heavy jobs: use `ubuntu-24.04` runners.
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"lspServers": {
|
||||
"kotlin": {
|
||||
"command": "kotlin-language-server",
|
||||
"args": [],
|
||||
"fileExtensions": {
|
||||
".kt": "kotlin",
|
||||
".kts": "kotlin"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.agent-governance.refresh
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.analyze
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.brownfield.bootstrap
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.brownfield.migrate
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.brownfield.scan
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.brownfield.validate
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.checklist
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.clarify
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.constitution
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.git.commit
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.git.feature
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.git.initialize
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.git.remote
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.git.validate
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.implement
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.optimize.learn
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.optimize.run
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.optimize.tokens
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.plan
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.review.code
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.review.comments
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.review.errors
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.review.run
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.review.simplify
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.review.tests
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.review.types
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.specify
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.tasks
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.taskstoissues
|
||||
---
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
agent: speckit.verify.run
|
||||
---
|
||||
+7
-5
@@ -1,4 +1,7 @@
|
||||
# .github/release.yml - GitHub Release Notes Configuration
|
||||
#
|
||||
# Labels here must match actual repo labels. Run `gh label list` to verify.
|
||||
# Auto-labeler: .github/workflows/pull-request-target.yml
|
||||
|
||||
changelog:
|
||||
exclude:
|
||||
@@ -12,10 +15,8 @@ changelog:
|
||||
- ci
|
||||
- build
|
||||
- testing
|
||||
- test
|
||||
- refactor
|
||||
- documentation
|
||||
- translation
|
||||
- l10n
|
||||
authors:
|
||||
- renovate[bot]
|
||||
- dependabot[bot]
|
||||
@@ -25,12 +26,13 @@ changelog:
|
||||
- title: 🏗️ Features
|
||||
labels:
|
||||
- enhancement
|
||||
- feature
|
||||
- title: 🖥️ Desktop
|
||||
labels:
|
||||
- desktop
|
||||
- title: 🛠️ Fixes
|
||||
labels:
|
||||
- bug
|
||||
- bugfix
|
||||
- fix
|
||||
- title: 📝 Other Changes
|
||||
labels:
|
||||
- '*'
|
||||
@@ -9,6 +9,7 @@
|
||||
"workarounds:all"
|
||||
],
|
||||
"commitMessageTopic": "{{depName}}",
|
||||
"osvVulnerabilityAlerts": true,
|
||||
"labels": [
|
||||
"dependencies"
|
||||
],
|
||||
@@ -56,6 +57,24 @@
|
||||
"changelogUrl": "https://github.com/meshtastic/protobufs/compare/{{currentDigest}}...{{newDigest}}",
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"description": "Protobufs: only accept the dot-form snapshot scheme (X.Y.Z.N-g<sha>-SNAPSHOT) or plain releases. Legacy hyphen-SHA snapshots (e.g. 2.7.26-678281c-SNAPSHOT) tokenize their digit-leading SHA as a huge int that outranks the dot-form commit-count in Renovate's maven comparator, so without this constraint Renovate keeps proposing a downgrade to an old snapshot (see PR #6229). The snapshot repo has no delete API; legacy versions auto-prune after 90 days.",
|
||||
"matchPackageNames": [
|
||||
"org.meshtastic:protobufs"
|
||||
],
|
||||
"allowedVersions": "/^\\d+\\.\\d+\\.\\d+(\\.\\d+-g[0-9a-f]+-SNAPSHOT)?$/"
|
||||
},
|
||||
{
|
||||
"description": "Group CMP and the androidx.compose artifacts that track it so Renovate bumps them together (see PR #5180). Every artifact pinned to the `androidx-compose-bom-aligned` catalog ref must be listed here: bumping any one of them rewrites that shared ref, which AndroidCompose.kt's resolutionStrategy force-aligns across the whole androidx.compose group, so an ungrouped artifact silently drags the entire Android Compose stack out of step with CMP (see PR #6651, where a solo ui-text-google-fonts bump broke screenshot preview discovery).",
|
||||
"groupName": "compose-multiplatform",
|
||||
"matchPackageNames": [
|
||||
"/^org\\.jetbrains\\.compose/",
|
||||
"androidx.compose.runtime:runtime-tracing",
|
||||
"androidx.compose.ui:ui-test-manifest",
|
||||
"androidx.compose.material:material",
|
||||
"androidx.compose.ui:ui-text-google-fonts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "Restrict sensitive infrastructure to manual minor updates",
|
||||
"matchUpdateTypes": [
|
||||
@@ -76,12 +95,52 @@
|
||||
],
|
||||
"automerge": false
|
||||
},
|
||||
{
|
||||
"description": "Block the Gradle 9.7 line. 9.7.0 crashed CMP's proguardReleaseJars (ExecSpec stdout null); 9.7.1 fixed that but broke main CI twice over (bumped in #6777, reverted next day): the configuration-cache fingerprint crashes reloading its own same-key entries (IsInIdeaSyncValueSource CNFE), and CMP's Windows packaging trips an Isolated Projects violation (':desktopApp' cannot access Project.layout on ':'). Lift only after a 9.7.x/9.8 patch proves out on both counts. Matches by depName so the flatpak-manifest custom manager (customManagers below) is pinned identically and can never propose the blocked version on its own.",
|
||||
"matchDepNames": [
|
||||
"gradle"
|
||||
],
|
||||
"matchManagers": [
|
||||
"gradle-wrapper",
|
||||
"custom.regex"
|
||||
],
|
||||
"allowedVersions": "!/^9\\.7\\./"
|
||||
},
|
||||
{
|
||||
"description": "Disable automerge for major updates (safety net)",
|
||||
"matchUpdateTypes": [
|
||||
"major"
|
||||
],
|
||||
"automerge": false
|
||||
},
|
||||
{
|
||||
"description": "Coordinate the Kotlin-compiler-locked toolchain in one human-reviewed PR. Kotlin, KSP, Mokkery, and the Koin compiler plugin are version-locked to the Kotlin compiler and break when bumped out of lockstep (e.g. Mokkery 3.4.0 and kable 0.43.1 both required Kotlin 2.4.0 — PRs #5750/#5740). Supersedes the built-in group:kotlinMonorepo/group:kspMonorepo for these packages so they land together; automerge stays off so compiler bumps always get a human review plus a green CI build. The `[.:]` after kotlin intentionally excludes org.jetbrains.kotlinx (coroutines/serialization runtime), which is not compiler-locked.",
|
||||
"groupName": "kotlin-toolchain",
|
||||
"matchManagers": [
|
||||
"gradle"
|
||||
],
|
||||
"matchPackageNames": [
|
||||
"/^org\\.jetbrains\\.kotlin[.:]/",
|
||||
"/^com\\.google\\.devtools\\.ksp/",
|
||||
"/^dev\\.mokkery/",
|
||||
"/^io\\.insert-koin\\.compiler\\.plugin/"
|
||||
],
|
||||
"automerge": false
|
||||
}
|
||||
],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Mirror gradle-wrapper.properties bumps into the vendored Gradle distribution URL in the verify-flatpak offline manifest (see PRs #6777/#6782, where a wrapper bump without this file broke both build-flatpak arches). Shares depName 'gradle' with the gradle-wrapper manager so both files update in the same Renovate branch/PR. The sha256 line below the URL cannot be auto-updated (the gradle-version datasource has no digest support and the hosted app disallows postUpgradeTasks); the fail-fast guard in verify-flatpak.yml's generate-sources job holds that PR red until the sha256 is copied from distributionSha256Sum.",
|
||||
"managerFilePatterns": [
|
||||
"/^scripts/verify-flatpak/desktop-offline\\.yaml$/"
|
||||
],
|
||||
"matchStrings": [
|
||||
"services\\.gradle\\.org/distributions/gradle-(?<currentValue>\\d+(?:\\.\\d+)*)-bin\\.zip"
|
||||
],
|
||||
"depNameTemplate": "gradle",
|
||||
"datasourceTemplate": "gradle-version",
|
||||
"versioningTemplate": "gradle"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -20,29 +20,46 @@ on:
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
build_desktop:
|
||||
description: 'Whether to build the desktop distribution'
|
||||
required: true
|
||||
no_review_in_flight:
|
||||
description: 'Promotions only: I checked Publishing overview > Submission activity and no submission is In review. Every promotion creates a new Play submission, which CANCELS and RESTARTS any review in flight.'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
|
||||
# Never allow two release pipelines to run at once — they race on tags,
|
||||
# release objects, and Play track state. Later dispatches queue.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
determine-tags:
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 10
|
||||
outputs:
|
||||
tag_to_process: ${{ steps.calculate_tags.outputs.tag_to_process }}
|
||||
release_name: ${{ steps.calculate_tags.outputs.release_name }}
|
||||
final_tag: ${{ steps.calculate_tags.outputs.final_tag }}
|
||||
from_channel: ${{ steps.calculate_tags.outputs.from_channel }}
|
||||
steps:
|
||||
# Internal releases are exempt: Play internal testing skips full review,
|
||||
# so only promotions (closed/open/production) can clobber an in-flight
|
||||
# review. Dry runs never reach Play.
|
||||
- name: Require review-in-flight confirmation for promotions
|
||||
if: ${{ !inputs.dry_run && inputs.channel != 'internal' && !inputs.no_review_in_flight }}
|
||||
run: |
|
||||
echo "::error::Promotion blocked: confirm no Play review is in flight. Check Play Console > Publishing overview > Submission activity — if a submission shows 'In review', WAIT (a new promotion cancels it and restarts the clock). If clear, re-dispatch with 'no_review_in_flight' checked."
|
||||
exit 1
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.CROWDIN_GITHUB_TOKEN }}
|
||||
@@ -65,9 +82,11 @@ jobs:
|
||||
|
||||
NEW_TAG="v${BASE_VERSION}-internal.${INCREMENT}"
|
||||
echo "Calculated new tag: $NEW_TAG"
|
||||
echo "tag_to_process=$NEW_TAG" >> $GITHUB_OUTPUT
|
||||
echo "release_name=$NEW_TAG" >> $GITHUB_OUTPUT
|
||||
echo "final_tag=$NEW_TAG" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo "tag_to_process=$NEW_TAG"
|
||||
echo "release_name=$NEW_TAG"
|
||||
echo "final_tag=$NEW_TAG"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
# This is a promotion, find the latest tag from the previous channel to promote
|
||||
FROM_CHANNEL="internal"
|
||||
@@ -104,10 +123,12 @@ jobs:
|
||||
|
||||
echo "New release name will be: $NEW_TAG"
|
||||
echo "Final tag will be: $NEW_TAG"
|
||||
echo "from_channel=${FROM_CHANNEL}" >> $GITHUB_OUTPUT
|
||||
echo "tag_to_process=${LATEST_TAG_TO_PROMOTE}" >> $GITHUB_OUTPUT
|
||||
echo "release_name=${NEW_TAG}" >> $GITHUB_OUTPUT
|
||||
echo "final_tag=${NEW_TAG}" >> $GITHUB_OUTPUT
|
||||
{
|
||||
echo "from_channel=${FROM_CHANNEL}"
|
||||
echo "tag_to_process=${LATEST_TAG_TO_PROMOTE}"
|
||||
echo "release_name=${NEW_TAG}"
|
||||
echo "final_tag=${NEW_TAG}"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
@@ -129,12 +150,25 @@ jobs:
|
||||
tag_name: ${{ needs.determine-tags.outputs.final_tag }}
|
||||
channel: ${{ inputs.channel }}
|
||||
base_version: ${{ inputs.base_version }}
|
||||
build_desktop: ${{ inputs.build_desktop }}
|
||||
build_desktop: true
|
||||
build_flatpak_src: true
|
||||
secrets: inherit
|
||||
|
||||
call-promote-workflow:
|
||||
if: ${{ !inputs.dry_run && inputs.channel != 'internal' }}
|
||||
needs: determine-tags
|
||||
# promote.yml's token is capped by this job's grants, and a called
|
||||
# workflow requesting more than its caller grants fails at startup —
|
||||
# so this must cover promote.yml's declared workflow-level set, plus
|
||||
# actions: write for its publish-workflow dispatch. Scoped to this job
|
||||
# so call-release-workflow doesn't carry it.
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
actions: write
|
||||
uses: ./.github/workflows/promote.yml
|
||||
with:
|
||||
tag_name: ${{ needs.determine-tags.outputs.tag_to_process }}
|
||||
@@ -149,9 +183,10 @@ jobs:
|
||||
needs: [determine-tags, call-release-workflow]
|
||||
if: ${{ (failure() || cancelled()) && !inputs.dry_run && inputs.channel == 'internal' }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Delete Failed or Cancelled Tag
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
name: Submit Dependency Graph
|
||||
|
||||
# PR runs can only generate-and-upload (fork tokens lack contents: write). This submits what
|
||||
# they saved, from the base repo's trusted context. Never checks out PR code — the snapshot
|
||||
# artifact is the only input.
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ['Pull Request CI']
|
||||
types: [completed]
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
|
||||
# head_branch alone would collide across forks that share a branch name (e.g. two "patch-1"s).
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
submit-dependency-graph:
|
||||
# failed runs may have partial graphs; skip
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
# skipped android-check (docs-only/bot PRs) uploads nothing — don't fail red on that
|
||||
- name: Check the run saved a dependency graph
|
||||
id: probe
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
count=$(gh api --paginate "repos/${{ github.repository }}/actions/runs/${{ github.event.workflow_run.id }}/artifacts?per_page=100" \
|
||||
--jq '[.artifacts[] | select(.name | startswith("dependency-graph"))] | length' | paste -sd+ | bc)
|
||||
echo "count=$count" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download and submit dependency graph
|
||||
if: steps.probe.outputs.count != '0'
|
||||
uses: gradle/actions/dependency-submission@v6
|
||||
with:
|
||||
dependency-graph: download-and-submit
|
||||
@@ -1,29 +0,0 @@
|
||||
name: Dependency Submission
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 'main' ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
dependency-submission:
|
||||
runs-on: ubuntu-24.04
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 21
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Generate and submit dependency graph
|
||||
uses: gradle/actions/dependency-submission@v6
|
||||
with:
|
||||
build-scan-publish: true
|
||||
build-scan-terms-of-use-url: "https://gradle.com/terms-of-service"
|
||||
build-scan-terms-of-use-agree: "yes"
|
||||
@@ -0,0 +1,135 @@
|
||||
name: Deploy Documentation
|
||||
|
||||
# Publishes the main-branch docs snapshot to /main/ (and refreshed Dokka to
|
||||
# /api/) on the persistent gh-pages branch. The site root (latest release)
|
||||
# and /vX.Y.Z/ folders are owned by docs-release.yml and are left untouched.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
# Dokka sources (KDoc in source files)
|
||||
- 'androidApp/src/**'
|
||||
- 'core/**/src/**'
|
||||
- 'feature/**/src/**'
|
||||
- 'desktopApp/src/**'
|
||||
# Docs site sources
|
||||
- 'docs/**'
|
||||
- 'feature/docs/**'
|
||||
# Build infrastructure. Module scripts are included because they can add or
|
||||
# drop exported `api` dependencies and reshape source sets, changing the
|
||||
# generated reference without any edit under src/.
|
||||
- 'build-logic/**'
|
||||
- '**/build.gradle.kts'
|
||||
- 'settings.gradle.kts'
|
||||
- '.github/workflows/docs-deploy.yml'
|
||||
- 'scripts/docs/**'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
# Shares the group with docs-release.yml so gh-pages pushes serialize.
|
||||
# cancel-in-progress must stay false: a snapshot deploy must never cancel an
|
||||
# in-flight release publish (GitHub still coalesces queued runs to one).
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
with:
|
||||
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
|
||||
develocity_access_key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '4.0.6'
|
||||
bundler-cache: true
|
||||
working-directory: docs
|
||||
|
||||
# Dokka is the slowest part of this workflow (~14 min) but KDoc changes far
|
||||
# less often than the prose docs. Rebuild /api/ only when something that can
|
||||
# actually change the generated reference was touched; otherwise the existing
|
||||
# /api/ on gh-pages is left in place (the publisher overlays per channel).
|
||||
# Manual runs always rebuild everything, so the diff is only needed on push.
|
||||
- name: Detect Dokka-relevant changes
|
||||
if: github.event_name != 'workflow_dispatch'
|
||||
uses: dorny/paths-filter@v4
|
||||
id: filter
|
||||
with:
|
||||
token: ''
|
||||
filters: |
|
||||
# Positive patterns only. paths-filter evaluates each pattern as an
|
||||
# independent predicate and ORs them together, so a '!excluded/**'
|
||||
# entry would match every path outside that dir and make the filter
|
||||
# always true. The modules in DOKKA_EXCLUDED_MODULES that live under
|
||||
# these prefixes (:core:konsist) therefore still trigger a rebuild;
|
||||
# they change rarely enough that the odd wasted run is fine.
|
||||
dokka:
|
||||
- 'androidApp/src/**'
|
||||
- 'core/**/src/**'
|
||||
- 'feature/**/src/**'
|
||||
- 'desktopApp/src/**'
|
||||
# Dokka config, module list and the plugin classpath. '**/build.gradle.kts'
|
||||
# covers the root script as well as every module's, since a module script
|
||||
# can change exported `api` deps or source sets with no src/ edit.
|
||||
- 'build-logic/**'
|
||||
- '**/build.gradle.kts'
|
||||
- 'settings.gradle.kts'
|
||||
- 'gradle/libs.versions.toml'
|
||||
|
||||
- name: Generate Docs Site (main channel)
|
||||
run: ./gradlew generateDocsBundle validateDocsBundle publishDocsSite -Pdocs.channel=main -Pci=true
|
||||
|
||||
# Dokka (Gradle) and Jekyll (Ruby) are independent — Dokka's output is only
|
||||
# copied in afterwards — so run them concurrently to overlap the two slowest
|
||||
# steps (~14 min Dokka vs ~5 min Jekyll) instead of summing them.
|
||||
- name: Build Dokka + Jekyll concurrently
|
||||
env:
|
||||
BUILD_DOKKA: ${{ steps.filter.outputs.dokka == 'true' || github.event_name == 'workflow_dispatch' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
BUNDLE_GEMFILE=docs/Gemfile bundle exec jekyll build \
|
||||
--source build/_site/main \
|
||||
--destination build/jekyll_site \
|
||||
--baseurl /${{ github.event.repository.name }}/main &
|
||||
jekyll_pid=$!
|
||||
if [ "$BUILD_DOKKA" = "true" ]; then
|
||||
./gradlew dokkaGeneratePublicationHtml -Dorg.gradle.isolated-projects=false --no-configuration-cache
|
||||
else
|
||||
echo "No Dokka-relevant changes — skipping API reference rebuild."
|
||||
fi
|
||||
wait "$jekyll_pid"
|
||||
|
||||
- name: Stage channels
|
||||
id: stage
|
||||
env:
|
||||
BUILD_DOKKA: ${{ steps.filter.outputs.dokka == 'true' || github.event_name == 'workflow_dispatch' }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p build/pages_staging
|
||||
cp -r build/jekyll_site build/pages_staging/main
|
||||
channels="main"
|
||||
if [ "$BUILD_DOKKA" = "true" ]; then
|
||||
cp -r build/dokka/html build/pages_staging/api
|
||||
channels="$channels api"
|
||||
fi
|
||||
echo "channels=$channels" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Publish to gh-pages
|
||||
run: scripts/docs/publish-to-gh-pages.sh build/pages_staging ${{ steps.stage.outputs.channels }}
|
||||
@@ -0,0 +1,150 @@
|
||||
name: Docs Release
|
||||
|
||||
# Publishes release docs to the persistent gh-pages branch.
|
||||
#
|
||||
# Production tags (vX.Y.Z) own the site root and get a permanent /vX.Y.Z/ copy,
|
||||
# plus a refreshed Dokka reference at /api/.
|
||||
#
|
||||
# Open- and closed-testing tags (vX.Y.Z-open.N / vX.Y.Z-closed.N) publish a
|
||||
# per-tag snapshot at /vX.Y.Z-open.N/ only. They deliberately do NOT touch the
|
||||
# root or /api/: the root belongs to production releases, and /api/ is an
|
||||
# unversioned channel already kept current by docs-deploy.yml on every push to
|
||||
# main — rebuilding Dokka (~14 min) for each of the many prerelease tags in a
|
||||
# cycle would cost far more than it refreshes.
|
||||
#
|
||||
# These per-tag prerelease directories accumulate during a version cycle and are
|
||||
# reaped by post-release-cleanup.yml once the production vX.Y.Z tag ships.
|
||||
#
|
||||
# The /main/ snapshot is owned by docs-deploy.yml and is left untouched here.
|
||||
#
|
||||
# workflow_dispatch exists for backfill: run it against a tag ref to (re)publish
|
||||
# that version without cutting a new tag.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
# Production plus the open/closed testing tracks. Internal builds are
|
||||
# excluded: they are tagged many times per cycle and are not a channel we
|
||||
# publish documentation for.
|
||||
- 'v*.*.*'
|
||||
- '!v*-internal.*'
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: pages
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
submodules: true
|
||||
fetch-depth: 0
|
||||
|
||||
# Resolves the tag into: the docs version label (which becomes the
|
||||
# published directory name) and whether this is a production release.
|
||||
- name: Resolve Release Channel
|
||||
id: version
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$GITHUB_REF" in
|
||||
refs/tags/*) TAG="${GITHUB_REF#refs/tags/}" ;;
|
||||
*)
|
||||
echo "This workflow must run against a release tag ref (got $GITHUB_REF)." >&2
|
||||
echo "For workflow_dispatch, select the tag under 'Use workflow from'." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "$TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
|
||||
echo "docs_version=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_production=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Production release: ${BASH_REMATCH[1]} -> / and /v${BASH_REMATCH[1]}/"
|
||||
elif [[ "$TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+-(open|closed)\.[0-9]+)$ ]]; then
|
||||
echo "docs_version=${BASH_REMATCH[1]}" >> "$GITHUB_OUTPUT"
|
||||
echo "is_production=false" >> "$GITHUB_OUTPUT"
|
||||
echo "${BASH_REMATCH[2]}-testing prerelease -> /v${BASH_REMATCH[1]}/ only"
|
||||
else
|
||||
echo "Tag '$TAG' is not a publishable docs channel." >&2
|
||||
echo "Expected vX.Y.Z, vX.Y.Z-open.N or vX.Y.Z-closed.N." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
with:
|
||||
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
|
||||
develocity_access_key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
|
||||
|
||||
- name: Setup Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '4.0.6'
|
||||
bundler-cache: true
|
||||
working-directory: docs
|
||||
|
||||
# Versioned docs (/vX.Y.Z/ or /vX.Y.Z-open.N/) — built for every channel.
|
||||
- name: Build Versioned Docs
|
||||
run: ./gradlew generateDocsBundle validateDocsBundle publishDocsSite -Pdocs.channel=release -Pdocs.version=${{ steps.version.outputs.docs_version }} -Pci=true
|
||||
|
||||
# Root site (/) — production releases only.
|
||||
- name: Build Root Docs Site
|
||||
if: steps.version.outputs.is_production == 'true'
|
||||
run: ./gradlew generateDocsBundle publishDocsSite -Pdocs.channel=root -Pci=true
|
||||
|
||||
# Dokka API reference (/api/) — production releases only.
|
||||
- name: Build Dokka HTML documentation
|
||||
if: steps.version.outputs.is_production == 'true'
|
||||
run: ./gradlew dokkaGeneratePublicationHtml -Dorg.gradle.isolated-projects=false --no-configuration-cache
|
||||
|
||||
- name: Compile Jekyll Sites
|
||||
env:
|
||||
DOCS_VERSION: ${{ steps.version.outputs.docs_version }}
|
||||
IS_PRODUCTION: ${{ steps.version.outputs.is_production }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Versioned site
|
||||
BUNDLE_GEMFILE=docs/Gemfile bundle exec jekyll build \
|
||||
--source "build/_site/v${DOCS_VERSION}" \
|
||||
--destination build/jekyll_release \
|
||||
--baseurl "/${{ github.event.repository.name }}/v${DOCS_VERSION}"
|
||||
|
||||
# Move the versioned source out of the root source tree so the root
|
||||
# build below doesn't try to nest it.
|
||||
mv "build/_site/v${DOCS_VERSION}" build/v_temp
|
||||
|
||||
if [ "$IS_PRODUCTION" = "true" ]; then
|
||||
BUNDLE_GEMFILE=docs/Gemfile bundle exec jekyll build \
|
||||
--source build/_site \
|
||||
--destination build/jekyll_root \
|
||||
--baseurl "/${{ github.event.repository.name }}"
|
||||
fi
|
||||
|
||||
- name: Stage channels
|
||||
id: stage
|
||||
env:
|
||||
DOCS_VERSION: ${{ steps.version.outputs.docs_version }}
|
||||
IS_PRODUCTION: ${{ steps.version.outputs.is_production }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p build/pages_staging
|
||||
cp -r build/jekyll_release "build/pages_staging/v${DOCS_VERSION}"
|
||||
channels="v${DOCS_VERSION}"
|
||||
if [ "$IS_PRODUCTION" = "true" ]; then
|
||||
cp -r build/jekyll_root build/pages_staging/root
|
||||
cp -r build/dokka/html build/pages_staging/api
|
||||
channels="root $channels api"
|
||||
fi
|
||||
echo "channels=$channels" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Publish to gh-pages
|
||||
run: scripts/docs/publish-to-gh-pages.sh build/pages_staging ${{ steps.stage.outputs.channels }}
|
||||
@@ -1,83 +0,0 @@
|
||||
# This workflow builds and deploys the Dokka documentation to GitHub Pages.
|
||||
|
||||
name: Deploy Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
# Only rebuild docs when source code changes (Dokka generates from KDoc)
|
||||
- 'app/src/**'
|
||||
- 'core/**/src/**'
|
||||
- 'feature/**/src/**'
|
||||
- 'desktop/src/**'
|
||||
- 'build-logic/**'
|
||||
- 'build.gradle.kts'
|
||||
- 'settings.gradle.kts'
|
||||
- '.github/workflows/docs.yml'
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'The branch, tag or SHA to checkout'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
# Allow this workflow to be called from other workflows
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'The branch, tag or SHA to checkout'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
contents: read
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment; cancel queued runs since only the latest
|
||||
# main state matters for documentation.
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-docs:
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: 'recursive'
|
||||
ref: ${{ inputs.ref || '' }}
|
||||
|
||||
- name: Gradle Setup
|
||||
uses: ./.github/actions/gradle-setup
|
||||
with:
|
||||
gradle_encryption_key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
|
||||
|
||||
- name: Build Dokka HTML documentation
|
||||
run: ./gradlew dokkaGeneratePublicationHtml
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v5
|
||||
with:
|
||||
path: build/dokka/html
|
||||
|
||||
deploy:
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
needs: build-docs
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v5
|
||||
@@ -15,12 +15,125 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
# Every commit on main arrives via the merge queue, which already ran lint,
|
||||
# screenshot validation, rb-check, and the (coverage-free) test shards on this
|
||||
# exact merge commit. This workflow:
|
||||
# - re-runs the test shards WITH coverage: Kover instrumentation lives here,
|
||||
# off the queue's critical path, and this is the sole source of Codecov
|
||||
# mainline coverage (PRs and the queue both skip it)
|
||||
# - builds the debug APKs for the snapshot release below with -SNAPSHOT
|
||||
# naming (the queue skips run_android_build; the PR already assembled them)
|
||||
# - builds the desktop distributables. Desktop packaging runs here post-merge
|
||||
# rather than in the merge queue: :desktopApp:test in the queue's shard-app
|
||||
# already covers compilation, and the 4-OS matrix (macos/windows queue
|
||||
# times) would slow every merge.
|
||||
# run_lint: false skips lint, screenshot-check, and rb-check (queue-verified).
|
||||
validate-and-build:
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
uses: ./.github/workflows/reusable-check.yml
|
||||
permissions:
|
||||
contents: write # dependency-graph submission (android-check)
|
||||
pull-requests: write
|
||||
with:
|
||||
run_lint: true
|
||||
run_unit_tests: false
|
||||
run_desktop_builds: false
|
||||
run_lint: false
|
||||
run_unit_tests: true
|
||||
run_coverage: true
|
||||
run_desktop_builds: true
|
||||
upload_artifacts: true
|
||||
secrets: inherit
|
||||
|
||||
# Republishes the debug APKs validate-and-build already produced as a rolling "snapshot"
|
||||
# prerelease that moves to HEAD on every push to main, so testers get a stable download
|
||||
# link instead of digging through Actions artifacts (which require a GitHub login and
|
||||
# expire after 7 days).
|
||||
publish-snapshot:
|
||||
needs: validate-and-build
|
||||
# !cancelled(): a desktop-matrix failure fails validate-and-build as a whole, but the
|
||||
# Android APKs may still have built fine — attempt the snapshot regardless. If the
|
||||
# APK build itself failed, the artifact download below fails and this job goes red.
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android' && !cancelled()
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
# CROWDIN_GITHUB_TOKEN (a PAT), not the default GITHUB_TOKEN, because the repo's tag
|
||||
# rulesets block the default token from creating/deleting tags — same token every other
|
||||
# tag-touching workflow here uses.
|
||||
GH_TOKEN: ${{ secrets.CROWDIN_GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 0 # git rev-list --count needs full history for the versionCode
|
||||
token: ${{ secrets.CROWDIN_GITHUB_TOKEN }}
|
||||
persist-credentials: false # no git push here; gh does the authed work via GH_TOKEN
|
||||
|
||||
- name: Download debug APKs
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: app-debug-apks
|
||||
path: artifacts
|
||||
|
||||
# Desktop installers land in per-OS artifacts (desktop-app-<os>-<arch>). A matrix leg
|
||||
# failing (e.g. one OS's packaging breaks) shouldn't block publishing the rest, so this
|
||||
# doesn't fail the job if some/all are missing — see the !cancelled() gate above.
|
||||
- name: Download desktop installers
|
||||
uses: actions/download-artifact@v8
|
||||
continue-on-error: true
|
||||
with:
|
||||
pattern: desktop-app-*
|
||||
path: desktop-artifacts
|
||||
|
||||
# A moving tag reuses the same release URL forever, and Obtainium fingerprints the
|
||||
# asset URL — so without a changing filename it would never detect a new build.
|
||||
# Embed the (monotonic) versionCode in each APK name; same formula the app build uses.
|
||||
- name: Rename APKs with versionCode
|
||||
run: |
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
OFFSET=$(grep '^VERSION_CODE_OFFSET=' config.properties | cut -d'=' -f2)
|
||||
VERSION_CODE=$((COMMIT_COUNT + OFFSET))
|
||||
echo "VERSION_CODE=$VERSION_CODE" >> "$GITHUB_ENV"
|
||||
mkdir -p upload
|
||||
find artifacts -name '*.apk' | while read -r f; do
|
||||
cp "$f" "upload/$(basename "$f" .apk)-${VERSION_CODE}.apk"
|
||||
done
|
||||
ls -l upload
|
||||
|
||||
# Desktop installer filenames already embed the OS/arch (jpackage) plus a "snapshot"
|
||||
# version tag baked into the AppImage step, but stamp the versionCode on too so every
|
||||
# asset in the release moves in lockstep and testers can tell builds apart at a glance.
|
||||
- name: Stage desktop installers
|
||||
run: |
|
||||
mkdir -p desktop-artifacts
|
||||
find desktop-artifacts -type f \( -name '*.dmg' -o -name '*.msi' -o -name '*.exe' \
|
||||
-o -name '*.deb' -o -name '*.rpm' -o -name '*.AppImage' \) | while read -r f; do
|
||||
base="$(basename "$f")"
|
||||
ext="${base##*.}"
|
||||
name="${base%.*}"
|
||||
cp "$f" "upload/${name}-${VERSION_CODE}.${ext}"
|
||||
done
|
||||
ls -l upload
|
||||
|
||||
# Delete the previous snapshot release AND its tag, then recreate both at HEAD. This
|
||||
# prunes the now-stale (differently-named) APKs so they don't pile up, and sidesteps the
|
||||
# Releases API refusing to retarget an already-existing tag.
|
||||
- name: Remove previous snapshot release
|
||||
run: gh release delete snapshot --yes --cleanup-tag || true
|
||||
|
||||
- name: Publish snapshot release
|
||||
run: |
|
||||
cat > notes.md <<EOF
|
||||
Automated debug build from the latest commit on \`main\` ($GITHUB_SHA), versionCode $VERSION_CODE.
|
||||
Unsigned/debug-keyed, F-Droid and Google flavors. Not for production use — this release is replaced on every push to main.
|
||||
|
||||
Also includes unsigned desktop installers (macOS .dmg, Windows .msi/.exe, Linux .deb/.rpm/.AppImage)
|
||||
built from the same commit, when that platform's build succeeded.
|
||||
|
||||
**Obtainium:** enable *Include prereleases*. Each build's APK filename carries the versionCode, so updates are detected.
|
||||
EOF
|
||||
gh release create snapshot upload/* \
|
||||
--title "Snapshot $VERSION_CODE ($GITHUB_SHA)" \
|
||||
--target "$GITHUB_SHA" \
|
||||
--prerelease \
|
||||
--notes-file notes.md
|
||||
@@ -1,71 +0,0 @@
|
||||
name: Main Push Changelog
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: read
|
||||
|
||||
concurrency:
|
||||
group: main-push-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
main-push-changelog:
|
||||
name: Generate main push changelog
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Determine last tag
|
||||
id: last_prod_tag
|
||||
run: |
|
||||
TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||
echo "Found last tag: $TAG"
|
||||
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate changelog from last tag to current
|
||||
if: steps.last_prod_tag.outputs.tag != ''
|
||||
uses: mikepenz/release-changelog-builder-action@v6
|
||||
id: changelog
|
||||
with:
|
||||
configuration: .github/release.yml
|
||||
fromTag: ${{ steps.last_prod_tag.outputs.tag }}
|
||||
toTag: ${{ github.sha }}
|
||||
outputFile: main-push-changelog.md
|
||||
fetchViaCommits: true
|
||||
fetchReviewers: false
|
||||
fetchReleaseInformation: false
|
||||
fetchReviews: false
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload changelog artifact
|
||||
if: steps.last_prod_tag.outputs.tag != ''
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: main-push-changelog
|
||||
path: main-push-changelog.md
|
||||
|
||||
- name: Print main push summary
|
||||
env:
|
||||
LAST_TAG: ${{ steps.last_prod_tag.outputs.tag }}
|
||||
run: |
|
||||
echo "Pushed to main"
|
||||
echo "SHA: $GITHUB_SHA"
|
||||
echo "Actor: $GITHUB_ACTOR"
|
||||
echo "Ref: $GITHUB_REF"
|
||||
echo ""
|
||||
if [ "$LAST_TAG" != "" ]; then
|
||||
echo "Changelog since last tag ($LAST_TAG)":
|
||||
echo "----------------------------------------"
|
||||
cat main-push-changelog.md
|
||||
else
|
||||
echo "No tag found. Skipping changelog generation."
|
||||
fi
|
||||
@@ -7,31 +7,114 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Note: github.ref is unique per merge-group entry (gh-readonly-queue/main/pr-N-<sha>),
|
||||
# so this group never dedupes across re-queues of the same PR — the cancel-superseded
|
||||
# job below handles that explicitly.
|
||||
concurrency:
|
||||
group: build-mq-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
android-check:
|
||||
# When a PR is re-queued (an entry ahead of it failed or was removed), GitHub creates a
|
||||
# new merge group but does NOT cancel the workflow runs of the destroyed one. Those stale
|
||||
# runs sit queued/running and starve the runner pool. Cancel any older merge-queue run
|
||||
# for the same PR — only the newest merge group per PR is ever valid.
|
||||
# No checkout, no toolchain — just gh api + jq. ubuntu-slim starts sooner than a VM, which
|
||||
# matters most here: every second before this runs is a second the superseded run keeps a slot.
|
||||
cancel-superseded:
|
||||
name: Cancel Superseded Queue Runs
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
actions: write
|
||||
steps:
|
||||
- name: Cancel older merge-queue runs for the same PR
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# github.ref_name: gh-readonly-queue/main/pr-<num>-<base-sha>
|
||||
PR_PREFIX=$(echo "${{ github.ref_name }}" | grep -oE 'gh-readonly-queue/.+/pr-[0-9]+-')
|
||||
if [ -z "$PR_PREFIX" ]; then
|
||||
echo "Could not parse PR from ref '${{ github.ref_name }}'; skipping."
|
||||
exit 0
|
||||
fi
|
||||
for status in queued in_progress; do
|
||||
gh api "repos/${{ github.repository }}/actions/runs?event=merge_group&status=${status}&per_page=100" \
|
||||
--jq ".workflow_runs[] | select(.head_branch | startswith(\"$PR_PREFIX\")) | select(.id < ${{ github.run_id }}) | .id"
|
||||
done | sort -u | while read -r run_id; do
|
||||
echo "Cancelling superseded run $run_id"
|
||||
gh run cancel "$run_id" --repo "${{ github.repository }}" || true
|
||||
done
|
||||
|
||||
# Docs-only queue entries (changelog updates, markdown fixes) cannot affect the build;
|
||||
# skip the heavy pipeline for them. Anything outside docs/ and *.md runs full CI.
|
||||
# Mirrors the paths-ignore list in main-check.yml.
|
||||
check-changes:
|
||||
name: Check Changes
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android'
|
||||
runs-on: ubuntu-24.04-arm
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
android: ${{ steps.filter.outputs.android }}
|
||||
steps:
|
||||
- uses: actions/checkout@v7.0.1
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- name: Diff merge group against its base
|
||||
id: filter
|
||||
run: |
|
||||
git fetch --depth=1 origin "${{ github.event.merge_group.base_sha }}"
|
||||
changed=$(git diff --name-only "${{ github.event.merge_group.base_sha }}" "${{ github.event.merge_group.head_sha }}")
|
||||
echo "Changed files:"
|
||||
echo "$changed"
|
||||
if echo "$changed" | grep -qvE '^docs/|\.md$|^$'; then
|
||||
echo "android=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "android=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
android-check:
|
||||
needs: check-changes
|
||||
if: github.repository == 'meshtastic/Meshtastic-Android' && needs.check-changes.outputs.android == 'true'
|
||||
uses: ./.github/workflows/reusable-check.yml
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
with:
|
||||
run_lint: true
|
||||
run_unit_tests: true
|
||||
# Coverage is produced by main-check on the identical merge commit right
|
||||
# after the queue merges it — keeping Kover instrumentation and report
|
||||
# generation out of the queue's critical-path test shards.
|
||||
run_coverage: false
|
||||
# The PR already assembled these APKs and main-check rebuilds them (with
|
||||
# -SNAPSHOT naming) for the snapshot release; a merge-combination
|
||||
# packaging break would surface there minutes later. Skipping saves a
|
||||
# runner slot per queue entry.
|
||||
run_android_build: false
|
||||
upload_artifacts: false
|
||||
secrets: inherit
|
||||
|
||||
# Pure gate job: no checkout, no toolchain, just reads `needs` results. ubuntu-slim is a
|
||||
# container-backed single-CPU runner that starts faster than a VM (15-min job cap, x64 only).
|
||||
check-workflow-status:
|
||||
name: Check Workflow Status
|
||||
runs-on: ubuntu-24.04-arm
|
||||
runs-on: ubuntu-slim
|
||||
timeout-minutes: 5
|
||||
permissions: {}
|
||||
needs:
|
||||
- check-changes
|
||||
- android-check
|
||||
if: always()
|
||||
steps:
|
||||
- name: Check Workflow Status
|
||||
run: |
|
||||
if [[ "${{ needs.android-check.result }}" == "failure" || "${{ needs.android-check.result }}" == "cancelled" ]]; then
|
||||
if [[ "${{ needs.check-changes.result }}" != "success" ]]; then
|
||||
echo "::error::Change detection failed"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "${{ needs.check-changes.outputs.android }}" == "true" && ("${{ needs.android-check.result }}" == "failure" || "${{ needs.android-check.result }}" == "cancelled") ]]; then
|
||||
echo "::error::Android Check failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
name: Issue Triage (Models)
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
models: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.issue.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
if: ${{ github.repository == 'meshtastic/Meshtastic-Android' && github.event.issue.user.type != 'Bot' }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 1: Quality check (spam/AI-slop detection) - runs first, exits early if spam
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Detect spam or low-quality content
|
||||
uses: actions/ai-inference@v2
|
||||
id: quality
|
||||
continue-on-error: true
|
||||
with:
|
||||
max-tokens: 20
|
||||
prompt: |
|
||||
Is this GitHub issue spam, AI-generated slop, or low quality?
|
||||
|
||||
Title: ${{ github.event.issue.title }}
|
||||
Body: ${{ github.event.issue.body }}
|
||||
|
||||
Respond with exactly one of: spam, ai-generated, needs-review, ok
|
||||
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
|
||||
model: openai/gpt-4o-mini
|
||||
|
||||
- name: Apply quality label if needed
|
||||
if: steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
|
||||
with:
|
||||
script: |
|
||||
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
|
||||
const labelMeta = {
|
||||
'spam': { color: 'd73a4a', description: 'Possible spam' },
|
||||
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
|
||||
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
|
||||
};
|
||||
const meta = labelMeta[label];
|
||||
if (!meta) return;
|
||||
|
||||
// Ensure label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) throw e;
|
||||
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
|
||||
}
|
||||
|
||||
// Apply label
|
||||
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] });
|
||||
|
||||
// Set output to skip remaining steps
|
||||
core.setOutput('is_spam', 'true');
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 2: Duplicate detection - only if not spam
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Detect duplicate issues
|
||||
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
|
||||
uses: pelikhan/action-genai-issue-dedup@bdb3b5d9451c1090ffcdf123d7447a5e7c7a2528 # v0.0.19
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 3: Completeness check + auto-labeling (combined into one AI call)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Determine if completeness check should be skipped
|
||||
if: steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == ''
|
||||
uses: actions/github-script@v9
|
||||
id: check-skip
|
||||
with:
|
||||
script: |
|
||||
const title = (context.payload.issue.title || '').toLowerCase();
|
||||
const labels = (context.payload.issue.labels || []).map(label => label.name);
|
||||
const hasFeatureRequest = title.includes('feature request');
|
||||
const hasEnhancement = labels.includes('enhancement');
|
||||
const shouldSkip = hasFeatureRequest && hasEnhancement;
|
||||
core.setOutput('should_skip', shouldSkip ? 'true' : 'false');
|
||||
|
||||
- name: Analyze issue completeness and determine labels
|
||||
if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true'
|
||||
uses: actions/ai-inference@v2
|
||||
id: analysis
|
||||
continue-on-error: true
|
||||
with:
|
||||
prompt: |
|
||||
Analyze this GitHub issue for the Meshtastic Android app and determine if it needs labels.
|
||||
|
||||
If this looks like a bug in the Android app (crash, ANR, UI glitch, connection failure, Bluetooth issues, notification problems, map issues), request app logs and explain how to get them:
|
||||
|
||||
Android app debug logs:
|
||||
- Open the Meshtastic app, go to Settings > Debug > Save Logs
|
||||
- Reproduce the problem, then share/attach the exported log file
|
||||
|
||||
Android logcat (if app logs are insufficient):
|
||||
- Connect phone via USB with USB debugging enabled
|
||||
- Run: adb logcat -s Meshtastic:* *:E
|
||||
- Reproduce the problem, then copy/paste the relevant output
|
||||
|
||||
Also request key context if missing: Android version, phone model, app version, Meshtastic device model, firmware version, connection type (BLE/USB/TCP), steps to reproduce, expected vs actual.
|
||||
|
||||
Respond ONLY with JSON:
|
||||
{
|
||||
"complete": true|false,
|
||||
"comment": "Your helpful comment requesting missing info, or empty string if complete",
|
||||
"label": "needs-logs" | "needs-info" | "none"
|
||||
}
|
||||
|
||||
Use "needs-logs" if this is an app bug AND no logs are attached.
|
||||
Use "needs-info" if basic info like firmware version or steps to reproduce are missing.
|
||||
Use "none" if the issue is complete or is a feature request.
|
||||
|
||||
Title: ${{ github.event.issue.title }}
|
||||
Body: ${{ github.event.issue.body }}
|
||||
system-prompt: You are a helpful assistant that triages GitHub issues. Be conservative with labels.
|
||||
model: openai/gpt-4o-mini
|
||||
|
||||
- name: Process analysis result
|
||||
if: (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '') && steps.check-skip.outputs.should_skip != 'true' && steps.analysis.outputs.response != ''
|
||||
uses: actions/github-script@v9
|
||||
id: process
|
||||
env:
|
||||
AI_RESPONSE: ${{ steps.analysis.outputs.response }}
|
||||
with:
|
||||
script: |
|
||||
const raw = (process.env.AI_RESPONSE || '').trim();
|
||||
|
||||
let complete = false;
|
||||
let comment = '';
|
||||
let label = 'none';
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
complete = !!parsed.complete;
|
||||
comment = (parsed.comment ?? '').toString().trim();
|
||||
label = (parsed.label ?? 'none').toString().trim().toLowerCase();
|
||||
} catch {
|
||||
// If JSON parse fails, treat as incomplete with raw response as comment
|
||||
complete = false;
|
||||
comment = raw;
|
||||
label = 'none';
|
||||
}
|
||||
|
||||
// Validate label
|
||||
const allowedLabels = new Set(['needs-logs', 'needs-info', 'none']);
|
||||
if (!allowedLabels.has(label)) label = 'none';
|
||||
|
||||
core.setOutput('should_comment', (!complete && comment.length > 0) ? 'true' : 'false');
|
||||
core.setOutput('comment_body', comment);
|
||||
core.setOutput('label', label);
|
||||
|
||||
- name: Apply triage label
|
||||
if: steps.process.outputs.label != '' && steps.process.outputs.label != 'none'
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
LABEL_NAME: ${{ steps.process.outputs.label }}
|
||||
with:
|
||||
script: |
|
||||
const label = process.env.LABEL_NAME;
|
||||
const labelMeta = {
|
||||
'needs-logs': { color: 'cfd3d7', description: 'Device logs requested for triage' },
|
||||
'needs-info': { color: 'f9d0c4', description: 'More information requested for triage' },
|
||||
};
|
||||
const meta = labelMeta[label];
|
||||
if (!meta) return;
|
||||
|
||||
// Ensure label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) throw e;
|
||||
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
|
||||
}
|
||||
|
||||
// Apply label
|
||||
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.issue.number, labels: [label] });
|
||||
|
||||
- name: Comment on issue
|
||||
if: steps.process.outputs.should_comment == 'true'
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
COMMENT_BODY: ${{ steps.process.outputs.comment_body }}
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
body: process.env.COMMENT_BODY
|
||||
});
|
||||
@@ -1,138 +0,0 @@
|
||||
name: PR Triage (Models)
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
models: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
if: ${{ github.repository == 'meshtastic/Meshtastic-Android' && github.event.pull_request.user.type != 'Bot' }}
|
||||
runs-on: ubuntu-24.04-arm
|
||||
steps:
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 1: Check if PR already has automation/type labels (skip if so)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Check existing labels
|
||||
uses: actions/github-script@v9
|
||||
id: check-labels
|
||||
with:
|
||||
script: |
|
||||
const skipLabels = new Set(['automation', 'release']);
|
||||
const typeLabels = new Set(['bugfix', 'enhancement', 'dependencies', 'repo', 'refactor']);
|
||||
const prLabels = context.payload.pull_request.labels.map(l => l.name);
|
||||
|
||||
const shouldSkipAll = prLabels.some(l => skipLabels.has(l));
|
||||
const hasTypeLabel = prLabels.some(l => typeLabels.has(l));
|
||||
|
||||
core.setOutput('skip_all', shouldSkipAll ? 'true' : 'false');
|
||||
core.setOutput('has_type_label', hasTypeLabel ? 'true' : 'false');
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 2: Quality check (spam/AI-slop detection)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Detect spam or low-quality content
|
||||
if: steps.check-labels.outputs.skip_all != 'true'
|
||||
uses: actions/ai-inference@v2
|
||||
id: quality
|
||||
continue-on-error: true
|
||||
with:
|
||||
max-tokens: 20
|
||||
prompt: |
|
||||
Is this GitHub pull request spam, AI-generated slop, or low quality?
|
||||
|
||||
Title: ${{ github.event.pull_request.title }}
|
||||
Body: ${{ github.event.pull_request.body }}
|
||||
|
||||
Respond with exactly one of: spam, ai-generated, needs-review, ok
|
||||
system-prompt: You detect spam and low-quality contributions. Be conservative - only flag obvious spam or AI slop.
|
||||
model: openai/gpt-4o-mini
|
||||
|
||||
- name: Apply quality label if needed
|
||||
if: steps.check-labels.outputs.skip_all != 'true' && steps.quality.outputs.response != '' && steps.quality.outputs.response != 'ok'
|
||||
uses: actions/github-script@v9
|
||||
id: quality-label
|
||||
env:
|
||||
QUALITY_LABEL: ${{ steps.quality.outputs.response }}
|
||||
with:
|
||||
script: |
|
||||
const label = (process.env.QUALITY_LABEL || '').trim().toLowerCase();
|
||||
const labelMeta = {
|
||||
'spam': { color: 'd73a4a', description: 'Possible spam' },
|
||||
'ai-generated': { color: 'fbca04', description: 'Possible AI-generated low-quality content' },
|
||||
'needs-review': { color: 'f9d0c4', description: 'Needs human review' },
|
||||
};
|
||||
const meta = labelMeta[label];
|
||||
if (!meta) return;
|
||||
|
||||
// Ensure label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) throw e;
|
||||
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
|
||||
}
|
||||
|
||||
// Apply label
|
||||
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });
|
||||
|
||||
core.setOutput('is_spam', 'true');
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# Step 3: Auto-label PR type (bugfix/enhancement/refactor)
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
- name: Classify PR for labeling
|
||||
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && (steps.quality.outputs.response == 'ok' || steps.quality.outputs.response == '')
|
||||
uses: actions/ai-inference@v2
|
||||
id: classify
|
||||
continue-on-error: true
|
||||
with:
|
||||
max-tokens: 30
|
||||
prompt: |
|
||||
Classify this pull request for the Meshtastic Android app into exactly one category.
|
||||
|
||||
Return exactly one of: bugfix, enhancement, refactor
|
||||
|
||||
Use bugfix if it fixes a bug, crash, or incorrect behavior.
|
||||
Use enhancement if it adds a new feature, improves performance, or adds new functionality.
|
||||
Use refactor if it restructures code without changing behavior, cleans up code, or improves architecture.
|
||||
|
||||
Title: ${{ github.event.pull_request.title }}
|
||||
Body: ${{ github.event.pull_request.body }}
|
||||
system-prompt: You classify pull requests into categories. Be conservative and pick the most appropriate single label.
|
||||
model: openai/gpt-4o-mini
|
||||
|
||||
- name: Apply type label
|
||||
if: steps.check-labels.outputs.skip_all != 'true' && steps.check-labels.outputs.has_type_label != 'true' && steps.classify.outputs.response != ''
|
||||
uses: actions/github-script@v9
|
||||
env:
|
||||
TYPE_LABEL: ${{ steps.classify.outputs.response }}
|
||||
with:
|
||||
script: |
|
||||
const label = (process.env.TYPE_LABEL || '').trim().toLowerCase();
|
||||
const labelMeta = {
|
||||
'bugfix': { color: 'd73a4a', description: 'Bug fix' },
|
||||
'enhancement': { color: 'a2eeef', description: 'New feature or enhancement' },
|
||||
'refactor': { color: 'c5def5', description: 'Code restructuring without behavior change' },
|
||||
};
|
||||
const meta = labelMeta[label];
|
||||
if (!meta) return;
|
||||
|
||||
// Ensure label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label });
|
||||
} catch (e) {
|
||||
if (e.status !== 404) throw e;
|
||||
await github.rest.issues.createLabel({ owner: context.repo.owner, repo: context.repo.repo, name: label, color: meta.color, description: meta.description });
|
||||
}
|
||||
|
||||
// Apply label
|
||||
await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.payload.pull_request.number, labels: [label] });
|
||||
Loaded 100 of 3894 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user