@promptbook/cli 0.112.0-36 → 0.112.0-39
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 +10 -6
- package/esm/index.es.js +1079 -437
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/find-refactor-candidates/RefactorCandidateLevel.d.ts +45 -0
- package/esm/scripts/find-refactor-candidates/analyzeSourceFileForRefactorCandidate.d.ts +5 -0
- package/esm/scripts/find-refactor-candidates/find-refactor-candidates.constants.d.ts +2 -14
- package/esm/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +13 -1
- package/esm/scripts/find-refactor-candidates/findRefactorCandidatesInProject.d.ts +23 -1
- package/esm/scripts/find-refactor-candidates/resolveRefactorCandidateProject.d.ts +23 -0
- package/esm/src/_packages/components.index.d.ts +2 -0
- package/esm/src/_packages/types.index.d.ts +48 -46
- package/esm/src/book-components/Chat/Chat/ChatActionsBar.d.ts +7 -0
- package/esm/src/book-components/Chat/Chat/ChatActionsBar.test.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatInputArea.d.ts +4 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageItem.d.ts +8 -0
- package/esm/src/book-components/Chat/Chat/ChatMessageList.d.ts +2 -0
- package/esm/src/book-components/Chat/Chat/ChatProps.d.ts +50 -1
- package/esm/src/book-components/Chat/Chat/ChatReplyPreview.d.ts +19 -0
- package/esm/src/book-components/Chat/Chat/createProgressCardChecklistMarkdown.d.ts +2 -2
- package/esm/src/book-components/Chat/MockedChat/MockedChat.d.ts +1 -1
- package/esm/src/book-components/Chat/types/ChatMessage.d.ts +35 -0
- package/esm/src/book-components/Chat/utils/resolveChatMessageReplyPreviewText.d.ts +25 -0
- package/esm/src/book-components/Chat/utils/resolveChatMessageReplySenderLabel.d.ts +12 -0
- package/esm/src/cli/cli-commands/coder/agentCodingFile.d.ts +14 -0
- package/esm/src/cli/cli-commands/coder/agentsFile.d.ts +12 -0
- package/esm/src/cli/cli-commands/coder/appendBlock.d.ts +6 -0
- package/esm/src/cli/cli-commands/coder/boilerplateTemplates.d.ts +10 -0
- package/esm/src/cli/cli-commands/coder/ensureCoderEnvFile.d.ts +15 -0
- package/esm/src/cli/cli-commands/coder/ensureCoderGitignoreFile.d.ts +7 -0
- package/esm/src/cli/cli-commands/coder/ensureCoderMarkdownFile.d.ts +7 -0
- package/esm/src/cli/cli-commands/coder/ensureCoderPackageJsonFile.d.ts +7 -0
- package/esm/src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.d.ts +7 -0
- package/esm/src/cli/cli-commands/coder/ensureDirectory.d.ts +7 -0
- package/esm/src/cli/cli-commands/coder/find-refactor-candidates.d.ts +1 -1
- package/esm/src/cli/cli-commands/coder/find-refactor-candidates.test.d.ts +1 -0
- package/esm/src/cli/cli-commands/coder/formatDisplayPath.d.ts +6 -0
- package/esm/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.d.ts +6 -0
- package/esm/src/cli/cli-commands/coder/getDefaultCoderVscodeSettings.d.ts +6 -0
- package/esm/src/cli/cli-commands/coder/init.d.ts +3 -38
- package/esm/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +25 -0
- package/esm/src/cli/cli-commands/coder/mergeStringRecordJsonFile.d.ts +18 -0
- package/esm/src/cli/cli-commands/coder/printInitializationSummary.d.ts +7 -0
- package/esm/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +6 -0
- package/esm/src/types/string_agent_url.d.ts +7 -0
- package/esm/src/types/string_agent_url_private.d.ts +9 -0
- package/esm/src/types/string_base64.d.ts +13 -0
- package/esm/src/types/string_base64_private.d.ts +2 -2
- package/esm/src/types/string_base_url.d.ts +7 -0
- package/esm/src/types/string_base_url_private.d.ts +9 -0
- package/esm/src/types/string_email.d.ts +13 -0
- package/esm/src/types/string_email_private.d.ts +2 -2
- package/esm/src/types/string_host.d.ts +42 -0
- package/esm/src/types/string_host_private.d.ts +7 -7
- package/esm/src/types/string_href.d.ts +19 -0
- package/esm/src/types/string_href_private.d.ts +24 -0
- package/esm/src/types/string_mime_type.d.ts +15 -0
- package/esm/src/types/string_mime_type_private.d.ts +2 -2
- package/esm/src/types/string_pipeline_root_url.d.ts +7 -0
- package/esm/src/types/string_pipeline_root_url_private.d.ts +9 -0
- package/esm/src/types/string_pipeline_url.d.ts +13 -0
- package/esm/src/types/string_pipeline_url_private.d.ts +17 -0
- package/esm/src/types/string_promptbook_server_url.d.ts +7 -0
- package/esm/src/types/string_promptbook_server_url_private.d.ts +9 -0
- package/esm/src/types/string_url.d.ts +14 -141
- package/esm/src/types/string_url_image.d.ts +7 -0
- package/esm/src/types/string_url_image_private.d.ts +9 -0
- package/esm/src/types/string_url_private.d.ts +0 -80
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -1
- package/umd/index.umd.js +1082 -440
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/find-refactor-candidates/RefactorCandidateLevel.d.ts +45 -0
- package/umd/scripts/find-refactor-candidates/analyzeSourceFileForRefactorCandidate.d.ts +5 -0
- package/umd/scripts/find-refactor-candidates/find-refactor-candidates.constants.d.ts +2 -14
- package/umd/scripts/find-refactor-candidates/find-refactor-candidates.d.ts +13 -1
- package/umd/scripts/find-refactor-candidates/findRefactorCandidatesInProject.d.ts +23 -1
- package/umd/scripts/find-refactor-candidates/resolveRefactorCandidateProject.d.ts +23 -0
- package/umd/src/_packages/components.index.d.ts +2 -0
- package/umd/src/_packages/types.index.d.ts +48 -46
- package/umd/src/book-components/Chat/Chat/ChatActionsBar.d.ts +7 -0
- package/umd/src/book-components/Chat/Chat/ChatActionsBar.test.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatInputArea.d.ts +4 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageItem.d.ts +8 -0
- package/umd/src/book-components/Chat/Chat/ChatMessageList.d.ts +2 -0
- package/umd/src/book-components/Chat/Chat/ChatProps.d.ts +50 -1
- package/umd/src/book-components/Chat/Chat/ChatReplyPreview.d.ts +19 -0
- package/umd/src/book-components/Chat/Chat/createProgressCardChecklistMarkdown.d.ts +2 -2
- package/umd/src/book-components/Chat/MockedChat/MockedChat.d.ts +1 -1
- package/umd/src/book-components/Chat/types/ChatMessage.d.ts +35 -0
- package/umd/src/book-components/Chat/utils/resolveChatMessageReplyPreviewText.d.ts +25 -0
- package/umd/src/book-components/Chat/utils/resolveChatMessageReplySenderLabel.d.ts +12 -0
- package/umd/src/cli/cli-commands/coder/agentCodingFile.d.ts +14 -0
- package/umd/src/cli/cli-commands/coder/agentsFile.d.ts +12 -0
- package/umd/src/cli/cli-commands/coder/appendBlock.d.ts +6 -0
- package/umd/src/cli/cli-commands/coder/boilerplateTemplates.d.ts +10 -0
- package/umd/src/cli/cli-commands/coder/ensureCoderEnvFile.d.ts +15 -0
- package/umd/src/cli/cli-commands/coder/ensureCoderGitignoreFile.d.ts +7 -0
- package/umd/src/cli/cli-commands/coder/ensureCoderMarkdownFile.d.ts +7 -0
- package/umd/src/cli/cli-commands/coder/ensureCoderPackageJsonFile.d.ts +7 -0
- package/umd/src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.d.ts +7 -0
- package/umd/src/cli/cli-commands/coder/ensureDirectory.d.ts +7 -0
- package/umd/src/cli/cli-commands/coder/find-refactor-candidates.d.ts +1 -1
- package/umd/src/cli/cli-commands/coder/find-refactor-candidates.test.d.ts +1 -0
- package/umd/src/cli/cli-commands/coder/formatDisplayPath.d.ts +6 -0
- package/umd/src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.d.ts +6 -0
- package/umd/src/cli/cli-commands/coder/getDefaultCoderVscodeSettings.d.ts +6 -0
- package/umd/src/cli/cli-commands/coder/init.d.ts +3 -38
- package/umd/src/cli/cli-commands/coder/initializeCoderProjectConfiguration.d.ts +25 -0
- package/umd/src/cli/cli-commands/coder/mergeStringRecordJsonFile.d.ts +18 -0
- package/umd/src/cli/cli-commands/coder/printInitializationSummary.d.ts +7 -0
- package/umd/src/cli/cli-commands/coder/readTextFileIfExists.d.ts +6 -0
- package/umd/src/types/string_agent_url.d.ts +7 -0
- package/umd/src/types/string_agent_url_private.d.ts +9 -0
- package/umd/src/types/string_base64.d.ts +13 -0
- package/umd/src/types/string_base64_private.d.ts +2 -2
- package/umd/src/types/string_base_url.d.ts +7 -0
- package/umd/src/types/string_base_url_private.d.ts +9 -0
- package/umd/src/types/string_email.d.ts +13 -0
- package/umd/src/types/string_email_private.d.ts +2 -2
- package/umd/src/types/string_host.d.ts +42 -0
- package/umd/src/types/string_host_private.d.ts +7 -7
- package/umd/src/types/string_href.d.ts +19 -0
- package/umd/src/types/string_href_private.d.ts +24 -0
- package/umd/src/types/string_mime_type.d.ts +15 -0
- package/umd/src/types/string_mime_type_private.d.ts +2 -2
- package/umd/src/types/string_pipeline_root_url.d.ts +7 -0
- package/umd/src/types/string_pipeline_root_url_private.d.ts +9 -0
- package/umd/src/types/string_pipeline_url.d.ts +13 -0
- package/umd/src/types/string_pipeline_url_private.d.ts +17 -0
- package/umd/src/types/string_promptbook_server_url.d.ts +7 -0
- package/umd/src/types/string_promptbook_server_url_private.d.ts +9 -0
- package/umd/src/types/string_url.d.ts +14 -141
- package/umd/src/types/string_url_image.d.ts +7 -0
- package/umd/src/types/string_url_image_private.d.ts +9 -0
- package/umd/src/types/string_url_private.d.ts +0 -80
- package/umd/src/version.d.ts +1 -1
package/umd/index.umd.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('fs'), require('path'), require('fs/promises'), require('waitasecond'), require('prompts'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('socket.io-client'), require('crypto-js'), require('jszip'), require('crypto'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('glob-promise'), require('moment'), require('express'), require('express-openapi-validator'), require('http'), require('socket.io'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('typescript'), require('readline'), require('child_process'), require('pg'), require('@supabase/supabase-js'), require('path/posix'), require('rxjs'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('openai')) :
|
|
3
|
-
typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'fs', 'path', 'fs/promises', 'waitasecond', 'prompts', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'socket.io-client', 'crypto-js', 'jszip', 'crypto', '@mozilla/readability', 'jsdom', 'showdown', 'glob-promise', 'moment', 'express', 'express-openapi-validator', 'http', 'socket.io', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'typescript', 'readline', 'child_process', 'pg', '@supabase/supabase-js', 'path/posix', 'rxjs', 'mime-types', 'papaparse', '@openai/agents', 'openai'], factory) :
|
|
4
|
-
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global._spaceTrim, global.fs, global.path, global.promises, global.waitasecond, global.prompts, global.dotenv, global.hexEncoder, global.sha256, global.socket_ioClient, global.cryptoJs, global.JSZip, global.crypto, global.readability, global.jsdom, global.showdown, global.glob, global.moment, global.express, global.OpenApiValidator, global.http, global.socket_io, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.ts, global.readline, global.child_process, global.pg, null, global.posix, global.rxjs, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
|
|
5
|
-
})(this, (function (exports, colors, commander, _spaceTrim, fs, path, promises, waitasecond, prompts, dotenv, hexEncoder, sha256, socket_ioClient, cryptoJs, JSZip, crypto, readability, jsdom, showdown, glob, moment, express, OpenApiValidator, http, socket_io, swaggerUi, react, server, Anthropic, Bottleneck, openai, ts, readline, child_process, pg, supabaseJs, posix, rxjs, mimeTypes, papaparse, agents, OpenAI) { 'use strict';
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('fs'), require('path'), require('fs/promises'), require('waitasecond'), require('prompts'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('socket.io-client'), require('crypto-js'), require('jszip'), require('crypto'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('glob-promise'), require('moment'), require('express'), require('express-openapi-validator'), require('http'), require('socket.io'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('typescript'), require('ignore'), require('readline'), require('child_process'), require('pg'), require('@supabase/supabase-js'), require('path/posix'), require('rxjs'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('openai')) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'fs', 'path', 'fs/promises', 'waitasecond', 'prompts', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'socket.io-client', 'crypto-js', 'jszip', 'crypto', '@mozilla/readability', 'jsdom', 'showdown', 'glob-promise', 'moment', 'express', 'express-openapi-validator', 'http', 'socket.io', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'typescript', 'ignore', 'readline', 'child_process', 'pg', '@supabase/supabase-js', 'path/posix', 'rxjs', 'mime-types', 'papaparse', '@openai/agents', 'openai'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global._spaceTrim, global.fs, global.path, global.promises, global.waitasecond, global.prompts, global.dotenv, global.hexEncoder, global.sha256, global.socket_ioClient, global.cryptoJs, global.JSZip, global.crypto, global.readability, global.jsdom, global.showdown, global.glob, global.moment, global.express, global.OpenApiValidator, global.http, global.socket_io, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.ts, global.ignore, global.readline, global.child_process, global.pg, null, global.posix, global.rxjs, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
|
|
5
|
+
})(this, (function (exports, colors, commander, _spaceTrim, fs, path, promises, waitasecond, prompts, dotenv, hexEncoder, sha256, socket_ioClient, cryptoJs, JSZip, crypto, readability, jsdom, showdown, glob, moment, express, OpenApiValidator, http, socket_io, swaggerUi, react, server, Anthropic, Bottleneck, openai, ts, ignore, readline, child_process, pg, supabaseJs, posix, rxjs, mimeTypes, papaparse, agents, OpenAI) { 'use strict';
|
|
6
6
|
|
|
7
7
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
8
8
|
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
var Anthropic__default = /*#__PURE__*/_interopDefaultLegacy(Anthropic);
|
|
44
44
|
var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
|
|
45
45
|
var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
|
|
46
|
+
var ignore__default = /*#__PURE__*/_interopDefaultLegacy(ignore);
|
|
46
47
|
var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
|
|
47
48
|
var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
|
|
48
49
|
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
* @generated
|
|
61
62
|
* @see https://github.com/webgptorg/promptbook
|
|
62
63
|
*/
|
|
63
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
64
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-39';
|
|
64
65
|
/**
|
|
65
66
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
66
67
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1664,6 +1665,181 @@
|
|
|
1664
1665
|
// Note: [🟡] Code for CLI command [find-fresh-emoji-tags](src/cli/cli-commands/coder/find-fresh-emoji-tags.ts) should never be published outside of `@promptbook/cli`
|
|
1665
1666
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
1666
1667
|
|
|
1668
|
+
/**
|
|
1669
|
+
* Root folders that contain source-like files for scanning.
|
|
1670
|
+
*/
|
|
1671
|
+
const SOURCE_ROOTS = ['src', 'apps', 'scripts', 'examples', 'agents', 'other'];
|
|
1672
|
+
/**
|
|
1673
|
+
* File extensions treated as source code.
|
|
1674
|
+
*/
|
|
1675
|
+
const SOURCE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
1676
|
+
/**
|
|
1677
|
+
* Glob patterns that should be ignored when scanning for source files.
|
|
1678
|
+
*/
|
|
1679
|
+
const SOURCE_FILE_IGNORE_GLOBS = [
|
|
1680
|
+
'**/node_modules/**',
|
|
1681
|
+
'**/packages/**',
|
|
1682
|
+
'**/.*/**',
|
|
1683
|
+
'**/.git/**',
|
|
1684
|
+
'**/.idea/**',
|
|
1685
|
+
'**/.vscode/**',
|
|
1686
|
+
'**/.promptbook/**',
|
|
1687
|
+
'**/.next/**',
|
|
1688
|
+
'**/.tmp/**',
|
|
1689
|
+
'**/tmp/**',
|
|
1690
|
+
'**/coverage/**',
|
|
1691
|
+
'**/dist/**',
|
|
1692
|
+
'**/build/**',
|
|
1693
|
+
'**/out/**',
|
|
1694
|
+
'**/prompts/**',
|
|
1695
|
+
'**/changelog/**',
|
|
1696
|
+
];
|
|
1697
|
+
/**
|
|
1698
|
+
* Glob patterns that are exempt from line-count checks.
|
|
1699
|
+
*/
|
|
1700
|
+
const LINE_COUNT_EXEMPT_GLOBS = ['other/cspell-dictionaries/**/*.txt'];
|
|
1701
|
+
/**
|
|
1702
|
+
* File extensions eligible for structural AST analysis.
|
|
1703
|
+
*/
|
|
1704
|
+
const STRUCTURAL_ANALYSIS_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
1705
|
+
/**
|
|
1706
|
+
* Markers that identify generated files which should be skipped.
|
|
1707
|
+
*/
|
|
1708
|
+
const GENERATED_CODE_MARKERS = [
|
|
1709
|
+
'WARNING: This code has been generated',
|
|
1710
|
+
'This code has been generated so that any manual changes will be overwritten',
|
|
1711
|
+
];
|
|
1712
|
+
/**
|
|
1713
|
+
* Name of the prompts directory.
|
|
1714
|
+
*/
|
|
1715
|
+
const PROMPTS_DIR_NAME = 'prompts';
|
|
1716
|
+
/**
|
|
1717
|
+
* Step size used for prompt numbering.
|
|
1718
|
+
*/
|
|
1719
|
+
const PROMPT_NUMBER_STEP = 10;
|
|
1720
|
+
/**
|
|
1721
|
+
* Prefix used for generated prompt slugs.
|
|
1722
|
+
*/
|
|
1723
|
+
const PROMPT_SLUG_PREFIX = 'refactor';
|
|
1724
|
+
/**
|
|
1725
|
+
* Label used to mark the target file in generated prompts.
|
|
1726
|
+
*/
|
|
1727
|
+
const PROMPT_TARGET_LABEL = 'Target file';
|
|
1728
|
+
/**
|
|
1729
|
+
* Maximum length for generated prompt slugs.
|
|
1730
|
+
*/
|
|
1731
|
+
const PROMPT_SLUG_MAX_LENGTH = 80;
|
|
1732
|
+
/**
|
|
1733
|
+
* Note: [?] Code in this file should never be published in any package
|
|
1734
|
+
*/
|
|
1735
|
+
|
|
1736
|
+
/**
|
|
1737
|
+
* Supported aggressiveness levels for refactor-candidate scanning.
|
|
1738
|
+
*/
|
|
1739
|
+
const REFACTOR_CANDIDATE_LEVEL_VALUES = ['xlow', 'low', 'medium', 'high', 'xhigh', 'extreme'];
|
|
1740
|
+
/**
|
|
1741
|
+
* Default aggressiveness level for refactor-candidate scanning.
|
|
1742
|
+
*/
|
|
1743
|
+
const DEFAULT_REFACTOR_CANDIDATE_LEVEL = 'medium';
|
|
1744
|
+
/**
|
|
1745
|
+
* Threshold table for each supported refactor-candidate scanning level.
|
|
1746
|
+
*/
|
|
1747
|
+
const REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL = {
|
|
1748
|
+
xlow: createRefactorCandidateLevelDetails({
|
|
1749
|
+
description: 'Very benevolent scan that only flags the most obvious refactor targets.',
|
|
1750
|
+
maxLineCount: 4200,
|
|
1751
|
+
maxEntityCountPerFile: 36,
|
|
1752
|
+
maxFunctionCountPerFile: 24,
|
|
1753
|
+
maxFunctionComplexity: 24,
|
|
1754
|
+
}),
|
|
1755
|
+
low: createRefactorCandidateLevelDetails({
|
|
1756
|
+
description: 'Conservative scan for only the most obvious refactor targets.',
|
|
1757
|
+
maxLineCount: 2800,
|
|
1758
|
+
maxEntityCountPerFile: 28,
|
|
1759
|
+
maxFunctionCountPerFile: 18,
|
|
1760
|
+
maxFunctionComplexity: 20,
|
|
1761
|
+
}),
|
|
1762
|
+
medium: createRefactorCandidateLevelDetails({
|
|
1763
|
+
description: 'Default scan using the current standard thresholds.',
|
|
1764
|
+
maxLineCount: 2000,
|
|
1765
|
+
maxEntityCountPerFile: 20,
|
|
1766
|
+
maxFunctionCountPerFile: 14,
|
|
1767
|
+
maxFunctionComplexity: 16,
|
|
1768
|
+
}),
|
|
1769
|
+
high: createRefactorCandidateLevelDetails({
|
|
1770
|
+
description: 'Strict scan that finds more crowded or complex files.',
|
|
1771
|
+
maxLineCount: 1400,
|
|
1772
|
+
maxEntityCountPerFile: 15,
|
|
1773
|
+
maxFunctionCountPerFile: 10,
|
|
1774
|
+
maxFunctionComplexity: 12,
|
|
1775
|
+
}),
|
|
1776
|
+
xhigh: createRefactorCandidateLevelDetails({
|
|
1777
|
+
description: 'Very strict scan for denser and more complex candidates.',
|
|
1778
|
+
maxLineCount: 900,
|
|
1779
|
+
maxEntityCountPerFile: 10,
|
|
1780
|
+
maxFunctionCountPerFile: 7,
|
|
1781
|
+
maxFunctionComplexity: 8,
|
|
1782
|
+
}),
|
|
1783
|
+
extreme: createRefactorCandidateLevelDetails({
|
|
1784
|
+
description: 'Most aggressive scan that also surfaces weaker refactor opportunities.',
|
|
1785
|
+
maxLineCount: 600,
|
|
1786
|
+
maxEntityCountPerFile: 6,
|
|
1787
|
+
maxFunctionCountPerFile: 4,
|
|
1788
|
+
maxFunctionComplexity: 6,
|
|
1789
|
+
}),
|
|
1790
|
+
};
|
|
1791
|
+
/**
|
|
1792
|
+
* Resolves the thresholds for a selected refactor-candidate scanning level.
|
|
1793
|
+
*/
|
|
1794
|
+
function getRefactorCandidateLevelConfiguration(level = DEFAULT_REFACTOR_CANDIDATE_LEVEL) {
|
|
1795
|
+
return REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL[level].configuration;
|
|
1796
|
+
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Resolves the user-facing description for a selected refactor-candidate scanning level.
|
|
1799
|
+
*/
|
|
1800
|
+
function getRefactorCandidateLevelDescription(level) {
|
|
1801
|
+
return REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL[level].description;
|
|
1802
|
+
}
|
|
1803
|
+
/**
|
|
1804
|
+
* Builds one normalized refactor-candidate level entry.
|
|
1805
|
+
*/
|
|
1806
|
+
function createRefactorCandidateLevelDetails(options) {
|
|
1807
|
+
const { description, maxLineCount, maxEntityCountPerFile, maxFunctionCountPerFile, maxFunctionComplexity } = options;
|
|
1808
|
+
return {
|
|
1809
|
+
description,
|
|
1810
|
+
configuration: createRefactorCandidateLevelConfiguration({
|
|
1811
|
+
maxLineCount,
|
|
1812
|
+
maxEntityCountPerFile,
|
|
1813
|
+
maxFunctionCountPerFile,
|
|
1814
|
+
maxFunctionComplexity,
|
|
1815
|
+
}),
|
|
1816
|
+
};
|
|
1817
|
+
}
|
|
1818
|
+
/**
|
|
1819
|
+
* Builds one normalized refactor-candidate level configuration entry.
|
|
1820
|
+
*/
|
|
1821
|
+
function createRefactorCandidateLevelConfiguration(options) {
|
|
1822
|
+
const { maxLineCount, maxEntityCountPerFile, maxFunctionCountPerFile, maxFunctionComplexity } = options;
|
|
1823
|
+
return {
|
|
1824
|
+
maxDefaultLineCount: maxLineCount,
|
|
1825
|
+
maxLineCountByExtension: createLineCountLimits(maxLineCount),
|
|
1826
|
+
maxEntityCountPerFile,
|
|
1827
|
+
maxFunctionCountPerFile,
|
|
1828
|
+
maxFunctionComplexity,
|
|
1829
|
+
};
|
|
1830
|
+
}
|
|
1831
|
+
/**
|
|
1832
|
+
* Creates a per-extension line-count table using one shared threshold.
|
|
1833
|
+
*/
|
|
1834
|
+
function createLineCountLimits(maxLineCount) {
|
|
1835
|
+
const maxLineCountByExtension = {};
|
|
1836
|
+
for (const extension of SOURCE_FILE_EXTENSIONS) {
|
|
1837
|
+
maxLineCountByExtension[extension] = maxLineCount;
|
|
1838
|
+
}
|
|
1839
|
+
return maxLineCountByExtension;
|
|
1840
|
+
}
|
|
1841
|
+
// Note: [🟡] Code for repository script [RefactorCandidateLevel](scripts/find-refactor-candidates/RefactorCandidateLevel.ts) should never be published outside of `@promptbook/cli`
|
|
1842
|
+
|
|
1667
1843
|
/**
|
|
1668
1844
|
* Initializes `coder find-refactor-candidates` command for Promptbook CLI utilities
|
|
1669
1845
|
*
|
|
@@ -1673,20 +1849,23 @@
|
|
|
1673
1849
|
*/
|
|
1674
1850
|
function $initializeCoderFindRefactorCandidatesCommand(program) {
|
|
1675
1851
|
const command = program.command('find-refactor-candidates');
|
|
1676
|
-
command.description(
|
|
1677
|
-
Scan source files to identify refactoring candidates
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
`))
|
|
1685
|
-
|
|
1852
|
+
command.description([
|
|
1853
|
+
'Scan source files to identify refactoring candidates',
|
|
1854
|
+
'',
|
|
1855
|
+
'Levels:',
|
|
1856
|
+
...REFACTOR_CANDIDATE_LEVEL_VALUES.map((level) => `- ${level}: ${getRefactorCandidateLevelDescription(level)}`),
|
|
1857
|
+
'',
|
|
1858
|
+
'Generates refactor prompts with guidance for identified candidates.',
|
|
1859
|
+
].join('\n'));
|
|
1860
|
+
command.addOption(new commander.Option('--level <level>', `Set scan aggressiveness (${REFACTOR_CANDIDATE_LEVEL_VALUES.join(', ')})`)
|
|
1861
|
+
.choices([...REFACTOR_CANDIDATE_LEVEL_VALUES])
|
|
1862
|
+
.default(DEFAULT_REFACTOR_CANDIDATE_LEVEL));
|
|
1863
|
+
command.action(handleActionErrors(async (cliOptions) => {
|
|
1864
|
+
const { level = DEFAULT_REFACTOR_CANDIDATE_LEVEL } = cliOptions;
|
|
1686
1865
|
// Note: Import the function dynamically to avoid loading heavy dependencies until needed
|
|
1687
1866
|
const { findRefactorCandidates } = await Promise.resolve().then(function () { return findRefactorCandidates$1; });
|
|
1688
1867
|
try {
|
|
1689
|
-
await findRefactorCandidates();
|
|
1868
|
+
await findRefactorCandidates({ level });
|
|
1690
1869
|
}
|
|
1691
1870
|
catch (error) {
|
|
1692
1871
|
assertsError(error);
|
|
@@ -1732,7 +1911,9 @@
|
|
|
1732
1911
|
*/
|
|
1733
1912
|
const PROMPTS_TEMPLATES_DIRECTORY_PATH = path.join(PROMPTS_DIRECTORY_PATH, 'templates');
|
|
1734
1913
|
/**
|
|
1735
|
-
* Built-in boilerplate templates
|
|
1914
|
+
* Built-in boilerplate templates available to `coder generate-boilerplates`.
|
|
1915
|
+
*
|
|
1916
|
+
* Only the project-agnostic subset is materialized by `coder init`.
|
|
1736
1917
|
*/
|
|
1737
1918
|
const DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS = [
|
|
1738
1919
|
{
|
|
@@ -1741,10 +1922,11 @@
|
|
|
1741
1922
|
slugPrefix: null,
|
|
1742
1923
|
content: buildCoderPromptTemplateContent([
|
|
1743
1924
|
'- @@@',
|
|
1744
|
-
|
|
1925
|
+
"- Keep in mind the DRY _(don't repeat yourself)_ principle.",
|
|
1745
1926
|
'- Do a proper analysis of the current functionality before you start implementing.',
|
|
1746
1927
|
'- Add the changes into the [changelog](./changelog/_current-preversion.md)',
|
|
1747
1928
|
]),
|
|
1929
|
+
isDefaultProjectTemplate: true,
|
|
1748
1930
|
},
|
|
1749
1931
|
{
|
|
1750
1932
|
id: 'agents-server',
|
|
@@ -1752,14 +1934,19 @@
|
|
|
1752
1934
|
slugPrefix: 'agents-server',
|
|
1753
1935
|
content: buildCoderPromptTemplateContent([
|
|
1754
1936
|
'- @@@',
|
|
1755
|
-
|
|
1937
|
+
"- Keep in mind the DRY _(don't repeat yourself)_ principle.",
|
|
1756
1938
|
'- Do a proper analysis of the current functionality before you start implementing.',
|
|
1757
1939
|
'- You are working with the [Agents Server](apps/agents-server)',
|
|
1758
1940
|
'- If you need to do the database migration, do it',
|
|
1759
1941
|
'- Add the changes into the [changelog](changelog/_current-preversion.md)',
|
|
1760
1942
|
]),
|
|
1943
|
+
isDefaultProjectTemplate: false,
|
|
1761
1944
|
},
|
|
1762
1945
|
];
|
|
1946
|
+
/**
|
|
1947
|
+
* Project-agnostic coder templates that `ptbk coder init` should materialize in any repository.
|
|
1948
|
+
*/
|
|
1949
|
+
const DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS = DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS.filter(({ isDefaultProjectTemplate }) => isDefaultProjectTemplate);
|
|
1763
1950
|
/**
|
|
1764
1951
|
* Lists the built-in coder boilerplate templates.
|
|
1765
1952
|
*
|
|
@@ -1768,6 +1955,14 @@
|
|
|
1768
1955
|
function getDefaultCoderPromptTemplateDefinitions() {
|
|
1769
1956
|
return DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS;
|
|
1770
1957
|
}
|
|
1958
|
+
/**
|
|
1959
|
+
* Lists the built-in coder prompt templates that are safe to initialize in any project.
|
|
1960
|
+
*
|
|
1961
|
+
* @private internal utility of `ptbk coder`
|
|
1962
|
+
*/
|
|
1963
|
+
function getDefaultCoderProjectPromptTemplateDefinitions() {
|
|
1964
|
+
return DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS;
|
|
1965
|
+
}
|
|
1771
1966
|
/**
|
|
1772
1967
|
* Resolves one built-in coder boilerplate template definition by its stable identifier.
|
|
1773
1968
|
*
|
|
@@ -1787,9 +1982,9 @@
|
|
|
1787
1982
|
*/
|
|
1788
1983
|
async function ensureDefaultCoderPromptTemplateFiles(projectPath) {
|
|
1789
1984
|
const ensuredTemplateFiles = [];
|
|
1790
|
-
for (const definition of
|
|
1985
|
+
for (const definition of DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS) {
|
|
1791
1986
|
const absoluteTemplatePath = path.join(projectPath, definition.relativeFilePath);
|
|
1792
|
-
if (await isExistingFile$
|
|
1987
|
+
if (await isExistingFile$2(absoluteTemplatePath)) {
|
|
1793
1988
|
ensuredTemplateFiles.push({
|
|
1794
1989
|
id: definition.id,
|
|
1795
1990
|
relativeFilePath: definition.relativeFilePath,
|
|
@@ -1903,7 +2098,7 @@
|
|
|
1903
2098
|
/**
|
|
1904
2099
|
* Checks whether a path exists and is a file.
|
|
1905
2100
|
*/
|
|
1906
|
-
async function isExistingFile$
|
|
2101
|
+
async function isExistingFile$2(path) {
|
|
1907
2102
|
try {
|
|
1908
2103
|
return (await promises.stat(path)).isFile();
|
|
1909
2104
|
}
|
|
@@ -2053,178 +2248,202 @@
|
|
|
2053
2248
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2054
2249
|
|
|
2055
2250
|
/**
|
|
2056
|
-
*
|
|
2251
|
+
* Relative path to the shared coder context file initialized in project roots.
|
|
2057
2252
|
*
|
|
2058
|
-
* @
|
|
2253
|
+
* @private internal utility of `ptbk coder`
|
|
2059
2254
|
*/
|
|
2060
|
-
|
|
2061
|
-
constructor(message) {
|
|
2062
|
-
super(message);
|
|
2063
|
-
this.name = 'ParseError';
|
|
2064
|
-
Object.setPrototypeOf(this, ParseError.prototype);
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
// TODO: Maybe split `ParseError` and `ApplyError`
|
|
2068
|
-
|
|
2255
|
+
const AGENTS_FILE_PATH = 'AGENTS.md';
|
|
2069
2256
|
/**
|
|
2070
|
-
*
|
|
2257
|
+
* Stable boilerplate instructions written into newly initialized `AGENTS.md` files.
|
|
2071
2258
|
*/
|
|
2072
|
-
const
|
|
2073
|
-
|
|
2074
|
-
name: 'CODING_AGENT_GIT_NAME',
|
|
2075
|
-
value: 'Promptbook Coding Agent',
|
|
2076
|
-
},
|
|
2077
|
-
{
|
|
2078
|
-
name: 'CODING_AGENT_GIT_EMAIL',
|
|
2079
|
-
value: 'coding-agent@promptbook.studio',
|
|
2080
|
-
},
|
|
2081
|
-
{
|
|
2082
|
-
name: 'CODING_AGENT_GIT_SIGNING_KEY',
|
|
2083
|
-
value: '13406525ED912F938FEA85AB4046C687298B2382',
|
|
2084
|
-
},
|
|
2259
|
+
const DEFAULT_CODER_AGENTS_FILE_LINES = [
|
|
2260
|
+
'<!-- TODO: Write instructions for the Promptbook AI Coder here -->',
|
|
2085
2261
|
];
|
|
2086
2262
|
/**
|
|
2087
|
-
*
|
|
2263
|
+
* Shared markdown boilerplate written into new `AGENTS.md` files.
|
|
2088
2264
|
*/
|
|
2089
|
-
const
|
|
2090
|
-
'coder:generate-boilerplates': 'npx ptbk coder generate-boilerplates',
|
|
2091
|
-
'coder:run': 'npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --no-wait',
|
|
2092
|
-
'coder:find-refactor-candidates': 'npx ptbk coder find-refactor-candidates',
|
|
2093
|
-
'coder:verify': 'npx ptbk coder verify',
|
|
2094
|
-
};
|
|
2265
|
+
const DEFAULT_CODER_AGENTS_FILE_CONTENT = DEFAULT_CODER_AGENTS_FILE_LINES.join('\n');
|
|
2095
2266
|
/**
|
|
2096
|
-
*
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
/**
|
|
2100
|
-
* Relative path to `package.json` in the initialized project.
|
|
2101
|
-
*/
|
|
2102
|
-
const PACKAGE_JSON_FILE_PATH = 'package.json';
|
|
2103
|
-
/**
|
|
2104
|
-
* Relative path to the VS Code settings file initialized by `ptbk coder init`.
|
|
2267
|
+
* Returns the default coder `AGENTS.md` boilerplate instructions.
|
|
2268
|
+
*
|
|
2269
|
+
* @private internal utility of `ptbk coder`
|
|
2105
2270
|
*/
|
|
2106
|
-
|
|
2271
|
+
function getDefaultCoderAgentsFileContent() {
|
|
2272
|
+
return DEFAULT_CODER_AGENTS_FILE_CONTENT;
|
|
2273
|
+
}
|
|
2274
|
+
// Note: [🟡] Code for coder AGENTS file boilerplate [agentsFile](src/cli/cli-commands/coder/agentsFile.ts) should never be published outside of `@promptbook/cli`
|
|
2275
|
+
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
2276
|
+
|
|
2107
2277
|
/**
|
|
2108
|
-
*
|
|
2278
|
+
* Normalizes one project-relative path for human-readable CLI output and markdown.
|
|
2279
|
+
*
|
|
2280
|
+
* @private internal utility of `ptbk coder`
|
|
2109
2281
|
*/
|
|
2110
|
-
|
|
2282
|
+
function formatDisplayPath(relativePath) {
|
|
2283
|
+
return relativePath.replace(/\\/gu, '/');
|
|
2284
|
+
}
|
|
2285
|
+
// Note: [🟡] Code for coder path formatting [formatDisplayPath](src/cli/cli-commands/coder/formatDisplayPath.ts) should never be published outside of `@promptbook/cli`
|
|
2286
|
+
|
|
2111
2287
|
/**
|
|
2112
|
-
*
|
|
2288
|
+
* Relative path to the Promptbook Coder quick-reference file initialized in project roots.
|
|
2289
|
+
*
|
|
2290
|
+
* @private internal utility of `ptbk coder`
|
|
2113
2291
|
*/
|
|
2114
|
-
const
|
|
2292
|
+
const AGENT_CODING_FILE_PATH = 'AGENT_CODING.md';
|
|
2115
2293
|
/**
|
|
2116
|
-
*
|
|
2294
|
+
* Returns the default coder `AGENT_CODING.md` quick-reference content.
|
|
2295
|
+
*
|
|
2296
|
+
* @private internal utility of `ptbk coder`
|
|
2117
2297
|
*/
|
|
2118
|
-
|
|
2298
|
+
function getDefaultCoderAgentCodingFileContent({ packageJsonScripts, }) {
|
|
2299
|
+
return [
|
|
2300
|
+
'# Promptbook Coder quick reference',
|
|
2301
|
+
'',
|
|
2302
|
+
`This project is prepared for the \`ptbk coder\` workflow. Promptbook Coder does not create a new model on its own; it orchestrates coding agents such as GitHub Copilot, OpenAI Codex, Claude Code, Opencode, Cline, and Gemini CLI through prompt files in \`${formatDisplayPath(PROMPTS_DIRECTORY_PATH)}/\`.`,
|
|
2303
|
+
'',
|
|
2304
|
+
'## Workflow',
|
|
2305
|
+
`1. Put repository-wide coding rules into \`${AGENTS_FILE_PATH}\`. The default \`npm run coder:run\` script already passes \`--context ${AGENTS_FILE_PATH}\`.`,
|
|
2306
|
+
`2. Create or customize prompt templates in \`${formatDisplayPath(PROMPTS_TEMPLATES_DIRECTORY_PATH)}/\`. ${buildStarterTemplateSentence()}`,
|
|
2307
|
+
'3. Generate prompt files with `npm run coder:generate-boilerplates` or `npx ptbk coder generate-boilerplates --template <template> --count <count>`.',
|
|
2308
|
+
'4. Replace every `@@@`, keep drafts as `[-]`, and switch prompts to `[ ]` when they are ready to run. Completed prompts are marked `[x]`.',
|
|
2309
|
+
'5. Run `npm run coder:run` to execute the next ready prompt with the configured coding agent.',
|
|
2310
|
+
`6. Use \`npm run coder:verify\` to archive finished prompts into \`${formatDisplayPath(PROMPTS_DONE_DIRECTORY_PATH)}/\` and append repair follow-up prompts when more work is needed.`,
|
|
2311
|
+
'7. Use `npm run coder:find-refactor-candidates` when you want Promptbook to suggest refactor prompts automatically.',
|
|
2312
|
+
'',
|
|
2313
|
+
'## Templates',
|
|
2314
|
+
`- Project-owned templates created by \`ptbk coder init\`: ${formatInlineCodeList(getDefaultCoderProjectPromptTemplateDefinitions().map(({ relativeFilePath }) => formatDisplayPath(relativeFilePath)))}`,
|
|
2315
|
+
`- Built-in \`--template\` aliases: ${formatInlineCodeList(getDefaultCoderPromptTemplateDefinitions().map(({ id }) => id))}`,
|
|
2316
|
+
`- To add a custom template, create a markdown file such as \`${formatDisplayPath(PROMPTS_TEMPLATES_DIRECTORY_PATH)}/backend.md\`.`,
|
|
2317
|
+
`- To use a project template, run \`npx ptbk coder generate-boilerplates --template ${formatDisplayPath(PROMPTS_TEMPLATES_DIRECTORY_PATH)}/backend.md\`.`,
|
|
2318
|
+
`- Keep shared repository rules in \`${AGENTS_FILE_PATH}\` and recurring task-family rules in template files so individual prompt files stay focused on the actual task.`,
|
|
2319
|
+
'',
|
|
2320
|
+
'## Created npm scripts',
|
|
2321
|
+
'| Script | Purpose |',
|
|
2322
|
+
'| --- | --- |',
|
|
2323
|
+
...buildPackageJsonScriptTableLines(packageJsonScripts),
|
|
2324
|
+
'',
|
|
2325
|
+
'## Customizing the workflow',
|
|
2326
|
+
'- Edit `package.json` if you want `npm run coder:run` to use another coding agent, model, thinking level, context file, or wait mode.',
|
|
2327
|
+
'- Use direct CLI commands when you need one-off flags such as `--priority`, `--ignore-git-changes`, `--dry-run`, `--allow-credits`, or `--auto-migrate`.',
|
|
2328
|
+
'- Use `npx ptbk coder --help` and `npx ptbk coder <command> --help` for the full CLI reference.',
|
|
2329
|
+
].join('\n');
|
|
2330
|
+
}
|
|
2119
2331
|
/**
|
|
2120
|
-
*
|
|
2332
|
+
* Builds the sentence describing the starter templates created during initialization.
|
|
2121
2333
|
*/
|
|
2122
|
-
|
|
2334
|
+
function buildStarterTemplateSentence() {
|
|
2335
|
+
const starterTemplatePaths = getDefaultCoderProjectPromptTemplateDefinitions().map(({ relativeFilePath }) => formatDisplayPath(relativeFilePath));
|
|
2336
|
+
if (starterTemplatePaths.length === 1) {
|
|
2337
|
+
return `The starter project template created by \`ptbk coder init\` is \`${starterTemplatePaths[0]}\`.`;
|
|
2338
|
+
}
|
|
2339
|
+
return `The starter project templates created by \`ptbk coder init\` are ${formatInlineCodeList(starterTemplatePaths)}.`;
|
|
2340
|
+
}
|
|
2123
2341
|
/**
|
|
2124
|
-
*
|
|
2342
|
+
* Builds the markdown table rows describing the initialized npm scripts.
|
|
2125
2343
|
*/
|
|
2126
|
-
|
|
2344
|
+
function buildPackageJsonScriptTableLines(packageJsonScripts) {
|
|
2345
|
+
return Object.entries(packageJsonScripts).map(([scriptName, scriptCommand]) => `| \`npm run ${scriptName}\` | ${describeDefaultCoderPackageJsonScript(scriptName, scriptCommand)} |`);
|
|
2346
|
+
}
|
|
2127
2347
|
/**
|
|
2128
|
-
*
|
|
2348
|
+
* Describes one initialized npm script in human-readable terms.
|
|
2129
2349
|
*/
|
|
2130
|
-
|
|
2350
|
+
function describeDefaultCoderPackageJsonScript(scriptName, scriptCommand) {
|
|
2351
|
+
if (scriptName === 'coder:generate-boilerplates') {
|
|
2352
|
+
return `Runs \`${scriptCommand}\` to create new prompt files in \`${formatDisplayPath(PROMPTS_DIRECTORY_PATH)}/\`.`;
|
|
2353
|
+
}
|
|
2354
|
+
if (scriptName === 'coder:run') {
|
|
2355
|
+
return `Runs \`${scriptCommand}\` to execute the next ready prompt with shared repository context from \`${AGENTS_FILE_PATH}\`.`;
|
|
2356
|
+
}
|
|
2357
|
+
if (scriptName === 'coder:find-refactor-candidates') {
|
|
2358
|
+
return `Runs \`${scriptCommand}\` to generate prompt candidates for large or crowded files.`;
|
|
2359
|
+
}
|
|
2360
|
+
if (scriptName === 'coder:verify') {
|
|
2361
|
+
return `Runs \`${scriptCommand}\` to archive verified prompts into \`${formatDisplayPath(PROMPTS_DONE_DIRECTORY_PATH)}/\` and append repair prompts when needed.`;
|
|
2362
|
+
}
|
|
2363
|
+
return `Runs \`${scriptCommand}\`.`;
|
|
2364
|
+
}
|
|
2131
2365
|
/**
|
|
2132
|
-
*
|
|
2366
|
+
* Formats one inline code list for human-readable markdown.
|
|
2133
2367
|
*/
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
`
|
|
2368
|
+
function formatInlineCodeList(values) {
|
|
2369
|
+
return values.map((value) => `\`${value}\``).join(', ');
|
|
2370
|
+
}
|
|
2371
|
+
// Note: [🟡] Code for coder AGENT_CODING file boilerplate [agentCodingFile](src/cli/cli-commands/coder/agentCodingFile.ts) should never be published outside of `@promptbook/cli`
|
|
2372
|
+
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
2373
|
+
|
|
2138
2374
|
/**
|
|
2139
|
-
*
|
|
2140
|
-
*
|
|
2141
|
-
* Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI.
|
|
2375
|
+
* Appends one text block to existing file content while preserving readable newlines.
|
|
2142
2376
|
*
|
|
2143
|
-
* @private
|
|
2377
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2144
2378
|
*/
|
|
2145
|
-
function
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
Creates or updates:
|
|
2152
|
-
- prompts/
|
|
2153
|
-
- prompts/done/
|
|
2154
|
-
- prompts/templates/common.md
|
|
2155
|
-
- prompts/templates/agents-server.md
|
|
2156
|
-
- .gitignore
|
|
2157
|
-
- package.json
|
|
2158
|
-
- .vscode/settings.json
|
|
2159
|
-
|
|
2160
|
-
Ensures required coding-agent environment variables in .env:
|
|
2161
|
-
- CODING_AGENT_GIT_NAME
|
|
2162
|
-
- CODING_AGENT_GIT_EMAIL
|
|
2163
|
-
- CODING_AGENT_GIT_SIGNING_KEY
|
|
2164
|
-
`));
|
|
2165
|
-
command.action(handleActionErrors(async () => {
|
|
2166
|
-
const summary = await initializeCoderProjectConfiguration(process.cwd());
|
|
2167
|
-
printInitializationSummary(summary);
|
|
2168
|
-
}));
|
|
2379
|
+
function appendBlock(currentContent, blockToAppend) {
|
|
2380
|
+
if (currentContent.trim() === '') {
|
|
2381
|
+
return `${blockToAppend}\n`;
|
|
2382
|
+
}
|
|
2383
|
+
const normalizedCurrentContent = currentContent.endsWith('\n') ? currentContent : `${currentContent}\n`;
|
|
2384
|
+
return `${normalizedCurrentContent}\n${blockToAppend}\n`;
|
|
2169
2385
|
}
|
|
2386
|
+
// Note: [🟡] Code for coder init text appending [appendBlock](src/cli/cli-commands/coder/appendBlock.ts) should never be published outside of `@promptbook/cli`
|
|
2387
|
+
|
|
2170
2388
|
/**
|
|
2171
|
-
*
|
|
2389
|
+
* Reads one text file when it exists, otherwise returns `undefined`.
|
|
2172
2390
|
*
|
|
2173
|
-
* @private
|
|
2391
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2174
2392
|
*/
|
|
2175
|
-
function
|
|
2176
|
-
|
|
2393
|
+
async function readTextFileIfExists(path) {
|
|
2394
|
+
try {
|
|
2395
|
+
const fileStats = await promises.stat(path);
|
|
2396
|
+
if (!fileStats.isFile()) {
|
|
2397
|
+
return undefined;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
catch (_a) {
|
|
2401
|
+
return undefined;
|
|
2402
|
+
}
|
|
2403
|
+
return promises.readFile(path, 'utf-8');
|
|
2177
2404
|
}
|
|
2405
|
+
// Note: [🟡] Code for coder init text-file reading [readTextFileIfExists](src/cli/cli-commands/coder/readTextFileIfExists.ts) should never be published outside of `@promptbook/cli`
|
|
2406
|
+
|
|
2178
2407
|
/**
|
|
2179
|
-
*
|
|
2180
|
-
*
|
|
2181
|
-
* @private internal utility of `coder init` command
|
|
2408
|
+
* Relative path to `.env` in the initialized project.
|
|
2182
2409
|
*/
|
|
2183
|
-
|
|
2184
|
-
const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH);
|
|
2185
|
-
const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
|
|
2186
|
-
const promptsTemplatesDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_TEMPLATES_DIRECTORY_PATH);
|
|
2187
|
-
const promptTemplateFileStatuses = await ensureDefaultCoderPromptTemplateFiles(projectPath);
|
|
2188
|
-
const { envFileStatus, initializedEnvVariableNames } = await ensureCoderEnvFile(projectPath);
|
|
2189
|
-
const gitignoreFileStatus = await ensureCoderGitignoreFile(projectPath);
|
|
2190
|
-
const packageJsonFileStatus = await ensureCoderPackageJsonFile(projectPath);
|
|
2191
|
-
const vscodeSettingsFileStatus = await ensureCoderVscodeSettingsFile(projectPath);
|
|
2192
|
-
return {
|
|
2193
|
-
promptsDirectoryStatus,
|
|
2194
|
-
promptsDoneDirectoryStatus,
|
|
2195
|
-
promptsTemplatesDirectoryStatus,
|
|
2196
|
-
promptTemplateFileStatuses,
|
|
2197
|
-
envFileStatus,
|
|
2198
|
-
gitignoreFileStatus,
|
|
2199
|
-
packageJsonFileStatus,
|
|
2200
|
-
vscodeSettingsFileStatus,
|
|
2201
|
-
initializedEnvVariableNames,
|
|
2202
|
-
};
|
|
2203
|
-
}
|
|
2410
|
+
const ENV_FILE_PATH = '.env';
|
|
2204
2411
|
/**
|
|
2205
|
-
*
|
|
2412
|
+
* Fallback `.env` content used when no required variables need to be appended.
|
|
2206
2413
|
*/
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2414
|
+
const EMPTY_CODER_ENV_FILE_CONTENT = '# Environment variables for Promptbook coder\n';
|
|
2415
|
+
/**
|
|
2416
|
+
* Required environment variables for coding-agent git identity.
|
|
2417
|
+
*/
|
|
2418
|
+
const REQUIRED_CODER_ENV_VARIABLES = [
|
|
2419
|
+
{
|
|
2420
|
+
name: 'CODING_AGENT_GIT_NAME',
|
|
2421
|
+
value: 'Promptbook Coding Agent',
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
name: 'CODING_AGENT_GIT_EMAIL',
|
|
2425
|
+
value: 'coding-agent@promptbook.studio',
|
|
2426
|
+
},
|
|
2427
|
+
{
|
|
2428
|
+
name: 'CODING_AGENT_GIT_SIGNING_KEY',
|
|
2429
|
+
value: '13406525ED912F938FEA85AB4046C687298B2382',
|
|
2430
|
+
},
|
|
2431
|
+
];
|
|
2216
2432
|
/**
|
|
2217
2433
|
* Ensures `.env` exists and contains all required coder environment variables.
|
|
2434
|
+
*
|
|
2435
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2218
2436
|
*/
|
|
2219
2437
|
async function ensureCoderEnvFile(projectPath) {
|
|
2220
|
-
const envFilePath = path.join(projectPath,
|
|
2221
|
-
const
|
|
2222
|
-
const
|
|
2223
|
-
const
|
|
2224
|
-
const
|
|
2438
|
+
const envFilePath = path.join(projectPath, ENV_FILE_PATH);
|
|
2439
|
+
const existingEnvContent = await readTextFileIfExists(envFilePath);
|
|
2440
|
+
const isEnvFileExisting = existingEnvContent !== undefined;
|
|
2441
|
+
const currentEnvContent = existingEnvContent || '';
|
|
2442
|
+
const existingEnvVariableNames = parseEnvVariableNames(currentEnvContent);
|
|
2443
|
+
const missingEnvVariables = REQUIRED_CODER_ENV_VARIABLES.filter(({ name }) => !existingEnvVariableNames.has(name));
|
|
2225
2444
|
if (missingEnvVariables.length === 0) {
|
|
2226
|
-
if (!
|
|
2227
|
-
await promises.writeFile(envFilePath,
|
|
2445
|
+
if (!isEnvFileExisting) {
|
|
2446
|
+
await promises.writeFile(envFilePath, EMPTY_CODER_ENV_FILE_CONTENT, 'utf-8');
|
|
2228
2447
|
return {
|
|
2229
2448
|
envFileStatus: 'created',
|
|
2230
2449
|
initializedEnvVariableNames: [],
|
|
@@ -2239,12 +2458,54 @@
|
|
|
2239
2458
|
const nextEnvContent = appendBlock(currentEnvContent, envBlockToAppend);
|
|
2240
2459
|
await promises.writeFile(envFilePath, nextEnvContent, 'utf-8');
|
|
2241
2460
|
return {
|
|
2242
|
-
envFileStatus:
|
|
2461
|
+
envFileStatus: isEnvFileExisting ? 'updated' : 'created',
|
|
2243
2462
|
initializedEnvVariableNames: missingEnvVariables.map(({ name }) => name),
|
|
2244
2463
|
};
|
|
2245
2464
|
}
|
|
2465
|
+
/**
|
|
2466
|
+
* Parses variable names currently defined in `.env` style content.
|
|
2467
|
+
*/
|
|
2468
|
+
function parseEnvVariableNames(envContent) {
|
|
2469
|
+
const variableNames = new Set();
|
|
2470
|
+
for (const line of envContent.split(/\r?\n/)) {
|
|
2471
|
+
const trimmedLine = line.trim();
|
|
2472
|
+
if (trimmedLine === '' || trimmedLine.startsWith('#')) {
|
|
2473
|
+
continue;
|
|
2474
|
+
}
|
|
2475
|
+
const match = trimmedLine.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
2476
|
+
if (!match || !match[1]) {
|
|
2477
|
+
continue;
|
|
2478
|
+
}
|
|
2479
|
+
variableNames.add(match[1]);
|
|
2480
|
+
}
|
|
2481
|
+
return variableNames;
|
|
2482
|
+
}
|
|
2483
|
+
/**
|
|
2484
|
+
* Builds a `.env` block containing missing coder environment variables.
|
|
2485
|
+
*/
|
|
2486
|
+
function buildMissingEnvVariablesBlock(variables) {
|
|
2487
|
+
return _spaceTrim.spaceTrim(`
|
|
2488
|
+
# Promptbook coder identity (initialized by \`ptbk coder init\`)
|
|
2489
|
+
${variables.map(({ name, value }) => `${name}=${JSON.stringify(value)}`).join('\n')}
|
|
2490
|
+
`);
|
|
2491
|
+
}
|
|
2492
|
+
// Note: [🟡] Code for coder init environment bootstrapping [ensureCoderEnvFile](src/cli/cli-commands/coder/ensureCoderEnvFile.ts) should never be published outside of `@promptbook/cli`
|
|
2493
|
+
|
|
2494
|
+
/**
|
|
2495
|
+
* Relative path to `.gitignore` in the initialized project.
|
|
2496
|
+
*/
|
|
2497
|
+
const GITIGNORE_FILE_PATH = '.gitignore';
|
|
2498
|
+
/**
|
|
2499
|
+
* `.gitignore` block required by standalone Promptbook coder projects.
|
|
2500
|
+
*/
|
|
2501
|
+
const CODER_GITIGNORE_BLOCK = _spaceTrim.spaceTrim(`
|
|
2502
|
+
# Promptbook Coder
|
|
2503
|
+
/.tmp
|
|
2504
|
+
`);
|
|
2246
2505
|
/**
|
|
2247
2506
|
* Ensures `.gitignore` contains the standalone Promptbook coder cache entry.
|
|
2507
|
+
*
|
|
2508
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2248
2509
|
*/
|
|
2249
2510
|
async function ensureCoderGitignoreFile(projectPath) {
|
|
2250
2511
|
const gitignorePath = path.join(projectPath, GITIGNORE_FILE_PATH);
|
|
@@ -2257,95 +2518,339 @@
|
|
|
2257
2518
|
return currentGitignoreContent === undefined ? 'created' : 'updated';
|
|
2258
2519
|
}
|
|
2259
2520
|
/**
|
|
2260
|
-
*
|
|
2521
|
+
* Detects whether `.gitignore` already covers the standalone coder temp directory.
|
|
2261
2522
|
*/
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2523
|
+
function hasTmpGitignoreRule(gitignoreContent) {
|
|
2524
|
+
return /(^|[\r\n])\/?\.tmp(?:[\r\n]|$)/u.test(gitignoreContent);
|
|
2525
|
+
}
|
|
2526
|
+
// Note: [🟡] Code for coder init gitignore bootstrapping [ensureCoderGitignoreFile](src/cli/cli-commands/coder/ensureCoderGitignoreFile.ts) should never be published outside of `@promptbook/cli`
|
|
2527
|
+
|
|
2528
|
+
/**
|
|
2529
|
+
* Ensures one coder markdown file exists with the provided default boilerplate.
|
|
2530
|
+
*
|
|
2531
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2532
|
+
*/
|
|
2533
|
+
async function ensureCoderMarkdownFile(projectPath, relativeFilePath, fileContent) {
|
|
2534
|
+
const absoluteFilePath = path.join(projectPath, relativeFilePath);
|
|
2535
|
+
if (await isExistingFile$1(absoluteFilePath)) {
|
|
2536
|
+
return 'unchanged';
|
|
2537
|
+
}
|
|
2538
|
+
await promises.writeFile(absoluteFilePath, `${fileContent}\n`, 'utf-8');
|
|
2539
|
+
return 'created';
|
|
2540
|
+
}
|
|
2541
|
+
/**
|
|
2542
|
+
* Checks whether a path exists and is a file.
|
|
2543
|
+
*/
|
|
2544
|
+
async function isExistingFile$1(path) {
|
|
2545
|
+
try {
|
|
2546
|
+
return (await promises.stat(path)).isFile();
|
|
2547
|
+
}
|
|
2548
|
+
catch (_a) {
|
|
2549
|
+
return false;
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
// Note: [🟡] Code for coder init markdown bootstrapping [ensureCoderMarkdownFile](src/cli/cli-commands/coder/ensureCoderMarkdownFile.ts) should never be published outside of `@promptbook/cli`
|
|
2553
|
+
|
|
2554
|
+
/**
|
|
2555
|
+
* Default npm scripts initialized by `ptbk coder init`.
|
|
2556
|
+
*/
|
|
2557
|
+
const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
|
|
2558
|
+
'coder:generate-boilerplates': 'npx ptbk coder generate-boilerplates',
|
|
2559
|
+
'coder:run': 'npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --no-wait',
|
|
2560
|
+
'coder:find-refactor-candidates': 'npx ptbk coder find-refactor-candidates',
|
|
2561
|
+
'coder:verify': 'npx ptbk coder verify',
|
|
2562
|
+
};
|
|
2563
|
+
/**
|
|
2564
|
+
* Lists the default npm scripts initialized by `ptbk coder init`.
|
|
2565
|
+
*
|
|
2566
|
+
* @private internal utility of `coder init` command
|
|
2567
|
+
*/
|
|
2568
|
+
function getDefaultCoderPackageJsonScripts() {
|
|
2569
|
+
return DEFAULT_CODER_PACKAGE_JSON_SCRIPTS;
|
|
2570
|
+
}
|
|
2571
|
+
// Note: [🟡] Code for coder init package scripts [getDefaultCoderPackageJsonScripts](src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts) should never be published outside of `@promptbook/cli`
|
|
2572
|
+
|
|
2573
|
+
/**
|
|
2574
|
+
* This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
|
|
2575
|
+
*
|
|
2576
|
+
* @public exported from `@promptbook/core`
|
|
2577
|
+
*/
|
|
2578
|
+
class ParseError extends Error {
|
|
2579
|
+
constructor(message) {
|
|
2580
|
+
super(message);
|
|
2581
|
+
this.name = 'ParseError';
|
|
2582
|
+
Object.setPrototypeOf(this, ParseError.prototype);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
// TODO: Maybe split `ParseError` and `ApplyError`
|
|
2586
|
+
|
|
2587
|
+
/**
|
|
2588
|
+
* Default indentation used when creating new JSON configuration files.
|
|
2589
|
+
*/
|
|
2590
|
+
const DEFAULT_JSON_FILE_INDENTATION = ' ';
|
|
2591
|
+
/**
|
|
2592
|
+
* Default newline used when creating new JSON configuration files.
|
|
2593
|
+
*/
|
|
2594
|
+
const DEFAULT_JSON_FILE_NEWLINE = '\n';
|
|
2595
|
+
/**
|
|
2596
|
+
* Ensures one JSON object field contains the provided string-record entries.
|
|
2597
|
+
*
|
|
2598
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2599
|
+
*/
|
|
2600
|
+
async function mergeStringRecordJsonFile({ projectPath, relativeFilePath, fieldPath, nextEntries, ensureParentDirectoryPath, }) {
|
|
2601
|
+
if (ensureParentDirectoryPath) {
|
|
2602
|
+
await promises.mkdir(path.join(projectPath, ensureParentDirectoryPath), { recursive: true });
|
|
2603
|
+
}
|
|
2604
|
+
const absoluteFilePath = path.join(projectPath, relativeFilePath);
|
|
2605
|
+
const fileContent = await readTextFileIfExists(absoluteFilePath);
|
|
2606
|
+
const formatting = detectJsonFileFormatting(fileContent);
|
|
2607
|
+
const jsonObject = fileContent === undefined ? {} : await parseJsonObjectFile(relativeFilePath, fileContent);
|
|
2608
|
+
const existingEntries = getStringRecordOrDefault(jsonObject[fieldPath], relativeFilePath, fieldPath);
|
|
2609
|
+
let hasChanges = fileContent === undefined;
|
|
2610
|
+
const mergedEntries = { ...existingEntries };
|
|
2611
|
+
for (const [entryKey, entryValue] of Object.entries(nextEntries)) {
|
|
2612
|
+
if (mergedEntries[entryKey] !== entryValue) {
|
|
2613
|
+
mergedEntries[entryKey] = entryValue;
|
|
2273
2614
|
hasChanges = true;
|
|
2274
2615
|
}
|
|
2275
2616
|
}
|
|
2276
2617
|
if (!hasChanges) {
|
|
2277
2618
|
return 'unchanged';
|
|
2278
2619
|
}
|
|
2279
|
-
const
|
|
2280
|
-
|
|
2281
|
-
await promises.writeFile(
|
|
2282
|
-
return
|
|
2620
|
+
const nextJsonObject = { ...jsonObject };
|
|
2621
|
+
nextJsonObject[fieldPath] = mergedEntries;
|
|
2622
|
+
await promises.writeFile(absoluteFilePath, serializeJsonObject(nextJsonObject, formatting), 'utf-8');
|
|
2623
|
+
return fileContent === undefined ? 'created' : 'updated';
|
|
2283
2624
|
}
|
|
2284
2625
|
/**
|
|
2285
|
-
*
|
|
2626
|
+
* Parses one JSON object file while accepting VS Code style comments and trailing commas.
|
|
2286
2627
|
*/
|
|
2287
|
-
async function
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
const vscodeSettingsContent = await readTextFileIfExists(vscodeSettingsPath);
|
|
2291
|
-
const formatting = detectJsonFileFormatting(vscodeSettingsContent);
|
|
2292
|
-
const vscodeSettings = vscodeSettingsContent === undefined
|
|
2293
|
-
? {}
|
|
2294
|
-
: await parseJsonObjectFile(VSCODE_SETTINGS_FILE_PATH, vscodeSettingsContent);
|
|
2295
|
-
const markdownCopyFilesDestinations = getStringRecordOrDefault(vscodeSettings[MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY], VSCODE_SETTINGS_FILE_PATH, MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY);
|
|
2296
|
-
let hasChanges = vscodeSettingsContent === undefined;
|
|
2297
|
-
const nextMarkdownCopyFilesDestinations = { ...markdownCopyFilesDestinations };
|
|
2298
|
-
if (nextMarkdownCopyFilesDestinations[PROMPTS_MARKDOWN_FILE_GLOB] !== PROMPTS_SCREENSHOT_DESTINATION) {
|
|
2299
|
-
nextMarkdownCopyFilesDestinations[PROMPTS_MARKDOWN_FILE_GLOB] = PROMPTS_SCREENSHOT_DESTINATION;
|
|
2300
|
-
hasChanges = true;
|
|
2628
|
+
async function parseJsonObjectFile(relativeFilePath, fileContent) {
|
|
2629
|
+
if (fileContent.trim() === '') {
|
|
2630
|
+
return {};
|
|
2301
2631
|
}
|
|
2302
|
-
|
|
2303
|
-
|
|
2632
|
+
const typescript = await import('typescript');
|
|
2633
|
+
const parsedFile = typescript.parseConfigFileTextToJson(relativeFilePath, fileContent);
|
|
2634
|
+
if (parsedFile.error) {
|
|
2635
|
+
throw new ParseError(_spaceTrim.spaceTrim(`
|
|
2636
|
+
Cannot parse \`${relativeFilePath}\` as JSON.
|
|
2637
|
+
|
|
2638
|
+
${typescript.flattenDiagnosticMessageText(parsedFile.error.messageText, '\n')}
|
|
2639
|
+
`));
|
|
2304
2640
|
}
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2641
|
+
if (!isPlainObject(parsedFile.config)) {
|
|
2642
|
+
throw new ParseError(_spaceTrim.spaceTrim(`
|
|
2643
|
+
File \`${relativeFilePath}\` must contain one top-level JSON object.
|
|
2644
|
+
`));
|
|
2645
|
+
}
|
|
2646
|
+
return parsedFile.config;
|
|
2309
2647
|
}
|
|
2310
2648
|
/**
|
|
2311
|
-
*
|
|
2649
|
+
* Reads one JSON object field as a string-to-string record.
|
|
2312
2650
|
*/
|
|
2313
|
-
function
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2651
|
+
function getStringRecordOrDefault(value, relativeFilePath, fieldPath) {
|
|
2652
|
+
if (value === undefined) {
|
|
2653
|
+
return {};
|
|
2654
|
+
}
|
|
2655
|
+
if (!isPlainObject(value)) {
|
|
2656
|
+
throw new ParseError(_spaceTrim.spaceTrim(`
|
|
2657
|
+
File \`${relativeFilePath}\` contains invalid \`${fieldPath}\`.
|
|
2658
|
+
|
|
2659
|
+
Expected \`${fieldPath}\` to be an object with string values.
|
|
2660
|
+
`));
|
|
2661
|
+
}
|
|
2662
|
+
const stringRecord = {};
|
|
2663
|
+
for (const [key, itemValue] of Object.entries(value)) {
|
|
2664
|
+
if (typeof itemValue !== 'string') {
|
|
2665
|
+
throw new ParseError(_spaceTrim.spaceTrim(`
|
|
2666
|
+
File \`${relativeFilePath}\` contains invalid \`${fieldPath}.${key}\`.
|
|
2667
|
+
|
|
2668
|
+
Expected \`${fieldPath}\` to be an object with string values.
|
|
2669
|
+
`));
|
|
2323
2670
|
}
|
|
2324
|
-
|
|
2671
|
+
stringRecord[key] = itemValue;
|
|
2325
2672
|
}
|
|
2326
|
-
return
|
|
2673
|
+
return stringRecord;
|
|
2327
2674
|
}
|
|
2328
2675
|
/**
|
|
2329
|
-
*
|
|
2676
|
+
* Serializes one JSON object using detected or default formatting.
|
|
2330
2677
|
*/
|
|
2331
|
-
function
|
|
2332
|
-
return
|
|
2333
|
-
# Promptbook coder identity (initialized by \`ptbk coder init\`)
|
|
2334
|
-
${variables.map(({ name, value }) => `${name}=${JSON.stringify(value)}`).join('\n')}
|
|
2335
|
-
`);
|
|
2678
|
+
function serializeJsonObject(value, formatting) {
|
|
2679
|
+
return `${JSON.stringify(value, null, formatting.indentation)}${formatting.newline}`;
|
|
2336
2680
|
}
|
|
2337
2681
|
/**
|
|
2338
|
-
*
|
|
2682
|
+
* Detects indentation and newline formatting from an existing JSON file.
|
|
2339
2683
|
*/
|
|
2340
|
-
function
|
|
2341
|
-
if (
|
|
2342
|
-
return
|
|
2684
|
+
function detectJsonFileFormatting(fileContent) {
|
|
2685
|
+
if (!fileContent) {
|
|
2686
|
+
return {
|
|
2687
|
+
indentation: DEFAULT_JSON_FILE_INDENTATION,
|
|
2688
|
+
newline: DEFAULT_JSON_FILE_NEWLINE,
|
|
2689
|
+
};
|
|
2343
2690
|
}
|
|
2344
|
-
const
|
|
2345
|
-
return
|
|
2691
|
+
const indentationMatch = fileContent.match(/^[ \t]+(?=")/mu);
|
|
2692
|
+
return {
|
|
2693
|
+
indentation: (indentationMatch === null || indentationMatch === void 0 ? void 0 : indentationMatch[0]) || DEFAULT_JSON_FILE_INDENTATION,
|
|
2694
|
+
newline: fileContent.includes('\r\n') ? '\r\n' : '\n',
|
|
2695
|
+
};
|
|
2696
|
+
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Checks whether one parsed JSON value is a plain object.
|
|
2699
|
+
*/
|
|
2700
|
+
function isPlainObject(value) {
|
|
2701
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2702
|
+
}
|
|
2703
|
+
// Note: [🟡] Code for coder init JSON merging [mergeStringRecordJsonFile](src/cli/cli-commands/coder/mergeStringRecordJsonFile.ts) should never be published outside of `@promptbook/cli`
|
|
2704
|
+
|
|
2705
|
+
/**
|
|
2706
|
+
* Relative path to `package.json` in the initialized project.
|
|
2707
|
+
*/
|
|
2708
|
+
const PACKAGE_JSON_FILE_PATH = 'package.json';
|
|
2709
|
+
/**
|
|
2710
|
+
* Ensures `package.json` contains the standalone Promptbook coder helper scripts.
|
|
2711
|
+
*
|
|
2712
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2713
|
+
*/
|
|
2714
|
+
async function ensureCoderPackageJsonFile(projectPath) {
|
|
2715
|
+
return mergeStringRecordJsonFile({
|
|
2716
|
+
projectPath,
|
|
2717
|
+
relativeFilePath: PACKAGE_JSON_FILE_PATH,
|
|
2718
|
+
fieldPath: 'scripts',
|
|
2719
|
+
nextEntries: getDefaultCoderPackageJsonScripts(),
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
// Note: [🟡] Code for coder init package.json bootstrapping [ensureCoderPackageJsonFile](src/cli/cli-commands/coder/ensureCoderPackageJsonFile.ts) should never be published outside of `@promptbook/cli`
|
|
2723
|
+
|
|
2724
|
+
/**
|
|
2725
|
+
* VS Code setting key used to route pasted markdown images into prompt-specific screenshots.
|
|
2726
|
+
*/
|
|
2727
|
+
const MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY = 'markdown.copyFiles.destination';
|
|
2728
|
+
/**
|
|
2729
|
+
* Markdown glob used for coder prompt files inside VS Code settings.
|
|
2730
|
+
*/
|
|
2731
|
+
const PROMPTS_MARKDOWN_FILE_GLOB = 'prompts/*md';
|
|
2732
|
+
/**
|
|
2733
|
+
* Screenshot destination used for pasted prompt images inside VS Code settings.
|
|
2734
|
+
*/
|
|
2735
|
+
const PROMPTS_SCREENSHOT_DESTINATION = './prompts/screenshots/${documentBaseName}.png';
|
|
2736
|
+
/**
|
|
2737
|
+
* Default VS Code settings initialized by `ptbk coder init`.
|
|
2738
|
+
*/
|
|
2739
|
+
const DEFAULT_CODER_VSCODE_SETTINGS = {
|
|
2740
|
+
[MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY]: {
|
|
2741
|
+
[PROMPTS_MARKDOWN_FILE_GLOB]: PROMPTS_SCREENSHOT_DESTINATION,
|
|
2742
|
+
},
|
|
2743
|
+
};
|
|
2744
|
+
/**
|
|
2745
|
+
* Lists the default VS Code settings initialized by `ptbk coder init`.
|
|
2746
|
+
*
|
|
2747
|
+
* @private internal utility of `coder init` command
|
|
2748
|
+
*/
|
|
2749
|
+
function getDefaultCoderVscodeSettings() {
|
|
2750
|
+
return DEFAULT_CODER_VSCODE_SETTINGS;
|
|
2751
|
+
}
|
|
2752
|
+
// Note: [🟡] Code for coder init VS Code settings [getDefaultCoderVscodeSettings](src/cli/cli-commands/coder/getDefaultCoderVscodeSettings.ts) should never be published outside of `@promptbook/cli`
|
|
2753
|
+
|
|
2754
|
+
/**
|
|
2755
|
+
* Relative path to the VS Code settings file initialized by `ptbk coder init`.
|
|
2756
|
+
*/
|
|
2757
|
+
const VSCODE_SETTINGS_FILE_PATH = '.vscode/settings.json';
|
|
2758
|
+
/**
|
|
2759
|
+
* Relative path to the VS Code directory initialized by `ptbk coder init`.
|
|
2760
|
+
*/
|
|
2761
|
+
const VSCODE_DIRECTORY_PATH = '.vscode';
|
|
2762
|
+
/**
|
|
2763
|
+
* Ensures VS Code routes pasted prompt images into `prompts/screenshots`.
|
|
2764
|
+
*
|
|
2765
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2766
|
+
*/
|
|
2767
|
+
async function ensureCoderVscodeSettingsFile(projectPath) {
|
|
2768
|
+
const [fieldPath, nextEntries] = resolveDefaultCoderVscodeSettingsEntry();
|
|
2769
|
+
return mergeStringRecordJsonFile({
|
|
2770
|
+
projectPath,
|
|
2771
|
+
relativeFilePath: VSCODE_SETTINGS_FILE_PATH,
|
|
2772
|
+
fieldPath,
|
|
2773
|
+
nextEntries,
|
|
2774
|
+
ensureParentDirectoryPath: VSCODE_DIRECTORY_PATH,
|
|
2775
|
+
});
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Resolves the default string-record entry that `coder init` merges into VS Code settings.
|
|
2779
|
+
*/
|
|
2780
|
+
function resolveDefaultCoderVscodeSettingsEntry() {
|
|
2781
|
+
const [defaultVscodeSettingsEntry] = Object.entries(getDefaultCoderVscodeSettings());
|
|
2782
|
+
if (!defaultVscodeSettingsEntry) {
|
|
2783
|
+
throw new Error('Default coder VS Code settings must define at least one string-record entry.');
|
|
2784
|
+
}
|
|
2785
|
+
return defaultVscodeSettingsEntry;
|
|
2786
|
+
}
|
|
2787
|
+
// Note: [🟡] Code for coder init VS Code bootstrapping [ensureCoderVscodeSettingsFile](src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.ts) should never be published outside of `@promptbook/cli`
|
|
2788
|
+
|
|
2789
|
+
/**
|
|
2790
|
+
* Ensures a relative directory exists in the project root.
|
|
2791
|
+
*
|
|
2792
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2793
|
+
*/
|
|
2794
|
+
async function ensureDirectory(projectPath, relativeDirectoryPath) {
|
|
2795
|
+
const directoryPath = path.join(projectPath, relativeDirectoryPath);
|
|
2796
|
+
const isDirectoryExisting = await isExistingDirectory(directoryPath);
|
|
2797
|
+
if (!isDirectoryExisting) {
|
|
2798
|
+
await promises.mkdir(directoryPath, { recursive: true });
|
|
2799
|
+
return 'created';
|
|
2800
|
+
}
|
|
2801
|
+
return 'unchanged';
|
|
2802
|
+
}
|
|
2803
|
+
/**
|
|
2804
|
+
* Checks whether a path exists and is a directory.
|
|
2805
|
+
*/
|
|
2806
|
+
async function isExistingDirectory(path) {
|
|
2807
|
+
try {
|
|
2808
|
+
return (await promises.stat(path)).isDirectory();
|
|
2809
|
+
}
|
|
2810
|
+
catch (_a) {
|
|
2811
|
+
return false;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
// Note: [🟡] Code for coder init directory creation [ensureDirectory](src/cli/cli-commands/coder/ensureDirectory.ts) should never be published outside of `@promptbook/cli`
|
|
2815
|
+
|
|
2816
|
+
/**
|
|
2817
|
+
* Creates or updates all coder configuration artifacts required in the current project.
|
|
2818
|
+
*
|
|
2819
|
+
* @private internal utility of `coder init` command
|
|
2820
|
+
*/
|
|
2821
|
+
async function initializeCoderProjectConfiguration(projectPath) {
|
|
2822
|
+
const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH);
|
|
2823
|
+
const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
|
|
2824
|
+
const promptsTemplatesDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_TEMPLATES_DIRECTORY_PATH);
|
|
2825
|
+
const promptTemplateFileStatuses = await ensureDefaultCoderPromptTemplateFiles(projectPath);
|
|
2826
|
+
const agentsFileStatus = await ensureCoderMarkdownFile(projectPath, AGENTS_FILE_PATH, getDefaultCoderAgentsFileContent());
|
|
2827
|
+
const agentCodingFileStatus = await ensureCoderMarkdownFile(projectPath, AGENT_CODING_FILE_PATH, getDefaultCoderAgentCodingFileContent({
|
|
2828
|
+
packageJsonScripts: getDefaultCoderPackageJsonScripts(),
|
|
2829
|
+
}));
|
|
2830
|
+
const { envFileStatus, initializedEnvVariableNames } = await ensureCoderEnvFile(projectPath);
|
|
2831
|
+
const gitignoreFileStatus = await ensureCoderGitignoreFile(projectPath);
|
|
2832
|
+
const packageJsonFileStatus = await ensureCoderPackageJsonFile(projectPath);
|
|
2833
|
+
const vscodeSettingsFileStatus = await ensureCoderVscodeSettingsFile(projectPath);
|
|
2834
|
+
return {
|
|
2835
|
+
promptsDirectoryStatus,
|
|
2836
|
+
promptsDoneDirectoryStatus,
|
|
2837
|
+
promptsTemplatesDirectoryStatus,
|
|
2838
|
+
promptTemplateFileStatuses,
|
|
2839
|
+
agentsFileStatus,
|
|
2840
|
+
agentCodingFileStatus,
|
|
2841
|
+
envFileStatus,
|
|
2842
|
+
gitignoreFileStatus,
|
|
2843
|
+
packageJsonFileStatus,
|
|
2844
|
+
vscodeSettingsFileStatus,
|
|
2845
|
+
initializedEnvVariableNames,
|
|
2846
|
+
};
|
|
2346
2847
|
}
|
|
2848
|
+
// Note: [🟡] Code for coder init project bootstrapping [initializeCoderProjectConfiguration](src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts) should never be published outside of `@promptbook/cli`
|
|
2849
|
+
|
|
2347
2850
|
/**
|
|
2348
2851
|
* Prints a readable summary of what was initialized for the user.
|
|
2852
|
+
*
|
|
2853
|
+
* @private function of `coder init` command
|
|
2349
2854
|
*/
|
|
2350
2855
|
function printInitializationSummary(summary) {
|
|
2351
2856
|
console.info(colors__default["default"].green('Promptbook coder configuration initialized.'));
|
|
@@ -2355,6 +2860,8 @@
|
|
|
2355
2860
|
for (const templateFileStatus of summary.promptTemplateFileStatuses) {
|
|
2356
2861
|
printInitializationStatusLine(formatDisplayPath(templateFileStatus.relativeFilePath), templateFileStatus.status);
|
|
2357
2862
|
}
|
|
2863
|
+
printInitializationStatusLine(AGENTS_FILE_PATH, summary.agentsFileStatus);
|
|
2864
|
+
printInitializationStatusLine(AGENT_CODING_FILE_PATH, summary.agentCodingFileStatus);
|
|
2358
2865
|
printInitializationStatusLine('.env', summary.envFileStatus);
|
|
2359
2866
|
printInitializationStatusLine('.gitignore', summary.gitignoreFileStatus);
|
|
2360
2867
|
printInitializationStatusLine('package.json', summary.packageJsonFileStatus);
|
|
@@ -2390,126 +2897,48 @@
|
|
|
2390
2897
|
function printInitializationNote(message, colorize) {
|
|
2391
2898
|
console.info(colorize(`✔ ${message}`));
|
|
2392
2899
|
}
|
|
2393
|
-
|
|
2394
|
-
* Normalizes one project-relative path for human-readable CLI output.
|
|
2395
|
-
*/
|
|
2396
|
-
function formatDisplayPath(relativePath) {
|
|
2397
|
-
return relativePath.replace(/\\/gu, '/');
|
|
2398
|
-
}
|
|
2399
|
-
/**
|
|
2400
|
-
* Detects whether `.gitignore` already covers the standalone coder temp directory.
|
|
2401
|
-
*/
|
|
2402
|
-
function hasTmpGitignoreRule(gitignoreContent) {
|
|
2403
|
-
return /(^|[\r\n])\/?\.tmp(?:[\r\n]|$)/u.test(gitignoreContent);
|
|
2404
|
-
}
|
|
2405
|
-
/**
|
|
2406
|
-
* Reads one text file when it exists, otherwise returns `undefined`.
|
|
2407
|
-
*/
|
|
2408
|
-
async function readTextFileIfExists(path) {
|
|
2409
|
-
if (!(await isExistingFile(path))) {
|
|
2410
|
-
return undefined;
|
|
2411
|
-
}
|
|
2412
|
-
return promises.readFile(path, 'utf-8');
|
|
2413
|
-
}
|
|
2414
|
-
/**
|
|
2415
|
-
* Parses one JSON object file while accepting VS Code style comments and trailing commas.
|
|
2416
|
-
*/
|
|
2417
|
-
async function parseJsonObjectFile(relativeFilePath, fileContent) {
|
|
2418
|
-
if (fileContent.trim() === '') {
|
|
2419
|
-
return {};
|
|
2420
|
-
}
|
|
2421
|
-
const typescript = await import('typescript');
|
|
2422
|
-
const parsedFile = typescript.parseConfigFileTextToJson(relativeFilePath, fileContent);
|
|
2423
|
-
if (parsedFile.error) {
|
|
2424
|
-
throw new ParseError(_spaceTrim.spaceTrim(`
|
|
2425
|
-
Cannot parse \`${relativeFilePath}\` as JSON.
|
|
2900
|
+
// Note: [🟡] Code for coder init summary printing [printInitializationSummary](src/cli/cli-commands/coder/printInitializationSummary.ts) should never be published outside of `@promptbook/cli`
|
|
2426
2901
|
|
|
2427
|
-
${typescript.flattenDiagnosticMessageText(parsedFile.error.messageText, '\n')}
|
|
2428
|
-
`));
|
|
2429
|
-
}
|
|
2430
|
-
if (!isPlainObject(parsedFile.config)) {
|
|
2431
|
-
throw new ParseError(_spaceTrim.spaceTrim(`
|
|
2432
|
-
File \`${relativeFilePath}\` must contain one top-level JSON object.
|
|
2433
|
-
`));
|
|
2434
|
-
}
|
|
2435
|
-
return parsedFile.config;
|
|
2436
|
-
}
|
|
2437
2902
|
/**
|
|
2438
|
-
*
|
|
2903
|
+
* Initializes `coder init` command for Promptbook CLI utilities.
|
|
2904
|
+
*
|
|
2905
|
+
* Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI.
|
|
2906
|
+
*
|
|
2907
|
+
* @private internal function of `promptbookCli`
|
|
2439
2908
|
*/
|
|
2440
|
-
function
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
throw new ParseError(_spaceTrim.spaceTrim(`
|
|
2446
|
-
File \`${relativeFilePath}\` contains invalid \`${fieldPath}\`.
|
|
2909
|
+
function $initializeCoderInitCommand(program) {
|
|
2910
|
+
const command = program.command('init');
|
|
2911
|
+
command.alias('initialize');
|
|
2912
|
+
command.description(_spaceTrim.spaceTrim(`
|
|
2913
|
+
Initialize Promptbook coder configuration for current project
|
|
2447
2914
|
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2915
|
+
Creates or updates:
|
|
2916
|
+
- prompts/
|
|
2917
|
+
- prompts/done/
|
|
2918
|
+
${listDefaultCoderProjectPromptTemplateDisplayPaths()}
|
|
2919
|
+
- ${AGENTS_FILE_PATH}
|
|
2920
|
+
- ${AGENT_CODING_FILE_PATH}
|
|
2921
|
+
- .gitignore
|
|
2922
|
+
- package.json
|
|
2923
|
+
- .vscode/settings.json
|
|
2456
2924
|
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
*/
|
|
2467
|
-
function serializeJsonObject(value, formatting) {
|
|
2468
|
-
return `${JSON.stringify(value, null, formatting.indentation)}${formatting.newline}`;
|
|
2469
|
-
}
|
|
2470
|
-
/**
|
|
2471
|
-
* Detects indentation and newline formatting from an existing JSON file.
|
|
2472
|
-
*/
|
|
2473
|
-
function detectJsonFileFormatting(fileContent) {
|
|
2474
|
-
if (!fileContent) {
|
|
2475
|
-
return {
|
|
2476
|
-
indentation: DEFAULT_JSON_FILE_INDENTATION,
|
|
2477
|
-
newline: DEFAULT_JSON_FILE_NEWLINE,
|
|
2478
|
-
};
|
|
2479
|
-
}
|
|
2480
|
-
const indentationMatch = fileContent.match(/^[ \t]+(?=")/mu);
|
|
2481
|
-
return {
|
|
2482
|
-
indentation: (indentationMatch === null || indentationMatch === void 0 ? void 0 : indentationMatch[0]) || DEFAULT_JSON_FILE_INDENTATION,
|
|
2483
|
-
newline: fileContent.includes('\r\n') ? '\r\n' : '\n',
|
|
2484
|
-
};
|
|
2485
|
-
}
|
|
2486
|
-
/**
|
|
2487
|
-
* Checks whether one parsed JSON value is a plain object.
|
|
2488
|
-
*/
|
|
2489
|
-
function isPlainObject(value) {
|
|
2490
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2491
|
-
}
|
|
2492
|
-
/**
|
|
2493
|
-
* Checks whether a path exists and is a file.
|
|
2494
|
-
*/
|
|
2495
|
-
async function isExistingFile(path) {
|
|
2496
|
-
try {
|
|
2497
|
-
return (await promises.stat(path)).isFile();
|
|
2498
|
-
}
|
|
2499
|
-
catch (_a) {
|
|
2500
|
-
return false;
|
|
2501
|
-
}
|
|
2925
|
+
Ensures required coding-agent environment variables in .env:
|
|
2926
|
+
- CODING_AGENT_GIT_NAME
|
|
2927
|
+
- CODING_AGENT_GIT_EMAIL
|
|
2928
|
+
- CODING_AGENT_GIT_SIGNING_KEY
|
|
2929
|
+
`));
|
|
2930
|
+
command.action(handleActionErrors(async () => {
|
|
2931
|
+
const summary = await initializeCoderProjectConfiguration(process.cwd());
|
|
2932
|
+
printInitializationSummary(summary);
|
|
2933
|
+
}));
|
|
2502
2934
|
}
|
|
2503
2935
|
/**
|
|
2504
|
-
*
|
|
2936
|
+
* Lists the project-owned template file paths created by `ptbk coder init`.
|
|
2505
2937
|
*/
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
catch (_a) {
|
|
2511
|
-
return false;
|
|
2512
|
-
}
|
|
2938
|
+
function listDefaultCoderProjectPromptTemplateDisplayPaths() {
|
|
2939
|
+
return getDefaultCoderProjectPromptTemplateDefinitions()
|
|
2940
|
+
.map(({ relativeFilePath }) => `- ${formatDisplayPath(relativeFilePath)}`)
|
|
2941
|
+
.join('\n');
|
|
2513
2942
|
}
|
|
2514
2943
|
// Note: [🟡] Code for CLI command [init](src/cli/cli-commands/coder/init.ts) should never be published outside of `@promptbook/cli`
|
|
2515
2944
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -40743,91 +41172,6 @@
|
|
|
40743
41172
|
findFreshEmojiTag: findFreshEmojiTag
|
|
40744
41173
|
});
|
|
40745
41174
|
|
|
40746
|
-
/**
|
|
40747
|
-
* Root folders that contain source-like files for scanning.
|
|
40748
|
-
*/
|
|
40749
|
-
const SOURCE_ROOTS = ['src', 'apps', 'scripts', 'examples', 'agents', 'other'];
|
|
40750
|
-
/**
|
|
40751
|
-
* File extensions treated as source code.
|
|
40752
|
-
*/
|
|
40753
|
-
const SOURCE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
40754
|
-
/**
|
|
40755
|
-
* Glob patterns that should be ignored when scanning for source files.
|
|
40756
|
-
*/
|
|
40757
|
-
const SOURCE_FILE_IGNORE_GLOBS = [
|
|
40758
|
-
'**/node_modules/**',
|
|
40759
|
-
'**/packages/**',
|
|
40760
|
-
'**/.*/**',
|
|
40761
|
-
'**/.git/**',
|
|
40762
|
-
'**/.idea/**',
|
|
40763
|
-
'**/.vscode/**',
|
|
40764
|
-
'**/.promptbook/**',
|
|
40765
|
-
'**/.next/**',
|
|
40766
|
-
'**/.tmp/**',
|
|
40767
|
-
'**/tmp/**',
|
|
40768
|
-
'**/coverage/**',
|
|
40769
|
-
'**/dist/**',
|
|
40770
|
-
'**/build/**',
|
|
40771
|
-
'**/out/**',
|
|
40772
|
-
'**/prompts/**',
|
|
40773
|
-
'**/changelog/**',
|
|
40774
|
-
];
|
|
40775
|
-
/**
|
|
40776
|
-
* Default maximum line count for source files.
|
|
40777
|
-
*/
|
|
40778
|
-
const DEFAULT_MAX_LINE_COUNT = 2000;
|
|
40779
|
-
/**
|
|
40780
|
-
* Per-extension line count limits.
|
|
40781
|
-
*/
|
|
40782
|
-
const LINE_COUNT_LIMITS_BY_EXTENSION = {
|
|
40783
|
-
'.ts': 2000,
|
|
40784
|
-
'.tsx': 2000,
|
|
40785
|
-
'.js': 2000,
|
|
40786
|
-
'.jsx': 2000,
|
|
40787
|
-
};
|
|
40788
|
-
/**
|
|
40789
|
-
* Glob patterns that are exempt from line-count checks.
|
|
40790
|
-
*/
|
|
40791
|
-
const LINE_COUNT_EXEMPT_GLOBS = ['other/cspell-dictionaries/**/*.txt'];
|
|
40792
|
-
/**
|
|
40793
|
-
* Maximum number of entities before a file is flagged.
|
|
40794
|
-
*/
|
|
40795
|
-
const MAX_ENTITIES_PER_FILE = 20;
|
|
40796
|
-
/**
|
|
40797
|
-
* File extensions eligible for entity counting.
|
|
40798
|
-
*/
|
|
40799
|
-
const ENTITY_COUNT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
40800
|
-
/**
|
|
40801
|
-
* Markers that identify generated files which should be skipped.
|
|
40802
|
-
*/
|
|
40803
|
-
const GENERATED_CODE_MARKERS = [
|
|
40804
|
-
'WARNING: This code has been generated',
|
|
40805
|
-
'This code has been generated so that any manual changes will be overwritten',
|
|
40806
|
-
];
|
|
40807
|
-
/**
|
|
40808
|
-
* Name of the prompts directory.
|
|
40809
|
-
*/
|
|
40810
|
-
const PROMPTS_DIR_NAME = 'prompts';
|
|
40811
|
-
/**
|
|
40812
|
-
* Step size used for prompt numbering.
|
|
40813
|
-
*/
|
|
40814
|
-
const PROMPT_NUMBER_STEP = 10;
|
|
40815
|
-
/**
|
|
40816
|
-
* Prefix used for generated prompt slugs.
|
|
40817
|
-
*/
|
|
40818
|
-
const PROMPT_SLUG_PREFIX = 'refactor';
|
|
40819
|
-
/**
|
|
40820
|
-
* Label used to mark the target file in generated prompts.
|
|
40821
|
-
*/
|
|
40822
|
-
const PROMPT_TARGET_LABEL = 'Target file';
|
|
40823
|
-
/**
|
|
40824
|
-
* Maximum length for generated prompt slugs.
|
|
40825
|
-
*/
|
|
40826
|
-
const PROMPT_SLUG_MAX_LENGTH = 80;
|
|
40827
|
-
/**
|
|
40828
|
-
* Note: [?] Code in this file should never be published in any package
|
|
40829
|
-
*/
|
|
40830
|
-
|
|
40831
41175
|
/**
|
|
40832
41176
|
* Normalizes a repo-relative path to use forward slashes.
|
|
40833
41177
|
*
|
|
@@ -40845,7 +41189,7 @@
|
|
|
40845
41189
|
* @private function of findRefactorCandidates
|
|
40846
41190
|
*/
|
|
40847
41191
|
async function analyzeSourceFileForRefactorCandidate(options) {
|
|
40848
|
-
const { filePath, lineCountExemptPaths, rootDir } = options;
|
|
41192
|
+
const { filePath, heuristics, lineCountExemptPaths, rootDir } = options;
|
|
40849
41193
|
const normalizedAbsolutePath = normalizeAbsolutePath$1(filePath);
|
|
40850
41194
|
const content = await promises.readFile(filePath, 'utf-8');
|
|
40851
41195
|
if (isGeneratedFile(content)) {
|
|
@@ -40856,15 +41200,21 @@
|
|
|
40856
41200
|
const reasons = [];
|
|
40857
41201
|
if (!lineCountExemptPaths.has(normalizedAbsolutePath)) {
|
|
40858
41202
|
const lineCount = countLines(content);
|
|
40859
|
-
const maxLines = getMaxLinesForExtension(extension);
|
|
41203
|
+
const maxLines = getMaxLinesForExtension(extension, heuristics);
|
|
40860
41204
|
if (lineCount > maxLines) {
|
|
40861
41205
|
reasons.push(`lines ${lineCount}/${maxLines}`);
|
|
40862
41206
|
}
|
|
40863
41207
|
}
|
|
40864
|
-
if (
|
|
40865
|
-
const
|
|
40866
|
-
if (entityCount >
|
|
40867
|
-
reasons.push(`entities ${entityCount}/${
|
|
41208
|
+
if (STRUCTURAL_ANALYSIS_EXTENSIONS.includes(extension)) {
|
|
41209
|
+
const structureSummary = summarizeSourceFileStructure(content, extension, filePath);
|
|
41210
|
+
if (structureSummary.entityCount > heuristics.maxEntityCountPerFile) {
|
|
41211
|
+
reasons.push(`entities ${structureSummary.entityCount}/${heuristics.maxEntityCountPerFile}`);
|
|
41212
|
+
}
|
|
41213
|
+
if (structureSummary.functionCount > heuristics.maxFunctionCountPerFile) {
|
|
41214
|
+
reasons.push(`functions ${structureSummary.functionCount}/${heuristics.maxFunctionCountPerFile}`);
|
|
41215
|
+
}
|
|
41216
|
+
if (structureSummary.maxFunctionComplexity > heuristics.maxFunctionComplexity) {
|
|
41217
|
+
reasons.push(buildComplexityReason(structureSummary, heuristics.maxFunctionComplexity));
|
|
40868
41218
|
}
|
|
40869
41219
|
}
|
|
40870
41220
|
if (reasons.length === 0) {
|
|
@@ -40889,9 +41239,9 @@
|
|
|
40889
41239
|
*
|
|
40890
41240
|
* @private function of analyzeSourceFileForRefactorCandidate
|
|
40891
41241
|
*/
|
|
40892
|
-
function getMaxLinesForExtension(extension) {
|
|
41242
|
+
function getMaxLinesForExtension(extension, heuristics) {
|
|
40893
41243
|
var _a;
|
|
40894
|
-
return (_a =
|
|
41244
|
+
return (_a = heuristics.maxLineCountByExtension[extension]) !== null && _a !== void 0 ? _a : heuristics.maxDefaultLineCount;
|
|
40895
41245
|
}
|
|
40896
41246
|
/**
|
|
40897
41247
|
* Counts lines while ignoring a trailing newline.
|
|
@@ -40906,14 +41256,17 @@
|
|
|
40906
41256
|
return lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
|
|
40907
41257
|
}
|
|
40908
41258
|
/**
|
|
40909
|
-
*
|
|
41259
|
+
* Summarizes the structural metrics used to score one source file.
|
|
40910
41260
|
*
|
|
40911
41261
|
* @private function of analyzeSourceFileForRefactorCandidate
|
|
40912
41262
|
*/
|
|
40913
|
-
function
|
|
41263
|
+
function summarizeSourceFileStructure(content, extension, filePath) {
|
|
40914
41264
|
const scriptKind = getScriptKindForExtension(extension);
|
|
40915
|
-
const sourceFile = ts__namespace.createSourceFile(filePath, content, ts__namespace.ScriptTarget.Latest,
|
|
40916
|
-
return
|
|
41265
|
+
const sourceFile = ts__namespace.createSourceFile(filePath, content, ts__namespace.ScriptTarget.Latest, true, scriptKind);
|
|
41266
|
+
return {
|
|
41267
|
+
entityCount: countEntitiesInSourceFile(sourceFile),
|
|
41268
|
+
...summarizeFunctionsInSourceFile(sourceFile),
|
|
41269
|
+
};
|
|
40917
41270
|
}
|
|
40918
41271
|
/**
|
|
40919
41272
|
* Counts top-level entities in a parsed TypeScript source file.
|
|
@@ -40947,6 +41300,169 @@
|
|
|
40947
41300
|
}
|
|
40948
41301
|
return count;
|
|
40949
41302
|
}
|
|
41303
|
+
/**
|
|
41304
|
+
* Summarizes named functions and methods in a parsed source file.
|
|
41305
|
+
*
|
|
41306
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41307
|
+
*/
|
|
41308
|
+
function summarizeFunctionsInSourceFile(sourceFile) {
|
|
41309
|
+
let functionCount = 0;
|
|
41310
|
+
let maxFunctionComplexity = 0;
|
|
41311
|
+
let mostComplexFunctionName = null;
|
|
41312
|
+
const visitNode = (node) => {
|
|
41313
|
+
if (isCountedFunctionLikeDeclaration(node)) {
|
|
41314
|
+
functionCount += 1;
|
|
41315
|
+
const functionComplexity = calculateFunctionComplexity(node);
|
|
41316
|
+
if (functionComplexity > maxFunctionComplexity) {
|
|
41317
|
+
maxFunctionComplexity = functionComplexity;
|
|
41318
|
+
mostComplexFunctionName = getFunctionDisplayName(node);
|
|
41319
|
+
}
|
|
41320
|
+
}
|
|
41321
|
+
ts__namespace.forEachChild(node, visitNode);
|
|
41322
|
+
};
|
|
41323
|
+
visitNode(sourceFile);
|
|
41324
|
+
return {
|
|
41325
|
+
functionCount,
|
|
41326
|
+
maxFunctionComplexity,
|
|
41327
|
+
mostComplexFunctionName,
|
|
41328
|
+
};
|
|
41329
|
+
}
|
|
41330
|
+
/**
|
|
41331
|
+
* Determines whether a node counts as a named function or method for density checks.
|
|
41332
|
+
*
|
|
41333
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41334
|
+
*/
|
|
41335
|
+
function isCountedFunctionLikeDeclaration(node) {
|
|
41336
|
+
if (ts__namespace.isFunctionDeclaration(node) ||
|
|
41337
|
+
ts__namespace.isMethodDeclaration(node) ||
|
|
41338
|
+
ts__namespace.isConstructorDeclaration(node) ||
|
|
41339
|
+
ts__namespace.isGetAccessorDeclaration(node) ||
|
|
41340
|
+
ts__namespace.isSetAccessorDeclaration(node)) {
|
|
41341
|
+
return true;
|
|
41342
|
+
}
|
|
41343
|
+
if (ts__namespace.isArrowFunction(node) || ts__namespace.isFunctionExpression(node)) {
|
|
41344
|
+
return isNamedFunctionExpression(node);
|
|
41345
|
+
}
|
|
41346
|
+
return false;
|
|
41347
|
+
}
|
|
41348
|
+
/**
|
|
41349
|
+
* Determines whether a function expression is attached to a named variable or property.
|
|
41350
|
+
*
|
|
41351
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41352
|
+
*/
|
|
41353
|
+
function isNamedFunctionExpression(node) {
|
|
41354
|
+
const parent = node.parent;
|
|
41355
|
+
return (ts__namespace.isVariableDeclaration(parent) || ts__namespace.isPropertyDeclaration(parent) || ts__namespace.isPropertyAssignment(parent));
|
|
41356
|
+
}
|
|
41357
|
+
/**
|
|
41358
|
+
* Calculates a lightweight cyclomatic-complexity score for one function.
|
|
41359
|
+
*
|
|
41360
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41361
|
+
*/
|
|
41362
|
+
function calculateFunctionComplexity(functionNode) {
|
|
41363
|
+
if (!functionNode.body) {
|
|
41364
|
+
return 1;
|
|
41365
|
+
}
|
|
41366
|
+
let complexity = 1;
|
|
41367
|
+
const visitNode = (node) => {
|
|
41368
|
+
if (node !== functionNode.body && isCountedFunctionLikeDeclaration(node)) {
|
|
41369
|
+
return;
|
|
41370
|
+
}
|
|
41371
|
+
if (isComplexityDecisionNode(node)) {
|
|
41372
|
+
complexity += 1;
|
|
41373
|
+
}
|
|
41374
|
+
ts__namespace.forEachChild(node, visitNode);
|
|
41375
|
+
};
|
|
41376
|
+
visitNode(functionNode.body);
|
|
41377
|
+
return complexity;
|
|
41378
|
+
}
|
|
41379
|
+
/**
|
|
41380
|
+
* Determines whether a node should increase the complexity score.
|
|
41381
|
+
*
|
|
41382
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41383
|
+
*/
|
|
41384
|
+
function isComplexityDecisionNode(node) {
|
|
41385
|
+
if (ts__namespace.isIfStatement(node) ||
|
|
41386
|
+
ts__namespace.isConditionalExpression(node) ||
|
|
41387
|
+
ts__namespace.isCatchClause(node) ||
|
|
41388
|
+
ts__namespace.isForStatement(node) ||
|
|
41389
|
+
ts__namespace.isForInStatement(node) ||
|
|
41390
|
+
ts__namespace.isForOfStatement(node) ||
|
|
41391
|
+
ts__namespace.isWhileStatement(node) ||
|
|
41392
|
+
ts__namespace.isDoStatement(node) ||
|
|
41393
|
+
ts__namespace.isCaseClause(node)) {
|
|
41394
|
+
return true;
|
|
41395
|
+
}
|
|
41396
|
+
if (ts__namespace.isBinaryExpression(node)) {
|
|
41397
|
+
const operatorKind = node.operatorToken.kind;
|
|
41398
|
+
return (operatorKind === ts__namespace.SyntaxKind.AmpersandAmpersandToken ||
|
|
41399
|
+
operatorKind === ts__namespace.SyntaxKind.BarBarToken ||
|
|
41400
|
+
operatorKind === ts__namespace.SyntaxKind.QuestionQuestionToken);
|
|
41401
|
+
}
|
|
41402
|
+
return false;
|
|
41403
|
+
}
|
|
41404
|
+
/**
|
|
41405
|
+
* Resolves a readable display name for a counted function-like declaration.
|
|
41406
|
+
*
|
|
41407
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41408
|
+
*/
|
|
41409
|
+
function getFunctionDisplayName(functionNode) {
|
|
41410
|
+
if (ts__namespace.isConstructorDeclaration(functionNode)) {
|
|
41411
|
+
return 'constructor';
|
|
41412
|
+
}
|
|
41413
|
+
if (ts__namespace.isFunctionDeclaration(functionNode) ||
|
|
41414
|
+
ts__namespace.isMethodDeclaration(functionNode) ||
|
|
41415
|
+
ts__namespace.isGetAccessorDeclaration(functionNode) ||
|
|
41416
|
+
ts__namespace.isSetAccessorDeclaration(functionNode)) {
|
|
41417
|
+
if (!functionNode.name) {
|
|
41418
|
+
return null;
|
|
41419
|
+
}
|
|
41420
|
+
return getPropertyNameText(functionNode.name);
|
|
41421
|
+
}
|
|
41422
|
+
if (ts__namespace.isArrowFunction(functionNode) || ts__namespace.isFunctionExpression(functionNode)) {
|
|
41423
|
+
if (functionNode.name) {
|
|
41424
|
+
return functionNode.name.text;
|
|
41425
|
+
}
|
|
41426
|
+
const parent = functionNode.parent;
|
|
41427
|
+
if (ts__namespace.isVariableDeclaration(parent)) {
|
|
41428
|
+
return getBindingNameText(parent.name);
|
|
41429
|
+
}
|
|
41430
|
+
if (ts__namespace.isPropertyDeclaration(parent) || ts__namespace.isPropertyAssignment(parent)) {
|
|
41431
|
+
return getPropertyNameText(parent.name);
|
|
41432
|
+
}
|
|
41433
|
+
}
|
|
41434
|
+
return null;
|
|
41435
|
+
}
|
|
41436
|
+
/**
|
|
41437
|
+
* Resolves text for a binding name when it is a simple identifier.
|
|
41438
|
+
*
|
|
41439
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41440
|
+
*/
|
|
41441
|
+
function getBindingNameText(name) {
|
|
41442
|
+
return ts__namespace.isIdentifier(name) ? name.text : null;
|
|
41443
|
+
}
|
|
41444
|
+
/**
|
|
41445
|
+
* Resolves text for a property name while preserving computed names when necessary.
|
|
41446
|
+
*
|
|
41447
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41448
|
+
*/
|
|
41449
|
+
function getPropertyNameText(name) {
|
|
41450
|
+
if (ts__namespace.isIdentifier(name) || ts__namespace.isPrivateIdentifier(name) || ts__namespace.isStringLiteral(name) || ts__namespace.isNumericLiteral(name)) {
|
|
41451
|
+
return name.text;
|
|
41452
|
+
}
|
|
41453
|
+
return name.getText();
|
|
41454
|
+
}
|
|
41455
|
+
/**
|
|
41456
|
+
* Formats the reason emitted when a function in the file exceeds the complexity threshold.
|
|
41457
|
+
*
|
|
41458
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41459
|
+
*/
|
|
41460
|
+
function buildComplexityReason(structureSummary, maxAllowedFunctionComplexity) {
|
|
41461
|
+
const functionSuffix = structureSummary.mostComplexFunctionName
|
|
41462
|
+
? ` in \`${structureSummary.mostComplexFunctionName}\``
|
|
41463
|
+
: '';
|
|
41464
|
+
return `complexity ${structureSummary.maxFunctionComplexity}/${maxAllowedFunctionComplexity}${functionSuffix}`;
|
|
41465
|
+
}
|
|
40950
41466
|
/**
|
|
40951
41467
|
* Resolves the script kind for a source file extension.
|
|
40952
41468
|
*
|
|
@@ -40980,13 +41496,15 @@
|
|
|
40980
41496
|
*
|
|
40981
41497
|
* @private function of findRefactorCandidates
|
|
40982
41498
|
*/
|
|
40983
|
-
async function findRefactorCandidatesInProject(
|
|
40984
|
-
const
|
|
40985
|
-
const
|
|
41499
|
+
async function findRefactorCandidatesInProject(options) {
|
|
41500
|
+
const { heuristics, isIgnoredRelativePath = () => false, rootDir } = options;
|
|
41501
|
+
const lineCountExemptPaths = await buildExemptPathSet(rootDir, LINE_COUNT_EXEMPT_GLOBS, isIgnoredRelativePath);
|
|
41502
|
+
const sourceFiles = await listSourceFiles(rootDir, isIgnoredRelativePath);
|
|
40986
41503
|
const candidates = [];
|
|
40987
41504
|
for (const filePath of sourceFiles) {
|
|
40988
41505
|
const candidate = await analyzeSourceFileForRefactorCandidate({
|
|
40989
41506
|
filePath,
|
|
41507
|
+
heuristics,
|
|
40990
41508
|
lineCountExemptPaths,
|
|
40991
41509
|
rootDir,
|
|
40992
41510
|
});
|
|
@@ -41001,7 +41519,7 @@
|
|
|
41001
41519
|
*
|
|
41002
41520
|
* @private function of findRefactorCandidatesInProject
|
|
41003
41521
|
*/
|
|
41004
|
-
async function listSourceFiles(rootDir) {
|
|
41522
|
+
async function listSourceFiles(rootDir, isIgnoredRelativePath) {
|
|
41005
41523
|
const extensions = SOURCE_FILE_EXTENSIONS.map((extension) => extension.replace(/^\./, '')).join(',');
|
|
41006
41524
|
const extensionGlob = `{${extensions}}`;
|
|
41007
41525
|
const patterns = [...SOURCE_ROOTS.map((root) => `${root}/**/*.${extensionGlob}`), `*.${extensionGlob}`];
|
|
@@ -41014,6 +41532,9 @@
|
|
|
41014
41532
|
absolute: true,
|
|
41015
41533
|
});
|
|
41016
41534
|
for (const match of matches) {
|
|
41535
|
+
if (shouldIgnoreAbsolutePath(rootDir, match, isIgnoredRelativePath)) {
|
|
41536
|
+
continue;
|
|
41537
|
+
}
|
|
41017
41538
|
files.add(match);
|
|
41018
41539
|
}
|
|
41019
41540
|
}
|
|
@@ -41024,7 +41545,7 @@
|
|
|
41024
41545
|
*
|
|
41025
41546
|
* @private function of findRefactorCandidatesInProject
|
|
41026
41547
|
*/
|
|
41027
|
-
async function buildExemptPathSet(rootDir, patterns) {
|
|
41548
|
+
async function buildExemptPathSet(rootDir, patterns, isIgnoredRelativePath) {
|
|
41028
41549
|
const exemptPaths = new Set();
|
|
41029
41550
|
for (const pattern of patterns) {
|
|
41030
41551
|
const matches = await glob__default["default"](pattern, {
|
|
@@ -41034,11 +41555,23 @@
|
|
|
41034
41555
|
absolute: true,
|
|
41035
41556
|
});
|
|
41036
41557
|
for (const match of matches) {
|
|
41558
|
+
if (shouldIgnoreAbsolutePath(rootDir, match, isIgnoredRelativePath)) {
|
|
41559
|
+
continue;
|
|
41560
|
+
}
|
|
41037
41561
|
exemptPaths.add(normalizeAbsolutePath(match));
|
|
41038
41562
|
}
|
|
41039
41563
|
}
|
|
41040
41564
|
return exemptPaths;
|
|
41041
41565
|
}
|
|
41566
|
+
/**
|
|
41567
|
+
* Resolves whether an absolute path falls under the project `.gitignore` rules.
|
|
41568
|
+
*
|
|
41569
|
+
* @private function of findRefactorCandidatesInProject
|
|
41570
|
+
*/
|
|
41571
|
+
function shouldIgnoreAbsolutePath(rootDir, absolutePath, isIgnoredRelativePath) {
|
|
41572
|
+
const relativePath = normalizeRefactorCandidatePath(path.relative(rootDir, absolutePath));
|
|
41573
|
+
return isIgnoredRelativePath(relativePath);
|
|
41574
|
+
}
|
|
41042
41575
|
/**
|
|
41043
41576
|
* Normalizes an absolute path for consistent comparisons.
|
|
41044
41577
|
*
|
|
@@ -41087,6 +41620,69 @@
|
|
|
41087
41620
|
}
|
|
41088
41621
|
// Note: [🟡] Code for repository script [loadExistingPromptTargets](scripts/find-refactor-candidates/loadExistingPromptTargets.ts) should never be published outside of `@promptbook/cli`
|
|
41089
41622
|
|
|
41623
|
+
/**
|
|
41624
|
+
* Filename used to discover the project root for refactor-candidate scanning.
|
|
41625
|
+
*/
|
|
41626
|
+
const GITIGNORE_FILE_NAME = '.gitignore';
|
|
41627
|
+
/**
|
|
41628
|
+
* Resolves the project root and `.gitignore` matcher for refactor-candidate scanning.
|
|
41629
|
+
*
|
|
41630
|
+
* @private function of findRefactorCandidates
|
|
41631
|
+
*/
|
|
41632
|
+
async function resolveRefactorCandidateProject(startDir) {
|
|
41633
|
+
const absoluteStartDir = path.resolve(startDir);
|
|
41634
|
+
const gitignorePath = await findNearestGitignorePath(absoluteStartDir);
|
|
41635
|
+
if (!gitignorePath) {
|
|
41636
|
+
return {
|
|
41637
|
+
rootDir: absoluteStartDir,
|
|
41638
|
+
isIgnoredRelativePath: () => false,
|
|
41639
|
+
};
|
|
41640
|
+
}
|
|
41641
|
+
const rootDir = path.dirname(gitignorePath);
|
|
41642
|
+
const gitignoreMatcher = ignore__default["default"]().add(await promises.readFile(gitignorePath, 'utf-8'));
|
|
41643
|
+
return {
|
|
41644
|
+
rootDir,
|
|
41645
|
+
isIgnoredRelativePath(relativePath) {
|
|
41646
|
+
return gitignoreMatcher.ignores(normalizeRefactorCandidatePath(relativePath));
|
|
41647
|
+
},
|
|
41648
|
+
};
|
|
41649
|
+
}
|
|
41650
|
+
/**
|
|
41651
|
+
* Finds the nearest ancestor `.gitignore` so scans work from any project subdirectory.
|
|
41652
|
+
*
|
|
41653
|
+
* @private function of resolveRefactorCandidateProject
|
|
41654
|
+
*/
|
|
41655
|
+
async function findNearestGitignorePath(startDir) {
|
|
41656
|
+
let currentDir = path.resolve(startDir);
|
|
41657
|
+
while (true) {
|
|
41658
|
+
const gitignorePath = path.join(currentDir, GITIGNORE_FILE_NAME);
|
|
41659
|
+
if (await isExistingFile(gitignorePath)) {
|
|
41660
|
+
return gitignorePath;
|
|
41661
|
+
}
|
|
41662
|
+
const parentDir = path.dirname(currentDir);
|
|
41663
|
+
if (parentDir === currentDir) {
|
|
41664
|
+
return null;
|
|
41665
|
+
}
|
|
41666
|
+
currentDir = parentDir;
|
|
41667
|
+
}
|
|
41668
|
+
}
|
|
41669
|
+
/**
|
|
41670
|
+
* Detects whether a file exists without swallowing unexpected filesystem failures.
|
|
41671
|
+
*
|
|
41672
|
+
* @private function of resolveRefactorCandidateProject
|
|
41673
|
+
*/
|
|
41674
|
+
async function isExistingFile(filePath) {
|
|
41675
|
+
var _a;
|
|
41676
|
+
const fileStats = await promises.stat(filePath).catch((error) => {
|
|
41677
|
+
if (error.code === 'ENOENT') {
|
|
41678
|
+
return undefined;
|
|
41679
|
+
}
|
|
41680
|
+
throw error;
|
|
41681
|
+
});
|
|
41682
|
+
return (_a = fileStats === null || fileStats === void 0 ? void 0 : fileStats.isFile()) !== null && _a !== void 0 ? _a : false;
|
|
41683
|
+
}
|
|
41684
|
+
// Note: [🟡] Code for repository script [resolveRefactorCandidateProject](scripts/find-refactor-candidates/resolveRefactorCandidateProject.ts) should never be published outside of `@promptbook/cli`
|
|
41685
|
+
|
|
41090
41686
|
/**
|
|
41091
41687
|
* Calculates the next available prompt numbering sequence for a month.
|
|
41092
41688
|
*/
|
|
@@ -41259,11 +41855,18 @@
|
|
|
41259
41855
|
if (counts.entityCount !== null && counts.maxEntities !== null) {
|
|
41260
41856
|
guidance.push(`- The file defines too many responsibilities (${counts.entityCount} in single file)`, ` - Keep in mind the Single Responsibility Principle (SRP)`, ` - Consider breaking it down into smaller, focused modules or components.`);
|
|
41261
41857
|
}
|
|
41858
|
+
if (counts.functionCount !== null && counts.maxFunctions !== null) {
|
|
41859
|
+
guidance.push(`- The file contains too many functions (${counts.functionCount}/${counts.maxFunctions})`, ` - Keep related responsibilities grouped behind small facades or focused modules.`, ` - Consider extracting private helpers or splitting independent concerns into dedicated files.`);
|
|
41860
|
+
}
|
|
41861
|
+
if (counts.functionComplexity !== null && counts.maxFunctionComplexity !== null) {
|
|
41862
|
+
const functionSuffix = counts.mostComplexFunctionName ? ` in \`${counts.mostComplexFunctionName}\`` : '';
|
|
41863
|
+
guidance.push(`- The file contains overly complex logic${functionSuffix} (${counts.functionComplexity}/${counts.maxFunctionComplexity})`, ` - Break branching logic into smaller, focused helper functions.`, ` - Keep each function responsible for one clear step or decision.`);
|
|
41864
|
+
}
|
|
41262
41865
|
guidance.push('- Purpose of this refactoring is to improve code maintainability and readability.', '- Look at the internal structure, the usage and also surrounding code to understand how to best refactor this file.', '- Consider breaking down large functions into smaller, more manageable ones, removing any redundant code, and ensuring that the file adheres to the project coding standards.', '- After the refactoring, ensure that (1) `npm run test-name-discrepancies` and (2) `npm run test-package-generation` are passing successfully.', ' 1. All the things you have moved to new files should correspond the thing in the file with the file name, for example `MyComponent.tsx` should export `MyComponent`.', ' 2. All the things you have moved to new files but are private things to the outside world should have `@private function of TheMainThing` JSDoc comment.', '- Keep in mind DRY *(Do not repeat yourself)* and SOLID principles while refactoring.', '- **Do not change the external behavior** of the code. Focus solely on improving the internal structure and organization of the code.', '- Before you start refactoring, make sure to read the code carefully and understand its current structure and functionality. Do a analysis of the current functionality before you start.');
|
|
41263
41866
|
return guidance;
|
|
41264
41867
|
}
|
|
41265
41868
|
/**
|
|
41266
|
-
* Extracts
|
|
41869
|
+
* Extracts structural counts from refactor reasons.
|
|
41267
41870
|
*
|
|
41268
41871
|
* @private function of buildPromptContent
|
|
41269
41872
|
*/
|
|
@@ -41272,6 +41875,11 @@
|
|
|
41272
41875
|
let maxLines = null;
|
|
41273
41876
|
let entityCount = null;
|
|
41274
41877
|
let maxEntities = null;
|
|
41878
|
+
let functionCount = null;
|
|
41879
|
+
let maxFunctions = null;
|
|
41880
|
+
let functionComplexity = null;
|
|
41881
|
+
let maxFunctionComplexity = null;
|
|
41882
|
+
let mostComplexFunctionName = null;
|
|
41275
41883
|
for (const reason of reasons) {
|
|
41276
41884
|
const lineMatch = reason.match(/lines\s+(?<count>\d+)\/(?<max>\d+)/i);
|
|
41277
41885
|
if (lineMatch === null || lineMatch === void 0 ? void 0 : lineMatch.groups) {
|
|
@@ -41283,6 +41891,19 @@
|
|
|
41283
41891
|
if (entityMatch === null || entityMatch === void 0 ? void 0 : entityMatch.groups) {
|
|
41284
41892
|
entityCount = Number(entityMatch.groups.count);
|
|
41285
41893
|
maxEntities = Number(entityMatch.groups.max);
|
|
41894
|
+
continue;
|
|
41895
|
+
}
|
|
41896
|
+
const functionMatch = reason.match(/functions\s+(?<count>\d+)\/(?<max>\d+)/i);
|
|
41897
|
+
if (functionMatch === null || functionMatch === void 0 ? void 0 : functionMatch.groups) {
|
|
41898
|
+
functionCount = Number(functionMatch.groups.count);
|
|
41899
|
+
maxFunctions = Number(functionMatch.groups.max);
|
|
41900
|
+
continue;
|
|
41901
|
+
}
|
|
41902
|
+
const complexityMatch = reason.match(/complexity\s+(?<count>\d+)\/(?<max>\d+)(?:\s+in\s+`(?<functionName>[^`]+)`)?/i);
|
|
41903
|
+
if (complexityMatch === null || complexityMatch === void 0 ? void 0 : complexityMatch.groups) {
|
|
41904
|
+
functionComplexity = Number(complexityMatch.groups.count);
|
|
41905
|
+
maxFunctionComplexity = Number(complexityMatch.groups.max);
|
|
41906
|
+
mostComplexFunctionName = complexityMatch.groups.functionName || null;
|
|
41286
41907
|
}
|
|
41287
41908
|
}
|
|
41288
41909
|
return {
|
|
@@ -41290,6 +41911,11 @@
|
|
|
41290
41911
|
maxLines,
|
|
41291
41912
|
entityCount,
|
|
41292
41913
|
maxEntities,
|
|
41914
|
+
functionCount,
|
|
41915
|
+
maxFunctions,
|
|
41916
|
+
functionComplexity,
|
|
41917
|
+
maxFunctionComplexity,
|
|
41918
|
+
mostComplexFunctionName,
|
|
41293
41919
|
};
|
|
41294
41920
|
}
|
|
41295
41921
|
/**
|
|
@@ -41298,14 +41924,23 @@
|
|
|
41298
41924
|
* @private function of buildPromptContent
|
|
41299
41925
|
*/
|
|
41300
41926
|
function buildDensityNote(counts) {
|
|
41301
|
-
|
|
41302
|
-
|
|
41927
|
+
const activeSignalsCount = [
|
|
41928
|
+
counts.lineCount !== null,
|
|
41929
|
+
counts.entityCount !== null,
|
|
41930
|
+
counts.functionCount !== null,
|
|
41931
|
+
counts.functionComplexity !== null,
|
|
41932
|
+
].filter(Boolean).length;
|
|
41933
|
+
if (activeSignalsCount > 1) {
|
|
41934
|
+
return 'The file mixes multiple concerns and dense logic, making it harder to follow.';
|
|
41303
41935
|
}
|
|
41304
41936
|
if (counts.lineCount !== null) {
|
|
41305
41937
|
return 'The file is large enough that it is hard to follow.';
|
|
41306
41938
|
}
|
|
41307
|
-
if (counts.entityCount !== null) {
|
|
41308
|
-
return 'The file
|
|
41939
|
+
if (counts.entityCount !== null || counts.functionCount !== null) {
|
|
41940
|
+
return 'The file packs too many responsibilities into one place.';
|
|
41941
|
+
}
|
|
41942
|
+
if (counts.functionComplexity !== null) {
|
|
41943
|
+
return 'The file contains logic that is too complex to follow comfortably.';
|
|
41309
41944
|
}
|
|
41310
41945
|
return null;
|
|
41311
41946
|
}
|
|
@@ -41399,13 +42034,20 @@
|
|
|
41399
42034
|
*
|
|
41400
42035
|
* @public exported from `@promptbook/cli`
|
|
41401
42036
|
*/
|
|
41402
|
-
async function findRefactorCandidates() {
|
|
42037
|
+
async function findRefactorCandidates(options = {}) {
|
|
42038
|
+
const { level = DEFAULT_REFACTOR_CANDIDATE_LEVEL } = options;
|
|
42039
|
+
const heuristics = getRefactorCandidateLevelConfiguration(level);
|
|
41403
42040
|
initializeFindRefactorCandidatesRun();
|
|
41404
|
-
console.info(colors__default["default"].cyan('
|
|
41405
|
-
|
|
42041
|
+
console.info(colors__default["default"].cyan('⚡🏭 Find refactor candidates'));
|
|
42042
|
+
console.info(colors__default["default"].gray(`Using \`${level}\` scan level.`));
|
|
42043
|
+
const { isIgnoredRelativePath, rootDir } = await resolveRefactorCandidateProject(process.cwd());
|
|
41406
42044
|
const promptsDir = path.join(rootDir, PROMPTS_DIR_NAME);
|
|
41407
42045
|
const existingTargets = await loadExistingPromptTargets(promptsDir);
|
|
41408
|
-
const candidates = await findRefactorCandidatesInProject(
|
|
42046
|
+
const candidates = await findRefactorCandidatesInProject({
|
|
42047
|
+
heuristics,
|
|
42048
|
+
isIgnoredRelativePath,
|
|
42049
|
+
rootDir,
|
|
42050
|
+
});
|
|
41409
42051
|
if (candidates.length === 0) {
|
|
41410
42052
|
console.info(colors__default["default"].green('No refactor candidates found.'));
|
|
41411
42053
|
return;
|