@ryuenn3123/agentic-senior-core 6.5.2 → 6.6.0

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.2",
3
+ "version": "6.5.3",
4
4
  "description": "Universal AI coding rules. Write code like a staff engineer.",
5
5
  "skills": "./skills/",
6
6
  "hooks": "./hooks/hooks.json",
@@ -116,6 +116,8 @@ process.stdin.on('data', chunk => {
116
116
  const config = loadDedupConfig();
117
117
  if (!isQualifyingEdit(toolName, toolInput, config)) { process.exit(0); return; }
118
118
 
119
+ if (hasValidInlineIgnore(filePath)) { process.exit(0); return; }
120
+
119
121
  const scanDir = resolveScanDir(filePath, config);
120
122
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'asc-dedup-'));
121
123
 
@@ -127,7 +129,7 @@ process.stdin.on('data', chunk => {
127
129
  const report = runJscpdScan(scanCmd, tmpDir);
128
130
  if (!report) { cleanup(tmpDir); process.exit(0); return; }
129
131
 
130
- const finding = checkForDuplicates(report, filePath);
132
+ const finding = checkForDuplicates(report, filePath, config);
131
133
  if (!finding) { cleanup(tmpDir); process.exit(0); return; }
132
134
 
133
135
  const nudge = '[ASC Dedup] ' + path.basename(filePath) + ' looks similar to '
@@ -298,10 +300,44 @@ function loadReport(tmpDir) {
298
300
  }
299
301
  }
300
302
 
301
- function checkForDuplicates(report, filePath) {
303
+ function hasValidInlineIgnore(filePath) {
304
+ try {
305
+ if (!fs.existsSync(filePath)) return false;
306
+ var content = fs.readFileSync(filePath, 'utf8');
307
+ if (content.indexOf('jscpd:ignore-start') !== -1) return true;
308
+ var match = content.match(/asc-dedup:ignore\s*--\s*(.+)/i);
309
+ return Boolean(match && match[1] && match[1].trim().length > 0);
310
+ } catch (_) {
311
+ return false;
312
+ }
313
+ }
314
+
315
+ function isAllowedDuplicate(fileA, fileB, allowedDuplicates) {
316
+ if (!Array.isArray(allowedDuplicates) || allowedDuplicates.length === 0) return false;
317
+ var normA = path.resolve(fileA).replace(/\\/g, '/').toLowerCase();
318
+ var normB = path.resolve(fileB).replace(/\\/g, '/').toLowerCase();
319
+ var baseA = path.basename(fileA).toLowerCase();
320
+ var baseB = path.basename(fileB).toLowerCase();
321
+
322
+ for (var i = 0; i < allowedDuplicates.length; i++) {
323
+ var pair = allowedDuplicates[i];
324
+ if (!Array.isArray(pair) || pair.length < 2) continue;
325
+ var p0Norm = path.resolve(pair[0]).replace(/\\/g, '/').toLowerCase();
326
+ var p1Norm = path.resolve(pair[1]).replace(/\\/g, '/').toLowerCase();
327
+ var p0Base = path.basename(pair[0]).toLowerCase();
328
+ var p1Base = path.basename(pair[1]).toLowerCase();
329
+
330
+ if ((normA === p0Norm && normB === p1Norm) || (normA === p1Norm && normB === p0Norm)) return true;
331
+ if ((baseA === p0Base && baseB === p1Base) || (baseA === p1Base && baseB === p0Base)) return true;
332
+ }
333
+ return false;
334
+ }
335
+
336
+ function checkForDuplicates(report, filePath, config) {
302
337
  var duplicates = report.duplicates || [];
303
338
  if (duplicates.length === 0) return null;
304
339
 
340
+ var allowedDuplicates = config && config.allowedDuplicates;
305
341
  var normalizedTarget = path.resolve(filePath).replace(/\\/g, '/').toLowerCase();
306
342
  var targetBasename = path.basename(filePath);
307
343
 
@@ -314,6 +350,10 @@ function checkForDuplicates(report, filePath) {
314
350
  var otherRaw = firstName === normalizedTarget ? dup.secondFile.name : dup.firstFile.name;
315
351
  var otherBasename = path.basename(otherRaw);
316
352
 
353
+ if (isAllowedDuplicate(filePath, otherRaw, allowedDuplicates)) {
354
+ continue;
355
+ }
356
+
317
357
  // Skip framework-conventional filenames in different directories — identical names
318
358
  // are mandated by the framework (e.g. Next.js page.tsx, Angular *.component.ts),
319
359
  // not copy-paste duplication.
@@ -0,0 +1,42 @@
1
+ // Native Kilo / OpenCode Plugin for Agentic Senior Core
2
+ // Hooks into system prompt transform, session compaction, and shell environment.
3
+
4
+ const ASC_GUARDRAILS = `
5
+ # Agentic Senior Core Rules
6
+ - Write code like a staff engineer. Efficient, safe, maintainable.
7
+ - Before writing code: 1) Need built? 2) Codebase reuse? 3) Stdlib? 4) Dependency? 5) One function? 6) Minimum code.
8
+ - Parameterize all queries. Validate inputs at trust boundaries. Never commit secrets.
9
+ - Early returns over deep nesting. Delete code that carries no value.
10
+ `;
11
+
12
+ const server = async ({ project, directory, worktree }) => {
13
+ return {
14
+ "experimental.chat.system.transform": async (input, output) => {
15
+ if (Array.isArray(output.system)) {
16
+ const hasASC = output.system.some(item => typeof item === 'string' && item.includes('Agentic Senior Core'));
17
+ if (!hasASC) {
18
+ output.system.push(ASC_GUARDRAILS.trim());
19
+ }
20
+ }
21
+ },
22
+ "experimental.session.compacting": async (input, output) => {
23
+ if (Array.isArray(output.context)) {
24
+ output.context.push(
25
+ "## Agentic Senior Core (Persisted Context)\n" +
26
+ "- Maintain strict security guardrails and non-breaking API contracts.\n" +
27
+ "- Prefer clean minimal logic over premature abstractions."
28
+ );
29
+ }
30
+ },
31
+ "shell.env": async (input, output) => {
32
+ if (output.env) {
33
+ output.env.ASC_ENABLED = "1";
34
+ }
35
+ },
36
+ };
37
+ };
38
+
39
+ export default {
40
+ id: "agentic-senior-core",
41
+ server,
42
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.5.2",
3
+ "version": "6.6.0",
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": [
@@ -2,6 +2,7 @@
2
2
  "mode": "advisory",
3
3
  "minTokens": 30,
4
4
  "scanRoot": null,
5
+ "allowedDuplicates": [],
5
6
  "ignoreDirs": [
6
7
  "tests", "test", "__tests__", "migrations", "generated", "node_modules",
7
8
  "dist", "build", ".next", ".nuxt", ".expo", "coverage", ".storybook",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.5.2",
3
+ "version": "6.6.0",
4
4
  "description": "Universal AI coding rules. Write code like a staff engineer.",
5
5
  "author": "fatidaprilian",
6
6
  "license": "MIT",
@@ -60,8 +60,11 @@ const ADAPTER_TARGETS = {
60
60
  },
61
61
  kilocode: {
62
62
  label: 'Kilo Code',
63
- targetPath: '.kilocode/rules/agentic-senior-core.md',
63
+ targetPath: '.kilo/rules/agentic-senior-core.md',
64
+ legacyTargetPath: '.kilocode/rules/agentic-senior-core.md',
65
+ pluginTargetPath: '.kilo/plugin/agentic-senior-core.js',
64
66
  sourcePath: '.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md',
67
+ pluginSourcePath: '.agents/plugins/agentic-senior-core/kilo-plugin/agentic-senior-core.js',
65
68
  },
66
69
  roo: {
67
70
  label: 'Roo Code',
@@ -117,7 +120,23 @@ async function generateAdapter(targetDirectory, adapterKey) {
117
120
  }
118
121
 
119
122
  await fs.writeFile(targetPath, finalContent, 'utf8');
120
-
123
+
124
+ if (adapterKey === 'kilocode') {
125
+ if (adapter.pluginSourcePath && adapter.pluginTargetPath) {
126
+ const pluginSource = path.join(REPOSITORY_ROOT, adapter.pluginSourcePath);
127
+ const pluginTarget = path.join(targetDirectory, adapter.pluginTargetPath);
128
+ if (await pathExists(pluginSource)) {
129
+ await fs.mkdir(path.dirname(pluginTarget), { recursive: true });
130
+ await fs.copyFile(pluginSource, pluginTarget);
131
+ }
132
+ }
133
+ if (adapter.legacyTargetPath) {
134
+ const legacyTarget = path.join(targetDirectory, adapter.legacyTargetPath);
135
+ await fs.mkdir(path.dirname(legacyTarget), { recursive: true });
136
+ await fs.writeFile(legacyTarget, finalContent, 'utf8');
137
+ }
138
+ }
139
+
121
140
  console.log(` ${adapter.label}: ${adapter.targetPath} ... OK`);
122
141
  return true;
123
142
  }
@@ -68,9 +68,42 @@ function isFrameworkConventional(basename) {
68
68
  return false;
69
69
  }
70
70
 
71
- // Minimum thresholds skip trivial matches (import boilerplate, etc.)
72
- const MIN_LINES_THRESHOLD = 10;
73
- const MIN_PERCENT_THRESHOLD = 10;
71
+ // Minimum blocking thresholds for Git pre-commit gate
72
+ const MIN_BLOCKING_LINES = 15;
73
+ const MIN_BLOCKING_PERCENT = 25;
74
+
75
+ function hasValidInlineIgnore(filePath) {
76
+ try {
77
+ if (!fs.existsSync(filePath)) return false;
78
+ const content = fs.readFileSync(filePath, 'utf8');
79
+ if (content.includes('jscpd:ignore-start')) return true;
80
+ const match = content.match(/asc-dedup:ignore\\s*--\\s*(.+)/i);
81
+ return Boolean(match && match[1] && match[1].trim().length > 0);
82
+ } catch (_) {
83
+ return false;
84
+ }
85
+ }
86
+
87
+ function isAllowedDuplicate(fileA, fileB, allowedDuplicates) {
88
+ if (!Array.isArray(allowedDuplicates) || allowedDuplicates.length === 0) return false;
89
+ const normA = path.resolve(fileA).replace(/\\\\/g, '/').toLowerCase();
90
+ const normB = path.resolve(fileB).replace(/\\\\/g, '/').toLowerCase();
91
+ const baseA = path.basename(fileA).toLowerCase();
92
+ const baseB = path.basename(fileB).toLowerCase();
93
+
94
+ for (let i = 0; i < allowedDuplicates.length; i++) {
95
+ const pair = allowedDuplicates[i];
96
+ if (!Array.isArray(pair) || pair.length < 2) continue;
97
+ const p0Norm = path.resolve(pair[0]).replace(/\\\\/g, '/').toLowerCase();
98
+ const p1Norm = path.resolve(pair[1]).replace(/\\\\/g, '/').toLowerCase();
99
+ const p0Base = path.basename(pair[0]).toLowerCase();
100
+ const p1Base = path.basename(pair[1]).toLowerCase();
101
+
102
+ if ((normA === p0Norm && normB === p1Norm) || (normA === p1Norm && normB === p0Norm)) return true;
103
+ if ((baseA === p0Base && baseB === p1Base) || (baseA === p1Base && baseB === p0Base)) return true;
104
+ }
105
+ return false;
106
+ }
74
107
 
75
108
  function getStagedFiles(cwd) {
76
109
  try {
@@ -158,10 +191,11 @@ function loadReport(tmpDir) {
158
191
  }
159
192
  }
160
193
 
161
- function checkForDuplicates(report, stagedSourceFiles, cwd) {
194
+ function checkForDuplicates(report, stagedSourceFiles, cwd, config) {
162
195
  const duplicates = report.duplicates || [];
163
196
  if (duplicates.length === 0) return null;
164
197
 
198
+ const allowedDuplicates = config && config.allowedDuplicates;
165
199
  const normalizedStagedMap = new Map();
166
200
  for (const f of stagedSourceFiles) {
167
201
  const norm = path.resolve(cwd, f).replace(/\\\\/g, '/').toLowerCase();
@@ -179,13 +213,24 @@ function checkForDuplicates(report, stagedSourceFiles, cwd) {
179
213
  if (isFirstStaged || isSecondStaged) {
180
214
  const stagedFile = isFirstStaged ? normalizedStagedMap.get(firstName) : normalizedStagedMap.get(secondName);
181
215
  const otherRaw = isFirstStaged ? dup.secondFile.name : dup.firstFile.name;
216
+ const stagedPath = path.resolve(cwd, stagedFile);
217
+ const otherPath = path.resolve(cwd, otherRaw);
218
+
219
+ if (hasValidInlineIgnore(stagedPath) || hasValidInlineIgnore(otherPath)) {
220
+ continue;
221
+ }
222
+
223
+ if (isAllowedDuplicate(stagedPath, otherPath, allowedDuplicates)) {
224
+ continue;
225
+ }
226
+
182
227
  const otherBasename = path.basename(otherRaw);
183
228
  const stagedBasename = path.basename(stagedFile);
184
229
 
185
230
  // Skip framework-conventional filenames in different directories
186
231
  if (isFrameworkConventional(stagedBasename)
187
232
  && isFrameworkConventional(otherBasename)
188
- && path.dirname(path.resolve(cwd, stagedFile)) !== path.dirname(path.resolve(cwd, otherRaw))) {
233
+ && path.dirname(stagedPath) !== path.dirname(otherPath)) {
189
234
  continue;
190
235
  }
191
236
 
@@ -193,19 +238,19 @@ function checkForDuplicates(report, stagedSourceFiles, cwd) {
193
238
  // Calculate percent relative to the actual staged file size
194
239
  let totalFileLines = 1;
195
240
  try {
196
- totalFileLines = fs.readFileSync(path.resolve(cwd, stagedFile), 'utf8').split('\\n').length || 1;
241
+ totalFileLines = fs.readFileSync(stagedPath, 'utf8').split('\\n').length || 1;
197
242
  } catch (_) {
198
243
  totalFileLines = dup.firstFile.lines || dup.secondFile.lines || lines || 1;
199
244
  }
200
245
  const percent = Math.round((lines / totalFileLines) * 100);
201
246
 
202
- // Skip trivial matches (import boilerplate, small overlaps)
203
- if (lines < MIN_LINES_THRESHOLD && percent < MIN_PERCENT_THRESHOLD) {
247
+ // Skip matches below blocking threshold
248
+ if (lines < MIN_BLOCKING_LINES || percent < MIN_BLOCKING_PERCENT) {
204
249
  continue;
205
250
  }
206
251
 
207
252
  // Show relative path for actionable messages
208
- const matchedFile = path.relative(cwd, path.resolve(cwd, otherRaw)).replace(/\\\\/g, '/');
253
+ const matchedFile = path.relative(cwd, otherPath).replace(/\\\\/g, '/');
209
254
  return { stagedFile, matchedFile, lines, percent };
210
255
  }
211
256
  }
@@ -274,7 +319,7 @@ function runPreCommitGate() {
274
319
  const scanCmd = \` "\${scanDir}" --min-tokens \${minTokens} --reporters json --silent --output "\${tmpDir}" \${ignoreFlags}\`;
275
320
  const report = runJscpdScan(scanCmd, cwd, tmpDir);
276
321
  if (report) {
277
- finding = checkForDuplicates(report, currentStagedSource, cwd);
322
+ finding = checkForDuplicates(report, currentStagedSource, cwd, config);
278
323
  if (finding) break;
279
324
  }
280
325
  }
@@ -56,10 +56,13 @@ const GLOBAL_TARGETS = {
56
56
  },
57
57
  kilocode: {
58
58
  label: 'Kilo Code',
59
- kind: 'file',
60
- sourcePath: '.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md',
61
- targetPath: () => path.join(HOME, '.kilocode', 'rules', 'agentic-senior-core.md'),
62
- note: 'Kilo v7+: prefer adding the file path to the instructions array in ~/.config/kilo/kilo.jsonc (auto-updates with npm).',
59
+ kind: 'kilo-global',
60
+ rulesSourcePath: '.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md',
61
+ pluginSourcePath: '.agents/plugins/agentic-senior-core/kilo-plugin/agentic-senior-core.js',
62
+ targetPath: () => path.join(HOME, '.config', 'kilo', 'rules', 'agentic-senior-core.md'),
63
+ legacyTargetPath: () => path.join(HOME, '.kilocode', 'rules', 'agentic-senior-core.md'),
64
+ pluginTargetPath: () => path.join(HOME, '.config', 'kilo', 'plugin', 'agentic-senior-core.js'),
65
+ note: 'Installs global plugin to ~/.config/kilo/plugin/ and rules to ~/.config/kilo/rules/.',
63
66
  },
64
67
  kiro: {
65
68
  label: 'Kiro',
@@ -201,6 +204,46 @@ async function installCodexGlobal(target) {
201
204
  return true;
202
205
  }
203
206
 
207
+ async function installKiloGlobal(target) {
208
+ const rulesSource = path.join(REPOSITORY_ROOT, target.rulesSourcePath);
209
+ const pluginSource = path.join(REPOSITORY_ROOT, target.pluginSourcePath);
210
+ const rulesTarget = target.targetPath();
211
+ const pluginTarget = target.pluginTargetPath();
212
+ const legacyRulesTarget = target.legacyTargetPath();
213
+
214
+ if (!(await pathExists(rulesSource))) {
215
+ console.error(` ${target.label}: rules source not found ... FAIL`);
216
+ return false;
217
+ }
218
+
219
+ await fs.mkdir(path.dirname(rulesTarget), { recursive: true });
220
+ await fs.copyFile(rulesSource, rulesTarget);
221
+
222
+ await fs.mkdir(path.dirname(legacyRulesTarget), { recursive: true });
223
+ await fs.copyFile(rulesSource, legacyRulesTarget);
224
+
225
+ if (await pathExists(pluginSource)) {
226
+ await fs.mkdir(path.dirname(pluginTarget), { recursive: true });
227
+ await fs.copyFile(pluginSource, pluginTarget);
228
+ }
229
+
230
+ if (process.env.APPDATA) {
231
+ const appDataKiloPlugin = path.join(process.env.APPDATA, 'kilo', 'plugin', 'agentic-senior-core.js');
232
+ const appDataKiloRules = path.join(process.env.APPDATA, 'kilo', 'rules', 'agentic-senior-core.md');
233
+ try {
234
+ if (await pathExists(pluginSource)) {
235
+ await fs.mkdir(path.dirname(appDataKiloPlugin), { recursive: true });
236
+ await fs.copyFile(pluginSource, appDataKiloPlugin);
237
+ }
238
+ await fs.mkdir(path.dirname(appDataKiloRules), { recursive: true });
239
+ await fs.copyFile(rulesSource, appDataKiloRules);
240
+ } catch (_) {}
241
+ }
242
+
243
+ console.log(` ${target.label}: ${rulesTarget} & ${pluginTarget} ... OK`);
244
+ return true;
245
+ }
246
+
204
247
  async function installAntigravityIde(target) {
205
248
  const pluginSource = path.join(REPOSITORY_ROOT, target.pluginSourcePath);
206
249
  const rulesSource = path.join(REPOSITORY_ROOT, target.rulesSourcePath);
@@ -319,6 +362,10 @@ async function installGlobalTarget(targetKey) {
319
362
  return await installCodexGlobal(target);
320
363
  }
321
364
 
365
+ if (target.kind === 'kilo-global') {
366
+ return await installKiloGlobal(target);
367
+ }
368
+
322
369
  const sourcePath = path.join(REPOSITORY_ROOT, target.sourcePath);
323
370
  const targetPath = target.targetPath();
324
371
 
@@ -111,8 +111,13 @@ const IDE_CHECKS = [
111
111
  {
112
112
  name: 'Kilo Code',
113
113
  type: 'adapter',
114
- checkPaths: [path.join(HOME, '.kilocode')],
115
- installHint: 'asc adapter --kilocode',
114
+ checkPaths: [
115
+ path.join(HOME, '.config', 'kilo'),
116
+ path.join(HOME, '.kilo'),
117
+ path.join(HOME, '.kilocode'),
118
+ ...(process.env.APPDATA ? [path.join(process.env.APPDATA, 'kilo')] : []),
119
+ ],
120
+ installHint: 'asc global --kilocode',
116
121
  },
117
122
  {
118
123
  name: 'Roo Code',
@@ -14,7 +14,9 @@ const ADAPTER_FILES = [
14
14
  { label: 'Continue', path: '.continue/rules/agentic-senior-core.md' },
15
15
  { label: 'Zed', path: '.zed/rules/agentic-senior-core.md' },
16
16
  { label: 'Aider', path: 'CONVENTIONS.md' },
17
- { label: 'Kilo Code', path: '.kilocode/rules/agentic-senior-core.md' },
17
+ { label: 'Kilo Code (modern rules)', path: '.kilo/rules/agentic-senior-core.md' },
18
+ { label: 'Kilo Code (modern plugin)', path: '.kilo/plugin/agentic-senior-core.js' },
19
+ { label: 'Kilo Code (legacy rules)', path: '.kilocode/rules/agentic-senior-core.md' },
18
20
  { label: 'Roo Code', path: '.roo/rules/agentic-senior-core.md' },
19
21
  { label: 'OpenHands', path: '.openhands/microagents/agentic-senior-core.md' },
20
22
  { label: 'ASC Compiled Validator', path: '.asc/hooks/pre-commit-validator.cjs' },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuenn3123/agentic-senior-core",
3
- "version": "6.5.2",
3
+ "version": "6.6.0",
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.2
2
+ version: 6.6.0
3
3
  description: Universal AI coding rules. Write code like a staff engineer.
4
4
  author: fatidaprilian
5
5
  provides_hooks: