@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.
Files changed (805) hide show
  1. package/AGENTS.md +135 -32
  2. package/README.md +253 -199
  3. package/blueprints/atoms/a-button.svelte +11 -9
  4. package/blueprints/atoms/a-button.vue +2 -1
  5. package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.svelte +8 -4
  6. package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.tsx +8 -4
  7. package/blueprints/molecule-capsule/m-chemx-badge/m-chemx-badge.vue +8 -4
  8. package/blueprints/molecule-capsule/m-sample-card.svelte +2 -1
  9. package/blueprints/molecule-capsule/m-sample-card.tsx +2 -1
  10. package/blueprints/molecule-capsule/m-sample-card.vue +4 -2
  11. package/blueprints/workflows/chemx-audit.yml +15 -46
  12. package/cli/agent-json.js +40 -0
  13. package/cli/apply-edits.js +195 -0
  14. package/cli/audit/ai-slop-detector.js +62 -35
  15. package/cli/audit/ast-passes.js +52 -0
  16. package/cli/audit/ast-visitors.js +142 -83
  17. package/cli/audit/audit-plain.js +26 -0
  18. package/cli/audit/audit-summary.js +16 -3
  19. package/cli/audit/autofix-content.js +122 -0
  20. package/cli/audit/autofix.js +74 -159
  21. package/cli/audit/budget-rules.js +84 -0
  22. package/cli/audit/catch-predicates.js +51 -0
  23. package/cli/audit/chemx-dir.js +76 -0
  24. package/cli/audit/clone-detector.js +4 -2
  25. package/cli/audit/comment-regions.js +83 -0
  26. package/cli/audit/coverage.js +48 -0
  27. package/cli/audit/csharp-analyzer.js +47 -22
  28. package/cli/audit/discussion-store.js +9 -4
  29. package/cli/audit/extended-text-patterns.js +104 -54
  30. package/cli/audit/extended-visitors.js +26 -12
  31. package/cli/audit/feed.d.ts +77 -0
  32. package/cli/audit/feed.js +146 -0
  33. package/cli/audit/gate-delta.js +91 -0
  34. package/cli/audit/gate-verdict.js +25 -3
  35. package/cli/audit/history.d.ts +14 -2
  36. package/cli/audit/history.js +89 -101
  37. package/cli/audit/hook-shape-validator.js +18 -9
  38. package/cli/audit/lexicon-predicates.js +125 -0
  39. package/cli/audit/lifecycle-predicates.js +106 -0
  40. package/cli/audit/line-budgets.js +154 -0
  41. package/cli/audit/metrics.js +56 -37
  42. package/cli/audit/mutable-ref-predicate.js +54 -0
  43. package/cli/audit/parseable-code.js +11 -0
  44. package/cli/audit/pattern-detector.js +56 -28
  45. package/cli/audit/project-figures.js +28 -0
  46. package/cli/audit/prompt-rule-lines.js +46 -0
  47. package/cli/audit/prompts.js +74 -46
  48. package/cli/audit/prop-surface.js +74 -0
  49. package/cli/audit/ratchet.js +112 -23
  50. package/cli/audit/render-depth.js +79 -0
  51. package/cli/audit/reporter-banner.js +7 -4
  52. package/cli/audit/reporter-grades.js +52 -34
  53. package/cli/audit/reporter-grouping.js +10 -5
  54. package/cli/audit/reporter-markdown.js +16 -8
  55. package/cli/audit/reporter-sections.js +31 -16
  56. package/cli/audit/reporter-summary.js +28 -16
  57. package/cli/audit/reporter-utils.js +54 -30
  58. package/cli/audit/roadmap.js +20 -15
  59. package/cli/audit/rule-overrides.js +78 -0
  60. package/cli/audit/rule-pillars.js +43 -0
  61. package/cli/audit/rule-revisions.js +42 -0
  62. package/cli/audit/rules-helpers.js +13 -63
  63. package/cli/audit/rules-predicates.d.ts +1 -0
  64. package/cli/audit/rules-predicates.js +84 -64
  65. package/cli/audit/rules-registry.js +94 -10
  66. package/cli/audit/rules.js +92 -171
  67. package/cli/audit/shallow-catch-csharp.js +49 -0
  68. package/cli/audit/shallow-catch-escalation.js +157 -0
  69. package/cli/audit/shallow-catch-reads.js +116 -0
  70. package/cli/audit/social-gh.js +18 -9
  71. package/cli/audit/social-git.js +82 -40
  72. package/cli/audit/social-http.js +17 -9
  73. package/cli/audit/social-publisher.js +14 -7
  74. package/cli/audit/social.js +39 -30
  75. package/cli/audit/staged-delta.js +67 -0
  76. package/cli/audit/status-file.js +67 -0
  77. package/cli/audit/structural-weight-evaluator.js +17 -29
  78. package/cli/audit/structural-weight.js +30 -13
  79. package/cli/audit/suppressions.js +130 -0
  80. package/cli/audit/template-rules.js +82 -0
  81. package/cli/audit/types.d.ts +24 -1
  82. package/cli/audit-engine.js +128 -0
  83. package/cli/audit-preflight-git.js +15 -1
  84. package/cli/audit-preflight.js +6 -4
  85. package/cli/audit-scan.js +180 -0
  86. package/cli/audit-scope.js +31 -14
  87. package/cli/audit.js +6 -201
  88. package/cli/babel-lazy.js +41 -0
  89. package/cli/badge.js +9 -6
  90. package/cli/banner.js +37 -0
  91. package/cli/build/child-registry.js +57 -0
  92. package/cli/build/executor.js +122 -48
  93. package/cli/build.js +75 -25
  94. package/cli/cli-args.js +107 -0
  95. package/cli/columnar.js +6 -3
  96. package/cli/commands/cmd-audit-each.js +164 -0
  97. package/cli/commands/cmd-audit-feed.js +78 -0
  98. package/cli/commands/cmd-audit.js +136 -57
  99. package/cli/commands/cmd-batch.js +60 -0
  100. package/cli/commands/cmd-check.js +74 -0
  101. package/cli/commands/cmd-project.js +18 -9
  102. package/cli/commands/cmd-router.js +148 -32
  103. package/cli/commands/cmd-show.js +56 -0
  104. package/cli/commands/cmd-staged-delta.js +27 -0
  105. package/cli/commands/cmd-wrappers-files.js +77 -0
  106. package/cli/commands/cmd-wrappers-git.js +122 -0
  107. package/cli/commands/cmd-wrappers-json.js +101 -0
  108. package/cli/commands/cmd-wrappers.js +7 -322
  109. package/cli/commands/unknown-flags.js +228 -0
  110. package/cli/commands-schema-badge.js +73 -0
  111. package/cli/commands-schema-commit.js +21 -0
  112. package/cli/commands-schema-edit.js +106 -0
  113. package/cli/commands-schema-host.js +65 -0
  114. package/cli/commands-schema-ops.js +127 -0
  115. package/cli/commands-schema-patterns.js +69 -0
  116. package/cli/commands-schema-report.js +18 -0
  117. package/cli/commands-schema-search.js +121 -0
  118. package/cli/commands-schema-setup.js +30 -0
  119. package/cli/commands-schema-verify.js +89 -0
  120. package/cli/commands-schema-wrappers.js +131 -0
  121. package/cli/commands-schema.js +26 -408
  122. package/cli/comment-ranges.js +108 -0
  123. package/cli/commit/commit-args.js +73 -0
  124. package/cli/commit/commit-cli.js +25 -0
  125. package/cli/commit/commit-facts.js +49 -0
  126. package/cli/commit/commit-git.js +98 -0
  127. package/cli/commit/commit-index.js +35 -0
  128. package/cli/commit/commit-message.js +52 -0
  129. package/cli/commit/commit-record.js +68 -0
  130. package/cli/commit/commit-run.js +160 -0
  131. package/cli/commit/commit-validate.js +62 -0
  132. package/cli/config/index.js +54 -8
  133. package/cli/config/loader.js +77 -8
  134. package/cli/config/profiles.js +7 -0
  135. package/cli/conflicts-cli.js +118 -0
  136. package/cli/conflicts.js +104 -0
  137. package/cli/create.js +11 -6
  138. package/cli/db-project-stamp.js +124 -0
  139. package/cli/docs-check/command-tree.js +27 -0
  140. package/cli/docs-check/extract.js +151 -0
  141. package/cli/docs-check/resolve.js +85 -0
  142. package/cli/docs-check/run.js +88 -0
  143. package/cli/doctor/check-env.js +43 -0
  144. package/cli/doctor/check-host.js +115 -0
  145. package/cli/doctor/check-index.js +93 -0
  146. package/cli/doctor/check-mcp.js +78 -0
  147. package/cli/doctor/doctor-cli.js +60 -0
  148. package/cli/doctor/kit-locate.js +40 -0
  149. package/cli/doctor/proc-scan.js +66 -0
  150. package/cli/edit-atomic.js +75 -0
  151. package/cli/edit-diff.js +98 -0
  152. package/cli/edit-guardrails.js +77 -0
  153. package/cli/edit-locks.js +128 -0
  154. package/cli/embeddings/vectorizer.js +9 -5
  155. package/cli/errors/catcher.js +45 -52
  156. package/cli/errors/formatter.js +14 -10
  157. package/cli/errors/policy.js +26 -0
  158. package/cli/errors/publisher.js +5 -1
  159. package/cli/errors/report-output.js +67 -0
  160. package/cli/errors/sanitizer.js +52 -3
  161. package/cli/errors/storage.js +17 -5
  162. package/cli/explode-importers.js +102 -0
  163. package/cli/explode-path-args.js +115 -0
  164. package/cli/explode-plan.js +153 -0
  165. package/cli/explode-prologue.js +43 -0
  166. package/cli/explode-relocate.js +73 -0
  167. package/cli/exploder.js +205 -193
  168. package/cli/forge/anchors.js +50 -0
  169. package/cli/forge/audit-forge.js +58 -0
  170. package/cli/forge/binding-walk.js +84 -0
  171. package/cli/forge/bindings.js +238 -0
  172. package/cli/forge/blueprint-cli.js +153 -0
  173. package/cli/forge/blueprint-context.js +69 -0
  174. package/cli/forge/blueprint-members.js +82 -0
  175. package/cli/forge/blueprint-store.js +106 -0
  176. package/cli/forge/blueprint-target.js +44 -0
  177. package/cli/forge/blueprint.js +276 -0
  178. package/cli/forge/body-end-cache.js +34 -0
  179. package/cli/forge/body-ends.js +86 -0
  180. package/cli/forge/canon-inline.js +342 -0
  181. package/cli/forge/canon-logic.js +106 -0
  182. package/cli/forge/canon-nodes.js +270 -0
  183. package/cli/forge/canon-print.js +120 -0
  184. package/cli/forge/canonicalize.js +49 -0
  185. package/cli/forge/conventions.js +51 -0
  186. package/cli/forge/drift.js +120 -0
  187. package/cli/forge/exclusions.js +21 -0
  188. package/cli/forge/exits.js +76 -0
  189. package/cli/forge/facets.js +86 -0
  190. package/cli/forge/file-units.js +53 -0
  191. package/cli/forge/fingerprint-file.js +52 -0
  192. package/cli/forge/fingerprint-session.js +215 -0
  193. package/cli/forge/fingerprint-sync.js +74 -0
  194. package/cli/forge/fingerprint-visitors.js +66 -0
  195. package/cli/forge/fixtures/heal/a7-dry-run.diff.txt +179 -0
  196. package/cli/forge/fixtures/heal/cli/commit/commit-facts.js.txt +49 -0
  197. package/cli/forge/fixtures/heal/cli/doctor/check-host.js.txt +117 -0
  198. package/cli/forge/fixtures/heal/cli/doctor/kit-locate.js.txt +42 -0
  199. package/cli/forge/fixtures/heal/cli/hooks/guard-paths.js.txt +80 -0
  200. package/cli/forge/fixtures/heal/cli/hooks/project-status.js.txt +62 -0
  201. package/cli/forge/fixtures/heal/cli/main.js.txt +169 -0
  202. package/cli/forge/fixtures/heal/cli/mcp/context.js.txt +81 -0
  203. package/cli/forge/fixtures/heal/cli/mcp/installer-report.js.txt +47 -0
  204. package/cli/forge/fixtures/heal/cli/path-scope.js.txt +169 -0
  205. package/cli/forge/fixtures/heal/cli/project-detector.js.txt +364 -0
  206. package/cli/forge/fixtures/heal/cli/project-markers.js.txt +42 -0
  207. package/cli/forge/fixtures/heal/cli/team/coordination-root.js.txt +161 -0
  208. package/cli/forge/fixtures/heal/cli/team/usage-reader.js.txt +109 -0
  209. package/cli/forge/fixtures/heal/cli/test-slots.js.txt +129 -0
  210. package/cli/forge/fixtures/social-gh.js.txt +131 -0
  211. package/cli/forge/fixtures/team-flags.js.txt +214 -0
  212. package/cli/forge/fold.js +199 -0
  213. package/cli/forge/forge-groups.js +200 -0
  214. package/cli/forge/forge-report.js +73 -0
  215. package/cli/forge/fuzz/binding-hook.js +36 -0
  216. package/cli/forge/fuzz/choices.js +40 -0
  217. package/cli/forge/fuzz/classes.js +41 -0
  218. package/cli/forge/fuzz/coverage.js +26 -0
  219. package/cli/forge/fuzz/gen-alias.js +61 -0
  220. package/cli/forge/fuzz/gen-control.js +73 -0
  221. package/cli/forge/fuzz/gen-kit.js +75 -0
  222. package/cli/forge/fuzz/gen-logic.js +77 -0
  223. package/cli/forge/fuzz/gen-mixed.js +204 -0
  224. package/cli/forge/fuzz/gen-modules.js +80 -0
  225. package/cli/forge/fuzz/gen-objects.js +92 -0
  226. package/cli/forge/fuzz/gen-scope.js +131 -0
  227. package/cli/forge/fuzz/gen-templates.js +87 -0
  228. package/cli/forge/fuzz/harness.js +314 -0
  229. package/cli/forge/fuzz/pair-check.js +115 -0
  230. package/cli/forge/fuzz/resolve.js +32 -0
  231. package/cli/forge/fuzz/run.js +121 -0
  232. package/cli/forge/fuzz/sandbox.js +108 -0
  233. package/cli/forge/fuzz/seeds.js +119 -0
  234. package/cli/forge/fuzz/slow-shard.js +60 -0
  235. package/cli/forge/fuzz/template-oracle.js +148 -0
  236. package/cli/forge/fuzz/transpile.js +61 -0
  237. package/cli/forge/gates.js +115 -0
  238. package/cli/forge/group-shape.js +137 -0
  239. package/cli/forge/group-store.js +160 -0
  240. package/cli/forge/group.js +280 -0
  241. package/cli/forge/gt-sandbox.js +83 -0
  242. package/cli/forge/gt-scaffold.js +110 -0
  243. package/cli/forge/hash-labels.js +117 -0
  244. package/cli/forge/hash.js +119 -0
  245. package/cli/forge/heal-apply.js +170 -0
  246. package/cli/forge/heal-cli.js +132 -0
  247. package/cli/forge/heal-leases.js +52 -0
  248. package/cli/forge/heal-locate.js +70 -0
  249. package/cli/forge/heal-match.js +208 -0
  250. package/cli/forge/heal-ops.js +197 -0
  251. package/cli/forge/heal-plan.js +143 -0
  252. package/cli/forge/heal-sandbox.js +126 -0
  253. package/cli/forge/heal-specs.js +80 -0
  254. package/cli/forge/heal-store.js +80 -0
  255. package/cli/forge/heal-text.js +60 -0
  256. package/cli/forge/heal-typecheck.js +108 -0
  257. package/cli/forge/heal-undo.js +74 -0
  258. package/cli/forge/heal-verify.js +148 -0
  259. package/cli/forge/hole-kinds.js +126 -0
  260. package/cli/forge/inline-mode.js +6 -0
  261. package/cli/forge/inline-on.setup.js +4 -0
  262. package/cli/forge/lgg-stage.js +125 -0
  263. package/cli/forge/lgg.js +197 -0
  264. package/cli/forge/library-match.js +74 -0
  265. package/cli/forge/murmur.js +86 -0
  266. package/cli/forge/naming.js +101 -0
  267. package/cli/forge/pattern-schema-ddl.js +157 -0
  268. package/cli/forge/placement.js +165 -0
  269. package/cli/forge/rank.js +174 -0
  270. package/cli/forge/refine.js +93 -0
  271. package/cli/forge/rejected-members.js +92 -0
  272. package/cli/forge/rejects.js +77 -0
  273. package/cli/forge/root-shapes.js +84 -0
  274. package/cli/forge/run-cache.js +145 -0
  275. package/cli/forge/sibling-blocks.js +41 -0
  276. package/cli/forge/siblings.js +140 -0
  277. package/cli/forge/store.js +180 -0
  278. package/cli/forge/template-normalize.js +209 -0
  279. package/cli/forge/template-roles.js +86 -0
  280. package/cli/forge/template-units.js +148 -0
  281. package/cli/forge/templates.js +42 -0
  282. package/cli/forge/unify-step.js +65 -0
  283. package/cli/forge/unit-floor.js +74 -0
  284. package/cli/forge/unit-hash.js +229 -0
  285. package/cli/forge/unit-trees.js +153 -0
  286. package/cli/forge/units.js +261 -0
  287. package/cli/forge/windows.js +129 -0
  288. package/cli/friction/friction-cli.js +71 -0
  289. package/cli/friction/friction-export.js +54 -0
  290. package/cli/friction/friction-log.js +54 -0
  291. package/cli/friction/usage-classify.js +56 -0
  292. package/cli/friction/usage-report.js +86 -0
  293. package/cli/friction/wrong-call.js +12 -0
  294. package/cli/fs-json.js +17 -0
  295. package/cli/generator-compact.js +55 -0
  296. package/cli/generator-help.js +16 -15
  297. package/cli/generator-indexer.js +2 -1
  298. package/cli/generator-jig-cli.js +11 -7
  299. package/cli/generator-jig.js +38 -33
  300. package/cli/generator-templates/archetypes/index.js +30 -13
  301. package/cli/generator-templates/archetypes/vector-matcher.js +8 -4
  302. package/cli/generator-templates/compact.js +4 -2
  303. package/cli/generator-templates/controller-converters.js +23 -14
  304. package/cli/generator-templates/controller.js +10 -5
  305. package/cli/generator-templates/hooks.js +4 -2
  306. package/cli/generator-templates/naming.js +2 -1
  307. package/cli/generator-templates/react.js +2 -1
  308. package/cli/generator-templates/specs.js +4 -2
  309. package/cli/generator-templates/svelte.js +4 -2
  310. package/cli/generator-templates/types.js +6 -3
  311. package/cli/generator-templates/views.js +4 -2
  312. package/cli/generator-templates/vue.js +4 -2
  313. package/cli/generator-writes.js +24 -0
  314. package/cli/generator.d.ts +1 -1
  315. package/cli/generator.js +86 -127
  316. package/cli/git-file-listing.js +69 -0
  317. package/cli/help-args.js +18 -0
  318. package/cli/help.d.ts +7 -3
  319. package/cli/help.js +94 -149
  320. package/cli/hooks/bypass-log.js +44 -0
  321. package/cli/hooks/claude-post-edit.js +79 -0
  322. package/cli/hooks/claude-pre-tool.js +178 -0
  323. package/cli/hooks/claude-settings-merge.js +101 -0
  324. package/cli/hooks/dispatch-identity.js +125 -0
  325. package/cli/hooks/entry.js +181 -0
  326. package/cli/hooks/foreign-identity.js +60 -0
  327. package/cli/hooks/guard-args.js +55 -0
  328. package/cli/hooks/guard-cases.js +94 -0
  329. package/cli/hooks/guard-config.js +43 -0
  330. package/cli/hooks/guard-invocation.js +106 -0
  331. package/cli/hooks/guard-paths.js +80 -0
  332. package/cli/hooks/guard-route.js +211 -0
  333. package/cli/hooks/guard-rules-nudge.js +37 -0
  334. package/cli/hooks/guard-rules-reads.js +66 -0
  335. package/cli/hooks/guard-rules-shell.js +239 -0
  336. package/cli/hooks/guard-rules.js +114 -0
  337. package/cli/hooks/hook-commands.js +21 -0
  338. package/cli/hooks/install-hooks-apply.js +36 -0
  339. package/cli/hooks/install-hooks-cli.js +100 -0
  340. package/cli/hooks/install-hooks-config.js +48 -0
  341. package/cli/hooks/install-hooks-pins.js +66 -0
  342. package/cli/hooks/install-hooks-plan.js +56 -0
  343. package/cli/hooks/install-hooks-status.js +7 -0
  344. package/cli/hooks/interpreter-writes.js +195 -0
  345. package/cli/hooks/launcher.js +60 -0
  346. package/cli/hooks/mcp-json-merge.js +39 -0
  347. package/cli/hooks/native-edit-lock.js +40 -0
  348. package/cli/hooks/native-tool-policy.js +152 -0
  349. package/cli/hooks/out-of-band-edits.js +141 -0
  350. package/cli/hooks/post-edit-scope.js +43 -0
  351. package/cli/hooks/project-status.js +59 -0
  352. package/cli/hooks/repo-membership.js +32 -0
  353. package/cli/hooks/run-hook.js +79 -0
  354. package/cli/hooks/session-identity.js +153 -0
  355. package/cli/hooks/session-start.js +50 -0
  356. package/cli/hooks/shell-lexer-readers.js +76 -0
  357. package/cli/hooks/shell-lexer-state.js +59 -0
  358. package/cli/hooks/shell-lexer.js +110 -0
  359. package/cli/hooks/shell-parse.js +232 -0
  360. package/cli/hooks/statusline.js +27 -0
  361. package/cli/hooks/xatoms-hints.js +38 -0
  362. package/cli/host-protocol.js +109 -0
  363. package/cli/host-shims.js +9 -8
  364. package/cli/index-admission.js +41 -0
  365. package/cli/index-freshness.js +83 -0
  366. package/cli/index-row-check.js +58 -0
  367. package/cli/index.js +52 -150
  368. package/cli/installer-templates.js +67 -75
  369. package/cli/installer.d.ts +16 -1
  370. package/cli/installer.js +159 -58
  371. package/cli/languages.js +8 -4
  372. package/cli/lattice-directives.js +24 -0
  373. package/cli/lattice-payload.js +34 -0
  374. package/cli/{tesseract-state.js → lattice-state.js} +8 -4
  375. package/cli/lease-view.js +48 -0
  376. package/cli/library/entry-fp.js +42 -0
  377. package/cli/library/entry-schema.js +90 -0
  378. package/cli/library/library-tasks.js +25 -0
  379. package/cli/library/registry.js +56 -0
  380. package/cli/library/reverify.js +56 -0
  381. package/cli/library/verify-checks.js +89 -0
  382. package/cli/library/verify.js +81 -0
  383. package/cli/license-config.js +124 -0
  384. package/cli/license.js +168 -135
  385. package/cli/line-count.js +23 -0
  386. package/cli/literal-replace.js +107 -0
  387. package/cli/main.js +166 -0
  388. package/cli/mcp/antigravity.js +11 -9
  389. package/cli/mcp/batch.js +59 -0
  390. package/cli/mcp/call-args.js +75 -0
  391. package/cli/mcp/call-effects.js +57 -0
  392. package/cli/mcp/call-scope.js +70 -62
  393. package/cli/mcp/context.js +81 -0
  394. package/cli/mcp/envelope.js +62 -0
  395. package/cli/mcp/fingerprint.js +26 -0
  396. package/cli/mcp/fresh-entry.js +34 -0
  397. package/cli/mcp/fresh-protocol.js +37 -0
  398. package/cli/mcp/fresh-runner.js +40 -0
  399. package/cli/mcp/help.js +53 -0
  400. package/cli/mcp/index.js +9 -5
  401. package/cli/mcp/inflight.js +47 -0
  402. package/cli/mcp/installer-jsonc.js +64 -0
  403. package/cli/mcp/installer-package.js +54 -0
  404. package/cli/mcp/installer-report.js +47 -0
  405. package/cli/mcp/installer-write.js +117 -0
  406. package/cli/mcp/installer.js +88 -187
  407. package/cli/mcp/manifests-analysis.js +156 -0
  408. package/cli/mcp/manifests-runners.js +48 -0
  409. package/cli/mcp/manifests-team.js +100 -0
  410. package/cli/mcp/manifests.js +69 -309
  411. package/cli/mcp/offload-worker.js +11 -0
  412. package/cli/mcp/offload.js +18 -0
  413. package/cli/mcp/patterns-cli.js +76 -0
  414. package/cli/mcp/patterns-forge-cli.js +120 -0
  415. package/cli/mcp/prompts.js +13 -10
  416. package/cli/mcp/resources.js +8 -35
  417. package/cli/mcp/roots.js +46 -0
  418. package/cli/mcp/scorecard-worker.js +7 -0
  419. package/cli/mcp/scorecard.js +42 -0
  420. package/cli/mcp/server-info.js +28 -0
  421. package/cli/mcp/server.js +91 -291
  422. package/cli/mcp/spec-harness.js +62 -0
  423. package/cli/mcp/stale-exec.js +48 -0
  424. package/cli/mcp/staleness.js +26 -0
  425. package/cli/mcp/stdio.js +141 -0
  426. package/cli/mcp/tool-call.js +73 -0
  427. package/cli/mcp/tools-audit-feed.js +33 -0
  428. package/cli/mcp/tools-audit.js +13 -7
  429. package/cli/mcp/tools-generate.js +3 -3
  430. package/cli/mcp/tools-lazy.js +39 -0
  431. package/cli/mcp/tools-patch.js +93 -21
  432. package/cli/mcp/tools-patterns.js +6 -3
  433. package/cli/mcp/tools-project.js +8 -4
  434. package/cli/mcp/tools-q-modes.js +15 -14
  435. package/cli/mcp/tools-q.js +94 -33
  436. package/cli/mcp/tools-read.js +30 -80
  437. package/cli/mcp/tools-team-feed.js +10 -8
  438. package/cli/mcp/tools-team-locks.js +22 -10
  439. package/cli/mcp/tools-team-mailbox.js +8 -7
  440. package/cli/mcp/tools-team-tasks.js +125 -38
  441. package/cli/mcp/tools-team.js +14 -7
  442. package/cli/mcp/tools-verify.js +44 -15
  443. package/cli/mcp/tools-write.js +11 -16
  444. package/cli/mcp/tools.js +202 -68
  445. package/cli/mcp/worker-run.js +24 -0
  446. package/cli/missing-imports.js +52 -0
  447. package/cli/mutation-args.js +96 -0
  448. package/cli/mutators.js +164 -61
  449. package/cli/navigator-actions.js +19 -13
  450. package/cli/navigator-banner-helpers.js +1 -26
  451. package/cli/navigator-banner.js +5 -3
  452. package/cli/navigator-conversion.js +23 -12
  453. package/cli/navigator-grades.js +70 -35
  454. package/cli/navigator-guide.js +13 -11
  455. package/cli/navigator-menu.js +80 -166
  456. package/cli/navigator-paged.js +15 -8
  457. package/cli/navigator-sections.js +32 -0
  458. package/cli/navigator-share.js +53 -39
  459. package/cli/navigator.d.ts +25 -5
  460. package/cli/navigator.js +14 -41
  461. package/cli/network-policy.js +72 -0
  462. package/cli/network-recorder.fixture.mjs +35 -0
  463. package/cli/outline/ast-outline.js +120 -0
  464. package/cli/outline/style-outline.js +42 -0
  465. package/cli/outline/vue-outline.js +88 -0
  466. package/cli/patcher-cli.js +255 -0
  467. package/cli/patcher.d.ts +55 -24
  468. package/cli/patcher.js +167 -358
  469. package/cli/path-scope.js +78 -22
  470. package/cli/patterns/fixtures/gt/A1.txt +109 -0
  471. package/cli/patterns/fixtures/gt/A10.txt +13 -0
  472. package/cli/patterns/fixtures/gt/A11.txt +10 -0
  473. package/cli/patterns/fixtures/gt/A12.txt +29 -0
  474. package/cli/patterns/fixtures/gt/A13.txt +39 -0
  475. package/cli/patterns/fixtures/gt/A14.txt +45 -0
  476. package/cli/patterns/fixtures/gt/A15.txt +70 -0
  477. package/cli/patterns/fixtures/gt/A16.txt +59 -0
  478. package/cli/patterns/fixtures/gt/A17.txt +29 -0
  479. package/cli/patterns/fixtures/gt/A18.txt +9 -0
  480. package/cli/patterns/fixtures/gt/A19.txt +29 -0
  481. package/cli/patterns/fixtures/gt/A2.txt +28 -0
  482. package/cli/patterns/fixtures/gt/A20.txt +83 -0
  483. package/cli/patterns/fixtures/gt/A21.txt +138 -0
  484. package/cli/patterns/fixtures/gt/A22.txt +32 -0
  485. package/cli/patterns/fixtures/gt/A23.txt +37 -0
  486. package/cli/patterns/fixtures/gt/A24.txt +67 -0
  487. package/cli/patterns/fixtures/gt/A25.txt +41 -0
  488. package/cli/patterns/fixtures/gt/A26.txt +29 -0
  489. package/cli/patterns/fixtures/gt/A3.txt +17 -0
  490. package/cli/patterns/fixtures/gt/A4.txt +44 -0
  491. package/cli/patterns/fixtures/gt/A5.txt +32 -0
  492. package/cli/patterns/fixtures/gt/A6.txt +15 -0
  493. package/cli/patterns/fixtures/gt/A7.txt +79 -0
  494. package/cli/patterns/fixtures/gt/A8.txt +41 -0
  495. package/cli/patterns/fixtures/gt/A9.txt +42 -0
  496. package/cli/patterns/fixtures/gt/B1.txt +11 -0
  497. package/cli/patterns/fixtures/gt/B10.txt +41 -0
  498. package/cli/patterns/fixtures/gt/B11.txt +7 -0
  499. package/cli/patterns/fixtures/gt/B2.txt +19 -0
  500. package/cli/patterns/fixtures/gt/B3.txt +70 -0
  501. package/cli/patterns/fixtures/gt/B4.txt +55 -0
  502. package/cli/patterns/fixtures/gt/B5.txt +21 -0
  503. package/cli/patterns/fixtures/gt/B6.txt +15 -0
  504. package/cli/patterns/fixtures/gt/B7.txt +24 -0
  505. package/cli/patterns/fixtures/gt/B8.txt +40 -0
  506. package/cli/patterns/fixtures/gt/B9.txt +18 -0
  507. package/cli/patterns/fixtures/gt/C1.txt +26 -0
  508. package/cli/patterns/fixtures/gt/C2.txt +33 -0
  509. package/cli/patterns/fixtures/gt/C3.txt +26 -0
  510. package/cli/patterns/fixtures/gt/C4.txt +7 -0
  511. package/cli/patterns/fixtures/gt/C5.txt +29 -0
  512. package/cli/patterns/fixtures/gt/C6.txt +7 -0
  513. package/cli/patterns/fixtures/gt/C7.txt +18 -0
  514. package/cli/patterns/fixtures/gt/labels.json +1837 -0
  515. package/cli/patterns/gt-build.js +55 -0
  516. package/cli/patterns/gt-locations.js +76 -0
  517. package/cli/patterns/gt-resolve.js +66 -0
  518. package/cli/patterns/gt-score-cli.js +80 -0
  519. package/cli/patterns/gt-score.js +188 -0
  520. package/cli/patterns/gt-text.js +79 -0
  521. package/cli/pillars-wizard.js +53 -24
  522. package/cli/pillars-write-guard.js +4 -1
  523. package/cli/postinstall.js +22 -19
  524. package/cli/project-detector.js +60 -35
  525. package/cli/project-markers.js +42 -0
  526. package/cli/project-scope.js +20 -0
  527. package/cli/read-revision.js +70 -0
  528. package/cli/reader-cards-gate.js +29 -0
  529. package/cli/reader-cards.js +153 -0
  530. package/cli/reader-cli.js +45 -18
  531. package/cli/reader-format.js +48 -0
  532. package/cli/reader-logic.js +128 -250
  533. package/cli/reader.js +210 -221
  534. package/cli/request-context.js +9 -0
  535. package/cli/result-status.js +23 -0
  536. package/cli/rules.js +81 -0
  537. package/cli/scaffold.js +80 -51
  538. package/cli/scan-ignore.js +12 -0
  539. package/cli/search-args.js +102 -0
  540. package/cli/search-ast-module.js +103 -0
  541. package/cli/search-ast.js +75 -112
  542. package/cli/search-commands-blast.js +70 -0
  543. package/cli/search-commands-graph.js +57 -105
  544. package/cli/search-commands-hazards.js +105 -0
  545. package/cli/search-commands-literal.js +102 -0
  546. package/cli/search-commands-semantic.js +39 -27
  547. package/cli/search-commands.js +93 -186
  548. package/cli/search-db.js +52 -252
  549. package/cli/search-debug.js +13 -0
  550. package/cli/search-graph-edges.js +112 -0
  551. package/cli/search-index-meta.js +66 -0
  552. package/cli/search-index-write.js +153 -0
  553. package/cli/search-literal-files.js +64 -0
  554. package/cli/search-literal.js +150 -0
  555. package/cli/search-output.js +88 -0
  556. package/cli/search-queries-graph.js +120 -193
  557. package/cli/search-queries-semantic.js +41 -14
  558. package/cli/search-queries-similar.js +2 -1
  559. package/cli/search-queries.js +46 -21
  560. package/cli/search-query-view.js +67 -0
  561. package/cli/search-rank.js +54 -0
  562. package/cli/search-replace-blocks.js +120 -0
  563. package/cli/search-resolve.js +52 -0
  564. package/cli/search-root.js +78 -0
  565. package/cli/search-scan.js +102 -0
  566. package/cli/search-schema-ddl.js +172 -0
  567. package/cli/search-schema.js +135 -220
  568. package/cli/search-session.js +11 -0
  569. package/cli/search-sync-paths.js +78 -0
  570. package/cli/search-sync-plan.js +63 -0
  571. package/cli/search-sync-rows.js +96 -0
  572. package/cli/search-sync.js +128 -0
  573. package/cli/search-tokenizer.js +8 -4
  574. package/cli/search.d.ts +26 -5
  575. package/cli/search.js +156 -410
  576. package/cli/sfc/component-resolver.js +96 -0
  577. package/cli/sfc/import-coverage.js +24 -0
  578. package/cli/sfc/module-aliases.js +102 -0
  579. package/cli/sfc/script-asts.js +51 -0
  580. package/cli/sfc/sfc-metadata.js +104 -0
  581. package/cli/sfc/sfc-parse.js +101 -0
  582. package/cli/sfc/svelte-parse.js +46 -0
  583. package/cli/sfc/template-walk.js +78 -0
  584. package/cli/sfc-scripts.js +60 -0
  585. package/cli/share-consent.js +49 -0
  586. package/cli/share-outcome.js +57 -0
  587. package/cli/silence-warnings.js +2 -1
  588. package/cli/source-parse.js +130 -0
  589. package/cli/spec-isolated-home.js +17 -0
  590. package/cli/spec-support/import-trace.mjs +22 -0
  591. package/cli/spec-support/run-cli.js +58 -0
  592. package/cli/sqlite-memory.js +47 -0
  593. package/cli/status-command.js +107 -0
  594. package/cli/symbol-locator.js +121 -0
  595. package/cli/team/agent-identity.js +58 -0
  596. package/cli/team/audit-run-adoption.js +117 -0
  597. package/cli/team/audit-run-bypass.js +177 -0
  598. package/cli/team/audit-run-calls.js +108 -0
  599. package/cli/team/audit-run-db.js +181 -0
  600. package/cli/team/audit-run-identity.js +80 -0
  601. package/cli/team/audit-run-invocations.js +130 -0
  602. package/cli/team/audit-run-protocol.js +258 -0
  603. package/cli/team/audit-run-render.js +107 -0
  604. package/cli/team/audit-run-shas.js +179 -0
  605. package/cli/team/audit-run.js +203 -0
  606. package/cli/team/claim-ignore-deps.js +16 -0
  607. package/cli/team/coordination-busy.js +164 -0
  608. package/cli/team/coordination-db.js +85 -0
  609. package/cli/team/coordination-fixture.js +53 -0
  610. package/cli/team/coordination-repos.js +72 -0
  611. package/cli/team/coordination-root.js +161 -0
  612. package/cli/team/coordination-target.js +114 -0
  613. package/cli/team/dispatch-templates/dispatch-v1.js +228 -0
  614. package/cli/team/edit-shas.js +45 -0
  615. package/cli/team/lease-activity.js +130 -0
  616. package/cli/team/lease-cap.js +67 -0
  617. package/cli/team/lease-contention.js +30 -0
  618. package/cli/team/lease-key.js +46 -0
  619. package/cli/team/lease-lapse.js +83 -0
  620. package/cli/team/lease-reacquire.js +99 -0
  621. package/cli/team/lease-renew.js +97 -0
  622. package/cli/team/lease-roots.js +30 -0
  623. package/cli/team/lease-waiter-notice.js +48 -0
  624. package/cli/team/staged-leases.js +64 -0
  625. package/cli/team/task-completion-output.js +78 -0
  626. package/cli/team/task-detail-sections.js +86 -0
  627. package/cli/team/task-list-view.js +10 -1
  628. package/cli/team/task-ref.js +73 -0
  629. package/cli/team/task-target.js +53 -0
  630. package/cli/team/task-tier.js +17 -0
  631. package/cli/team/team-brief.js +78 -0
  632. package/cli/team/team-commands-board.js +118 -0
  633. package/cli/team/team-commands-dispatch.js +85 -0
  634. package/cli/team/team-commands-lock-staged.js +87 -0
  635. package/cli/team/team-commands-lock-views.js +144 -0
  636. package/cli/team/team-commands-lock.js +115 -26
  637. package/cli/team/team-commands-migrate.js +171 -0
  638. package/cli/team/team-commands-profile.js +44 -0
  639. package/cli/team/team-commands-repo.js +114 -0
  640. package/cli/team/team-commands-task.js +312 -0
  641. package/cli/team/team-commands-tokens.js +57 -0
  642. package/cli/team/team-commands-triage.js +43 -0
  643. package/cli/team/team-commands-vds.js +2 -1
  644. package/cli/team/team-commands.js +108 -463
  645. package/cli/team/team-db-agents.js +12 -6
  646. package/cli/team/team-db-feed.js +25 -13
  647. package/cli/team/team-db-lock-promotion.js +95 -51
  648. package/cli/team/team-db-locks.js +167 -35
  649. package/cli/team/team-db-mailbox.js +15 -8
  650. package/cli/team/team-db-readonly.js +76 -0
  651. package/cli/team/team-db-task-helpers.js +68 -19
  652. package/cli/team/team-db-tasks.js +21 -9
  653. package/cli/team/team-db-transaction.js +20 -5
  654. package/cli/team/team-db.js +11 -4
  655. package/cli/team/team-dispatch-batches.js +271 -0
  656. package/cli/team/team-dispatch-prompts.js +152 -0
  657. package/cli/team/team-dispatch-render.js +154 -0
  658. package/cli/team/team-dispatch-run-cli.js +154 -0
  659. package/cli/team/team-dispatch-runs.js +155 -0
  660. package/cli/team/team-dispatch-script.js +67 -0
  661. package/cli/team/team-dispatch-select.js +220 -0
  662. package/cli/team/team-dispatch-v2-fixture.js +62 -0
  663. package/cli/team/team-dispatch-v2.js +207 -0
  664. package/cli/team/team-dispatch.js +229 -0
  665. package/cli/team/team-dispatcher-authority.js +43 -0
  666. package/cli/team/team-flags.js +222 -48
  667. package/cli/team/team-format.js +80 -26
  668. package/cli/team/team-handoff-hint.js +9 -0
  669. package/cli/team/team-migrate-agents.js +52 -0
  670. package/cli/team/team-migrate-apply.js +178 -0
  671. package/cli/team/team-migrate-junk.js +55 -0
  672. package/cli/team/team-migrate-metadata.js +80 -0
  673. package/cli/team/team-migrate-paths.js +50 -0
  674. package/cli/team/team-migrate-plan.js +85 -0
  675. package/cli/team/team-migrate-report.js +89 -0
  676. package/cli/team/team-migrate-tables.js +85 -0
  677. package/cli/team/team-migrate.js +193 -0
  678. package/cli/team/team-needs.js +99 -0
  679. package/cli/team/team-profile-format.js +126 -0
  680. package/cli/team/team-profile.js +144 -0
  681. package/cli/team/team-projects-coordinator.js +54 -33
  682. package/cli/team/team-projects-format.js +8 -4
  683. package/cli/team/team-projects-memory.js +6 -3
  684. package/cli/team/team-projects.js +2 -1
  685. package/cli/team/team-release-train.js +2 -1
  686. package/cli/team/team-route.js +132 -0
  687. package/cli/team/team-schema-repo.js +48 -0
  688. package/cli/team/team-schema-usage.js +25 -0
  689. package/cli/team/team-schema-vds.js +7 -3
  690. package/cli/team/team-schema.js +24 -4
  691. package/cli/team/team-task-close.js +132 -0
  692. package/cli/team/team-task-guard.js +24 -0
  693. package/cli/team/team-task-handoff.js +122 -0
  694. package/cli/team/team-task-ownership.js +74 -0
  695. package/cli/team/team-task-rules.js +21 -0
  696. package/cli/team/team-task-uncommitted.js +49 -0
  697. package/cli/team/team-telemetry.js +44 -32
  698. package/cli/team/team-tokens.js +4 -1
  699. package/cli/team/team-triage-generate.js +212 -0
  700. package/cli/team/team-triage-log.js +11 -0
  701. package/cli/team/team-triage-verify.js +153 -0
  702. package/cli/team/team-triage.js +92 -339
  703. package/cli/team/team-vds.js +2 -1
  704. package/cli/team/usage-compute.js +70 -0
  705. package/cli/team/usage-pricing.js +62 -0
  706. package/cli/team/usage-reader.js +106 -0
  707. package/cli/team/usage-render.js +72 -0
  708. package/cli/team/usage-store.js +56 -0
  709. package/cli/team/usage-transcript.js +127 -0
  710. package/cli/team/write-lock-guard.js +48 -0
  711. package/cli/telemetry/call-ledger.js +264 -0
  712. package/cli/telemetry/call-schema.js +53 -0
  713. package/cli/telemetry/savings-dbs.js +111 -0
  714. package/cli/telemetry/savings-render.js +68 -0
  715. package/cli/telemetry/savings-report.js +101 -0
  716. package/cli/telemetry/savings-routing.js +66 -0
  717. package/cli/telemetry/savings-tooling.js +140 -0
  718. package/cli/terminal.js +38 -40
  719. package/cli/tesseract-manifesto.js +2 -17
  720. package/cli/tesseract.js +8 -21
  721. package/cli/test-audit.js +218 -0
  722. package/cli/test-changes.js +60 -0
  723. package/cli/test-command.js +201 -0
  724. package/cli/test-depth.js +43 -0
  725. package/cli/test-failures.js +170 -0
  726. package/cli/test-graph-open.js +150 -0
  727. package/cli/test-graph.js +188 -0
  728. package/cli/test-lanes.js +117 -0
  729. package/cli/test-output.js +155 -0
  730. package/cli/test-paths.js +51 -0
  731. package/cli/test-profile.js +64 -0
  732. package/cli/test-report.js +112 -0
  733. package/cli/test-run.js +53 -0
  734. package/cli/test-scope.js +55 -0
  735. package/cli/test-select.js +107 -0
  736. package/cli/test-slots.js +276 -0
  737. package/cli/test-workers.js +45 -0
  738. package/cli/test-workspace.js +83 -0
  739. package/cli/theme.js +6 -3
  740. package/cli/timers.js +6 -0
  741. package/cli/trend.js +10 -5
  742. package/cli/tsconfig-shape.js +20 -0
  743. package/cli/tty-banner.js +11 -0
  744. package/cli/typecheck-audit.js +146 -0
  745. package/cli/typecheck-command.js +94 -0
  746. package/cli/typecheck-sandbox.js +93 -0
  747. package/cli/ui-actions-codebase.js +20 -7
  748. package/cli/ui-actions-helpers.js +9 -0
  749. package/cli/ui-actions-studio.js +11 -7
  750. package/cli/ui-actions-tasks.js +3 -2
  751. package/cli/ui-actions-vds.js +2 -1
  752. package/cli/ui-actions.js +4 -2
  753. package/cli/ui-attention.js +28 -12
  754. package/cli/ui-auth-hosts.js +46 -0
  755. package/cli/ui-auth.js +89 -0
  756. package/cli/ui-client-cert.js +2 -1
  757. package/cli/ui-client-filetree.js +2 -2
  758. package/cli/ui-db-studio.js +23 -33
  759. package/cli/ui-dev-watcher.js +2 -1
  760. package/cli/ui-forum-data.js +45 -46
  761. package/cli/ui-forum-topics.js +6 -3
  762. package/cli/ui-fresh-index.js +11 -0
  763. package/cli/ui-handlers.js +6 -6
  764. package/cli/ui-html.js +6 -12
  765. package/cli/ui-index.html +3 -3
  766. package/cli/ui-route-helpers.js +10 -0
  767. package/cli/ui-server-routes.js +15 -17
  768. package/cli/ui-server.js +49 -50
  769. package/cli/ui-sql-guard.js +76 -0
  770. package/cli/ui-sql-mask.js +61 -0
  771. package/cli/ui-sse.js +5 -4
  772. package/cli/ui-template-filetree.js +4 -4
  773. package/cli/ui-template-kanban.js +4 -7
  774. package/cli/verify-changed.js +43 -0
  775. package/cli/verify-helpers.js +21 -207
  776. package/cli/verify-lint.js +54 -20
  777. package/cli/verify-report.js +117 -0
  778. package/cli/verify-steps.js +71 -0
  779. package/cli/verify.js +111 -372
  780. package/cli/wait-command.js +147 -0
  781. package/cli/workspace-run.js +153 -0
  782. package/cli/workspace.js +147 -0
  783. package/cli/write-append.js +28 -0
  784. package/library/node-js/is-path-inside/entry.json +57 -0
  785. package/library/node-js/is-path-inside/negative/path-scope.js.txt +13 -0
  786. package/library/node-js/is-path-inside/piece.js +15 -0
  787. package/library/node-js/is-path-inside/piece.spec.js +38 -0
  788. package/library/node-js/read-json-or/entry.json +70 -0
  789. package/library/node-js/read-json-or/negative/ratchet.js.txt +14 -0
  790. package/library/node-js/read-json-or/negative/workspace.js.txt +8 -0
  791. package/library/node-js/read-json-or/piece.js +17 -0
  792. package/library/node-js/read-json-or/piece.spec.js +36 -0
  793. package/library/vue-ts/nullable-timer-handle/ambient.d.ts +5 -0
  794. package/library/vue-ts/nullable-timer-handle/entry.json +84 -0
  795. package/library/vue-ts/nullable-timer-handle/negative/const-alias.ts.txt +17 -0
  796. package/library/vue-ts/nullable-timer-handle/negative/inline-guard.ts.txt +15 -0
  797. package/library/vue-ts/nullable-timer-handle/piece.spec.js +48 -0
  798. package/library/vue-ts/nullable-timer-handle/piece.ts +41 -0
  799. package/package.json +9 -3
  800. package/scripts/check-framework-generation.mjs +32 -12
  801. package/scripts/direct-run.mjs +9 -0
  802. package/scripts/pre-commit.sh +48 -74
  803. package/scripts/publish-both.mjs +24 -16
  804. package/scripts/publish-summary.mjs +14 -0
  805. package/scripts/postinstall.mjs +0 -27
@@ -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 (filter.since_id) {
62
+ if (hasCursor) {
54
63
  conditions.push('id > ?');
55
64
  params.push(Number(filter.since_id));
56
65
  }
57
- if (filter.thread_id) {
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
- if (filter.task_id) {
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
- if (filter.event_type) {
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
- if (filter.agent_id) {
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
- const hasConditions = conditions.length > 0;
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
- query += ` ORDER BY id ASC LIMIT ${limit}`;
90
+ const direction = hasCursor ? 'ASC' : 'DESC';
91
+ query += ` ORDER BY id ${direction} LIMIT ${limit}`;
79
92
 
80
- return db.prepare(query).all(...params).map((r) => ({
81
- ...r,
82
- metadata: JSON.parse(r.metadata || '{}')
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
- export const promoteNextWaiter = (db, filePath, pid = 0) => {
11
- const hasDb = Boolean(db);
12
- const hasPath = Boolean(filePath);
13
- const canPromote = hasDb && hasPath;
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
- db.prepare("UPDATE file_lock_queue SET status = 'granted' WHERE id = ?").run(waiter.id);
25
-
26
- const upsertSql = `INSERT INTO file_leases (file_path, locked_by, acquired_at, expires_at, purpose, pid)
27
- VALUES (?, ?, ?, ?, ?, ?)
28
- ON CONFLICT(file_path) DO UPDATE SET locked_by = excluded.locked_by, acquired_at = excluded.acquired_at, expires_at = excluded.expires_at, purpose = excluded.purpose, pid = excluded.pid`;
29
- db.prepare(upsertSql).run(filePath, waiter.agent_id, now, expiresAt, waiter.purpose || 'Promoted from FIFO queue', pid);
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
- export const enqueueWaiter = (db, filePath, cleanId, existingLease, options = {}, now = Date.now()) => {
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, info.lastInsertRowid);
55
- const position = posRow?.pos || 1;
71
+ `).get(filePath, queueId);
72
+ return posRow?.pos || 1;
73
+ };
56
74
 
57
- postFeedEvent(db, {
58
- author_id: cleanId,
59
- event_type: 'lock_queued',
60
- file_path: filePath,
61
- message: `${cleanId} joined FIFO queue at position ${position} for ${filePath}`,
62
- metadata: { queueId: info.lastInsertRowid, position, heldBy: existingLease.locked_by }
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
- queueId: info.lastInsertRowid,
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 leases = db.prepare('SELECT * FROM file_leases').all();
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 leases) {
84
- const isExpired = lease.expires_at <= now;
85
- const isDeadProcess = typeof lease.pid === 'number' && lease.pid > 0 && !isPidAlive(lease.pid);
86
- const shouldClean = isExpired || isDeadProcess;
87
- if (shouldClean) {
88
- db.prepare('DELETE FROM file_leases WHERE file_path = ?').run(lease.file_path);
89
- const reason = isDeadProcess ? `Process #${lease.pid} terminated` : 'TTL expired';
90
- postFeedEvent(db, {
91
- author_id: '@system',
92
- event_type: 'lock_expired',
93
- file_path: lease.file_path,
94
- message: `Lease expired for ${lease.file_path} held by ${lease.locked_by} (${reason})`
95
- });
96
- promoteNextWaiter(db, lease.file_path);
97
- cleaned.push(lease);
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
- const resolveBaseDir = (db, options = {}) => {
15
- if (options.cwd) return options.cwd;
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
- if (typeof db?.location === 'function') {
18
- const loc = db.location();
19
- if (loc && loc !== ':memory:' && loc.includes('.chemx')) {
20
- return path.dirname(path.dirname(path.resolve(loc)));
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 baseDir = resolveBaseDir(db, options);
44
- if (isPathTraversal(filePath, baseDir)) {
45
- return { granted: false, reason: 'path_traversal' };
46
- }
47
- const cleanPath = path.relative(baseDir, resolveSafePath(filePath, baseDir));
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
- return { granted: true, lease: { file_path: cleanPath, locked_by: cleanId, expires_at: expiresAt, pid } };
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
- return enqueueWaiter(db, cleanPath, cleanId, existingLease, options, now);
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 baseDir = resolveBaseDir(db, options);
93
- if (isPathTraversal(filePath, baseDir)) {
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
- return { success: false, reason: 'not_holder' };
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 baseDir = resolveBaseDir(db, options);
130
- if (isPathTraversal(filePath, baseDir)) {
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
- cleanExpiredLeases(db);
136
- const lease = db.prepare('SELECT * FROM file_leases WHERE file_path = ?').get(cleanPath);
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: lease || null, waiters };
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 = normalizeHandle(params.author_id) || '@agent';
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 = 'SELECT id, timestamp, author_id, thread_id, task_id, read_at, message FROM agent_feed WHERE recipient_id = ?';
51
- if (options.since) {
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
- if (options.markRead) {
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