@ryuenn3123/agentic-senior-core 6.9.0 → 6.11.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.
Files changed (29) hide show
  1. package/.agents/plugins/agentic-senior-core/.codex-plugin/plugin.json +1 -1
  2. package/.agents/plugins/agentic-senior-core/hooks/constants.cjs +6 -7
  3. package/.agents/plugins/agentic-senior-core/hooks/lib/known-duplicates.json +27 -17
  4. package/.agents/plugins/agentic-senior-core/hooks/lib/known-stub-patterns.json +29 -0
  5. package/.agents/plugins/agentic-senior-core/hooks/lib/known-ui-slop-patterns.json +15 -0
  6. package/.agents/plugins/agentic-senior-core/hooks/post-edit-enforce.js +165 -65
  7. package/.agents/plugins/agentic-senior-core/hooks/pre-tool-dependency-gate.js +66 -15
  8. package/.agents/plugins/agentic-senior-core/hooks.json +118 -1
  9. package/.agents/plugins/agentic-senior-core/plugin.json +1 -1
  10. package/.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md +45 -72
  11. package/.agents/plugins/agentic-senior-core/skills/asc/SKILL.md +1 -1
  12. package/.agents/plugins/agentic-senior-core/skills/asc-adapter/SKILL.md +3 -2
  13. package/.agents/plugins/agentic-senior-core/skills/asc-add-feature/SKILL.md +7 -7
  14. package/.agents/plugins/agentic-senior-core/skills/asc-audit/SKILL.md +6 -0
  15. package/.agents/plugins/agentic-senior-core/skills/asc-bootstrap/SKILL.md +4 -1
  16. package/.agents/plugins/agentic-senior-core/skills/asc-debt/SKILL.md +16 -6
  17. package/.agents/plugins/agentic-senior-core/skills/asc-dedup/SKILL.md +2 -1
  18. package/.agents/plugins/agentic-senior-core/skills/asc-fingerprint/SKILL.md +4 -3
  19. package/.agents/plugins/agentic-senior-core/skills/asc-learn/SKILL.md +1 -1
  20. package/.agents/plugins/agentic-senior-core/skills/asc-new-project/SKILL.md +1 -1
  21. package/.agents/plugins/agentic-senior-core/skills/asc-refactor/SKILL.md +8 -9
  22. package/.agents/plugins/agentic-senior-core/skills/asc-reference/SKILL.md +4 -10
  23. package/.agents/plugins/agentic-senior-core/skills/asc-review/SKILL.md +7 -6
  24. package/README.md +2 -1
  25. package/gemini-extension.json +1 -1
  26. package/lib/cli/ascx/runtime.mjs +26 -0
  27. package/lib/cli/commands/git-hook-generator.mjs +28 -0
  28. package/package.json +1 -1
  29. package/plugin.yaml +4 -1
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.9.0",
3
+ "version": "6.11.0",
4
4
  "description": "Universal AI coding rules. Write code like a staff engineer.",
5
5
  "skills": "./skills/",
6
6
  "hooks": "./hooks/hooks.json",
@@ -21,20 +21,19 @@ function loadDedupConfig(cwd = process.cwd()) {
21
21
  if (fs.existsSync(candidates[i])) {
22
22
  return JSON.parse(fs.readFileSync(candidates[i], 'utf8'));
23
23
  }
24
- } catch (_) {}
24
+ } catch (_) { }
25
25
  }
26
26
  return {};
27
27
  }
28
28
 
