@ghl-ai/aw 0.1.39-beta.8 → 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/cli.mjs +8 -1
- package/codex.mjs +839 -0
- package/commands/doctor.mjs +1086 -0
- package/commands/init.mjs +71 -81
- package/commands/link-project.mjs +12 -1
- package/commands/nuke.mjs +14 -4
- package/commands/pull.mjs +111 -11
- package/commands/push-rules.mjs +4 -15
- package/commands/push.mjs +4 -4
- package/commands/search.mjs +1 -1
- package/commands/startup.mjs +87 -0
- package/constants.mjs +3 -1
- package/ecc.mjs +130 -42
- package/git.mjs +4 -23
- package/hook-manifest.mjs +195 -0
- package/hooks/codex-home.mjs +184 -0
- package/hooks/shared-phase-scripts.mjs +69 -0
- package/integrate.mjs +219 -47
- package/link.mjs +36 -1
- package/mcp.mjs +2 -10
- package/package.json +8 -2
- package/paths.mjs +1 -1
- package/registry.mjs +1 -1
- package/render-rules.mjs +267 -27
- package/startup.mjs +562 -0
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghl-ai/aw",
|
|
3
|
-
"version": "0.1.39
|
|
3
|
+
"version": "0.1.39",
|
|
4
4
|
"description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"bin":
|
|
6
|
+
"bin": {
|
|
7
|
+
"aw": "bin.js"
|
|
8
|
+
},
|
|
7
9
|
"files": [
|
|
8
10
|
"bin.js",
|
|
9
11
|
"cli.mjs",
|
|
10
12
|
"commands/",
|
|
11
13
|
"config.mjs",
|
|
14
|
+
"codex.mjs",
|
|
12
15
|
"constants.mjs",
|
|
13
16
|
"fmt.mjs",
|
|
14
17
|
"git.mjs",
|
|
@@ -23,8 +26,11 @@
|
|
|
23
26
|
"slack-sim/",
|
|
24
27
|
"file-tree.mjs",
|
|
25
28
|
"apply.mjs",
|
|
29
|
+
"hook-manifest.mjs",
|
|
26
30
|
"update.mjs",
|
|
27
31
|
"hooks.mjs",
|
|
32
|
+
"hooks/",
|
|
33
|
+
"startup.mjs",
|
|
28
34
|
"ecc.mjs",
|
|
29
35
|
"render-rules.mjs",
|
|
30
36
|
"telemetry.mjs"
|
package/paths.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { existsSync, statSync, lstatSync, readlinkSync } from 'node:fs';
|
|
4
4
|
import { join, resolve, relative, basename, dirname } from 'node:path';
|
|
5
5
|
|
|
6
|
-
const VALID_TYPES = new Set(['agents', 'skills', 'commands', 'evals']);
|
|
6
|
+
const VALID_TYPES = new Set(['agents', 'skills', 'commands', 'evals', 'references']);
|
|
7
7
|
|
|
8
8
|
// IDE dirs that may contain symlinks into .aw_registry/
|
|
9
9
|
const IDE_PREFIXES = ['.claude/', '.cursor/', '.codex/', '.agents/'];
|
package/registry.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
|
4
4
|
import { join, relative } from 'node:path';
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
|
|
7
|
-
const TYPE_DIRS = new Set(['agents', 'skills', 'commands', 'evals']);
|
|
7
|
+
const TYPE_DIRS = new Set(['agents', 'skills', 'commands', 'evals', 'references']);
|
|
8
8
|
const SKIP_DIRS = new Set(['docs']);
|
|
9
9
|
|
|
10
10
|
export function sha256(content) {
|
package/render-rules.mjs
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
-
// render-rules.mjs — Render
|
|
1
|
+
// render-rules.mjs — Render runtime AW rules into IDE-specific output files.
|
|
2
2
|
|
|
3
3
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, unlinkSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
4
|
+
import { dirname, join, relative } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
6
|
import * as fmt from './fmt.mjs';
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
import { RULES_RUNTIME_DIR } from './constants.mjs';
|
|
8
|
+
|
|
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 */
|
|
@@ -160,6 +165,33 @@ function scopeToLabel(scope) {
|
|
|
160
165
|
return scope.replaceAll('/', ' ');
|
|
161
166
|
}
|
|
162
167
|
|
|
168
|
+
function toPosixPath(filePath) {
|
|
169
|
+
return filePath.replaceAll('\\', '/');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function relativeRuleLink(outputDir, targetPath) {
|
|
173
|
+
return toPosixPath(relative(outputDir, targetPath));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function resolveRulesSourceDir(cwd, options = {}) {
|
|
177
|
+
const HOME = options.homeDir || homedir();
|
|
178
|
+
const candidates = [
|
|
179
|
+
join(cwd, RULES_RUNTIME_DIR),
|
|
180
|
+
];
|
|
181
|
+
|
|
182
|
+
if (cwd !== HOME) {
|
|
183
|
+
candidates.push(
|
|
184
|
+
join(HOME, RULES_RUNTIME_DIR),
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return candidates.find(existsSync) || null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function rulesRootDir(rulesDir) {
|
|
192
|
+
return dirname(dirname(rulesDir));
|
|
193
|
+
}
|
|
194
|
+
|
|
163
195
|
function pruneStaleGeneratedRules(outputDir, expectedFilenames) {
|
|
164
196
|
if (!existsSync(outputDir)) return;
|
|
165
197
|
|
|
@@ -169,7 +201,8 @@ function pruneStaleGeneratedRules(outputDir, expectedFilenames) {
|
|
|
169
201
|
|
|
170
202
|
const fullPath = join(outputDir, entry.name);
|
|
171
203
|
const content = readOrNull(fullPath);
|
|
172
|
-
|
|
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;
|
|
173
206
|
|
|
174
207
|
try {
|
|
175
208
|
unlinkSync(fullPath);
|
|
@@ -267,19 +300,101 @@ function renderCursorRules(cwd, rulesDir, options = {}) {
|
|
|
267
300
|
const refs = readdirSync(referencesDir).filter(f => f.endsWith('.md')).sort();
|
|
268
301
|
if (refs.length > 0) {
|
|
269
302
|
refSection = '\n## References\n\n' +
|
|
270
|
-
refs.map(f =>
|
|
303
|
+
refs.map((f) => {
|
|
304
|
+
const targetPath = join(rulesDir, 'platform', scope, 'references', f);
|
|
305
|
+
const refPath = relativeRuleLink(cursorRulesDir, targetPath);
|
|
306
|
+
return `- [${f.replace('.md', '')}](${refPath})`;
|
|
307
|
+
}).join('\n') +
|
|
271
308
|
'\n';
|
|
272
309
|
}
|
|
273
310
|
}
|
|
274
311
|
|
|
275
|
-
|
|
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;
|
|
276
315
|
writeFileSync(join(cursorRulesDir, `${scopeToFilename(scope)}.mdc`), content);
|
|
277
316
|
count++;
|
|
278
317
|
}
|
|
279
318
|
|
|
319
|
+
// Generate the AW routing rule — this is the hard gate that forces
|
|
320
|
+
// route selection → stage skill read → follow behavior → respond.
|
|
321
|
+
const routingRule = generateCursorAwRoutingRule();
|
|
322
|
+
writeFileSync(join(cursorRulesDir, 'common-aw-routing.mdc'), routingRule);
|
|
323
|
+
count++;
|
|
324
|
+
|
|
280
325
|
return count;
|
|
281
326
|
}
|
|
282
327
|
|
|
328
|
+
function generateCursorAwRoutingRule() {
|
|
329
|
+
// Frontmatter MUST be at byte 0 for Cursor's alwaysApply/globs detection.
|
|
330
|
+
return `---
|
|
331
|
+
description: "AW global routing: select route, READ stage skill, then respond"
|
|
332
|
+
alwaysApply: true
|
|
333
|
+
---
|
|
334
|
+
${GENERATED_MARKER}
|
|
335
|
+
|
|
336
|
+
# AW Global Routing
|
|
337
|
+
|
|
338
|
+
## Hard Gate (MUST — do not skip)
|
|
339
|
+
|
|
340
|
+
For every non-trivial request, execute these steps in order before any substantive response:
|
|
341
|
+
|
|
342
|
+
1. **Load the router** — Read the using-aw-skills SKILL.md from the skills directory.
|
|
343
|
+
|
|
344
|
+
2. **Select route** — using the decision tree below, pick the smallest correct AW route.
|
|
345
|
+
|
|
346
|
+
3. **Read the stage skill** — you MUST Read the matching skill file before responding:
|
|
347
|
+
- /aw-plan → Read aw-plan/SKILL.md
|
|
348
|
+
- /aw-build → Read aw-build/SKILL.md
|
|
349
|
+
- /aw-investigate → Read aw-investigate/SKILL.md
|
|
350
|
+
- /aw-test → Read aw-test/SKILL.md
|
|
351
|
+
- /aw-review → Read aw-review/SKILL.md
|
|
352
|
+
- /aw-deploy → Read aw-deploy/SKILL.md
|
|
353
|
+
- /aw-ship → Read aw-ship/SKILL.md
|
|
354
|
+
|
|
355
|
+
4. **Follow the skill's behavior** — produce the artifacts the skill defines, not general-knowledge answers.
|
|
356
|
+
|
|
357
|
+
5. **Then respond** — only after steps 1-4.
|
|
358
|
+
|
|
359
|
+
Stating a route without Reading the skill file is NOT compliance.
|
|
360
|
+
|
|
361
|
+
## Route Decision Tree
|
|
362
|
+
|
|
363
|
+
\`\`\`text
|
|
364
|
+
Does an approved plan/spec already exist for this exact work?
|
|
365
|
+
├── NO → Is the request about a bug, alert, or unclear failure?
|
|
366
|
+
│ ├── YES → /aw-investigate
|
|
367
|
+
│ └── NO → /aw-plan ← DEFAULT for anything new
|
|
368
|
+
└── YES → Is the work implemented and needs testing/review?
|
|
369
|
+
├── YES → /aw-test or /aw-review
|
|
370
|
+
└── NO → Is this a deploy or release action?
|
|
371
|
+
├── YES → /aw-deploy or /aw-ship
|
|
372
|
+
└── NO → /aw-build
|
|
373
|
+
\`\`\`
|
|
374
|
+
|
|
375
|
+
**Plan-first rule**: New endpoints, services, schemas, features, architecture changes, and integrations ALL go through /aw-plan first. /aw-build requires an approved plan or a mechanical change.
|
|
376
|
+
|
|
377
|
+
## Default Assumptions
|
|
378
|
+
|
|
379
|
+
- /aw-plan → write file artifacts under .aw_docs/features/<slug>/ unless user says "chat only".
|
|
380
|
+
- /aw-build → code changes with tests.
|
|
381
|
+
- /aw-investigate → reproduction + root cause evidence.
|
|
382
|
+
|
|
383
|
+
## Org Rules
|
|
384
|
+
|
|
385
|
+
After the stage skill is loaded, also read the relevant org rules:
|
|
386
|
+
|
|
387
|
+
- Always read: universal/AGENTS.md and security/AGENTS.md
|
|
388
|
+
- Then pick the smallest correct domain rule (api-design, backend, data, frontend, infra, mobile, sdet).
|
|
389
|
+
- If repo-local instructions conflict with org-level AW rules, follow org-level.
|
|
390
|
+
|
|
391
|
+
## Compatibility
|
|
392
|
+
|
|
393
|
+
- /aw-execute → resolve to /aw-build
|
|
394
|
+
- /aw-verify → resolve to /aw-test or /aw-review
|
|
395
|
+
`;
|
|
396
|
+
}
|
|
397
|
+
|
|
283
398
|
/** Rule scope → Claude Code paths: frontmatter (only supported field for .claude/rules/) */
|
|
284
399
|
const SCOPE_PATHS = {
|
|
285
400
|
'api-design': ['src/**/*.controller.ts', 'src/**/dto/**/*.ts', 'src/**/*.client.ts'],
|
|
@@ -347,12 +462,17 @@ function renderClaudeRules(cwd, rulesDir, options = {}) {
|
|
|
347
462
|
const refs = readdirSync(referencesDir).filter(f => f.endsWith('.md')).sort();
|
|
348
463
|
if (refs.length > 0) {
|
|
349
464
|
refSection = '\n## References\n\n' +
|
|
350
|
-
refs.map(f =>
|
|
465
|
+
refs.map((f) => {
|
|
466
|
+
const targetPath = join(rulesDir, 'platform', scope, 'references', f);
|
|
467
|
+
const refPath = relativeRuleLink(claudeRulesDir, targetPath);
|
|
468
|
+
return `- [${f.replace('.md', '')}](${refPath})`;
|
|
469
|
+
}).join('\n') +
|
|
351
470
|
'\n';
|
|
352
471
|
}
|
|
353
472
|
}
|
|
354
473
|
|
|
355
|
-
|
|
474
|
+
// Claude Code reads .md frontmatter similarly — keep marker after frontmatter.
|
|
475
|
+
const content = frontmatter + GENERATED_MARKER + '\n\n' + agentsMd.trim() + '\n' + refSection;
|
|
356
476
|
writeFileSync(join(claudeRulesDir, `${scopeToFilename(scope)}.md`), content);
|
|
357
477
|
count++;
|
|
358
478
|
}
|
|
@@ -361,44 +481,144 @@ function renderClaudeRules(cwd, rulesDir, options = {}) {
|
|
|
361
481
|
}
|
|
362
482
|
|
|
363
483
|
/**
|
|
364
|
-
* Generate a rules section for CLAUDE.md from .
|
|
484
|
+
* Generate a rules section for CLAUDE.md from runtime AW rules.
|
|
365
485
|
*/
|
|
366
|
-
|
|
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 = {}) {
|
|
367
575
|
const manifest = readManifest(rulesDir);
|
|
368
576
|
if (!manifest) return '';
|
|
369
577
|
|
|
370
578
|
const mustRules = manifest.rules.filter(r => r.severity === 'MUST');
|
|
371
|
-
|
|
579
|
+
const applicableScopes = options.applicableScopes;
|
|
580
|
+
const filteredRules = applicableScopes
|
|
581
|
+
? filterRulesByScopes(mustRules, applicableScopes)
|
|
582
|
+
: mustRules;
|
|
583
|
+
if (filteredRules.length === 0) return '';
|
|
372
584
|
|
|
373
585
|
const lines = [
|
|
374
586
|
'## Platform Rules (MUST)',
|
|
375
587
|
'',
|
|
376
|
-
'>
|
|
377
|
-
'',
|
|
588
|
+
'> Rendered from platform `.aw/.aw_rules/`. Full details in reference files.',
|
|
378
589
|
];
|
|
379
590
|
|
|
380
|
-
|
|
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) {
|
|
381
600
|
lines.push(`- [ ] **${rule.id}** — ${rule.description}`);
|
|
382
601
|
}
|
|
383
602
|
|
|
384
603
|
lines.push('');
|
|
385
|
-
lines.push('See `.
|
|
604
|
+
lines.push('See `.aw/.aw_rules/rule-manifest.json` for all rules including SHOULD/MAY.');
|
|
386
605
|
lines.push('');
|
|
387
606
|
|
|
388
607
|
return lines.join('\n');
|
|
389
608
|
}
|
|
390
609
|
|
|
391
610
|
/**
|
|
392
|
-
* Generate a rules section for AGENTS.md from the top-level AGENTS.md in .
|
|
611
|
+
* Generate a rules section for AGENTS.md from the top-level AGENTS.md in runtime AW rules.
|
|
393
612
|
*/
|
|
394
613
|
export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
395
614
|
const topLevelAgents = readOrNull(join(rulesDir, 'AGENTS.md'));
|
|
396
615
|
if (!topLevelAgents) return '';
|
|
616
|
+
const outputDir = options.outputDir || rulesRootDir(rulesDir);
|
|
397
617
|
|
|
398
618
|
const lines = [
|
|
399
619
|
'## Platform Rules — Non-Negotiables',
|
|
400
620
|
'',
|
|
401
|
-
'>
|
|
621
|
+
'> Rendered from platform `.aw/.aw_rules/`.',
|
|
402
622
|
'',
|
|
403
623
|
topLevelAgents.trim(),
|
|
404
624
|
'',
|
|
@@ -407,14 +627,28 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
407
627
|
// Reference table — tells all IDEs (especially Codex) where to read domain rules.
|
|
408
628
|
// Codex can't auto-trigger by glob, but it CAN read these files when working in
|
|
409
629
|
// the matching area. Keep AGENTS.md lean; full content stays in the source files.
|
|
410
|
-
const
|
|
630
|
+
const allScopes = listRuleScopes(rulesDir, {
|
|
411
631
|
includeNestedScopes: stackOverlaysEnabled(options),
|
|
412
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));
|
|
413
640
|
const domains = scopes.filter(({ scope }) => !scope.includes('/'));
|
|
414
641
|
const overlays = scopes.filter(({ scope }) => scope.includes('/'));
|
|
415
642
|
if (domains.length > 0) {
|
|
416
643
|
lines.push('### Domain Rules');
|
|
417
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
|
+
}
|
|
418
652
|
lines.push('When working in a specific domain, read the matching rules file:');
|
|
419
653
|
lines.push('');
|
|
420
654
|
lines.push('| Domain | Read when editing | Rules file |');
|
|
@@ -422,7 +656,8 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
422
656
|
|
|
423
657
|
for (const { scope } of domains) {
|
|
424
658
|
const hint = SCOPE_HINTS[scope] || 'Related files';
|
|
425
|
-
|
|
659
|
+
const rulesFile = relativeRuleLink(outputDir, join(rulesDir, 'platform', scope, 'AGENTS.md'));
|
|
660
|
+
lines.push(`| ${scope} | ${hint} | \`${rulesFile}\` |`);
|
|
426
661
|
}
|
|
427
662
|
lines.push('');
|
|
428
663
|
}
|
|
@@ -437,7 +672,8 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
437
672
|
|
|
438
673
|
for (const { scope } of overlays) {
|
|
439
674
|
const hint = SCOPE_HINTS[scope] || 'Stack-specific files';
|
|
440
|
-
|
|
675
|
+
const rulesFile = relativeRuleLink(outputDir, join(rulesDir, 'platform', scope, 'AGENTS.md'));
|
|
676
|
+
lines.push(`| ${scope} | ${hint} | \`${rulesFile}\` |`);
|
|
441
677
|
}
|
|
442
678
|
lines.push('');
|
|
443
679
|
}
|
|
@@ -447,18 +683,22 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
447
683
|
|
|
448
684
|
/**
|
|
449
685
|
* Main render function. Call after aw pull / aw sync.
|
|
450
|
-
* Reads .
|
|
686
|
+
* Reads runtime .aw/.aw_rules/ and renders:
|
|
451
687
|
* 1. .cursor/rules/<scope>.mdc — at cwd AND at $HOME (global)
|
|
452
688
|
* 2. Returns sections for CLAUDE.md and AGENTS.md injection
|
|
453
689
|
*/
|
|
454
690
|
export function renderRules(cwd, options = {}) {
|
|
455
|
-
const rulesDir =
|
|
456
|
-
if (!
|
|
691
|
+
const rulesDir = resolveRulesSourceDir(cwd, options);
|
|
692
|
+
if (!rulesDir) return { cursorCount: 0, claudeSection: '', agentsSection: '' };
|
|
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);
|
|
457
697
|
|
|
458
698
|
const cursorCount = renderCursorRules(cwd, rulesDir, options);
|
|
459
699
|
const claudeCount = renderClaudeRules(cwd, rulesDir, options);
|
|
460
|
-
const claudeSection = generateClaudeMdRulesSection(rulesDir);
|
|
461
|
-
const agentsSection = generateAgentsMdRulesSection(rulesDir, options);
|
|
700
|
+
const claudeSection = generateClaudeMdRulesSection(rulesDir, { applicableScopes });
|
|
701
|
+
const agentsSection = generateAgentsMdRulesSection(rulesDir, { ...options, outputDir: cwd, applicableScopes });
|
|
462
702
|
|
|
463
703
|
// Also render to global dirs so rules apply everywhere
|
|
464
704
|
const HOME = options.homeDir || homedir();
|