@ghl-ai/aw 0.1.39-beta.9 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ecc.mjs +1 -1
- package/integrate.mjs +54 -33
- package/package.json +1 -1
- package/render-rules.mjs +141 -12
package/ecc.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import { applyStoredStartupPreferences } from "./startup.mjs";
|
|
|
10
10
|
|
|
11
11
|
const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
|
|
12
12
|
const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
|
|
13
|
-
export const AW_ECC_TAG = "v1.4.
|
|
13
|
+
export const AW_ECC_TAG = "v1.4.37";
|
|
14
14
|
|
|
15
15
|
const MARKETPLACE_NAME = "aw-marketplace";
|
|
16
16
|
const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
|
package/integrate.mjs
CHANGED
|
@@ -114,12 +114,41 @@ function shouldResetHomeInstructionFile(content, file) {
|
|
|
114
114
|
return legacyMarkers.some(marker => content.includes(marker));
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
function stripLegacyRepoInstructionContent(content, file) {
|
|
118
|
+
const legacyMarkers = file === 'CLAUDE.md'
|
|
119
|
+
? [
|
|
120
|
+
'# CLAUDE.md — ',
|
|
121
|
+
'## Routing Rule (ABSOLUTE)',
|
|
122
|
+
'This supplements the root `AGENTS.md` with Codex-specific guidance.',
|
|
123
|
+
'<!-- BEGIN ECC -->',
|
|
124
|
+
]
|
|
125
|
+
: [
|
|
126
|
+
'# AGENTS.md — ',
|
|
127
|
+
'# ECC for Codex CLI',
|
|
128
|
+
'# AW SDLC Repo Instructions',
|
|
129
|
+
'Use the repo-local AW SDLC files as the source of truth for routing and stage behavior.',
|
|
130
|
+
'## Agent System',
|
|
131
|
+
'<!-- BEGIN ECC -->',
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
const startIndexes = legacyMarkers
|
|
135
|
+
.map(marker => content.indexOf(marker))
|
|
136
|
+
.filter(idx => idx !== -1);
|
|
137
|
+
|
|
138
|
+
if (startIndexes.length === 0) return content;
|
|
139
|
+
|
|
140
|
+
const startIdx = Math.min(...startIndexes);
|
|
141
|
+
const preserved = content.slice(0, startIdx).trimEnd();
|
|
142
|
+
return preserved ? `${preserved}\n` : '';
|
|
143
|
+
}
|
|
144
|
+
|
|
117
145
|
function applyManagedInstructionSections(content, file, rulesSections = {}, options = {}) {
|
|
118
146
|
const rulesHeader = file === 'CLAUDE.md' ? CLAUDE_RULES_HEADER : AGENTS_RULES_HEADER;
|
|
119
147
|
const rulesSection = file === 'CLAUDE.md' ? rulesSections.claudeSection : rulesSections.agentsSection;
|
|
120
148
|
const includeBridge = options.includeBridge !== false;
|
|
121
149
|
|
|
122
|
-
let next =
|
|
150
|
+
let next = stripLegacyRepoInstructionContent(content, file);
|
|
151
|
+
next = stripManagedBlock(next, AW_ROUTER_BRIDGE_START_MARKER, AW_ROUTER_BRIDGE_END_MARKER);
|
|
123
152
|
next = stripManagedSection(next, AW_ROUTER_BRIDGE_HEADER, [rulesHeader]);
|
|
124
153
|
next = stripManagedSection(next, rulesHeader);
|
|
125
154
|
next = next.trimEnd();
|
|
@@ -129,8 +158,15 @@ function applyManagedInstructionSections(content, file, rulesSections = {}, opti
|
|
|
129
158
|
return next ? `${next}\n` : '';
|
|
130
159
|
}
|
|
131
160
|
|
|
132
|
-
|
|
133
|
-
|
|
161
|
+
// Marker tells users (and aw init) where the managed section starts.
|
|
162
|
+
// Everything BEFORE this marker is repo-owned and never touched by aw.
|
|
163
|
+
// Everything AFTER is managed by aw — re-rendered on every aw init.
|
|
164
|
+
const managedBoundary = '<!-- aw-managed: content below is regenerated by `aw init` — put your own content above this line -->';
|
|
165
|
+
const appended = [managedBoundary, ...sections].join('\n\n').trim();
|
|
166
|
+
// Strip any prior managedBoundary line from `next` so we don't accumulate them
|
|
167
|
+
// when re-running aw init.
|
|
168
|
+
const cleaned = next.split('\n').filter(line => line.trim() !== managedBoundary).join('\n').trimEnd();
|
|
169
|
+
return cleaned ? `${cleaned}\n\n${appended}\n` : `${appended}\n`;
|
|
134
170
|
}
|
|
135
171
|
|
|
136
172
|
/**
|
|
@@ -188,49 +224,34 @@ function findFiles(dir, typeName) {
|
|
|
188
224
|
}
|
|
189
225
|
|
|
190
226
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
227
|
+
* Refresh rules sections in any existing AGENTS.md/CLAUDE.md at the repo
|
|
228
|
+
* root.
|
|
229
|
+
*
|
|
230
|
+
* Repo instruction files are user-owned. aw init no longer creates or updates
|
|
231
|
+
* managed sections in repo-local AGENTS.md / CLAUDE.md.
|
|
232
|
+
*
|
|
233
|
+
* The only repo-file behavior left is cleanup: if a repo still contains old
|
|
234
|
+
* aw-managed sections from prior versions, strip those sections while leaving
|
|
235
|
+
* the user's own content intact.
|
|
194
236
|
*/
|
|
195
237
|
export function copyInstructions(cwd, tempDir, namespace) {
|
|
196
238
|
const rulesSections = renderRules(cwd);
|
|
197
239
|
const createdFiles = [];
|
|
240
|
+
|
|
198
241
|
for (const file of ['AGENTS.md', 'CLAUDE.md']) {
|
|
199
242
|
const dest = join(cwd, file);
|
|
200
243
|
if (existsSync(dest)) {
|
|
201
244
|
const existing = readFileSync(dest, 'utf8');
|
|
202
|
-
const updated = applyManagedInstructionSections(existing, file,
|
|
245
|
+
const updated = applyManagedInstructionSections(existing, file, {}, { includeBridge: false });
|
|
203
246
|
|
|
204
247
|
if (updated !== existing) {
|
|
205
248
|
writeFileSync(dest, updated);
|
|
206
|
-
fmt.
|
|
249
|
+
fmt.logStep(`Stripped aw-managed sections from ${file} (now in ~/.claude/CLAUDE.md / ~/.codex/AGENTS.md)`);
|
|
207
250
|
}
|
|
208
251
|
continue;
|
|
209
252
|
}
|
|
210
253
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (tempDir) {
|
|
214
|
-
const src = join(tempDir, '.aw_registry', file);
|
|
215
|
-
if (existsSync(src)) {
|
|
216
|
-
let content = readFileSync(src, 'utf8');
|
|
217
|
-
if (namespace) {
|
|
218
|
-
content = content.replace(/\{\{TEAM\}\}/g, namespace);
|
|
219
|
-
}
|
|
220
|
-
content = applyManagedInstructionSections(content, file, rulesSections, { includeBridge: false });
|
|
221
|
-
writeFileSync(dest, content);
|
|
222
|
-
fmt.logSuccess(`Created ${file}`);
|
|
223
|
-
createdFiles.push(dest);
|
|
224
|
-
continue;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const content = generateAgentsMd(cwd, namespace, rulesSections);
|
|
229
|
-
if (content) {
|
|
230
|
-
writeFileSync(dest, applyManagedInstructionSections(content, file, rulesSections, { includeBridge: false }));
|
|
231
|
-
fmt.logSuccess(`Created ${file}`);
|
|
232
|
-
createdFiles.push(dest);
|
|
233
|
-
}
|
|
254
|
+
// Never create repo instruction files anymore.
|
|
234
255
|
}
|
|
235
256
|
return createdFiles;
|
|
236
257
|
}
|
|
@@ -286,7 +307,7 @@ function generateClaudeMd(cwd, namespace, rulesSections = {}) {
|
|
|
286
307
|
const team = namespace || 'my-team';
|
|
287
308
|
let base = `# CLAUDE.md — ${team}
|
|
288
309
|
|
|
289
|
-
Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\` (shared knowledge), \`
|
|
310
|
+
Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\` (shared knowledge), \`jenkins_*\` (CI/CD via ghl-ai MCP), \`grafana\` (observability)
|
|
290
311
|
|
|
291
312
|
## Routing Rule (ABSOLUTE)
|
|
292
313
|
|
|
@@ -357,7 +378,7 @@ memory/search → Search shared team knowledge base
|
|
|
357
378
|
memory/store → Push learnings to shared knowledge (eager sync after runs)
|
|
358
379
|
memory/get → Fetch specific memory by ID
|
|
359
380
|
grafana/* → External observability
|
|
360
|
-
|
|
381
|
+
jenkins_* → CI/CD pipelines (provided by ghl-ai MCP)
|
|
361
382
|
stitch/* → External design generation
|
|
362
383
|
\`\`\`
|
|
363
384
|
|
package/package.json
CHANGED
package/render-rules.mjs
CHANGED
|
@@ -6,7 +6,12 @@ import { homedir } from 'node:os';
|
|
|
6
6
|
import * as fmt from './fmt.mjs';
|
|
7
7
|
import { RULES_RUNTIME_DIR } from './constants.mjs';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// The marker is placed AFTER the YAML frontmatter so Cursor/Markdown parsers
|
|
10
|
+
// see the frontmatter at byte 0. Putting an HTML comment before --- breaks
|
|
11
|
+
// Cursor's alwaysApply/globs detection.
|
|
12
|
+
const GENERATED_MARKER = '<!-- Generated by aw — do not edit manually -->';
|
|
13
|
+
// Legacy header (pre-pattern comment first) — kept for pruning old files.
|
|
14
|
+
const LEGACY_GENERATED_HEADER = '<!-- Generated by aw — do not edit manually -->\n\n';
|
|
10
15
|
const STACK_OVERLAY_FLAG = 'AW_ENABLE_STACK_OVERLAY_RULES';
|
|
11
16
|
|
|
12
17
|
/** Rule scope → Cursor .mdc glob patterns */
|
|
@@ -196,7 +201,8 @@ function pruneStaleGeneratedRules(outputDir, expectedFilenames) {
|
|
|
196
201
|
|
|
197
202
|
const fullPath = join(outputDir, entry.name);
|
|
198
203
|
const content = readOrNull(fullPath);
|
|
199
|
-
|
|
204
|
+
// Match both new pattern (marker after frontmatter) and legacy (marker at top).
|
|
205
|
+
if (!content || (!content.includes(GENERATED_MARKER) && !content.startsWith(LEGACY_GENERATED_HEADER))) continue;
|
|
200
206
|
|
|
201
207
|
try {
|
|
202
208
|
unlinkSync(fullPath);
|
|
@@ -303,7 +309,9 @@ function renderCursorRules(cwd, rulesDir, options = {}) {
|
|
|
303
309
|
}
|
|
304
310
|
}
|
|
305
311
|
|
|
306
|
-
|
|
312
|
+
// Frontmatter MUST be at byte 0 for Cursor's YAML parser.
|
|
313
|
+
// Generated marker goes on the line immediately after the closing ---.
|
|
314
|
+
const content = frontmatter.join('\n') + '\n' + GENERATED_MARKER + '\n\n' + agentsMd.trim() + '\n' + refSection;
|
|
307
315
|
writeFileSync(join(cursorRulesDir, `${scopeToFilename(scope)}.mdc`), content);
|
|
308
316
|
count++;
|
|
309
317
|
}
|
|
@@ -318,10 +326,13 @@ function renderCursorRules(cwd, rulesDir, options = {}) {
|
|
|
318
326
|
}
|
|
319
327
|
|
|
320
328
|
function generateCursorAwRoutingRule() {
|
|
321
|
-
|
|
329
|
+
// Frontmatter MUST be at byte 0 for Cursor's alwaysApply/globs detection.
|
|
330
|
+
return `---
|
|
322
331
|
description: "AW global routing: select route, READ stage skill, then respond"
|
|
323
332
|
alwaysApply: true
|
|
324
333
|
---
|
|
334
|
+
${GENERATED_MARKER}
|
|
335
|
+
|
|
325
336
|
# AW Global Routing
|
|
326
337
|
|
|
327
338
|
## Hard Gate (MUST — do not skip)
|
|
@@ -460,7 +471,8 @@ function renderClaudeRules(cwd, rulesDir, options = {}) {
|
|
|
460
471
|
}
|
|
461
472
|
}
|
|
462
473
|
|
|
463
|
-
|
|
474
|
+
// Claude Code reads .md frontmatter similarly — keep marker after frontmatter.
|
|
475
|
+
const content = frontmatter + GENERATED_MARKER + '\n\n' + agentsMd.trim() + '\n' + refSection;
|
|
464
476
|
writeFileSync(join(claudeRulesDir, `${scopeToFilename(scope)}.md`), content);
|
|
465
477
|
count++;
|
|
466
478
|
}
|
|
@@ -471,21 +483,120 @@ function renderClaudeRules(cwd, rulesDir, options = {}) {
|
|
|
471
483
|
/**
|
|
472
484
|
* Generate a rules section for CLAUDE.md from runtime AW rules.
|
|
473
485
|
*/
|
|
474
|
-
|
|
486
|
+
/**
|
|
487
|
+
* Resolve the final scope set for AGENTS.md/CLAUDE.md filtering.
|
|
488
|
+
* Precedence: explicit option > .aw/config.json awRuleScopes > auto-detect.
|
|
489
|
+
* Returns undefined if user explicitly disabled filtering (awRuleScopes: "all").
|
|
490
|
+
*/
|
|
491
|
+
export function resolveApplicableScopes(cwd, options = {}) {
|
|
492
|
+
if (options.applicableScopes) return options.applicableScopes;
|
|
493
|
+
|
|
494
|
+
// Read .aw/config.json if present.
|
|
495
|
+
const configPath = join(cwd, '.aw', 'config.json');
|
|
496
|
+
if (existsSync(configPath)) {
|
|
497
|
+
try {
|
|
498
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
499
|
+
const setting = config.awRuleScopes;
|
|
500
|
+
if (setting === 'all') return undefined; // disable filtering
|
|
501
|
+
if (Array.isArray(setting)) return new Set(setting);
|
|
502
|
+
} catch { /* fall through to auto-detect */ }
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return detectApplicableScopes(cwd);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Detect applicable rule scopes for a consumer repo based on filesystem signals.
|
|
510
|
+
* Returns a Set of scope names (e.g. 'frontend', 'backend', 'mobile').
|
|
511
|
+
* 'universal' and 'security' are ALWAYS returned (cross-cutting, apply everywhere).
|
|
512
|
+
*/
|
|
513
|
+
export function detectApplicableScopes(cwd) {
|
|
514
|
+
const scopes = new Set(['universal', 'security']);
|
|
515
|
+
|
|
516
|
+
const has = (rel) => existsSync(join(cwd, rel));
|
|
517
|
+
const readPkg = () => {
|
|
518
|
+
try {
|
|
519
|
+
if (!has('package.json')) return null;
|
|
520
|
+
return JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
|
|
521
|
+
} catch {
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
const pkg = readPkg();
|
|
527
|
+
if (pkg) {
|
|
528
|
+
const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
529
|
+
// Frontend signals
|
|
530
|
+
if (deps.vue || deps.nuxt || deps['@vue/cli-service'] || deps.react || deps.next || deps.svelte) {
|
|
531
|
+
scopes.add('frontend');
|
|
532
|
+
}
|
|
533
|
+
// Backend signals
|
|
534
|
+
if (deps['@nestjs/core'] || deps.express || deps.fastify || deps.koa || deps['@platform-core/base-service']) {
|
|
535
|
+
scopes.add('backend');
|
|
536
|
+
scopes.add('api-design');
|
|
537
|
+
}
|
|
538
|
+
// Data signals
|
|
539
|
+
if (deps.mongoose || deps.typeorm || deps.prisma || deps.knex || deps['@platform-core/firestore']) {
|
|
540
|
+
scopes.add('data');
|
|
541
|
+
}
|
|
542
|
+
// SDET signals
|
|
543
|
+
if (deps.playwright || deps['@playwright/test'] || deps['@gohighlevel/sdet-platform-core']) {
|
|
544
|
+
scopes.add('sdet');
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (has('pubspec.yaml')) scopes.add('mobile'); // Flutter/Dart
|
|
549
|
+
if (has('pom.xml') || has('build.gradle') || has('build.gradle.kts')) scopes.add('backend');
|
|
550
|
+
if (has('go.mod')) scopes.add('backend');
|
|
551
|
+
if (has('Cargo.toml')) scopes.add('backend');
|
|
552
|
+
if (has('requirements.txt') || has('pyproject.toml')) scopes.add('backend');
|
|
553
|
+
|
|
554
|
+
// Infra signals
|
|
555
|
+
if (has('Dockerfile') || has('Jenkinsfile') || has('helm') || has('terraform')) {
|
|
556
|
+
scopes.add('infra');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// If we only found the defaults (universal + security) with no other signals,
|
|
560
|
+
// return undefined to preserve current behavior (show all rules). Filtering
|
|
561
|
+
// only kicks in when we positively detect a framework or stack.
|
|
562
|
+
if (scopes.size <= 2) return undefined;
|
|
563
|
+
return scopes;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function filterRulesByScopes(rules, applicableScopes) {
|
|
567
|
+
if (!applicableScopes || applicableScopes.size === 0) return rules;
|
|
568
|
+
return rules.filter(r => {
|
|
569
|
+
const scope = String(r.id || '').split('/')[0];
|
|
570
|
+
return applicableScopes.has(scope);
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export function generateClaudeMdRulesSection(rulesDir, options = {}) {
|
|
475
575
|
const manifest = readManifest(rulesDir);
|
|
476
576
|
if (!manifest) return '';
|
|
477
577
|
|
|
478
578
|
const mustRules = manifest.rules.filter(r => r.severity === 'MUST');
|
|
479
|
-
|
|
579
|
+
const applicableScopes = options.applicableScopes;
|
|
580
|
+
const filteredRules = applicableScopes
|
|
581
|
+
? filterRulesByScopes(mustRules, applicableScopes)
|
|
582
|
+
: mustRules;
|
|
583
|
+
if (filteredRules.length === 0) return '';
|
|
480
584
|
|
|
481
585
|
const lines = [
|
|
482
586
|
'## Platform Rules (MUST)',
|
|
483
587
|
'',
|
|
484
588
|
'> Rendered from platform `.aw/.aw_rules/`. Full details in reference files.',
|
|
485
|
-
'',
|
|
486
589
|
];
|
|
487
590
|
|
|
488
|
-
|
|
591
|
+
if (applicableScopes) {
|
|
592
|
+
lines.push(
|
|
593
|
+
`> Filtered to scopes detected in this repo: \`${[...applicableScopes].sort().join('`, `')}\`. ` +
|
|
594
|
+
`To override, set \`awRuleScopes\` in \`.aw/config.json\`.`,
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
lines.push('');
|
|
598
|
+
|
|
599
|
+
for (const rule of filteredRules) {
|
|
489
600
|
lines.push(`- [ ] **${rule.id}** — ${rule.description}`);
|
|
490
601
|
}
|
|
491
602
|
|
|
@@ -516,14 +627,28 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
516
627
|
// Reference table — tells all IDEs (especially Codex) where to read domain rules.
|
|
517
628
|
// Codex can't auto-trigger by glob, but it CAN read these files when working in
|
|
518
629
|
// the matching area. Keep AGENTS.md lean; full content stays in the source files.
|
|
519
|
-
const
|
|
630
|
+
const allScopes = listRuleScopes(rulesDir, {
|
|
520
631
|
includeNestedScopes: stackOverlaysEnabled(options),
|
|
521
632
|
});
|
|
633
|
+
// Filter domain scopes by what's relevant to this consumer repo.
|
|
634
|
+
// 'universal' and 'security' always apply; other domains require detection.
|
|
635
|
+
const applicableScopes = options.applicableScopes;
|
|
636
|
+
const isApplicable = (scope) => !applicableScopes
|
|
637
|
+
|| applicableScopes.has(scope)
|
|
638
|
+
|| applicableScopes.has(scope.split('/')[0]);
|
|
639
|
+
const scopes = allScopes.filter(({ scope }) => isApplicable(scope));
|
|
522
640
|
const domains = scopes.filter(({ scope }) => !scope.includes('/'));
|
|
523
641
|
const overlays = scopes.filter(({ scope }) => scope.includes('/'));
|
|
524
642
|
if (domains.length > 0) {
|
|
525
643
|
lines.push('### Domain Rules');
|
|
526
644
|
lines.push('');
|
|
645
|
+
if (applicableScopes) {
|
|
646
|
+
lines.push(
|
|
647
|
+
`> Filtered to scopes detected in this repo: \`${[...applicableScopes].sort().join('`, `')}\`. ` +
|
|
648
|
+
`To override, set \`awRuleScopes\` (array or "all") in \`.aw/config.json\`.`,
|
|
649
|
+
);
|
|
650
|
+
lines.push('');
|
|
651
|
+
}
|
|
527
652
|
lines.push('When working in a specific domain, read the matching rules file:');
|
|
528
653
|
lines.push('');
|
|
529
654
|
lines.push('| Domain | Read when editing | Rules file |');
|
|
@@ -566,10 +691,14 @@ export function renderRules(cwd, options = {}) {
|
|
|
566
691
|
const rulesDir = resolveRulesSourceDir(cwd, options);
|
|
567
692
|
if (!rulesDir) return { cursorCount: 0, claudeSection: '', agentsSection: '' };
|
|
568
693
|
|
|
694
|
+
// Resolve applicable scopes for AGENTS.md / CLAUDE.md MUST-rule list.
|
|
695
|
+
// Order: explicit option → .aw/config.json awRuleScopes → auto-detect.
|
|
696
|
+
const applicableScopes = resolveApplicableScopes(cwd, options);
|
|
697
|
+
|
|
569
698
|
const cursorCount = renderCursorRules(cwd, rulesDir, options);
|
|
570
699
|
const claudeCount = renderClaudeRules(cwd, rulesDir, options);
|
|
571
|
-
const claudeSection = generateClaudeMdRulesSection(rulesDir);
|
|
572
|
-
const agentsSection = generateAgentsMdRulesSection(rulesDir, { ...options, outputDir: cwd });
|
|
700
|
+
const claudeSection = generateClaudeMdRulesSection(rulesDir, { applicableScopes });
|
|
701
|
+
const agentsSection = generateAgentsMdRulesSection(rulesDir, { ...options, outputDir: cwd, applicableScopes });
|
|
573
702
|
|
|
574
703
|
// Also render to global dirs so rules apply everywhere
|
|
575
704
|
const HOME = options.homeDir || homedir();
|