29
29
  function getThresholds(cwd = process.cwd()) {
30
30
  const config = loadDedupConfig(cwd);
31
+ const num = (value, fallback) => (typeof value === 'number' ? value : fallback);
31
32
  return {
32
- NEW_FILE_LINE_THRESHOLD: typeof config.NEW_FILE_LINE_THRESHOLD === 'number'
33
- ? config.NEW_FILE_LINE_THRESHOLD
34
- : NEW_FILE_LINE_THRESHOLD,
35
- LOC_DELTA_THRESHOLD: typeof config.LOC_DELTA_THRESHOLD === 'number'
36
- ? config.LOC_DELTA_THRESHOLD
37
- : LOC_DELTA_THRESHOLD,
33
+ NEW_FILE_LINE_THRESHOLD: num(config.NEW_FILE_LINE_THRESHOLD, NEW_FILE_LINE_THRESHOLD),
34
+ LOC_DELTA_THRESHOLD: num(config.LOC_DELTA_THRESHOLD, LOC_DELTA_THRESHOLD),
35
+ SESSION_DRIFT_THRESHOLD: num(config.SESSION_DRIFT_THRESHOLD, SESSION_DRIFT_THRESHOLD),
36
+ LADDER_PULSE_INTERVAL: num(config.LADDER_PULSE_INTERVAL, LADDER_PULSE_INTERVAL),
38
37
  };
39
38
  }
40
39
 
@@ -1,9 +1,23 @@
1
1
  {
2
- "description": "Single source of truth for packages that duplicate standard library or native platform features.",
3
- "duplicates": [
2
+ "description": "Packages that duplicate standard library or native platform features. Tiered per 2026 evidence: 'block' packages are always redundant; 'review' packages have legitimate remaining use cases and only trigger a nudge, not a hard block.",
3
+ "block": [
4
+ "left-pad",
5
+ "pad-left",
6
+ "is-odd",
7
+ "is-even",
8
+ "is-number",
9
+ "is-string",
10
+ "path-exists",
11
+ "mkdirp",
12
+ "rimraf",
13
+ "del",
14
+ "node-fetch",
15
+ "superagent",
16
+ "underscore"
17
+ ],
18
+ "review": [
4
19
  "lodash",
5
20
  "lodash-es",
6
- "underscore",
7
21
  "moment",
8
22
  "dayjs",
9
23
  "uuid",
@@ -13,20 +27,16 @@
13
27
  "colorette",
14
28
  "axios",
15
29
  "got",
16
- "node-fetch",
17
- "superagent",
18
- "mkdirp",
19
- "rimraf",
20
- "del",
21
30
  "glob",
22
31
  "globby",
23
- "left-pad",
24
- "pad-left",
25
- "is-odd",
26
- "is-even",
27
- "is-number",
28
- "is-string",
29
- "path-exists",
30
32
  "fs-extra"
31
- ]
32
- }
33
+ ],
34
+ "reviewReasons": {
35
+ "uuid": "crypto.randomUUID() covers v4 (Node 14.17+); uuid package still needed for v5/v7 (sortable DB keys) and validation",
36
+ "axios": "prefer native fetch (stable since Node 18; axios supply-chain compromise March 2026); axios still justified for interceptors and uniform non-2xx error throwing",
37
+ "chalk": "maintainer notes npm dedup means it is likely already in the tree; legitimate when cross-platform color-support detection is needed",
38
+ "lodash": "native Array/Object methods cover most uses; justify per-method need",
39
+ "moment": "prefer native Intl/Temporal or dayjs; moment is legacy and in maintenance mode",
40
+ "glob": "Node 22+ fs.glob covers common cases; justify if complex patterns are needed"
41
+ }
42
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "description": "Regex patterns for temporary stubs, untracked debt, and fake completions across all languages.",
3
+ "patterns": [
4
+ {
5
+ "id": "asc-stub-untracked",
6
+ "regex": "(?:\\/\\/|#|\\/\\*)\\s*ASC-STUB:(?!.*\\b(ASC-DEBT|#\\d+|[A-Z]+-\\d+)\\b)",
7
+ "message": "ASC-STUB marker detected without tracked debt reference. Add tracking ID (e.g. // ASC-STUB: [reason] [ASC-DEBT-001]) and record in debt ledger.",
8
+ "languages": ["universal"]
9
+ },
10
+ {
11
+ "id": "untracked-todo",
12
+ "regex": "(?:\\/\\/|#|\\/\\*)\\s*TODO(?!.*\\b(ASC-DEBT|#\\d+|[A-Z]+-\\d+)\\b)",
13
+ "message": "TODO without tracked debt or issue reference. Track via /asc-debt or resolve before completing task.",
14
+ "languages": ["universal"]
15
+ },
16
+ {
17
+ "id": "fake-success-mock",
18
+ "regex": "(?:return\\s*\\{\\s*success:\\s*true\\s*\\}|return\\s*True|return\\s*true|return\\s*nil)\\s*;?\\s*(?:\\/\\/|#).*?(mock|fake|stub|placeholder|todo)",
19
+ "message": "Hardcoded success return near mock/stub marker detected. Never simulate success to mimic real integration.",
20
+ "languages": ["universal"]
21
+ },
22
+ {
23
+ "id": "hardcoded-endpoint-stub",
24
+ "regex": "(?:baseURL|apiUrl|endpoint|api_url)\\s*[:=]\\s*['\"]https?:\\/\\/(?!localhost|127\\.0\\.0\\.1)",
25
+ "message": "Hardcoded external endpoint detected outside config/env. Store endpoints in environment variables.",
26
+ "languages": ["js", "ts", "jsx", "tsx", "py", "go", "rs"]
27
+ }
28
+ ]
29
+ }
@@ -29,6 +29,21 @@
29
29
  "id": "ui-pill-badge-generic",
