@ghl-ai/aw 0.1.39-beta.15 → 0.1.39-beta.16
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/integrate.mjs +63 -7
- package/package.json +1 -1
- package/render-rules.mjs +124 -60
package/integrate.mjs
CHANGED
|
@@ -129,8 +129,15 @@ function applyManagedInstructionSections(content, file, rulesSections = {}, opti
|
|
|
129
129
|
return next ? `${next}\n` : '';
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
// Marker tells users (and aw init) where the managed section starts.
|
|
133
|
+
// Everything BEFORE this marker is repo-owned and never touched by aw.
|
|
134
|
+
// Everything AFTER is managed by aw — re-rendered on every aw init.
|
|
135
|
+
const managedBoundary = '<!-- aw-managed: content below is regenerated by `aw init` — put your own content above this line -->';
|
|
136
|
+
const appended = [managedBoundary, ...sections].join('\n\n').trim();
|
|
137
|
+
// Strip any prior managedBoundary line from `next` so we don't accumulate them
|
|
138
|
+
// when re-running aw init.
|
|
139
|
+
const cleaned = next.split('\n').filter(line => line.trim() !== managedBoundary).join('\n').trimEnd();
|
|
140
|
+
return cleaned ? `${cleaned}\n\n${appended}\n` : `${appended}\n`;
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
/**
|
|
@@ -188,28 +195,77 @@ function findFiles(dir, typeName) {
|
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
198
|
+
* Read consumer config to decide whether to write managed sections into
|
|
199
|
+
* the repo's AGENTS.md / CLAUDE.md.
|
|
200
|
+
*
|
|
201
|
+
* Default behaviour: do NOT touch repo-local AGENTS.md/CLAUDE.md. The same
|
|
202
|
+
* AW Router Bridge + Platform Rules content is already injected into the
|
|
203
|
+
* GLOBAL files (~/.claude/CLAUDE.md and ~/.codex/AGENTS.md) by aw init,
|
|
204
|
+
* and Claude/Codex always read those. Modifying the repo file is invasive
|
|
205
|
+
* and bloats it with content that's already loaded globally.
|
|
206
|
+
*
|
|
207
|
+
* Repos that DO want managed sections in their repo file (e.g. for editors
|
|
208
|
+
* that don't read the global files, or to share AW context with collaborators
|
|
209
|
+
* who haven't run aw init) can opt in via .aw/config.json:
|
|
210
|
+
*
|
|
211
|
+
* { "writeRepoInstructionFiles": true }
|
|
212
|
+
*
|
|
213
|
+
* Existing repo files with managed sections are STRIPPED on next aw init
|
|
214
|
+
* (so users see the managed content go away — clean migration). Repos with
|
|
215
|
+
* the opt-in flag get them updated as before.
|
|
216
|
+
*/
|
|
217
|
+
function shouldWriteRepoInstructionFiles(cwd) {
|
|
218
|
+
const configPath = join(cwd, '.aw', 'config.json');
|
|
219
|
+
if (!existsSync(configPath)) return false;
|
|
220
|
+
try {
|
|
221
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
222
|
+
return config.writeRepoInstructionFiles === true;
|
|
223
|
+
} catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Refresh rules sections in any existing AGENTS.md/CLAUDE.md at the repo
|
|
230
|
+
* root.
|
|
231
|
+
*
|
|
232
|
+
* By default, only STRIPS managed sections (we no longer want them in repo
|
|
233
|
+
* files — they're in global ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md).
|
|
234
|
+
*
|
|
235
|
+
* If `writeRepoInstructionFiles: true` is set in `.aw/config.json`, behaves
|
|
236
|
+
* as before: re-renders the AW Router Bridge + Platform Rules in the repo
|
|
237
|
+
* file too.
|
|
194
238
|
*/
|
|
195
239
|
export function copyInstructions(cwd, tempDir, namespace) {
|
|
196
240
|
const rulesSections = renderRules(cwd);
|
|
241
|
+
const writeManaged = shouldWriteRepoInstructionFiles(cwd);
|
|
197
242
|
const createdFiles = [];
|
|
243
|
+
|
|
198
244
|
for (const file of ['AGENTS.md', 'CLAUDE.md']) {
|
|
199
245
|
const dest = join(cwd, file);
|
|
200
246
|
if (existsSync(dest)) {
|
|
201
247
|
const existing = readFileSync(dest, 'utf8');
|
|
202
|
-
const updated =
|
|
248
|
+
const updated = writeManaged
|
|
249
|
+
? applyManagedInstructionSections(existing, file, rulesSections, { includeBridge: false })
|
|
250
|
+
: applyManagedInstructionSections(existing, file, {}, { includeBridge: false });
|
|
203
251
|
|
|
204
252
|
if (updated !== existing) {
|
|
205
253
|
writeFileSync(dest, updated);
|
|
206
|
-
|
|
254
|
+
if (writeManaged) {
|
|
255
|
+
fmt.logSuccess(`Updated ${file}`);
|
|
256
|
+
} else {
|
|
257
|
+
fmt.logStep(`Stripped aw-managed sections from ${file} (now in ~/.claude/CLAUDE.md / ~/.codex/AGENTS.md)`);
|
|
258
|
+
}
|
|
207
259
|
}
|
|
208
260
|
continue;
|
|
209
261
|
}
|
|
210
262
|
|
|
211
263
|
if (file === 'CLAUDE.md') continue;
|
|
212
264
|
|
|
265
|
+
// Only CREATE a repo AGENTS.md if writeRepoInstructionFiles is opted in.
|
|
266
|
+
// Without the opt-in, repo files are user-owned and aw init never creates them.
|
|
267
|
+
if (!writeManaged) continue;
|
|
268
|
+
|
|
213
269
|
if (tempDir) {
|
|
214
270
|
const src = join(tempDir, '.aw_registry', file);
|
|
215
271
|
if (existsSync(src)) {
|
package/package.json
CHANGED
package/render-rules.mjs
CHANGED
|
@@ -322,62 +322,9 @@ function renderCursorRules(cwd, rulesDir, options = {}) {
|
|
|
322
322
|
writeFileSync(join(cursorRulesDir, 'common-aw-routing.mdc'), routingRule);
|
|
323
323
|
count++;
|
|
324
324
|
|
|
325
|
-
// Generate the prompt-response-format rule — machine-parseable trace
|
|
326
|
-
// of what skills/rules/MCPs were used during a response.
|
|
327
|
-
const traceRule = generateCursorPromptTraceRule();
|
|
328
|
-
writeFileSync(join(cursorRulesDir, 'common-prompt-trace.mdc'), traceRule);
|
|
329
|
-
count++;
|
|
330
|
-
|
|
331
325
|
return count;
|
|
332
326
|
}
|
|
333
327
|
|
|
334
|
-
function generateCursorPromptTraceRule() {
|
|
335
|
-
// Frontmatter MUST be at byte 0. Machine-parseable JSON block at end of response.
|
|
336
|
-
return `---
|
|
337
|
-
description: "Append a machine-parseable AW trace at the end of every response"
|
|
338
|
-
alwaysApply: true
|
|
339
|
-
---
|
|
340
|
-
${GENERATED_MARKER}
|
|
341
|
-
|
|
342
|
-
# AW Prompt Trace
|
|
343
|
-
|
|
344
|
-
At the end of every substantive response, append a JSON-fenced block named \`aw-trace\` with the skills, rules, and MCPs that were loaded or applied during the response.
|
|
345
|
-
|
|
346
|
-
## Format
|
|
347
|
-
|
|
348
|
-
\`\`\`aw-trace
|
|
349
|
-
{
|
|
350
|
-
"route": "/aw-plan",
|
|
351
|
-
"skills_used": ["using-aw-skills", "aw-plan", "incremental-implementation"],
|
|
352
|
-
"rules_applied": ["common-aw-routing", "universal", "security", "backend"],
|
|
353
|
-
"mcps_used": ["context7", "exa-search"],
|
|
354
|
-
"artifacts_written": [".aw_docs/features/<slug>/spec.md"]
|
|
355
|
-
}
|
|
356
|
-
\`\`\`
|
|
357
|
-
|
|
358
|
-
## Field semantics
|
|
359
|
-
|
|
360
|
-
| Field | Type | Required | Notes |
|
|
361
|
-
|---|---|---|---|
|
|
362
|
-
| \`route\` | string | yes | Selected AW route (e.g. \`/aw-plan\`, \`/aw-build\`) |
|
|
363
|
-
| \`skills_used\` | string[] | yes | Skill names actually Read during the response. \`[]\` if none |
|
|
364
|
-
| \`rules_applied\` | string[] | yes | Rule names from \`~/.cursor/rules/*.mdc\` or \`~/.aw_rules/\` that informed the response |
|
|
365
|
-
| \`mcps_used\` | string[] | yes | MCP server names invoked. \`[]\` if none |
|
|
366
|
-
| \`artifacts_written\` | string[] | optional | Files created or modified |
|
|
367
|
-
|
|
368
|
-
## Rules
|
|
369
|
-
|
|
370
|
-
- Always include the trace block — even for trivial responses (use \`[]\` for empty arrays).
|
|
371
|
-
- Skill and rule names must match the on-disk basenames (no \`.mdc\` / \`.md\` extension).
|
|
372
|
-
- The trace goes AFTER the substantive response, never before.
|
|
373
|
-
- Do not include skills/rules that were merely "available" — only those that actually influenced this response.
|
|
374
|
-
|
|
375
|
-
## Why
|
|
376
|
-
|
|
377
|
-
This makes routing observable. Tools, audits, and compliance checks can parse the trace JSON to verify that the right AW routing happened (route selected → stage skill loaded → relevant rules applied) without scraping prose.
|
|
378
|
-
`;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
328
|
function generateCursorAwRoutingRule() {
|
|
382
329
|
// Frontmatter MUST be at byte 0 for Cursor's alwaysApply/globs detection.
|
|
383
330
|
return `---
|
|
@@ -536,21 +483,120 @@ function renderClaudeRules(cwd, rulesDir, options = {}) {
|
|
|
536
483
|
/**
|
|
537
484
|
* Generate a rules section for CLAUDE.md from runtime AW rules.
|
|
538
485
|
*/
|
|
539
|
-
|
|
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 = {}) {
|
|
540
575
|
const manifest = readManifest(rulesDir);
|
|
541
576
|
if (!manifest) return '';
|
|
542
577
|
|
|
543
578
|
const mustRules = manifest.rules.filter(r => r.severity === 'MUST');
|
|
544
|
-
|
|
579
|
+
const applicableScopes = options.applicableScopes;
|
|
580
|
+
const filteredRules = applicableScopes
|
|
581
|
+
? filterRulesByScopes(mustRules, applicableScopes)
|
|
582
|
+
: mustRules;
|
|
583
|
+
if (filteredRules.length === 0) return '';
|
|
545
584
|
|
|
546
585
|
const lines = [
|
|
547
586
|
'## Platform Rules (MUST)',
|
|
548
587
|
'',
|
|
549
588
|
'> Rendered from platform `.aw/.aw_rules/`. Full details in reference files.',
|
|
550
|
-
'',
|
|
551
589
|
];
|
|
552
590
|
|
|
553
|
-
|
|
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) {
|
|
554
600
|
lines.push(`- [ ] **${rule.id}** — ${rule.description}`);
|
|
555
601
|
}
|
|
556
602
|
|
|
@@ -581,14 +627,28 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
581
627
|
// Reference table — tells all IDEs (especially Codex) where to read domain rules.
|
|
582
628
|
// Codex can't auto-trigger by glob, but it CAN read these files when working in
|
|
583
629
|
// the matching area. Keep AGENTS.md lean; full content stays in the source files.
|
|
584
|
-
const
|
|
630
|
+
const allScopes = listRuleScopes(rulesDir, {
|
|
585
631
|
includeNestedScopes: stackOverlaysEnabled(options),
|
|
586
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));
|
|
587
640
|
const domains = scopes.filter(({ scope }) => !scope.includes('/'));
|
|
588
641
|
const overlays = scopes.filter(({ scope }) => scope.includes('/'));
|
|
589
642
|
if (domains.length > 0) {
|
|
590
643
|
lines.push('### Domain Rules');
|
|
591
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
|
+
}
|
|
592
652
|
lines.push('When working in a specific domain, read the matching rules file:');
|
|
593
653
|
lines.push('');
|
|
594
654
|
lines.push('| Domain | Read when editing | Rules file |');
|
|
@@ -631,10 +691,14 @@ export function renderRules(cwd, options = {}) {
|
|
|
631
691
|
const rulesDir = resolveRulesSourceDir(cwd, options);
|
|
632
692
|
if (!rulesDir) return { cursorCount: 0, claudeSection: '', agentsSection: '' };
|
|
633
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
|
+
|
|
634
698
|
const cursorCount = renderCursorRules(cwd, rulesDir, options);
|
|
635
699
|
const claudeCount = renderClaudeRules(cwd, rulesDir, options);
|
|
636
|
-
const claudeSection = generateClaudeMdRulesSection(rulesDir);
|
|
637
|
-
const agentsSection = generateAgentsMdRulesSection(rulesDir, { ...options, outputDir: cwd });
|
|
700
|
+
const claudeSection = generateClaudeMdRulesSection(rulesDir, { applicableScopes });
|
|
701
|
+
const agentsSection = generateAgentsMdRulesSection(rulesDir, { ...options, outputDir: cwd, applicableScopes });
|
|
638
702
|
|
|
639
703
|
// Also render to global dirs so rules apply everywhere
|
|
640
704
|
const HOME = options.homeDir || homedir();
|