@cachly-dev/mcp-server 0.10.129 → 0.10.131

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 (223) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +1 -7
  3. package/dist/packages/telegram-notify/client.js +2 -75
  4. package/dist/src/ambient-cli.js +7 -253
  5. package/dist/src/ambient-deps.js +1 -55
  6. package/dist/src/ambient-hooks.js +5 -278
  7. package/dist/src/ambient-ledger.js +6 -95
  8. package/dist/src/ambient-memory.js +1 -67
  9. package/dist/src/ambient-recall.js +1 -221
  10. package/dist/src/aussetzer.js +1 -225
  11. package/dist/src/auth.js +9 -140
  12. package/dist/src/autor-abzeichen.js +1 -56
  13. package/dist/src/autor.js +1 -136
  14. package/dist/src/bauabdruck.js +1 -93
  15. package/dist/src/bedeutung.js +2 -323
  16. package/dist/src/bench/cachly-bench.js +4 -249
  17. package/dist/src/bench/external-corpus.js +4 -135
  18. package/dist/src/bench/fixtures.js +1 -225
  19. package/dist/src/brain-export.js +16 -119
  20. package/dist/src/brain-watch-hook.js +8 -86
  21. package/dist/src/cachly-url.js +1 -21
  22. package/dist/src/ckg.js +1 -108
  23. package/dist/src/cls-hook.js +8 -94
  24. package/dist/src/confidence.js +1 -50
  25. package/dist/src/credentials.js +1 -85
  26. package/dist/src/deutsch.js +1 -223
  27. package/dist/src/doctor.js +2 -248
  28. package/dist/src/editor.js +1 -63
  29. package/dist/src/eingaenge-nachruesten.js +1 -290
  30. package/dist/src/eingaenge.js +2 -260
  31. package/dist/src/embeddings.js +13 -223
  32. package/dist/src/feldreparatur.js +3 -113
  33. package/dist/src/first-contact.js +4 -108
  34. package/dist/src/first-recall-proof.js +4 -41
  35. package/dist/src/funnel-milestones.js +2 -48
  36. package/dist/src/geliefert.js +1 -114
  37. package/dist/src/handlers/advanced.js +38 -537
  38. package/dist/src/handlers/brain.js +69 -4397
  39. package/dist/src/handlers/cache.js +59 -638
  40. package/dist/src/handlers/context.js +17 -182
  41. package/dist/src/handlers/fedbrain.js +40 -1436
  42. package/dist/src/handlers/instances.js +16 -201
  43. package/dist/src/handlers/roadmap.js +13 -175
  44. package/dist/src/handlers/share.js +19 -530
  45. package/dist/src/handlers/syndicate.js +38 -904
  46. package/dist/src/handlers/team.js +89 -1865
  47. package/dist/src/handlers/viz.js +3 -148
  48. package/dist/src/index.js +321 -4281
  49. package/dist/src/interferenz.js +1 -141
  50. package/dist/src/kostprobe.js +7 -125
  51. package/dist/src/lesson-preview.js +2 -133
  52. package/dist/src/namespace.js +1 -128
  53. package/dist/src/notifier.js +1 -6
  54. package/dist/src/rangfolge-stellschrauben.js +1 -0
  55. package/dist/src/rangfolge.js +1 -249
  56. package/dist/src/raumkorrektur.js +1 -197
  57. package/dist/src/recall-tiefe.js +1 -42
  58. package/dist/src/rerank.js +1 -154
  59. package/dist/src/resonanz.js +1 -140
  60. package/dist/src/search.js +1 -2104
  61. package/dist/src/seltenheitsbestand.js +1 -92
  62. package/dist/src/speicherlage.js +1 -84
  63. package/dist/src/spuren.js +1 -150
  64. package/dist/src/starter-corpus.js +1 -165
  65. package/dist/src/telemetry-types.js +0 -13
  66. package/dist/src/tools.js +10 -2465
  67. package/dist/src/toolspecs.js +1 -121
  68. package/dist/src/upgrade-nudge.js +1 -29
  69. package/dist/src/utils.js +1 -89
  70. package/dist/src/versuch.js +1 -264
  71. package/dist/src/vorspann.js +1 -146
  72. package/dist/src/wartung/vektoren-verdichten.js +1 -200
  73. package/dist/src/werkzeug-nutzung.js +1 -72
  74. package/dist/src/wertbeitrag.js +1 -203
  75. package/dist/src/wirkungsspur.js +1 -0
  76. package/glama.json +1 -1
  77. package/package.json +8 -7
  78. package/server.json +3 -3
  79. package/dist/index.d.ts +0 -1
  80. package/dist/index.js.map +0 -1
  81. package/dist/packages/telegram-notify/client.d.ts +0 -19
  82. package/dist/packages/telegram-notify/client.js.map +0 -1
  83. package/dist/src/ambient-cli.d.ts +0 -101
  84. package/dist/src/ambient-cli.js.map +0 -1
  85. package/dist/src/ambient-deps.d.ts +0 -26
  86. package/dist/src/ambient-deps.js.map +0 -1
  87. package/dist/src/ambient-hooks.d.ts +0 -137
  88. package/dist/src/ambient-hooks.js.map +0 -1
  89. package/dist/src/ambient-ledger.d.ts +0 -19
  90. package/dist/src/ambient-ledger.js.map +0 -1
  91. package/dist/src/ambient-memory.d.ts +0 -12
  92. package/dist/src/ambient-memory.js.map +0 -1
  93. package/dist/src/ambient-recall.d.ts +0 -127
  94. package/dist/src/ambient-recall.js.map +0 -1
  95. package/dist/src/aussetzer.d.ts +0 -156
  96. package/dist/src/aussetzer.js.map +0 -1
  97. package/dist/src/auth.d.ts +0 -43
  98. package/dist/src/auth.js.map +0 -1
  99. package/dist/src/autor-abzeichen.d.ts +0 -41
  100. package/dist/src/autor-abzeichen.js.map +0 -1
  101. package/dist/src/autor.d.ts +0 -101
  102. package/dist/src/autor.js.map +0 -1
  103. package/dist/src/bauabdruck-daten.generiert.json +0 -5
  104. package/dist/src/bauabdruck.d.ts +0 -38
  105. package/dist/src/bauabdruck.js.map +0 -1
  106. package/dist/src/bedeutung.d.ts +0 -168
  107. package/dist/src/bedeutung.js.map +0 -1
  108. package/dist/src/bench/cachly-bench.d.ts +0 -61
  109. package/dist/src/bench/cachly-bench.js.map +0 -1
  110. package/dist/src/bench/external-corpus.d.ts +0 -44
  111. package/dist/src/bench/external-corpus.js.map +0 -1
  112. package/dist/src/bench/fixtures.d.ts +0 -37
  113. package/dist/src/bench/fixtures.js.map +0 -1
  114. package/dist/src/brain-export.d.ts +0 -64
  115. package/dist/src/brain-export.js.map +0 -1
  116. package/dist/src/brain-watch-hook.d.ts +0 -21
  117. package/dist/src/brain-watch-hook.js.map +0 -1
  118. package/dist/src/cachly-url.d.ts +0 -68
  119. package/dist/src/cachly-url.js.map +0 -1
  120. package/dist/src/ckg.d.ts +0 -54
  121. package/dist/src/ckg.js.map +0 -1
  122. package/dist/src/cls-hook.d.ts +0 -15
  123. package/dist/src/cls-hook.js.map +0 -1
  124. package/dist/src/confidence.d.ts +0 -19
  125. package/dist/src/confidence.js.map +0 -1
  126. package/dist/src/credentials.d.ts +0 -37
  127. package/dist/src/credentials.js.map +0 -1
  128. package/dist/src/deutsch.d.ts +0 -98
  129. package/dist/src/deutsch.js.map +0 -1
  130. package/dist/src/doctor.d.ts +0 -47
  131. package/dist/src/doctor.js.map +0 -1
  132. package/dist/src/editor.d.ts +0 -9
  133. package/dist/src/editor.js.map +0 -1
  134. package/dist/src/eingaenge-nachruesten.d.ts +0 -41
  135. package/dist/src/eingaenge-nachruesten.js.map +0 -1
  136. package/dist/src/eingaenge.d.ts +0 -159
  137. package/dist/src/eingaenge.js.map +0 -1
  138. package/dist/src/embeddings.d.ts +0 -48
  139. package/dist/src/embeddings.js.map +0 -1
  140. package/dist/src/feldreparatur.d.ts +0 -58
  141. package/dist/src/feldreparatur.js.map +0 -1
  142. package/dist/src/first-contact.d.ts +0 -56
  143. package/dist/src/first-contact.js.map +0 -1
  144. package/dist/src/first-recall-proof.d.ts +0 -19
  145. package/dist/src/first-recall-proof.js.map +0 -1
  146. package/dist/src/funnel-milestones.d.ts +0 -5
  147. package/dist/src/funnel-milestones.js.map +0 -1
  148. package/dist/src/geliefert.d.ts +0 -63
  149. package/dist/src/geliefert.js.map +0 -1
  150. package/dist/src/handlers/advanced.d.ts +0 -6
  151. package/dist/src/handlers/advanced.js.map +0 -1
  152. package/dist/src/handlers/brain.d.ts +0 -41
  153. package/dist/src/handlers/brain.js.map +0 -1
  154. package/dist/src/handlers/cache.d.ts +0 -6
  155. package/dist/src/handlers/cache.js.map +0 -1
  156. package/dist/src/handlers/context.d.ts +0 -6
  157. package/dist/src/handlers/context.js.map +0 -1
  158. package/dist/src/handlers/fedbrain.d.ts +0 -18
  159. package/dist/src/handlers/fedbrain.js.map +0 -1
  160. package/dist/src/handlers/instances.d.ts +0 -33
  161. package/dist/src/handlers/instances.js.map +0 -1
  162. package/dist/src/handlers/roadmap.d.ts +0 -6
  163. package/dist/src/handlers/roadmap.js.map +0 -1
  164. package/dist/src/handlers/share.d.ts +0 -8
  165. package/dist/src/handlers/share.js.map +0 -1
  166. package/dist/src/handlers/syndicate.d.ts +0 -6
  167. package/dist/src/handlers/syndicate.js.map +0 -1
  168. package/dist/src/handlers/team.d.ts +0 -45
  169. package/dist/src/handlers/team.js.map +0 -1
  170. package/dist/src/handlers/viz.d.ts +0 -6
  171. package/dist/src/handlers/viz.js.map +0 -1
  172. package/dist/src/index.d.ts +0 -93
  173. package/dist/src/index.js.map +0 -1
  174. package/dist/src/interferenz.d.ts +0 -111
  175. package/dist/src/interferenz.js.map +0 -1
  176. package/dist/src/kostprobe.d.ts +0 -25
  177. package/dist/src/kostprobe.js.map +0 -1
  178. package/dist/src/lesson-preview.d.ts +0 -81
  179. package/dist/src/lesson-preview.js.map +0 -1
  180. package/dist/src/namespace.d.ts +0 -20
  181. package/dist/src/namespace.js.map +0 -1
  182. package/dist/src/notifier.d.ts +0 -5
  183. package/dist/src/notifier.js.map +0 -1
  184. package/dist/src/rangfolge.d.ts +0 -205
  185. package/dist/src/rangfolge.js.map +0 -1
  186. package/dist/src/raumkorrektur.d.ts +0 -100
  187. package/dist/src/raumkorrektur.js.map +0 -1
  188. package/dist/src/recall-tiefe.d.ts +0 -48
  189. package/dist/src/recall-tiefe.js.map +0 -1
  190. package/dist/src/rerank.d.ts +0 -48
  191. package/dist/src/rerank.js.map +0 -1
  192. package/dist/src/resonanz.d.ts +0 -85
  193. package/dist/src/resonanz.js.map +0 -1
  194. package/dist/src/search.d.ts +0 -153
  195. package/dist/src/search.js.map +0 -1
  196. package/dist/src/seltenheitsbestand.d.ts +0 -53
  197. package/dist/src/seltenheitsbestand.js.map +0 -1
  198. package/dist/src/speicherlage.d.ts +0 -51
  199. package/dist/src/speicherlage.js.map +0 -1
  200. package/dist/src/spuren.d.ts +0 -97
  201. package/dist/src/spuren.js.map +0 -1
  202. package/dist/src/starter-corpus.d.ts +0 -28
  203. package/dist/src/starter-corpus.js.map +0 -1
  204. package/dist/src/telemetry-types.d.ts +0 -87
  205. package/dist/src/telemetry-types.js.map +0 -1
  206. package/dist/src/tools.d.ts +0 -2933
  207. package/dist/src/tools.js.map +0 -1
  208. package/dist/src/toolspecs.d.ts +0 -43
  209. package/dist/src/toolspecs.js.map +0 -1
  210. package/dist/src/upgrade-nudge.d.ts +0 -15
  211. package/dist/src/upgrade-nudge.js.map +0 -1
  212. package/dist/src/utils.d.ts +0 -34
  213. package/dist/src/utils.js.map +0 -1
  214. package/dist/src/versuch.d.ts +0 -187
  215. package/dist/src/versuch.js.map +0 -1
  216. package/dist/src/vorspann.d.ts +0 -73
  217. package/dist/src/vorspann.js.map +0 -1
  218. package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
  219. package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
  220. package/dist/src/werkzeug-nutzung.d.ts +0 -62
  221. package/dist/src/werkzeug-nutzung.js.map +0 -1
  222. package/dist/src/wertbeitrag.d.ts +0 -148
  223. package/dist/src/wertbeitrag.js.map +0 -1