30
30
  "regex": "rounded-full\\s+px-4\\s+py-1\\s+text-sm\\s+font-medium\\s+bg-(?:blue|purple|indigo)-100\\s+text-(?:blue|purple|indigo)-800",
31
31
  "message": "note: 'generic pill badge' (AI default tag) was flagged, consider continuing with the project's existing badge component instead."
32
+ },
33
+ {
34
+ "id": "ui-gradient-text-cliche",
35
+ "regex": "bg-clip-text\\s+text-transparent|text-transparent\\s+bg-clip-text",
36
+ "message": "note: 'gradient text (bg-clip-text text-transparent)' is an overused AI landing page trope. Prefer solid semantic typography with strong hierarchy."
37
+ },
38
+ {
39
+ "id": "ui-transition-all-layout-cost",
40
+ "regex": "\\btransition-all(?:\\s+duration-[0-9]+)?\\b|transition:\\s*all\\b",
41
+ "message": "note: 'transition-all' triggers layout repaints on all properties. Transition specific GPU-accelerated properties (transform, opacity) instead."
42
+ },
43
+ {
44
+ "id": "ui-stripped-focus-ring",
45
+ "regex": "(?:\\boutline-none\\b|\\bfocus:outline-none\\b)(?!.*(?:focus-visible:ring|focus:ring|focus-visible:outline))",
46
+ "message": "warning: 'outline-none' without a focus-visible ring breaks keyboard navigation (WCAG 2.4.7 violation). Pair with focus-visible:ring-2."
32
47
  }
33
48
  ]
34
49
  }
@@ -6,28 +6,23 @@
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
 
9
- let STDLIB_DUPLICATES = new Set([
10
- 'lodash', 'lodash-es', 'underscore',
11
- 'moment', 'dayjs',
12
- 'uuid', 'nanoid',
13
- 'chalk', 'kleur', 'colorette',
14
- 'axios', 'got', 'node-fetch', 'superagent',
15
- 'mkdirp', 'rimraf', 'del',
16
- 'glob', 'globby',
17
- 'left-pad', 'pad-left',
18
- 'is-odd', 'is-even', 'is-number', 'is-string',
19
- 'path-exists', 'fs-extra',
20
- ]);
9
+ let STDLIB_DUPLICATES = new Set();
10
+ let REVIEW_DUPLICATES = new Set();
21
11
 
