kld-sdd 2.4.13 → 2.4.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/lib/init.js CHANGED
@@ -599,6 +599,36 @@ function deployOpsxSkills(selectedTools = Object.keys(TOOL_CONFIGS)) {
599
599
  * 部署 Claude Code Hook Pack(可选增强)
600
600
  * Hook 只调用 skywalk-sdd/log.cjs,不写私有日志,不替代 OPSX 模板采集。
601
601
  */
602
+ function hookCommandText(hook) {
603
+ const args = Array.isArray(hook.args) ? hook.args : [];
604
+ return [hook.command, ...args].filter(Boolean).join(' ').replace(/\s+/g, ' ').trim();
605
+ }
606
+
607
+ function isSddClaudeHook(hook) {
608
+ const text = hookCommandText(hook);
609
+ return /(?:^|\s|["'])\.claude[\\/]+hooks[\\/]+sdd-(?:prompt|pre-tool|post-tool|stop)\.(?:cjs|js)(?=$|\s|["'])/i.test(text) ||
610
+ /(?:^|\s|["'])\$\{CLAUDE_PROJECT_DIR\}[\\/]+\.claude[\\/]+hooks[\\/]+sdd-(?:prompt|pre-tool|post-tool|stop)\.(?:cjs|js)(?=$|\s|["'])/i.test(text);
611
+ }
612
+
613
+ function sameHookCommand(left, right) {
614
+ return hookCommandText(left) === hookCommandText(right) &&
615
+ String(left.type || '') === String(right.type || '');
616
+ }
617
+
618
+ function removeManagedSddHooks(entries) {
619
+ return entries
620
+ .map(entry => {
621
+ if (!Array.isArray(entry.hooks)) {
622
+ return entry;
623
+ }
624
+ return {
625
+ ...entry,
626
+ hooks: entry.hooks.filter(hook => !isSddClaudeHook(hook)),
627
+ };
628
+ })
629
+ .filter(entry => !Array.isArray(entry.hooks) || entry.hooks.length > 0);
630
+ }
631
+
602
632
  function deployClaudeHookPack(selectedTools = Object.keys(TOOL_CONFIGS)) {
603
633
  if (!selectedTools.includes('claude')) {
604
634
  return true;
@@ -642,11 +672,14 @@ function deployClaudeHookPack(selectedTools = Object.keys(TOOL_CONFIGS)) {
642
672
 
643
673
  existingSettings.hooks = existingSettings.hooks || {};
644
674
  for (const [eventName, entries] of Object.entries(templateSettings.hooks || {})) {
645
- existingSettings.hooks[eventName] = existingSettings.hooks[eventName] || [];
675
+ existingSettings.hooks[eventName] = removeManagedSddHooks(existingSettings.hooks[eventName] || []);
646
676
  for (const entry of entries) {
647
- const templateCommands = (entry.hooks || []).map(h => h.command).filter(Boolean);
677
+ const templateHooks = entry.hooks || [];
648
678
  const exists = existingSettings.hooks[eventName].some(existingEntry => {
649
- return (existingEntry.hooks || []).some(h => templateCommands.includes(h.command));
679
+ const existingHooks = existingEntry.hooks || [];
680
+ return templateHooks.every(templateHook => {
681
+ return existingHooks.some(existingHook => sameHookCommand(existingHook, templateHook));
682
+ });
650
683
  });
651
684
  if (!exists) {
652
685
  existingSettings.hooks[eventName].push(entry);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kld-sdd",
3
- "version": "2.4.13",
3
+ "version": "2.4.14",
4
4
  "description": "KLD SDD OpenSpec 项目初始化工具 - 内置模版一键初始化",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -39,11 +39,18 @@ function main() {
39
39
  });
40
40
 
41
41
  if (result.error) {
42
- console.error(`SDD pre-push: failed to run doctor: ${result.error.message}`);
43
- process.exit(1);
42
+ console.warn(`SDD pre-push: failed to run doctor: ${result.error.message}; push will continue.`);
43
+ return;
44
44
  }
45
45
 
46
- process.exit(result.status || 0);
46
+ if (result.status && result.status !== 0) {
47
+ console.warn(`SDD pre-push: doctor reported issues with exit code ${result.status}; push will continue.`);
48
+ return;
49
+ }
50
+
51
+ if (result.signal) {
52
+ console.warn(`SDD pre-push: doctor stopped by signal ${result.signal}; push will continue.`);
53
+ }
47
54
  }
48
55
 
49
56
  main();
@@ -19,16 +19,38 @@ function parseInput(raw) {
19
19
  }
20
20
  }
21
21
 
22
+ function hasTelemetryCli(dir) {
23
+ return fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.cjs')) ||
24
+ fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.js'));
25
+ }
26
+
27
+ function findTelemetryRoot(startDir) {
28
+ if (!startDir) return '';
29
+
30
+ let current = path.resolve(startDir);
31
+ while (true) {
32
+ if (hasTelemetryCli(current)) return current;
33
+ const parent = path.dirname(current);
34
+ if (parent === current) return '';
35
+ current = parent;
36
+ }
37
+ }
38
+
22
39
  function findProjectRoot(input) {
23
40
  const toolInput = input.tool_input || input.toolInput || {};
24
41
  const candidates = [
25
42
  toolInput.cwd,
26
43
  input.cwd,
27
44
  input.project_root,
45
+ process.env.CLAUDE_PROJECT_DIR,
28
46
  process.env.PWD,
29
47
  process.cwd(),
30
48
  ].filter(Boolean);
31
- return candidates.find(dir => fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.js'))) || process.cwd();
49
+ for (const dir of candidates) {
50
+ const root = findTelemetryRoot(dir);
51
+ if (root) return root;
52
+ }
53
+ return process.cwd();
32
54
  }
33
55
 
34
56
  function latestActiveStage(projectRoot) {
@@ -75,7 +97,11 @@ function inferRecord(command) {
75
97
  }
76
98
 
77
99
  function recordTelemetry(projectRoot, activeStage, record, result) {
78
- const logPath = path.join(projectRoot, 'skywalk-sdd', 'log.js');
100
+ const logPath = path.join(projectRoot, 'skywalk-sdd', 'log.cjs');
101
+ const legacyLogPath = path.join(projectRoot, 'skywalk-sdd', 'log.js');
102
+ const logCli = fs.existsSync(logPath) ? logPath : legacyLogPath;
103
+ if (!fs.existsSync(logCli)) return;
104
+
79
105
  const details = { [record.detailsKey]: { ...record.details } };
80
106
  if (record.type === 'build_result') {
81
107
  details.build_results.success = result === 'success';
@@ -83,7 +109,7 @@ function recordTelemetry(projectRoot, activeStage, record, result) {
83
109
  }
84
110
 
85
111
  const args = [
86
- logPath,
112
+ logCli,
87
113
  'record',
88
114
  `--type=${record.type}`,
89
115
  `--command=${activeStage.command || activeStage.stage || 'unknown'}`,
@@ -18,14 +18,36 @@ function parseInput(raw) {
18
18
  }
19
19
  }
20
20
 
21
+ function hasTelemetryCli(dir) {
22
+ return fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.cjs')) ||
23
+ fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.js'));
24
+ }
25
+
26
+ function findTelemetryRoot(startDir) {
27
+ if (!startDir) return '';
28
+
29
+ let current = path.resolve(startDir);
30
+ while (true) {
31
+ if (hasTelemetryCli(current)) return current;
32
+ const parent = path.dirname(current);
33
+ if (parent === current) return '';
34
+ current = parent;
35
+ }
36
+ }
37
+
21
38
  function findProjectRoot(input) {
22
39
  const candidates = [
23
40
  input.cwd,
24
41
  input.project_root,
42
+ process.env.CLAUDE_PROJECT_DIR,
25
43
  process.env.PWD,
26
44
  process.cwd(),
27
45
  ].filter(Boolean);
28
- return candidates.find(dir => fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.js'))) || process.cwd();
46
+ for (const dir of candidates) {
47
+ const root = findTelemetryRoot(dir);
48
+ if (root) return root;
49
+ }
50
+ return process.cwd();
29
51
  }
30
52
 
31
53
  function readActiveStages(projectRoot) {
@@ -19,14 +19,36 @@ function parseInput(raw) {
19
19
  }
20
20
  }
21
21
 
22
+ function hasTelemetryCli(dir) {
23
+ return fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.cjs')) ||
24
+ fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.js'));
25
+ }
26
+
27
+ function findTelemetryRoot(startDir) {
28
+ if (!startDir) return '';
29
+
30
+ let current = path.resolve(startDir);
31
+ while (true) {
32
+ if (hasTelemetryCli(current)) return current;
33
+ const parent = path.dirname(current);
34
+ if (parent === current) return '';
35
+ current = parent;
36
+ }
37
+ }
38
+
22
39
  function findProjectRoot(input) {
23
40
  const candidates = [
24
41
  input.cwd,
25
42
  input.project_root,
43
+ process.env.CLAUDE_PROJECT_DIR,
26
44
  process.env.PWD,
27
45
  process.cwd(),
28
46
  ].filter(Boolean);
29
- return candidates.find(dir => fs.existsSync(path.join(dir, 'skywalk-sdd', 'log.js'))) || process.cwd();
47
+ for (const dir of candidates) {
48
+ const root = findTelemetryRoot(dir);
49
+ if (root) return root;
50
+ }
51
+ return process.cwd();
30
52
  }
31
53
 
32
54
  function readActiveStages(projectRoot) {
@@ -45,9 +67,13 @@ function readActiveStages(projectRoot) {
45
67
  }
46
68
 
47
69
  function writeWarning(projectRoot, event) {
48
- const logPath = path.join(projectRoot, 'skywalk-sdd', 'log.js');
70
+ const logPath = path.join(projectRoot, 'skywalk-sdd', 'log.cjs');
71
+ const legacyLogPath = path.join(projectRoot, 'skywalk-sdd', 'log.js');
72
+ const logCli = fs.existsSync(logPath) ? logPath : legacyLogPath;
73
+ if (!fs.existsSync(logCli)) return;
74
+
49
75
  const args = [
50
- logPath,
76
+ logCli,
51
77
  'record',
52
78
  '--type=telemetry_warning',
53
79
  `--command=${event.command || event.stage || 'unknown'}`,
@@ -5,7 +5,7 @@
5
5
  "hooks": [
6
6
  {
7
7
  "type": "command",
8
- "command": "node .claude/hooks/sdd-prompt.cjs"
8
+ "command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/sdd-prompt.cjs\""
9
9
  }
10
10
  ]
11
11
  }
@@ -16,7 +16,7 @@
16
16
  "hooks": [
17
17
  {
18
18
  "type": "command",
19
- "command": "node .claude/hooks/sdd-pre-tool.cjs"
19
+ "command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/sdd-pre-tool.cjs\""
20
20
  }
21
21
  ]
22
22
  }
@@ -27,7 +27,7 @@
27
27
  "hooks": [
28
28
  {
29
29
  "type": "command",
30
- "command": "node .claude/hooks/sdd-post-tool.cjs"
30
+ "command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/sdd-post-tool.cjs\""
31
31
  }
32
32
  ]
33
33
  }
@@ -37,7 +37,7 @@
37
37
  "hooks": [
38
38
  {
39
39
  "type": "command",
40
- "command": "node .claude/hooks/sdd-stop.cjs"
40
+ "command": "node \"${CLAUDE_PROJECT_DIR}/.claude/hooks/sdd-stop.cjs\""
41
41
  }
42
42
  ]
43
43
  }