@@ -1,121 +1 @@
1
- /**
2
- * Agent-framework tool-spec exporter.
3
- *
4
- * cachly exposes 122 MCP tools. MCP-native clients (Claude Code, Cursor,
5
- * Windsurf, Copilot) speak MCP directly. But the wider agent ecosystem —
6
- * OpenAI Assistants / function calling, the Anthropic Messages API, LangChain,
7
- * CrewAI, AutoGen — each want the SAME tool definitions in their own dialect.
8
- *
9
- * Rather than hand-maintain four copies, we derive every dialect from the single
10
- * TOOLS source of truth. Zero new runtime dependencies: these are just shape
11
- * transforms over the JSON-Schema input schemas the tools already declare.
12
- *
13
- * CLI: npx @cachly-dev/mcp-server tool-specs --format=openapi|openai|anthropic|langchain
14
- */
15
- const SERVER_TITLE = 'cachly AI Brain — MCP tools';
16
- /** OpenAI function-calling / Assistants tool array. */
17
- export function toOpenAITools(tools) {
18
- return tools.map(t => ({
19
- type: 'function',
20
- function: {
21
- name: t.name,
22
- description: t.description,
23
- parameters: t.inputSchema ?? { type: 'object', properties: {} },
24
- },
25
- }));
26
- }
27
- /** Anthropic Messages API tool array (input_schema, not parameters). */
28
- export function toAnthropicTools(tools) {
29
- return tools.map(t => ({
30
- name: t.name,
31
- description: t.description,
32
- input_schema: t.inputSchema ?? { type: 'object', properties: {} },
33
- }));
34
- }
35
- /**
36
- * LangChain / CrewAI StructuredTool descriptors. LangChain consumes a name +
37
- * description + JSON-schema args; this shape is accepted by
38
- * `convertToOpenAITool`-style helpers and by CrewAI's tool adapters.
39
- */
40
- export function toLangChainTools(tools) {
41
- return tools.map(t => ({
42
- name: t.name,
43
- description: t.description,
44
- schema: t.inputSchema ?? { type: 'object', properties: {} },
45
- }));
46
- }
47
- /**
48
- * OpenAPI 3.1 document. Each MCP tool becomes a POST operation under
49
- * /mcp/tools/{name}, with the tool's input schema as the request body. This lets
50
- * any OpenAPI-driven agent (OpenAI Assistants "from OpenAPI", Postman, codegen)
51
- * wrap cachly automatically. The operation maps 1:1 onto an MCP tools/call.
52
- */
53
- export function toOpenAPI(tools, version, serverUrl = 'https://api.cachly.dev') {
54
- const paths = {};
55
- for (const t of tools) {
56
- paths[`/mcp/tools/${t.name}`] = {
57
- post: {
58
- operationId: t.name,
59
- summary: t.description.split('. ')[0],
60
- description: t.description,
61
- requestBody: {
62
- required: (t.inputSchema?.required?.length ?? 0) > 0,
63
- content: {
64
- 'application/json': {
65
- schema: t.inputSchema ?? { type: 'object', properties: {} },
66
- },
67
- },
68
- },
69
- responses: {
70
- '200': {
71
- description: 'Tool result (MCP content blocks).',
72
- content: {
73
- 'application/json': {
74
- schema: {
75
- type: 'object',
76
- properties: {
77
- content: {
78
- type: 'array',
79
- items: {
80
- type: 'object',
81
- properties: { type: { type: 'string' }, text: { type: 'string' } },
82
- },
83
- },
84
- },
85
- },
86
- },
87
- },
88
- },
89
- },
90
- },
91
- };
92
- }
93
- return {
94
- openapi: '3.1.0',
95
- info: {
96
- title: SERVER_TITLE,
97
- version,
98
- description: 'OpenAPI projection of the cachly MCP tool surface. Each path maps 1:1 onto an MCP ' +
99
- 'tools/call. Authenticate with a bearer token (CACHLY_JWT, a cky_ API key, or an ' +
100
- 'OAuth2 client_credentials access token).',
101
- },
102
- servers: [{ url: serverUrl }],
103
- components: {
104
- securitySchemes: {
105
- bearerAuth: { type: 'http', scheme: 'bearer', bearerFormat: 'JWT' },
106
- },
107
- },
108
- security: [{ bearerAuth: [] }],
109
- paths,
110
- };
111
- }
112
- /** Render the requested format as a pretty-printed JSON string. */
113
- export function renderToolSpecs(tools, format, version) {
114
- switch (format) {
115
- case 'openapi': return JSON.stringify(toOpenAPI(tools, version), null, 2);
116
- case 'openai': return JSON.stringify({ tools: toOpenAITools(tools) }, null, 2);
117
- case 'anthropic': return JSON.stringify({ tools: toAnthropicTools(tools) }, null, 2);
118
- case 'langchain': return JSON.stringify({ tools: toLangChainTools(tools) }, null, 2);
119
- }
120
- }
121
- //# sourceMappingURL=toolspecs.js.map
1
+ const i="cachly AI Brain \u2014 MCP tools";function p(t){return t.map(e=>({type:"function",function:{name:e.name,description:e.description,parameters:e.inputSchema??{type:"object",properties:{}}}}))}function c(t){return t.map(e=>({name:e.name,description:e.description,input_schema:e.inputSchema??{type:"object",properties:{}}}))}function s(t){return t.map(e=>({name:e.name,description:e.description,schema:e.inputSchema??{type:"object",properties:{}}}))}function a(t,e,o="https://api.cachly.dev"){const r={};for(const n of t)r[`/mcp/tools/${n.name}`]={post:{operationId:n.name,summary:n.description.split(". ")[0],description:n.description,requestBody:{required:(n.inputSchema?.required?.length??0)>0,content:{"application/json":{schema:n.inputSchema??{type:"object",properties:{}}}}},responses:{200:{description:"Tool result (MCP content blocks).",content:{"application/json":{schema:{type:"object",properties:{content:{type:"array",items:{type:"object",properties:{type:{type:"string"},text:{type:"string"}}}}}}}}}}}};return{openapi:"3.1.0",info:{title:i,version:e,description:"OpenAPI projection of the cachly MCP tool surface. Each path maps 1:1 onto an MCP tools/call. Authenticate with a bearer token (CACHLY_JWT, a cky_ API key, or an OAuth2 client_credentials access token)."},servers:[{url:o}],components:{securitySchemes:{bearerAuth:{type:"http",scheme:"bearer",bearerFormat:"JWT"}}},security:[{bearerAuth:[]}],paths:r}}function u(t,e,o){switch(e){case"openapi":return JSON.stringify(a(t,o),null,2);case"openai":return JSON.stringify({tools:p(t)},null,2);case"anthropic":return JSON.stringify({tools:c(t)},null,2);case"langchain":return JSON.stringify({tools:s(t)},null,2)}}export{u as renderToolSpecs,c as toAnthropicTools,s as toLangChainTools,p as toOpenAITools,a as toOpenAPI};
@@ -1,29 +1 @@
1
- /**
2
- * Builds a plain-language upgrade nudge for a free-tier user who is close to
3
- * or over their monthly recall limit. Returns null when there is nothing to
4
- * say: an unlimited or unknown limit, or usage below 80 percent.
5
- *
6
- * Deliberately carries no price and no percentage — a number that lives in
7
- * two places drifts apart (see RES-017: a stale German price table quoted
8
- * 9/29 EUR while Stripe charged 19/49).
9
- */
10
- export function upgradeNudge({ used, limit, savedMins }) {
11
- if (!Number.isFinite(limit) || limit <= 0)
12
- return null;
13
- if (!Number.isFinite(used))
14
- return null;
15
- const ratio = used / limit;
16
- if (ratio < 0.8)
17
- return null;
18
- const remaining = Math.max(0, limit - used);
19
- const unit = remaining === 1 ? 'recall' : 'recalls';
20
- let text = `📈 ${remaining} ${unit} left this month on the free plan — see cachly.dev/pricing to keep going.`;
21
- if (Number.isFinite(savedMins) && savedMins >= 60) {
22
- const h = Math.floor(savedMins / 60);
23
- const m = Math.round(savedMins % 60);
24
- const timeStr = h > 0 ? `${h}h${m > 0 ? ` ${m}m` : ''}` : `${m}m`;
25
- text += ` You've already saved ~${timeStr} with Brain — keep that going.`;
26
- }
27
- return text;
28
- }
29
- //# sourceMappingURL=upgrade-nudge.js.map
1
+ function h({used:n,limit:t,savedMins:e}){if(!Number.isFinite(t)||t<=0||!Number.isFinite(n)||n/t<.8)return null;const i=Math.max(0,t-n);let o=`\u{1F4C8} ${i} ${i===1?"recall":"recalls"} left this month on the free plan \u2014 see cachly.dev/pricing to keep going.`;if(Number.isFinite(e)&&e>=60){const u=Math.floor(e/60),r=Math.round(e%60),a=u>0?`${u}h${r>0?` ${r}m`:""}`:`${r}m`;o+=` You've already saved ~${a} with Brain \u2014 keep that going.`}return o}export{h as upgradeNudge};
package/dist/src/utils.js CHANGED
@@ -1,89 +1 @@
1
- // Safely parses a JSON string. Returns `fallback` on null, empty, or malformed
2
- // input instead of throwing a SyntaxError that would fail the entire tool call.
3
- export function safeJsonParse(raw, fallback) {
4
- if (!raw)
5
- return fallback;
6
- try {
7
- return JSON.parse(raw);
8
- }
9
- catch {
10
- return fallback;
11
- }
12
- }
13
- /**
14
- * Runs a promise with a hard timeout. If it doesn't settle within `ms`, resolves
15
- * with `fallback` instead of hanging. A memory tool must NEVER block the agent's
16
- * turn — a slow Redis scan or unreachable backend degrades gracefully to a fallback.
17
- */
18
- export async function withTimeout(p, ms, fallback) {
19
- let timer;
20
- const guard = new Promise((resolve) => {
21
- timer = setTimeout(() => resolve(fallback), ms);
22
- });
23
- try {
24
- return await Promise.race([p, guard]);
25
- }
26
- catch {
27
- return fallback;
28
- }
29
- finally {
30
- if (timer)
31
- clearTimeout(timer);
32
- }
33
- }
34
- /**
35
- * Collects keys matching a pattern with a hard cap and timeout. Prevents a tool
36
- * from hanging on a huge keyspace: stops early at `max` keys, and resolves with
37
- * whatever was gathered if the scan exceeds `timeoutMs`. Never rejects.
38
- */
39
- export function scanKeys(redis, pattern, opts = {}) {
40
- const max = opts.max ?? 1000;
41
- const timeoutMs = opts.timeoutMs ?? 3000;
42
- return new Promise((resolve) => {
43
- const keys = [];
44
- let done = false;
45
- const finish = () => { if (!done) {
46
- done = true;
47
- resolve(keys);
48
- } };
49
- const timer = setTimeout(finish, timeoutMs);
50
- try {
51
- const stream = redis.scanStream({ match: pattern, count: opts.count ?? 200 });
52
- stream.on('data', (batch) => {
53
- for (const k of batch) {
54
- keys.push(k);
55
- if (keys.length >= max) {
56
- clearTimeout(timer);
57
- finish();
58
- return;
59
- }
60
- }
61
- });
62
- stream.on('end', () => { clearTimeout(timer); finish(); });
63
- stream.on('error', () => { clearTimeout(timer); finish(); });
64
- }
65
- catch {
66
- clearTimeout(timer);
67
- finish();
68
- }
69
- });
70
- }
71
- /**
72
- * Normalizes a git path that may come from `--name-only` with rename notation,
73
- * e.g. `src/{old => new}/file.ts` → `src/new/file.ts`, and `a => b` → `b`.
74
- * Returns the post-rename path so file nodes don't fragment on renames.
75
- */
76
- export function normalizeGitPath(raw) {
77
- let p = raw.trim();
78
- if (!p)
79
- return p;
80
- // Brace rename: dir/{old => new}/file → dir/new/file
81
- p = p.replace(/\{[^}]*=>\s*([^}]*)\}/g, (_m, after) => after.trim());
82
- // Bare rename: old => new → new
83
- const arrow = p.split('=>');
84
- if (arrow.length === 2)
85
- p = arrow[1].trim();
86
- // Collapse any double slashes introduced by an empty rename segment.
87
- return p.replace(/\/{2,}/g, '/').replace(/^\/|\/$/g, '');
88
- }
89
- //# sourceMappingURL=utils.js.map
1
+ function h(r,t){if(!r)return t;try{return JSON.parse(r)}catch{return t}}async function p(r,t,e){let n;const o=new Promise(s=>{n=setTimeout(()=>s(e),t)});try{return await Promise.race([r,o])}catch{return e}finally{n&&clearTimeout(n)}}function T(r,t,e={}){const n=e.max??1e3,o=e.timeoutMs??3e3;return new Promise(s=>{const a=[];let m=!1;const i=()=>{m||(m=!0,s(a))},c=setTimeout(i,o);try{const u=r.scanStream({match:t,count:e.count??200});u.on("data",f=>{for(const l of f)if(a.push(l),a.length>=n){clearTimeout(c),i();return}}),u.on("end",()=>{clearTimeout(c),i()}),u.on("error",()=>{clearTimeout(c),i()})}catch{clearTimeout(c),i()}})}function y(r){let t=r.trim();if(!t)return t;t=t.replace(/\{[^}]*=>\s*([^}]*)\}/g,(n,o)=>o.trim());const e=t.split("=>");return e.length===2&&(t=e[1].trim()),t.replace(/\/{2,}/g,"/").replace(/^\/|\/$/g,"")}export{y as normalizeGitPath,h as safeJsonParse,T as scanKeys,p as withTimeout};
@@ -1,264 +1 @@
1
- /**
2
- * Messtechnik fuer den Rueckfall-Versuch (dev.to "rules-before-the-run").
3
- *
4
- * ── Worum es geht ────────────────────────────────────────────────────────────
5
- *
6
- * Der Artikel sagt zu: wir messen, ob eine abgerufene Lektion, die WIRKLICH bis
7
- * ins Modell kommt, den Rueckfall senkt (dieselbe Lektion muss kein zweites Mal
8
- * geschrieben werden). Diese Datei baut nur das Messgeraet. Sie startet nichts.
9
- *
10
- * Ohne den Schalter `CACHLY_VERSUCH=an` (siehe `versuchStart`) aendert sich am
11
- * Verhalten des Recall-Pfads NICHTS — kein Zurueckhalten, kein Schreiben nach
12
- * Redis, keine Markierung im Text. Das ist keine Nebenbedingung, das ist der
13
- * ganze Zweck: der Versuch soll erst beginnen, wenn Heinrich ihn startet.
14
- *
15
- * ── Der Kanarienvogel ────────────────────────────────────────────────────────
16
- *
17
- * Zu pruefen ist nicht, ob eine Lektion ABGERUFEN wurde. Zu pruefen ist, ob ihr
18
- * Text VOLLSTAENDIG in der Antwort landet, die das Modell sieht. Anlass: eine
19
- * Lektion trug die gesuchte Adresse an Zeichen 323 von `what_worked`, die
20
- * Anzeige schnitt bei 100 Zeichen ab (siehe lesson-preview.ts). Die Lektion war
21
- * eingeblendet, der Fehler passierte trotzdem — genau der Zustand, den ein
22
- * "wurde abgerufen"-Zaehler nie sieht.
23
- *
24
- * Die Markierung sitzt deshalb am Ende von `what_worked` — dem Feld, an dem der
25
- * Anlass hing, und dem Feld, das `lessonPreviewLines` als Erstes und am
26
- * grosszuegigsten kuerzt. Sie besteht aus acht Zero-Width-Space-Zeichen
27
- * (U+200B): unsichtbar in jedem Renderer (Terminal, Markdown, HTML), und sie
28
- * uebersteht sowohl `flat()`s Leerzeichen-Kollaps als auch `String#trim()` —
29
- * beide fassen nur "richtige" Leerzeichen an, U+200B gehoert zu keiner der
30
- * beiden Kategorien. Acht Wiederholungen, damit ein einzelnes zufaelliges
31
- * Zero-Width-Zeichen in echtem Nutzertext die Markierung nicht vortaeuscht.
32
- *
33
- * WICHTIG: die Markierung wird NICHT roh ans Ende des JSON-Strings gehaengt.
34
- * Das wuerde `JSON.parse` in `recallVorschau` brechen und den Fallback-Pfad
35
- * (rohes `trimTo` ueber das GANZE JSON-Objekt) erzwingen — einen Pfad, den
36
- * echte, unmarkierte Lektionen nie durchlaufen. Das Ergebnis waere ein
37
- * Fehlalarm, kein Messwert. Die Markierung wird darum in die geparste
38
- * `what_worked`-Zeichenkette eingefuegt, das Objekt bleibt gueltiges JSON, und
39
- * der Kanarienvogel durchlaeuft exakt den Pfad, den eine echte Lektion auch
40
- * durchlaeuft.
41
- *
42
- * Geprueft wird am Ende die WIRKLICH zusammengebaute Antwort (`lines.join`),
43
- * nicht eine Nebenrechnung — siehe `schliesseVersuchAb`. Die Markierung wird
44
- * danach aus dem Text entfernt, bevor er den Nutzer erreicht.
45
- */
46
- import { randomBytes, createHash } from 'node:crypto';
47
- import { meldeEinmal, meldeUndVermerke, VERSUCH_LEER } from './aussetzer.js';
48
- // ── Der Schalter ─────────────────────────────────────────────────────────────
49
- export const VERSUCH_ENV = 'CACHLY_VERSUCH';
50
- export const VERSUCH_SALZ_ENV = 'CACHLY_VERSUCH_SALZ';
51
- /** Grund fuer `meldeEinmal`: Versuch eingeschaltet, aber kein Salz gesetzt. */
52
- export const OHNE_SALZ = 'versuch-ohne-salz';
53
- /**
54
- * Der einzige Einstiegspunkt fuer die Frage "soll der Versuch jetzt laufen?".
55
- *
56
- * Gibt das Salz zurueck, wenn ja — sonst `null`. Zwei Faelle enden bei `null`:
57
- * der Schalter steht nicht auf "an", oder er steht auf "an", aber das Salz
58
- * fehlt. Der zweite Fall meldet sich einmal je Prozess nach stderr (ueber
59
- * `meldeEinmal`) und verhaelt sich danach wie der erste — still weiterlaufen
60
- * waere die Fehlerklasse, gegen die `aussetzer.ts` gebaut wurde.
61
- */
62
- export function versuchStart(speicher) {
63
- if (process.env[VERSUCH_ENV] !== 'an')
64
- return null;
65
- const salz = process.env[VERSUCH_SALZ_ENV];
66
- if (!salz) {
67
- const text = `${VERSUCH_ENV}=an, aber ${VERSUCH_SALZ_ENV} fehlt — der Versuch verhaelt sich wie "aus"`;
68
- if (speicher) {
69
- // Ohne Speicher stand die Meldung nur im Arbeitsspeicher und war nach
70
- // jedem Deploy weg (Karte opupbt3l9wcq, gefunden von der Gegenrede,
71
- // Rolle: Der Betreiber). Der Versuch konnte damit WOCHENLANG leer
72
- // laufen: "keine Daten" sah genauso aus wie "noch nicht genug Daten".
73
- //
74
- // meldeUndVermerke faengt jeden eigenen Fehler ab und gibt eine
75
- // Zusage zurueck, die niemand einloesen muss — deshalb `void`. Diese
76
- // Funktion bleibt synchron, weil ihre Aufrufer es sind.
77
- void meldeUndVermerke(speicher, VERSUCH_LEER, text);
78
- }
79
- else {
80
- meldeEinmal(OHNE_SALZ, text);
81
- }
82
- return null;
83
- }
84
- return salz;
85
- }
86
- // ── Turn-Kennung ─────────────────────────────────────────────────────────────
87
- /**
88
- * Eine neue, stabile Kennung je Recall-Aufruf.
89
- *
90
- * Im Recall-Pfad gibt es keine vorhandene Anfrage- oder Turn-Kennung, die man
91
- * verlaengern koennte — `requester` ist ein Autorenname, `cachly:session:current`
92
- * haelt Zustand je INSTANZ, nicht je Aufruf. Darum: Zeitstempel (sortierbar,
93
- * in einer Zeile lesbar) plus vier zufaellige Byte, damit zwei Aufrufe in
94
- * derselben Millisekunde nicht kollidieren.
95
- */
96
- export function neueKennung() {
97
- const zeit = new Date().toISOString().replace(/[-:.]/g, '').slice(0, 15); // 20260820T171523
98
- const zufall = randomBytes(4).toString('hex');
99
- return `${zeit}-${zufall}`;
100
- }
101
- /**
102
- * Reine Funktion, ohne Netz testbar: Kennung + Salz -> HOLD oder DELIVER.
103
- *
104
- * Berechnung exakt wie oeffentlich angekuendigt: Kennung und Salz aneinander-
105
- * haengen, SHA-256, die ersten VIER Hex-Ziffern als Zahl lesen, modulo 100.
106
- * Unter 50 heisst HOLD, sonst DELIVER.
107
- */
108
- export function zuteilung(kennung, salz) {
109
- const hash = createHash('sha256').update(`${kennung}${salz}`).digest('hex');
110
- const ersteVier = hash.slice(0, 4);
111
- const zahl = parseInt(ersteVier, 16) % 100;
112
- return zahl < 50 ? 'HOLD' : 'DELIVER';
113
- }
114
- // ── Zulassung ────────────────────────────────────────────────────────────────
115
- /** Ohne einen Treffer ueber dieser Punktzahl gibt es nichts zu messen. */
116
- export const ZULASSUNGS_SCHWELLE = 0;
117
- /** Gibt es ueberhaupt einen Treffer, der ueber der Schwelle liegt? */
118
- export function istZulassungsfaehig(hoechstePunktzahl, schwelle = ZULASSUNGS_SCHWELLE) {
119
- return hoechstePunktzahl > schwelle;
120
- }
121
- /** Der Grund, warum eine Anfrage NICHT zulassungsfaehig ist — oder `undefined`. */
122
- export function zulassungsGrund(hoechstePunktzahl, schwelle = ZULASSUNGS_SCHWELLE) {
123
- if (istZulassungsfaehig(hoechstePunktzahl, schwelle))
124
- return undefined;
125
- return `hoechste Punktzahl ${hoechstePunktzahl.toFixed(3)} liegt nicht ueber der Schwelle ${schwelle}`;
126
- }
127
- // ── Kanarienvogel ────────────────────────────────────────────────────────────
128
- /**
129
- * Acht Zero-Width-Spaces. Siehe Dateikopf fuer die Begruendung der Wahl.
130
- *
131
- * Ueber String.fromCharCode, nicht als roher Unicode-Buchstabe im
132
- * Quelltext — ein unsichtbares Zeichen zwischen Anfuehrungszeichen ist beim
133
- * Lesen und in `git diff` nicht von "kein Zeichen" zu unterscheiden.
134
- */
135
- export const KANARIEN_MARKIERUNG = String.fromCharCode(0x200b).repeat(8);
136
- /**
137
- * Setzt die Kanarien-Markierung ans Ende des `what_worked`-Feldes.
138
- *
139
- * `inhalt` ist der rohe Redis-Wert, wie ihn `recallVorschau` bekommt — meist
140
- * das JSON einer Lektion, manchmal (Kontext-Eintraege, Index) einfacher Text.
141
- * Ist es eine Lektion mit `what_worked`, wandert die Markierung dorthin und
142
- * das Objekt bleibt gueltiges JSON (Begruendung siehe Dateikopf). Sonst wird
143
- * roh ans Ende angehaengt — das ist fuer Nicht-Lektionen korrekt, weil
144
- * `recallVorschau` sie ohnehin nur als Rohtext kuerzt.
145
- */
146
- export function markiereDatensatz(inhalt, markierung = KANARIEN_MARKIERUNG) {
147
- try {
148
- const objekt = JSON.parse(inhalt);
149
- if (typeof objekt.what_worked === 'string') {
150
- objekt.what_worked = `${objekt.what_worked}${markierung}`;
151
- return JSON.stringify(objekt);
152
- }
153
- }
154
- catch {
155
- // kein JSON — Markierung ans Ende des Rohtexts, siehe unten
156
- }
157
- return `${inhalt}${markierung}`;
158
- }
159
- /**
160
- * Ueberlebt die Markierung in der fertig zusammengebauten Antwort?
161
- *
162
- * Ueberlebt sie: der ganze Datensatz kam durch. Fehlt sie: er wurde gekuerzt.
163
- */
164
- export function kanarienUeberlebt(fertigeAntwort, markierung = KANARIEN_MARKIERUNG) {
165
- return fertigeAntwort.includes(markierung);
166
- }
167
- /** Entfernt die Markierung, bevor der Text den Nutzer erreicht. */
168
- export function entferneMarkierung(text, markierung = KANARIEN_MARKIERUNG) {
169
- return text.split(markierung).join('');
170
- }
171
- // ── Themenschluessel ─────────────────────────────────────────────────────────
172
- const LEKTION_PRAEFIX = 'cachly:lesson:best:';
173
- /** Redis-Schluessel einer Lektion -> ihr Themenname (ohne Praefix). */
174
- export function themaVon(key) {
175
- return key.startsWith(LEKTION_PRAEFIX) ? key.slice(LEKTION_PRAEFIX.length) : key;
176
- }
177
- // ── Protokoll je Turn ────────────────────────────────────────────────────────
178
- /** Hash der Frage — die Frage selbst wird nie gespeichert (Zusage des Artikels). */
179
- export function hashFrage(frage) {
180
- return createHash('sha256').update(frage).digest('hex');
181
- }
182
- const TURN_TTL_SEKUNDEN = 90 * 24 * 60 * 60; // 90 Tage — der Versuch dauert 14
183
- /** Redis-Schluessel fuer den Protokoll-Eintrag einer Kennung. */
184
- export function turnSchluessel(kennung) {
185
- return `cachly:versuch:turn:${kennung}`;
186
- }
187
- /**
188
- * Schreibt den Turn-Eintrag. Schlaegt das fehl, wird NICHTS geworfen — der
189
- * Nutzer wartet auf eine Antwort, nicht auf ein Protokoll.
190
- */
191
- export async function schreibeTurnProtokoll(redis, kennung, protokoll) {
192
- try {
193
- await redis.set(turnSchluessel(kennung), JSON.stringify(protokoll), 'EX', TURN_TTL_SEKUNDEN);
194
- }
195
- catch {
196
- // Ein fehlendes Protokoll ist besser als eine gescheiterte Suche.
197
- }
198
- }
199
- /**
200
- * Wendet Zulassung + Zuteilung auf eine Trefferliste an. Reine Funktion,
201
- * mutiert `treffer` nicht.
202
- *
203
- * Ohne zulassungsfaehigen Treffer passiert nichts — Zuteilung wird trotzdem
204
- * berechnet und protokolliert, aber es gibt nichts zurueckzuhalten oder zu
205
- * markieren. Bei HOLD verschwindet der beste Treffer aus der Liste, die
206
- * uebrigen bleiben unveraendert. Bei DELIVER traegt der beste Treffer danach
207
- * die Kanarien-Markierung.
208
- */
209
- export function wendeZuteilungAn(treffer, kennung, salz, hoechstePunktzahl, schwelle = ZULASSUNGS_SCHWELLE) {
210
- const zulassungsfaehig = istZulassungsfaehig(hoechstePunktzahl, schwelle);
211
- const grund = zulassungsGrund(hoechstePunktzahl, schwelle);
212
- const entscheidung = zuteilung(kennung, salz);
213
- if (!zulassungsfaehig || treffer.length === 0) {
214
- return {
215
- treffer: [...treffer],
216
- zuteilung: entscheidung,
217
- zulassungsfaehig,
218
- zulassungsGrund: grund,
219
- markierungGesetzt: false,
220
- };
221
- }
222
- const [bester, ...rest] = treffer;
223
- if (entscheidung === 'HOLD') {
224
- return {
225
- treffer: rest,
226
- zuteilung: entscheidung,
227
- zulassungsfaehig,
228
- zulassungsGrund: grund,
229
- markierungGesetzt: false,
230
- weggelassenesThema: themaVon(bester.key),
231
- };
232
- }
233
- const markiert = { ...bester, content: markiereDatensatz(bester.content) };
234
- return {
235
- treffer: [markiert, ...rest],
236
- zuteilung: entscheidung,
237
- zulassungsfaehig,
238
- zulassungsGrund: grund,
239
- markierungGesetzt: true,
240
- };
241
- }
242
- /**
243
- * Letzter Schritt: prueft den Kanarienvogel gegen die WIRKLICH zusammen-
244
- * gebaute Antwort, entfernt die Markierung, schreibt das Protokoll (fire-and-
245
- * forget) und gibt den bereinigten Text zurueck — den, den der Nutzer sieht.
246
- */
247
- export function schliesseVersuchAb(redis, kennung, teil, fertigeAntwort) {
248
- const kanarienBestanden = teil.markierungGesetzt ? kanarienUeberlebt(fertigeAntwort) : false;
249
- const bereinigt = entferneMarkierung(fertigeAntwort);
250
- const protokoll = {
251
- ts: new Date().toISOString(),
252
- frageHash: hashFrage(teil.frage),
253
- hoechstePunktzahl: teil.hoechstePunktzahl,
254
- themen: teil.themen,
255
- zulassungsfaehig: teil.zulassungsfaehig,
256
- zulassungsGrund: teil.zulassungsGrund,
257
- zuteilung: teil.zuteilung,
258
- weggelassenesThema: teil.weggelassenesThema,
259
- kanarienBestanden,
260
- };
261
- void schreibeTurnProtokoll(redis, kennung, protokoll);
262
- return bereinigt;
263
- }
264
- //# sourceMappingURL=versuch.js.map
1
+ import{randomBytes as x,createHash as l}from"node:crypto";import{meldeEinmal as S,meldeUndVermerke as m,VERSUCH_LEER as E}from"./aussetzer.js";const f="CACHLY_VERSUCH",p="CACHLY_VERSUCH_SALZ",k="versuch-ohne-salz";function O(e){if(process.env[f]!=="an")return null;const t=process.env[p];if(!t){const n=`${f}=an, aber ${p} fehlt \u2014 der Versuch verhaelt sich wie "aus"`;return e?m(e,E,n):S(k,n),null}return t}function v(){const e=new Date().toISOString().replace(/[-:.]/g,"").slice(0,15),t=x(4).toString("hex");return`${e}-${t}`}function _(e,t){const r=l("sha256").update(`${e}${t}`).digest("hex").slice(0,4);return parseInt(r,16)%100<50?"HOLD":"DELIVER"}const c=0;function d(e,t=c){return e>t}function L(e,t=c){if(!d(e,t))return`hoechste Punktzahl ${e.toFixed(3)} liegt nicht ueber der Schwelle ${t}`}const g="\u200B".repeat(8);function N(e,t=g){try{const n=JSON.parse(e);if(typeof n.what_worked=="string")return n.what_worked=`${n.what_worked}${t}`,JSON.stringify(n)}catch{}return`${e}${t}`}function H(e,t=g){return e.includes(t)}function V(e,t=g){return e.split(t).join("")}const z="cachly:lesson:best:";function $(e){return e.startsWith(z)?e.slice(z.length):e}function w(e){return l("sha256").update(e).digest("hex")}const C=2160*60*60;function G(e){return`cachly:versuch:turn:${e}`}async function R(e,t,n){try{await e.set(G(t),JSON.stringify(n),"EX",C)}catch{}}function y(e,t,n,r,u=c){const s=d(r,u),o=L(r,u),a=_(t,n);if(!s||e.length===0)return{treffer:[...e],zuteilung:a,zulassungsfaehig:s,zulassungsGrund:o,markierungGesetzt:!1};const[i,...h]=e;return a==="HOLD"?{treffer:h,zuteilung:a,zulassungsfaehig:s,zulassungsGrund:o,markierungGesetzt:!1,weggelassenesThema:$(i.key)}:{treffer:[{...i,content:N(i.content)},...h],zuteilung:a,zulassungsfaehig:s,zulassungsGrund:o,markierungGesetzt:!0}}function I(e,t,n,r){const u=n.markierungGesetzt?H(r):!1,s=V(r),o={ts:new Date().toISOString(),frageHash:w(n.frage),hoechstePunktzahl:n.hoechstePunktzahl,themen:n.themen,zulassungsfaehig:n.zulassungsfaehig,zulassungsGrund:n.zulassungsGrund,zuteilung:n.zuteilung,weggelassenesThema:n.weggelassenesThema,kanarienBestanden:u};return R(e,t,o),s}export{g as KANARIEN_MARKIERUNG,k as OHNE_SALZ,f as VERSUCH_ENV,p as VERSUCH_SALZ_ENV,c as ZULASSUNGS_SCHWELLE,V as entferneMarkierung,w as hashFrage,d as istZulassungsfaehig,H as kanarienUeberlebt,N as markiereDatensatz,v as neueKennung,I as schliesseVersuchAb,R as schreibeTurnProtokoll,$ as themaVon,G as turnSchluessel,O as versuchStart,y as wendeZuteilungAn,L as zulassungsGrund,_ as zuteilung};
@@ -1,146 +1 @@
1
- /**
2
- * Der Vorspann einer Lektion — die ersten 100 Zeichen, und nur die.
3
- *
4
- * GEMESSEN am 16.08.2026: Das Session-Briefing zeigt je Lektion rund 100
5
- * Zeichen. In `node4:einrichtung-contabo-und-fallen` stand die gesuchte
6
- * Adresse `10.8.0.7` an Zeichen 323, und die passende Warnung im Feld
7
- * `what_failed`, das im Briefing gar nicht erscheint. Die Lektion war
8
- * eingeblendet. Der Fehler passierte trotzdem.
9
- *
10
- * Die Abhilfe war eine SCHREIBREGEL, kein Codefix: die entscheidende Tatsache
11
- * gehoert in die ersten 100 Zeichen — die Zahl, die Adresse, der Befehl. Nicht
12
- * der Anlass, nicht die Ausstattung, nicht die Vorgeschichte.
13
- *
14
- * Eine Regel, die kein Skript prueft, ist aber kein Gesetz, sondern ein
15
- * Wunsch. Dieses Modul prueft sie — im Moment des Schreibens, wo der Autor
16
- * (Mensch oder Agent) die Lektion noch in der Hand hat.
17
- *
18
- * Bewusst ein HINWEIS und keine Ablehnung: es gibt Lektionen ohne harte Zahl
19
- * (eine Entscheidung, eine Richtung), und eine abgelehnte Lektion ist immer
20
- * schlechter als eine unscharf formulierte.
21
- */
22
- /** So viel sieht die naechste Sitzung im Briefing. Gemessen, nicht geschaetzt. */
23
- export const VORSPANN_ZEICHEN = 100;
24
- /**
25
- * Was als "harte Tatsache" zaehlt.
26
- *
27
- * Jedes Muster steht fuer etwas, das man beim naechsten Mal ABSCHREIBEN will
28
- * und nicht neu herausfinden: eine Adresse, ein Port, eine Zahl mit Einheit,
29
- * ein Pfad, ein Befehl, eine Version, ein Schluessel-Wert-Paar.
30
- */
31
- const HARTE_MUSTER = [
32
- { name: 'IP-Adresse', re: /\b\d{1,3}(?:\.\d{1,3}){3}\b/ },
33
- { name: 'Port', re: /:\d{2,5}\b/ },
34
- { name: 'Zahl', re: /\b\d+(?:[.,]\d+)?\s*(?:%|s|ms|m|h|d|MB|GB|TB|KB|k|x|×|EUR|€|\$)?\b/ },
35
- { name: 'Pfad', re: /(?:^|\s)(?:\/[\w.-]+){2,}|[A-Za-z]:\\[\w\\.-]+/ },
36
- { name: 'Befehl oder Datei', re: /`[^`]+`|\b[\w-]+\.(?:ts|tsx|go|js|mjs|py|sh|yml|yaml|json|sql|env)\b/ },
37
- // Ein Name in Grossbuchstaben mit mindestens zwei Unterstrichen ist in der
38
- // Praxis immer eine Umgebungsvariable oder ein Schalter — also etwas zum
39
- // Abschreiben. Zwei Unterstriche, damit nicht jedes betonte Wort greift.
40
- // Der Doppelpunkt ist bewusst NICHT dabei. Er war zuerst erlaubt, und die
41
- // Gegenprobe fing sofort den Fehlalarm: "WICHTIG:" ist ein betontes Wort,
42
- // keine Angabe. Bleibt das Gleichheitszeichen (PORT=3095) und der Name mit
43
- // mindestens zwei Unterstrichen, der in der Praxis immer eine
44
- // Umgebungsvariable ist.
45
- { name: 'Schluessel', re: /\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+){2,}\b|\b[A-Z][A-Z0-9_]{3,}\s*=/ },
46
- { name: 'Version', re: /\bv?\d+\.\d+(?:\.\d+)?\b/ },
47
- ];
48
- /**
49
- * Steht im Vorspann ein abgeschlossener Gedanke?
50
- *
51
- * Mechanisch, nicht inhaltlich: gesucht wird ein Satzende (Punkt, Semikolon,
52
- * Ausrufe- oder Fragezeichen) innerhalb der ersten 100 Zeichen. Findet sich
53
- * keines, endet der Vorspann mitten im Satz.
54
- *
55
- * Absichtlich KEINE Bedeutungspruefung. Ein Waechter, der raet, ob ein Satz
56
- * "vollstaendig genug" ist, schlaegt bei jeder zweiten Lektion falschen Alarm
57
- * — und ein Waechter, der zu oft Nein sagt, wird abgeschaltet. Ein fehlendes
58
- * Satzzeichen ist dagegen eine Tatsache.
59
- *
60
- * Ein Text, der insgesamt kuerzer ist als der Vorspann, gilt NICHT als
61
- * abgeschnitten: dort steht alles, was es gibt.
62
- */
63
- function endetMittenImSatz(text) {
64
- if (text.length <= VORSPANN_ZEICHEN)
65
- return false;
66
- const vorspann = text.slice(0, VORSPANN_ZEICHEN);
67
- // Zwei Feinheiten, beide am echten Fall gelernt:
68
- //
69
- // 1. Ein Punkt in "10.8.0.7" oder "v1.2" beendet keinen Satz — deshalb muss
70
- // auf das Satzzeichen ein Leerzeichen oder das Textende folgen.
71
- // 2. Der DOPPELPUNKT zaehlt NICHT. Er leitet ein, er schliesst nicht ab.
72
- // Genau daran scheiterte die erste Fassung: "GEMESSEN 19.08.2026: 109
73
- // Testdateien ... 13 davon GRUEN weil" galt als abgeschlossen, weil der
74
- // Doppelpunkt nach dem Datum mitzaehlte. Der Satz endet dort aber nicht,
75
- // er faengt an.
76
- return !/[.;!?](?:\s|$)/.test(vorspann);
77
- }
78
- /** Frueheste Fundstelle irgendeines harten Musters, oder -1. */
79
- function ersteFundstelle(text) {
80
- let best = -1;
81
- for (const m of HARTE_MUSTER) {
82
- const treffer = m.re.exec(text);
83
- if (!treffer)
84
- continue;
85
- const i = treffer.index;
86
- if (best === -1 || i < best)
87
- best = i;
88
- }
89
- return best;
90
- }
91
- /**
92
- * Prueft den Vorspann von `what_worked`.
93
- *
94
- * Leerer oder fehlender Text ergibt einen Befund ohne Tatsache und ohne
95
- * "steht spaeter" — es gibt dann nichts umzustellen, sondern nichts zu lesen.
96
- */
97
- export function pruefeVorspann(whatWorked) {
98
- const text = typeof whatWorked === 'string' ? whatWorked : '';
99
- const vorspann = text.slice(0, VORSPANN_ZEICHEN);
100
- const gefunden = HARTE_MUSTER.filter(m => m.re.test(vorspann)).map(m => m.name);
101
- const ersteTatsacheBei = ersteFundstelle(text);
102
- return {
103
- vorspann,
104
- traegtTatsache: gefunden.length > 0,
105
- gefunden,
106
- tatsacheStehtSpaeter: gefunden.length === 0 && ersteTatsacheBei >= VORSPANN_ZEICHEN,
107
- ersteTatsacheBei,
108
- abgeschnitten: endetMittenImSatz(text),
109
- };
110
- }
111
- /**
112
- * Ein Satz fuer den Autor — oder null, wenn alles in Ordnung ist.
113
- *
114
- * Der Hinweis nennt die POSITION, wenn die Tatsache nur zu weit hinten steht.
115
- * Ohne Zahl waere er eine Ermahnung; mit Zahl ist er eine Anweisung, die man
116
- * in zehn Sekunden befolgen kann.
117
- */
118
- export function vorspannHinweis(whatWorked) {
119
- const b = pruefeVorspann(whatWorked);
120
- if (!b.vorspann.trim())
121
- return null;
122
- /*
123
- * Die Zahl ist da, aber der Satz ist es nicht.
124
- *
125
- * Dieser Fall kam am 19.08.2026 dazu und ist der feinere: die alte Pruefung
126
- * war zufrieden, weil drei Zahlen in den ersten 100 Zeichen standen — und
127
- * genau bei "weil" war Schluss. Zahlen ohne ihre Wirkung sind im Briefing
128
- * kein Wissen, sondern ein Raetsel.
129
- */
130
- if (b.traegtTatsache && b.abgeschnitten) {
131
- return (`📏 **Der Vorspann endet mitten im Satz** — die Zahl steht vorn, ihre Wirkung nicht. ` +
132
- `Das Briefing der nächsten Sitzung bricht bei ${VORSPANN_ZEICHEN} Zeichen ab und zeigt dann ` +
133
- `eine Zahl ohne Bedeutung. Ein abgeschlossener Satz in den ersten ${VORSPANN_ZEICHEN} Zeichen ` +
134
- `genügt: nicht „13 davon grün weil", sondern „13 Wächter waren grün, ohne etwas zu prüfen."`);
135
- }
136
- if (b.traegtTatsache)
137
- return null;
138
- if (b.tatsacheStehtSpaeter) {
139
- return (`📏 **Die entscheidende Tatsache steht bei Zeichen ${b.ersteTatsacheBei}** — das Briefing der nächsten ` +
140
- `Sitzung endet bei ${VORSPANN_ZEICHEN}. Stell die Zahl, Adresse oder den Befehl an den Anfang von ` +
141
- `\`what_worked\`; die Vorgeschichte gehört ans Ende oder in \`context\`.`);
142
- }
143
- return (`📏 **In den ersten ${VORSPANN_ZEICHEN} Zeichen steht keine Zahl, Adresse oder Befehl** — nur so viel sieht ` +
144
- `die nächste Sitzung im Briefing. Trägt diese Lektion eine harte Tatsache, gehört sie nach vorn.`);
145
- }
146
- //# sourceMappingURL=vorspann.js.map
1
+ const d=100,a=[{name:"IP-Adresse",re:/\b\d{1,3}(?:\.\d{1,3}){3}\b/},{name:"Port",re:/:\d{2,5}\b/},{name:"Zahl",re:/\b\d+(?:[.,]\d+)?\s*(?:%|s|ms|m|h|d|MB|GB|TB|KB|k|x|×|EUR|€|\$)?\b/},{name:"Pfad",re:/(?:^|\s)(?:\/[\w.-]+){2,}|[A-Za-z]:\\[\w\\.-]+/},{name:"Befehl oder Datei",re:/`[^`]+`|\b[\w-]+\.(?:ts|tsx|go|js|mjs|py|sh|yml|yaml|json|sql|env)\b/},{name:"Schluessel",re:/\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+){2,}\b|\b[A-Z][A-Z0-9_]{3,}\s*=/},{name:"Version",re:/\bv?\d+\.\d+(?:\.\d+)?\b/}];function h(n){if(n.length<=100)return!1;const e=n.slice(0,100);return!/[.;!?](?:\s|$)/.test(e)}function c(n){let e=-1;for(const s of a){const t=s.re.exec(n);if(!t)continue;const r=t.index;(e===-1||r<e)&&(e=r)}return e}function o(n){const e=typeof n=="string"?n:"",s=e.slice(0,100),t=a.filter(i=>i.re.test(s)).map(i=>i.name),r=c(e);return{vorspann:s,traegtTatsache:t.length>0,gefunden:t,tatsacheStehtSpaeter:t.length===0&&r>=100,ersteTatsacheBei:r,abgeschnitten:h(e)}}function l(n){const e=o(n);return e.vorspann.trim()?e.traegtTatsache&&e.abgeschnitten?'\u{1F4CF} **Der Vorspann endet mitten im Satz** \u2014 die Zahl steht vorn, ihre Wirkung nicht. Das Briefing der n\xE4chsten Sitzung bricht bei 100 Zeichen ab und zeigt dann eine Zahl ohne Bedeutung. Ein abgeschlossener Satz in den ersten 100 Zeichen gen\xFCgt: nicht \u201E13 davon gr\xFCn weil", sondern \u201E13 W\xE4chter waren gr\xFCn, ohne etwas zu pr\xFCfen."':e.traegtTatsache?null:e.tatsacheStehtSpaeter?`\u{1F4CF} **Die entscheidende Tatsache steht bei Zeichen ${e.ersteTatsacheBei}** \u2014 das Briefing der n\xE4chsten Sitzung endet bei 100. Stell die Zahl, Adresse oder den Befehl an den Anfang von \`what_worked\`; die Vorgeschichte geh\xF6rt ans Ende oder in \`context\`.`:"\u{1F4CF} **In den ersten 100 Zeichen steht keine Zahl, Adresse oder Befehl** \u2014 nur so viel sieht die n\xE4chste Sitzung im Briefing. Tr\xE4gt diese Lektion eine harte Tatsache, geh\xF6rt sie nach vorn.":null}export{d as VORSPANN_ZEICHEN,o as pruefeVorspann,l as vorspannHinweis};