@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
package/commands/push.mjs
CHANGED
|
@@ -29,7 +29,7 @@ import { hasRulesChanges, isRulesPushInput, pushRulesCommand } from './push-rule
|
|
|
29
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
30
30
|
const VERSION = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
31
31
|
|
|
32
|
-
const PUSHABLE_TYPES = ['agents', 'skills', 'commands', 'evals'];
|
|
32
|
+
const PUSHABLE_TYPES = ['agents', 'skills', 'commands', 'evals', 'references'];
|
|
33
33
|
|
|
34
34
|
// ── PR content generation ────────────────────────────────────────────
|
|
35
35
|
|
|
@@ -635,8 +635,8 @@ export async function pushCommand(args) {
|
|
|
635
635
|
const registryAbsPath = join(registrySubDir, relFromRegistry);
|
|
636
636
|
const allFiles = collectBatchFiles(registryAbsPath, registrySubDir);
|
|
637
637
|
if (allFiles.length === 0) {
|
|
638
|
-
|
|
639
|
-
|
|
638
|
+
// Folder/namespace input → batch push
|
|
639
|
+
fmt.cancel(`Nothing to push in ${chalk.cyan(input)} — no agents, skills, commands, evals, or references found.`);
|
|
640
640
|
}
|
|
641
641
|
// Only include files that actually have changes (new or modified)
|
|
642
642
|
const folderChanges = detectChanges(awHome, REGISTRY_DIR);
|
|
@@ -688,7 +688,7 @@ export async function pushCommand(args) {
|
|
|
688
688
|
fmt.cancel([
|
|
689
689
|
`Invalid push path: ${chalk.red(resolved.registryPath)}`,
|
|
690
690
|
'',
|
|
691
|
-
` Content must live under an ${chalk.bold('agents/')}, ${chalk.bold('skills/')}, ${chalk.bold('commands/')}, or ${chalk.bold('
|
|
691
|
+
` Content must live under an ${chalk.bold('agents/')}, ${chalk.bold('skills/')}, ${chalk.bold('commands/')}, ${chalk.bold('evals/')}, or ${chalk.bold('references/')} directory.`,
|
|
692
692
|
'',
|
|
693
693
|
` ${chalk.dim('Valid examples:')}`,
|
|
694
694
|
` aw push .aw_registry/commerce/quality/agents/unit-tester.md`,
|
package/commands/search.mjs
CHANGED
|
@@ -98,7 +98,7 @@ function searchLocal(workspaceDir, query) {
|
|
|
98
98
|
if (!nsEntry.isDirectory() || nsEntry.name.startsWith('.')) continue;
|
|
99
99
|
const ns = nsEntry.name;
|
|
100
100
|
|
|
101
|
-
for (const type of ['agents', 'skills', 'commands', 'evals']) {
|
|
101
|
+
for (const type of ['agents', 'skills', 'commands', 'evals', 'references']) {
|
|
102
102
|
const typeDir = join(workspaceDir, ns, type);
|
|
103
103
|
if (!existsSync(typeDir)) continue;
|
|
104
104
|
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import * as fmt from '../fmt.mjs';
|
|
5
|
+
import { chalk } from '../fmt.mjs';
|
|
6
|
+
import { removeWorkspaceHookDefaults } from '../codex.mjs';
|
|
7
|
+
import {
|
|
8
|
+
applyStoredStartupPreferences,
|
|
9
|
+
getStartupStatus,
|
|
10
|
+
hasLegacyRepoStartupDefaults,
|
|
11
|
+
saveStartupPreferences,
|
|
12
|
+
} from '../startup.mjs';
|
|
13
|
+
|
|
14
|
+
const HOME = homedir();
|
|
15
|
+
|
|
16
|
+
export function routingCommand(args) {
|
|
17
|
+
const action = String(args._positional?.[0] || 'status').toLowerCase();
|
|
18
|
+
|
|
19
|
+
if (!['status', 'enable', 'disable'].includes(action)) {
|
|
20
|
+
fmt.cancel(`Unknown routing action: ${action}. Use: aw routing status|enable|disable`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
fmt.intro(`aw routing ${action}`);
|
|
25
|
+
|
|
26
|
+
if (action === 'status') {
|
|
27
|
+
return renderStatus();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const mode = action === 'disable' ? 'disabled' : 'enabled';
|
|
31
|
+
const cwd = process.cwd();
|
|
32
|
+
const removedLegacyFiles = cwd !== HOME ? removeWorkspaceHookDefaults(cwd) : [];
|
|
33
|
+
|
|
34
|
+
saveStartupPreferences(mode, HOME);
|
|
35
|
+
const updatedFiles = applyStoredStartupPreferences(HOME);
|
|
36
|
+
const status = getStartupStatus(HOME);
|
|
37
|
+
|
|
38
|
+
fmt.outro([
|
|
39
|
+
`⟁ Session routing ${action === 'disable' ? 'disabled' : 'enabled'}`,
|
|
40
|
+
'',
|
|
41
|
+
` ${chalk.green('✓')} Preference saved: ${chalk.dim(status.preferencesPath.replace(`${HOME}/`, '~/'))}`,
|
|
42
|
+
` ${chalk.green('✓')} Claude session routing: ${status.claudeDisabled ? chalk.yellow('disabled') : chalk.green('enabled')}`,
|
|
43
|
+
` ${chalk.green('✓')} Codex session routing: ${status.codexHooksEnabled && status.codexSessionStartPresent ? chalk.green('enabled') : chalk.yellow('disabled')}`,
|
|
44
|
+
` ${chalk.green('✓')} Cursor session routing: ${status.cursorSessionStartPresent ? chalk.green('enabled') : chalk.yellow('disabled')}`,
|
|
45
|
+
removedLegacyFiles.length > 0
|
|
46
|
+
? ` ${chalk.green('✓')} Removed ${removedLegacyFiles.length} legacy repo routing file${removedLegacyFiles.length > 1 ? 's' : ''} from the current project`
|
|
47
|
+
: null,
|
|
48
|
+
updatedFiles.length === 0
|
|
49
|
+
? ` ${chalk.dim('Note:')} no global routing files needed changes`
|
|
50
|
+
: null,
|
|
51
|
+
].filter(Boolean).join('\n'));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderStatus() {
|
|
55
|
+
const status = getStartupStatus(HOME);
|
|
56
|
+
const codexStatus = status.codexHooksEnabled && status.codexSessionStartPresent
|
|
57
|
+
? 'enabled'
|
|
58
|
+
: status.codexSessionStartPresent && !status.codexHooksEnabled
|
|
59
|
+
? 'hook installed but codex_hooks is disabled'
|
|
60
|
+
: 'disabled';
|
|
61
|
+
const claudeStatus = status.claudeDisabled
|
|
62
|
+
? 'disabled via ~/.claude/settings.json override'
|
|
63
|
+
: status.claudeLegacySessionStartPresent
|
|
64
|
+
? 'enabled (legacy hook present; run aw routing enable to clean)'
|
|
65
|
+
: status.claudePluginEnabled && status.claudePluginInstalled
|
|
66
|
+
? 'enabled (plugin installed)'
|
|
67
|
+
: status.claudePluginEnabled && !status.claudePluginInstalled
|
|
68
|
+
? 'misconfigured (plugin enabled in settings but not installed)'
|
|
69
|
+
: 'disabled (plugin not installed)';
|
|
70
|
+
|
|
71
|
+
fmt.note([
|
|
72
|
+
`${chalk.dim('mode:')} ${status.mode}`,
|
|
73
|
+
`${chalk.dim('prefs:')} ${status.preferencesPath.replace(`${HOME}/`, '~/')}`,
|
|
74
|
+
`${chalk.dim('claude session routing:')} ${claudeStatus}`,
|
|
75
|
+
`${chalk.dim('codex session routing:')} ${codexStatus}`,
|
|
76
|
+
`${chalk.dim('codex runtime:')} ${status.codexSessionStartScriptInstalled ? 'installed' : 'not installed'}`,
|
|
77
|
+
`${chalk.dim('cursor session routing:')} ${status.cursorSessionStartPresent ? 'enabled' : 'disabled'}`,
|
|
78
|
+
`${chalk.dim('cursor runtime:')} ${status.cursorSessionStartScriptInstalled ? 'installed' : 'not installed'}`,
|
|
79
|
+
process.cwd() !== HOME
|
|
80
|
+
? `${chalk.dim('current repo legacy routing files:')} ${hasLegacyRepoStartupDefaults(process.cwd()) ? 'present' : 'not detected'}`
|
|
81
|
+
: null,
|
|
82
|
+
].join('\n'), 'Routing');
|
|
83
|
+
|
|
84
|
+
fmt.outro('⟁ Routing status complete');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const startupCommand = routingCommand;
|
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
|
@@ -6,21 +6,30 @@ import {
|
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
import * as fmt from "./fmt.mjs";
|
|
9
|
+
import { applyStoredStartupPreferences } from "./startup.mjs";
|
|
9
10
|
|
|
10
11
|
const AW_ECC_REPO_SSH = "git@github.com:shreyansh-ghl/aw-ecc.git";
|
|
11
12
|
const AW_ECC_REPO_HTTPS = "https://github.com/shreyansh-ghl/aw-ecc.git";
|
|
12
|
-
const AW_ECC_TAG = "v1.4.
|
|
13
|
+
export const AW_ECC_TAG = "v1.4.37";
|
|
13
14
|
|
|
14
15
|
const MARKETPLACE_NAME = "aw-marketplace";
|
|
15
16
|
const PLUGIN_KEY = `aw@${MARKETPLACE_NAME}`;
|
|
16
17
|
|
|
17
18
|
function eccDir() { return join(homedir(), ".aw-ecc"); }
|
|
18
19
|
|
|
19
|
-
// "claude"
|
|
20
|
-
// and
|
|
21
|
-
// marketplace
|
|
22
|
-
//
|
|
20
|
+
// "claude" uses a no-commands module set so hooks, rules, shared references,
|
|
21
|
+
// and install-state land in ~/.claude/ — while slash commands stay owned by
|
|
22
|
+
// the marketplace plugin under /aw:*.
|
|
23
|
+
// Using `--without baseline:commands` with broader profiles is unsafe because
|
|
24
|
+
// some optional modules depend on commands-core and cause install-apply to fail.
|
|
23
25
|
const FILE_COPY_TARGETS = ["claude", "cursor", "codex"];
|
|
26
|
+
const CLAUDE_FILE_COPY_MODULES = [
|
|
27
|
+
"rules-core",
|
|
28
|
+
"agents-core",
|
|
29
|
+
"hooks-runtime",
|
|
30
|
+
"platform-configs",
|
|
31
|
+
"workflow-quality",
|
|
32
|
+
];
|
|
24
33
|
|
|
25
34
|
const TARGET_STATE = {
|
|
26
35
|
claude: { state: ".claude/ecc/install-state.json" },
|
|
@@ -40,6 +49,20 @@ function run(cmd, opts = {}) {
|
|
|
40
49
|
return execSync(cmd, { stdio: "pipe", ...opts });
|
|
41
50
|
}
|
|
42
51
|
|
|
52
|
+
function fetchOverrideRef(dest, ref) {
|
|
53
|
+
run(`git -C ${dest} fetch --quiet --depth 1 origin ${ref}`);
|
|
54
|
+
run(`git -C ${dest} checkout --quiet FETCH_HEAD`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function cloneWithRef(url, ref, dest) {
|
|
58
|
+
try {
|
|
59
|
+
run(`git clone --quiet --depth 1 --branch ${ref} "${url}" "${dest}"`);
|
|
60
|
+
} catch {
|
|
61
|
+
run(`git clone --quiet --depth 1 "${url}" "${dest}"`);
|
|
62
|
+
fetchOverrideRef(dest, ref);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
43
66
|
function readIfExists(path) {
|
|
44
67
|
try { return existsSync(path) ? readFileSync(path, "utf8") : null; } catch { return null; }
|
|
45
68
|
}
|
|
@@ -96,20 +119,39 @@ function relProtectedPath(absPath, home) {
|
|
|
96
119
|
function cloneOrUpdate(tag, dest) {
|
|
97
120
|
// AW_ECC_CLONE_URL overrides the remote (used in tests to point at a local fake repo)
|
|
98
121
|
const overrideUrl = process.env.AW_ECC_CLONE_URL;
|
|
122
|
+
// AW_ECC_CLONE_REF lets aw init install ECC from a non-default branch or tag.
|
|
123
|
+
const overrideRef = process.env.AW_ECC_CLONE_REF?.trim();
|
|
99
124
|
|
|
100
125
|
if (existsSync(join(dest, ".git"))) {
|
|
101
126
|
try {
|
|
102
|
-
if (!overrideUrl) {
|
|
127
|
+
if (!overrideUrl && !overrideRef) {
|
|
103
128
|
run(`git -C ${dest} fetch --quiet --depth 1 origin tag ${tag}`);
|
|
104
129
|
run(`git -C ${dest} checkout --quiet ${tag}`);
|
|
130
|
+
} else {
|
|
131
|
+
if (overrideUrl) {
|
|
132
|
+
run(`git -C ${dest} remote set-url origin "${overrideUrl}"`);
|
|
133
|
+
}
|
|
134
|
+
fetchOverrideRef(dest, overrideRef || tag);
|
|
105
135
|
}
|
|
106
136
|
return;
|
|
107
137
|
} catch { /* fall through to fresh clone */ }
|
|
108
138
|
}
|
|
109
139
|
if (existsSync(dest)) rmSync(dest, { recursive: true, force: true });
|
|
110
140
|
if (overrideUrl) {
|
|
111
|
-
|
|
141
|
+
if (overrideRef) {
|
|
142
|
+
cloneWithRef(overrideUrl, overrideRef, dest);
|
|
143
|
+
} else {
|
|
144
|
+
run(`git clone --quiet --depth 1 "${overrideUrl}" "${dest}"`);
|
|
145
|
+
}
|
|
112
146
|
} else {
|
|
147
|
+
if (overrideRef) {
|
|
148
|
+
try {
|
|
149
|
+
cloneWithRef(AW_ECC_REPO_SSH, overrideRef, dest);
|
|
150
|
+
} catch {
|
|
151
|
+
cloneWithRef(AW_ECC_REPO_HTTPS, overrideRef, dest);
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
113
155
|
try {
|
|
114
156
|
run(`git clone --quiet --depth 1 --branch ${tag} ${AW_ECC_REPO_SSH} ${dest}`);
|
|
115
157
|
} catch {
|
|
@@ -118,6 +160,32 @@ function cloneOrUpdate(tag, dest) {
|
|
|
118
160
|
}
|
|
119
161
|
}
|
|
120
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
|
+
|
|
121
189
|
function installClaudePlugin(repoDir) {
|
|
122
190
|
// Always remove first so we re-register from the correct repoDir.
|
|
123
191
|
// Falling back to `marketplace update` is unsafe — it updates from the
|
|
@@ -174,7 +242,8 @@ function namespaceCursorCommands(home) {
|
|
|
174
242
|
|
|
175
243
|
/**
|
|
176
244
|
* Run scripts/sync-ecc-to-codex.sh from the cloned ecc repo.
|
|
177
|
-
* Generates ~/.codex/prompts
|
|
245
|
+
* Generates ~/.codex/prompts/*.md (Codex equivalent of slash commands),
|
|
246
|
+
* using aw-*.md for AW-namespaced commands and ecc-*.md for the rest,
|
|
178
247
|
* and merges ~/.codex/AGENTS.md. Best-effort — failure doesn't block init.
|
|
179
248
|
*/
|
|
180
249
|
function syncEccToCodex(repoDir) {
|
|
@@ -185,25 +254,6 @@ function syncEccToCodex(repoDir) {
|
|
|
185
254
|
} catch { /* best effort — codex sync failure is non-blocking */ }
|
|
186
255
|
}
|
|
187
256
|
|
|
188
|
-
/**
|
|
189
|
-
* Run ECC repair after install so managed home manifests self-heal when local
|
|
190
|
-
* hook/config files have drifted. This is especially important for Codex where
|
|
191
|
-
* hook registration and wrapper scripts can get out of sync with ~/.aw-ecc.
|
|
192
|
-
*/
|
|
193
|
-
function repairEccInstall(repoDir, targets) {
|
|
194
|
-
const repairScript = join(repoDir, "scripts", "repair.js");
|
|
195
|
-
if (!existsSync(repairScript) || !Array.isArray(targets) || targets.length === 0) return;
|
|
196
|
-
|
|
197
|
-
const targetArgs = targets
|
|
198
|
-
.filter(Boolean)
|
|
199
|
-
.map((target) => ` --target ${target}`)
|
|
200
|
-
.join("");
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
run(`node "${repairScript}"${targetArgs}`, { cwd: homedir() });
|
|
204
|
-
} catch { /* best effort — repair failure is non-blocking */ }
|
|
205
|
-
}
|
|
206
|
-
|
|
207
257
|
export async function installAwEcc(
|
|
208
258
|
cwd,
|
|
209
259
|
{ targets = ["cursor", "claude", "codex"], silent = false } = {},
|
|
@@ -232,25 +282,41 @@ export async function installAwEcc(
|
|
|
232
282
|
run("npm install --no-audit --no-fund --ignore-scripts --loglevel=error", {
|
|
233
283
|
cwd: repoDir,
|
|
234
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
|
+
}
|
|
235
294
|
for (const target of fileCopyTargets) {
|
|
236
295
|
try {
|
|
237
296
|
const snapshot = snapshotProtectedConfigs(home, target);
|
|
238
297
|
|
|
239
298
|
// Always use HOME as cwd so files land in ~/.<target>/ globally.
|
|
240
299
|
const runCwd = homedir();
|
|
241
|
-
// For claude:
|
|
242
|
-
//
|
|
243
|
-
|
|
300
|
+
// For claude: install the safe no-commands module set. The plugin
|
|
301
|
+
// already owns /aw:* command registration, and broader profiles with
|
|
302
|
+
// `--without baseline:commands` can fail on commands-core deps.
|
|
303
|
+
const installArgs = target === "claude"
|
|
304
|
+
? `--target ${target} --modules ${CLAUDE_FILE_COPY_MODULES.join(",")}`
|
|
305
|
+
: `--target ${target} --profile full`;
|
|
244
306
|
run(
|
|
245
|
-
`node ${join(repoDir, "scripts/install-apply.js")}
|
|
307
|
+
`node ${join(repoDir, "scripts/install-apply.js")} ${installArgs}`,
|
|
246
308
|
{ cwd: runCwd },
|
|
247
309
|
);
|
|
248
310
|
// Move cursor commands into aw/ subfolder for namespace consistency
|
|
249
311
|
// so they're accessible as /aw:tdd, /aw:plan — same as Claude Code plugin.
|
|
250
312
|
if (target === "cursor") {
|
|
251
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);
|
|
252
318
|
}
|
|
253
|
-
// Run sync script for codex: generates ~/.codex/prompts
|
|
319
|
+
// Run sync script for codex: generates ~/.codex/prompts/*.md and
|
|
254
320
|
// merges AGENTS.md — Codex has no slash commands, so prompts are the
|
|
255
321
|
// equivalent of commands.
|
|
256
322
|
if (target === "codex") {
|
|
@@ -262,13 +328,10 @@ export async function installAwEcc(
|
|
|
262
328
|
restoreProtectedConfigs(snapshot);
|
|
263
329
|
} catch { /* target not supported — skip */ }
|
|
264
330
|
}
|
|
265
|
-
|
|
266
|
-
// Enforce post-install parity for managed home files after all target-
|
|
267
|
-
// specific steps have run. This catches drifted local manifests that the
|
|
268
|
-
// initial install/sync path may otherwise leave partially updated.
|
|
269
|
-
repairEccInstall(repoDir, fileCopyTargets);
|
|
270
331
|
}
|
|
271
332
|
|
|
333
|
+
applyStoredStartupPreferences();
|
|
334
|
+
|
|
272
335
|
if (!silent) fmt.logSuccess("aw-ecc engine installed");
|
|
273
336
|
} catch (err) {
|
|
274
337
|
if (!silent) fmt.logWarn(`aw-ecc install failed: ${err.message}`);
|
|
@@ -312,17 +375,42 @@ export function uninstallAwEcc({ silent = false } = {}) {
|
|
|
312
375
|
} catch { /* corrupted state — skip */ }
|
|
313
376
|
}
|
|
314
377
|
|
|
315
|
-
// Codex: remove
|
|
316
|
-
// (not tracked in install-state — cleaned
|
|
378
|
+
// Codex: remove generated prompt files from sync-ecc-to-codex.sh
|
|
379
|
+
// (not tracked in install-state — cleaned via manifests, with prefix fallback)
|
|
317
380
|
const codexPromptsDir = join(HOME, ".codex", "prompts");
|
|
318
381
|
if (existsSync(codexPromptsDir)) {
|
|
319
382
|
try {
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
383
|
+
const promptFiles = new Set();
|
|
384
|
+
for (const manifestName of ["ecc-prompts-manifest.txt", "ecc-extension-prompts-manifest.txt"]) {
|
|
385
|
+
const manifestPath = join(codexPromptsDir, manifestName);
|
|
386
|
+
if (!existsSync(manifestPath)) continue;
|
|
387
|
+
try {
|
|
388
|
+
const entries = readFileSync(manifestPath, "utf8")
|
|
389
|
+
.split(/\r?\n/)
|
|
390
|
+
.map((line) => line.trim())
|
|
391
|
+
.filter(Boolean);
|
|
392
|
+
for (const entry of entries) promptFiles.add(entry);
|
|
393
|
+
} catch { /* best effort */ }
|
|
394
|
+
rmSync(manifestPath, { force: true });
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
for (const file of promptFiles) {
|
|
398
|
+
const promptPath = join(codexPromptsDir, file);
|
|
399
|
+
if (existsSync(promptPath)) {
|
|
400
|
+
rmSync(promptPath, { force: true });
|
|
323
401
|
removed++;
|
|
324
402
|
}
|
|
325
403
|
}
|
|
404
|
+
|
|
405
|
+
for (const file of readdirSync(codexPromptsDir)) {
|
|
406
|
+
const isLegacyGeneratedPrompt = file.endsWith(".md")
|
|
407
|
+
&& (file.startsWith("ecc-") || file.startsWith("aw-"));
|
|
408
|
+
if (!isLegacyGeneratedPrompt) continue;
|
|
409
|
+
|
|
410
|
+
rmSync(join(codexPromptsDir, file), { force: true });
|
|
411
|
+
removed++;
|
|
412
|
+
}
|
|
413
|
+
|
|
326
414
|
pruneEmptyParents(codexPromptsDir, join(HOME, ".codex"));
|
|
327
415
|
} catch { /* best effort */ }
|
|
328
416
|
}
|
package/git.mjs
CHANGED
|
@@ -124,34 +124,15 @@ export async function initPersistentClone(repoUrl, awHome, sparsePaths) {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
try {
|
|
127
|
-
|
|
128
|
-
stdio: 'pipe',
|
|
129
|
-
encoding: 'utf8',
|
|
130
|
-
env: {
|
|
131
|
-
...process.env,
|
|
132
|
-
GIT_TERMINAL_PROMPT: process.env.GIT_TERMINAL_PROMPT ?? '0',
|
|
133
|
-
},
|
|
134
|
-
});
|
|
127
|
+
await exec(`git clone --filter=blob:none --no-checkout "${repoUrl}" "${awHome}"`);
|
|
135
128
|
} catch (e) {
|
|
136
129
|
throw new Error(`Failed to clone ${repoUrl}: ${e.message}`);
|
|
137
130
|
}
|
|
138
131
|
|
|
139
132
|
try {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
encoding: 'utf8',
|
|
144
|
-
});
|
|
145
|
-
execSync(`git sparse-checkout set ${sparsePaths.map(p => `"${p}"`).join(' ')}`, {
|
|
146
|
-
cwd: awHome,
|
|
147
|
-
stdio: 'pipe',
|
|
148
|
-
encoding: 'utf8',
|
|
149
|
-
});
|
|
150
|
-
execSync(`git checkout ${REGISTRY_BASE_BRANCH}`, {
|
|
151
|
-
cwd: awHome,
|
|
152
|
-
stdio: 'pipe',
|
|
153
|
-
encoding: 'utf8',
|
|
154
|
-
});
|
|
133
|
+
await exec('git sparse-checkout init --no-cone', { cwd: awHome });
|
|
134
|
+
await exec(`git sparse-checkout set ${sparsePaths.map(p => `"${p}"`).join(' ')}`, { cwd: awHome });
|
|
135
|
+
await exec(`git checkout ${REGISTRY_BASE_BRANCH}`, { cwd: awHome });
|
|
155
136
|
} catch (e) {
|
|
156
137
|
throw new Error(`Failed to configure sparse checkout: ${e.message}`);
|
|
157
138
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
export const AW_HOOK_PHASES = [
|
|
2
|
+
'SessionStart',
|
|
3
|
+
'UserPromptSubmit',
|
|
4
|
+
'PreToolUse',
|
|
5
|
+
'PostToolUse',
|
|
6
|
+
'Stop',
|
|
7
|
+
'SessionEnd',
|
|
8
|
+
'PostToolUseFailure',
|
|
9
|
+
'PreCompact',
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const REQUIRED = 'required';
|
|
13
|
+
const EXTENDED = 'extended';
|
|
14
|
+
|
|
15
|
+
const NATIVE = 'native';
|
|
16
|
+
const EQUIVALENT = 'equivalent';
|
|
17
|
+
const UNSUPPORTED = 'unsupported';
|
|
18
|
+
|
|
19
|
+
export const AW_HOOK_MANIFEST = [
|
|
20
|
+
{
|
|
21
|
+
phase: 'SessionStart',
|
|
22
|
+
tier: REQUIRED,
|
|
23
|
+
sharedImplementation: 'session-start',
|
|
24
|
+
harnesses: {
|
|
25
|
+
claude: {
|
|
26
|
+
home: { status: NATIVE, events: ['SessionStart'] },
|
|
27
|
+
workspace: { status: NATIVE, events: ['SessionStart'] },
|
|
28
|
+
},
|
|
29
|
+
codex: {
|
|
30
|
+
home: { status: NATIVE, events: ['SessionStart'] },
|
|
31
|
+
workspace: { status: NATIVE, events: ['SessionStart'] },
|
|
32
|
+
},
|
|
33
|
+
cursor: {
|
|
34
|
+
home: { status: NATIVE, events: ['sessionStart'] },
|
|
35
|
+
workspace: { status: NATIVE, events: ['sessionStart'] },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
phase: 'UserPromptSubmit',
|
|
41
|
+
tier: REQUIRED,
|
|
42
|
+
sharedImplementation: 'user-prompt-submit',
|
|
43
|
+
harnesses: {
|
|
44
|
+
claude: {
|
|
45
|
+
home: { status: NATIVE, events: ['UserPromptSubmit'] },
|
|
46
|
+
workspace: { status: NATIVE, events: ['UserPromptSubmit'] },
|
|
47
|
+
},
|
|
48
|
+
codex: {
|
|
49
|
+
home: { status: NATIVE, events: ['UserPromptSubmit'] },
|
|
50
|
+
workspace: { status: NATIVE, events: ['UserPromptSubmit'] },
|
|
51
|
+
},
|
|
52
|
+
cursor: {
|
|
53
|
+
home: { status: EQUIVALENT, events: ['beforeSubmitPrompt'] },
|
|
54
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
phase: 'PreToolUse',
|
|
60
|
+
tier: REQUIRED,
|
|
61
|
+
sharedImplementation: 'pre-tool-use',
|
|
62
|
+
harnesses: {
|
|
63
|
+
claude: {
|
|
64
|
+
home: { status: NATIVE, events: ['PreToolUse'] },
|
|
65
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
66
|
+
},
|
|
67
|
+
codex: {
|
|
68
|
+
home: { status: NATIVE, events: ['PreToolUse'] },
|
|
69
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
70
|
+
},
|
|
71
|
+
cursor: {
|
|
72
|
+
home: { status: EQUIVALENT, events: ['beforeShellExecution', 'beforeMCPExecution'] },
|
|
73
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
phase: 'PostToolUse',
|
|
79
|
+
tier: REQUIRED,
|
|
80
|
+
sharedImplementation: 'post-tool-use',
|
|
81
|
+
harnesses: {
|
|
82
|
+
claude: {
|
|
83
|
+
home: { status: NATIVE, events: ['PostToolUse'] },
|
|
84
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
85
|
+
},
|
|
86
|
+
codex: {
|
|
87
|
+
home: { status: NATIVE, events: ['PostToolUse'] },
|
|
88
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
89
|
+
},
|
|
90
|
+
cursor: {
|
|
91
|
+
home: { status: EQUIVALENT, events: ['afterShellExecution', 'afterFileEdit', 'afterMCPExecution'] },
|
|
92
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
phase: 'Stop',
|
|
98
|
+
tier: REQUIRED,
|
|
99
|
+
sharedImplementation: 'stop',
|
|
100
|
+
harnesses: {
|
|
101
|
+
claude: {
|
|
102
|
+
home: { status: NATIVE, events: ['Stop'] },
|
|
103
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
104
|
+
},
|
|
105
|
+
codex: {
|
|
106
|
+
home: { status: NATIVE, events: ['Stop'] },
|
|
107
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
108
|
+
},
|
|
109
|
+
cursor: {
|
|
110
|
+
home: { status: NATIVE, events: ['stop'] },
|
|
111
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
phase: 'SessionEnd',
|
|
117
|
+
tier: EXTENDED,
|
|
118
|
+
sharedImplementation: 'session-end',
|
|
119
|
+
harnesses: {
|
|
120
|
+
claude: {
|
|
121
|
+
home: { status: UNSUPPORTED, events: [] },
|
|
122
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
123
|
+
},
|
|
124
|
+
codex: {
|
|
125
|
+
home: { status: UNSUPPORTED, events: [] },
|
|
126
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
127
|
+
},
|
|
128
|
+
cursor: {
|
|
129
|
+
home: { status: NATIVE, events: ['sessionEnd'] },
|
|
130
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
phase: 'PostToolUseFailure',
|
|
136
|
+
tier: EXTENDED,
|
|
137
|
+
sharedImplementation: 'post-tool-use-failure',
|
|
138
|
+
harnesses: {
|
|
139
|
+
claude: {
|
|
140
|
+
home: { status: NATIVE, events: ['PostToolUseFailure'] },
|
|
141
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
142
|
+
},
|
|
143
|
+
codex: {
|
|
144
|
+
home: { status: UNSUPPORTED, events: [] },
|
|
145
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
146
|
+
},
|
|
147
|
+
cursor: {
|
|
148
|
+
home: { status: UNSUPPORTED, events: [] },
|
|
149
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
phase: 'PreCompact',
|
|
155
|
+
tier: EXTENDED,
|
|
156
|
+
sharedImplementation: 'pre-compact',
|
|
157
|
+
harnesses: {
|
|
158
|
+
claude: {
|
|
159
|
+
home: { status: NATIVE, events: ['PreCompact'] },
|
|
160
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
161
|
+
},
|
|
162
|
+
codex: {
|
|
163
|
+
home: { status: UNSUPPORTED, events: [] },
|
|
164
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
165
|
+
},
|
|
166
|
+
cursor: {
|
|
167
|
+
home: { status: NATIVE, events: ['preCompact'] },
|
|
168
|
+
workspace: { status: UNSUPPORTED, events: [] },
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
export function getHookManifestEntry(phase) {
|
|
175
|
+
const entry = AW_HOOK_MANIFEST.find(item => item.phase === phase);
|
|
176
|
+
if (!entry) {
|
|
177
|
+
throw new Error(`Unknown AW hook phase: ${phase}`);
|
|
178
|
+
}
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getHarnessPhaseEntries(harness, surface = 'home') {
|
|
183
|
+
return AW_HOOK_MANIFEST.map(entry => ({
|
|
184
|
+
...entry,
|
|
185
|
+
harness: entry.harnesses[harness]?.[surface] || { status: UNSUPPORTED, events: [] },
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function getSupportedHarnessPhaseEntries(harness, surface = 'home') {
|
|
190
|
+
return getHarnessPhaseEntries(harness, surface).filter(entry => entry.harness.status !== UNSUPPORTED);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function getRequiredHarnessPhaseEntries(harness, surface = 'home') {
|
|
194
|
+
return getSupportedHarnessPhaseEntries(harness, surface).filter(entry => entry.tier === REQUIRED);
|
|
195
|
+
}
|