quiver-cli 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +197 -239
- package/dist/cli.js +3163 -2040
- package/package.json +10 -11
- package/template/.agents/AGENTS.md +0 -26
- package/template/.agents/commands/cp.md +0 -108
- package/template/.agents/commands/next-setup.md +0 -1064
- package/template/.agents/commands/review.md +0 -68
- package/template/.agents/commands/terraform-readme.md +0 -38
- package/template/.agents/config.json +0 -80
- package/template/.agents/plugins/opencode/rtk.ts +0 -34
- package/template/.agents/skills/agent-browser/SKILL.md +0 -51
- package/template/.agents/skills/apps/skybridge/SKILL.md +0 -50
- package/template/.agents/skills/code/cleanup/SKILL.md +0 -26
- package/template/.agents/skills/code/improve/SKILL.md +0 -122
- package/template/.agents/skills/code/improve/references/audit-playbook.md +0 -130
- package/template/.agents/skills/code/improve/references/closing-the-loop.md +0 -96
- package/template/.agents/skills/code/improve/references/plan-template.md +0 -197
- package/template/.agents/skills/code/vercel-react-best-practices/AGENTS.md +0 -3810
- package/template/.agents/skills/code/vercel-react-best-practices/README.md +0 -123
- package/template/.agents/skills/code/vercel-react-best-practices/SKILL.md +0 -149
- package/template/.agents/skills/code/vercel-react-best-practices/metadata.json +0 -15
- package/template/.agents/skills/code/vercel-react-best-practices/rules/_sections.md +0 -46
- package/template/.agents/skills/code/vercel-react-best-practices/rules/_template.md +0 -28
- package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-effect-event-deps.md +0 -56
- package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
- package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-init-once.md +0 -42
- package/template/.agents/skills/code/vercel-react-best-practices/rules/advanced-use-latest.md +0 -39
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-api-routes.md +0 -38
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-cheap-condition-before-await.md +0 -37
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-defer-await.md +0 -82
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-dependencies.md +0 -51
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-parallel.md +0 -28
- package/template/.agents/skills/code/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-analyzable-paths.md +0 -63
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -60
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
- package/template/.agents/skills/code/vercel-react-best-practices/rules/bundle-preload.md +0 -50
- package/template/.agents/skills/code/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
- package/template/.agents/skills/code/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
- package/template/.agents/skills/code/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
- package/template/.agents/skills/code/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -107
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-early-exit.md +0 -50
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-flatmap-filter.md +0 -60
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-index-maps.md +0 -37
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-length-check-first.md +0 -49
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-request-idle-callback.md +0 -105
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/template/.agents/skills/code/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-activity.md +0 -26
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +0 -30
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-resource-hints.md +0 -85
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-script-defer-async.md +0 -68
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rendering-usetransition-loading.md +0 -75
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +0 -40
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +0 -38
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-memo.md +0 -44
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +0 -45
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-no-inline-components.md +0 -82
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +0 -35
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-split-combined-hooks.md +0 -64
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-use-deferred-value.md +0 -59
- package/template/.agents/skills/code/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +0 -73
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-auth-actions.md +0 -96
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-cache-react.md +0 -76
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-dedup-props.md +0 -65
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-hoist-static-io.md +0 -149
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-no-shared-module-state.md +0 -50
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-parallel-nested-fetching.md +0 -34
- package/template/.agents/skills/code/vercel-react-best-practices/rules/server-serialization.md +0 -38
- package/template/.agents/skills/data/prisma-cli/SKILL.md +0 -247
- package/template/.agents/skills/data/prisma-cli/references/db-execute.md +0 -78
- package/template/.agents/skills/data/prisma-cli/references/db-pull.md +0 -185
- package/template/.agents/skills/data/prisma-cli/references/db-push.md +0 -148
- package/template/.agents/skills/data/prisma-cli/references/db-seed.md +0 -188
- package/template/.agents/skills/data/prisma-cli/references/debug.md +0 -46
- package/template/.agents/skills/data/prisma-cli/references/dev.md +0 -157
- package/template/.agents/skills/data/prisma-cli/references/format.md +0 -48
- package/template/.agents/skills/data/prisma-cli/references/generate.md +0 -173
- package/template/.agents/skills/data/prisma-cli/references/init.md +0 -136
- package/template/.agents/skills/data/prisma-cli/references/mcp.md +0 -38
- package/template/.agents/skills/data/prisma-cli/references/migrate-deploy.md +0 -127
- package/template/.agents/skills/data/prisma-cli/references/migrate-dev.md +0 -145
- package/template/.agents/skills/data/prisma-cli/references/migrate-diff.md +0 -89
- package/template/.agents/skills/data/prisma-cli/references/migrate-reset.md +0 -78
- package/template/.agents/skills/data/prisma-cli/references/migrate-resolve.md +0 -57
- package/template/.agents/skills/data/prisma-cli/references/migrate-status.md +0 -65
- package/template/.agents/skills/data/prisma-cli/references/studio.md +0 -137
- package/template/.agents/skills/data/prisma-cli/references/validate.md +0 -53
- package/template/.agents/skills/data/prisma-client-api/SKILL.md +0 -216
- package/template/.agents/skills/data/prisma-client-api/references/client-methods.md +0 -223
- package/template/.agents/skills/data/prisma-client-api/references/constructor.md +0 -208
- package/template/.agents/skills/data/prisma-client-api/references/filters.md +0 -256
- package/template/.agents/skills/data/prisma-client-api/references/model-queries.md +0 -281
- package/template/.agents/skills/data/prisma-client-api/references/query-options.md +0 -276
- package/template/.agents/skills/data/prisma-client-api/references/raw-queries.md +0 -194
- package/template/.agents/skills/data/prisma-client-api/references/relations.md +0 -308
- package/template/.agents/skills/data/prisma-client-api/references/transactions.md +0 -184
- package/template/.agents/skills/design/impeccable/SKILL.md +0 -84
- package/template/.agents/skills/design/impeccable/reference/adapt.md +0 -312
- package/template/.agents/skills/design/impeccable/reference/adapt.native.md +0 -58
- package/template/.agents/skills/design/impeccable/reference/android.md +0 -40
- package/template/.agents/skills/design/impeccable/reference/animate.md +0 -86
- package/template/.agents/skills/design/impeccable/reference/audit.md +0 -136
- package/template/.agents/skills/design/impeccable/reference/audit.native.md +0 -139
- package/template/.agents/skills/design/impeccable/reference/bolder.md +0 -31
- package/template/.agents/skills/design/impeccable/reference/clarify.md +0 -94
- package/template/.agents/skills/design/impeccable/reference/colorize.md +0 -86
- package/template/.agents/skills/design/impeccable/reference/craft-floor.md +0 -42
- package/template/.agents/skills/design/impeccable/reference/craft.md +0 -5
- package/template/.agents/skills/design/impeccable/reference/critique.md +0 -788
- package/template/.agents/skills/design/impeccable/reference/degraded/asset-producer.md +0 -90
- package/template/.agents/skills/design/impeccable/reference/degraded/documenter.md +0 -24
- package/template/.agents/skills/design/impeccable/reference/degraded/finish-reviewer.md +0 -37
- package/template/.agents/skills/design/impeccable/reference/degraded/manual-edit-applier.md +0 -92
- package/template/.agents/skills/design/impeccable/reference/delight.md +0 -70
- package/template/.agents/skills/design/impeccable/reference/distill.md +0 -111
- package/template/.agents/skills/design/impeccable/reference/doctor.md +0 -53
- package/template/.agents/skills/design/impeccable/reference/document.md +0 -416
- package/template/.agents/skills/design/impeccable/reference/extract.md +0 -69
- package/template/.agents/skills/design/impeccable/reference/harden.md +0 -336
- package/template/.agents/skills/design/impeccable/reference/hooks.md +0 -105
- package/template/.agents/skills/design/impeccable/reference/init.md +0 -125
- package/template/.agents/skills/design/impeccable/reference/ios.md +0 -45
- package/template/.agents/skills/design/impeccable/reference/layout.md +0 -84
- package/template/.agents/skills/design/impeccable/reference/live-setup.md +0 -102
- package/template/.agents/skills/design/impeccable/reference/live.md +0 -323
- package/template/.agents/skills/design/impeccable/reference/new-work.md +0 -105
- package/template/.agents/skills/design/impeccable/reference/onboard.md +0 -234
- package/template/.agents/skills/design/impeccable/reference/operate.md +0 -61
- package/template/.agents/skills/design/impeccable/reference/optimize.md +0 -258
- package/template/.agents/skills/design/impeccable/reference/overdrive.md +0 -127
- package/template/.agents/skills/design/impeccable/reference/polish.md +0 -97
- package/template/.agents/skills/design/impeccable/reference/quieter.md +0 -99
- package/template/.agents/skills/design/impeccable/reference/routing.md +0 -18
- package/template/.agents/skills/design/impeccable/reference/shape.md +0 -59
- package/template/.agents/skills/design/impeccable/reference/typeset.md +0 -80
- package/template/.agents/skills/design/impeccable/reference/visualize.md +0 -47
- package/template/.agents/skills/design/impeccable/scripts/command-metadata.json +0 -94
- package/template/.agents/skills/design/impeccable/scripts/concept-seed.mjs +0 -558
- package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +0 -334
- package/template/.agents/skills/design/impeccable/scripts/context.mjs +0 -1450
- package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +0 -213
- package/template/.agents/skills/design/impeccable/scripts/detect-csp.mjs +0 -198
- package/template/.agents/skills/design/impeccable/scripts/detect.mjs +0 -21
- package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +0 -2023
- package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +0 -438
- package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +0 -983
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +0 -8283
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +0 -50
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +0 -372
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +0 -768
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +0 -1186
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +0 -264
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +0 -189
- package/template/.agents/skills/design/impeccable/scripts/detector/findings.mjs +0 -18
- package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +0 -212
- package/template/.agents/skills/design/impeccable/scripts/detector/profile/profiler.mjs +0 -166
- package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +0 -617
- package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +0 -5580
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/color.mjs +0 -124
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/constants.mjs +0 -112
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/fonts.mjs +0 -30
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/inline-ignores.mjs +0 -148
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/page.mjs +0 -7
- package/template/.agents/skills/design/impeccable/scripts/doctor.mjs +0 -336
- package/template/.agents/skills/design/impeccable/scripts/embed-prompt.mjs +0 -133
- package/template/.agents/skills/design/impeccable/scripts/generate-image.mjs +0 -240
- package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +0 -741
- package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +0 -516
- package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +0 -2100
- package/template/.agents/skills/design/impeccable/scripts/hook.mjs +0 -78
- package/template/.agents/skills/design/impeccable/scripts/lib/artifact-schema.mjs +0 -93
- package/template/.agents/skills/design/impeccable/scripts/lib/composition-catalog.mjs +0 -200
- package/template/.agents/skills/design/impeccable/scripts/lib/concept-catalog.mjs +0 -357
- package/template/.agents/skills/design/impeccable/scripts/lib/design-parser.mjs +0 -842
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +0 -658
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +0 -137
- package/template/.agents/skills/design/impeccable/scripts/lib/is-generated.mjs +0 -69
- package/template/.agents/skills/design/impeccable/scripts/lib/provider.mjs +0 -5
- package/template/.agents/skills/design/impeccable/scripts/lib/roll-selection.mjs +0 -362
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness-deep.mjs +0 -457
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness-notice.mjs +0 -169
- package/template/.agents/skills/design/impeccable/scripts/lib/staleness.mjs +0 -457
- package/template/.agents/skills/design/impeccable/scripts/lib/surface-briefs.mjs +0 -151
- package/template/.agents/skills/design/impeccable/scripts/lib/target-args.mjs +0 -42
- package/template/.agents/skills/design/impeccable/scripts/lib/target-slug.mjs +0 -33
- package/template/.agents/skills/design/impeccable/scripts/lib/template-extensions.mjs +0 -146
- package/template/.agents/skills/design/impeccable/scripts/live/accept-css.mjs +0 -617
- package/template/.agents/skills/design/impeccable/scripts/live/accept-verify.mjs +0 -60
- package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +0 -55
- package/template/.agents/skills/design/impeccable/scripts/live/completion.mjs +0 -28
- package/template/.agents/skills/design/impeccable/scripts/live/event-validation.mjs +0 -199
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/astro.mjs +0 -47
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/detect-utils.mjs +0 -73
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/index.mjs +0 -143
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/journal.mjs +0 -197
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nextjs.mjs +0 -49
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/nuxt.mjs +0 -161
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/script-src.mjs +0 -17
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/static-html.mjs +0 -26
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/sveltekit.mjs +0 -71
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tag-strategy.mjs +0 -247
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/tanstack-start.mjs +0 -70
- package/template/.agents/skills/design/impeccable/scripts/live/frameworks/vite-generic.mjs +0 -42
- package/template/.agents/skills/design/impeccable/scripts/live/generation-preflight.mjs +0 -149
- package/template/.agents/skills/design/impeccable/scripts/live/insert-ui.mjs +0 -458
- package/template/.agents/skills/design/impeccable/scripts/live/instructions.mjs +0 -142
- package/template/.agents/skills/design/impeccable/scripts/live/manual-apply.mjs +0 -939
- package/template/.agents/skills/design/impeccable/scripts/live/manual-edit-routes.mjs +0 -357
- package/template/.agents/skills/design/impeccable/scripts/live/manual-edits-buffer.mjs +0 -152
- package/template/.agents/skills/design/impeccable/scripts/live/poll-lanes.mjs +0 -14
- package/template/.agents/skills/design/impeccable/scripts/live/roots.mjs +0 -508
- package/template/.agents/skills/design/impeccable/scripts/live/session-store.mjs +0 -563
- package/template/.agents/skills/design/impeccable/scripts/live/source-lock.mjs +0 -105
- package/template/.agents/skills/design/impeccable/scripts/live/source-search.mjs +0 -105
- package/template/.agents/skills/design/impeccable/scripts/live/svelte-ast.mjs +0 -961
- package/template/.agents/skills/design/impeccable/scripts/live/svelte-component.mjs +0 -1342
- package/template/.agents/skills/design/impeccable/scripts/live/sveltekit-adapter.mjs +0 -316
- package/template/.agents/skills/design/impeccable/scripts/live/tanstack-adapter.mjs +0 -280
- package/template/.agents/skills/design/impeccable/scripts/live/ui-core.mjs +0 -180
- package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +0 -171
- package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +0 -954
- package/template/.agents/skills/design/impeccable/scripts/live-browser-dom.js +0 -146
- package/template/.agents/skills/design/impeccable/scripts/live-browser-session.js +0 -123
- package/template/.agents/skills/design/impeccable/scripts/live-browser.js +0 -12512
- package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +0 -1244
- package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +0 -107
- package/template/.agents/skills/design/impeccable/scripts/live-copy-edit-agent.mjs +0 -683
- package/template/.agents/skills/design/impeccable/scripts/live-discard-manual-edits.mjs +0 -51
- package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +0 -503
- package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +0 -292
- package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +0 -368
- package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +0 -429
- package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +0 -123
- package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +0 -1661
- package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +0 -71
- package/template/.agents/skills/design/impeccable/scripts/live-target.mjs +0 -30
- package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +0 -927
- package/template/.agents/skills/design/impeccable/scripts/live.mjs +0 -359
- package/template/.agents/skills/design/impeccable/scripts/modern-screenshot.umd.js +0 -14
- package/template/.agents/skills/design/impeccable/scripts/palette.mjs +0 -628
- package/template/.agents/skills/design/impeccable/scripts/pin.mjs +0 -221
- package/template/.agents/skills/design/impeccable/scripts/serve-question.mjs +0 -932
- package/template/.agents/skills/design/impeccable/scripts/surface-brief.mjs +0 -74
- package/template/.agents/skills/design/shadcn/SKILL.md +0 -277
- package/template/.agents/skills/design/shadcn/agents/openai.yml +0 -5
- package/template/.agents/skills/design/shadcn/assets/shadcn-small.png +0 -0
- package/template/.agents/skills/design/shadcn/assets/shadcn.png +0 -0
- package/template/.agents/skills/design/shadcn/cli.md +0 -290
- package/template/.agents/skills/design/shadcn/customization.md +0 -209
- package/template/.agents/skills/design/shadcn/evals/evals.json +0 -77
- package/template/.agents/skills/design/shadcn/mcp.md +0 -105
- package/template/.agents/skills/design/shadcn/registry.md +0 -277
- package/template/.agents/skills/design/shadcn/rules/base-vs-radix.md +0 -306
- package/template/.agents/skills/design/shadcn/rules/chat.md +0 -224
- package/template/.agents/skills/design/shadcn/rules/composition.md +0 -213
- package/template/.agents/skills/design/shadcn/rules/forms.md +0 -192
- package/template/.agents/skills/design/shadcn/rules/icons.md +0 -101
- package/template/.agents/skills/design/shadcn/rules/styling.md +0 -185
- package/template/.agents/skills/find-skills/SKILL.md +0 -141
- package/template/.agents/skills/hono/SKILL.md +0 -579
- package/template/.agents/skills/integrations/langfuse/SKILL.md +0 -146
- package/template/.agents/skills/integrations/langfuse/references/ci-cd.md +0 -41
- package/template/.agents/skills/integrations/langfuse/references/cli.md +0 -60
- package/template/.agents/skills/integrations/langfuse/references/error-analysis.md +0 -88
- package/template/.agents/skills/integrations/langfuse/references/instrumentation.md +0 -121
- package/template/.agents/skills/integrations/langfuse/references/judge-calibration.md +0 -291
- package/template/.agents/skills/integrations/langfuse/references/prompt-engineering.md +0 -35
- package/template/.agents/skills/integrations/langfuse/references/prompt-migration.md +0 -79
- package/template/.agents/skills/integrations/langfuse/references/skill-feedback.md +0 -55
- package/template/.agents/skills/integrations/langfuse/references/trace-evaluator-upgrade.md +0 -76
- package/template/.agents/skills/integrations/langfuse/references/user-feedback.md +0 -92
- package/template/.agents/skills/integrations/langfuse/references/v4-project-migration.md +0 -73
- package/template/.agents/skills/integrations/posthog/SKILL.md +0 -102
- package/template/.agents/skills/integrations/posthog/references/error-tracking-alerts.md +0 -63
- package/template/.agents/skills/integrations/posthog/references/error-tracking-assigning-issues.md +0 -77
- package/template/.agents/skills/integrations/posthog/references/error-tracking-fingerprints.md +0 -57
- package/template/.agents/skills/integrations/posthog/references/error-tracking-monitoring.md +0 -140
- package/template/.agents/skills/integrations/posthog/references/error-tracking-nextjs.md +0 -490
- package/template/.agents/skills/integrations/posthog/references/error-tracking-source-maps.md +0 -45
- package/template/.agents/skills/integrations/posthog/references/feature-flags-best-practices.md +0 -139
- package/template/.agents/skills/integrations/posthog/references/feature-flags-react.md +0 -302
- package/template/.agents/skills/integrations/posthog/references/identify-users.md +0 -202
- package/template/.agents/skills/integrations/posthog/references/integration-example.md +0 -706
- package/template/.agents/skills/integrations/posthog/references/integration-nextjs.md +0 -385
- package/template/.agents/skills/integrations/posthog/references/integration-step-1-begin.md +0 -43
- package/template/.agents/skills/integrations/posthog/references/integration-step-2-edit.md +0 -37
- package/template/.agents/skills/integrations/posthog/references/integration-step-3-revise.md +0 -22
- package/template/.agents/skills/integrations/posthog/references/integration-step-4-conclude.md +0 -38
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-anthropic.md +0 -200
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-basics.md +0 -62
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-costs.md +0 -197
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-manual-capture.md +0 -397
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-traces.md +0 -98
- package/template/.agents/skills/integrations/posthog/references/llm-analytics-vercel-ai.md +0 -120
- package/template/.agents/skills/repo/repo-ci/SKILL.md +0 -265
- package/template/.agents/skills/repo/repo-init-next-js/SKILL.md +0 -129
- package/template/.agents/skills/repo/repo-init-next-js/references/file-contents.md +0 -800
- package/template/.agents/skills/repo/repo-init-next-js/scripts/setup.sh +0 -47
- package/template/.agents/skills/repo/repo-init-node/SKILL.md +0 -196
- package/template/.agents/skills/skill-creator/LICENSE.txt +0 -202
- package/template/.agents/skills/skill-creator/SKILL.md +0 -485
- package/template/.agents/skills/skill-creator/agents/analyzer.md +0 -274
- package/template/.agents/skills/skill-creator/agents/comparator.md +0 -202
- package/template/.agents/skills/skill-creator/agents/grader.md +0 -223
- package/template/.agents/skills/skill-creator/assets/eval_review.html +0 -146
- package/template/.agents/skills/skill-creator/eval-viewer/generate_review.py +0 -471
- package/template/.agents/skills/skill-creator/eval-viewer/viewer.html +0 -1325
- package/template/.agents/skills/skill-creator/references/schemas.md +0 -430
- package/template/.agents/skills/skill-creator/scripts/__init__.py +0 -0
- package/template/.agents/skills/skill-creator/scripts/aggregate_benchmark.py +0 -401
- package/template/.agents/skills/skill-creator/scripts/generate_report.py +0 -326
- package/template/.agents/skills/skill-creator/scripts/improve_description.py +0 -247
- package/template/.agents/skills/skill-creator/scripts/package_skill.py +0 -136
- package/template/.agents/skills/skill-creator/scripts/quick_validate.py +0 -103
- package/template/.agents/skills/skill-creator/scripts/run_eval.py +0 -310
- package/template/.agents/skills/skill-creator/scripts/run_loop.py +0 -328
- package/template/.agents/skills/skill-creator/scripts/utils.py +0 -47
- package/template/.agents/skills/supabase/CHANGELOG.md +0 -71
- package/template/.agents/skills/supabase/SKILL.md +0 -145
- package/template/.agents/skills/supabase/assets/feedback-issue-template.md +0 -17
- package/template/.agents/skills/supabase/references/skill-feedback.md +0 -17
- package/template/.agents/skills/supabase-postgres-best-practices/CHANGELOG.md +0 -73
- package/template/.agents/skills/supabase-postgres-best-practices/SKILL.md +0 -64
- package/template/.agents/skills/supabase-postgres-best-practices/references/_contributing.md +0 -170
- package/template/.agents/skills/supabase-postgres-best-practices/references/_sections.md +0 -39
- package/template/.agents/skills/supabase-postgres-best-practices/references/_template.md +0 -34
- package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +0 -55
- package/template/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +0 -49
- package/template/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +0 -46
- package/template/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +0 -44
- package/template/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +0 -41
- package/template/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +0 -46
- package/template/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +0 -54
- package/template/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +0 -53
- package/template/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +0 -50
- package/template/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +0 -50
- package/template/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +0 -56
- package/template/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +0 -68
- package/template/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +0 -50
- package/template/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +0 -54
- package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +0 -45
- package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +0 -55
- package/template/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +0 -55
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +0 -44
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +0 -40
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +0 -48
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +0 -43
- package/template/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +0 -45
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-constraints.md +0 -80
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +0 -46
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +0 -59
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +0 -55
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +0 -55
- package/template/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +0 -61
- package/template/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +0 -54
- package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +0 -50
- package/template/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +0 -63
- package/template/.agents/skills/writing/humanizer/SKILL.md +0 -621
- package/template/.agents/upstreams.json +0 -116
|
@@ -1,563 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { getLegacyLiveSessionsDir, getLiveSessionsDir, safeSessionId } from '../lib/impeccable-paths.mjs';
|
|
4
|
-
import { COMPLETED_SESSION_PHASES, GENERATION_FENCED_SESSION_PHASES } from './vocabulary.mjs';
|
|
5
|
-
|
|
6
|
-
const COMPLETED_PHASES = new Set(COMPLETED_SESSION_PHASES);
|
|
7
|
-
export const GENERATION_FENCED_PHASES = new Set(GENERATION_FENCED_SESSION_PHASES);
|
|
8
|
-
|
|
9
|
-
// The snapshot file carries two bookkeeping fields the snapshot itself does not
|
|
10
|
-
// own: how large the journal was when the snapshot was written, and the next
|
|
11
|
-
// sequence number. Both are stripped before a snapshot is handed to a caller.
|
|
12
|
-
// The byte count is what makes a cached snapshot verifiable — the journal is
|
|
13
|
-
// append-only, so a matching size means no event has landed since.
|
|
14
|
-
const META_JOURNAL_BYTES = '__journalBytes';
|
|
15
|
-
const META_NEXT_SEQ = '__nextSeq';
|
|
16
|
-
|
|
17
|
-
// TODO(revision-unification): `checkpointRevision`, `browserCheckpointRevision`,
|
|
18
|
-
// and `publicationCheckpointRevision` are three counters for two domains.
|
|
19
|
-
// `checkpointRevision` is a compatibility mirror of the browser counter kept for
|
|
20
|
-
// older readers. Collapsing them means changing what a resumed browser compares
|
|
21
|
-
// its local revision against, so it belongs in a pass that owns resume ordering,
|
|
22
|
-
// not in a caching change.
|
|
23
|
-
|
|
24
|
-
export function createLiveSessionStore({ cwd = process.cwd(), sessionId } = {}) {
|
|
25
|
-
const rootDir = getLiveSessionsDir(cwd);
|
|
26
|
-
const legacyRootDir = getLegacyLiveSessionsDir(cwd);
|
|
27
|
-
fs.mkdirSync(rootDir, { recursive: true });
|
|
28
|
-
|
|
29
|
-
// Derived state per session, keyed by what the journal looked like when it was
|
|
30
|
-
// derived. Publisher/complete helpers append from other processes, so the key
|
|
31
|
-
// is the journal's own (path, size, mtime) rather than a trusted local write
|
|
32
|
-
// count: an append this process did not make invalidates the entry and the
|
|
33
|
-
// next read replays. Without the cache every append and every read replayed
|
|
34
|
-
// the whole journal, which made a long session quadratic in its own length.
|
|
35
|
-
/** @type {Map<string, { snapshot: object, nextSeq: number, journalPath: string, size: number, mtimeMs: number }>} */
|
|
36
|
-
const derived = new Map();
|
|
37
|
-
|
|
38
|
-
function getReadableJournalPath(id) {
|
|
39
|
-
const primary = getJournalPath(rootDir, id);
|
|
40
|
-
if (fs.existsSync(primary)) return primary;
|
|
41
|
-
const legacy = getJournalPath(legacyRootDir, id);
|
|
42
|
-
if (fs.existsSync(legacy)) return legacy;
|
|
43
|
-
return primary;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* The current derived state for a session, from the in-memory cache when the
|
|
48
|
-
* journal has not moved, from the snapshot file when that file is provably
|
|
49
|
-
* current, and from a full replay otherwise.
|
|
50
|
-
*/
|
|
51
|
-
function readState(id, { allowSnapshotFile = true } = {}) {
|
|
52
|
-
const journalPath = getReadableJournalPath(id);
|
|
53
|
-
const stat = statOrNull(journalPath);
|
|
54
|
-
const size = stat ? stat.size : -1;
|
|
55
|
-
const mtimeMs = stat ? stat.mtimeMs : -1;
|
|
56
|
-
|
|
57
|
-
const cached = derived.get(id);
|
|
58
|
-
if (cached && cached.journalPath === journalPath && cached.size === size && cached.mtimeMs === mtimeMs) {
|
|
59
|
-
return cached;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (allowSnapshotFile && stat) {
|
|
63
|
-
const hydrated = readSnapshotFile(getSnapshotPath(rootDir, id), id, size);
|
|
64
|
-
if (hydrated) {
|
|
65
|
-
const entry = { ...hydrated, journalPath, size, mtimeMs };
|
|
66
|
-
derived.set(id, entry);
|
|
67
|
-
return entry;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const rebuilt = rebuildSnapshotFromJournal(journalPath, id);
|
|
72
|
-
const entry = { snapshot: rebuilt.snapshot, nextSeq: rebuilt.nextSeq, journalPath, size, mtimeMs };
|
|
73
|
-
derived.set(id, entry);
|
|
74
|
-
return entry;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function persist(id, snapshot, nextSeq) {
|
|
78
|
-
const snapshotPath = getSnapshotPath(rootDir, id);
|
|
79
|
-
const journalPath = getReadableJournalPath(id);
|
|
80
|
-
const stat = statOrNull(journalPath);
|
|
81
|
-
writeSnapshot(snapshotPath, snapshot, { journalBytes: stat ? stat.size : -1, nextSeq });
|
|
82
|
-
derived.set(id, {
|
|
83
|
-
snapshot,
|
|
84
|
-
nextSeq,
|
|
85
|
-
journalPath,
|
|
86
|
-
size: stat ? stat.size : -1,
|
|
87
|
-
mtimeMs: stat ? stat.mtimeMs : -1,
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return {
|
|
92
|
-
rootDir,
|
|
93
|
-
legacyRootDir,
|
|
94
|
-
appendEvent(event) {
|
|
95
|
-
const normalized = normalizeEvent(event, sessionId);
|
|
96
|
-
const journalPath = getJournalPath(rootDir, normalized.id);
|
|
97
|
-
const legacyJournalPath = getJournalPath(legacyRootDir, normalized.id);
|
|
98
|
-
if (!fs.existsSync(journalPath) && fs.existsSync(legacyJournalPath)) {
|
|
99
|
-
fs.copyFileSync(legacyJournalPath, journalPath);
|
|
100
|
-
// The readable path just moved from legacy to primary; anything derived
|
|
101
|
-
// against the old path describes a file this session no longer reads.
|
|
102
|
-
derived.delete(normalized.id);
|
|
103
|
-
}
|
|
104
|
-
// Reuse the derived state when the journal has not changed under us, and
|
|
105
|
-
// apply the new event on top of it. Correctness still comes from the
|
|
106
|
-
// journal: any append from another process invalidates the entry above
|
|
107
|
-
// and this replays before writing, so sequence numbers and phase fences
|
|
108
|
-
// are never taken from a stale copy.
|
|
109
|
-
const prior = readState(normalized.id);
|
|
110
|
-
const entry = {
|
|
111
|
-
seq: prior.nextSeq,
|
|
112
|
-
id: normalized.id,
|
|
113
|
-
type: normalized.type,
|
|
114
|
-
ts: new Date().toISOString(),
|
|
115
|
-
event: normalized,
|
|
116
|
-
};
|
|
117
|
-
fs.appendFileSync(journalPath, JSON.stringify(entry) + '\n');
|
|
118
|
-
const next = applyEvent(prior.snapshot, entry);
|
|
119
|
-
persist(normalized.id, next, prior.nextSeq + 1);
|
|
120
|
-
return next;
|
|
121
|
-
},
|
|
122
|
-
/**
|
|
123
|
-
* True when a journal exists for the id in either root. appendEvent
|
|
124
|
-
* CREATES a journal for any id it is handed, so callers that should only
|
|
125
|
-
* ever touch existing sessions (browser checkpoints, mount acks) check
|
|
126
|
-
* here first — otherwise a stale id from another project's browser
|
|
127
|
-
* storage materializes a ghost session in this store.
|
|
128
|
-
*/
|
|
129
|
-
has(id) {
|
|
130
|
-
if (!id || typeof id !== 'string') return false;
|
|
131
|
-
return fs.existsSync(getJournalPath(rootDir, id))
|
|
132
|
-
|| fs.existsSync(getJournalPath(legacyRootDir, id));
|
|
133
|
-
},
|
|
134
|
-
/**
|
|
135
|
-
* Read-only. `live-status` and `live-resume` call this against a session a
|
|
136
|
-
* running server owns; writing the snapshot file here made every read a
|
|
137
|
-
* write and let a reader's replay of a half-written journal land on disk.
|
|
138
|
-
* Snapshot files are written by appendEvent and by flush().
|
|
139
|
-
*/
|
|
140
|
-
getSnapshot(id = sessionId, opts = {}) {
|
|
141
|
-
if (!id) throw new Error('session id required');
|
|
142
|
-
const { snapshot } = readState(id);
|
|
143
|
-
if (!opts.includeCompleted && COMPLETED_PHASES.has(snapshot.phase)) return null;
|
|
144
|
-
return snapshot;
|
|
145
|
-
},
|
|
146
|
-
/**
|
|
147
|
-
* Write the snapshot file for a session without appending an event. The
|
|
148
|
-
* durable truth is the journal, so this only refreshes the read cache other
|
|
149
|
-
* processes use; callers that need the state itself should use getSnapshot.
|
|
150
|
-
*/
|
|
151
|
-
flush(id = sessionId) {
|
|
152
|
-
if (!id) throw new Error('session id required');
|
|
153
|
-
const state = readState(id, { allowSnapshotFile: false });
|
|
154
|
-
persist(id, state.snapshot, state.nextSeq);
|
|
155
|
-
return state.snapshot;
|
|
156
|
-
},
|
|
157
|
-
listActiveSessions() {
|
|
158
|
-
const ids = new Set();
|
|
159
|
-
for (const dir of [legacyRootDir, rootDir]) {
|
|
160
|
-
if (!fs.existsSync(dir)) continue;
|
|
161
|
-
for (const name of fs.readdirSync(dir)) {
|
|
162
|
-
if (name.endsWith('.jsonl')) ids.add(name.slice(0, -'.jsonl'.length));
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
// Each id goes through readState, so a session whose journal has not moved
|
|
166
|
-
// since it was last derived costs a stat and nothing more. The server calls
|
|
167
|
-
// this on every /status and on every SSE connect.
|
|
168
|
-
return [...ids]
|
|
169
|
-
.sort()
|
|
170
|
-
.map((id) => this.getSnapshot(id))
|
|
171
|
-
.filter(Boolean);
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function statOrNull(filePath) {
|
|
177
|
-
try {
|
|
178
|
-
return fs.statSync(filePath);
|
|
179
|
-
} catch {
|
|
180
|
-
return null;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* Hydrate derived state from a snapshot file, but only when it provably
|
|
186
|
-
* describes the journal as it stands right now. Anything short of an exact byte
|
|
187
|
-
* match on an append-only file means events landed after the snapshot was
|
|
188
|
-
* written, and the caller replays instead.
|
|
189
|
-
*/
|
|
190
|
-
function readSnapshotFile(snapshotPath, id, journalBytes) {
|
|
191
|
-
let parsed;
|
|
192
|
-
try {
|
|
193
|
-
parsed = JSON.parse(fs.readFileSync(snapshotPath, 'utf-8'));
|
|
194
|
-
} catch {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
if (!parsed || typeof parsed !== 'object') return null;
|
|
198
|
-
if (parsed[META_JOURNAL_BYTES] !== journalBytes) return null;
|
|
199
|
-
if (!Number.isInteger(parsed[META_NEXT_SEQ])) return null;
|
|
200
|
-
const nextSeq = parsed[META_NEXT_SEQ];
|
|
201
|
-
delete parsed[META_JOURNAL_BYTES];
|
|
202
|
-
delete parsed[META_NEXT_SEQ];
|
|
203
|
-
// The journal owns identity; a snapshot file copied between session ids is
|
|
204
|
-
// not a reason to answer with the wrong id.
|
|
205
|
-
if (parsed.id !== id) return null;
|
|
206
|
-
return { snapshot: { ...baseSnapshot(id), ...parsed }, nextSeq };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function normalizeEvent(event, fallbackId) {
|
|
210
|
-
if (!event || typeof event !== 'object') throw new Error('event object required');
|
|
211
|
-
const id = event.id || fallbackId;
|
|
212
|
-
if (!id || typeof id !== 'string') throw new Error('event id required');
|
|
213
|
-
if (!event.type || typeof event.type !== 'string') throw new Error('event type required');
|
|
214
|
-
return { ...event, id };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function getJournalPath(rootDir, id) {
|
|
218
|
-
return path.join(rootDir, safeSessionId(id) + '.jsonl');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function getSnapshotPath(rootDir, id) {
|
|
222
|
-
return path.join(rootDir, safeSessionId(id) + '.snapshot.json');
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function baseSnapshot(id) {
|
|
226
|
-
return {
|
|
227
|
-
id,
|
|
228
|
-
phase: 'new',
|
|
229
|
-
pageUrl: null,
|
|
230
|
-
sourceFile: null,
|
|
231
|
-
previewFile: null,
|
|
232
|
-
previewMode: null,
|
|
233
|
-
expectedVariants: 0,
|
|
234
|
-
arrivedVariants: 0,
|
|
235
|
-
visibleVariant: null,
|
|
236
|
-
paramValues: {},
|
|
237
|
-
pendingEventSeq: null,
|
|
238
|
-
pendingEvent: null,
|
|
239
|
-
deliveryLease: null,
|
|
240
|
-
checkpointRevision: 0,
|
|
241
|
-
browserCheckpointRevision: 0,
|
|
242
|
-
publicationCheckpointRevision: 0,
|
|
243
|
-
activeOwner: null,
|
|
244
|
-
sourceMarkers: {},
|
|
245
|
-
fallbackMode: null,
|
|
246
|
-
generationPhase: null,
|
|
247
|
-
generationCompletedAt: null,
|
|
248
|
-
generationTimings: {},
|
|
249
|
-
variantPlan: null,
|
|
250
|
-
generationCanceled: false,
|
|
251
|
-
generationCanceledAt: null,
|
|
252
|
-
cancelReason: null,
|
|
253
|
-
annotationArtifacts: [],
|
|
254
|
-
// Render truth. `arrivedVariants` says what the agent published; these say
|
|
255
|
-
// what the browser actually got on screen. They are kept alongside the
|
|
256
|
-
// published counters rather than replacing them so older readers keep
|
|
257
|
-
// working, but they are the only fields that answer "did the user ever see
|
|
258
|
-
// a variant".
|
|
259
|
-
mountedVariants: [],
|
|
260
|
-
mountFailures: [],
|
|
261
|
-
renderState: null,
|
|
262
|
-
diagnostics: [],
|
|
263
|
-
updatedAt: null,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// How many mount failures a session keeps. The card in the browser shows the
|
|
268
|
-
// newest one; the agent needs enough history to spot a variant that fails
|
|
269
|
-
// every republish, not the whole retry storm.
|
|
270
|
-
const MOUNT_FAILURE_HISTORY = 5;
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* `pending` = the agent published and nothing has acked yet, `mounted` = at
|
|
274
|
-
* least one variant reached the DOM, `failed` = the browser reported failures
|
|
275
|
-
* and nothing ever mounted. A single success outranks any number of failures:
|
|
276
|
-
* the user is looking at something.
|
|
277
|
-
*/
|
|
278
|
-
function deriveRenderState(snapshot) {
|
|
279
|
-
if (snapshot.mountedVariants.length > 0) return 'mounted';
|
|
280
|
-
if (snapshot.mountFailures.length > 0) return 'failed';
|
|
281
|
-
if (snapshot.generationCompletedAt) return 'pending';
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
function rebuildSnapshotFromJournal(journalPath, id) {
|
|
286
|
-
let snapshot = baseSnapshot(id);
|
|
287
|
-
const diagnostics = [];
|
|
288
|
-
let nextSeq = 1;
|
|
289
|
-
if (!fs.existsSync(journalPath)) return { snapshot, diagnostics, nextSeq };
|
|
290
|
-
|
|
291
|
-
const lines = fs.readFileSync(journalPath, 'utf-8').split('\n');
|
|
292
|
-
for (let i = 0; i < lines.length; i++) {
|
|
293
|
-
const line = lines[i];
|
|
294
|
-
if (!line.trim()) continue;
|
|
295
|
-
try {
|
|
296
|
-
const entry = JSON.parse(line);
|
|
297
|
-
if (!entry || typeof entry !== 'object') throw new Error('entry is not object');
|
|
298
|
-
if (Number.isInteger(entry.seq)) nextSeq = Math.max(nextSeq, entry.seq + 1);
|
|
299
|
-
snapshot = applyEvent(snapshot, entry);
|
|
300
|
-
} catch (err) {
|
|
301
|
-
diagnostics.push({
|
|
302
|
-
error: 'journal_parse_failed',
|
|
303
|
-
line: i + 1,
|
|
304
|
-
message: err.message,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
snapshot.diagnostics = [...snapshot.diagnostics, ...diagnostics];
|
|
309
|
-
return { snapshot, diagnostics, nextSeq };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function applyEvent(snapshot, entry) {
|
|
313
|
-
const event = entry.event || entry;
|
|
314
|
-
const next = {
|
|
315
|
-
...snapshot,
|
|
316
|
-
paramValues: { ...(snapshot.paramValues || {}) },
|
|
317
|
-
sourceMarkers: { ...(snapshot.sourceMarkers || {}) },
|
|
318
|
-
generationTimings: { ...(snapshot.generationTimings || {}) },
|
|
319
|
-
variantPlan: snapshot.variantPlan || null,
|
|
320
|
-
annotationArtifacts: [...(snapshot.annotationArtifacts || [])],
|
|
321
|
-
mountedVariants: [...(snapshot.mountedVariants || [])],
|
|
322
|
-
mountFailures: [...(snapshot.mountFailures || [])],
|
|
323
|
-
renderState: snapshot.renderState ?? null,
|
|
324
|
-
diagnostics: [...(snapshot.diagnostics || [])],
|
|
325
|
-
updatedAt: entry.ts || new Date().toISOString(),
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
switch (event.type) {
|
|
329
|
-
case 'generate':
|
|
330
|
-
next.phase = 'generate_requested';
|
|
331
|
-
next.pageUrl = event.pageUrl ?? next.pageUrl;
|
|
332
|
-
next.expectedVariants = event.count ?? next.expectedVariants;
|
|
333
|
-
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
334
|
-
next.pendingEvent = toPendingEvent(event);
|
|
335
|
-
next.variantPlan = null;
|
|
336
|
-
// A new cycle publishes new files: everything the browser told us about
|
|
337
|
-
// the previous batch is now about modules that no longer exist.
|
|
338
|
-
next.mountedVariants = [];
|
|
339
|
-
next.mountFailures = [];
|
|
340
|
-
next.renderState = null;
|
|
341
|
-
if (event.screenshotPath) upsertArtifact(next.annotationArtifacts, { type: 'screenshot', path: event.screenshotPath });
|
|
342
|
-
break;
|
|
343
|
-
case 'variant_plan':
|
|
344
|
-
if (!next.generationCanceled && !GENERATION_FENCED_PHASES.has(next.phase)) {
|
|
345
|
-
next.variantPlan = event.plan ?? next.variantPlan;
|
|
346
|
-
}
|
|
347
|
-
break;
|
|
348
|
-
case 'detector_waivers':
|
|
349
|
-
if (!next.generationCanceled && !GENERATION_FENCED_PHASES.has(next.phase)) {
|
|
350
|
-
next.detectorWaivers = [
|
|
351
|
-
...(next.detectorWaivers || []),
|
|
352
|
-
...(Array.isArray(event.waivers) ? event.waivers : []),
|
|
353
|
-
];
|
|
354
|
-
}
|
|
355
|
-
break;
|
|
356
|
-
case 'agent_phase':
|
|
357
|
-
next.generationPhase = event.phase ?? next.generationPhase;
|
|
358
|
-
if (event.phase) {
|
|
359
|
-
next.generationTimings[event.phase] = {
|
|
360
|
-
at: event.at ?? (Date.parse(entry.ts || '') || null),
|
|
361
|
-
durationMs: event.durationMs ?? null,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
break;
|
|
365
|
-
case 'variants_ready':
|
|
366
|
-
case 'agent_done':
|
|
367
|
-
if ((next.generationCanceled || GENERATION_FENCED_PHASES.has(next.phase))
|
|
368
|
-
&& !(event.type === 'agent_done' && event.carbonize === true && next.phase === 'accept_requested')) {
|
|
369
|
-
next.diagnostics.push({
|
|
370
|
-
error: 'late_generation_event_ignored',
|
|
371
|
-
type: event.type,
|
|
372
|
-
phase: next.phase,
|
|
373
|
-
});
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
next.phase = event.carbonize === true ? 'carbonize_required' : 'variants_ready';
|
|
377
|
-
// Durable completion marker: later browser checkpoints (a resumed page
|
|
378
|
-
// reporting phase "generating") regress `phase`, but generation staying
|
|
379
|
-
// finished is monotone — the live server keys missed-`done` redelivery
|
|
380
|
-
// on this field.
|
|
381
|
-
next.generationCompletedAt = event.at ?? (Date.parse(entry.ts || '') || Date.now());
|
|
382
|
-
next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;
|
|
383
|
-
next.previewFile = event.previewFile ?? next.previewFile;
|
|
384
|
-
next.previewMode = event.previewMode ?? next.previewMode;
|
|
385
|
-
next.arrivedVariants = event.arrivedVariants ?? (next.expectedVariants || next.arrivedVariants || 0);
|
|
386
|
-
next.pendingEventSeq = null;
|
|
387
|
-
next.pendingEvent = null;
|
|
388
|
-
if (event.carbonize === true) {
|
|
389
|
-
next.diagnostics.push({
|
|
390
|
-
error: 'carbonize_cleanup_required',
|
|
391
|
-
file: event.file || null,
|
|
392
|
-
message: 'Accepted variant still has carbonize markers that must be folded into source CSS.',
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
next.renderState = deriveRenderState(next);
|
|
396
|
-
break;
|
|
397
|
-
case 'variant_mounted': {
|
|
398
|
-
const variant = Number(event.variant);
|
|
399
|
-
if (!Number.isInteger(variant) || variant < 1) {
|
|
400
|
-
next.diagnostics.push({ error: 'malformed_mount_ack', type: event.type, variant: event.variant ?? null });
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
if (!next.mountedVariants.includes(variant)) {
|
|
404
|
-
next.mountedVariants = [...next.mountedVariants, variant].sort((a, b) => a - b);
|
|
405
|
-
}
|
|
406
|
-
next.renderState = deriveRenderState(next);
|
|
407
|
-
break;
|
|
408
|
-
}
|
|
409
|
-
case 'variant_mount_failed': {
|
|
410
|
-
const variant = Number(event.variant);
|
|
411
|
-
if (!Number.isInteger(variant) || variant < 1) {
|
|
412
|
-
next.diagnostics.push({ error: 'malformed_mount_ack', type: event.type, variant: event.variant ?? null });
|
|
413
|
-
break;
|
|
414
|
-
}
|
|
415
|
-
next.mountFailures = [
|
|
416
|
-
...next.mountFailures,
|
|
417
|
-
{
|
|
418
|
-
variant,
|
|
419
|
-
url: typeof event.url === 'string' ? event.url : null,
|
|
420
|
-
error: typeof event.error === 'string' ? event.error : null,
|
|
421
|
-
at: event.at ?? (Date.parse(entry.ts || '') || Date.now()),
|
|
422
|
-
},
|
|
423
|
-
].slice(-MOUNT_FAILURE_HISTORY);
|
|
424
|
-
next.renderState = deriveRenderState(next);
|
|
425
|
-
// The failure needs an agent reply, so it must survive a helper
|
|
426
|
-
// restart the same way a generate does. Never clobber a still-pending
|
|
427
|
-
// generate: a progressive publish can fail an early mount while the
|
|
428
|
-
// generate event itself is still leased.
|
|
429
|
-
if (!next.pendingEvent) {
|
|
430
|
-
next.pendingEvent = toPendingEvent(event);
|
|
431
|
-
}
|
|
432
|
-
break;
|
|
433
|
-
}
|
|
434
|
-
case 'checkpoint':
|
|
435
|
-
if (next.generationCanceled || GENERATION_FENCED_PHASES.has(next.phase)) {
|
|
436
|
-
next.diagnostics.push({ error: 'checkpoint_after_terminal_ignored', phase: event.phase ?? null, revision: event.revision ?? null });
|
|
437
|
-
break;
|
|
438
|
-
}
|
|
439
|
-
{
|
|
440
|
-
const revisionDomain = event.revisionDomain === 'publication'
|
|
441
|
-
|| (event.reason === 'variants_progress' && !event.owner)
|
|
442
|
-
? 'publication'
|
|
443
|
-
: 'browser';
|
|
444
|
-
const revisionField = revisionDomain === 'publication'
|
|
445
|
-
? 'publicationCheckpointRevision'
|
|
446
|
-
: 'browserCheckpointRevision';
|
|
447
|
-
const currentRevision = next[revisionField]
|
|
448
|
-
?? (revisionDomain === 'browser' ? next.checkpointRevision : 0)
|
|
449
|
-
?? 0;
|
|
450
|
-
if ((event.revision ?? 0) >= currentRevision) {
|
|
451
|
-
next.phase = event.phase ?? next.phase;
|
|
452
|
-
next[revisionField] = event.revision ?? currentRevision;
|
|
453
|
-
if (revisionDomain === 'browser') {
|
|
454
|
-
next.checkpointRevision = event.revision ?? next.checkpointRevision;
|
|
455
|
-
next.activeOwner = event.owner ?? next.activeOwner;
|
|
456
|
-
}
|
|
457
|
-
next.arrivedVariants = event.arrivedVariants ?? next.arrivedVariants;
|
|
458
|
-
if (revisionDomain === 'browser') next.visibleVariant = event.visibleVariant ?? next.visibleVariant;
|
|
459
|
-
next.sourceFile = event.sourceFile ?? next.sourceFile;
|
|
460
|
-
next.previewFile = event.previewFile ?? next.previewFile;
|
|
461
|
-
next.previewMode = event.previewMode ?? next.previewMode;
|
|
462
|
-
if (revisionDomain === 'browser' && event.paramValues) next.paramValues = { ...event.paramValues };
|
|
463
|
-
} else {
|
|
464
|
-
next.diagnostics.push({ error: 'stale_checkpoint_ignored', revision: event.revision, revisionDomain });
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
break;
|
|
468
|
-
case 'accept':
|
|
469
|
-
case 'accept_intent':
|
|
470
|
-
next.phase = 'accept_requested';
|
|
471
|
-
next.generationCanceled = true;
|
|
472
|
-
next.generationCanceledAt = event.at ?? (Date.parse(entry.ts || '') || Date.now());
|
|
473
|
-
next.cancelReason = 'accept';
|
|
474
|
-
next.visibleVariant = Number(event.variantId ?? next.visibleVariant);
|
|
475
|
-
if (event.paramValues) next.paramValues = { ...event.paramValues };
|
|
476
|
-
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
477
|
-
next.pendingEvent = toPendingEvent(event);
|
|
478
|
-
break;
|
|
479
|
-
case 'manual_edit_apply':
|
|
480
|
-
next.phase = 'manual_edit_apply_requested';
|
|
481
|
-
next.pageUrl = event.pageUrl ?? next.pageUrl;
|
|
482
|
-
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
483
|
-
next.pendingEvent = toPendingEvent(event);
|
|
484
|
-
break;
|
|
485
|
-
case 'steer':
|
|
486
|
-
next.phase = 'steer_requested';
|
|
487
|
-
next.pageUrl = event.pageUrl ?? next.pageUrl;
|
|
488
|
-
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
489
|
-
next.pendingEvent = toPendingEvent(event);
|
|
490
|
-
break;
|
|
491
|
-
case 'carbonize_cleanup':
|
|
492
|
-
next.phase = 'carbonize_cleanup_requested';
|
|
493
|
-
next.sourceFile = event.file ?? next.sourceFile;
|
|
494
|
-
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
495
|
-
next.pendingEvent = toPendingEvent(event);
|
|
496
|
-
break;
|
|
497
|
-
case 'steer_done':
|
|
498
|
-
next.phase = 'steer_done';
|
|
499
|
-
next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;
|
|
500
|
-
next.previewFile = event.previewFile ?? next.previewFile;
|
|
501
|
-
next.previewMode = event.previewMode ?? next.previewMode;
|
|
502
|
-
next.message = event.message ?? next.message;
|
|
503
|
-
next.pendingEventSeq = null;
|
|
504
|
-
next.pendingEvent = null;
|
|
505
|
-
break;
|
|
506
|
-
case 'discard':
|
|
507
|
-
next.phase = 'discard_requested';
|
|
508
|
-
next.generationCanceled = true;
|
|
509
|
-
next.generationCanceledAt = event.at ?? (Date.parse(entry.ts || '') || Date.now());
|
|
510
|
-
next.cancelReason = 'discard';
|
|
511
|
-
next.pendingEventSeq = entry.seq ?? next.pendingEventSeq;
|
|
512
|
-
next.pendingEvent = toPendingEvent(event);
|
|
513
|
-
break;
|
|
514
|
-
case 'discarded':
|
|
515
|
-
next.phase = 'discarded';
|
|
516
|
-
next.pendingEventSeq = null;
|
|
517
|
-
next.pendingEvent = null;
|
|
518
|
-
break;
|
|
519
|
-
case 'complete':
|
|
520
|
-
next.phase = 'completed';
|
|
521
|
-
next.sourceFile = event.sourceFile ?? event.file ?? next.sourceFile;
|
|
522
|
-
next.previewFile = event.previewFile ?? next.previewFile;
|
|
523
|
-
next.previewMode = event.previewMode ?? next.previewMode;
|
|
524
|
-
next.pendingEventSeq = null;
|
|
525
|
-
next.pendingEvent = null;
|
|
526
|
-
break;
|
|
527
|
-
case 'agent_error':
|
|
528
|
-
if (next.generationCanceled && event.sourceEventType === 'generate') {
|
|
529
|
-
next.diagnostics.push({ error: 'late_generation_event_ignored', type: event.type, phase: next.phase });
|
|
530
|
-
break;
|
|
531
|
-
}
|
|
532
|
-
next.phase = 'agent_error';
|
|
533
|
-
next.pendingEventSeq = null;
|
|
534
|
-
next.pendingEvent = null;
|
|
535
|
-
next.diagnostics.push({ error: 'agent_error', message: event.message || 'unknown agent error' });
|
|
536
|
-
break;
|
|
537
|
-
default:
|
|
538
|
-
next.diagnostics.push({ error: 'unknown_event_type', type: event.type });
|
|
539
|
-
break;
|
|
540
|
-
}
|
|
541
|
-
return next;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
function toPendingEvent(event) {
|
|
545
|
-
const pending = { ...event };
|
|
546
|
-
delete pending.token;
|
|
547
|
-
return pending;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function upsertArtifact(artifacts, artifact) {
|
|
551
|
-
if (!artifacts.some((existing) => existing.path === artifact.path && existing.type === artifact.type)) {
|
|
552
|
-
artifacts.push(artifact);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
function writeSnapshot(snapshotPath, snapshot, meta) {
|
|
557
|
-
const payload = {
|
|
558
|
-
...snapshot,
|
|
559
|
-
[META_JOURNAL_BYTES]: meta?.journalBytes ?? -1,
|
|
560
|
-
[META_NEXT_SEQ]: meta?.nextSeq ?? 1,
|
|
561
|
-
};
|
|
562
|
-
fs.writeFileSync(snapshotPath, JSON.stringify(payload, null, 2) + '\n');
|
|
563
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
-
import { getLiveDir, isLiveServerPidReachable } from '../lib/impeccable-paths.mjs';
|
|
5
|
-
|
|
6
|
-
// Only used to retire a lock whose contents we cannot read (empty or truncated
|
|
7
|
-
// by a crash mid-write). A readable lock's fate is decided by its owner's
|
|
8
|
-
// liveness instead, so a slow critical section is never swept.
|
|
9
|
-
const UNREADABLE_LOCK_STALE_MS = 60_000;
|
|
10
|
-
|
|
11
|
-
export function sourceLockPath(file, cwd = process.cwd()) {
|
|
12
|
-
const digest = createHash('sha256').update(path.resolve(cwd, file)).digest('hex').slice(0, 24);
|
|
13
|
-
return path.join(getLiveDir(cwd), 'locks', digest + '.lock');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function withSourceLockSync(file, owner, fn, {
|
|
17
|
-
cwd = process.cwd(),
|
|
18
|
-
waitMs = 0,
|
|
19
|
-
retryMs = 5,
|
|
20
|
-
} = {}) {
|
|
21
|
-
const lockPath = sourceLockPath(file, cwd);
|
|
22
|
-
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
23
|
-
const deadline = Date.now() + Math.max(0, Number(waitMs) || 0);
|
|
24
|
-
// Identifies this acquisition specifically, so release can tell our own lock
|
|
25
|
-
// from a replacement that some other writer created.
|
|
26
|
-
const token = randomUUID();
|
|
27
|
-
let acquired = false;
|
|
28
|
-
|
|
29
|
-
while (!acquired) {
|
|
30
|
-
clearStaleLock(lockPath);
|
|
31
|
-
let fd;
|
|
32
|
-
try {
|
|
33
|
-
fd = fs.openSync(lockPath, 'wx');
|
|
34
|
-
fs.writeFileSync(fd, JSON.stringify({
|
|
35
|
-
owner,
|
|
36
|
-
token,
|
|
37
|
-
pid: process.pid,
|
|
38
|
-
at: Date.now(),
|
|
39
|
-
file: path.resolve(cwd, file),
|
|
40
|
-
}) + '\n');
|
|
41
|
-
acquired = true;
|
|
42
|
-
} catch (error) {
|
|
43
|
-
if (error?.code !== 'EEXIST') throw error;
|
|
44
|
-
if (Date.now() >= deadline) {
|
|
45
|
-
const locked = new Error('source_locked');
|
|
46
|
-
locked.code = 'SOURCE_LOCKED';
|
|
47
|
-
locked.lockPath = lockPath;
|
|
48
|
-
throw locked;
|
|
49
|
-
}
|
|
50
|
-
sleepSync(Math.max(1, Math.min(Number(retryMs) || 5, deadline - Date.now())));
|
|
51
|
-
} finally {
|
|
52
|
-
try { if (fd !== undefined) fs.closeSync(fd); } catch {}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
try {
|
|
57
|
-
return fn();
|
|
58
|
-
} finally {
|
|
59
|
-
releaseOwnLock(lockPath, token);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function sleepSync(ms) {
|
|
64
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function readLock(lockPath) {
|
|
68
|
-
try { return JSON.parse(fs.readFileSync(lockPath, 'utf-8')); } catch { return null; }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Remove the lock only if it is still the one this call created. If a sweeper
|
|
73
|
-
* judged our lock stale and another writer replaced it, unlinking here would
|
|
74
|
-
* end *their* critical section and admit a third writer to the same file.
|
|
75
|
-
*/
|
|
76
|
-
function releaseOwnLock(lockPath, token) {
|
|
77
|
-
const held = readLock(lockPath);
|
|
78
|
-
if (held && held.token !== token) return;
|
|
79
|
-
try { fs.unlinkSync(lockPath); } catch {}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* A lock is stale when its owner is gone, not when it is old.
|
|
84
|
-
*
|
|
85
|
-
* Age alone cuts both ways: it sweeps a live holder whose critical section
|
|
86
|
-
* outran the timeout (a suspended laptop, a stopped process), letting two
|
|
87
|
-
* writers into the same source file, while still making every accept on a
|
|
88
|
-
* crashed holder's file wait out the full timeout. Asking the OS whether the
|
|
89
|
-
* recorded pid is alive answers both correctly: a dead owner releases at once,
|
|
90
|
-
* and a live owner keeps its lock however long it needs.
|
|
91
|
-
*/
|
|
92
|
-
function clearStaleLock(lockPath) {
|
|
93
|
-
const held = readLock(lockPath);
|
|
94
|
-
if (!held) {
|
|
95
|
-
// Unreadable: either a crash truncated it, or we caught the brief window
|
|
96
|
-
// between create and write in a live acquisition. mtime distinguishes them.
|
|
97
|
-
try {
|
|
98
|
-
const stat = fs.statSync(lockPath);
|
|
99
|
-
if (Date.now() - stat.mtimeMs > UNREADABLE_LOCK_STALE_MS) fs.unlinkSync(lockPath);
|
|
100
|
-
} catch { /* gone already */ }
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
if (typeof held.pid === 'number' && isLiveServerPidReachable(held.pid)) return;
|
|
104
|
-
try { fs.unlinkSync(lockPath); } catch {}
|
|
105
|
-
}
|