alchemylab 1.0.3 → 1.0.5

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.
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ /**
3
+ * OpenCode System Logger Plugin
4
+ *
5
+ * Hooks into experimental.chat.system.transform to log the full system prompt
6
+ * to .alchemy_lab/system-prompt.log. Used to diagnose whether entity context
7
+ * (from @ mentions) reaches the LLM via the user.system field.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ const getLogFile = (directory) => (0, path_1.join)(directory, '.alchemy_lab', 'system-prompt.log');
13
+ const debugLog = (logFile, msg, data) => {
14
+ const line = `[${new Date().toISOString()}] ${msg}${data ? ' ' + JSON.stringify(data) : ''}\n`;
15
+ try {
16
+ (0, fs_1.mkdirSync)((0, path_1.join)(logFile, '..'), { recursive: true });
17
+ (0, fs_1.appendFileSync)(logFile, line);
18
+ }
19
+ catch (e) {
20
+ // ignore
21
+ }
22
+ };
23
+ /**
24
+ * Strip Task tool references from system prompt for alchemist sessions.
25
+ * The OpenCode base prompt heavily promotes using the Task tool, which causes
26
+ * the alchemist to hallucinate the tool even when it's disabled in tool config.
27
+ */
28
+ function stripTaskReferences(system) {
29
+ for (let i = 0; i < system.length; i++) {
30
+ let s = system[i];
31
+ // Remove lines that promote the Task tool
32
+ s = s.replace(/^- When doing file search, prefer to use the Task tool.*$/gm, '');
33
+ s = s.replace(/^- You should proactively use the Task tool.*$/gm, '');
34
+ s = s.replace(/^- VERY IMPORTANT:.*use the Task tool instead of running search commands directly\.$/gm, '');
35
+ s = s.replace(/^- If the user specifies that they want you to run tools "in parallel".*multiple Task tool calls\.$/gm, '');
36
+ // Remove Task tool examples
37
+ s = s.replace(/<example>\n.*?\[Uses the Task tool.*?<\/example>/gs, '');
38
+ // Clean up excessive blank lines left behind
39
+ s = s.replace(/\n{3,}/g, '\n\n');
40
+ system[i] = s;
41
+ }
42
+ }
43
+ exports.default = async ({ directory }) => {
44
+ const logFile = getLogFile(directory);
45
+ debugLog(logFile, 'system-logger plugin loaded', { directory });
46
+ return {
47
+ "experimental.chat.system.transform": async (hookInput, output) => {
48
+ // Strip Task tool references for alchemist sessions
49
+ const isAlchemist = output.system.some((s) => s.includes('head of engineering'));
50
+ debugLog(logFile, 'Alchemist detection', { isAlchemist, sessionID: hookInput.sessionID, systemParts: output.system.length, partLengths: output.system.map((s) => s.length), hasSpecialist: output.system.map((s) => s.includes('specialist_ops')) });
51
+ if (isAlchemist) {
52
+ stripTaskReferences(output.system);
53
+ debugLog(logFile, 'Stripped Task tool references for alchemist session', { sessionID: hookInput.sessionID });
54
+ }
55
+ const hasEntityContext = output.system.some((s) => s.includes('referenced the following') || s.includes('entity'));
56
+ const lines = [
57
+ '='.repeat(80),
58
+ `Timestamp: ${new Date().toISOString()}`,
59
+ `Session: ${hookInput.sessionID || 'N/A'}`,
60
+ `Model: ${hookInput.model?.providerID || '?'}/${hookInput.model?.modelID || '?'}`,
61
+ `System parts: ${output.system.length}`,
62
+ `Has entity context: ${hasEntityContext}`,
63
+ '-'.repeat(80),
64
+ ...output.system.map((s, i) => {
65
+ const preview = s.length > 500
66
+ ? s.slice(0, 250) + '\n...[truncated]...\n' + s.slice(-250)
67
+ : s;
68
+ return `--- Part ${i} (${s.length} chars) ---\n${preview}`;
69
+ }),
70
+ '='.repeat(80),
71
+ '',
72
+ ];
73
+ try {
74
+ (0, fs_1.mkdirSync)((0, path_1.join)(logFile, '..'), { recursive: true });
75
+ (0, fs_1.appendFileSync)(logFile, lines.join('\n') + '\n');
76
+ // Also write full untruncated prompt to separate file (overwrite each time)
77
+ const fullLogFile = (0, path_1.join)(logFile, '..', 'system-prompt-full.log');
78
+ const fullLines = [
79
+ `Timestamp: ${new Date().toISOString()}`,
80
+ `Session: ${hookInput.sessionID || 'N/A'}`,
81
+ `IsAlchemist: ${isAlchemist}`,
82
+ `System parts: ${output.system.length}`,
83
+ ...output.system.map((s, i) => `--- Part ${i} (${s.length} chars) ---\n${s}`),
84
+ '='.repeat(80),
85
+ ];
86
+ const { writeFileSync } = require('fs');
87
+ writeFileSync(fullLogFile, fullLines.join('\n') + '\n');
88
+ }
89
+ catch (e) {
90
+ // ignore
91
+ }
92
+ },
93
+ };
94
+ };
95
+ //# sourceMappingURL=system-logger.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alchemylab",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "private": false,
5
5
  "description": "AlchemyLab - agentic coding IDEm",
6
6
  "author": "Testfire Software",
@@ -22,13 +22,15 @@
22
22
  },
23
23
  "files": [
24
24
  "dist/**/*.js",
25
+ "dist/plugins/**/*.js",
25
26
  "package.json",
26
27
  "README.md"
27
28
  ],
28
29
  "scripts": {
29
30
  "build": "npm run build:dev",
30
31
  "build:dev": "tsc -p tsconfig.json && chmod +x dist-dev/*.js",
31
- "build:prod": "webpack --mode=production && chmod +x dist/*.js",
32
+ "build:prod": "webpack --mode=production && chmod +x dist/*.js && npm run copy:plugins",
33
+ "copy:plugins": "mkdir -p dist/plugins/hooks/lib && cp dist-dev/plugins/hooks/plugin.js dist/plugins/hooks/ && cp dist-dev/plugins/hooks/lib/*.js dist/plugins/hooks/lib/ && cp dist-dev/plugins/inject-session.js dist/plugins/ && cp dist-dev/plugins/system-logger.js dist/plugins/",
32
34
  "build:analyze": "ANALYZE=true webpack --mode=production",
33
35
  "test": "npm run build:dev && mocha --require ts-node/register test/**/*.test.ts",
34
36
  "test:watch": "mocha --require ts-node/register test/**/*.test.ts --watch --watch-files src/**/*.ts,test/**/*.ts",