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.
@@ -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.0',
89
+ core_version: '1.2',
61
90
  platform_version: '1.0',
62
- capability_version: '1.0',
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
- copyFileSync(src, dst);
14
- return { action: 'copy', src, dst, applied: true };
15
- }
16
-
17
- export function copyDir(srcDir, dstDir, { dryRun = false, overwrite = false } = {}) {
18
- const results = [];
19
- if (!existsSync(srcDir)) return results;
20
-
21
- const entries = readdirSync(srcDir, { withFileTypes: true });
22
- for (const entry of entries) {
23
- const srcPath = join(srcDir, entry.name);
24
- const dstPath = join(dstDir, entry.name);
25
-
26
- if (entry.isDirectory()) {
27
- results.push(...copyDir(srcPath, dstPath, { dryRun, overwrite }));
28
- } else if (entry.isFile()) {
29
- if (!overwrite && existsSync(dstPath)) {
30
- results.push({ action: 'skip', src: srcPath, dst: dstPath, reason: 'exists' });
31
- continue;
32
- }
33
- results.push(copyFile(srcPath, dstPath, { dryRun }));
34
- }
35
- }
36
- return results;
37
- }
38
-
39
- /**
40
- * Copy files from srcDir to dstDir, respecting an overrides directory.
41
- * Files present in overridesDir take precedence over srcDir.
42
- */
43
- export function copyWithOverrides(srcDir, dstDir, overridesDir, { dryRun = false } = {}) {
44
- const results = [];
45
- if (!existsSync(srcDir)) return results;
46
-
47
- const entries = readdirSync(srcDir, { withFileTypes: true });
48
- for (const entry of entries) {
49
- const srcPath = join(srcDir, entry.name);
50
- const dstPath = join(dstDir, entry.name);
51
- const overridePath = overridesDir ? join(overridesDir, entry.name) : null;
52
-
53
- if (entry.isDirectory()) {
54
- const subOverride = overridePath && existsSync(overridePath) ? overridePath : null;
55
- results.push(...copyWithOverrides(srcPath, dstPath, subOverride, { dryRun }));
56
- } else if (entry.isFile()) {
57
- if (overridePath && existsSync(overridePath)) {
58
- results.push({
59
- action: 'override',
60
- src: overridePath,
61
- dst: dstPath,
62
- ...(dryRun ? { applied: false } : (() => { ensureDir(dirname(dstPath)); copyFileSync(overridePath, dstPath); return { applied: true }; })()),
63
- });
64
- } else {
65
- results.push(copyFile(srcPath, dstPath, { dryRun }));
66
- }
67
- }
68
- }
69
- return results;
70
- }
71
-
72
- /**
73
- * Compute file differences between two directories.
74
- * Returns { added, modified, unchanged } arrays of relative paths.
75
- */
76
- export function diffDirs(sourceDir, targetDir) {
77
- const added = [];
78
- const modified = [];
79
- const unchanged = [];
80
-
81
- if (!existsSync(sourceDir)) return { added, modified, unchanged };
82
-
83
- function walk(dir, base) {
84
- const entries = readdirSync(dir, { withFileTypes: true });
85
- for (const entry of entries) {
86
- const srcPath = join(dir, entry.name);
87
- const relPath = base ? join(base, entry.name) : entry.name;
88
-
89
- if (entry.isDirectory()) {
90
- walk(srcPath, relPath);
91
- } else if (entry.isFile()) {
92
- const targetPath = join(targetDir, relPath);
93
- if (!existsSync(targetPath)) {
94
- added.push(relPath);
95
- } else {
96
- const srcContent = readFileSync(srcPath);
97
- const tgtContent = readFileSync(targetPath);
98
- if (srcContent.equals(tgtContent)) {
99
- unchanged.push(relPath);
100
- } else {
101
- modified.push(relPath);
102
- }
103
- }
104
- }
105
- }
106
- }
107
-
108
- walk(sourceDir, '');
109
- return { added, modified, unchanged };
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
-