@ryuenn3123/agentic-senior-core 6.5.1 → 6.5.2

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.5.1",
3
+ "version": "6.5.2",
4
4
  "description": "Universal AI coding rules. Write code like a staff engineer.",
5
5
  "skills": "./skills/",
6
6
  "hooks": "./hooks/hooks.json",
@@ -120,7 +120,7 @@ process.stdin.on('data', chunk => {
120
120
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'asc-dedup-'));
121
121
 
122
122
  const ignoreFlags = (config.ignoreDirs || []).map(function (d) { return '--ignore "' + d + '"'; }).join(' ');
123
- const minTokens = config.minTokens || 50;
123
+ const minTokens = config.minTokens || 30;
124
124
  const scanCmd = ' "' + scanDir + '" --min-tokens ' + minTokens
125
125
  + ' --reporters json --silent --output "' + tmpDir + '" ' + ignoreFlags;
126
126
 
@@ -260,7 +260,7 @@ function loadDedupConfig() {
260
260
  }
261
261
  return {
262
262
  mode: 'advisory',
263
- minTokens: 50,
263
+ minTokens: 30,
264
264
  ignoreDirs: [
265
265
  'tests', 'test', '__tests__', 'migrations', 'generated', 'node_modules',
266
266
  'dist', 'build', '.next', '.nuxt', '.expo', 'coverage', '.storybook',
@@ -327,9 +327,19 @@ function checkForDuplicates(report, filePath) {
327
327
  var cwd = process.cwd();
328
328
  var matchedFile = path.relative(cwd, path.resolve(otherRaw)).replace(/\\/g, '/');
329
329
  var lines = dup.lines || 0;
330
- // jscpd v5 reports fragments; estimate overlap percentage from line count
331
- var totalLines = (report.statistics && report.statistics.total && report.statistics.total.lines) || 1;
332
- var percent = Math.round((lines / totalLines) * 100);
330
+ var totalFileLines = 1;
331
+ try {
332
+ totalFileLines = fs.readFileSync(path.resolve(filePath), 'utf8').split('\n').length || 1;
333
+ } catch (_) {
334
+ totalFileLines = dup.firstFile.lines || dup.secondFile.lines || lines || 1;
335
+ }
336
+ var percent = Math.round((lines / totalFileLines) * 100);
337
+
338
+ // Skip trivial matches (import boilerplate, small overlaps)
339
+ if (lines < 10 && percent < 10) {
340
+ continue;
341
+ }
342
+
333
343
  return { matchedFile: matchedFile, lines: lines, percent: percent };
334
344
  }
335
345
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.5.1",
3
+ "version": "6.5.2",
4
4
  "description": "Universal AI coding rules. Because your AI writes code like it gets paid by the line.",
5
5
  "contextFileName": "rules/agentic-senior-core.md",
6
6
  "rules": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "mode": "advisory",
3
- "minTokens": 50,
3
+ "minTokens": 30,
4
4
  "scanRoot": null,
5
5
  "ignoreDirs": [
6
6
  "tests", "test", "__tests__", "migrations", "generated", "node_modules",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.5.1",
3
+ "version": "6.5.2",
4
4
  "description": "Universal AI coding rules. Write code like a staff engineer.",
5
5
  "author": "fatidaprilian",
6
6
  "license": "MIT",
@@ -27,6 +27,51 @@ const SOURCE_EXTENSIONS = new Set([${extensionsList}]);
27
27
 
28
28
  const JSCPD_TIMEOUT_MS = 10000;
29
29
 
30
+ // Framework-conventional filenames that MUST be identical across directories by design.
31
+ const FRAMEWORK_CONVENTIONAL_BASENAMES = new Set([
32
+ 'page.tsx', 'page.jsx', 'page.ts', 'page.js',
33
+ 'layout.tsx', 'layout.jsx', 'layout.ts', 'layout.js',
34
+ 'loading.tsx', 'loading.jsx', 'loading.ts', 'loading.js',
35
+ 'error.tsx', 'error.jsx', 'error.ts', 'error.js',
36
+ 'not-found.tsx', 'not-found.jsx', 'not-found.ts', 'not-found.js',
37
+ 'template.tsx', 'template.jsx', 'template.ts', 'template.js',
38
+ 'route.tsx', 'route.ts', 'route.js',
39
+ 'default.tsx', 'default.jsx', 'default.ts', 'default.js',
40
+ 'root.tsx', 'root.jsx', 'root.ts', 'root.js',
41
+ 'entry.server.tsx', 'entry.server.ts', 'entry.client.tsx', 'entry.client.ts',
42
+ '_layout.tsx', '_layout.jsx', '_layout.ts', '_layout.js',
43
+ 'index.vue', 'app.vue',
44
+ '+page.svelte', '+layout.svelte', '+page.server.ts', '+page.server.js',
45
+ '+error.svelte', '+layout.server.ts', '+layout.server.js',
46
+ 'index.ts', 'index.js', 'index.tsx', 'index.jsx',
47
+ 'types.ts', 'types.d.ts',
48
+ 'tailwind.config.ts', 'tailwind.config.js', 'tailwind.config.mjs',
49
+ 'postcss.config.js', 'postcss.config.mjs', 'postcss.config.cjs',
50
+ 'next.config.ts', 'next.config.js', 'next.config.mjs',
51
+ 'vite.config.ts', 'vite.config.js', 'vite.config.mjs',
52
+ 'tsconfig.json', 'jest.config.ts', 'jest.config.js', 'vitest.config.ts',
53
+ ]);
54
+
55
+ const FRAMEWORK_CONVENTIONAL_SUFFIXES = [
56
+ '.component.ts', '.component.js', '.module.ts', '.service.ts',
57
+ '.pipe.ts', '.directive.ts', '.guard.ts', '.resolver.ts',
58
+ '.stories.tsx', '.stories.jsx', '.stories.ts', '.stories.js',
59
+ '.spec.ts', '.spec.tsx', '.spec.js', '.spec.jsx',
60
+ '.test.ts', '.test.tsx', '.test.js', '.test.jsx',
61
+ ];
62
+
63
+ function isFrameworkConventional(basename) {
64
+ if (FRAMEWORK_CONVENTIONAL_BASENAMES.has(basename)) return true;
65
+ for (var i = 0; i < FRAMEWORK_CONVENTIONAL_SUFFIXES.length; i++) {
66
+ if (basename.endsWith(FRAMEWORK_CONVENTIONAL_SUFFIXES[i])) return true;
67
+ }
68
+ return false;
69
+ }
70
+
71
+ // Minimum thresholds — skip trivial matches (import boilerplate, etc.)
72
+ const MIN_LINES_THRESHOLD = 10;
73
+ const MIN_PERCENT_THRESHOLD = 10;
74
+
30
75
  function getStagedFiles(cwd) {
31
76
  try {
32
77
  const output = execSync('git diff --cached --name-only --diff-filter=ACM', { encoding: 'utf8', cwd });
@@ -48,7 +93,14 @@ function loadDedupConfig(cwd) {
48
93
  }
49
94
  } catch (_) {}
50
95
  }
51
- return { minTokens: 30, ignoreDirs: ['tests', 'migrations', 'generated', 'node_modules'] };
96
+ return {
97
+ minTokens: 30,
98
+ ignoreDirs: [
99
+ 'tests', 'test', '__tests__', 'migrations', 'generated', 'node_modules',
100
+ 'dist', 'build', '.next', '.nuxt', '.expo', 'coverage', '.storybook',
101
+ 'prisma/migrations', 'android', 'ios',
102
+ ],
103
+ };
52
104
  }
53
105
 
54
106
  function runEslintAutoFix(cwd, stagedFiles) {
@@ -126,10 +178,34 @@ function checkForDuplicates(report, stagedSourceFiles, cwd) {
126
178
 
127
179
  if (isFirstStaged || isSecondStaged) {
128
180
  const stagedFile = isFirstStaged ? normalizedStagedMap.get(firstName) : normalizedStagedMap.get(secondName);
129
- const matchedFile = isFirstStaged ? path.basename(dup.secondFile.name) : path.basename(dup.firstFile.name);
181
+ const otherRaw = isFirstStaged ? dup.secondFile.name : dup.firstFile.name;
182
+ const otherBasename = path.basename(otherRaw);
183
+ const stagedBasename = path.basename(stagedFile);
184
+
185
+ // Skip framework-conventional filenames in different directories
186
+ if (isFrameworkConventional(stagedBasename)
187
+ && isFrameworkConventional(otherBasename)
188
+ && path.dirname(path.resolve(cwd, stagedFile)) !== path.dirname(path.resolve(cwd, otherRaw))) {
189
+ continue;
190
+ }
191
+
130
192
  const lines = dup.lines || 0;
131
- const totalLines = (report.statistics && report.statistics.total && report.statistics.total.lines) || 1;
132
- const percent = Math.round((lines / totalLines) * 100);
193
+ // Calculate percent relative to the actual staged file size
194
+ let totalFileLines = 1;
195
+ try {
196
+ totalFileLines = fs.readFileSync(path.resolve(cwd, stagedFile), 'utf8').split('\\n').length || 1;
197
+ } catch (_) {
198
+ totalFileLines = dup.firstFile.lines || dup.secondFile.lines || lines || 1;
199
+ }
200
+ const percent = Math.round((lines / totalFileLines) * 100);
201
+
202
+ // Skip trivial matches (import boilerplate, small overlaps)
203
+ if (lines < MIN_LINES_THRESHOLD && percent < MIN_PERCENT_THRESHOLD) {
204
+ continue;
205
+ }
206
+
207
+ // Show relative path for actionable messages
208
+ const matchedFile = path.relative(cwd, path.resolve(cwd, otherRaw)).replace(/\\\\/g, '/');
133
209
  return { stagedFile, matchedFile, lines, percent };
134
210
  }
135
211
  }
@@ -185,8 +261,11 @@ function runPreCommitGate() {
185
261
  })));
