Why:
After the repository moves forward, updating a running service meant
manually figuring out the new commit, rebuilding and redeploying by hand;
the user asked for the tool to detect and update automatically.
Evidence:
- "update [name...] [--check] [--all] [--no-fetch]": fetches origin,
compares each service's registered source against the latest (Dev repo
origin/main for local@/dev@ sources, newest Releases version for
release@), prints a per-service table (current / latest / state), and
without --check refreshes outdated running services by reusing the full
deploy pipeline (stop → build/verify → start → health check; persisted
exec= config and tags survive; update events appended to history).
- local@ services: auto git pull --ff-only origin main + make sync before
rebuilding, guarded three ways (current branch == main, clean worktree,
ff-only) — dirty trees or non-main branches are skipped with a warning,
never touching uncommitted work.
- dev@ sources go straight to the remote main commit via an isolated
worktree; release@ sources deploy the newest Releases version with
sha256 verification; postgres and static --dir services are reported as
not applicable; sources that diverged from or lead the remote are never
force-updated.
- Verified end to end with five test services: dev@73b2802 → dev@ffda6d3
(real isolated build, HTTP 200, tag/port preserved, worktree recycled),
non-main-branch skip, stopped-service warning vs --all, release
sha256-verified update, --check read-only (svc.env checksums unchanged),
unregistered name exits 2.
- Fixed en route: a latent bug from the v2 refactor — materialize_dev_commit
leaked submodule/make stdout into the captured worktree path, breaking
every real dev@sha deploy since (cp: File name too long); build output
now goes to stderr. Also fixed an unbound-variable crash in the update
loop (local statement self-reference under set -u).
Impact:
New read-mostly operational command; no behavior change to existing
commands except dev@sha deploys now actually working end to end.
Makefile gains svc-update; --help and docs/svc.md document the command.
Refs: .task/feature-svc-auto-update.md(任务卡=PR 描述)
Why:
svc.sh grew into a three-kind service manager with database operations,
but its only documentation was a flat --help wall of text; the user asked
for a complete usage document and a better help experience.
Evidence:
- docs/svc.md: 14-section manual (prerequisites, quick start, concepts
and directory layout, per-command reference for service/global/db
commands, deploy sources, database operations handbook, configuration
layering, registry fields and event types, safety boundaries, exit
codes, troubleshooting table, recipe index) — every claim cross-checked
against the implementation from the previous two rounds.
- Help layering: no-args and -h print a one-screen overview; --help
renders the header comment reorganized into 7 labeled sections; the new
"help [topic]" command deep-dives eight topics (model, deploy,
lifecycle, db, dbops, config, examples, safety); usage errors now show
the short overview plus a pointer to --help / help / docs/svc.md.
All topics verified to render; unknown topic exits 2.
- No runtime behavior changed: end-to-end db lifecycle regression
(create → start → backup → status → stop → rm --purge) passes after the
refactor.
- Makefile help and README's documentation section point to docs/svc.md.
Impact:
Documentation-only plus display-layer changes; command semantics,
exit codes and file formats are untouched. Maintenance note: behavior
changes must now update both --help and docs/svc.md.
Refs: .task/feature-svc-docs-help.md(任务卡=PR 描述)
Why:
The service manager could create and run postgres instances but had no
way to retarget a port explicitly, back data up, clone an instance to
another port, or move a database between instances — the user asked for
exactly these operations.
Evidence:
- "set <name> [--port N] [--host H] [--apply] [--tag T...]": explicit
registration change; running postgres requires --apply (stop → rewrite
registration → start); port availability checked against both
registrations and live listeners. Verified: 8890→8895 with data intact
and DSN updated, old port freed; refusal without --apply.
- "db backup <name> [--db X] [--out F]": pg_dump -Fc into
runtime/<name>/backups/ (0600) with pg_restore --list self-check;
"db backups" lists them.
- "db restore <name> --file F [--db X] [--force]": restores into a new db
or refuses on a non-empty target unless --force (drop with FORCE +
recreate + restore), reporting table counts afterwards. Verified all
three paths.
- "db clone <src> <dst> [--port P] [--password PW] [--tag] [--online]":
default physical clone (brief source stop, cp -a of pgdata minus
postmaster files, new registration, source restarted on its own port);
--online copies via dump/restore without stopping the source (verified
source pid unchanged). Clone lands on the requested or derived port.
- "db copy <src> <dst> [--db X] [--force]": single-database logical move
between two running instances with the same non-empty-target guard.
"db ls <name>" lists databases.
- Fixed during verification: clone/copy leaked the CLI --port into the
source restart path (source briefly came up on the clone's port); the
address overrides are now cleared inside those commands.
Impact:
All operations stay inside runtime/<name>/, append to history.jsonl, and
reuse the existing safety constraints (own-process stop, port conflicts
reported not seized, secrets in 0600 files). Overwriting a non-empty
database requires an explicit --force everywhere. Makefile help line
mentions the new db subcommands.
Refs: .task/feature-svc-db-ops.md(任务卡=PR 描述)
Why:
svc.sh only managed agent-system environments; databases, standalone
frontend serving, per-service tags and reusable start configs were out of
scope, and the user asked for a unified service lifecycle on this host.
Evidence:
- Service model v2: kind=agent|static|postgres registered in
runtime/<name>/svc.env (dotenv, mode 600); legacy entries without kind=
are adopted as agent.
- postgres: "db create <name> --port/--user/--password/--db/--auth-mode/
--tag" runs initdb (scram-sha-256, pwfile 0600), start/stop/restart use
pg_ctl (-m fast, never SIGKILL on postmaster) with pg_isready checks and
first-start database creation; "db dsn <name> [--reveal]" prints a
composable DSN (password masked by default). Verified end to end against
a real postgresql@18 instance: data survives restart, wrong passwords
rejected, stop/restart clean.
- static: "deploy --kind static --from dev[@sha]|local" builds frontend
dist into htdocs/ in an isolated worktree, or "--dir PATH" serves an
existing directory; verified serving HTTP 200, restart, stop, dry-run
plan, and explicit rejection of release sources.
- agent: unchanged three deploy sources plus --tag; bare start/restart now
reuses the persisted exec= from svc.env (verified: same custom port and
args across restart); old binaries lacking --dsn/--auth get those flags
auto-omitted with a warning.
- "list|status [--tag]" shows NAME/KIND/STATE/PID/ADDR/SOURCE/TAGS for all
registered services; "<name> status" gives per-kind detail; "rm <name>
[--purge]" unregisters (and optionally deletes) only runtime/<name>/.
- Fixed during verification: psql arg mangling (-c -c), tags lost because
record_state read svc.env inside its own redirect block, postgres
--version field parsing, unbound vars for unregistered names.
Impact:
Same invariants as before: writes stay under runtime/<name>/ and
runtime/.worktrees/ (only --from local also writes main-checkout build
outputs, all gitignored), stops only own pidfile+cmdline-verified
processes, port conflicts reported not seized. Prompt output moved to
stderr so stdout (tables, DSNs) stays pipeable. Makefile gains svc-list.
Refs: .task/feature-svc-services-registry.md(任务卡=PR 描述)
Why:
svc.sh only knew two hardcoded environments (dev/prod) and always built
the current workspace, so there was no way to deploy a specific Dev- repo
commit or a Releases artifact, and no record of what was actually started.
Evidence:
- scripts/svc.sh commits dev|release lists deployable commits (release
entries annotated with version + devCommit; both mark HEAD and the
environments already running them).
- deploy --from local|dev@<sha>|release@<version|sha> materializes the
chosen source: dev commits build in an isolated worktree
(runtime/.worktrees/<sha>, submodule pins verified to land exactly),
release artifacts are sha256-verified against versions/<v>/manifest.json
(a forged manifest is refused); --pick offers an interactive numbered
choice and --dry-run prints the plan.
- Environment names are generalized (^[a-z][a-z0-9-]{0,15}$, derived port
8800-9099 on collision-free scan); each env is self-contained under
runtime/<env>/{bin,plugins,data} with svc.env (current state) and
history.jsonl (append-only deploy/start/stop events). status scans all
registered envs and history [env] [N] merges the event stream.
- Old artifacts that predate --dsn/--auth have those flags auto-omitted
(probed via serve usage); legacy svc.env files still render in status.
- Verified end to end in the task worktree: stub-binary lifecycle, a real
release@0.1.0 deploy (HTTP 200, source shown in status, commits marked),
failure paths (sha mismatch, immediate exit, non-TTY --pick).
Impact:
scripts/svc.sh users gain multi-version side-by-side environments and
rollback by redeploying an older source; existing dev/prod defaults and
safety constraints (own-pidfile-only stop, port-occupancy check) are
unchanged. Environments deployed by the previous script need one fresh
deploy before start/restart works again. Makefile gains svc-commits and
svc-history targets.
Refs: .task/feature-svc-deploy-source-registry.md(任务卡=PR 描述)
Why: the manager only took configuration from SVC_* environment
variables; binding a public interface or pointing at a database meant
exporting vars first, which is awkward for one-off deploys and
invisible in shell history review.
Evidence: live matrix on the worktree - --port (8801), --host 0.0.0.0
(bind verified via ss, health probe auto-maps to 127.0.0.1), --addr
whole-form, CLI-over-env precedence (--port beats SVC_DEV_ADDR),
--mock reaching the server cmdline, --dsn passthrough proven by the
postgres connect attempt in the log, -- passthrough (--artifacts), and
make svc ARGS passthrough. A set -e silent-exit trap in env_config was
caught by the first run and fixed (condition-as-last-statement).
Impact: scripts/svc.sh <env> [action] [--addr|--host|--port|--data|
--dsn|--auth|--mock|--plugins| -- <extra>]; precedence CLI > env vars >
defaults; help rewritten with examples; make svc gains ARGS; logs keeps
the positional line-count form. The real-world port guard held: a dev
instance started from the main checkout was refused with owner info,
untouched.
Refs: feature/svc-cli-args task card.
Why: deploying a new build meant manually finding and killing the old
process, rebuilding and re-launching with the right flags; dev and prod
instances could collide on ports, data dirs or kill each other.
Evidence: live matrix on the worktree - dev deploy (stop -> make build
-> start with health check), redeploy swaps the pid and keeps seeded
data, prod deploys alongside dev on separate port/data with fully
isolated /api/projects, port takeover is refused with the owning
process named while the running env stays up, stop clears pidfile and
frees the port. A pid-capture bug (compound-background subshell wrote
the wrapper pid instead of the server) was caught and fixed during the
first live run.
Impact: scripts/svc.sh <dev|prod> [deploy|stop|start|restart|status|
logs]; runtime/<env>/{data,svc.pid,svc.log,svc.env} isolation; defaults
dev 127.0.0.1:8788 / prod 127.0.0.1:8787 overridable via SVC_* env
vars (incl. SVC_PROD_DSN for the PG tier); make svc ENV=... shortcut;
runtime/ gitignored. Safety: only processes started by the script are
stopped (pidfile + /proc cmdline double check).
Refs: feature/svc-env-script task card.
Why: 11 merged branches had left their task cards open (bookkeeping debt found on 2026-09-30); closure was manual and never triggered, so card status rotted after each merge.
Evidence: dogfood run of `task.sh close --fold` closed and archived the 11 cards (previous commit); temp-repo matrix 33/33 PASS covering detection, pending-branch idempotence, dirty/no-fold refusals, post-merge auto-close on merge-type pulls, worktree guard, and the full lifecycle (closure PR merge archives cards on main).
Impact: scripts/task.sh (+close, precheck [cards] report, finish-tail trigger), scripts/hooks/post-merge (new), Makefile (+task-close target, setup chmod), AGENTS.md rule; archive fixes correct PR links missed by the first dogfood run; no module or gitlink change.
Refs: task card feature/card-autoclose-r1
Why: Provider repos merged for T1 (core PR#2, agent-command PR#2, frontend
PR#3, ui PR#3, documents PR#9); gitlinks pinned to those mains. The
task-finish guard prevents a repeat of the T0 incident where running
finish from the main checkout deleted it (scripts/task.sh treated $PWD as
the worktree to remove).
Evidence: make test green on the pinned combo — core, backend 13 pkgs,
frontend 9, ui 6, agent-command (vet + tests incl. conformance); make build
produces the binary; combo version string now includes agentcmd SHA.
Impact: combo grows its version traceability; AGENTS.md documents the
worktree-cwd requirement for finish.
Refs: modules/documents docs/records/2026-09-30-t1-core-cards-commands-verification.md
Why: Foundation task T0 of the UI/interaction base (proposal 2026-09-30,
D25-D33): add the new UI component library and command-bus ecosystem repos,
extend build/test/pin orchestration to twelve submodules.
Evidence: make test green on the pinned combo (core + backend 13 pkgs +
frontend 9 + ui 6 tests); make build produces bin/agent-system with combo
version string including ui; make contracts now generates to frontend and ui
with submodules staying clean; renderer/tokens migration verified
CSS-rule-identical (zero behavior change).
Impact: New modules/ui and deps/agent-command gitlinks; frontend consumes ui
via source alias; backend vendors agent-command (replace only, no import
yet); follows provider-first merges (ui PR#1/#2, agent-command PR#1,
documents PR#8, frontend PR#2, backend PR#6).
Refs: modules/documents docs/proposals/2026-09-30-ui-inbox-command-workspace.md
Why: PR test evidence depended on the agent's word (single point); the
running binary could not answer what combination it was built from; and
irreversible operations lacked an explicit agent-stops-here boundary.
Evidence: Gitea runner online (home-runner, ubuntu-latest+docker); make
version prints describe+component SHAs; two-key line maps to baseline hard
boundary 1. CI first run observed on this PR before merge (see Actions tab).
Impact: .gitea/workflows/ci.yaml runs make test + make build on PR/push;
Makefile injects main.version (make version / agent-system version);
AGENTS.md and adoption register the two-key rule. No product code change.
Refs: policy/adoption.md; release-operations requirements
Why: agents skipped triggered reading and wrote into shared checkouts holding
other tasks' in-flight changes; the 2026-09-30 empty-list incident session
found 24 overlapping in-flight files only by accident at first write.
Evidence: positive/negative suite in a real scratch repo passed 19/19
(precheck READY/BUSY/SYNC_REQUIRED, start refuse/establish, hook main/badname/
longsubject refusals, sync checkpoint, finish recycle and abandon double-
confirm); the suite caught three real defects (pipefail empty-grep abort,
worktree gitdir COMMIT_EDITMSG resolution, message checks in the wrong hook
phase).
Impact: make setup enables pre-commit/commit-msg hooks; make precheck/task-
start/task-sync/task-finish provide the governed path; no existing workflow
removed. AGENTS.md carries the resident gate rules.
Refs: Development-Guidelines 0.1.0-draft.7; task card .task/delivery-mechanism.md