@prestack/thread 0.0.1

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 (248) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/dist/attach.d.ts +1 -0
  3. package/dist/attach.js +1 -0
  4. package/dist/core/actor.d.ts +12 -0
  5. package/dist/core/actor.js +8 -0
  6. package/dist/core/asking.d.ts +180 -0
  7. package/dist/core/asking.js +1109 -0
  8. package/dist/core/attach.d.ts +54 -0
  9. package/dist/core/attach.js +510 -0
  10. package/dist/core/chain.d.ts +16 -0
  11. package/dist/core/chain.js +114 -0
  12. package/dist/core/cli.d.ts +2 -0
  13. package/dist/core/cli.js +1682 -0
  14. package/dist/core/condition.d.ts +52 -0
  15. package/dist/core/condition.js +198 -0
  16. package/dist/core/descend.d.ts +81 -0
  17. package/dist/core/descend.js +879 -0
  18. package/dist/core/finding.d.ts +12 -0
  19. package/dist/core/finding.js +23 -0
  20. package/dist/core/generators/meta.d.ts +2 -0
  21. package/dist/core/generators/meta.js +823 -0
  22. package/dist/core/init.d.ts +15 -0
  23. package/dist/core/init.js +134 -0
  24. package/dist/core/invoked.d.ts +9 -0
  25. package/dist/core/invoked.js +41 -0
  26. package/dist/core/round.d.ts +1 -0
  27. package/dist/core/round.js +57 -0
  28. package/dist/core/rows.d.ts +26 -0
  29. package/dist/core/rows.js +354 -0
  30. package/dist/core/scope.d.ts +6 -0
  31. package/dist/core/scope.js +12 -0
  32. package/dist/core/thread.d.ts +26 -0
  33. package/dist/core/thread.js +5 -0
  34. package/dist/core/tree.d.ts +12 -0
  35. package/dist/core/tree.js +56 -0
  36. package/dist/core/types.d.ts +49 -0
  37. package/dist/core/types.js +311 -0
  38. package/dist/derivation/attach.d.ts +2 -0
  39. package/dist/derivation/attach.js +61 -0
  40. package/dist/derivation/generators/build.d.ts +16 -0
  41. package/dist/derivation/generators/build.js +217 -0
  42. package/dist/derivation/generators/check.d.ts +18 -0
  43. package/dist/derivation/generators/check.js +78 -0
  44. package/dist/derivation/generators/derivation.d.ts +3 -0
  45. package/dist/derivation/generators/derivation.js +273 -0
  46. package/dist/derivation/generators/determinism.d.ts +20 -0
  47. package/dist/derivation/generators/determinism.js +110 -0
  48. package/dist/derivation/generators/release.d.ts +3 -0
  49. package/dist/derivation/generators/release.js +183 -0
  50. package/dist/derivation/makers/release.d.ts +1 -0
  51. package/dist/derivation/makers/release.js +107 -0
  52. package/dist/derivation/release.d.ts +10 -0
  53. package/dist/derivation/release.js +12 -0
  54. package/dist/integrations/attach.d.ts +6 -0
  55. package/dist/integrations/attach.js +177 -0
  56. package/dist/integrations/generators/read.d.ts +1 -0
  57. package/dist/integrations/generators/read.js +92 -0
  58. package/dist/integrations/generators/sdk.d.ts +1 -0
  59. package/dist/integrations/generators/sdk.js +148 -0
  60. package/dist/integrations/generators/secret.d.ts +1 -0
  61. package/dist/integrations/generators/secret.js +233 -0
  62. package/dist/integrations/generators/target.d.ts +1 -0
  63. package/dist/integrations/generators/target.js +765 -0
  64. package/dist/integrations/generators/vault.d.ts +1 -0
  65. package/dist/integrations/generators/vault.js +164 -0
  66. package/dist/integrations/sdk.d.ts +6 -0
  67. package/dist/integrations/sdk.js +151 -0
  68. package/dist/integrations/secret.d.ts +16 -0
  69. package/dist/integrations/secret.js +30 -0
  70. package/dist/integrations/target.d.ts +49 -0
  71. package/dist/integrations/target.js +225 -0
  72. package/dist/integrations/vault.d.ts +12 -0
  73. package/dist/integrations/vault.js +104 -0
  74. package/dist/integrity/attach.d.ts +2 -0
  75. package/dist/integrity/attach.js +213 -0
  76. package/dist/integrity/check-threads.d.ts +10 -0
  77. package/dist/integrity/check-threads.js +153 -0
  78. package/dist/integrity/constraint.d.ts +16 -0
  79. package/dist/integrity/constraint.js +1105 -0
  80. package/dist/integrity/generators/compiler.d.ts +1 -0
  81. package/dist/integrity/generators/compiler.js +130 -0
  82. package/dist/integrity/generators/gate.d.ts +1 -0
  83. package/dist/integrity/generators/gate.js +172 -0
  84. package/dist/integrity/generators/issues.d.ts +1 -0
  85. package/dist/integrity/generators/issues.js +100 -0
  86. package/dist/integrity/generators/publish.d.ts +1 -0
  87. package/dist/integrity/generators/publish.js +186 -0
  88. package/dist/integrity/generators/rule.d.ts +2 -0
  89. package/dist/integrity/generators/rule.js +213 -0
  90. package/dist/integrity/generators/seal.d.ts +4 -0
  91. package/dist/integrity/generators/seal.js +101 -0
  92. package/dist/integrity/generators/trial.d.ts +39 -0
  93. package/dist/integrity/generators/trial.js +254 -0
  94. package/dist/integrity/lint.d.ts +50 -0
  95. package/dist/integrity/lint.js +161 -0
  96. package/dist/integrity/lsp/bin.d.ts +2 -0
  97. package/dist/integrity/lsp/bin.js +35 -0
  98. package/dist/integrity/lsp/diagnostics.d.ts +25 -0
  99. package/dist/integrity/lsp/diagnostics.js +57 -0
  100. package/dist/integrity/lsp/findings.d.ts +20 -0
  101. package/dist/integrity/lsp/findings.js +39 -0
  102. package/dist/integrity/lsp/lint.d.ts +7 -0
  103. package/dist/integrity/lsp/lint.js +95 -0
  104. package/dist/integrity/lsp/protocol.d.ts +20 -0
  105. package/dist/integrity/lsp/protocol.js +57 -0
  106. package/dist/integrity/lsp/server.d.ts +15 -0
  107. package/dist/integrity/lsp/server.js +171 -0
  108. package/dist/integrity/sealed.d.ts +1 -0
  109. package/dist/integrity/sealed.js +56 -0
  110. package/dist/integrity/slopnator.d.ts +90 -0
  111. package/dist/integrity/slopnator.js +267 -0
  112. package/dist/integrity/slopnators/sl_broken_lineage.d.ts +2 -0
  113. package/dist/integrity/slopnators/sl_broken_lineage.js +35 -0
  114. package/dist/integrity/slopnators/sl_declared_elsewhere.d.ts +2 -0
  115. package/dist/integrity/slopnators/sl_declared_elsewhere.js +33 -0
  116. package/dist/integrity/slopnators/sl_derived_drift.d.ts +8 -0
  117. package/dist/integrity/slopnators/sl_derived_drift.js +91 -0
  118. package/dist/integrity/slopnators/sl_hardcoded_style.d.ts +2 -0
  119. package/dist/integrity/slopnators/sl_hardcoded_style.js +46 -0
  120. package/dist/integrity/slopnators/sl_inert_island.d.ts +2 -0
  121. package/dist/integrity/slopnators/sl_inert_island.js +85 -0
  122. package/dist/integrity/slopnators/sl_inert_slopnator.d.ts +2 -0
  123. package/dist/integrity/slopnators/sl_inert_slopnator.js +76 -0
  124. package/dist/integrity/slopnators/sl_missing_row.d.ts +2 -0
  125. package/dist/integrity/slopnators/sl_missing_row.js +180 -0
  126. package/dist/integrity/slopnators/sl_no_barrel.d.ts +2 -0
  127. package/dist/integrity/slopnators/sl_no_barrel.js +41 -0
  128. package/dist/integrity/slopnators/sl_no_child_process.d.ts +2 -0
  129. package/dist/integrity/slopnators/sl_no_child_process.js +61 -0
  130. package/dist/integrity/slopnators/sl_no_comment.d.ts +2 -0
  131. package/dist/integrity/slopnators/sl_no_comment.js +41 -0
  132. package/dist/integrity/slopnators/sl_no_dispatch_bypass.d.ts +2 -0
  133. package/dist/integrity/slopnators/sl_no_dispatch_bypass.js +301 -0
  134. package/dist/integrity/slopnators/sl_no_import_cycle.d.ts +2 -0
  135. package/dist/integrity/slopnators/sl_no_import_cycle.js +42 -0
  136. package/dist/integrity/slopnators/sl_no_such_command.d.ts +2 -0
  137. package/dist/integrity/slopnators/sl_no_such_command.js +55 -0
  138. package/dist/integrity/slopnators/sl_only_takes_a_literal.d.ts +2 -0
  139. package/dist/integrity/slopnators/sl_only_takes_a_literal.js +59 -0
  140. package/dist/integrity/slopnators/sl_secret_literal.d.ts +2 -0
  141. package/dist/integrity/slopnators/sl_secret_literal.js +89 -0
  142. package/dist/integrity/slopnators/sl_stale_path.d.ts +2 -0
  143. package/dist/integrity/slopnators/sl_stale_path.js +92 -0
  144. package/dist/integrity/slopnators/sl_twice_meant.d.ts +2 -0
  145. package/dist/integrity/slopnators/sl_twice_meant.js +30 -0
  146. package/dist/integrity/slopnators/sl_twice_parented.d.ts +2 -0
  147. package/dist/integrity/slopnators/sl_twice_parented.js +34 -0
  148. package/dist/integrity/slopnators/sl_undeclared_crossing.d.ts +2 -0
  149. package/dist/integrity/slopnators/sl_undeclared_crossing.js +78 -0
  150. package/dist/integrity/slopnators/sl_undeclared_destination.d.ts +2 -0
  151. package/dist/integrity/slopnators/sl_undeclared_destination.js +56 -0
  152. package/dist/integrity/slopnators/sl_undeclared_element.d.ts +2 -0
  153. package/dist/integrity/slopnators/sl_undeclared_element.js +34 -0
  154. package/dist/integrity/slopnators/sl_undeclared_node.d.ts +2 -0
  155. package/dist/integrity/slopnators/sl_undeclared_node.js +59 -0
  156. package/dist/integrity/slopnators/sl_undeclared_operation.d.ts +2 -0
  157. package/dist/integrity/slopnators/sl_undeclared_operation.js +120 -0
  158. package/dist/integrity/slopnators/sl_undeclared_variant.d.ts +2 -0
  159. package/dist/integrity/slopnators/sl_undeclared_variant.js +117 -0
  160. package/dist/integrity/slopnators/sl_undescribed.d.ts +2 -0
  161. package/dist/integrity/slopnators/sl_undescribed.js +54 -0
  162. package/dist/integrity/slopnators/sl_ungoverned_island.d.ts +2 -0
  163. package/dist/integrity/slopnators/sl_ungoverned_island.js +58 -0
  164. package/dist/integrity/slopnators/sl_unkept_rows.d.ts +2 -0
  165. package/dist/integrity/slopnators/sl_unkept_rows.js +104 -0
  166. package/dist/integrity/slopnators/sl_unnamed_island.d.ts +2 -0
  167. package/dist/integrity/slopnators/sl_unnamed_island.js +37 -0
  168. package/dist/integrity/slopnators/sl_unpaired_member.d.ts +2 -0
  169. package/dist/integrity/slopnators/sl_unpaired_member.js +114 -0
  170. package/dist/integrity/slopnators/sl_unpaired_slopnator.d.ts +2 -0
  171. package/dist/integrity/slopnators/sl_unpaired_slopnator.js +60 -0
  172. package/dist/integrity/slopnators/sl_unprefixed_member.d.ts +2 -0
  173. package/dist/integrity/slopnators/sl_unprefixed_member.js +126 -0
  174. package/dist/integrity/slopnators/sl_unseamed_reach.d.ts +2 -0
  175. package/dist/integrity/slopnators/sl_unseamed_reach.js +73 -0
  176. package/dist/integrity/slopnators/sl_unthreaded.d.ts +56 -0
  177. package/dist/integrity/slopnators/sl_unthreaded.js +352 -0
  178. package/dist/integrity/slopnators/sl_untranslated_key.d.ts +2 -0
  179. package/dist/integrity/slopnators/sl_untranslated_key.js +157 -0
  180. package/dist/integrity/slopnators/sl_unversioned.d.ts +2 -0
  181. package/dist/integrity/slopnators/sl_unversioned.js +31 -0
  182. package/dist/integrity/sweep.d.ts +24 -0
  183. package/dist/integrity/sweep.js +264 -0
  184. package/dist/knowledge/attach.d.ts +2 -0
  185. package/dist/knowledge/attach.js +339 -0
  186. package/dist/knowledge/generators/docsgen.d.ts +1 -0
  187. package/dist/knowledge/generators/docsgen.js +145 -0
  188. package/dist/knowledge/generators/glossary.d.ts +1 -0
  189. package/dist/knowledge/generators/glossary.js +56 -0
  190. package/dist/knowledge/generators/skills.d.ts +1 -0
  191. package/dist/knowledge/generators/skills.js +222 -0
  192. package/dist/knowledge/glossary.d.ts +6 -0
  193. package/dist/knowledge/glossary.js +1 -0
  194. package/dist/operations/access.d.ts +27 -0
  195. package/dist/operations/access.js +61 -0
  196. package/dist/operations/attach.d.ts +6 -0
  197. package/dist/operations/attach.js +414 -0
  198. package/dist/operations/capabilities.d.ts +37 -0
  199. package/dist/operations/capabilities.js +35 -0
  200. package/dist/operations/cost.d.ts +14 -0
  201. package/dist/operations/cost.js +37 -0
  202. package/dist/operations/dispatch.d.ts +14 -0
  203. package/dist/operations/dispatch.js +327 -0
  204. package/dist/operations/error-results.d.ts +4 -0
  205. package/dist/operations/error-results.js +25 -0
  206. package/dist/operations/errors/catalog.d.ts +58 -0
  207. package/dist/operations/errors/catalog.js +91 -0
  208. package/dist/operations/errors/classes.d.ts +73 -0
  209. package/dist/operations/errors/classes.js +22 -0
  210. package/dist/operations/errors/errors.d.ts +52 -0
  211. package/dist/operations/errors/errors.js +257 -0
  212. package/dist/operations/generators/capability.d.ts +5 -0
  213. package/dist/operations/generators/capability.js +603 -0
  214. package/dist/operations/generators/compile.d.ts +2 -0
  215. package/dist/operations/generators/compile.js +541 -0
  216. package/dist/operations/generators/grant.d.ts +1 -0
  217. package/dist/operations/generators/grant.js +267 -0
  218. package/dist/operations/generators/operation.d.ts +1 -0
  219. package/dist/operations/generators/operation.js +811 -0
  220. package/dist/operations/generators/permits.d.ts +1 -0
  221. package/dist/operations/generators/permits.js +196 -0
  222. package/dist/operations/generators/seed.d.ts +20 -0
  223. package/dist/operations/generators/seed.js +285 -0
  224. package/dist/operations/generators/trial.d.ts +53 -0
  225. package/dist/operations/generators/trial.js +376 -0
  226. package/dist/operations/http.d.ts +30 -0
  227. package/dist/operations/http.js +121 -0
  228. package/dist/operations/makers/capability.d.ts +1 -0
  229. package/dist/operations/makers/capability.js +117 -0
  230. package/dist/operations/makers/trial.d.ts +1 -0
  231. package/dist/operations/makers/trial.js +413 -0
  232. package/dist/operations/memory.d.ts +104 -0
  233. package/dist/operations/memory.js +365 -0
  234. package/dist/operations/metering.d.ts +55 -0
  235. package/dist/operations/metering.js +247 -0
  236. package/dist/operations/nodes.d.ts +35 -0
  237. package/dist/operations/nodes.js +15 -0
  238. package/dist/operations/operations.d.ts +18 -0
  239. package/dist/operations/operations.js +5 -0
  240. package/dist/operations/queue-policy.d.ts +3 -0
  241. package/dist/operations/queue-policy.js +8 -0
  242. package/dist/operations/result.d.ts +19 -0
  243. package/dist/operations/result.js +10 -0
  244. package/dist/operations/seams.d.ts +239 -0
  245. package/dist/operations/seams.js +45 -0
  246. package/dist/operations/store.d.ts +14 -0
  247. package/dist/operations/store.js +1 -0
  248. package/package.json +16 -0
@@ -0,0 +1,161 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { Linter } from 'eslint';
3
+ import tseslint from 'typescript-eslint';
4
+ import { THING } from "../core/scope.js";
5
+ import { CONSTRAINTS } from "./constraint.js";
6
+ import { NOT_WRITTEN, governs, reads, visits, load, ROOT, rules as appRules, slopnatorName, } from "./slopnator.js";
7
+ const PROBE = {
8
+ filename: 'probe.ts',
9
+ text: '',
10
+ ancestors: () => [],
11
+ comments: () => [],
12
+ };
13
+ export function ruleMessage(rule) {
14
+ const cites = (rule.cites ?? []).map((cite) => `${cite.on} — ${cite.said}`).join(' · ');
15
+ return cites === '' ? `${rule.bans} — ${rule.because}. Instead: ${rule.instead}.` : `${rule.bans} — ${rule.because}. Instead: ${rule.instead}. (${cites})`;
16
+ }
17
+ const oneLine = (text) => text.replace(/\s*\n\s*/g, ' ').trim();
18
+ export function agentInstructions() {
19
+ return [...CONSTRAINTS]
20
+ .sort((one, two) => (one.id < two.id ? -1 : one.id > two.id ? 1 : 0))
21
+ .map((rule) => `${oneLine(rule.instead)} — never ${oneLine(rule.bans)}. Wrong: \`${oneLine(rule.wrong)}\`. Right: \`${oneLine(rule.right)}\`. [${rule.id}]`);
22
+ }
23
+ export function defineRule(rule, slopnators) {
24
+ const expected = slopnatorName(rule.id);
25
+ if (rule.where.paths.length === 0) {
26
+ throw new Error(`rule "${rule.id}" names no path, so it governs nothing. A rule that cannot fire is indistinguishable from a rule being obeyed.`);
27
+ }
28
+ const found = slopnators.get(expected);
29
+ if (found === undefined) {
30
+ throw new Error(`"${rule.id}" names "${expected}", and no such file is beside the tree or shipped with the package. ` +
31
+ 'One that cannot run reads as enforced, reports nothing, and is indistinguishable from one being obeyed. ' +
32
+ 'Seed it.');
33
+ }
34
+ return rule;
35
+ }
36
+ export function defineRules(slopnators) {
37
+ const seen = new Set();
38
+ for (const rule of GOVERNING) {
39
+ if (seen.has(rule.id))
40
+ throw new Error(`rule "${rule.id}" is declared twice in packages/core/src/integrity/constraint.ts`);
41
+ seen.add(rule.id);
42
+ defineRule(rule, slopnators);
43
+ }
44
+ return GOVERNING;
45
+ }
46
+ export function ruleRule(rule, slopnators) {
47
+ const slopnator = slopnators.get(slopnatorName(rule.id));
48
+ return {
49
+ meta: {
50
+ type: 'problem',
51
+ docs: { description: rule.bans },
52
+ schema: [],
53
+ ...(slopnator !== undefined && reads(slopnator) && slopnator.fixes !== undefined ? { fixable: 'code' } : {}),
54
+ messages: { broken: `${ruleMessage(rule)} Found: {{found}}.` },
55
+ },
56
+ create(context) {
57
+ if (!governs(rule, context.filename))
58
+ return {};
59
+ const found = slopnator;
60
+ if (found === undefined || !visits(found))
61
+ return {};
62
+ const visit = (node) => {
63
+ const at = {
64
+ filename: context.filename,
65
+ text: context.sourceCode.getText(),
66
+ ancestors: () => context.sourceCode.getAncestors(node),
67
+ comments: () => context.sourceCode.getAllComments().map((comment) => ({
68
+ value: comment.value,
69
+ range: (comment.range ?? [0, 0]),
70
+ })),
71
+ };
72
+ const held = found.holds(node, at);
73
+ if (held === false || held === NOT_WRITTEN)
74
+ return;
75
+ const mend = found.fixes;
76
+ const mended = mend === undefined ? undefined : mend(node, at);
77
+ context.report({
78
+ node: node,
79
+ messageId: 'broken',
80
+ data: { found: typeof held === 'string' ? held : slopnatorName(rule.id) },
81
+ ...(mended === undefined ? {} : { fix: (fixer) => fixer.replaceTextRange([0, at.text.length], mended) }),
82
+ });
83
+ };
84
+ return Object.fromEntries(found.on.map((type) => [type, visit]));
85
+ },
86
+ };
87
+ }
88
+ export function ruleRules(slopnators) {
89
+ return Object.fromEntries(GOVERNING.map((rule) => [rule.id, ruleRule(rule, slopnators)]));
90
+ }
91
+ export function ruleSeverities() {
92
+ return Object.fromEntries(GOVERNING.map((rule) => [`lint/${rule.id}`, rule.severity === 'warning' ? 'warn' : 'error']));
93
+ }
94
+ const GOVERNING = [...CONSTRAINTS, ...((await appRules()) ?? [])];
95
+ const SLOPNATORS = await load(ROOT, GOVERNING);
96
+ defineRules(SLOPNATORS);
97
+ export const plugin = { rules: ruleRules(SLOPNATORS) };
98
+ export default plugin;
99
+ function ignored() {
100
+ try {
101
+ return execFileSync('git', ['ls-files', '--others', '--ignored', '--exclude-standard', '--directory'], {
102
+ cwd: ROOT,
103
+ encoding: 'utf8',
104
+ stdio: ['ignore', 'pipe', 'ignore'],
105
+ })
106
+ .split('\n')
107
+ .filter(Boolean)
108
+ .map((one) => (one.endsWith('/') ? one + '**' : one));
109
+ }
110
+ catch {
111
+ return [];
112
+ }
113
+ }
114
+ export const rules = {
115
+ files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.mjs', '**/*.js'],
116
+ ignores: ignored(),
117
+ languageOptions: {
118
+ parser: tseslint.parser,
119
+ parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true } },
120
+ },
121
+ plugins: { lint: plugin },
122
+ rules: ruleSeverities(),
123
+ };
124
+ export function ruleDeclarations() {
125
+ return [...CONSTRAINTS]
126
+ .sort((one, two) => (one.id < two.id ? -1 : one.id > two.id ? 1 : 0))
127
+ .map((rule) => ({ ...rule, name: rule.id }));
128
+ }
129
+ export function theRules() {
130
+ const linter = new Linter();
131
+ const declared = new Map(ruleDeclarations().map((one) => [one.name, one]));
132
+ const config = [
133
+ {
134
+ files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
135
+ linterOptions: { noInlineConfig: true },
136
+ languageOptions: {
137
+ parser: tseslint.parser,
138
+ parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true } },
139
+ },
140
+ plugins: { [`@${THING}`]: plugin },
141
+ rules: Object.fromEntries(GOVERNING.map((rule) => [`@${THING}/${rule.id}`, rule.severity === 'warning' ? 'warn' : 'error'])),
142
+ },
143
+ ];
144
+ return (file, source) => linter
145
+ .verify(source, config, file)
146
+ .filter((message) => message.ruleId != null)
147
+ .map((message) => {
148
+ const rule = String(message.ruleId).replace(`@${THING}/`, '');
149
+ const found = declared.get(rule);
150
+ return {
151
+ file,
152
+ line: message.line,
153
+ column: message.column,
154
+ rule,
155
+ message: message.message,
156
+ ...(found === undefined
157
+ ? {}
158
+ : { bans: found.bans, instead: found.instead, wrong: found.wrong, right: found.right }),
159
+ };
160
+ });
161
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env node
2
+ import { cwd, exit, stderr, stdin, stdout } from 'node:process';
3
+ import { repoSource } from "./diagnostics.js";
4
+ import { encode, MessageBuffer } from "./protocol.js";
5
+ import { LspServer } from "./server.js";
6
+ const server = new LspServer({
7
+ root: cwd(),
8
+ sourceFor: repoSource,
9
+ send: (message) => stdout.write(encode(message)),
10
+ });
11
+ const buffer = new MessageBuffer();
12
+ let queue = Promise.resolve();
13
+ const fatal = (error) => {
14
+ stderr.write(`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
15
+ exit(1);
16
+ };
17
+ stdin.on('data', (chunk) => {
18
+ let messages;
19
+ try {
20
+ messages = buffer.push(chunk);
21
+ }
22
+ catch (error) {
23
+ fatal(error);
24
+ return;
25
+ }
26
+ for (const message of messages) {
27
+ queue = queue.then(async () => {
28
+ await server.handle(message);
29
+ if (server.exited)
30
+ exit(server.exitCode);
31
+ });
32
+ queue.catch(fatal);
33
+ }
34
+ });
35
+ stdin.on('end', () => exit(server.exitCode));
@@ -0,0 +1,25 @@
1
+ import { type Finding } from './findings.ts';
2
+ export interface DiagnosticSource {
3
+ lint(relative: string, text: string): Promise<readonly Finding[]>;
4
+ read(relative: string): string | null;
5
+ }
6
+ export declare function repoSource(root: string): DiagnosticSource;
7
+ export declare const ERROR = 1;
8
+ export declare const WARNING = 2;
9
+ export interface Position {
10
+ readonly line: number;
11
+ readonly character: number;
12
+ }
13
+ export interface Diagnostic {
14
+ readonly range: {
15
+ readonly start: Position;
16
+ readonly end: Position;
17
+ };
18
+ readonly severity: typeof ERROR | typeof WARNING;
19
+ readonly source: string;
20
+ readonly code: string;
21
+ readonly message: string;
22
+ }
23
+ export declare const SOURCE_NAME = "thread";
24
+ export declare function toDiagnostic(finding: Finding, text: string): Diagnostic;
25
+ export declare function report(source: DiagnosticSource, open: ReadonlyMap<string, string>, previous?: ReadonlySet<string>): Promise<ReadonlyMap<string, readonly Diagnostic[]>>;
@@ -0,0 +1,57 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { detailOf, rangeOf } from "./findings.js";
4
+ import { lintFindings } from "./lint.js";
5
+ export function repoSource(root) {
6
+ return {
7
+ lint: (relative, text) => lintFindings(root, relative, text),
8
+ read: (relative) => {
9
+ try {
10
+ return readFileSync(join(root, relative), 'utf8');
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ },
16
+ };
17
+ }
18
+ export const ERROR = 1;
19
+ export const WARNING = 2;
20
+ export const SOURCE_NAME = 'thread';
21
+ export function toDiagnostic(finding, text) {
22
+ const at = rangeOf(finding, text);
23
+ return {
24
+ range: {
25
+ start: { line: at.line - 1, character: at.column - 1 },
26
+ end: { line: (at.endLine ?? at.line) - 1, character: (at.endColumn ?? at.column) - 1 },
27
+ },
28
+ severity: finding.severity === 'error' ? ERROR : WARNING,
29
+ source: SOURCE_NAME,
30
+ code: finding.check,
31
+ message: detailOf(finding),
32
+ };
33
+ }
34
+ export async function report(source, open, previous = new Set()) {
35
+ const findings = [];
36
+ for (const [relative, text] of open) {
37
+ findings.push(...(await source.lint(relative, text)));
38
+ }
39
+ const byFile = new Map();
40
+ for (const finding of findings) {
41
+ const bucket = byFile.get(finding.file);
42
+ if (bucket === undefined)
43
+ byFile.set(finding.file, [finding]);
44
+ else
45
+ bucket.push(finding);
46
+ }
47
+ const published = new Map();
48
+ for (const [relative, group] of byFile) {
49
+ const text = open.get(relative) ?? source.read(relative) ?? '';
50
+ published.set(relative, group.map((finding) => toDiagnostic(finding, text)));
51
+ }
52
+ for (const relative of [...previous, ...open.keys()]) {
53
+ if (!published.has(relative))
54
+ published.set(relative, []);
55
+ }
56
+ return published;
57
+ }
@@ -0,0 +1,20 @@
1
+ export interface At {
2
+ readonly line: number;
3
+ readonly column: number;
4
+ readonly endLine?: number;
5
+ readonly endColumn?: number;
6
+ }
7
+ export interface Finding {
8
+ readonly severity: 'error' | 'warning';
9
+ readonly check: string;
10
+ readonly detail: string;
11
+ readonly file: string;
12
+ readonly at?: At;
13
+ readonly anchor?: string;
14
+ readonly suggestions?: readonly string[];
15
+ }
16
+ export declare function detailOf(finding: Finding): string;
17
+ export declare function anchorIn(detail: string): string | undefined;
18
+ export declare function locate(text: string, anchor: string): At | undefined;
19
+ export declare function headOf(text: string): At;
20
+ export declare function rangeOf(finding: Finding, text: string): At;
@@ -0,0 +1,39 @@
1
+ export function detailOf(finding) {
2
+ const offered = finding.suggestions ?? [];
3
+ return offered.length === 0 ? finding.detail : `${finding.detail} ${offered.join(' ')}`;
4
+ }
5
+ const DECLARED_ID = /\b[a-z]{2,6}_[a-z][a-z0-9_]*\b/g;
6
+ export function anchorIn(detail) {
7
+ const candidates = [
8
+ ...(detail.match(DECLARED_ID) ?? []),
9
+ ...[...detail.matchAll(/"([a-z][a-z0-9_]*(?:\.[a-z][a-z0-9_*]+)+)"/g)].map((m) => m[1]),
10
+ ];
11
+ if (candidates.length === 0)
12
+ return undefined;
13
+ return candidates.reduce((longest, one) => (one.length > longest.length ? one : longest));
14
+ }
15
+ export function locate(text, anchor) {
16
+ const lines = text.split('\n');
17
+ for (const [index, line] of lines.entries()) {
18
+ const column = line.indexOf(anchor);
19
+ if (column === -1)
20
+ continue;
21
+ return {
22
+ line: index + 1,
23
+ column: column + 1,
24
+ endLine: index + 1,
25
+ endColumn: column + 1 + anchor.length,
26
+ };
27
+ }
28
+ return undefined;
29
+ }
30
+ export function headOf(text) {
31
+ const first = text.split('\n')[0] ?? '';
32
+ return { line: 1, column: 1, endLine: 1, endColumn: Math.max(first.length, 1) + 1 };
33
+ }
34
+ export function rangeOf(finding, text) {
35
+ if (finding.at !== undefined)
36
+ return finding.at;
37
+ const anchor = finding.anchor ?? anchorIn(finding.detail);
38
+ return (anchor === undefined ? undefined : locate(text, anchor)) ?? headOf(text);
39
+ }
@@ -0,0 +1,7 @@
1
+ import type { Finding } from './findings.ts';
2
+ export declare const isLintable: (relative: string) => boolean;
3
+ export declare const LINT_CONFIG = "eslint.config.js";
4
+ export declare class LintUnavailableError extends Error {
5
+ constructor(detail: string);
6
+ }
7
+ export declare function lintFindings(root: string, relative: string, text: string): Promise<readonly Finding[]>;
@@ -0,0 +1,95 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { extname, join } from 'node:path';
4
+ import { theRules } from "../lint.js";
5
+ const LINTABLE = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.mjs', '.cjs', '.jsx']);
6
+ export const isLintable = (relative) => LINTABLE.has(extname(relative));
7
+ export const LINT_CONFIG = 'eslint.config.js';
8
+ export class LintUnavailableError extends Error {
9
+ constructor(detail) {
10
+ super(`ESLint could not be run over this repository: ${detail}. This server reports what the build ` +
11
+ 'computes, so a lint pass that answered "no problems" because it never ran would be ' +
12
+ 'indistinguishable from a clean file — which is the one thing it must never be.');
13
+ this.name = 'LintUnavailableError';
14
+ }
15
+ }
16
+ function binaryIn(root) {
17
+ const local = join(root, 'node_modules', '.bin', 'eslint');
18
+ return existsSync(local) ? local : undefined;
19
+ }
20
+ async function ruleFindings(relative, text) {
21
+ const rules = theRules();
22
+ return rules(relative, text).map((violation) => ({
23
+ severity: 'error',
24
+ check: violation.rule,
25
+ detail: violation.bans ?? violation.message,
26
+ file: relative,
27
+ at: { line: violation.line, column: violation.column },
28
+ suggestions: [
29
+ ...(violation.instead === undefined ? [] : [violation.instead]),
30
+ ...(violation.right === undefined ? [] : [`Write: ${violation.right}`]),
31
+ ],
32
+ }));
33
+ }
34
+ const suggestionsOf = (message) => (message.suggestions ?? []).map((one) => one.desc).filter((desc) => desc !== undefined);
35
+ export async function lintFindings(root, relative, text) {
36
+ if (!isLintable(relative))
37
+ return [];
38
+ if (!existsSync(join(root, LINT_CONFIG)))
39
+ return await ruleFindings(relative, text);
40
+ const binary = binaryIn(root);
41
+ if (binary === undefined) {
42
+ return [
43
+ {
44
+ severity: 'error',
45
+ check: 'lint:unavailable',
46
+ detail: `${LINT_CONFIG} is here and node_modules/.bin/eslint is not, so none of this repository's ` +
47
+ 'rules are being checked while you type. Install the dev dependencies.',
48
+ file: LINT_CONFIG,
49
+ },
50
+ ];
51
+ }
52
+ const report = await runEslint(binary, root, relative, text);
53
+ return report.flatMap((file) => (file.messages ?? []).map((message) => ({
54
+ severity: message.severity === 2 ? 'error' : 'warning',
55
+ check: message.ruleId ?? 'lint',
56
+ detail: message.message,
57
+ file: relative,
58
+ at: {
59
+ line: message.line ?? 1,
60
+ column: message.column ?? 1,
61
+ ...(message.endLine === undefined ? {} : { endLine: message.endLine }),
62
+ ...(message.endColumn === undefined ? {} : { endColumn: message.endColumn }),
63
+ },
64
+ ...(suggestionsOf(message).length === 0 ? {} : { suggestions: suggestionsOf(message) }),
65
+ })));
66
+ }
67
+ function runEslint(binary, root, relative, text) {
68
+ return new Promise((resolve, reject) => {
69
+ const child = spawn(binary, ['--format', 'json', '--no-warn-ignored', '--stdin', '--stdin-filename', relative], {
70
+ cwd: root,
71
+ shell: false,
72
+ });
73
+ let out = '';
74
+ let err = '';
75
+ child.stdout.on('data', (chunk) => (out += chunk.toString()));
76
+ child.stderr.on('data', (chunk) => (err += chunk.toString()));
77
+ child.on('error', (error) => reject(new LintUnavailableError(error.message)));
78
+ child.on('close', () => {
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(out);
82
+ }
83
+ catch {
84
+ reject(new LintUnavailableError(err.trim() === '' ? `it wrote ${out.length} bytes of non-JSON` : err.trim()));
85
+ return;
86
+ }
87
+ if (!Array.isArray(parsed)) {
88
+ reject(new LintUnavailableError('its JSON report was not an array of files'));
89
+ return;
90
+ }
91
+ resolve(parsed);
92
+ });
93
+ child.stdin.end(text);
94
+ });
95
+ }
@@ -0,0 +1,20 @@
1
+ export interface RpcMessage {
2
+ readonly jsonrpc: '2.0';
3
+ readonly id?: number | string | null;
4
+ readonly method?: string;
5
+ readonly params?: unknown;
6
+ readonly result?: unknown;
7
+ readonly error?: {
8
+ readonly code: number;
9
+ readonly message: string;
10
+ };
11
+ }
12
+ export declare const METHOD_NOT_FOUND = -32601;
13
+ export declare function encode(message: RpcMessage): Buffer;
14
+ export declare class MalformedMessageError extends Error {
15
+ constructor(detail: string);
16
+ }
17
+ export declare class MessageBuffer {
18
+ #private;
19
+ push(chunk: Buffer): readonly RpcMessage[];
20
+ }
@@ -0,0 +1,57 @@
1
+ const HEADER_END = '\r\n\r\n';
2
+ export const METHOD_NOT_FOUND = -32601;
3
+ export function encode(message) {
4
+ const body = Buffer.from(JSON.stringify(message), 'utf8');
5
+ return Buffer.concat([Buffer.from(`Content-Length: ${body.length}${HEADER_END}`, 'ascii'), body]);
6
+ }
7
+ export class MalformedMessageError extends Error {
8
+ constructor(detail) {
9
+ super(`the editor sent something this server cannot parse: ${detail}. ` +
10
+ 'A language server that skipped it would answer the next request against a stream it no ' +
11
+ 'longer understands, which reads as a file with no problems.');
12
+ this.name = 'MalformedMessageError';
13
+ }
14
+ }
15
+ export class MessageBuffer {
16
+ #pending = Buffer.alloc(0);
17
+ push(chunk) {
18
+ this.#pending = Buffer.concat([this.#pending, chunk]);
19
+ const found = [];
20
+ for (;;) {
21
+ const split = this.#pending.indexOf(HEADER_END, 0, 'ascii');
22
+ if (split === -1)
23
+ return found;
24
+ const header = this.#pending.subarray(0, split).toString('ascii');
25
+ const length = contentLength(header);
26
+ const start = split + HEADER_END.length;
27
+ if (this.#pending.length < start + length)
28
+ return found;
29
+ const body = this.#pending.subarray(start, start + length).toString('utf8');
30
+ this.#pending = this.#pending.subarray(start + length);
31
+ let parsed;
32
+ try {
33
+ parsed = JSON.parse(body);
34
+ }
35
+ catch {
36
+ throw new MalformedMessageError(`a ${length}-byte body that is not JSON`);
37
+ }
38
+ if (typeof parsed !== 'object' || parsed === null) {
39
+ throw new MalformedMessageError(`a ${length}-byte body that is not a JSON object`);
40
+ }
41
+ found.push(parsed);
42
+ }
43
+ }
44
+ }
45
+ function contentLength(header) {
46
+ for (const line of header.split('\r\n')) {
47
+ const [name, value] = line.split(':', 2);
48
+ if (name?.trim().toLowerCase() !== 'content-length')
49
+ continue;
50
+ const length = Number.parseInt((value ?? '').trim(), 10);
51
+ if (!Number.isInteger(length) || length < 0) {
52
+ throw new MalformedMessageError(`Content-Length: ${(value ?? '').trim()}`);
53
+ }
54
+ return length;
55
+ }
56
+ throw new MalformedMessageError(`a header block with no Content-Length: ${JSON.stringify(header)}`);
57
+ }
@@ -0,0 +1,15 @@
1
+ import { type DiagnosticSource } from './diagnostics.ts';
2
+ import { type RpcMessage } from './protocol.ts';
3
+ export interface ServerOptions {
4
+ readonly root: string;
5
+ readonly sourceFor: (root: string) => DiagnosticSource;
6
+ send(message: RpcMessage): void;
7
+ settle?(): Promise<void>;
8
+ }
9
+ export declare class LspServer {
10
+ #private;
11
+ constructor(options: ServerOptions);
12
+ get exited(): boolean;
13
+ get exitCode(): number;
14
+ handle(message: RpcMessage): Promise<void>;
15
+ }