@mthanhlm/autodev 0.1.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/LICENSE +21 -0
- package/PUBLISH.md +75 -0
- package/README.md +53 -0
- package/autodev/bin/autodev-tools.cjs +346 -0
- package/autodev/templates/config.json +20 -0
- package/autodev/templates/plan.md +25 -0
- package/autodev/templates/project.md +21 -0
- package/autodev/templates/requirements.md +12 -0
- package/autodev/templates/roadmap.md +17 -0
- package/autodev/templates/state.md +10 -0
- package/autodev/templates/summary.md +16 -0
- package/autodev/templates/uat.md +15 -0
- package/autodev/workflows/execute-phase.md +50 -0
- package/autodev/workflows/help.md +57 -0
- package/autodev/workflows/new-project.md +62 -0
- package/autodev/workflows/plan-phase.md +54 -0
- package/autodev/workflows/progress.md +15 -0
- package/autodev/workflows/verify-work.md +39 -0
- package/bin/install.js +565 -0
- package/commands/autodev/execute-phase.md +26 -0
- package/commands/autodev/help.md +18 -0
- package/commands/autodev/new-project.md +28 -0
- package/commands/autodev/plan-phase.md +25 -0
- package/commands/autodev/progress.md +18 -0
- package/commands/autodev/verify-work.md +24 -0
- package/hooks/autodev-context-monitor.js +66 -0
- package/hooks/autodev-git-guard.js +55 -0
- package/hooks/autodev-phase-boundary.sh +20 -0
- package/hooks/autodev-prompt-guard.js +55 -0
- package/hooks/autodev-read-guard.js +49 -0
- package/hooks/autodev-session-state.sh +22 -0
- package/hooks/autodev-statusline.js +45 -0
- package/hooks/autodev-workflow-guard.js +51 -0
- package/package.json +38 -0
- package/scripts/run-tests.cjs +23 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const WARNING_THRESHOLD = 35;
|
|
8
|
+
const CRITICAL_THRESHOLD = 20;
|
|
9
|
+
|
|
10
|
+
function readProjectConfig(cwd) {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(fs.readFileSync(path.join(cwd, '.autodev', 'config.json'), 'utf8'));
|
|
13
|
+
} catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let input = '';
|
|
19
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 10000);
|
|
20
|
+
process.stdin.setEncoding('utf8');
|
|
21
|
+
process.stdin.on('data', chunk => {
|
|
22
|
+
input += chunk;
|
|
23
|
+
});
|
|
24
|
+
process.stdin.on('end', () => {
|
|
25
|
+
clearTimeout(stdinTimeout);
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const data = JSON.parse(input);
|
|
29
|
+
const cwd = data.cwd || process.cwd();
|
|
30
|
+
const config = readProjectConfig(cwd);
|
|
31
|
+
if (config && config.hooks?.context_warnings === false) {
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sessionId = data.session_id;
|
|
36
|
+
if (!sessionId || /[/\\]|\.\./.test(sessionId)) {
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const metricsPath = path.join(os.tmpdir(), `claude-ctx-${sessionId}.json`);
|
|
41
|
+
if (!fs.existsSync(metricsPath)) {
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const metrics = JSON.parse(fs.readFileSync(metricsPath, 'utf8'));
|
|
46
|
+
const remaining = metrics.remaining_percentage;
|
|
47
|
+
const used = metrics.used_pct;
|
|
48
|
+
|
|
49
|
+
if (typeof remaining !== 'number' || remaining > WARNING_THRESHOLD) {
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const additionalContext = remaining <= CRITICAL_THRESHOLD
|
|
54
|
+
? `CONTEXT CRITICAL: usage is at ${used}%. Wrap up the current step, avoid new exploration, and tell the user context is low.`
|
|
55
|
+
: `CONTEXT WARNING: usage is at ${used}%. Finish the current step cleanly and avoid starting new complex work.`;
|
|
56
|
+
|
|
57
|
+
process.stdout.write(JSON.stringify({
|
|
58
|
+
hookSpecificOutput: {
|
|
59
|
+
hookEventName: 'PostToolUse',
|
|
60
|
+
additionalContext
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
} catch {
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function readProjectConfig(cwd) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(fs.readFileSync(path.join(cwd, '.autodev', 'config.json'), 'utf8'));
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function shouldBlock(command) {
|
|
15
|
+
const blockedPatterns = [
|
|
16
|
+
/\bgit(?:\s+-C\s+\S+|\s+--[^\s]+(?:=\S+)?|\s+-c\s+\S+)*\s+init\b/,
|
|
17
|
+
/\bgit(?:\s+-C\s+\S+|\s+--[^\s]+(?:=\S+)?|\s+-c\s+\S+)*\s+add\b/,
|
|
18
|
+
/\bgit(?:\s+-C\s+\S+|\s+--[^\s]+(?:=\S+)?|\s+-c\s+\S+)*\s+commit\b/,
|
|
19
|
+
/\bgit(?:\s+-C\s+\S+|\s+--[^\s]+(?:=\S+)?|\s+-c\s+\S+)*\s+(checkout|switch|merge|rebase|worktree|push|pull|stash|reset|fetch|clone|clean|cherry-pick)\b/,
|
|
20
|
+
/\bgit(?:\s+-C\s+\S+|\s+--[^\s]+(?:=\S+)?|\s+-c\s+\S+)*\s+branch\b(?!\s+--(?:show-current|list)\b)/,
|
|
21
|
+
/\bgit(?:\s+-C\s+\S+|\s+--[^\s]+(?:=\S+)?|\s+-c\s+\S+)*\s+tag\b(?!\s+-l\b)/,
|
|
22
|
+
/\bgit(?:\s+-C\s+\S+|\s+--[^\s]+(?:=\S+)?|\s+-c\s+\S+)*\s+remote\s+(add|remove|rename|set-url)\b/
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
return blockedPatterns.some(pattern => pattern.test(command));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let input = '';
|
|
29
|
+
try {
|
|
30
|
+
input = fs.readFileSync(0, 'utf8');
|
|
31
|
+
const data = JSON.parse(input);
|
|
32
|
+
if (data.tool_name !== 'Bash') {
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const cwd = data.cwd || process.cwd();
|
|
37
|
+
const config = readProjectConfig(cwd);
|
|
38
|
+
if (!config || config.git?.mode !== 'read-only' || config.hooks?.git_guard === false) {
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const command = data.tool_input?.command || '';
|
|
43
|
+
if (!command || !shouldBlock(command)) {
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fs.writeSync(1, `${JSON.stringify({
|
|
48
|
+
decision: 'block',
|
|
49
|
+
reason:
|
|
50
|
+
'autodev git policy is read-only. Allowed examples: git status, git diff, git log, git show, git rev-parse, git ls-files, git branch --show-current.'
|
|
51
|
+
})}\n`);
|
|
52
|
+
process.exit(2);
|
|
53
|
+
} catch {
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
CONFIG=".autodev/config.json"
|
|
4
|
+
|
|
5
|
+
if [ ! -f "$CONFIG" ]; then
|
|
6
|
+
exit 0
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
ENABLED=$(node -e "try{const c=require('./.autodev/config.json');process.stdout.write(c.hooks?.phase_boundary===false?'0':'1')}catch{process.stdout.write('0')}" 2>/dev/null)
|
|
10
|
+
if [ "$ENABLED" != "1" ]; then
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
INPUT=$(cat)
|
|
15
|
+
FILE=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).tool_input?.file_path||'')}catch{}})" 2>/dev/null)
|
|
16
|
+
|
|
17
|
+
if [[ "$FILE" == *.autodev/* ]] || [[ "$FILE" == .autodev/* ]]; then
|
|
18
|
+
echo ".autodev file updated: $FILE"
|
|
19
|
+
echo "Check whether STATE.md or the current summary also needs an update."
|
|
20
|
+
fi
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const PATTERNS = [
|
|
6
|
+
/ignore\s+(all\s+)?previous\s+instructions/i,
|
|
7
|
+
/ignore\s+(all\s+)?above\s+instructions/i,
|
|
8
|
+
/disregard\s+(all\s+)?previous/i,
|
|
9
|
+
/override\s+(system|previous)\s+(prompt|instructions)/i,
|
|
10
|
+
/you\s+are\s+now\s+(?:a|an|the)\s+/i,
|
|
11
|
+
/<\/?(?:system|assistant|human)>/i
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
let input = '';
|
|
15
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
16
|
+
process.stdin.setEncoding('utf8');
|
|
17
|
+
process.stdin.on('data', chunk => {
|
|
18
|
+
input += chunk;
|
|
19
|
+
});
|
|
20
|
+
process.stdin.on('end', () => {
|
|
21
|
+
clearTimeout(stdinTimeout);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(input);
|
|
25
|
+
if (!['Write', 'Edit'].includes(data.tool_name)) {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const filePath = data.tool_input?.file_path || '';
|
|
30
|
+
if (!filePath.includes('.autodev/')) {
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const content = data.tool_input?.content || data.tool_input?.new_string || '';
|
|
35
|
+
if (!content) {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const findings = PATTERNS.filter(pattern => pattern.test(content));
|
|
40
|
+
if (findings.length === 0) {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
process.stdout.write(JSON.stringify({
|
|
45
|
+
hookSpecificOutput: {
|
|
46
|
+
hookEventName: 'PreToolUse',
|
|
47
|
+
additionalContext:
|
|
48
|
+
`PROMPT WARNING: content being written to ${path.basename(filePath)} looks like instruction text. ` +
|
|
49
|
+
'Review it before treating it as trusted workflow state.'
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
} catch {
|
|
53
|
+
process.exit(0);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function readProjectConfig(cwd) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(fs.readFileSync(path.join(cwd, '.autodev', 'config.json'), 'utf8'));
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let input = '';
|
|
15
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
16
|
+
process.stdin.setEncoding('utf8');
|
|
17
|
+
process.stdin.on('data', chunk => {
|
|
18
|
+
input += chunk;
|
|
19
|
+
});
|
|
20
|
+
process.stdin.on('end', () => {
|
|
21
|
+
clearTimeout(stdinTimeout);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(input);
|
|
25
|
+
if (!['Write', 'Edit'].includes(data.tool_name)) {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const config = readProjectConfig(data.cwd || process.cwd());
|
|
30
|
+
if (config && config.hooks?.read_guard === false) {
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const filePath = data.tool_input?.file_path || '';
|
|
35
|
+
if (!filePath || !fs.existsSync(filePath)) {
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
process.stdout.write(JSON.stringify({
|
|
40
|
+
hookSpecificOutput: {
|
|
41
|
+
hookEventName: 'PreToolUse',
|
|
42
|
+
additionalContext:
|
|
43
|
+
`READ BEFORE EDIT: ${path.basename(filePath)} already exists. Make sure you have read it before editing.`
|
|
44
|
+
}
|
|
45
|
+
}));
|
|
46
|
+
} catch {
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
CONFIG=".autodev/config.json"
|
|
4
|
+
STATE=".autodev/STATE.md"
|
|
5
|
+
|
|
6
|
+
if [ ! -f "$CONFIG" ]; then
|
|
7
|
+
exit 0
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
ENABLED=$(node -e "try{const c=require('./.autodev/config.json');process.stdout.write(c.hooks?.session_state===false?'0':'1')}catch{process.stdout.write('0')}" 2>/dev/null)
|
|
11
|
+
if [ "$ENABLED" != "1" ]; then
|
|
12
|
+
exit 0
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
echo "## autodev state"
|
|
16
|
+
echo ""
|
|
17
|
+
|
|
18
|
+
if [ -f "$STATE" ]; then
|
|
19
|
+
head -20 "$STATE"
|
|
20
|
+
else
|
|
21
|
+
echo "No .autodev/STATE.md yet. Start with /autodev-new-project."
|
|
22
|
+
fi
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
let input = '';
|
|
8
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
9
|
+
process.stdin.setEncoding('utf8');
|
|
10
|
+
process.stdin.on('data', chunk => {
|
|
11
|
+
input += chunk;
|
|
12
|
+
});
|
|
13
|
+
process.stdin.on('end', () => {
|
|
14
|
+
clearTimeout(stdinTimeout);
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const data = JSON.parse(input);
|
|
18
|
+
const model = data.model?.display_name || 'Claude';
|
|
19
|
+
const currentDir = data.workspace?.current_dir || process.cwd();
|
|
20
|
+
const remaining = data.context_window?.remaining_percentage;
|
|
21
|
+
const sessionId = data.session_id || '';
|
|
22
|
+
|
|
23
|
+
let contextLabel = '';
|
|
24
|
+
if (typeof remaining === 'number') {
|
|
25
|
+
const used = Math.max(0, Math.min(100, Math.round(100 - remaining)));
|
|
26
|
+
const filled = Math.floor(used / 10);
|
|
27
|
+
const bar = `${'█'.repeat(filled)}${'░'.repeat(10 - filled)}`;
|
|
28
|
+
contextLabel = ` ${bar} ${used}%`;
|
|
29
|
+
|
|
30
|
+
if (sessionId && !/[/\\]|\.\./.test(sessionId)) {
|
|
31
|
+
const bridgePath = path.join(os.tmpdir(), `claude-ctx-${sessionId}.json`);
|
|
32
|
+
fs.writeFileSync(bridgePath, JSON.stringify({
|
|
33
|
+
session_id: sessionId,
|
|
34
|
+
remaining_percentage: remaining,
|
|
35
|
+
used_pct: used,
|
|
36
|
+
timestamp: Math.floor(Date.now() / 1000)
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
process.stdout.write(`${model} | ${path.basename(currentDir)}${contextLabel}`);
|
|
42
|
+
} catch {
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function readProjectConfig(cwd) {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(fs.readFileSync(path.join(cwd, '.autodev', 'config.json'), 'utf8'));
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let input = '';
|
|
15
|
+
const stdinTimeout = setTimeout(() => process.exit(0), 3000);
|
|
16
|
+
process.stdin.setEncoding('utf8');
|
|
17
|
+
process.stdin.on('data', chunk => {
|
|
18
|
+
input += chunk;
|
|
19
|
+
});
|
|
20
|
+
process.stdin.on('end', () => {
|
|
21
|
+
clearTimeout(stdinTimeout);
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const data = JSON.parse(input);
|
|
25
|
+
if (!['Write', 'Edit'].includes(data.tool_name)) {
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const cwd = data.cwd || process.cwd();
|
|
30
|
+
const config = readProjectConfig(cwd);
|
|
31
|
+
if (!config || config.hooks?.workflow_guard === false) {
|
|
32
|
+
process.exit(0);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
|
|
36
|
+
if (!filePath || filePath.includes('.autodev/')) {
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
process.stdout.write(JSON.stringify({
|
|
41
|
+
hookSpecificOutput: {
|
|
42
|
+
hookEventName: 'PreToolUse',
|
|
43
|
+
additionalContext:
|
|
44
|
+
`WORKFLOW ADVISORY: you are editing ${path.basename(filePath)} outside .autodev state files. ` +
|
|
45
|
+
'If this belongs to the current phase, keep STATE.md and the phase summary aligned.'
|
|
46
|
+
}
|
|
47
|
+
}));
|
|
48
|
+
} catch {
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mthanhlm/autodev",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A lean Claude Code workflow system for planning and shipping without automatic git writes.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"autodev": "bin/install.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"commands",
|
|
11
|
+
"autodev",
|
|
12
|
+
"hooks",
|
|
13
|
+
"scripts",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"README.md",
|
|
16
|
+
"PUBLISH.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"workflow",
|
|
22
|
+
"planning",
|
|
23
|
+
"autodev"
|
|
24
|
+
],
|
|
25
|
+
"author": "mthanhlm",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public",
|
|
29
|
+
"registry": "https://registry.npmjs.org/"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=20.0.0"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "node scripts/run-tests.cjs",
|
|
36
|
+
"prepublishOnly": "npm test"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
const root = path.resolve(__dirname, '..');
|
|
8
|
+
const testsDir = path.join(root, 'tests');
|
|
9
|
+
const files = fs.readdirSync(testsDir)
|
|
10
|
+
.filter(file => file.endsWith('.test.cjs'))
|
|
11
|
+
.sort()
|
|
12
|
+
.map(file => path.join(testsDir, file));
|
|
13
|
+
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const result = spawnSync(process.execPath, [file], {
|
|
16
|
+
cwd: root,
|
|
17
|
+
stdio: 'inherit'
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if ((result.status ?? 1) !== 0) {
|
|
21
|
+
process.exit(result.status ?? 1);
|
|
22
|
+
}
|
|
23
|
+
}
|