@aramassa/ai-rules 0.1.1-npmjs.20250910072942 → 0.1.5
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/chatmodes/Bug Reproduce.md +296 -0
- package/artifact/chatmodes/Instruction Improve.md +17 -3
- package/artifact/chatmodes/Planning.md +234 -20
- package/artifact/instructions/planning.md +188 -7
- package/artifact/instructions/rules/development/chrome-extension-general.md +121 -0
- package/artifact/instructions/rules/development/chrome-extension-html.md +157 -0
- package/artifact/instructions/rules/development/chrome-extension-javascript.md +190 -0
- package/artifact/instructions/rules/development/chrome-extension-manifest.md +110 -0
- package/artifact/instructions/rules/development/electron.md +258 -0
- package/artifact/instructions/rules/test/environment-independent-paths.md +206 -0
- package/artifact/prompts/todo_plans.prompt.md +47 -0
- package/dist/cli.d.ts +4 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +273 -19
- package/dist/utils/pathExpansion.d.ts +24 -0
- package/dist/utils/pathExpansion.d.ts.map +1 -0
- package/dist/utils/pathExpansion.js +67 -0
- package/package.json +5 -3
- package/presets/README.md +26 -0
- package/presets/chatmodes.yaml +16 -51
- package/presets/chrome-extension.yaml +50 -0
- package/presets/electron.yaml +11 -0
- package/presets/prompts/todo-planning.yaml +8 -0
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import require$$3 from 'node:fs';
|
|
|
10
10
|
import require$$4 from 'node:process';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import { createHash } from 'crypto';
|
|
13
|
+
import { homedir } from 'os';
|
|
13
14
|
|
|
14
15
|
/******************************************************************************
|
|
15
16
|
Copyright (c) Microsoft Corporation.
|
|
@@ -10042,6 +10043,53 @@ class VariableResolver {
|
|
|
10042
10043
|
}
|
|
10043
10044
|
}
|
|
10044
10045
|
|
|
10046
|
+
/**
|
|
10047
|
+
* Path expansion utility that supports tilde (~) and environment variable expansion
|
|
10048
|
+
*
|
|
10049
|
+
* Features:
|
|
10050
|
+
* - Tilde expansion: ~/path -> /Users/username/path (Unix-like systems only)
|
|
10051
|
+
* - Environment variable expansion: $HOME/path -> /Users/username/path
|
|
10052
|
+
* - Environment variable expansion: ${VAR}/path -> /value/path
|
|
10053
|
+
* - Complex combinations: ~/projects/${PROJECT_NAME}/file.md
|
|
10054
|
+
*
|
|
10055
|
+
* @param inputPath - Path to expand and resolve
|
|
10056
|
+
* @returns Expanded and resolved absolute path
|
|
10057
|
+
* @throws Error if required environment variables are missing
|
|
10058
|
+
*/
|
|
10059
|
+
function resolvePath(inputPath) {
|
|
10060
|
+
if (!inputPath || typeof inputPath !== 'string') {
|
|
10061
|
+
throw new Error('Path must be a non-empty string');
|
|
10062
|
+
}
|
|
10063
|
+
let expandedPath = inputPath;
|
|
10064
|
+
// 1. Handle tilde expansion first (Unix-like systems only)
|
|
10065
|
+
if (expandedPath.startsWith('~/')) {
|
|
10066
|
+
// Check if we're on Windows
|
|
10067
|
+
if (process.platform === 'win32') {
|
|
10068
|
+
throw new Error('Tilde (~) expansion is not supported on Windows. Use %USERPROFILE% or environment variables instead.');
|
|
10069
|
+
}
|
|
10070
|
+
const homeDir = homedir();
|
|
10071
|
+
expandedPath = path.join(homeDir, expandedPath.slice(2));
|
|
10072
|
+
}
|
|
10073
|
+
else if (expandedPath === '~') {
|
|
10074
|
+
if (process.platform === 'win32') {
|
|
10075
|
+
throw new Error('Tilde (~) expansion is not supported on Windows. Use %USERPROFILE% or environment variables instead.');
|
|
10076
|
+
}
|
|
10077
|
+
expandedPath = homedir();
|
|
10078
|
+
}
|
|
10079
|
+
// 2. Handle environment variable expansion
|
|
10080
|
+
// Support both $VAR and ${VAR} syntax
|
|
10081
|
+
expandedPath = expandedPath.replace(/\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, bracedVar, simpleVar) => {
|
|
10082
|
+
const varName = bracedVar || simpleVar;
|
|
10083
|
+
const envValue = process.env[varName];
|
|
10084
|
+
if (envValue === undefined) {
|
|
10085
|
+
throw new Error(`Environment variable '${varName}' is not defined`);
|
|
10086
|
+
}
|
|
10087
|
+
return envValue;
|
|
10088
|
+
});
|
|
10089
|
+
// 3. Resolve to absolute path
|
|
10090
|
+
return path.resolve(expandedPath);
|
|
10091
|
+
}
|
|
10092
|
+
|
|
10045
10093
|
const EXCLUDED_STATS_ATTRS = new Set(["human-instruction", "title"]);
|
|
10046
10094
|
const MAX_IMPORT_DEPTH = 10;
|
|
10047
10095
|
/**
|
|
@@ -10094,7 +10142,7 @@ class ContentTracker {
|
|
|
10094
10142
|
* Checks if the same content has already been written to the file
|
|
10095
10143
|
*/
|
|
10096
10144
|
hasContent(outputFile, content) {
|
|
10097
|
-
const resolvedFile =
|
|
10145
|
+
const resolvedFile = resolvePath(outputFile);
|
|
10098
10146
|
const contentHash = this.generateContentHash(content);
|
|
10099
10147
|
const fileHashes = this.fileContentHashes.get(resolvedFile);
|
|
10100
10148
|
return fileHashes ? fileHashes.has(contentHash) : false;
|
|
@@ -10103,7 +10151,7 @@ class ContentTracker {
|
|
|
10103
10151
|
* Records that content has been written to a file
|
|
10104
10152
|
*/
|
|
10105
10153
|
addContent(outputFile, content) {
|
|
10106
|
-
const resolvedFile =
|
|
10154
|
+
const resolvedFile = resolvePath(outputFile);
|
|
10107
10155
|
const contentHash = this.generateContentHash(content);
|
|
10108
10156
|
if (!this.fileContentHashes.has(resolvedFile)) {
|
|
10109
10157
|
this.fileContentHashes.set(resolvedFile, new Set());
|
|
@@ -10114,7 +10162,7 @@ class ContentTracker {
|
|
|
10114
10162
|
* Checks if a file has been written to (for auto-append logic)
|
|
10115
10163
|
*/
|
|
10116
10164
|
hasFile(outputFile) {
|
|
10117
|
-
const resolvedFile =
|
|
10165
|
+
const resolvedFile = resolvePath(outputFile);
|
|
10118
10166
|
return this.fileContentHashes.has(resolvedFile);
|
|
10119
10167
|
}
|
|
10120
10168
|
/**
|
|
@@ -10160,8 +10208,56 @@ function resolveDefaultSrcDir(providedSrc) {
|
|
|
10160
10208
|
return path.join(packageRoot, 'artifact');
|
|
10161
10209
|
}
|
|
10162
10210
|
/**
|
|
10163
|
-
* Resolves
|
|
10211
|
+
* Resolves output path with baseDir support following priority order:
|
|
10212
|
+
* 1. CLI baseDir option (highest priority)
|
|
10213
|
+
* 2. Import-level baseDir (new priority level)
|
|
10214
|
+
* 3. Recipe config.baseDir
|
|
10215
|
+
* 4. Existing behavior (relative to recipe file or current directory)
|
|
10164
10216
|
*/
|
|
10217
|
+
function resolveOutputPath(itemOut, cliBaseDir, importBaseDir, recipeBaseDir, recipePath) {
|
|
10218
|
+
// Check if the itemOut contains environment variables or tilde - if so, try to expand
|
|
10219
|
+
let processedItemOut = itemOut;
|
|
10220
|
+
if (itemOut.includes('$') || itemOut.startsWith('~')) {
|
|
10221
|
+
try {
|
|
10222
|
+
processedItemOut = resolvePath(itemOut);
|
|
10223
|
+
// If expansion resulted in an absolute path, use it as-is (ignore baseDir)
|
|
10224
|
+
if (path.isAbsolute(processedItemOut)) {
|
|
10225
|
+
return processedItemOut;
|
|
10226
|
+
}
|
|
10227
|
+
}
|
|
10228
|
+
catch (error) {
|
|
10229
|
+
// If expansion fails, continue with original itemOut
|
|
10230
|
+
processedItemOut = itemOut;
|
|
10231
|
+
}
|
|
10232
|
+
}
|
|
10233
|
+
// If output path is already absolute, use it as-is (ignore baseDir)
|
|
10234
|
+
if (path.isAbsolute(processedItemOut)) {
|
|
10235
|
+
return processedItemOut;
|
|
10236
|
+
}
|
|
10237
|
+
// Determine effective baseDir with priority: CLI > Import > Recipe > undefined
|
|
10238
|
+
const effectiveBaseDir = cliBaseDir || importBaseDir || recipeBaseDir;
|
|
10239
|
+
if (effectiveBaseDir) {
|
|
10240
|
+
// Use baseDir + relative path
|
|
10241
|
+
const expandedBaseDir = resolvePath(effectiveBaseDir);
|
|
10242
|
+
return path.resolve(expandedBaseDir, processedItemOut);
|
|
10243
|
+
}
|
|
10244
|
+
// Existing behavior: resolve relative to recipe file directory or current directory
|
|
10245
|
+
// For preset files (located in presets/ directory), resolve relative to project root instead
|
|
10246
|
+
let baseDirectory = '.';
|
|
10247
|
+
if (recipePath) {
|
|
10248
|
+
const packageRoot = findPackageRoot();
|
|
10249
|
+
const presetsDir = path.join(packageRoot, 'presets');
|
|
10250
|
+
// If the recipe is in the presets directory, use project root as base
|
|
10251
|
+
if (path.dirname(recipePath) === presetsDir) {
|
|
10252
|
+
baseDirectory = process.cwd();
|
|
10253
|
+
}
|
|
10254
|
+
else {
|
|
10255
|
+
// For regular recipe files, use recipe file directory as base
|
|
10256
|
+
baseDirectory = path.dirname(recipePath);
|
|
10257
|
+
}
|
|
10258
|
+
}
|
|
10259
|
+
return path.resolve(baseDirectory, processedItemOut);
|
|
10260
|
+
}
|
|
10165
10261
|
function resolveRecipePath(recipePath) {
|
|
10166
10262
|
// If recipe starts with ':', resolve to package preset
|
|
10167
10263
|
if (recipePath.startsWith(':')) {
|
|
@@ -10260,11 +10356,26 @@ async function expandRecipeImports(items, currentPath, debugLogger, visited = ne
|
|
|
10260
10356
|
throw new Error(`Invalid imported recipe file '${importPath}': 'recipe' array not found`);
|
|
10261
10357
|
}
|
|
10262
10358
|
debugLogger?.log(`Import contains ${importData.recipe.length} items`);
|
|
10359
|
+
// Check if this import item has an import-level baseDir
|
|
10360
|
+
const importLevelBaseDir = item.baseDir;
|
|
10361
|
+
if (importLevelBaseDir) {
|
|
10362
|
+
debugLogger?.log(`Import has baseDir: ${importLevelBaseDir}`);
|
|
10363
|
+
}
|
|
10263
10364
|
// Recursively expand imports in the imported recipe
|
|
10264
10365
|
debugLogger?.time(`Expanding nested imports in: ${resolvedImportPath}`);
|
|
10265
10366
|
const expandedImported = await expandRecipeImports(importData.recipe, resolvedImportPath, debugLogger, newVisited, imported, depth + 1);
|
|
10266
10367
|
debugLogger?.timeEnd(`Expanding nested imports in: ${resolvedImportPath}`);
|
|
10267
10368
|
debugLogger?.log(`Nested expansion yielded ${expandedImported.length} items`);
|
|
10369
|
+
// If import has baseDir, tag all expanded items with it
|
|
10370
|
+
if (importLevelBaseDir) {
|
|
10371
|
+
expandedImported.forEach(expandedItem => {
|
|
10372
|
+
// Only set _importBaseDir if not already set (to preserve nested import baseDir priority)
|
|
10373
|
+
if (!expandedItem._importBaseDir) {
|
|
10374
|
+
expandedItem._importBaseDir = importLevelBaseDir;
|
|
10375
|
+
debugLogger?.log(`Tagged item '${expandedItem.title || expandedItem.out || 'untitled'}' with import baseDir: ${importLevelBaseDir}`);
|
|
10376
|
+
}
|
|
10377
|
+
});
|
|
10378
|
+
}
|
|
10268
10379
|
// Add all expanded items from the import
|
|
10269
10380
|
expanded.push(...expandedImported);
|
|
10270
10381
|
}
|
|
@@ -10313,13 +10424,14 @@ function setupProgram() {
|
|
|
10313
10424
|
.command('extract')
|
|
10314
10425
|
.description('Extract and merge markdown files')
|
|
10315
10426
|
.option('--src <path>', 'Source directory')
|
|
10316
|
-
.option('--out <path>', 'Output file'
|
|
10427
|
+
.option('--out <path>', 'Output file')
|
|
10317
10428
|
.option('--type <types>', 'Filter by type (comma-separated)')
|
|
10318
10429
|
.option('--language <languages>', 'Filter by language (comma-separated)')
|
|
10319
10430
|
.option('--attr <attributes>', 'Filter by attributes (comma-separated)')
|
|
10320
10431
|
.option('--title <title>', 'Title for the output')
|
|
10321
10432
|
.option('--mode <mode>', 'Write mode: append, prepend, overwrite', 'overwrite')
|
|
10322
10433
|
.option('--recipe <path>', 'Recipe file path or package preset (e.g., :typescript). Can be specified multiple times or comma-separated.', collectRecipeOptions, [])
|
|
10434
|
+
.option('--base-dir <path>', 'Base directory for output files (supports ~ and environment variable expansion)')
|
|
10323
10435
|
.option('--vars <variables>', 'Template variables in key=value format (comma-separated)')
|
|
10324
10436
|
.option('--env-file <path>', 'Path to .env file for template variables')
|
|
10325
10437
|
.option('--debug', 'Enable debug logging')
|
|
@@ -10345,6 +10457,33 @@ function setupProgram() {
|
|
|
10345
10457
|
});
|
|
10346
10458
|
return program;
|
|
10347
10459
|
}
|
|
10460
|
+
/**
|
|
10461
|
+
* Recursively scans a directory for YAML files and returns their relative paths
|
|
10462
|
+
*/
|
|
10463
|
+
async function findYamlFilesRecursively(dir, baseDir = dir) {
|
|
10464
|
+
const yamlFiles = [];
|
|
10465
|
+
try {
|
|
10466
|
+
const items = await fs.readdir(dir, { withFileTypes: true });
|
|
10467
|
+
for (const item of items) {
|
|
10468
|
+
const fullPath = path.join(dir, item.name);
|
|
10469
|
+
if (item.isDirectory()) {
|
|
10470
|
+
// Recursively scan subdirectories
|
|
10471
|
+
const subFiles = await findYamlFilesRecursively(fullPath, baseDir);
|
|
10472
|
+
yamlFiles.push(...subFiles);
|
|
10473
|
+
}
|
|
10474
|
+
else if (item.isFile() && (item.name.endsWith('.yaml') || item.name.endsWith('.yml'))) {
|
|
10475
|
+
// Get relative path from base directory
|
|
10476
|
+
const relativePath = path.relative(baseDir, fullPath);
|
|
10477
|
+
yamlFiles.push(relativePath);
|
|
10478
|
+
}
|
|
10479
|
+
}
|
|
10480
|
+
}
|
|
10481
|
+
catch (error) {
|
|
10482
|
+
// Log errors for individual directories/files that can't be read
|
|
10483
|
+
console.warn(`Warning: Could not read directory "${dir}":`, error);
|
|
10484
|
+
}
|
|
10485
|
+
return yamlFiles;
|
|
10486
|
+
}
|
|
10348
10487
|
/**
|
|
10349
10488
|
* Lists available package presets
|
|
10350
10489
|
*/
|
|
@@ -10352,8 +10491,7 @@ async function listPresets() {
|
|
|
10352
10491
|
const packageRoot = findPackageRoot();
|
|
10353
10492
|
const presetsDir = path.join(packageRoot, 'presets');
|
|
10354
10493
|
try {
|
|
10355
|
-
const
|
|
10356
|
-
const yamlFiles = files.filter(file => file.endsWith('.yaml') || file.endsWith('.yml'));
|
|
10494
|
+
const yamlFiles = await findYamlFilesRecursively(presetsDir);
|
|
10357
10495
|
if (yamlFiles.length === 0) {
|
|
10358
10496
|
// eslint-disable-next-line no-console
|
|
10359
10497
|
console.log('No presets found in package.');
|
|
@@ -10458,7 +10596,7 @@ async function processContentWithMode(outFile, newContent, mode, debugLogger) {
|
|
|
10458
10596
|
return newContent;
|
|
10459
10597
|
}
|
|
10460
10598
|
try {
|
|
10461
|
-
const existing = await fs.readFile(
|
|
10599
|
+
const existing = await fs.readFile(resolvePath(outFile), "utf-8");
|
|
10462
10600
|
debugLogger?.log(`Existing file found with ${existing.length} characters`);
|
|
10463
10601
|
if (mode === WriteMode.APPEND) {
|
|
10464
10602
|
debugLogger?.log('Appending new content to existing content');
|
|
@@ -10577,7 +10715,7 @@ async function processSingle(options, debugLogger) {
|
|
|
10577
10715
|
debugLogger.time('Content mode processing');
|
|
10578
10716
|
const finalContent = await processContentWithMode(outFile, contentWithTitle, mode, debugLogger);
|
|
10579
10717
|
debugLogger.timeEnd('Content mode processing');
|
|
10580
|
-
const resolved =
|
|
10718
|
+
const resolved = resolvePath(outFile);
|
|
10581
10719
|
const writer = new MarkdownWriter();
|
|
10582
10720
|
debugLogger.log(`Writing to output file: ${resolved}`);
|
|
10583
10721
|
// Build front matter
|
|
@@ -10723,7 +10861,7 @@ async function handleExtractCommand(options) {
|
|
|
10723
10861
|
});
|
|
10724
10862
|
const extractOptions = {
|
|
10725
10863
|
srcDir: resolveDefaultSrcDir(options.src),
|
|
10726
|
-
outFile: options.out,
|
|
10864
|
+
outFile: options.out || './out/instruction.md',
|
|
10727
10865
|
types: parseCommaSeparated(options.type),
|
|
10728
10866
|
languages: parseCommaSeparated(options.language),
|
|
10729
10867
|
attrFilters: parseCommaSeparated(options.attr) || [],
|
|
@@ -10740,15 +10878,20 @@ async function handleExtractCommand(options) {
|
|
|
10740
10878
|
await validateRecipeFilesExist(options.recipe);
|
|
10741
10879
|
if (options.recipe.length === 1) {
|
|
10742
10880
|
// Single recipe - maintain backward compatibility
|
|
10743
|
-
await processRecipe(options.recipe[0], extractOptions, new ContentTracker(), debugLogger);
|
|
10881
|
+
await processRecipe(options.recipe[0], extractOptions, new ContentTracker(), debugLogger, options.baseDir, options.out);
|
|
10744
10882
|
}
|
|
10745
10883
|
else {
|
|
10746
10884
|
// Multiple recipes - process in order with auto-append
|
|
10747
|
-
await processMultipleRecipes(options.recipe, extractOptions, debugLogger);
|
|
10885
|
+
await processMultipleRecipes(options.recipe, extractOptions, debugLogger, options.baseDir, options.out);
|
|
10748
10886
|
}
|
|
10749
10887
|
}
|
|
10750
10888
|
else {
|
|
10751
10889
|
debugLogger.log('Processing single extraction without recipe');
|
|
10890
|
+
// For single extraction, apply baseDir to outFile if provided
|
|
10891
|
+
if (options.baseDir) {
|
|
10892
|
+
extractOptions.outFile = resolveOutputPath(extractOptions.outFile, options.baseDir, undefined, undefined, undefined);
|
|
10893
|
+
debugLogger.log(`Applied CLI baseDir to output file: ${extractOptions.outFile}`);
|
|
10894
|
+
}
|
|
10752
10895
|
await processSingle(extractOptions, debugLogger);
|
|
10753
10896
|
}
|
|
10754
10897
|
}
|
|
@@ -10765,13 +10908,13 @@ async function handleStatsCommand(options) {
|
|
|
10765
10908
|
/**
|
|
10766
10909
|
* Processes multiple recipe files in the specified order
|
|
10767
10910
|
*/
|
|
10768
|
-
async function processMultipleRecipes(recipePaths, baseOptions, debugLogger) {
|
|
10911
|
+
async function processMultipleRecipes(recipePaths, baseOptions, debugLogger, cliBaseDir, cliOutFile) {
|
|
10769
10912
|
const contentTracker = new ContentTracker();
|
|
10770
10913
|
debugLogger.log(`Processing ${recipePaths.length} recipes in order`);
|
|
10771
10914
|
for (const recipePath of recipePaths) {
|
|
10772
10915
|
try {
|
|
10773
10916
|
debugLogger.log(`Starting processing of recipe: ${recipePath}`);
|
|
10774
|
-
await processRecipe(recipePath, baseOptions, contentTracker, debugLogger);
|
|
10917
|
+
await processRecipe(recipePath, baseOptions, contentTracker, debugLogger, cliBaseDir, cliOutFile);
|
|
10775
10918
|
debugLogger.log(`Completed processing of recipe: ${recipePath}`);
|
|
10776
10919
|
}
|
|
10777
10920
|
catch (error) {
|
|
@@ -10784,10 +10927,103 @@ async function processMultipleRecipes(recipePaths, baseOptions, debugLogger) {
|
|
|
10784
10927
|
}
|
|
10785
10928
|
}
|
|
10786
10929
|
}
|
|
10930
|
+
/**
|
|
10931
|
+
* Reads template files and extracts frontmatter for inheritance
|
|
10932
|
+
*/
|
|
10933
|
+
async function loadTemplateFrontmatter(srcDir, types, languages, attrFilters, debugLogger) {
|
|
10934
|
+
debugLogger?.log('Loading template frontmatter for inheritance');
|
|
10935
|
+
const templateFiles = await loadAndFilterFiles(srcDir, types, languages, attrFilters, debugLogger);
|
|
10936
|
+
debugLogger?.log(`Found ${templateFiles.length} template files for frontmatter inheritance`);
|
|
10937
|
+
return templateFiles.map(file => file.attrs);
|
|
10938
|
+
}
|
|
10939
|
+
/**
|
|
10940
|
+
* Merges frontmatter values from multiple templates according to merge rules:
|
|
10941
|
+
* - Strings: concatenate with newlines, removing duplicates while preserving order
|
|
10942
|
+
* - Arrays: combine all elements
|
|
10943
|
+
* - Objects: later values overwrite
|
|
10944
|
+
*/
|
|
10945
|
+
function mergeTemplateFrontmatterValues(templateFrontmatters, fieldName) {
|
|
10946
|
+
const values = templateFrontmatters
|
|
10947
|
+
.map(fm => fm[fieldName])
|
|
10948
|
+
.filter(val => val !== undefined && val !== null);
|
|
10949
|
+
if (values.length === 0) {
|
|
10950
|
+
return undefined;
|
|
10951
|
+
}
|
|
10952
|
+
if (values.length === 1) {
|
|
10953
|
+
return values[0];
|
|
10954
|
+
}
|
|
10955
|
+
// Check if all values are strings
|
|
10956
|
+
if (values.every(val => typeof val === 'string')) {
|
|
10957
|
+
// Split each string by newlines, flatten, remove duplicates while preserving order
|
|
10958
|
+
const allLines = [];
|
|
10959
|
+
const seen = new Set();
|
|
10960
|
+
for (const value of values) {
|
|
10961
|
+
const lines = value.split('\n').map(line => line.trim()).filter(line => line.length > 0);
|
|
10962
|
+
for (const line of lines) {
|
|
10963
|
+
if (!seen.has(line)) {
|
|
10964
|
+
seen.add(line);
|
|
10965
|
+
allLines.push(line);
|
|
10966
|
+
}
|
|
10967
|
+
}
|
|
10968
|
+
}
|
|
10969
|
+
return allLines.join('\n');
|
|
10970
|
+
}
|
|
10971
|
+
// Check if all values are arrays
|
|
10972
|
+
if (values.every(val => Array.isArray(val))) {
|
|
10973
|
+
return values.flat();
|
|
10974
|
+
}
|
|
10975
|
+
// For objects and mixed types, use the last value
|
|
10976
|
+
return values[values.length - 1];
|
|
10977
|
+
}
|
|
10978
|
+
/**
|
|
10979
|
+
* Processes @ syntax in frontmatter to inherit from template files
|
|
10980
|
+
*/
|
|
10981
|
+
async function processFrontmatterInheritance(frontmatter, srcDir, types, languages, attrFilters, debugLogger) {
|
|
10982
|
+
if (!frontmatter || typeof frontmatter !== 'object') {
|
|
10983
|
+
return {};
|
|
10984
|
+
}
|
|
10985
|
+
const result = {};
|
|
10986
|
+
const inheritanceFields = [];
|
|
10987
|
+
// Separate inheritance fields (@ syntax) from regular fields
|
|
10988
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
10989
|
+
if (key.startsWith('@') && value === true) {
|
|
10990
|
+
inheritanceFields.push(key.slice(1)); // Remove @ prefix
|
|
10991
|
+
debugLogger?.log(`Found inheritance field: ${key} -> ${key.slice(1)}`);
|
|
10992
|
+
}
|
|
10993
|
+
else {
|
|
10994
|
+
result[key] = value;
|
|
10995
|
+
}
|
|
10996
|
+
}
|
|
10997
|
+
// If no inheritance fields, return as-is
|
|
10998
|
+
if (inheritanceFields.length === 0) {
|
|
10999
|
+
debugLogger?.log('No frontmatter inheritance fields found');
|
|
11000
|
+
return result;
|
|
11001
|
+
}
|
|
11002
|
+
debugLogger?.log(`Processing ${inheritanceFields.length} inheritance fields:`, inheritanceFields);
|
|
11003
|
+
// Load template frontmatters
|
|
11004
|
+
const templateFrontmatters = await loadTemplateFrontmatter(srcDir, types, languages, attrFilters, debugLogger);
|
|
11005
|
+
if (templateFrontmatters.length === 0) {
|
|
11006
|
+
debugLogger?.log('No template files found for inheritance');
|
|
11007
|
+
return result;
|
|
11008
|
+
}
|
|
11009
|
+
// Process each inheritance field
|
|
11010
|
+
for (const fieldName of inheritanceFields) {
|
|
11011
|
+
const mergedValue = mergeTemplateFrontmatterValues(templateFrontmatters, fieldName);
|
|
11012
|
+
if (mergedValue !== undefined) {
|
|
11013
|
+
result[fieldName] = mergedValue;
|
|
11014
|
+
debugLogger?.log(`Inherited field ${fieldName}:`, mergedValue);
|
|
11015
|
+
}
|
|
11016
|
+
else {
|
|
11017
|
+
debugLogger?.log(`No value found for inherited field ${fieldName} in templates`);
|
|
11018
|
+
}
|
|
11019
|
+
}
|
|
11020
|
+
debugLogger?.log('Frontmatter after inheritance processing:', Object.keys(result));
|
|
11021
|
+
return result;
|
|
11022
|
+
}
|
|
10787
11023
|
/**
|
|
10788
11024
|
* Processes a recipe file with multiple extract operations
|
|
10789
11025
|
*/
|
|
10790
|
-
async function processRecipe(recipePath, baseOptions, contentTracker, debugLogger) {
|
|
11026
|
+
async function processRecipe(recipePath, baseOptions, contentTracker, debugLogger, cliBaseDir, cliOutFile) {
|
|
10791
11027
|
const resolvedPath = resolveRecipePath(recipePath);
|
|
10792
11028
|
debugLogger?.log(`Processing recipe at path: ${resolvedPath}`);
|
|
10793
11029
|
try {
|
|
@@ -10799,6 +11035,9 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
10799
11035
|
throw new Error("Invalid recipe file: 'recipe' array not found");
|
|
10800
11036
|
}
|
|
10801
11037
|
debugLogger?.log(`Recipe contains ${data.recipe.length} items`);
|
|
11038
|
+
// Read recipe config for baseDir
|
|
11039
|
+
const recipeBaseDir = data.config?.baseDir;
|
|
11040
|
+
debugLogger?.log('Recipe config:', { baseDir: recipeBaseDir });
|
|
10802
11041
|
// Expand any imports in the recipe
|
|
10803
11042
|
debugLogger?.time('Recipe import expansion');
|
|
10804
11043
|
const expandedRecipe = await expandRecipeImports(data.recipe, resolvedPath, debugLogger);
|
|
@@ -10814,8 +11053,17 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
10814
11053
|
language: item.language,
|
|
10815
11054
|
mode: item.mode
|
|
10816
11055
|
});
|
|
10817
|
-
|
|
10818
|
-
const
|
|
11056
|
+
// Priority: CLI --out option > recipe item.out > baseOptions.outFile default
|
|
11057
|
+
const itemOut = cliOutFile || item.out || baseOptions.outFile;
|
|
11058
|
+
const outputFile = resolveOutputPath(itemOut, cliBaseDir, item._importBaseDir, recipeBaseDir, resolvedPath);
|
|
11059
|
+
debugLogger?.log(`Resolved output path: ${itemOut} -> ${outputFile}`, {
|
|
11060
|
+
cliOutFile,
|
|
11061
|
+
itemOut: item.out,
|
|
11062
|
+
fallback: baseOptions.outFile,
|
|
11063
|
+
cliBaseDir,
|
|
11064
|
+
importBaseDir: item._importBaseDir,
|
|
11065
|
+
recipeBaseDir
|
|
11066
|
+
});
|
|
10819
11067
|
// Generate the content that would be written to check for duplicates
|
|
10820
11068
|
const { srcDir, types, languages, attrFilters, title, attr, vars, envFile } = baseOptions;
|
|
10821
11069
|
const itemTypes = item.type ? parseCommaSeparated(item.type) : types;
|
|
@@ -10867,7 +11115,7 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
10867
11115
|
effectiveMode = parseWriteMode(item.mode);
|
|
10868
11116
|
debugLogger?.log(`Using explicit mode from recipe item: ${effectiveMode}`);
|
|
10869
11117
|
}
|
|
10870
|
-
else if (localTracker.hasFile(
|
|
11118
|
+
else if (localTracker.hasFile(outputFile)) {
|
|
10871
11119
|
// Auto-append for duplicate output files when no explicit mode
|
|
10872
11120
|
effectiveMode = WriteMode.APPEND;
|
|
10873
11121
|
debugLogger?.log(`Auto-appending due to existing output file: ${effectiveMode}`);
|
|
@@ -10875,6 +11123,10 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
10875
11123
|
else {
|
|
10876
11124
|
debugLogger?.log(`Using base mode: ${effectiveMode}`);
|
|
10877
11125
|
}
|
|
11126
|
+
// Process frontmatter inheritance (@ syntax)
|
|
11127
|
+
debugLogger?.time(`Frontmatter inheritance processing for item ${index + 1}`);
|
|
11128
|
+
const processedFrontmatter = await processFrontmatterInheritance(item.frontmatter, baseOptions.srcDir, itemTypes, itemLanguages, combinedAttrFilters, debugLogger);
|
|
11129
|
+
debugLogger?.timeEnd(`Frontmatter inheritance processing for item ${index + 1}`);
|
|
10878
11130
|
const options = {
|
|
10879
11131
|
srcDir: baseOptions.srcDir,
|
|
10880
11132
|
outFile: outputFile,
|
|
@@ -10883,7 +11135,7 @@ async function processRecipe(recipePath, baseOptions, contentTracker, debugLogge
|
|
|
10883
11135
|
attrFilters: combinedAttrFilters,
|
|
10884
11136
|
title: itemTitle,
|
|
10885
11137
|
mode: effectiveMode,
|
|
10886
|
-
attr:
|
|
11138
|
+
attr: processedFrontmatter,
|
|
10887
11139
|
debug: baseOptions.debug,
|
|
10888
11140
|
vars: itemVars,
|
|
10889
11141
|
envFile: envFile,
|
|
@@ -10932,3 +11184,5 @@ run().catch((error) => {
|
|
|
10932
11184
|
console.error("Unhandled error:", error);
|
|
10933
11185
|
process.exit(1);
|
|
10934
11186
|
});
|
|
11187
|
+
|
|
11188
|
+
export { findYamlFilesRecursively };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path expansion utility that supports tilde (~) and environment variable expansion
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Tilde expansion: ~/path -> /Users/username/path (Unix-like systems only)
|
|
6
|
+
* - Environment variable expansion: $HOME/path -> /Users/username/path
|
|
7
|
+
* - Environment variable expansion: ${VAR}/path -> /value/path
|
|
8
|
+
* - Complex combinations: ~/projects/${PROJECT_NAME}/file.md
|
|
9
|
+
*
|
|
10
|
+
* @param inputPath - Path to expand and resolve
|
|
11
|
+
* @returns Expanded and resolved absolute path
|
|
12
|
+
* @throws Error if required environment variables are missing
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolvePath(inputPath: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Safely resolve a path with environment variable expansion, returning the original path
|
|
17
|
+
* if expansion fails (useful for optional environment variables)
|
|
18
|
+
*
|
|
19
|
+
* @param inputPath - Path to expand and resolve
|
|
20
|
+
* @param fallbackToOriginal - If true, return path.resolve(inputPath) when expansion fails
|
|
21
|
+
* @returns Expanded and resolved absolute path, or resolved original path on failure
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolvePathSafe(inputPath: string, fallbackToOriginal?: boolean): string;
|
|
24
|
+
//# sourceMappingURL=pathExpansion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathExpansion.d.ts","sourceRoot":"","sources":["../../src/utils/pathExpansion.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAuCrD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,GAAE,OAAc,GAAG,MAAM,CAS7F"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { homedir } from "os";
|
|
2
|
+
import path from "path";
|
|
3
|
+
/**
|
|
4
|
+
* Path expansion utility that supports tilde (~) and environment variable expansion
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - Tilde expansion: ~/path -> /Users/username/path (Unix-like systems only)
|
|
8
|
+
* - Environment variable expansion: $HOME/path -> /Users/username/path
|
|
9
|
+
* - Environment variable expansion: ${VAR}/path -> /value/path
|
|
10
|
+
* - Complex combinations: ~/projects/${PROJECT_NAME}/file.md
|
|
11
|
+
*
|
|
12
|
+
* @param inputPath - Path to expand and resolve
|
|
13
|
+
* @returns Expanded and resolved absolute path
|
|
14
|
+
* @throws Error if required environment variables are missing
|
|
15
|
+
*/
|
|
16
|
+
export function resolvePath(inputPath) {
|
|
17
|
+
if (!inputPath || typeof inputPath !== 'string') {
|
|
18
|
+
throw new Error('Path must be a non-empty string');
|
|
19
|
+
}
|
|
20
|
+
let expandedPath = inputPath;
|
|
21
|
+
// 1. Handle tilde expansion first (Unix-like systems only)
|
|
22
|
+
if (expandedPath.startsWith('~/')) {
|
|
23
|
+
// Check if we're on Windows
|
|
24
|
+
if (process.platform === 'win32') {
|
|
25
|
+
throw new Error('Tilde (~) expansion is not supported on Windows. Use %USERPROFILE% or environment variables instead.');
|
|
26
|
+
}
|
|
27
|
+
const homeDir = homedir();
|
|
28
|
+
expandedPath = path.join(homeDir, expandedPath.slice(2));
|
|
29
|
+
}
|
|
30
|
+
else if (expandedPath === '~') {
|
|
31
|
+
if (process.platform === 'win32') {
|
|
32
|
+
throw new Error('Tilde (~) expansion is not supported on Windows. Use %USERPROFILE% or environment variables instead.');
|
|
33
|
+
}
|
|
34
|
+
expandedPath = homedir();
|
|
35
|
+
}
|
|
36
|
+
// 2. Handle environment variable expansion
|
|
37
|
+
// Support both $VAR and ${VAR} syntax
|
|
38
|
+
expandedPath = expandedPath.replace(/\$\{([^}]+)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (match, bracedVar, simpleVar) => {
|
|
39
|
+
const varName = bracedVar || simpleVar;
|
|
40
|
+
const envValue = process.env[varName];
|
|
41
|
+
if (envValue === undefined) {
|
|
42
|
+
throw new Error(`Environment variable '${varName}' is not defined`);
|
|
43
|
+
}
|
|
44
|
+
return envValue;
|
|
45
|
+
});
|
|
46
|
+
// 3. Resolve to absolute path
|
|
47
|
+
return path.resolve(expandedPath);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Safely resolve a path with environment variable expansion, returning the original path
|
|
51
|
+
* if expansion fails (useful for optional environment variables)
|
|
52
|
+
*
|
|
53
|
+
* @param inputPath - Path to expand and resolve
|
|
54
|
+
* @param fallbackToOriginal - If true, return path.resolve(inputPath) when expansion fails
|
|
55
|
+
* @returns Expanded and resolved absolute path, or resolved original path on failure
|
|
56
|
+
*/
|
|
57
|
+
export function resolvePathSafe(inputPath, fallbackToOriginal = true) {
|
|
58
|
+
try {
|
|
59
|
+
return resolvePath(inputPath);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (fallbackToOriginal) {
|
|
63
|
+
return path.resolve(inputPath);
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aramassa/ai-rules",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "This repository collects guidelines and instructions for developing AI agents. It contains documents covering communication rules, coding standards, testing strategies, and general operational practices.",
|
|
5
5
|
"workspaces": [
|
|
6
6
|
"packages/extract",
|
|
@@ -24,8 +24,10 @@
|
|
|
24
24
|
"start:cli": "node dist/cli.js",
|
|
25
25
|
"start:dev:cli": "tsx src/cli.ts",
|
|
26
26
|
"test": "vitest run --environment node test",
|
|
27
|
+
"test:ci": "vitest run --environment node",
|
|
27
28
|
"test:all": "npm run test --workspaces",
|
|
28
|
-
"build": "npm run --ws build && tsc -p tsconfig.build.json",
|
|
29
|
+
"build": "npm run --ws build && tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
30
|
+
"build:ci": "npm run --ws build && tsc -p tsconfig.build.json && chmod +x dist/cli.js",
|
|
29
31
|
"build:pkg:clean": "npm run --ws clean",
|
|
30
32
|
"clean": "npm run build:pkg:clean && rm -rf dist",
|
|
31
33
|
"root:build": "npm run build && if [ \"$NODE_ENV\" != \"test\" ]; then rollup -c && chmod +x dist/cli.js; fi"
|
|
@@ -51,7 +53,7 @@
|
|
|
51
53
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
|
52
54
|
"@rollup/plugin-typescript": "^12.1.4",
|
|
53
55
|
"@types/commander": "^2.12.0",
|
|
54
|
-
"@types/node": "^24.
|
|
56
|
+
"@types/node": "^24.5.2",
|
|
55
57
|
"fast-glob": "^3.3.3",
|
|
56
58
|
"gray-matter": "^4.0.3",
|
|
57
59
|
"rollup": "^4.45.0",
|
package/presets/README.md
CHANGED
|
@@ -62,6 +62,31 @@ npx @aramassa/ai-rules extract --recipe presets/infrastructure-ansible.yaml --sr
|
|
|
62
62
|
- DevOps・SRE関連プロジェクト
|
|
63
63
|
- 設定管理・自動化プロジェクト
|
|
64
64
|
|
|
65
|
+
### `chrome-extension.yaml`
|
|
66
|
+
|
|
67
|
+
Chrome Extension(Manifest V3)開発プロジェクト向けの包括的なinstruction構成です。
|
|
68
|
+
|
|
69
|
+
**含まれる内容:**
|
|
70
|
+
|
|
71
|
+
- Chrome Extension Manifest V3 Development Rules - 現代的なChrome Extension開発のベストプラクティス
|
|
72
|
+
- Code Quality Tools - JavaScript/Chrome Extension向けのコード品質管理
|
|
73
|
+
- Package Management Rules - パッケージ管理とビルドプロセス
|
|
74
|
+
- Git Rules - バージョン管理とワークフロー
|
|
75
|
+
- Basic Communication Rules - 基本的なコミュニケーションルール
|
|
76
|
+
|
|
77
|
+
**使用方法:**
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npx @aramassa/ai-rules extract --recipe presets/chrome-extension.yaml --src artifact/instructions
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**対象プロジェクト:**
|
|
84
|
+
|
|
85
|
+
- Chrome Extension開発プロジェクト(Manifest V3)
|
|
86
|
+
- JavaScript/TypeScriptベースのブラウザ拡張機能
|
|
87
|
+
- Webブラウザの機能拡張・自動化プロジェクト
|
|
88
|
+
- Service Worker、Content Scripts、Popup UIを含む複合アプリケーション
|
|
89
|
+
|
|
65
90
|
### `chatmodes.yaml`
|
|
66
91
|
|
|
67
92
|
AI対話支援機能(chatmodes)をプロジェクトに統合するための設定です。
|
|
@@ -70,6 +95,7 @@ AI対話支援機能(chatmodes)をプロジェクトに統合するための
|
|
|
70
95
|
|
|
71
96
|
- Instruction Improve Chatmode - プロジェクト指示の改善支援
|
|
72
97
|
- Planning Chatmode - todo_plans作成支援
|
|
98
|
+
- Bug Reproduce Chatmode - GitHub Issue バグ再現・調査・修正提案支援
|
|
73
99
|
|
|
74
100
|
**使用方法:**
|
|
75
101
|
|