22
12
  try {
23
13
  const knownPath = path.join(__dirname, 'lib', 'known-duplicates.json');
24
14
  if (fs.existsSync(knownPath)) {
25
15
  const raw = JSON.parse(fs.readFileSync(knownPath, 'utf8'));
26
- if (Array.isArray(raw.duplicates)) {
27
- STDLIB_DUPLICATES = new Set(raw.duplicates);
16
+ // Tiered structure (v5): 'block' + 'review'. This hook is advisory, so both
17
+ // tiers land here; the pre-tool gate is what distinguishes them.
18
+ if (Array.isArray(raw.block) || Array.isArray(raw.review)) {
19
+ STDLIB_DUPLICATES = new Set(raw.block || []);
20
+ REVIEW_DUPLICATES = new Set(raw.review || []);
21
+ } else if (Array.isArray(raw.duplicates)) {
22
+ REVIEW_DUPLICATES = new Set(raw.duplicates);
28
23
  }
29
24
  }
30
- } catch (_) {}
25
+ } catch (_) { }
31
26
 
32
27
  let SECURITY_PATTERNS = { patterns: [], fileSpecific: {} };
33
28
  try {
@@ -35,7 +30,7 @@ try {
35
30
  if (fs.existsSync(secPath)) {
36
31
  SECURITY_PATTERNS = JSON.parse(fs.readFileSync(secPath, 'utf8'));
37
32
  }
38
- } catch (_) {}
33
+ } catch (_) { }
39
34
 
40
35
  let UI_SLOP_PATTERNS = { patterns: [] };
41
36
  try {
@@ -43,7 +38,15 @@ try {
43
38
  if (fs.existsSync(slopPath)) {
44
39
  UI_SLOP_PATTERNS = JSON.parse(fs.readFileSync(slopPath, 'utf8'));
45
40
  }
46
- } catch (_) {}
41
+ } catch (_) { }
42
+
43
+ let STUB_PATTERNS = { patterns: [] };
44
+ try {
45
+ const stubPath = path.join(__dirname, 'lib', 'known-stub-patterns.json');
46
+ if (fs.existsSync(stubPath)) {
47
+ STUB_PATTERNS = JSON.parse(fs.readFileSync(stubPath, 'utf8'));
48
+ }
49
+ } catch (_) { }
47
50
 
