@ghl-ai/aw 0.1.39-beta.8 → 0.1.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.mjs +8 -1
- package/codex.mjs +839 -0
- package/commands/doctor.mjs +1086 -0
- package/commands/init.mjs +71 -81
- package/commands/link-project.mjs +12 -1
- package/commands/nuke.mjs +14 -4
- package/commands/pull.mjs +111 -11
- package/commands/push-rules.mjs +4 -15
- package/commands/push.mjs +4 -4
- package/commands/search.mjs +1 -1
- package/commands/startup.mjs +87 -0
- package/constants.mjs +3 -1
- package/ecc.mjs +130 -42
- package/git.mjs +4 -23
- package/hook-manifest.mjs +195 -0
- package/hooks/codex-home.mjs +184 -0
- package/hooks/shared-phase-scripts.mjs +69 -0
- package/integrate.mjs +219 -47
- package/link.mjs +36 -1
- package/mcp.mjs +2 -10
- package/package.json +8 -2
- package/paths.mjs +1 -1
- package/registry.mjs +1 -1
- package/render-rules.mjs +267 -27
- package/startup.mjs +562 -0
|
@@ -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 {
|
|
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
|
-
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
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
|
|
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.
|
|
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
|
-
|
|
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), \`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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({
|