@promptbook/cli 0.112.0-35 → 0.112.0-38
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 +119 -18
- package/esm/index.es.js +1289 -323
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/find-refactor-candidates/RefactorCandidateLevel.d.ts +41 -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 +18 -1
- 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 +137 -0
- package/esm/src/cli/cli-commands/coder/boilerplateTemplates.test.d.ts +1 -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/generate-boilerplates.d.ts +10 -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 -0
- 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 +1 -1
- package/umd/index.umd.js +1287 -321
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/find-refactor-candidates/RefactorCandidateLevel.d.ts +41 -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 +18 -1
- 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 +137 -0
- package/umd/src/cli/cli-commands/coder/boilerplateTemplates.test.d.ts +1 -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/generate-boilerplates.d.ts +10 -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 -0
- 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/esm/index.es.js
CHANGED
|
@@ -2,10 +2,10 @@ import colors from 'colors';
|
|
|
2
2
|
import commander, { Option } from 'commander';
|
|
3
3
|
import _spaceTrim, { spaceTrim as spaceTrim$1 } from 'spacetrim';
|
|
4
4
|
import * as fs from 'fs';
|
|
5
|
-
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
5
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import { join, basename, dirname, isAbsolute, relative, extname, resolve } from 'path';
|
|
8
|
-
import {
|
|
8
|
+
import { readFile, writeFile, stat, mkdir, access, constants, readdir, watch, unlink, rm, rename, rmdir } from 'fs/promises';
|
|
9
9
|
import { forTime, forEver } from 'waitasecond';
|
|
10
10
|
import prompts from 'prompts';
|
|
11
11
|
import * as dotenv from 'dotenv';
|
|
@@ -57,7 +57,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
57
57
|
* @generated
|
|
58
58
|
* @see https://github.com/webgptorg/promptbook
|
|
59
59
|
*/
|
|
60
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
60
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-38';
|
|
61
61
|
/**
|
|
62
62
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
63
63
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1661,6 +1661,142 @@ function $initializeCoderFindFreshEmojiTagCommand(program) {
|
|
|
1661
1661
|
// 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`
|
|
1662
1662
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
1663
1663
|
|
|
1664
|
+
/**
|
|
1665
|
+
* Root folders that contain source-like files for scanning.
|
|
1666
|
+
*/
|
|
1667
|
+
const SOURCE_ROOTS = ['src', 'apps', 'scripts', 'examples', 'agents', 'other'];
|
|
1668
|
+
/**
|
|
1669
|
+
* File extensions treated as source code.
|
|
1670
|
+
*/
|
|
1671
|
+
const SOURCE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
1672
|
+
/**
|
|
1673
|
+
* Glob patterns that should be ignored when scanning for source files.
|
|
1674
|
+
*/
|
|
1675
|
+
const SOURCE_FILE_IGNORE_GLOBS = [
|
|
1676
|
+
'**/node_modules/**',
|
|
1677
|
+
'**/packages/**',
|
|
1678
|
+
'**/.*/**',
|
|
1679
|
+
'**/.git/**',
|
|
1680
|
+
'**/.idea/**',
|
|
1681
|
+
'**/.vscode/**',
|
|
1682
|
+
'**/.promptbook/**',
|
|
1683
|
+
'**/.next/**',
|
|
1684
|
+
'**/.tmp/**',
|
|
1685
|
+
'**/tmp/**',
|
|
1686
|
+
'**/coverage/**',
|
|
1687
|
+
'**/dist/**',
|
|
1688
|
+
'**/build/**',
|
|
1689
|
+
'**/out/**',
|
|
1690
|
+
'**/prompts/**',
|
|
1691
|
+
'**/changelog/**',
|
|
1692
|
+
];
|
|
1693
|
+
/**
|
|
1694
|
+
* Glob patterns that are exempt from line-count checks.
|
|
1695
|
+
*/
|
|
1696
|
+
const LINE_COUNT_EXEMPT_GLOBS = ['other/cspell-dictionaries/**/*.txt'];
|
|
1697
|
+
/**
|
|
1698
|
+
* File extensions eligible for structural AST analysis.
|
|
1699
|
+
*/
|
|
1700
|
+
const STRUCTURAL_ANALYSIS_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
1701
|
+
/**
|
|
1702
|
+
* Markers that identify generated files which should be skipped.
|
|
1703
|
+
*/
|
|
1704
|
+
const GENERATED_CODE_MARKERS = [
|
|
1705
|
+
'WARNING: This code has been generated',
|
|
1706
|
+
'This code has been generated so that any manual changes will be overwritten',
|
|
1707
|
+
];
|
|
1708
|
+
/**
|
|
1709
|
+
* Name of the prompts directory.
|
|
1710
|
+
*/
|
|
1711
|
+
const PROMPTS_DIR_NAME = 'prompts';
|
|
1712
|
+
/**
|
|
1713
|
+
* Step size used for prompt numbering.
|
|
1714
|
+
*/
|
|
1715
|
+
const PROMPT_NUMBER_STEP = 10;
|
|
1716
|
+
/**
|
|
1717
|
+
* Prefix used for generated prompt slugs.
|
|
1718
|
+
*/
|
|
1719
|
+
const PROMPT_SLUG_PREFIX = 'refactor';
|
|
1720
|
+
/**
|
|
1721
|
+
* Label used to mark the target file in generated prompts.
|
|
1722
|
+
*/
|
|
1723
|
+
const PROMPT_TARGET_LABEL = 'Target file';
|
|
1724
|
+
/**
|
|
1725
|
+
* Maximum length for generated prompt slugs.
|
|
1726
|
+
*/
|
|
1727
|
+
const PROMPT_SLUG_MAX_LENGTH = 80;
|
|
1728
|
+
/**
|
|
1729
|
+
* Note: [?] Code in this file should never be published in any package
|
|
1730
|
+
*/
|
|
1731
|
+
|
|
1732
|
+
/**
|
|
1733
|
+
* Supported aggressiveness levels for refactor-candidate scanning.
|
|
1734
|
+
*/
|
|
1735
|
+
const REFACTOR_CANDIDATE_LEVEL_VALUES = ['low', 'medium', 'high', 'xhigh'];
|
|
1736
|
+
/**
|
|
1737
|
+
* Default aggressiveness level for refactor-candidate scanning.
|
|
1738
|
+
*/
|
|
1739
|
+
const DEFAULT_REFACTOR_CANDIDATE_LEVEL = 'medium';
|
|
1740
|
+
/**
|
|
1741
|
+
* Threshold table for each supported refactor-candidate scanning level.
|
|
1742
|
+
*/
|
|
1743
|
+
const REFACTOR_CANDIDATE_LEVEL_CONFIGURATION_BY_LEVEL = {
|
|
1744
|
+
low: createRefactorCandidateLevelConfiguration({
|
|
1745
|
+
maxLineCount: 2800,
|
|
1746
|
+
maxEntityCountPerFile: 28,
|
|
1747
|
+
maxFunctionCountPerFile: 18,
|
|
1748
|
+
maxFunctionComplexity: 20,
|
|
1749
|
+
}),
|
|
1750
|
+
medium: createRefactorCandidateLevelConfiguration({
|
|
1751
|
+
maxLineCount: 2000,
|
|
1752
|
+
maxEntityCountPerFile: 20,
|
|
1753
|
+
maxFunctionCountPerFile: 14,
|
|
1754
|
+
maxFunctionComplexity: 16,
|
|
1755
|
+
}),
|
|
1756
|
+
high: createRefactorCandidateLevelConfiguration({
|
|
1757
|
+
maxLineCount: 1500,
|
|
1758
|
+
maxEntityCountPerFile: 16,
|
|
1759
|
+
maxFunctionCountPerFile: 10,
|
|
1760
|
+
maxFunctionComplexity: 12,
|
|
1761
|
+
}),
|
|
1762
|
+
xhigh: createRefactorCandidateLevelConfiguration({
|
|
1763
|
+
maxLineCount: 1000,
|
|
1764
|
+
maxEntityCountPerFile: 12,
|
|
1765
|
+
maxFunctionCountPerFile: 8,
|
|
1766
|
+
maxFunctionComplexity: 8,
|
|
1767
|
+
}),
|
|
1768
|
+
};
|
|
1769
|
+
/**
|
|
1770
|
+
* Resolves the thresholds for a selected refactor-candidate scanning level.
|
|
1771
|
+
*/
|
|
1772
|
+
function getRefactorCandidateLevelConfiguration(level = DEFAULT_REFACTOR_CANDIDATE_LEVEL) {
|
|
1773
|
+
return REFACTOR_CANDIDATE_LEVEL_CONFIGURATION_BY_LEVEL[level];
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Builds one normalized refactor-candidate level configuration entry.
|
|
1777
|
+
*/
|
|
1778
|
+
function createRefactorCandidateLevelConfiguration(options) {
|
|
1779
|
+
const { maxLineCount, maxEntityCountPerFile, maxFunctionCountPerFile, maxFunctionComplexity } = options;
|
|
1780
|
+
return {
|
|
1781
|
+
maxDefaultLineCount: maxLineCount,
|
|
1782
|
+
maxLineCountByExtension: createLineCountLimits(maxLineCount),
|
|
1783
|
+
maxEntityCountPerFile,
|
|
1784
|
+
maxFunctionCountPerFile,
|
|
1785
|
+
maxFunctionComplexity,
|
|
1786
|
+
};
|
|
1787
|
+
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Creates a per-extension line-count table using one shared threshold.
|
|
1790
|
+
*/
|
|
1791
|
+
function createLineCountLimits(maxLineCount) {
|
|
1792
|
+
const maxLineCountByExtension = {};
|
|
1793
|
+
for (const extension of SOURCE_FILE_EXTENSIONS) {
|
|
1794
|
+
maxLineCountByExtension[extension] = maxLineCount;
|
|
1795
|
+
}
|
|
1796
|
+
return maxLineCountByExtension;
|
|
1797
|
+
}
|
|
1798
|
+
// Note: [🟡] Code for repository script [RefactorCandidateLevel](scripts/find-refactor-candidates/RefactorCandidateLevel.ts) should never be published outside of `@promptbook/cli`
|
|
1799
|
+
|
|
1664
1800
|
/**
|
|
1665
1801
|
* Initializes `coder find-refactor-candidates` command for Promptbook CLI utilities
|
|
1666
1802
|
*
|
|
@@ -1673,17 +1809,23 @@ function $initializeCoderFindRefactorCandidatesCommand(program) {
|
|
|
1673
1809
|
command.description(spaceTrim$1(`
|
|
1674
1810
|
Scan source files to identify refactoring candidates
|
|
1675
1811
|
|
|
1676
|
-
|
|
1677
|
-
-
|
|
1678
|
-
-
|
|
1812
|
+
Levels:
|
|
1813
|
+
- low: Conservative scan for only the most obvious refactor targets
|
|
1814
|
+
- medium: Default scan using the current standard thresholds
|
|
1815
|
+
- high: Stricter scan that finds more crowded or complex files
|
|
1816
|
+
- xhigh: Most aggressive scan for denser and more complex candidates
|
|
1679
1817
|
|
|
1680
1818
|
Generates refactor prompts with guidance for identified candidates.
|
|
1681
1819
|
`));
|
|
1682
|
-
command.
|
|
1820
|
+
command.addOption(new Option('--level <level>', `Set scan aggressiveness (${REFACTOR_CANDIDATE_LEVEL_VALUES.join(', ')})`)
|
|
1821
|
+
.choices([...REFACTOR_CANDIDATE_LEVEL_VALUES])
|
|
1822
|
+
.default(DEFAULT_REFACTOR_CANDIDATE_LEVEL));
|
|
1823
|
+
command.action(handleActionErrors(async (cliOptions) => {
|
|
1824
|
+
const { level = DEFAULT_REFACTOR_CANDIDATE_LEVEL } = cliOptions;
|
|
1683
1825
|
// Note: Import the function dynamically to avoid loading heavy dependencies until needed
|
|
1684
1826
|
const { findRefactorCandidates } = await Promise.resolve().then(function () { return findRefactorCandidates$1; });
|
|
1685
1827
|
try {
|
|
1686
|
-
await findRefactorCandidates();
|
|
1828
|
+
await findRefactorCandidates({ level });
|
|
1687
1829
|
}
|
|
1688
1830
|
catch (error) {
|
|
1689
1831
|
assertsError(error);
|
|
@@ -1697,6 +1839,236 @@ function $initializeCoderFindRefactorCandidatesCommand(program) {
|
|
|
1697
1839
|
// Note: [🟡] Code for CLI command [find-refactor-candidates](src/cli/cli-commands/coder/find-refactor-candidates.ts) should never be published outside of `@promptbook/cli`
|
|
1698
1840
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
1699
1841
|
|
|
1842
|
+
/**
|
|
1843
|
+
* This error indicates that promptbook not found in the collection
|
|
1844
|
+
*
|
|
1845
|
+
* @public exported from `@promptbook/core`
|
|
1846
|
+
*/
|
|
1847
|
+
class NotFoundError extends Error {
|
|
1848
|
+
constructor(message) {
|
|
1849
|
+
super(message);
|
|
1850
|
+
this.name = 'NotFoundError';
|
|
1851
|
+
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
/**
|
|
1856
|
+
* Relative path to the root prompts directory used by Promptbook coder utilities.
|
|
1857
|
+
*
|
|
1858
|
+
* @private internal utility of `ptbk coder`
|
|
1859
|
+
*/
|
|
1860
|
+
const PROMPTS_DIRECTORY_PATH = 'prompts';
|
|
1861
|
+
/**
|
|
1862
|
+
* Relative path to the archive directory used by `coder verify`.
|
|
1863
|
+
*
|
|
1864
|
+
* @private internal utility of `ptbk coder`
|
|
1865
|
+
*/
|
|
1866
|
+
const PROMPTS_DONE_DIRECTORY_PATH = join(PROMPTS_DIRECTORY_PATH, 'done');
|
|
1867
|
+
/**
|
|
1868
|
+
* Relative path to the project-owned boilerplate templates directory.
|
|
1869
|
+
*
|
|
1870
|
+
* @private internal utility of `ptbk coder`
|
|
1871
|
+
*/
|
|
1872
|
+
const PROMPTS_TEMPLATES_DIRECTORY_PATH = join(PROMPTS_DIRECTORY_PATH, 'templates');
|
|
1873
|
+
/**
|
|
1874
|
+
* Built-in boilerplate templates available to `coder generate-boilerplates`.
|
|
1875
|
+
*
|
|
1876
|
+
* Only the project-agnostic subset is materialized by `coder init`.
|
|
1877
|
+
*/
|
|
1878
|
+
const DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS = [
|
|
1879
|
+
{
|
|
1880
|
+
id: 'common',
|
|
1881
|
+
relativeFilePath: join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'common.md'),
|
|
1882
|
+
slugPrefix: null,
|
|
1883
|
+
content: buildCoderPromptTemplateContent([
|
|
1884
|
+
'- @@@',
|
|
1885
|
+
"- Keep in mind the DRY _(don't repeat yourself)_ principle.",
|
|
1886
|
+
'- Do a proper analysis of the current functionality before you start implementing.',
|
|
1887
|
+
'- Add the changes into the [changelog](./changelog/_current-preversion.md)',
|
|
1888
|
+
]),
|
|
1889
|
+
isDefaultProjectTemplate: true,
|
|
1890
|
+
},
|
|
1891
|
+
{
|
|
1892
|
+
id: 'agents-server',
|
|
1893
|
+
relativeFilePath: join(PROMPTS_TEMPLATES_DIRECTORY_PATH, 'agents-server.md'),
|
|
1894
|
+
slugPrefix: 'agents-server',
|
|
1895
|
+
content: buildCoderPromptTemplateContent([
|
|
1896
|
+
'- @@@',
|
|
1897
|
+
"- Keep in mind the DRY _(don't repeat yourself)_ principle.",
|
|
1898
|
+
'- Do a proper analysis of the current functionality before you start implementing.',
|
|
1899
|
+
'- You are working with the [Agents Server](apps/agents-server)',
|
|
1900
|
+
'- If you need to do the database migration, do it',
|
|
1901
|
+
'- Add the changes into the [changelog](changelog/_current-preversion.md)',
|
|
1902
|
+
]),
|
|
1903
|
+
isDefaultProjectTemplate: false,
|
|
1904
|
+
},
|
|
1905
|
+
];
|
|
1906
|
+
/**
|
|
1907
|
+
* Project-agnostic coder templates that `ptbk coder init` should materialize in any repository.
|
|
1908
|
+
*/
|
|
1909
|
+
const DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS = DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS.filter(({ isDefaultProjectTemplate }) => isDefaultProjectTemplate);
|
|
1910
|
+
/**
|
|
1911
|
+
* Lists the built-in coder boilerplate templates.
|
|
1912
|
+
*
|
|
1913
|
+
* @private internal utility of `ptbk coder`
|
|
1914
|
+
*/
|
|
1915
|
+
function getDefaultCoderPromptTemplateDefinitions() {
|
|
1916
|
+
return DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS;
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Lists the built-in coder prompt templates that are safe to initialize in any project.
|
|
1920
|
+
*
|
|
1921
|
+
* @private internal utility of `ptbk coder`
|
|
1922
|
+
*/
|
|
1923
|
+
function getDefaultCoderProjectPromptTemplateDefinitions() {
|
|
1924
|
+
return DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS;
|
|
1925
|
+
}
|
|
1926
|
+
/**
|
|
1927
|
+
* Resolves one built-in coder boilerplate template definition by its stable identifier.
|
|
1928
|
+
*
|
|
1929
|
+
* @private internal utility of `ptbk coder`
|
|
1930
|
+
*/
|
|
1931
|
+
function getDefaultCoderPromptTemplateDefinition(template) {
|
|
1932
|
+
const definition = getDefaultCoderPromptTemplateDefinitionOrUndefined(template);
|
|
1933
|
+
if (!definition) {
|
|
1934
|
+
throw new NotFoundError(`Built-in coder prompt template \`${template}\` was not found.`);
|
|
1935
|
+
}
|
|
1936
|
+
return definition;
|
|
1937
|
+
}
|
|
1938
|
+
/**
|
|
1939
|
+
* Ensures the default project-owned coder template files exist without overwriting user customizations.
|
|
1940
|
+
*
|
|
1941
|
+
* @private internal utility of `ptbk coder`
|
|
1942
|
+
*/
|
|
1943
|
+
async function ensureDefaultCoderPromptTemplateFiles(projectPath) {
|
|
1944
|
+
const ensuredTemplateFiles = [];
|
|
1945
|
+
for (const definition of DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS) {
|
|
1946
|
+
const absoluteTemplatePath = join(projectPath, definition.relativeFilePath);
|
|
1947
|
+
if (await isExistingFile$1(absoluteTemplatePath)) {
|
|
1948
|
+
ensuredTemplateFiles.push({
|
|
1949
|
+
id: definition.id,
|
|
1950
|
+
relativeFilePath: definition.relativeFilePath,
|
|
1951
|
+
status: 'unchanged',
|
|
1952
|
+
});
|
|
1953
|
+
continue;
|
|
1954
|
+
}
|
|
1955
|
+
await writeFile(absoluteTemplatePath, `${definition.content}\n`, 'utf-8');
|
|
1956
|
+
ensuredTemplateFiles.push({
|
|
1957
|
+
id: definition.id,
|
|
1958
|
+
relativeFilePath: definition.relativeFilePath,
|
|
1959
|
+
status: 'created',
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
return ensuredTemplateFiles;
|
|
1963
|
+
}
|
|
1964
|
+
/**
|
|
1965
|
+
* Resolves the template requested by `coder generate-boilerplates`.
|
|
1966
|
+
*
|
|
1967
|
+
* Supports three modes:
|
|
1968
|
+
* - omitted option => built-in `common`
|
|
1969
|
+
* - built-in alias => one of the shared default templates
|
|
1970
|
+
* - relative path => markdown template file resolved from the project root
|
|
1971
|
+
*
|
|
1972
|
+
* @private internal utility of `ptbk coder`
|
|
1973
|
+
*/
|
|
1974
|
+
async function resolveCoderPromptTemplate({ projectPath, templateOption, }) {
|
|
1975
|
+
const normalizedTemplateOption = normalizeCoderPromptTemplateOption(templateOption);
|
|
1976
|
+
if (!normalizedTemplateOption) {
|
|
1977
|
+
return createResolvedBuiltInCoderPromptTemplate('common');
|
|
1978
|
+
}
|
|
1979
|
+
const builtInTemplateDefinition = getDefaultCoderPromptTemplateDefinitionOrUndefined(normalizedTemplateOption);
|
|
1980
|
+
if (builtInTemplateDefinition) {
|
|
1981
|
+
return createResolvedBuiltInCoderPromptTemplate(builtInTemplateDefinition.id);
|
|
1982
|
+
}
|
|
1983
|
+
const absoluteTemplatePath = join(projectPath, normalizedTemplateOption);
|
|
1984
|
+
try {
|
|
1985
|
+
const content = (await readFile(absoluteTemplatePath, 'utf-8')).trim();
|
|
1986
|
+
return {
|
|
1987
|
+
identifier: normalizedTemplateOption,
|
|
1988
|
+
relativeFilePath: normalizedTemplateOption,
|
|
1989
|
+
content,
|
|
1990
|
+
slugPrefix: deriveCoderPromptSlugPrefix(normalizedTemplateOption),
|
|
1991
|
+
};
|
|
1992
|
+
}
|
|
1993
|
+
catch (error) {
|
|
1994
|
+
if (isNodeJsErrorWithCode(error, 'ENOENT')) {
|
|
1995
|
+
throw new NotFoundError(spaceTrim$1(`
|
|
1996
|
+
Prompt boilerplate template was not found at \`${normalizedTemplateOption}\`.
|
|
1997
|
+
|
|
1998
|
+
- The \`--template\` option resolves paths relative to the current project root: \`${projectPath}\`
|
|
1999
|
+
- Run \`ptbk coder init\` to create the default project templates in \`${PROMPTS_TEMPLATES_DIRECTORY_PATH}\`
|
|
2000
|
+
- Or omit \`--template\` / use the built-in aliases \`common\` or \`agents-server\`
|
|
2001
|
+
`));
|
|
2002
|
+
}
|
|
2003
|
+
throw error;
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
/**
|
|
2007
|
+
* Normalizes one raw `--template` option to either a trimmed string or `undefined`.
|
|
2008
|
+
*/
|
|
2009
|
+
function normalizeCoderPromptTemplateOption(templateOption) {
|
|
2010
|
+
const normalizedTemplateOption = templateOption === null || templateOption === void 0 ? void 0 : templateOption.trim();
|
|
2011
|
+
if (!normalizedTemplateOption) {
|
|
2012
|
+
return undefined;
|
|
2013
|
+
}
|
|
2014
|
+
return normalizedTemplateOption;
|
|
2015
|
+
}
|
|
2016
|
+
/**
|
|
2017
|
+
* Resolves one built-in template definition without throwing.
|
|
2018
|
+
*/
|
|
2019
|
+
function getDefaultCoderPromptTemplateDefinitionOrUndefined(template) {
|
|
2020
|
+
return DEFAULT_CODER_PROMPT_TEMPLATE_DEFINITIONS.find((definition) => definition.id === template);
|
|
2021
|
+
}
|
|
2022
|
+
/**
|
|
2023
|
+
* Builds stable markdown content for one coder prompt template without indentation drift.
|
|
2024
|
+
*/
|
|
2025
|
+
function buildCoderPromptTemplateContent(lines) {
|
|
2026
|
+
return lines.join('\n');
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Creates a fully resolved template payload from one built-in definition.
|
|
2030
|
+
*/
|
|
2031
|
+
function createResolvedBuiltInCoderPromptTemplate(template) {
|
|
2032
|
+
const definition = getDefaultCoderPromptTemplateDefinition(template);
|
|
2033
|
+
return {
|
|
2034
|
+
identifier: definition.id,
|
|
2035
|
+
relativeFilePath: definition.relativeFilePath,
|
|
2036
|
+
content: definition.content,
|
|
2037
|
+
slugPrefix: definition.slugPrefix,
|
|
2038
|
+
};
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Derives the filename slug prefix from a project-relative template path.
|
|
2042
|
+
*/
|
|
2043
|
+
function deriveCoderPromptSlugPrefix(relativeTemplatePath) {
|
|
2044
|
+
const templateBasename = basename(relativeTemplatePath)
|
|
2045
|
+
.replace(/\.[^.]+$/u, '')
|
|
2046
|
+
.replace(/\.template$/u, '');
|
|
2047
|
+
if (templateBasename === 'common') {
|
|
2048
|
+
return null;
|
|
2049
|
+
}
|
|
2050
|
+
return templateBasename;
|
|
2051
|
+
}
|
|
2052
|
+
/**
|
|
2053
|
+
* Checks whether the provided error object exposes a specific Node.js `code`.
|
|
2054
|
+
*/
|
|
2055
|
+
function isNodeJsErrorWithCode(error, code) {
|
|
2056
|
+
return typeof error === 'object' && error !== null && 'code' in error && error.code === code;
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Checks whether a path exists and is a file.
|
|
2060
|
+
*/
|
|
2061
|
+
async function isExistingFile$1(path) {
|
|
2062
|
+
try {
|
|
2063
|
+
return (await stat(path)).isFile();
|
|
2064
|
+
}
|
|
2065
|
+
catch (_a) {
|
|
2066
|
+
return false;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
// Note: [🟡] Code for coder boilerplate templates [boilerplateTemplates](src/cli/cli-commands/coder/boilerplateTemplates.ts) should never be published outside of `@promptbook/cli`
|
|
2070
|
+
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
2071
|
+
|
|
1700
2072
|
/**
|
|
1701
2073
|
* Initializes `coder generate-boilerplates` command for Promptbook CLI utilities
|
|
1702
2074
|
*
|
|
@@ -1710,12 +2082,21 @@ function $initializeCoderGenerateBoilerplatesCommand(program) {
|
|
|
1710
2082
|
Generate prompt boilerplate files with unique emoji tags
|
|
1711
2083
|
`));
|
|
1712
2084
|
command.option('--count <count>', `Number of prompt boilerplate files to generate`, '5');
|
|
1713
|
-
command.option('--template <template>', `
|
|
2085
|
+
command.option('--template <template>', spaceTrim$1(`
|
|
2086
|
+
Prompt template to use.
|
|
2087
|
+
|
|
2088
|
+
Accepts either a built-in alias (${getDefaultCoderPromptTemplateDefinitions()
|
|
2089
|
+
.map(({ id }) => id)
|
|
2090
|
+
.join(', ')}) or a markdown file path relative to the current project root.
|
|
2091
|
+
`));
|
|
1714
2092
|
command.action(handleActionErrors(async (cliOptions) => {
|
|
1715
2093
|
const { count: countOption, template: templateOption } = cliOptions;
|
|
1716
2094
|
const filesCount = parseFilesCount(countOption);
|
|
1717
|
-
|
|
1718
|
-
|
|
2095
|
+
await generatePromptBoilerplate({
|
|
2096
|
+
projectPath: process.cwd(),
|
|
2097
|
+
filesCount,
|
|
2098
|
+
templateOption,
|
|
2099
|
+
});
|
|
1719
2100
|
return process.exit(0);
|
|
1720
2101
|
}));
|
|
1721
2102
|
}
|
|
@@ -1724,14 +2105,15 @@ function $initializeCoderGenerateBoilerplatesCommand(program) {
|
|
|
1724
2105
|
*
|
|
1725
2106
|
* @private internal function of `generatePromptBoilerplate` command
|
|
1726
2107
|
*/
|
|
1727
|
-
async function generatePromptBoilerplate({ filesCount,
|
|
2108
|
+
async function generatePromptBoilerplate({ projectPath, filesCount, templateOption, }) {
|
|
1728
2109
|
// Note: Import these dynamically to avoid circular dependencies and keep CLI fast
|
|
1729
2110
|
const { buildPromptFilename, getPromptNumbering } = await Promise.resolve().then(function () { return getPromptNumbering$1; });
|
|
1730
2111
|
const { formatPromptEmojiTag, getFreshPromptEmojiTags } = await Promise.resolve().then(function () { return promptEmojiTags; });
|
|
1731
2112
|
console.info(`🚀 Generate prompt boilerplate files`);
|
|
1732
|
-
|
|
2113
|
+
mkdirSync(join(projectPath, PROMPTS_DIRECTORY_PATH), { recursive: true });
|
|
2114
|
+
const promptTemplate = await resolveCoderPromptTemplate({ projectPath, templateOption });
|
|
1733
2115
|
const promptNumbering = await getPromptNumbering({
|
|
1734
|
-
promptsDir: join(
|
|
2116
|
+
promptsDir: join(projectPath, PROMPTS_DIRECTORY_PATH),
|
|
1735
2117
|
step: 10,
|
|
1736
2118
|
ignoreGlobs: ['**/node_modules/**'],
|
|
1737
2119
|
});
|
|
@@ -1740,7 +2122,7 @@ async function generatePromptBoilerplate({ filesCount, template, }) {
|
|
|
1740
2122
|
console.info(colors.blue(`Highest existing number for ${promptNumbering.datePrefix} found: ${highestNumberFormatted}`));
|
|
1741
2123
|
const { availableCount, selectedEmojis } = await getFreshPromptEmojiTags({
|
|
1742
2124
|
count: filesCount,
|
|
1743
|
-
rootDir:
|
|
2125
|
+
rootDir: projectPath,
|
|
1744
2126
|
});
|
|
1745
2127
|
console.info(colors.green(`Found ${availableCount} available fresh emojis`));
|
|
1746
2128
|
console.info(colors.green(`Selected emojis: ${selectedEmojis.map((emoji) => formatPromptEmojiTag(emoji)).join(' ')}`));
|
|
@@ -1752,8 +2134,9 @@ async function generatePromptBoilerplate({ filesCount, template, }) {
|
|
|
1752
2134
|
const number = promptNumbering.startNumber + i * promptNumbering.step;
|
|
1753
2135
|
const title = titles[i % titles.length];
|
|
1754
2136
|
const emoji = selectedEmojis[i];
|
|
1755
|
-
const filename = buildPromptFilename(promptNumbering.datePrefix, number, buildPromptSlug$1(
|
|
1756
|
-
const filepath = join(
|
|
2137
|
+
const filename = buildPromptFilename(promptNumbering.datePrefix, number, buildPromptSlug$1(promptTemplate.slugPrefix, title));
|
|
2138
|
+
const filepath = join(PROMPTS_DIRECTORY_PATH, filename);
|
|
2139
|
+
const absoluteFilepath = join(projectPath, filepath);
|
|
1757
2140
|
const emojiTag = formatPromptEmojiTag(emoji);
|
|
1758
2141
|
const one = spaceTrim$1((block) => `
|
|
1759
2142
|
|
|
@@ -1761,7 +2144,7 @@ async function generatePromptBoilerplate({ filesCount, template, }) {
|
|
|
1761
2144
|
|
|
1762
2145
|
${emojiTag} ${title}
|
|
1763
2146
|
|
|
1764
|
-
${block(
|
|
2147
|
+
${block(promptTemplate.content)}
|
|
1765
2148
|
`);
|
|
1766
2149
|
const content = spaceTrim$1((block) => `
|
|
1767
2150
|
|
|
@@ -1782,6 +2165,7 @@ async function generatePromptBoilerplate({ filesCount, template, }) {
|
|
|
1782
2165
|
`);
|
|
1783
2166
|
filesToCreate.push({
|
|
1784
2167
|
filepath,
|
|
2168
|
+
absoluteFilepath,
|
|
1785
2169
|
filename,
|
|
1786
2170
|
content,
|
|
1787
2171
|
emoji,
|
|
@@ -1791,7 +2175,7 @@ async function generatePromptBoilerplate({ filesCount, template, }) {
|
|
|
1791
2175
|
// Create the files
|
|
1792
2176
|
console.info(colors.yellow(`Creating ${filesToCreate.length} files:`));
|
|
1793
2177
|
for (const file of filesToCreate) {
|
|
1794
|
-
writeFileSync(file.
|
|
2178
|
+
writeFileSync(file.absoluteFilepath, file.content, 'utf-8');
|
|
1795
2179
|
console.info(colors.green(`✓ Created: ${file.filename} with ${formatPromptEmojiTag(file.emoji)}`));
|
|
1796
2180
|
}
|
|
1797
2181
|
console.info(colors.bgGreen(` Successfully created ${filesToCreate.length} prompt boilerplate files! `));
|
|
@@ -1810,51 +2194,184 @@ function parseFilesCount(countOption) {
|
|
|
1810
2194
|
return Math.floor(filesCount);
|
|
1811
2195
|
}
|
|
1812
2196
|
/**
|
|
1813
|
-
*
|
|
2197
|
+
* Builds filename slug from template and placeholder title.
|
|
1814
2198
|
*
|
|
1815
2199
|
* @private internal utility of `generatePromptBoilerplate` command
|
|
1816
2200
|
*/
|
|
1817
|
-
function
|
|
1818
|
-
if (
|
|
1819
|
-
|
|
1820
|
-
// <- TODO: Unhardcode and allow this dynamically by the template files.
|
|
1821
|
-
) {
|
|
1822
|
-
return templateOption;
|
|
2201
|
+
function buildPromptSlug$1(templateSlugPrefix, title) {
|
|
2202
|
+
if (!templateSlugPrefix) {
|
|
2203
|
+
return title;
|
|
1823
2204
|
}
|
|
1824
|
-
|
|
1825
|
-
return 'common';
|
|
2205
|
+
return `${templateSlugPrefix}-${title}`;
|
|
1826
2206
|
}
|
|
2207
|
+
// Note: [🟡] Code for CLI command [generate-boilerplates](src/cli/cli-commands/coder/generate-boilerplates.ts) should never be published outside of `@promptbook/cli`
|
|
2208
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
2209
|
+
|
|
1827
2210
|
/**
|
|
1828
|
-
*
|
|
2211
|
+
* Relative path to the shared coder context file initialized in project roots.
|
|
1829
2212
|
*
|
|
1830
|
-
* @private internal utility of `
|
|
2213
|
+
* @private internal utility of `ptbk coder`
|
|
1831
2214
|
*/
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
2215
|
+
const AGENTS_FILE_PATH = 'AGENTS.md';
|
|
2216
|
+
/**
|
|
2217
|
+
* Stable boilerplate instructions written into newly initialized `AGENTS.md` files.
|
|
2218
|
+
*/
|
|
2219
|
+
const DEFAULT_CODER_AGENTS_FILE_LINES = [
|
|
2220
|
+
'<!-- TODO: Write instructions for the Promptbook AI Coder here -->',
|
|
2221
|
+
];
|
|
2222
|
+
/**
|
|
2223
|
+
* Shared markdown boilerplate written into new `AGENTS.md` files.
|
|
2224
|
+
*/
|
|
2225
|
+
const DEFAULT_CODER_AGENTS_FILE_CONTENT = DEFAULT_CODER_AGENTS_FILE_LINES.join('\n');
|
|
2226
|
+
/**
|
|
2227
|
+
* Returns the default coder `AGENTS.md` boilerplate instructions.
|
|
2228
|
+
*
|
|
2229
|
+
* @private internal utility of `ptbk coder`
|
|
2230
|
+
*/
|
|
2231
|
+
function getDefaultCoderAgentsFileContent() {
|
|
2232
|
+
return DEFAULT_CODER_AGENTS_FILE_CONTENT;
|
|
1835
2233
|
}
|
|
2234
|
+
// Note: [🟡] Code for coder AGENTS file boilerplate [agentsFile](src/cli/cli-commands/coder/agentsFile.ts) should never be published outside of `@promptbook/cli`
|
|
2235
|
+
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
2236
|
+
|
|
1836
2237
|
/**
|
|
1837
|
-
*
|
|
2238
|
+
* Normalizes one project-relative path for human-readable CLI output and markdown.
|
|
1838
2239
|
*
|
|
1839
|
-
* @private internal utility of `
|
|
2240
|
+
* @private internal utility of `ptbk coder`
|
|
1840
2241
|
*/
|
|
1841
|
-
function
|
|
1842
|
-
|
|
1843
|
-
|
|
2242
|
+
function formatDisplayPath(relativePath) {
|
|
2243
|
+
return relativePath.replace(/\\/gu, '/');
|
|
2244
|
+
}
|
|
2245
|
+
// Note: [🟡] Code for coder path formatting [formatDisplayPath](src/cli/cli-commands/coder/formatDisplayPath.ts) should never be published outside of `@promptbook/cli`
|
|
2246
|
+
|
|
2247
|
+
/**
|
|
2248
|
+
* Relative path to the Promptbook Coder quick-reference file initialized in project roots.
|
|
2249
|
+
*
|
|
2250
|
+
* @private internal utility of `ptbk coder`
|
|
2251
|
+
*/
|
|
2252
|
+
const AGENT_CODING_FILE_PATH = 'AGENT_CODING.md';
|
|
2253
|
+
/**
|
|
2254
|
+
* Returns the default coder `AGENT_CODING.md` quick-reference content.
|
|
2255
|
+
*
|
|
2256
|
+
* @private internal utility of `ptbk coder`
|
|
2257
|
+
*/
|
|
2258
|
+
function getDefaultCoderAgentCodingFileContent({ packageJsonScripts, }) {
|
|
2259
|
+
return [
|
|
2260
|
+
'# Promptbook Coder quick reference',
|
|
2261
|
+
'',
|
|
2262
|
+
`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)}/\`.`,
|
|
2263
|
+
'',
|
|
2264
|
+
'## Workflow',
|
|
2265
|
+
`1. Put repository-wide coding rules into \`${AGENTS_FILE_PATH}\`. The default \`npm run coder:run\` script already passes \`--context ${AGENTS_FILE_PATH}\`.`,
|
|
2266
|
+
`2. Create or customize prompt templates in \`${formatDisplayPath(PROMPTS_TEMPLATES_DIRECTORY_PATH)}/\`. ${buildStarterTemplateSentence()}`,
|
|
2267
|
+
'3. Generate prompt files with `npm run coder:generate-boilerplates` or `npx ptbk coder generate-boilerplates --template <template> --count <count>`.',
|
|
2268
|
+
'4. Replace every `@@@`, keep drafts as `[-]`, and switch prompts to `[ ]` when they are ready to run. Completed prompts are marked `[x]`.',
|
|
2269
|
+
'5. Run `npm run coder:run` to execute the next ready prompt with the configured coding agent.',
|
|
2270
|
+
`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.`,
|
|
2271
|
+
'7. Use `npm run coder:find-refactor-candidates` when you want Promptbook to suggest refactor prompts automatically.',
|
|
2272
|
+
'',
|
|
2273
|
+
'## Templates',
|
|
2274
|
+
`- Project-owned templates created by \`ptbk coder init\`: ${formatInlineCodeList(getDefaultCoderProjectPromptTemplateDefinitions().map(({ relativeFilePath }) => formatDisplayPath(relativeFilePath)))}`,
|
|
2275
|
+
`- Built-in \`--template\` aliases: ${formatInlineCodeList(getDefaultCoderPromptTemplateDefinitions().map(({ id }) => id))}`,
|
|
2276
|
+
`- To add a custom template, create a markdown file such as \`${formatDisplayPath(PROMPTS_TEMPLATES_DIRECTORY_PATH)}/backend.md\`.`,
|
|
2277
|
+
`- To use a project template, run \`npx ptbk coder generate-boilerplates --template ${formatDisplayPath(PROMPTS_TEMPLATES_DIRECTORY_PATH)}/backend.md\`.`,
|
|
2278
|
+
`- 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.`,
|
|
2279
|
+
'',
|
|
2280
|
+
'## Created npm scripts',
|
|
2281
|
+
'| Script | Purpose |',
|
|
2282
|
+
'| --- | --- |',
|
|
2283
|
+
...buildPackageJsonScriptTableLines(packageJsonScripts),
|
|
2284
|
+
'',
|
|
2285
|
+
'## Customizing the workflow',
|
|
2286
|
+
'- Edit `package.json` if you want `npm run coder:run` to use another coding agent, model, thinking level, context file, or wait mode.',
|
|
2287
|
+
'- Use direct CLI commands when you need one-off flags such as `--priority`, `--ignore-git-changes`, `--dry-run`, `--allow-credits`, or `--auto-migrate`.',
|
|
2288
|
+
'- Use `npx ptbk coder --help` and `npx ptbk coder <command> --help` for the full CLI reference.',
|
|
2289
|
+
].join('\n');
|
|
2290
|
+
}
|
|
2291
|
+
/**
|
|
2292
|
+
* Builds the sentence describing the starter templates created during initialization.
|
|
2293
|
+
*/
|
|
2294
|
+
function buildStarterTemplateSentence() {
|
|
2295
|
+
const starterTemplatePaths = getDefaultCoderProjectPromptTemplateDefinitions().map(({ relativeFilePath }) => formatDisplayPath(relativeFilePath));
|
|
2296
|
+
if (starterTemplatePaths.length === 1) {
|
|
2297
|
+
return `The starter project template created by \`ptbk coder init\` is \`${starterTemplatePaths[0]}\`.`;
|
|
1844
2298
|
}
|
|
1845
|
-
return
|
|
2299
|
+
return `The starter project templates created by \`ptbk coder init\` are ${formatInlineCodeList(starterTemplatePaths)}.`;
|
|
1846
2300
|
}
|
|
1847
|
-
|
|
1848
|
-
|
|
2301
|
+
/**
|
|
2302
|
+
* Builds the markdown table rows describing the initialized npm scripts.
|
|
2303
|
+
*/
|
|
2304
|
+
function buildPackageJsonScriptTableLines(packageJsonScripts) {
|
|
2305
|
+
return Object.entries(packageJsonScripts).map(([scriptName, scriptCommand]) => `| \`npm run ${scriptName}\` | ${describeDefaultCoderPackageJsonScript(scriptName, scriptCommand)} |`);
|
|
2306
|
+
}
|
|
2307
|
+
/**
|
|
2308
|
+
* Describes one initialized npm script in human-readable terms.
|
|
2309
|
+
*/
|
|
2310
|
+
function describeDefaultCoderPackageJsonScript(scriptName, scriptCommand) {
|
|
2311
|
+
if (scriptName === 'coder:generate-boilerplates') {
|
|
2312
|
+
return `Runs \`${scriptCommand}\` to create new prompt files in \`${formatDisplayPath(PROMPTS_DIRECTORY_PATH)}/\`.`;
|
|
2313
|
+
}
|
|
2314
|
+
if (scriptName === 'coder:run') {
|
|
2315
|
+
return `Runs \`${scriptCommand}\` to execute the next ready prompt with shared repository context from \`${AGENTS_FILE_PATH}\`.`;
|
|
2316
|
+
}
|
|
2317
|
+
if (scriptName === 'coder:find-refactor-candidates') {
|
|
2318
|
+
return `Runs \`${scriptCommand}\` to generate prompt candidates for large or crowded files.`;
|
|
2319
|
+
}
|
|
2320
|
+
if (scriptName === 'coder:verify') {
|
|
2321
|
+
return `Runs \`${scriptCommand}\` to archive verified prompts into \`${formatDisplayPath(PROMPTS_DONE_DIRECTORY_PATH)}/\` and append repair prompts when needed.`;
|
|
2322
|
+
}
|
|
2323
|
+
return `Runs \`${scriptCommand}\`.`;
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* Formats one inline code list for human-readable markdown.
|
|
2327
|
+
*/
|
|
2328
|
+
function formatInlineCodeList(values) {
|
|
2329
|
+
return values.map((value) => `\`${value}\``).join(', ');
|
|
2330
|
+
}
|
|
2331
|
+
// Note: [🟡] Code for coder AGENT_CODING file boilerplate [agentCodingFile](src/cli/cli-commands/coder/agentCodingFile.ts) should never be published outside of `@promptbook/cli`
|
|
2332
|
+
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
1849
2333
|
|
|
1850
2334
|
/**
|
|
1851
|
-
*
|
|
2335
|
+
* Appends one text block to existing file content while preserving readable newlines.
|
|
2336
|
+
*
|
|
2337
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
1852
2338
|
*/
|
|
1853
|
-
|
|
2339
|
+
function appendBlock(currentContent, blockToAppend) {
|
|
2340
|
+
if (currentContent.trim() === '') {
|
|
2341
|
+
return `${blockToAppend}\n`;
|
|
2342
|
+
}
|
|
2343
|
+
const normalizedCurrentContent = currentContent.endsWith('\n') ? currentContent : `${currentContent}\n`;
|
|
2344
|
+
return `${normalizedCurrentContent}\n${blockToAppend}\n`;
|
|
2345
|
+
}
|
|
2346
|
+
// Note: [🟡] Code for coder init text appending [appendBlock](src/cli/cli-commands/coder/appendBlock.ts) should never be published outside of `@promptbook/cli`
|
|
2347
|
+
|
|
1854
2348
|
/**
|
|
1855
|
-
*
|
|
2349
|
+
* Reads one text file when it exists, otherwise returns `undefined`.
|
|
2350
|
+
*
|
|
2351
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
1856
2352
|
*/
|
|
1857
|
-
|
|
2353
|
+
async function readTextFileIfExists(path) {
|
|
2354
|
+
try {
|
|
2355
|
+
const fileStats = await stat(path);
|
|
2356
|
+
if (!fileStats.isFile()) {
|
|
2357
|
+
return undefined;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
catch (_a) {
|
|
2361
|
+
return undefined;
|
|
2362
|
+
}
|
|
2363
|
+
return readFile(path, 'utf-8');
|
|
2364
|
+
}
|
|
2365
|
+
// Note: [🟡] Code for coder init text-file reading [readTextFileIfExists](src/cli/cli-commands/coder/readTextFileIfExists.ts) should never be published outside of `@promptbook/cli`
|
|
2366
|
+
|
|
2367
|
+
/**
|
|
2368
|
+
* Relative path to `.env` in the initialized project.
|
|
2369
|
+
*/
|
|
2370
|
+
const ENV_FILE_PATH = '.env';
|
|
2371
|
+
/**
|
|
2372
|
+
* Fallback `.env` content used when no required variables need to be appended.
|
|
2373
|
+
*/
|
|
2374
|
+
const EMPTY_CODER_ENV_FILE_CONTENT = '# Environment variables for Promptbook coder\n';
|
|
1858
2375
|
/**
|
|
1859
2376
|
* Required environment variables for coding-agent git identity.
|
|
1860
2377
|
*/
|
|
@@ -1872,6 +2389,476 @@ const REQUIRED_CODER_ENV_VARIABLES = [
|
|
|
1872
2389
|
value: '13406525ED912F938FEA85AB4046C687298B2382',
|
|
1873
2390
|
},
|
|
1874
2391
|
];
|
|
2392
|
+
/**
|
|
2393
|
+
* Ensures `.env` exists and contains all required coder environment variables.
|
|
2394
|
+
*
|
|
2395
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2396
|
+
*/
|
|
2397
|
+
async function ensureCoderEnvFile(projectPath) {
|
|
2398
|
+
const envFilePath = join(projectPath, ENV_FILE_PATH);
|
|
2399
|
+
const existingEnvContent = await readTextFileIfExists(envFilePath);
|
|
2400
|
+
const isEnvFileExisting = existingEnvContent !== undefined;
|
|
2401
|
+
const currentEnvContent = existingEnvContent || '';
|
|
2402
|
+
const existingEnvVariableNames = parseEnvVariableNames(currentEnvContent);
|
|
2403
|
+
const missingEnvVariables = REQUIRED_CODER_ENV_VARIABLES.filter(({ name }) => !existingEnvVariableNames.has(name));
|
|
2404
|
+
if (missingEnvVariables.length === 0) {
|
|
2405
|
+
if (!isEnvFileExisting) {
|
|
2406
|
+
await writeFile(envFilePath, EMPTY_CODER_ENV_FILE_CONTENT, 'utf-8');
|
|
2407
|
+
return {
|
|
2408
|
+
envFileStatus: 'created',
|
|
2409
|
+
initializedEnvVariableNames: [],
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
return {
|
|
2413
|
+
envFileStatus: 'unchanged',
|
|
2414
|
+
initializedEnvVariableNames: [],
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
const envBlockToAppend = buildMissingEnvVariablesBlock(missingEnvVariables);
|
|
2418
|
+
const nextEnvContent = appendBlock(currentEnvContent, envBlockToAppend);
|
|
2419
|
+
await writeFile(envFilePath, nextEnvContent, 'utf-8');
|
|
2420
|
+
return {
|
|
2421
|
+
envFileStatus: isEnvFileExisting ? 'updated' : 'created',
|
|
2422
|
+
initializedEnvVariableNames: missingEnvVariables.map(({ name }) => name),
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
/**
|
|
2426
|
+
* Parses variable names currently defined in `.env` style content.
|
|
2427
|
+
*/
|
|
2428
|
+
function parseEnvVariableNames(envContent) {
|
|
2429
|
+
const variableNames = new Set();
|
|
2430
|
+
for (const line of envContent.split(/\r?\n/)) {
|
|
2431
|
+
const trimmedLine = line.trim();
|
|
2432
|
+
if (trimmedLine === '' || trimmedLine.startsWith('#')) {
|
|
2433
|
+
continue;
|
|
2434
|
+
}
|
|
2435
|
+
const match = trimmedLine.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
2436
|
+
if (!match || !match[1]) {
|
|
2437
|
+
continue;
|
|
2438
|
+
}
|
|
2439
|
+
variableNames.add(match[1]);
|
|
2440
|
+
}
|
|
2441
|
+
return variableNames;
|
|
2442
|
+
}
|
|
2443
|
+
/**
|
|
2444
|
+
* Builds a `.env` block containing missing coder environment variables.
|
|
2445
|
+
*/
|
|
2446
|
+
function buildMissingEnvVariablesBlock(variables) {
|
|
2447
|
+
return spaceTrim$1(`
|
|
2448
|
+
# Promptbook coder identity (initialized by \`ptbk coder init\`)
|
|
2449
|
+
${variables.map(({ name, value }) => `${name}=${JSON.stringify(value)}`).join('\n')}
|
|
2450
|
+
`);
|
|
2451
|
+
}
|
|
2452
|
+
// Note: [🟡] Code for coder init environment bootstrapping [ensureCoderEnvFile](src/cli/cli-commands/coder/ensureCoderEnvFile.ts) should never be published outside of `@promptbook/cli`
|
|
2453
|
+
|
|
2454
|
+
/**
|
|
2455
|
+
* Relative path to `.gitignore` in the initialized project.
|
|
2456
|
+
*/
|
|
2457
|
+
const GITIGNORE_FILE_PATH = '.gitignore';
|
|
2458
|
+
/**
|
|
2459
|
+
* `.gitignore` block required by standalone Promptbook coder projects.
|
|
2460
|
+
*/
|
|
2461
|
+
const CODER_GITIGNORE_BLOCK = spaceTrim$1(`
|
|
2462
|
+
# Promptbook Coder
|
|
2463
|
+
/.tmp
|
|
2464
|
+
`);
|
|
2465
|
+
/**
|
|
2466
|
+
* Ensures `.gitignore` contains the standalone Promptbook coder cache entry.
|
|
2467
|
+
*
|
|
2468
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2469
|
+
*/
|
|
2470
|
+
async function ensureCoderGitignoreFile(projectPath) {
|
|
2471
|
+
const gitignorePath = join(projectPath, GITIGNORE_FILE_PATH);
|
|
2472
|
+
const currentGitignoreContent = await readTextFileIfExists(gitignorePath);
|
|
2473
|
+
if (currentGitignoreContent !== undefined && hasTmpGitignoreRule(currentGitignoreContent)) {
|
|
2474
|
+
return 'unchanged';
|
|
2475
|
+
}
|
|
2476
|
+
const nextGitignoreContent = appendBlock(currentGitignoreContent || '', CODER_GITIGNORE_BLOCK);
|
|
2477
|
+
await writeFile(gitignorePath, nextGitignoreContent, 'utf-8');
|
|
2478
|
+
return currentGitignoreContent === undefined ? 'created' : 'updated';
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Detects whether `.gitignore` already covers the standalone coder temp directory.
|
|
2482
|
+
*/
|
|
2483
|
+
function hasTmpGitignoreRule(gitignoreContent) {
|
|
2484
|
+
return /(^|[\r\n])\/?\.tmp(?:[\r\n]|$)/u.test(gitignoreContent);
|
|
2485
|
+
}
|
|
2486
|
+
// Note: [🟡] Code for coder init gitignore bootstrapping [ensureCoderGitignoreFile](src/cli/cli-commands/coder/ensureCoderGitignoreFile.ts) should never be published outside of `@promptbook/cli`
|
|
2487
|
+
|
|
2488
|
+
/**
|
|
2489
|
+
* Ensures one coder markdown file exists with the provided default boilerplate.
|
|
2490
|
+
*
|
|
2491
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2492
|
+
*/
|
|
2493
|
+
async function ensureCoderMarkdownFile(projectPath, relativeFilePath, fileContent) {
|
|
2494
|
+
const absoluteFilePath = join(projectPath, relativeFilePath);
|
|
2495
|
+
if (await isExistingFile(absoluteFilePath)) {
|
|
2496
|
+
return 'unchanged';
|
|
2497
|
+
}
|
|
2498
|
+
await writeFile(absoluteFilePath, `${fileContent}\n`, 'utf-8');
|
|
2499
|
+
return 'created';
|
|
2500
|
+
}
|
|
2501
|
+
/**
|
|
2502
|
+
* Checks whether a path exists and is a file.
|
|
2503
|
+
*/
|
|
2504
|
+
async function isExistingFile(path) {
|
|
2505
|
+
try {
|
|
2506
|
+
return (await stat(path)).isFile();
|
|
2507
|
+
}
|
|
2508
|
+
catch (_a) {
|
|
2509
|
+
return false;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
// Note: [🟡] Code for coder init markdown bootstrapping [ensureCoderMarkdownFile](src/cli/cli-commands/coder/ensureCoderMarkdownFile.ts) should never be published outside of `@promptbook/cli`
|
|
2513
|
+
|
|
2514
|
+
/**
|
|
2515
|
+
* Default npm scripts initialized by `ptbk coder init`.
|
|
2516
|
+
*/
|
|
2517
|
+
const DEFAULT_CODER_PACKAGE_JSON_SCRIPTS = {
|
|
2518
|
+
'coder:generate-boilerplates': 'npx ptbk coder generate-boilerplates',
|
|
2519
|
+
'coder:run': 'npx ptbk coder run --agent github-copilot --model gpt-5.4 --thinking-level xhigh --context AGENTS.md --no-wait',
|
|
2520
|
+
'coder:find-refactor-candidates': 'npx ptbk coder find-refactor-candidates',
|
|
2521
|
+
'coder:verify': 'npx ptbk coder verify',
|
|
2522
|
+
};
|
|
2523
|
+
/**
|
|
2524
|
+
* Lists the default npm scripts initialized by `ptbk coder init`.
|
|
2525
|
+
*
|
|
2526
|
+
* @private internal utility of `coder init` command
|
|
2527
|
+
*/
|
|
2528
|
+
function getDefaultCoderPackageJsonScripts() {
|
|
2529
|
+
return DEFAULT_CODER_PACKAGE_JSON_SCRIPTS;
|
|
2530
|
+
}
|
|
2531
|
+
// Note: [🟡] Code for coder init package scripts [getDefaultCoderPackageJsonScripts](src/cli/cli-commands/coder/getDefaultCoderPackageJsonScripts.ts) should never be published outside of `@promptbook/cli`
|
|
2532
|
+
|
|
2533
|
+
/**
|
|
2534
|
+
* This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
|
|
2535
|
+
*
|
|
2536
|
+
* @public exported from `@promptbook/core`
|
|
2537
|
+
*/
|
|
2538
|
+
class ParseError extends Error {
|
|
2539
|
+
constructor(message) {
|
|
2540
|
+
super(message);
|
|
2541
|
+
this.name = 'ParseError';
|
|
2542
|
+
Object.setPrototypeOf(this, ParseError.prototype);
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
// TODO: Maybe split `ParseError` and `ApplyError`
|
|
2546
|
+
|
|
2547
|
+
/**
|
|
2548
|
+
* Default indentation used when creating new JSON configuration files.
|
|
2549
|
+
*/
|
|
2550
|
+
const DEFAULT_JSON_FILE_INDENTATION = ' ';
|
|
2551
|
+
/**
|
|
2552
|
+
* Default newline used when creating new JSON configuration files.
|
|
2553
|
+
*/
|
|
2554
|
+
const DEFAULT_JSON_FILE_NEWLINE = '\n';
|
|
2555
|
+
/**
|
|
2556
|
+
* Ensures one JSON object field contains the provided string-record entries.
|
|
2557
|
+
*
|
|
2558
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2559
|
+
*/
|
|
2560
|
+
async function mergeStringRecordJsonFile({ projectPath, relativeFilePath, fieldPath, nextEntries, ensureParentDirectoryPath, }) {
|
|
2561
|
+
if (ensureParentDirectoryPath) {
|
|
2562
|
+
await mkdir(join(projectPath, ensureParentDirectoryPath), { recursive: true });
|
|
2563
|
+
}
|
|
2564
|
+
const absoluteFilePath = join(projectPath, relativeFilePath);
|
|
2565
|
+
const fileContent = await readTextFileIfExists(absoluteFilePath);
|
|
2566
|
+
const formatting = detectJsonFileFormatting(fileContent);
|
|
2567
|
+
const jsonObject = fileContent === undefined ? {} : await parseJsonObjectFile(relativeFilePath, fileContent);
|
|
2568
|
+
const existingEntries = getStringRecordOrDefault(jsonObject[fieldPath], relativeFilePath, fieldPath);
|
|
2569
|
+
let hasChanges = fileContent === undefined;
|
|
2570
|
+
const mergedEntries = { ...existingEntries };
|
|
2571
|
+
for (const [entryKey, entryValue] of Object.entries(nextEntries)) {
|
|
2572
|
+
if (mergedEntries[entryKey] !== entryValue) {
|
|
2573
|
+
mergedEntries[entryKey] = entryValue;
|
|
2574
|
+
hasChanges = true;
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
if (!hasChanges) {
|
|
2578
|
+
return 'unchanged';
|
|
2579
|
+
}
|
|
2580
|
+
const nextJsonObject = { ...jsonObject };
|
|
2581
|
+
nextJsonObject[fieldPath] = mergedEntries;
|
|
2582
|
+
await writeFile(absoluteFilePath, serializeJsonObject(nextJsonObject, formatting), 'utf-8');
|
|
2583
|
+
return fileContent === undefined ? 'created' : 'updated';
|
|
2584
|
+
}
|
|
2585
|
+
/**
|
|
2586
|
+
* Parses one JSON object file while accepting VS Code style comments and trailing commas.
|
|
2587
|
+
*/
|
|
2588
|
+
async function parseJsonObjectFile(relativeFilePath, fileContent) {
|
|
2589
|
+
if (fileContent.trim() === '') {
|
|
2590
|
+
return {};
|
|
2591
|
+
}
|
|
2592
|
+
const typescript = await import('typescript');
|
|
2593
|
+
const parsedFile = typescript.parseConfigFileTextToJson(relativeFilePath, fileContent);
|
|
2594
|
+
if (parsedFile.error) {
|
|
2595
|
+
throw new ParseError(spaceTrim$1(`
|
|
2596
|
+
Cannot parse \`${relativeFilePath}\` as JSON.
|
|
2597
|
+
|
|
2598
|
+
${typescript.flattenDiagnosticMessageText(parsedFile.error.messageText, '\n')}
|
|
2599
|
+
`));
|
|
2600
|
+
}
|
|
2601
|
+
if (!isPlainObject(parsedFile.config)) {
|
|
2602
|
+
throw new ParseError(spaceTrim$1(`
|
|
2603
|
+
File \`${relativeFilePath}\` must contain one top-level JSON object.
|
|
2604
|
+
`));
|
|
2605
|
+
}
|
|
2606
|
+
return parsedFile.config;
|
|
2607
|
+
}
|
|
2608
|
+
/**
|
|
2609
|
+
* Reads one JSON object field as a string-to-string record.
|
|
2610
|
+
*/
|
|
2611
|
+
function getStringRecordOrDefault(value, relativeFilePath, fieldPath) {
|
|
2612
|
+
if (value === undefined) {
|
|
2613
|
+
return {};
|
|
2614
|
+
}
|
|
2615
|
+
if (!isPlainObject(value)) {
|
|
2616
|
+
throw new ParseError(spaceTrim$1(`
|
|
2617
|
+
File \`${relativeFilePath}\` contains invalid \`${fieldPath}\`.
|
|
2618
|
+
|
|
2619
|
+
Expected \`${fieldPath}\` to be an object with string values.
|
|
2620
|
+
`));
|
|
2621
|
+
}
|
|
2622
|
+
const stringRecord = {};
|
|
2623
|
+
for (const [key, itemValue] of Object.entries(value)) {
|
|
2624
|
+
if (typeof itemValue !== 'string') {
|
|
2625
|
+
throw new ParseError(spaceTrim$1(`
|
|
2626
|
+
File \`${relativeFilePath}\` contains invalid \`${fieldPath}.${key}\`.
|
|
2627
|
+
|
|
2628
|
+
Expected \`${fieldPath}\` to be an object with string values.
|
|
2629
|
+
`));
|
|
2630
|
+
}
|
|
2631
|
+
stringRecord[key] = itemValue;
|
|
2632
|
+
}
|
|
2633
|
+
return stringRecord;
|
|
2634
|
+
}
|
|
2635
|
+
/**
|
|
2636
|
+
* Serializes one JSON object using detected or default formatting.
|
|
2637
|
+
*/
|
|
2638
|
+
function serializeJsonObject(value, formatting) {
|
|
2639
|
+
return `${JSON.stringify(value, null, formatting.indentation)}${formatting.newline}`;
|
|
2640
|
+
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Detects indentation and newline formatting from an existing JSON file.
|
|
2643
|
+
*/
|
|
2644
|
+
function detectJsonFileFormatting(fileContent) {
|
|
2645
|
+
if (!fileContent) {
|
|
2646
|
+
return {
|
|
2647
|
+
indentation: DEFAULT_JSON_FILE_INDENTATION,
|
|
2648
|
+
newline: DEFAULT_JSON_FILE_NEWLINE,
|
|
2649
|
+
};
|
|
2650
|
+
}
|
|
2651
|
+
const indentationMatch = fileContent.match(/^[ \t]+(?=")/mu);
|
|
2652
|
+
return {
|
|
2653
|
+
indentation: (indentationMatch === null || indentationMatch === void 0 ? void 0 : indentationMatch[0]) || DEFAULT_JSON_FILE_INDENTATION,
|
|
2654
|
+
newline: fileContent.includes('\r\n') ? '\r\n' : '\n',
|
|
2655
|
+
};
|
|
2656
|
+
}
|
|
2657
|
+
/**
|
|
2658
|
+
* Checks whether one parsed JSON value is a plain object.
|
|
2659
|
+
*/
|
|
2660
|
+
function isPlainObject(value) {
|
|
2661
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2662
|
+
}
|
|
2663
|
+
// Note: [🟡] Code for coder init JSON merging [mergeStringRecordJsonFile](src/cli/cli-commands/coder/mergeStringRecordJsonFile.ts) should never be published outside of `@promptbook/cli`
|
|
2664
|
+
|
|
2665
|
+
/**
|
|
2666
|
+
* Relative path to `package.json` in the initialized project.
|
|
2667
|
+
*/
|
|
2668
|
+
const PACKAGE_JSON_FILE_PATH = 'package.json';
|
|
2669
|
+
/**
|
|
2670
|
+
* Ensures `package.json` contains the standalone Promptbook coder helper scripts.
|
|
2671
|
+
*
|
|
2672
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2673
|
+
*/
|
|
2674
|
+
async function ensureCoderPackageJsonFile(projectPath) {
|
|
2675
|
+
return mergeStringRecordJsonFile({
|
|
2676
|
+
projectPath,
|
|
2677
|
+
relativeFilePath: PACKAGE_JSON_FILE_PATH,
|
|
2678
|
+
fieldPath: 'scripts',
|
|
2679
|
+
nextEntries: getDefaultCoderPackageJsonScripts(),
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
// Note: [🟡] Code for coder init package.json bootstrapping [ensureCoderPackageJsonFile](src/cli/cli-commands/coder/ensureCoderPackageJsonFile.ts) should never be published outside of `@promptbook/cli`
|
|
2683
|
+
|
|
2684
|
+
/**
|
|
2685
|
+
* VS Code setting key used to route pasted markdown images into prompt-specific screenshots.
|
|
2686
|
+
*/
|
|
2687
|
+
const MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY = 'markdown.copyFiles.destination';
|
|
2688
|
+
/**
|
|
2689
|
+
* Markdown glob used for coder prompt files inside VS Code settings.
|
|
2690
|
+
*/
|
|
2691
|
+
const PROMPTS_MARKDOWN_FILE_GLOB = 'prompts/*md';
|
|
2692
|
+
/**
|
|
2693
|
+
* Screenshot destination used for pasted prompt images inside VS Code settings.
|
|
2694
|
+
*/
|
|
2695
|
+
const PROMPTS_SCREENSHOT_DESTINATION = './prompts/screenshots/${documentBaseName}.png';
|
|
2696
|
+
/**
|
|
2697
|
+
* Default VS Code settings initialized by `ptbk coder init`.
|
|
2698
|
+
*/
|
|
2699
|
+
const DEFAULT_CODER_VSCODE_SETTINGS = {
|
|
2700
|
+
[MARKDOWN_COPY_FILES_DESTINATION_SETTING_KEY]: {
|
|
2701
|
+
[PROMPTS_MARKDOWN_FILE_GLOB]: PROMPTS_SCREENSHOT_DESTINATION,
|
|
2702
|
+
},
|
|
2703
|
+
};
|
|
2704
|
+
/**
|
|
2705
|
+
* Lists the default VS Code settings initialized by `ptbk coder init`.
|
|
2706
|
+
*
|
|
2707
|
+
* @private internal utility of `coder init` command
|
|
2708
|
+
*/
|
|
2709
|
+
function getDefaultCoderVscodeSettings() {
|
|
2710
|
+
return DEFAULT_CODER_VSCODE_SETTINGS;
|
|
2711
|
+
}
|
|
2712
|
+
// Note: [🟡] Code for coder init VS Code settings [getDefaultCoderVscodeSettings](src/cli/cli-commands/coder/getDefaultCoderVscodeSettings.ts) should never be published outside of `@promptbook/cli`
|
|
2713
|
+
|
|
2714
|
+
/**
|
|
2715
|
+
* Relative path to the VS Code settings file initialized by `ptbk coder init`.
|
|
2716
|
+
*/
|
|
2717
|
+
const VSCODE_SETTINGS_FILE_PATH = '.vscode/settings.json';
|
|
2718
|
+
/**
|
|
2719
|
+
* Relative path to the VS Code directory initialized by `ptbk coder init`.
|
|
2720
|
+
*/
|
|
2721
|
+
const VSCODE_DIRECTORY_PATH = '.vscode';
|
|
2722
|
+
/**
|
|
2723
|
+
* Ensures VS Code routes pasted prompt images into `prompts/screenshots`.
|
|
2724
|
+
*
|
|
2725
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2726
|
+
*/
|
|
2727
|
+
async function ensureCoderVscodeSettingsFile(projectPath) {
|
|
2728
|
+
const [fieldPath, nextEntries] = resolveDefaultCoderVscodeSettingsEntry();
|
|
2729
|
+
return mergeStringRecordJsonFile({
|
|
2730
|
+
projectPath,
|
|
2731
|
+
relativeFilePath: VSCODE_SETTINGS_FILE_PATH,
|
|
2732
|
+
fieldPath,
|
|
2733
|
+
nextEntries,
|
|
2734
|
+
ensureParentDirectoryPath: VSCODE_DIRECTORY_PATH,
|
|
2735
|
+
});
|
|
2736
|
+
}
|
|
2737
|
+
/**
|
|
2738
|
+
* Resolves the default string-record entry that `coder init` merges into VS Code settings.
|
|
2739
|
+
*/
|
|
2740
|
+
function resolveDefaultCoderVscodeSettingsEntry() {
|
|
2741
|
+
const [defaultVscodeSettingsEntry] = Object.entries(getDefaultCoderVscodeSettings());
|
|
2742
|
+
if (!defaultVscodeSettingsEntry) {
|
|
2743
|
+
throw new Error('Default coder VS Code settings must define at least one string-record entry.');
|
|
2744
|
+
}
|
|
2745
|
+
return defaultVscodeSettingsEntry;
|
|
2746
|
+
}
|
|
2747
|
+
// Note: [🟡] Code for coder init VS Code bootstrapping [ensureCoderVscodeSettingsFile](src/cli/cli-commands/coder/ensureCoderVscodeSettingsFile.ts) should never be published outside of `@promptbook/cli`
|
|
2748
|
+
|
|
2749
|
+
/**
|
|
2750
|
+
* Ensures a relative directory exists in the project root.
|
|
2751
|
+
*
|
|
2752
|
+
* @private function of `initializeCoderProjectConfiguration`
|
|
2753
|
+
*/
|
|
2754
|
+
async function ensureDirectory(projectPath, relativeDirectoryPath) {
|
|
2755
|
+
const directoryPath = join(projectPath, relativeDirectoryPath);
|
|
2756
|
+
const isDirectoryExisting = await isExistingDirectory(directoryPath);
|
|
2757
|
+
if (!isDirectoryExisting) {
|
|
2758
|
+
await mkdir(directoryPath, { recursive: true });
|
|
2759
|
+
return 'created';
|
|
2760
|
+
}
|
|
2761
|
+
return 'unchanged';
|
|
2762
|
+
}
|
|
2763
|
+
/**
|
|
2764
|
+
* Checks whether a path exists and is a directory.
|
|
2765
|
+
*/
|
|
2766
|
+
async function isExistingDirectory(path) {
|
|
2767
|
+
try {
|
|
2768
|
+
return (await stat(path)).isDirectory();
|
|
2769
|
+
}
|
|
2770
|
+
catch (_a) {
|
|
2771
|
+
return false;
|
|
2772
|
+
}
|
|
2773
|
+
}
|
|
2774
|
+
// Note: [🟡] Code for coder init directory creation [ensureDirectory](src/cli/cli-commands/coder/ensureDirectory.ts) should never be published outside of `@promptbook/cli`
|
|
2775
|
+
|
|
2776
|
+
/**
|
|
2777
|
+
* Creates or updates all coder configuration artifacts required in the current project.
|
|
2778
|
+
*
|
|
2779
|
+
* @private internal utility of `coder init` command
|
|
2780
|
+
*/
|
|
2781
|
+
async function initializeCoderProjectConfiguration(projectPath) {
|
|
2782
|
+
const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH);
|
|
2783
|
+
const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
|
|
2784
|
+
const promptsTemplatesDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_TEMPLATES_DIRECTORY_PATH);
|
|
2785
|
+
const promptTemplateFileStatuses = await ensureDefaultCoderPromptTemplateFiles(projectPath);
|
|
2786
|
+
const agentsFileStatus = await ensureCoderMarkdownFile(projectPath, AGENTS_FILE_PATH, getDefaultCoderAgentsFileContent());
|
|
2787
|
+
const agentCodingFileStatus = await ensureCoderMarkdownFile(projectPath, AGENT_CODING_FILE_PATH, getDefaultCoderAgentCodingFileContent({
|
|
2788
|
+
packageJsonScripts: getDefaultCoderPackageJsonScripts(),
|
|
2789
|
+
}));
|
|
2790
|
+
const { envFileStatus, initializedEnvVariableNames } = await ensureCoderEnvFile(projectPath);
|
|
2791
|
+
const gitignoreFileStatus = await ensureCoderGitignoreFile(projectPath);
|
|
2792
|
+
const packageJsonFileStatus = await ensureCoderPackageJsonFile(projectPath);
|
|
2793
|
+
const vscodeSettingsFileStatus = await ensureCoderVscodeSettingsFile(projectPath);
|
|
2794
|
+
return {
|
|
2795
|
+
promptsDirectoryStatus,
|
|
2796
|
+
promptsDoneDirectoryStatus,
|
|
2797
|
+
promptsTemplatesDirectoryStatus,
|
|
2798
|
+
promptTemplateFileStatuses,
|
|
2799
|
+
agentsFileStatus,
|
|
2800
|
+
agentCodingFileStatus,
|
|
2801
|
+
envFileStatus,
|
|
2802
|
+
gitignoreFileStatus,
|
|
2803
|
+
packageJsonFileStatus,
|
|
2804
|
+
vscodeSettingsFileStatus,
|
|
2805
|
+
initializedEnvVariableNames,
|
|
2806
|
+
};
|
|
2807
|
+
}
|
|
2808
|
+
// Note: [🟡] Code for coder init project bootstrapping [initializeCoderProjectConfiguration](src/cli/cli-commands/coder/initializeCoderProjectConfiguration.ts) should never be published outside of `@promptbook/cli`
|
|
2809
|
+
|
|
2810
|
+
/**
|
|
2811
|
+
* Prints a readable summary of what was initialized for the user.
|
|
2812
|
+
*
|
|
2813
|
+
* @private function of `coder init` command
|
|
2814
|
+
*/
|
|
2815
|
+
function printInitializationSummary(summary) {
|
|
2816
|
+
console.info(colors.green('Promptbook coder configuration initialized.'));
|
|
2817
|
+
printInitializationStatusLine('prompts/', summary.promptsDirectoryStatus);
|
|
2818
|
+
printInitializationStatusLine('prompts/done/', summary.promptsDoneDirectoryStatus);
|
|
2819
|
+
printInitializationStatusLine('prompts/templates/', summary.promptsTemplatesDirectoryStatus);
|
|
2820
|
+
for (const templateFileStatus of summary.promptTemplateFileStatuses) {
|
|
2821
|
+
printInitializationStatusLine(formatDisplayPath(templateFileStatus.relativeFilePath), templateFileStatus.status);
|
|
2822
|
+
}
|
|
2823
|
+
printInitializationStatusLine(AGENTS_FILE_PATH, summary.agentsFileStatus);
|
|
2824
|
+
printInitializationStatusLine(AGENT_CODING_FILE_PATH, summary.agentCodingFileStatus);
|
|
2825
|
+
printInitializationStatusLine('.env', summary.envFileStatus);
|
|
2826
|
+
printInitializationStatusLine('.gitignore', summary.gitignoreFileStatus);
|
|
2827
|
+
printInitializationStatusLine('package.json', summary.packageJsonFileStatus);
|
|
2828
|
+
printInitializationStatusLine('.vscode/settings.json', summary.vscodeSettingsFileStatus);
|
|
2829
|
+
if (summary.initializedEnvVariableNames.length > 0) {
|
|
2830
|
+
printInitializationNote(`Added env variables: ${summary.initializedEnvVariableNames.join(', ')}`, colors.cyan);
|
|
2831
|
+
}
|
|
2832
|
+
else {
|
|
2833
|
+
printInitializationNote('Required coder env variables are already present.', colors.gray);
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Formats one initialization status into a human-readable label.
|
|
2838
|
+
*/
|
|
2839
|
+
function formatInitializationStatus(status) {
|
|
2840
|
+
if (status === 'created') {
|
|
2841
|
+
return 'created';
|
|
2842
|
+
}
|
|
2843
|
+
if (status === 'updated') {
|
|
2844
|
+
return 'updated';
|
|
2845
|
+
}
|
|
2846
|
+
return 'unchanged';
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Prints one checked initialization-status line.
|
|
2850
|
+
*/
|
|
2851
|
+
function printInitializationStatusLine(relativePath, status) {
|
|
2852
|
+
console.info(colors.gray(`✔ ${relativePath}: ${formatInitializationStatus(status)}`));
|
|
2853
|
+
}
|
|
2854
|
+
/**
|
|
2855
|
+
* Prints one checked initialization note.
|
|
2856
|
+
*/
|
|
2857
|
+
function printInitializationNote(message, colorize) {
|
|
2858
|
+
console.info(colorize(`✔ ${message}`));
|
|
2859
|
+
}
|
|
2860
|
+
// Note: [🟡] Code for coder init summary printing [printInitializationSummary](src/cli/cli-commands/coder/printInitializationSummary.ts) should never be published outside of `@promptbook/cli`
|
|
2861
|
+
|
|
1875
2862
|
/**
|
|
1876
2863
|
* Initializes `coder init` command for Promptbook CLI utilities.
|
|
1877
2864
|
*
|
|
@@ -1885,9 +2872,15 @@ function $initializeCoderInitCommand(program) {
|
|
|
1885
2872
|
command.description(spaceTrim$1(`
|
|
1886
2873
|
Initialize Promptbook coder configuration for current project
|
|
1887
2874
|
|
|
1888
|
-
Creates:
|
|
2875
|
+
Creates or updates:
|
|
1889
2876
|
- prompts/
|
|
1890
2877
|
- prompts/done/
|
|
2878
|
+
${listDefaultCoderProjectPromptTemplateDisplayPaths()}
|
|
2879
|
+
- ${AGENTS_FILE_PATH}
|
|
2880
|
+
- ${AGENT_CODING_FILE_PATH}
|
|
2881
|
+
- .gitignore
|
|
2882
|
+
- package.json
|
|
2883
|
+
- .vscode/settings.json
|
|
1891
2884
|
|
|
1892
2885
|
Ensures required coding-agent environment variables in .env:
|
|
1893
2886
|
- CODING_AGENT_GIT_NAME
|
|
@@ -1900,146 +2893,12 @@ function $initializeCoderInitCommand(program) {
|
|
|
1900
2893
|
}));
|
|
1901
2894
|
}
|
|
1902
2895
|
/**
|
|
1903
|
-
*
|
|
1904
|
-
*/
|
|
1905
|
-
async function initializeCoderProjectConfiguration(projectPath) {
|
|
1906
|
-
const promptsDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DIRECTORY_PATH);
|
|
1907
|
-
const promptsDoneDirectoryStatus = await ensureDirectory(projectPath, PROMPTS_DONE_DIRECTORY_PATH);
|
|
1908
|
-
const { envFileStatus, initializedEnvVariableNames } = await ensureCoderEnvFile(projectPath);
|
|
1909
|
-
return {
|
|
1910
|
-
promptsDirectoryStatus,
|
|
1911
|
-
promptsDoneDirectoryStatus,
|
|
1912
|
-
envFileStatus,
|
|
1913
|
-
initializedEnvVariableNames,
|
|
1914
|
-
};
|
|
1915
|
-
}
|
|
1916
|
-
/**
|
|
1917
|
-
* Ensures a relative directory exists in the project root.
|
|
1918
|
-
*/
|
|
1919
|
-
async function ensureDirectory(projectPath, relativeDirectoryPath) {
|
|
1920
|
-
const directoryPath = join(projectPath, relativeDirectoryPath);
|
|
1921
|
-
const existedBefore = await isExistingDirectory(directoryPath);
|
|
1922
|
-
if (!existedBefore) {
|
|
1923
|
-
await mkdir(directoryPath, { recursive: true });
|
|
1924
|
-
return 'created';
|
|
1925
|
-
}
|
|
1926
|
-
return 'unchanged';
|
|
1927
|
-
}
|
|
1928
|
-
/**
|
|
1929
|
-
* Ensures `.env` exists and contains all required coder environment variables.
|
|
2896
|
+
* Lists the project-owned template file paths created by `ptbk coder init`.
|
|
1930
2897
|
*/
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
const existingEnvVariables = parseEnvVariableNames(currentEnvContent);
|
|
1936
|
-
const missingEnvVariables = REQUIRED_CODER_ENV_VARIABLES.filter(({ name }) => !existingEnvVariables.has(name));
|
|
1937
|
-
if (missingEnvVariables.length === 0) {
|
|
1938
|
-
if (!envFileExistedBefore) {
|
|
1939
|
-
await writeFile(envFilePath, '# Environment variables for Promptbook coder\n', 'utf-8');
|
|
1940
|
-
return {
|
|
1941
|
-
envFileStatus: 'created',
|
|
1942
|
-
initializedEnvVariableNames: [],
|
|
1943
|
-
};
|
|
1944
|
-
}
|
|
1945
|
-
return {
|
|
1946
|
-
envFileStatus: 'unchanged',
|
|
1947
|
-
initializedEnvVariableNames: [],
|
|
1948
|
-
};
|
|
1949
|
-
}
|
|
1950
|
-
const envBlockToAppend = buildMissingEnvVariablesBlock(missingEnvVariables);
|
|
1951
|
-
const nextEnvContent = appendBlock(currentEnvContent, envBlockToAppend);
|
|
1952
|
-
await writeFile(envFilePath, nextEnvContent, 'utf-8');
|
|
1953
|
-
return {
|
|
1954
|
-
envFileStatus: envFileExistedBefore ? 'updated' : 'created',
|
|
1955
|
-
initializedEnvVariableNames: missingEnvVariables.map(({ name }) => name),
|
|
1956
|
-
};
|
|
1957
|
-
}
|
|
1958
|
-
/**
|
|
1959
|
-
* Parses variable names currently defined in `.env` style content.
|
|
1960
|
-
*/
|
|
1961
|
-
function parseEnvVariableNames(envContent) {
|
|
1962
|
-
const variableNames = new Set();
|
|
1963
|
-
for (const line of envContent.split(/\r?\n/)) {
|
|
1964
|
-
const trimmedLine = line.trim();
|
|
1965
|
-
if (trimmedLine === '' || trimmedLine.startsWith('#')) {
|
|
1966
|
-
continue;
|
|
1967
|
-
}
|
|
1968
|
-
const match = trimmedLine.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
1969
|
-
if (!match || !match[1]) {
|
|
1970
|
-
continue;
|
|
1971
|
-
}
|
|
1972
|
-
variableNames.add(match[1]);
|
|
1973
|
-
}
|
|
1974
|
-
return variableNames;
|
|
1975
|
-
}
|
|
1976
|
-
/**
|
|
1977
|
-
* Builds a `.env` block containing missing coder environment variables.
|
|
1978
|
-
*/
|
|
1979
|
-
function buildMissingEnvVariablesBlock(variables) {
|
|
1980
|
-
return spaceTrim$1(`
|
|
1981
|
-
# Promptbook coder identity (initialized by \`ptbk coder init\`)
|
|
1982
|
-
${variables.map(({ name, value }) => `${name}=${JSON.stringify(value)}`).join('\n')}
|
|
1983
|
-
`);
|
|
1984
|
-
}
|
|
1985
|
-
/**
|
|
1986
|
-
* Appends one text block to existing file content while preserving readable newlines.
|
|
1987
|
-
*/
|
|
1988
|
-
function appendBlock(currentContent, blockToAppend) {
|
|
1989
|
-
if (currentContent.trim() === '') {
|
|
1990
|
-
return `${blockToAppend}\n`;
|
|
1991
|
-
}
|
|
1992
|
-
const normalizedCurrentContent = currentContent.endsWith('\n') ? currentContent : `${currentContent}\n`;
|
|
1993
|
-
return `${normalizedCurrentContent}\n${blockToAppend}\n`;
|
|
1994
|
-
}
|
|
1995
|
-
/**
|
|
1996
|
-
* Prints a readable summary of what was initialized for the user.
|
|
1997
|
-
*/
|
|
1998
|
-
function printInitializationSummary(summary) {
|
|
1999
|
-
console.info(colors.green('Promptbook coder configuration initialized.'));
|
|
2000
|
-
console.info(colors.gray(`- prompts/: ${formatInitializationStatus(summary.promptsDirectoryStatus)}`));
|
|
2001
|
-
console.info(colors.gray(`- prompts/done/: ${formatInitializationStatus(summary.promptsDoneDirectoryStatus)}`));
|
|
2002
|
-
console.info(colors.gray(`- .env: ${formatInitializationStatus(summary.envFileStatus)}`));
|
|
2003
|
-
if (summary.initializedEnvVariableNames.length > 0) {
|
|
2004
|
-
console.info(colors.cyan(`- Added env variables: ${summary.initializedEnvVariableNames.join(', ')}`));
|
|
2005
|
-
}
|
|
2006
|
-
else {
|
|
2007
|
-
console.info(colors.gray('- Required coder env variables are already present.'));
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
/**
|
|
2011
|
-
* Formats one initialization status into a human-readable label.
|
|
2012
|
-
*/
|
|
2013
|
-
function formatInitializationStatus(status) {
|
|
2014
|
-
if (status === 'created') {
|
|
2015
|
-
return 'created';
|
|
2016
|
-
}
|
|
2017
|
-
if (status === 'updated') {
|
|
2018
|
-
return 'updated';
|
|
2019
|
-
}
|
|
2020
|
-
return 'unchanged';
|
|
2021
|
-
}
|
|
2022
|
-
/**
|
|
2023
|
-
* Checks whether a path exists and is a file.
|
|
2024
|
-
*/
|
|
2025
|
-
async function isExistingFile(path) {
|
|
2026
|
-
try {
|
|
2027
|
-
return (await stat(path)).isFile();
|
|
2028
|
-
}
|
|
2029
|
-
catch (_a) {
|
|
2030
|
-
return false;
|
|
2031
|
-
}
|
|
2032
|
-
}
|
|
2033
|
-
/**
|
|
2034
|
-
* Checks whether a path exists and is a directory.
|
|
2035
|
-
*/
|
|
2036
|
-
async function isExistingDirectory(path) {
|
|
2037
|
-
try {
|
|
2038
|
-
return (await stat(path)).isDirectory();
|
|
2039
|
-
}
|
|
2040
|
-
catch (_a) {
|
|
2041
|
-
return false;
|
|
2042
|
-
}
|
|
2898
|
+
function listDefaultCoderProjectPromptTemplateDisplayPaths() {
|
|
2899
|
+
return getDefaultCoderProjectPromptTemplateDefinitions()
|
|
2900
|
+
.map(({ relativeFilePath }) => `- ${formatDisplayPath(relativeFilePath)}`)
|
|
2901
|
+
.join('\n');
|
|
2043
2902
|
}
|
|
2044
2903
|
// Note: [🟡] Code for CLI command [init](src/cli/cli-commands/coder/init.ts) should never be published outside of `@promptbook/cli`
|
|
2045
2904
|
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -4043,33 +4902,6 @@ class NotAllowed extends Error {
|
|
|
4043
4902
|
}
|
|
4044
4903
|
}
|
|
4045
4904
|
|
|
4046
|
-
/**
|
|
4047
|
-
* This error indicates that promptbook not found in the collection
|
|
4048
|
-
*
|
|
4049
|
-
* @public exported from `@promptbook/core`
|
|
4050
|
-
*/
|
|
4051
|
-
class NotFoundError extends Error {
|
|
4052
|
-
constructor(message) {
|
|
4053
|
-
super(message);
|
|
4054
|
-
this.name = 'NotFoundError';
|
|
4055
|
-
Object.setPrototypeOf(this, NotFoundError.prototype);
|
|
4056
|
-
}
|
|
4057
|
-
}
|
|
4058
|
-
|
|
4059
|
-
/**
|
|
4060
|
-
* This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
|
|
4061
|
-
*
|
|
4062
|
-
* @public exported from `@promptbook/core`
|
|
4063
|
-
*/
|
|
4064
|
-
class ParseError extends Error {
|
|
4065
|
-
constructor(message) {
|
|
4066
|
-
super(message);
|
|
4067
|
-
this.name = 'ParseError';
|
|
4068
|
-
Object.setPrototypeOf(this, ParseError.prototype);
|
|
4069
|
-
}
|
|
4070
|
-
}
|
|
4071
|
-
// TODO: Maybe split `ParseError` and `ApplyError`
|
|
4072
|
-
|
|
4073
4905
|
/**
|
|
4074
4906
|
* Generates random token
|
|
4075
4907
|
*
|
|
@@ -40300,91 +41132,6 @@ var findFreshEmojiTags = /*#__PURE__*/Object.freeze({
|
|
|
40300
41132
|
findFreshEmojiTag: findFreshEmojiTag
|
|
40301
41133
|
});
|
|
40302
41134
|
|
|
40303
|
-
/**
|
|
40304
|
-
* Root folders that contain source-like files for scanning.
|
|
40305
|
-
*/
|
|
40306
|
-
const SOURCE_ROOTS = ['src', 'apps', 'scripts', 'examples', 'agents', 'other'];
|
|
40307
|
-
/**
|
|
40308
|
-
* File extensions treated as source code.
|
|
40309
|
-
*/
|
|
40310
|
-
const SOURCE_FILE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
40311
|
-
/**
|
|
40312
|
-
* Glob patterns that should be ignored when scanning for source files.
|
|
40313
|
-
*/
|
|
40314
|
-
const SOURCE_FILE_IGNORE_GLOBS = [
|
|
40315
|
-
'**/node_modules/**',
|
|
40316
|
-
'**/packages/**',
|
|
40317
|
-
'**/.*/**',
|
|
40318
|
-
'**/.git/**',
|
|
40319
|
-
'**/.idea/**',
|
|
40320
|
-
'**/.vscode/**',
|
|
40321
|
-
'**/.promptbook/**',
|
|
40322
|
-
'**/.next/**',
|
|
40323
|
-
'**/.tmp/**',
|
|
40324
|
-
'**/tmp/**',
|
|
40325
|
-
'**/coverage/**',
|
|
40326
|
-
'**/dist/**',
|
|
40327
|
-
'**/build/**',
|
|
40328
|
-
'**/out/**',
|
|
40329
|
-
'**/prompts/**',
|
|
40330
|
-
'**/changelog/**',
|
|
40331
|
-
];
|
|
40332
|
-
/**
|
|
40333
|
-
* Default maximum line count for source files.
|
|
40334
|
-
*/
|
|
40335
|
-
const DEFAULT_MAX_LINE_COUNT = 2000;
|
|
40336
|
-
/**
|
|
40337
|
-
* Per-extension line count limits.
|
|
40338
|
-
*/
|
|
40339
|
-
const LINE_COUNT_LIMITS_BY_EXTENSION = {
|
|
40340
|
-
'.ts': 2000,
|
|
40341
|
-
'.tsx': 2000,
|
|
40342
|
-
'.js': 2000,
|
|
40343
|
-
'.jsx': 2000,
|
|
40344
|
-
};
|
|
40345
|
-
/**
|
|
40346
|
-
* Glob patterns that are exempt from line-count checks.
|
|
40347
|
-
*/
|
|
40348
|
-
const LINE_COUNT_EXEMPT_GLOBS = ['other/cspell-dictionaries/**/*.txt'];
|
|
40349
|
-
/**
|
|
40350
|
-
* Maximum number of entities before a file is flagged.
|
|
40351
|
-
*/
|
|
40352
|
-
const MAX_ENTITIES_PER_FILE = 20;
|
|
40353
|
-
/**
|
|
40354
|
-
* File extensions eligible for entity counting.
|
|
40355
|
-
*/
|
|
40356
|
-
const ENTITY_COUNT_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
40357
|
-
/**
|
|
40358
|
-
* Markers that identify generated files which should be skipped.
|
|
40359
|
-
*/
|
|
40360
|
-
const GENERATED_CODE_MARKERS = [
|
|
40361
|
-
'WARNING: This code has been generated',
|
|
40362
|
-
'This code has been generated so that any manual changes will be overwritten',
|
|
40363
|
-
];
|
|
40364
|
-
/**
|
|
40365
|
-
* Name of the prompts directory.
|
|
40366
|
-
*/
|
|
40367
|
-
const PROMPTS_DIR_NAME = 'prompts';
|
|
40368
|
-
/**
|
|
40369
|
-
* Step size used for prompt numbering.
|
|
40370
|
-
*/
|
|
40371
|
-
const PROMPT_NUMBER_STEP = 10;
|
|
40372
|
-
/**
|
|
40373
|
-
* Prefix used for generated prompt slugs.
|
|
40374
|
-
*/
|
|
40375
|
-
const PROMPT_SLUG_PREFIX = 'refactor';
|
|
40376
|
-
/**
|
|
40377
|
-
* Label used to mark the target file in generated prompts.
|
|
40378
|
-
*/
|
|
40379
|
-
const PROMPT_TARGET_LABEL = 'Target file';
|
|
40380
|
-
/**
|
|
40381
|
-
* Maximum length for generated prompt slugs.
|
|
40382
|
-
*/
|
|
40383
|
-
const PROMPT_SLUG_MAX_LENGTH = 80;
|
|
40384
|
-
/**
|
|
40385
|
-
* Note: [?] Code in this file should never be published in any package
|
|
40386
|
-
*/
|
|
40387
|
-
|
|
40388
41135
|
/**
|
|
40389
41136
|
* Normalizes a repo-relative path to use forward slashes.
|
|
40390
41137
|
*
|
|
@@ -40402,7 +41149,7 @@ function normalizeRefactorCandidatePath(pathValue) {
|
|
|
40402
41149
|
* @private function of findRefactorCandidates
|
|
40403
41150
|
*/
|
|
40404
41151
|
async function analyzeSourceFileForRefactorCandidate(options) {
|
|
40405
|
-
const { filePath, lineCountExemptPaths, rootDir } = options;
|
|
41152
|
+
const { filePath, heuristics, lineCountExemptPaths, rootDir } = options;
|
|
40406
41153
|
const normalizedAbsolutePath = normalizeAbsolutePath$1(filePath);
|
|
40407
41154
|
const content = await readFile(filePath, 'utf-8');
|
|
40408
41155
|
if (isGeneratedFile(content)) {
|
|
@@ -40413,15 +41160,21 @@ async function analyzeSourceFileForRefactorCandidate(options) {
|
|
|
40413
41160
|
const reasons = [];
|
|
40414
41161
|
if (!lineCountExemptPaths.has(normalizedAbsolutePath)) {
|
|
40415
41162
|
const lineCount = countLines(content);
|
|
40416
|
-
const maxLines = getMaxLinesForExtension(extension);
|
|
41163
|
+
const maxLines = getMaxLinesForExtension(extension, heuristics);
|
|
40417
41164
|
if (lineCount > maxLines) {
|
|
40418
41165
|
reasons.push(`lines ${lineCount}/${maxLines}`);
|
|
40419
41166
|
}
|
|
40420
41167
|
}
|
|
40421
|
-
if (
|
|
40422
|
-
const
|
|
40423
|
-
if (entityCount >
|
|
40424
|
-
reasons.push(`entities ${entityCount}/${
|
|
41168
|
+
if (STRUCTURAL_ANALYSIS_EXTENSIONS.includes(extension)) {
|
|
41169
|
+
const structureSummary = summarizeSourceFileStructure(content, extension, filePath);
|
|
41170
|
+
if (structureSummary.entityCount > heuristics.maxEntityCountPerFile) {
|
|
41171
|
+
reasons.push(`entities ${structureSummary.entityCount}/${heuristics.maxEntityCountPerFile}`);
|
|
41172
|
+
}
|
|
41173
|
+
if (structureSummary.functionCount > heuristics.maxFunctionCountPerFile) {
|
|
41174
|
+
reasons.push(`functions ${structureSummary.functionCount}/${heuristics.maxFunctionCountPerFile}`);
|
|
41175
|
+
}
|
|
41176
|
+
if (structureSummary.maxFunctionComplexity > heuristics.maxFunctionComplexity) {
|
|
41177
|
+
reasons.push(buildComplexityReason(structureSummary, heuristics.maxFunctionComplexity));
|
|
40425
41178
|
}
|
|
40426
41179
|
}
|
|
40427
41180
|
if (reasons.length === 0) {
|
|
@@ -40446,9 +41199,9 @@ function isGeneratedFile(content) {
|
|
|
40446
41199
|
*
|
|
40447
41200
|
* @private function of analyzeSourceFileForRefactorCandidate
|
|
40448
41201
|
*/
|
|
40449
|
-
function getMaxLinesForExtension(extension) {
|
|
41202
|
+
function getMaxLinesForExtension(extension, heuristics) {
|
|
40450
41203
|
var _a;
|
|
40451
|
-
return (_a =
|
|
41204
|
+
return (_a = heuristics.maxLineCountByExtension[extension]) !== null && _a !== void 0 ? _a : heuristics.maxDefaultLineCount;
|
|
40452
41205
|
}
|
|
40453
41206
|
/**
|
|
40454
41207
|
* Counts lines while ignoring a trailing newline.
|
|
@@ -40463,14 +41216,17 @@ function countLines(content) {
|
|
|
40463
41216
|
return lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
|
|
40464
41217
|
}
|
|
40465
41218
|
/**
|
|
40466
|
-
*
|
|
41219
|
+
* Summarizes the structural metrics used to score one source file.
|
|
40467
41220
|
*
|
|
40468
41221
|
* @private function of analyzeSourceFileForRefactorCandidate
|
|
40469
41222
|
*/
|
|
40470
|
-
function
|
|
41223
|
+
function summarizeSourceFileStructure(content, extension, filePath) {
|
|
40471
41224
|
const scriptKind = getScriptKindForExtension(extension);
|
|
40472
|
-
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest,
|
|
40473
|
-
return
|
|
41225
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, scriptKind);
|
|
41226
|
+
return {
|
|
41227
|
+
entityCount: countEntitiesInSourceFile(sourceFile),
|
|
41228
|
+
...summarizeFunctionsInSourceFile(sourceFile),
|
|
41229
|
+
};
|
|
40474
41230
|
}
|
|
40475
41231
|
/**
|
|
40476
41232
|
* Counts top-level entities in a parsed TypeScript source file.
|
|
@@ -40504,6 +41260,169 @@ function countEntitiesInSourceFile(sourceFile) {
|
|
|
40504
41260
|
}
|
|
40505
41261
|
return count;
|
|
40506
41262
|
}
|
|
41263
|
+
/**
|
|
41264
|
+
* Summarizes named functions and methods in a parsed source file.
|
|
41265
|
+
*
|
|
41266
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41267
|
+
*/
|
|
41268
|
+
function summarizeFunctionsInSourceFile(sourceFile) {
|
|
41269
|
+
let functionCount = 0;
|
|
41270
|
+
let maxFunctionComplexity = 0;
|
|
41271
|
+
let mostComplexFunctionName = null;
|
|
41272
|
+
const visitNode = (node) => {
|
|
41273
|
+
if (isCountedFunctionLikeDeclaration(node)) {
|
|
41274
|
+
functionCount += 1;
|
|
41275
|
+
const functionComplexity = calculateFunctionComplexity(node);
|
|
41276
|
+
if (functionComplexity > maxFunctionComplexity) {
|
|
41277
|
+
maxFunctionComplexity = functionComplexity;
|
|
41278
|
+
mostComplexFunctionName = getFunctionDisplayName(node);
|
|
41279
|
+
}
|
|
41280
|
+
}
|
|
41281
|
+
ts.forEachChild(node, visitNode);
|
|
41282
|
+
};
|
|
41283
|
+
visitNode(sourceFile);
|
|
41284
|
+
return {
|
|
41285
|
+
functionCount,
|
|
41286
|
+
maxFunctionComplexity,
|
|
41287
|
+
mostComplexFunctionName,
|
|
41288
|
+
};
|
|
41289
|
+
}
|
|
41290
|
+
/**
|
|
41291
|
+
* Determines whether a node counts as a named function or method for density checks.
|
|
41292
|
+
*
|
|
41293
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41294
|
+
*/
|
|
41295
|
+
function isCountedFunctionLikeDeclaration(node) {
|
|
41296
|
+
if (ts.isFunctionDeclaration(node) ||
|
|
41297
|
+
ts.isMethodDeclaration(node) ||
|
|
41298
|
+
ts.isConstructorDeclaration(node) ||
|
|
41299
|
+
ts.isGetAccessorDeclaration(node) ||
|
|
41300
|
+
ts.isSetAccessorDeclaration(node)) {
|
|
41301
|
+
return true;
|
|
41302
|
+
}
|
|
41303
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
41304
|
+
return isNamedFunctionExpression(node);
|
|
41305
|
+
}
|
|
41306
|
+
return false;
|
|
41307
|
+
}
|
|
41308
|
+
/**
|
|
41309
|
+
* Determines whether a function expression is attached to a named variable or property.
|
|
41310
|
+
*
|
|
41311
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41312
|
+
*/
|
|
41313
|
+
function isNamedFunctionExpression(node) {
|
|
41314
|
+
const parent = node.parent;
|
|
41315
|
+
return (ts.isVariableDeclaration(parent) || ts.isPropertyDeclaration(parent) || ts.isPropertyAssignment(parent));
|
|
41316
|
+
}
|
|
41317
|
+
/**
|
|
41318
|
+
* Calculates a lightweight cyclomatic-complexity score for one function.
|
|
41319
|
+
*
|
|
41320
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41321
|
+
*/
|
|
41322
|
+
function calculateFunctionComplexity(functionNode) {
|
|
41323
|
+
if (!functionNode.body) {
|
|
41324
|
+
return 1;
|
|
41325
|
+
}
|
|
41326
|
+
let complexity = 1;
|
|
41327
|
+
const visitNode = (node) => {
|
|
41328
|
+
if (node !== functionNode.body && isCountedFunctionLikeDeclaration(node)) {
|
|
41329
|
+
return;
|
|
41330
|
+
}
|
|
41331
|
+
if (isComplexityDecisionNode(node)) {
|
|
41332
|
+
complexity += 1;
|
|
41333
|
+
}
|
|
41334
|
+
ts.forEachChild(node, visitNode);
|
|
41335
|
+
};
|
|
41336
|
+
visitNode(functionNode.body);
|
|
41337
|
+
return complexity;
|
|
41338
|
+
}
|
|
41339
|
+
/**
|
|
41340
|
+
* Determines whether a node should increase the complexity score.
|
|
41341
|
+
*
|
|
41342
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41343
|
+
*/
|
|
41344
|
+
function isComplexityDecisionNode(node) {
|
|
41345
|
+
if (ts.isIfStatement(node) ||
|
|
41346
|
+
ts.isConditionalExpression(node) ||
|
|
41347
|
+
ts.isCatchClause(node) ||
|
|
41348
|
+
ts.isForStatement(node) ||
|
|
41349
|
+
ts.isForInStatement(node) ||
|
|
41350
|
+
ts.isForOfStatement(node) ||
|
|
41351
|
+
ts.isWhileStatement(node) ||
|
|
41352
|
+
ts.isDoStatement(node) ||
|
|
41353
|
+
ts.isCaseClause(node)) {
|
|
41354
|
+
return true;
|
|
41355
|
+
}
|
|
41356
|
+
if (ts.isBinaryExpression(node)) {
|
|
41357
|
+
const operatorKind = node.operatorToken.kind;
|
|
41358
|
+
return (operatorKind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
41359
|
+
operatorKind === ts.SyntaxKind.BarBarToken ||
|
|
41360
|
+
operatorKind === ts.SyntaxKind.QuestionQuestionToken);
|
|
41361
|
+
}
|
|
41362
|
+
return false;
|
|
41363
|
+
}
|
|
41364
|
+
/**
|
|
41365
|
+
* Resolves a readable display name for a counted function-like declaration.
|
|
41366
|
+
*
|
|
41367
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41368
|
+
*/
|
|
41369
|
+
function getFunctionDisplayName(functionNode) {
|
|
41370
|
+
if (ts.isConstructorDeclaration(functionNode)) {
|
|
41371
|
+
return 'constructor';
|
|
41372
|
+
}
|
|
41373
|
+
if (ts.isFunctionDeclaration(functionNode) ||
|
|
41374
|
+
ts.isMethodDeclaration(functionNode) ||
|
|
41375
|
+
ts.isGetAccessorDeclaration(functionNode) ||
|
|
41376
|
+
ts.isSetAccessorDeclaration(functionNode)) {
|
|
41377
|
+
if (!functionNode.name) {
|
|
41378
|
+
return null;
|
|
41379
|
+
}
|
|
41380
|
+
return getPropertyNameText(functionNode.name);
|
|
41381
|
+
}
|
|
41382
|
+
if (ts.isArrowFunction(functionNode) || ts.isFunctionExpression(functionNode)) {
|
|
41383
|
+
if (functionNode.name) {
|
|
41384
|
+
return functionNode.name.text;
|
|
41385
|
+
}
|
|
41386
|
+
const parent = functionNode.parent;
|
|
41387
|
+
if (ts.isVariableDeclaration(parent)) {
|
|
41388
|
+
return getBindingNameText(parent.name);
|
|
41389
|
+
}
|
|
41390
|
+
if (ts.isPropertyDeclaration(parent) || ts.isPropertyAssignment(parent)) {
|
|
41391
|
+
return getPropertyNameText(parent.name);
|
|
41392
|
+
}
|
|
41393
|
+
}
|
|
41394
|
+
return null;
|
|
41395
|
+
}
|
|
41396
|
+
/**
|
|
41397
|
+
* Resolves text for a binding name when it is a simple identifier.
|
|
41398
|
+
*
|
|
41399
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41400
|
+
*/
|
|
41401
|
+
function getBindingNameText(name) {
|
|
41402
|
+
return ts.isIdentifier(name) ? name.text : null;
|
|
41403
|
+
}
|
|
41404
|
+
/**
|
|
41405
|
+
* Resolves text for a property name while preserving computed names when necessary.
|
|
41406
|
+
*
|
|
41407
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41408
|
+
*/
|
|
41409
|
+
function getPropertyNameText(name) {
|
|
41410
|
+
if (ts.isIdentifier(name) || ts.isPrivateIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
|
41411
|
+
return name.text;
|
|
41412
|
+
}
|
|
41413
|
+
return name.getText();
|
|
41414
|
+
}
|
|
41415
|
+
/**
|
|
41416
|
+
* Formats the reason emitted when a function in the file exceeds the complexity threshold.
|
|
41417
|
+
*
|
|
41418
|
+
* @private function of analyzeSourceFileForRefactorCandidate
|
|
41419
|
+
*/
|
|
41420
|
+
function buildComplexityReason(structureSummary, maxAllowedFunctionComplexity) {
|
|
41421
|
+
const functionSuffix = structureSummary.mostComplexFunctionName
|
|
41422
|
+
? ` in \`${structureSummary.mostComplexFunctionName}\``
|
|
41423
|
+
: '';
|
|
41424
|
+
return `complexity ${structureSummary.maxFunctionComplexity}/${maxAllowedFunctionComplexity}${functionSuffix}`;
|
|
41425
|
+
}
|
|
40507
41426
|
/**
|
|
40508
41427
|
* Resolves the script kind for a source file extension.
|
|
40509
41428
|
*
|
|
@@ -40537,13 +41456,15 @@ function normalizeAbsolutePath$1(pathValue) {
|
|
|
40537
41456
|
*
|
|
40538
41457
|
* @private function of findRefactorCandidates
|
|
40539
41458
|
*/
|
|
40540
|
-
async function findRefactorCandidatesInProject(
|
|
41459
|
+
async function findRefactorCandidatesInProject(options) {
|
|
41460
|
+
const { heuristics, rootDir } = options;
|
|
40541
41461
|
const lineCountExemptPaths = await buildExemptPathSet(rootDir, LINE_COUNT_EXEMPT_GLOBS);
|
|
40542
41462
|
const sourceFiles = await listSourceFiles(rootDir);
|
|
40543
41463
|
const candidates = [];
|
|
40544
41464
|
for (const filePath of sourceFiles) {
|
|
40545
41465
|
const candidate = await analyzeSourceFileForRefactorCandidate({
|
|
40546
41466
|
filePath,
|
|
41467
|
+
heuristics,
|
|
40547
41468
|
lineCountExemptPaths,
|
|
40548
41469
|
rootDir,
|
|
40549
41470
|
});
|
|
@@ -40816,11 +41737,18 @@ function buildPromptGuidance(candidate) {
|
|
|
40816
41737
|
if (counts.entityCount !== null && counts.maxEntities !== null) {
|
|
40817
41738
|
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.`);
|
|
40818
41739
|
}
|
|
41740
|
+
if (counts.functionCount !== null && counts.maxFunctions !== null) {
|
|
41741
|
+
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.`);
|
|
41742
|
+
}
|
|
41743
|
+
if (counts.functionComplexity !== null && counts.maxFunctionComplexity !== null) {
|
|
41744
|
+
const functionSuffix = counts.mostComplexFunctionName ? ` in \`${counts.mostComplexFunctionName}\`` : '';
|
|
41745
|
+
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.`);
|
|
41746
|
+
}
|
|
40819
41747
|
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.');
|
|
40820
41748
|
return guidance;
|
|
40821
41749
|
}
|
|
40822
41750
|
/**
|
|
40823
|
-
* Extracts
|
|
41751
|
+
* Extracts structural counts from refactor reasons.
|
|
40824
41752
|
*
|
|
40825
41753
|
* @private function of buildPromptContent
|
|
40826
41754
|
*/
|
|
@@ -40829,6 +41757,11 @@ function extractReasonCounts(reasons) {
|
|
|
40829
41757
|
let maxLines = null;
|
|
40830
41758
|
let entityCount = null;
|
|
40831
41759
|
let maxEntities = null;
|
|
41760
|
+
let functionCount = null;
|
|
41761
|
+
let maxFunctions = null;
|
|
41762
|
+
let functionComplexity = null;
|
|
41763
|
+
let maxFunctionComplexity = null;
|
|
41764
|
+
let mostComplexFunctionName = null;
|
|
40832
41765
|
for (const reason of reasons) {
|
|
40833
41766
|
const lineMatch = reason.match(/lines\s+(?<count>\d+)\/(?<max>\d+)/i);
|
|
40834
41767
|
if (lineMatch === null || lineMatch === void 0 ? void 0 : lineMatch.groups) {
|
|
@@ -40840,6 +41773,19 @@ function extractReasonCounts(reasons) {
|
|
|
40840
41773
|
if (entityMatch === null || entityMatch === void 0 ? void 0 : entityMatch.groups) {
|
|
40841
41774
|
entityCount = Number(entityMatch.groups.count);
|
|
40842
41775
|
maxEntities = Number(entityMatch.groups.max);
|
|
41776
|
+
continue;
|
|
41777
|
+
}
|
|
41778
|
+
const functionMatch = reason.match(/functions\s+(?<count>\d+)\/(?<max>\d+)/i);
|
|
41779
|
+
if (functionMatch === null || functionMatch === void 0 ? void 0 : functionMatch.groups) {
|
|
41780
|
+
functionCount = Number(functionMatch.groups.count);
|
|
41781
|
+
maxFunctions = Number(functionMatch.groups.max);
|
|
41782
|
+
continue;
|
|
41783
|
+
}
|
|
41784
|
+
const complexityMatch = reason.match(/complexity\s+(?<count>\d+)\/(?<max>\d+)(?:\s+in\s+`(?<functionName>[^`]+)`)?/i);
|
|
41785
|
+
if (complexityMatch === null || complexityMatch === void 0 ? void 0 : complexityMatch.groups) {
|
|
41786
|
+
functionComplexity = Number(complexityMatch.groups.count);
|
|
41787
|
+
maxFunctionComplexity = Number(complexityMatch.groups.max);
|
|
41788
|
+
mostComplexFunctionName = complexityMatch.groups.functionName || null;
|
|
40843
41789
|
}
|
|
40844
41790
|
}
|
|
40845
41791
|
return {
|
|
@@ -40847,6 +41793,11 @@ function extractReasonCounts(reasons) {
|
|
|
40847
41793
|
maxLines,
|
|
40848
41794
|
entityCount,
|
|
40849
41795
|
maxEntities,
|
|
41796
|
+
functionCount,
|
|
41797
|
+
maxFunctions,
|
|
41798
|
+
functionComplexity,
|
|
41799
|
+
maxFunctionComplexity,
|
|
41800
|
+
mostComplexFunctionName,
|
|
40850
41801
|
};
|
|
40851
41802
|
}
|
|
40852
41803
|
/**
|
|
@@ -40855,14 +41806,23 @@ function extractReasonCounts(reasons) {
|
|
|
40855
41806
|
* @private function of buildPromptContent
|
|
40856
41807
|
*/
|
|
40857
41808
|
function buildDensityNote(counts) {
|
|
40858
|
-
|
|
40859
|
-
|
|
41809
|
+
const activeSignalsCount = [
|
|
41810
|
+
counts.lineCount !== null,
|
|
41811
|
+
counts.entityCount !== null,
|
|
41812
|
+
counts.functionCount !== null,
|
|
41813
|
+
counts.functionComplexity !== null,
|
|
41814
|
+
].filter(Boolean).length;
|
|
41815
|
+
if (activeSignalsCount > 1) {
|
|
41816
|
+
return 'The file mixes multiple concerns and dense logic, making it harder to follow.';
|
|
40860
41817
|
}
|
|
40861
41818
|
if (counts.lineCount !== null) {
|
|
40862
41819
|
return 'The file is large enough that it is hard to follow.';
|
|
40863
41820
|
}
|
|
40864
|
-
if (counts.entityCount !== null) {
|
|
40865
|
-
return 'The file
|
|
41821
|
+
if (counts.entityCount !== null || counts.functionCount !== null) {
|
|
41822
|
+
return 'The file packs too many responsibilities into one place.';
|
|
41823
|
+
}
|
|
41824
|
+
if (counts.functionComplexity !== null) {
|
|
41825
|
+
return 'The file contains logic that is too complex to follow comfortably.';
|
|
40866
41826
|
}
|
|
40867
41827
|
return null;
|
|
40868
41828
|
}
|
|
@@ -40956,13 +41916,19 @@ function initializeFindRefactorCandidatesRun() {
|
|
|
40956
41916
|
*
|
|
40957
41917
|
* @public exported from `@promptbook/cli`
|
|
40958
41918
|
*/
|
|
40959
|
-
async function findRefactorCandidates() {
|
|
41919
|
+
async function findRefactorCandidates(options = {}) {
|
|
41920
|
+
const { level = DEFAULT_REFACTOR_CANDIDATE_LEVEL } = options;
|
|
41921
|
+
const heuristics = getRefactorCandidateLevelConfiguration(level);
|
|
40960
41922
|
initializeFindRefactorCandidatesRun();
|
|
40961
|
-
console.info(colors.cyan('
|
|
41923
|
+
console.info(colors.cyan('⚡🏭 Find refactor candidates'));
|
|
41924
|
+
console.info(colors.gray(`Using \`${level}\` scan level.`));
|
|
40962
41925
|
const rootDir = process.cwd();
|
|
40963
41926
|
const promptsDir = join(rootDir, PROMPTS_DIR_NAME);
|
|
40964
41927
|
const existingTargets = await loadExistingPromptTargets(promptsDir);
|
|
40965
|
-
const candidates = await findRefactorCandidatesInProject(
|
|
41928
|
+
const candidates = await findRefactorCandidatesInProject({
|
|
41929
|
+
heuristics,
|
|
41930
|
+
rootDir,
|
|
41931
|
+
});
|
|
40966
41932
|
if (candidates.length === 0) {
|
|
40967
41933
|
console.info(colors.green('No refactor candidates found.'));
|
|
40968
41934
|
return;
|
|
@@ -44048,11 +45014,11 @@ function buildGitHubCopilotScript(options) {
|
|
|
44048
45014
|
${block(options.prompt)}
|
|
44049
45015
|
|
|
44050
45016
|
${delimiter}
|
|
44051
|
-
)"
|
|
44052
|
-
--yolo
|
|
44053
|
-
--no-ask-user
|
|
44054
|
-
--no-color
|
|
44055
|
-
--output-format json
|
|
45017
|
+
)" \\
|
|
45018
|
+
--yolo \\
|
|
45019
|
+
--no-ask-user \\
|
|
45020
|
+
--no-color \\
|
|
45021
|
+
--output-format json \\
|
|
44056
45022
|
--stream off${modelArgument}${thinkingLevelArgument}
|
|
44057
45023
|
`);
|
|
44058
45024
|
}
|