@eventmodelers/cli 1.0.52 → 1.0.54
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.js +5 -1
- package/package.json +1 -1
- package/shared/build-kit/lib/util/find-slice.cjs +2 -0
- package/stacks/node/templates/build-kit/lib/check-commit-scope.cjs +17 -4
- package/stacks/node/templates/build-kit/lib/checks/10-slice-scope.cjs +25 -2
- package/stacks/node/templates/root/.githooks/pre-commit +10 -3
- package/stacks/supabase/templates/build-kit/lib/check-commit-scope.cjs +17 -4
- package/stacks/supabase/templates/build-kit/lib/checks/10-slice-scope.cjs +25 -2
- package/stacks/supabase/templates/root/.githooks/pre-commit +10 -3
package/cli.js
CHANGED
|
@@ -1874,7 +1874,11 @@ program
|
|
|
1874
1874
|
|
|
1875
1875
|
console.log(`🔎 Running checks from ${relative(cwd, checkScript)}...`);
|
|
1876
1876
|
try {
|
|
1877
|
-
|
|
1877
|
+
// cwd must be the project root (not kitDir/.build-kit) — the script's
|
|
1878
|
+
// `git diff --relative` scopes its output to cwd's subtree, so running
|
|
1879
|
+
// it from inside .build-kit/ makes every changed file outside .build-kit/
|
|
1880
|
+
// (i.e. everything under src/) invisible, and the check silently no-ops.
|
|
1881
|
+
execSync(`node "${checkScript}"`, { cwd, stdio: 'inherit' });
|
|
1878
1882
|
} catch (err) {
|
|
1879
1883
|
process.exit(err.status || 1);
|
|
1880
1884
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.54",
|
|
4
4
|
"description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@ function findSliceJson(repoRoot, context, sliceName) {
|
|
|
21
21
|
const root = path.join(repoRoot, '.build-kit', '.slices');
|
|
22
22
|
if (!fs.existsSync(root)) return null;
|
|
23
23
|
|
|
24
|
+
const wantContext = normalize(context);
|
|
24
25
|
const wantSlice = normalize(sliceName);
|
|
25
26
|
const candidates = [];
|
|
26
27
|
|
|
@@ -33,6 +34,7 @@ function findSliceJson(repoRoot, context, sliceName) {
|
|
|
33
34
|
|
|
34
35
|
for (const contextDir of contextDirs) {
|
|
35
36
|
if (!contextDir.isDirectory()) continue;
|
|
37
|
+
if (normalize(contextDir.name) !== wantContext) continue;
|
|
36
38
|
const contextPath = path.join(root, contextDir.name);
|
|
37
39
|
let sliceDirs;
|
|
38
40
|
try {
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
// all uncommitted changes by default, staged-only with --staged
|
|
23
23
|
// touchesSlice true — this commit touches src/slices/{context}/{slice}/**
|
|
24
24
|
// (the runner already gates on this before loading checks)
|
|
25
|
-
// repoRoot absolute path to
|
|
25
|
+
// repoRoot absolute path to this project's own root (process.cwd()) —
|
|
26
|
+
// not the outer git repo's top-level when this project is a
|
|
27
|
+
// subdirectory of a larger repo
|
|
26
28
|
// SLICE_PATTERN RegExp matching a path inside a slice's own folder
|
|
27
29
|
//
|
|
28
30
|
// Zero dependencies — plain Node, so it works from git's pre-commit hook
|
|
@@ -45,14 +47,20 @@ function parseNameStatus(out) {
|
|
|
45
47
|
});
|
|
46
48
|
}
|
|
47
49
|
|
|
50
|
+
// --relative scopes and rewrites paths relative to cwd instead of the git
|
|
51
|
+
// top-level — required when this runs from a subdirectory of a larger repo
|
|
52
|
+
// (e.g. a `backend/` folder inside a monorepo): without it, every path comes
|
|
53
|
+
// back prefixed (`backend/src/slices/...`), SLICE_PATTERN never matches, and
|
|
54
|
+
// the whole guard silently no-ops on every commit. `git ls-files` is already
|
|
55
|
+
// cwd-relative by default, so it needs no such flag.
|
|
48
56
|
function stagedChanges() {
|
|
49
|
-
return parseNameStatus(execSync('git diff --cached --name-status --no-renames', { encoding: 'utf8' }));
|
|
57
|
+
return parseNameStatus(execSync('git diff --cached --name-status --no-renames --relative', { encoding: 'utf8' }));
|
|
50
58
|
}
|
|
51
59
|
|
|
52
60
|
function allChanges() {
|
|
53
61
|
// Working tree vs HEAD already covers both staged and unstaged edits to
|
|
54
62
|
// tracked files; untracked (never-`git add`ed) files need a separate call.
|
|
55
|
-
const tracked = parseNameStatus(execSync('git diff HEAD --name-status --no-renames', { encoding: 'utf8' }));
|
|
63
|
+
const tracked = parseNameStatus(execSync('git diff HEAD --name-status --no-renames --relative', { encoding: 'utf8' }));
|
|
56
64
|
const untracked = execSync('git ls-files --others --exclude-standard', { encoding: 'utf8' })
|
|
57
65
|
.split('\n')
|
|
58
66
|
.filter(Boolean)
|
|
@@ -102,7 +110,12 @@ function main() {
|
|
|
102
110
|
const ctx = {
|
|
103
111
|
changes,
|
|
104
112
|
touchesSlice,
|
|
105
|
-
|
|
113
|
+
// Deliberately process.cwd(), not `git rev-parse --show-toplevel` — this
|
|
114
|
+
// project can be a subdirectory of a larger repo (see the --relative note
|
|
115
|
+
// on allChanges/stagedChanges above), and every path here (and every path
|
|
116
|
+
// checks join onto repoRoot, e.g. `.build-kit/.slices/`) is relative to
|
|
117
|
+
// this project's own root, not the outer git repo's.
|
|
118
|
+
repoRoot: process.cwd(),
|
|
106
119
|
SLICE_PATTERN,
|
|
107
120
|
};
|
|
108
121
|
|
|
@@ -14,15 +14,38 @@ const ALLOWED_EXCEPTIONS = [
|
|
|
14
14
|
|
|
15
15
|
const MIGRATION_PATTERN = /^migrations\/V\d+__.*\.sql$/;
|
|
16
16
|
|
|
17
|
+
// Captures the {context}/{slice} segment so files from two different slices
|
|
18
|
+
// in one commit can be told apart — SLICE_PATTERN alone only proves a path is
|
|
19
|
+
// *inside some* slice folder, not which one.
|
|
20
|
+
const SLICE_KEY_PATTERN = /^src\/slices\/([^/]+\/[^/]+)\//;
|
|
21
|
+
|
|
17
22
|
module.exports = {
|
|
18
23
|
name: 'slice-scope',
|
|
19
24
|
run(ctx) {
|
|
20
25
|
const violations = [];
|
|
26
|
+
|
|
27
|
+
const sliceKeys = new Set();
|
|
28
|
+
for (const { path: p } of ctx.changes) {
|
|
29
|
+
const m = p.match(SLICE_KEY_PATTERN);
|
|
30
|
+
if (m) sliceKeys.add(m[1]);
|
|
31
|
+
}
|
|
32
|
+
const primarySlice = [...sliceKeys].sort()[0] || null;
|
|
33
|
+
|
|
21
34
|
for (const { path: p } of ctx.changes) {
|
|
22
|
-
if (ctx.SLICE_PATTERN.test(p)) continue;
|
|
23
35
|
if (MIGRATION_PATTERN.test(p)) continue;
|
|
24
36
|
if (ALLOWED_EXCEPTIONS.some((r) => r.test(p))) continue;
|
|
25
|
-
|
|
37
|
+
|
|
38
|
+
const m = p.match(SLICE_KEY_PATTERN);
|
|
39
|
+
if (!m) {
|
|
40
|
+
violations.push({ path: p, reason: 'outside src/slices/{context}/{slice}/ and not a documented exception' });
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (m[1] !== primarySlice) {
|
|
44
|
+
violations.push({
|
|
45
|
+
path: p,
|
|
46
|
+
reason: `touches slice "${m[1]}" but this commit's scope is "${primarySlice}" — split into separate commits`,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
26
49
|
}
|
|
27
50
|
return violations;
|
|
28
51
|
},
|
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
# is versioned and shared by every clone instead of living only in .git/hooks/.
|
|
4
4
|
set -e
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# Deliberately NOT `git rev-parse --show-toplevel` — when this project is a
|
|
7
|
+
# subdirectory of a larger repo (a monorepo with `.githooks` installed inside
|
|
8
|
+
# e.g. `backend/`), show-toplevel returns the outer repo's root, `.build-kit`
|
|
9
|
+
# doesn't exist there, and the hook would silently no-op on every commit.
|
|
10
|
+
# This script's own location is always `<project_root>/.githooks/pre-commit`,
|
|
11
|
+
# so use that instead — and cd into it so check-commit-scope.cjs's own
|
|
12
|
+
# process.cwd()/--relative git-diff logic sees the right root too.
|
|
13
|
+
project_root=$(cd "$(dirname "$0")/.." && pwd)
|
|
14
|
+
script="$project_root/.build-kit/lib/check-commit-scope.cjs"
|
|
8
15
|
|
|
9
16
|
if [ -f "$script" ]; then
|
|
10
|
-
node "$script" --staged
|
|
17
|
+
(cd "$project_root" && node "$script" --staged)
|
|
11
18
|
fi
|
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
// all uncommitted changes by default, staged-only with --staged
|
|
23
23
|
// touchesSlice true — this commit touches src/slices/{context}/{slice}/**
|
|
24
24
|
// (the runner already gates on this before loading checks)
|
|
25
|
-
// repoRoot absolute path to
|
|
25
|
+
// repoRoot absolute path to this project's own root (process.cwd()) —
|
|
26
|
+
// not the outer git repo's top-level when this project is a
|
|
27
|
+
// subdirectory of a larger repo
|
|
26
28
|
// SLICE_PATTERN RegExp matching a path inside a slice's own folder
|
|
27
29
|
//
|
|
28
30
|
// Zero dependencies — plain Node, so it works from git's pre-commit hook
|
|
@@ -48,14 +50,20 @@ function parseNameStatus(out) {
|
|
|
48
50
|
});
|
|
49
51
|
}
|
|
50
52
|
|
|
53
|
+
// --relative scopes and rewrites paths relative to cwd instead of the git
|
|
54
|
+
// top-level — required when this runs from a subdirectory of a larger repo
|
|
55
|
+
// (e.g. a `backend/` folder inside a monorepo): without it, every path comes
|
|
56
|
+
// back prefixed (`backend/src/slices/...`), SLICE_PATTERN never matches, and
|
|
57
|
+
// the whole guard silently no-ops on every commit. `git ls-files` is already
|
|
58
|
+
// cwd-relative by default, so it needs no such flag.
|
|
51
59
|
function stagedChanges() {
|
|
52
|
-
return parseNameStatus(execSync('git diff --cached --name-status --no-renames', { encoding: 'utf8' }));
|
|
60
|
+
return parseNameStatus(execSync('git diff --cached --name-status --no-renames --relative', { encoding: 'utf8' }));
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
function allChanges() {
|
|
56
64
|
// Working tree vs HEAD already covers both staged and unstaged edits to
|
|
57
65
|
// tracked files; untracked (never-`git add`ed) files need a separate call.
|
|
58
|
-
const tracked = parseNameStatus(execSync('git diff HEAD --name-status --no-renames', { encoding: 'utf8' }));
|
|
66
|
+
const tracked = parseNameStatus(execSync('git diff HEAD --name-status --no-renames --relative', { encoding: 'utf8' }));
|
|
59
67
|
const untracked = execSync('git ls-files --others --exclude-standard', { encoding: 'utf8' })
|
|
60
68
|
.split('\n')
|
|
61
69
|
.filter(Boolean)
|
|
@@ -105,7 +113,12 @@ function main() {
|
|
|
105
113
|
const ctx = {
|
|
106
114
|
changes,
|
|
107
115
|
touchesSlice,
|
|
108
|
-
|
|
116
|
+
// Deliberately process.cwd(), not `git rev-parse --show-toplevel` — this
|
|
117
|
+
// project can be a subdirectory of a larger repo (see the --relative note
|
|
118
|
+
// on allChanges/stagedChanges above), and every path here (and every path
|
|
119
|
+
// checks join onto repoRoot, e.g. `.build-kit/.slices/`) is relative to
|
|
120
|
+
// this project's own root, not the outer git repo's.
|
|
121
|
+
repoRoot: process.cwd(),
|
|
109
122
|
SLICE_PATTERN,
|
|
110
123
|
};
|
|
111
124
|
|
|
@@ -18,16 +18,39 @@ const ALLOWED_EXCEPTIONS = [
|
|
|
18
18
|
|
|
19
19
|
const MIGRATION_PATTERN = /^supabase\/migrations\/V\d+__.*\.sql$/;
|
|
20
20
|
|
|
21
|
+
// Captures the {context}/{slice} segment so files from two different slices
|
|
22
|
+
// in one commit can be told apart — SLICE_PATTERN alone only proves a path is
|
|
23
|
+
// *inside some* slice folder, not which one.
|
|
24
|
+
const SLICE_KEY_PATTERN = /^src\/slices\/([^/]+\/[^/]+)\//;
|
|
25
|
+
|
|
21
26
|
module.exports = {
|
|
22
27
|
name: 'slice-scope',
|
|
23
28
|
run(ctx) {
|
|
24
29
|
const violations = [];
|
|
30
|
+
|
|
31
|
+
const sliceKeys = new Set();
|
|
32
|
+
for (const { path: p } of ctx.changes) {
|
|
33
|
+
const m = p.match(SLICE_KEY_PATTERN);
|
|
34
|
+
if (m) sliceKeys.add(m[1]);
|
|
35
|
+
}
|
|
36
|
+
const primarySlice = [...sliceKeys].sort()[0] || null;
|
|
37
|
+
|
|
25
38
|
for (const { path: p } of ctx.changes) {
|
|
26
|
-
if (ctx.SLICE_PATTERN.test(p)) continue;
|
|
27
39
|
if (WEBHOOK_FUNCTION_PATTERN.test(p)) continue;
|
|
28
40
|
if (MIGRATION_PATTERN.test(p)) continue;
|
|
29
41
|
if (ALLOWED_EXCEPTIONS.some((r) => r.test(p))) continue;
|
|
30
|
-
|
|
42
|
+
|
|
43
|
+
const m = p.match(SLICE_KEY_PATTERN);
|
|
44
|
+
if (!m) {
|
|
45
|
+
violations.push({ path: p, reason: 'outside src/slices/{context}/{slice}/ and not a documented exception' });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (m[1] !== primarySlice) {
|
|
49
|
+
violations.push({
|
|
50
|
+
path: p,
|
|
51
|
+
reason: `touches slice "${m[1]}" but this commit's scope is "${primarySlice}" — split into separate commits`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
31
54
|
}
|
|
32
55
|
return violations;
|
|
33
56
|
},
|
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
# is versioned and shared by every clone instead of living only in .git/hooks/.
|
|
4
4
|
set -e
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# Deliberately NOT `git rev-parse --show-toplevel` — when this project is a
|
|
7
|
+
# subdirectory of a larger repo (a monorepo with `.githooks` installed inside
|
|
8
|
+
# e.g. `backend/`), show-toplevel returns the outer repo's root, `.build-kit`
|
|
9
|
+
# doesn't exist there, and the hook would silently no-op on every commit.
|
|
10
|
+
# This script's own location is always `<project_root>/.githooks/pre-commit`,
|
|
11
|
+
# so use that instead — and cd into it so check-commit-scope.cjs's own
|
|
12
|
+
# process.cwd()/--relative git-diff logic sees the right root too.
|
|
13
|
+
project_root=$(cd "$(dirname "$0")/.." && pwd)
|
|
14
|
+
script="$project_root/.build-kit/lib/check-commit-scope.cjs"
|
|
8
15
|
|
|
9
16
|
if [ -f "$script" ]; then
|
|
10
|
-
node "$script" --staged
|
|
17
|
+
(cd "$project_root" && node "$script" --staged)
|
|
11
18
|
fi
|