@aramassa/ai-rules 0.4.0 → 0.4.3
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/artifact/instructions/rules/workspace-management/nodejs.md +358 -0
- package/artifact/instructions/{python/workspace-management.md → rules/workspace-management/python.md} +12 -12
- package/artifact/instructions/rules/workspace-management/typescript.md +337 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +270 -202
- package/package.json +1 -1
- package/presets/monorepo/nodejs.yaml +14 -0
- package/presets/monorepo/typescript.yaml +25 -0
package/dist/cli.js
CHANGED
|
@@ -17904,7 +17904,7 @@ class ContentTracker {
|
|
|
17904
17904
|
* Generates a hash for the given content
|
|
17905
17905
|
*/
|
|
17906
17906
|
generateContentHash(content) {
|
|
17907
|
-
return createHash(
|
|
17907
|
+
return createHash("sha256").update(content.trim()).digest("hex");
|
|
17908
17908
|
}
|
|
17909
17909
|
/**
|
|
17910
17910
|
* Checks if the same content has already been written to the file
|
|
@@ -17949,7 +17949,7 @@ function findPackageRoot() {
|
|
|
17949
17949
|
let dir = path.dirname(currentFile);
|
|
17950
17950
|
// Walk up directory tree looking for package.json
|
|
17951
17951
|
while (dir !== path.dirname(dir)) {
|
|
17952
|
-
const packageJsonPath = path.join(dir,
|
|
17952
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
17953
17953
|
try {
|
|
17954
17954
|
// Use synchronous check since this is initialization
|
|
17955
17955
|
fsSync.accessSync(packageJsonPath);
|
|
@@ -17973,17 +17973,49 @@ function resolveDefaultSrcDir(providedSrc) {
|
|
|
17973
17973
|
}
|
|
17974
17974
|
// Find package root and return artifact/ directory
|
|
17975
17975
|
const packageRoot = findPackageRoot();
|
|
17976
|
-
return path.join(packageRoot,
|
|
17976
|
+
return path.join(packageRoot, "artifact");
|
|
17977
17977
|
}
|
|
17978
17978
|
/**
|
|
17979
|
-
* Resolves source directory for a recipe item following priority order:
|
|
17979
|
+
* Resolves source directory for a recipe item following priority order (Issue #197):
|
|
17980
17980
|
* 1. CLI --src option (highest priority)
|
|
17981
|
-
* 2.
|
|
17982
|
-
* 3.
|
|
17981
|
+
* 2. Import-level src field (overrides item-level src)
|
|
17982
|
+
* 3. Recipe item src field (lower priority - resolved relative to recipe file or import src)
|
|
17983
17983
|
* 4. Default package artifact/ directory (lowest priority)
|
|
17984
17984
|
*/
|
|
17985
|
-
function resolveItemSrcDir(cliSrc, itemSrc, importSrc) {
|
|
17986
|
-
|
|
17985
|
+
function resolveItemSrcDir(cliSrc, itemSrc, importSrc, recipePath) {
|
|
17986
|
+
if (cliSrc) {
|
|
17987
|
+
return cliSrc;
|
|
17988
|
+
}
|
|
17989
|
+
// Per Issue #197: import-level src should override item-level src
|
|
17990
|
+
if (importSrc) {
|
|
17991
|
+
// If itemSrc is specified and relative, resolve it relative to importSrc
|
|
17992
|
+
if (itemSrc && !path.isAbsolute(itemSrc)) {
|
|
17993
|
+
// If importSrc is absolute, resolve itemSrc relative to it
|
|
17994
|
+
if (path.isAbsolute(importSrc)) {
|
|
17995
|
+
return path.resolve(importSrc, itemSrc);
|
|
17996
|
+
}
|
|
17997
|
+
// If importSrc is also relative, we need to resolve it first
|
|
17998
|
+
if (recipePath) {
|
|
17999
|
+
const resolvedImportSrc = path.resolve(path.dirname(recipePath), importSrc);
|
|
18000
|
+
return path.resolve(resolvedImportSrc, itemSrc);
|
|
18001
|
+
}
|
|
18002
|
+
}
|
|
18003
|
+
// Use importSrc as-is (either no itemSrc, or itemSrc is absolute and ignored)
|
|
18004
|
+
return importSrc;
|
|
18005
|
+
}
|
|
18006
|
+
if (itemSrc) {
|
|
18007
|
+
// If itemSrc is absolute, use it as-is
|
|
18008
|
+
if (path.isAbsolute(itemSrc)) {
|
|
18009
|
+
return itemSrc;
|
|
18010
|
+
}
|
|
18011
|
+
// If itemSrc is relative and we have a recipe path, resolve relative to recipe file directory
|
|
18012
|
+
if (recipePath) {
|
|
18013
|
+
return path.resolve(path.dirname(recipePath), itemSrc);
|
|
18014
|
+
}
|
|
18015
|
+
// Fallback to current working directory (existing behavior)
|
|
18016
|
+
return itemSrc;
|
|
18017
|
+
}
|
|
18018
|
+
return resolveDefaultSrcDir();
|
|
17987
18019
|
}
|
|
17988
18020
|
/**
|
|
17989
18021
|
* Resolves output path with baseDir support following priority order:
|
|
@@ -17995,7 +18027,7 @@ function resolveItemSrcDir(cliSrc, itemSrc, importSrc) {
|
|
|
17995
18027
|
function resolveOutputPath(itemOut, cliBaseDir, importBaseDir, recipeBaseDir, recipePath) {
|
|
17996
18028
|
// Check if the itemOut contains environment variables or tilde - if so, try to expand
|
|
17997
18029
|
let processedItemOut = itemOut;
|
|
17998
|
-
if (itemOut.includes(
|
|
18030
|
+
if (itemOut.includes("$") || itemOut.startsWith("~")) {
|
|
17999
18031
|
try {
|
|
18000
18032
|
processedItemOut = resolvePath(itemOut);
|
|
18001
18033
|
// If expansion resulted in an absolute path, use it as-is (ignore baseDir)
|
|
@@ -18021,10 +18053,10 @@ function resolveOutputPath(itemOut, cliBaseDir, importBaseDir, recipeBaseDir, re
|
|
|
18021
18053
|
}
|
|
18022
18054
|
// Existing behavior: resolve relative to recipe file directory or current directory
|
|
18023
18055
|
// For preset files (located in presets/ directory), resolve relative to project root instead
|
|
18024
|
-
let baseDirectory =
|
|
18056
|
+
let baseDirectory = ".";
|
|
18025
18057
|
if (recipePath) {
|
|
18026
18058
|
const packageRoot = findPackageRoot();
|
|
18027
|
-
const presetsDir = path.join(packageRoot,
|
|
18059
|
+
const presetsDir = path.join(packageRoot, "presets");
|
|
18028
18060
|
// If the recipe is in the presets directory, use project root as base
|
|
18029
18061
|
if (path.dirname(recipePath) === presetsDir) {
|
|
18030
18062
|
baseDirectory = process.cwd();
|
|
@@ -18038,10 +18070,10 @@ function resolveOutputPath(itemOut, cliBaseDir, importBaseDir, recipeBaseDir, re
|
|
|
18038
18070
|
}
|
|
18039
18071
|
function resolveRecipePath(recipePath) {
|
|
18040
18072
|
// If recipe starts with ':', resolve to package preset
|
|
18041
|
-
if (recipePath.startsWith(
|
|
18073
|
+
if (recipePath.startsWith(":")) {
|
|
18042
18074
|
const presetName = recipePath.slice(1); // Remove the ':'
|
|
18043
18075
|
const packageRoot = findPackageRoot();
|
|
18044
|
-
return path.join(packageRoot,
|
|
18076
|
+
return path.join(packageRoot, "presets", `${presetName}.yaml`);
|
|
18045
18077
|
}
|
|
18046
18078
|
// Otherwise, use the path as-is
|
|
18047
18079
|
return recipePath;
|
|
@@ -18061,8 +18093,8 @@ async function validateRecipeFilesExist(recipePaths) {
|
|
|
18061
18093
|
}
|
|
18062
18094
|
}
|
|
18063
18095
|
if (missingFiles.length > 0) {
|
|
18064
|
-
const errorMessages = missingFiles.map(file => {
|
|
18065
|
-
if (file.startsWith(
|
|
18096
|
+
const errorMessages = missingFiles.map((file) => {
|
|
18097
|
+
if (file.startsWith(":")) {
|
|
18066
18098
|
const presetName = file.slice(1);
|
|
18067
18099
|
return `Preset ':${presetName}' not found. Use 'ai-rules presets' to list available presets.`;
|
|
18068
18100
|
}
|
|
@@ -18074,7 +18106,7 @@ async function validateRecipeFilesExist(recipePaths) {
|
|
|
18074
18106
|
throw new Error(errorMessages[0]);
|
|
18075
18107
|
}
|
|
18076
18108
|
else {
|
|
18077
|
-
throw new Error(`Multiple recipe files not found:\n${errorMessages.join(
|
|
18109
|
+
throw new Error(`Multiple recipe files not found:\n${errorMessages.join("\n")}`);
|
|
18078
18110
|
}
|
|
18079
18111
|
}
|
|
18080
18112
|
}
|
|
@@ -18083,7 +18115,7 @@ async function validateRecipeFilesExist(recipePaths) {
|
|
|
18083
18115
|
*/
|
|
18084
18116
|
function resolveImportPath(importPath, currentRecipePath) {
|
|
18085
18117
|
// If import starts with ':', resolve to package preset
|
|
18086
|
-
if (importPath.startsWith(
|
|
18118
|
+
if (importPath.startsWith(":")) {
|
|
18087
18119
|
return resolveRecipePath(importPath);
|
|
18088
18120
|
}
|
|
18089
18121
|
// Otherwise, resolve relative to current recipe file directory
|
|
@@ -18109,8 +18141,8 @@ async function expandRecipeImports(items, currentPath, debugLogger, visited = ne
|
|
|
18109
18141
|
// Check for circular imports
|
|
18110
18142
|
if (visited.has(resolvedImportPath)) {
|
|
18111
18143
|
const visitedArray = Array.from(visited);
|
|
18112
|
-
debugLogger?.log(`Circular import detected in path: ${visitedArray.join(
|
|
18113
|
-
throw new Error(`Circular import detected: ${visitedArray.join(
|
|
18144
|
+
debugLogger?.log(`Circular import detected in path: ${visitedArray.join(" -> ")} -> ${resolvedImportPath}`);
|
|
18145
|
+
throw new Error(`Circular import detected: ${visitedArray.join(" -> ")} -> ${resolvedImportPath}`);
|
|
18114
18146
|
}
|
|
18115
18147
|
// Check for duplicate imports and skip if already imported
|
|
18116
18148
|
if (imported.has(resolvedImportPath)) {
|
|
@@ -18152,18 +18184,25 @@ async function expandRecipeImports(items, currentPath, debugLogger, visited = ne
|
|
|
18152
18184
|
const expandedImported = await expandRecipeImports(importData.recipe, resolvedImportPath, debugLogger, newVisited, imported, depth + 1);
|
|
18153
18185
|
debugLogger?.timeEnd(`Expanding nested imports in: ${resolvedImportPath}`);
|
|
18154
18186
|
debugLogger?.log(`Nested expansion yielded ${expandedImported.length} items`);
|
|
18155
|
-
// Apply import-level configurations to all expanded items
|
|
18187
|
+
// Apply import-level configurations to all expanded items
|
|
18188
|
+
expandedImported.forEach((expandedItem) => {
|
|
18189
|
+
// Ensure _recipePath is set for proper path resolution
|
|
18190
|
+
if (!expandedItem._recipePath) {
|
|
18191
|
+
expandedItem._recipePath = resolvedImportPath;
|
|
18192
|
+
debugLogger?.log(`Tagged item '${expandedItem.title || expandedItem.out || "untitled"}' with recipe path: ${resolvedImportPath}`);
|
|
18193
|
+
}
|
|
18194
|
+
});
|
|
18156
18195
|
if (importLevelBaseDir || importLevelSrc || importLevelVariables) {
|
|
18157
|
-
expandedImported.forEach(expandedItem => {
|
|
18196
|
+
expandedImported.forEach((expandedItem) => {
|
|
18158
18197
|
// Apply baseDir if specified (only if not already set to preserve nested import priority)
|
|
18159
18198
|
if (importLevelBaseDir && !expandedItem._importBaseDir) {
|
|
18160
18199
|
expandedItem._importBaseDir = importLevelBaseDir;
|
|
18161
|
-
debugLogger?.log(`Tagged item '${expandedItem.title || expandedItem.out ||
|
|
18200
|
+
debugLogger?.log(`Tagged item '${expandedItem.title || expandedItem.out || "untitled"}' with import baseDir: ${importLevelBaseDir}`);
|
|
18162
18201
|
}
|
|
18163
18202
|
// Apply src if specified (only if not already set to preserve nested import priority)
|
|
18164
18203
|
if (importLevelSrc && !expandedItem._importSrc) {
|
|
18165
18204
|
expandedItem._importSrc = importLevelSrc;
|
|
18166
|
-
debugLogger?.log(`Tagged item '${expandedItem.title || expandedItem.out ||
|
|
18205
|
+
debugLogger?.log(`Tagged item '${expandedItem.title || expandedItem.out || "untitled"}' with import src: ${importLevelSrc}`);
|
|
18167
18206
|
}
|
|
18168
18207
|
// Apply variables if specified (merge with existing variables, with import-level taking priority)
|
|
18169
18208
|
if (importLevelVariables) {
|
|
@@ -18174,10 +18213,10 @@ async function expandRecipeImports(items, currentPath, debugLogger, visited = ne
|
|
|
18174
18213
|
// Merge variables, with import-level taking priority over nested imports
|
|
18175
18214
|
expandedItem._importVariables = {
|
|
18176
18215
|
...expandedItem._importVariables,
|
|
18177
|
-
...importLevelVariables
|
|
18216
|
+
...importLevelVariables,
|
|
18178
18217
|
};
|
|
18179
18218
|
}
|
|
18180
|
-
debugLogger?.log(`Tagged item '${expandedItem.title || expandedItem.out ||
|
|
18219
|
+
debugLogger?.log(`Tagged item '${expandedItem.title || expandedItem.out || "untitled"}' with import variables:`, expandedItem._importVariables);
|
|
18181
18220
|
}
|
|
18182
18221
|
});
|
|
18183
18222
|
}
|
|
@@ -18188,10 +18227,10 @@ async function expandRecipeImports(items, currentPath, debugLogger, visited = ne
|
|
|
18188
18227
|
// Remove from imported set if there was an error to allow retry
|
|
18189
18228
|
imported.delete(resolvedImportPath);
|
|
18190
18229
|
debugLogger?.log(`Error processing import ${resolvedImportPath}:`, error);
|
|
18191
|
-
if (item.import.startsWith(
|
|
18230
|
+
if (item.import.startsWith(":")) {
|
|
18192
18231
|
const presetName = item.import.slice(1);
|
|
18193
18232
|
// Only treat ENOENT errors as "preset not found"
|
|
18194
|
-
if (error instanceof Error && error.message.includes(
|
|
18233
|
+
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
18195
18234
|
throw new Error(`Preset ':${presetName}' not found. Use 'ai-rules presets' to list available presets.`);
|
|
18196
18235
|
}
|
|
18197
18236
|
// For other errors, add context but preserve the original error message
|
|
@@ -18200,7 +18239,7 @@ async function expandRecipeImports(items, currentPath, debugLogger, visited = ne
|
|
|
18200
18239
|
}
|
|
18201
18240
|
throw error;
|
|
18202
18241
|
}
|
|
18203
|
-
if (error instanceof Error && error.message.includes(
|
|
18242
|
+
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
18204
18243
|
throw new Error(`Import file '${item.import}' not found (resolved to: ${resolvedImportPath})`);
|
|
18205
18244
|
}
|
|
18206
18245
|
throw error;
|
|
@@ -18208,8 +18247,13 @@ async function expandRecipeImports(items, currentPath, debugLogger, visited = ne
|
|
|
18208
18247
|
}
|
|
18209
18248
|
else {
|
|
18210
18249
|
// This is a regular recipe item
|
|
18211
|
-
debugLogger?.log(`Adding regular recipe item: ${item.title ||
|
|
18212
|
-
|
|
18250
|
+
debugLogger?.log(`Adding regular recipe item: ${item.title || "untitled"}`);
|
|
18251
|
+
// Tag the item with the recipe path it originated from
|
|
18252
|
+
const taggedItem = { ...item };
|
|
18253
|
+
if (!taggedItem._recipePath) {
|
|
18254
|
+
taggedItem._recipePath = currentPath;
|
|
18255
|
+
}
|
|
18256
|
+
expanded.push(taggedItem);
|
|
18213
18257
|
}
|
|
18214
18258
|
}
|
|
18215
18259
|
debugLogger?.log(`Import expansion complete: ${expanded.length} total items after processing`);
|
|
@@ -18221,43 +18265,43 @@ async function expandRecipeImports(items, currentPath, debugLogger, visited = ne
|
|
|
18221
18265
|
function setupProgram() {
|
|
18222
18266
|
const program = new Command();
|
|
18223
18267
|
program
|
|
18224
|
-
.name(
|
|
18225
|
-
.description(
|
|
18226
|
-
.version(
|
|
18268
|
+
.name("ai-rules")
|
|
18269
|
+
.description("CLI for extracting and analyzing markdown files")
|
|
18270
|
+
.version("0.0.1");
|
|
18227
18271
|
// Extract command
|
|
18228
18272
|
program
|
|
18229
|
-
.command(
|
|
18230
|
-
.description(
|
|
18231
|
-
.option(
|
|
18232
|
-
.option(
|
|
18233
|
-
.option(
|
|
18234
|
-
.option(
|
|
18235
|
-
.option(
|
|
18236
|
-
.option(
|
|
18237
|
-
.option(
|
|
18238
|
-
.option(
|
|
18239
|
-
.option(
|
|
18240
|
-
.option(
|
|
18241
|
-
.option(
|
|
18242
|
-
.option(
|
|
18243
|
-
.option(
|
|
18244
|
-
.option(
|
|
18273
|
+
.command("extract")
|
|
18274
|
+
.description("Extract and merge markdown files")
|
|
18275
|
+
.option("--src <path>", "Source directory")
|
|
18276
|
+
.option("--out <path>", "Output file")
|
|
18277
|
+
.option("--type <types>", "Filter by type (comma-separated)")
|
|
18278
|
+
.option("--language <languages>", "Filter by language (comma-separated)")
|
|
18279
|
+
.option("--attr <attributes>", "Filter by attributes (comma-separated)")
|
|
18280
|
+
.option("--title <title>", "Title for the output")
|
|
18281
|
+
.option("--mode <mode>", "Write mode: append, prepend, overwrite", "overwrite")
|
|
18282
|
+
.option("--recipe <path>", "Recipe file path or package preset (e.g., :typescript). Can be specified multiple times or comma-separated.", collectRecipeOptions, [])
|
|
18283
|
+
.option("--base-dir <path>", "Base directory for output files (supports ~ and environment variable expansion)")
|
|
18284
|
+
.option("--vars <variables>", "Template variables in key=value format (comma-separated)")
|
|
18285
|
+
.option("--env-file <path>", "Path to .env file for template variables")
|
|
18286
|
+
.option("--debug", "Enable debug logging")
|
|
18287
|
+
.option("--verbose", "Enable verbose logging (alias for --debug)")
|
|
18288
|
+
.option("--dry-run", "Preview input/output files without writing")
|
|
18245
18289
|
.action(async (options) => {
|
|
18246
18290
|
await handleExtractCommand(options);
|
|
18247
18291
|
});
|
|
18248
|
-
// Stats command
|
|
18292
|
+
// Stats command
|
|
18249
18293
|
program
|
|
18250
|
-
.command(
|
|
18251
|
-
.description(
|
|
18252
|
-
.option(
|
|
18253
|
-
.option(
|
|
18294
|
+
.command("stats")
|
|
18295
|
+
.description("Generate statistics from markdown files")
|
|
18296
|
+
.option("--src <path>", "Source directory")
|
|
18297
|
+
.option("--variables", "Show available template variables")
|
|
18254
18298
|
.action(async (options) => {
|
|
18255
18299
|
await handleStatsCommand(options);
|
|
18256
18300
|
});
|
|
18257
18301
|
// Presets command
|
|
18258
18302
|
program
|
|
18259
|
-
.command(
|
|
18260
|
-
.description(
|
|
18303
|
+
.command("presets")
|
|
18304
|
+
.description("List available package presets")
|
|
18261
18305
|
.action(async () => {
|
|
18262
18306
|
await listPresets();
|
|
18263
18307
|
});
|
|
@@ -18277,7 +18321,8 @@ async function findYamlFilesRecursively(dir, baseDir = dir) {
|
|
|
18277
18321
|
const subFiles = await findYamlFilesRecursively(fullPath, baseDir);
|
|
18278
18322
|
yamlFiles.push(...subFiles);
|
|
18279
18323
|
}
|
|
18280
|
-
else if (item.isFile() &&
|
|
18324
|
+
else if (item.isFile() &&
|
|
18325
|
+
(item.name.endsWith(".yaml") || item.name.endsWith(".yml"))) {
|
|
18281
18326
|
// Get relative path from base directory
|
|
18282
18327
|
const relativePath = path.relative(baseDir, fullPath);
|
|
18283
18328
|
yamlFiles.push(relativePath);
|
|
@@ -18295,25 +18340,25 @@ async function findYamlFilesRecursively(dir, baseDir = dir) {
|
|
|
18295
18340
|
*/
|
|
18296
18341
|
async function listPresets() {
|
|
18297
18342
|
const packageRoot = findPackageRoot();
|
|
18298
|
-
const presetsDir = path.join(packageRoot,
|
|
18343
|
+
const presetsDir = path.join(packageRoot, "presets");
|
|
18299
18344
|
try {
|
|
18300
18345
|
const yamlFiles = await findYamlFilesRecursively(presetsDir);
|
|
18301
18346
|
if (yamlFiles.length === 0) {
|
|
18302
18347
|
// eslint-disable-next-line no-console
|
|
18303
|
-
console.log(
|
|
18348
|
+
console.log("No presets found in package.");
|
|
18304
18349
|
return;
|
|
18305
18350
|
}
|
|
18306
18351
|
// eslint-disable-next-line no-console
|
|
18307
|
-
console.log(
|
|
18308
|
-
yamlFiles.forEach(file => {
|
|
18309
|
-
const presetName = file.replace(/\.(yaml|yml)$/,
|
|
18352
|
+
console.log("Available presets:");
|
|
18353
|
+
yamlFiles.forEach((file) => {
|
|
18354
|
+
const presetName = file.replace(/\.(yaml|yml)$/, "");
|
|
18310
18355
|
// eslint-disable-next-line no-console
|
|
18311
18356
|
console.log(` :${presetName}`);
|
|
18312
18357
|
});
|
|
18313
18358
|
}
|
|
18314
18359
|
catch (error) {
|
|
18315
18360
|
// eslint-disable-next-line no-console
|
|
18316
|
-
console.log(
|
|
18361
|
+
console.log("No presets directory found in package.");
|
|
18317
18362
|
}
|
|
18318
18363
|
}
|
|
18319
18364
|
/**
|
|
@@ -18321,23 +18366,26 @@ async function listPresets() {
|
|
|
18321
18366
|
*/
|
|
18322
18367
|
function collectRecipeOptions(value, previous) {
|
|
18323
18368
|
// Split comma-separated values and add to the accumulated array
|
|
18324
|
-
const newValues = value
|
|
18369
|
+
const newValues = value
|
|
18370
|
+
.split(",")
|
|
18371
|
+
.map((v) => v.trim())
|
|
18372
|
+
.filter((v) => v.length > 0);
|
|
18325
18373
|
return previous.concat(newValues);
|
|
18326
18374
|
}
|
|
18327
18375
|
/**
|
|
18328
18376
|
* Parses comma-separated values into array
|
|
18329
18377
|
*/
|
|
18330
18378
|
function parseCommaSeparated(value) {
|
|
18331
|
-
return value ? value.split(
|
|
18379
|
+
return value ? value.split(",") : undefined;
|
|
18332
18380
|
}
|
|
18333
18381
|
/**
|
|
18334
18382
|
* Validates and converts mode string to WriteMode enum
|
|
18335
18383
|
*/
|
|
18336
18384
|
function parseWriteMode(mode) {
|
|
18337
18385
|
const modeMap = {
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18386
|
+
append: WriteMode.APPEND,
|
|
18387
|
+
prepend: WriteMode.PREPEND,
|
|
18388
|
+
overwrite: WriteMode.OVERWRITE,
|
|
18341
18389
|
};
|
|
18342
18390
|
return modeMap[mode] || WriteMode.OVERWRITE;
|
|
18343
18391
|
}
|
|
@@ -18352,10 +18400,10 @@ function convertFiltersToAttrFilters(filters) {
|
|
|
18352
18400
|
if (Array.isArray(value)) {
|
|
18353
18401
|
// For array values, join with pipe (|) to indicate OR logic
|
|
18354
18402
|
// This needs to be handled specially in the filter logic
|
|
18355
|
-
const joinedValues = value.join(
|
|
18403
|
+
const joinedValues = value.join("|");
|
|
18356
18404
|
attrFilters.push(`${key}=${joinedValues}`);
|
|
18357
18405
|
}
|
|
18358
|
-
else if (typeof value ===
|
|
18406
|
+
else if (typeof value === "string") {
|
|
18359
18407
|
// For string values, create single filter entry
|
|
18360
18408
|
attrFilters.push(`${key}=${value}`);
|
|
18361
18409
|
}
|
|
@@ -18369,8 +18417,8 @@ function convertFiltersToAttrFilters(filters) {
|
|
|
18369
18417
|
* Displays dry-run preview for recipe processing
|
|
18370
18418
|
*/
|
|
18371
18419
|
async function displayRecipeDryRunPreview(recipePath, recipeData, expandedRecipe, baseOptions, debugLogger, cliBaseDir, cliOutFile, cliSrc) {
|
|
18372
|
-
console.log(
|
|
18373
|
-
const presetName = recipePath.startsWith(
|
|
18420
|
+
console.log("=== Recipe Dry-Run Preview ===\n");
|
|
18421
|
+
const presetName = recipePath.startsWith(":") ? recipePath : undefined;
|
|
18374
18422
|
if (presetName) {
|
|
18375
18423
|
console.log(`📦 Recipe: ${presetName} (${expandedRecipe.length} steps)`);
|
|
18376
18424
|
}
|
|
@@ -18387,32 +18435,36 @@ async function displayRecipeDryRunPreview(recipePath, recipeData, expandedRecipe
|
|
|
18387
18435
|
// Resolve paths and options for this item
|
|
18388
18436
|
const itemOut = cliOutFile || item.out || baseOptions.outFile;
|
|
18389
18437
|
const outputFile = resolveOutputPath(itemOut, cliBaseDir, item._importBaseDir, recipeBaseDir, resolveRecipePath(recipePath));
|
|
18390
|
-
const itemTypes = item.type
|
|
18391
|
-
|
|
18438
|
+
const itemTypes = item.type
|
|
18439
|
+
? parseCommaSeparated(item.type)
|
|
18440
|
+
: baseOptions.types;
|
|
18441
|
+
const itemLanguages = item.language
|
|
18442
|
+
? parseCommaSeparated(item.language)
|
|
18443
|
+
: baseOptions.languages;
|
|
18392
18444
|
let combinedAttrFilters = [...baseOptions.attrFilters];
|
|
18393
18445
|
if (item.filters) {
|
|
18394
18446
|
const itemAttrFilters = convertFiltersToAttrFilters(item.filters);
|
|
18395
18447
|
combinedAttrFilters = combinedAttrFilters.concat(itemAttrFilters);
|
|
18396
18448
|
}
|
|
18397
|
-
const itemSrcDir = resolveItemSrcDir(cliSrc, item.src, item._importSrc);
|
|
18449
|
+
const itemSrcDir = resolveItemSrcDir(cliSrc, item.src, item._importSrc, item._recipePath);
|
|
18398
18450
|
console.log(` 📂 Source: ${itemSrcDir}`);
|
|
18399
|
-
console.log(` 🔍 Filters: type=${itemTypes?.join(
|
|
18451
|
+
console.log(` 🔍 Filters: type=${itemTypes?.join(",") || "(none)"}, language=${itemLanguages?.join(",") || "(none)"}`);
|
|
18400
18452
|
if (combinedAttrFilters.length > 0) {
|
|
18401
|
-
console.log(` attributes=${combinedAttrFilters.join(
|
|
18453
|
+
console.log(` attributes=${combinedAttrFilters.join(", ")}`);
|
|
18402
18454
|
}
|
|
18403
18455
|
try {
|
|
18404
18456
|
const { allFiles, filteredFiles } = await loadAndFilterFilesWithDetails(itemSrcDir, itemTypes, itemLanguages, combinedAttrFilters, debugLogger);
|
|
18405
18457
|
totalInputFiles += allFiles.length;
|
|
18406
18458
|
totalIncludedFiles += filteredFiles.length;
|
|
18407
|
-
totalExcludedFiles +=
|
|
18459
|
+
totalExcludedFiles += allFiles.length - filteredFiles.length;
|
|
18408
18460
|
console.log(`\n 📄 Input Files Analysis:`);
|
|
18409
18461
|
if (filteredFiles.length > 0) {
|
|
18410
18462
|
console.log(` ✓ INCLUDED (${filteredFiles.length} files):`);
|
|
18411
|
-
filteredFiles.forEach(file => {
|
|
18463
|
+
filteredFiles.forEach((file) => {
|
|
18412
18464
|
console.log(` - ${file.path}`);
|
|
18413
18465
|
});
|
|
18414
18466
|
}
|
|
18415
|
-
const excludedFiles = allFiles.filter(f => !filteredFiles.includes(f));
|
|
18467
|
+
const excludedFiles = allFiles.filter((f) => !filteredFiles.includes(f));
|
|
18416
18468
|
// Note: Individual excluded files are not displayed to keep output concise
|
|
18417
18469
|
// Only the count is shown in the summary below
|
|
18418
18470
|
console.log(`\n 📝 Output: ${outputFile}`);
|
|
@@ -18444,20 +18496,20 @@ function formatFileSize(sizeInBytes) {
|
|
|
18444
18496
|
* Displays dry-run preview for single extraction
|
|
18445
18497
|
*/
|
|
18446
18498
|
function displayDryRunPreview(srcDir, outFile, allFiles, filteredFiles, types, languages, attrFilters = [], verbose = false) {
|
|
18447
|
-
console.log(
|
|
18499
|
+
console.log("=== Extract Command Dry-Run Preview ===\n");
|
|
18448
18500
|
console.log(`📂 Source Directory: ${srcDir}`);
|
|
18449
|
-
console.log(
|
|
18450
|
-
console.log(` - Type: ${types?.length ? types.join(
|
|
18451
|
-
console.log(` - Language: ${languages?.length ? languages.join(
|
|
18452
|
-
console.log(` - Attributes: ${attrFilters.length ? attrFilters.join(
|
|
18453
|
-
console.log(
|
|
18501
|
+
console.log("🔍 Filters Applied:");
|
|
18502
|
+
console.log(` - Type: ${types?.length ? types.join(", ") : "(none)"}`);
|
|
18503
|
+
console.log(` - Language: ${languages?.length ? languages.join(", ") : "(none)"}`);
|
|
18504
|
+
console.log(` - Attributes: ${attrFilters.length ? attrFilters.join(", ") : "(none)"}\n`);
|
|
18505
|
+
console.log("📄 Input Files Analysis:");
|
|
18454
18506
|
if (filteredFiles.length > 0) {
|
|
18455
18507
|
console.log(` ✓ INCLUDED (${filteredFiles.length} files):`);
|
|
18456
|
-
filteredFiles.forEach(file => {
|
|
18508
|
+
filteredFiles.forEach((file) => {
|
|
18457
18509
|
const attrs = Object.entries(file.attrs)
|
|
18458
|
-
.filter(([key]) => key !==
|
|
18459
|
-
.map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(
|
|
18460
|
-
.join(
|
|
18510
|
+
.filter(([key]) => key !== "content")
|
|
18511
|
+
.map(([key, value]) => `${key}: ${Array.isArray(value) ? value.join(",") : value}`)
|
|
18512
|
+
.join(", ");
|
|
18461
18513
|
console.log(` - ${file.path}`);
|
|
18462
18514
|
if (verbose && attrs) {
|
|
18463
18515
|
console.log(` → ${attrs}`);
|
|
@@ -18467,7 +18519,7 @@ function displayDryRunPreview(srcDir, outFile, allFiles, filteredFiles, types, l
|
|
|
18467
18519
|
}
|
|
18468
18520
|
});
|
|
18469
18521
|
}
|
|
18470
|
-
const excludedFiles = allFiles.filter(f => !filteredFiles.includes(f));
|
|
18522
|
+
const excludedFiles = allFiles.filter((f) => !filteredFiles.includes(f));
|
|
18471
18523
|
// Note: Individual excluded files are not displayed to keep output concise
|
|
18472
18524
|
// Only the count is shown in the summary below
|
|
18473
18525
|
const totalSize = filteredFiles.reduce((sum, f) => sum + f.content.length, 0);
|
|
@@ -18482,26 +18534,26 @@ function displayDryRunPreview(srcDir, outFile, allFiles, filteredFiles, types, l
|
|
|
18482
18534
|
}
|
|
18483
18535
|
async function loadAndFilterFiles(srcDir, types, languages, attrFilters = [], debugLogger) {
|
|
18484
18536
|
debugLogger?.log(`Starting file scan in directory: ${srcDir}`);
|
|
18485
|
-
debugLogger?.time(
|
|
18537
|
+
debugLogger?.time("MarkdownFileScanner.parseMarkdownFiles");
|
|
18486
18538
|
const files = await MarkdownFileScanner.parseMarkdownFiles(srcDir);
|
|
18487
|
-
debugLogger?.timeEnd(
|
|
18539
|
+
debugLogger?.timeEnd("MarkdownFileScanner.parseMarkdownFiles");
|
|
18488
18540
|
debugLogger?.log(`Total markdown files found: ${files.length}`);
|
|
18489
18541
|
if (debugLogger?.isEnabled) {
|
|
18490
|
-
debugLogger.log(
|
|
18542
|
+
debugLogger.log("Files discovered:");
|
|
18491
18543
|
files.forEach((file, index) => {
|
|
18492
18544
|
debugLogger.log(` ${index + 1}. ${file.path}`);
|
|
18493
18545
|
debugLogger.log(` Frontmatter attributes:`, Object.keys(file.attrs));
|
|
18494
18546
|
});
|
|
18495
18547
|
}
|
|
18496
|
-
debugLogger?.time(
|
|
18548
|
+
debugLogger?.time("File filtering");
|
|
18497
18549
|
const filtered = filterFiles(files, { types, languages, attrFilters });
|
|
18498
|
-
debugLogger?.timeEnd(
|
|
18550
|
+
debugLogger?.timeEnd("File filtering");
|
|
18499
18551
|
debugLogger?.log(`Files after filtering: ${filtered.length}`);
|
|
18500
18552
|
if (debugLogger?.isEnabled && filtered.length !== files.length) {
|
|
18501
18553
|
const excludedCount = files.length - filtered.length;
|
|
18502
18554
|
debugLogger.log(`Excluded ${excludedCount} files due to filtering`);
|
|
18503
|
-
const excludedFiles = files.filter(f => !filtered.includes(f));
|
|
18504
|
-
excludedFiles.forEach(file => {
|
|
18555
|
+
const excludedFiles = files.filter((f) => !filtered.includes(f));
|
|
18556
|
+
excludedFiles.forEach((file) => {
|
|
18505
18557
|
debugLogger.log(` Excluded: ${file.path} (attributes: ${JSON.stringify(file.attrs)})`);
|
|
18506
18558
|
});
|
|
18507
18559
|
}
|
|
@@ -18513,26 +18565,26 @@ async function loadAndFilterFiles(srcDir, types, languages, attrFilters = [], de
|
|
|
18513
18565
|
*/
|
|
18514
18566
|
async function loadAndFilterFilesWithDetails(srcDir, types, languages, attrFilters = [], debugLogger) {
|
|
18515
18567
|
debugLogger?.log(`Starting file scan in directory: ${srcDir}`);
|
|
18516
|
-
debugLogger?.time(
|
|
18568
|
+
debugLogger?.time("MarkdownFileScanner.parseMarkdownFiles");
|
|
18517
18569
|
const files = await MarkdownFileScanner.parseMarkdownFiles(srcDir);
|
|
18518
|
-
debugLogger?.timeEnd(
|
|
18570
|
+
debugLogger?.timeEnd("MarkdownFileScanner.parseMarkdownFiles");
|
|
18519
18571
|
debugLogger?.log(`Total markdown files found: ${files.length}`);
|
|
18520
18572
|
if (debugLogger?.isEnabled) {
|
|
18521
|
-
debugLogger.log(
|
|
18573
|
+
debugLogger.log("Files discovered:");
|
|
18522
18574
|
files.forEach((file, index) => {
|
|
18523
18575
|
debugLogger.log(` ${index + 1}. ${file.path}`);
|
|
18524
18576
|
debugLogger.log(` Frontmatter attributes:`, Object.keys(file.attrs));
|
|
18525
18577
|
});
|
|
18526
18578
|
}
|
|
18527
|
-
debugLogger?.time(
|
|
18579
|
+
debugLogger?.time("File filtering");
|
|
18528
18580
|
const filtered = filterFiles(files, { types, languages, attrFilters });
|
|
18529
|
-
debugLogger?.timeEnd(
|
|
18581
|
+
debugLogger?.timeEnd("File filtering");
|
|
18530
18582
|
debugLogger?.log(`Files after filtering: ${filtered.length}`);
|
|
18531
18583
|
if (debugLogger?.isEnabled && filtered.length !== files.length) {
|
|
18532
18584
|
const excludedCount = files.length - filtered.length;
|
|
18533
18585
|
debugLogger.log(`Excluded ${excludedCount} files due to filtering`);
|
|
18534
|
-
const excludedFiles = files.filter(f => !filtered.includes(f));
|
|
18535
|
-
excludedFiles.forEach(file => {
|
|
18586
|
+
const excludedFiles = files.filter((f) => !filtered.includes(f));
|
|
18587
|
+
excludedFiles.forEach((file) => {
|
|
18536
18588
|
debugLogger.log(` Excluded: ${file.path} (attributes: ${JSON.stringify(file.attrs)})`);
|
|
18537
18589
|
});
|
|
18538
18590
|
}
|
|
@@ -18544,21 +18596,21 @@ async function loadAndFilterFilesWithDetails(srcDir, types, languages, attrFilte
|
|
|
18544
18596
|
async function processContentWithMode(outFile, newContent, mode, debugLogger) {
|
|
18545
18597
|
debugLogger?.log(`Processing content with mode: ${mode} for file: ${outFile}`);
|
|
18546
18598
|
if (mode === WriteMode.OVERWRITE) {
|
|
18547
|
-
debugLogger?.log(
|
|
18599
|
+
debugLogger?.log("Using overwrite mode - returning new content as-is");
|
|
18548
18600
|
return newContent;
|
|
18549
18601
|
}
|
|
18550
18602
|
try {
|
|
18551
18603
|
const existing = await fs.readFile(resolvePath(outFile), "utf-8");
|
|
18552
18604
|
debugLogger?.log(`Existing file found with ${existing.length} characters`);
|
|
18553
18605
|
if (mode === WriteMode.APPEND) {
|
|
18554
|
-
debugLogger?.log(
|
|
18606
|
+
debugLogger?.log("Appending new content to existing content");
|
|
18555
18607
|
const needsNewline = !/\n\s*$/.test(existing);
|
|
18556
18608
|
return needsNewline
|
|
18557
18609
|
? `${existing}\n\n${newContent}`
|
|
18558
18610
|
: `${existing}\n${newContent}`;
|
|
18559
18611
|
}
|
|
18560
18612
|
else if (mode === WriteMode.PREPEND) {
|
|
18561
|
-
debugLogger?.log(
|
|
18613
|
+
debugLogger?.log("Prepending new content to existing content");
|
|
18562
18614
|
const needsNewline = !/\n\s*$/.test(newContent);
|
|
18563
18615
|
return needsNewline
|
|
18564
18616
|
? `${newContent}\n\n${existing}`
|
|
@@ -18567,7 +18619,7 @@ async function processContentWithMode(outFile, newContent, mode, debugLogger) {
|
|
|
18567
18619
|
}
|
|
18568
18620
|
catch (error) {
|
|
18569
18621
|
debugLogger?.log(`No existing file found or error reading file:`, error);
|
|
18570
|
-
debugLogger?.log(
|
|
18622
|
+
debugLogger?.log("Proceeding with new content only");
|
|
18571
18623
|
}
|
|
18572
18624
|
return newContent;
|
|
18573
18625
|
}
|
|
@@ -18577,7 +18629,7 @@ async function processContentWithMode(outFile, newContent, mode, debugLogger) {
|
|
|
18577
18629
|
function applyTemplateToObject(obj, templateOptions, templateEngine) {
|
|
18578
18630
|
const result = {};
|
|
18579
18631
|
for (const [key, value] of Object.entries(obj)) {
|
|
18580
|
-
if (typeof value ===
|
|
18632
|
+
if (typeof value === "string") {
|
|
18581
18633
|
// Apply template processing to string values
|
|
18582
18634
|
if (templateEngine.hasTemplateVariables(value)) {
|
|
18583
18635
|
result[key] = templateEngine.renderTemplate(value, templateOptions);
|
|
@@ -18588,11 +18640,11 @@ function applyTemplateToObject(obj, templateOptions, templateEngine) {
|
|
|
18588
18640
|
}
|
|
18589
18641
|
else if (Array.isArray(value)) {
|
|
18590
18642
|
// Process array elements
|
|
18591
|
-
result[key] = value.map(item => typeof item ===
|
|
18643
|
+
result[key] = value.map((item) => typeof item === "string" && templateEngine.hasTemplateVariables(item)
|
|
18592
18644
|
? templateEngine.renderTemplate(item, templateOptions)
|
|
18593
18645
|
: item);
|
|
18594
18646
|
}
|
|
18595
|
-
else if (value && typeof value ===
|
|
18647
|
+
else if (value && typeof value === "object") {
|
|
18596
18648
|
// Recursively process nested objects
|
|
18597
18649
|
result[key] = applyTemplateToObject(value, templateOptions, templateEngine);
|
|
18598
18650
|
}
|
|
@@ -18605,35 +18657,37 @@ function applyTemplateToObject(obj, templateOptions, templateEngine) {
|
|
|
18605
18657
|
}
|
|
18606
18658
|
async function processSingle(options, debugLogger) {
|
|
18607
18659
|
CliOptionValidator.validateExtractOptions(options);
|
|
18608
|
-
const { srcDir, outFile, types, languages, attrFilters, title, mode, attr, vars, envFile, dryRun } = options;
|
|
18660
|
+
const { srcDir, outFile, types, languages, attrFilters, title, mode, attr, vars, envFile, dryRun, } = options;
|
|
18609
18661
|
// Resolve template variables first
|
|
18610
18662
|
const templateEngine = new TemplateEngine();
|
|
18611
18663
|
const variableResolver = new VariableResolver();
|
|
18612
18664
|
let resolvedVariables = {};
|
|
18613
18665
|
// Always resolve variables - even if no CLI variables are provided,
|
|
18614
18666
|
// we still need to check for required variables and use environment variables
|
|
18615
|
-
debugLogger.time(
|
|
18616
|
-
debugLogger.log(
|
|
18667
|
+
debugLogger.time("Variable resolution");
|
|
18668
|
+
debugLogger.log("Resolving template variables", { vars, envFile });
|
|
18617
18669
|
try {
|
|
18618
|
-
const cliVariables = vars
|
|
18670
|
+
const cliVariables = vars
|
|
18671
|
+
? VariableResolver.parseCliVariables(vars)
|
|
18672
|
+
: undefined;
|
|
18619
18673
|
resolvedVariables = await variableResolver.resolveVariables({
|
|
18620
18674
|
cliVariables,
|
|
18621
18675
|
envFile,
|
|
18622
|
-
environmentVariables: true
|
|
18676
|
+
environmentVariables: true,
|
|
18623
18677
|
});
|
|
18624
|
-
debugLogger.log(
|
|
18678
|
+
debugLogger.log("Resolved variables:", Object.keys(resolvedVariables));
|
|
18625
18679
|
}
|
|
18626
18680
|
catch (error) {
|
|
18627
18681
|
throw new Error(`Template variable resolution failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
18628
18682
|
}
|
|
18629
|
-
debugLogger.timeEnd(
|
|
18630
|
-
debugLogger.time(
|
|
18683
|
+
debugLogger.timeEnd("Variable resolution");
|
|
18684
|
+
debugLogger.time("File scanning and filtering");
|
|
18631
18685
|
debugLogger.log(`Scanning directory: ${srcDir}`);
|
|
18632
|
-
debugLogger.log(
|
|
18686
|
+
debugLogger.log("Applied filters:", { types, languages, attrFilters });
|
|
18633
18687
|
// In dry-run mode, we need both all files and filtered files to show exclusion details
|
|
18634
18688
|
if (dryRun) {
|
|
18635
18689
|
const { allFiles, filteredFiles } = await loadAndFilterFilesWithDetails(srcDir, types, languages, attrFilters, debugLogger);
|
|
18636
|
-
debugLogger.timeEnd(
|
|
18690
|
+
debugLogger.timeEnd("File scanning and filtering");
|
|
18637
18691
|
debugLogger.log(`Found ${filteredFiles.length} files after filtering`);
|
|
18638
18692
|
// Check if verbose mode is enabled (via debug flag)
|
|
18639
18693
|
const verbose = debugLogger.isEnabled;
|
|
@@ -18642,42 +18696,42 @@ async function processSingle(options, debugLogger) {
|
|
|
18642
18696
|
return;
|
|
18643
18697
|
}
|
|
18644
18698
|
const filtered = await loadAndFilterFiles(srcDir, types, languages, attrFilters, debugLogger);
|
|
18645
|
-
debugLogger.timeEnd(
|
|
18699
|
+
debugLogger.timeEnd("File scanning and filtering");
|
|
18646
18700
|
debugLogger.log(`Found ${filtered.length} files after filtering`);
|
|
18647
|
-
debugLogger.time(
|
|
18701
|
+
debugLogger.time("Content merging");
|
|
18648
18702
|
let merged = filtered.map((f) => f.content.trim()).join("\n\n");
|
|
18649
18703
|
// Always create templateOptions with strict mode enabled
|
|
18650
18704
|
const templateOptions = {
|
|
18651
18705
|
variables: resolvedVariables,
|
|
18652
|
-
strictMode: true
|
|
18706
|
+
strictMode: true,
|
|
18653
18707
|
};
|
|
18654
18708
|
// Pre-validate required variables before processing
|
|
18655
18709
|
if (templateEngine.hasTemplateVariables(merged)) {
|
|
18656
|
-
debugLogger.time(
|
|
18710
|
+
debugLogger.time("Required variable validation");
|
|
18657
18711
|
const requiredVars = templateEngine.extractRequiredVariables(merged);
|
|
18658
|
-
debugLogger.log(
|
|
18712
|
+
debugLogger.log("Found required variables:", requiredVars);
|
|
18659
18713
|
if (requiredVars.length > 0) {
|
|
18660
18714
|
try {
|
|
18661
18715
|
templateEngine.validateRequiredVariables(merged, resolvedVariables);
|
|
18662
|
-
debugLogger.log(
|
|
18716
|
+
debugLogger.log("All required variables are satisfied");
|
|
18663
18717
|
}
|
|
18664
18718
|
catch (error) {
|
|
18665
|
-
debugLogger.log(
|
|
18719
|
+
debugLogger.log("Required variable validation failed");
|
|
18666
18720
|
throw error;
|
|
18667
18721
|
}
|
|
18668
18722
|
}
|
|
18669
|
-
debugLogger.timeEnd(
|
|
18670
|
-
debugLogger.time(
|
|
18671
|
-
debugLogger.log(
|
|
18723
|
+
debugLogger.timeEnd("Required variable validation");
|
|
18724
|
+
debugLogger.time("Template processing");
|
|
18725
|
+
debugLogger.log("Applying template processing to merged content");
|
|
18672
18726
|
merged = templateEngine.renderTemplate(merged, templateOptions);
|
|
18673
|
-
debugLogger.timeEnd(
|
|
18727
|
+
debugLogger.timeEnd("Template processing");
|
|
18674
18728
|
}
|
|
18675
18729
|
const contentWithTitle = title ? `# ${title}\n\n${merged}` : merged;
|
|
18676
|
-
debugLogger.timeEnd(
|
|
18730
|
+
debugLogger.timeEnd("Content merging");
|
|
18677
18731
|
debugLogger.log(`Generated content size: ${contentWithTitle.length} characters`);
|
|
18678
|
-
debugLogger.time(
|
|
18732
|
+
debugLogger.time("Content mode processing");
|
|
18679
18733
|
const finalContent = await processContentWithMode(outFile, contentWithTitle, mode, debugLogger);
|
|
18680
|
-
debugLogger.timeEnd(
|
|
18734
|
+
debugLogger.timeEnd("Content mode processing");
|
|
18681
18735
|
const resolved = resolvePath(outFile);
|
|
18682
18736
|
const writer = new MarkdownWriter();
|
|
18683
18737
|
debugLogger.log(`Writing to output file: ${resolved}`);
|
|
@@ -18685,21 +18739,21 @@ async function processSingle(options, debugLogger) {
|
|
|
18685
18739
|
let frontMatter = attr ? { ...attr } : {};
|
|
18686
18740
|
// Apply template processing to front matter if needed
|
|
18687
18741
|
if (Object.keys(frontMatter).length > 0) {
|
|
18688
|
-
debugLogger.log(
|
|
18742
|
+
debugLogger.log("Applying template processing to front matter");
|
|
18689
18743
|
frontMatter = applyTemplateToObject(frontMatter, templateOptions, templateEngine);
|
|
18690
18744
|
}
|
|
18691
|
-
debugLogger.log(
|
|
18692
|
-
debugLogger.time(
|
|
18745
|
+
debugLogger.log("Front matter to add:", frontMatter);
|
|
18746
|
+
debugLogger.time("File writing");
|
|
18693
18747
|
// Write file with or without front matter
|
|
18694
18748
|
if (Object.keys(frontMatter).length > 0) {
|
|
18695
18749
|
await writer.writeMarkdownFileWithFrontMatter(resolved, finalContent, frontMatter);
|
|
18696
|
-
debugLogger.log(
|
|
18750
|
+
debugLogger.log("File written with front matter");
|
|
18697
18751
|
}
|
|
18698
18752
|
else {
|
|
18699
18753
|
await writer.writeMarkdownFile(resolved, finalContent);
|
|
18700
|
-
debugLogger.log(
|
|
18754
|
+
debugLogger.log("File written without front matter");
|
|
18701
18755
|
}
|
|
18702
|
-
debugLogger.timeEnd(
|
|
18756
|
+
debugLogger.timeEnd("File writing");
|
|
18703
18757
|
// eslint-disable-next-line no-console
|
|
18704
18758
|
console.log(`Extracted ${filtered.length} files to ${outFile}`);
|
|
18705
18759
|
}
|
|
@@ -18757,7 +18811,7 @@ async function displayVariables(srcDir) {
|
|
|
18757
18811
|
variableMap.set(variable.name, {
|
|
18758
18812
|
required: variable.required,
|
|
18759
18813
|
defaultValue: variable.defaultValue,
|
|
18760
|
-
sources: new Set([file.path])
|
|
18814
|
+
sources: new Set([file.path]),
|
|
18761
18815
|
});
|
|
18762
18816
|
}
|
|
18763
18817
|
}
|
|
@@ -18766,32 +18820,32 @@ async function displayVariables(srcDir) {
|
|
|
18766
18820
|
for (const [name, info] of variableMap.entries()) {
|
|
18767
18821
|
allVariables.push({
|
|
18768
18822
|
variable: name,
|
|
18769
|
-
required: info.required ?
|
|
18770
|
-
defaultValue: info.defaultValue ??
|
|
18771
|
-
sources: Array.from(info.sources)
|
|
18823
|
+
required: info.required ? "Yes" : "No",
|
|
18824
|
+
defaultValue: info.defaultValue ?? "(none)",
|
|
18825
|
+
sources: Array.from(info.sources),
|
|
18772
18826
|
});
|
|
18773
18827
|
}
|
|
18774
18828
|
if (allVariables.length === 0) {
|
|
18775
18829
|
// eslint-disable-next-line no-console
|
|
18776
|
-
console.log(
|
|
18830
|
+
console.log("No template variables found in source files.");
|
|
18777
18831
|
// eslint-disable-next-line no-console
|
|
18778
|
-
console.log(
|
|
18832
|
+
console.log("\nTemplate variables use the syntax ${VAR:default} for optional variables and !{VAR} for required variables.");
|
|
18779
18833
|
return;
|
|
18780
18834
|
}
|
|
18781
18835
|
// eslint-disable-next-line no-console
|
|
18782
|
-
console.log(
|
|
18836
|
+
console.log("Template variables found in source files:\n");
|
|
18783
18837
|
// Calculate column widths with reasonable limits
|
|
18784
|
-
const maxVariableLength = Math.max(8, Math.min(30, ...allVariables.map(v => v.variable.length)));
|
|
18838
|
+
const maxVariableLength = Math.max(8, Math.min(30, ...allVariables.map((v) => v.variable.length)));
|
|
18785
18839
|
const maxRequiredLength = 8; // "Required" header length
|
|
18786
|
-
const maxDefaultLength = Math.max(12, Math.min(30, ...allVariables.map(v => v.defaultValue.length)));
|
|
18840
|
+
const maxDefaultLength = Math.max(12, Math.min(30, ...allVariables.map((v) => v.defaultValue.length)));
|
|
18787
18841
|
// Header
|
|
18788
|
-
const variableHeader =
|
|
18789
|
-
const requiredHeader =
|
|
18790
|
-
const defaultHeader =
|
|
18842
|
+
const variableHeader = "Variable".padEnd(maxVariableLength);
|
|
18843
|
+
const requiredHeader = "Required".padEnd(maxRequiredLength);
|
|
18844
|
+
const defaultHeader = "Default".padEnd(maxDefaultLength);
|
|
18791
18845
|
// eslint-disable-next-line no-console
|
|
18792
18846
|
console.log(`${variableHeader} | ${requiredHeader} | ${defaultHeader}`);
|
|
18793
18847
|
// eslint-disable-next-line no-console
|
|
18794
|
-
console.log(
|
|
18848
|
+
console.log("-".repeat(maxVariableLength + maxRequiredLength + maxDefaultLength + 6));
|
|
18795
18849
|
// Sort by variable name for consistent output
|
|
18796
18850
|
allVariables.sort((a, b) => a.variable.localeCompare(b.variable));
|
|
18797
18851
|
// Data rows
|
|
@@ -18803,7 +18857,7 @@ async function displayVariables(srcDir) {
|
|
|
18803
18857
|
console.log(`${variableCol} | ${requiredCol} | ${defaultCol}`);
|
|
18804
18858
|
});
|
|
18805
18859
|
// eslint-disable-next-line no-console
|
|
18806
|
-
console.log(
|
|
18860
|
+
console.log("\nNote: Template variables use ${VAR:default} syntax for optional variables and !{VAR} for required variables.");
|
|
18807
18861
|
}
|
|
18808
18862
|
/**
|
|
18809
18863
|
* Handles the extract command with options parsing
|
|
@@ -18811,7 +18865,7 @@ async function displayVariables(srcDir) {
|
|
|
18811
18865
|
async function handleExtractCommand(options) {
|
|
18812
18866
|
const isDebugMode = Boolean(options.debug || options.verbose);
|
|
18813
18867
|
const debugLogger = new DebugLogger(isDebugMode);
|
|
18814
|
-
debugLogger.log(
|
|
18868
|
+
debugLogger.log("Starting extract command with options:", {
|
|
18815
18869
|
src: options.src,
|
|
18816
18870
|
out: options.out,
|
|
18817
18871
|
type: options.type,
|
|
@@ -18820,22 +18874,22 @@ async function handleExtractCommand(options) {
|
|
|
18820
18874
|
title: options.title,
|
|
18821
18875
|
mode: options.mode,
|
|
18822
18876
|
recipeCount: options.recipe?.length || 0,
|
|
18823
|
-
debug: isDebugMode
|
|
18877
|
+
debug: isDebugMode,
|
|
18824
18878
|
});
|
|
18825
18879
|
const extractOptions = {
|
|
18826
18880
|
srcDir: resolveDefaultSrcDir(options.src),
|
|
18827
|
-
outFile: options.out ||
|
|
18881
|
+
outFile: options.out || "./out/instruction.md",
|
|
18828
18882
|
types: parseCommaSeparated(options.type),
|
|
18829
18883
|
languages: parseCommaSeparated(options.language),
|
|
18830
18884
|
attrFilters: parseCommaSeparated(options.attr) || [],
|
|
18831
18885
|
title: options.title,
|
|
18832
|
-
mode: parseWriteMode(options.mode ||
|
|
18886
|
+
mode: parseWriteMode(options.mode || "overwrite"),
|
|
18833
18887
|
debug: isDebugMode,
|
|
18834
18888
|
vars: options.vars,
|
|
18835
18889
|
envFile: options.envFile,
|
|
18836
18890
|
dryRun: options.dryRun,
|
|
18837
18891
|
};
|
|
18838
|
-
debugLogger.log(
|
|
18892
|
+
debugLogger.log("Resolved extract options:", extractOptions);
|
|
18839
18893
|
if (options.recipe && options.recipe.length > 0) {
|
|
18840
18894
|
debugLogger.log(`Processing ${options.recipe.length} recipe(s):`, options.recipe);
|
|
18841
18895
|
// Validate all recipe files exist before processing starts
|
|
@@ -18850,7 +18904,7 @@ async function handleExtractCommand(options) {
|
|
|
18850
18904
|
}
|
|
18851
18905
|
}
|
|
18852
18906
|
else {
|
|
18853
|
-
debugLogger.log(
|
|
18907
|
+
debugLogger.log("Processing single extraction without recipe");
|
|
18854
18908
|
// For single extraction, apply baseDir to outFile if provided
|
|
18855
18909
|
if (options.baseDir) {
|
|
18856
18910
|
extractOptions.outFile = resolveOutputPath(extractOptions.outFile, options.baseDir, undefined, undefined, undefined);
|
|
@@ -18895,10 +18949,10 @@ async function processMultipleRecipes(recipePaths, baseOptions, debugLogger, cli
|
|
|
18895
18949
|
* Reads template files and extracts frontmatter for inheritance
|
|
18896
18950
|
*/
|
|
18897
18951
|
async function loadTemplateFrontmatter(srcDir, types, languages, attrFilters, debugLogger) {
|
|
18898
|
-
debugLogger?.log(
|
|
18952
|
+
debugLogger?.log("Loading template frontmatter for inheritance");
|
|
18899
18953
|
const templateFiles = await loadAndFilterFiles(srcDir, types, languages, attrFilters, debugLogger);
|
|
18900
18954
|
debugLogger?.log(`Found ${templateFiles.length} template files for frontmatter inheritance`);
|
|
18901
|
-
return templateFiles.map(file => file.attrs);
|
|
18955
|
+
return templateFiles.map((file) => file.attrs);
|
|
18902
18956
|
}
|
|
18903
18957
|
/**
|
|
18904
18958
|
* Merges frontmatter values from multiple templates according to merge rules:
|
|
@@ -18908,8 +18962,8 @@ async function loadTemplateFrontmatter(srcDir, types, languages, attrFilters, de
|
|
|
18908
18962
|
*/
|
|
18909
18963
|
function mergeTemplateFrontmatterValues(templateFrontmatters, fieldName) {
|
|
18910
18964
|
const values = templateFrontmatters
|
|
18911
|
-
.map(fm => fm[fieldName])
|
|
18912
|
-
.filter(val => val !== undefined && val !== null);
|
|
18965
|
+
.map((fm) => fm[fieldName])
|
|
18966
|
+
.filter((val) => val !== undefined && val !== null);
|
|
18913
18967
|
if (values.length === 0) {
|
|
18914
18968
|
return undefined;
|
|
18915
18969
|
}
|
|
@@ -18917,12 +18971,15 @@ function mergeTemplateFrontmatterValues(templateFrontmatters, fieldName) {
|
|
|
18917
18971
|
return values[0];
|
|
18918
18972
|
}
|
|
18919
18973
|
// Check if all values are strings
|
|
18920
|
-
if (values.every(val => typeof val ===
|
|
18974
|
+
if (values.every((val) => typeof val === "string")) {
|
|
18921
18975
|
// Split each string by newlines, flatten, remove duplicates while preserving order
|
|
18922
18976
|
const allLines = [];
|
|
18923
18977
|
const seen = new Set();
|
|
18924
18978
|
for (const value of values) {
|
|
18925
|
-
const lines = value
|
|
18979
|
+
const lines = value
|
|
18980
|
+
.split("\n")
|
|
18981
|
+
.map((line) => line.trim())
|
|
18982
|
+
.filter((line) => line.length > 0);
|
|
18926
18983
|
for (const line of lines) {
|
|
18927
18984
|
if (!seen.has(line)) {
|
|
18928
18985
|
seen.add(line);
|
|
@@ -18930,10 +18987,10 @@ function mergeTemplateFrontmatterValues(templateFrontmatters, fieldName) {
|
|
|
18930
18987
|
}
|
|
18931
18988
|
}
|
|
18932
18989
|
}
|
|
18933
|
-
return allLines.join(
|
|
18990
|
+
return allLines.join("\n");
|
|
18934
18991
|
}
|
|
18935
18992
|
// Check if all values are arrays
|
|
18936
|
-
if (values.every(val => Array.isArray(val))) {
|
|
18993
|
+
if (values.every((val) => Array.isArray(val))) {
|
|
18937
18994
|
return values.flat();
|
|
18938
18995
|
}
|
|
18939
18996
|
// For objects and mixed types, use the last value
|
|
@@ -18943,14 +19000,14 @@ function mergeTemplateFrontmatterValues(templateFrontmatters, fieldName) {
|
|
|
18943
19000
|
* Processes @ syntax in frontmatter to inherit from template files
|
|
18944
19001
|
*/
|
|
18945
19002
|
async function processFrontmatterInheritance(frontmatter, srcDir, types, languages, attrFilters, debugLogger) {
|
|
18946
|
-
if (!frontmatter || typeof frontmatter !==
|
|
19003
|
+
if (!frontmatter || typeof frontmatter !== "object") {
|
|
18947
19004
|
return {};
|
|
18948
19005
|
}
|
|
18949
19006
|
const result = {};
|
|
18950
19007
|
const inheritanceFields = [];
|
|
18951
19008
|
// Separate inheritance fields (@ syntax) from regular fields
|
|
18952
19009
|
for (const [key, value] of Object.entries(frontmatter)) {
|
|
18953
|
-
if (key.startsWith(
|
|
19010
|
+
if (key.startsWith("@") && value === true) {
|
|
18954
19011
|
inheritanceFields.push(key.slice(1)); // Remove @ prefix
|
|
18955
19012
|
debugLogger?.log(`Found inheritance field: ${key} -> ${key.slice(1)}`);
|
|
18956
19013
|
}
|
|
@@ -18960,14 +19017,14 @@ async function processFrontmatterInheritance(frontmatter, srcDir, types, languag
|
|
|
18960
19017
|
}
|
|
18961
19018
|
// If no inheritance fields, return as-is
|
|
18962
19019
|
if (inheritanceFields.length === 0) {
|
|
18963
|
-
debugLogger?.log(
|
|
19020
|
+
debugLogger?.log("No frontmatter inheritance fields found");
|
|
18964
19021
|
return result;
|
|
18965
19022
|
}
|
|
18966
19023
|
debugLogger?.log(`Processing ${inheritanceFields.length} inheritance fields:`, inheritanceFields);
|
|
18967
19024
|
// Load template frontmatters
|
|
18968
19025
|
const templateFrontmatters = await loadTemplateFrontmatter(srcDir, types, languages, attrFilters, debugLogger);
|
|
18969
19026
|
if (templateFrontmatters.length === 0) {
|
|
18970
|
-
debugLogger?.log(
|
|
19027
|
+
debugLogger?.log("No template files found for inheritance");
|
|
18971
19028
|
return result;
|
|
18972
19029
|
}
|
|
18973
19030
|
// Process each inheritance field
|
|
@@ -18981,7 +19038,7 @@ async function processFrontmatterInheritance(frontmatter, srcDir, types, languag
|
|
|
18981
19038
|
debugLogger?.log(`No value found for inherited field ${fieldName} in templates`);
|
|
18982
19039
|
}
|
|
18983
19040
|
}
|
|
18984
|
-
debugLogger?.log(
|
|
19041
|
+
debugLogger?.log("Frontmatter after inheritance processing:", Object.keys(result));
|
|
18985
19042
|
return result;
|
|
18986
19043
|
}
|
|
18987
19044
|
/**
|
|
@@ -18991,44 +19048,44 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
18991
19048
|
const resolvedPath = resolveRecipePath(recipePath);
|
|
18992
19049
|
debugLogger?.log(`Processing recipe at path: ${resolvedPath}`);
|
|
18993
19050
|
try {
|
|
18994
|
-
debugLogger?.time(
|
|
19051
|
+
debugLogger?.time("Recipe file reading and parsing");
|
|
18995
19052
|
const content = await fs.readFile(resolvedPath, "utf-8");
|
|
18996
19053
|
const data = YAML.parse(content);
|
|
18997
|
-
debugLogger?.timeEnd(
|
|
19054
|
+
debugLogger?.timeEnd("Recipe file reading and parsing");
|
|
18998
19055
|
if (!Array.isArray(data?.recipe)) {
|
|
18999
19056
|
throw new Error("Invalid recipe file: 'recipe' array not found");
|
|
19000
19057
|
}
|
|
19001
19058
|
// Validate recipe structure and show warnings
|
|
19002
|
-
debugLogger?.time(
|
|
19059
|
+
debugLogger?.time("Recipe validation");
|
|
19003
19060
|
try {
|
|
19004
19061
|
const validator = await getRecipeValidator();
|
|
19005
19062
|
const validationResult = validator.validateRecipe(data);
|
|
19006
19063
|
// Always log validation warnings to help users improve their recipes
|
|
19007
19064
|
if (validationResult.warnings.length > 0) {
|
|
19008
19065
|
console.warn(`\nRecipe validation warnings for '${recipePath}':`);
|
|
19009
|
-
validationResult.warnings.forEach(warning => {
|
|
19066
|
+
validationResult.warnings.forEach((warning) => {
|
|
19010
19067
|
console.warn(` ${warning}`);
|
|
19011
19068
|
});
|
|
19012
|
-
console.warn(
|
|
19069
|
+
console.warn(" These warnings do not prevent recipe execution but may indicate configuration issues.\n");
|
|
19013
19070
|
}
|
|
19014
19071
|
// Log errors but don't fail (maintain backward compatibility)
|
|
19015
19072
|
if (validationResult.errors.length > 0) {
|
|
19016
|
-
debugLogger?.log(
|
|
19073
|
+
debugLogger?.log("Recipe validation errors (non-blocking):", validationResult.errors);
|
|
19017
19074
|
}
|
|
19018
19075
|
}
|
|
19019
19076
|
catch (validationError) {
|
|
19020
19077
|
// Don't fail recipe processing if validation fails
|
|
19021
|
-
debugLogger?.log(
|
|
19078
|
+
debugLogger?.log("Recipe validation failed:", validationError);
|
|
19022
19079
|
}
|
|
19023
|
-
debugLogger?.timeEnd(
|
|
19080
|
+
debugLogger?.timeEnd("Recipe validation");
|
|
19024
19081
|
debugLogger?.log(`Recipe contains ${data.recipe.length} items`);
|
|
19025
19082
|
// Read recipe config for baseDir
|
|
19026
19083
|
const recipeBaseDir = data.config?.baseDir;
|
|
19027
|
-
debugLogger?.log(
|
|
19084
|
+
debugLogger?.log("Recipe config:", { baseDir: recipeBaseDir });
|
|
19028
19085
|
// Expand any imports in the recipe
|
|
19029
|
-
debugLogger?.time(
|
|
19086
|
+
debugLogger?.time("Recipe import expansion");
|
|
19030
19087
|
const expandedRecipe = await expandRecipeImports(data.recipe, resolvedPath, debugLogger);
|
|
19031
|
-
debugLogger?.timeEnd(
|
|
19088
|
+
debugLogger?.timeEnd("Recipe import expansion");
|
|
19032
19089
|
debugLogger?.log(`After import expansion: ${expandedRecipe.length} items`);
|
|
19033
19090
|
// If dry-run mode, display preview and exit
|
|
19034
19091
|
if (baseOptions.dryRun) {
|
|
@@ -19043,7 +19100,7 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
19043
19100
|
out: item.out,
|
|
19044
19101
|
type: item.type,
|
|
19045
19102
|
language: item.language,
|
|
19046
|
-
mode: item.mode
|
|
19103
|
+
mode: item.mode,
|
|
19047
19104
|
});
|
|
19048
19105
|
// Priority: CLI --out option > recipe item.out > baseOptions.outFile default
|
|
19049
19106
|
const itemOut = cliOutFile || item.out || baseOptions.outFile;
|
|
@@ -19054,12 +19111,14 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
19054
19111
|
fallback: baseOptions.outFile,
|
|
19055
19112
|
cliBaseDir,
|
|
19056
19113
|
importBaseDir: item._importBaseDir,
|
|
19057
|
-
recipeBaseDir
|
|
19114
|
+
recipeBaseDir,
|
|
19058
19115
|
});
|
|
19059
19116
|
// Generate the content that would be written to check for duplicates
|
|
19060
|
-
const { srcDir, types, languages, attrFilters, title, attr, vars, envFile } = baseOptions;
|
|
19117
|
+
const { srcDir, types, languages, attrFilters, title, attr, vars, envFile, } = baseOptions;
|
|
19061
19118
|
const itemTypes = item.type ? parseCommaSeparated(item.type) : types;
|
|
19062
|
-
const itemLanguages = item.language
|
|
19119
|
+
const itemLanguages = item.language
|
|
19120
|
+
? parseCommaSeparated(item.language)
|
|
19121
|
+
: languages;
|
|
19063
19122
|
const itemTitle = item.title || title;
|
|
19064
19123
|
// Combine base attrFilters with item-specific filters
|
|
19065
19124
|
let combinedAttrFilters = [...attrFilters];
|
|
@@ -19073,43 +19132,52 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
19073
19132
|
itemLanguages,
|
|
19074
19133
|
combinedAttrFilters,
|
|
19075
19134
|
baseAttrFilters: attrFilters,
|
|
19076
|
-
itemFilters: item.filters ||
|
|
19135
|
+
itemFilters: item.filters || "none",
|
|
19077
19136
|
});
|
|
19078
19137
|
// Resolve template variables for this item (merge import variables, recipe item variables, and CLI variables)
|
|
19079
19138
|
let itemVars = vars;
|
|
19080
19139
|
if (item.variables || item._importVariables) {
|
|
19081
|
-
const cliVariables = vars
|
|
19140
|
+
const cliVariables = vars
|
|
19141
|
+
? VariableResolver.parseCliVariables(vars)
|
|
19142
|
+
: {};
|
|
19082
19143
|
const importVariables = item._importVariables || {};
|
|
19083
19144
|
const itemVariables = item.variables || {};
|
|
19084
19145
|
// Merge with priority: CLI > item > import
|
|
19085
|
-
const mergedVariables = {
|
|
19146
|
+
const mergedVariables = {
|
|
19147
|
+
...importVariables,
|
|
19148
|
+
...itemVariables,
|
|
19149
|
+
...cliVariables,
|
|
19150
|
+
};
|
|
19086
19151
|
itemVars = Object.entries(mergedVariables)
|
|
19087
19152
|
.map(([key, value]) => `${key}=${value}`)
|
|
19088
|
-
.join(
|
|
19153
|
+
.join(",");
|
|
19089
19154
|
debugLogger?.log(`Merged variables for item ${index + 1}:`, {
|
|
19090
19155
|
importVariables,
|
|
19091
19156
|
itemVariables,
|
|
19092
19157
|
cliVariables,
|
|
19093
|
-
merged: mergedVariables
|
|
19158
|
+
merged: mergedVariables,
|
|
19094
19159
|
});
|
|
19095
19160
|
}
|
|
19096
19161
|
// Resolve source directory for this item with priority: CLI --src > item.src > import.src > default
|
|
19097
|
-
const itemSrcDir = resolveItemSrcDir(cliSrc, item.src, item._importSrc);
|
|
19162
|
+
const itemSrcDir = resolveItemSrcDir(cliSrc, item.src, item._importSrc, item._recipePath);
|
|
19098
19163
|
debugLogger?.log(`Resolved source directory for item ${index + 1}:`, {
|
|
19099
19164
|
cliSrc,
|
|
19100
19165
|
itemSrc: item.src,
|
|
19101
19166
|
importSrc: item._importSrc,
|
|
19102
|
-
|
|
19167
|
+
recipePath: item._recipePath,
|
|
19168
|
+
resolved: itemSrcDir,
|
|
19103
19169
|
});
|
|
19104
19170
|
debugLogger?.time(`Content generation for item ${index + 1}`);
|
|
19105
19171
|
const filtered = await loadAndFilterFiles(itemSrcDir, itemTypes, itemLanguages, combinedAttrFilters, debugLogger);
|
|
19106
19172
|
const merged = filtered.map((f) => f.content.trim()).join("\n\n");
|
|
19107
|
-
const contentWithTitle = itemTitle
|
|
19173
|
+
const contentWithTitle = itemTitle
|
|
19174
|
+
? `# ${itemTitle}\n\n${merged}`
|
|
19175
|
+
: merged;
|
|
19108
19176
|
debugLogger?.timeEnd(`Content generation for item ${index + 1}`);
|
|
19109
19177
|
// Check if this exact content has already been written to this file
|
|
19110
|
-
debugLogger?.time(
|
|
19178
|
+
debugLogger?.time("Duplicate content check");
|
|
19111
19179
|
const isDuplicate = localTracker.hasContent(outputFile, contentWithTitle);
|
|
19112
|
-
debugLogger?.timeEnd(
|
|
19180
|
+
debugLogger?.timeEnd("Duplicate content check");
|
|
19113
19181
|
if (isDuplicate) {
|
|
19114
19182
|
debugLogger?.log(`Skipping duplicate content for ${outputFile}`);
|
|
19115
19183
|
// eslint-disable-next-line no-console
|
|
@@ -19158,11 +19226,11 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
19158
19226
|
}
|
|
19159
19227
|
}
|
|
19160
19228
|
catch (error) {
|
|
19161
|
-
if (recipePath.startsWith(
|
|
19229
|
+
if (recipePath.startsWith(":")) {
|
|
19162
19230
|
const presetName = recipePath.slice(1);
|
|
19163
19231
|
debugLogger?.log(`Error processing preset ${presetName}:`, error);
|
|
19164
19232
|
// Only treat ENOENT errors as "preset not found"
|
|
19165
|
-
if (error instanceof Error && error.message.includes(
|
|
19233
|
+
if (error instanceof Error && error.message.includes("ENOENT")) {
|
|
19166
19234
|
throw new Error(`Preset ':${presetName}' not found. Use 'ai-rules presets' to list available presets.`);
|
|
19167
19235
|
}
|
|
19168
19236
|
// For other errors (like variable validation), add context but preserve the original error
|