dw-kit 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/hooks/post-write.sh +9 -11
- package/.claude/hooks/privacy-block.sh +94 -0
- package/.claude/hooks/scout-block.sh +82 -0
- package/.claude/hooks/session-init.sh +85 -0
- package/.claude/hooks/stop-check.sh +36 -0
- package/.claude/rules/dw-core.md +100 -0
- package/.claude/rules/dw-skills.md +53 -0
- package/.claude/settings.json +101 -71
- package/.claude/skills/dw-kit-report/SKILL.md +152 -0
- package/.claude/skills/dw-research/SKILL.md +17 -1
- package/.claude/templates/agent-report.md +35 -0
- package/.dw/config/dw.config.yml +82 -82
- package/.dw/core/AGENTS.md +53 -0
- package/CLAUDE.md +27 -99
- package/README.md +127 -119
- package/package.json +84 -57
- package/src/cli.mjs +100 -92
- package/src/commands/init.mjs +17 -17
- package/src/commands/upgrade.mjs +297 -262
- package/src/lib/config.mjs +31 -2
- package/src/lib/copy.mjs +118 -110
- package/scripts/e2e-local-check.sh +0 -75
- package/src/__fixtures__/claude-cli-bug-snippet.js +0 -15
- package/src/smoke-test.mjs +0 -351
package/src/lib/config.mjs
CHANGED
|
@@ -7,6 +7,35 @@ export function loadConfig(configPath) {
|
|
|
7
7
|
return yaml.load(content);
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Load config với local override (v1.2+).
|
|
12
|
+
* dw.config.yml — shared, committed
|
|
13
|
+
* dw.config.local.yml — machine-specific, gitignored
|
|
14
|
+
* Local values win over base values (shallow merge per top-level key).
|
|
15
|
+
*/
|
|
16
|
+
export function loadConfigWithLocal(configDir) {
|
|
17
|
+
const basePath = `${configDir}/dw.config.yml`;
|
|
18
|
+
const localPath = `${configDir}/dw.config.local.yml`;
|
|
19
|
+
|
|
20
|
+
const base = loadConfig(basePath);
|
|
21
|
+
if (!base) return null;
|
|
22
|
+
|
|
23
|
+
if (!existsSync(localPath)) return base;
|
|
24
|
+
|
|
25
|
+
const local = loadConfig(localPath);
|
|
26
|
+
if (!local) return base;
|
|
27
|
+
|
|
28
|
+
const merged = { ...base };
|
|
29
|
+
for (const key of Object.keys(local)) {
|
|
30
|
+
if (typeof local[key] === 'object' && !Array.isArray(local[key]) && local[key] !== null) {
|
|
31
|
+
merged[key] = { ...(merged[key] || {}), ...local[key] };
|
|
32
|
+
} else {
|
|
33
|
+
merged[key] = local[key];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return merged;
|
|
37
|
+
}
|
|
38
|
+
|
|
10
39
|
export function writeConfig(configPath, data) {
|
|
11
40
|
const content = yaml.dump(data, {
|
|
12
41
|
indent: 2,
|
|
@@ -57,9 +86,9 @@ export function buildConfig({ projectName, language, depth, roles }) {
|
|
|
57
86
|
mcp: [],
|
|
58
87
|
},
|
|
59
88
|
_toolkit: {
|
|
60
|
-
core_version: '1.
|
|
89
|
+
core_version: '1.2',
|
|
61
90
|
platform_version: '1.0',
|
|
62
|
-
capability_version: '1.
|
|
91
|
+
capability_version: '1.2',
|
|
63
92
|
installed: today,
|
|
64
93
|
last_upgrade: today,
|
|
65
94
|
},
|
package/src/lib/copy.mjs
CHANGED
|
@@ -1,110 +1,118 @@
|
|
|
1
|
-
import {
|
|
2
|
-
existsSync, mkdirSync, copyFileSync, readdirSync, readFileSync,
|
|
3
|
-
} from 'node:fs';
|
|
4
|
-
import { join, dirname } from 'node:path';
|
|
5
|
-
|
|
6
|
-
export function ensureDir(dir) {
|
|
7
|
-
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function copyFile(src, dst, { dryRun = false } = {}) {
|
|
11
|
-
if (dryRun) return { action: 'copy', src, dst, applied: false };
|
|
12
|
-
ensureDir(dirname(dst));
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
existsSync, mkdirSync, copyFileSync, readdirSync, readFileSync, writeFileSync,
|
|
3
|
+
} from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
|
|
6
|
+
export function ensureDir(dir) {
|
|
7
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function copyFile(src, dst, { dryRun = false } = {}) {
|
|
11
|
+
if (dryRun) return { action: 'copy', src, dst, applied: false };
|
|
12
|
+
ensureDir(dirname(dst));
|
|
13
|
+
if (src.endsWith('.sh')) {
|
|
14
|
+
// Always write shell scripts with LF endings.
|
|
15
|
+
// Prevents CRLF contamination when git later checks out these files on Windows
|
|
16
|
+
// (core.autocrlf=true overrides .gitattributes in the *user's* repo, which dw-kit cannot control).
|
|
17
|
+
const content = readFileSync(src, 'utf-8').replace(/\r\n/g, '\n');
|
|
18
|
+
writeFileSync(dst, content, 'utf-8');
|
|
19
|
+
} else {
|
|
20
|
+
copyFileSync(src, dst);
|
|
21
|
+
}
|
|
22
|
+
return { action: 'copy', src, dst, applied: true };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function copyDir(srcDir, dstDir, { dryRun = false, overwrite = false } = {}) {
|
|
26
|
+
const results = [];
|
|
27
|
+
if (!existsSync(srcDir)) return results;
|
|
28
|
+
|
|
29
|
+
const entries = readdirSync(srcDir, { withFileTypes: true });
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
const srcPath = join(srcDir, entry.name);
|
|
32
|
+
const dstPath = join(dstDir, entry.name);
|
|
33
|
+
|
|
34
|
+
if (entry.isDirectory()) {
|
|
35
|
+
results.push(...copyDir(srcPath, dstPath, { dryRun, overwrite }));
|
|
36
|
+
} else if (entry.isFile()) {
|
|
37
|
+
if (!overwrite && existsSync(dstPath)) {
|
|
38
|
+
results.push({ action: 'skip', src: srcPath, dst: dstPath, reason: 'exists' });
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
results.push(copyFile(srcPath, dstPath, { dryRun }));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return results;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Copy files from srcDir to dstDir, respecting an overrides directory.
|
|
49
|
+
* Files present in overridesDir take precedence over srcDir.
|
|
50
|
+
*/
|
|
51
|
+
export function copyWithOverrides(srcDir, dstDir, overridesDir, { dryRun = false } = {}) {
|
|
52
|
+
const results = [];
|
|
53
|
+
if (!existsSync(srcDir)) return results;
|
|
54
|
+
|
|
55
|
+
const entries = readdirSync(srcDir, { withFileTypes: true });
|
|
56
|
+
for (const entry of entries) {
|
|
57
|
+
const srcPath = join(srcDir, entry.name);
|
|
58
|
+
const dstPath = join(dstDir, entry.name);
|
|
59
|
+
const overridePath = overridesDir ? join(overridesDir, entry.name) : null;
|
|
60
|
+
|
|
61
|
+
if (entry.isDirectory()) {
|
|
62
|
+
const subOverride = overridePath && existsSync(overridePath) ? overridePath : null;
|
|
63
|
+
results.push(...copyWithOverrides(srcPath, dstPath, subOverride, { dryRun }));
|
|
64
|
+
} else if (entry.isFile()) {
|
|
65
|
+
if (overridePath && existsSync(overridePath)) {
|
|
66
|
+
results.push({
|
|
67
|
+
action: 'override',
|
|
68
|
+
src: overridePath,
|
|
69
|
+
dst: dstPath,
|
|
70
|
+
...(dryRun ? { applied: false } : (() => { ensureDir(dirname(dstPath)); copyFileSync(overridePath, dstPath); return { applied: true }; })()),
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
results.push(copyFile(srcPath, dstPath, { dryRun }));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Compute file differences between two directories.
|
|
82
|
+
* Returns { added, modified, unchanged } arrays of relative paths.
|
|
83
|
+
*/
|
|
84
|
+
export function diffDirs(sourceDir, targetDir) {
|
|
85
|
+
const added = [];
|
|
86
|
+
const modified = [];
|
|
87
|
+
const unchanged = [];
|
|
88
|
+
|
|
89
|
+
if (!existsSync(sourceDir)) return { added, modified, unchanged };
|
|
90
|
+
|
|
91
|
+
function walk(dir, base) {
|
|
92
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
const srcPath = join(dir, entry.name);
|
|
95
|
+
const relPath = base ? join(base, entry.name) : entry.name;
|
|
96
|
+
|
|
97
|
+
if (entry.isDirectory()) {
|
|
98
|
+
walk(srcPath, relPath);
|
|
99
|
+
} else if (entry.isFile()) {
|
|
100
|
+
const targetPath = join(targetDir, relPath);
|
|
101
|
+
if (!existsSync(targetPath)) {
|
|
102
|
+
added.push(relPath);
|
|
103
|
+
} else {
|
|
104
|
+
const srcContent = readFileSync(srcPath);
|
|
105
|
+
const tgtContent = readFileSync(targetPath);
|
|
106
|
+
if (srcContent.equals(tgtContent)) {
|
|
107
|
+
unchanged.push(relPath);
|
|
108
|
+
} else {
|
|
109
|
+
modified.push(relPath);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
walk(sourceDir, '');
|
|
117
|
+
return { added, modified, unchanged };
|
|
118
|
+
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
# scripts/e2e-local-check.sh
|
|
3
|
-
# End-to-end local publish check (pack -> install -> run CLI)
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
8
|
-
cd "$ROOT_DIR"
|
|
9
|
-
|
|
10
|
-
log() { echo " $*"; }
|
|
11
|
-
info() { echo ""; echo "▶ $*"; }
|
|
12
|
-
ok() { echo " ✓ $*"; }
|
|
13
|
-
warn() { echo " ⚠ $*"; }
|
|
14
|
-
|
|
15
|
-
cleanup() {
|
|
16
|
-
if [ -n "${TMP_DIR:-}" ] && [ -d "${TMP_DIR:-}" ]; then
|
|
17
|
-
rm -rf "$TMP_DIR"
|
|
18
|
-
fi
|
|
19
|
-
if [ -n "${PACK_FILE:-}" ] && [ -f "${PACK_FILE:-}" ]; then
|
|
20
|
-
rm -f "$PACK_FILE"
|
|
21
|
-
fi
|
|
22
|
-
}
|
|
23
|
-
trap cleanup EXIT
|
|
24
|
-
|
|
25
|
-
info "Step 1: Run smoke tests"
|
|
26
|
-
npm test
|
|
27
|
-
ok "Smoke tests passed"
|
|
28
|
-
|
|
29
|
-
info "Step 2: Build npm package tarball"
|
|
30
|
-
PACK_OUTPUT="$(npm pack)"
|
|
31
|
-
PACK_FILE="$(echo "$PACK_OUTPUT" | tail -n 1 | tr -d '\r')"
|
|
32
|
-
if [ ! -f "$PACK_FILE" ]; then
|
|
33
|
-
echo " ✗ Failed to produce package tarball"
|
|
34
|
-
exit 1
|
|
35
|
-
fi
|
|
36
|
-
ok "Tarball: $PACK_FILE"
|
|
37
|
-
|
|
38
|
-
info "Step 3: Create isolated test project"
|
|
39
|
-
TMP_DIR="$(mktemp -d 2>/dev/null || true)"
|
|
40
|
-
if [ -z "${TMP_DIR}" ]; then
|
|
41
|
-
TMP_DIR=".tmp-e2e-local-check"
|
|
42
|
-
rm -rf "$TMP_DIR"
|
|
43
|
-
mkdir -p "$TMP_DIR"
|
|
44
|
-
fi
|
|
45
|
-
TEST_DIR="$TMP_DIR/e2e-project"
|
|
46
|
-
mkdir -p "$TEST_DIR"
|
|
47
|
-
cd "$TEST_DIR"
|
|
48
|
-
npm init -y >/dev/null 2>&1
|
|
49
|
-
git init >/dev/null 2>&1 || true
|
|
50
|
-
ok "Created isolated project at $TEST_DIR"
|
|
51
|
-
|
|
52
|
-
info "Step 4: Install from local tarball"
|
|
53
|
-
npm install "$ROOT_DIR/$PACK_FILE" >/dev/null
|
|
54
|
-
ok "Installed package from tarball"
|
|
55
|
-
|
|
56
|
-
info "Step 5: Run CLI checks in isolated project"
|
|
57
|
-
VERSION_OUT="$(npx dw --version | tr -d '\r')"
|
|
58
|
-
log "dw --version: $VERSION_OUT"
|
|
59
|
-
npx dw init --preset small-team
|
|
60
|
-
npx dw validate
|
|
61
|
-
npx dw doctor
|
|
62
|
-
npx dw upgrade --check
|
|
63
|
-
|
|
64
|
-
info "Step 6: Verify task-depth override guidance artifacts"
|
|
65
|
-
grep -q "Task-Level Depth Override" ".dw/core/WORKFLOW.md"
|
|
66
|
-
grep -q "Depth Source: default (from config) | override (task-specific)" ".dw/core/templates/vi/task-context.md"
|
|
67
|
-
ok "Depth override guidance exists in generated artifacts"
|
|
68
|
-
|
|
69
|
-
ok "CLI flow passed in isolated project"
|
|
70
|
-
|
|
71
|
-
echo ""
|
|
72
|
-
echo "══════════════════════════════════════════"
|
|
73
|
-
echo " E2E local check passed"
|
|
74
|
-
echo "══════════════════════════════════════════"
|
|
75
|
-
echo ""
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
// Minimal fixture that contains the known Vietnamese IME bug pattern.
|
|
2
|
-
// This is NOT the real Claude CLI; only used for testing the patcher logic.
|
|
3
|
-
//
|
|
4
|
-
// IMPORTANT: The comment below must contain both '@anthropic-ai' and 'claude-code'
|
|
5
|
-
// to pass the bundle signature guard in patchCliJs(). Do not remove it.
|
|
6
|
-
// @anthropic-ai/claude-code bundle stub
|
|
7
|
-
|
|
8
|
-
function demo(INPUT) {
|
|
9
|
-
if(INPUT.includes("\x7f")){
|
|
10
|
-
let COUNT=(INPUT.match(/\x7f/g)||[]).length,STATE=CURSTATE;
|
|
11
|
-
UPDATETEXT(STATE.text);UPDATEOFFSET(STATE.offset)
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|