@promptbook/cli 0.112.0-38 → 0.112.0-39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Supported aggressiveness levels for refactor-candidate scanning.
3
3
  */
4
- export declare const REFACTOR_CANDIDATE_LEVEL_VALUES: readonly ["low", "medium", "high", "xhigh"];
4
+ export declare const REFACTOR_CANDIDATE_LEVEL_VALUES: readonly ["xlow", "low", "medium", "high", "xhigh", "extreme"];
5
5
  /**
6
6
  * Supported aggressiveness levels for refactor-candidate scanning.
7
7
  */
@@ -39,3 +39,7 @@ export declare const DEFAULT_REFACTOR_CANDIDATE_LEVEL: RefactorCandidateLevel;
39
39
  * Resolves the thresholds for a selected refactor-candidate scanning level.
40
40
  */
41
41
  export declare function getRefactorCandidateLevelConfiguration(level?: RefactorCandidateLevel): RefactorCandidateLevelConfiguration;
42
+ /**
43
+ * Resolves the user-facing description for a selected refactor-candidate scanning level.
44
+ */
45
+ export declare function getRefactorCandidateLevelDescription(level: RefactorCandidateLevel): string;
@@ -1,5 +1,6 @@
1
1
  import type { RefactorCandidate } from './RefactorCandidate';
2
2
  import type { RefactorCandidateLevelConfiguration } from './RefactorCandidateLevel';
3
+ import type { IsIgnoredRelativePath } from './resolveRefactorCandidateProject';
3
4
  /**
4
5
  * Input required to scan one project for refactor candidates.
5
6
  *
@@ -14,6 +15,10 @@ type FindRefactorCandidatesInProjectOptions = {
14
15
  * Thresholds used to score files.
15
16
  */
16
17
  readonly heuristics: RefactorCandidateLevelConfiguration;
18
+ /**
19
+ * Matcher for project-relative paths that should be skipped because they are matched by `.gitignore`.
20
+ */
21
+ readonly isIgnoredRelativePath?: IsIgnoredRelativePath;
17
22
  };
18
23
  /**
19
24
  * Scans the repository and returns all files that qualify as refactor candidates.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Project-relative path matcher used to skip paths matched by `.gitignore`.
3
+ */
4
+ export type IsIgnoredRelativePath = (relativePath: string) => boolean;
5
+ /**
6
+ * Project metadata resolved before scanning for refactor candidates.
7
+ */
8
+ export type ResolvedRefactorCandidateProject = {
9
+ /**
10
+ * Absolute directory used as the scan root.
11
+ */
12
+ readonly rootDir: string;
13
+ /**
14
+ * Matcher used to skip project-relative paths covered by `.gitignore`.
15
+ */
16
+ readonly isIgnoredRelativePath: IsIgnoredRelativePath;
17
+ };
18
+ /**
19
+ * Resolves the project root and `.gitignore` matcher for refactor-candidate scanning.
20
+ *
21
+ * @private function of findRefactorCandidates
22
+ */
23
+ export declare function resolveRefactorCandidateProject(startDir: string): Promise<ResolvedRefactorCandidateProject>;
@@ -15,7 +15,7 @@ export declare const BOOK_LANGUAGE_VERSION: string_semantic_version;
15
15
  export declare const PROMPTBOOK_ENGINE_VERSION: string_promptbook_version;
16
16
  /**
17
17
  * Represents the version string of the Promptbook engine.
18
- * It follows semantic versioning (e.g., `0.112.0-37`).
18
+ * It follows semantic versioning (e.g., `0.112.0-38`).
19
19
  *
20
20
  * @generated
21
21
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@promptbook/cli",
3
- "version": "0.112.0-38",
3
+ "version": "0.112.0-39",
4
4
  "description": "Promptbook: Turn your company's scattered knowledge into AI ready books",
5
5
  "private": false,
6
6
  "sideEffects": false,
@@ -111,6 +111,7 @@
111
111
  "express": "4.21.2",
112
112
  "express-openapi-validator": "5.4.9",
113
113
  "glob-promise": "6.0.7",
114
+ "ignore": "^5.3.2",
114
115
  "jsdom": "25.0.1",
115
116
  "jszip": "3.10.1",
116
117
  "markitdown-ts": "0.0.10",
package/umd/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('fs'), require('path'), require('fs/promises'), require('waitasecond'), require('prompts'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('socket.io-client'), require('crypto-js'), require('jszip'), require('crypto'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('glob-promise'), require('moment'), require('express'), require('express-openapi-validator'), require('http'), require('socket.io'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('typescript'), require('readline'), require('child_process'), require('pg'), require('@supabase/supabase-js'), require('path/posix'), require('rxjs'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('openai')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'fs', 'path', 'fs/promises', 'waitasecond', 'prompts', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'socket.io-client', 'crypto-js', 'jszip', 'crypto', '@mozilla/readability', 'jsdom', 'showdown', 'glob-promise', 'moment', 'express', 'express-openapi-validator', 'http', 'socket.io', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'typescript', 'readline', 'child_process', 'pg', '@supabase/supabase-js', 'path/posix', 'rxjs', 'mime-types', 'papaparse', '@openai/agents', 'openai'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global._spaceTrim, global.fs, global.path, global.promises, global.waitasecond, global.prompts, global.dotenv, global.hexEncoder, global.sha256, global.socket_ioClient, global.cryptoJs, global.JSZip, global.crypto, global.readability, global.jsdom, global.showdown, global.glob, global.moment, global.express, global.OpenApiValidator, global.http, global.socket_io, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.ts, global.readline, global.child_process, global.pg, null, global.posix, global.rxjs, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
5
- })(this, (function (exports, colors, commander, _spaceTrim, fs, path, promises, waitasecond, prompts, dotenv, hexEncoder, sha256, socket_ioClient, cryptoJs, JSZip, crypto, readability, jsdom, showdown, glob, moment, express, OpenApiValidator, http, socket_io, swaggerUi, react, server, Anthropic, Bottleneck, openai, ts, readline, child_process, pg, supabaseJs, posix, rxjs, mimeTypes, papaparse, agents, OpenAI) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('fs'), require('path'), require('fs/promises'), require('waitasecond'), require('prompts'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('socket.io-client'), require('crypto-js'), require('jszip'), require('crypto'), require('@mozilla/readability'), require('jsdom'), require('showdown'), require('glob-promise'), require('moment'), require('express'), require('express-openapi-validator'), require('http'), require('socket.io'), require('swagger-ui-express'), require('react'), require('react-dom/server'), require('@anthropic-ai/sdk'), require('bottleneck'), require('@azure/openai'), require('typescript'), require('ignore'), require('readline'), require('child_process'), require('pg'), require('@supabase/supabase-js'), require('path/posix'), require('rxjs'), require('mime-types'), require('papaparse'), require('@openai/agents'), require('openai')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'fs', 'path', 'fs/promises', 'waitasecond', 'prompts', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'socket.io-client', 'crypto-js', 'jszip', 'crypto', '@mozilla/readability', 'jsdom', 'showdown', 'glob-promise', 'moment', 'express', 'express-openapi-validator', 'http', 'socket.io', 'swagger-ui-express', 'react', 'react-dom/server', '@anthropic-ai/sdk', 'bottleneck', '@azure/openai', 'typescript', 'ignore', 'readline', 'child_process', 'pg', '@supabase/supabase-js', 'path/posix', 'rxjs', 'mime-types', 'papaparse', '@openai/agents', 'openai'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global._spaceTrim, global.fs, global.path, global.promises, global.waitasecond, global.prompts, global.dotenv, global.hexEncoder, global.sha256, global.socket_ioClient, global.cryptoJs, global.JSZip, global.crypto, global.readability, global.jsdom, global.showdown, global.glob, global.moment, global.express, global.OpenApiValidator, global.http, global.socket_io, global.swaggerUi, global.react, global.server, global.Anthropic, global.Bottleneck, global.openai, global.ts, global.ignore, global.readline, global.child_process, global.pg, null, global.posix, global.rxjs, global.mimeTypes, global.papaparse, global.agents, global.OpenAI));
5
+ })(this, (function (exports, colors, commander, _spaceTrim, fs, path, promises, waitasecond, prompts, dotenv, hexEncoder, sha256, socket_ioClient, cryptoJs, JSZip, crypto, readability, jsdom, showdown, glob, moment, express, OpenApiValidator, http, socket_io, swaggerUi, react, server, Anthropic, Bottleneck, openai, ts, ignore, readline, child_process, pg, supabaseJs, posix, rxjs, mimeTypes, papaparse, agents, OpenAI) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -43,6 +43,7 @@
43
43
  var Anthropic__default = /*#__PURE__*/_interopDefaultLegacy(Anthropic);
