@promptbook/cli 0.112.0-38 → 0.112.0-40
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 +1 -1
- package/esm/index.es.js +159 -43
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/find-refactor-candidates/RefactorCandidateLevel.d.ts +5 -1
- package/esm/scripts/find-refactor-candidates/findRefactorCandidatesInProject.d.ts +5 -0
- package/esm/scripts/find-refactor-candidates/resolveRefactorCandidateProject.d.ts +23 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -1
- package/umd/index.umd.js +163 -47
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/find-refactor-candidates/RefactorCandidateLevel.d.ts +5 -1
- package/umd/scripts/find-refactor-candidates/findRefactorCandidatesInProject.d.ts +5 -0
- package/umd/scripts/find-refactor-candidates/resolveRefactorCandidateProject.d.ts +23 -0
- package/umd/src/version.d.ts +1 -1
package/README.md
CHANGED
|
@@ -602,7 +602,7 @@ npx ptbk coder verify
|
|
|
602
602
|
| `ptbk coder init` | Creates `prompts/`, `prompts/done/`, the project-generic template files materialized in `prompts/templates/` (currently `common.md`), and a starter `AGENTS.md`; ensures `.env` contains `CODING_AGENT_GIT_NAME`, `CODING_AGENT_GIT_EMAIL`, and `CODING_AGENT_GIT_SIGNING_KEY`; adds helper coder scripts to `package.json`; ensures `.gitignore` contains `/.tmp`; and configures `.vscode/settings.json` to save pasted prompt images into `prompts/screenshots/`. |
|
|
603
603
|
| `ptbk coder generate-boilerplates` | Creates new prompt markdown files with fresh emoji tags so you can quickly fill in coding tasks; `--template` accepts either a built-in alias or a markdown file path relative to the project root. |
|
|
604
604
|
| `ptbk coder run` | Picks the next ready prompt, appends optional context, runs it through the selected coding agent, marks success or failure, then commits and pushes the result. |
|
|
605
|
-
| `ptbk coder find-refactor-candidates` | Scans the repository for oversized or overpacked files and writes prompt files for likely refactors; `--level <low|medium|high|xhigh>`
|
|
605
|
+
| `ptbk coder find-refactor-candidates` | Scans the repository for oversized or overpacked files and writes prompt files for likely refactors; `--level <xlow|low|medium|high|xhigh|extreme>` ranges from a very benevolent scan to a very aggressive sweep. |
|
|
606
606
|
| `ptbk coder verify` | Walks through completed prompts, archives truly finished work, and adds follow-up repair prompts for unfinished results. |
|
|
607
607
|
|
|
608
608
|
#### Most useful `ptbk coder run` flags
|
package/esm/index.es.js
CHANGED
|
@@ -31,6 +31,7 @@ import Anthropic from '@anthropic-ai/sdk';
|
|
|
31
31
|
import Bottleneck from 'bottleneck';
|
|
32
32
|
import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
|
|
33
33
|
import * as ts from 'typescript';
|
|
34
|
+
import ignore from 'ignore';
|
|
34
35
|
import * as readline from 'readline';
|
|
35
36
|
import { cursorTo, clearLine, createInterface } from 'readline';
|
|
36
37
|
import { spawn } from 'child_process';
|
|
@@ -57,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
57
58
|
* @generated
|
|
58
59
|
* @see https://github.com/webgptorg/promptbook
|
|
59
60
|
*/
|
|
60
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-
|
|
61
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.112.0-40';
|
|
61
62
|
/**
|
|
62
63
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
63
64
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -1732,7 +1733,7 @@ const PROMPT_SLUG_MAX_LENGTH = 80;
|
|
|
1732
1733
|
/**
|
|
1733
1734
|
* Supported aggressiveness levels for refactor-candidate scanning.
|
|
1734
1735
|
*/
|
|
1735
|
-
const REFACTOR_CANDIDATE_LEVEL_VALUES = ['low', 'medium', 'high', 'xhigh'];
|
|
1736
|
+
const REFACTOR_CANDIDATE_LEVEL_VALUES = ['xlow', 'low', 'medium', 'high', 'xhigh', 'extreme'];
|
|
1736
1737
|
/**
|
|
1737
1738
|
* Default aggressiveness level for refactor-candidate scanning.
|
|
1738
1739
|
*/
|
|
@@ -1740,37 +1741,76 @@ const DEFAULT_REFACTOR_CANDIDATE_LEVEL = 'medium';
|
|
|
1740
1741
|
/**
|
|
1741
1742
|
* Threshold table for each supported refactor-candidate scanning level.
|
|
1742
1743
|
*/
|
|
1743
|
-
const
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1744
|
+
const REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL = {
|
|
1745
|
+
xlow: createRefactorCandidateLevelDetails({
|
|
1746
|
+
description: 'Extremely benevolent scan that flags only very obvious refactor targets.',
|
|
1747
|
+
maxLineCount: 9600,
|
|
1748
|
+
maxEntityCountPerFile: 72,
|
|
1749
|
+
maxFunctionCountPerFile: 48,
|
|
1750
|
+
maxFunctionComplexity: 40,
|
|
1749
1751
|
}),
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1752
|
+
low: createRefactorCandidateLevelDetails({
|
|
1753
|
+
description: 'Conservative scan for only the most obvious refactor targets.',
|
|
1754
|
+
maxLineCount: 3600,
|
|
1755
|
+
maxEntityCountPerFile: 30,
|
|
1756
|
+
maxFunctionCountPerFile: 20,
|
|
1757
|
+
maxFunctionComplexity: 24,
|
|
1755
1758
|
}),
|
|
1756
|
-
|
|
1757
|
-
|
|
1759
|
+
medium: createRefactorCandidateLevelDetails({
|
|
1760
|
+
description: 'Default scan using the current standard thresholds.',
|
|
1761
|
+
maxLineCount: 1800,
|
|
1758
1762
|
maxEntityCountPerFile: 16,
|
|
1759
|
-
maxFunctionCountPerFile:
|
|
1760
|
-
maxFunctionComplexity:
|
|
1763
|
+
maxFunctionCountPerFile: 12,
|
|
1764
|
+
maxFunctionComplexity: 16,
|
|
1761
1765
|
}),
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1766
|
+
high: createRefactorCandidateLevelDetails({
|
|
1767
|
+
description: 'Strict scan that finds more crowded or complex files.',
|
|
1768
|
+
maxLineCount: 900,
|
|
1769
|
+
maxEntityCountPerFile: 9,
|
|
1765
1770
|
maxFunctionCountPerFile: 8,
|
|
1766
|
-
maxFunctionComplexity:
|
|
1771
|
+
maxFunctionComplexity: 11,
|
|
1772
|
+
}),
|
|
1773
|
+
xhigh: createRefactorCandidateLevelDetails({
|
|
1774
|
+
description: 'Very strict scan for denser and more complex candidates.',
|
|
1775
|
+
maxLineCount: 450,
|
|
1776
|
+
maxEntityCountPerFile: 5,
|
|
1777
|
+
maxFunctionCountPerFile: 5,
|
|
1778
|
+
maxFunctionComplexity: 7,
|
|
1779
|
+
}),
|
|
1780
|
+
extreme: createRefactorCandidateLevelDetails({
|
|
1781
|
+
description: 'Most aggressive scan that surfaces even weak refactor opportunities.',
|
|
1782
|
+
maxLineCount: 180,
|
|
1783
|
+
maxEntityCountPerFile: 2,
|
|
1784
|
+
maxFunctionCountPerFile: 2,
|
|
1785
|
+
maxFunctionComplexity: 4,
|
|
1767
1786
|
}),
|
|
1768
1787
|
};
|
|
1769
1788
|
/**
|
|
1770
1789
|
* Resolves the thresholds for a selected refactor-candidate scanning level.
|
|
1771
1790
|
*/
|
|
1772
1791
|
function getRefactorCandidateLevelConfiguration(level = DEFAULT_REFACTOR_CANDIDATE_LEVEL) {
|
|
1773
|
-
return
|
|
1792
|
+
return REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL[level].configuration;
|
|
1793
|
+
}
|
|
1794
|
+
/**
|
|
1795
|
+
* Resolves the user-facing description for a selected refactor-candidate scanning level.
|
|
1796
|
+
*/
|
|
1797
|
+
function getRefactorCandidateLevelDescription(level) {
|
|
1798
|
+
return REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL[level].description;
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Builds one normalized refactor-candidate level entry.
|
|
1802
|
+
*/
|
|
1803
|
+
function createRefactorCandidateLevelDetails(options) {
|
|
1804
|
+
const { description, maxLineCount, maxEntityCountPerFile, maxFunctionCountPerFile, maxFunctionComplexity } = options;
|
|
1805
|
+
return {
|
|
1806
|
+
description,
|
|
1807
|
+
configuration: createRefactorCandidateLevelConfiguration({
|
|
1808
|
+
maxLineCount,
|
|
1809
|
+
maxEntityCountPerFile,
|
|
1810
|
+
maxFunctionCountPerFile,
|
|
1811
|
+
maxFunctionComplexity,
|
|
1812
|
+
}),
|
|
1813
|
+
};
|
|
1774
1814
|
}
|
|
1775
1815
|
/**
|
|
1776
1816
|
* Builds one normalized refactor-candidate level configuration entry.
|
|
@@ -1806,17 +1846,14 @@ function createLineCountLimits(maxLineCount) {
|
|
|
1806
1846
|
*/
|
|
1807
1847
|
function $initializeCoderFindRefactorCandidatesCommand(program) {
|
|
1808
1848
|
const command = program.command('find-refactor-candidates');
|
|
1809
|
-
command.description(
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
Generates refactor prompts with guidance for identified candidates.
|
|
1819
|
-
`));
|
|
1849
|
+
command.description([
|
|
1850
|
+
'Scan source files to identify refactoring candidates',
|
|
1851
|
+
'',
|
|
1852
|
+
'Levels:',
|
|
1853
|
+
...REFACTOR_CANDIDATE_LEVEL_VALUES.map((level) => `- ${level}: ${getRefactorCandidateLevelDescription(level)}`),
|
|
1854
|
+
'',
|
|
1855
|
+
'Generates refactor prompts with guidance for identified candidates.',
|
|
1856
|
+
].join('\n'));
|
|
1820
1857
|
command.addOption(new Option('--level <level>', `Set scan aggressiveness (${REFACTOR_CANDIDATE_LEVEL_VALUES.join(', ')})`)
|
|
1821
1858
|
.choices([...REFACTOR_CANDIDATE_LEVEL_VALUES])
|
|
1822
1859
|
.default(DEFAULT_REFACTOR_CANDIDATE_LEVEL));
|
|
@@ -1944,7 +1981,7 @@ async function ensureDefaultCoderPromptTemplateFiles(projectPath) {
|
|
|
1944
1981
|
const ensuredTemplateFiles = [];
|
|
1945
1982
|
for (const definition of DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS) {
|
|
1946
1983
|
const absoluteTemplatePath = join(projectPath, definition.relativeFilePath);
|
|
1947
|
-
if (await isExistingFile$
|
|
1984
|
+
if (await isExistingFile$2(absoluteTemplatePath)) {
|
|
1948
1985
|
ensuredTemplateFiles.push({
|
|
1949
1986
|
id: definition.id,
|
|
1950
1987
|
relativeFilePath: definition.relativeFilePath,
|
|
@@ -2058,7 +2095,7 @@ function isNodeJsErrorWithCode(error, code) {
|
|
|
2058
2095
|
/**
|
|
2059
2096
|
* Checks whether a path exists and is a file.
|
|
2060
2097
|
*/
|
|
2061
|
-
async function isExistingFile$
|
|
2098
|
+
async function isExistingFile$2(path) {
|
|
2062
2099
|
try {
|
|
2063
2100
|
return (await stat(path)).isFile();
|
|
2064
2101
|
}
|
|
@@ -2492,7 +2529,7 @@ function hasTmpGitignoreRule(gitignoreContent) {
|
|
|
2492
2529
|
*/
|
|
2493
2530
|
async function ensureCoderMarkdownFile(projectPath, relativeFilePath, fileContent) {
|
|
2494
2531
|
const absoluteFilePath = join(projectPath, relativeFilePath);
|
|
2495
|
-
if (await isExistingFile(absoluteFilePath)) {
|
|
2532
|
+
if (await isExistingFile$1(absoluteFilePath)) {
|
|
2496
2533
|
return 'unchanged';
|
|
2497
2534
|
}
|
|
2498
2535
|
await writeFile(absoluteFilePath, `${fileContent}\n`, 'utf-8');
|
|
@@ -2501,7 +2538,7 @@ async function ensureCoderMarkdownFile(projectPath, relativeFilePath, fileConten
|
|
|
2501
2538
|
/**
|
|
2502
2539
|
* Checks whether a path exists and is a file.
|
|
2503
2540
|
*/
|
|
2504
|
-
async function isExistingFile(path) {
|
|
2541
|
+
async function isExistingFile$1(path) {
|
|
2505
2542
|
try {
|
|
2506
2543
|
return (await stat(path)).isFile();
|
|
2507
2544
|
}
|
|
@@ -41457,9 +41494,9 @@ function normalizeAbsolutePath$1(pathValue) {
|
|
|
41457
41494
|
* @private function of findRefactorCandidates
|
|
41458
41495
|
*/
|
|
41459
41496
|
async function findRefactorCandidatesInProject(options) {
|
|
41460
|
-
const { heuristics, rootDir } = options;
|
|
41461
|
-
const lineCountExemptPaths = await buildExemptPathSet(rootDir, LINE_COUNT_EXEMPT_GLOBS);
|
|
41462
|
-
const sourceFiles = await listSourceFiles(rootDir);
|
|
41497
|
+
const { heuristics, isIgnoredRelativePath = () => false, rootDir } = options;
|
|
41498
|
+
const lineCountExemptPaths = await buildExemptPathSet(rootDir, LINE_COUNT_EXEMPT_GLOBS, isIgnoredRelativePath);
|
|
41499
|
+
const sourceFiles = await listSourceFiles(rootDir, isIgnoredRelativePath);
|
|
41463
41500
|
const candidates = [];
|
|
41464
41501
|
for (const filePath of sourceFiles) {
|
|
41465
41502
|
const candidate = await analyzeSourceFileForRefactorCandidate({
|
|
@@ -41479,7 +41516,7 @@ async function findRefactorCandidatesInProject(options) {
|
|
|
41479
41516
|
*
|
|
41480
41517
|
* @private function of findRefactorCandidatesInProject
|
|
41481
41518
|
*/
|
|
41482
|
-
async function listSourceFiles(rootDir) {
|
|
41519
|
+
async function listSourceFiles(rootDir, isIgnoredRelativePath) {
|
|
41483
41520
|
const extensions = SOURCE_FILE_EXTENSIONS.map((extension) => extension.replace(/^\./, '')).join(',');
|
|
41484
41521
|
const extensionGlob = `{${extensions}}`;
|
|
41485
41522
|
const patterns = [...SOURCE_ROOTS.map((root) => `${root}/**/*.${extensionGlob}`), `*.${extensionGlob}`];
|
|
@@ -41492,6 +41529,9 @@ async function listSourceFiles(rootDir) {
|
|
|
41492
41529
|
absolute: true,
|
|
41493
41530
|
});
|
|
41494
41531
|
for (const match of matches) {
|
|
41532
|
+
if (shouldIgnoreAbsolutePath(rootDir, match, isIgnoredRelativePath)) {
|
|
41533
|
+
continue;
|
|
41534
|
+
}
|
|
41495
41535
|
files.add(match);
|
|
41496
41536
|
}
|
|
41497
41537
|
}
|
|
@@ -41502,7 +41542,7 @@ async function listSourceFiles(rootDir) {
|
|
|
41502
41542
|
*
|
|
41503
41543
|
* @private function of findRefactorCandidatesInProject
|
|
41504
41544
|
*/
|
|
41505
|
-
async function buildExemptPathSet(rootDir, patterns) {
|
|
41545
|
+
async function buildExemptPathSet(rootDir, patterns, isIgnoredRelativePath) {
|
|
41506
41546
|
const exemptPaths = new Set();
|
|
41507
41547
|
for (const pattern of patterns) {
|
|
41508
41548
|
const matches = await glob(pattern, {
|
|
@@ -41512,11 +41552,23 @@ async function buildExemptPathSet(rootDir, patterns) {
|
|
|
41512
41552
|
absolute: true,
|
|
41513
41553
|
});
|
|
41514
41554
|
for (const match of matches) {
|
|
41555
|
+
if (shouldIgnoreAbsolutePath(rootDir, match, isIgnoredRelativePath)) {
|
|
41556
|
+
continue;
|
|
41557
|
+
}
|
|
41515
41558
|
exemptPaths.add(normalizeAbsolutePath(match));
|
|
41516
41559
|
}
|
|
41517
41560
|
}
|
|
41518
41561
|
return exemptPaths;
|
|
41519
41562
|
}
|
|
41563
|
+
/**
|
|
41564
|
+
* Resolves whether an absolute path falls under the project `.gitignore` rules.
|
|
41565
|
+
*
|
|
41566
|
+
* @private function of findRefactorCandidatesInProject
|
|
41567
|
+
*/
|
|
41568
|
+
function shouldIgnoreAbsolutePath(rootDir, absolutePath, isIgnoredRelativePath) {
|
|
41569
|
+
const relativePath = normalizeRefactorCandidatePath(relative(rootDir, absolutePath));
|
|
41570
|
+
return isIgnoredRelativePath(relativePath);
|
|
41571
|
+
}
|
|
41520
41572
|
/**
|
|
41521
41573
|
* Normalizes an absolute path for consistent comparisons.
|
|
41522
41574
|
*
|
|
@@ -41565,6 +41617,69 @@ function escapeRegExp(value) {
|
|
|
41565
41617
|
}
|
|
41566
41618
|
// Note: [🟡] Code for repository script [loadExistingPromptTargets](scripts/find-refactor-candidates/loadExistingPromptTargets.ts) should never be published outside of `@promptbook/cli`
|
|
41567
41619
|
|
|
41620
|
+
/**
|
|
41621
|
+
* Filename used to discover the project root for refactor-candidate scanning.
|
|
41622
|
+
*/
|
|
41623
|
+
const GITIGNORE_FILE_NAME = '.gitignore';
|
|
41624
|
+
/**
|
|
41625
|
+
* Resolves the project root and `.gitignore` matcher for refactor-candidate scanning.
|
|
41626
|
+
*
|
|
41627
|
+
* @private function of findRefactorCandidates
|
|
41628
|
+
*/
|
|
41629
|
+
async function resolveRefactorCandidateProject(startDir) {
|
|
41630
|
+
const absoluteStartDir = resolve(startDir);
|
|
41631
|
+
const gitignorePath = await findNearestGitignorePath(absoluteStartDir);
|
|
41632
|
+
if (!gitignorePath) {
|
|
41633
|
+
return {
|
|
41634
|
+
rootDir: absoluteStartDir,
|
|
41635
|
+
isIgnoredRelativePath: () => false,
|
|
41636
|
+
};
|
|
41637
|
+
}
|
|
41638
|
+
const rootDir = dirname(gitignorePath);
|
|
41639
|
+
const gitignoreMatcher = ignore().add(await readFile(gitignorePath, 'utf-8'));
|
|
41640
|
+
return {
|
|
41641
|
+
rootDir,
|
|
41642
|
+
isIgnoredRelativePath(relativePath) {
|
|
41643
|
+
return gitignoreMatcher.ignores(normalizeRefactorCandidatePath(relativePath));
|
|
41644
|
+
},
|
|
41645
|
+
};
|
|
41646
|
+
}
|
|
41647
|
+
/**
|
|
41648
|
+
* Finds the nearest ancestor `.gitignore` so scans work from any project subdirectory.
|
|
41649
|
+
*
|
|
41650
|
+
* @private function of resolveRefactorCandidateProject
|
|
41651
|
+
*/
|
|
41652
|
+
async function findNearestGitignorePath(startDir) {
|
|
41653
|
+
let currentDir = resolve(startDir);
|
|
41654
|
+
while (true) {
|
|
41655
|
+
const gitignorePath = join(currentDir, GITIGNORE_FILE_NAME);
|
|
41656
|
+
if (await isExistingFile(gitignorePath)) {
|
|
41657
|
+
return gitignorePath;
|
|
41658
|
+
}
|
|
41659
|
+
const parentDir = dirname(currentDir);
|
|
41660
|
+
if (parentDir === currentDir) {
|
|
41661
|
+
return null;
|
|
41662
|
+
}
|
|
41663
|
+
currentDir = parentDir;
|
|
41664
|
+
}
|
|
41665
|
+
}
|
|
41666
|
+
/**
|
|
41667
|
+
* Detects whether a file exists without swallowing unexpected filesystem failures.
|
|
41668
|
+
*
|
|
41669
|
+
* @private function of resolveRefactorCandidateProject
|
|
41670
|
+
*/
|
|
41671
|
+
async function isExistingFile(filePath) {
|
|
41672
|
+
var _a;
|
|
41673
|
+
const fileStats = await stat(filePath).catch((error) => {
|
|
41674
|
+
if (error.code === 'ENOENT') {
|
|
41675
|
+
return undefined;
|
|
41676
|
+
}
|
|
41677
|
+
throw error;
|
|
41678
|
+
});
|
|
41679
|
+
return (_a = fileStats === null || fileStats === void 0 ? void 0 : fileStats.isFile()) !== null && _a !== void 0 ? _a : false;
|
|
41680
|
+
}
|
|
41681
|
+
// Note: [🟡] Code for repository script [resolveRefactorCandidateProject](scripts/find-refactor-candidates/resolveRefactorCandidateProject.ts) should never be published outside of `@promptbook/cli`
|
|
41682
|
+
|
|
41568
41683
|
/**
|
|
41569
41684
|
* Calculates the next available prompt numbering sequence for a month.
|
|
41570
41685
|
*/
|
|
@@ -41922,11 +42037,12 @@ async function findRefactorCandidates(options = {}) {
|
|
|
41922
42037
|
initializeFindRefactorCandidatesRun();
|
|
41923
42038
|
console.info(colors.cyan('⚡🏭 Find refactor candidates'));
|
|
41924
42039
|
console.info(colors.gray(`Using \`${level}\` scan level.`));
|
|
41925
|
-
const rootDir = process.cwd();
|
|
42040
|
+
const { isIgnoredRelativePath, rootDir } = await resolveRefactorCandidateProject(process.cwd());
|
|
41926
42041
|
const promptsDir = join(rootDir, PROMPTS_DIR_NAME);
|
|
41927
42042
|
const existingTargets = await loadExistingPromptTargets(promptsDir);
|
|
41928
42043
|
const candidates = await findRefactorCandidatesInProject({
|
|
41929
42044
|
heuristics,
|
|
42045
|
+
isIgnoredRelativePath,
|
|
41930
42046
|
rootDir,
|
|
41931
42047
|
});
|
|
41932
42048
|
if (candidates.length === 0) {
|