Skip to content

feat(stack): extension stacklets and stack update - #102

Merged
arthware-dev merged 11 commits into
mainfrom
feat/extension-stacklets
Sep 21, 2026
Merged

arthware-dev merged 11 commits into
mainfrom
feat/extension-stacklets

Conversation

@arthware-dev

@arthware-dev arthware-dev commented Sep 20, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Stacklets can live outside the repo, in ~/famstack-extensions by default, and a stacklet can declare a stage so an unfinished one says so. stack list and stack status group extensions in their own section, and every stacklet reports source and path so a caller never infers an origin from a path.
  • ./stack update moves the checkout between releases. It sets local edits aside and puts them back, winds the whole update back if they collide with the release, and names what to restart rather than restarting it: new code on disk is not new code running, and when a family loses a service is the admin's call.
  • Every container is stamped at stack up with the commit it started from (stack.commit), so stack list, stack doctor and a bare ./stack restart can tell when running code is behind the checkout. A container with no stamp is reported as unknown, never guessed about.

Also in here, because the update work exposed them: a written commit convention with tools/commit-lint enforcing it in the commit-msg hook and in CI, tools/init-repo for one-time clone setup, RFC-002 on installation and updates, and a corrected stack up description in the operator docs (it force-recreates; the docs claimed otherwise).

Scope added after review started, declared here rather than silently: the test lanes are consolidated to one name per lane (eighteen phony targets became six, and test-all ran only the lifecycle lane), with make printing a table of what each costs, what it needs and whether it is exclusive.

Larger than the one-PR-one-entry rule this branch itself introduces. Happy to split it into extensions / update / stamping if that reads better.

Upgrade: containers created before this release carry no stamp, so ./stack list reports "cannot tell" for them until each stacklet is next started. Run ./stack restart all once to stamp everything, or let it happen stacklet by stacklet.

Add a commit message section to docs/agent/dev.md, based on
Conventional Commits and the Git and Linux kernel guidelines:

- the allowed types and scopes, and how each one renders in the
  release notes
- imperative subjects of at most 72 characters
- a body that explains what changed and why, with guidance per type
- the `Upgrade:` and `BREAKING CHANGE:` footers

Release notes are generated from commit subjects, so every subject
must parse. AGENTS.md links to the section.
Add `tools/commit-lint`, which checks commit headers against the format
in docs/agent/dev.md. It runs in:

- the `commit-msg` hook
- CI, on the PR title and on every commit in the PR
- the release gate, on the range being tagged

Scopes are only checked for types that appear in the changelog. CI
prints the number of checked subjects and fails on an empty range. A
test verifies that the type and scope lists match dev.md.

Scopes are now stacklet ids and bot names, rendered as "<id> Stacklet"
and "<name> Bot".
Add `[core] extension_dirs`, a list of directories that are searched
after `stacklets/` for stacklets that do not ship with the release. The
default is `~/famstack-extensions`. The directory is not created
automatically, and configured paths that do not exist are skipped.

- Hooks, CLI plugins, secrets and env templates work the same way as
  for built-in stacklets. On an id clash the repo wins, then the first
  extension directory in the list.
- Add a `stage` manifest field (`beta`, `incubating`). `stack list`
  shows it and `stack up` prints a warning.
- `stack list` and `stack status` show extensions in a separate section,
  including the directory each one was loaded from.
- The bot runner mounts the first extension directory. `stack up` warns
  when a stacklet in another directory ships a bot.

Extensions are kept outside the repo and the data dir because the repo
is replaced on update and the data dir is deleted by `stack uninstall`.
They are not covered by the backup instructions for `~/famstack-data`.
Describe the limitations of the current git checkout install model and
the options for packaging, updates and multiple instances per Mac.

Rewrite the update procedure in the admin guide:

- switch between release tags instead of running `git pull`
- stash local changes before switching, and recover from conflicts
- do not run `git pull origin main`, which moves the instance to
  unreleased code

Remove `./stack updates` from the operator docs. The command does not
exist.

Also write `[core] name` to `stack.toml` during installation.
Add `stack update [<tag>]`. It fetches release tags from all remotes,
shows the current and target release, and checks out the target tag.
`--dry-run` shows the plan, `--yes` skips the confirmation.

- No services are restarted. The command lists the stacklets that need
  a restart: running stacklets with changed files, or all running
  stacklets if `lib/` changed.
- Local changes are stashed and re-applied. If they conflict with the
  release, the update is rolled back and the changes are restored.
- A checkout that is already ahead of the newest release is not moved.
- On a branch, the output explains that the tag checkout detaches HEAD
  and how to switch back. On a fork without release tags, the error
  lists the remotes it checked and how to add the upstream remote.
Label each container with the commit it was started from
(`stack.commit`) during `stack up`.

- `stack list` marks stacklets whose containers run an older commit.
- `stack doctor` reports them.
- `stack restart` without arguments restarts only those stacklets.

Containers without the label are reported as unknown.

Upgrade: containers created before this release have no label. Run
`./stack restart all` once, or restart each stacklet when convenient.
Add a script that checks for Python 3.11+ and uv, runs `uv sync`,
installs the language server and sets `core.hooksPath`. It can be run
again safely.

Git does not let a repository enable its own hooks on clone, so the
`lint`, `typecheck` and `test-unit` make targets run
`tools/init-repo --hooks-only` first. An existing `core.hooksPath` is
not changed.
Keep one make target per test lane and remove the aliases.

- `make` without a target prints each lane's duration, requirements,
  and whether it uses fixed container names and ports.
- Add `make lint` for the whole tree.
- Recommend `make test-unit` before a push instead of before every
  commit.

Move the details to tests/README.md.
`stack doctor` compared a container's environment with the stacklet's
rendered `.env`. Services that set a different value in their compose
file, such as a container path or an internal hostname, were reported
as drifted after every recreate.

Compare with the environment that `docker compose config` resolves for
the service. This also removes the special cases for image defaults and
for variables a service does not receive.
Derive the version from `git describe --tags --dirty` instead of the
`VERSION` constant, for example `0.3.0-beta.3-104-g9a5bdc9-dirty`.
`version`, `status`, `list`, `doctor` and `update` print the same value.

- `stack doctor` prints the version and warns when a newer release tag
  exists locally. It does not fetch.
- `stack update` prints the current version first, and shows how many
  commits the checkout is ahead of the newest release.

The release gate still uses `VERSION`. If it differs from the tag, both
values are shown.
Prefix suggested fixes with "to fix:" and print stack commands with the
`./` prefix, for example `./stack restart memory`. The JSON output keeps
the command without the prefix.
@arthware-dev
arthware-dev force-pushed the feat/extension-stacklets branch from 255b777 to 21e8861 Compare September 21, 2026 11:22
@arthware-dev
arthware-dev merged commit 2282674 into main Sep 21, 2026
1 check passed
@arthware-dev
arthware-dev deleted the feat/extension-stacklets branch October 7, 2026 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant