@zibby/core 0.1.0

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +147 -0
  3. package/package.json +94 -0
  4. package/src/agents/base.js +361 -0
  5. package/src/constants.js +47 -0
  6. package/src/enrichment/base.js +49 -0
  7. package/src/enrichment/enrichers/accessibility-enricher.js +197 -0
  8. package/src/enrichment/enrichers/dom-enricher.js +171 -0
  9. package/src/enrichment/enrichers/page-state-enricher.js +129 -0
  10. package/src/enrichment/enrichers/position-enricher.js +67 -0
  11. package/src/enrichment/index.js +96 -0
  12. package/src/enrichment/mcp-integration.js +149 -0
  13. package/src/enrichment/mcp-ref-enricher.js +78 -0
  14. package/src/enrichment/pipeline.js +192 -0
  15. package/src/enrichment/trace-text-enricher.js +115 -0
  16. package/src/framework/AGENTS.md +98 -0
  17. package/src/framework/agents/base.js +72 -0
  18. package/src/framework/agents/claude-strategy.js +278 -0
  19. package/src/framework/agents/cursor-strategy.js +459 -0
  20. package/src/framework/agents/index.js +105 -0
  21. package/src/framework/agents/utils/cursor-output-formatter.js +67 -0
  22. package/src/framework/agents/utils/openai-proxy-formatter.js +249 -0
  23. package/src/framework/code-generator.js +301 -0
  24. package/src/framework/constants.js +33 -0
  25. package/src/framework/context-loader.js +101 -0
  26. package/src/framework/function-bridge.js +78 -0
  27. package/src/framework/function-skill-registry.js +20 -0
  28. package/src/framework/graph-compiler.js +342 -0
  29. package/src/framework/graph.js +610 -0
  30. package/src/framework/index.js +28 -0
  31. package/src/framework/node-registry.js +163 -0
  32. package/src/framework/node.js +259 -0
  33. package/src/framework/output-parser.js +71 -0
  34. package/src/framework/skill-registry.js +55 -0
  35. package/src/framework/state-utils.js +52 -0
  36. package/src/framework/state.js +67 -0
  37. package/src/framework/tool-resolver.js +65 -0
  38. package/src/index.js +342 -0
  39. package/src/runtime/generation/base.js +46 -0
  40. package/src/runtime/generation/index.js +70 -0
  41. package/src/runtime/generation/mcp-ref-strategy.js +197 -0
  42. package/src/runtime/generation/stable-id-strategy.js +170 -0
  43. package/src/runtime/stable-id-runtime.js +248 -0
  44. package/src/runtime/verification/base.js +44 -0
  45. package/src/runtime/verification/index.js +67 -0
  46. package/src/runtime/verification/playwright-json-strategy.js +119 -0
  47. package/src/runtime/zibby-runtime.js +299 -0
  48. package/src/sync/index.js +2 -0
  49. package/src/sync/uploader.js +29 -0
  50. package/src/tools/run-playwright-test.js +158 -0
  51. package/src/utils/adf-converter.js +68 -0
  52. package/src/utils/ast-utils.js +37 -0
  53. package/src/utils/ci-setup.js +124 -0
  54. package/src/utils/cursor-utils.js +71 -0
  55. package/src/utils/logger.js +144 -0
  56. package/src/utils/mcp-config-writer.js +115 -0
  57. package/src/utils/node-schema-parser.js +522 -0
  58. package/src/utils/post-process-events.js +55 -0
  59. package/src/utils/result-handler.js +102 -0
  60. package/src/utils/ripple-effect.js +84 -0
  61. package/src/utils/selector-generator.js +239 -0
  62. package/src/utils/streaming-parser.js +387 -0
  63. package/src/utils/test-post-processor.js +211 -0
  64. package/src/utils/timeline.js +217 -0
  65. package/src/utils/trace-parser.js +325 -0
  66. package/src/utils/video-organizer.js +91 -0
  67. package/templates/browser-test-automation/README.md +114 -0
  68. package/templates/browser-test-automation/graph.js +54 -0
  69. package/templates/browser-test-automation/nodes/execute-live.js +250 -0
  70. package/templates/browser-test-automation/nodes/generate-script.js +77 -0
  71. package/templates/browser-test-automation/nodes/index.js +3 -0
  72. package/templates/browser-test-automation/nodes/preflight.js +59 -0
  73. package/templates/browser-test-automation/nodes/utils.js +154 -0
  74. package/templates/browser-test-automation/result-handler.js +286 -0
  75. package/templates/code-analysis/graph.js +72 -0
  76. package/templates/code-analysis/index.js +18 -0
  77. package/templates/code-analysis/nodes/analyze-ticket-node.js +204 -0
  78. package/templates/code-analysis/nodes/create-pr-node.js +175 -0
  79. package/templates/code-analysis/nodes/finalize-node.js +118 -0
  80. package/templates/code-analysis/nodes/generate-code-node.js +425 -0
  81. package/templates/code-analysis/nodes/generate-test-cases-node.js +376 -0
  82. package/templates/code-analysis/nodes/services/prMetaService.js +86 -0
  83. package/templates/code-analysis/nodes/setup-node.js +142 -0
  84. package/templates/code-analysis/prompts/analyze-ticket.md +181 -0
  85. package/templates/code-analysis/prompts/generate-code.md +33 -0
  86. package/templates/code-analysis/prompts/generate-test-cases.md +110 -0
  87. package/templates/code-analysis/state.js +40 -0
  88. package/templates/code-implementation/graph.js +35 -0
  89. package/templates/code-implementation/index.js +7 -0
  90. package/templates/code-implementation/state.js +14 -0
  91. package/templates/global-setup.js +56 -0
  92. package/templates/index.js +94 -0
  93. package/templates/register-nodes.js +24 -0
@@ -0,0 +1,211 @@
1
+ import fs from 'fs/promises';
2
+ import { SelectorGenerator } from './selector-generator.js';
3
+
4
+ export class TestPostProcessor {
5
+ static async generateFromEvents(testFilePath, eventsPath, traceActions, testTitle) {
6
+ try {
7
+ console.log(`[TestPostProcessor] 🎯 Generating test from events.json (100% accurate)`);
8
+
9
+ const { readFileSync } = await import('fs');
10
+ const events = JSON.parse(readFileSync(eventsPath, 'utf-8'));
11
+
12
+ const actionEvents = events.filter(e => ['navigate', 'type', 'fill', 'click', 'select_option'].includes(e.type));
13
+
14
+ // Match trace actions with events by sequence order (both are chronological)
15
+ // Skip navigate events when matching trace actions (trace doesn't record navigations)
16
+ const _actionEventsNoNav = actionEvents.filter(e => e.type !== 'navigate');
17
+
18
+ console.log(`[TestPostProcessor] Found ${actionEvents.length} action events, ${traceActions.length} trace actions`);
19
+
20
+ let testCode = `import { ZibbyRuntime } from '@zibby/core';\nimport { test, expect } from '@playwright/test';\n\n`;
21
+ testCode += `test('${testTitle}', async ({ page }) => {\n`;
22
+ testCode += ` const timestamp = Date.now();\n\n`;
23
+
24
+ let traceIdx = 0;
25
+
26
+ for (const event of actionEvents) {
27
+ if (event.type === 'navigate') {
28
+ testCode += ` await page.goto('${event.data.params.url}');\n\n`;
29
+ } else if (event.type === 'type' || event.type === 'fill') {
30
+ const { element, text } = event.data.params;
31
+ const strategies = (traceActions[traceIdx]?.strategies) || [];
32
+
33
+ const fingerprint = {
34
+ name: element,
35
+ action: 'fill',
36
+ value: text,
37
+ strategies
38
+ };
39
+
40
+ testCode += ` await ZibbyRuntime.step(page, ${JSON.stringify(fingerprint, null, 2)});\n\n`;
41
+ traceIdx++;
42
+ } else if (event.type === 'click') {
43
+ const { element } = event.data.params;
44
+ const strategies = (traceActions[traceIdx]?.strategies) || [];
45
+
46
+ const fingerprint = {
47
+ name: element,
48
+ action: 'click',
49
+ value: '',
50
+ strategies
51
+ };
52
+
53
+ testCode += ` await ZibbyRuntime.step(page, ${JSON.stringify(fingerprint, null, 2)});\n\n`;
54
+ traceIdx++;
55
+ } else if (event.type === 'select_option') {
56
+ const { element, values } = event.data.params;
57
+ const strategies = (traceActions[traceIdx]?.strategies) || [];
58
+
59
+ const fingerprint = {
60
+ name: element,
61
+ action: 'selectOption',
62
+ value: Array.isArray(values) ? values[0] : values,
63
+ strategies
64
+ };
65
+
66
+ testCode += ` await ZibbyRuntime.step(page, ${JSON.stringify(fingerprint, null, 2)});\n\n`;
67
+ traceIdx++;
68
+ }
69
+ }
70
+
71
+ testCode += `});\n`;
72
+
73
+ const { dirname } = await import('path');
74
+ const { mkdirSync } = await import('fs');
75
+ mkdirSync(dirname(testFilePath), { recursive: true });
76
+ await fs.writeFile(testFilePath, testCode, 'utf-8');
77
+
78
+ console.log(`[TestPostProcessor] ✅ Generated test with ${events.filter(e => ['type', 'fill', 'click', 'select_option'].includes(e.type)).length} actions`);
79
+
80
+ return true;
81
+ } catch (err) {
82
+ console.warn('[TestPostProcessor] Failed to generate from events:', err.message);
83
+ return false;
84
+ }
85
+ }
86
+
87
+ static async enhanceSelectorsWithTrace(testFilePath, traceActions, _executionData) {
88
+ try {
89
+ console.log(`[TestPostProcessor] 🛡️ Applying Zibby Safe Action Wrappers...`);
90
+
91
+ let testCode = await fs.readFile(testFilePath, 'utf-8');
92
+
93
+ if (!testCode.includes('ZibbyRuntime')) {
94
+ testCode = `import { ZibbyRuntime } from '@zibby/core';\n${ testCode}`;
95
+ }
96
+
97
+ for (let i = 0; i < traceActions.length; i++) {
98
+ const action = traceActions[i];
99
+ const elementVar = `element${i}`;
100
+
101
+ const actionPattern = new RegExp(
102
+ `const ${elementVar}\\b\\s*=\\s*page\\.[^;]+;(\\s*await ${elementVar}\\.waitFor\\([^)]*\\);)?\\s*await ${elementVar}\\.(click|fill|type|selectOption|pressSequentially)\\(([^)]*)\\);`,
103
+ 's'
104
+ );
105
+
106
+ const match = testCode.match(actionPattern);
107
+ if (!match) continue;
108
+
109
+ const fingerprint = {
110
+ name: action.name || `Action ${i}`,
111
+ action: action.method === 'type' ? 'fill' : action.method,
112
+ value: match[3].trim().replace(/^['"]|['"]$/g, ''),
113
+ strategies: action.strategies || []
114
+ };
115
+
116
+ const zibbyStep = `await ZibbyRuntime.step(page, ${JSON.stringify(fingerprint, null, 2)});`;
117
+
118
+ testCode = testCode.replace(match[0], zibbyStep);
119
+ }
120
+
121
+ await fs.writeFile(testFilePath, testCode, 'utf-8');
122
+ console.log(`[TestPostProcessor] ✅ Successfully converted test to Resilient Zibby format`);
123
+
124
+ return true;
125
+ } catch (err) {
126
+ console.warn('[TestPostProcessor] Failed to apply safe wrappers:', err.message);
127
+ return false;
128
+ }
129
+ }
130
+
131
+ static async enhanceSelectors(testFilePath, executionData) {
132
+ try {
133
+ const { actions = [] } = executionData;
134
+ if (!actions.length) return false;
135
+
136
+ let testCode = await fs.readFile(testFilePath, 'utf-8');
137
+
138
+ const selectorMap = this.buildSelectorMap(actions);
139
+ testCode = this.replaceSimpleSelectors(testCode, selectorMap);
140
+
141
+ await fs.writeFile(testFilePath, testCode, 'utf-8');
142
+
143
+ return true;
144
+ } catch (err) {
145
+ console.warn('Failed to enhance selectors:', err);
146
+ return false;
147
+ }
148
+ }
149
+
150
+ static buildSelectorMap(actions) {
151
+ const map = new Map();
152
+
153
+ for (let i = 0; i < actions.length; i++) {
154
+ const action = actions[i];
155
+ if (!action.selectors || action.type === 'navigate') continue;
156
+
157
+ const varName = `element${i}`;
158
+ const robustCode = SelectorGenerator.generate(action, varName);
159
+
160
+ const match = robustCode.match(/= (.+);/s);
161
+ if (match) {
162
+ const locatorExpr = match[1].trim();
163
+
164
+ const key = `${action.type}:${this.normalizeDescription(action.description)}`;
165
+ map.set(key, locatorExpr);
166
+
167
+ if (action.selectors.role) {
168
+ map.set(`role:${action.selectors.role.name}`, locatorExpr);
169
+ }
170
+ if (action.selectors.attributes?.placeholder) {
171
+ map.set(`placeholder:${action.selectors.attributes.placeholder}`, locatorExpr);
172
+ }
173
+ }
174
+ }
175
+
176
+ return map;
177
+ }
178
+
179
+ static replaceSimpleSelectors(code, selectorMap) {
180
+ let enhanced = code;
181
+
182
+ const selectorPatterns = [
183
+ /await page\.(getByRole|getByPlaceholder|getByText|getByLabel|locator)\([^)]+\)\.(fill|click|type)\([^)]*\)/g
184
+ ];
185
+
186
+ for (const pattern of selectorPatterns) {
187
+ enhanced = enhanced.replace(pattern, (match) => {
188
+ for (const [key, robustLocator] of selectorMap.entries()) {
189
+ if (match.includes(key.split(':')[1])) {
190
+ const actionMatch = match.match(/\.(fill|click|type)\(([^)]*)\)/);
191
+ if (actionMatch) {
192
+ const [, method, args] = actionMatch;
193
+ const varName = 'element';
194
+ return `const ${varName} = ${robustLocator};\n await ${varName}.${method}(${args})`;
195
+ }
196
+ }
197
+ }
198
+ return match;
199
+ });
200
+ }
201
+
202
+ return enhanced;
203
+ }
204
+
205
+ static normalizeDescription(desc) {
206
+ if (!desc) return '';
207
+ return desc.toLowerCase().replace(/[^a-z0-9]+/g, ' ').trim();
208
+ }
209
+ }
210
+
211
+ export default TestPostProcessor;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Timeline UI - OpenClaw-style progressive output with vertical line connector
3
+ *
4
+ * When inside a node, ALL console output is automatically prefixed with │
5
+ * and soft-wrapped at terminal width so the pipe stays visible on every
6
+ * visual line, even when text wraps.
7
+ *
8
+ * Milestone dots (◆) sit ON the line, replacing │ at that point.
9
+ */
10
+
11
+ import chalk from 'chalk';
12
+
13
+ const PIPE = chalk.gray('│');
14
+ const PIPE_START = chalk.gray('┌');
15
+ const PIPE_END = chalk.gray('└');
16
+ const DOT = chalk.green('◆');
17
+ const DOT_TOOL = chalk.hex('#c084fc')('◆');
18
+ const DOT_MEMORY = chalk.hex('#2dd4bf')('◆');
19
+ const DOT_FAIL = chalk.red('◆');
20
+
21
+ const PIPE_PREFIX = `${PIPE} `;
22
+ const PIPE_VISUAL_WIDTH = 2;
23
+
24
+ function formatDuration(ms) {
25
+ if (ms < 1000) return `${ms}ms`;
26
+ return `${(ms / 1000).toFixed(1)}s`;
27
+ }
28
+
29
+ function makeWrapWriter(orig, state) {
30
+ return (chunk, encoding, callback) => {
31
+ if (typeof chunk !== 'string') {
32
+ return orig(chunk, encoding, callback);
33
+ }
34
+
35
+ const cols = process.stdout.columns || 120;
36
+ let out = '';
37
+
38
+ for (let i = 0; i < chunk.length; i++) {
39
+ const ch = chunk[i];
40
+
41
+ if (state.lineStart) {
42
+ out += PIPE_PREFIX;
43
+ state.col = PIPE_VISUAL_WIDTH;
44
+ state.lineStart = false;
45
+ }
46
+
47
+ if (ch === '\n') {
48
+ out += ch;
49
+ state.lineStart = true;
50
+ state.col = 0;
51
+ state.inEsc = false;
52
+ } else if (ch === '\x1b') {
53
+ state.inEsc = true;
54
+ out += ch;
55
+ } else if (state.inEsc) {
56
+ out += ch;
57
+ if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z')) {
58
+ state.inEsc = false;
59
+ }
60
+ } else {
61
+ state.col++;
62
+ out += ch;
63
+ if (state.col >= cols) {
64
+ out += `\n${ PIPE_PREFIX}`;
65
+ state.col = PIPE_VISUAL_WIDTH;
66
+ }
67
+ }
68
+ }
69
+
70
+ return orig(out, encoding, callback);
71
+ };
72
+ }
73
+
74
+ class Timeline {
75
+ constructor() {
76
+ this._currentNode = null;
77
+ this._origStdoutWrite = null;
78
+ this._origStderrWrite = null;
79
+ }
80
+
81
+ get isInsideNode() {
82
+ return this._currentNode !== null;
83
+ }
84
+
85
+ _startIntercepting() {
86
+ this._origStdoutWrite = process.stdout.write.bind(process.stdout);
87
+ this._origStderrWrite = process.stderr.write.bind(process.stderr);
88
+
89
+ const outState = { lineStart: true, col: 0, inEsc: false };
90
+ const errState = { lineStart: true, col: 0, inEsc: false };
91
+ this._outState = outState;
92
+ this._errState = errState;
93
+
94
+ process.stdout.write = makeWrapWriter(this._origStdoutWrite, outState);
95
+ process.stderr.write = makeWrapWriter(this._origStderrWrite, errState);
96
+ }
97
+
98
+ _stopIntercepting() {
99
+ if (this._origStdoutWrite) {
100
+ if (this._outState && !this._outState.lineStart) {
101
+ this._origStdoutWrite('\n');
102
+ }
103
+ process.stdout.write = this._origStdoutWrite;
104
+ }
105
+ if (this._origStderrWrite) {
106
+ if (this._errState && !this._errState.lineStart) {
107
+ this._origStderrWrite('\n');
108
+ }
109
+ process.stderr.write = this._origStderrWrite;
110
+ }
111
+ this._origStdoutWrite = null;
112
+ this._origStderrWrite = null;
113
+ }
114
+
115
+ _rawWrite(msg) {
116
+ const write = this._origStdoutWrite || process.stdout.write.bind(process.stdout);
117
+ write(`${msg }\n`);
118
+ }
119
+
120
+ /**
121
+ * Write a milestone dot ON the line. If interceptor is active and we're
122
+ * mid-line, finish that line first so the dot starts on a fresh line.
123
+ */
124
+ _writeDot(dot, message) {
125
+ if (this._origStdoutWrite) {
126
+ if (this._outState && !this._outState.lineStart) {
127
+ this._origStdoutWrite('\n');
128
+ this._outState.lineStart = true;
129
+ this._outState.col = 0;
130
+ }
131
+ this._origStdoutWrite(`${dot} ${message}\n`);
132
+ } else {
133
+ const write = process.stdout.write.bind(process.stdout);
134
+ write(`${dot} ${message}\n`);
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Milestone dot ON the line (inside a node) or with pipe prefix (outside).
140
+ */
141
+ step(message) {
142
+ if (this._origStdoutWrite) {
143
+ this._writeDot(DOT, message);
144
+ } else {
145
+ const write = process.stdout.write.bind(process.stdout);
146
+ write(`${PIPE} ${DOT} ${message}\n`);
147
+ }
148
+ }
149
+
150
+ stepTool(message) {
151
+ if (this._origStdoutWrite) {
152
+ this._writeDot(DOT_TOOL, message);
153
+ } else {
154
+ const write = process.stdout.write.bind(process.stdout);
155
+ write(`${PIPE} ${DOT_TOOL} ${message}\n`);
156
+ }
157
+ }
158
+
159
+ stepMemory(message) {
160
+ const colored = chalk.hex('#2dd4bf')(message);
161
+ if (this._origStdoutWrite) {
162
+ this._writeDot(DOT_MEMORY, colored);
163
+ } else {
164
+ const write = process.stdout.write.bind(process.stdout);
165
+ write(`${PIPE} ${DOT_MEMORY} ${colored}\n`);
166
+ }
167
+ }
168
+
169
+ stepFail(message) {
170
+ if (this._origStdoutWrite) {
171
+ this._writeDot(DOT_FAIL, chalk.red(message));
172
+ } else {
173
+ const write = process.stdout.write.bind(process.stdout);
174
+ write(`${PIPE} ${DOT_FAIL} ${chalk.red(message)}\n`);
175
+ }
176
+ }
177
+
178
+ nodeStart(name) {
179
+ this._currentNode = name;
180
+ this._rawWrite(`${PIPE_START} ${name}`);
181
+ this._startIntercepting();
182
+ }
183
+
184
+ nodeComplete(name, opts = {}) {
185
+ this._stopIntercepting();
186
+ const { duration, details } = opts;
187
+ if (details) {
188
+ for (const d of details) {
189
+ this._rawWrite(`${DOT} ${d}`);
190
+ }
191
+ }
192
+ const durationStr = duration ? chalk.dim(` ${formatDuration(duration)}`) : '';
193
+ this._rawWrite(`${PIPE_END} ${chalk.green('done')}${durationStr}`);
194
+ this._rawWrite('');
195
+ }
196
+
197
+ nodeFailed(name, error, opts = {}) {
198
+ this._stopIntercepting();
199
+ const { duration } = opts;
200
+ const durationStr = duration ? chalk.dim(` ${formatDuration(duration)}`) : '';
201
+ this._rawWrite(`${DOT_FAIL} ${chalk.red(error)}`);
202
+ this._rawWrite(`${PIPE_END} ${chalk.red('failed')}${durationStr}`);
203
+ this._rawWrite('');
204
+ }
205
+
206
+ route(from, to) {
207
+ this._rawWrite(chalk.dim(` ${from} → ${to}`));
208
+ this._rawWrite('');
209
+ }
210
+
211
+ graphComplete() {
212
+ this._rawWrite(chalk.green.bold('✓ Workflow completed'));
213
+ }
214
+ }
215
+
216
+ export const timeline = new Timeline();
217
+ export default timeline;