48
51
  const {
49
52
  SOURCE_EXTENSIONS,
@@ -62,7 +65,7 @@ process.stdin.on('data', chunk => {
62
65
  inputBuffer += chunk;
63
66
  try {
64
67
  const data = JSON.parse(inputBuffer);
65
-
68
+
66
69
  if (data.invocationNum !== undefined && data.transcriptPath) {
67
70
  handleAntigravityPostInvocation(data);
68
71
  return;
@@ -70,7 +73,7 @@ process.stdin.on('data', chunk => {
70
73
 
71
74
  const toolName = data.tool_name || '';
72
75
  const toolInput = data.tool_input || {};
73
- processSingleEdit(toolName, toolInput, function(nudge) {
76
+ processSingleEdit(toolName, toolInput, function (nudge) {
74
77
  emitClaude(nudge);
75
78
  });
76
79
  process.exit(0);
@@ -84,7 +87,7 @@ function handleAntigravityPostInvocation(data) {
84
87
  if (!fs.existsSync(data.transcriptPath)) return;
85
88
  const lines = fs.readFileSync(data.transcriptPath, 'utf8').split('\n').filter(Boolean);
86
89
  const findings = [];
87
-
90
+
88
91
  // Session drift check: count source file edits since initialNumSteps
89
92
  let sourceEditsSinceStart = 0;
90
93
  for (let i = 0; i < lines.length; i++) {
@@ -94,7 +97,7 @@ function handleAntigravityPostInvocation(data) {
94
97
  const tc = step.tool_calls[j];
95
98
  let toolName = '';
96
99
  let toolInput = {};
97
-
100
+
98
101
  if (tc.name === 'replace_file_content' || tc.name === 'multi_replace_file_content') {
99
102
  toolName = 'Edit';
100
103
  toolInput = {
@@ -109,29 +112,29 @@ function handleAntigravityPostInvocation(data) {
109
112
  content: tc.args.CodeContent || ''
110
113
  };
111
114
  }
112
-
115
+
113
116
  if (toolName) {
114
117
  const fp = toolInput.file_path || '';
115
118
  const ext = path.extname(fp).slice(1);
116
119
  if (SOURCE_EXTENSIONS.has(ext)) sourceEditsSinceStart++;
117
-
118
- processSingleEdit(toolName, toolInput, function(nudge) {
120
+
121
+ processSingleEdit(toolName, toolInput, function (nudge) {
119
122
  findings.push(nudge);
120
123
  }, true);
121
124
  }
122
125
  }
123
126
  }
124
127
  }
125
-
128
+
126
129
  // Inject drift nudge if 4+ source files modified this invocation
127
130
  if (sourceEditsSinceStart >= SESSION_DRIFT_THRESHOLD) {
128
131
  findings.push('[ASC Session Drift] ' + sourceEditsSinceStart + ' source file edits this invocation. '
129
132
  + 'Re-read the decision ladder before continuing: (1) Does this need to be built? '
130
133
  + '(2) Does the codebase already have this? (3) Stdlib/native? (4) Existing dependency?');
131
134
  }
132
-
135
+
133
136
  if (findings.length > 0) {
134
- const injectSteps = findings.map(function(f) { return { ephemeralMessage: f }; });
137
+ const injectSteps = findings.map(function (f) { return { ephemeralMessage: f }; });
135
138
  process.stdout.write(JSON.stringify({ injectSteps: injectSteps }) + '\n');
136
139
  } else {
137
140
  process.stdout.write(JSON.stringify({}) + '\n');
@@ -173,16 +176,16 @@ function processSingleEdit(toolName, toolInput, emitFn, skipArray) {
173
176
  }
174
177
 
175
178
  checkSecurityPatterns(toolName, toolInput, filePath, findings);
176
-
179
+ checkStubPatterns(toolName, toolInput, filePath, findings);
180
+
177
181
  if (ext === 'html' || ext === 'css' || ext === 'jsx' || ext === 'tsx' || ext === 'vue' || ext === 'svelte') {
178
182
  checkUiSlopPatterns(toolName, toolInput, filePath, findings);
179
183
  }
180
184
 
181
- if (ext === 'js' || ext === 'ts' || ext === 'jsx' || ext === 'tsx' || ext === 'mjs' || ext === 'cjs') {
182
- checkLinter(filePath, findings);
183
- }
185
+ checkUniversalLinter(filePath, ext, findings);
184
186
 
185
187
  checkLivingDocNudge(filePath, findings);
188
+ checkTestTampering(filePath, findings);
186
189
 
187
190
  if (ext !== 'md') {
188
191
  checkWorkflowGate(toolName, filePath, ext, findings);
@@ -207,7 +210,7 @@ function emitClaude(nudge) {
207
210
  output = { additionalContext: nudge };
208
211
  }
209
212
  process.stdout.write(JSON.stringify(output) + '\n');
210
- } catch (_) {}
213
+ } catch (_) { }
211
214
  }
212
215
 
213
216
  function checkDependencyAddition(toolName, toolInput, findings) {
@@ -222,12 +225,20 @@ function checkDependencyAddition(toolName, toolInput, findings) {
222
225
  if (added.length === 0) return;
223
226
 
224
227
  var stdlibDupes = added.filter(function (d) { return STDLIB_DUPLICATES.has(d); });
228
+ var reviewDupes = added.filter(function (d) { return REVIEW_DUPLICATES.has(d); });
225
229
  if (stdlibDupes.length > 0) {
226
230
  findings.push(
227
- 'Dependency ' + stdlibDupes.join(', ') + ' may duplicate stdlib/platform features. '
228
- + 'Ladder step 3: does the standard library cover this?'
231
+ 'Dependency ' + stdlibDupes.join(', ') + ' duplicates stdlib/platform features. '
232
+ + 'Ladder step 3: use the standard library instead.'
229
233
  );
230
- } else {
234
+ }
235
+ if (reviewDupes.length > 0) {
236
+ findings.push(
237
+ 'Dependency ' + reviewDupes.join(', ') + ' may duplicate stdlib/native features (context-dependent). '
238
+ + 'Ladder step 3: confirm the package-specific need, or use the native equivalent.'
239
+ );
240
+ }
241
+ if (stdlibDupes.length === 0 && reviewDupes.length === 0) {
231
242
  findings.push(
232
243
  'New dependency added: ' + added.join(', ') + '. '
233
244
  + 'Ladder step 3-4: stdlib or already-installed alternative?'
@@ -272,7 +283,7 @@ function logPatternCheck(checkType, patternId, isMatch) {
272
283
  try {
273
284
  var result = isMatch ? 'match' : 'no-match';
274
285
  process.stderr.write('[pattern-check] ' + patternId + ': ' + result + '\n');
275
- } catch (_) {}
286
+ } catch (_) { }
276
287
  }
277
288
 
278
289
  function checkSecurityPatterns(toolName, toolInput, filePath, findings) {
@@ -295,7 +306,7 @@ function checkSecurityPatterns(toolName, toolInput, filePath, findings) {
295
306
  if (isMatch) {
296
307
  findings.push('[ASC Security] ' + p.message);
297
308
  }
298
- } catch (_) {}
309
+ } catch (_) { }
299
310
  });
300
311
  }
301
312
 
@@ -311,14 +322,14 @@ function checkSecurityPatterns(toolName, toolInput, filePath, findings) {
311
322
  findings.push('[ASC Security] ' + spec.message);
312
323
  }
313
324
  }
314
- } catch (_) {}
325
+ } catch (_) { }
315
326
  }
316
327
  }
317
328
 
318
329
  function checkUiSlopPatterns(toolName, toolInput, filePath, findings) {
319
330
  var target = toolName === 'Edit' ? (toolInput.new_string || '') : (toolInput.content || '');
320
331
  if (!target) return;
321
-
332
+
322
333
  if (UI_SLOP_PATTERNS.patterns) {
323
334
  UI_SLOP_PATTERNS.patterns.forEach(function (p) {
324
335
  try {
@@ -328,32 +339,100 @@ function checkUiSlopPatterns(toolName, toolInput, filePath, findings) {
328
339
  if (isMatch) {
329
340
  findings.push('[ASC UI Note] ' + p.message);
330
341
  }
331
- } catch (_) {}
342
+ } catch (_) { }
332
343
  });
333
344
  }
334
345
  }
335
346
 
336
- function checkLinter(filePath, findings) {
337
- try {
338
- var cwd = process.cwd();
339
- var hasEslint = fs.existsSync(path.join(cwd, '.eslintrc.json')) ||
340
- fs.existsSync(path.join(cwd, '.eslintrc.js')) ||
341
- fs.existsSync(path.join(cwd, 'eslint.config.js')) ||
342
- (fs.existsSync(path.join(cwd, 'package.json')) && fs.readFileSync(path.join(cwd, 'package.json'), 'utf8').includes('eslintConfig'));
343
-
344
- if (hasEslint) {
345
- var execSync = require('child_process').execSync;
346
- execSync('npx eslint "' + filePath + '" --format json', { cwd: cwd, stdio: 'pipe' });
347
- }
348
- } catch (error) {
349
- if (error.stdout) {
347
+ function checkStubPatterns(toolName, toolInput, filePath, findings) {
348
+ var target = toolName === 'Edit' ? (toolInput.new_string || toolInput.ReplacementContent || '') : (toolInput.content || toolInput.CodeContent || '');
349
+ if (!target) return;
350
+
351
+ var ext = path.extname(filePath).slice(1);
352
+ if (STUB_PATTERNS.patterns) {
353
+ STUB_PATTERNS.patterns.forEach(function (p) {
350
354
  try {
351
- var out = JSON.parse(error.stdout.toString());
352
- if (Array.isArray(out) && out.length > 0 && out[0].messages && out[0].messages.length > 0) {
353
- var firstErr = out[0].messages[0];
354
- findings.push('[ASC Linter] ' + firstErr.message + ' at line ' + firstErr.line + '.');
355
+ var langs = p.languages || [];
356
+ var isUniversal = langs.length === 0 || langs.indexOf('universal') !== -1;
357
+ if (!isUniversal && langs.indexOf(ext) === -1) return;
358
+
359
+ var regex = new RegExp(p.regex, 'im');
360
+ var isMatch = regex.test(target);
361
+ logPatternCheck('stub', p.id || 'stub-pattern', isMatch);
362
+ if (isMatch) {
363
+ findings.push('[ASC Stub/Fake-Done Alert] ' + p.message);
364
+ }
365
+ } catch (_) { }
366
+ });
367
+ }
368
+ }
369
+
370
+ function checkUniversalLinter(filePath, ext, findings) {
371
+ var execSync = require('child_process').execSync;
372
+ var cwd = process.cwd();
373
+
374
+ if (ext === 'js' || ext === 'ts' || ext === 'jsx' || ext === 'tsx' || ext === 'mjs' || ext === 'cjs') {
375
+ try {
376
+ var hasBiome = fs.existsSync(path.join(cwd, 'biome.json')) || fs.existsSync(path.join(cwd, 'biome.jsonc'));
377
+ if (hasBiome) {
378
+ execSync('npx @biomejs/biome lint "' + filePath + '"', { cwd: cwd, stdio: 'pipe', timeout: 3000 });
379
+ return;
380
+ }
381
+ var hasEslint = fs.existsSync(path.join(cwd, '.eslintrc.json')) ||
382
+ fs.existsSync(path.join(cwd, '.eslintrc.js')) ||
383
+ fs.existsSync(path.join(cwd, 'eslint.config.js')) ||
384
+ fs.existsSync(path.join(cwd, 'eslint.config.mjs')) ||
385
+ (fs.existsSync(path.join(cwd, 'package.json')) && fs.readFileSync(path.join(cwd, 'package.json'), 'utf8').includes('eslintConfig'));
386
+
387
+ if (hasEslint) {
388
+ execSync('npx eslint "' + filePath + '" --format json', { cwd: cwd, stdio: 'pipe', timeout: 4000 });
389
+ }
390
+ } catch (error) {
391
+ if (error.stdout) {
392
+ try {
393
+ var out = JSON.parse(error.stdout.toString());
394
+ if (Array.isArray(out) && out.length > 0 && out[0].messages && out[0].messages.length > 0) {
395
+ var firstErr = out[0].messages[0];
396
+ findings.push('[ASC Linter] ' + firstErr.message + ' at line ' + firstErr.line + '.');
397
+ }
398
+ } catch (_) {
399
+ var msg = error.stdout.toString().split('\n')[0];
400
+ if (msg) findings.push('[ASC Linter] ' + msg);
355
401
  }
356
- } catch (_) {}
402
+ }
403
+ }
404
+ } else if (ext === 'py') {
405
+ try {
406
+ if (fs.existsSync(path.join(cwd, 'pyproject.toml')) || fs.existsSync(path.join(cwd, 'ruff.toml'))) {
407
+ execSync('ruff check --quiet "' + filePath + '"', { cwd: cwd, stdio: 'pipe', timeout: 3000 });
408
+ }
409
+ } catch (error) {
410
+ if (error.stdout) {
411
+ var first = error.stdout.toString().split('\n')[0];
412
+ if (first) findings.push('[ASC Python Linter] ' + first);
413
+ }
414
+ }
415
+ } else if (ext === 'go') {
416
+ try {
417
+ if (fs.existsSync(path.join(cwd, 'go.mod'))) {
418
+ execSync('go vet "' + filePath + '"', { cwd: cwd, stdio: 'pipe', timeout: 4000 });
419
+ }
420
+ } catch (error) {
421
+ if (error.stderr) {
422
+ var first = error.stderr.toString().split('\n')[0];
423
+ if (first) findings.push('[ASC Go Vet] ' + first);
424
+ }
425
+ }
426
+ } else if (ext === 'rs') {
427
+ try {
428
+ if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
429
+ execSync('cargo clippy --quiet --message-format=short', { cwd: cwd, stdio: 'pipe', timeout: 5000 });
430
+ }
431
+ } catch (error) {
432
+ if (error.stderr) {
433
+ var first = error.stderr.toString().split('\n')[0];
434
+ if (first) findings.push('[ASC Clippy] ' + first);
435
+ }
357
436
  }
358
437
  }
359
438
  }
@@ -368,15 +447,36 @@ function checkLivingDocNudge(filePath, findings) {
368
447
  }
369
448
  }
370
449
 
450
+ function checkTestTampering(filePath, findings) {
451
+ var isTest = /(?:\.test\.|\.spec\.|_test\.|__tests__[\/\\]|tests?[\/\\]).*\.(?:js|ts|jsx|tsx|py|go|rs|rb)$/i.test(filePath);
452
+ if (!isTest) return;
453
+
454
+ try {
455
+ var pathUtil = require('./path-util.cjs');
456
+ var localGate = path.join(process.cwd(), 'workflow-gate.json');
457
+ var gatePath = fs.existsSync(localGate) ? localGate : pathUtil.getWorkflowGatePath(process.cwd());
458
+ if (!fs.existsSync(gatePath)) return;
459
+
460
+ var gate = JSON.parse(fs.readFileSync(gatePath, 'utf8'));
461
+ if (gate.phase === 'implement' || gate.phase === 'bugfix') {
462
+ findings.push(
463
+ '[ASC Test Integrity Alert] Test file modified (' + path.basename(filePath) + ') during ' + gate.phase + ' phase. '
464
+ + 'Ensure assertions were NOT weakened to force a pass; fix the underlying business logic instead.'
465
+ );
466
+ }
467
+ } catch (_) { }
468
+ }
469
+
371
470
  function checkWorkflowGate(toolName, filePath, ext, findings) {
372
471
  try {
373
472
  var pathUtil = require('./path-util.cjs');
374
- var gatePath = pathUtil.getWorkflowGatePath(process.cwd());
473
+ var localGate = path.join(process.cwd(), 'workflow-gate.json');
474
+ var gatePath = fs.existsSync(localGate) ? localGate : pathUtil.getWorkflowGatePath(process.cwd());
375
475
  if (!fs.existsSync(gatePath)) return;
376
-
476
+
377
477
  var gateStr = fs.readFileSync(gatePath, 'utf8');
378
478
  var gate = JSON.parse(gateStr);
379
-
479
+
380
480
  if (gate.updatedAt) {
381
481
  var ageHours = (Date.now() - new Date(gate.updatedAt).getTime()) / (1000 * 60 * 60);
382
482
  if (ageHours > 4) {
@@ -385,7 +485,7 @@ function checkWorkflowGate(toolName, filePath, ext, findings) {
385
485
  return;
386
486
  }
387
487
  }
388
-
488
+
389
489
  if (gate.phase === 'research' || gate.phase === 'plan') {
390
490
  findings.push(
391
491
  'Workflow gate bypass: ' + gate.workflow + ' is in ' + gate.phase + ' phase but source code was edited. '
@@ -403,7 +503,7 @@ function validateDocSpecs(workflow, findings) {
403
503
  try {
404
504
  var cwd = process.cwd();
405
505
  var docsDir = path.join(cwd, 'docs');
406
-
506
+
407
507
  // Anti-typo check for common doc typos (e.g. Architectyre.md)
408
508
  var searchDirs = [cwd, docsDir];
409
509
  var typoFound = false;
@@ -438,7 +538,7 @@ function validateDocSpecs(workflow, findings) {
438
538
  );
439
539
  }
440
540
  }
441
- } catch (_) {}
541
+ } catch (_) { }
442
542
  }
443
543
 
444
544