@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.
- package/.agents/plugins/agentic-senior-core/.codex-plugin/plugin.json +1 -1
- package/.agents/plugins/agentic-senior-core/hooks/constants.cjs +6 -7
- package/.agents/plugins/agentic-senior-core/hooks/lib/known-duplicates.json +27 -17
- 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 +165 -65
- package/.agents/plugins/agentic-senior-core/hooks/pre-tool-dependency-gate.js +66 -15
- package/.agents/plugins/agentic-senior-core/hooks.json +118 -1
- package/.agents/plugins/agentic-senior-core/plugin.json +1 -1
- package/.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md +45 -72
- package/.agents/plugins/agentic-senior-core/skills/asc/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-adapter/SKILL.md +3 -2
- package/.agents/plugins/agentic-senior-core/skills/asc-add-feature/SKILL.md +7 -7
- package/.agents/plugins/agentic-senior-core/skills/asc-audit/SKILL.md +6 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-bootstrap/SKILL.md +4 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-debt/SKILL.md +16 -6
- package/.agents/plugins/agentic-senior-core/skills/asc-dedup/SKILL.md +2 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-fingerprint/SKILL.md +4 -3
- package/.agents/plugins/agentic-senior-core/skills/asc-learn/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-new-project/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-refactor/SKILL.md +8 -9
- package/.agents/plugins/agentic-senior-core/skills/asc-reference/SKILL.md +4 -10
- package/.agents/plugins/agentic-senior-core/skills/asc-review/SKILL.md +7 -6
- package/README.md +2 -1
- package/gemini-extension.json +1 -1
- package/lib/cli/ascx/runtime.mjs +26 -0
- package/lib/cli/commands/git-hook-generator.mjs +28 -0
- package/package.json +1 -1
- package/plugin.yaml +4 -1
|
@@ -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:
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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": "
|
|
3
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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(', ') + '
|
|
228
|
-
+ 'Ladder step 3:
|
|
231
|
+
'Dependency ' + stdlibDupes.join(', ') + ' duplicates stdlib/platform features. '
|
|
232
|
+
+ 'Ladder step 3: use the standard library instead.'
|
|
229
233
|
);
|
|
230
|
-
}
|
|
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
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
}
|
|
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
|
|
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
|
|