@ghl-ai/aw 0.1.38-beta.8 → 0.1.38
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 +1 -1
- package/commands/doctor.mjs +25 -24
- package/commands/init.mjs +25 -5
- package/commands/pull.mjs +28 -7
- package/commands/push-rules.mjs +4 -15
- package/commands/telemetry.mjs +3 -59
- package/constants.mjs +3 -1
- package/ecc.mjs +40 -4
- package/hooks/codex-home.mjs +3 -3
- package/hooks/shared-phase-scripts.mjs +0 -17
- package/integrate.mjs +3 -3
- package/package.json +1 -1
- package/render-rules.mjs +100 -17
package/cli.mjs
CHANGED
|
@@ -134,7 +134,7 @@ function printHelp() {
|
|
|
134
134
|
cmd('aw push .aw_registry/agents/<name>.md', 'Push a single agent'),
|
|
135
135
|
cmd('aw push .aw_registry/skills/<name>/', 'Push a single skill folder'),
|
|
136
136
|
cmd('aw push .aw_rules', 'Auto-redirects to aw push-rules'),
|
|
137
|
-
cmd('aw push-rules', 'Pushes .aw_rules
|
|
137
|
+
cmd('aw push-rules', 'Pushes .aw_rules'),
|
|
138
138
|
'',
|
|
139
139
|
` ${chalk.dim('# Remove content from workspace')}`,
|
|
140
140
|
cmd('aw drop <team>', 'Stop syncing a namespace (removes all files)'),
|
package/commands/doctor.mjs
CHANGED
|
@@ -162,10 +162,11 @@ function resolveAwRegistryDir(homeDir) {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
function resolveRulesDir(homeDir) {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
const candidates = [
|
|
166
|
+
join(homeDir, '.aw', '.aw_rules'),
|
|
167
|
+
join(homeDir, '.aw', '.aw_registry', '.aw_rules'),
|
|
168
|
+
];
|
|
169
|
+
return candidates.find(existsSync) || null;
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
function findClaudePlugin(homeDir) {
|
|
@@ -471,7 +472,7 @@ function getProjectClaudeSessionStartStatus(cwd) {
|
|
|
471
472
|
}
|
|
472
473
|
|
|
473
474
|
function textHasRulesReference(text) {
|
|
474
|
-
return text.includes('Platform Rules') && text.includes('.
|
|
475
|
+
return text.includes('Platform Rules') && text.includes('.aw/.aw_rules/platform/');
|
|
475
476
|
}
|
|
476
477
|
|
|
477
478
|
function textHasManagedRouterBridge(text) {
|
|
@@ -484,9 +485,9 @@ function textHasManagedRouterBridge(text) {
|
|
|
484
485
|
&& text.includes('incremental-implementation');
|
|
485
486
|
}
|
|
486
487
|
|
|
487
|
-
function
|
|
488
|
+
function extractRuleReferencePaths(text) {
|
|
488
489
|
const matches = [];
|
|
489
|
-
const pattern = /(?:\]\(|`)([^)\n`]
|
|
490
|
+
const pattern = /(?:\]\(|`)([^)\n`]*(?:\.aw_rules|\.aw_registry\/\.aw_rules)\/[^)\n`]+\.md)(?:\)|`)/g;
|
|
490
491
|
for (const match of text.matchAll(pattern)) {
|
|
491
492
|
if (match[1]) matches.push(match[1]);
|
|
492
493
|
}
|
|
@@ -505,11 +506,11 @@ function listGeneratedRuleFiles(dirPath, extension) {
|
|
|
505
506
|
}
|
|
506
507
|
}
|
|
507
508
|
|
|
508
|
-
function
|
|
509
|
+
function findBrokenRuleReferences(filePaths) {
|
|
509
510
|
const broken = [];
|
|
510
511
|
for (const filePath of filePaths) {
|
|
511
512
|
const content = readText(filePath);
|
|
512
|
-
const refs =
|
|
513
|
+
const refs = extractRuleReferencePaths(content);
|
|
513
514
|
for (const ref of refs) {
|
|
514
515
|
if (ref.includes('<domain>')) continue;
|
|
515
516
|
const resolvedPath = join(dirname(filePath), ref);
|
|
@@ -565,8 +566,8 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
565
566
|
'aw-rules-source',
|
|
566
567
|
'AW rules source',
|
|
567
568
|
'fail',
|
|
568
|
-
'No synced .aw_rules tree found under ~/.
|
|
569
|
-
'Run `aw init` or `aw pull platform` to sync the AW rules source into ~/.
|
|
569
|
+
'No synced .aw/.aw_rules tree found under ~/.aw/.aw_rules',
|
|
570
|
+
'Run `aw init` or `aw pull platform` to sync the AW rules source into ~/.aw/.aw_rules.',
|
|
570
571
|
));
|
|
571
572
|
} else {
|
|
572
573
|
const topLevelRulesAgents = join(rulesDir, 'AGENTS.md');
|
|
@@ -578,8 +579,8 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
578
579
|
'aw-rules-source',
|
|
579
580
|
'AW rules source',
|
|
580
581
|
'fail',
|
|
581
|
-
'Rules source is incomplete under ~/.
|
|
582
|
-
'Run `aw pull platform` to refresh .aw_rules, then rerun `aw doctor`.',
|
|
582
|
+
'Rules source is incomplete under ~/.aw/.aw_rules',
|
|
583
|
+
'Run `aw pull platform` to refresh .aw/.aw_rules, then rerun `aw doctor`.',
|
|
583
584
|
),
|
|
584
585
|
);
|
|
585
586
|
}
|
|
@@ -778,30 +779,30 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
778
779
|
'claude-rules',
|
|
779
780
|
'Claude AW rules',
|
|
780
781
|
rulesDir ? 'fail' : 'warn',
|
|
781
|
-
rulesDir ? 'Claude platform rules are missing or not generated by AW' : 'Claude platform rules were not checked because .aw_rules is not synced',
|
|
782
|
+
rulesDir ? 'Claude platform rules are missing or not generated by AW' : 'Claude platform rules were not checked because .aw/.aw_rules is not synced',
|
|
782
783
|
'Run `aw pull platform` or `aw init` to render Claude platform rules.',
|
|
783
784
|
),
|
|
784
785
|
);
|
|
785
786
|
|
|
786
787
|
const claudeRuleFiles = listGeneratedRuleFiles(claudeRulesDir, '.md');
|
|
787
|
-
const brokenClaudeRuleRefs =
|
|
788
|
+
const brokenClaudeRuleRefs = findBrokenRuleReferences(claudeRuleFiles);
|
|
788
789
|
checks.push(
|
|
789
790
|
claudeRuleFiles.length === 0
|
|
790
791
|
? makeCheck(
|
|
791
792
|
'claude-rule-references',
|
|
792
793
|
'Claude rule references',
|
|
793
794
|
rulesDir ? 'fail' : 'warn',
|
|
794
|
-
rulesDir ? 'Claude generated rule files are missing, so reference links could not be validated' : 'Claude rule references were not checked because .aw_rules is not synced',
|
|
795
|
+
rulesDir ? 'Claude generated rule files are missing, so reference links could not be validated' : 'Claude rule references were not checked because .aw/.aw_rules is not synced',
|
|
795
796
|
'Run `aw pull platform` or `aw init` to render Claude rules, then rerun `aw doctor`.',
|
|
796
797
|
)
|
|
797
798
|
: brokenClaudeRuleRefs.length === 0
|
|
798
|
-
? makeCheck('claude-rule-references', 'Claude rule references', 'pass', 'Claude generated rule references resolve to real files in ~/.
|
|
799
|
+
? makeCheck('claude-rule-references', 'Claude rule references', 'pass', 'Claude generated rule references resolve to real files in ~/.aw/.aw_rules')
|
|
799
800
|
: makeCheck(
|
|
800
801
|
'claude-rule-references',
|
|
801
802
|
'Claude rule references',
|
|
802
803
|
'fail',
|
|
803
804
|
`Claude rule reference is broken in ${brokenClaudeRuleRefs[0].filePath.replace(`${homeDir}/`, '~/')}: ${brokenClaudeRuleRefs[0].ref}`,
|
|
804
|
-
'Refresh the rendered Claude rules so embedded reference links point at real files under ~/.
|
|
805
|
+
'Refresh the rendered Claude rules so embedded reference links point at real files under ~/.aw/.aw_rules.',
|
|
805
806
|
),
|
|
806
807
|
);
|
|
807
808
|
|
|
@@ -894,7 +895,7 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
894
895
|
);
|
|
895
896
|
|
|
896
897
|
const codexAgentsText = readText(codexAgentsPath);
|
|
897
|
-
const codexBrokenRuleRefs = existsSync(codexAgentsPath) ?
|
|
898
|
+
const codexBrokenRuleRefs = existsSync(codexAgentsPath) ? findBrokenRuleReferences([codexAgentsPath]) : [];
|
|
898
899
|
checks.push(
|
|
899
900
|
existsSync(codexAgentsPath) && textHasRulesReference(codexAgentsText)
|
|
900
901
|
? codexBrokenRuleRefs.length === 0
|
|
@@ -997,31 +998,31 @@ function buildDoctorChecks(homeDir, cwd) {
|
|
|
997
998
|
'cursor-rules',
|
|
998
999
|
'Cursor AW rules',
|
|
999
1000
|
rulesDir ? 'fail' : 'warn',
|
|
1000
|
-
rulesDir ? 'Cursor rules are missing or not generated by AW' : 'Cursor rules were not checked because .aw_rules is not synced',
|
|
1001
|
+
rulesDir ? 'Cursor rules are missing or not generated by AW' : 'Cursor rules were not checked because .aw/.aw_rules is not synced',
|
|
1001
1002
|
'Run `aw pull platform` or `aw init` to render Cursor rule files.',
|
|
1002
1003
|
),
|
|
1003
1004
|
);
|
|
1004
1005
|
|
|
1005
1006
|
const cursorRulesDir = join(homeDir, '.cursor', 'rules');
|
|
1006
1007
|
const cursorRuleFiles = listGeneratedRuleFiles(cursorRulesDir, '.mdc');
|
|
1007
|
-
const brokenCursorRuleRefs =
|
|
1008
|
+
const brokenCursorRuleRefs = findBrokenRuleReferences(cursorRuleFiles);
|
|
1008
1009
|
checks.push(
|
|
1009
1010
|
cursorRuleFiles.length === 0
|
|
1010
1011
|
? makeCheck(
|
|
1011
1012
|
'cursor-rule-references',
|
|
1012
1013
|
'Cursor rule references',
|
|
1013
1014
|
rulesDir ? 'fail' : 'warn',
|
|
1014
|
-
rulesDir ? 'Cursor generated rule files are missing, so reference links could not be validated' : 'Cursor rule references were not checked because .aw_rules is not synced',
|
|
1015
|
+
rulesDir ? 'Cursor generated rule files are missing, so reference links could not be validated' : 'Cursor rule references were not checked because .aw/.aw_rules is not synced',
|
|
1015
1016
|
'Run `aw pull platform` or `aw init` to render Cursor rules, then rerun `aw doctor`.',
|
|
1016
1017
|
)
|
|
1017
1018
|
: brokenCursorRuleRefs.length === 0
|
|
1018
|
-
? makeCheck('cursor-rule-references', 'Cursor rule references', 'pass', 'Cursor generated rule references resolve to real files in ~/.
|
|
1019
|
+
? makeCheck('cursor-rule-references', 'Cursor rule references', 'pass', 'Cursor generated rule references resolve to real files in ~/.aw/.aw_rules')
|
|
1019
1020
|
: makeCheck(
|
|
1020
1021
|
'cursor-rule-references',
|
|
1021
1022
|
'Cursor rule references',
|
|
1022
1023
|
'fail',
|
|
1023
1024
|
`Cursor rule reference is broken in ${brokenCursorRuleRefs[0].filePath.replace(`${homeDir}/`, '~/')}: ${brokenCursorRuleRefs[0].ref}`,
|
|
1024
|
-
'Refresh the rendered Cursor rules so embedded reference links point at real files under ~/.
|
|
1025
|
+
'Refresh the rendered Cursor rules so embedded reference links point at real files under ~/.aw/.aw_rules.',
|
|
1025
1026
|
),
|
|
1026
1027
|
);
|
|
1027
1028
|
|
package/commands/init.mjs
CHANGED
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
syncWorktreeSparseCheckout,
|
|
45
45
|
findNearestWorktree,
|
|
46
46
|
} from '../git.mjs';
|
|
47
|
-
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, RULES_SOURCE_DIR } from '../constants.mjs';
|
|
47
|
+
import { REGISTRY_DIR, REGISTRY_REPO, REGISTRY_URL, RULES_SOURCE_DIR, RULES_RUNTIME_DIR } from '../constants.mjs';
|
|
48
48
|
import { syncFileTree } from '../file-tree.mjs';
|
|
49
49
|
|
|
50
50
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -59,6 +59,22 @@ const HOME = (() => { try { return realpathSync(_rawHome); } catch { return _raw
|
|
|
59
59
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
60
60
|
const AW_HOME = join(HOME, '.aw');
|
|
61
61
|
|
|
62
|
+
function syncRulesTargets(targetDir) {
|
|
63
|
+
const rulesSrc = join(AW_HOME, RULES_SOURCE_DIR);
|
|
64
|
+
if (!existsSync(rulesSrc)) return false;
|
|
65
|
+
syncFileTree(rulesSrc, join(targetDir, RULES_RUNTIME_DIR));
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function removeLegacyRegistryRules() {
|
|
70
|
+
try {
|
|
71
|
+
rmSync(join(GLOBAL_AW_DIR, RULES_SOURCE_DIR), { recursive: true, force: true });
|
|
72
|
+
rmSync(join(HOME, RULES_SOURCE_DIR), { recursive: true, force: true });
|
|
73
|
+
} catch {
|
|
74
|
+
// best effort cleanup
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
62
78
|
function syncInstructionsAndAwDocs(targetDir, namespace) {
|
|
63
79
|
copyInstructions(targetDir, null, namespace);
|
|
64
80
|
initAwDocs(targetDir);
|
|
@@ -262,9 +278,11 @@ export async function initCommand(args) {
|
|
|
262
278
|
|
|
263
279
|
ensureAwGitignore(AW_HOME);
|
|
264
280
|
const freshCfg = config.load(GLOBAL_AW_DIR);
|
|
265
|
-
|
|
266
|
-
|
|
281
|
+
syncRulesTargets(HOME);
|
|
282
|
+
if (cwd !== HOME) {
|
|
283
|
+
syncRulesTargets(cwd);
|
|
267
284
|
}
|
|
285
|
+
removeLegacyRegistryRules();
|
|
268
286
|
|
|
269
287
|
// Ensure project worktree sparse checkout matches the global clone.
|
|
270
288
|
// Covers the case where a namespace was added from HOME (or another project)
|
|
@@ -404,9 +422,11 @@ export async function initCommand(args) {
|
|
|
404
422
|
if (folderName) {
|
|
405
423
|
config.addPattern(GLOBAL_AW_DIR, folderName);
|
|
406
424
|
}
|
|
407
|
-
|
|
408
|
-
|
|
425
|
+
syncRulesTargets(HOME);
|
|
426
|
+
if (cwd !== HOME) {
|
|
427
|
+
syncRulesTargets(cwd);
|
|
409
428
|
}
|
|
429
|
+
removeLegacyRegistryRules();
|
|
410
430
|
|
|
411
431
|
// Step 3: Setup tasks, MCP, hooks
|
|
412
432
|
await installAwEcc(cwd, { silent });
|
package/commands/pull.mjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {
|
|
4
4
|
existsSync,
|
|
5
5
|
lstatSync,
|
|
6
|
+
rmSync,
|
|
6
7
|
} from 'node:fs';
|
|
7
8
|
import { dirname, join, extname } from 'node:path';
|
|
8
9
|
import { homedir } from 'node:os';
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
REGISTRY_URL,
|
|
28
29
|
DOCS_SOURCE_DIR,
|
|
29
30
|
RULES_SOURCE_DIR,
|
|
31
|
+
RULES_RUNTIME_DIR,
|
|
30
32
|
} from '../constants.mjs';
|
|
31
33
|
import { collectAllPaths, syncFileTree } from '../file-tree.mjs';
|
|
32
34
|
import { linkWorkspace } from '../link.mjs';
|
|
@@ -38,6 +40,22 @@ const HOME = homedir();
|
|
|
38
40
|
const AW_HOME = join(HOME, '.aw');
|
|
39
41
|
const GLOBAL_AW_DIR = join(HOME, '.aw_registry');
|
|
40
42
|
|
|
43
|
+
function syncRulesTargets(targetDir) {
|
|
44
|
+
const rulesSrc = join(AW_HOME, RULES_SOURCE_DIR);
|
|
45
|
+
if (!existsSync(rulesSrc)) return false;
|
|
46
|
+
syncFileTree(rulesSrc, join(targetDir, RULES_RUNTIME_DIR));
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function removeLegacyRegistryRules() {
|
|
51
|
+
try {
|
|
52
|
+
rmSync(join(GLOBAL_AW_DIR, RULES_SOURCE_DIR), { recursive: true, force: true });
|
|
53
|
+
rmSync(join(HOME, RULES_SOURCE_DIR), { recursive: true, force: true });
|
|
54
|
+
} catch {
|
|
55
|
+
// best effort cleanup
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
41
59
|
export async function pullCommand(args) {
|
|
42
60
|
const input = args._positional?.[0] || '';
|
|
43
61
|
const cwd = process.cwd();
|
|
@@ -179,13 +197,6 @@ export async function pullCommand(args) {
|
|
|
179
197
|
log.logWarn(`Conflicts in: ${fetchResult.conflicts.join(', ')}`);
|
|
180
198
|
}
|
|
181
199
|
|
|
182
|
-
const rulesSrc = join(AW_HOME, RULES_SOURCE_DIR);
|
|
183
|
-
if (existsSync(rulesSrc)) {
|
|
184
|
-
const rulesDest = join(GLOBAL_AW_DIR, RULES_SOURCE_DIR);
|
|
185
|
-
syncFileTree(rulesSrc, rulesDest);
|
|
186
|
-
if (!silent) log.logSuccess('Synced .aw_rules');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
200
|
// Rebase project worktree branch onto origin/main — only for legacy git worktrees.
|
|
190
201
|
// In the symlink model, <project>/.aw IS ~/.aw (same repo), so fetchAndMerge already
|
|
191
202
|
// brought it up to date. Nothing to rebase.
|
|
@@ -244,6 +255,16 @@ export async function pullCommand(args) {
|
|
|
244
255
|
}
|
|
245
256
|
}
|
|
246
257
|
|
|
258
|
+
let rulesSynced = syncRulesTargets(HOME);
|
|
259
|
+
const workspaceRoot = localAw ? dirname(localAw) : (cwd !== HOME ? cwd : null);
|
|
260
|
+
if (workspaceRoot && workspaceRoot !== HOME) {
|
|
261
|
+
rulesSynced = syncRulesTargets(workspaceRoot) || rulesSynced;
|
|
262
|
+
}
|
|
263
|
+
removeLegacyRegistryRules();
|
|
264
|
+
if (rulesSynced && !silent) {
|
|
265
|
+
log.logSuccess('Synced .aw_rules');
|
|
266
|
+
}
|
|
267
|
+
|
|
247
268
|
if (!args._skipIntegrate) {
|
|
248
269
|
const projectRegistryDir = cwd !== HOME ? join(cwd, '.aw', REGISTRY_DIR) : null;
|
|
249
270
|
const awDirForLinks = (projectRegistryDir && existsSync(projectRegistryDir)) ? projectRegistryDir : null;
|
package/commands/push-rules.mjs
CHANGED
|
@@ -23,11 +23,9 @@ export function isRulesPushInput(input) {
|
|
|
23
23
|
|
|
24
24
|
export function resolveRulesPushSource(input, cwd = process.cwd()) {
|
|
25
25
|
const localRulesRoot = join(cwd, RULES_SOURCE_DIR);
|
|
26
|
-
const syncedRulesRoot = join(cwd, '.aw_registry', RULES_SOURCE_DIR);
|
|
27
26
|
|
|
28
27
|
if (!input) {
|
|
29
28
|
if (existsSync(localRulesRoot)) return { sourceRoot: localRulesRoot, sourceType: 'local' };
|
|
30
|
-
if (existsSync(syncedRulesRoot)) return { sourceRoot: syncedRulesRoot, sourceType: 'synced' };
|
|
31
29
|
return null;
|
|
32
30
|
}
|
|
33
31
|
|
|
@@ -39,22 +37,19 @@ export function resolveRulesPushSource(input, cwd = process.cwd()) {
|
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
if (normalizedInput === `.aw_registry/${RULES_SOURCE_DIR}` || normalizedInput.startsWith(`.aw_registry/${RULES_SOURCE_DIR}/`)) {
|
|
42
|
-
if (!existsSync(syncedRulesRoot)) return null;
|
|
43
|
-
|
|
44
40
|
const relativeRulesPath = normalizedInput.slice(`.aw_registry/${RULES_SOURCE_DIR}`.length).replace(/^\/+/, '');
|
|
45
41
|
const localOverridePath = relativeRulesPath ? join(localRulesRoot, relativeRulesPath) : localRulesRoot;
|
|
46
42
|
if (existsSync(localOverridePath)) {
|
|
47
43
|
return { sourceRoot: localRulesRoot, sourceType: 'local' };
|
|
48
44
|
}
|
|
49
|
-
|
|
50
|
-
return { sourceRoot: syncedRulesRoot, sourceType: 'synced' };
|
|
45
|
+
return null;
|
|
51
46
|
}
|
|
52
47
|
|
|
53
48
|
return null;
|
|
54
49
|
}
|
|
55
50
|
|
|
56
51
|
export function hasRulesChanges(cwd = process.cwd()) {
|
|
57
|
-
const candidateDirs = [RULES_SOURCE_DIR
|
|
52
|
+
const candidateDirs = [RULES_SOURCE_DIR]
|
|
58
53
|
.filter(rel => existsSync(join(cwd, rel)));
|
|
59
54
|
|
|
60
55
|
if (candidateDirs.length === 0) return false;
|
|
@@ -141,9 +136,7 @@ function pushRulesTree(sourceRoot, { repo, dryRun, cwd }) {
|
|
|
141
136
|
fmt.cancel('Nothing to push — remote rules already match local content.');
|
|
142
137
|
}
|
|
143
138
|
|
|
144
|
-
const sourceType =
|
|
145
|
-
? 'synced'
|
|
146
|
-
: 'local';
|
|
139
|
+
const sourceType = 'local';
|
|
147
140
|
const prTitle = buildRulesPrTitle(sourceRoot, cwd);
|
|
148
141
|
const prBody = buildRulesPrBody(sourceRoot, sourceType, cwd);
|
|
149
142
|
|
|
@@ -191,16 +184,12 @@ export function pushRulesCommand(args) {
|
|
|
191
184
|
fmt.cancel([
|
|
192
185
|
'Could not find a rules source to push.',
|
|
193
186
|
'',
|
|
194
|
-
` Checked ${chalk.cyan('.aw_rules/')}
|
|
187
|
+
` Checked ${chalk.cyan('.aw_rules/')}.`,
|
|
195
188
|
'',
|
|
196
189
|
' Use `aw pull platform` first or create a local `.aw_rules/` authoring tree.',
|
|
197
190
|
].join('\n'));
|
|
198
191
|
}
|
|
199
192
|
|
|
200
|
-
if (resolved.sourceType === 'synced') {
|
|
201
|
-
fmt.logWarn('Pushing from synced `.aw_registry/.aw_rules/`. Local `.aw_rules/` is safer for authoring.');
|
|
202
|
-
}
|
|
203
|
-
|
|
204
193
|
pushRulesTree(resolved.sourceRoot, { repo, dryRun, cwd });
|
|
205
194
|
}
|
|
206
195
|
|
package/commands/telemetry.mjs
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
// commands/telemetry.mjs — `aw telemetry [enable|disable|status
|
|
1
|
+
// commands/telemetry.mjs — `aw telemetry [enable|disable|status]`
|
|
2
2
|
|
|
3
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { homedir } from 'node:os';
|
|
6
3
|
import { enableTelemetry, disableTelemetry, getStatus } from '../telemetry.mjs';
|
|
7
4
|
import * as fmt from '../fmt.mjs';
|
|
8
5
|
import { chalk } from '../fmt.mjs';
|
|
@@ -22,66 +19,13 @@ export async function telemetryCommand(args) {
|
|
|
22
19
|
return;
|
|
23
20
|
}
|
|
24
21
|
|
|
25
|
-
if (sub === 'flush-queue') {
|
|
26
|
-
await flushQueueCommand();
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
22
|
// status (default)
|
|
31
23
|
const status = getStatus();
|
|
32
24
|
fmt.intro('aw telemetry');
|
|
33
25
|
fmt.logStep(`Status: ${status.enabled ? chalk.green('enabled') : chalk.red('disabled')}`);
|
|
34
26
|
fmt.logStep(`Machine ID: ${chalk.dim(status.machine_id)}`);
|
|
35
27
|
fmt.logStep(`Config: ${chalk.dim(status.config_path)}`);
|
|
36
|
-
|
|
37
|
-
// Show queue depth if available
|
|
38
|
-
const queueFile = join(homedir(), '.aw', 'telemetry', 'queue.jsonl');
|
|
39
|
-
if (existsSync(queueFile)) {
|
|
40
|
-
try {
|
|
41
|
-
const lines = readFileSync(queueFile, 'utf8').trim().split('\n').filter(Boolean);
|
|
42
|
-
fmt.logStep(`Queue: ${chalk.yellow(lines.length)} pending prompt(s)`);
|
|
43
|
-
} catch { /* best effort */ }
|
|
44
|
-
}
|
|
45
|
-
|
|
46
28
|
fmt.logMessage('');
|
|
47
|
-
fmt.logMessage(` ${chalk.dim('aw telemetry disable')}
|
|
48
|
-
fmt.logMessage(` ${chalk.dim('aw telemetry enable')}
|
|
49
|
-
fmt.logMessage(` ${chalk.dim('aw telemetry flush-queue')} — manually flush pending queue`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function flushQueueCommand() {
|
|
53
|
-
const eccBase = join(homedir(), '.aw-ecc');
|
|
54
|
-
const libPath = join(eccBase, 'scripts', 'hooks', 'capabilities', 'telemetry', 'telemetry-lib.js');
|
|
55
|
-
|
|
56
|
-
if (!existsSync(libPath)) {
|
|
57
|
-
fmt.logWarn('Telemetry library not found. Run aw init first.');
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const {
|
|
62
|
-
readQueue,
|
|
63
|
-
flushQueueToApi,
|
|
64
|
-
getNamespace,
|
|
65
|
-
buildTelemetryHeaders,
|
|
66
|
-
} = await import(libPath);
|
|
67
|
-
|
|
68
|
-
const entries = readQueue();
|
|
69
|
-
if (entries.length === 0) {
|
|
70
|
-
fmt.logSuccess('Queue is empty — nothing to flush.');
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
fmt.logStep(`Flushing ${entries.length} pending prompt(s)...`);
|
|
75
|
-
|
|
76
|
-
const namespace = getNamespace();
|
|
77
|
-
const headers = buildTelemetryHeaders(namespace);
|
|
78
|
-
const result = await flushQueueToApi(headers);
|
|
79
|
-
|
|
80
|
-
if (result.flushed > 0) {
|
|
81
|
-
fmt.logSuccess(`Flushed ${result.flushed} prompt(s) to API.`);
|
|
82
|
-
} else if (result.failed) {
|
|
83
|
-
fmt.logWarn('Flush failed — entries remain in queue for retry.');
|
|
84
|
-
} else {
|
|
85
|
-
fmt.logSuccess('Nothing to flush.');
|
|
86
|
-
}
|
|
29
|
+
fmt.logMessage(` ${chalk.dim('aw telemetry disable')} — opt out of anonymous analytics`);
|
|
30
|
+
fmt.logMessage(` ${chalk.dim('aw telemetry enable')} — re-enable analytics`);
|
|
87
31
|
}
|
package/constants.mjs
CHANGED
|
@@ -25,8 +25,10 @@ export const DOCS_SOURCE_DIR = 'content';
|
|
|
25
25
|
/** Persistent git clone root — ~/.aw/ */
|
|
26
26
|
export const AW_HOME = join(homedir(), '.aw');
|
|
27
27
|
|
|
28
|
-
/** Directory in platform-docs repo containing platform rules (
|
|
28
|
+
/** Directory in platform-docs repo containing platform rules (synced into root-level .aw_rules/) */
|
|
29
29
|
export const RULES_SOURCE_DIR = '.aw_rules';
|
|
30
|
+
/** Runtime location exposed to harnesses and generated instructions */
|
|
31
|
+
export const RULES_RUNTIME_DIR = '.aw/.aw_rules';
|
|
30
32
|
/** Telemetry endpoint — override with AW_TELEMETRY_URL env var */
|
|
31
33
|
export const TELEMETRY_URL = process.env.AW_TELEMETRY_URL || 'https://services.leadconnectorhq.com/agentic-workspace/api/telemetry/events';
|
|
32
34
|
|
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.
|
|
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}`;
|
|
@@ -160,6 +160,32 @@ function cloneOrUpdate(tag, dest) {
|
|
|
160
160
|
}
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Transform canonical /aw: references to Cursor-compatible /aw- in installed
|
|
165
|
+
* skill and rule files. Cursor namespaces commands via directory structure
|
|
166
|
+
* (commands/aw/plan.md → /aw-plan) rather than colons.
|
|
167
|
+
*/
|
|
168
|
+
function transformCursorAwRefs(home) {
|
|
169
|
+
const dirs = [
|
|
170
|
+
join(home, ".cursor", "skills"),
|
|
171
|
+
join(home, ".cursor", "rules"),
|
|
172
|
+
];
|
|
173
|
+
for (const dir of dirs) {
|
|
174
|
+
if (!existsSync(dir)) continue;
|
|
175
|
+
for (const entry of readdirSync(dir, { withFileTypes: true, recursive: true })) {
|
|
176
|
+
if (!entry.isFile()) continue;
|
|
177
|
+
const ext = entry.name.split(".").pop();
|
|
178
|
+
if (!["md", "mdc"].includes(ext)) continue;
|
|
179
|
+
const filePath = join(entry.parentPath || entry.path, entry.name);
|
|
180
|
+
try {
|
|
181
|
+
const content = readFileSync(filePath, "utf8");
|
|
182
|
+
if (!content.includes("/aw:")) continue;
|
|
183
|
+
writeFileSync(filePath, content.replace(/\/aw:/g, "/aw-"));
|
|
184
|
+
} catch { /* best effort */ }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
163
189
|
function installClaudePlugin(repoDir) {
|
|
164
190
|
// Always remove first so we re-register from the correct repoDir.
|
|
165
191
|
// Falling back to `marketplace update` is unsafe — it updates from the
|
|
@@ -241,9 +267,6 @@ export async function installAwEcc(
|
|
|
241
267
|
try {
|
|
242
268
|
cloneOrUpdate(AW_ECC_TAG, repoDir);
|
|
243
269
|
|
|
244
|
-
// Ensure telemetry state directory exists (vendor-agnostic, shared across IDEs)
|
|
245
|
-
mkdirSync(join(home, ".aw", "telemetry"), { recursive: true });
|
|
246
|
-
|
|
247
270
|
// Claude Code: plugin install via marketplace CLI (proper agent dispatch)
|
|
248
271
|
if (targets.includes("claude")) {
|
|
249
272
|
try {
|
|
@@ -259,6 +282,15 @@ export async function installAwEcc(
|
|
|
259
282
|
run("npm install --no-audit --no-fund --ignore-scripts --loglevel=error", {
|
|
260
283
|
cwd: repoDir,
|
|
261
284
|
});
|
|
285
|
+
// generate-aw-hooks.js produces hooks.json and hook script sources that
|
|
286
|
+
// install-apply.js needs. npm install uses --ignore-scripts so the
|
|
287
|
+
// prepare script (which normally runs this) is skipped — call explicitly.
|
|
288
|
+
const generateHooksScript = join(repoDir, "scripts", "generate-aw-hooks.js");
|
|
289
|
+
if (existsSync(generateHooksScript)) {
|
|
290
|
+
try {
|
|
291
|
+
run(`node "${generateHooksScript}"`, { cwd: repoDir });
|
|
292
|
+
} catch { /* best effort — older engine versions may not have this script */ }
|
|
293
|
+
}
|
|
262
294
|
for (const target of fileCopyTargets) {
|
|
263
295
|
try {
|
|
264
296
|
const snapshot = snapshotProtectedConfigs(home, target);
|
|
@@ -279,6 +311,10 @@ export async function installAwEcc(
|
|
|
279
311
|
// so they're accessible as /aw:tdd, /aw:plan — same as Claude Code plugin.
|
|
280
312
|
if (target === "cursor") {
|
|
281
313
|
namespaceCursorCommands(runCwd);
|
|
314
|
+
// Cursor commands use hyphens (commands/aw/plan.md -> /aw-plan)
|
|
315
|
+
// but source skill/rule files use canonical colons (/aw:plan).
|
|
316
|
+
// Transform /aw: -> /aw- in installed cursor skill/rule files.
|
|
317
|
+
transformCursorAwRefs(home);
|
|
282
318
|
}
|
|
283
319
|
// Run sync script for codex: generates ~/.codex/prompts/*.md and
|
|
284
320
|
// merges AGENTS.md — Codex has no slash commands, so prompts are the
|
package/hooks/codex-home.mjs
CHANGED
|
@@ -3,7 +3,6 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { getSupportedHarnessPhaseEntries } from '../hook-manifest.mjs';
|
|
4
4
|
import {
|
|
5
5
|
buildDelegatingPhaseScript,
|
|
6
|
-
buildNodeSilentPhaseScript,
|
|
7
6
|
buildRegistryDelegatingPhaseScript,
|
|
8
7
|
buildReservedPhaseScript,
|
|
9
8
|
} from './shared-phase-scripts.mjs';
|
|
@@ -111,9 +110,10 @@ const CODEX_HOME_PHASE_BLUEPRINTS = {
|
|
|
111
110
|
scriptName: 'aw-stop.sh',
|
|
112
111
|
scriptMarker: '# aw-managed: codex-global-stop',
|
|
113
112
|
buildScriptContent() {
|
|
114
|
-
return
|
|
113
|
+
return buildReservedPhaseScript({
|
|
115
114
|
marker: this.scriptMarker,
|
|
116
|
-
|
|
115
|
+
phase: 'Stop',
|
|
116
|
+
harnessLabel: 'Codex home routing',
|
|
117
117
|
});
|
|
118
118
|
},
|
|
119
119
|
buildEntry(command) {
|
|
@@ -54,23 +54,6 @@ exit 0
|
|
|
54
54
|
`;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export function buildNodeSilentPhaseScript({
|
|
58
|
-
marker,
|
|
59
|
-
targetPath,
|
|
60
|
-
}) {
|
|
61
|
-
return `#!/usr/bin/env bash
|
|
62
|
-
${marker}
|
|
63
|
-
set -euo pipefail
|
|
64
|
-
|
|
65
|
-
TARGET="${targetPath}"
|
|
66
|
-
if [[ -f "$TARGET" ]]; then
|
|
67
|
-
node "$TARGET" > /dev/null
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
exit 0
|
|
71
|
-
`;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
57
|
export function buildReservedPhaseScript({
|
|
75
58
|
marker,
|
|
76
59
|
phase,
|
package/integrate.mjs
CHANGED
|
@@ -29,7 +29,7 @@ function generateAwRouterBridgeSection() {
|
|
|
29
29
|
- Do not assume the previous skill stack is still active
|
|
30
30
|
|
|
31
31
|
### 2. Read Rules Before Acting (MANDATORY)
|
|
32
|
-
- Read applicable AW rules from \`~/.aw/.
|
|
32
|
+
- Read applicable AW rules from \`~/.aw/.aw_rules/platform/<domain>/AGENTS.md\`
|
|
33
33
|
- Also read \`universal\` and \`security\` rules whenever they apply
|
|
34
34
|
- If repo-local instructions conflict with org-level sources of truth, follow org-level sources
|
|
35
35
|
|
|
@@ -260,12 +260,12 @@ export function syncHomeHarnessInstructions(homeDir = homedir()) {
|
|
|
260
260
|
const codexRulesSection = rulesDir
|
|
261
261
|
? generateAgentsMdRulesSection(rulesDir, {
|
|
262
262
|
outputDir: join(homeDir, '.codex'),
|
|
263
|
-
})
|
|
263
|
+
})
|
|
264
264
|
: '';
|
|
265
265
|
const cursorRulesSection = rulesDir
|
|
266
266
|
? generateAgentsMdRulesSection(rulesDir, {
|
|
267
267
|
outputDir: join(homeDir, '.cursor'),
|
|
268
|
-
})
|
|
268
|
+
})
|
|
269
269
|
: '';
|
|
270
270
|
const claudeRulesSection = rulesDir ? generateClaudeMdRulesSection(rulesDir) : '';
|
|
271
271
|
|
package/package.json
CHANGED
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
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 */
|
|
@@ -171,14 +176,12 @@ function relativeRuleLink(outputDir, targetPath) {
|
|
|
171
176
|
export function resolveRulesSourceDir(cwd, options = {}) {
|
|
172
177
|
const HOME = options.homeDir || homedir();
|
|
173
178
|
const candidates = [
|
|
174
|
-
join(cwd,
|
|
175
|
-
join(cwd, '.aw', '.aw_registry', RULES_SOURCE_DIR),
|
|
179
|
+
join(cwd, RULES_RUNTIME_DIR),
|
|
176
180
|
];
|
|
177
181
|
|
|
178
182
|
if (cwd !== HOME) {
|
|
179
183
|
candidates.push(
|
|
180
|
-
join(HOME,
|
|
181
|
-
join(HOME, '.aw', '.aw_registry', RULES_SOURCE_DIR),
|
|
184
|
+
join(HOME, RULES_RUNTIME_DIR),
|
|
182
185
|
);
|
|
183
186
|
}
|
|
184
187
|
|
|
@@ -198,7 +201,8 @@ function pruneStaleGeneratedRules(outputDir, expectedFilenames) {
|
|
|
198
201
|
|
|
199
202
|
const fullPath = join(outputDir, entry.name);
|
|
200
203
|
const content = readOrNull(fullPath);
|
|
201
|
-
|
|
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;
|
|
202
206
|
|
|
203
207
|
try {
|
|
204
208
|
unlinkSync(fullPath);
|
|
@@ -305,14 +309,92 @@ function renderCursorRules(cwd, rulesDir, options = {}) {
|
|
|
305
309
|
}
|
|
306
310
|
}
|
|
307
311
|
|
|
308
|
-
|
|
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;
|
|
309
315
|
writeFileSync(join(cursorRulesDir, `${scopeToFilename(scope)}.mdc`), content);
|
|
310
316
|
count++;
|
|
311
317
|
}
|
|
312
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
|
+
|
|
313
325
|
return count;
|
|
314
326
|
}
|
|
315
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
|
+
|
|
316
398
|
/** Rule scope → Claude Code paths: frontmatter (only supported field for .claude/rules/) */
|
|
317
399
|
const SCOPE_PATHS = {
|
|
318
400
|
'api-design': ['src/**/*.controller.ts', 'src/**/dto/**/*.ts', 'src/**/*.client.ts'],
|
|
@@ -389,7 +471,8 @@ function renderClaudeRules(cwd, rulesDir, options = {}) {
|
|
|
389
471
|
}
|
|
390
472
|
}
|
|
391
473
|
|
|
392
|
-
|
|
474
|
+
// Claude Code reads .md frontmatter similarly — keep marker after frontmatter.
|
|
475
|
+
const content = frontmatter + GENERATED_MARKER + '\n\n' + agentsMd.trim() + '\n' + refSection;
|
|
393
476
|
writeFileSync(join(claudeRulesDir, `${scopeToFilename(scope)}.md`), content);
|
|
394
477
|
count++;
|
|
395
478
|
}
|
|
@@ -398,7 +481,7 @@ function renderClaudeRules(cwd, rulesDir, options = {}) {
|
|
|
398
481
|
}
|
|
399
482
|
|
|
400
483
|
/**
|
|
401
|
-
* Generate a rules section for CLAUDE.md from .
|
|
484
|
+
* Generate a rules section for CLAUDE.md from runtime AW rules.
|
|
402
485
|
*/
|
|
403
486
|
export function generateClaudeMdRulesSection(rulesDir) {
|
|
404
487
|
const manifest = readManifest(rulesDir);
|
|
@@ -410,7 +493,7 @@ export function generateClaudeMdRulesSection(rulesDir) {
|
|
|
410
493
|
const lines = [
|
|
411
494
|
'## Platform Rules (MUST)',
|
|
412
495
|
'',
|
|
413
|
-
'>
|
|
496
|
+
'> Rendered from platform `.aw/.aw_rules/`. Full details in reference files.',
|
|
414
497
|
'',
|
|
415
498
|
];
|
|
416
499
|
|
|
@@ -419,14 +502,14 @@ export function generateClaudeMdRulesSection(rulesDir) {
|
|
|
419
502
|
}
|
|
420
503
|
|
|
421
504
|
lines.push('');
|
|
422
|
-
lines.push('See `.
|
|
505
|
+
lines.push('See `.aw/.aw_rules/rule-manifest.json` for all rules including SHOULD/MAY.');
|
|
423
506
|
lines.push('');
|
|
424
507
|
|
|
425
508
|
return lines.join('\n');
|
|
426
509
|
}
|
|
427
510
|
|
|
428
511
|
/**
|
|
429
|
-
* Generate a rules section for AGENTS.md from the top-level AGENTS.md in .
|
|
512
|
+
* Generate a rules section for AGENTS.md from the top-level AGENTS.md in runtime AW rules.
|
|
430
513
|
*/
|
|
431
514
|
export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
432
515
|
const topLevelAgents = readOrNull(join(rulesDir, 'AGENTS.md'));
|
|
@@ -436,7 +519,7 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
436
519
|
const lines = [
|
|
437
520
|
'## Platform Rules — Non-Negotiables',
|
|
438
521
|
'',
|
|
439
|
-
'>
|
|
522
|
+
'> Rendered from platform `.aw/.aw_rules/`.',
|
|
440
523
|
'',
|
|
441
524
|
topLevelAgents.trim(),
|
|
442
525
|
'',
|
|
@@ -487,7 +570,7 @@ export function generateAgentsMdRulesSection(rulesDir, options = {}) {
|
|
|
487
570
|
|
|
488
571
|
/**
|
|
489
572
|
* Main render function. Call after aw pull / aw sync.
|
|
490
|
-
* Reads .
|
|
573
|
+
* Reads runtime .aw/.aw_rules/ and renders:
|
|
491
574
|
* 1. .cursor/rules/<scope>.mdc — at cwd AND at $HOME (global)
|
|
492
575
|
* 2. Returns sections for CLAUDE.md and AGENTS.md injection
|
|
493
576
|
*/
|