@ryuenn3123/agentic-senior-core 6.10.0 → 6.11.1

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 (24) hide show
  1. package/.agents/plugins/agentic-senior-core/.codex-plugin/plugin.json +1 -1
  2. package/.agents/plugins/agentic-senior-core/hooks/lib/known-stub-patterns.json +29 -0
  3. package/.agents/plugins/agentic-senior-core/hooks/lib/known-ui-slop-patterns.json +15 -0
  4. package/.agents/plugins/agentic-senior-core/hooks/post-edit-enforce.js +201 -32
  5. package/.agents/plugins/agentic-senior-core/hooks.json +23 -22
  6. package/.agents/plugins/agentic-senior-core/plugin.json +1 -1
  7. package/.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md +5 -1
  8. package/.agents/plugins/agentic-senior-core/skills/asc-add-feature/SKILL.md +6 -6
  9. package/.agents/plugins/agentic-senior-core/skills/asc-audit/SKILL.md +6 -0
  10. package/.agents/plugins/agentic-senior-core/skills/asc-bootstrap/SKILL.md +3 -0
  11. package/.agents/plugins/agentic-senior-core/skills/asc-debt/SKILL.md +15 -5
  12. package/.agents/plugins/agentic-senior-core/skills/asc-refactor/SKILL.md +7 -8
  13. package/.agents/plugins/agentic-senior-core/skills/asc-reference/SKILL.md +4 -10
  14. package/.agents/plugins/agentic-senior-core/skills/asc-review/SKILL.md +6 -5
  15. package/bin/agentic-senior-core.js +0 -8
  16. package/gemini-extension.json +1 -1
  17. package/lib/cli/commands/git-hook-generator.mjs +40 -2
  18. package/package.json +1 -3
  19. package/plugin.yaml +1 -1
  20. package/lib/cli/commands/mcp.mjs +0 -3
  21. package/scripts/mcp-server/constants.mjs +0 -57
  22. package/scripts/mcp-server/tool-registry.mjs +0 -204
  23. package/scripts/mcp-server/tools.mjs +0 -592
  24. package/scripts/mcp-server.mjs +0 -202
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.10.0",
3
+ "version": "6.11.1",
4
4
  "description": "Universal AI coding rules. Write code like a staff engineer.",
5
5
  "skills": "./skills/",
6
6
  "hooks": "./hooks/hooks.json",
@@ -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
  }
@@ -40,6 +40,14 @@ try {
40
40
  }
41
41
  } catch (_) { }
