@ghl-ai/aw 0.1.37-beta.8 → 0.1.37
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 +12 -2
- package/commands/init.mjs +27 -7
- package/commands/link-project.mjs +3 -0
- package/commands/nuke.mjs +19 -14
- package/commands/pull.mjs +74 -32
- package/commands/push-rules.mjs +212 -0
- package/commands/push.mjs +27 -3
- package/commands/slack-sim.mjs +128 -0
- package/config.mjs +1 -1
- package/constants.mjs +8 -1
- package/ecc.mjs +80 -4
- package/file-tree.mjs +76 -0
- package/fmt.mjs +14 -0
- package/git.mjs +2 -1
- package/hooks.mjs +101 -0
- package/integrate.mjs +49 -11
- package/package.json +10 -4
- package/render-rules.mjs +483 -0
- package/slack-sim/fake-slack.mjs +200 -0
- package/slack-sim/http.mjs +170 -0
- package/slack-sim/in-process.mjs +263 -0
- package/slack-sim/render.mjs +42 -0
- package/slack-sim/scenario.mjs +64 -0
- package/slack-sim/scenarios/checkpoint-approve.json +21 -0
- package/slack-sim/scenarios/image-thread.json +27 -0
- package/slack-sim/scenarios/implementation-basic.json +18 -0
- package/slack-sim/scenarios/poll-webhook-race.json +18 -0
- package/slack-sim/scenarios/review-pr.json +14 -0
- package/telemetry.mjs +5 -3
package/git.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { mkdtempSync, existsSync, lstatSync, rmSync, readFileSync, symlinkSync,
|
|
|
5
5
|
import { join, basename, dirname } from 'node:path';
|
|
6
6
|
import { homedir, tmpdir } from 'node:os';
|
|
7
7
|
import { promisify } from 'node:util';
|
|
8
|
-
import { REGISTRY_BASE_BRANCH, REGISTRY_DIR, DOCS_SOURCE_DIR } from './constants.mjs';
|
|
8
|
+
import { REGISTRY_BASE_BRANCH, REGISTRY_DIR, DOCS_SOURCE_DIR, RULES_SOURCE_DIR } from './constants.mjs';
|
|
9
9
|
|
|
10
10
|
const exec = promisify(execCb);
|
|
11
11
|
|
|
@@ -91,6 +91,7 @@ export function includeToSparsePaths(paths) {
|
|
|
91
91
|
result.add(`${REGISTRY_DIR}/AW-PROTOCOL.md`);
|
|
92
92
|
if (paths.includes('platform')) {
|
|
93
93
|
result.add(DOCS_SOURCE_DIR);
|
|
94
|
+
result.add(RULES_SOURCE_DIR);
|
|
94
95
|
}
|
|
95
96
|
return [...result];
|
|
96
97
|
}
|
package/hooks.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { execSync } from 'node:child_process';
|
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
11
|
import * as fmt from './fmt.mjs';
|
|
12
|
+
import { AW_CO_AUTHOR } from './constants.mjs';
|
|
12
13
|
|
|
13
14
|
const HOME = homedir();
|
|
14
15
|
const HOOKS_DIR = join(HOME, '.aw', 'hooks');
|
|
@@ -113,6 +114,27 @@ fi
|
|
|
113
114
|
exit 0
|
|
114
115
|
`;
|
|
115
116
|
|
|
117
|
+
// prepare-commit-msg: synchronous hook — appends Co-Authored-By trailer to commits
|
|
118
|
+
// in AW-linked repos. Skips merge commits and respects AW_NO_COAUTHOR=1 opt-out.
|
|
119
|
+
const PREPARE_COMMIT_MSG = makeDispatcher('prepare-commit-msg', `\
|
|
120
|
+
# Inject co-author trailer unless it's a merge commit or opt-out is set
|
|
121
|
+
if [ "$2" != "merge" ] && [ "$AW_NO_COAUTHOR" != "1" ]; then
|
|
122
|
+
# Only brand AW-linked repos (check for .aw/ symlink or worktree)
|
|
123
|
+
if [ -L ".aw" ] || [ -f ".aw/.git" ]; then
|
|
124
|
+
TRAILER="${AW_CO_AUTHOR}"
|
|
125
|
+
# Replace Claude/Cursor co-author trailers with AW
|
|
126
|
+
sed -i.bak '/^Co-Authored-By:.*Claude/d' "$1" 2>/dev/null || true
|
|
127
|
+
sed -i.bak '/^Co-Authored-By:.*noreply@anthropic\\.com/d' "$1" 2>/dev/null || true
|
|
128
|
+
sed -i.bak '/^Co-Authored-By:.*Cursor/d' "$1" 2>/dev/null || true
|
|
129
|
+
rm -f "$1.bak"
|
|
130
|
+
# Only append if not already present (idempotent)
|
|
131
|
+
if ! grep -qF "$TRAILER" "$1"; then
|
|
132
|
+
echo "" >> "$1"
|
|
133
|
+
echo "$TRAILER" >> "$1"
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
fi`);
|
|
137
|
+
|
|
116
138
|
/**
|
|
117
139
|
* Install global git hooks at ~/.aw/hooks/ and set core.hooksPath.
|
|
118
140
|
* If core.hooksPath is already set by another tool, saves the previous
|
|
@@ -136,6 +158,9 @@ export function installGlobalHooks() {
|
|
|
136
158
|
writeFileSync(join(HOOKS_DIR, 'post-commit'), POST_COMMIT);
|
|
137
159
|
chmodSync(join(HOOKS_DIR, 'post-commit'), '755');
|
|
138
160
|
|
|
161
|
+
writeFileSync(join(HOOKS_DIR, 'prepare-commit-msg'), PREPARE_COMMIT_MSG);
|
|
162
|
+
chmodSync(join(HOOKS_DIR, 'prepare-commit-msg'), '755');
|
|
163
|
+
|
|
139
164
|
// Detect and preserve existing core.hooksPath
|
|
140
165
|
let previousPath = null;
|
|
141
166
|
try {
|
|
@@ -199,3 +224,79 @@ export function removeGlobalHooks() {
|
|
|
199
224
|
|
|
200
225
|
fmt.logStep('Global git hooks removed');
|
|
201
226
|
}
|
|
227
|
+
|
|
228
|
+
// Standalone prepare-commit-msg for local .git/hooks/ installation.
|
|
229
|
+
// Unlike the global dispatcher, this does NOT chain to repo-local hooks
|
|
230
|
+
// (because it IS the repo-local hook — chaining would cause infinite recursion).
|
|
231
|
+
const LOCAL_PREPARE_COMMIT_MSG = `#!/bin/sh
|
|
232
|
+
# aw: local prepare-commit-msg hook (installed by aw init)
|
|
233
|
+
|
|
234
|
+
# Skip aw temp dirs
|
|
235
|
+
case "$(pwd)" in /tmp/aw-*|/var/folders/*/aw-*|*/.aw|*/.aw/*) exit 0 ;; esac
|
|
236
|
+
|
|
237
|
+
# Skip merge commits
|
|
238
|
+
[ "$2" = "merge" ] && exit 0
|
|
239
|
+
|
|
240
|
+
# Opt-out escape hatch
|
|
241
|
+
[ "$AW_NO_COAUTHOR" = "1" ] && exit 0
|
|
242
|
+
|
|
243
|
+
# Only brand AW-linked repos (check for .aw/ symlink or worktree)
|
|
244
|
+
if [ -L ".aw" ] || [ -f ".aw/.git" ]; then
|
|
245
|
+
TRAILER="${AW_CO_AUTHOR}"
|
|
246
|
+
# Replace Claude/Cursor co-author trailers with AW
|
|
247
|
+
sed -i.bak '/^Co-Authored-By:.*Claude/d' "$1" 2>/dev/null || true
|
|
248
|
+
sed -i.bak '/^Co-Authored-By:.*noreply@anthropic\\.com/d' "$1" 2>/dev/null || true
|
|
249
|
+
sed -i.bak '/^Co-Authored-By:.*Cursor/d' "$1" 2>/dev/null || true
|
|
250
|
+
rm -f "$1.bak"
|
|
251
|
+
# Only append if not already present (idempotent)
|
|
252
|
+
if ! grep -qF "$TRAILER" "$1"; then
|
|
253
|
+
echo "" >> "$1"
|
|
254
|
+
echo "$TRAILER" >> "$1"
|
|
255
|
+
fi
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
exit 0
|
|
259
|
+
`;
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Install prepare-commit-msg hook into a project's local .git/hooks/.
|
|
263
|
+
* This covers repos where another tool (e.g. Claude Code) sets a local
|
|
264
|
+
* core.hooksPath that overrides the global one.
|
|
265
|
+
*
|
|
266
|
+
* @param {string} projectDir — root of the project (must contain .git/)
|
|
267
|
+
*/
|
|
268
|
+
export function installLocalCommitHook(projectDir) {
|
|
269
|
+
if (process.env.AW_NO_HOOKS === '1') return;
|
|
270
|
+
try {
|
|
271
|
+
const gitDir = join(projectDir, '.git');
|
|
272
|
+
if (!existsSync(gitDir)) return;
|
|
273
|
+
|
|
274
|
+
// Only install if a local core.hooksPath is set (overrides our global hooks)
|
|
275
|
+
let localHooksPath;
|
|
276
|
+
try {
|
|
277
|
+
localHooksPath = execSync('git config --local core.hooksPath', {
|
|
278
|
+
cwd: projectDir, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
|
|
279
|
+
}).trim();
|
|
280
|
+
} catch { return; /* no local override — global hooks will handle it */ }
|
|
281
|
+
|
|
282
|
+
if (!localHooksPath) return;
|
|
283
|
+
|
|
284
|
+
// Resolve relative paths against projectDir
|
|
285
|
+
const hooksDir = localHooksPath.startsWith('/')
|
|
286
|
+
? localHooksPath
|
|
287
|
+
: join(projectDir, localHooksPath);
|
|
288
|
+
|
|
289
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
290
|
+
|
|
291
|
+
const hookPath = join(hooksDir, 'prepare-commit-msg');
|
|
292
|
+
// Don't overwrite if a non-aw hook already exists
|
|
293
|
+
if (existsSync(hookPath)) {
|
|
294
|
+
const content = readFileSync(hookPath, 'utf8');
|
|
295
|
+
if (!content.includes('aw:') && !content.includes('Co-Authored-By')) return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
writeFileSync(hookPath, LOCAL_PREPARE_COMMIT_MSG);
|
|
299
|
+
chmodSync(hookPath, '755');
|
|
300
|
+
} catch { /* best effort */ }
|
|
301
|
+
}
|
|
302
|
+
|
package/integrate.mjs
CHANGED
|
@@ -6,6 +6,19 @@ 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';
|
|
10
|
+
|
|
11
|
+
function upsertRulesSection(content, header, section) {
|
|
12
|
+
if (!section) return content;
|
|
13
|
+
|
|
14
|
+
const marker = `## ${header}`;
|
|
15
|
+
const idx = content.indexOf(marker);
|
|
16
|
+
if (idx === -1) {
|
|
17
|
+
return `${content.trimEnd()}\n\n${section}\n`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return `${content.slice(0, idx).trimEnd()}\n\n${section}\n`;
|
|
21
|
+
}
|
|
9
22
|
|
|
10
23
|
/**
|
|
11
24
|
* Count hand-written commands already present in the registry.
|
|
@@ -62,15 +75,29 @@ function findFiles(dir, typeName) {
|
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
/**
|
|
65
|
-
* Copy AGENTS.md to project root
|
|
66
|
-
* CLAUDE.md
|
|
67
|
-
*
|
|
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.
|
|
68
81
|
*/
|
|
69
82
|
export function copyInstructions(cwd, tempDir, namespace) {
|
|
83
|
+
const rulesSections = renderRules(cwd);
|
|
70
84
|
const createdFiles = [];
|
|
71
|
-
for (const file of ['AGENTS.md']) {
|
|
85
|
+
for (const file of ['AGENTS.md', 'CLAUDE.md']) {
|
|
72
86
|
const dest = join(cwd, file);
|
|
73
|
-
if (existsSync(dest))
|
|
87
|
+
if (existsSync(dest)) {
|
|
88
|
+
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);
|
|
92
|
+
|
|
93
|
+
if (updated !== existing) {
|
|
94
|
+
writeFileSync(dest, updated);
|
|
95
|
+
fmt.logSuccess(`Updated ${file}`);
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (file === 'CLAUDE.md') continue;
|
|
74
101
|
|
|
75
102
|
if (tempDir) {
|
|
76
103
|
const src = join(tempDir, '.aw_registry', file);
|
|
@@ -86,7 +113,7 @@ export function copyInstructions(cwd, tempDir, namespace) {
|
|
|
86
113
|
}
|
|
87
114
|
}
|
|
88
115
|
|
|
89
|
-
const content = generateAgentsMd(cwd, namespace);
|
|
116
|
+
const content = generateAgentsMd(cwd, namespace, rulesSections);
|
|
90
117
|
if (content) {
|
|
91
118
|
writeFileSync(dest, content);
|
|
92
119
|
fmt.logSuccess(`Created ${file}`);
|
|
@@ -96,9 +123,9 @@ export function copyInstructions(cwd, tempDir, namespace) {
|
|
|
96
123
|
return createdFiles;
|
|
97
124
|
}
|
|
98
125
|
|
|
99
|
-
function generateClaudeMd(cwd, namespace) {
|
|
126
|
+
function generateClaudeMd(cwd, namespace, rulesSections = {}) {
|
|
100
127
|
const team = namespace || 'my-team';
|
|
101
|
-
|
|
128
|
+
let base = `# CLAUDE.md — ${team}
|
|
102
129
|
|
|
103
130
|
Team: ${team} | Local-first orchestration via \`.aw_docs/\` | MCPs: \`memory/*\` (shared knowledge), \`git-jenkins\` (CI/CD), \`grafana\` (observability)
|
|
104
131
|
|
|
@@ -231,11 +258,17 @@ Gates are shell scripts in \`scripts/gates/\` — CANNOT be bypassed by LLM judg
|
|
|
231
258
|
- **Agents**: Definitions ≤5 KB, reference material in linked skills
|
|
232
259
|
- **Local-first**: No MCP calls for orchestration — file reads are faster and always available
|
|
233
260
|
`;
|
|
261
|
+
|
|
262
|
+
if (rulesSections.claudeSection) {
|
|
263
|
+
base += '\n' + rulesSections.claudeSection;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return base;
|
|
234
267
|
}
|
|
235
268
|
|
|
236
|
-
function generateAgentsMd(cwd, namespace) {
|
|
269
|
+
function generateAgentsMd(cwd, namespace, rulesSections = {}) {
|
|
237
270
|
const team = namespace || 'my-team';
|
|
238
|
-
|
|
271
|
+
let base = `# AGENTS.md — ${team}
|
|
239
272
|
|
|
240
273
|
## Agent System
|
|
241
274
|
|
|
@@ -367,6 +400,12 @@ Each agent has quality test cases in \`.claude/evals/\`:
|
|
|
367
400
|
|
|
368
401
|
Run with: \`/platform:eval agent:<slug>\` or \`/platform:eval skill:<slug>\`
|
|
369
402
|
`;
|
|
403
|
+
|
|
404
|
+
if (rulesSections.agentsSection) {
|
|
405
|
+
base += '\n' + rulesSections.agentsSection;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return base;
|
|
370
409
|
}
|
|
371
410
|
|
|
372
411
|
/**
|
|
@@ -450,4 +489,3 @@ function getTeamNamespaces(awDir) {
|
|
|
450
489
|
.map(p => p.split('/')[0]),
|
|
451
490
|
)];
|
|
452
491
|
}
|
|
453
|
-
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghl-ai/aw",
|
|
3
|
-
"version": "0.1.37
|
|
3
|
+
"version": "0.1.37",
|
|
4
4
|
"description": "Agentic Workspace CLI — pull, push & manage agents, skills and commands from the registry",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": "bin.js",
|
|
@@ -20,10 +20,13 @@
|
|
|
20
20
|
"paths.mjs",
|
|
21
21
|
"plan.mjs",
|
|
22
22
|
"registry.mjs",
|
|
23
|
+
"slack-sim/",
|
|
24
|
+
"file-tree.mjs",
|
|
23
25
|
"apply.mjs",
|
|
24
26
|
"update.mjs",
|
|
25
27
|
"hooks.mjs",
|
|
26
28
|
"ecc.mjs",
|
|
29
|
+
"render-rules.mjs",
|
|
27
30
|
"telemetry.mjs"
|
|
28
31
|
],
|
|
29
32
|
"engines": {
|
|
@@ -39,8 +42,10 @@
|
|
|
39
42
|
"author": "GoHighLevel",
|
|
40
43
|
"license": "MIT",
|
|
41
44
|
"scripts": {
|
|
42
|
-
"test": "vitest
|
|
43
|
-
"test:
|
|
45
|
+
"test": "yarn test:vitest && yarn test:node",
|
|
46
|
+
"test:vitest": "vitest run --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs",
|
|
47
|
+
"test:node": "node tests/run-node-tests.mjs",
|
|
48
|
+
"test:watch": "vitest --reporter=verbose tests/commands tests/mcp.test.mjs tests/telemetry.test.mjs",
|
|
44
49
|
"preuninstall": "node bin.js nuke 2>/dev/null || true"
|
|
45
50
|
},
|
|
46
51
|
"publishConfig": {
|
|
@@ -49,7 +54,8 @@
|
|
|
49
54
|
"dependencies": {
|
|
50
55
|
"@clack/prompts": "0.8.2",
|
|
51
56
|
"chalk": "^5.6.2",
|
|
52
|
-
"figlet": "^1.11.0"
|
|
57
|
+
"figlet": "^1.11.0",
|
|
58
|
+
"pg": "^8.13.0"
|
|
53
59
|
},
|
|
54
60
|
"devDependencies": {
|
|
55
61
|
"vitest": "^4.1.2"
|