Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions BACKLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ so the offline guarantee holds structurally.**
- ✅ **Pillar 2 — Issues as ephemeral defect intake.** `specify issues list|create|close`
(confirmation-gated, `--json`); the `github/` scaffold subtree now drops the full
per-target `.github/` (`PULL_REQUEST_TEMPLATE.md`, `ISSUE_TEMPLATE/defect.yml` stamping
`type: Bug` + label fallback, `config.yml`, `CODEOWNERS` for `/features` `/specs`,
`dependabot.yml`). No durable issue↔scenario link (GitHub cross-refs). Close-on-green is
`type: Bug` + label fallback, `config.yml`, `CODEOWNERS` for `/features` `/specs`).
Dependency updates moved off Dependabot to a local Renovate `deps` gate (per-stack
`mise.toml`). No durable issue↔scenario link (GitHub cross-refs). Close-on-green is
the discipline.
- ✅ **Pillar 3 — Projects board (Beads-informed).** `specify work ready|claim|move|discover`
on the inlined Projects v2 GraphQL client (resolve project/fields/options, add item, set
Expand Down
5 changes: 4 additions & 1 deletion docs/design/github-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ the same hierarchy Beads gets from parent-child. *Caveat:* Issue Types are an
| `ISSUE_TEMPLATE/defect.yml` | a defect form — stamps `type: Bug`, a label, the project; prompts for repro + the target |
| `ISSUE_TEMPLATE/config.yml` | points docs at *this* repo |
| `CODEOWNERS` | maps `/features/**` and `/specs/**` to the spec owner, so **spec changes require human review** |
| `dependabot.yml` | for the stack's ecosystem (web → npm, go → gomod) |

_No `dependabot.yml` is projected — dependency updates are surfaced locally by
each stack's **`deps`** mise task (Renovate dry-run, never opens PRs), part of
`mise run check`._

## Pillar 3 — Projects as the work surface (Beads-informed, simplified)

Expand Down
5 changes: 4 additions & 1 deletion docs/usage/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ specify issues close <issue#> # close on green
| `ISSUE_TEMPLATE/config.yml` | disables blank issues, links out to the SpecKit docs |
| `PULL_REQUEST_TEMPLATE.md` | a spec-touch checklist (specs changed? scenarios bound? `verify` green? `drift` clean?) |
| `CODEOWNERS` | routes `/features/`, `/specs/`, and `/.speckit/` to the spec owner, so **spec changes require human review** (replace `@OWNER` with a user or team) |
| `dependabot.yml` | for the stack's ecosystem (web → npm) |

_Dependency updates aren't a GitHub surface: each stack's `mise.toml` ships a
local **`deps`** task (Renovate dry-run, never opens PRs) wired into `mise run
check`, so no `dependabot.yml` is projected._

The lifecycle is **scenario-canonical**:

Expand Down
15 changes: 15 additions & 0 deletions internal/coreassets/templates/scaffolds/apple/files/mise.toml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
# engine's `swift` format joins.
[tools]
tuist = "4.196.1"
# node runs Renovate (the local dependency gate); jq formats its report.
node = "24"
"npm:renovate" = "latest"
jq = "latest"

[tasks.test]
description = "run the Core suite, writing the event-stream report the engine joins"
Expand Down Expand Up @@ -62,3 +66,14 @@ for d in Core macOS iOS; do
done
if [ -n "$dirs" ]; then swift format lint --strict --recursive $dirs; fi
'''

# Local dependency check — advisory (never opens PRs, always exits 0). Replaces
# Dependabot: surfaces available updates as part of `mise run check`.
[tasks.deps]
description = "report available dependency updates (local Renovate dry-run; no PRs)"
run = "bash scripts/deps-check.sh"

[tasks.check]
description = "all local quality gates: lint + the dependency heads-up"
depends = ["lint", "deps"]
run = ":"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"dependencyDashboard": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# Local dependency-update gate.
#
# Runs Renovate against the working tree in local/dry-run mode — it NEVER opens
# PRs or writes anything; it just reports which dependencies (npm packages, Go
# modules, Swift packages, GitHub Actions, mise tools, …) have newer versions
# available. Wired into `mise run check` so updates surface as ongoing work
# instead of as autonomously-opened Dependabot PRs.
#
# Advisory only: always exits 0. Available updates are a heads-up, not a failure,
# and a Renovate/network hiccup must not break the rest of `check`.
set -uo pipefail

if ! command -v renovate >/dev/null 2>&1; then
echo "deps: renovate not found — enable the gate with: mise use 'npm:renovate@latest' jq"
exit 0
fi

# Renovate needs a github.com token to look up Actions / Go-module / Swift-package
# release versions (npm needs none). Reuse the user's existing gh auth when
# present; anonymous still works for npm, just rate-limited on github.com.
github_token="$(gh auth token 2>/dev/null || true)"

log="$(mktemp -t renovate.XXXXXX)"
trap 'rm -f "$log"' EXIT

GITHUB_COM_TOKEN="$github_token" \
RENOVATE_ONBOARDING=false \
LOG_LEVEL=debug \
LOG_FORMAT=json \
renovate --platform=local --dry-run=full >"$log" 2>&1
status=$?

if [ "$status" -ne 0 ]; then
echo "deps: renovate exited $status (skipping the update check this run)"
grep -iE '"level":(50|60)|FATAL|ERROR' "$log" | head -5
exit 0
fi

# All available updates live in the single "packageFiles with updates" debug
# record: walk every dependency that carries a non-empty updates[] array and
# emit one row per update — updateType, ecosystem, name, current → target.
# Everything Renovate found is surfaced; scope what it *looks at* in renovate.json.
updates="$(jq -r '
select(.msg == "packageFiles with updates")
| [.. | objects | select(.depName? and (.updates? | type == "array") and ((.updates | length) > 0))]
| .[] as $dep
| $dep.updates[]
| "\(.updateType // "?")\t\($dep.datasource // "-")\t\($dep.depName)\t\($dep.currentValue // $dep.currentVersion // "-")\t\(.newVersion // .newValue)"
' "$log" | sort -u | sort -t"$(printf '\t')" -k2,2 -k1,1 -k3,3)"

if [ -z "$updates" ]; then
echo "deps: ✓ all dependencies up to date"
exit 0
fi

count="$(printf '%s\n' "$updates" | grep -c '')"
echo "deps: ⚠ $count update(s) available — local Renovate dry-run, no PRs opened"
echo
# Majors get their own flagged section (each is a breaking-change decision, not
# routine work); the in-range minor/patch updates follow, grouped by ecosystem.
printf '%s\n' "$updates" | awk -F'\t' '
{
type[NR] = $1; eco[NR] = $2; name[NR] = $3; cur[NR] = $4; tgt[NR] = $5
if ($1 == "major") majors++
else inrange[$2]++
}
END {
if (majors > 0) {
printf " major — review before upgrading (%d)\n", majors
for (i = 1; i <= NR; i++)
if (type[i] == "major")
printf " %-7s %-34s %s → %s\n", eco[i], name[i], cur[i], tgt[i]
print ""
}
prev = ""
for (i = 1; i <= NR; i++) {
if (type[i] == "major") continue
if (eco[i] != prev) {
if (prev != "") print ""
printf " %s (%d)\n", eco[i], inrange[eco[i]]
prev = eco[i]
}
printf " %-6s %-34s %s → %s\n", type[i], name[i], cur[i], tgt[i]
}
}'
echo
echo " In-range bumps: apply them with your stack's package manager, then re-run \`mise run deps\`."
echo " Majors are listed separately — review each one's changelog before upgrading."
exit 0
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
# engine's gotest format joins.
[tools]
go = "1.26"
# node runs Renovate (the local dependency gate); jq formats its report.
node = "24"
"npm:renovate" = "latest"
jq = "latest"

[tasks.dev]
description = "run the service"
Expand Down Expand Up @@ -35,3 +39,14 @@ run = 'test -z "$(gofmt -l .)"'
description = "regenerate the Go server from the OpenAPI contract (oapi-codegen)"
run = "mkdir -p internal/api && go tool oapi-codegen -config oapi-codegen.yaml openapi.yaml"
{{end}}

# Local dependency check — advisory (never opens PRs, always exits 0). Replaces
# Dependabot: surfaces available updates as part of `mise run check`.
[tasks.deps]
description = "report available dependency updates (local Renovate dry-run; no PRs)"
run = "bash scripts/deps-check.sh"

[tasks.check]
description = "all local quality gates: fmt:check, vet + the dependency heads-up"
depends = ["fmt:check", "vet", "deps"]
run = ":"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"dependencyDashboard": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# Local dependency-update gate.
#
# Runs Renovate against the working tree in local/dry-run mode — it NEVER opens
# PRs or writes anything; it just reports which dependencies (npm packages, Go
# modules, Swift packages, GitHub Actions, mise tools, …) have newer versions
# available. Wired into `mise run check` so updates surface as ongoing work
# instead of as autonomously-opened Dependabot PRs.
#
# Advisory only: always exits 0. Available updates are a heads-up, not a failure,
# and a Renovate/network hiccup must not break the rest of `check`.
set -uo pipefail

if ! command -v renovate >/dev/null 2>&1; then
echo "deps: renovate not found — enable the gate with: mise use 'npm:renovate@latest' jq"
exit 0
fi

# Renovate needs a github.com token to look up Actions / Go-module / Swift-package
# release versions (npm needs none). Reuse the user's existing gh auth when
# present; anonymous still works for npm, just rate-limited on github.com.
github_token="$(gh auth token 2>/dev/null || true)"

log="$(mktemp -t renovate.XXXXXX)"
trap 'rm -f "$log"' EXIT

GITHUB_COM_TOKEN="$github_token" \
RENOVATE_ONBOARDING=false \
LOG_LEVEL=debug \
LOG_FORMAT=json \
renovate --platform=local --dry-run=full >"$log" 2>&1
status=$?

if [ "$status" -ne 0 ]; then
echo "deps: renovate exited $status (skipping the update check this run)"
grep -iE '"level":(50|60)|FATAL|ERROR' "$log" | head -5
exit 0
fi

# All available updates live in the single "packageFiles with updates" debug
# record: walk every dependency that carries a non-empty updates[] array and
# emit one row per update — updateType, ecosystem, name, current → target.
# Everything Renovate found is surfaced; scope what it *looks at* in renovate.json.
updates="$(jq -r '
select(.msg == "packageFiles with updates")
| [.. | objects | select(.depName? and (.updates? | type == "array") and ((.updates | length) > 0))]
| .[] as $dep
| $dep.updates[]
| "\(.updateType // "?")\t\($dep.datasource // "-")\t\($dep.depName)\t\($dep.currentValue // $dep.currentVersion // "-")\t\(.newVersion // .newValue)"
' "$log" | sort -u | sort -t"$(printf '\t')" -k2,2 -k1,1 -k3,3)"

if [ -z "$updates" ]; then
echo "deps: ✓ all dependencies up to date"
exit 0
fi

count="$(printf '%s\n' "$updates" | grep -c '')"
echo "deps: ⚠ $count update(s) available — local Renovate dry-run, no PRs opened"
echo
# Majors get their own flagged section (each is a breaking-change decision, not
# routine work); the in-range minor/patch updates follow, grouped by ecosystem.
printf '%s\n' "$updates" | awk -F'\t' '
{
type[NR] = $1; eco[NR] = $2; name[NR] = $3; cur[NR] = $4; tgt[NR] = $5
if ($1 == "major") majors++
else inrange[$2]++
}
END {
if (majors > 0) {
printf " major — review before upgrading (%d)\n", majors
for (i = 1; i <= NR; i++)
if (type[i] == "major")
printf " %-7s %-34s %s → %s\n", eco[i], name[i], cur[i], tgt[i]
print ""
}
prev = ""
for (i = 1; i <= NR; i++) {
if (type[i] == "major") continue
if (eco[i] != prev) {
if (prev != "") print ""
printf " %s (%d)\n", eco[i], inrange[eco[i]]
prev = eco[i]
}
printf " %-6s %-34s %s → %s\n", type[i], name[i], cur[i], tgt[i]
}
}'
echo
echo " In-range bumps: apply them with your stack's package manager, then re-run \`mise run deps\`."
echo " Majors are listed separately — review each one's changelog before upgrading."
exit 0
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
# `mise run test` writes the Swift Testing event stream the engine's `swift` format
# joins.

# The local dependency gate (`mise run deps`) runs Renovate (a node tool) + jq;
# pin them here since the stack otherwise leans on the ambient Swift toolchain.
[tools]
node = "24"
"npm:renovate" = "latest"
jq = "latest"

[tasks.test]
description = "run the test suite, writing the event-stream report the engine joins"
run = "swift test --event-stream-output-path test.swift-events.ndjson --event-stream-version 0"
Expand Down Expand Up @@ -41,3 +48,14 @@ for d in Sources Tests; do
done
if [ -n "$dirs" ]; then swift format lint --strict --recursive $dirs; fi
'''

# Local dependency check — advisory (never opens PRs, always exits 0). Replaces
# Dependabot: surfaces available updates as part of `mise run check`.
[tasks.deps]
description = "report available dependency updates (local Renovate dry-run; no PRs)"
run = "bash scripts/deps-check.sh"

[tasks.check]
description = "all local quality gates: lint + the dependency heads-up"
depends = ["lint", "deps"]
run = ":"
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"dependencyDashboard": false
}
Loading