@chemx/starter-kit 26.10.8-344 → 26.10.10-75
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/AGENTS.md +135 -32
- package/README.md +253 -199
- package/blueprints/atoms/a-button.svelte +11 -9
- package/blueprints/atoms/a-button.vue +2 -1
- package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.svelte +8 -4
- package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.tsx +8 -4
- package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.vue +8 -4
- package/blueprints/molecule-capsule/m-sample-card.svelte +2 -1
- package/blueprints/molecule-capsule/m-sample-card.tsx +2 -1
- package/blueprints/molecule-capsule/m-sample-card.vue +4 -2
- package/blueprints/workflows/chemx-audit.yml +15 -46
- package/cli/agent-json.js +40 -0
- package/cli/apply-edits.js +195 -0
- package/cli/audit/ai-slop-detector.js +62 -35
- package/cli/audit/ast-passes.js +52 -0
- package/cli/audit/ast-visitors.js +142 -83
- package/cli/audit/audit-plain.js +26 -0
- package/cli/audit/audit-summary.js +16 -3
- package/cli/audit/autofix-content.js +122 -0
- package/cli/audit/autofix.js +74 -159
- package/cli/audit/budget-rules.js +84 -0
- package/cli/audit/catch-predicates.js +51 -0
- package/cli/audit/chemx-dir.js +76 -0
- package/cli/audit/clone-detector.js +4 -2
- package/cli/audit/comment-regions.js +83 -0
- package/cli/audit/coverage.js +48 -0
- package/cli/audit/csharp-analyzer.js +47 -22
- package/cli/audit/discussion-store.js +9 -4
- package/cli/audit/extended-text-patterns.js +104 -54
- package/cli/audit/extended-visitors.js +26 -12
- package/cli/audit/feed.d.ts +77 -0
- package/cli/audit/feed.js +146 -0
- package/cli/audit/gate-delta.js +91 -0
- package/cli/audit/gate-verdict.js +25 -3
- package/cli/audit/history.d.ts +14 -2
- package/cli/audit/history.js +89 -101
- package/cli/audit/hook-shape-validator.js +18 -9
- package/cli/audit/lexicon-predicates.js +125 -0
- package/cli/audit/lifecycle-predicates.js +106 -0
- package/cli/audit/line-budgets.js +154 -0
- package/cli/audit/metrics.js +56 -37
- package/cli/audit/mutable-ref-predicate.js +54 -0
- package/cli/audit/parseable-code.js +11 -0
- package/cli/audit/pattern-detector.js +56 -28
- package/cli/audit/project-figures.js +28 -0
- package/cli/audit/prompt-rule-lines.js +46 -0
- package/cli/audit/prompts.js +74 -46
- package/cli/audit/prop-surface.js +74 -0
- package/cli/audit/ratchet.js +112 -23
- package/cli/audit/render-depth.js +79 -0
- package/cli/audit/reporter-banner.js +7 -4
- package/cli/audit/reporter-grades.js +52 -34
- package/cli/audit/reporter-grouping.js +10 -5
- package/cli/audit/reporter-markdown.js +16 -8
- package/cli/audit/reporter-sections.js +31 -16
- package/cli/audit/reporter-summary.js +28 -16
- package/cli/audit/reporter-utils.js +54 -30
- package/cli/audit/roadmap.js +20 -15
- package/cli/audit/rule-overrides.js +78 -0
- package/cli/audit/rule-pillars.js +43 -0
- package/cli/audit/rule-revisions.js +42 -0
- package/cli/audit/rules-helpers.js +13 -63
- package/cli/audit/rules-predicates.d.ts +1 -0
- package/cli/audit/rules-predicates.js +84 -64
- package/cli/audit/rules-registry.js +94 -10
- package/cli/audit/rules.js +92 -171
- package/cli/audit/shallow-catch-csharp.js +49 -0
- package/cli/audit/shallow-catch-escalation.js +157 -0
- package/cli/audit/shallow-catch-reads.js +116 -0
- package/cli/audit/social-gh.js +18 -9
- package/cli/audit/social-git.js +82 -40
- package/cli/audit/social-http.js +17 -9
- package/cli/audit/social-publisher.js +14 -7
- package/cli/audit/social.js +39 -30
- package/cli/audit/staged-delta.js +67 -0
- package/cli/audit/status-file.js +67 -0
- package/cli/audit/structural-weight-evaluator.js +17 -29
- package/cli/audit/structural-weight.js +30 -13
- package/cli/audit/suppressions.js +130 -0
- package/cli/audit/template-rules.js +82 -0
- package/cli/audit/types.d.ts +24 -1
- package/cli/audit-engine.js +128 -0
- package/cli/audit-preflight-git.js +15 -1
- package/cli/audit-preflight.js +6 -4
- package/cli/audit-scan.js +180 -0
- package/cli/audit-scope.js +31 -14
- package/cli/audit.js +6 -201
- package/cli/babel-lazy.js +41 -0
- package/cli/badge.js +9 -6
- package/cli/banner.js +37 -0
- package/cli/build/child-registry.js +57 -0
- package/cli/build/executor.js +122 -48
- package/cli/build.js +75 -25
- package/cli/cli-args.js +107 -0
- package/cli/columnar.js +6 -3
- package/cli/commands/cmd-audit-each.js +164 -0
- package/cli/commands/cmd-audit-feed.js +78 -0
- package/cli/commands/cmd-audit.js +136 -57
- package/cli/commands/cmd-batch.js +60 -0
- package/cli/commands/cmd-check.js +74 -0
- package/cli/commands/cmd-project.js +18 -9
- package/cli/commands/cmd-router.js +148 -32
- package/cli/commands/cmd-show.js +56 -0
- package/cli/commands/cmd-staged-delta.js +27 -0
- package/cli/commands/cmd-wrappers-files.js +77 -0
- package/cli/commands/cmd-wrappers-git.js +122 -0
- package/cli/commands/cmd-wrappers-json.js +101 -0
- package/cli/commands/cmd-wrappers.js +7 -322
- package/cli/commands/unknown-flags.js +228 -0
- package/cli/commands-schema-badge.js +73 -0
- package/cli/commands-schema-commit.js +21 -0
- package/cli/commands-schema-edit.js +106 -0
- package/cli/commands-schema-host.js +65 -0
- package/cli/commands-schema-ops.js +127 -0
- package/cli/commands-schema-patterns.js +69 -0
- package/cli/commands-schema-report.js +18 -0
- package/cli/commands-schema-search.js +121 -0
- package/cli/commands-schema-setup.js +30 -0
- package/cli/commands-schema-verify.js +89 -0
- package/cli/commands-schema-wrappers.js +131 -0
- package/cli/commands-schema.js +26 -408
- package/cli/comment-ranges.js +108 -0
- package/cli/commit/commit-args.js +73 -0
- package/cli/commit/commit-cli.js +25 -0
- package/cli/commit/commit-facts.js +49 -0
- package/cli/commit/commit-git.js +98 -0
- package/cli/commit/commit-index.js +35 -0
- package/cli/commit/commit-message.js +52 -0
- package/cli/commit/commit-record.js +68 -0
- package/cli/commit/commit-run.js +160 -0
- package/cli/commit/commit-validate.js +62 -0
- package/cli/config/index.js +54 -8
- package/cli/config/loader.js +77 -8
- package/cli/config/profiles.js +7 -0
- package/cli/conflicts-cli.js +118 -0
- package/cli/conflicts.js +104 -0
- package/cli/create.js +11 -6
- package/cli/db-project-stamp.js +124 -0
- package/cli/docs-check/command-tree.js +27 -0
- package/cli/docs-check/extract.js +151 -0
- package/cli/docs-check/resolve.js +85 -0
- package/cli/docs-check/run.js +88 -0
- package/cli/doctor/check-env.js +43 -0
- package/cli/doctor/check-host.js +115 -0
- package/cli/doctor/check-index.js +93 -0
- package/cli/doctor/check-mcp.js +78 -0
- package/cli/doctor/doctor-cli.js +60 -0
- package/cli/doctor/kit-locate.js +40 -0
- package/cli/doctor/proc-scan.js +66 -0
- package/cli/edit-atomic.js +75 -0
- package/cli/edit-diff.js +98 -0
- package/cli/edit-guardrails.js +77 -0
- package/cli/edit-locks.js +128 -0
- package/cli/embeddings/vectorizer.js +9 -5
- package/cli/errors/catcher.js +45 -52
- package/cli/errors/formatter.js +14 -10
- package/cli/errors/policy.js +26 -0
- package/cli/errors/publisher.js +5 -1
- package/cli/errors/report-output.js +67 -0
- package/cli/errors/sanitizer.js +52 -3
- package/cli/errors/storage.js +17 -5
- package/cli/explode-importers.js +102 -0
- package/cli/explode-path-args.js +115 -0
- package/cli/explode-plan.js +153 -0
- package/cli/explode-prologue.js +43 -0
- package/cli/explode-relocate.js +73 -0
- package/cli/exploder.js +205 -193
- package/cli/forge/anchors.js +50 -0
- package/cli/forge/audit-forge.js +58 -0
- package/cli/forge/binding-walk.js +84 -0
- package/cli/forge/bindings.js +238 -0
- package/cli/forge/blueprint-cli.js +153 -0
- package/cli/forge/blueprint-context.js +69 -0
- package/cli/forge/blueprint-members.js +82 -0
- package/cli/forge/blueprint-store.js +106 -0
- package/cli/forge/blueprint-target.js +44 -0
- package/cli/forge/blueprint.js +276 -0
- package/cli/forge/body-end-cache.js +34 -0
- package/cli/forge/body-ends.js +86 -0
- package/cli/forge/canon-inline.js +342 -0
- package/cli/forge/canon-logic.js +106 -0
- package/cli/forge/canon-nodes.js +270 -0
- package/cli/forge/canon-print.js +120 -0
- package/cli/forge/canonicalize.js +49 -0
- package/cli/forge/conventions.js +51 -0
- package/cli/forge/drift.js +120 -0
- package/cli/forge/exclusions.js +21 -0
- package/cli/forge/exits.js +76 -0
- package/cli/forge/facets.js +86 -0
- package/cli/forge/file-units.js +53 -0
- package/cli/forge/fingerprint-file.js +52 -0
- package/cli/forge/fingerprint-session.js +215 -0
- package/cli/forge/fingerprint-sync.js +74 -0
- package/cli/forge/fingerprint-visitors.js +66 -0
- package/cli/forge/fixtures/heal/a7-dry-run.diff.txt +179 -0
- package/cli/forge/fixtures/heal/cli/commit/commit-facts.js.txt +49 -0
- package/cli/forge/fixtures/heal/cli/doctor/check-host.js.txt +117 -0
- package/cli/forge/fixtures/heal/cli/doctor/kit-locate.js.txt +42 -0
- package/cli/forge/fixtures/heal/cli/hooks/guard-paths.js.txt +80 -0
- package/cli/forge/fixtures/heal/cli/hooks/project-status.js.txt +62 -0
- package/cli/forge/fixtures/heal/cli/main.js.txt +169 -0
- package/cli/forge/fixtures/heal/cli/mcp/context.js.txt +81 -0
- package/cli/forge/fixtures/heal/cli/mcp/installer-report.js.txt +47 -0
- package/cli/forge/fixtures/heal/cli/path-scope.js.txt +169 -0
- package/cli/forge/fixtures/heal/cli/project-detector.js.txt +364 -0
- package/cli/forge/fixtures/heal/cli/project-markers.js.txt +42 -0
- package/cli/forge/fixtures/heal/cli/team/coordination-root.js.txt +161 -0
- package/cli/forge/fixtures/heal/cli/team/usage-reader.js.txt +109 -0
- package/cli/forge/fixtures/heal/cli/test-slots.js.txt +129 -0
- package/cli/forge/fixtures/social-gh.js.txt +131 -0
- package/cli/forge/fixtures/team-flags.js.txt +214 -0
- package/cli/forge/fold.js +199 -0
- package/cli/forge/forge-groups.js +200 -0
- package/cli/forge/forge-report.js +73 -0
- package/cli/forge/fuzz/binding-hook.js +36 -0
- package/cli/forge/fuzz/choices.js +40 -0
- package/cli/forge/fuzz/classes.js +41 -0
- package/cli/forge/fuzz/coverage.js +26 -0
- package/cli/forge/fuzz/gen-alias.js +61 -0
- package/cli/forge/fuzz/gen-control.js +73 -0
- package/cli/forge/fuzz/gen-kit.js +75 -0
- package/cli/forge/fuzz/gen-logic.js +77 -0
- package/cli/forge/fuzz/gen-mixed.js +204 -0
- package/cli/forge/fuzz/gen-modules.js +80 -0
- package/cli/forge/fuzz/gen-objects.js +92 -0
- package/cli/forge/fuzz/gen-scope.js +131 -0
- package/cli/forge/fuzz/gen-templates.js +87 -0
- package/cli/forge/fuzz/harness.js +314 -0
- package/cli/forge/fuzz/pair-check.js +115 -0
- package/cli/forge/fuzz/resolve.js +32 -0
- package/cli/forge/fuzz/run.js +121 -0
- package/cli/forge/fuzz/sandbox.js +108 -0
- package/cli/forge/fuzz/seeds.js +119 -0
- package/cli/forge/fuzz/slow-shard.js +60 -0
- package/cli/forge/fuzz/template-oracle.js +148 -0
- package/cli/forge/fuzz/transpile.js +61 -0
- package/cli/forge/gates.js +115 -0
- package/cli/forge/group-shape.js +137 -0
- package/cli/forge/group-store.js +160 -0
- package/cli/forge/group.js +280 -0
- package/cli/forge/gt-sandbox.js +83 -0
- package/cli/forge/gt-scaffold.js +110 -0
- package/cli/forge/hash-labels.js +117 -0
- package/cli/forge/hash.js +119 -0
- package/cli/forge/heal-apply.js +170 -0
- package/cli/forge/heal-cli.js +132 -0
- package/cli/forge/heal-leases.js +52 -0
- package/cli/forge/heal-locate.js +70 -0
- package/cli/forge/heal-match.js +208 -0
- package/cli/forge/heal-ops.js +197 -0
- package/cli/forge/heal-plan.js +143 -0
- package/cli/forge/heal-sandbox.js +126 -0
- package/cli/forge/heal-specs.js +80 -0
- package/cli/forge/heal-store.js +80 -0
- package/cli/forge/heal-text.js +60 -0
- package/cli/forge/heal-typecheck.js +108 -0
- package/cli/forge/heal-undo.js +74 -0
- package/cli/forge/heal-verify.js +148 -0
- package/cli/forge/hole-kinds.js +126 -0
- package/cli/forge/inline-mode.js +6 -0
- package/cli/forge/inline-on.setup.js +4 -0
- package/cli/forge/lgg-stage.js +125 -0
- package/cli/forge/lgg.js +197 -0
- package/cli/forge/library-match.js +74 -0
- package/cli/forge/murmur.js +86 -0
- package/cli/forge/naming.js +101 -0
- package/cli/forge/pattern-schema-ddl.js +157 -0
- package/cli/forge/placement.js +165 -0
- package/cli/forge/rank.js +174 -0
- package/cli/forge/refine.js +93 -0
- package/cli/forge/rejected-members.js +92 -0
- package/cli/forge/rejects.js +77 -0
- package/cli/forge/root-shapes.js +84 -0
- package/cli/forge/run-cache.js +145 -0
- package/cli/forge/sibling-blocks.js +41 -0
- package/cli/forge/siblings.js +140 -0
- package/cli/forge/store.js +180 -0
- package/cli/forge/template-normalize.js +209 -0
- package/cli/forge/template-roles.js +86 -0
- package/cli/forge/template-units.js +148 -0
- package/cli/forge/templates.js +42 -0
- package/cli/forge/unify-step.js +65 -0
- package/cli/forge/unit-floor.js +74 -0
- package/cli/forge/unit-hash.js +229 -0
- package/cli/forge/unit-trees.js +153 -0
- package/cli/forge/units.js +261 -0
- package/cli/forge/windows.js +129 -0
- package/cli/friction/friction-cli.js +71 -0
- package/cli/friction/friction-export.js +54 -0
- package/cli/friction/friction-log.js +54 -0
- package/cli/friction/usage-classify.js +56 -0
- package/cli/friction/usage-report.js +86 -0
- package/cli/friction/wrong-call.js +12 -0
- package/cli/fs-json.js +17 -0
- package/cli/generator-compact.js +55 -0
- package/cli/generator-help.js +16 -15
- package/cli/generator-indexer.js +2 -1
- package/cli/generator-jig-cli.js +11 -7
- package/cli/generator-jig.js +38 -33
- package/cli/generator-templates/archetypes/index.js +30 -13
- package/cli/generator-templates/archetypes/vector-matcher.js +8 -4
- package/cli/generator-templates/compact.js +4 -2
- package/cli/generator-templates/controller-converters.js +23 -14
- package/cli/generator-templates/controller.js +10 -5
- package/cli/generator-templates/hooks.js +4 -2
- package/cli/generator-templates/naming.js +2 -1
- package/cli/generator-templates/react.js +2 -1
- package/cli/generator-templates/specs.js +4 -2
- package/cli/generator-templates/svelte.js +4 -2
- package/cli/generator-templates/types.js +6 -3
- package/cli/generator-templates/views.js +4 -2
- package/cli/generator-templates/vue.js +4 -2
- package/cli/generator-writes.js +24 -0
- package/cli/generator.d.ts +1 -1
- package/cli/generator.js +86 -127
- package/cli/git-file-listing.js +69 -0
- package/cli/help-args.js +18 -0
- package/cli/help.d.ts +7 -3
- package/cli/help.js +94 -149
- package/cli/hooks/bypass-log.js +44 -0
- package/cli/hooks/claude-post-edit.js +79 -0
- package/cli/hooks/claude-pre-tool.js +178 -0
- package/cli/hooks/claude-settings-merge.js +101 -0
- package/cli/hooks/dispatch-identity.js +125 -0
- package/cli/hooks/entry.js +181 -0
- package/cli/hooks/foreign-identity.js +60 -0
- package/cli/hooks/guard-args.js +55 -0
- package/cli/hooks/guard-cases.js +94 -0
- package/cli/hooks/guard-config.js +43 -0
- package/cli/hooks/guard-invocation.js +106 -0
- package/cli/hooks/guard-paths.js +80 -0
- package/cli/hooks/guard-route.js +211 -0
- package/cli/hooks/guard-rules-nudge.js +37 -0
- package/cli/hooks/guard-rules-reads.js +66 -0
- package/cli/hooks/guard-rules-shell.js +239 -0
- package/cli/hooks/guard-rules.js +114 -0
- package/cli/hooks/hook-commands.js +21 -0
- package/cli/hooks/install-hooks-apply.js +36 -0
- package/cli/hooks/install-hooks-cli.js +100 -0
- package/cli/hooks/install-hooks-config.js +48 -0
- package/cli/hooks/install-hooks-pins.js +66 -0
- package/cli/hooks/install-hooks-plan.js +56 -0
- package/cli/hooks/install-hooks-status.js +7 -0
- package/cli/hooks/interpreter-writes.js +195 -0
- package/cli/hooks/launcher.js +60 -0
- package/cli/hooks/mcp-json-merge.js +39 -0
- package/cli/hooks/native-edit-lock.js +40 -0
- package/cli/hooks/native-tool-policy.js +152 -0
- package/cli/hooks/out-of-band-edits.js +141 -0
- package/cli/hooks/post-edit-scope.js +43 -0
- package/cli/hooks/project-status.js +59 -0
- package/cli/hooks/repo-membership.js +32 -0
- package/cli/hooks/run-hook.js +79 -0
- package/cli/hooks/session-identity.js +153 -0
- package/cli/hooks/session-start.js +50 -0
- package/cli/hooks/shell-lexer-readers.js +76 -0
- package/cli/hooks/shell-lexer-state.js +59 -0
- package/cli/hooks/shell-lexer.js +110 -0
- package/cli/hooks/shell-parse.js +232 -0
- package/cli/hooks/statusline.js +27 -0
- package/cli/hooks/xatoms-hints.js +38 -0
- package/cli/host-protocol.js +109 -0
- package/cli/host-shims.js +9 -8
- package/cli/index-admission.js +41 -0
- package/cli/index-freshness.js +83 -0
- package/cli/index-row-check.js +58 -0
- package/cli/index.js +52 -150
- package/cli/installer-templates.js +67 -75
- package/cli/installer.d.ts +16 -1
- package/cli/installer.js +159 -58
- package/cli/languages.js +8 -4
- package/cli/lattice-directives.js +24 -0
- package/cli/lattice-payload.js +34 -0
- package/cli/{tesseract-state.js → lattice-state.js} +8 -4
- package/cli/lease-view.js +48 -0
- package/cli/library/entry-fp.js +42 -0
- package/cli/library/entry-schema.js +90 -0
- package/cli/library/library-tasks.js +25 -0
- package/cli/library/registry.js +56 -0
- package/cli/library/reverify.js +56 -0
- package/cli/library/verify-checks.js +89 -0
- package/cli/library/verify.js +81 -0
- package/cli/license-config.js +124 -0
- package/cli/license.js +168 -135
- package/cli/line-count.js +23 -0
- package/cli/literal-replace.js +107 -0
- package/cli/main.js +166 -0
- package/cli/mcp/antigravity.js +11 -9
- package/cli/mcp/batch.js +59 -0
- package/cli/mcp/call-args.js +75 -0
- package/cli/mcp/call-effects.js +57 -0
- package/cli/mcp/call-scope.js +70 -62
- package/cli/mcp/context.js +81 -0
- package/cli/mcp/envelope.js +62 -0
- package/cli/mcp/fingerprint.js +26 -0
- package/cli/mcp/fresh-entry.js +34 -0
- package/cli/mcp/fresh-protocol.js +37 -0
- package/cli/mcp/fresh-runner.js +40 -0
- package/cli/mcp/help.js +53 -0
- package/cli/mcp/index.js +9 -5
- package/cli/mcp/inflight.js +47 -0
- package/cli/mcp/installer-jsonc.js +64 -0
- package/cli/mcp/installer-package.js +54 -0
- package/cli/mcp/installer-report.js +47 -0
- package/cli/mcp/installer-write.js +117 -0
- package/cli/mcp/installer.js +88 -187
- package/cli/mcp/manifests-analysis.js +156 -0
- package/cli/mcp/manifests-runners.js +48 -0
- package/cli/mcp/manifests-team.js +100 -0
- package/cli/mcp/manifests.js +69 -309
- package/cli/mcp/offload-worker.js +11 -0
- package/cli/mcp/offload.js +18 -0
- package/cli/mcp/patterns-cli.js +76 -0
- package/cli/mcp/patterns-forge-cli.js +120 -0
- package/cli/mcp/prompts.js +13 -10
- package/cli/mcp/resources.js +8 -35
- package/cli/mcp/roots.js +46 -0
- package/cli/mcp/scorecard-worker.js +7 -0
- package/cli/mcp/scorecard.js +42 -0
- package/cli/mcp/server-info.js +28 -0
- package/cli/mcp/server.js +91 -291
- package/cli/mcp/spec-harness.js +62 -0
- package/cli/mcp/stale-exec.js +48 -0
- package/cli/mcp/staleness.js +26 -0
- package/cli/mcp/stdio.js +141 -0
- package/cli/mcp/tool-call.js +73 -0
- package/cli/mcp/tools-audit-feed.js +33 -0
- package/cli/mcp/tools-audit.js +13 -7
- package/cli/mcp/tools-generate.js +3 -3
- package/cli/mcp/tools-lazy.js +39 -0
- package/cli/mcp/tools-patch.js +93 -21
- package/cli/mcp/tools-patterns.js +6 -3
- package/cli/mcp/tools-project.js +8 -4
- package/cli/mcp/tools-q-modes.js +15 -14
- package/cli/mcp/tools-q.js +94 -33
- package/cli/mcp/tools-read.js +30 -80
- package/cli/mcp/tools-team-feed.js +10 -8
- package/cli/mcp/tools-team-locks.js +22 -10
- package/cli/mcp/tools-team-mailbox.js +8 -7
- package/cli/mcp/tools-team-tasks.js +125 -38
- package/cli/mcp/tools-team.js +14 -7
- package/cli/mcp/tools-verify.js +44 -15
- package/cli/mcp/tools-write.js +11 -16
- package/cli/mcp/tools.js +202 -68
- package/cli/mcp/worker-run.js +24 -0
- package/cli/missing-imports.js +52 -0
- package/cli/mutation-args.js +96 -0
- package/cli/mutators.js +164 -61
- package/cli/navigator-actions.js +19 -13
- package/cli/navigator-banner-helpers.js +1 -26
- package/cli/navigator-banner.js +5 -3
- package/cli/navigator-conversion.js +23 -12
- package/cli/navigator-grades.js +70 -35
- package/cli/navigator-guide.js +13 -11
- package/cli/navigator-menu.js +80 -166
- package/cli/navigator-paged.js +15 -8
- package/cli/navigator-sections.js +32 -0
- package/cli/navigator-share.js +53 -39
- package/cli/navigator.d.ts +25 -5
- package/cli/navigator.js +14 -41
- package/cli/network-policy.js +72 -0
- package/cli/network-recorder.fixture.mjs +35 -0
- package/cli/outline/ast-outline.js +120 -0
- package/cli/outline/style-outline.js +42 -0
- package/cli/outline/vue-outline.js +88 -0
- package/cli/patcher-cli.js +255 -0
- package/cli/patcher.d.ts +55 -24
- package/cli/patcher.js +167 -358
- package/cli/path-scope.js +78 -22
- package/cli/patterns/fixtures/gt/A1.txt +109 -0
- package/cli/patterns/fixtures/gt/A10.txt +13 -0
- package/cli/patterns/fixtures/gt/A11.txt +10 -0
- package/cli/patterns/fixtures/gt/A12.txt +29 -0
- package/cli/patterns/fixtures/gt/A13.txt +39 -0
- package/cli/patterns/fixtures/gt/A14.txt +45 -0
- package/cli/patterns/fixtures/gt/A15.txt +70 -0
- package/cli/patterns/fixtures/gt/A16.txt +59 -0
- package/cli/patterns/fixtures/gt/A17.txt +29 -0
- package/cli/patterns/fixtures/gt/A18.txt +9 -0
- package/cli/patterns/fixtures/gt/A19.txt +29 -0
- package/cli/patterns/fixtures/gt/A2.txt +28 -0
- package/cli/patterns/fixtures/gt/A20.txt +83 -0
- package/cli/patterns/fixtures/gt/A21.txt +138 -0
- package/cli/patterns/fixtures/gt/A22.txt +32 -0
- package/cli/patterns/fixtures/gt/A23.txt +37 -0
- package/cli/patterns/fixtures/gt/A24.txt +67 -0
- package/cli/patterns/fixtures/gt/A25.txt +41 -0
- package/cli/patterns/fixtures/gt/A26.txt +29 -0
- package/cli/patterns/fixtures/gt/A3.txt +17 -0
- package/cli/patterns/fixtures/gt/A4.txt +44 -0
- package/cli/patterns/fixtures/gt/A5.txt +32 -0
- package/cli/patterns/fixtures/gt/A6.txt +15 -0
- package/cli/patterns/fixtures/gt/A7.txt +79 -0
- package/cli/patterns/fixtures/gt/A8.txt +41 -0
- package/cli/patterns/fixtures/gt/A9.txt +42 -0
- package/cli/patterns/fixtures/gt/B1.txt +11 -0
- package/cli/patterns/fixtures/gt/B10.txt +41 -0
- package/cli/patterns/fixtures/gt/B11.txt +7 -0
- package/cli/patterns/fixtures/gt/B2.txt +19 -0
- package/cli/patterns/fixtures/gt/B3.txt +70 -0
- package/cli/patterns/fixtures/gt/B4.txt +55 -0
- package/cli/patterns/fixtures/gt/B5.txt +21 -0
- package/cli/patterns/fixtures/gt/B6.txt +15 -0
- package/cli/patterns/fixtures/gt/B7.txt +24 -0
- package/cli/patterns/fixtures/gt/B8.txt +40 -0
- package/cli/patterns/fixtures/gt/B9.txt +18 -0
- package/cli/patterns/fixtures/gt/C1.txt +26 -0
- package/cli/patterns/fixtures/gt/C2.txt +33 -0
- package/cli/patterns/fixtures/gt/C3.txt +26 -0
- package/cli/patterns/fixtures/gt/C4.txt +7 -0
- package/cli/patterns/fixtures/gt/C5.txt +29 -0
- package/cli/patterns/fixtures/gt/C6.txt +7 -0
- package/cli/patterns/fixtures/gt/C7.txt +18 -0
- package/cli/patterns/fixtures/gt/labels.json +1837 -0
- package/cli/patterns/gt-build.js +55 -0
- package/cli/patterns/gt-locations.js +76 -0
- package/cli/patterns/gt-resolve.js +66 -0
- package/cli/patterns/gt-score-cli.js +80 -0
- package/cli/patterns/gt-score.js +188 -0
- package/cli/patterns/gt-text.js +79 -0
- package/cli/pillars-wizard.js +53 -24
- package/cli/pillars-write-guard.js +4 -1
- package/cli/postinstall.js +22 -19
- package/cli/project-detector.js +60 -35
- package/cli/project-markers.js +42 -0
- package/cli/project-scope.js +20 -0
- package/cli/read-revision.js +70 -0
- package/cli/reader-cards-gate.js +29 -0
- package/cli/reader-cards.js +153 -0
- package/cli/reader-cli.js +45 -18
- package/cli/reader-format.js +48 -0
- package/cli/reader-logic.js +128 -250
- package/cli/reader.js +210 -221
- package/cli/request-context.js +9 -0
- package/cli/result-status.js +23 -0
- package/cli/rules.js +81 -0
- package/cli/scaffold.js +80 -51
- package/cli/scan-ignore.js +12 -0
- package/cli/search-args.js +102 -0
- package/cli/search-ast-module.js +103 -0
- package/cli/search-ast.js +75 -112
- package/cli/search-commands-blast.js +70 -0
- package/cli/search-commands-graph.js +57 -105
- package/cli/search-commands-hazards.js +105 -0
- package/cli/search-commands-literal.js +102 -0
- package/cli/search-commands-semantic.js +39 -27
- package/cli/search-commands.js +93 -186
- package/cli/search-db.js +52 -252
- package/cli/search-debug.js +13 -0
- package/cli/search-graph-edges.js +112 -0
- package/cli/search-index-meta.js +66 -0
- package/cli/search-index-write.js +153 -0
- package/cli/search-literal-files.js +64 -0
- package/cli/search-literal.js +150 -0
- package/cli/search-output.js +88 -0
- package/cli/search-queries-graph.js +120 -193
- package/cli/search-queries-semantic.js +41 -14
- package/cli/search-queries-similar.js +2 -1
- package/cli/search-queries.js +46 -21
- package/cli/search-query-view.js +67 -0
- package/cli/search-rank.js +54 -0
- package/cli/search-replace-blocks.js +120 -0
- package/cli/search-resolve.js +52 -0
- package/cli/search-root.js +78 -0
- package/cli/search-scan.js +102 -0
- package/cli/search-schema-ddl.js +172 -0
- package/cli/search-schema.js +135 -220
- package/cli/search-session.js +11 -0
- package/cli/search-sync-paths.js +78 -0
- package/cli/search-sync-plan.js +63 -0
- package/cli/search-sync-rows.js +96 -0
- package/cli/search-sync.js +128 -0
- package/cli/search-tokenizer.js +8 -4
- package/cli/search.d.ts +26 -5
- package/cli/search.js +156 -410
- package/cli/sfc/component-resolver.js +96 -0
- package/cli/sfc/import-coverage.js +24 -0
- package/cli/sfc/module-aliases.js +102 -0
- package/cli/sfc/script-asts.js +51 -0
- package/cli/sfc/sfc-metadata.js +104 -0
- package/cli/sfc/sfc-parse.js +101 -0
- package/cli/sfc/svelte-parse.js +46 -0
- package/cli/sfc/template-walk.js +78 -0
- package/cli/sfc-scripts.js +60 -0
- package/cli/share-consent.js +49 -0
- package/cli/share-outcome.js +57 -0
- package/cli/silence-warnings.js +2 -1
- package/cli/source-parse.js +130 -0
- package/cli/spec-isolated-home.js +17 -0
- package/cli/spec-support/import-trace.mjs +22 -0
- package/cli/spec-support/run-cli.js +58 -0
- package/cli/sqlite-memory.js +47 -0
- package/cli/status-command.js +107 -0
- package/cli/symbol-locator.js +121 -0
- package/cli/team/agent-identity.js +58 -0
- package/cli/team/audit-run-adoption.js +117 -0
- package/cli/team/audit-run-bypass.js +177 -0
- package/cli/team/audit-run-calls.js +108 -0
- package/cli/team/audit-run-db.js +181 -0
- package/cli/team/audit-run-identity.js +80 -0
- package/cli/team/audit-run-invocations.js +130 -0
- package/cli/team/audit-run-protocol.js +258 -0
- package/cli/team/audit-run-render.js +107 -0
- package/cli/team/audit-run-shas.js +179 -0
- package/cli/team/audit-run.js +203 -0
- package/cli/team/claim-ignore-deps.js +16 -0
- package/cli/team/coordination-busy.js +164 -0
- package/cli/team/coordination-db.js +85 -0
- package/cli/team/coordination-fixture.js +53 -0
- package/cli/team/coordination-repos.js +72 -0
- package/cli/team/coordination-root.js +161 -0
- package/cli/team/coordination-target.js +114 -0
- package/cli/team/dispatch-templates/dispatch-v1.js +228 -0
- package/cli/team/edit-shas.js +45 -0
- package/cli/team/lease-activity.js +130 -0
- package/cli/team/lease-cap.js +67 -0
- package/cli/team/lease-contention.js +30 -0
- package/cli/team/lease-key.js +46 -0
- package/cli/team/lease-lapse.js +83 -0
- package/cli/team/lease-reacquire.js +99 -0
- package/cli/team/lease-renew.js +97 -0
- package/cli/team/lease-roots.js +30 -0
- package/cli/team/lease-waiter-notice.js +48 -0
- package/cli/team/staged-leases.js +64 -0
- package/cli/team/task-completion-output.js +78 -0
- package/cli/team/task-detail-sections.js +86 -0
- package/cli/team/task-list-view.js +10 -1
- package/cli/team/task-ref.js +73 -0
- package/cli/team/task-target.js +53 -0
- package/cli/team/task-tier.js +17 -0
- package/cli/team/team-brief.js +78 -0
- package/cli/team/team-commands-board.js +118 -0
- package/cli/team/team-commands-dispatch.js +85 -0
- package/cli/team/team-commands-lock-staged.js +87 -0
- package/cli/team/team-commands-lock-views.js +144 -0
- package/cli/team/team-commands-lock.js +115 -26
- package/cli/team/team-commands-migrate.js +171 -0
- package/cli/team/team-commands-profile.js +44 -0
- package/cli/team/team-commands-repo.js +114 -0
- package/cli/team/team-commands-task.js +312 -0
- package/cli/team/team-commands-tokens.js +57 -0
- package/cli/team/team-commands-triage.js +43 -0
- package/cli/team/team-commands-vds.js +2 -1
- package/cli/team/team-commands.js +108 -463
- package/cli/team/team-db-agents.js +12 -6
- package/cli/team/team-db-feed.js +25 -13
- package/cli/team/team-db-lock-promotion.js +95 -51
- package/cli/team/team-db-locks.js +167 -35
- package/cli/team/team-db-mailbox.js +15 -8
- package/cli/team/team-db-readonly.js +76 -0
- package/cli/team/team-db-task-helpers.js +68 -19
- package/cli/team/team-db-tasks.js +21 -9
- package/cli/team/team-db-transaction.js +20 -5
- package/cli/team/team-db.js +11 -4
- package/cli/team/team-dispatch-batches.js +271 -0
- package/cli/team/team-dispatch-prompts.js +152 -0
- package/cli/team/team-dispatch-render.js +154 -0
- package/cli/team/team-dispatch-run-cli.js +154 -0
- package/cli/team/team-dispatch-runs.js +155 -0
- package/cli/team/team-dispatch-script.js +67 -0
- package/cli/team/team-dispatch-select.js +220 -0
- package/cli/team/team-dispatch-v2-fixture.js +62 -0
- package/cli/team/team-dispatch-v2.js +207 -0
- package/cli/team/team-dispatch.js +229 -0
- package/cli/team/team-dispatcher-authority.js +43 -0
- package/cli/team/team-flags.js +222 -48
- package/cli/team/team-format.js +80 -26
- package/cli/team/team-handoff-hint.js +9 -0
- package/cli/team/team-migrate-agents.js +52 -0
- package/cli/team/team-migrate-apply.js +178 -0
- package/cli/team/team-migrate-junk.js +55 -0
- package/cli/team/team-migrate-metadata.js +80 -0
- package/cli/team/team-migrate-paths.js +50 -0
- package/cli/team/team-migrate-plan.js +85 -0
- package/cli/team/team-migrate-report.js +89 -0
- package/cli/team/team-migrate-tables.js +85 -0
- package/cli/team/team-migrate.js +193 -0
- package/cli/team/team-needs.js +99 -0
- package/cli/team/team-profile-format.js +126 -0
- package/cli/team/team-profile.js +144 -0
- package/cli/team/team-projects-coordinator.js +54 -33
- package/cli/team/team-projects-format.js +8 -4
- package/cli/team/team-projects-memory.js +6 -3
- package/cli/team/team-projects.js +2 -1
- package/cli/team/team-release-train.js +2 -1
- package/cli/team/team-route.js +132 -0
- package/cli/team/team-schema-repo.js +48 -0
- package/cli/team/team-schema-usage.js +25 -0
- package/cli/team/team-schema-vds.js +7 -3
- package/cli/team/team-schema.js +24 -4
- package/cli/team/team-task-close.js +132 -0
- package/cli/team/team-task-guard.js +24 -0
- package/cli/team/team-task-handoff.js +122 -0
- package/cli/team/team-task-ownership.js +74 -0
- package/cli/team/team-task-rules.js +21 -0
- package/cli/team/team-task-uncommitted.js +49 -0
- package/cli/team/team-telemetry.js +44 -32
- package/cli/team/team-tokens.js +4 -1
- package/cli/team/team-triage-generate.js +212 -0
- package/cli/team/team-triage-log.js +11 -0
- package/cli/team/team-triage-verify.js +153 -0
- package/cli/team/team-triage.js +92 -339
- package/cli/team/team-vds.js +2 -1
- package/cli/team/usage-compute.js +70 -0
- package/cli/team/usage-pricing.js +62 -0
- package/cli/team/usage-reader.js +106 -0
- package/cli/team/usage-render.js +72 -0
- package/cli/team/usage-store.js +56 -0
- package/cli/team/usage-transcript.js +127 -0
- package/cli/team/write-lock-guard.js +48 -0
- package/cli/telemetry/call-ledger.js +264 -0
- package/cli/telemetry/call-schema.js +53 -0
- package/cli/telemetry/savings-dbs.js +111 -0
- package/cli/telemetry/savings-render.js +68 -0
- package/cli/telemetry/savings-report.js +101 -0
- package/cli/telemetry/savings-routing.js +66 -0
- package/cli/telemetry/savings-tooling.js +140 -0
- package/cli/terminal.js +38 -40
- package/cli/tesseract-manifesto.js +2 -17
- package/cli/tesseract.js +8 -21
- package/cli/test-audit.js +218 -0
- package/cli/test-changes.js +60 -0
- package/cli/test-command.js +201 -0
- package/cli/test-depth.js +43 -0
- package/cli/test-failures.js +170 -0
- package/cli/test-graph-open.js +150 -0
- package/cli/test-graph.js +188 -0
- package/cli/test-lanes.js +117 -0
- package/cli/test-output.js +155 -0
- package/cli/test-paths.js +51 -0
- package/cli/test-profile.js +64 -0
- package/cli/test-report.js +112 -0
- package/cli/test-run.js +53 -0
- package/cli/test-scope.js +55 -0
- package/cli/test-select.js +107 -0
- package/cli/test-slots.js +276 -0
- package/cli/test-workers.js +45 -0
- package/cli/test-workspace.js +83 -0
- package/cli/theme.js +6 -3
- package/cli/timers.js +6 -0
- package/cli/trend.js +10 -5
- package/cli/tsconfig-shape.js +20 -0
- package/cli/tty-banner.js +11 -0
- package/cli/typecheck-audit.js +146 -0
- package/cli/typecheck-command.js +94 -0
- package/cli/typecheck-sandbox.js +93 -0
- package/cli/ui-actions-codebase.js +20 -7
- package/cli/ui-actions-helpers.js +9 -0
- package/cli/ui-actions-studio.js +11 -7
- package/cli/ui-actions-tasks.js +3 -2
- package/cli/ui-actions-vds.js +2 -1
- package/cli/ui-actions.js +4 -2
- package/cli/ui-attention.js +28 -12
- package/cli/ui-auth-hosts.js +46 -0
- package/cli/ui-auth.js +89 -0
- package/cli/ui-client-cert.js +2 -1
- package/cli/ui-client-filetree.js +2 -2
- package/cli/ui-db-studio.js +23 -33
- package/cli/ui-dev-watcher.js +2 -1
- package/cli/ui-forum-data.js +45 -46
- package/cli/ui-forum-topics.js +6 -3
- package/cli/ui-fresh-index.js +11 -0
- package/cli/ui-handlers.js +6 -6
- package/cli/ui-html.js +6 -12
- package/cli/ui-index.html +3 -3
- package/cli/ui-route-helpers.js +10 -0
- package/cli/ui-server-routes.js +15 -17
- package/cli/ui-server.js +49 -50
- package/cli/ui-sql-guard.js +76 -0
- package/cli/ui-sql-mask.js +61 -0
- package/cli/ui-sse.js +5 -4
- package/cli/ui-template-filetree.js +4 -4
- package/cli/ui-template-kanban.js +4 -7
- package/cli/verify-changed.js +43 -0
- package/cli/verify-helpers.js +21 -207
- package/cli/verify-lint.js +54 -20
- package/cli/verify-report.js +117 -0
- package/cli/verify-steps.js +71 -0
- package/cli/verify.js +111 -372
- package/cli/wait-command.js +147 -0
- package/cli/workspace-run.js +153 -0
- package/cli/workspace.js +147 -0
- package/cli/write-append.js +28 -0
- package/library/node-js/is-path-inside/entry.json +57 -0
- package/library/node-js/is-path-inside/negative/path-scope.js.txt +13 -0
- package/library/node-js/is-path-inside/piece.js +15 -0
- package/library/node-js/is-path-inside/piece.spec.js +38 -0
- package/library/node-js/read-json-or/entry.json +70 -0
- package/library/node-js/read-json-or/negative/ratchet.js.txt +14 -0
- package/library/node-js/read-json-or/negative/workspace.js.txt +8 -0
- package/library/node-js/read-json-or/piece.js +17 -0
- package/library/node-js/read-json-or/piece.spec.js +36 -0
- package/library/vue-ts/nullable-timer-handle/ambient.d.ts +5 -0
- package/library/vue-ts/nullable-timer-handle/entry.json +84 -0
- package/library/vue-ts/nullable-timer-handle/negative/const-alias.ts.txt +17 -0
- package/library/vue-ts/nullable-timer-handle/negative/inline-guard.ts.txt +15 -0
- package/library/vue-ts/nullable-timer-handle/piece.spec.js +48 -0
- package/library/vue-ts/nullable-timer-handle/piece.ts +41 -0
- package/package.json +9 -3
- package/scripts/check-framework-generation.mjs +32 -12
- package/scripts/direct-run.mjs +9 -0
- package/scripts/pre-commit.sh +48 -74
- package/scripts/publish-both.mjs +24 -16
- package/scripts/publish-summary.mjs +14 -0
- package/scripts/postinstall.mjs +0 -27
package/cli/team/team-db-feed.js
CHANGED
|
@@ -44,41 +44,53 @@ export const postFeedEvent = (db, eventData = {}) => {
|
|
|
44
44
|
return { ...row, metadata: JSON.parse(row.metadata || '{}') };
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
// Rows whose metadata carries $.archived = 1 stay in the table (cleanup archives, never deletes)
|
|
48
|
+
// but drop out of feed, status and mailbox listings.
|
|
49
|
+
export const NOT_ARCHIVED_SQL = "COALESCE(json_extract(CASE WHEN json_valid(metadata) THEN metadata END, '$.archived'), 0) != 1";
|
|
50
|
+
|
|
51
|
+
const parseRow = (r) => ({ ...r, metadata: JSON.parse(r.metadata || '{}') });
|
|
52
|
+
|
|
53
|
+
// With since_id the feed reads forward in id order (a cursor). Without it, it returns the newest
|
|
54
|
+
// `limit` rows, still in chronological order, so status cards show recent activity, not the oldest rows.
|
|
47
55
|
export const queryFeed = (db, filter = {}) => {
|
|
48
56
|
if (!db) return [];
|
|
49
57
|
let query = 'SELECT * FROM agent_feed';
|
|
50
|
-
const conditions = [];
|
|
58
|
+
const conditions = [NOT_ARCHIVED_SQL];
|
|
51
59
|
const params = [];
|
|
60
|
+
const hasCursor = Boolean(filter.since_id);
|
|
52
61
|
|
|
53
|
-
if (
|
|
62
|
+
if (hasCursor) {
|
|
54
63
|
conditions.push('id > ?');
|
|
55
64
|
params.push(Number(filter.since_id));
|
|
56
65
|
}
|
|
57
|
-
|
|
66
|
+
const hasThread = Boolean(filter.thread_id);
|
|
67
|
+
if (hasThread) {
|
|
58
68
|
conditions.push('(thread_id = ? OR id = ?)');
|
|
59
69
|
params.push(Number(filter.thread_id), Number(filter.thread_id));
|
|
60
70
|
}
|
|
61
|
-
|
|
71
|
+
const hasTask = Boolean(filter.task_id);
|
|
72
|
+
if (hasTask) {
|
|
62
73
|
conditions.push('task_id = ?');
|
|
63
74
|
params.push(Number(filter.task_id));
|
|
64
75
|
}
|
|
65
|
-
|
|
76
|
+
const hasEventType = Boolean(filter.event_type);
|
|
77
|
+
if (hasEventType) {
|
|
66
78
|
conditions.push('event_type = ?');
|
|
67
79
|
params.push(filter.event_type);
|
|
68
80
|
}
|
|
69
|
-
|
|
81
|
+
const hasAgent = Boolean(filter.agent_id);
|
|
82
|
+
if (hasAgent) {
|
|
70
83
|
const cleanId = formatHandle(filter.agent_id);
|
|
71
84
|
conditions.push('(recipient_id IS NULL OR recipient_id = ? OR author_id = ?)');
|
|
72
85
|
params.push(cleanId, cleanId);
|
|
73
86
|
}
|
|
74
87
|
|
|
75
|
-
|
|
76
|
-
if (hasConditions) query += ` WHERE ${conditions.join(' AND ')}`;
|
|
88
|
+
query += ` WHERE ${conditions.join(' AND ')}`;
|
|
77
89
|
const limit = Math.min(Number(filter.limit || 50), 200);
|
|
78
|
-
|
|
90
|
+
const direction = hasCursor ? 'ASC' : 'DESC';
|
|
91
|
+
query += ` ORDER BY id ${direction} LIMIT ${limit}`;
|
|
79
92
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}));
|
|
93
|
+
const rows = db.prepare(query).all(...params).map(parseRow);
|
|
94
|
+
if (hasCursor) return rows;
|
|
95
|
+
return rows.reverse();
|
|
84
96
|
};
|
|
@@ -1,33 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Chemical X Protocol: FIFO Lock Promotion & Expiration Cleanup
|
|
2
|
+
* Chemical X Protocol: FIFO Lock Promotion, Waiter Hygiene & Expiration Cleanup
|
|
3
|
+
* Every write here only changes the exact row it judged, so a concurrent acquire is never clobbered.
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
6
|
import { postFeedEvent } from './team-db-feed.js';
|
|
6
7
|
import { isPidAlive } from './team-db-transaction.js';
|
|
7
8
|
|
|
8
9
|
export const DEFAULT_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
10
|
+
export const WAITER_TTL_MS = 2 * DEFAULT_TTL_MS;
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const hasTrackedPid = (pid) => typeof pid === 'number' && pid > 0;
|
|
13
|
+
|
|
14
|
+
export const isOwnerProcessDead = (row) => hasTrackedPid(row?.pid) && !isPidAlive(row.pid);
|
|
15
|
+
|
|
16
|
+
export const describeLease = (lease, now = Date.now()) => {
|
|
17
|
+
const hasLease = Boolean(lease);
|
|
18
|
+
if (!hasLease) return null;
|
|
19
|
+
const expired = lease.expires_at <= now;
|
|
20
|
+
const holderDead = isOwnerProcessDead(lease);
|
|
21
|
+
return { ...lease, expired, holderDead, active: !expired && !holderDead };
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const lastSeenAt = (waiter) => (waiter.last_seen_at > 0 ? waiter.last_seen_at : waiter.requested_at);
|
|
25
|
+
|
|
26
|
+
const isWaiterStale = (waiter, now) => lastSeenAt(waiter) <= now - WAITER_TTL_MS || isOwnerProcessDead(waiter);
|
|
27
|
+
|
|
28
|
+
export const expireStaleWaiters = (db, filePath, now = Date.now()) => {
|
|
29
|
+
const waiting = db.prepare("SELECT * FROM file_lock_queue WHERE file_path = ? AND status = 'waiting'").all(filePath);
|
|
30
|
+
const stale = waiting.filter((waiter) => isWaiterStale(waiter, now));
|
|
31
|
+
const markExpired = db.prepare("UPDATE file_lock_queue SET status = 'expired' WHERE id = ? AND status = 'waiting'");
|
|
32
|
+
for (const waiter of stale) markExpired.run(waiter.id);
|
|
33
|
+
return stale;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const promoteNextWaiter = (db, filePath) => {
|
|
37
|
+
const canPromote = Boolean(db) && Boolean(filePath);
|
|
14
38
|
if (!canPromote) return null;
|
|
15
39
|
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
expireStaleWaiters(db, filePath, now);
|
|
16
42
|
const waiterQuery = "SELECT * FROM file_lock_queue WHERE file_path = ? AND status = 'waiting' ORDER BY priority ASC, id ASC LIMIT 1";
|
|
17
43
|
const waiter = db.prepare(waiterQuery).get(filePath);
|
|
18
|
-
|
|
19
44
|
const hasWaiter = Boolean(waiter);
|
|
20
45
|
if (!hasWaiter) return null;
|
|
21
46
|
|
|
22
|
-
const now = Date.now();
|
|
23
47
|
const expiresAt = now + DEFAULT_TTL_MS;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
48
|
+
const insertSql = `INSERT INTO file_leases (file_path, locked_by, acquired_at, expires_at, purpose, pid)
|
|
49
|
+
VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(file_path) DO NOTHING`;
|
|
50
|
+
const purpose = waiter.purpose || 'Promoted from FIFO queue';
|
|
51
|
+
const inserted = db.prepare(insertSql).run(filePath, waiter.agent_id, now, expiresAt, purpose, waiter.pid || 0);
|
|
52
|
+
const isGranted = inserted.changes === 1;
|
|
53
|
+
if (!isGranted) return null; // another agent already holds the file; the waiter keeps its place
|
|
30
54
|
|
|
55
|
+
db.prepare("UPDATE file_lock_queue SET status = 'granted' WHERE id = ? AND status = 'waiting'").run(waiter.id);
|
|
31
56
|
postFeedEvent(db, {
|
|
32
57
|
author_id: '@system',
|
|
33
58
|
recipient_id: waiter.agent_id,
|
|
@@ -36,66 +61,85 @@ export const promoteNextWaiter = (db, filePath, pid = 0) => {
|
|
|
36
61
|
message: `File lock granted to ${waiter.agent_id} for ${filePath} from FIFO queue`,
|
|
37
62
|
metadata: { queueId: waiter.id, expiresAt }
|
|
38
63
|
});
|
|
39
|
-
|
|
40
64
|
return { waiter, expiresAt };
|
|
41
65
|
};
|
|
42
66
|
|
|
43
|
-
|
|
44
|
-
const priority = Number(options.priority || 2);
|
|
45
|
-
const purpose = options.purpose || '';
|
|
46
|
-
const info = db.prepare(`
|
|
47
|
-
INSERT INTO file_lock_queue (file_path, agent_id, requested_at, status, priority, purpose)
|
|
48
|
-
VALUES (?, ?, ?, 'waiting', ?, ?)
|
|
49
|
-
`).run(filePath, cleanId, now, priority, purpose);
|
|
50
|
-
|
|
67
|
+
const countPosition = (db, filePath, queueId) => {
|
|
51
68
|
const posRow = db.prepare(`
|
|
52
69
|
SELECT COUNT(*) as pos FROM file_lock_queue
|
|
53
70
|
WHERE file_path = ? AND status = 'waiting' AND id <= ?
|
|
54
|
-
`).get(filePath,
|
|
55
|
-
|
|
71
|
+
`).get(filePath, queueId);
|
|
72
|
+
return posRow?.pos || 1;
|
|
73
|
+
};
|
|
56
74
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
75
|
+
export const enqueueWaiter = (db, filePath, cleanId, existingLease, options = {}, now = Date.now()) => {
|
|
76
|
+
expireStaleWaiters(db, filePath, now);
|
|
77
|
+
const pid = hasTrackedPid(options.pid) ? options.pid : 0;
|
|
78
|
+
const findSql = "SELECT * FROM file_lock_queue WHERE file_path = ? AND agent_id = ? AND status = 'waiting' ORDER BY id ASC LIMIT 1";
|
|
79
|
+
const existingEntry = db.prepare(findSql).get(filePath, cleanId);
|
|
80
|
+
const isRepoll = Boolean(existingEntry);
|
|
81
|
+
|
|
82
|
+
let queueId = existingEntry?.id;
|
|
83
|
+
if (isRepoll) {
|
|
84
|
+
db.prepare('UPDATE file_lock_queue SET last_seen_at = ?, pid = CASE WHEN ? > 0 THEN ? ELSE pid END WHERE id = ?')
|
|
85
|
+
.run(now, pid, pid, queueId);
|
|
86
|
+
} else {
|
|
87
|
+
const info = db.prepare(`
|
|
88
|
+
INSERT INTO file_lock_queue (file_path, agent_id, requested_at, status, priority, purpose, pid, last_seen_at)
|
|
89
|
+
VALUES (?, ?, ?, 'waiting', ?, ?, ?, ?)
|
|
90
|
+
`).run(filePath, cleanId, now, Number(options.priority || 2), options.purpose || '', pid, now);
|
|
91
|
+
queueId = Number(info.lastInsertRowid);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const position = countPosition(db, filePath, queueId);
|
|
95
|
+
if (!isRepoll) {
|
|
96
|
+
postFeedEvent(db, {
|
|
97
|
+
author_id: cleanId,
|
|
98
|
+
event_type: 'lock_queued',
|
|
99
|
+
file_path: filePath,
|
|
100
|
+
message: `${cleanId} joined FIFO queue at position ${position} for ${filePath}`,
|
|
101
|
+
metadata: { queueId, position, heldBy: existingLease.locked_by }
|
|
102
|
+
});
|
|
103
|
+
}
|
|
64
104
|
|
|
65
105
|
return {
|
|
66
106
|
granted: false,
|
|
67
107
|
queued: true,
|
|
68
|
-
|
|
108
|
+
requeued: isRepoll,
|
|
109
|
+
queueId,
|
|
69
110
|
position,
|
|
70
111
|
currentHolder: existingLease.locked_by,
|
|
71
112
|
expiresAt: existingLease.expires_at
|
|
72
113
|
};
|
|
73
114
|
};
|
|
74
115
|
|
|
75
|
-
export const cleanExpiredLeases = (db) => {
|
|
116
|
+
export const cleanExpiredLeases = (db, now = Date.now()) => {
|
|
76
117
|
const hasDb = Boolean(db);
|
|
77
118
|
if (!hasDb) return [];
|
|
78
119
|
|
|
79
|
-
const now = Date.now();
|
|
80
120
|
try {
|
|
81
|
-
const
|
|
121
|
+
const candidates = db.prepare('SELECT * FROM file_leases WHERE expires_at <= ? OR pid > 0').all(now);
|
|
122
|
+
const removeExactLease = db.prepare(
|
|
123
|
+
'DELETE FROM file_leases WHERE file_path = ? AND locked_by = ? AND acquired_at = ? AND expires_at = ?'
|
|
124
|
+
);
|
|
82
125
|
const cleaned = [];
|
|
83
|
-
for (const lease of
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
126
|
+
for (const lease of candidates) {
|
|
127
|
+
const state = describeLease(lease, now);
|
|
128
|
+
const isActive = Boolean(state.active);
|
|
129
|
+
if (isActive) continue;
|
|
130
|
+
const removal = removeExactLease.run(lease.file_path, lease.locked_by, lease.acquired_at, lease.expires_at);
|
|
131
|
+
const isRemovedHere = removal.changes === 1;
|
|
132
|
+
if (!isRemovedHere) continue; // a concurrent writer replaced this lease; it is not ours to delete
|
|
133
|
+
const reason = state.holderDead ? `Process #${lease.pid} terminated` : 'TTL expired';
|
|
134
|
+
postFeedEvent(db, {
|
|
135
|
+
author_id: '@system',
|
|
136
|
+
event_type: 'lock_expired',
|
|
137
|
+
file_path: lease.file_path,
|
|
138
|
+
message: `Lease expired for ${lease.file_path} held by ${lease.locked_by} (${reason})`,
|
|
139
|
+
metadata: { holder: lease.locked_by, expires_at: lease.expires_at, acquired_at: lease.acquired_at, purpose: lease.purpose || '', reason }
|
|
140
|
+
});
|
|
141
|
+
promoteNextWaiter(db, lease.file_path);
|
|
142
|
+
cleaned.push(lease);
|
|
99
143
|
}
|
|
100
144
|
return cleaned;
|
|
101
145
|
} catch (err) {
|
|
@@ -3,29 +3,65 @@
|
|
|
3
3
|
* Prevents race collisions with deterministic queueing and reactive promotion
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import fs from 'node:fs';
|
|
6
7
|
import path from 'node:path';
|
|
7
|
-
import { isPathTraversal, resolveSafePath } from '../path-scope.js';
|
|
8
8
|
import { postFeedEvent } from './team-db-feed.js';
|
|
9
|
-
import { DEFAULT_TTL_MS, cleanExpiredLeases, promoteNextWaiter, enqueueWaiter } from './team-db-lock-promotion.js';
|
|
9
|
+
import { DEFAULT_TTL_MS, cleanExpiredLeases, promoteNextWaiter, enqueueWaiter, describeLease } from './team-db-lock-promotion.js';
|
|
10
10
|
import { withImmediateTransaction } from './team-db-transaction.js';
|
|
11
|
+
import { resolveLeaseScope, toLeaseKey } from './lease-key.js';
|
|
12
|
+
import { findLapse, explainNotHolder } from './lease-lapse.js';
|
|
13
|
+
import { notifyHolderOfWaiter } from './lease-waiter-notice.js';
|
|
14
|
+
import { lockRoots } from './lease-roots.js';
|
|
15
|
+
import { openTeamDbReadOnly, closeQuietly, safeGet } from './team-db-readonly.js';
|
|
11
16
|
|
|
12
|
-
export { cleanExpiredLeases, promoteNextWaiter } from './team-db-lock-promotion.js';
|
|
17
|
+
export { cleanExpiredLeases, promoteNextWaiter, describeLease } from './team-db-lock-promotion.js';
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
// Leases are keyed from the project root; a relative input resolves from options.cwd.
|
|
20
|
+
const leaseKeyFor = (db, filePath, options) => toLeaseKey(filePath, resolveLeaseScope(db, options));
|
|
21
|
+
|
|
22
|
+
const leaseInOtherDb = (lockRoot, absPath, cleanId, now) => {
|
|
23
|
+
const key = path.relative(lockRoot, absPath);
|
|
24
|
+
const isInside = key !== '' && !key.startsWith('..') && !path.isAbsolute(key);
|
|
25
|
+
const db = isInside ? openTeamDbReadOnly(lockRoot) : null;
|
|
26
|
+
const hasDb = Boolean(db);
|
|
27
|
+
if (!hasDb) return null;
|
|
16
28
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
} catch {
|
|
24
|
-
// fallback
|
|
29
|
+
const lease = describeLease(safeGet(db, 'SELECT * FROM file_leases WHERE file_path = ?', [key]), now);
|
|
30
|
+
const isForeign = Boolean(lease?.active) && lease.locked_by !== cleanId;
|
|
31
|
+
return isForeign ? { ...lease, lockRoot } : null;
|
|
32
|
+
} finally {
|
|
33
|
+
closeQuietly(db);
|
|
25
34
|
}
|
|
26
|
-
return process.cwd();
|
|
27
35
|
};
|
|
28
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A live lease another handle holds on the same file in another team db (#2581). Until
|
|
39
|
+
* `chemx team migrate` merges a package db, it and the coordination db both serve leases, keyed
|
|
40
|
+
* from different roots; a lock taken from the root and one taken from the package must still
|
|
41
|
+
* collide. Read-only and outside this db's transaction, so two requests racing through different
|
|
42
|
+
* dbs in the same instant can both pass; the edit guard (edit-locks.js) still reads both dbs.
|
|
43
|
+
*/
|
|
44
|
+
export const findLeaseInOtherDb = (db, cleanPath, cleanId, options = {}) => {
|
|
45
|
+
const scope = resolveLeaseScope(db, options);
|
|
46
|
+
const absPath = path.resolve(scope.projectRoot, cleanPath);
|
|
47
|
+
const now = options.now ?? Date.now();
|
|
48
|
+
const others = lockRoots(scope.projectRoot, absPath).filter((lockRoot) => lockRoot !== scope.projectRoot);
|
|
49
|
+
for (const lockRoot of others) {
|
|
50
|
+
const lease = leaseInOtherDb(lockRoot, absPath, cleanId, now);
|
|
51
|
+
if (lease) return lease;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const refuseForOtherDb = (lease, cleanPath) => ({
|
|
57
|
+
granted: false,
|
|
58
|
+
reason: 'held_in_other_db',
|
|
59
|
+
heldBy: lease.locked_by,
|
|
60
|
+
lease: { file_path: lease.file_path, locked_by: lease.locked_by, expires_at: Number(lease.expires_at), purpose: lease.purpose || '' },
|
|
61
|
+
dbPath: path.join(lease.lockRoot, '.chemx', 'index.db'),
|
|
62
|
+
message: `${cleanPath} is leased by ${lease.locked_by} in ${path.join(lease.lockRoot, '.chemx', 'index.db')} (an unmerged package db or the coordination db); not queued here. Wait with chemx wait --lock-free=<file>.`
|
|
63
|
+
});
|
|
64
|
+
|
|
29
65
|
const normalizeAgentId = (id) => {
|
|
30
66
|
const hasId = Boolean(id);
|
|
31
67
|
if (!hasId) return null;
|
|
@@ -33,6 +69,38 @@ const normalizeAgentId = (id) => {
|
|
|
33
69
|
return isPrefixed ? id : `@${id}`;
|
|
34
70
|
};
|
|
35
71
|
|
|
72
|
+
// A path that does not exist leases nothing real (#4544). Suggest the same relative path under the
|
|
73
|
+
// coordination root or the repo root when it exists there; `allowNew` skips the check.
|
|
74
|
+
// Returns null when the path exists; otherwise where it was looked for and, when the same path
|
|
75
|
+
// exists under the coordination root, repo root or project root env, a suggestion relative to the
|
|
76
|
+
// caller's cwd (absolute when outside it).
|
|
77
|
+
export const adviseMissingPath = (db, filePath, options = {}) => {
|
|
78
|
+
const scope = resolveLeaseScope(db, options);
|
|
79
|
+
const absPath = path.resolve(scope.from, filePath);
|
|
80
|
+
const isPresent = fs.existsSync(absPath);
|
|
81
|
+
if (isPresent) return null;
|
|
82
|
+
const bases = [scope.projectRoot, process.env.CHEMX_PROJECT_ROOT, options.repoRoot].filter(Boolean);
|
|
83
|
+
const rel = path.isAbsolute(filePath) ? path.relative(scope.projectRoot, absPath) : filePath;
|
|
84
|
+
const hit = bases.map((base) => path.resolve(base, rel)).find((candidate) => fs.existsSync(candidate));
|
|
85
|
+
const fromCwd = hit ? path.relative(scope.from, hit) : '';
|
|
86
|
+
const isInside = fromCwd !== '' && !fromCwd.startsWith('..') && !path.isAbsolute(fromCwd);
|
|
87
|
+
const nearest = isInside ? fromCwd : hit;
|
|
88
|
+
const suggestion = hit ? nearest : null;
|
|
89
|
+
return { absPath, suggestion, from: scope.from };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const missingPathRefusal = (db, filePath, options) => {
|
|
93
|
+
const advice = adviseMissingPath(db, filePath, options);
|
|
94
|
+
if (!advice) return null;
|
|
95
|
+
const hint = advice.suggestion ? ` Did you mean ${advice.suggestion}? (relative to ${advice.from})` : '';
|
|
96
|
+
return {
|
|
97
|
+
granted: false,
|
|
98
|
+
reason: 'path_not_found',
|
|
99
|
+
suggestion: advice.suggestion,
|
|
100
|
+
message: `${filePath} does not exist (looked for ${advice.absPath}); no lease was taken.${hint} Pass --new to lease a file you are about to create.`
|
|
101
|
+
};
|
|
102
|
+
};
|
|
103
|
+
|
|
36
104
|
export const requestFileLock = (db, filePath, agentId, options = {}) => {
|
|
37
105
|
const hasDb = Boolean(db);
|
|
38
106
|
const hasPath = Boolean(filePath);
|
|
@@ -40,14 +108,17 @@ export const requestFileLock = (db, filePath, agentId, options = {}) => {
|
|
|
40
108
|
const canRequest = hasDb && hasPath && hasAgent;
|
|
41
109
|
if (!canRequest) return { granted: false, reason: 'missing_args' };
|
|
42
110
|
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
111
|
+
const cleanPath = leaseKeyFor(db, filePath, options);
|
|
112
|
+
if (!cleanPath) return { granted: false, reason: 'path_traversal' };
|
|
113
|
+
|
|
114
|
+
const mustExist = options.requireExisting === true;
|
|
115
|
+
const missing = mustExist ? missingPathRefusal(db, filePath, options) : null;
|
|
116
|
+
if (missing) return missing;
|
|
48
117
|
|
|
49
118
|
const cleanId = normalizeAgentId(agentId);
|
|
50
119
|
const pid = typeof options.pid === 'number' ? options.pid : 0;
|
|
120
|
+
const otherDbLease = findLeaseInOtherDb(db, cleanPath, cleanId, options);
|
|
121
|
+
if (otherDbLease) return refuseForOtherDb(otherDbLease, cleanPath);
|
|
51
122
|
|
|
52
123
|
return withImmediateTransaction(db, () => {
|
|
53
124
|
cleanExpiredLeases(db);
|
|
@@ -60,6 +131,8 @@ export const requestFileLock = (db, filePath, agentId, options = {}) => {
|
|
|
60
131
|
|
|
61
132
|
const isAvailable = !existingLease || existingLease.locked_by === cleanId;
|
|
62
133
|
if (isAvailable) {
|
|
134
|
+
// Read before the grant: a granted lease ends the lapse record's relevance.
|
|
135
|
+
const lapse = existingLease ? null : findLapse(db, cleanPath, cleanId);
|
|
63
136
|
const upsertSql = `INSERT INTO file_leases (file_path, locked_by, acquired_at, expires_at, purpose, pid)
|
|
64
137
|
VALUES (?, ?, ?, ?, ?, ?)
|
|
65
138
|
ON CONFLICT(file_path) DO UPDATE SET
|
|
@@ -75,10 +148,13 @@ export const requestFileLock = (db, filePath, agentId, options = {}) => {
|
|
|
75
148
|
message: `${cleanId} acquired lock on ${cleanPath}`
|
|
76
149
|
});
|
|
77
150
|
|
|
78
|
-
|
|
151
|
+
const lapseNote = lapse ? { previousLapse: lapse } : {};
|
|
152
|
+
return { granted: true, lease: { file_path: cleanPath, locked_by: cleanId, expires_at: expiresAt, pid }, ...lapseNote };
|
|
79
153
|
}
|
|
80
154
|
|
|
81
|
-
|
|
155
|
+
const queued = enqueueWaiter(db, cleanPath, cleanId, existingLease, options, now);
|
|
156
|
+
const notice = notifyHolderOfWaiter(db, { queueId: queued.queueId, waiter: cleanId, lease: existingLease, options });
|
|
157
|
+
return { ...queued, ...notice };
|
|
82
158
|
});
|
|
83
159
|
};
|
|
84
160
|
|
|
@@ -89,11 +165,8 @@ export const releaseFileLock = (db, filePath, agentId, options = {}) => {
|
|
|
89
165
|
const canRelease = hasDb && hasPath && hasAgent;
|
|
90
166
|
if (!canRelease) return { success: false, reason: 'missing_args' };
|
|
91
167
|
|
|
92
|
-
const
|
|
93
|
-
if (
|
|
94
|
-
return { success: false, reason: 'path_traversal' };
|
|
95
|
-
}
|
|
96
|
-
const cleanPath = path.relative(baseDir, resolveSafePath(filePath, baseDir));
|
|
168
|
+
const cleanPath = leaseKeyFor(db, filePath, options);
|
|
169
|
+
if (!cleanPath) return { success: false, reason: 'path_traversal' };
|
|
97
170
|
|
|
98
171
|
const cleanId = normalizeAgentId(agentId);
|
|
99
172
|
|
|
@@ -103,7 +176,14 @@ export const releaseFileLock = (db, filePath, agentId, options = {}) => {
|
|
|
103
176
|
const existingLease = db.prepare('SELECT * FROM file_leases WHERE file_path = ?').get(cleanPath);
|
|
104
177
|
const isHolder = Boolean(existingLease) && existingLease.locked_by === cleanId;
|
|
105
178
|
if (!isHolder) {
|
|
106
|
-
|
|
179
|
+
// A queued non-holder leaves the FIFO queue: release doubles as "cancel my wait".
|
|
180
|
+
const left = db.prepare("UPDATE file_lock_queue SET status = 'cancelled' WHERE file_path = ? AND agent_id = ? AND status = 'waiting'").run(cleanPath, cleanId);
|
|
181
|
+
const wasQueued = left.changes > 0;
|
|
182
|
+
if (wasQueued) return { success: true, dequeued: true, promotedWaiter: null };
|
|
183
|
+
const lapse = findLapse(db, cleanPath, cleanId);
|
|
184
|
+
const message = explainNotHolder(cleanPath, lapse, existingLease, Date.now());
|
|
185
|
+
const lapseNote = lapse ? { lapse } : {};
|
|
186
|
+
return { success: false, reason: 'not_holder', message, heldBy: existingLease?.locked_by ?? null, ...lapseNote };
|
|
107
187
|
}
|
|
108
188
|
|
|
109
189
|
db.prepare('DELETE FROM file_leases WHERE file_path = ?').run(cleanPath);
|
|
@@ -120,21 +200,73 @@ export const releaseFileLock = (db, filePath, agentId, options = {}) => {
|
|
|
120
200
|
});
|
|
121
201
|
};
|
|
122
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Renew-on-edit and `lock renew`: extends the holder's own live lease to now + one TTL.
|
|
205
|
+
* One UPDATE scoped to the holder's unexpired row; it never shortens a longer lease and
|
|
206
|
+
* never touches another agent's lease, the queue or the feed.
|
|
207
|
+
*/
|
|
208
|
+
export const renewHeldLease = (db, leaseKey, agentId, options = {}) => {
|
|
209
|
+
const now = options.now ?? Date.now();
|
|
210
|
+
const expiresAt = now + (options.ttlMs || DEFAULT_TTL_MS);
|
|
211
|
+
const cleanId = normalizeAgentId(agentId);
|
|
212
|
+
const sql = 'UPDATE file_leases SET expires_at = MAX(expires_at, ?) WHERE file_path = ? AND locked_by = ? AND expires_at > ?';
|
|
213
|
+
const res = db.prepare(sql).run(expiresAt, leaseKey, cleanId, now);
|
|
214
|
+
return { renewed: Number(res.changes) > 0, expires_at: expiresAt };
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const renewRefusal = (state, cleanId) => {
|
|
218
|
+
const hasLease = Boolean(state);
|
|
219
|
+
if (!hasLease) return 'no_lease';
|
|
220
|
+
const isHolder = state.locked_by === cleanId;
|
|
221
|
+
if (!isHolder) return 'not_holder';
|
|
222
|
+
const isLive = Boolean(state.active);
|
|
223
|
+
if (!isLive) return 'expired';
|
|
224
|
+
return null;
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export const renewFileLock = (db, filePath, agentId, options = {}) => {
|
|
228
|
+
const hasDb = Boolean(db);
|
|
229
|
+
const hasPath = Boolean(filePath);
|
|
230
|
+
const hasAgent = Boolean(agentId);
|
|
231
|
+
const canRenew = hasDb && hasPath && hasAgent;
|
|
232
|
+
if (!canRenew) return { renewed: false, reason: 'missing_args' };
|
|
233
|
+
|
|
234
|
+
const cleanPath = leaseKeyFor(db, filePath, options);
|
|
235
|
+
if (!cleanPath) return { renewed: false, reason: 'path_traversal' };
|
|
236
|
+
|
|
237
|
+
const cleanId = normalizeAgentId(agentId);
|
|
238
|
+
const state = describeLease(db.prepare('SELECT * FROM file_leases WHERE file_path = ?').get(cleanPath));
|
|
239
|
+
const reason = renewRefusal(state, cleanId);
|
|
240
|
+
const isRefused = Boolean(reason);
|
|
241
|
+
if (isRefused) return { renewed: false, reason, file_path: cleanPath, lease: state };
|
|
242
|
+
|
|
243
|
+
const res = renewHeldLease(db, cleanPath, cleanId, options);
|
|
244
|
+
const expiresAt = Math.max(Number(state.expires_at), res.expires_at);
|
|
245
|
+
return { renewed: res.renewed, file_path: cleanPath, locked_by: cleanId, expires_at: expiresAt, purpose: state.purpose || '' };
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/** Live leases only (unexpired, holder alive), soonest expiry first. Read-only. */
|
|
249
|
+
export const listActiveLeases = (db, now = Date.now()) => {
|
|
250
|
+
const hasDb = Boolean(db);
|
|
251
|
+
if (!hasDb) return [];
|
|
252
|
+
const rows = db.prepare('SELECT * FROM file_leases ORDER BY expires_at ASC').all();
|
|
253
|
+
return rows.map((row) => describeLease(row, now)).filter((lease) => lease.active);
|
|
254
|
+
};
|
|
255
|
+
|
|
123
256
|
export const getFileLockStatus = (db, filePath, options = {}) => {
|
|
124
257
|
const hasDb = Boolean(db);
|
|
125
258
|
const hasPath = Boolean(filePath);
|
|
126
259
|
const canGet = hasDb && hasPath;
|
|
127
260
|
if (!canGet) return null;
|
|
128
261
|
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
return { lease: null, waiters: [], error: 'path_traversal' };
|
|
132
|
-
}
|
|
133
|
-
const cleanPath = path.relative(baseDir, resolveSafePath(filePath, baseDir));
|
|
262
|
+
const cleanPath = leaseKeyFor(db, filePath, options);
|
|
263
|
+
if (!cleanPath) return { lease: null, waiters: [], error: 'path_traversal' };
|
|
134
264
|
|
|
135
|
-
|
|
136
|
-
|
|
265
|
+
// Read-only: an expired or orphaned lease is reported, never deleted, so a status
|
|
266
|
+
// check cannot race a concurrent acquire. Acquire and release do the cleanup.
|
|
267
|
+
const state = describeLease(db.prepare('SELECT * FROM file_leases WHERE file_path = ?').get(cleanPath));
|
|
268
|
+
const isActive = Boolean(state?.active);
|
|
137
269
|
const query = "SELECT * FROM file_lock_queue WHERE file_path = ? AND status = 'waiting' ORDER BY priority ASC, id ASC";
|
|
138
270
|
const waiters = db.prepare(query).all(cleanPath);
|
|
139
|
-
return { lease:
|
|
271
|
+
return { lease: isActive ? state : null, expiredLease: isActive ? null : state, waiters };
|
|
140
272
|
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { toColumnar } from '../columnar.js';
|
|
2
2
|
import { registerAgent } from './team-db-agents.js';
|
|
3
|
+
import { NOT_ARCHIVED_SQL } from './team-db-feed.js';
|
|
4
|
+
import { describeLease } from './team-db-lock-promotion.js';
|
|
5
|
+
import { resolveAgentId } from './agent-identity.js';
|
|
3
6
|
|
|
4
7
|
export const normalizeHandle = (handle) => {
|
|
5
8
|
if (!handle) return null;
|
|
@@ -8,7 +11,7 @@ export const normalizeHandle = (handle) => {
|
|
|
8
11
|
|
|
9
12
|
export const sendDirectMessage = (db, params = {}) => {
|
|
10
13
|
if (!db) return null;
|
|
11
|
-
const author =
|
|
14
|
+
const author = resolveAgentId(params.author_id);
|
|
12
15
|
const recipient = normalizeHandle(params.recipient_id);
|
|
13
16
|
if (!recipient) throw new Error('recipient_id is required');
|
|
14
17
|
registerAgent(db, { id: author, role: 'contributor' });
|
|
@@ -47,8 +50,9 @@ export const getAgentMailbox = (db, agentId, options = {}) => {
|
|
|
47
50
|
|
|
48
51
|
const limit = Math.min(Number(options.limit || 50), 200);
|
|
49
52
|
const msgParams = [cleanId];
|
|
50
|
-
let msgSql =
|
|
51
|
-
|
|
53
|
+
let msgSql = `SELECT id, timestamp, author_id, thread_id, task_id, read_at, message FROM agent_feed WHERE recipient_id = ? AND ${NOT_ARCHIVED_SQL}`;
|
|
54
|
+
const hasSince = Boolean(options.since);
|
|
55
|
+
if (hasSince) {
|
|
52
56
|
msgSql += ' AND id > ?';
|
|
53
57
|
msgParams.push(Number(options.since));
|
|
54
58
|
}
|
|
@@ -56,23 +60,26 @@ export const getAgentMailbox = (db, agentId, options = {}) => {
|
|
|
56
60
|
msgParams.push(limit);
|
|
57
61
|
const messages = db.prepare(msgSql).all(...msgParams);
|
|
58
62
|
|
|
63
|
+
// Expired leases and leases whose holder process is dead are not held any more; listing them
|
|
64
|
+
// would tell the agent it still owns files it lost.
|
|
59
65
|
const leases = db.prepare(`
|
|
60
|
-
SELECT file_path, locked_by, expires_at, purpose
|
|
66
|
+
SELECT file_path, locked_by, expires_at, purpose, pid
|
|
61
67
|
FROM file_leases
|
|
62
68
|
WHERE locked_by = ?
|
|
63
69
|
ORDER BY expires_at ASC
|
|
64
|
-
`).all(cleanId);
|
|
70
|
+
`).all(cleanId).map((lease) => describeLease(lease)).filter((lease) => lease.active);
|
|
65
71
|
|
|
66
72
|
const unreadRow = db.prepare(`
|
|
67
73
|
SELECT COUNT(*) as count FROM agent_feed
|
|
68
|
-
WHERE recipient_id = ? AND read_at IS NULL
|
|
74
|
+
WHERE recipient_id = ? AND read_at IS NULL AND ${NOT_ARCHIVED_SQL}
|
|
69
75
|
`).get(cleanId);
|
|
70
76
|
const unreadCount = Number(unreadRow?.count || 0);
|
|
71
77
|
|
|
72
|
-
|
|
78
|
+
const shouldMarkRead = Boolean(options.markRead);
|
|
79
|
+
if (shouldMarkRead) {
|
|
73
80
|
db.prepare(`
|
|
74
81
|
UPDATE agent_feed SET read_at = ?
|
|
75
|
-
WHERE recipient_id = ? AND read_at IS NULL
|
|
82
|
+
WHERE recipient_id = ? AND read_at IS NULL AND ${NOT_ARCHIVED_SQL}
|
|
76
83
|
`).run(Date.now(), cleanId);
|
|
77
84
|
}
|
|
78
85
|
|