@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.
- package/.agents/plugins/agentic-senior-core/.codex-plugin/plugin.json +1 -1
- package/.agents/plugins/agentic-senior-core/hooks/lib/known-stub-patterns.json +29 -0
- package/.agents/plugins/agentic-senior-core/hooks/lib/known-ui-slop-patterns.json +15 -0
- package/.agents/plugins/agentic-senior-core/hooks/post-edit-enforce.js +201 -32
- package/.agents/plugins/agentic-senior-core/hooks.json +23 -22
- package/.agents/plugins/agentic-senior-core/plugin.json +1 -1
- package/.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md +5 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-add-feature/SKILL.md +6 -6
- package/.agents/plugins/agentic-senior-core/skills/asc-audit/SKILL.md +6 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-bootstrap/SKILL.md +3 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-debt/SKILL.md +15 -5
- package/.agents/plugins/agentic-senior-core/skills/asc-refactor/SKILL.md +7 -8
- package/.agents/plugins/agentic-senior-core/skills/asc-reference/SKILL.md +4 -10
- package/.agents/plugins/agentic-senior-core/skills/asc-review/SKILL.md +6 -5
- package/bin/agentic-senior-core.js +0 -8
- package/gemini-extension.json +1 -1
- package/lib/cli/commands/git-hook-generator.mjs +40 -2
- package/package.json +1 -3
- package/plugin.yaml +1 -1
- package/lib/cli/commands/mcp.mjs +0 -3
- package/scripts/mcp-server/constants.mjs +0 -57
- package/scripts/mcp-server/tool-registry.mjs +0 -204
- package/scripts/mcp-server/tools.mjs +0 -592
- package/scripts/mcp-server.mjs +0 -202
|
@@ -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
|
-
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
241
|
+
if (isEdit) {
|
|
164
242
|
checkLocDelta(toolInput, filePath, findings);
|
|
165
|
-
} else if (
|
|
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
|
-
|
|
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
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
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
|
|
10
|
-
"commandWindows": "
|
|
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
|
|
23
|
-
"commandWindows": "
|
|
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
|
|
38
|
-
"commandWindows": "
|
|
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
|
|
46
|
-
"commandWindows": "
|
|
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
|
|
58
|
-
"commandWindows": "
|
|
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
|
|
72
|
-
"commandWindows": "
|
|
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
|
|
79
|
-
"commandWindows": "
|
|
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
|
|
90
|
-
"commandWindows": "
|
|
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
|
|
97
|
-
"commandWindows": "
|
|
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
|
|
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
|
|
113
|
-
"commandWindows": "
|
|
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
|
+
}
|
|
@@ -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`
|
|
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.
|
|
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.
|
|
54
|
-
5. On
|
|
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 `{}`.
|