* feat(loops): regeluppdat + docs-freshness scans (#1417) Two new local loops per .claude/loops.md conventions: - loop-regeluppdat (monthly): sweeps official Swedish sources (Skatteverket, BFN, Bolagsverket, regeringen/riksdagen, BAS, DIGG/ViDA) for regulatory changes, verifies each against the codebase anchors, and files deduped tickets for gaps. Tickets only, never code: regulatory changes touch money math and compliance surfaces. - loop-docs-freshness (weekly): runs scripts/check-docs-freshness.mts, which builds every docs page from source and diffs it against the live .md mirrors on docs.accounted.se; files one deduped drift issue and proposes the re-export PR in the gnubok-website repo. Both self-gate on run markers so any invocation is idempotent; loop-ignite now runs them when due (session crons cannot express weekly/monthly). Labels loop:docs and loop:regeluppdat created on the repo. Closes #1417 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(loops): explicit types for closure-captured docs-content imports next build's type check rejects the bare let-in-try pattern when the variables are read inside a nested function (implicit any). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
Jakob Wennberg
parent
ce1e0b7642
commit
4ccebd3645
+12
-2
@@ -37,7 +37,8 @@ the PR is opened.** No exceptions.
|
||||
**Destination:** GitHub Issues + PRs in `erp-mafia/accounted` (via `gh`). Not Linear.
|
||||
|
||||
**Labels:** `loop:auto` (always, on anything a loop creates), `loop:vercel`, `loop:triage`,
|
||||
`loop:design`, `loop:needs-human` (a loop tried and could not safely proceed).
|
||||
`loop:design`, `loop:docs`, `loop:regeluppdat`, `loop:needs-human` (a loop tried and could not
|
||||
safely proceed).
|
||||
|
||||
**Idempotency / anti-spam: MANDATORY.** Before filing anything:
|
||||
1. Compute a stable **fingerprint** (error signature, file:line, rule id, never a timestamp).
|
||||
@@ -62,6 +63,8 @@ comment what was tried. Never retry the same failing action in a cycle.
|
||||
| 2 | Vercel errors → tickets | `loop-vercel-errors` | **Local** (Vercel MCP) | daily / on-demand | ≤8 issues, ≤2 PRs |
|
||||
| 3 | Issue triage + easy-fix | `loop-issue-triage` | **Local** (session cron / `/loop`) | ~2x/day while a session is open | triage all; ≤2 PRs |
|
||||
| 4 | UI/UX + design scan | `loop-design-scan` | **Local** (`/loop`) | on-demand | ≤1 area, ≤6 findings |
|
||||
| 5 | Docs freshness (docs.accounted.se) | `loop-docs-freshness` | **Local** (`/loop` / ignite-when-due) | weekly | 1 issue, 1 website PR |
|
||||
| 6 | Regeluppdat (Swedish regulatory watch) | `loop-regeluppdat` | **Local** (`/loop` / ignite-when-due) | monthly | ≤6 issues, **0 PRs** |
|
||||
|
||||
**All loops are local.** Cloud routines were retired 2026-07-20: the three claude.ai triggers
|
||||
(`trig_01J2nG7eB9gsdAb9YSGBVwa8`, `trig_014CmE3gTJ7ErnvL2trPYymu`, `trig_017hB94ieGVwreJqHpGRDVoM`)
|
||||
@@ -70,6 +73,12 @@ to disable them rather than provision. Do not re-enable or re-create them. Loop
|
||||
the Vercel MCP (local-only; Sentry is not used). Loop 4 needs `npm run dev` + Chrome.
|
||||
The `loop-ignite` skill audits and (re)schedules the local cadence each session.
|
||||
|
||||
Loops 5 and 6 self-gate on a due check (a run marker in their tracking issues), so any invocation
|
||||
is idempotent: session crons cannot outlive 7 days, which makes weekly/monthly cadences
|
||||
unschedulable directly. Instead `loop-ignite` runs them **when due** at the start of a session.
|
||||
Loop 6 files tickets only, never PRs: regulatory changes touch money math and compliance logic,
|
||||
which the autonomy policy forbids auto-fixing.
|
||||
|
||||
---
|
||||
|
||||
## The verification gate (`loop-verify`)
|
||||
@@ -95,7 +104,8 @@ of that deliberately, treat cloud as retired: see issue #993 for the full histor
|
||||
## Operating the loops
|
||||
- **Audit / (re)ignite each session:** `/loop-ignite` (audits evidence, schedules session-local crons).
|
||||
- **Run on-demand:** `/loop-pr-ci-triage`, `/loop-issue-triage`, `/loop-vercel-errors`,
|
||||
`/loop-design-scan <area>`. Wrap in `/loop <interval>` to repeat locally; `/goal` for a hard exit.
|
||||
`/loop-design-scan <area>`, `/loop-docs-freshness`, `/loop-regeluppdat`. Wrap in
|
||||
`/loop <interval>` to repeat locally; `/goal` for a hard exit.
|
||||
- **Cost:** route mechanical steps to cheaper models; reserve judgment for the strong model. `/usage`.
|
||||
Don't run more often than the watched thing changes.
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
name: loop-docs-freshness
|
||||
description: Weekly loop that checks the public API docs (docs.accounted.se) against what this repo generates today, files one deduped drift issue, and proposes the re-export PR in the gnubok-website repo. Use on a weekly cadence or on-demand via /loop-docs-freshness. Follows .claude/loops.md (propose-don't-merge).
|
||||
---
|
||||
|
||||
# loop-docs-freshness
|
||||
|
||||
**Goal:** what docs.accounted.se serves is byte-identical to what this repo would generate today.
|
||||
The docs site lives in the separate `jakobwennberg/gnubok-website` repo and is fed by manual
|
||||
snapshot exports, so it silently drifts whenever an endpoint, error code, or content page changes
|
||||
here. **Never merge.** Read `.claude/loops.md` first.
|
||||
|
||||
## Preflight
|
||||
|
||||
`gh auth status`, repo `erp-mafia/accounted`. If it fails, stop and report "environment not provisioned".
|
||||
|
||||
## 1. Due check (weekly semantics)
|
||||
|
||||
Find the most recent trace: `gh issue list --label loop:docs --state all --limit 5 --json number,title,updatedAt`
|
||||
plus the newest `<!-- loop-docs-freshness-run: YYYY-MM-DD -->` marker in those issues' comments.
|
||||
If a run marker is dated **less than 6 days ago**, report "not due" and stop, unless the user
|
||||
invoked `/loop-docs-freshness` explicitly (explicit invocation always runs).
|
||||
|
||||
## 2. Run the deterministic check
|
||||
|
||||
From an up-to-date `main` checkout (the comparison is "what main generates" vs "what the site
|
||||
serves"; a feature branch would produce false drift):
|
||||
|
||||
```bash
|
||||
npx tsx scripts/check-docs-freshness.mts
|
||||
```
|
||||
|
||||
- **Exit 0** (in sync): if an open `loop:docs` drift issue exists, comment that the site is back
|
||||
in sync and close it. Post the run marker (step 5). Done.
|
||||
- **Exit 2** (fetch/build failure): the site is unreachable or the content modules changed shape.
|
||||
Do NOT file a drift issue. If this is the second consecutive exit-2 run, file/update one issue
|
||||
labeled `loop:auto`, `loop:docs`, `loop:needs-human` describing the failure. Stop.
|
||||
- **Exit 1** (drift/missing): continue.
|
||||
|
||||
## 3. File or update ONE drift issue (cap: 1 per run)
|
||||
|
||||
The script prints a `FINGERPRINT` per drifted page. Compute the run fingerprint = first 12 hex
|
||||
chars of sha256 over the sorted page fingerprints, then dedupe per `.claude/loops.md`:
|
||||
|
||||
- `gh issue list --search "<run-fingerprint> in:body" --state all` : match open → comment the fresh
|
||||
script summary instead of filing; match closed → reopen with a note.
|
||||
- Otherwise file **one** issue (never one per page):
|
||||
- Title: `docs drift: docs.accounted.se out of sync (<N> pages)`
|
||||
- Body: the script's non-ok output verbatim, the remediation steps below, and
|
||||
`<!-- loop-fingerprint: <run-fingerprint> -->`.
|
||||
- Labels: `loop:auto`, `loop:docs`, `documentation`.
|
||||
|
||||
If an *older* open `loop:docs` issue has a different fingerprint (the drift changed), comment on
|
||||
it with a pointer to the new issue and close it: one open drift issue at a time.
|
||||
|
||||
## 4. Propose the fix (cap: 1 website PR per run)
|
||||
|
||||
Two failure classes, two remedies:
|
||||
|
||||
- **Generated snapshots stale** (`reference*`, `errors.md`, `connect-claude.md`): run
|
||||
`npx tsx scripts/export-docs-to-website.mts`. It writes `*.generated.ts` into
|
||||
`~/gnubok-website`.
|
||||
- **Hand-copied pages stale** (`changelog.md`, `versioning.md`, `webhooks.md`, `cookbook/*`):
|
||||
copy the exported markdown constants from `lib/docs/content/` in erp-base over the website
|
||||
repo's duplicates in `lib/docs/content/`. Mechanical copy only; if the two sides have diverged
|
||||
in structure (not just content), escalate with `loop:needs-human` instead.
|
||||
|
||||
The `~/gnubok-website` checkout is often on a human's WIP branch. **Never commit on it.** Work in
|
||||
a fresh worktree from origin/main:
|
||||
|
||||
```bash
|
||||
git -C ~/gnubok-website fetch origin
|
||||
git -C ~/gnubok-website worktree add /tmp/loop-docs-freshness-wt -b loop/docs-freshness-<run-fingerprint> origin/main
|
||||
```
|
||||
|
||||
Re-run the export/copy against the worktree, commit, push, and open a PR **in
|
||||
`jakobwennberg/gnubok-website`** whose body links the erp-base drift issue and embeds the same
|
||||
fingerprint marker. Then remove the worktree. Never merge; the founder deploys the website.
|
||||
|
||||
**404s on pages whose routes exist on the website's origin/main** (seen 2026-08-09 for every
|
||||
`reference/<slug>.md` and `cookbook/<slug>.md` mirror) are a deploy/runtime problem, not a
|
||||
snapshot problem: a re-export PR will not fix them. Label the issue `loop:needs-human` and say
|
||||
so explicitly.
|
||||
|
||||
## 5. Run marker + report
|
||||
|
||||
Comment `<!-- loop-docs-freshness-run: YYYY-MM-DD -->` plus a one-line summary on the drift issue
|
||||
touched this run (or, when in sync and nothing is open, skip the marker comment and rely on the
|
||||
closed issue's timestamp). Summarize: pages in sync / drifted / missing, issue filed or updated,
|
||||
website PR opened, escalations. That summary is the completion notification.
|
||||
|
||||
## Anti-thrash
|
||||
|
||||
If a previous run already opened a website PR for the **same run fingerprint** and it is still
|
||||
open, do not open another: comment on the existing PR/issue instead. If the same fingerprint
|
||||
drifts again after its PR merged and deployed, something upstream regenerates differently:
|
||||
`loop:needs-human`.
|
||||
@@ -21,12 +21,20 @@ audits. All loops run locally now.
|
||||
- Local schedule: `CronList` for session-local jobs invoking `/loop-*` skills.
|
||||
- Evidence of firing: recent `loop/*` branches, PR comments by the loop, `loop:needs-human` labels
|
||||
(`gh pr list`, `gh issue list --label loop:needs-human`).
|
||||
2. **Report a one-screen status table**: loop, expected cadence, last observed run, verdict (LIVE / DEAD / NEVER RAN).
|
||||
- Slow loops: last `<!-- loop-docs-freshness-run: ... -->` marker on `loop:docs` issues (due
|
||||
weekly) and last `<!-- loop-regeluppdat-run: YYYY-MM -->` marker on the `Regeluppdat scan log`
|
||||
issue (due monthly).
|
||||
2. **Report a one-screen status table**: loop, expected cadence, last observed run, verdict (LIVE / DEAD / NEVER RAN / NOT DUE).
|
||||
3. **Ignite what is dead.** For each non-live loop:
|
||||
- Run the loop skill once NOW (`/loop-pr-ci-triage`, `/loop-issue-triage`, `/loop-vercel-errors`)
|
||||
so the backlog is cleared this session.
|
||||
- Then schedule a session-local cadence (`CronCreate` invoking the matching `/loop-*` skill) and
|
||||
say clearly that it only runs while this session is alive (7-day auto-expiry).
|
||||
- **Slow loops run when due, not on a cron**: session crons cannot outlive 7 days, so weekly and
|
||||
monthly cadences cannot be scheduled directly. If the docs-freshness marker is older than 6
|
||||
days (or absent), run `/loop-docs-freshness` once now; if the regeluppdat marker is not from
|
||||
the current calendar month, run `/loop-regeluppdat` once now. Both self-gate, so running them
|
||||
again is a cheap no-op.
|
||||
- If a loop cannot run at all, file the blocker as a GitHub issue so the gap is visible instead of silent.
|
||||
4. **Switch-on check (mandatory):** after igniting, verify via `CronList` that the jobs exist.
|
||||
End with either "ALL LOOPS LIVE (session-local)" or "NOT SWITCHED ON YET: <loop> - <what remains, who flips it>".
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
---
|
||||
name: loop-regeluppdat
|
||||
description: Monthly loop that scans official Swedish sources for new or changed laws and rules affecting accounting/finance software (VAT, payroll, BFN standards, deadlines, BAS chart, e-invoicing), checks whether the codebase already handles each change, and files deduped GitHub issues for the gaps. Use monthly or on-demand via /loop-regeluppdat. Follows .claude/loops.md (propose-don't-merge, tickets only, no auto-fix).
|
||||
---
|
||||
|
||||
# loop-regeluppdat
|
||||
|
||||
**Goal:** no Swedish regulatory change that affects Accounted lands as a surprise. Each month:
|
||||
sweep the official sources, keep only what touches our product surface, verify against the code,
|
||||
and file a well-formed ticket for every real gap. **This loop never writes code.** Regulatory
|
||||
changes touch money math, tax logic, and compliance surfaces, which the autonomy policy forbids
|
||||
auto-fixing: tickets only. Read `.claude/loops.md` first.
|
||||
|
||||
## Preflight
|
||||
|
||||
`gh auth status`, repo `erp-mafia/accounted`. If it fails, stop and report "environment not
|
||||
provisioned". Web access (WebSearch/WebFetch) must be available; without it, stop and report.
|
||||
|
||||
## 1. Due check (monthly semantics)
|
||||
|
||||
The scan log lives on a single pinned issue titled `Regeluppdat scan log` labeled
|
||||
`loop:regeluppdat` (create it on the first ever run). Each run ends by commenting
|
||||
`<!-- loop-regeluppdat-run: YYYY-MM -->` plus the run summary on it.
|
||||
If a marker for the **current calendar month** already exists, report "not due" and stop, unless
|
||||
the user invoked `/loop-regeluppdat` explicitly.
|
||||
|
||||
## 2. Sweep the sources
|
||||
|
||||
Cover every row; a source with nothing new is still reported as swept (silent truncation reads
|
||||
as "covered everything"). Window: since the previous run marker (default: the last 45 days),
|
||||
plus anything already announced with a future effective date.
|
||||
|
||||
| Source | What to look for |
|
||||
|---|---|
|
||||
| skatteverket.se (nyheter + rättslig vägledning, ställningstaganden) | VAT rates/rules, arbetsgivaravgifter, traktamente/milersättning amounts, basbelopp, deadline changes, AGI changes |
|
||||
| bfn.se (beslut, nya/ändrade BFNAR, remisser) | K2/K3 changes, bokföringsregler, verifikationskrav |
|
||||
| bolagsverket.se (nyheter) | filing mandates (digital årsredovisning), fees, form changes |
|
||||
| regeringen.se + riksdagen.se (propositioner, SFS) | changes to BFL, ÅRL, ML (moms), SFL, ABL, IL that touch bookkeeping, invoicing, payroll, or retention |
|
||||
| bas.se (BAS-kontogruppen) | new BAS chart year, new/removed/renamed accounts, SRU mapping changes |
|
||||
| digg.se + EU ViDA track | e-invoicing/Peppol mandates and timelines |
|
||||
| Secondary sweep: srfkonsulterna.se, far.se news | catch anything the primary sources buried |
|
||||
|
||||
Also sweep for date-triggered knowns: every January 1 and July 1 batch (rate years, basbelopp,
|
||||
skiktgränser, avgifter) and any transition our own docs promise (search the repo for hardcoded
|
||||
years/rates near their expiry).
|
||||
|
||||
**Relevance filter.** Keep a finding only if it affects: bookkeeping/verifikation rules, VAT
|
||||
(rates, rutor, reverse charge, OSS), employer contributions or payroll (AGI, förmåner,
|
||||
traktamente, semester), tax rates or deadlines, chart of accounts/SRU, year-end or
|
||||
årsredovisning, SIE/INK2/NE formats, e-invoicing, document retention, or company law that our
|
||||
flows encode. General business news is out of scope.
|
||||
|
||||
## 3. Verify against the codebase (the actual value of this loop)
|
||||
|
||||
For each surviving finding, before filing anything:
|
||||
|
||||
1. **Load the matching `swedish-*` skill** for the domain (vat, payroll, year-end-closing,
|
||||
financial-reporting, e-invoicing, ...). Never assess Swedish domain questions from training
|
||||
data.
|
||||
2. **Grep the anchors** to see whether the change is already handled:
|
||||
- VAT: `lib/vat/`, `lib/bookkeeping/vat-entries.ts`, `lib/reports/` (moms), momsdeklaration rutor
|
||||
- Payroll/rates: `lib/salary/calculation-engine.ts`, `lib/salary/lonevaxling.ts`, `lib/salary/semesterberedning.ts`
|
||||
- Deadlines: `lib/tax/deadline-config.ts`, `lib/deadlines/`, `lib/tax/swedish-holidays.ts`
|
||||
- Chart of accounts: `lib/bookkeeping/bas-data/` (currently BAS 2026), `bas-data/sru-mapping.ts`
|
||||
- Year-end/bokslut: `lib/bokslut/`, `lib/core/bookkeeping/year-end-service.ts` (bolagsskatt)
|
||||
- Formats: `lib/reports/` (SIE, INK2, NE-bilaga, SRU), `lib/skatteverket/`
|
||||
- E-invoicing/Peppol: the `swedish-e-invoicing` skill documents our current stance
|
||||
3. Check `DECISIONS.md` and search issues **state:all** for the same change: it may already be
|
||||
tracked, done, or explicitly declined.
|
||||
4. Classify: **handled** (code already correct, note it in the run summary), **gap** (file a
|
||||
ticket), or **unclear** (file a ticket flagged for founder judgment; do not guess).
|
||||
|
||||
The `.claude/skills/swedish-*` skills are themselves a compliance surface: if a law changed,
|
||||
the skill text is now stale. A confirmed change therefore usually yields a ticket with two
|
||||
checkboxes: update the code AND update the affected skill.
|
||||
|
||||
## 4. File tickets (cap: 6 new issues per run)
|
||||
|
||||
Fingerprint = the change's official identifier (SFS number, BFNAR number, Skatteverket dnr, or
|
||||
`<source-domain>/<slug>` when nothing better exists) plus the effective year, never a timestamp.
|
||||
Dedupe per `.claude/loops.md` (search state:all, comment instead of duplicate, reopen if
|
||||
recurring). Then file:
|
||||
|
||||
- Title: `regeluppdat: <short description> (effective <date>)`
|
||||
- Body: what changed (with source links), who it affects (EF/AB, which flows), what the code
|
||||
does today (file:line evidence from step 3), suggested change, effective date, and
|
||||
`<!-- loop-fingerprint: regeluppdat-<id> -->`.
|
||||
- Labels: `loop:auto`, `loop:regeluppdat`, plus `bug` if we are already non-compliant today or
|
||||
`enhancement` if the change is upcoming.
|
||||
- Anything already in force that we get wrong also gets `loop:needs-human` (compliance exposure
|
||||
is a founder call, not a backlog item).
|
||||
|
||||
If the sweep yields more than 6 gaps, file the 6 with the earliest effective dates and list the
|
||||
rest in the run summary so the next run (or a human) picks them up.
|
||||
|
||||
## 5. Run marker + report
|
||||
|
||||
Comment on the `Regeluppdat scan log` issue: `<!-- loop-regeluppdat-run: YYYY-MM -->`, sources
|
||||
swept, findings kept/discarded by the relevance filter, handled vs gap vs unclear counts, issues
|
||||
filed (numbers), overflow list. That summary is the completion notification.
|
||||
|
||||
## Anti-thrash
|
||||
|
||||
Never re-file a fingerprint that a human closed without action: closed + `wontfix` (or a closing
|
||||
comment declining it) means the decision is made; note it in the run summary instead. If the
|
||||
same finding keeps resurfacing across sources, one ticket, many source links.
|
||||
Reference in New Issue
Block a user