clikit-plugin 0.2.0 → 0.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/dist/cli.js CHANGED
@@ -48,11 +48,25 @@ function parseConfig(configPath) {
48
48
  }
49
49
  const content = fs.readFileSync(configPath, "utf-8");
50
50
  const cleaned = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
51
- return JSON.parse(cleaned);
52
- } catch {
51
+ const cleanedTrailing = cleaned.replace(/,\s*([}\]])/g, "$1");
52
+ return JSON.parse(cleanedTrailing);
53
+ } catch (err) {
54
+ console.error(`Warning: Failed to parse config at ${configPath}: ${err}`);
53
55
  return {};
54
56
  }
55
57
  }
58
+ function backupConfig(configPath) {
59
+ try {
60
+ if (!fs.existsSync(configPath)) {
61
+ return null;
62
+ }
63
+ const backupPath = `${configPath}.backup-${Date.now()}`;
64
+ fs.copyFileSync(configPath, backupPath);
65
+ return backupPath;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
56
70
  function writeConfig(configPath, config) {
57
71
  ensureConfigDir();
58
72
  const tmpPath = `${configPath}.tmp`;
@@ -75,12 +89,20 @@ async function install() {
75
89
  }
76
90
  const configPath = getConfigPath();
77
91
  try {
78
- const config = parseConfig(configPath) || {};
79
- const plugins = config.plugin || [];
80
- const filteredPlugins = plugins.filter((p) => p !== PLUGIN_NAME && !p.startsWith(`${PLUGIN_NAME}@`));
92
+ const backupPath = backupConfig(configPath);
93
+ if (backupPath) {
94
+ console.log(` Backup created: ${backupPath}`);
95
+ }
96
+ const config = parseConfig(configPath);
97
+ const existingPlugins = Array.isArray(config.plugin) ? config.plugin : [];
98
+ const preservedKeys = Object.keys(config).filter((k) => k !== "plugin");
99
+ if (preservedKeys.length > 0) {
100
+ console.log(` Preserving existing config: ${preservedKeys.join(", ")}`);
101
+ }
102
+ const filteredPlugins = existingPlugins.filter((p) => p !== PLUGIN_NAME && !p.startsWith(`${PLUGIN_NAME}@`));
81
103
  filteredPlugins.push(PLUGIN_NAME);
82
- config.plugin = filteredPlugins;
83
- writeConfig(configPath, config);
104
+ const newConfig = { ...config, plugin: filteredPlugins };
105
+ writeConfig(configPath, newConfig);
84
106
  console.log(`\u2713 Plugin added to ${configPath}`);
85
107
  } catch (err) {
86
108
  console.error(`\u2717 Failed to update OpenCode config: ${err}`);
@@ -11,7 +11,7 @@ export interface CommentCheckResult {
11
11
  totalLines: number;
12
12
  ratio: number;
13
13
  }
14
- export declare function checkCommentDensity(content: string, threshold?: number): CommentCheckResult;
15
- export declare function hasExcessiveAIComments(content: string): boolean;
14
+ export declare function checkCommentDensity(content: unknown, threshold?: number): CommentCheckResult;
15
+ export declare function hasExcessiveAIComments(content: unknown): boolean;
16
16
  export declare function formatCommentWarning(result: CommentCheckResult): string;
17
17
  //# sourceMappingURL=comment-checker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"comment-checker.d.ts","sourceRoot":"","sources":["../../src/hooks/comment-checker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,GAAE,MAAY,GAAG,kBAAkB,CAkDhG;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAS/D;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAGvE"}
1
+ {"version":3,"file":"comment-checker.d.ts","sourceRoot":"","sources":["../../src/hooks/comment-checker.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,GAAE,MAAY,GAAG,kBAAkB,CAqDjG;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAYhE;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAGvE"}
@@ -1 +1 @@
1
- {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/hooks/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAuFzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,SAAS,EAAE,CAyDlF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,GAAE,MAAa,GAAG,MAAM,CA2ChG;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,gBAAgB,GACxB,iBAAiB,CAYnB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAMtE"}
1
+ {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../src/hooks/compaction.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,WAAW,gBAAgB;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAC;IACvB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAuFzE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,SAAS,EAAE,CA2DlF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,iBAAiB,EAAE,QAAQ,GAAE,MAAa,GAAG,MAAM,CA4ChG;AAED,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,gBAAgB,GACxB,iBAAiB,CAYnB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAMtE"}
@@ -1 +1 @@
1
- {"version":3,"file":"security-check.d.ts","sourceRoot":"","sources":["../../src/hooks/security-check.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyBH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAkB7F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAUzE"}
1
+ {"version":3,"file":"security-check.d.ts","sourceRoot":"","sources":["../../src/hooks/security-check.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyBH,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,mBAAmB,CAmB7F;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAUzE"}
@@ -1 +1 @@
1
- {"version":3,"file":"truncator.d.ts","sourceRoot":"","sources":["../../src/hooks/truncator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAUT;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,eAAe,GACvB,cAAc,CA2EhB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAIlE"}
1
+ {"version":3,"file":"truncator.d.ts","sourceRoot":"","sources":["../../src/hooks/truncator.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;CACjB;AAOD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,eAAe,GACvB,OAAO,CAYT;AAED,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,eAAe,GACvB,cAAc,CAsFhB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAIlE"}
package/dist/index.js CHANGED
@@ -3917,6 +3917,8 @@ var SENSITIVE_FILES = [
3917
3917
  /id_ed25519$/
3918
3918
  ];
3919
3919
  function scanContentForSecrets(content, filename) {
3920
+ if (typeof content !== "string")
3921
+ return { safe: true, findings: [] };
3920
3922
  const findings = [];
3921
3923
  const lines = content.split(`
3922
3924
  `);
@@ -3984,6 +3986,9 @@ var EXCESSIVE_COMMENT_PATTERNS = [
3984
3986
  /#\s*(?:This|The|A|An) (?:function|method|class|script)/i
3985
3987
  ];
3986
3988
  function checkCommentDensity(content, threshold = 0.3) {
3989
+ if (typeof content !== "string") {
3990
+ return { excessive: false, count: 0, totalLines: 0, ratio: 0 };
3991
+ }
3987
3992
  const lines = content.split(`
3988
3993
  `);
3989
3994
  const totalLines = lines.filter((l) => l.trim().length > 0).length;
@@ -4026,6 +4031,9 @@ function checkCommentDensity(content, threshold = 0.3) {
4026
4031
  };
4027
4032
  }
4028
4033
  function hasExcessiveAIComments(content) {
4034
+ if (typeof content !== "string") {
4035
+ return false;
4036
+ }
4029
4037
  let matches = 0;
4030
4038
  for (const pattern of EXCESSIVE_COMMENT_PATTERNS) {
4031
4039
  const found = content.match(new RegExp(pattern, "gm"));
@@ -4450,6 +4458,8 @@ var DEFAULT_MAX_LINES = 500;
4450
4458
  var DEFAULT_HEAD_LINES = 50;
4451
4459
  var DEFAULT_TAIL_LINES = 50;
4452
4460
  function shouldTruncate(content, config) {
4461
+ if (typeof content !== "string")
4462
+ return false;
4453
4463
  const maxChars = config?.max_output_chars ?? DEFAULT_MAX_CHARS;
4454
4464
  const maxLines = config?.max_output_lines ?? DEFAULT_MAX_LINES;
4455
4465
  if (content.length > maxChars)
@@ -4461,6 +4471,16 @@ function shouldTruncate(content, config) {
4461
4471
  return false;
4462
4472
  }
4463
4473
  function truncateOutput(content, config) {
4474
+ if (typeof content !== "string") {
4475
+ return {
4476
+ truncated: false,
4477
+ originalLength: 0,
4478
+ truncatedLength: 0,
4479
+ originalLines: 0,
4480
+ truncatedLines: 0,
4481
+ content: ""
4482
+ };
4483
+ }
4464
4484
  const maxChars = config?.max_output_chars ?? DEFAULT_MAX_CHARS;
4465
4485
  const maxLines = config?.max_output_lines ?? DEFAULT_MAX_LINES;
4466
4486
  const headLines = config?.preserve_head_lines ?? DEFAULT_HEAD_LINES;
@@ -4632,6 +4652,8 @@ function readMemoryRefs(projectDir, limit = 10) {
4632
4652
  const fullPath = path7.join(subdirPath, file);
4633
4653
  const stat = fs7.statSync(fullPath);
4634
4654
  const raw = fs7.readFileSync(fullPath, "utf-8");
4655
+ if (typeof raw !== "string" || !raw.trim())
4656
+ continue;
4635
4657
  let summary;
4636
4658
  const ext = path7.extname(file);
4637
4659
  if (ext === ".md") {
@@ -4639,7 +4661,8 @@ function readMemoryRefs(projectDir, limit = 10) {
4639
4661
  summary = headingMatch ? headingMatch[1] : raw.substring(0, 100).trim();
4640
4662
  } else if (ext === ".json") {
4641
4663
  const parsed = JSON.parse(raw);
4642
- summary = parsed.summary || parsed.title || parsed.content?.substring(0, 100) || "";
4664
+ const content = parsed.content;
4665
+ summary = parsed.summary || parsed.title || (typeof content === "string" ? content.substring(0, 100) : "") || "";
4643
4666
  } else {
4644
4667
  summary = raw.substring(0, 100).trim();
4645
4668
  }
@@ -4700,6 +4723,9 @@ Recent Memory References:`);
4700
4723
  lines.push("</compaction-context>");
4701
4724
  const block = lines.join(`
4702
4725
  `);
4726
+ if (typeof block !== "string")
4727
+ return `<compaction-context>
4728
+ </compaction-context>`;
4703
4729
  if (block.length > maxChars) {
4704
4730
  return block.substring(0, maxChars) + `
4705
4731
  ... [compaction context truncated]
@@ -0,0 +1,140 @@
1
+ ---
2
+ date: 2026-02-15
3
+ phase: implementing
4
+ branch: main
5
+ ---
6
+
7
+ # Handoff: CliKit Plugin Installation & Runtime Fixes
8
+
9
+ ---
10
+
11
+ ## Status Summary
12
+
13
+ CliKit plugin published to npm (v0.1.9 → v0.2.0). Fixed snap/bun path detection and multiple runtime errors. **CRITICAL ISSUE IDENTIFIED**: Installer overwrites `opencode.json` instead of merging with existing config, deleting user's existing settings. Multiple `.split()`, `.substring()` errors still occurring in hooks due to non-string arguments.
14
+
15
+ ---
16
+
17
+ ## Artifacts
18
+
19
+ | Type | Path | Status |
20
+ |------|------|--------|
21
+ | Handoff | `.opencode/memory/handoffs/2026-02-15-installing.md` | 📚 Previous |
22
+ | Config | `~/.config/opencode/opencode.json` | ⚠️ Gets overwritten |
23
+
24
+ ---
25
+
26
+ ## Task Status
27
+
28
+ ### ✅ Completed
29
+ - [x] T-001-T-007: Plugin implementation (10 agents, 19 commands, 48 skills, 14 hooks, 6 tools)
30
+ - [x] Fix snap/bun path detection using `SNAP_REAL_HOME` and regex fallback
31
+ - [x] Fix `buildErrorNotification` to handle Error objects
32
+ - [x] Fix `buildIdleNotification` to handle non-string sessionId
33
+ - [x] Fix `checkCommentDensity` and `hasExcessiveAIComments` to handle non-string content
34
+ - [x] Published v0.1.7, v0.1.8, v0.1.9, v0.2.0 to npm
35
+
36
+ ### 🔄 In Progress
37
+ - [ ] **CRITICAL**: Fix installer to MERGE with existing opencode.json instead of overwriting
38
+ - **Current state:** `parseConfig()` reads existing config, but may not preserve all keys
39
+ - **User report:** "nó ghi đè file opencode.json làm xóa config có sẵn của tôi"
40
+ - **Root cause:** Need to deep merge, not shallow overwrite
41
+
42
+ - [ ] Fix remaining runtime errors in hooks
43
+ - **Files needing defensive type checks:**
44
+ - `src/hooks/truncator.ts` - `shouldTruncate()`, `truncateOutput()`
45
+ - `src/hooks/security-check.ts` - `scanContentForSecrets()`
46
+ - `src/hooks/compaction.ts` - multiple `.substring()` calls
47
+ - **Pattern:** Add `if (typeof content !== "string") return defaultResult;` at function start
48
+
49
+ ### 📋 Not Started
50
+ - [ ] T-009: Verify plugin loads when OpenCode starts
51
+ - [ ] T-010: Test installation in fresh environment
52
+ - [ ] Add automated tests for installer
53
+
54
+ ---
55
+
56
+ ## Files Modified
57
+
58
+ | File | Status | Notes |
59
+ |------|--------|-------|
60
+ | `src/cli.ts` | Modified | Added `getRealHome()` for snap path detection |
61
+ | `src/hooks/session-notification.ts` | Modified | Fixed `buildIdleNotification`, `buildErrorNotification` |
62
+ | `src/hooks/comment-checker.ts` | Modified | Added type guards for content param |
63
+ | `src/index.ts` | Modified | Pass raw error to `buildErrorNotification` |
64
+ | `package.json` | Modified | Version bumps to 0.2.0 |
65
+
66
+ ---
67
+
68
+ ## Git State
69
+
70
+ - **Branch:** `main`
71
+ - **Last commit:** `31b200c` - Update README with simplified installation
72
+ - **Uncommitted changes:** Yes (5 modified files)
73
+
74
+ ---
75
+
76
+ ## Known Issues
77
+
78
+ 1. **CRITICAL: Installer overwrites opencode.json** - deletes user's existing config (mcp, provider settings)
79
+ - Need to deep merge instead of shallow merge
80
+ - Should backup existing config before modifying
81
+
82
+ 2. **Runtime TypeError: `.split()`, `.substring()` on undefined**
83
+ - Multiple hooks receive non-string arguments from OpenCode events
84
+ - Need defensive type checks in ALL functions that call string methods
85
+
86
+ 3. **Snap/bun path mismatch** - partially fixed but user still sees wrong path
87
+ - `bun x` may cache old versions
88
+ - Need to verify `SNAP_REAL_HOME` detection works in user's environment
89
+
90
+ ---
91
+
92
+ ## Next Steps
93
+
94
+ 1. [ ] **FIX INSTALLER** - Merge with existing config, don't overwrite:
95
+ ```typescript
96
+ // In cli.ts, preserve existing config keys
97
+ const existingConfig = parseConfig(configPath);
98
+ const newConfig = { ...existingConfig, plugin: filteredPlugins };
99
+ writeConfig(configPath, newConfig);
100
+ ```
101
+
102
+ 2. [ ] Add defensive type checks to all hooks that use string methods:
103
+ - `truncator.ts`: `shouldTruncate()`, `truncateOutput()`
104
+ - `security-check.ts`: `scanContentForSecrets()`
105
+ - `compaction.ts`: All substring calls
106
+
107
+ 3. [ ] Publish v0.2.1 with fixes
108
+
109
+ 4. [ ] Test: `bun x clikit-plugin@latest install` should preserve existing config
110
+
111
+ ---
112
+
113
+ ## Context for Resumption
114
+
115
+ ### The Core Problem
116
+ The installer's `writeConfig()` function writes the entire config object to disk. The current code does spread, but needs to ensure `parseConfig()` returns complete existing config.
117
+
118
+ ### Solution Approach
119
+ ```typescript
120
+ // cli.ts - install() function:
121
+ const existingConfig = parseConfig(configPath) || {};
122
+ const plugins = Array.isArray(existingConfig.plugin) ? [...existingConfig.plugin] : [];
123
+ // ... modify plugins array ...
124
+ config.plugin = filteredPlugins;
125
+ // existingConfig already has all keys, we just update plugin
126
+ ```
127
+
128
+ ### Files to Review
129
+ - `src/cli.ts:55-108` — Installer logic
130
+ - `src/hooks/truncator.ts` — Needs type guards
131
+ - `src/hooks/security-check.ts` — Needs type guards
132
+
133
+ ---
134
+
135
+ ## Resume Command
136
+
137
+ To resume, use `/resume` and it will:
138
+ 1. Load this handoff
139
+ 2. Check for drift
140
+ 3. Propose fixing installer merge logic first
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clikit-plugin",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "OpenCode plugin with 10 agents, 19 commands, 48 skills, 14 hooks",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",