186
262
 
187
263
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'asc-git-dedup-'));
188
- const ignoreFlags = (config.ignoreDirs || ['tests', 'migrations', 'generated', 'node_modules'])
189
- .map(d => \`--ignore "\${d}"\`).join(' ');
264
+ const ignoreFlags = (config.ignoreDirs || [
265
+ 'tests', 'test', '__tests__', 'migrations', 'generated', 'node_modules',
266
+ 'dist', 'build', '.next', '.nuxt', '.expo', 'coverage', '.storybook',
267
+ 'prisma/migrations', 'android', 'ios',
268
+ ]).map(d => \`--ignore "\${d}"\`).join(' ');
190
269
  const minTokens = config.minTokens || 30;
191
270
 
192
271
  let finding = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuenn3123/agentic-senior-core",
3
- "version": "6.5.1",
3
+ "version": "6.5.2",
4
4
  "type": "module",
5
5
  "description": "Agentic Senior Core: Universal AI coding rules and workflows. Write code like a staff engineer, not a junior.",
6
6
  "bin": {
package/plugin.yaml CHANGED
@@ -1,5 +1,5 @@
1
1
  name: agentic-senior-core
2
- version: 6.5.1
2
+ version: 6.5.2
3
3
  description: Universal AI coding rules. Write code like a staff engineer.
4
4
  author: fatidaprilian
5
5
  provides_hooks: