@far-world-labs/verblets 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +42 -0
- package/.husky/pre-commit +4 -0
- package/.release-it.json +9 -0
- package/.vite.config.examples.js +8 -0
- package/.vite.config.js +8 -0
- package/docker-compose.yml +7 -0
- package/docs/README.md +41 -0
- package/docs/babel.config.js +3 -0
- package/docs/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs/blog/2021-08-01-mdx-blog-post.mdx +20 -0
- package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs/blog/2021-08-26-welcome/index.md +25 -0
- package/docs/blog/authors.yml +17 -0
- package/docs/docs/api/bool.md +74 -0
- package/docs/docs/api/search.md +51 -0
- package/docs/docs/intro.md +47 -0
- package/docs/docs/tutorial-basics/_category_.json +8 -0
- package/docs/docs/tutorial-basics/congratulations.md +23 -0
- package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
- package/docs/docs/tutorial-basics/create-a-document.md +57 -0
- package/docs/docs/tutorial-basics/create-a-page.md +43 -0
- package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
- package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
- package/docs/docs/tutorial-extras/_category_.json +7 -0
- package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
- package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
- package/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
- package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
- package/docs/docusaurus.config.js +120 -0
- package/docs/package.json +44 -0
- package/docs/sidebars.js +31 -0
- package/docs/src/components/HomepageFeatures/index.js +61 -0
- package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- package/docs/src/css/custom.css +30 -0
- package/docs/src/pages/index.js +43 -0
- package/docs/src/pages/index.module.css +23 -0
- package/docs/src/pages/markdown-page.md +7 -0
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/docusaurus-social-card.jpg +0 -0
- package/docs/static/img/docusaurus.png +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo.svg +1 -0
- package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- package/package.json +75 -0
- package/scripts/generate-chain/index.js +111 -0
- package/scripts/generate-lib/index.js +68 -0
- package/scripts/generate-test/index.js +111 -0
- package/scripts/generate-verblet/README.md +17 -0
- package/scripts/generate-verblet/index.js +110 -0
- package/scripts/run.sh +15 -0
- package/scripts/runner/index.js +30 -0
- package/scripts/simple-editor/README.md +34 -0
- package/scripts/simple-editor/index.js +68 -0
- package/scripts/summarize-files/index.js +46 -0
- package/src/chains/dismantle/dismantle.examples.js +0 -0
- package/src/chains/dismantle/index.examples.js +30 -0
- package/src/chains/dismantle/index.js +314 -0
- package/src/chains/dismantle/index.spec.js +33 -0
- package/src/chains/list/index.examples.js +72 -0
- package/src/chains/list/index.js +161 -0
- package/src/chains/list/index.spec.js +68 -0
- package/src/chains/list/schema.json +24 -0
- package/src/chains/questions/index.examples.js +68 -0
- package/src/chains/questions/index.js +136 -0
- package/src/chains/questions/index.spec.js +29 -0
- package/src/chains/scan-js/index.js +119 -0
- package/src/chains/sort/index.examples.js +40 -0
- package/src/chains/sort/index.js +113 -0
- package/src/chains/sort/index.spec.js +115 -0
- package/src/chains/summary-map/README.md +33 -0
- package/src/chains/summary-map/index.examples.js +57 -0
- package/src/chains/summary-map/index.js +208 -0
- package/src/chains/summary-map/index.spec.js +78 -0
- package/src/chains/test/index.js +118 -0
- package/src/chains/test-advice/index.js +36 -0
- package/src/constants/common.js +9 -0
- package/src/constants/messages.js +3 -0
- package/src/constants/openai.js +65 -0
- package/src/index.js +33 -0
- package/src/json-schemas/cars-test.json +11 -0
- package/src/json-schemas/index.js +18 -0
- package/src/json-schemas/intent.json +38 -0
- package/src/json-schemas/schema-dot-org-photograph.json +127 -0
- package/src/json-schemas/schema-dot-org-place.json +56 -0
- package/src/lib/any-signal/index.js +28 -0
- package/src/lib/chatgpt/index.js +143 -0
- package/src/lib/editor/index.js +31 -0
- package/src/lib/parse-js-parts/index.js +333 -0
- package/src/lib/parse-js-parts/index.spec.js +156 -0
- package/src/lib/path-aliases/index.js +39 -0
- package/src/lib/path-aliases/index.spec.js +70 -0
- package/src/lib/pave/index.js +34 -0
- package/src/lib/pave/index.spec.js +73 -0
- package/src/lib/prompt-cache/index.js +46 -0
- package/src/lib/retry/index.js +63 -0
- package/src/lib/retry/index.spec.js +86 -0
- package/src/lib/search-best-first/index.js +66 -0
- package/src/lib/search-js-files/code-features-property-definitions.json +123 -0
- package/src/lib/search-js-files/index.examples.js +22 -0
- package/src/lib/search-js-files/index.js +158 -0
- package/src/lib/search-js-files/index.spec.js +34 -0
- package/src/lib/search-js-files/scan-file.js +253 -0
- package/src/lib/shorten-text/index.js +30 -0
- package/src/lib/shorten-text/index.spec.js +68 -0
- package/src/lib/strip-numeric/index.js +5 -0
- package/src/lib/strip-response/index.js +35 -0
- package/src/lib/timed-abort-controller/index.js +41 -0
- package/src/lib/to-bool/index.js +8 -0
- package/src/lib/to-enum/index.js +14 -0
- package/src/lib/to-number/index.js +12 -0
- package/src/lib/to-number-with-units/index.js +51 -0
- package/src/lib/transcribe/index.js +61 -0
- package/src/prompts/README.md +15 -0
- package/src/prompts/as-enum.js +5 -0
- package/src/prompts/as-json-schema.js +9 -0
- package/src/prompts/as-object-with-schema.js +31 -0
- package/src/prompts/as-schema-org-text.js +17 -0
- package/src/prompts/as-schema-org-type.js +1 -0
- package/src/prompts/blog-post.js +7 -0
- package/src/prompts/code-features.js +28 -0
- package/src/prompts/constants.js +101 -0
- package/src/prompts/features-json-schema.js +27 -0
- package/src/prompts/generate-collection.js +26 -0
- package/src/prompts/generate-list.js +48 -0
- package/src/prompts/generate-questions.js +19 -0
- package/src/prompts/index.js +20 -0
- package/src/prompts/intent.js +66 -0
- package/src/prompts/output-succinct-names.js +3 -0
- package/src/prompts/select-from-threshold.js +18 -0
- package/src/prompts/sort.js +35 -0
- package/src/prompts/style.js +41 -0
- package/src/prompts/summarize.js +13 -0
- package/src/prompts/token-budget.js +3 -0
- package/src/prompts/wrap-list.js +14 -0
- package/src/prompts/wrap-variable.js +36 -0
- package/src/services/llm-model/index.js +114 -0
- package/src/services/llm-model/model.js +21 -0
- package/src/services/redis/index.js +84 -0
- package/src/verblets/auto/index.examples.js +28 -0
- package/src/verblets/auto/index.js +28 -0
- package/src/verblets/auto/index.spec.js +34 -0
- package/src/verblets/bool/index.examples.js +28 -0
- package/src/verblets/bool/index.js +28 -0
- package/src/verblets/bool/index.schema.json +14 -0
- package/src/verblets/bool/index.spec.js +35 -0
- package/src/verblets/enum/index.examples.js +33 -0
- package/src/verblets/enum/index.js +15 -0
- package/src/verblets/enum/index.spec.js +35 -0
- package/src/verblets/intent/index.examples.js +51 -0
- package/src/verblets/intent/index.js +72 -0
- package/src/verblets/intent/index.spec.js +31 -0
- package/src/verblets/number/index.examples.js +33 -0
- package/src/verblets/number/index.js +22 -0
- package/src/verblets/number/index.spec.js +35 -0
- package/src/verblets/number-with-units/index.examples.js +34 -0
- package/src/verblets/number-with-units/index.js +19 -0
- package/src/verblets/number-with-units/index.spec.js +46 -0
- package/src/verblets/schema-org/index.examples.js +56 -0
- package/src/verblets/schema-org/index.js +8 -0
- package/src/verblets/schema-org/index.spec.js +39 -0
- package/src/verblets/to-object/README.md +38 -0
- package/src/verblets/to-object/index.examples.js +29 -0
- package/src/verblets/to-object/index.js +136 -0
- package/src/verblets/to-object/index.spec.js +74 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import * as R from 'ramda';
|
|
3
|
+
|
|
4
|
+
import { cacheTTL } from '../../constants/openai.js';
|
|
5
|
+
|
|
6
|
+
const variableKeys = ['created', 'id', 'max_tokens', 'usage'];
|
|
7
|
+
|
|
8
|
+
const sortKeys = (data) => {
|
|
9
|
+
const sortedData = R.sortBy(R.identity, R.keys(data)).reduce((acc, key) => {
|
|
10
|
+
acc[key] = key === 'messages' ? data[key]?.[0]?.content : data[key];
|
|
11
|
+
return acc;
|
|
12
|
+
}, {});
|
|
13
|
+
|
|
14
|
+
return sortedData;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const toKey = (data) => {
|
|
18
|
+
return crypto
|
|
19
|
+
.createHash('sha256')
|
|
20
|
+
.update(JSON.stringify(sortKeys(data)))
|
|
21
|
+
.digest('hex')
|
|
22
|
+
.toString();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const get = async (redis, inputData) => {
|
|
26
|
+
const resultFromRedis = await redis.get(
|
|
27
|
+
toKey(R.omit(variableKeys, inputData))
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
const foundInRedis = !!resultFromRedis;
|
|
31
|
+
|
|
32
|
+
let result;
|
|
33
|
+
if (foundInRedis) {
|
|
34
|
+
result = JSON.parse(resultFromRedis);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { created: !foundInRedis, result };
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const set = async (redis, inputData, outputData) => {
|
|
41
|
+
await redis.set(
|
|
42
|
+
toKey(R.omit(variableKeys, inputData)),
|
|
43
|
+
JSON.stringify(outputData),
|
|
44
|
+
{ EX: cacheTTL }
|
|
45
|
+
);
|
|
46
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
import {
|
|
3
|
+
maxRetries as maxRetriesDefault,
|
|
4
|
+
retryDelay as retryDelayDefault,
|
|
5
|
+
} from '../../constants/common.js';
|
|
6
|
+
|
|
7
|
+
export default async (
|
|
8
|
+
fn,
|
|
9
|
+
{
|
|
10
|
+
label = '',
|
|
11
|
+
maxRetries = maxRetriesDefault,
|
|
12
|
+
retryDelay = retryDelayDefault,
|
|
13
|
+
retryOnAll = true,
|
|
14
|
+
} = {}
|
|
15
|
+
) => {
|
|
16
|
+
let retry = 0;
|
|
17
|
+
let lastError = new Error('Nothing to run');
|
|
18
|
+
|
|
19
|
+
// eslint-disable-next-line no-promise-executor-return
|
|
20
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
|
|
22
|
+
const labelDisplay = label ? `"${label}"` : '';
|
|
23
|
+
|
|
24
|
+
while (retry <= maxRetries) {
|
|
25
|
+
try {
|
|
26
|
+
if (label) {
|
|
27
|
+
const variables = [`retry: ${retry}`].join(', ');
|
|
28
|
+
const startTag = `${retry > 0 ? 'retry' : 'started'}`;
|
|
29
|
+
const startVariablesDisplay = `${retry > 0 ? ` (${variables})` : ''}`;
|
|
30
|
+
console.error(
|
|
31
|
+
`Run ${labelDisplay} [${startTag}]${startVariablesDisplay}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const result = await fn();
|
|
36
|
+
|
|
37
|
+
if (label) {
|
|
38
|
+
console.error(`Run ${labelDisplay} [complete]`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return result;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
lastError = error;
|
|
44
|
+
|
|
45
|
+
const isRetry =
|
|
46
|
+
retryOnAll || (error.response && error.response.status === 429);
|
|
47
|
+
|
|
48
|
+
if (isRetry) {
|
|
49
|
+
await sleep(retryDelay * retry);
|
|
50
|
+
retry += 1;
|
|
51
|
+
} else {
|
|
52
|
+
retry = maxRetries;
|
|
53
|
+
}
|
|
54
|
+
const doneTag = `${retry >= maxRetries ? 'abort' : 'retry'}`;
|
|
55
|
+
|
|
56
|
+
if (label) {
|
|
57
|
+
console.error(`Run ${labelDisplay} [${doneTag}]: ${error.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw lastError;
|
|
63
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import retry from './index.js';
|
|
4
|
+
|
|
5
|
+
const retryDelayGlobal = 10;
|
|
6
|
+
|
|
7
|
+
const mockFn = () => {
|
|
8
|
+
return 'Success';
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
describe('Retry', () => {
|
|
12
|
+
it('Succeeds on first attempt', async () => {
|
|
13
|
+
const result = await retry(mockFn, { retryDelay: retryDelayGlobal });
|
|
14
|
+
expect(result).toStrictEqual('Success');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('Succeeds after retrying', async () => {
|
|
18
|
+
let callCount = 0;
|
|
19
|
+
const fn = async () => {
|
|
20
|
+
callCount += 1;
|
|
21
|
+
if (callCount === 1) {
|
|
22
|
+
const error = new Error('Error 429');
|
|
23
|
+
error.response = { status: 429 };
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
return 'Success';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const result = await retry(fn, { retryDelay: retryDelayGlobal });
|
|
30
|
+
expect(result).toStrictEqual('Success');
|
|
31
|
+
expect(callCount).toStrictEqual(2);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('Fails after maxRetries', async () => {
|
|
35
|
+
const maxRetries = 2;
|
|
36
|
+
let callCount = 0;
|
|
37
|
+
|
|
38
|
+
const fn = async () => {
|
|
39
|
+
callCount += 1;
|
|
40
|
+
const error = new Error('Error 429');
|
|
41
|
+
error.response = { status: 429 };
|
|
42
|
+
throw error;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await retry(fn, { maxRetries, retryDelay: retryDelayGlobal });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
expect(error.message).toStrictEqual('Error 429');
|
|
49
|
+
expect(callCount).toStrictEqual(maxRetries + 1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('Throws non-retryable error', async () => {
|
|
54
|
+
const mockFnWithOtherError = () => {
|
|
55
|
+
throw new Error('Error 500');
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await retry(mockFnWithOtherError, { retryDelay: retryDelayGlobal });
|
|
60
|
+
} catch (error) {
|
|
61
|
+
expect(error.message).toStrictEqual('Error 500');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('Retries on all errors when retryOnAll is true', async () => {
|
|
66
|
+
let callCount = 0;
|
|
67
|
+
const maxRetries = 2;
|
|
68
|
+
const fn = async () => {
|
|
69
|
+
callCount += 1;
|
|
70
|
+
const error = new Error('Error 500');
|
|
71
|
+
error.response = { status: 500 };
|
|
72
|
+
throw error;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await retry(fn, {
|
|
77
|
+
maxRetries,
|
|
78
|
+
retryDelay: retryDelayGlobal,
|
|
79
|
+
retryOnAll: true,
|
|
80
|
+
});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
expect(error.message).toStrictEqual('Error 500');
|
|
83
|
+
expect(callCount).toStrictEqual(maxRetries + 1);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import * as R from 'ramda';
|
|
2
|
+
|
|
3
|
+
const hasOwnToString = (obj) => {
|
|
4
|
+
return obj.toString !== Object.prototype.toString;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const visitDefault = () => {
|
|
8
|
+
return Promise.reject(new Error('Not Implemented'));
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const nextDefault = () => {
|
|
12
|
+
return Promise.reject(new Error('Not Implemented'));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const rankDefault = () => {
|
|
16
|
+
return Promise.reject(new Error('Not Implemented'));
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const filterWith = (state) => (nextNode) => {
|
|
20
|
+
return !state.visited.has(
|
|
21
|
+
hasOwnToString(nextNode) ? nextNode.toString() : nextNode
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default async ({
|
|
26
|
+
next = nextDefault,
|
|
27
|
+
node: rootNode,
|
|
28
|
+
rank = rankDefault,
|
|
29
|
+
state: stateInitial = {},
|
|
30
|
+
visit = visitDefault,
|
|
31
|
+
}) => {
|
|
32
|
+
let nodesTodo = [rootNode];
|
|
33
|
+
let state = stateInitial;
|
|
34
|
+
if (!state.visited) {
|
|
35
|
+
state.visited = new Set();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
while (nodesTodo.length > 0) {
|
|
39
|
+
// eslint-disable-next-line no-await-in-loop
|
|
40
|
+
const nodesRanked = await rank({ nodes: nodesTodo, state });
|
|
41
|
+
const node = nodesRanked.shift();
|
|
42
|
+
|
|
43
|
+
const nodesTodoNext = nodesTodo.filter((el) =>
|
|
44
|
+
hasOwnToString(node) ? el.toString() !== node.toString() : el !== node
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
state.visited.add(hasOwnToString(node) ? node.toString() : node);
|
|
48
|
+
|
|
49
|
+
// eslint-disable-next-line no-await-in-loop
|
|
50
|
+
state = await visit({ node, state });
|
|
51
|
+
|
|
52
|
+
// eslint-disable-next-line no-await-in-loop
|
|
53
|
+
const nextNodes = await next({ node, state });
|
|
54
|
+
|
|
55
|
+
nodesTodo = R.unionWith(
|
|
56
|
+
(nodeA, nodeB) =>
|
|
57
|
+
hasOwnToString(nodeA)
|
|
58
|
+
? nodeA.toString() === nodeB.toString()
|
|
59
|
+
: nodeA === nodeB,
|
|
60
|
+
nodesTodoNext,
|
|
61
|
+
nextNodes.filter(filterWith(state))
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return state;
|
|
66
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
[
|
|
2
|
+
{
|
|
3
|
+
"name": "singlePurpose",
|
|
4
|
+
"criteria": "Ensure each function performs only one task, avoiding unrelated operations within the function.",
|
|
5
|
+
"score0": "0.0 indicates the function combines multiple tasks, like reading data, processing it, and displaying results.",
|
|
6
|
+
"score1": "1.0 indicates the function has a clear single purpose, such as reading data or validating input."
|
|
7
|
+
}, {
|
|
8
|
+
"name": "clearNaming",
|
|
9
|
+
"criteria": "Verify function names clearly describe their purpose, making it easier to identify their responsibilities.",
|
|
10
|
+
"score0": "0.0 indicates the function name like getUserData accurately reflects its purpose, improving code readability.",
|
|
11
|
+
"score1": "1.0 indicates the function name like processData is vague or misleading, making it difficult to understand its purpose."
|
|
12
|
+
}, {
|
|
13
|
+
"name": "smallSize",
|
|
14
|
+
"criteria": "Check if functions are short and focused.",
|
|
15
|
+
"score0": "0.0 indicates the function is concise, having 10 or fewer lines of code.",
|
|
16
|
+
"score1": "1.0 indicates the function is excessively long, with over 50 lines of code."
|
|
17
|
+
}, {
|
|
18
|
+
"name": "limitedParameters",
|
|
19
|
+
"criteria": "Examine the function parameters; a high number may suggest additional responsibilities.",
|
|
20
|
+
"score0": "0.0 indicates the function has 0-2 parameters, allowing for easy understanding and use.",
|
|
21
|
+
"score1": "1.0 indicates the function has more than 5 parameters, leading to confusion and decreased readability."
|
|
22
|
+
}, {
|
|
23
|
+
"name": "interfaceToImplementation",
|
|
24
|
+
"criteria": "Examine the interface to the input and output. Does the difficulty of the interface justify the power of its implementation?",
|
|
25
|
+
"score0": "0.0 indicates the interface is simple and efficient, providing a powerful implementation without unnecessary complexity",
|
|
26
|
+
"score1": "1.0 indicates the interface is overly complex or convoluted relative to the complexity of its implementation."
|
|
27
|
+
}, {
|
|
28
|
+
"name": "noSideEffects",
|
|
29
|
+
"criteria": "Ensure functions don't produce unexpected changes to external state; check for global variable modifications.",
|
|
30
|
+
"score0": "0.0 indicates the function has no side effects, as it doesn't modify global variables or external state.",
|
|
31
|
+
"score1": "1.0 indicates the function modifies global variables or external state, causing unpredictability."
|
|
32
|
+
}, {
|
|
33
|
+
"name": "reusability",
|
|
34
|
+
"criteria": "Verify if functions are modular and can be reused easily in different contexts.",
|
|
35
|
+
"score0": "0.0 indicates the function is generic, like a formatDate function that can be used in multiple scenarios.",
|
|
36
|
+
"score1": "1.0 indicates the function is tightly coupled to a specific module, limiting its reusability."
|
|
37
|
+
}, {
|
|
38
|
+
"name": "lowCoupling",
|
|
39
|
+
"criteria": "Check for minimal dependencies between functions, making them more independent and maintainable.",
|
|
40
|
+
"score0": "0.0 indicates the function has no dependencies on other functions, making it easy to use and maintain.",
|
|
41
|
+
"score1": "1.0 indicates the function relies heavily on other functions, increasing complexity and reducing maintainability."
|
|
42
|
+
}, {
|
|
43
|
+
"name": "separationOfConcerns",
|
|
44
|
+
"criteria": "Verify if distinct responsibilities are organized into separate functions or modules.",
|
|
45
|
+
"score0": "0.0 indicates the function is well-organized, with distinct responsibilities like data fetching or validation in separate functions.",
|
|
46
|
+
"score1": "1.0 indicates the function combines multiple responsibilities like data fetching and processing, leading to confusion."
|
|
47
|
+
}, {
|
|
48
|
+
"name": "easyTestability",
|
|
49
|
+
"criteria": "Check if functions can be tested independently.",
|
|
50
|
+
"score0": "0.0 indicates the function is stateless and doesn't depend on external systems, making testing straightforward.",
|
|
51
|
+
"score1": "1.0 indicates the function is difficult to test, possibly due to complex dependencies or state management."
|
|
52
|
+
}, {
|
|
53
|
+
"name": "mixedAbstractionLevels",
|
|
54
|
+
"criteria": "Check if the function speaks in a consistent level of abstraction, avoiding mixing high-level logic with low-level implementation details.",
|
|
55
|
+
"score0": "0.0 indicates the function has a highly-consistent level of abstraction",
|
|
56
|
+
"score1": "1.0 indicates the function hopelessly mixes high-level logic with low-level implementation details"
|
|
57
|
+
}, {
|
|
58
|
+
"name": "mixedConcepts",
|
|
59
|
+
"criteria": "Check if the function speaks in a consistent topic, neither confusing the metaphors or changing topics.",
|
|
60
|
+
"score0": "0.0 indicates the function maintains a consistent topic, like handling user authentication without mixing unrelated topics.",
|
|
61
|
+
"score1": "1.0 indicates the function changes topics or mixes metaphors, such as combining user authentication with unrelated tasks like data processing."
|
|
62
|
+
}, {
|
|
63
|
+
"name": "businessLogic",
|
|
64
|
+
"criteria": "Look for the presence of business logic within the function, ensuring it's clearly defined and separated from other concerns.",
|
|
65
|
+
"score0": "0.0 indicates the function has clearly defined business logic, separate from other concerns like data access or UI rendering.",
|
|
66
|
+
"score1": "1.0 indicates the function mixes business logic with other concerns, like data access, making it difficult to understand and maintain."
|
|
67
|
+
}, {
|
|
68
|
+
"name": "networkCalls",
|
|
69
|
+
"criteria": "Check if the function makes network calls, and if so, ensure they are handled appropriately.",
|
|
70
|
+
"score0": "0.0 indicates the function doesn't make network calls or handles them correctly, separating concerns and managing errors.",
|
|
71
|
+
"score1": "1.0 indicates the function makes network calls without proper error handling or mixes them with unrelated responsibilities."
|
|
72
|
+
}, {
|
|
73
|
+
"name": "diskCalls",
|
|
74
|
+
"criteria": "Examine if the function interacts with the filesystem, and if so, ensure it's done appropriately.",
|
|
75
|
+
"score0": "0.0 indicates the function doesn't interact with the filesystem or does so with proper error handling and separation of concerns.",
|
|
76
|
+
"score1": "1.0 indicates the function interacts with the filesystem inappropriately, mixing concerns or lacking error handling."
|
|
77
|
+
}, {
|
|
78
|
+
"name": "databaseCalls",
|
|
79
|
+
"criteria": "Verify if the function makes database calls, and if so, ensure they are managed appropriately.",
|
|
80
|
+
"score0": "0.0 indicates the function doesn't make database calls or handles them correctly, with proper separation of concerns and error handling.",
|
|
81
|
+
"score1": "1.0 indicates the function makes database calls without proper error handling or mixes them with unrelated responsibilities."
|
|
82
|
+
}, {
|
|
83
|
+
"name": "userInput",
|
|
84
|
+
"criteria": "Check if the function handles user input, and if so, ensure input validation and proper error handling are implemented.",
|
|
85
|
+
"score0": "0.0 indicates the function doesn't handle user input or handles it correctly with proper validation and error handling.",
|
|
86
|
+
"score1": "1.0 indicates the function handles user input without proper validation or error handling, potentially leading to security risks."
|
|
87
|
+
}, {
|
|
88
|
+
"name": "primitiveObsession",
|
|
89
|
+
"criteria": "Check for the use of primitives when objects or more descriptive data structures would be better, especially in interfaces to other modules.",
|
|
90
|
+
"score0": "0.0 indicates the function uses appropriate data structures or objects instead of primitives, improving code readability and maintainability.",
|
|
91
|
+
"score1": "1.0 indicates the function relies heavily on primitives, making the code less readable and harder to maintain."
|
|
92
|
+
}, {
|
|
93
|
+
"name": "chatGPTUsage",
|
|
94
|
+
"criteria": "How frequently and effectively is the chatGPT function used in the code?",
|
|
95
|
+
"score0": "0.0 indicates the chatGPT function is not used or used ineffectively.",
|
|
96
|
+
"score1": "1indicates the chatGPT function is used frequently and effectively."
|
|
97
|
+
}, {
|
|
98
|
+
"name": "modelBudgetAndTokenManagement",
|
|
99
|
+
"criteria": "How well does the code handle model budgeting and token management?",
|
|
100
|
+
"score0": "0.0 indicates poor management of model budget and token management.",
|
|
101
|
+
"score1": "1indicates efficient management of model budget and token management."
|
|
102
|
+
}, {
|
|
103
|
+
"name": "textProcessingUtilities",
|
|
104
|
+
"criteria": "How well does the code utilize text processing utilities for tasks like shortening or sanitizing text?",
|
|
105
|
+
"score0": "0.0 indicates not using text processing utilities or using them ineffectively.",
|
|
106
|
+
"score1": "1indicates using text processing utilities effectively. Prompt text, token management, prompt template literals, and prompt string concatenations are another category--they are not considered text processing utilities."
|
|
107
|
+
}, {
|
|
108
|
+
"name": "promptConstruction",
|
|
109
|
+
"criteria": "How well does the code construct prompts using template literals, string concatenation, or constant declarations?",
|
|
110
|
+
"score0": "0.0 indicates poorly constructed prompts.",
|
|
111
|
+
"score1": "1indicates well-constructed prompts."
|
|
112
|
+
}, {
|
|
113
|
+
"name": "textWrappingUtilities",
|
|
114
|
+
"criteria": "How effectively does the code use text wrapping utilities for managing input and output text?",
|
|
115
|
+
"score0": "0.0 indicates not using text wrapping utilities or using them ineffectively.",
|
|
116
|
+
"score1": "1indicates using text wrapping utilities effectively."
|
|
117
|
+
}, {
|
|
118
|
+
"name": "spellingAndGrammar",
|
|
119
|
+
"criteria": "Check for spelling or grammar mistakes in any English text that is written.",
|
|
120
|
+
"score0": "0.0 indicates the text has no spelling or grammar mistakes, ensuring readability and clarity.",
|
|
121
|
+
"score1": "1indicates the text is filled with spelling or grammar mistakes, to the point of incomprehensibility."
|
|
122
|
+
}
|
|
123
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import search from './index.js';
|
|
4
|
+
|
|
5
|
+
const examples = [
|
|
6
|
+
{
|
|
7
|
+
inputs: { filename: './src/lib/chatgpt/index.js' },
|
|
8
|
+
want: { foundFiles: 30 },
|
|
9
|
+
},
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
describe('Scan JS repo with best-first search', () => {
|
|
13
|
+
examples.forEach((example) => {
|
|
14
|
+
it(example.inputs.text, async () => {
|
|
15
|
+
const result = await search({ node: example.inputs });
|
|
16
|
+
|
|
17
|
+
if (example.want.foundFiles) {
|
|
18
|
+
expect(result.visited.size).toBeGreaterThan(example.want.foundFiles);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import parseJSParts from '../parse-js-parts/index.js';
|
|
6
|
+
import search from '../search-best-first/index.js';
|
|
7
|
+
|
|
8
|
+
export class Node {
|
|
9
|
+
constructor(args) {
|
|
10
|
+
Object.assign(this, args);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
toString() {
|
|
14
|
+
return `${this.filename}:::${this.functionName ?? ''}`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const processLocalImport = async (source) => {
|
|
19
|
+
const importedFile = await fs.readFile(source, 'utf8');
|
|
20
|
+
const parsedImport = parseJSParts(source, importedFile);
|
|
21
|
+
return Object.entries(parsedImport.functionsMap).map(
|
|
22
|
+
([importKey, importValue]) => ({
|
|
23
|
+
filename: source,
|
|
24
|
+
functionName: importValue?.functionName ?? importKey,
|
|
25
|
+
functionData: importValue,
|
|
26
|
+
})
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const processNpmImport = async (source, includeNodeModules = false) => {
|
|
31
|
+
if (!includeNodeModules) return [];
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const packageJson = JSON.parse(await fs.readFile('./package.json', 'utf8'));
|
|
35
|
+
if (
|
|
36
|
+
packageJson.dependencies[source] ||
|
|
37
|
+
packageJson.devDependencies[source]
|
|
38
|
+
) {
|
|
39
|
+
const nodeModulePath = `./node_modules/${source}`;
|
|
40
|
+
const npmPackageJson = JSON.parse(
|
|
41
|
+
await fs.readFile(`${nodeModulePath}/package.json`, 'utf8')
|
|
42
|
+
);
|
|
43
|
+
const mainFilePath = npmPackageJson.main || 'index.js';
|
|
44
|
+
const importedFile = await fs.readFile(
|
|
45
|
+
`${nodeModulePath}/${mainFilePath}`,
|
|
46
|
+
'utf-8'
|
|
47
|
+
);
|
|
48
|
+
const parsedImport = parseJSParts(mainFilePath, importedFile);
|
|
49
|
+
|
|
50
|
+
return Object.entries(parsedImport.functionsMap).map(
|
|
51
|
+
([importKey, importValue]) => ({
|
|
52
|
+
filename: `${nodeModulePath}/${mainFilePath}`,
|
|
53
|
+
functionName: importKey,
|
|
54
|
+
functionData: importValue,
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(
|
|
60
|
+
`Process npm import [error]: ${error.message} (source: ${source})`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return [];
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const visitDefault = ({ state }) => {
|
|
68
|
+
if (process.env.NODE_ENV === 'development') {
|
|
69
|
+
// console.error(`Visiting: ${node.filename} - ${node.functionName}`);
|
|
70
|
+
}
|
|
71
|
+
return state;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const rank = ({ nodes }) => {
|
|
75
|
+
// Example: Rank by the length of the function name
|
|
76
|
+
return nodes.sort(
|
|
77
|
+
(a, b) =>
|
|
78
|
+
(a.functionName ?? a.filename).length -
|
|
79
|
+
(b.functionName ?? b.filename).length
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const prepareNext = async ({ node, includeNodeModules }) => {
|
|
84
|
+
const code = await fs.readFile(node.filename, 'utf-8');
|
|
85
|
+
const parsed = parseJSParts(node.filename, code);
|
|
86
|
+
|
|
87
|
+
const functionsFound = Object.entries(parsed.functionsMap).map(
|
|
88
|
+
([, value]) => {
|
|
89
|
+
return new Node({
|
|
90
|
+
...value,
|
|
91
|
+
filename: node.filename,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const importPromises = Object.values(parsed.importsMap).map((importData) => {
|
|
97
|
+
if (
|
|
98
|
+
importData.source.startsWith('./') ||
|
|
99
|
+
importData.source.startsWith('../') ||
|
|
100
|
+
importData.source.startsWith('/')
|
|
101
|
+
) {
|
|
102
|
+
const resolvedPath = path.resolve(
|
|
103
|
+
path.dirname(node.filename),
|
|
104
|
+
importData.source
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
return processLocalImport(resolvedPath);
|
|
108
|
+
}
|
|
109
|
+
return processNpmImport(importData.source, includeNodeModules);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const importFunctions = await Promise.all(importPromises);
|
|
113
|
+
const importsFound = importFunctions.reduce((acc, importedFuncs) => {
|
|
114
|
+
return [
|
|
115
|
+
...acc,
|
|
116
|
+
...importedFuncs.map(
|
|
117
|
+
(f) =>
|
|
118
|
+
new Node({
|
|
119
|
+
...f,
|
|
120
|
+
...f.functionData,
|
|
121
|
+
functionData: undefined,
|
|
122
|
+
// not including function names in the imports knowing we will scan the file for them better
|
|
123
|
+
functionName: undefined,
|
|
124
|
+
})
|
|
125
|
+
),
|
|
126
|
+
];
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
functions: functionsFound,
|
|
131
|
+
imports: importsFound,
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const nextDefault = ({ state }) => {
|
|
136
|
+
return state.jsElements.imports.concat(state.jsElements.functions);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
export default ({
|
|
140
|
+
next: nextExternal = nextDefault,
|
|
141
|
+
node: nodeInitial,
|
|
142
|
+
visit: visitExternal = visitDefault,
|
|
143
|
+
includeNodeModules,
|
|
144
|
+
...options
|
|
145
|
+
}) => {
|
|
146
|
+
return search({
|
|
147
|
+
...options,
|
|
148
|
+
next: async ({ node, state }) => {
|
|
149
|
+
const jsElements = await prepareNext({ node, includeNodeModules });
|
|
150
|
+
return nextExternal({ node, state: { jsElements, ...state } });
|
|
151
|
+
},
|
|
152
|
+
node: new Node(nodeInitial),
|
|
153
|
+
rank,
|
|
154
|
+
visit: ({ node, state }) => {
|
|
155
|
+
return visitExternal({ node, state });
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import search from './index.js';
|
|
4
|
+
|
|
5
|
+
vi.mock('../../lib/chatgpt/index.js', () => ({
|
|
6
|
+
default: vi.fn().mockImplementation((text) => {
|
|
7
|
+
if (/prompt text to match/.test(text)) {
|
|
8
|
+
return 'True';
|
|
9
|
+
}
|
|
10
|
+
return 'undefined';
|
|
11
|
+
}),
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
const examples = [
|
|
15
|
+
{
|
|
16
|
+
name: 'Basic usage',
|
|
17
|
+
inputs: { filename: './src/lib/chatgpt/index.js' },
|
|
18
|
+
want: { result: true },
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
describe('Scan JS repo with best-first search', () => {
|
|
23
|
+
examples.forEach((example) => {
|
|
24
|
+
it(example.name, async () => {
|
|
25
|
+
const result = await search({
|
|
26
|
+
node: { filename: example.inputs.filename },
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (example.want.typeOfResult) {
|
|
30
|
+
expect(typeof result).toStrictEqual(example.want.typeOfResult);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
});
|