42
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 (_) { }
50
+
43
51
  const {
44
52
  SOURCE_EXTENSIONS,
45
53
  LOC_DELTA_THRESHOLD,
@@ -49,16 +57,50 @@ const {
49
57
  } = require('./constants.cjs');
50
58
 
51
59
  // Module-level counter for Claude Code path (resets per process spawn)
52
- let sourceEditCount = 0;
60
+ function logHook(event, msg) {
61
+ try {
62
+ const os = require('os');
63
+ const logDir = path.join(os.homedir(), '.asc');
64
+ if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
65
+ const logFile = path.join(logDir, 'hooks.log');
66
+ try {
67
+ if (fs.existsSync(logFile)) {
68
+ const stats = fs.statSync(logFile);
69
+ if (stats.size > 100 * 1024) {
70
+ const content = fs.readFileSync(logFile, 'utf8');
71
+ const trimmed = content.slice(-50 * 1024);
72
+ const firstNewline = trimmed.indexOf('\n');
73
+ fs.writeFileSync(logFile, firstNewline >= 0 ? trimmed.slice(firstNewline + 1) : trimmed, 'utf8');
74
+ }
75
+ }
76
+ } catch (_) {}
77
+ fs.appendFileSync(logFile, `[${new Date().toISOString()}] [post-edit-enforce] [${event}] ${msg}\n`, 'utf8');
78
+ } catch (_) {}
79
+ }
80
+
81
+ logHook('ScriptStart', `pid=${process.pid} cwd=${process.cwd()}`);
82
+
83
+ if (process.stdin.isTTY) {
84
+ logHook('StdinTTY', 'Exiting because stdin is a TTY');
85
+ process.exit(0);
86
+ }
87
+
88
+ const stdinTimeout = setTimeout(() => {
89
+ logHook('StdinTimeout', `No input after 3s, exiting bufferLen=${inputBuffer.length}`);
90
+ process.exit(0);
91
+ }, 3000);
53
92
 
54
93
  let inputBuffer = '';
55
94
  process.stdin.setEncoding('utf8');
56
95
  process.stdin.on('data', chunk => {
57
96
  inputBuffer += chunk;
97
+ logHook('DataChunk', `len=${chunk.length} preview=${chunk.slice(0, 100).replace(/[\r\n]+/g, ' ')}`);
58
98
  try {
59
99
  const data = JSON.parse(inputBuffer);
100
+ clearTimeout(stdinTimeout);
101
+ logHook('StdinData', 'keys=' + Object.keys(data).join(','));
60
102
 
61
- if (data.invocationNum !== undefined && data.transcriptPath) {
103
+ if (data.transcriptPath) {
62
104
  handleAntigravityPostInvocation(data);
63
105
  return;
64
106
  }
@@ -74,6 +116,32 @@ process.stdin.on('data', chunk => {
74
116
  }
75
117
  });
76
118
 
119
+ process.stdin.on('end', () => {
120
+ clearTimeout(stdinTimeout);
121
+ logHook('StdinEnd', `bufferLen=${inputBuffer.length}`);
122
+ if (inputBuffer.trim()) {
123
+ try {
124
+ const data = JSON.parse(inputBuffer);
125
+ logHook('ParsedInputOnEnd', 'keys=' + Object.keys(data).join(','));
126
+ if (data.transcriptPath) {
127
+ handleAntigravityPostInvocation(data);
128
+ return;
129
+ }
130
+ const toolName = data.tool_name || '';
131
+ const toolInput = data.tool_input || {};
132
+ processSingleEdit(toolName, toolInput, function (nudge) {
133
+ emitClaude(nudge);
134
+ });
135
+ process.exit(0);
136
+ } catch (e) {
137
+ logHook('JsonParseErrorOnEnd', e.message);
138
+ process.exit(0);
139
+ }
140
+ } else {
141
+ process.exit(0);
142
+ }
143
+ });
144
+
77
145
  function handleAntigravityPostInvocation(data) {
78
146
  try {
79
147
  if (!fs.existsSync(data.transcriptPath)) return;
@@ -125,22 +193,32 @@ function handleAntigravityPostInvocation(data) {
125
193
  + '(2) Does the codebase already have this? (3) Stdlib/native? (4) Existing dependency?');
126
194
  }
127
195
 
128
- if (findings.length > 0) {
196
+ const isPostToolUse = data.stepIdx !== undefined;
197
+ logHook('AntigravityResult', `sourceEdits=${sourceEditsSinceStart}, findings=${findings.length}, isPostToolUse=${isPostToolUse}`);
198
+
199
+ if (isPostToolUse) {
200
+ process.stdout.write(JSON.stringify({}) + '\n');
201
+ } else if (findings.length > 0) {
129
202
  const injectSteps = findings.map(function (f) { return { ephemeralMessage: f }; });
130
203
  process.stdout.write(JSON.stringify({ injectSteps: injectSteps }) + '\n');
131
204
  } else {
132
205
  process.stdout.write(JSON.stringify({}) + '\n');
133
206
  }
134
207
  } catch (e) {
208
+ logHook('AntigravityError', e.message);
135
209
  process.stdout.write(JSON.stringify({}) + '\n');
136
210
  }
211
+ process.exit(0);
137
212
  }
138
213
 
139
214
  function processSingleEdit(toolName, toolInput, emitFn, skipArray) {
140
- const filePath = toolInput.file_path || '';
215
+ const filePath = toolInput.file_path || toolInput.TargetFile || toolInput.filePath || '';
141
216
  if (!filePath) return;
142
217
  const findings = [];
143
218
 
219
+ const isEdit = toolName === 'Edit' || toolName === 'replace_file_content' || toolName === 'multi_replace_file_content';
220
+ const isWrite = toolName === 'Write' || toolName === 'write_to_file';
221
+
144
222
  // Increment per-process counter for Claude Code drift detection
145
223
  const ext = path.extname(filePath).slice(1);
146
224
  if (SOURCE_EXTENSIONS.has(ext)) {
@@ -160,29 +238,31 @@ function processSingleEdit(toolName, toolInput, emitFn, skipArray) {
160
238
  }
161
239
 
162
240
  if (SOURCE_EXTENSIONS.has(ext)) {
163
- if (toolName === 'Edit') {
241
+ if (isEdit) {
164
242
  checkLocDelta(toolInput, filePath, findings);
165
- } else if (toolName === 'Write') {
243
+ } else if (isWrite) {
166
244
  checkNewFileSize(toolInput, filePath, findings);
167
245
  }
168
246
  }
169
247
 
170
248
  checkSecurityPatterns(toolName, toolInput, filePath, findings);
249
+ checkStubPatterns(toolName, toolInput, filePath, findings);
171
250
 
172
251
  if (ext === 'html' || ext === 'css' || ext === 'jsx' || ext === 'tsx' || ext === 'vue' || ext === 'svelte') {
173
252
  checkUiSlopPatterns(toolName, toolInput, filePath, findings);
174
253
  }
175
254
 
176
- if (ext === 'js' || ext === 'ts' || ext === 'jsx' || ext === 'tsx' || ext === 'mjs' || ext === 'cjs') {
177
- checkLinter(filePath, findings);
178
- }
255
+ checkUniversalLinter(filePath, ext, findings);
179
256
 
180
257
  checkLivingDocNudge(filePath, findings);
258
+ checkTestTampering(filePath, findings);
181
259
 
182
260
  if (ext !== 'md') {
183
261
  checkWorkflowGate(toolName, filePath, ext, findings);
184
262
  }
185
263
 
264
+ logHook('ProcessSingleEdit', 'tool=' + toolName + ', file=' + filePath + ', findings=' + findings.length);
265
+
186
266
  if (findings.length === 0) return;
187
267
 
188
268
  const nudge = '[ASC enforcement] ' + findings.join(' ') + ' Review the decision ladder before continuing.';
@@ -248,8 +328,8 @@ function extractDeps(text, pattern) {
248
328
  }
249
329
 
250
330
  function checkLocDelta(toolInput, filePath, findings) {
251
- var newLines = (toolInput.new_string || '').split('\n').length;
252
- var oldLines = (toolInput.old_string || '').split('\n').length;
331
+ var newLines = (toolInput.new_string || toolInput.ReplacementContent || '').split('\n').length;
332
+ var oldLines = (toolInput.old_string || toolInput.TargetContent || '').split('\n').length;
253
333
  var delta = newLines - oldLines;
254
334
  if (delta > LOC_DELTA_THRESHOLD) {
255
335
  findings.push(
@@ -260,7 +340,7 @@ function checkLocDelta(toolInput, filePath, findings) {
260
340
  }
261
341
 
262
342
  function checkNewFileSize(toolInput, filePath, findings) {
263
- var lines = (toolInput.content || '').split('\n').length;
343
+ var lines = (toolInput.content || toolInput.CodeContent || '').split('\n').length;
264
344
  if (lines > NEW_FILE_LINE_THRESHOLD) {
265
345
  // Step 1-2 coverage ("does this already exist?") moved to dedup-gate.js
266
346
  // which provides concrete file-name + overlap-percentage feedback.
@@ -319,7 +399,7 @@ function checkSecurityPatterns(toolName, toolInput, filePath, findings) {
319
399
  }
320
400
 
321
401
  function checkUiSlopPatterns(toolName, toolInput, filePath, findings) {
322
- var target = toolName === 'Edit' ? (toolInput.new_string || '') : (toolInput.content || '');
402
+ var target = toolName === 'Edit' ? (toolInput.new_string || toolInput.ReplacementContent || '') : (toolInput.content || toolInput.CodeContent || '');
323
403
  if (!target) return;
324
404
 
325
405
  if (UI_SLOP_PATTERNS.patterns) {
@@ -336,27 +416,95 @@ function checkUiSlopPatterns(toolName, toolInput, filePath, findings) {
336
416
  }
337
417
  }
338
418
 
339
- function checkLinter(filePath, findings) {
340
- try {
341
- var cwd = process.cwd();
342
- var hasEslint = fs.existsSync(path.join(cwd, '.eslintrc.json')) ||
343
- fs.existsSync(path.join(cwd, '.eslintrc.js')) ||
344
- fs.existsSync(path.join(cwd, 'eslint.config.js')) ||
345
- (fs.existsSync(path.join(cwd, 'package.json')) && fs.readFileSync(path.join(cwd, 'package.json'), 'utf8').includes('eslintConfig'));
346
-
347
- if (hasEslint) {
348
- var execSync = require('child_process').execSync;
349
- execSync('npx eslint "' + filePath + '" --format json', { cwd: cwd, stdio: 'pipe' });
350
- }
351
- } catch (error) {
352
- if (error.stdout) {
419
+ function checkStubPatterns(toolName, toolInput, filePath, findings) {
420
+ var target = toolName === 'Edit' ? (toolInput.new_string || toolInput.ReplacementContent || '') : (toolInput.content || toolInput.CodeContent || '');
421
+ if (!target) return;
422
+
423
+ var ext = path.extname(filePath).slice(1);
424
+ if (STUB_PATTERNS.patterns) {
425
+ STUB_PATTERNS.patterns.forEach(function (p) {
353
426
  try {
354
- var out = JSON.parse(error.stdout.toString());
355
- if (Array.isArray(out) && out.length > 0 && out[0].messages && out[0].messages.length > 0) {
356
- var firstErr = out[0].messages[0];
357
- findings.push('[ASC Linter] ' + firstErr.message + ' at line ' + firstErr.line + '.');
427
+ var langs = p.languages || [];
428
+ var isUniversal = langs.length === 0 || langs.indexOf('universal') !== -1;
429
+ if (!isUniversal && langs.indexOf(ext) === -1) return;
430
+
431
+ var regex = new RegExp(p.regex, 'im');
432
+ var isMatch = regex.test(target);
433
+ logPatternCheck('stub', p.id || 'stub-pattern', isMatch);
434
+ if (isMatch) {
435
+ findings.push('[ASC Stub/Fake-Done Alert] ' + p.message);
358
436
  }
359
437
  } catch (_) { }
438
+ });
439
+ }
440
+ }
441
+
442
+ function checkUniversalLinter(filePath, ext, findings) {
443
+ var execSync = require('child_process').execSync;
444
+ var cwd = process.cwd();
445
+
446
+ if (ext === 'js' || ext === 'ts' || ext === 'jsx' || ext === 'tsx' || ext === 'mjs' || ext === 'cjs') {
447
+ try {
448
+ var hasBiome = fs.existsSync(path.join(cwd, 'biome.json')) || fs.existsSync(path.join(cwd, 'biome.jsonc'));
449
+ if (hasBiome) {
450
+ execSync('npx @biomejs/biome lint "' + filePath + '"', { cwd: cwd, stdio: 'pipe', timeout: 3000 });
451
+ return;
452
+ }
453
+ var hasEslint = fs.existsSync(path.join(cwd, '.eslintrc.json')) ||
454
+ fs.existsSync(path.join(cwd, '.eslintrc.js')) ||
455
+ fs.existsSync(path.join(cwd, 'eslint.config.js')) ||
456
+ fs.existsSync(path.join(cwd, 'eslint.config.mjs')) ||
457
+ (fs.existsSync(path.join(cwd, 'package.json')) && fs.readFileSync(path.join(cwd, 'package.json'), 'utf8').includes('eslintConfig'));
458
+
459
+ if (hasEslint) {
460
+ execSync('npx eslint "' + filePath + '" --format json', { cwd: cwd, stdio: 'pipe', timeout: 4000 });
461
+ }
462
+ } catch (error) {
463
+ if (error.stdout) {
464
+ try {
465
+ var out = JSON.parse(error.stdout.toString());
466
+ if (Array.isArray(out) && out.length > 0 && out[0].messages && out[0].messages.length > 0) {
467
+ var firstErr = out[0].messages[0];
468
+ findings.push('[ASC Linter] ' + firstErr.message + ' at line ' + firstErr.line + '.');
469
+ }
470
+ } catch (_) {
471
+ var msg = error.stdout.toString().split('\n')[0];
472
+ if (msg) findings.push('[ASC Linter] ' + msg);
473
+ }
474
+ }
475
+ }
476
+ } else if (ext === 'py') {
477
+ try {
478
+ if (fs.existsSync(path.join(cwd, 'pyproject.toml')) || fs.existsSync(path.join(cwd, 'ruff.toml'))) {
479
+ execSync('ruff check --quiet "' + filePath + '"', { cwd: cwd, stdio: 'pipe', timeout: 3000 });
480
+ }
481
+ } catch (error) {
482
+ if (error.stdout) {
483
+ var first = error.stdout.toString().split('\n')[0];
484
+ if (first) findings.push('[ASC Python Linter] ' + first);
485
+ }
486
+ }
487
+ } else if (ext === 'go') {
488
+ try {
489
+ if (fs.existsSync(path.join(cwd, 'go.mod'))) {
490
+ execSync('go vet "' + filePath + '"', { cwd: cwd, stdio: 'pipe', timeout: 4000 });
491
+ }
492
+ } catch (error) {
493
+ if (error.stderr) {
494
+ var first = error.stderr.toString().split('\n')[0];
495
+ if (first) findings.push('[ASC Go Vet] ' + first);
496
+ }
497
+ }
498
+ } else if (ext === 'rs') {
499
+ try {
500
+ if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) {
501
+ execSync('cargo clippy --quiet --message-format=short', { cwd: cwd, stdio: 'pipe', timeout: 5000 });
502
+ }
503
+ } catch (error) {
504
+ if (error.stderr) {
505
+ var first = error.stderr.toString().split('\n')[0];
506
+ if (first) findings.push('[ASC Clippy] ' + first);
507
+ }
360
508
  }
361
509
  }
362
510
  }
@@ -371,10 +519,31 @@ function checkLivingDocNudge(filePath, findings) {
371
519
  }
372
520
  }
373
521
 
522
+ function checkTestTampering(filePath, findings) {
523
+ var isTest = /(?:\.test\.|\.spec\.|_test\.|__tests__[\/\\]|tests?[\/\\]).*\.(?:js|ts|jsx|tsx|py|go|rs|rb)$/i.test(filePath);
524
+ if (!isTest) return;
525
+
526
+ try {
527
+ var pathUtil = require('./path-util.cjs');
528
+ var localGate = path.join(process.cwd(), 'workflow-gate.json');
529
+ var gatePath = fs.existsSync(localGate) ? localGate : pathUtil.getWorkflowGatePath(process.cwd());
530
+ if (!fs.existsSync(gatePath)) return;
531
+
532
+ var gate = JSON.parse(fs.readFileSync(gatePath, 'utf8'));
533
+ if (gate.phase === 'implement' || gate.phase === 'bugfix') {
534
+ findings.push(
535
+ '[ASC Test Integrity Alert] Test file modified (' + path.basename(filePath) + ') during ' + gate.phase + ' phase. '
536
+ + 'Ensure assertions were NOT weakened to force a pass; fix the underlying business logic instead.'
537
+ );
538
+ }
539
+ } catch (_) { }
540
+ }
541
+
374
542
  function checkWorkflowGate(toolName, filePath, ext, findings) {
375
543
  try {
376
544
  var pathUtil = require('./path-util.cjs');
377
- var gatePath = pathUtil.getWorkflowGatePath(process.cwd());
545
+ var localGate = path.join(process.cwd(), 'workflow-gate.json');
546
+ var gatePath = fs.existsSync(localGate) ? localGate : pathUtil.getWorkflowGatePath(process.cwd());
378
547
  if (!fs.existsSync(gatePath)) return;
379
548
 
380
549
  var gateStr = fs.readFileSync(gatePath, 'utf8');
@@ -6,8 +6,8 @@
6
6
  "hooks": [
7
7
  {
8
8
  "type": "command",
9
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','session-start.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','session-start.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','session-start.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','session-start.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
10
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\session-start.js\" }",
9
+ "command": "node hooks/session-start.js",
10
+ "commandWindows": "node hooks/session-start.js",
11
11
  "timeout": 5,
12
12
  "statusMessage": "Loading ASC rules..."
13
13
  }
@@ -19,8 +19,8 @@
19
19
  "hooks": [
20
20
  {
21
21
  "type": "command",
22
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','subagent-start.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','subagent-start.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','subagent-start.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','subagent-start.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
23
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\subagent-start.js\" }",
22
+ "command": "node hooks/subagent-start.js",
23
+ "commandWindows": "node hooks/subagent-start.js",
24
24
  "timeout": 5,
25
25
  "statusMessage": "Loading ASC rules..."
26
26
  }
@@ -34,16 +34,16 @@
34
34
  {
35
35
  "type": "command",
36
36
  "if": "Edit(**/package.json)|Edit(**/requirements.txt)|Edit(**/pyproject.toml)|Edit(**/go.mod)|Edit(**/Cargo.toml)|Edit(**/Gemfile)",
37
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
38
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\pre-tool-dependency-gate.js\" }",
37
+ "command": "node hooks/pre-tool-dependency-gate.js",
38
+ "commandWindows": "node hooks/pre-tool-dependency-gate.js",
39
39
  "timeout": 5,
40
40
  "statusMessage": "ASC Pre-tool dependency check (Edit)..."
41
41
  },
42
42
  {
43
43
  "type": "command",
44
44
  "if": "Write(**/package.json)|Write(**/requirements.txt)|Write(**/pyproject.toml)|Write(**/go.mod)|Write(**/Cargo.toml)|Write(**/Gemfile)",
45
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
46
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\pre-tool-dependency-gate.js\" }",
45
+ "command": "node hooks/pre-tool-dependency-gate.js",
46
+ "commandWindows": "node hooks/pre-tool-dependency-gate.js",
47
47
  "timeout": 5,
48
48
  "statusMessage": "ASC Pre-tool dependency check (Write)..."
49
49
  }
@@ -54,8 +54,8 @@
54
54
  "hooks": [
55
55
  {
56
56
  "type": "command",
57
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','pre-tool-dependency-gate.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
58
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\pre-tool-dependency-gate.js\" }",
57
+ "command": "node hooks/pre-tool-dependency-gate.js",
58
+ "commandWindows": "node hooks/pre-tool-dependency-gate.js",
59
59
  "timeout": 5,
60
60
  "statusMessage": "ASC Pre-tool dependency check (Terminal)..."
61
61
  }
@@ -68,15 +68,15 @@
68
68
  "hooks": [
69
69
  {
70
70
  "type": "command",
71
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','post-edit-enforce.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','post-edit-enforce.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','post-edit-enforce.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','post-edit-enforce.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
72
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\post-edit-enforce.js\" }",
71
+ "command": "node hooks/post-edit-enforce.js",
72
+ "commandWindows": "node hooks/post-edit-enforce.js",
73
73
  "timeout": 5,
74
74
  "statusMessage": "ASC ladder & spec gate check..."
75
75
  },
76
76
  {
77
77
  "type": "command",
78
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','dedup-gate.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','dedup-gate.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','dedup-gate.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','dedup-gate.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
79
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\dedup-gate.js\" }",
78
+ "command": "node hooks/dedup-gate.js",
79
+ "commandWindows": "node hooks/dedup-gate.js",
80
80
  "timeout": 15,
81
81
  "statusMessage": "ASC duplicate-code scan..."
82
82
  }
@@ -86,15 +86,15 @@
86
86
  "PreInvocation": [
87
87
  {
88
88
  "type": "command",
89
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','session-pulse.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','session-pulse.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','session-pulse.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','session-pulse.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
90
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\session-pulse.js\" }",
89
+ "command": "node hooks/session-pulse.js",
90
+ "commandWindows": "node hooks/session-pulse.js",
91
91
  "timeout": 5,
92
92
  "statusMessage": "Loading ASC rules..."
93
93
  },
94
94
  {
95
95
  "type": "command",
96
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','ladder-pulse.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','ladder-pulse.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','ladder-pulse.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','ladder-pulse.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
97
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\ladder-pulse.js\" }",
96
+ "command": "node hooks/ladder-pulse.js",
97
+ "commandWindows": "node hooks/ladder-pulse.js",
98
98
  "timeout": 5,
99
99
  "statusMessage": "ASC ladder pulse..."
100
100
  }
@@ -102,18 +102,19 @@
102
102
  "PostInvocation": [
103
103
  {
104
104
  "type": "command",
105
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','post-edit-enforce.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','post-edit-enforce.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','post-edit-enforce.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','post-edit-enforce.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
105
+ "command": "node hooks/post-edit-enforce.js",
106
+ "commandWindows": "node hooks/post-edit-enforce.js",
106
107
  "timeout": 15
107
108
  }
108
109
  ],
109
110
  "PreCompact": [
110
111
  {
111
112
  "type": "command",
112
- "command": "node -e \"const p=require('path'),fs=require('fs'),os=require('os');const local=p.join(process.cwd(),'.agents','plugins','agentic-senior-core','hooks','pre-compact-pin.js');const g1=p.join(os.homedir(),'.agents','plugins','agentic-senior-core','hooks','pre-compact-pin.js');const g2=p.join(os.homedir(),'.gemini','config','plugins','agentic-senior-core','hooks','pre-compact-pin.js');const g3=p.join(os.homedir(),'.gemini','antigravity-cli','plugins','agentic-senior-core','hooks','pre-compact-pin.js');require(fs.existsSync(local)?local:(fs.existsSync(g1)?g1:(fs.existsSync(g2)?g2:g3)));\"",
113
- "commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { $root = if ($env:PLUGIN_ROOT) { $env:PLUGIN_ROOT } else { if ($env:CLAUDE_PLUGIN_ROOT) { $env:CLAUDE_PLUGIN_ROOT } else { if (Test-Path \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\") { \"$env:USERPROFILE\\.agents\\plugins\\agentic-senior-core\" } else { \"$env:USERPROFILE\\.gemini\\config\\plugins\\agentic-senior-core\" } } }; node \"$root\\hooks\\pre-compact-pin.js\" }",
113
+ "command": "node hooks/pre-compact-pin.js",
114
+ "commandWindows": "node hooks/pre-compact-pin.js",
114
115
  "timeout": 5,
115
116
  "statusMessage": "ASC constraint pinning..."
116
117
  }
117
118
  ]
118
119
  }
119
- }
120
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-senior-core",
3
- "version": "6.10.0",
3
+ "version": "6.11.1",
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": [
@@ -18,7 +18,7 @@ Before writing code, stop at the lowest step that holds:
18
18
  When picking step 5 or 6 (unless trivial):
19
19
  - One-line comment noting rationale and upgrade trigger if there is a ceiling.
20
20
  - One runnable check (assertion, test, or demo) proving it works.
21
- - Never simulate success or return hardcoded values mimicking live integrations.
21
+ - Never simulate success or return hardcoded values mimicking live integrations. Temporary stubs must be marked with `// ASC-STUB: [reason] [ASC-DEBT-xxx]` and tracked in the debt ledger.
22
22
 
23
23
  ## Security (never skip)
24
24
  - Validate and normalize ALL inputs at trust boundaries.
@@ -35,6 +35,7 @@ When picking step 5 or 6 (unless trivial):
35
35
  - All identifiers (variables, functions, classes, file names) must be in English. No emojis in code or commit messages.
36
36
  - Early returns over deep nesting (keep happy path flat).
37
37
  - Three similar lines is better than a premature abstraction (duplication over wrong abstraction).
38
+ - Single design source of truth: UI styling must reuse defined design tokens, theme variables, or existing page patterns. No ad-hoc palette, radius, or font deviations between pages.
38
39
  - Delete code that carries no behavior, safety, or test value. Readability wins over brevity.
39
40
  - Detect and respect project linter/formatter configs. Do not restate style rules enforced by tooling.
40
41
 
@@ -46,10 +47,12 @@ When picking step 5 or 6 (unless trivial):
46
47
  - Scope and direction changes require explicit user confirmation before modifying abstractions or altering system contracts.
47
48
  - Before implementing a feature, locate an analogous module and follow its layer split, naming, and error-handling.
48
49
  - Atomic writes must be wrapped in transactions. Flag shared mutable state under concurrent requests.
50
+ - UI component reuse: Component re-invention across pages is forbidden. Before declaring markup or styling, check existing components in `components/` or `ui/`. Compose or extend existing components via props/variants. Do not introduce single-page clones or ad-hoc styling that diverges from established pages.
49
51
 
50
52
  ## Error Handling & Observability
51
53
  - Fail fast on invalid input at trust boundaries. Handle only errors that can actually occur.
52
54
  - Structured error responses with safe details (RFC 9457). Distinguish client (4xx) from server (5xx) errors.
55
+ - Frontend data-fetching components must explicitly handle loading, error (toast/notification with retry), and data states.
53
56
  - Surface every operational error with context. Empty catch blocks mask production issues.
54
57
  - Structured key-value logging for significant events. Never leak stack traces or credentials in production logs.
55
58
 
@@ -58,6 +61,7 @@ When picking step 5 or 6 (unless trivial):
58
61
  - Never run `git commit`, `git push`, or `git push --force` unless explicitly requested this turn.
59
62
  - Testing baseline: new business logic gets one happy-path test and one failure-mode test, unless waived by user.
60
63
  - Test quality: Never mock the unit under test — mock only external boundaries/dependencies.
64
+ - Test integrity: Never weaken or delete test assertions to simulate a passing run. Fix the underlying logic.
61
65
  - Sycophancy mitigation: State technical objections and trade-offs plainly before implementing. Answer direct questions honestly.
62
66
  - Preserve findings and decisions outside chat context. Recommend a fresh context at phase boundaries or after 20-30 tool calls.
63
67
 
@@ -16,7 +16,7 @@ This workflow nudges the agent to stop at each phase boundary, same enforcement
16
16
 
17
17
  For brownfield feature development (`asc-add-feature`), Phase 2 requires a lightweight **PRD.md** (or feature spec in `docs/PRD.md`) defining product intent, goals, and non-goals to avoid scope creep and context rot.
18
18
 
19
- To track phase, write to `workflow-gate.json` via the `state_write` MCP tool.
19
+ To track phase, write to `workflow-gate.json` at the project root (or via standard file writing tools).
20
20
  Format:
21
21
  ```json
22
22
  {
@@ -30,8 +30,8 @@ Format:
30
30
 
31
31
  1. Write `workflow-gate.json` with phase `research`.
32
32
  2. Map existing code: patterns, utilities, dependencies already in use. Locate at least one analogous feature/module and record its file paths.
33
- 3. Identify what must NOT be rebuilt (e.g., existing validation helpers).
34
- 4. Output a factual research summary that separates what exists from what is proposed, with file paths for the two or three claims that drive the plan.
33
+ 3. **UI Component & Design Token Inventory**: For any UI or frontend work, scan existing shared components (e.g. `components/`, `ui/`) and existing page patterns. Explicitly identify what must NOT be rebuilt (buttons, cards, modals, inputs, tables, layout wrappers, validation helpers).
34
+ 4. Output a factual research summary that separates what exists from what is proposed, including an explicit `[REUSED COMPONENTS & HELPERS]` list, with file paths for the two or three claims that drive the plan.
35
35
  5. **STOP and wait for user approval.** Do not plan or implement.
36
36
 
37
37
  ## Phase 2: Plan
@@ -40,7 +40,7 @@ Format:
40
40
  2. Ensure `docs/PRD.md` or feature brief exists.
41
41
  3. Check if `.github/workflows/asc-quality-gate.yml` exists. If not, include scaffolding it in your plan (must run linter, type-check, and audit) and remind the user to enable Branch Protection.
42
42
  4. Create a numbered, step-by-step implementation plan with specific files, functions, and line references.
43
- 5. Include a "Don't Build" list from the research phase.
43
+ 5. Include a "Don't Build" list and a "Reused Components" list from the research phase. For any proposed new UI component, justify why existing components cannot be reused or extended via props/variants.
44
44
  6. **Callout: Plan-Reading Illusion.** Ask the user to explicitly verify the two or three critical plan claims against the referenced files, not just skim it.
45
45
  7. Output the plan.
46
46
  8. **STOP and wait for user approval.** Do not implement.
@@ -50,5 +50,5 @@ Format:
50
50
  1. On approval of Phase 2, update `workflow-gate.json` phase to `implement`.
51
51
  2. Recommend a fresh context (intentional compaction) at the phase boundary or after roughly 20-30 tool calls. Do not wait until degradation is subjectively noticeable.
52
52
  3. Execute the approved plan.
53
- 4. Validate: tests pass with empirical execution logs outputted, no duplicate code introduced, plan items checked off.
54
- 5. On completion, give a short comprehension summary of what changed and why, then clear the state in `workflow-gate.json` by overwriting it with `{}`.
53
+ 4. **Mandatory Verification**: Execute the project test suite (`ascx test`, `npm test`, `pytest`, `go test ./...`, or `cargo test`). The test runner MUST exit with code 0. Do NOT declare completion or weaken test assertions if tests fail.
54
+ 5. On verified pass, give a short comprehension summary of what changed and why, then clear the state in `workflow-gate.json` by overwriting it with `{}`.