@ryuenn3123/agentic-senior-core 6.16.3 → 6.16.6
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/commands/asc-pentest.md +2 -0
- package/.agents/plugins/agentic-senior-core/commands/asc-pentest.toml +3 -0
- package/.agents/plugins/agentic-senior-core/commands/asc-test.md +1 -1
- package/.agents/plugins/agentic-senior-core/commands/asc-ui.md +1 -1
- package/.agents/plugins/agentic-senior-core/hooks/dedup-gate.js +137 -128
- package/.agents/plugins/agentic-senior-core/hooks/hooks.json +97 -0
- package/.agents/plugins/agentic-senior-core/hooks/ladder-pulse.js +53 -36
- package/.agents/plugins/agentic-senior-core/hooks/lib/known-ui-slop-patterns.json +5 -0
- package/.agents/plugins/agentic-senior-core/hooks/path-util.cjs +118 -4
- package/.agents/plugins/agentic-senior-core/hooks/post-edit-enforce.js +98 -81
- package/.agents/plugins/agentic-senior-core/hooks/pre-tool-dependency-gate.js +168 -191
- package/.agents/plugins/agentic-senior-core/hooks/session-pulse.js +48 -35
- package/.agents/plugins/agentic-senior-core/hooks/session-start.js +62 -11
- package/.agents/plugins/agentic-senior-core/hooks.json +4 -94
- package/.agents/plugins/agentic-senior-core/plugin.json +5 -13
- package/.agents/plugins/agentic-senior-core/rules/agentic-senior-core.md +3 -1
- package/.agents/plugins/agentic-senior-core/skills/asc/SKILL.md +5 -2
- package/.agents/plugins/agentic-senior-core/skills/asc/references/writing-skills.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-develop/SKILL.md +2 -2
- package/.agents/plugins/agentic-senior-core/skills/asc-pentest/SKILL.md +97 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/api-security-idor.md +105 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/dast-baseline.md +78 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/fuzzing-dsl.md +66 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-pentest/references/pentest-task-tree.md +70 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-review/SKILL.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-test/SKILL.md +10 -3
- package/.agents/plugins/agentic-senior-core/skills/asc-test/references/mutation-testing.md +55 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-test/references/performance-budgets.md +67 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-test/references/property-testing.md +72 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-test/references/regression-proof.md +2 -2
- package/.agents/plugins/agentic-senior-core/skills/asc-test/references/tdd-cycle.md +2 -2
- package/.agents/plugins/agentic-senior-core/skills/asc-test/references/uat.md +1 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/SKILL.md +31 -34
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/accessibility.md +2 -2
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/copy-i18n.md +24 -8
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/craft/color-surfaces.md +32 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/craft/composition-patterns.md +299 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/craft/themes-catalog.md +199 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/divergence.md +65 -24
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/experience.md +4 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/genre-calibration.md +93 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/operate.md +31 -1
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/persuade.md +33 -23
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/modes/read.md +4 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/redesign-audit.md +5 -10
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/states-feedback.md +2 -2
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-attio-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-attio.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-cal-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-cal.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-clerk-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-clerk.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-dub-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-dub.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-linear-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-linear.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-marketplace-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-marketplace.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-mintlify-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-mintlify.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-raycast-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-raycast.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-resend-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-resend.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-stripe-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-stripe.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-supabase-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-supabase.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-tldraw-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-tldraw.png +0 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-vercel-dom.json +5 -0
- package/.agents/plugins/agentic-senior-core/skills/asc-ui/references/visuals/real-vercel.png +0 -0
- package/bin/agentic-senior-core.js +19 -4
- package/gemini-extension.json +2 -2
- package/lib/cli/ascx/runtime.mjs +5 -27
- package/lib/cli/ascx/windows-spawn.mjs +348 -0
- package/lib/cli/commands/bootstrap.mjs +56 -0
- package/lib/cli/commands/dedup.mjs +500 -0
- package/lib/cli/commands/git-hook-generator.mjs +3 -2
- package/lib/cli/commands/global.mjs +109 -53
- package/lib/cli/commands/preview.mjs +34 -20
- package/package.json +1 -1
- package/plugin.yaml +3 -1
|
Binary file
|
|
@@ -25,17 +25,20 @@ function printUsage() {
|
|
|
25
25
|
console.log('Plugin install (always-on, no per-project files):');
|
|
26
26
|
console.log(' Claude Code: /plugin marketplace add fatidaprilian/Agentic-Senior-Core');
|
|
27
27
|
console.log(' Codex CLI: codex plugins install agentic-senior-core\n');
|
|
28
|
-
console.log('Global install (
|
|
29
|
-
console.log(' asc global
|
|
28
|
+
console.log('Global install (auto-detects installed tools, zero project files):');
|
|
29
|
+
console.log(' asc global');
|
|
30
|
+
console.log(' asc global --all\n');
|
|
30
31
|
console.log('Adapter install (one file per project):');
|
|
31
32
|
console.log(' asc adapter --cursor --devin --cline --copilot --kiro --continue --zed --aider --kilocode --roo --openhands --windsurf --all\n');
|
|
32
33
|
console.log('Commands:');
|
|
34
|
+
console.log(' global Auto-detect and install rules to user-level (global) locations');
|
|
33
35
|
console.log(' adapter Generate instruction-tier adapter files');
|
|
34
|
-
console.log('
|
|
36
|
+
console.log(' status Show detected IDEs and install hints');
|
|
37
|
+
console.log(' dedup Scan codebase for duplicate code blocks (Rule of Three)');
|
|
38
|
+
console.log(' bootstrap List or seed anti-slop preference rules');
|
|
35
39
|
console.log(' install-git-hook Install Git pre-commit hook for duplicate code enforcement');
|
|
36
40
|
console.log(' uninstall Remove ASC adapter files and git hooks from this project');
|
|
37
41
|
console.log(' clean Remove v4 per-project artifacts');
|
|
38
|
-
console.log(' status Show detected IDEs and install hints');
|
|
39
42
|
console.log(' preview Render zero-dependency multi-browser snapshot or preview');
|
|
40
43
|
console.log(' --version Show version');
|
|
41
44
|
console.log(' --help Show this help');
|
|
@@ -91,6 +94,18 @@ async function main() {
|
|
|
91
94
|
return;
|
|
92
95
|
}
|
|
93
96
|
|
|
97
|
+
if (commandArgument === 'dedup') {
|
|
98
|
+
const { runDedupCommand } = await import('../lib/cli/commands/dedup.mjs');
|
|
99
|
+
await runDedupCommand(commandArguments);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (commandArgument === 'bootstrap') {
|
|
104
|
+
const { runBootstrapCommand } = await import('../lib/cli/commands/bootstrap.mjs');
|
|
105
|
+
await runBootstrapCommand(commandArguments);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
94
109
|
if (commandArgument === 'preview') {
|
|
95
110
|
const { runPreviewCommand } = await import('../lib/cli/commands/preview.mjs');
|
|
96
111
|
await runPreviewCommand(commandArguments);
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-senior-core",
|
|
3
|
-
"version": "6.16.
|
|
3
|
+
"version": "6.16.6",
|
|
4
4
|
"description": "Universal AI coding rules. Write code like a staff engineer.",
|
|
5
5
|
"author": "fatidaprilian",
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
"skills": [
|
|
15
15
|
".agents/plugins/agentic-senior-core/skills/"
|
|
16
16
|
],
|
|
17
|
-
"hooks": ".agents/plugins/agentic-senior-core/hooks.json"
|
|
17
|
+
"hooks": ".agents/plugins/agentic-senior-core/hooks/hooks.json"
|
|
18
18
|
}
|
package/lib/cli/ascx/runtime.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { compressRgOutput } from './adapters/rg.mjs';
|
|
|
10
10
|
import { buildAscxFooter, shouldWriteSafetyTee } from './formatter.mjs';
|
|
11
11
|
import { classifyAscxInvocation, parseAscxCommand } from './lexer.mjs';
|
|
12
12
|
import { writeRawTeeFile } from './tee-writer.mjs';
|
|
13
|
+
import { prepareSpawnInvocation } from './windows-spawn.mjs';
|
|
13
14
|
|
|
14
15
|
const ADAPTERS = {
|
|
15
16
|
'git-diff': compressGitDiffOutput,
|
|
@@ -21,20 +22,6 @@ const ADAPTERS = {
|
|
|
21
22
|
'rg': compressRgOutput,
|
|
22
23
|
};
|
|
23
24
|
|
|
24
|
-
function buildCommandEnvironment(baseEnvironment, parsedCommand) {
|
|
25
|
-
const commandEnvironment = { ...baseEnvironment };
|
|
26
|
-
|
|
27
|
-
for (const assignment of parsedCommand.environment) {
|
|
28
|
-
const separatorIndex = assignment.indexOf('=');
|
|
29
|
-
const variableName = assignment.slice(0, separatorIndex);
|
|
30
|
-
const variableValue = assignment.slice(separatorIndex + 1);
|
|
31
|
-
|
|
32
|
-
commandEnvironment[variableName] = variableValue;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return commandEnvironment;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
25
|
function runSpawnedCommand(parsedCommand, options = {}) {
|
|
39
26
|
const {
|
|
40
27
|
cwd = process.cwd(),
|
|
@@ -43,19 +30,10 @@ function runSpawnedCommand(parsedCommand, options = {}) {
|
|
|
43
30
|
} = options;
|
|
44
31
|
|
|
45
32
|
return new Promise((resolve) => {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
? parsedCommand.commandText
|
|
51
|
-
: parsedCommand.executable;
|
|
52
|
-
const args = shell ? [] : parsedCommand.args;
|
|
53
|
-
const childProcess = spawn(executable, args, {
|
|
54
|
-
cwd,
|
|
55
|
-
env: buildCommandEnvironment(env, parsedCommand),
|
|
56
|
-
shell: shell || useWindowsNpmShell,
|
|
57
|
-
windowsHide: true,
|
|
58
|
-
});
|
|
33
|
+
const prepared = prepareSpawnInvocation(parsedCommand, { cwd, env, shell });
|
|
34
|
+
const childProcess = prepared.args === undefined
|
|
35
|
+
? spawn(prepared.command, prepared.options)
|
|
36
|
+
: spawn(prepared.command, prepared.args, prepared.options);
|
|
59
37
|
let stdout = '';
|
|
60
38
|
let stderr = '';
|
|
61
39
|
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const REPOSITORY_ROOT = path.resolve(CURRENT_DIR, '..', '..', '..');
|
|
7
|
+
const ASC_BIN_PATH = path.join(REPOSITORY_ROOT, 'bin', 'agentic-senior-core.js');
|
|
8
|
+
const ASCX_BIN_PATH = path.join(REPOSITORY_ROOT, 'bin', 'ascx.js');
|
|
9
|
+
|
|
10
|
+
const CMD_BUILTINS = new Set([
|
|
11
|
+
'assoc', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date', 'del', 'dir',
|
|
12
|
+
'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto', 'if', 'md',
|
|
13
|
+
'mkdir', 'mklink', 'move', 'path', 'pause', 'popd', 'prompt', 'pushd', 'rd',
|
|
14
|
+
'rem', 'ren', 'rename', 'rmdir', 'set', 'setlocal', 'shift', 'start', 'time',
|
|
15
|
+
'title', 'type', 'ver', 'verify', 'vol',
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const POWERSHELL_CMDLET_PATTERN = /^[A-Za-z]+-[A-Za-z0-9]+$/;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Finds the canonical PATH key in an environment object (handles Windows `Path` vs `PATH`).
|
|
22
|
+
*/
|
|
23
|
+
export function getPathEnvKey(env = process.env) {
|
|
24
|
+
for (const key of Object.keys(env)) {
|
|
25
|
+
if (key.toUpperCase() === 'PATH') {
|
|
26
|
+
return key;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return process.platform === 'win32' ? 'Path' : 'PATH';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Collects local `node_modules/.bin` directories walking up from `cwd`.
|
|
34
|
+
*/
|
|
35
|
+
export function collectLocalNodeBinDirs(cwd) {
|
|
36
|
+
const dirs = [];
|
|
37
|
+
let current = path.resolve(cwd);
|
|
38
|
+
|
|
39
|
+
while (true) {
|
|
40
|
+
const candidate = path.join(current, 'node_modules', '.bin');
|
|
41
|
+
if (fs.existsSync(candidate)) {
|
|
42
|
+
dirs.push(candidate);
|
|
43
|
+
}
|
|
44
|
+
const parent = path.dirname(current);
|
|
45
|
+
if (parent === current) {
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
current = parent;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return dirs;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns a copy of `env` with local `node_modules/.bin` prepended to PATH without duplicating Path/PATH keys.
|
|
56
|
+
*/
|
|
57
|
+
export function buildAugmentedEnvironment(baseEnv, assignments = [], cwd = process.cwd()) {
|
|
58
|
+
const env = { ...baseEnv };
|
|
59
|
+
const pathKey = getPathEnvKey(env);
|
|
60
|
+
const localBins = collectLocalNodeBinDirs(cwd);
|
|
61
|
+
|
|
62
|
+
if (localBins.length > 0) {
|
|
63
|
+
const currentPath = env[pathKey] || '';
|
|
64
|
+
const existingSet = new Set(
|
|
65
|
+
currentPath.split(path.delimiter).map(p => (process.platform === 'win32' ? p.toLowerCase() : p))
|
|
66
|
+
);
|
|
67
|
+
const toPrepend = localBins.filter(
|
|
68
|
+
dir => !existingSet.has(process.platform === 'win32' ? dir.toLowerCase() : dir)
|
|
69
|
+
);
|
|
70
|
+
if (toPrepend.length > 0) {
|
|
71
|
+
env[pathKey] = [...toPrepend, currentPath].filter(Boolean).join(path.delimiter);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (Array.isArray(assignments)) {
|
|
76
|
+
for (const assignment of assignments) {
|
|
77
|
+
const separatorIndex = assignment.indexOf('=');
|
|
78
|
+
if (separatorIndex === -1) continue;
|
|
79
|
+
const variableName = assignment.slice(0, separatorIndex);
|
|
80
|
+
const variableValue = assignment.slice(separatorIndex + 1);
|
|
81
|
+
if (variableName.toUpperCase() === 'PATH') {
|
|
82
|
+
env[pathKey] = variableValue;
|
|
83
|
+
} else {
|
|
84
|
+
env[variableName] = variableValue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else if (assignments && typeof assignments === 'object') {
|
|
88
|
+
for (const [variableName, variableValue] of Object.entries(assignments)) {
|
|
89
|
+
if (variableName.toUpperCase() === 'PATH') {
|
|
90
|
+
env[pathKey] = String(variableValue);
|
|
91
|
+
} else {
|
|
92
|
+
env[variableName] = String(variableValue);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return env;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Resolves an executable on Windows across PATH and PATHEXT, prioritizing native `.exe`/`.com` before `.cmd`/`.bat`/`.ps1`.
|
|
102
|
+
*/
|
|
103
|
+
export function resolveWindowsExecutable(executable, env = process.env, cwd = process.cwd()) {
|
|
104
|
+
if (!executable) return null;
|
|
105
|
+
|
|
106
|
+
const hasPathSep = executable.includes('/') || executable.includes('\\');
|
|
107
|
+
const rawPathExt = env.PATHEXT || '.COM;.EXE;.BAT;.CMD;.PS1';
|
|
108
|
+
const parsedExts = rawPathExt
|
|
109
|
+
.split(';')
|
|
110
|
+
.map(ext => ext.trim().toLowerCase())
|
|
111
|
+
.filter(Boolean);
|
|
112
|
+
const priorityExts = ['.exe', '.com', '.cmd', '.bat', '.ps1', ...parsedExts.filter(
|
|
113
|
+
e => !['.exe', '.com', '.cmd', '.bat', '.ps1'].includes(e)
|
|
114
|
+
)];
|
|
115
|
+
|
|
116
|
+
const checkCandidateBase = (basePath) => {
|
|
117
|
+
const lowerExt = path.extname(basePath).toLowerCase();
|
|
118
|
+
if (lowerExt && fs.existsSync(basePath) && fs.statSync(basePath).isFile()) {
|
|
119
|
+
return basePath;
|
|
120
|
+
}
|
|
121
|
+
for (const ext of priorityExts) {
|
|
122
|
+
const withExt = `${basePath}${ext}`;
|
|
123
|
+
try {
|
|
124
|
+
if (fs.existsSync(withExt) && fs.statSync(withExt).isFile()) {
|
|
125
|
+
return withExt;
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
// Ignore inaccessible paths
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
if (hasPathSep || path.isAbsolute(executable)) {
|
|
135
|
+
const fullBase = path.isAbsolute(executable) ? executable : path.resolve(cwd, executable);
|
|
136
|
+
return checkCandidateBase(fullBase);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const pathKey = getPathEnvKey(env);
|
|
140
|
+
const searchDirs = (env[pathKey] || '')
|
|
141
|
+
.split(path.delimiter)
|
|
142
|
+
.map(dir => dir.replace(/^"+|"+$/g, '').trim())
|
|
143
|
+
.filter(Boolean);
|
|
144
|
+
|
|
145
|
+
for (const dir of searchDirs) {
|
|
146
|
+
const resolved = checkCandidateBase(path.join(dir, executable));
|
|
147
|
+
if (resolved) {
|
|
148
|
+
return resolved;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Safely quotes a single argument for `cmd.exe /d /s /c "..."` execution on Windows,
|
|
157
|
+
* preventing CVE-2024-27980 batch argument injection and `%VAR%` expansion.
|
|
158
|
+
*/
|
|
159
|
+
export function escapeCmdArg(value, isExecutable = false) {
|
|
160
|
+
const str = String(value);
|
|
161
|
+
if (str.length === 0) {
|
|
162
|
+
return '""';
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Escape % so cmd.exe does not expand %TEMP% or strip %tokens
|
|
166
|
+
let escaped = str.replace(/%/g, '%%');
|
|
167
|
+
|
|
168
|
+
if (isExecutable) {
|
|
169
|
+
return /[\s&|<>()^"!;,]/.test(escaped) ? `"${escaped.replace(/"/g, '')}"` : escaped;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const needsQuotes = /[\s&|<>()^"!;,]/.test(escaped);
|
|
173
|
+
if (!needsQuotes) {
|
|
174
|
+
return escaped;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Escape trailing backslashes before closing quote and internal double quotes
|
|
178
|
+
escaped = escaped.replace(/(\\*)"/g, '$1$1\\"');
|
|
179
|
+
escaped = escaped.replace(/(\\*)$/, '$1$1');
|
|
180
|
+
return `"${escaped}"`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Safely quotes an argument for PowerShell `-Command` invocation.
|
|
185
|
+
*/
|
|
186
|
+
function escapePowerShellArg(value) {
|
|
187
|
+
const str = String(value);
|
|
188
|
+
if (/^[A-Za-z0-9_./:\\-]+$/.test(str)) {
|
|
189
|
+
return str;
|
|
190
|
+
}
|
|
191
|
+
return `'${str.replace(/'/g, "''")}'`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Quotes tokens for POSIX / Windows `shell: true` command strings when shell operators are present.
|
|
196
|
+
*/
|
|
197
|
+
const SHELL_OPERATORS = new Set(['|', '||', '&', '&&', ';', '>', '>>', '<', '2>', '2>>']);
|
|
198
|
+
|
|
199
|
+
export function buildShellCommandString(executable, args) {
|
|
200
|
+
const quoteToken = (token) => {
|
|
201
|
+
if (SHELL_OPERATORS.has(token) || /^(?:[12]?>|[12]?>>|<)/.test(token)) {
|
|
202
|
+
return token;
|
|
203
|
+
}
|
|
204
|
+
if (!/[\s"'`$\\]/.test(token)) {
|
|
205
|
+
return token;
|
|
206
|
+
}
|
|
207
|
+
if (process.platform === 'win32') {
|
|
208
|
+
return `"${token.replace(/"/g, '\\"')}"`;
|
|
209
|
+
}
|
|
210
|
+
return `'${token.replace(/'/g, "'\\''")}'`;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
return [quoteToken(executable), ...args.map(quoteToken)].join(' ');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Builds spawn parameters `{ command, args, options }` that work seamlessly across Windows and POSIX
|
|
218
|
+
* without triggering Node DEP0190 warnings or CVE-2024-27980 `.cmd` EINVAL/ENOENT failures.
|
|
219
|
+
*/
|
|
220
|
+
export function prepareSpawnInvocation(parsedCommand, { cwd = process.cwd(), env = process.env, shell = false } = {}) {
|
|
221
|
+
const augmentedEnv = buildAugmentedEnvironment(env, parsedCommand.environment, cwd);
|
|
222
|
+
const rawExe = parsedCommand.executable;
|
|
223
|
+
const rawArgs = parsedCommand.args;
|
|
224
|
+
|
|
225
|
+
// Direct fast-path for ASC's own binaries on all platforms (zero shim overhead)
|
|
226
|
+
if (!shell) {
|
|
227
|
+
const normalizedBase = rawExe.toLowerCase();
|
|
228
|
+
if (normalizedBase === 'asc' || normalizedBase === 'agentic-senior-core') {
|
|
229
|
+
return {
|
|
230
|
+
command: process.execPath,
|
|
231
|
+
args: [ASC_BIN_PATH, ...rawArgs],
|
|
232
|
+
options: { cwd, env: augmentedEnv, shell: false, windowsHide: true },
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (normalizedBase === 'ascx') {
|
|
236
|
+
return {
|
|
237
|
+
command: process.execPath,
|
|
238
|
+
args: [ASCX_BIN_PATH, ...rawArgs],
|
|
239
|
+
options: { cwd, env: augmentedEnv, shell: false, windowsHide: true },
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Explicit shell pipeline / redirect mode: omit args array to avoid Node DEP0190
|
|
245
|
+
if (shell) {
|
|
246
|
+
const commandLine = buildShellCommandString(rawExe, rawArgs);
|
|
247
|
+
return {
|
|
248
|
+
command: commandLine,
|
|
249
|
+
args: undefined,
|
|
250
|
+
options: { cwd, env: augmentedEnv, shell: true, windowsHide: true },
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (process.platform !== 'win32') {
|
|
255
|
+
return {
|
|
256
|
+
command: rawExe,
|
|
257
|
+
args: rawArgs,
|
|
258
|
+
options: { cwd, env: augmentedEnv, shell: false, windowsHide: true },
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Windows resolution: native .exe/.com, .cmd/.bat shims, .ps1 scripts, CMD builtins, and PowerShell cmdlets
|
|
263
|
+
const resolved = resolveWindowsExecutable(rawExe, augmentedEnv, cwd);
|
|
264
|
+
const comspec = augmentedEnv.ComSpec || augmentedEnv.COMSPEC || 'cmd.exe';
|
|
265
|
+
|
|
266
|
+
if (resolved) {
|
|
267
|
+
const ext = path.extname(resolved).toLowerCase();
|
|
268
|
+
|
|
269
|
+
if (ext === '.exe' || ext === '.com') {
|
|
270
|
+
return {
|
|
271
|
+
command: resolved,
|
|
272
|
+
args: rawArgs,
|
|
273
|
+
options: { cwd, env: augmentedEnv, shell: false, windowsHide: true },
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (ext === '.js' || ext === '.mjs' || ext === '.cjs') {
|
|
278
|
+
return {
|
|
279
|
+
command: process.execPath,
|
|
280
|
+
args: [resolved, ...rawArgs],
|
|
281
|
+
options: { cwd, env: augmentedEnv, shell: false, windowsHide: true },
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (ext === '.cmd' || ext === '.bat') {
|
|
286
|
+
const innerCmd = [escapeCmdArg(resolved, true), ...rawArgs.map(a => escapeCmdArg(a, false))].join(' ');
|
|
287
|
+
return {
|
|
288
|
+
command: comspec,
|
|
289
|
+
args: ['/d', '/s', '/c', `"${innerCmd}"`],
|
|
290
|
+
options: {
|
|
291
|
+
cwd,
|
|
292
|
+
env: augmentedEnv,
|
|
293
|
+
shell: false,
|
|
294
|
+
windowsVerbatimArguments: true,
|
|
295
|
+
windowsHide: true,
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (ext === '.ps1') {
|
|
301
|
+
const psExe = resolveWindowsExecutable('pwsh', augmentedEnv, cwd)
|
|
302
|
+
|| resolveWindowsExecutable('powershell', augmentedEnv, cwd)
|
|
303
|
+
|| 'powershell.exe';
|
|
304
|
+
return {
|
|
305
|
+
command: psExe,
|
|
306
|
+
args: ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', resolved, ...rawArgs],
|
|
307
|
+
options: { cwd, env: augmentedEnv, shell: false, windowsHide: true },
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Check CMD builtins (dir, echo, type, where, etc.)
|
|
313
|
+
if (CMD_BUILTINS.has(rawExe.toLowerCase())) {
|
|
314
|
+
const innerCmd = [rawExe, ...rawArgs.map(a => escapeCmdArg(a, false))].join(' ');
|
|
315
|
+
return {
|
|
316
|
+
command: comspec,
|
|
317
|
+
args: ['/d', '/s', '/c', `"${innerCmd}"`],
|
|
318
|
+
options: {
|
|
319
|
+
cwd,
|
|
320
|
+
env: augmentedEnv,
|
|
321
|
+
shell: false,
|
|
322
|
+
windowsVerbatimArguments: true,
|
|
323
|
+
windowsHide: true,
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check PowerShell Verb-Noun cmdlets (Get-ChildItem, Select-String, Test-Path, etc.)
|
|
329
|
+
if (POWERSHELL_CMDLET_PATTERN.test(rawExe)) {
|
|
330
|
+
const psExe = resolveWindowsExecutable('pwsh', augmentedEnv, cwd)
|
|
331
|
+
|| resolveWindowsExecutable('powershell', augmentedEnv, cwd)
|
|
332
|
+
|| 'powershell.exe';
|
|
333
|
+
const psCommand = [rawExe, ...rawArgs.map(escapePowerShellArg)].join(' ');
|
|
334
|
+
return {
|
|
335
|
+
command: psExe,
|
|
336
|
+
args: ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', psCommand],
|
|
337
|
+
options: { cwd, env: augmentedEnv, shell: false, windowsHide: true },
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Fallback for unknown binaries (preserves clean ENOENT behavior)
|
|
342
|
+
return {
|
|
343
|
+
command: rawExe,
|
|
344
|
+
args: rawArgs,
|
|
345
|
+
options: { cwd, env: augmentedEnv, shell: false, windowsHide: true },
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getSlopCatalog, bootstrapPreferences } from '../../core/bootstrap-wizard.mjs';
|
|
2
|
+
import { getPreferencesFilePath } from '../../core/adaptive-preferences.mjs';
|
|
3
|
+
|
|
4
|
+
const RECOMMENDED_PATTERN_IDS = [
|
|
5
|
+
'ui-gradient-purple-blue',
|
|
6
|
+
'ui-generic-card-shadcn',
|
|
7
|
+
'ui-card-in-card-nesting',
|
|
8
|
+
'ui-ghost-card',
|
|
9
|
+
'ui-eyebrow-tracked-caps',
|
|
10
|
+
'ui-gradient-text-cliche',
|
|
11
|
+
'ui-transition-all-layout-cost',
|
|
12
|
+
'ui-stripped-focus-ring',
|
|
13
|
+
'ui-viewport-100vh-mobile',
|
|
14
|
+
'ui-missing-reduced-motion',
|
|
15
|
+
'copy-marketing-buzzwords',
|
|
16
|
+
'copy-ai-buzzwords',
|
|
17
|
+
'copy-cadence-tricolon',
|
|
18
|
+
'copy-cadence-rebuttal',
|
|
19
|
+
'copy-hybrid-neologism',
|
|
20
|
+
'code-decorative-banner',
|
|
21
|
+
'code-step-narration',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
export async function runBootstrapCommand(args = []) {
|
|
25
|
+
const catalog = getSlopCatalog();
|
|
26
|
+
const isHelp = args.includes('--help') || args.includes('-h');
|
|
27
|
+
const isList = args.includes('--list');
|
|
28
|
+
const isRecommended = args.includes('--recommended') || args.includes('--all');
|
|
29
|
+
const scope = (args.includes('--global') || args.includes('--user')) ? 'user' : 'project';
|
|
30
|
+
const explicitIds = args.filter(a => !a.startsWith('-'));
|
|
31
|
+
|
|
32
|
+
if (isHelp || isList || (!isRecommended && explicitIds.length === 0)) {
|
|
33
|
+
console.log('Agentic Senior Core -- Anti-Slop Preference Bootstrap\n');
|
|
34
|
+
console.log('Usage:');
|
|
35
|
+
console.log(' asc bootstrap --list List all curated anti-slop pattern IDs');
|
|
36
|
+
console.log(' asc bootstrap --recommended [--global] Seed core UI & prose anti-slop rules');
|
|
37
|
+
console.log(' asc bootstrap <id1> <id2> [--global] Seed specific pattern IDs\n');
|
|
38
|
+
console.log(`Available catalog patterns (${catalog.length}):`);
|
|
39
|
+
for (const item of catalog) {
|
|
40
|
+
console.log(` ${item.id.padEnd(32)} ${item.message}`);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const selectedIds = isRecommended ? RECOMMENDED_PATTERN_IDS : explicitIds;
|
|
46
|
+
const results = bootstrapPreferences({
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
scope,
|
|
49
|
+
selectedIds,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const targetFile = getPreferencesFilePath({ cwd: process.cwd(), scope });
|
|
53
|
+
console.log(`Seeded ${results.length} preference rule(s) into ${scope} scope:`);
|
|
54
|
+
console.log(` ${targetFile}`);
|
|
55
|
+
}
|
|
56
|
+
|