Commit graph

276 commits

Author SHA1 Message Date
TÂCHES
4334f3a27f Merge pull request #415 from fluxlabs/perf/repo-hotpath-optimizations 2026-03-14 15:58:04 -06:00
Flux Labs
f981d5aa79 perf: optimize discovery and interactive hot paths 2026-03-14 16:03:44 -05:00
Lex Christopherson
8a64a1c1da fix(tests): invalidate both state and path caches before assertions expecting fresh disk state
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:47:29 -06:00
Lex Christopherson
e739c4cab7 perf: memoize deriveState() per dispatch cycle
deriveState() was called ~7 times per dispatch cycle, each call re-reading
the entire .gsd/milestones/ tree from disk (~50-60 file reads per call,
~350-420 redundant reads per cycle). Add a 100ms TTL cache keyed by
basePath so repeated calls within the same dispatch cycle return the
cached result. Expose invalidateStateCache() and call it at every
mutation boundary in auto.ts: handleAgentEnd start, post-merge
re-derivations, and resume-from-pause.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:38:19 -06:00
TÂCHES
86f705c8e4 Merge pull request #398 from gsd-build/perf/path-resolution-cache
perf: session-scoped directory listing cache for path resolution
2026-03-14 14:36:59 -06:00
Lex Christopherson
d7faf8a4e5 fix(tests): invalidate path cache between deriveState calls that expect fresh disk state
Tests that write files and immediately call deriveState() got stale results
because the path resolution cache (dirEntryCache/dirListCache) returned
cached directory listings that didn't include newly written files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 14:14:40 -06:00
TÂCHES
5b62a3c7b3 Merge pull request #407 from fluxlabs/feat/model-provider-preferences-350 2026-03-14 14:13:56 -06:00
TÂCHES
4139e0d194 Merge pull request #406 from fluxlabs/fix/auto-mode-openrouter-model-resolution 2026-03-14 14:12:47 -06:00
TÂCHES
6004b5caaa Merge pull request #405 from fluxlabs/fix/git-svn-noise-404 2026-03-14 14:12:13 -06:00
TÂCHES
6ec179c743 Merge pull request #408 from fluxlabs/feat/custom-openai-compatible-endpoint 2026-03-14 14:11:14 -06:00
TÂCHES
5e0d333ea5 Merge pull request #400 from gsd-build/perf/prompt-dedup 2026-03-14 14:10:19 -06:00
TÂCHES
a4999a406e Merge pull request #399 from gsd-build/perf/parse-cache 2026-03-14 14:09:54 -06:00
TÂCHES
879c476df5 Merge pull request #394 from ASRagab/main 2026-03-14 14:09:34 -06:00
TÂCHES
d22226e3b8 Merge pull request #391 from deseltrus/refactor/dynamic-extension-discovery 2026-03-14 14:09:22 -06:00
Flux Labs
595c778250 feat: add custom OpenAI-compatible endpoint option to onboarding wizard (#335)
Adds a "Custom (OpenAI-compatible)" provider option to the API key
flow in the onboarding wizard. When selected, prompts for base URL,
API key, and model ID, then writes the config to models.json.
2026-03-14 15:07:47 -05:00
Flux Labs
c1dcca6820 feat: allow specifying model provider in preferences (#350)
Add explicit provider targeting for model preferences when the same
model ID exists across multiple providers (e.g., claude-sonnet-4-6
on both Anthropic and Bedrock).

Two formats supported:
- String: "bedrock/claude-sonnet-4-6"
- Object: { model: claude-sonnet-4-6, provider: bedrock }

The provider/model string format already worked in the resolution
code but was undocumented. This adds the provider field to the
object format and documents both approaches.
2026-03-14 15:06:49 -05:00
Flux Labs
8abbccea54 fix: resolve OpenRouter model IDs in auto-mode and show active model per phase
OpenRouter models use slash-separated IDs (e.g. "moonshotai/kimi-k2.5") where the
full string is the model ID on the "openrouter" provider. The auto-mode model
switcher incorrectly split on the first slash and treated the prefix as a provider
name, causing all OpenRouter preference models to fail resolution and fall back to
the default model for every phase.

Now the resolver first checks whether the slash-prefix is a known provider, and if
not (or if no match is found), falls back to matching the full string as a model ID
— consistent with model-resolver.ts.

Also improves the progress widget and notifications to show [PHASE] and
provider/model so users can confirm the correct model is active.

Closes #402
2026-03-14 15:00:03 -05:00
Flux Labs
9eb5a00f4f fix: suppress git-svn noise that causes confusing errors on affected systems (#404)
Systems with a buggy git-svn Perl module (notably Arch Linux) emit
"Duplicate specification" warnings on every git invocation. Filter
these from error messages and suppress git-svn loading via GIT_SVN_ID.
Also update repository URLs from stale glittercowboy/gsd-pi to
gsd-build/gsd-2.
2026-03-14 14:59:02 -05:00
deseltrus
aca53c5853 refactor: replace hardcoded extension list with dynamic discovery in loader
loader.ts previously maintained a hardcoded list of bundled extension paths
for GSD_BUNDLED_EXTENSION_PATHS. This required manual updates whenever
extensions were added or removed, and created a consistency gap with
buildResourceLoader() which already discovers extensions dynamically.

Replace with runtime directory scanning that mirrors the discovery rules
in resource-loader.ts:
- Top-level .ts/.js files → extension entry point
- Directories with index.ts or index.js → extension entry point
- Directories without either (shared/, remote-questions/) → skipped

Benefits:
- Adding a new extension no longer requires editing loader.ts
- GSD_BUNDLED_EXTENSION_PATHS stays in sync with what buildResourceLoader()
  loads in the main process — subagents now receive the same extensions
- Fixes: 5 extensions (google-search, mcporter, ttsr, universal-config,
  voice) were loaded in the main process but missing from
  GSD_BUNDLED_EXTENSION_PATHS, meaning subagents did not receive them
- Eliminates a common source of merge conflicts for contributors and forks
  that add custom extensions
2026-03-14 20:37:18 +01:00
Lex Christopherson
c3b518457a perf: deduplicate transitive dependency summaries in prompt builders
Prevent duplicate slice/dependency summaries from being inlined into
prompts when the same ID appears more than once. Uses a Set to track
already-included IDs in inlineDependencySummaries and
buildCompleteMilestonePrompt.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 13:35:34 -06:00
Lex Christopherson
3a705cb501 perf: add content-hash-keyed parse cache for file parsing
parseRoadmap, parsePlan, parseSummary, and parseContinue are pure
functions that get called repeatedly with the same content during
deriveState dispatch cycles. A module-scoped Map keyed by a fast
composite key (length + first 100 chars + last 100 chars) avoids
redundant parsing. Cache caps at 50 entries and clears when full.
Exports clearParseCache() for explicit invalidation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 13:34:40 -06:00
Lex Christopherson
1b6fdd6aa6 perf: add session-scoped directory listing cache to path resolution
resolveDir(), resolveFile(), and resolveTaskFiles() call readdirSync()
on every invocation with no caching. These are called dozens of times
per dispatch cycle through resolveMilestonePath, resolveSliceFile,
relTaskFile, etc. This adds a module-level Map cache for directory
listings with an exported clearPathCache() function for invalidation
at dispatch cycle boundaries and milestone transitions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 13:34:29 -06:00
Lex Christopherson
7e9b792dc4 perf: reduce dispatch gap timeout from 30s to 5s
The dispatch gap watchdog is a safety net for when the dispatch chain
silently breaks. 30s is unnecessarily long and makes failures feel
sluggish. 5s is plenty of time to detect a stall without the wait.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 13:34:24 -06:00
Ahmad Ragab
5af1f752cb fix: add missing front matter to github-workflows skill 2026-03-14 15:24:07 -04:00
Kassie Povinelli
a883008be6 feat(gsd): implement auto-mode fallback model rotation on network errors (#386) 2026-03-14 13:13:22 -06:00
TÂCHES
33cf0dcabd feat(native): add libgit2-backed git read operations for dispatch hotpath (#388) 2026-03-14 13:07:02 -06:00
TÂCHES
ea439b99e4 perf(state): wire native batch parser into deriveState() hotpath (#389) 2026-03-14 13:06:36 -06:00
Flux Labs
fd459041f5 fix: bypass pre-commit hooks on GSD infrastructure commits to prevent lint-staged empty commit errors (#385)
GSD's internal git commits (auto-commits, slice merges, runtime file cleanup)
trigger pre-commit hooks like lint-staged. When lint-staged runs formatters
(e.g. prettier) on already-formatted files, it detects no changes from its
tasks and blocks the commit with "Prevented an empty git commit!" — even
though there are legitimate staged changes.

This is a known incompatibility between lint-staged's backup/restore mechanism
and squash-merge commits. The code being committed already passed hooks when
originally committed on the slice branch, so re-running hooks on the merge
commit is redundant.

Adds --no-verify to all 7 internal commit calls in git-service.ts:
- writeIntegrationBranch() — records integration branch metadata
- smartStage() cleanup — untracks runtime files from git index
- commit() — general commit method used by GSD
- autoCommit() — auto-commits dirty working tree between units
- mergeSliceToMain() — pre-merge runtime file untracking (2 calls)
- mergeSliceToMain() — squash-merge commit and --no-ff amend
2026-03-14 12:48:23 -06:00
Flux Labs
95643861ec fix(auto): prevent hang when dispatch chain breaks after slice tasks complete (#381) (#382) 2026-03-14 12:31:37 -06:00
Flux Labs
b6ec4f9fad Fix packaging verification and path portability (#378) 2026-03-14 12:28:14 -06:00
deseltrus
5d510ca6aa fix: read resources from dist/ to prevent branch-drift in npm-link setups (#314)
* fix: read resources from dist/ to prevent branch-drift in npm-link setups

initResources() reads extensions, prompts, skills, and agents from
src/resources/ — which points into the live working tree when gsd is
installed via npm link. Switching branches in the gsd repo changes
src/resources/ for ALL projects using gsd, causing stale or broken
extensions to be synced to ~/.gsd/agent/ on next launch.

Fix: the build step now copies src/resources/ to dist/resources/.
At runtime, resource-loader.ts and loader.ts prefer dist/resources/
(stable, set at build time) over src/resources/ (live working tree).
Fallback to src/resources/ is preserved for setups without a build.

Also adds npm run dev watch-resources watcher that syncs src/resources/
to dist/resources/ on file changes, running alongside tsc --watch.

* fix: cache prompt templates per session to prevent cross-session invalidation

When two gsd sessions run concurrently, the second session's
initResources() overwrites ~/.gsd/agent/ templates on disk. The first
session then reads a newer template that expects variables its in-memory
code doesn't know about, causing 'template declares {{X}} but no value
was provided' crashes that hang auto-mode indefinitely.

Fix: cache each template on first read. A running session uses the
template versions from when it first loaded them, immune to later
disk overwrites by other sessions.
2026-03-14 11:47:03 -06:00
Juan Francisco Lebrero
f6f1f9aa27 fix(search): always use native Anthropic web search when available (#374)
Three bugs prevented native web_search from being used with Anthropic:

1. model_select never fires on session restore (SDK's modelsAreEqual
   guard suppresses it when the default model matches the restored one),
   so isAnthropicProvider stayed false and native search was never
   injected. Fix: fall back to detecting "claude-" in the payload model
   name when model_select hasn't fired.

2. Custom search tools (search-the-web, search_and_read) were only
   removed from the payload when BRAVE_API_KEY was missing. With the
   key present, the model saw both native and Brave tools and often
   picked the Brave ones, which failed with network errors. Fix: always
   remove custom search tools from Anthropic requests.

3. google_search (Gemini-based) was not included in the disable list,
   so the model could call it even without GEMINI_API_KEY set. Fix: new
   CUSTOM_SEARCH_TOOL_NAMES list covers all three custom search tools.
2026-03-14 11:46:50 -06:00
TÂCHES
c69be2078f fix: npx fails — loader creates workspace symlinks at runtime (#380)
npx runs with --ignore-scripts, skipping postinstall. The loader now
creates node_modules/@gsd/* symlinks pointing to packages/* before
importing cli.js, so @gsd/* packages resolve without postinstall.

Bumps to v2.10.12.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 11:38:33 -06:00
deseltrus
8a64ac054c feat: multi-milestone readiness flow with per-milestone discussion gate (#377) 2026-03-14 11:26:30 -06:00
jpmarques19
41555b6f1e fix(voice): check GROQ_API_KEY before entering voice mode (#367) 2026-03-14 10:15:23 -06:00
jpmarques19
6a39f7226b feat(voice): Groq Whisper API backend for Linux voice mode (#366) 2026-03-14 09:08:36 -06:00
Flux Labs
a7ac787165 fix: handle empty index after runtime file stripping in squash-merge (#364) 2026-03-14 08:50:22 -06:00
Juan Francisco Lebrero
3a1b8f457d feat: opus 4.6 1M default, model selector UX, Discord onboarding (#290) 2026-03-14 08:43:56 -06:00
Copilot
8f8a9db7cb fix(auto): stale lock detection, SIGTERM handler, live-session guard (#362) 2026-03-14 08:43:33 -06:00
Adam Dry
db2c864e9e refactor: PR quality fixes — deduplicate findMilestoneIds, rename prefix→suffix, shared test helpers, fix typo (#360) 2026-03-14 08:42:22 -06:00
Copilot
7b17138ea4 Fix infinite loop when complete-slice merges to main are interrupted (#345)
* Initial plan

* fix: detect and merge orphaned completed slice branches at startup to prevent infinite loop

Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: add missing closing brace in mergeOrphanedSliceBranches

The for-loop body was missing its closing brace, causing a parse error
that broke all tests importing auto.ts.

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
Co-authored-by: TÂCHES <afromanguy@me.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Lex Christopherson <lex@glittercowboy.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 07:38:02 -06:00
TÂCHES
4c33642e3c fix: normalize .ts import extensions to .js for Node 22.22+ compatibility (#354)
Node 22.22.0's --experimental-strip-types handles .ts import specifiers
differently, causing ERR_INVALID_TYPESCRIPT_SYNTAX in CI. The project
convention uses .js specifiers with a custom resolve hook that rewrites
them to .ts at test time.

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 07:27:32 -06:00
Copilot
ce7bde14f3 Fix execute-task loop detection: adaptive reconciliation instead of hard stop (#342)
* Initial plan

* Add adaptive reconciliation, concrete remediation steps, and xxHash32 JS fallback

- auto.ts: final reconciliation pass before halting on loop detection
  (runs skipExecuteTask to write blocker artifacts and advance pipeline)
- auto.ts: adaptive retry on prevCount>=2: write stub summary when still
  missing after two agent sessions, so the agent has a recovery context
- auto.ts: buildLoopRemediationSteps() helper with concrete manual steps
  when automatic reconciliation cannot resolve the unit
- xxhash/index.ts: pure-JS xxHash32 fallback when native addon does not
  export xxHash32 (prevents 'native.xxHash32 is not a function' crash)
- idle-recovery.test.ts: tests for buildLoopRemediationSteps and loop-recovery
  path in skipExecuteTask

Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>

* Address code review feedback

- xxhash/index.ts: extract accumulate() helper to remove duplicate lane
  processing lines in the 16-byte loop
- auto.ts: replace magic number 2 with named constant STUB_RECOVERY_THRESHOLD
- idle-recovery.test.ts: add comment explaining the 3 (== MAX_UNIT_DISPATCHES)
  used in the loop-recovery test

Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>

* Address PR review: verify post-state, fix stub wording, fix checkbox syntax, add fallback test

- auto.ts (MAX branch): check verifyExpectedArtifact() after skipExecuteTask()
  before advancing; annotate both conditions explaining what each validates
- auto.ts (self-repair branch): guard advance with verifyExpectedArtifact() so
  regex mismatches in the slice plan don't silently re-dispatch
- auto.ts (stub-recovery): write stub summary directly with "PARTIAL RECOVERY"
  wording instead of using skipExecuteTask (which would claim "exhausted N attempts")
- auto.ts (remediation): fix checkbox example to "- [x] **${tid}:" (no trailing **)
- xxhash/index.ts: export xxHash32Fallback so CI can test JS path independently
- native/src/index.ts: re-export xxHash32Fallback
- xxhash.test.mjs: add "xxHash32Fallback (pure-JS path)" describe block that
  validates the JS implementation against the reference on all test cases

Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>

* chore: retrigger CI

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
Co-authored-by: CI Trigger <noreply@github.com>
2026-03-14 07:26:10 -06:00
TÂCHES
d4312c60ec fix: handle secrets skip gracefully in auto mode without crashing (#352)
Fixes #296

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-14 07:08:49 -06:00
Copilot
2f15affeb5 fix: discard untracked runtime files before branch switch to prevent checkout conflicts (#346)
* Initial plan

* fix: add discardUntrackedRuntimeFiles to handle STATE.md checkout conflicts

Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TÂCHES <afromanguy@me.com>
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
2026-03-14 07:03:58 -06:00
Adam Dry
cf8dfc8c37 feat: Allow teams to work together without milestone name clashes and share context by checking in certain .gsd/ directory artifacts (#338)
* feat(M001/S01): ID generation and config plumbing

Tasks:
- chore(M001/S01/T02): auto-commit after execute-task
- chore(M001/S01): auto-commit after plan-slice
- docs(S01): add slice plan

Branch: gsd/M001/S01

* feat(M001/S02): Regex hardening and backwards compat

Tasks:
- chore(M001/S02/T02): auto-commit after execute-task
- chore(M001/S02/T01): auto-commit after execute-task
- chore: untrack .gsd/ runtime files from git index
- docs(S02): add slice plan

Branch: gsd/M001/S02

* docs(M001/S03): UX — wizard toggle and documentation

Tasks:
- chore(M001/S03/T01): auto-commit after execute-task
- docs(S03): add slice plan

Branch: gsd/M001/S03

* test(M001/S04): Integration tests and end-to-end verification

Tasks:
- chore(M001/S04/T02): auto-commit after execute-task
- chore(M001/S04/T01): auto-commit after execute-task
- docs(S04): add slice plan

Branch: gsd/M001/S04

* chore(M001): record integration branch

* chore(M002): record integration branch

* docs(M002/S01): Format swap — production code, tests, and docs

Tasks:
- chore(gsd/M002/S01): auto-commit after pre-switch
- chore(M002/S01/T01): auto-commit after execute-task
- chore: untrack .gsd/ runtime files from git index
- docs(S01): add slice plan

Branch: gsd/M002/S01

* chore(M002): auto-commit after complete-milestone

* Updated to document that we don't automatically always squash to main if you started on a different branch (like a dev or feature branch)

* fix: replace vitest import with node:test in regex-hardening test

The test imported from 'vitest' which isn't installed, causing
ERR_MODULE_NOT_FOUND and failing the CI unit test step. All other
test files use node:test. Swapped the import and removed the
vitest conditional wrapper.

* chore: untrack .gsd/ (already gitignored)

* docs: fix stale 'main' references in merge comments and prompts

The slice merge code correctly resolves to the integration branch
(which may be a feature branch, worktree branch, etc.), but comments,
JSDoc, and prompt templates still said 'main' as if it were always
the literal main branch.

Updated git-service.ts, worktree.ts, system.md, and
guided-complete-slice.md to say 'integration branch' with a clear
explanation of what that means.

* Fixed preferences.md case mismatch; Added fallback for backwards compat

* Updated preferences file example to show new unique_milestone_ids setting

* Updated readme to explain best practice for working in teams

---------

Co-authored-by: TÂCHES <afromanguy@me.com>
2026-03-14 07:00:14 -06:00
deseltrus
c13b1bfc6e fix: force-add .gsd/ planning artifacts and guard handleAgentEnd reentrancy (#341)
Two bugs that interact to silently kill auto-mode:

1. smartStage() uses `git add -A` which respects .gitignore. When .gsd/
   is gitignored (common — GSD's own baseline patterns only ignore runtime
   files, but many projects ignore all of .gsd/), new planning artifacts
   (CONTEXT.md, SUMMARY.md, PLAN.md, UAT.md, DECISIONS.md) are never
   staged. They exist on disk but not in git. Squash-merges then delete
   them on main because they appear as "removed relative to main."

   Fix: after `git add -A`, force-add `.gsd/milestones/` and root
   planning files. Runtime paths are still excluded by the subsequent
   `git reset HEAD` step.

2. handleAgentEnd() has no reentrancy guard. Background job notifications
   (async_bash results) trigger additional agent_end events while the
   first handler is still running (it yields at every await). Concurrent
   dispatchNextUnit() calls race on newSession() — one cancels the other,
   silently stopping auto-mode. Combined with bug #1, the second
   dispatchNextUnit call may find the active milestone's CONTEXT.md
   missing (never committed, lost during branch switch) and stop with
   "No context or roadmap yet."

   Fix: boolean guard prevents concurrent execution. Reset in stopAuto()
   so restarts aren't blocked.

Fixes #TBD

Co-authored-by: TÂCHES <afromanguy@me.com>
2026-03-14 07:00:07 -06:00
copilot-swe-agent[bot]
097cd3c8e0 fix: break infinite skip loop in gsd auto by adding roadmap [x] check to verifyExpectedArtifact
After a crash where complete-slice wrote SUMMARY+UAT but didn't mark
the roadmap [x], the idempotency check incorrectly reported the unit
as "done" (artifacts exist), while the state machine kept returning
the same complete-slice unit (roadmap shows [ ]). This caused
dispatchNextUnit to recurse forever.

Fix: verifyExpectedArtifact for complete-slice now also checks that
the slice is marked [x] in the roadmap. If not, it returns false so
the stale completion key is evicted and the unit re-runs.

Lenient if roadmap file is missing or corrupt (returns true).

Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
2026-03-14 12:34:54 +00:00
copilot-swe-agent[bot]
a89f45ce91 fix(#300): update stale comments that described old buggy behavior
Co-authored-by: glittercowboy <186001655+glittercowboy@users.noreply.github.com>
2026-03-14 06:10:42 +00:00
TÂCHES
500cb5f6f3 Merge branch 'main' into fix/stale-completed-unit-oom-313 2026-03-13 23:51:34 -06:00