oficina / meet Public

Merge remote-tracking branch 'origin/main' into feat/native-start-meeting

Paul Campbell pushed by paul@keymail.dev a262bba3135cb327af44a2eb8b4598ad7c798b10 parent 4320285parent 6440c84
2 files changed, +79 −5
  • AGENTS.md +39 −0
  • internal/calls/credits_inventory_test.go +40 −5

CI failed — run details

diff --git a/AGENTS.md b/AGENTS.md
index d658e1c..5420d63 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -122,3 +122,42 @@ Edit any line. **This file is the source of truth, not the profile name.**
The profile only seeded this list at scaffold time; nothing re-reads it, so
changing a line here changes what the app does. A convention deleted from
this file is not a convention to reinstate.
+
+## Work in a worktree, and catch main up first
+
+Take a worktree for the work. Do not work in the shared checkout.
+
+ git -C <repo> fetch origin
+ git -C <repo> worktree add ../<repo>-<task> -b <branch> origin/main
+
+Both halves matter, for different reasons.
+
+**The worktree**, because several sessions share this machine and some
+share this repository, and a checkout has one HEAD, one index and one
+working tree between all of them. A branch switch under somebody else's
+uncommitted work destroys it with nothing on screen to say so, and two
+sessions editing the same paths disagree quietly instead of conflicting
+loudly. A worktree gives the work its own HEAD and its own files, and
+costs a directory.
+
+**From `origin/main`, after a fetch** — not from whatever the checkout
+happens to be sitting on. A branch cut from a stale main is a merge
+conflict scheduled for later, against files somebody else has renamed in
+the meantime. A branch cut from another task's branch is worse: it
+carries that task's commits into yours, where they are reviewed as
+though you wrote them, and the two can only be separated by hand.
+
+Remove it when the work has landed — `git worktree remove` — so that
+`git worktree list` keeps saying something true.
+
+Written down because of 2026-09-17, in this repository, while it was
+still called `meet`. The shared checkout was
+sitting on one task's branch while holding a *different* task's feature
+uncommitted on disk — some four hundred lines, a new migration among
+them — put there by a session nobody else knew was working in that tree.
+Two other sessions were meanwhile coordinating over the same checkout by
+asking each other not to touch it, which is the arrangement a worktree
+makes unnecessary. Nothing was lost. Nothing about the arrangement would
+have said so if it had been, and by then the same feature existed twice,
+once on each side of a rename, because the two sessions had branched
+from different starting points.
diff --git a/internal/calls/credits_inventory_test.go b/internal/calls/credits_inventory_test.go
index d1cfff2..458deac 100644
--- a/internal/calls/credits_inventory_test.go
+++ b/internal/calls/credits_inventory_test.go
@@ -20,6 +20,32 @@ import (
// Check the production package graph, not the test binary's dependencies.
// A dependency or notice change must update the embedded attribution too.
func TestCreditsMatchProductionDependencies(t *testing.T) {
+ // Read the embedded inventory first, and check the toolchain against
+ // it before anything else runs. Everything below depends on this
+ // binary being the Go the notices were generated with:
+ // runtime.Version() goes into the expected inventory, and
+ // runtime.GOROOT()/LICENSE is read out of it.
+ //
+ // Under any other toolchain the failure is real and says nothing
+ // about credits, and it surfaces two different misleading ways. A
+ // Homebrew keg without LICENSE at GOROOT fails the read with a bare
+ // file error that never mentions credits at all. A keg with it gets
+ // past that and fails the comparison instead, whose message sends
+ // you to `make credits` — which would rewrite notices.json to name
+ // whatever toolchain you happen to be on, burying the dependency
+ // inventory the test exists to protect. Both cost a session a while
+ // before this guard was here.
+ var embedded creditInventory
+ if err := json.Unmarshal(creditNotices, &embedded); err != nil {
+ t.Fatal(err)
+ }
+ if want := embeddedGoVersion(embedded); want != "" && want != runtime.Version() {
+ t.Fatalf("credits were generated with %s, this test is running under %s.\n"+
+ "Run `GOTOOLCHAIN=%s make ci`. Do not run `make credits` to fix this: it would\n"+
+ "rewrite notices.json to name this toolchain instead of checking the inventory.",
+ want, runtime.Version(), want)
+ }
+
ctx, cancel := context.WithTimeout(t.Context(), 45*time.Second)
defer cancel()
command := exec.CommandContext(ctx, "go", "list", "-deps", "-json", "./cmd/calls")
@@ -44,13 +70,22 @@ func TestCreditsMatchProductionDependencies(t *testing.T) {
}
inventory.Components = append(inventory.Components, credits.Lucide(), credits.Component{Name: "Go", Version: runtime.Version(), SourceURL: "https://go.dev/LICENSE", Notices: []credits.Notice{{Name: "LICENSE", Text: string(goLicense)}}})
sort.Slice(inventory.Components, func(i, j int) bool { return inventory.Components[i].Name < inventory.Components[j].Name })
- var embedded creditInventory
- if err := json.Unmarshal(creditNotices, &embedded); err != nil {
- t.Fatal(err)
- }
expected, _ := json.Marshal(creditInventory{Components: inventory.Components, MissingNotices: inventory.MissingNotices})
actual, _ := json.Marshal(embedded)
if !bytes.Equal(expected, actual) {
- t.Fatal("embedded credits differ from production dependencies; run make credits")
+ t.Fatal("embedded credits differ from production dependencies; run make credits " +
+ "(the toolchain is already ruled out by the check at the top of this test)")
+ }
+}
+
+// embeddedGoVersion is the Go toolchain the embedded notices name, or ""
+// when they name none — in which case there is nothing to compare against
+// and the test proceeds exactly as it did before the guard existed.
+func embeddedGoVersion(inventory creditInventory) string {
+ for _, component := range inventory.Components {
+ if component.Name == "Go" {
+ return component.Version
+ }
}
+ return ""
}