Commit graph

3806 commits

Author SHA1 Message Date
Lex Christopherson
ef9a38c802 2.43.0-next.6 2026-03-24 07:43:26 -06:00
Lex Christopherson
cfc377fd9b fix(gsd): use correct notify severity type ("warning" not "warn")
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:43:17 -06:00
Jeremy McSpadden
9153506fba chore(contrib): add CODEOWNERS and team workflow docs (#2286)
* chore(contrib): add commit-msg hook, CODEOWNERS, team workflow docs

- Extend install-hooks.sh with commit-msg hook that enforces
  Conventional Commits format on every commit
- Add .github/CODEOWNERS mapping packages, CI, scripts, and
  security-sensitive files to @gsd-build/maintainers
- CONTRIBUTING.md: add Branching and commits section with naming
  convention, commit format, and rebase guidance
- CONTRIBUTING.md: add Working with GSD section covering mode: team,
  unique milestone IDs, and worktree isolation for multi-dev workflows
- CONTRIBUTING.md: surface npm run secret-scan:install-hook in Local
  development with explanation of both hooks it installs
- CONTRIBUTING.md: align AI disclosure section — no AI tool authorship
  in commits, Draft PR requirement for multi-phase agent work

* chore: remove install-hooks.sh — local git hook installation is too intrusive for a contributor PR
2026-03-24 07:35:40 -06:00
Tom Boucher
7a413bb84f fix(web): resolve compiled .js modules for all subprocess calls under node_modules (#2320)
Node v24 unconditionally refuses .ts files under node_modules/ — even
with --experimental-transform-types. When GSD is installed globally via
npm, every web service subprocess that loads a .ts extension module
crashes with ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING.

Add resolveSubprocessModule() and buildSubprocessPrefixArgs() to
ts-subprocess-flags.ts. When packageRoot is under node_modules/ and the
compiled dist/*.js file exists, subprocess calls use the compiled JS
directly without TS flags or the resolve-ts.mjs loader.

Updated all 14 web service files: auto-dashboard, bridge, captures,
cleanup, doctor, export, forensics, history, hooks, recovery-diagnostics,
settings, skill-health, undo, and visualizer.

Fixes #2279

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:34:41 -06:00
Tom Boucher
a0c0896a75 fix(test): increase perf assertion threshold to prevent CI flake (#2327)
The `deriveStateFromDb() <1ms` assertion failed at 1.050ms on GitHub
Actions runners under load. Increased threshold to 10ms — still catches
real regressions (10x) without flaking on CI jitter.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:32:42 -06:00
mastertyko
7ecf87829d fix: add missing SQLite WAL sidecars and journal to runtime exclusion lists (#2299)
gsd.db-shm, gsd.db-wal, journal/, and doctor-history.jsonl are always
created alongside gsd.db during normal operation but were missing from
both RUNTIME_EXCLUSION_PATHS (git-service.ts) and GSD_RUNTIME_PATTERNS
(gitignore.ts). This caused them to be staged by nativeAddAllWithExclusions,
left untracked by untrackRuntimeFiles, and omitted from .gitignore by
ensureGitignore — leading to squash merge failures when these files were
tracked and modified during milestone execution.

Closes #2296
2026-03-24 07:31:48 -06:00
Lex Christopherson
dd96ad3002 2.43.0-next.5 2026-03-24 07:30:49 -06:00
Lex Christopherson
7ca3ce04a4 fix(gsd): remove stale observability validator + fix greenfield worktree check
The observability validator checked for markdown headings (## Observability / Diagnostics,
## Observability Impact) that the DB-backed renderer never produces, causing false-positive
warnings on every dispatch. Removed entirely — the DB schema enforces structure at write time.

The worktree health check blocked execution in directories without recognized project files
(package.json, Cargo.toml, etc.), preventing greenfield projects from scaffolding. Downgraded
to a warning — .git check remains as the hard gate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:27:48 -06:00
Jeremy McSpadden
867a4be297 fix(memory): fix memory and resource leaks across TUI, LSP, DB, and automation (#2314)
* fix(memory): fix memory and resource leaks across TUI, LSP, DB, and automation

Addresses all findings from a systematic memory leak audit across five
dimensions: event listeners, timers, file system handles, subscriptions/
closures, and GSD automation lifecycle.

Critical fixes:

rpc-client.ts: stderr .on("data") handler attached in start() was never
removed in stop(). Now stored as _stderrHandler and removed via
removeListener() on stop.

lsp/client.ts: Three process.on() handlers (beforeExit, SIGINT, SIGTERM)
registered at module load time with anonymous functions — impossible to
remove. Now stored as named references; new removeProcessHandlers() export
allows graceful teardown. stdout/stderr stream listeners in
startMessageReader/startStderrReader also stored per-client in
clientStreamHandlers map and removed in shutdownClient() and shutdownAll().

parallel-orchestrator.ts: spawnWorker() attached 5 listeners to child
process streams on every spawn with no removal on worker stop/respawn,
accumulating listeners indefinitely. Added cleanup() field to WorkerInfo;
called via removeAllListeners() on exit, graceful stop, stale detection,
and dead PID cleanup paths. Also: module-level state.workers Map was never
cleared between orchestration runs; startParallel() and resetOrchestrator()
now iterate and clean up all WorkerInfo entries before reassigning state.

scripts/watch-resources.js: fs.watch() return value was discarded (OS
watcher never closed) and the fallback setInterval handle was also
discarded (timer ran forever). Both now stored; process.on("exit") handler
closes/clears them.

gsd-db.ts: closeDatabase() did not checkpoint the WAL before closing —
.db-shm/.db-wal files accumulated on disk across crash-recovery cycles.
Now runs PRAGMA wal_checkpoint(TRUNCATE) before close. Also added a
one-time process.on("exit") handler in openDatabase() so the handle is
always closed even on unclean exits.

Medium fixes:

bg-shell/overlay.ts: 1-second refresh setInterval only cleared in
keyboard exit handler; abnormal teardown leaked the timer. Added dispose()
method that unconditionally clears it.

file-watcher.ts: pending debounce Map was scoped inside startFileWatcher()
making it inaccessible to stopFileWatcher(). Moved to module scope;
stopFileWatcher() now clears all pending timers and empties the map before
closing the watcher.

auto-supervisor.ts: registerSigtermHandler() could accumulate multiple
SIGTERM handlers if called without passing back the previous reference.
Added module-level _currentSigtermHandler; old handler is always removed
before registering the new one regardless of whether caller passes it.

Low-severity fixes:

print-mode.ts: session.subscribe() return value was discarded. Now stored
and called in a finally block to guarantee cleanup on both normal
completion and errors.

rpc-mode.ts: same — subscribe() unsubscribe now called in the shutdown
path before process.exit().

theme.ts: onThemeChangeCallback singleton silently overwrote any previous
subscriber. Converted to Set<() => void>; onThemeChange() now returns a
cleanup function. All four internal call sites updated to forEach().
Backward-compatible — existing callers that discard the return are unaffected.

* fix: ensure unsubscribe is called on error/abort in print-mode

The PR #2314 added unsubscribe storage but still called process.exit(1)
directly, bypassing the unsubscribe. Wrapped in try/finally to guarantee
cleanup runs before exit.
2026-03-24 07:23:36 -06:00
Tom Boucher
30daeeb8f4 fix(gsd): preserve freeform DECISIONS.md content on decision save (#2319)
`saveDecisionToDb` previously regenerated DECISIONS.md from DB state
unconditionally, which silently destroyed any freeform/prose content
since `parseDecisionsTable` only parses table rows.

Now detects whether the existing file is in canonical table format
(starts with "# Decisions Register" + has the standard table header).
When freeform content is detected, the original content is preserved
and a decisions table section is appended/updated at the end instead
of overwriting the entire file.

Fixes #2301

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:23:11 -06:00
Tom Boucher
6793489b78 fix(pi-ai): restore alibaba-coding-plan provider via models.custom.ts (#2350)
The alibaba-coding-plan provider (8 models) was silently dropped when
models.generated.ts was regenerated from models.dev in PR #2118. This
provider uses a proprietary DashScope endpoint not tracked by models.dev,
so regeneration removes it every time.

Add models.custom.ts for manually-maintained providers that don't exist
in models.dev. The model registry (models.ts) now merges both generated
and custom models at startup. Custom entries are additive and never
overwrite generated ones.

Restores: qwen3.5-plus, qwen3-max-2026-01-23, qwen3-coder-next,
qwen3-coder-plus, MiniMax-M2.5, glm-5, glm-4.7, kimi-k2.5

Fixes #2339

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:19:27 -06:00
Tom Boucher
eb30d3afd4 feat(gsd): show per-prompt token cost in footer behind show_token_cost preference (#2357)
Adds opt-in per-prompt cost display to the interactive footer. Users
enable it by setting `show_token_cost: true` in their preferences.md.
Disabled by default — the footer behavior is unchanged unless opted in.

Fixes #1515

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:18:57 -06:00
Tom Boucher
21f66058ad feat(web): add "Change project root" button to web UI (#2355)
Adds a visible control to change the devRoot directory from both the
project selection gate and the slide-out projects panel, so users no
longer need to hand-edit ~/.gsd/web-preferences.json.

- New /api/switch-root POST endpoint: validates path (exists, is dir),
  persists to web-preferences.json (clearing lastActiveProject), and
  returns discovered projects under the new root
- ProjectSelectionGate: shows current devRoot with "Change" link above
  the project list; also shows "Change project root" link when no
  projects are found under the current root
- ProjectsPanel: shows "Change" link next to the devRoot path in the
  slide-out header
- Both views use the existing FolderPickerDialog for directory browsing
- 17 tests covering path validation, preference persistence, tilde
  expansion, and end-to-end switch scenarios

Fixes #2264

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:18:05 -06:00
Tom Boucher
57c4939bee fix(doctor): skip false env_dependencies error in auto-worktrees (#2318)
* fix(test): increase perf assertion threshold to prevent CI flake

The `deriveStateFromDb() <1ms` assertion failed at 1.050ms on GitHub
Actions runners under load. Increased threshold to 10ms — still catches
real regressions (10x) without flaking on CI jitter.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(doctor): skip false env_dependencies error in auto-worktrees

Auto-worktrees don't have their own node_modules by design — they
symlink to the project root's copy.  The doctor environment check
now resolves the project root (via .gsd/worktrees/ path segment or
GSD_WORKTREE env var) and checks its node_modules before reporting
an error.

Fixes #2303

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 07:17:52 -06:00
mastertyko
865dae2462 fix(gsd): auto-stash dirty files before squash merge and surface dirty filenames in error (#2298)
* fix: auto-stash dirty files before squash merge and surface dirty filenames in error

Two bugs in mergeMilestoneToMain caused milestone completion to fail when
the project root had pre-existing dirty tracked files:

Bug 1 — No auto-stash: clearProjectRootStateFiles only removes untracked
.gsd/ files. Any tracked dirty file elsewhere (e.g. .planning/work-state.json
with stash conflict markers) caused `git merge --squash` to reject with
"local changes would be overwritten". Fixed by adding a stash/pop wrapper
around the squash merge — dirty files are stashed before merge and restored
after commit. Stash is also popped on all error paths so local work is never
lost.

Bug 2 — Misleading error message: nativeMergeSquash discarded the filenames
from git stderr and the caller hardcoded blame on .gsd/ regardless of which
files were actually dirty. Fixed by parsing tab-indented filenames from git
stderr into a new `dirtyFiles` field on GitMergeResult, and surfacing them
in the error message.

Closes #2151

* ci: re-trigger CI (derive-state-db perf assertion is nondeterministic on slow runners)

* review: move #2151 tests to node:test format in separate file

Per review feedback, moved Tests 20 and 21 from the script-style
auto-worktree-milestone-merge.test.ts into a new auto-stash-merge.test.ts
using node:test's test() function and assert module.
2026-03-24 07:17:26 -06:00
Lex Christopherson
d3173d6512 2.43.0-next.4 2026-03-24 06:17:53 -06:00
Lex Christopherson
c722442bb3 fix(gsd): keep params as any in db-tools executors (CI tsconfig is stricter)
Local tsconfig excludes src/resources/ but CI compiles everything.
Record<string, unknown> for params broke handler calls since handlers
expect typed params (validated at runtime). Keep params: any with
eslint-disable annotation, type all other executor params properly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 06:17:20 -06:00
Lex Christopherson
ea77a04851 2.43.0-next.3 2026-03-24 06:07:50 -06:00
Lex Christopherson
dc3fe88369 fix(gsd): replace any types in db-tools executor signatures
Tool executor lambdas now use proper types (string, Record<string, unknown>,
AbortSignal | undefined) instead of any for all parameters.
registerAlias toolDef param also properly typed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 06:06:51 -06:00
Lex Christopherson
2f7208150a fix(gsd): resolve 4 TS compilation errors from parser migration
- github-sync/sync.ts: import parseRoadmap/parsePlan from parsers-legacy
- auto-worktree.ts: replace dangling roadmap.title with getMilestone() DB query
- markdown-renderer.ts: add explicit type annotations on lazy-loaded parser callbacks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 06:04:29 -06:00
Lex Christopherson
6c1c31b91e 2.43.0-next.2 2026-03-24 05:49:23 -06:00
TÂCHES
e9e36f9568 feat(gsd): Tool-driven write-side state transitions — replace markdown mutation with atomic SQLite tool calls (#2141) 2026-03-23 14:16:32 -06:00
Lex Christopherson
1194548d61 fix(gsd): wrap plan-task DB writes in transaction + untrack .gsd/ artifacts
plan-task.ts was the only planning tool handler not wrapping its
insertTask/upsertTaskPlanning calls in a transaction(), risking partial
DB state if the upsert failed after insert. Matches the pattern used by
plan-slice, replan-slice, reassess-roadmap, and plan-milestone.

Also removes 80 .gsd/ working artifacts that were force-added despite
being in .gitignore.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:42:38 -06:00
TÂCHES
108845dd4b chore(M001): auto-commit after complete-milestone 2026-03-23 13:32:31 -06:00
Jeremy McSpadden
47bedc5540 feat: add contextual tips system for TUI and web terminal
Add a session-scoped contextual tips system that shows non-intrusive
hints when user behavior suggests they'd benefit from knowing a feature.

Tips:
- Shell command prefix: nudge when bare ls/git/npm typed without !
- Large paste: warn when >2000 char input sent to agent
- Thinking level: hint when short question with high/xhigh thinking
- Double-bang reminder: after 3+ single-! commands, suggest !!
- Compaction nudge: when context >= 70% full

Each tip fires at most N times per session, resets on /new.
Wired into both TUI (dim inline text) and web terminal (system line).
31 unit tests covering all tips, suppression, reset, and priority.
2026-03-23 14:28:50 -05:00
TÂCHES
dff941b1dc chore(M001): auto-commit after validate-milestone 2026-03-23 13:19:14 -06:00
TÂCHES
6f156ed053 chore(M001/S06): auto-commit after complete-slice 2026-03-23 13:14:14 -06:00
TÂCHES
f76fe8ec1e feat(S06/T02): Strip all 16 lazy createRequire fallback paths from migr…
- src/resources/extensions/gsd/dispatch-guard.ts
- src/resources/extensions/gsd/auto-dispatch.ts
- src/resources/extensions/gsd/auto-verification.ts
- src/resources/extensions/gsd/parallel-eligibility.ts
- src/resources/extensions/gsd/doctor.ts
- src/resources/extensions/gsd/doctor-checks.ts
- src/resources/extensions/gsd/visualizer-data.ts
- src/resources/extensions/gsd/workspace-index.ts
2026-03-23 13:09:37 -06:00
TÂCHES
56efa72886 test(S06/T01): Extract parseRoadmap/parsePlan into parsers-legacy.ts, u…
- src/resources/extensions/gsd/parsers-legacy.ts
- src/resources/extensions/gsd/files.ts
- src/resources/extensions/gsd/state.ts
- src/resources/extensions/gsd/md-importer.ts
- src/resources/extensions/gsd/commands-maintenance.ts
- src/resources/extensions/gsd/markdown-renderer.ts
- src/resources/extensions/gsd/auto-recovery.ts
- src/resources/extensions/gsd/tests/parsers.test.ts
2026-03-23 12:53:49 -06:00
github-actions[bot]
b67ba7c086 release: v2.43.0 2026-03-23 18:50:53 +00:00
TÂCHES
3af95e601b chore(M001/S06): auto-commit after plan-slice 2026-03-23 12:35:52 -06:00
TÂCHES
aabd34fdd3 chore(M001/S06): auto-commit after research-slice 2026-03-23 12:27:45 -06:00
TÂCHES
f9c4d6bedc chore(M001/S05): auto-commit after complete-slice 2026-03-23 12:23:04 -06:00
TÂCHES
460f6f3933 feat(S05/T04): Migrate remaining 6 callers (auto-prompts, auto-recovery…
- src/resources/extensions/gsd/auto-prompts.ts
- src/resources/extensions/gsd/auto-recovery.ts
- src/resources/extensions/gsd/auto-direct-dispatch.ts
- src/resources/extensions/gsd/auto-worktree.ts
- src/resources/extensions/gsd/reactive-graph.ts
- src/resources/extensions/gsd/markdown-renderer.ts
2026-03-23 12:17:23 -06:00
TÂCHES
06a876676a feat(S05/T03): Migrate 7 warm/cold callers (doctor, doctor-checks, visu…
- src/resources/extensions/gsd/doctor.ts
- src/resources/extensions/gsd/doctor-checks.ts
- src/resources/extensions/gsd/visualizer-data.ts
- src/resources/extensions/gsd/workspace-index.ts
- src/resources/extensions/gsd/dashboard-overlay.ts
- src/resources/extensions/gsd/auto-dashboard.ts
- src/resources/extensions/gsd/guided-flow.ts
2026-03-23 12:07:01 -06:00
TÂCHES
4d3ccb5b08 feat(S05/T02): Extend migrateHierarchyToDb to populate v8 planning colu…
- src/resources/extensions/gsd/md-importer.ts
- src/resources/extensions/gsd/tests/gsd-recover.test.ts
2026-03-23 11:52:46 -06:00
mastertyko
b3d12628f9 fix: prevent banner from printing twice on first run (#2251)
On first launch (before ~/.gsd/ exists), loader.ts prints a branded
ASCII logo and welcome message. Later, cli.ts unconditionally calls
printWelcomeScreen(), resulting in a duplicate banner.

Set GSD_FIRST_RUN_BANNER env flag in loader.ts after printing the
first-run banner. cli.ts now checks for this flag and skips the
welcome screen when it is already set.

The session-restart banner in register-hooks.ts is unaffected because
it only fires on non-first sessions (isFirstSession guard).

Closes #2245
2026-03-23 11:51:08 -06:00
TÂCHES
012f1cf06a fix(test): Windows CI — use double quotes in git commit message (#2252)
The symlink test used single quotes in a commit message
(`-m 'add gitignore'`) inside a `&&`-chained shell command. On Windows,
`cmd.exe` doesn't treat single quotes as string delimiters, so git
received a mangled pathspec `gitignore'`. Split into two separate `run()`
calls with double-quoted commit message, matching every other test in
the file.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:49:50 -06:00
TÂCHES
64908fc822 feat(S05/T01): Schema v10 adds replan_triggered_at column; deriveStateF…
- src/resources/extensions/gsd/gsd-db.ts
- src/resources/extensions/gsd/state.ts
- src/resources/extensions/gsd/triage-resolution.ts
- src/resources/extensions/gsd/tests/flag-file-db.test.ts
- src/resources/extensions/gsd/tests/derive-state-db.test.ts
2026-03-23 11:46:28 -06:00
TÂCHES
b8aaded95e chore(M001/S05): auto-commit after plan-slice 2026-03-23 11:37:37 -06:00
TÂCHES
4f829131f6 chore(M001/S05): auto-commit after research-slice 2026-03-23 11:29:45 -06:00
TÂCHES
953598524d fix(async-jobs): suppress duplicate follow-up for awaited job results (#2248) (#2250)
When await_job consumed async job results, onJobComplete still fired
follow-up messages for each job. Each follow-up triggered a wasteful
LLM turn where the agent could only say "Already captured...".

Add an `awaited` flag to Job. await_job sets it on all watched jobs
before waiting (avoiding a race with the promise .then() callback).
onJobComplete skips follow-up delivery for awaited jobs. Fire-and-forget
jobs still get follow-up messages as before.

Closes #2248

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:28:53 -06:00
TÂCHES
6e94a5693d chore(M001/S04): auto-commit after complete-slice 2026-03-23 11:22:11 -06:00
TÂCHES
d7994a1538 fix(S04/T04): Add planning-crossval tests proving DB↔rendered↔parsed pa…
- src/resources/extensions/gsd/tests/planning-crossval.test.ts
- src/resources/extensions/gsd/markdown-renderer.ts
- .gsd/milestones/M001/slices/S04/tasks/T04-PLAN.md
2026-03-23 11:16:24 -06:00
TÂCHES
93e46c3712 feat(S04/T03): Migrate auto-dispatch.ts (3 rules), auto-verification.ts…
- src/resources/extensions/gsd/auto-dispatch.ts
- src/resources/extensions/gsd/auto-verification.ts
- src/resources/extensions/gsd/parallel-eligibility.ts
- .gsd/milestones/M001/slices/S04/tasks/T03-PLAN.md
2026-03-23 11:09:38 -06:00
TÂCHES
08c3fcc57c feat(S04/T02): Migrate dispatch-guard.ts to DB queries with isDbAvailab…
- src/resources/extensions/gsd/dispatch-guard.ts
- src/resources/extensions/gsd/tests/dispatch-guard.test.ts
- .gsd/milestones/M001/slices/S04/tasks/T02-PLAN.md
2026-03-23 11:03:42 -06:00
TÂCHES
61c9e62d37 fix(gsd): remove force-staging of .gsd/milestones/ through symlinks (#2247) (#2249)
smartStage() was using git hash-object + update-index to bypass .gitignore
and force-stage .gsd/milestones/ files when .gsd is a symlink. This
contradicts the external state design (symlink = state lives outside repo)
and the documented deprecation of commit_docs.

Remove the force-add block, finish the commit_docs deprecation in
auto-prompts (always emit "do not commit"), and clean up the commitDocs
parameter from all call sites. The deprecation warning in
preferences-validation remains so users are told to remove the setting.

Closes #2247

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 11:00:02 -06:00
TÂCHES
f86882bde5 fix(S04/T01): Add schema v9 migration with sequence column on slices/ta…
- src/resources/extensions/gsd/gsd-db.ts
- src/resources/extensions/gsd/tests/schema-v9-sequence.test.ts
- .gsd/milestones/M001/slices/S04/S04-PLAN.md
- .gsd/milestones/M001/slices/S04/tasks/T01-PLAN.md
2026-03-23 10:57:27 -06:00
TÂCHES
b73f525834 docs(S04): add slice plan 2026-03-23 10:52:22 -06:00
TÂCHES
5d93a71374 chore(M001/S04): auto-commit after research-slice 2026-03-23 10:45:24 -06:00