44
44
  var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
45
45
  var ts__namespace = /*#__PURE__*/_interopNamespace(ts);
46
+ var ignore__default = /*#__PURE__*/_interopDefaultLegacy(ignore);
46
47
  var readline__namespace = /*#__PURE__*/_interopNamespace(readline);
47
48
  var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
48
49
 
@@ -60,7 +61,7 @@
60
61
  * @generated
61
62
  * @see https://github.com/webgptorg/promptbook
62
63
  */
63
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-38';
64
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-39';
64
65
  /**
65
66
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
66
67
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -1735,7 +1736,7 @@
1735
1736
  /**
1736
1737
  * Supported aggressiveness levels for refactor-candidate scanning.
1737
1738
  */
1738
- const REFACTOR_CANDIDATE_LEVEL_VALUES = ['low', 'medium', 'high', 'xhigh'];
1739
+ const REFACTOR_CANDIDATE_LEVEL_VALUES = ['xlow', 'low', 'medium', 'high', 'xhigh', 'extreme'];
1739
1740
  /**
1740
1741
  * Default aggressiveness level for refactor-candidate scanning.
1741
1742
  */
@@ -1743,37 +1744,76 @@
1743
1744
  /**
1744
1745
  * Threshold table for each supported refactor-candidate scanning level.
1745
1746
  */
1746
- const REFACTOR_CANDIDATE_LEVEL_CONFIGURATION_BY_LEVEL = {
1747
- low: createRefactorCandidateLevelConfiguration({
1747
+ const REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL = {
1748
+ xlow: createRefactorCandidateLevelDetails({
1749
+ description: 'Very benevolent scan that only flags the most obvious refactor targets.',
1750
+ maxLineCount: 4200,
1751
+ maxEntityCountPerFile: 36,
1752
+ maxFunctionCountPerFile: 24,
1753
+ maxFunctionComplexity: 24,
1754
+ }),
1755
+ low: createRefactorCandidateLevelDetails({
1756
+ description: 'Conservative scan for only the most obvious refactor targets.',
1748
1757
  maxLineCount: 2800,
1749
1758
  maxEntityCountPerFile: 28,
1750
1759
  maxFunctionCountPerFile: 18,
1751
1760
  maxFunctionComplexity: 20,
1752
1761
  }),
1753
- medium: createRefactorCandidateLevelConfiguration({
1762
+ medium: createRefactorCandidateLevelDetails({
1763
+ description: 'Default scan using the current standard thresholds.',
1754
1764
  maxLineCount: 2000,
1755
1765
  maxEntityCountPerFile: 20,
1756
1766
  maxFunctionCountPerFile: 14,
1757
1767
  maxFunctionComplexity: 16,
1758
1768
  }),
1759
- high: createRefactorCandidateLevelConfiguration({
1760
- maxLineCount: 1500,
1761
- maxEntityCountPerFile: 16,
1769
+ high: createRefactorCandidateLevelDetails({
1770
+ description: 'Strict scan that finds more crowded or complex files.',
1771
+ maxLineCount: 1400,
1772
+ maxEntityCountPerFile: 15,
1762
1773
  maxFunctionCountPerFile: 10,
1763
1774
  maxFunctionComplexity: 12,
1764
1775
  }),
1765
- xhigh: createRefactorCandidateLevelConfiguration({
1766
- maxLineCount: 1000,
1767
- maxEntityCountPerFile: 12,
1768
- maxFunctionCountPerFile: 8,
1776
+ xhigh: createRefactorCandidateLevelDetails({
1777
+ description: 'Very strict scan for denser and more complex candidates.',
1778
+ maxLineCount: 900,
1779
+ maxEntityCountPerFile: 10,
1780
+ maxFunctionCountPerFile: 7,
1769
1781
  maxFunctionComplexity: 8,
1770
1782
  }),
1783
+ extreme: createRefactorCandidateLevelDetails({
1784
+ description: 'Most aggressive scan that also surfaces weaker refactor opportunities.',
1785
+ maxLineCount: 600,
1786
+ maxEntityCountPerFile: 6,
1787
+ maxFunctionCountPerFile: 4,
1788
+ maxFunctionComplexity: 6,
1789
+ }),
1771
1790
  };
1772
1791
  /**
1773
1792
  * Resolves the thresholds for a selected refactor-candidate scanning level.
1774
1793
  */
1775
1794
  function getRefactorCandidateLevelConfiguration(level = DEFAULT_REFACTOR_CANDIDATE_LEVEL) {
1776
- return REFACTOR_CANDIDATE_LEVEL_CONFIGURATION_BY_LEVEL[level];
1795
+ return REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL[level].configuration;
1796
+ }
1797
+ /**
1798
+ * Resolves the user-facing description for a selected refactor-candidate scanning level.
1799
+ */
1800
+ function getRefactorCandidateLevelDescription(level) {
1801
+ return REFACTOR_CANDIDATE_LEVEL_DETAILS_BY_LEVEL[level].description;
1802
+ }
1803
+ /**
1804
+ * Builds one normalized refactor-candidate level entry.
1805
+ */
1806
+ function createRefactorCandidateLevelDetails(options) {
1807
+ const { description, maxLineCount, maxEntityCountPerFile, maxFunctionCountPerFile, maxFunctionComplexity } = options;
1808
+ return {
1809
+ description,
1810
+ configuration: createRefactorCandidateLevelConfiguration({
1811
+ maxLineCount,
1812
+ maxEntityCountPerFile,
1813
+ maxFunctionCountPerFile,
1814
+ maxFunctionComplexity,
1815
+ }),
1816
+ };
1777
1817
  }
1778
1818
  /**
1779
1819
  * Builds one normalized refactor-candidate level configuration entry.
@@ -1809,17 +1849,14 @@
1809
1849
  */
1810
1850
  function $initializeCoderFindRefactorCandidatesCommand(program) {
1811
1851
  const command = program.command('find-refactor-candidates');
1812
- command.description(_spaceTrim.spaceTrim(`
1813
- Scan source files to identify refactoring candidates
1814
-
1815
- Levels:
1816
- - low: Conservative scan for only the most obvious refactor targets
1817
- - medium: Default scan using the current standard thresholds
1818
- - high: Stricter scan that finds more crowded or complex files
1819
- - xhigh: Most aggressive scan for denser and more complex candidates
1820
-
1821
- Generates refactor prompts with guidance for identified candidates.
1822
- `));
1852
+ command.description([
1853
+ 'Scan source files to identify refactoring candidates',
1854
+ '',
1855
+ 'Levels:',
1856
+ ...REFACTOR_CANDIDATE_LEVEL_VALUES.map((level) => `- ${level}: ${getRefactorCandidateLevelDescription(level)}`),
1857
+ '',
1858
+ 'Generates refactor prompts with guidance for identified candidates.',
1859
+ ].join('\n'));
1823
1860
  command.addOption(new commander.Option('--level <level>', `Set scan aggressiveness (${REFACTOR_CANDIDATE_LEVEL_VALUES.join(', ')})`)
1824
1861
  .choices([...REFACTOR_CANDIDATE_LEVEL_VALUES])
1825
1862
  .default(DEFAULT_REFACTOR_CANDIDATE_LEVEL));
@@ -1947,7 +1984,7 @@
1947
1984
  const ensuredTemplateFiles = [];
1948
1985
  for (const definition of DEFAULT_CODER_PROJECT_PROMPT_TEMPLATE_DEFINITIONS) {
1949
1986
  const absoluteTemplatePath = path.join(projectPath, definition.relativeFilePath);
1950
- if (await isExistingFile$1(absoluteTemplatePath)) {
1987
+ if (await isExistingFile$2(absoluteTemplatePath)) {
1951
1988
  ensuredTemplateFiles.push({
1952
1989
  id: definition.id,
1953
1990
  relativeFilePath: definition.relativeFilePath,
@@ -2061,7 +2098,7 @@
2061
2098
  /**
2062
2099
  * Checks whether a path exists and is a file.
2063
2100
  */
2064
- async function isExistingFile$1(path) {
2101
+ async function isExistingFile$2(path) {
2065
2102
  try {
2066
2103
  return (await promises.stat(path)).isFile();
2067
2104
  }
@@ -2495,7 +2532,7 @@
2495
2532
  */
2496
2533
  async function ensureCoderMarkdownFile(projectPath, relativeFilePath, fileContent) {
2497
2534
  const absoluteFilePath = path.join(projectPath, relativeFilePath);
2498
- if (await isExistingFile(absoluteFilePath)) {
2535
+ if (await isExistingFile$1(absoluteFilePath)) {
2499
2536
  return 'unchanged';
2500
2537
  }
2501
2538
  await promises.writeFile(absoluteFilePath, `${fileContent}\n`, 'utf-8');
@@ -2504,7 +2541,7 @@
2504
2541
  /**
2505
2542
  * Checks whether a path exists and is a file.
2506
2543
  */
2507
- async function isExistingFile(path) {
2544
+ async function isExistingFile$1(path) {
2508
2545
  try {
2509
2546
  return (await promises.stat(path)).isFile();
2510
2547
  }
@@ -41460,9 +41497,9 @@
41460
41497
  * @private function of findRefactorCandidates
41461
41498
  */
41462
41499
  async function findRefactorCandidatesInProject(options) {
41463
- const { heuristics, rootDir } = options;
41464
- const lineCountExemptPaths = await buildExemptPathSet(rootDir, LINE_COUNT_EXEMPT_GLOBS);
41465
- const sourceFiles = await listSourceFiles(rootDir);
41500
+ const { heuristics, isIgnoredRelativePath = () => false, rootDir } = options;
41501
+ const lineCountExemptPaths = await buildExemptPathSet(rootDir, LINE_COUNT_EXEMPT_GLOBS, isIgnoredRelativePath);
41502
+ const sourceFiles = await listSourceFiles(rootDir, isIgnoredRelativePath);
41466
41503
  const candidates = [];
41467
41504
  for (const filePath of sourceFiles) {
41468
41505
  const candidate = await analyzeSourceFileForRefactorCandidate({
@@ -41482,7 +41519,7 @@
41482
41519
  *
41483
41520
  * @private function of findRefactorCandidatesInProject
41484
41521
  */
41485
- async function listSourceFiles(rootDir) {
41522
+ async function listSourceFiles(rootDir, isIgnoredRelativePath) {
41486
41523
  const extensions = SOURCE_FILE_EXTENSIONS.map((extension) => extension.replace(/^\./, '')).join(',');
41487
41524
  const extensionGlob = `{${extensions}}`;
41488
41525
  const patterns = [...SOURCE_ROOTS.map((root) => `${root}/**/*.${extensionGlob}`), `*.${extensionGlob}`];
@@ -41495,6 +41532,9 @@
41495
41532
  absolute: true,
41496
41533
  });
41497
41534
  for (const match of matches) {
41535
+ if (shouldIgnoreAbsolutePath(rootDir, match, isIgnoredRelativePath)) {
41536
+ continue;
41537
+ }
41498
41538
  files.add(match);
41499
41539
  }
41500
41540
  }
@@ -41505,7 +41545,7 @@
41505
41545
  *
41506
41546
  * @private function of findRefactorCandidatesInProject
41507
41547
  */
41508
- async function buildExemptPathSet(rootDir, patterns) {
41548
+ async function buildExemptPathSet(rootDir, patterns, isIgnoredRelativePath) {
41509
41549
  const exemptPaths = new Set();
41510
41550
  for (const pattern of patterns) {
41511
41551
  const matches = await glob__default["default"](pattern, {
@@ -41515,11 +41555,23 @@
41515
41555
  absolute: true,
41516
41556
  });
41517
41557
  for (const match of matches) {
41558
+ if (shouldIgnoreAbsolutePath(rootDir, match, isIgnoredRelativePath)) {
41559
+ continue;
41560
+ }
41518
41561
  exemptPaths.add(normalizeAbsolutePath(match));
41519
41562
  }
41520
41563
  }
41521
41564
  return exemptPaths;
41522
41565
  }
41566
+ /**
41567
+ * Resolves whether an absolute path falls under the project `.gitignore` rules.
41568
+ *
41569
+ * @private function of findRefactorCandidatesInProject
41570
+ */
41571
+ function shouldIgnoreAbsolutePath(rootDir, absolutePath, isIgnoredRelativePath) {
41572
+ const relativePath = normalizeRefactorCandidatePath(path.relative(rootDir, absolutePath));
41573
+ return isIgnoredRelativePath(relativePath);
41574
+ }
41523
41575
  /**
41524
41576
  * Normalizes an absolute path for consistent comparisons.
41525
41577
  *
@@ -41568,6 +41620,69 @@
41568
41620
  }
41569
41621
  // Note: [🟡] Code for repository script [loadExistingPromptTargets](scripts/find-refactor-candidates/loadExistingPromptTargets.ts) should never be published outside of `@promptbook/cli`
41570
41622
 
41623
+ /**
41624
+ * Filename used to discover the project root for refactor-candidate scanning.
41625
+ */
41626
+ const GITIGNORE_FILE_NAME = '.gitignore';
41627
+ /**
41628
+ * Resolves the project root and `.gitignore` matcher for refactor-candidate scanning.
41629
+ *
41630
+ * @private function of findRefactorCandidates
41631
+ */
41632
+ async function resolveRefactorCandidateProject(startDir) {
41633
+ const absoluteStartDir = path.resolve(startDir);
41634
+ const gitignorePath = await findNearestGitignorePath(absoluteStartDir);
41635
+ if (!gitignorePath) {
41636
+ return {
41637
+ rootDir: absoluteStartDir,
41638
+ isIgnoredRelativePath: () => false,
41639
+ };
41640
+ }
41641
+ const rootDir = path.dirname(gitignorePath);
41642
+ const gitignoreMatcher = ignore__default["default"]().add(await promises.readFile(gitignorePath, 'utf-8'));
41643
+ return {
41644
+ rootDir,
41645
+ isIgnoredRelativePath(relativePath) {
41646
+ return gitignoreMatcher.ignores(normalizeRefactorCandidatePath(relativePath));
41647
+ },
41648
+ };
41649
+ }
41650
+ /**
41651
+ * Finds the nearest ancestor `.gitignore` so scans work from any project subdirectory.
41652
+ *
41653
+ * @private function of resolveRefactorCandidateProject
41654
+ */
41655
+ async function findNearestGitignorePath(startDir) {
41656
+ let currentDir = path.resolve(startDir);
41657
+ while (true) {
41658
+ const gitignorePath = path.join(currentDir, GITIGNORE_FILE_NAME);
41659
+ if (await isExistingFile(gitignorePath)) {
41660
+ return gitignorePath;
41661
+ }
41662
+ const parentDir = path.dirname(currentDir);
41663
+ if (parentDir === currentDir) {
41664
+ return null;
41665
+ }
41666
+ currentDir = parentDir;
41667
+ }
41668
+ }
41669
+ /**
41670
+ * Detects whether a file exists without swallowing unexpected filesystem failures.
41671
+ *
41672
+ * @private function of resolveRefactorCandidateProject
41673
+ */
41674
+ async function isExistingFile(filePath) {
41675
+ var _a;
41676
+ const fileStats = await promises.stat(filePath).catch((error) => {
41677
+ if (error.code === 'ENOENT') {
41678
+ return undefined;
41679
+ }
41680
+ throw error;
41681
+ });
41682
+ return (_a = fileStats === null || fileStats === void 0 ? void 0 : fileStats.isFile()) !== null && _a !== void 0 ? _a : false;
41683
+ }
41684
+ // Note: [🟡] Code for repository script [resolveRefactorCandidateProject](scripts/find-refactor-candidates/resolveRefactorCandidateProject.ts) should never be published outside of `@promptbook/cli`
41685
+
41571
41686
  /**
41572
41687
  * Calculates the next available prompt numbering sequence for a month.
41573
41688
  */
@@ -41925,11 +42040,12 @@
41925
42040
  initializeFindRefactorCandidatesRun();
41926
42041
  console.info(colors__default["default"].cyan('⚡🏭 Find refactor candidates'));
41927
42042
  console.info(colors__default["default"].gray(`Using \`${level}\` scan level.`));
41928
- const rootDir = process.cwd();
42043
+ const { isIgnoredRelativePath, rootDir } = await resolveRefactorCandidateProject(process.cwd());
41929
42044
  const promptsDir = path.join(rootDir, PROMPTS_DIR_NAME);
41930
42045
  const existingTargets = await loadExistingPromptTargets(promptsDir);
41931
42046
  const candidates = await findRefactorCandidatesInProject({
41932
42047
  heuristics,
42048
+ isIgnoredRelativePath,
41933
42049
  rootDir,
41934
42050
  });
41935
42051
  if (candidates.length === 0) {