@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.
@@ -0,0 +1,184 @@
1
+ import { join } from 'node:path';
2
+
3
+ import { getSupportedHarnessPhaseEntries } from '../hook-manifest.mjs';
4
+ import {
5
+ buildDelegatingPhaseScript,
6
+ buildRegistryDelegatingPhaseScript,
7
+ buildReservedPhaseScript,
8
+ } from './shared-phase-scripts.mjs';
9
+
10
+ export const CODEX_HOME_HOOK_MATCHER = 'startup|resume';
11
+ export const CODEX_HOME_HOOK_STATUS = 'Loading AW router';
12
+
13
+ function buildCodexCommand(scriptName) {
14
+ return `bash "$HOME/.codex/hooks/${scriptName}"`;
15
+ }
16
+
17
+ const CODEX_HOME_PHASE_BLUEPRINTS = {
18
+ SessionStart: {
19
+ scriptName: 'aw-session-start.sh',
20
+ scriptMarker: '# aw-managed: codex-global-session-start',
21
+ buildScriptContent() {
22
+ return buildRegistryDelegatingPhaseScript({
23
+ marker: this.scriptMarker,
24
+ phase: 'SessionStart',
25
+ targetCandidates: [
26
+ '$HOME/.aw_registry/platform/core/skills/using-aw-skills/hooks/session-start.sh',
27
+ '$HOME/.aw/.aw_registry/platform/core/skills/using-aw-skills/hooks/session-start.sh',
28
+ ],
29
+ warningMessage: 'WARNING: AW using-aw-skills hook not found in ~/.aw_registry. Run aw init or aw pull platform.',
30
+ });
31
+ },
32
+ buildEntry(command) {
33
+ return {
34
+ matcher: CODEX_HOME_HOOK_MATCHER,
35
+ hooks: [
36
+ {
37
+ type: 'command',
38
+ command,
39
+ statusMessage: CODEX_HOME_HOOK_STATUS,
40
+ },
41
+ ],
42
+ };
43
+ },
44
+ },
45
+ UserPromptSubmit: {
46
+ scriptName: 'aw-user-prompt-submit.sh',
47
+ scriptMarker: '# aw-managed: codex-global-user-prompt-submit',
48
+ buildScriptContent() {
49
+ return buildDelegatingPhaseScript({
50
+ marker: this.scriptMarker,
51
+ targetPath: '$HOME/.aw-ecc/scripts/hooks/session-start-rules-context.sh',
52
+ });
53
+ },
54
+ buildEntry(command) {
55
+ return {
56
+ hooks: [
57
+ {
58
+ type: 'command',
59
+ command,
60
+ },
61
+ ],
62
+ };
63
+ },
64
+ },
65
+ PreToolUse: {
66
+ scriptName: 'aw-pre-tool-use.sh',
67
+ scriptMarker: '# aw-managed: codex-global-pre-tool-use',
68
+ buildScriptContent() {
69
+ return buildReservedPhaseScript({
70
+ marker: this.scriptMarker,
71
+ phase: 'PreToolUse',
72
+ harnessLabel: 'Codex home routing',
73
+ });
74
+ },
75
+ buildEntry(command) {
76
+ return {
77
+ matcher: '*',
78
+ hooks: [
79
+ {
80
+ type: 'command',
81
+ command,
82
+ },
83
+ ],
84
+ };
85
+ },
86
+ },
87
+ PostToolUse: {
88
+ scriptName: 'aw-post-tool-use.sh',
89
+ scriptMarker: '# aw-managed: codex-global-post-tool-use',
90
+ buildScriptContent() {
91
+ return buildReservedPhaseScript({
92
+ marker: this.scriptMarker,
93
+ phase: 'PostToolUse',
94
+ harnessLabel: 'Codex home routing',
95
+ });
96
+ },
97
+ buildEntry(command) {
98
+ return {
99
+ matcher: '*',
100
+ hooks: [
101
+ {
102
+ type: 'command',
103
+ command,
104
+ },
105
+ ],
106
+ };
107
+ },
108
+ },
109
+ Stop: {
110
+ scriptName: 'aw-stop.sh',
111
+ scriptMarker: '# aw-managed: codex-global-stop',
112
+ buildScriptContent() {
113
+ return buildReservedPhaseScript({
114
+ marker: this.scriptMarker,
115
+ phase: 'Stop',
116
+ harnessLabel: 'Codex home routing',
117
+ });
118
+ },
119
+ buildEntry(command) {
120
+ return {
121
+ hooks: [
122
+ {
123
+ type: 'command',
124
+ command,
125
+ },
126
+ ],
127
+ };
128
+ },
129
+ },
130
+ };
131
+
132
+ export function getCodexHomePhaseDefinitions() {
133
+ return getSupportedHarnessPhaseEntries('codex', 'home').map(entry => {
134
+ const blueprint = CODEX_HOME_PHASE_BLUEPRINTS[entry.phase];
135
+ if (!blueprint) {
136
+ throw new Error(`Missing Codex home hook blueprint for phase: ${entry.phase}`);
137
+ }
138
+
139
+ const command = buildCodexCommand(blueprint.scriptName);
140
+
141
+ return {
142
+ ...entry,
143
+ hookConfigKey: entry.phase,
144
+ scriptName: blueprint.scriptName,
145
+ scriptMarker: blueprint.scriptMarker,
146
+ command,
147
+ entry: blueprint.buildEntry(command),
148
+ scriptContent: blueprint.buildScriptContent(),
149
+ };
150
+ });
151
+ }
152
+
153
+ export function getCodexHomePhaseDefinition(phase) {
154
+ const definition = getCodexHomePhaseDefinitions().find(entry => entry.phase === phase);
155
+ if (!definition) {
156
+ throw new Error(`Unsupported Codex home AW phase: ${phase}`);
157
+ }
158
+ return definition;
159
+ }
160
+
161
+ export function codexHomeHookScriptPath(homeDir, definitionOrPhase) {
162
+ const definition = typeof definitionOrPhase === 'string'
163
+ ? getCodexHomePhaseDefinition(definitionOrPhase)
164
+ : definitionOrPhase;
165
+
166
+ return join(homeDir, '.codex', 'hooks', definition.scriptName);
167
+ }
168
+
169
+ export function isManagedCodexHomeEntry(entry, definitionOrPhase) {
170
+ const definition = typeof definitionOrPhase === 'string'
171
+ ? getCodexHomePhaseDefinition(definitionOrPhase)
172
+ : definitionOrPhase;
173
+
174
+ if ('matcher' in definition.entry) {
175
+ if (entry?.matcher !== definition.entry.matcher) {
176
+ return false;
177
+ }
178
+ } else if (entry?.matcher !== undefined) {
179
+ return false;
180
+ }
181
+
182
+ return Array.isArray(entry?.hooks)
183
+ && entry.hooks.some(hook => hook?.type === 'command' && hook?.command === definition.command);
184
+ }
@@ -0,0 +1,69 @@
1
+ function escapeDoubleQuotes(value) {
2
+ return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
3
+ }
4
+
5
+ export function buildRegistryDelegatingPhaseScript({
6
+ marker,
7
+ phase,
8
+ targetCandidates,
9
+ warningMessage,
10
+ }) {
11
+ const targetsBlock = targetCandidates
12
+ .map(target => ` "${target}"`)
13
+ .join('\n');
14
+ const escapedWarning = escapeDoubleQuotes(warningMessage);
15
+
16
+ return `#!/usr/bin/env bash
17
+ ${marker}
18
+ set -euo pipefail
19
+
20
+ TARGETS=(
21
+ ${targetsBlock}
22
+ )
23
+
24
+ for target in "\${TARGETS[@]}"; do
25
+ if [[ -f "\$target" ]]; then
26
+ exec bash "\$target"
27
+ fi
28
+ done
29
+
30
+ CONTEXT="# AW Session Context
31
+
32
+ ${escapedWarning}"
33
+
34
+ JSON_CONTEXT=$(printf '%s' "\$CONTEXT" | python3 -c 'import json, sys; print(json.dumps(sys.stdin.read()))')
35
+
36
+ echo "{\\"hookSpecificOutput\\":{\\"hookEventName\\":\\"${phase}\\",\\"additionalContext\\":\${JSON_CONTEXT}}}"
37
+ `;
38
+ }
39
+
40
+ export function buildDelegatingPhaseScript({
41
+ marker,
42
+ targetPath,
43
+ }) {
44
+ return `#!/usr/bin/env bash
45
+ ${marker}
46
+ set -euo pipefail
47
+
48
+ TARGET="${targetPath}"
49
+ if [[ -f "$TARGET" ]]; then
50
+ exec bash "$TARGET"
51
+ fi
52
+
53
+ exit 0
54
+ `;
55
+ }
56
+
57
+ export function buildReservedPhaseScript({
58
+ marker,
59
+ phase,
60
+ harnessLabel,
61
+ }) {
62
+ return `#!/usr/bin/env bash
63
+ ${marker}
64
+ set -euo pipefail
65
+
66
+ # Reserved AW ${phase} phase for ${harnessLabel}.
67
+ exit 0
68
+ `;
69
+ }
package/integrate.mjs CHANGED
@@ -1,23 +1,172 @@
1
1
  // integrate.mjs — Generate commands for all IDEs, instructions (CLAUDE.md, AGENTS.md)
