@ghl-ai/aw 0.1.39-beta.15 → 0.1.39-beta.17
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 +22 -30
- 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,49 +195,34 @@ function findFiles(dir, typeName) {
|
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
/**
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
198
|
+
* Refresh rules sections in any existing AGENTS.md/CLAUDE.md at the repo
|
|
199
|
+
* root.
|
|
200
|
+
*
|
|
201
|
+
* Repo instruction files are user-owned. aw init no longer creates or updates
|
|
202
|
+
* managed sections in repo-local AGENTS.md / CLAUDE.md.
|
|
203
|
+
*
|
|
204
|
+
* The only repo-file behavior left is cleanup: if a repo still contains old
|
|
205
|
+
* aw-managed sections from prior versions, strip those sections while leaving
|
|
206
|
+
* the user's own content intact.
|
|
194
207
|
*/
|
|
195
208
|
export function copyInstructions(cwd, tempDir, namespace) {
|
|
196
209
|
const rulesSections = renderRules(cwd);
|
|
197
210
|
const createdFiles = [];
|
|
211
|
+
|
|
198
212
|
for (const file of ['AGENTS.md', 'CLAUDE.md']) {
|
|
199
213
|
const dest = join(cwd, file);
|
|
200
214
|
if (existsSync(dest)) {
|
|
201
215
|
const existing = readFileSync(dest, 'utf8');
|
|
202
|
-
const updated = applyManagedInstructionSections(existing, file,
|
|
216
|
+
const updated = applyManagedInstructionSections(existing, file, {}, { includeBridge: false });
|
|
203
217
|
|
|
204
218
|
if (updated !== existing) {
|
|
205
219
|
writeFileSync(dest, updated);
|
|
206
|
-
fmt.
|
|
220
|
+
fmt.logStep(`Stripped aw-managed sections from ${file} (now in ~/.claude/CLAUDE.md / ~/.codex/AGENTS.md)`);
|
|
207
221
|
}
|
|
208
222
|
continue;
|
|
209
223
|
}
|
|
210
224
|
|
|
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
|
-
}
|
|
225
|
+
// Never create repo instruction files anymore.
|
|
234
226
|
}
|
|
235
227
|
return createdFiles;
|
|
236
228
|
}
|
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();
|