@cachly-dev/mcp-server 0.10.129 → 0.10.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +1 -7
- package/dist/packages/telegram-notify/client.js +2 -75
- package/dist/src/ambient-cli.js +7 -253
- package/dist/src/ambient-deps.js +1 -55
- package/dist/src/ambient-hooks.js +5 -278
- package/dist/src/ambient-ledger.js +6 -95
- package/dist/src/ambient-memory.js +1 -67
- package/dist/src/ambient-recall.js +1 -221
- package/dist/src/aussetzer.js +1 -225
- package/dist/src/auth.js +9 -140
- package/dist/src/autor-abzeichen.js +1 -56
- package/dist/src/autor.js +1 -136
- package/dist/src/bauabdruck.js +1 -93
- package/dist/src/bedeutung.js +2 -323
- package/dist/src/bench/cachly-bench.js +4 -249
- package/dist/src/bench/external-corpus.js +4 -135
- package/dist/src/bench/fixtures.js +1 -225
- package/dist/src/brain-export.js +16 -119
- package/dist/src/brain-watch-hook.js +8 -86
- package/dist/src/cachly-url.js +1 -21
- package/dist/src/ckg.js +1 -108
- package/dist/src/cls-hook.js +8 -94
- package/dist/src/confidence.js +1 -50
- package/dist/src/credentials.js +1 -85
- package/dist/src/deutsch.js +1 -223
- package/dist/src/doctor.js +2 -248
- package/dist/src/editor.js +1 -63
- package/dist/src/eingaenge-nachruesten.js +1 -290
- package/dist/src/eingaenge.js +2 -260
- package/dist/src/embeddings.js +13 -223
- package/dist/src/feldreparatur.js +3 -113
- package/dist/src/first-contact.js +4 -108
- package/dist/src/first-recall-proof.js +4 -41
- package/dist/src/funnel-milestones.js +2 -48
- package/dist/src/geliefert.js +1 -114
- package/dist/src/handlers/advanced.js +38 -537
- package/dist/src/handlers/brain.js +69 -4397
- package/dist/src/handlers/cache.js +59 -638
- package/dist/src/handlers/context.js +17 -182
- package/dist/src/handlers/fedbrain.js +40 -1436
- package/dist/src/handlers/instances.js +16 -201
- package/dist/src/handlers/roadmap.js +13 -175
- package/dist/src/handlers/share.js +19 -530
- package/dist/src/handlers/syndicate.js +38 -904
- package/dist/src/handlers/team.js +89 -1865
- package/dist/src/handlers/viz.js +3 -148
- package/dist/src/index.js +321 -4281
- package/dist/src/interferenz.js +1 -141
- package/dist/src/kostprobe.js +7 -125
- package/dist/src/lesson-preview.js +2 -133
- package/dist/src/namespace.js +1 -128
- package/dist/src/notifier.js +1 -6
- package/dist/src/rangfolge-stellschrauben.js +1 -0
- package/dist/src/rangfolge.js +1 -249
- package/dist/src/raumkorrektur.js +1 -197
- package/dist/src/recall-tiefe.js +1 -42
- package/dist/src/rerank.js +1 -154
- package/dist/src/resonanz.js +1 -140
- package/dist/src/search.js +1 -2104
- package/dist/src/seltenheitsbestand.js +1 -92
- package/dist/src/speicherlage.js +1 -84
- package/dist/src/spuren.js +1 -150
- package/dist/src/starter-corpus.js +1 -165
- package/dist/src/telemetry-types.js +0 -13
- package/dist/src/tools.js +10 -2465
- package/dist/src/toolspecs.js +1 -121
- package/dist/src/upgrade-nudge.js +1 -29
- package/dist/src/utils.js +1 -89
- package/dist/src/versuch.js +1 -264
- package/dist/src/vorspann.js +1 -146
- package/dist/src/wartung/vektoren-verdichten.js +1 -200
- package/dist/src/werkzeug-nutzung.js +1 -72
- package/dist/src/wertbeitrag.js +1 -203
- package/dist/src/wirkungsspur.js +1 -0
- package/glama.json +1 -1
- package/package.json +8 -7
- package/server.json +3 -3
- package/dist/index.d.ts +0 -1
- package/dist/index.js.map +0 -1
- package/dist/packages/telegram-notify/client.d.ts +0 -19
- package/dist/packages/telegram-notify/client.js.map +0 -1
- package/dist/src/ambient-cli.d.ts +0 -101
- package/dist/src/ambient-cli.js.map +0 -1
- package/dist/src/ambient-deps.d.ts +0 -26
- package/dist/src/ambient-deps.js.map +0 -1
- package/dist/src/ambient-hooks.d.ts +0 -137
- package/dist/src/ambient-hooks.js.map +0 -1
- package/dist/src/ambient-ledger.d.ts +0 -19
- package/dist/src/ambient-ledger.js.map +0 -1
- package/dist/src/ambient-memory.d.ts +0 -12
- package/dist/src/ambient-memory.js.map +0 -1
- package/dist/src/ambient-recall.d.ts +0 -127
- package/dist/src/ambient-recall.js.map +0 -1
- package/dist/src/aussetzer.d.ts +0 -156
- package/dist/src/aussetzer.js.map +0 -1
- package/dist/src/auth.d.ts +0 -43
- package/dist/src/auth.js.map +0 -1
- package/dist/src/autor-abzeichen.d.ts +0 -41
- package/dist/src/autor-abzeichen.js.map +0 -1
- package/dist/src/autor.d.ts +0 -101
- package/dist/src/autor.js.map +0 -1
- package/dist/src/bauabdruck-daten.generiert.json +0 -5
- package/dist/src/bauabdruck.d.ts +0 -38
- package/dist/src/bauabdruck.js.map +0 -1
- package/dist/src/bedeutung.d.ts +0 -168
- package/dist/src/bedeutung.js.map +0 -1
- package/dist/src/bench/cachly-bench.d.ts +0 -61
- package/dist/src/bench/cachly-bench.js.map +0 -1
- package/dist/src/bench/external-corpus.d.ts +0 -44
- package/dist/src/bench/external-corpus.js.map +0 -1
- package/dist/src/bench/fixtures.d.ts +0 -37
- package/dist/src/bench/fixtures.js.map +0 -1
- package/dist/src/brain-export.d.ts +0 -64
- package/dist/src/brain-export.js.map +0 -1
- package/dist/src/brain-watch-hook.d.ts +0 -21
- package/dist/src/brain-watch-hook.js.map +0 -1
- package/dist/src/cachly-url.d.ts +0 -68
- package/dist/src/cachly-url.js.map +0 -1
- package/dist/src/ckg.d.ts +0 -54
- package/dist/src/ckg.js.map +0 -1
- package/dist/src/cls-hook.d.ts +0 -15
- package/dist/src/cls-hook.js.map +0 -1
- package/dist/src/confidence.d.ts +0 -19
- package/dist/src/confidence.js.map +0 -1
- package/dist/src/credentials.d.ts +0 -37
- package/dist/src/credentials.js.map +0 -1
- package/dist/src/deutsch.d.ts +0 -98
- package/dist/src/deutsch.js.map +0 -1
- package/dist/src/doctor.d.ts +0 -47
- package/dist/src/doctor.js.map +0 -1
- package/dist/src/editor.d.ts +0 -9
- package/dist/src/editor.js.map +0 -1
- package/dist/src/eingaenge-nachruesten.d.ts +0 -41
- package/dist/src/eingaenge-nachruesten.js.map +0 -1
- package/dist/src/eingaenge.d.ts +0 -159
- package/dist/src/eingaenge.js.map +0 -1
- package/dist/src/embeddings.d.ts +0 -48
- package/dist/src/embeddings.js.map +0 -1
- package/dist/src/feldreparatur.d.ts +0 -58
- package/dist/src/feldreparatur.js.map +0 -1
- package/dist/src/first-contact.d.ts +0 -56
- package/dist/src/first-contact.js.map +0 -1
- package/dist/src/first-recall-proof.d.ts +0 -19
- package/dist/src/first-recall-proof.js.map +0 -1
- package/dist/src/funnel-milestones.d.ts +0 -5
- package/dist/src/funnel-milestones.js.map +0 -1
- package/dist/src/geliefert.d.ts +0 -63
- package/dist/src/geliefert.js.map +0 -1
- package/dist/src/handlers/advanced.d.ts +0 -6
- package/dist/src/handlers/advanced.js.map +0 -1
- package/dist/src/handlers/brain.d.ts +0 -41
- package/dist/src/handlers/brain.js.map +0 -1
- package/dist/src/handlers/cache.d.ts +0 -6
- package/dist/src/handlers/cache.js.map +0 -1
- package/dist/src/handlers/context.d.ts +0 -6
- package/dist/src/handlers/context.js.map +0 -1
- package/dist/src/handlers/fedbrain.d.ts +0 -18
- package/dist/src/handlers/fedbrain.js.map +0 -1
- package/dist/src/handlers/instances.d.ts +0 -33
- package/dist/src/handlers/instances.js.map +0 -1
- package/dist/src/handlers/roadmap.d.ts +0 -6
- package/dist/src/handlers/roadmap.js.map +0 -1
- package/dist/src/handlers/share.d.ts +0 -8
- package/dist/src/handlers/share.js.map +0 -1
- package/dist/src/handlers/syndicate.d.ts +0 -6
- package/dist/src/handlers/syndicate.js.map +0 -1
- package/dist/src/handlers/team.d.ts +0 -45
- package/dist/src/handlers/team.js.map +0 -1
- package/dist/src/handlers/viz.d.ts +0 -6
- package/dist/src/handlers/viz.js.map +0 -1
- package/dist/src/index.d.ts +0 -93
- package/dist/src/index.js.map +0 -1
- package/dist/src/interferenz.d.ts +0 -111
- package/dist/src/interferenz.js.map +0 -1
- package/dist/src/kostprobe.d.ts +0 -25
- package/dist/src/kostprobe.js.map +0 -1
- package/dist/src/lesson-preview.d.ts +0 -81
- package/dist/src/lesson-preview.js.map +0 -1
- package/dist/src/namespace.d.ts +0 -20
- package/dist/src/namespace.js.map +0 -1
- package/dist/src/notifier.d.ts +0 -5
- package/dist/src/notifier.js.map +0 -1
- package/dist/src/rangfolge.d.ts +0 -205
- package/dist/src/rangfolge.js.map +0 -1
- package/dist/src/raumkorrektur.d.ts +0 -100
- package/dist/src/raumkorrektur.js.map +0 -1
- package/dist/src/recall-tiefe.d.ts +0 -48
- package/dist/src/recall-tiefe.js.map +0 -1
- package/dist/src/rerank.d.ts +0 -48
- package/dist/src/rerank.js.map +0 -1
- package/dist/src/resonanz.d.ts +0 -85
- package/dist/src/resonanz.js.map +0 -1
- package/dist/src/search.d.ts +0 -153
- package/dist/src/search.js.map +0 -1
- package/dist/src/seltenheitsbestand.d.ts +0 -53
- package/dist/src/seltenheitsbestand.js.map +0 -1
- package/dist/src/speicherlage.d.ts +0 -51
- package/dist/src/speicherlage.js.map +0 -1
- package/dist/src/spuren.d.ts +0 -97
- package/dist/src/spuren.js.map +0 -1
- package/dist/src/starter-corpus.d.ts +0 -28
- package/dist/src/starter-corpus.js.map +0 -1
- package/dist/src/telemetry-types.d.ts +0 -87
- package/dist/src/telemetry-types.js.map +0 -1
- package/dist/src/tools.d.ts +0 -2933
- package/dist/src/tools.js.map +0 -1
- package/dist/src/toolspecs.d.ts +0 -43
- package/dist/src/toolspecs.js.map +0 -1
- package/dist/src/upgrade-nudge.d.ts +0 -15
- package/dist/src/upgrade-nudge.js.map +0 -1
- package/dist/src/utils.d.ts +0 -34
- package/dist/src/utils.js.map +0 -1
- package/dist/src/versuch.d.ts +0 -187
- package/dist/src/versuch.js.map +0 -1
- package/dist/src/vorspann.d.ts +0 -73
- package/dist/src/vorspann.js.map +0 -1
- package/dist/src/wartung/vektoren-verdichten.d.ts +0 -50
- package/dist/src/wartung/vektoren-verdichten.js.map +0 -1
- package/dist/src/werkzeug-nutzung.d.ts +0 -62
- package/dist/src/werkzeug-nutzung.js.map +0 -1
- package/dist/src/wertbeitrag.d.ts +0 -148
- package/dist/src/wertbeitrag.js.map +0 -1
package/dist/src/toolspecs.js
CHANGED
|
@@ -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
|
-
|
|
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};
|
package/dist/src/versuch.js
CHANGED
|
@@ -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};
|
package/dist/src/vorspann.js
CHANGED
|
@@ -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};
|