2
2
 
3
3
  import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rmSync } from 'node:fs';
4
- import { join } from 'node:path';
4
+ import { dirname, join } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  import * as fmt from './fmt.mjs';
7
7
  import * as config from './config.mjs';
8
8
  import { getLocalRegistryDir } from './git.mjs';
9
- import { renderRules } from './render-rules.mjs';
9
+ import {
10
+ generateAgentsMdRulesSection,
11
+ generateClaudeMdRulesSection,
12
+ renderRules,
13
+ resolveRulesSourceDir,
14
+ } from './render-rules.mjs';
15
+
16
+ const AW_ROUTER_BRIDGE_HEADER = 'AW Router Bridge';
17
+ const AW_ROUTER_BRIDGE_START_MARKER = '<!-- aw-managed:start router-bridge -->';
18
+ const AW_ROUTER_BRIDGE_END_MARKER = '<!-- aw-managed:end router-bridge -->';
19
+ const AGENTS_RULES_HEADER = 'Platform Rules — Non-Negotiables';
20
+ const CLAUDE_RULES_HEADER = 'Platform Rules (MUST)';
21
+
22
+ function generateAwRouterBridgeSection() {
23
+ return `${AW_ROUTER_BRIDGE_START_MARKER}
24
+ ## ${AW_ROUTER_BRIDGE_HEADER}
25
+
26
+ ### 1. Route First (MANDATORY)
27
+ - Before any substantive response, resolve and apply \`using-aw-skills\`
28
+ - Select the smallest correct AW route and stage skill for this task
29
+ - Do not assume the previous skill stack is still active
30
+
31
+ ### 2. Read Rules Before Acting (MANDATORY)
32
+ - Read applicable AW rules from \`~/.aw/.aw_rules/platform/<domain>/AGENTS.md\`
33
+ - Also read \`universal\` and \`security\` rules whenever they apply
34
+ - If repo-local instructions conflict with org-level sources of truth, follow org-level sources
35
+
36
+ ### 3. Explore Before Building
37
+ - Read existing code, adjacent patterns, configs, generated artifacts, and tests before writing anything
38
+ - For non-trivial work, inspect platform docs, runtime behavior, app surfaces, and integration boundaries first
39
+ - Ask one short clarifying question only when ambiguity remains after exploration
40
+
41
+ ### 4. Build in Small Steps
42
+ - For multi-step work, resolve and apply \`incremental-implementation\`
43
+ - Break work into thin, reversible slices with validation at each step
44
+ - Prefer extending existing patterns over inventing new ones
45
+ - Never batch unrelated changes in one pass
46
+
47
+ ### 5. Verify With Proof
48
+ - For bug fixes and behavior changes, use Red -> Green -> Refactor
49
+ - For config, docs, scaffolding, and integration wiring, use focused verification appropriate to the changed surface
50
+ - Use \`aw-debug\`, \`aw-build\`, \`aw-test\`, and \`aw-review\` as needed
51
+ - Never mark work complete without focused proof
52
+
53
+ ### 6. Core Principles
54
+ - Correctness over cleverness
55
+ - Clarity over complexity
56
+ - Small diffs over broad rewrites
57
+ - Evidence over assumption
58
+ - When corrected, update the working pattern and do not repeat the same mistake
59
+ ${AW_ROUTER_BRIDGE_END_MARKER}`;
60
+ }
61
+
62
+ function stripManagedBlock(content, startMarker, endMarker) {
63
+ const startIdx = content.indexOf(startMarker);
64
+ if (startIdx === -1) {
65
+ return content;
66
+ }
67
+
68
+ const endIdx = content.indexOf(endMarker, startIdx + startMarker.length);
69
+ if (endIdx === -1) {
70
+ return content;
71
+ }
10
72
 
11
- function upsertRulesSection(content, header, section) {
12
- if (!section) return content;
73
+ return `${content.slice(0, startIdx).trimEnd()}${content.slice(endIdx + endMarker.length)}`;
74
+ }
13
75
 
76
+ function stripManagedSection(content, header, stopHeaders = []) {
14
77
  const marker = `## ${header}`;
15
78
  const idx = content.indexOf(marker);
16
79
  if (idx === -1) {
17
- return `${content.trimEnd()}\n\n${section}\n`;
80
+ return content;
81
+ }
82
+
83
+ let end = content.length;
84
+ for (const stopHeader of stopHeaders) {
85
+ const stopIdx = content.indexOf(`## ${stopHeader}`, idx + marker.length);
86
+ if (stopIdx !== -1 && stopIdx < end) {
87
+ end = stopIdx;
88
+ }
89
+ }
90
+
91
+ return `${content.slice(0, idx).trimEnd()}${content.slice(end)}`;
92
+ }
93
+
94
+ function defaultInstructionPreamble(file) {
95
+ return file === 'CLAUDE.md' ? '# CLAUDE.md\n' : '# AGENTS.md\n';
96
+ }
97
+
98
+ function shouldResetHomeInstructionFile(content, file) {
99
+ const legacyMarkers = file === 'CLAUDE.md'
100
+ ? [
101
+ '# CLAUDE.md — ',
102
+ '## Routing Rule (ABSOLUTE)',
103
+ 'This supplements the root `AGENTS.md` with Codex-specific guidance.',
104
+ '<!-- BEGIN ECC -->',
105
+ ]
106
+ : [
107
+ '# AGENTS.md — ',
108
+ '# ECC for Codex CLI',
109
+ '# AW SDLC Repo Instructions',
110
+ 'Use the repo-local AW SDLC files as the source of truth for routing and stage behavior.',
111
+ '<!-- BEGIN ECC -->',
112
+ ];
113
+
114
+ return legacyMarkers.some(marker => content.includes(marker));
115
+ }
116
+
117
+ function stripLegacyRepoInstructionContent(content, file) {
118
+ const legacyMarkers = file === 'CLAUDE.md'
119
+ ? [
120
+ '# CLAUDE.md — ',
121
+ '## Routing Rule (ABSOLUTE)',
122
+ 'This supplements the root `AGENTS.md` with Codex-specific guidance.',
123
+ '<!-- BEGIN ECC -->',
124
+ ]
125
+ : [
126
+ '# AGENTS.md — ',
127
+ '# ECC for Codex CLI',
128
+ '# AW SDLC Repo Instructions',
129
+ 'Use the repo-local AW SDLC files as the source of truth for routing and stage behavior.',
130
+ '## Agent System',
131
+ '<!-- BEGIN ECC -->',
132
+ ];
133
+
134
+ const startIndexes = legacyMarkers
135
+ .map(marker => content.indexOf(marker))
136
+ .filter(idx => idx !== -1);
137
+
138
+ if (startIndexes.length === 0) return content;
139
+
140
+ const startIdx = Math.min(...startIndexes);
141
+ const preserved = content.slice(0, startIdx).trimEnd();
142
+ return preserved ? `${preserved}\n` : '';
143
+ }
144
+
145
+ function applyManagedInstructionSections(content, file, rulesSections = {}, options = {}) {
146
+ const rulesHeader = file === 'CLAUDE.md' ? CLAUDE_RULES_HEADER : AGENTS_RULES_HEADER;
147
+ const rulesSection = file === 'CLAUDE.md' ? rulesSections.claudeSection : rulesSections.agentsSection;
148
+ const includeBridge = options.includeBridge !== false;
149
+
150
+ let next = stripLegacyRepoInstructionContent(content, file);
151
+ next = stripManagedBlock(next, AW_ROUTER_BRIDGE_START_MARKER, AW_ROUTER_BRIDGE_END_MARKER);
152
+ next = stripManagedSection(next, AW_ROUTER_BRIDGE_HEADER, [rulesHeader]);
153
+ next = stripManagedSection(next, rulesHeader);
154
+ next = next.trimEnd();
155
+
156
+ const sections = [includeBridge ? generateAwRouterBridgeSection() : null, rulesSection].filter(Boolean);
157
+ if (sections.length === 0) {
158
+ return next ? `${next}\n` : '';
18
159
  }
19
160
 
20
- return `${content.slice(0, idx).trimEnd()}\n\n${section}\n`;
161
+ // Marker tells users (and aw init) where the managed section starts.
162
+ // Everything BEFORE this marker is repo-owned and never touched by aw.
163
+ // Everything AFTER is managed by aw — re-rendered on every aw init.
164
+ const managedBoundary = '<!-- aw-managed: content below is regenerated by `aw init` — put your own content above this line -->';
165
+ const appended = [managedBoundary, ...sections].join('\n\n').trim();
166
+ // Strip any prior managedBoundary line from `next` so we don't accumulate them
167
+ // when re-running aw init.
168
+ const cleaned = next.split('\n').filter(line => line.trim() !== managedBoundary).join('\n').trimEnd();
169
+ return cleaned ? `${cleaned}\n\n${appended}\n` : `${appended}\n`;
21
170
  }
22
171
 
23
172
  /**
@@ -75,59 +224,90 @@ function findFiles(dir, typeName) {
75
224
  }
76
225
 
77
226
  /**
78
- * Copy AGENTS.md to project root and refresh rules sections in any existing
79
- * CLAUDE.md. New CLAUDE.md files are intentionally not generated because their
80
- * routing rules can hijack plugin command dispatch in some workspaces.
227
+ * Refresh rules sections in any existing AGENTS.md/CLAUDE.md at the repo
228
+ * root.
229
+ *
230
+ * Repo instruction files are user-owned. aw init no longer creates or updates
231
+ * managed sections in repo-local AGENTS.md / CLAUDE.md.
232
+ *
233
+ * The only repo-file behavior left is cleanup: if a repo still contains old
234
+ * aw-managed sections from prior versions, strip those sections while leaving
235
+ * the user's own content intact.
81
236
  */
82
237
  export function copyInstructions(cwd, tempDir, namespace) {
83
238
  const rulesSections = renderRules(cwd);
84
239
  const createdFiles = [];
240
+
85
241
  for (const file of ['AGENTS.md', 'CLAUDE.md']) {
86
242
  const dest = join(cwd, file);
87
243
  if (existsSync(dest)) {
88
244
  const existing = readFileSync(dest, 'utf8');
89
- const updated = file === 'CLAUDE.md'
90
- ? upsertRulesSection(existing, 'Platform Rules (MUST)', rulesSections.claudeSection)
91
- : upsertRulesSection(existing, 'Platform Rules — Non-Negotiables', rulesSections.agentsSection);
245
+ const updated = applyManagedInstructionSections(existing, file, {}, { includeBridge: false });
92
246
 
93
247
  if (updated !== existing) {
94
248
  writeFileSync(dest, updated);
95
- fmt.logSuccess(`Updated ${file}`);
249
+ fmt.logStep(`Stripped aw-managed sections from ${file} (now in ~/.claude/CLAUDE.md / ~/.codex/AGENTS.md)`);
96
250
  }
97
251
  continue;
98
252
  }
99
253
 
100
- if (file === 'CLAUDE.md') continue;
101
-
102
- if (tempDir) {
103
- const src = join(tempDir, '.aw_registry', file);
104
- if (existsSync(src)) {
105
- let content = readFileSync(src, 'utf8');
106
- if (namespace) {
107
- content = content.replace(/\{\{TEAM\}\}/g, namespace);
108
- }
109
- writeFileSync(dest, content);
110
- fmt.logSuccess(`Created ${file}`);
111
- createdFiles.push(dest);
112
- continue;
113
- }
114
- }
115
-
116
- const content = generateAgentsMd(cwd, namespace, rulesSections);
117
- if (content) {
118
- writeFileSync(dest, content);
119
- fmt.logSuccess(`Created ${file}`);
120
- createdFiles.push(dest);
121
- }
254
+ // Never create repo instruction files anymore.
122
255
  }
123
256
  return createdFiles;
124
257
  }
125
258
 
259
+ function syncHomeInstructionFile(destPath, file, rulesSection) {
260
+ const existing = existsSync(destPath)
261
+ ? readFileSync(destPath, 'utf8')
262
+ : defaultInstructionPreamble(file);
263
+ const base = shouldResetHomeInstructionFile(existing, file)
264
+ ? defaultInstructionPreamble(file)
265
+ : existing;
266
+ const updated = applyManagedInstructionSections(
267
+ base,
268
+ file,
269
+ file === 'CLAUDE.md' ? { claudeSection: rulesSection } : { agentsSection: rulesSection },
270
+ );
271
+
272
+ if (updated === existing) return null;
273
+
274
+ mkdirSync(dirname(destPath), { recursive: true });
275
+ writeFileSync(destPath, updated);
276
+ return destPath;
277
+ }
278
+
279
+ export function syncHomeHarnessInstructions(homeDir = homedir()) {
280
+ const rulesDir = resolveRulesSourceDir(homeDir, { homeDir });
281
+ const codexRulesSection = rulesDir
282
+ ? generateAgentsMdRulesSection(rulesDir, {
283
+ outputDir: join(homeDir, '.codex'),
284
+ })
285
+ : '';
286
+ const cursorRulesSection = rulesDir
287
+ ? generateAgentsMdRulesSection(rulesDir, {
288
+ outputDir: join(homeDir, '.cursor'),
289
+ })
290
+ : '';
291
+ const claudeRulesSection = rulesDir ? generateClaudeMdRulesSection(rulesDir) : '';
292
+
293
+ return [
294
+ syncHomeInstructionFile(join(homeDir, '.codex', 'AGENTS.md'), 'AGENTS.md', codexRulesSection),
295
+ syncHomeInstructionFile(join(homeDir, '.claude', 'CLAUDE.md'), 'CLAUDE.md', claudeRulesSection),
296
+ syncHomeInstructionFile(join(homeDir, '.cursor', 'AGENTS.md'), 'AGENTS.md', cursorRulesSection),
297
+ ].filter(Boolean);
298
+ }
299
+
300
+ export function syncCodexHomeAgents(homeDir = homedir()) {
301
+ const codexAgentsPath = join(homeDir, '.codex', 'AGENTS.md');
302
+ syncHomeHarnessInstructions(homeDir);
303
+ return existsSync(codexAgentsPath) ? codexAgentsPath : null;
304
+ }
305
+
126
306
  function generateClaudeMd(cwd, namespace, rulesSections = {}) {
127
307
  const team = namespace || 'my-team';
128
308
  let base = `# CLAUDE.md — ${team}
129
309
 
130
- Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\` (shared knowledge), \`git-jenkins\` (CI/CD), \`grafana\` (observability)
310
+ Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\` (shared knowledge), \`jenkins_*\` (CI/CD via ghl-ai MCP), \`grafana\` (observability)
131
311
 
132
312
  ## Routing Rule (ABSOLUTE)
133
313
 
@@ -198,7 +378,7 @@ memory/search → Search shared team knowledge base
198
378
  memory/store → Push learnings to shared knowledge (eager sync after runs)
199
379
  memory/get → Fetch specific memory by ID
200
380
  grafana/* → External observability
201
- git-jenkins/* External CI/CD pipelines
381
+ jenkins_* → CI/CD pipelines (provided by ghl-ai MCP)
202
382
  stitch/* → External design generation
203
383
  \`\`\`
204
384
 
@@ -259,11 +439,7 @@ Gates are shell scripts in \`scripts/gates/\` — CANNOT be bypassed by LLM judg
259
439
  - **Local-first**: No MCP calls for orchestration — file reads are faster and always available
260
440
  `;
261
441
 
262
- if (rulesSections.claudeSection) {
263
- base += '\n' + rulesSections.claudeSection;
264
- }
265
-
266
- return base;
442
+ return applyManagedInstructionSections(base, 'CLAUDE.md', rulesSections);
267
443
  }
268
444
 
269
445
  function generateAgentsMd(cwd, namespace, rulesSections = {}) {
@@ -401,11 +577,7 @@ Each agent has quality test cases in \`.claude/evals/\`:
401
577
  Run with: \`/platform:eval agent:<slug>\` or \`/platform:eval skill:<slug>\`
402
578
  `;
403
579
 
404
- if (rulesSections.agentsSection) {
405
- base += '\n' + rulesSections.agentsSection;
406
- }
407
-
408
- return base;
580
+ return applyManagedInstructionSections(base, 'AGENTS.md', rulesSections);
409
581
  }
410
582
 
411
583
  /**
package/link.mjs CHANGED
@@ -14,7 +14,7 @@ function forceSymlink(target, linkPath) {
14
14
  const IDE_DIRS = ['.claude', '.cursor', '.codex'];
15
15
  // Per-file symlink types
16
16
  const FILE_TYPES = ['agents'];
17
- const ALL_KNOWN_TYPES = new Set([...FILE_TYPES, 'skills', 'commands', 'evals', 'docs']);
17
+ const ALL_KNOWN_TYPES = new Set([...FILE_TYPES, 'skills', 'commands', 'evals', 'references', 'docs']);
18
18
 
19
19
  /**
20
20
  * List namespace directories inside .aw_registry/ (skip dotfiles).
@@ -77,6 +77,8 @@ function cleanIdeSymlinks(cwd) {
77
77
  if (cwd === HOME) {
78
78
  const agentsSkillsDir = join(cwd, '.agents', 'skills');
79
79
  if (existsSync(agentsSkillsDir)) cleanSymlinksRecursive(agentsSkillsDir);
80
+ const agentsReferencesDir = join(cwd, '.agents', 'references');
81
+ if (existsSync(agentsReferencesDir)) cleanSymlinksRecursive(agentsReferencesDir);
80
82
  }
81
83
  }
82
84
 
@@ -203,6 +205,25 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
203
205
  }
204
206
  }
205
207
 
208
+ // Shared references: flatten namespace references into each IDE's references/
209
+ // so links like ../../references/foo.md continue to work from flattened skill dirs.
210
+ for (const ns of namespaces) {
211
+ for (const { typeDirPath: referencesDir } of findNestedTypeDirs(join(awDir, ns), 'references')) {
212
+ for (const file of readdirSync(referencesDir).filter(f => !f.startsWith('.'))) {
213
+ const targetPath = join(referencesDir, file);
214
+ if (lstatSync(targetPath).isDirectory()) continue;
215
+
216
+ for (const ide of IDE_DIRS) {
217
+ const linkDir = join(cwd, ide, 'references');
218
+ mkdirSync(linkDir, { recursive: true });
219
+ const linkPath = join(linkDir, file);
220
+ const relTarget = relative(linkDir, targetPath);
221
+ try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
222
+ }
223
+ }
224
+ }
225
+ }
226
+
206
227
  // Codex per-skill symlinks: ~/.agents/skills/<name> (global only)
207
228
  if (cwd === homedir()) {
208
229
  const agentsSkillsDir = join(cwd, '.agents/skills');
@@ -218,6 +239,20 @@ export function linkWorkspace(cwd, awDirOverride = null, { silent = false } = {}
218
239
  }
219
240
  }
220
241
  }
242
+
243
+ const agentsReferencesDir = join(cwd, '.agents', 'references');
244
+ for (const ns of namespaces) {
245
+ for (const { typeDirPath: referencesDir } of findNestedTypeDirs(join(awDir, ns), 'references')) {
246
+ mkdirSync(agentsReferencesDir, { recursive: true });
247
+ for (const file of readdirSync(referencesDir).filter(f => !f.startsWith('.'))) {
248
+ const targetPath = join(referencesDir, file);
249
+ if (lstatSync(targetPath).isDirectory()) continue;
250
+ const linkPath = join(agentsReferencesDir, file);
251
+ const relTarget = relative(agentsReferencesDir, targetPath);
252
+ try { forceSymlink(relTarget, linkPath); created++; } catch { /* best effort */ }
253
+ }
254
+ }
255
+ }
221
256
  }
222
257
 
223
258
  // Commands: per-file symlinks (recursive for nested domain dirs)
package/mcp.mjs CHANGED
@@ -168,16 +168,8 @@ async function resolveClickUpToken(silent = false, cwd = process.cwd()) {
168
168
  return existing.token;
169
169
  }
170
170
 
171
- // In silent mode (git hooks, auto-pull) there is no terminal — skip prompt.
172
- // Also skip in any non-TTY context so automation and CI runs never crash
173
- // while trying to render interactive prompts for an optional integration.
174
- const hasInteractiveTty = Boolean(process.stdin.isTTY && process.stdout.isTTY);
175
- if (silent || !hasInteractiveTty) {
176
- if (!silent && !hasInteractiveTty) {
177
- fmt.logInfo('Skipping ClickUp setup — no interactive terminal detected');
178
- }
179
- return null;
180
- }
171
+ // In silent mode (git hooks, auto-pull) there is no terminal — skip prompt
172
+ if (silent) return null;
181
173
 
182
174
  // 3. Ask first — don't assume the user wants ClickUp
183
175
  const wantsClickUp = await p.select({