@ryuenn3123/agentic-senior-core 5.8.11 → 5.8.14
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/hooks/hooks.json +74 -0
- package/.agents/plugins/agentic-senior-core/hooks/lib/known-duplicates.json +32 -0
- package/.agents/plugins/agentic-senior-core/hooks/package.json +3 -0
- package/.agents/plugins/agentic-senior-core/hooks/path-util.cjs +23 -0
- package/.agents/plugins/agentic-senior-core/hooks/post-edit-enforce.js +234 -0
- package/.agents/plugins/agentic-senior-core/hooks/pre-tool-dependency-gate.js +108 -0
- package/.agents/plugins/agentic-senior-core/hooks/session-start.js +36 -0
- package/.agents/plugins/agentic-senior-core/hooks/subagent-start.js +38 -0
- package/.agents/plugins/agentic-senior-core/skills/asc/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-adapter/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-add-feature/SKILL.md +9 -8
- package/.agents/plugins/agentic-senior-core/skills/asc-audit/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-debt/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-new-project/SKILL.md +16 -9
- package/.agents/plugins/agentic-senior-core/skills/asc-refactor/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-reference/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-review/SKILL.md +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.devin-plugin/plugin.json +1 -1
- package/.github/plugin/plugin.json +1 -1
- package/README.md +21 -2
- package/gemini-extension.json +1 -1
- package/hooks/hooks.json +30 -1
- package/hooks/lib/known-duplicates.json +32 -0
- package/hooks/package.json +3 -0
- package/hooks/post-edit-enforce.js +68 -2
- package/hooks/pre-tool-dependency-gate.js +108 -0
- package/lib/cli/commands/global.mjs +0 -1
- package/package.json +1 -1
- package/plugin.yaml +1 -1
- package/skills/asc-add-feature/SKILL.md +9 -8
- package/skills/asc-new-project/SKILL.md +16 -9
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume|clear|compact",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.js\"; exit 0",
|
|
10
|
+
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\session-start.js\" }",
|
|
11
|
+
"timeout": 5,
|
|
12
|
+
"statusMessage": "Loading ASC rules..."
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"SubagentStart": [
|
|
18
|
+
{
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/subagent-start.js\"; exit 0",
|
|
23
|
+
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\subagent-start.js\" }",
|
|
24
|
+
"timeout": 5,
|
|
25
|
+
"statusMessage": "Loading ASC rules..."
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"PreToolUse": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "Edit|Write",
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"if": "Edit(**/package.json)",
|
|
37
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-dependency-gate.js\"; exit 0",
|
|
38
|
+
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\pre-tool-dependency-gate.js\" }",
|
|
39
|
+
"timeout": 5,
|
|
40
|
+
"statusMessage": "ASC Pre-tool dependency check (Edit)..."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "command",
|
|
44
|
+
"if": "Write(**/package.json)",
|
|
45
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-dependency-gate.js\"; exit 0",
|
|
46
|
+
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\pre-tool-dependency-gate.js\" }",
|
|
47
|
+
"timeout": 5,
|
|
48
|
+
"statusMessage": "ASC Pre-tool dependency check (Write)..."
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"PostToolUse": [
|
|
54
|
+
{
|
|
55
|
+
"matcher": "Edit|Write",
|
|
56
|
+
"hooks": [
|
|
57
|
+
{
|
|
58
|
+
"type": "command",
|
|
59
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/post-edit-enforce.js\"; exit 0",
|
|
60
|
+
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\post-edit-enforce.js\" }",
|
|
61
|
+
"timeout": 5,
|
|
62
|
+
"statusMessage": "ASC ladder & spec gate check..."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "prompt",
|
|
66
|
+
"if": "Edit(**/package.json)",
|
|
67
|
+
"prompt": "A new dependency '$ARGUMENTS' was added to package.json. Does it duplicate Node.js standard library or platform features? If yes, return JSON: {\"hookSpecificOutput\": {\"hookEventName\": \"PostToolUse\", \"additionalContext\": \"[ASC Advisory] The newly added dependency appears to duplicate stdlib or platform features. Consider native alternatives.\"}}. If no, return {}.",
|
|
68
|
+
"timeout": 30
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Single source of truth for packages that duplicate standard library or native platform features.",
|
|
3
|
+
"duplicates": [
|
|
4
|
+
"lodash",
|
|
5
|
+
"lodash-es",
|
|
6
|
+
"underscore",
|
|
7
|
+
"moment",
|
|
8
|
+
"dayjs",
|
|
9
|
+
"uuid",
|
|
10
|
+
"nanoid",
|
|
11
|
+
"chalk",
|
|
12
|
+
"kleur",
|
|
13
|
+
"colorette",
|
|
14
|
+
"axios",
|
|
15
|
+
"got",
|
|
16
|
+
"node-fetch",
|
|
17
|
+
"superagent",
|
|
18
|
+
"mkdirp",
|
|
19
|
+
"rimraf",
|
|
20
|
+
"del",
|
|
21
|
+
"glob",
|
|
22
|
+
"globby",
|
|
23
|
+
"left-pad",
|
|
24
|
+
"pad-left",
|
|
25
|
+
"is-odd",
|
|
26
|
+
"is-even",
|
|
27
|
+
"is-number",
|
|
28
|
+
"is-string",
|
|
29
|
+
"path-exists",
|
|
30
|
+
"fs-extra"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Computes a stable path for the workflow gate file based on the project path.
|
|
7
|
+
* Normalizes the path (resolves absolute, lowercase, forward slashes, no trailing slash)
|
|
8
|
+
* to ensure identical hashes regardless of OS path separator or drive letter casing.
|
|
9
|
+
* Note: .toLowerCase() deliberately treats Linux/Mac paths case-insensitively,
|
|
10
|
+
* accepting the negligible risk of collisions for the benefit of Windows stability.
|
|
11
|
+
*
|
|
12
|
+
* @param {string} projectPath - The root path of the project.
|
|
13
|
+
* @returns {string} Absolute path to the global workflow gate JSON file.
|
|
14
|
+
*/
|
|
15
|
+
function getWorkflowGatePath(projectPath) {
|
|
16
|
+
const normalizedPath = path.resolve(projectPath).replace(/\\/g, '/').toLowerCase().replace(/\/$/, '');
|
|
17
|
+
const projectHash = crypto.createHash('sha256').update(normalizedPath).digest('hex').substring(0, 16);
|
|
18
|
+
return path.join(os.homedir(), '.config', 'agentic-senior-core', 'gates', `${projectHash}.json`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
getWorkflowGatePath
|
|
23
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Agentic Senior Core — PostToolUse enforcement hook
|
|
3
|
+
// Fires after Edit/Write. Checks for ladder violations and injects a nudge.
|
|
4
|
+
// Supports Claude Code, Codex CLI, and GitHub Copilot CLI.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
let STDLIB_DUPLICATES = new Set([
|
|
10
|
+
'lodash', 'lodash-es', 'underscore',
|
|
11
|
+
'moment', 'dayjs',
|
|
12
|
+
'uuid', 'nanoid',
|
|
13
|
+
'chalk', 'kleur', 'colorette',
|
|
14
|
+
'axios', 'got', 'node-fetch', 'superagent',
|
|
15
|
+
'mkdirp', 'rimraf', 'del',
|
|
16
|
+
'glob', 'globby',
|
|
17
|
+
'left-pad', 'pad-left',
|
|
18
|
+
'is-odd', 'is-even', 'is-number', 'is-string',
|
|
19
|
+
'path-exists', 'fs-extra',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const knownPath = path.join(__dirname, 'lib', 'known-duplicates.json');
|
|
24
|
+
if (fs.existsSync(knownPath)) {
|
|
25
|
+
const raw = JSON.parse(fs.readFileSync(knownPath, 'utf8'));
|
|
26
|
+
if (Array.isArray(raw.duplicates)) {
|
|
27
|
+
STDLIB_DUPLICATES = new Set(raw.duplicates);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (_) {}
|
|
31
|
+
|
|
32
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
33
|
+
'js', 'ts', 'mjs', 'cjs', 'jsx', 'tsx',
|
|
34
|
+
'py', 'rb', 'go', 'rs', 'java', 'kt', 'swift', 'cs',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
const LOC_DELTA_THRESHOLD = 30;
|
|
38
|
+
const NEW_FILE_LINE_THRESHOLD = 50;
|
|
39
|
+
|
|
40
|
+
let inputBuffer = '';
|
|
41
|
+
process.stdin.setEncoding('utf8');
|
|
42
|
+
process.stdin.on('data', function (chunk) { inputBuffer += chunk; });
|
|
43
|
+
process.stdin.on('end', function () {
|
|
44
|
+
try {
|
|
45
|
+
const data = JSON.parse(inputBuffer);
|
|
46
|
+
const toolName = data.tool_name || '';
|
|
47
|
+
const toolInput = data.tool_input || {};
|
|
48
|
+
const filePath = toolInput.file_path || '';
|
|
49
|
+
const findings = [];
|
|
50
|
+
|
|
51
|
+
if (filePath.endsWith('package.json')) {
|
|
52
|
+
checkDependencyAddition(toolName, toolInput, findings);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const ext = path.extname(filePath).slice(1);
|
|
56
|
+
if (SOURCE_EXTENSIONS.has(ext)) {
|
|
57
|
+
if (toolName === 'Edit') {
|
|
58
|
+
checkLocDelta(toolInput, filePath, findings);
|
|
59
|
+
} else if (toolName === 'Write') {
|
|
60
|
+
checkNewFileSize(toolInput, filePath, findings);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
checkLivingDocNudge(filePath, findings);
|
|
65
|
+
|
|
66
|
+
if (ext !== 'md') {
|
|
67
|
+
checkWorkflowGate(toolName, filePath, ext, findings);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (findings.length === 0) return;
|
|
71
|
+
|
|
72
|
+
const nudge = '[ASC enforcement] ' + findings.join(' ') + ' Review the decision ladder before continuing.';
|
|
73
|
+
emit(nudge);
|
|
74
|
+
} catch (_) {
|
|
75
|
+
// Silent fail — enforcement must not break the session
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
function checkDependencyAddition(toolName, toolInput, findings) {
|
|
80
|
+
var target = toolName === 'Edit' ? (toolInput.new_string || '') : (toolInput.content || '');
|
|
81
|
+
var baseline = toolName === 'Edit' ? (toolInput.old_string || '') : '';
|
|
82
|
+
|
|
83
|
+
var depPattern = /"([^"]+)"\s*:\s*"[~^>=<*]?\d/g;
|
|
84
|
+
var newDeps = extractDeps(target, depPattern);
|
|
85
|
+
var oldDeps = extractDeps(baseline, depPattern);
|
|
86
|
+
|
|
87
|
+
var added = newDeps.filter(function (d) { return oldDeps.indexOf(d) === -1; });
|
|
88
|
+
if (added.length === 0) return;
|
|
89
|
+
|
|
90
|
+
var stdlibDupes = added.filter(function (d) { return STDLIB_DUPLICATES.has(d); });
|
|
91
|
+
if (stdlibDupes.length > 0) {
|
|
92
|
+
findings.push(
|
|
93
|
+
'Dependency ' + stdlibDupes.join(', ') + ' may duplicate stdlib/platform features. '
|
|
94
|
+
+ 'Ladder step 3: does the standard library cover this?'
|
|
95
|
+
);
|
|
96
|
+
} else {
|
|
97
|
+
findings.push(
|
|
98
|
+
'New dependency added: ' + added.join(', ') + '. '
|
|
99
|
+
+ 'Ladder step 3-4: stdlib or already-installed alternative?'
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function extractDeps(text, pattern) {
|
|
105
|
+
var matches = [];
|
|
106
|
+
var match;
|
|
107
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
108
|
+
matches.push(match[1]);
|
|
109
|
+
}
|
|
110
|
+
return matches;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function checkLocDelta(toolInput, filePath, findings) {
|
|
114
|
+
var newLines = (toolInput.new_string || '').split('\n').length;
|
|
115
|
+
var oldLines = (toolInput.old_string || '').split('\n').length;
|
|
116
|
+
var delta = newLines - oldLines;
|
|
117
|
+
if (delta > LOC_DELTA_THRESHOLD) {
|
|
118
|
+
findings.push(
|
|
119
|
+
'Edit added ' + delta + ' net lines to ' + path.basename(filePath) + '. '
|
|
120
|
+
+ 'Ladder step 5: can this be one straightforward function?'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function checkNewFileSize(toolInput, filePath, findings) {
|
|
126
|
+
var lines = (toolInput.content || '').split('\n').length;
|
|
127
|
+
if (lines > NEW_FILE_LINE_THRESHOLD) {
|
|
128
|
+
findings.push(
|
|
129
|
+
'New file ' + path.basename(filePath) + ' created with ' + lines + ' lines. '
|
|
130
|
+
+ 'Ladder step 1-2: does this need to be built? Does the codebase already have this?'
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function checkLivingDocNudge(filePath, findings) {
|
|
136
|
+
var lower = filePath.toLowerCase();
|
|
137
|
+
if (lower.includes('schema') || lower.includes('migration') || lower.includes('model') || lower.includes('prisma')) {
|
|
138
|
+
findings.push(
|
|
139
|
+
'[ASC Living Doc] Data contract/model modified in ' + path.basename(filePath) + '. '
|
|
140
|
+
+ 'Ensure docs/Schema.md and docs/Architecture.md are kept in sync to prevent spec drift.'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function checkWorkflowGate(toolName, filePath, ext, findings) {
|
|
146
|
+
try {
|
|
147
|
+
var pathUtil = require('./path-util.cjs');
|
|
148
|
+
var gatePath = pathUtil.getWorkflowGatePath(process.cwd());
|
|
149
|
+
if (!fs.existsSync(gatePath)) return;
|
|
150
|
+
|
|
151
|
+
var gateStr = fs.readFileSync(gatePath, 'utf8');
|
|
152
|
+
var gate = JSON.parse(gateStr);
|
|
153
|
+
|
|
154
|
+
if (gate.updatedAt) {
|
|
155
|
+
var ageHours = (Date.now() - new Date(gate.updatedAt).getTime()) / (1000 * 60 * 60);
|
|
156
|
+
if (ageHours > 4) {
|
|
157
|
+
fs.unlinkSync(gatePath);
|
|
158
|
+
findings.push('Cleared stale workflow gate (' + gate.workflow + ').');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (gate.phase === 'research' || gate.phase === 'plan') {
|
|
164
|
+
findings.push(
|
|
165
|
+
'Workflow gate bypass: ' + gate.workflow + ' is in ' + gate.phase + ' phase but source code was edited. '
|
|
166
|
+
+ 'Stop and wait for phase approval. If you must proceed, log this bypass to the debt ledger.'
|
|
167
|
+
);
|
|
168
|
+
} else if (gate.phase === 'implement') {
|
|
169
|
+
validateDocSpecs(gate.workflow, findings);
|
|
170
|
+
}
|
|
171
|
+
} catch (_) {
|
|
172
|
+
// Silent fail
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function validateDocSpecs(workflow, findings) {
|
|
177
|
+
try {
|
|
178
|
+
var cwd = process.cwd();
|
|
179
|
+
var docsDir = path.join(cwd, 'docs');
|
|
180
|
+
|
|
181
|
+
// Anti-typo check for common doc typos (e.g. Architectyre.md)
|
|
182
|
+
var searchDirs = [cwd, docsDir];
|
|
183
|
+
var typoFound = false;
|
|
184
|
+
searchDirs.forEach(function (d) {
|
|
185
|
+
if (fs.existsSync(d)) {
|
|
186
|
+
var files = fs.readdirSync(d);
|
|
187
|
+
files.forEach(function (f) {
|
|
188
|
+
if (f.toLowerCase() !== f && /architect[y|u]re/i.test(f) && f !== 'Architecture.md') {
|
|
189
|
+
findings.push('[ASC Spec Typo Alert] Typo detected in doc filename "' + f + '". Rename to "Architecture.md".');
|
|
190
|
+
typoFound = true;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (workflow === 'asc-new-project') {
|
|
197
|
+
var requiredDocs = ['PRD.md', 'Architecture.md', 'Design.md', 'Schema.md'];
|
|
198
|
+
var missing = requiredDocs.filter(function (doc) {
|
|
199
|
+
return !fs.existsSync(path.join(cwd, doc)) && !fs.existsSync(path.join(docsDir, doc));
|
|
200
|
+
});
|
|
201
|
+
if (missing.length > 0) {
|
|
202
|
+
findings.push(
|
|
203
|
+
'[ASC 5-Doc SDD Gate] Greenfield project missing spec document(s): ' + missing.join(', ') + '. '
|
|
204
|
+
+ 'Create them in docs/ to align requirements before continuing implementation.'
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
} else if (workflow === 'asc-add-feature') {
|
|
208
|
+
var hasPrd = fs.existsSync(path.join(cwd, 'PRD.md')) || fs.existsSync(path.join(docsDir, 'PRD.md'));
|
|
209
|
+
if (!hasPrd) {
|
|
210
|
+
findings.push(
|
|
211
|
+
'[ASC SDD Gate] Feature addition missing docs/PRD.md. Define goals and non-goals before coding.'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch (_) {}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function emit(nudge) {
|
|
219
|
+
try {
|
|
220
|
+
var isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
|
|
221
|
+
var output = {
|
|
222
|
+
hookSpecificOutput: {
|
|
223
|
+
hookEventName: 'PostToolUse',
|
|
224
|
+
additionalContext: nudge,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
if (isCopilot) {
|
|
228
|
+
output = { additionalContext: nudge };
|
|
229
|
+
}
|
|
230
|
+
process.stdout.write(JSON.stringify(output));
|
|
231
|
+
} catch (_) {
|
|
232
|
+
// EPIPE — silent
|
|
233
|
+
}
|
|
234
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Agentic Senior Core — PreToolUse dependency gate hook
|
|
3
|
+
// Hard-blocks edits/writes to package.json if new dependencies duplicate stdlib/platform features.
|
|
4
|
+
// Supports Claude Code hookSpecificOutput permissionDecision deny response format.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
let knownDuplicates = new Set();
|
|
10
|
+
try {
|
|
11
|
+
const knownPath = path.join(__dirname, 'lib', 'known-duplicates.json');
|
|
12
|
+
if (fs.existsSync(knownPath)) {
|
|
13
|
+
const raw = JSON.parse(fs.readFileSync(knownPath, 'utf8'));
|
|
14
|
+
if (Array.isArray(raw.duplicates)) {
|
|
15
|
+
knownDuplicates = new Set(raw.duplicates);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} catch (_) {
|
|
19
|
+
// Fallback set if JSON loading fails
|
|
20
|
+
knownDuplicates = new Set(['lodash', 'underscore', 'moment', 'dayjs', 'uuid', 'axios', 'rimraf']);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let inputBuffer = '';
|
|
24
|
+
process.stdin.setEncoding('utf8');
|
|
25
|
+
process.stdin.on('data', function (chunk) { inputBuffer += chunk; });
|
|
26
|
+
process.stdin.on('end', function () {
|
|
27
|
+
try {
|
|
28
|
+
const data = JSON.parse(inputBuffer);
|
|
29
|
+
const toolName = data.tool_name || '';
|
|
30
|
+
const toolInput = data.tool_input || {};
|
|
31
|
+
const filePath = toolInput.file_path || '';
|
|
32
|
+
|
|
33
|
+
if (!filePath.endsWith('package.json')) {
|
|
34
|
+
process.exit(0);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const target = toolName === 'Edit' ? (toolInput.new_string || '') : (toolInput.content || '');
|
|
39
|
+
const baseline = toolName === 'Edit' ? (toolInput.old_string || '') : '';
|
|
40
|
+
|
|
41
|
+
const depPattern = /"([^"]+)"\s*:\s*"[~^>=<*]?\d/g;
|
|
42
|
+
const newDeps = extractDeps(target, depPattern);
|
|
43
|
+
const oldDeps = extractDeps(baseline, depPattern);
|
|
44
|
+
|
|
45
|
+
const added = newDeps.filter(function (d) { return oldDeps.indexOf(d) === -1; });
|
|
46
|
+
if (added.length === 0) {
|
|
47
|
+
process.exit(0);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const allowlist = loadAllowlist();
|
|
52
|
+
const forbidden = added.filter(function (dep) {
|
|
53
|
+
return knownDuplicates.has(dep) && !allowlist.has(dep);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (forbidden.length > 0) {
|
|
57
|
+
const reason = '[ASC Hard-Block] Dependency ' + forbidden.map(function(d){ return "'" + d + "'"; }).join(', ')
|
|
58
|
+
+ ' duplicates standard library or native platform features. '
|
|
59
|
+
+ 'Ladder step 3: use stdlib/native features instead, or add to .asc/dependency-allowlist.json to override.';
|
|
60
|
+
|
|
61
|
+
const output = {
|
|
62
|
+
hookSpecificOutput: {
|
|
63
|
+
hookEventName: 'PreToolUse',
|
|
64
|
+
permissionDecision: 'deny',
|
|
65
|
+
permissionDecisionReason: reason
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
process.stdout.write(JSON.stringify(output));
|
|
69
|
+
process.exit(0);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
} catch (_) {
|
|
73
|
+
// Silent fail to ensure session stability
|
|
74
|
+
}
|
|
75
|
+
process.exit(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function extractDeps(text, pattern) {
|
|
79
|
+
const matches = [];
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
82
|
+
matches.push(match[1]);
|
|
83
|
+
}
|
|
84
|
+
return matches;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadAllowlist() {
|
|
88
|
+
const allowed = new Set();
|
|
89
|
+
const candidates = [
|
|
90
|
+
path.join(process.cwd(), '.asc', 'dependency-allowlist.json'),
|
|
91
|
+
path.join(process.cwd(), '.agents', 'dependency-allowlist.json')
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
95
|
+
try {
|
|
96
|
+
if (fs.existsSync(candidates[i])) {
|
|
97
|
+
const content = JSON.parse(fs.readFileSync(candidates[i], 'utf8'));
|
|
98
|
+
const deps = content.allowedDependencies || content.allowed || {};
|
|
99
|
+
if (Array.isArray(deps)) {
|
|
100
|
+
deps.forEach(function (d) { allowed.add(d); });
|
|
101
|
+
} else if (typeof deps === 'object') {
|
|
102
|
+
Object.keys(deps).forEach(function (d) { allowed.add(d); });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (_) {}
|
|
106
|
+
}
|
|
107
|
+
return allowed;
|
|
108
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Agentic Senior Core — SessionStart hook
|
|
3
|
+
// Injects universal coding rules on every session start.
|
|
4
|
+
// Supports Claude Code, Codex CLI, and GitHub Copilot CLI.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..');
|
|
10
|
+
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
|
|
11
|
+
const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA);
|
|
12
|
+
|
|
13
|
+
let content;
|
|
14
|
+
try {
|
|
15
|
+
content = fs.readFileSync(path.join(pluginRoot, 'AGENTS.md'), 'utf8');
|
|
16
|
+
} catch (e) {
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
if (isCopilot) {
|
|
22
|
+
process.stdout.write(JSON.stringify({ additionalContext: content }));
|
|
23
|
+
} else if (isCodex) {
|
|
24
|
+
process.stdout.write(JSON.stringify({
|
|
25
|
+
systemMessage: 'ASC:ACTIVE',
|
|
26
|
+
hookSpecificOutput: {
|
|
27
|
+
hookEventName: 'SessionStart',
|
|
28
|
+
additionalContext: content,
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
} else {
|
|
32
|
+
process.stdout.write(content);
|
|
33
|
+
}
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// Silent fail — stdout closed/EPIPE at hook exit must not surface as a hook failure
|
|
36
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Agentic Senior Core — SubagentStart hook
|
|
3
|
+
// SessionStart context is parent-thread only and never reaches subagents.
|
|
4
|
+
// This injects the same ruleset into each subagent.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT || path.resolve(__dirname, '..');
|
|
10
|
+
const isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
|
|
11
|
+
const isCodex = !isCopilot && Boolean(process.env.PLUGIN_DATA);
|
|
12
|
+
|
|
13
|
+
let content;
|
|
14
|
+
try {
|
|
15
|
+
content = fs.readFileSync(path.join(pluginRoot, 'AGENTS.md'), 'utf8');
|
|
16
|
+
} catch (e) {
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
if (isCodex) {
|
|
22
|
+
process.stdout.write(JSON.stringify({
|
|
23
|
+
hookSpecificOutput: {
|
|
24
|
+
hookEventName: 'SubagentStart',
|
|
25
|
+
additionalContext: content,
|
|
26
|
+
},
|
|
27
|
+
}));
|
|
28
|
+
} else {
|
|
29
|
+
process.stdout.write(JSON.stringify({
|
|
30
|
+
hookSpecificOutput: {
|
|
31
|
+
hookEventName: 'SubagentStart',
|
|
32
|
+
additionalContext: content,
|
|
33
|
+
},
|
|
34
|
+
}));
|
|
35
|
+
}
|
|
36
|
+
} catch (e) {
|
|
37
|
+
// Silent fail
|
|
38
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc
|
|
3
3
|
description: >
|
|
4
|
-
Universal AI coding rules. Write code like a staff engineer.
|
|
4
|
+
Universal AI coding rules. Write code like a staff engineer. Use this skill when user asks for general coding guidelines, best practices, standard rules, code quality standards, or when acting as a senior/staff software engineer.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Agentic Senior Core
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-adapter
|
|
3
3
|
description: >
|
|
4
|
-
Detect installed AI coding hosts and generate adapter files for the current project.
|
|
4
|
+
Detect installed AI coding hosts and generate adapter files for the current project. Use this skill when user asks to install, configure, setup, or initialize Agentic Senior Core rules, adapter files, or plugins for their IDE (Cursor, Windsurf, Devin, Copilot, Roo, Cline, Aider, Kiro).
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# ASC Adapter
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-add-feature
|
|
3
3
|
description: >
|
|
4
|
-
Structured brownfield workflow. Adapted from QRSPI to prevent context rot and ensure alignment before building.
|
|
4
|
+
Structured brownfield workflow. Adapted from QRSPI to prevent context rot and ensure alignment before building. Use this skill when user asks to add new features, build new endpoints, extend existing functionality, implement new UI components, modify an existing codebase, or work on brownfield development.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Add Feature Workflow
|
|
@@ -10,11 +10,11 @@ Structured brownfield workflow. Adapted from QRSPI to prevent context rot and en
|
|
|
10
10
|
|
|
11
11
|
Grounded in: RPI (Dex Horthy, HumanLayer 2025) with corrections from QRSPI 8-stage evolution (Coding Agents Conference, March 2026). Plan-reading illusion fix and instruction budget constraint applied. Stages 2/5/7 adapted; stages 1/3/4/6/8 skipped as too heavyweight for individual-developer workflow.
|
|
12
12
|
|
|
13
|
-
## Gate Mechanism
|
|
13
|
+
## Gate Mechanism & Scaled Spec Requirement
|
|
14
14
|
|
|
15
15
|
This workflow nudges the agent to stop at each phase boundary, same enforcement tier as the existing decision ladder — not a hard block. Bypasses are logged to the debt ledger.
|
|
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
19
|
To track phase, write to `workflow-gate.json` via the `state_write` MCP tool.
|
|
20
20
|
Format:
|
|
@@ -37,11 +37,12 @@ Format:
|
|
|
37
37
|
## Phase 2: Plan
|
|
38
38
|
|
|
39
39
|
1. On approval of Phase 1, update `workflow-gate.json` phase to `plan`.
|
|
40
|
-
2.
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
43
|
-
5.
|
|
44
|
-
6.
|
|
40
|
+
2. Ensure `docs/PRD.md` or feature brief exists.
|
|
41
|
+
3. Create a numbered, step-by-step implementation plan with specific files, functions, and line references.
|
|
42
|
+
4. Include a "Don't Build" list from the research phase.
|
|
43
|
+
5. **Callout: Plan-Reading Illusion.** Ask the user to explicitly verify the plan against the codebase, not just skim it.
|
|
44
|
+
6. Output the plan.
|
|
45
|
+
7. **STOP and wait for user approval.** Do not implement.
|
|
45
46
|
|
|
46
47
|
## Phase 3: Implement
|
|
47
48
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-audit
|
|
3
3
|
description: >
|
|
4
|
-
Security and architecture audit. Deeper than review, focused on finding vulnerabilities and structural anti-patterns.
|
|
4
|
+
Security and architecture audit. Deeper than review, focused on finding vulnerabilities and structural anti-patterns. Use this skill for deep security audits, architecture reviews, vulnerability scanning, threat modeling, finding OWASP risks (XSS, SQLi), penetration testing, or identifying structural anti-patterns.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Audit Skill
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-debt
|
|
3
3
|
description: >
|
|
4
|
-
Track deferred enforcement violations. When an ASC ladder nudge fires and the shortcut is accepted rather than fixed, log it here for later resolution.
|
|
4
|
+
Track deferred enforcement violations. When an ASC ladder nudge fires and the shortcut is accepted rather than fixed, log it here for later resolution. Use this skill when user wants to log technical debt, track skipped rules, defer a fix, note a code smell for later, or manage deferred violations.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Debt Ledger
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-new-project
|
|
3
3
|
description: >
|
|
4
|
-
Structured greenfield workflow. Prevents building before alignment on what to build.
|
|
4
|
+
Structured greenfield workflow. Prevents building before alignment on what to build. Use this skill for greenfield projects, scaffolding new repositories, bootstrapping apps, starting from scratch, planning new system architectures, or creating a new project.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# New Project Workflow
|
|
8
8
|
|
|
9
9
|
Structured greenfield workflow. Prevents building before alignment on what to build.
|
|
10
10
|
|
|
11
|
-
Grounded in: Spec-Driven Development (SDD) with scaffolding-spec approach.
|
|
11
|
+
Grounded in: Spec-Driven Development (SDD) with scaffolding-spec approach. Core spec documents guide greenfield implementation (`PRD.md`, `Architecture.md`, `Design.md`, `Schema.md`). Once implemented, the code becomes the primary ground truth, while docs are updated on structural changes.
|
|
12
12
|
|
|
13
|
-
## Gate Mechanism
|
|
13
|
+
## Gate Mechanism & 4 Spec Document Requirement
|
|
14
14
|
|
|
15
15
|
This workflow nudges the agent to stop at each phase boundary, same enforcement tier as the existing decision ladder — not a hard block. Bypasses are logged to the debt ledger.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
For greenfield projects (`asc-new-project`), Phase 2 requires creating the **4 Core SDD Documents** in `docs/` or project root:
|
|
18
|
+
1. `PRD.md` — Product intent, goals, non-goals, and user problems.
|
|
19
|
+
2. `Architecture.md` — System structure, module boundaries, and tech stack choices.
|
|
20
|
+
3. `Design.md` — UX / UI layout, component hierarchy, interaction rules.
|
|
21
|
+
4. `Schema.md` — Data contracts, database entities, API endpoints.
|
|
22
|
+
|
|
23
|
+
*Note on Rules:* Coding conventions and project constraints are automatically loaded from global plugin rules (`agentic-senior-core.md`) or workspace `AGENTS.md`. No duplicate `docs/Rules.md` file is required.
|
|
18
24
|
|
|
19
25
|
To track phase, write to `workflow-gate.json` via the `state_write` MCP tool.
|
|
20
26
|
Format:
|
|
@@ -37,16 +43,17 @@ Format:
|
|
|
37
43
|
## Phase 2: Spec (No Implementation Code)
|
|
38
44
|
|
|
39
45
|
1. On approval of Phase 1, update `workflow-gate.json` phase to `plan`.
|
|
40
|
-
2.
|
|
41
|
-
3.
|
|
46
|
+
2. Generate the 4 core SDD documents from templates (`docs/PRD.md`, `docs/Architecture.md`, `docs/Design.md`, `docs/Schema.md`).
|
|
47
|
+
3. Verify exact file naming — check for typos like `Architectyre.md`.
|
|
42
48
|
4. Output specs for review.
|
|
43
|
-
5. **STOP and wait for user approval.** Do not implement.
|
|
49
|
+
5. **STOP and wait for user approval.** Do not implement code.
|
|
44
50
|
|
|
45
51
|
## Phase 3: Implement
|
|
46
52
|
|
|
47
53
|
1. On approval of Phase 2, update `workflow-gate.json` phase to `implement`.
|
|
48
|
-
2.
|
|
49
|
-
3.
|
|
54
|
+
2. Run Anti Context-Blindness check: verify entities/tables mentioned in `Schema.md` or `Architecture.md` align with proposed code targets.
|
|
55
|
+
3. Build against the approved specs. Apply the ASC decision ladder on every file.
|
|
56
|
+
4. Run the decision ladder: does this need to exist? Does stdlib cover it? One function or full module?
|
|
50
57
|
|
|
51
58
|
## Phase 4: Validate
|
|
52
59
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-refactor
|
|
3
3
|
description: >
|
|
4
|
-
Structured refactoring workflow. Preserves existing behavior while improving structure.
|
|
4
|
+
Structured refactoring workflow. Preserves existing behavior while improving structure. Use this skill when user asks to refactor code, clean up code, improve code structure, rewrite legacy code, extract components, reduce technical debt, apply SOLID/DRY principles, or migrate codebases.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Refactor Skill
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-reference
|
|
3
3
|
description: >
|
|
4
|
-
Domain-specific coding rules for testing, API design, database queries, frontend components, infrastructure configs, and service resilience. Load this skill when working on any of these domains.
|
|
4
|
+
Domain-specific coding rules for testing, API design, database queries, frontend components, infrastructure configs, and service resilience. Load this skill when working on any of these domains. Use this skill for guidance on writing unit tests, designing REST/GraphQL APIs, optimizing SQL queries, writing React/frontend components, setting up Docker/CI/CD infrastructure, or improving backend service resilience.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# ASC Domain Reference
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-review
|
|
3
3
|
description: >
|
|
4
|
-
Production-risk code review. Prioritize findings by severity.
|
|
4
|
+
Production-risk code review. Prioritize findings by severity. Use this skill when user asks to review a pull request, perform code review, check for production risks, critique code, analyze recent changes, or provide feedback on code quality.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Review Skill
|
package/README.md
CHANGED
|
@@ -13,16 +13,35 @@
|
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
16
|
+
## How Skills & Hooks Work (Multi-Tier Architecture)
|
|
17
|
+
|
|
18
|
+
Agentic Senior Core operates on a two-tier architecture:
|
|
19
|
+
|
|
20
|
+
1. **Instructional Layer (Universal — Works in 23+ AI Tools)**:
|
|
21
|
+
- **Rules (`AGENTS.md` / `agentic-senior-core.md`) & Skills (`SKILL.md`)** are cross-compatible across **Google Antigravity IDE, Claude Code, Cursor, Windsurf, Copilot, Codex, Kiro, Roo, OpenCode, Zed, Aider, etc.**.
|
|
22
|
+
- **Automatic Skill Triggering**: Agents attempt to detect and load skills if your prompt matches the skill's description (e.g., asking "perform a security audit" loads `asc-audit`).
|
|
23
|
+
- **Manual Skill Triggering (Highly Recommended)**: Explicitly call skills using commands like `/asc-refactor` or `/asc-new-project` for guaranteed execution.
|
|
24
|
+
|
|
25
|
+
2. **Active Enforcement Layer (Hooks — Host-Specific Hard Guardrails)**:
|
|
26
|
+
- **Hard-Block Guardrails**: For tools supporting active hook execution engines (Claude Code, GitHub Copilot CLI, Google Antigravity IDE, Cursor), ASC automatically intercepts tool calls:
|
|
27
|
+
- **PreToolUse Hard Block**: Immediately rejects edits adding stdlib-duplicating dependencies (e.g., `lodash`, `moment`, `uuid`) before execution (`permissionDecision: "deny"`). Escape hatch available via `.asc/dependency-allowlist.json`.
|
|
28
|
+
- **PostToolUse Advisory**: Soft nudges for LOC deltas, spec drift, and workflow gate bypasses.
|
|
29
|
+
|
|
16
30
|
---
|
|
17
31
|
|
|
18
32
|
## Install
|
|
19
33
|
|
|
20
|
-
### Step 1: Install the package
|
|
34
|
+
### Step 1: Install / Update the package
|
|
35
|
+
|
|
36
|
+
To install or forcefully update to the absolute latest version:
|
|
21
37
|
|
|
22
38
|
```bash
|
|
23
|
-
npm install -g @ryuenn3123/agentic-senior-core
|
|
39
|
+
npm install -g @ryuenn3123/agentic-senior-core@latest
|
|
24
40
|
```
|
|
25
41
|
|
|
42
|
+
> [!TIP]
|
|
43
|
+
> **Why not `npm update -g`?** npm's update command aggressively respects SemVer restrictions and local cache, which can trap you on older patch versions. Always use `@latest` to forcefully pull the absolute newest build.
|
|
44
|
+
|
|
26
45
|
### Step 2: Set up for your AI tool
|
|
27
46
|
|
|
28
47
|
<details>
|
package/gemini-extension.json
CHANGED
package/hooks/hooks.json
CHANGED
|
@@ -27,6 +27,29 @@
|
|
|
27
27
|
]
|
|
28
28
|
}
|
|
29
29
|
],
|
|
30
|
+
"PreToolUse": [
|
|
31
|
+
{
|
|
32
|
+
"matcher": "Edit|Write",
|
|
33
|
+
"hooks": [
|
|
34
|
+
{
|
|
35
|
+
"type": "command",
|
|
36
|
+
"if": "Edit(**/package.json)",
|
|
37
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-dependency-gate.js\"; exit 0",
|
|
38
|
+
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\pre-tool-dependency-gate.js\" }",
|
|
39
|
+
"timeout": 5,
|
|
40
|
+
"statusMessage": "ASC Pre-tool dependency check (Edit)..."
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"type": "command",
|
|
44
|
+
"if": "Write(**/package.json)",
|
|
45
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-tool-dependency-gate.js\"; exit 0",
|
|
46
|
+
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\pre-tool-dependency-gate.js\" }",
|
|
47
|
+
"timeout": 5,
|
|
48
|
+
"statusMessage": "ASC Pre-tool dependency check (Write)..."
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
],
|
|
30
53
|
"PostToolUse": [
|
|
31
54
|
{
|
|
32
55
|
"matcher": "Edit|Write",
|
|
@@ -36,7 +59,13 @@
|
|
|
36
59
|
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/post-edit-enforce.js\"; exit 0",
|
|
37
60
|
"commandWindows": "if (Get-Command node -ErrorAction SilentlyContinue) { node \"$env:CLAUDE_PLUGIN_ROOT\\hooks\\post-edit-enforce.js\" }",
|
|
38
61
|
"timeout": 5,
|
|
39
|
-
"statusMessage": "ASC ladder check..."
|
|
62
|
+
"statusMessage": "ASC ladder & spec gate check..."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "prompt",
|
|
66
|
+
"if": "Edit(**/package.json)",
|
|
67
|
+
"prompt": "A new dependency '$ARGUMENTS' was added to package.json. Does it duplicate Node.js standard library or platform features? If yes, return JSON: {\"hookSpecificOutput\": {\"hookEventName\": \"PostToolUse\", \"additionalContext\": \"[ASC Advisory] The newly added dependency appears to duplicate stdlib or platform features. Consider native alternatives.\"}}. If no, return {}.",
|
|
68
|
+
"timeout": 30
|
|
40
69
|
}
|
|
41
70
|
]
|
|
42
71
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Single source of truth for packages that duplicate standard library or native platform features.",
|
|
3
|
+
"duplicates": [
|
|
4
|
+
"lodash",
|
|
5
|
+
"lodash-es",
|
|
6
|
+
"underscore",
|
|
7
|
+
"moment",
|
|
8
|
+
"dayjs",
|
|
9
|
+
"uuid",
|
|
10
|
+
"nanoid",
|
|
11
|
+
"chalk",
|
|
12
|
+
"kleur",
|
|
13
|
+
"colorette",
|
|
14
|
+
"axios",
|
|
15
|
+
"got",
|
|
16
|
+
"node-fetch",
|
|
17
|
+
"superagent",
|
|
18
|
+
"mkdirp",
|
|
19
|
+
"rimraf",
|
|
20
|
+
"del",
|
|
21
|
+
"glob",
|
|
22
|
+
"globby",
|
|
23
|
+
"left-pad",
|
|
24
|
+
"pad-left",
|
|
25
|
+
"is-odd",
|
|
26
|
+
"is-even",
|
|
27
|
+
"is-number",
|
|
28
|
+
"is-string",
|
|
29
|
+
"path-exists",
|
|
30
|
+
"fs-extra"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
const fs = require('fs');
|
|
7
7
|
const path = require('path');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
let STDLIB_DUPLICATES = new Set([
|
|
10
10
|
'lodash', 'lodash-es', 'underscore',
|
|
11
11
|
'moment', 'dayjs',
|
|
12
12
|
'uuid', 'nanoid',
|
|
@@ -19,6 +19,16 @@ const STDLIB_DUPLICATES = new Set([
|
|
|
19
19
|
'path-exists', 'fs-extra',
|
|
20
20
|
]);
|
|
21
21
|
|
|
22
|
+
try {
|
|
23
|
+
const knownPath = path.join(__dirname, 'lib', 'known-duplicates.json');
|
|
24
|
+
if (fs.existsSync(knownPath)) {
|
|
25
|
+
const raw = JSON.parse(fs.readFileSync(knownPath, 'utf8'));
|
|
26
|
+
if (Array.isArray(raw.duplicates)) {
|
|
27
|
+
STDLIB_DUPLICATES = new Set(raw.duplicates);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (_) {}
|
|
31
|
+
|
|
22
32
|
const SOURCE_EXTENSIONS = new Set([
|
|
23
33
|
'js', 'ts', 'mjs', 'cjs', 'jsx', 'tsx',
|
|
24
34
|
'py', 'rb', 'go', 'rs', 'java', 'kt', 'swift', 'cs',
|
|
@@ -38,7 +48,7 @@ process.stdin.on('end', function () {
|
|
|
38
48
|
const filePath = toolInput.file_path || '';
|
|
39
49
|
const findings = [];
|
|
40
50
|
|
|
41
|
-
if (filePath.endsWith('package.json')
|
|
51
|
+
if (filePath.endsWith('package.json')) {
|
|
42
52
|
checkDependencyAddition(toolName, toolInput, findings);
|
|
43
53
|
}
|
|
44
54
|
|
|
@@ -51,6 +61,8 @@ process.stdin.on('end', function () {
|
|
|
51
61
|
}
|
|
52
62
|
}
|
|
53
63
|
|
|
64
|
+
checkLivingDocNudge(filePath, findings);
|
|
65
|
+
|
|
54
66
|
if (ext !== 'md') {
|
|
55
67
|
checkWorkflowGate(toolName, filePath, ext, findings);
|
|
56
68
|
}
|
|
@@ -120,6 +132,16 @@ function checkNewFileSize(toolInput, filePath, findings) {
|
|
|
120
132
|
}
|
|
121
133
|
}
|
|
122
134
|
|
|
135
|
+
function checkLivingDocNudge(filePath, findings) {
|
|
136
|
+
var lower = filePath.toLowerCase();
|
|
137
|
+
if (lower.includes('schema') || lower.includes('migration') || lower.includes('model') || lower.includes('prisma')) {
|
|
138
|
+
findings.push(
|
|
139
|
+
'[ASC Living Doc] Data contract/model modified in ' + path.basename(filePath) + '. '
|
|
140
|
+
+ 'Ensure docs/Schema.md and docs/Architecture.md are kept in sync to prevent spec drift.'
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
123
145
|
function checkWorkflowGate(toolName, filePath, ext, findings) {
|
|
124
146
|
try {
|
|
125
147
|
var pathUtil = require('./path-util.cjs');
|
|
@@ -143,12 +165,56 @@ function checkWorkflowGate(toolName, filePath, ext, findings) {
|
|
|
143
165
|
'Workflow gate bypass: ' + gate.workflow + ' is in ' + gate.phase + ' phase but source code was edited. '
|
|
144
166
|
+ 'Stop and wait for phase approval. If you must proceed, log this bypass to the debt ledger.'
|
|
145
167
|
);
|
|
168
|
+
} else if (gate.phase === 'implement') {
|
|
169
|
+
validateDocSpecs(gate.workflow, findings);
|
|
146
170
|
}
|
|
147
171
|
} catch (_) {
|
|
148
172
|
// Silent fail
|
|
149
173
|
}
|
|
150
174
|
}
|
|
151
175
|
|
|
176
|
+
function validateDocSpecs(workflow, findings) {
|
|
177
|
+
try {
|
|
178
|
+
var cwd = process.cwd();
|
|
179
|
+
var docsDir = path.join(cwd, 'docs');
|
|
180
|
+
|
|
181
|
+
// Anti-typo check for common doc typos (e.g. Architectyre.md)
|
|
182
|
+
var searchDirs = [cwd, docsDir];
|
|
183
|
+
var typoFound = false;
|
|
184
|
+
searchDirs.forEach(function (d) {
|
|
185
|
+
if (fs.existsSync(d)) {
|
|
186
|
+
var files = fs.readdirSync(d);
|
|
187
|
+
files.forEach(function (f) {
|
|
188
|
+
if (f.toLowerCase() !== f && /architect[y|u]re/i.test(f) && f !== 'Architecture.md') {
|
|
189
|
+
findings.push('[ASC Spec Typo Alert] Typo detected in doc filename "' + f + '". Rename to "Architecture.md".');
|
|
190
|
+
typoFound = true;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
if (workflow === 'asc-new-project') {
|
|
197
|
+
var requiredDocs = ['PRD.md', 'Architecture.md', 'Design.md', 'Schema.md'];
|
|
198
|
+
var missing = requiredDocs.filter(function (doc) {
|
|
199
|
+
return !fs.existsSync(path.join(cwd, doc)) && !fs.existsSync(path.join(docsDir, doc));
|
|
200
|
+
});
|
|
201
|
+
if (missing.length > 0) {
|
|
202
|
+
findings.push(
|
|
203
|
+
'[ASC 5-Doc SDD Gate] Greenfield project missing spec document(s): ' + missing.join(', ') + '. '
|
|
204
|
+
+ 'Create them in docs/ to align requirements before continuing implementation.'
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
} else if (workflow === 'asc-add-feature') {
|
|
208
|
+
var hasPrd = fs.existsSync(path.join(cwd, 'PRD.md')) || fs.existsSync(path.join(docsDir, 'PRD.md'));
|
|
209
|
+
if (!hasPrd) {
|
|
210
|
+
findings.push(
|
|
211
|
+
'[ASC SDD Gate] Feature addition missing docs/PRD.md. Define goals and non-goals before coding.'
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch (_) {}
|
|
216
|
+
}
|
|
217
|
+
|
|
152
218
|
function emit(nudge) {
|
|
153
219
|
try {
|
|
154
220
|
var isCopilot = Boolean(process.env.COPILOT_PLUGIN_DATA);
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Agentic Senior Core — PreToolUse dependency gate hook
|
|
3
|
+
// Hard-blocks edits/writes to package.json if new dependencies duplicate stdlib/platform features.
|
|
4
|
+
// Supports Claude Code hookSpecificOutput permissionDecision deny response format.
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
let knownDuplicates = new Set();
|
|
10
|
+
try {
|
|
11
|
+
const knownPath = path.join(__dirname, 'lib', 'known-duplicates.json');
|
|
12
|
+
if (fs.existsSync(knownPath)) {
|
|
13
|
+
const raw = JSON.parse(fs.readFileSync(knownPath, 'utf8'));
|
|
14
|
+
if (Array.isArray(raw.duplicates)) {
|
|
15
|
+
knownDuplicates = new Set(raw.duplicates);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
} catch (_) {
|
|
19
|
+
// Fallback set if JSON loading fails
|
|
20
|
+
knownDuplicates = new Set(['lodash', 'underscore', 'moment', 'dayjs', 'uuid', 'axios', 'rimraf']);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let inputBuffer = '';
|
|
24
|
+
process.stdin.setEncoding('utf8');
|
|
25
|
+
process.stdin.on('data', function (chunk) { inputBuffer += chunk; });
|
|
26
|
+
process.stdin.on('end', function () {
|
|
27
|
+
try {
|
|
28
|
+
const data = JSON.parse(inputBuffer);
|
|
29
|
+
const toolName = data.tool_name || '';
|
|
30
|
+
const toolInput = data.tool_input || {};
|
|
31
|
+
const filePath = toolInput.file_path || '';
|
|
32
|
+
|
|
33
|
+
if (!filePath.endsWith('package.json')) {
|
|
34
|
+
process.exit(0);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const target = toolName === 'Edit' ? (toolInput.new_string || '') : (toolInput.content || '');
|
|
39
|
+
const baseline = toolName === 'Edit' ? (toolInput.old_string || '') : '';
|
|
40
|
+
|
|
41
|
+
const depPattern = /"([^"]+)"\s*:\s*"[~^>=<*]?\d/g;
|
|
42
|
+
const newDeps = extractDeps(target, depPattern);
|
|
43
|
+
const oldDeps = extractDeps(baseline, depPattern);
|
|
44
|
+
|
|
45
|
+
const added = newDeps.filter(function (d) { return oldDeps.indexOf(d) === -1; });
|
|
46
|
+
if (added.length === 0) {
|
|
47
|
+
process.exit(0);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const allowlist = loadAllowlist();
|
|
52
|
+
const forbidden = added.filter(function (dep) {
|
|
53
|
+
return knownDuplicates.has(dep) && !allowlist.has(dep);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (forbidden.length > 0) {
|
|
57
|
+
const reason = '[ASC Hard-Block] Dependency ' + forbidden.map(function(d){ return "'" + d + "'"; }).join(', ')
|
|
58
|
+
+ ' duplicates standard library or native platform features. '
|
|
59
|
+
+ 'Ladder step 3: use stdlib/native features instead, or add to .asc/dependency-allowlist.json to override.';
|
|
60
|
+
|
|
61
|
+
const output = {
|
|
62
|
+
hookSpecificOutput: {
|
|
63
|
+
hookEventName: 'PreToolUse',
|
|
64
|
+
permissionDecision: 'deny',
|
|
65
|
+
permissionDecisionReason: reason
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
process.stdout.write(JSON.stringify(output));
|
|
69
|
+
process.exit(0);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
} catch (_) {
|
|
73
|
+
// Silent fail to ensure session stability
|
|
74
|
+
}
|
|
75
|
+
process.exit(0);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function extractDeps(text, pattern) {
|
|
79
|
+
const matches = [];
|
|
80
|
+
let match;
|
|
81
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
82
|
+
matches.push(match[1]);
|
|
83
|
+
}
|
|
84
|
+
return matches;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadAllowlist() {
|
|
88
|
+
const allowed = new Set();
|
|
89
|
+
const candidates = [
|
|
90
|
+
path.join(process.cwd(), '.asc', 'dependency-allowlist.json'),
|
|
91
|
+
path.join(process.cwd(), '.agents', 'dependency-allowlist.json')
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
95
|
+
try {
|
|
96
|
+
if (fs.existsSync(candidates[i])) {
|
|
97
|
+
const content = JSON.parse(fs.readFileSync(candidates[i], 'utf8'));
|
|
98
|
+
const deps = content.allowedDependencies || content.allowed || {};
|
|
99
|
+
if (Array.isArray(deps)) {
|
|
100
|
+
deps.forEach(function (d) { allowed.add(d); });
|
|
101
|
+
} else if (typeof deps === 'object') {
|
|
102
|
+
Object.keys(deps).forEach(function (d) { allowed.add(d); });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (_) {}
|
|
106
|
+
}
|
|
107
|
+
return allowed;
|
|
108
|
+
}
|
|
@@ -155,7 +155,6 @@ async function installGlobalTarget(targetKey) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
async function copyDirRecursive(src, dest) {
|
|
158
|
-
console.log("DEBUG COPYDIR:", src, "->", dest);
|
|
159
158
|
await fs.mkdir(dest, { recursive: true });
|
|
160
159
|
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
161
160
|
for (const entry of entries) {
|
package/package.json
CHANGED
package/plugin.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-add-feature
|
|
3
3
|
description: >
|
|
4
|
-
Structured brownfield workflow. Adapted from QRSPI to prevent context rot and ensure alignment before building.
|
|
4
|
+
Structured brownfield workflow. Adapted from QRSPI to prevent context rot and ensure alignment before building. Use this skill when user asks to add new features, build new endpoints, extend existing functionality, implement new UI components, modify an existing codebase, or work on brownfield development.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Add Feature Workflow
|
|
@@ -10,11 +10,11 @@ Structured brownfield workflow. Adapted from QRSPI to prevent context rot and en
|
|
|
10
10
|
|
|
11
11
|
Grounded in: RPI (Dex Horthy, HumanLayer 2025) with corrections from QRSPI 8-stage evolution (Coding Agents Conference, March 2026). Plan-reading illusion fix and instruction budget constraint applied. Stages 2/5/7 adapted; stages 1/3/4/6/8 skipped as too heavyweight for individual-developer workflow.
|
|
12
12
|
|
|
13
|
-
## Gate Mechanism
|
|
13
|
+
## Gate Mechanism & Scaled Spec Requirement
|
|
14
14
|
|
|
15
15
|
This workflow nudges the agent to stop at each phase boundary, same enforcement tier as the existing decision ladder — not a hard block. Bypasses are logged to the debt ledger.
|
|
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
19
|
To track phase, write to `workflow-gate.json` via the `state_write` MCP tool.
|
|
20
20
|
Format:
|
|
@@ -37,11 +37,12 @@ Format:
|
|
|
37
37
|
## Phase 2: Plan
|
|
38
38
|
|
|
39
39
|
1. On approval of Phase 1, update `workflow-gate.json` phase to `plan`.
|
|
40
|
-
2.
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
43
|
-
5.
|
|
44
|
-
6.
|
|
40
|
+
2. Ensure `docs/PRD.md` or feature brief exists.
|
|
41
|
+
3. Create a numbered, step-by-step implementation plan with specific files, functions, and line references.
|
|
42
|
+
4. Include a "Don't Build" list from the research phase.
|
|
43
|
+
5. **Callout: Plan-Reading Illusion.** Ask the user to explicitly verify the plan against the codebase, not just skim it.
|
|
44
|
+
6. Output the plan.
|
|
45
|
+
7. **STOP and wait for user approval.** Do not implement.
|
|
45
46
|
|
|
46
47
|
## Phase 3: Implement
|
|
47
48
|
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: asc-new-project
|
|
3
3
|
description: >
|
|
4
|
-
Structured greenfield workflow. Prevents building before alignment on what to build.
|
|
4
|
+
Structured greenfield workflow. Prevents building before alignment on what to build. Use this skill for greenfield projects, scaffolding new repositories, bootstrapping apps, starting from scratch, planning new system architectures, or creating a new project.
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# New Project Workflow
|
|
8
8
|
|
|
9
9
|
Structured greenfield workflow. Prevents building before alignment on what to build.
|
|
10
10
|
|
|
11
|
-
Grounded in: Spec-Driven Development (SDD) with scaffolding-spec approach.
|
|
11
|
+
Grounded in: Spec-Driven Development (SDD) with scaffolding-spec approach. Core spec documents guide greenfield implementation (`PRD.md`, `Architecture.md`, `Design.md`, `Schema.md`). Once implemented, the code becomes the primary ground truth, while docs are updated on structural changes.
|
|
12
12
|
|
|
13
|
-
## Gate Mechanism
|
|
13
|
+
## Gate Mechanism & 4 Spec Document Requirement
|
|
14
14
|
|
|
15
15
|
This workflow nudges the agent to stop at each phase boundary, same enforcement tier as the existing decision ladder — not a hard block. Bypasses are logged to the debt ledger.
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
For greenfield projects (`asc-new-project`), Phase 2 requires creating the **4 Core SDD Documents** in `docs/` or project root:
|
|
18
|
+
1. `PRD.md` — Product intent, goals, non-goals, and user problems.
|
|
19
|
+
2. `Architecture.md` — System structure, module boundaries, and tech stack choices.
|
|
20
|
+
3. `Design.md` — UX / UI layout, component hierarchy, interaction rules.
|
|
21
|
+
4. `Schema.md` — Data contracts, database entities, API endpoints.
|
|
22
|
+
|
|
23
|
+
*Note on Rules:* Coding conventions and project constraints are automatically loaded from global plugin rules (`agentic-senior-core.md`) or workspace `AGENTS.md`. No duplicate `docs/Rules.md` file is required.
|
|
18
24
|
|
|
19
25
|
To track phase, write to `workflow-gate.json` via the `state_write` MCP tool.
|
|
20
26
|
Format:
|
|
@@ -37,16 +43,17 @@ Format:
|
|
|
37
43
|
## Phase 2: Spec (No Implementation Code)
|
|
38
44
|
|
|
39
45
|
1. On approval of Phase 1, update `workflow-gate.json` phase to `plan`.
|
|
40
|
-
2.
|
|
41
|
-
3.
|
|
46
|
+
2. Generate the 4 core SDD documents from templates (`docs/PRD.md`, `docs/Architecture.md`, `docs/Design.md`, `docs/Schema.md`).
|
|
47
|
+
3. Verify exact file naming — check for typos like `Architectyre.md`.
|
|
42
48
|
4. Output specs for review.
|
|
43
|
-
5. **STOP and wait for user approval.** Do not implement.
|
|
49
|
+
5. **STOP and wait for user approval.** Do not implement code.
|
|
44
50
|
|
|
45
51
|
## Phase 3: Implement
|
|
46
52
|
|
|
47
53
|
1. On approval of Phase 2, update `workflow-gate.json` phase to `implement`.
|
|
48
|
-
2.
|
|
49
|
-
3.
|
|
54
|
+
2. Run Anti Context-Blindness check: verify entities/tables mentioned in `Schema.md` or `Architecture.md` align with proposed code targets.
|
|
55
|
+
3. Build against the approved specs. Apply the ASC decision ladder on every file.
|
|
56
|
+
4. Run the decision ladder: does this need to exist? Does stdlib cover it? One function or full module?
|
|
50
57
|
|
|
51
58
|
## Phase 4: Validate
|
|
52
59
|
|