pi-harness-runtime 0.10.26 → 0.10.42

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.
Files changed (150) hide show
  1. package/.pi/plugins/herdr-clipboard/herdr-plugin.toml +22 -0
  2. package/.pi/plugins/herdr-clipboard/start-bridge.sh +29 -0
  3. package/.pi/plugins/herdr-clipboard/tmux-watch.sh +39 -0
  4. package/CHANGELOG.md +41 -0
  5. package/README.md +87 -1
  6. package/config.toml.example +35 -0
  7. package/package.json +6 -4
  8. package/packages/architecture-generator/dist/adr.d.ts +15 -0
  9. package/packages/architecture-generator/dist/adr.d.ts.map +1 -0
  10. package/packages/architecture-generator/dist/adr.js +42 -0
  11. package/packages/architecture-generator/dist/adr.js.map +1 -0
  12. package/packages/architecture-generator/dist/index.d.ts +9 -0
  13. package/packages/architecture-generator/dist/index.d.ts.map +1 -0
  14. package/packages/architecture-generator/dist/index.js +9 -0
  15. package/packages/architecture-generator/dist/index.js.map +1 -0
  16. package/packages/architecture-generator/dist/mermaid.d.ts +30 -0
  17. package/packages/architecture-generator/dist/mermaid.d.ts.map +1 -0
  18. package/packages/architecture-generator/dist/mermaid.js +58 -0
  19. package/packages/architecture-generator/dist/mermaid.js.map +1 -0
  20. package/packages/architecture-generator/dist/types.d.ts +30 -0
  21. package/packages/architecture-generator/dist/types.d.ts.map +1 -0
  22. package/packages/architecture-generator/dist/types.js +7 -0
  23. package/packages/architecture-generator/dist/types.js.map +1 -0
  24. package/packages/architecture-generator/src/adr.ts +51 -0
  25. package/packages/architecture-generator/src/index.ts +9 -0
  26. package/packages/architecture-generator/src/mermaid.ts +76 -0
  27. package/packages/architecture-generator/src/types.ts +30 -0
  28. package/packages/capability-registry/src/types.js +10 -0
  29. package/packages/clipboard/dist/src/copy-sync.d.ts.map +1 -1
  30. package/packages/clipboard/dist/src/copy-sync.js +45 -3
  31. package/packages/clipboard/dist/src/copy-sync.js.map +1 -1
  32. package/packages/clipboard/dist/src/index.d.ts +1 -0
  33. package/packages/clipboard/dist/src/index.d.ts.map +1 -1
  34. package/packages/clipboard/dist/src/index.js +1 -0
  35. package/packages/clipboard/dist/src/index.js.map +1 -1
  36. package/packages/clipboard/dist/src/paste-bridge.d.ts +19 -0
  37. package/packages/clipboard/dist/src/paste-bridge.d.ts.map +1 -0
  38. package/packages/clipboard/dist/src/paste-bridge.js +78 -0
  39. package/packages/clipboard/dist/src/paste-bridge.js.map +1 -0
  40. package/packages/clipboard/src/copy-sync.ts +52 -3
  41. package/packages/clipboard/src/index.ts +1 -0
  42. package/packages/clipboard/src/paste-bridge.ts +86 -0
  43. package/packages/clipboard-plugin/dist/index.d.ts +28 -0
  44. package/packages/clipboard-plugin/dist/index.d.ts.map +1 -0
  45. package/packages/clipboard-plugin/dist/index.js +64 -0
  46. package/packages/clipboard-plugin/dist/index.js.map +1 -0
  47. package/packages/clipboard-plugin/dist/types.d.ts +30 -0
  48. package/packages/clipboard-plugin/dist/types.d.ts.map +1 -0
  49. package/packages/clipboard-plugin/dist/types.js +5 -0
  50. package/packages/clipboard-plugin/dist/types.js.map +1 -0
  51. package/packages/clipboard-plugin/src/index.ts +74 -0
  52. package/packages/clipboard-plugin/src/types.ts +30 -0
  53. package/packages/file-copy-helper/dist/src/extension.js +84 -25
  54. package/packages/file-copy-helper/src/extension.debug.ts +226 -0
  55. package/packages/file-copy-helper/src/extension.ts +89 -59
  56. package/packages/knowledge-graph/dist/graph-extractor.d.ts +27 -0
  57. package/packages/knowledge-graph/dist/graph-extractor.d.ts.map +1 -0
  58. package/packages/knowledge-graph/dist/graph-extractor.js +276 -0
  59. package/packages/knowledge-graph/dist/graph-extractor.js.map +1 -0
  60. package/packages/knowledge-graph/dist/index.d.ts +27 -0
  61. package/packages/knowledge-graph/dist/index.d.ts.map +1 -0
  62. package/packages/knowledge-graph/dist/index.js +27 -0
  63. package/packages/knowledge-graph/dist/index.js.map +1 -0
  64. package/packages/knowledge-graph/dist/mcp-client.d.ts +38 -0
  65. package/packages/knowledge-graph/dist/mcp-client.d.ts.map +1 -0
  66. package/packages/knowledge-graph/dist/mcp-client.js +157 -0
  67. package/packages/knowledge-graph/dist/mcp-client.js.map +1 -0
  68. package/packages/knowledge-graph/dist/provenance-linker.d.ts +19 -0
  69. package/packages/knowledge-graph/dist/provenance-linker.d.ts.map +1 -0
  70. package/packages/knowledge-graph/dist/provenance-linker.js +91 -0
  71. package/packages/knowledge-graph/dist/provenance-linker.js.map +1 -0
  72. package/packages/knowledge-graph/dist/types.d.ts +53 -0
  73. package/packages/knowledge-graph/dist/types.d.ts.map +1 -0
  74. package/packages/knowledge-graph/dist/types.js +7 -0
  75. package/packages/knowledge-graph/dist/types.js.map +1 -0
  76. package/packages/knowledge-graph/src/graph-extractor.ts +338 -0
  77. package/packages/knowledge-graph/src/index.ts +27 -0
  78. package/packages/knowledge-graph/src/mcp-client.ts +237 -0
  79. package/packages/knowledge-graph/src/provenance-linker.ts +101 -0
  80. package/packages/knowledge-graph/src/types.ts +86 -0
  81. package/packages/milestone-manager/dist/engine.d.ts +27 -0
  82. package/packages/milestone-manager/dist/engine.d.ts.map +1 -0
  83. package/packages/milestone-manager/dist/engine.js +78 -0
  84. package/packages/milestone-manager/dist/engine.js.map +1 -0
  85. package/packages/milestone-manager/dist/index.d.ts +6 -0
  86. package/packages/milestone-manager/dist/index.d.ts.map +1 -0
  87. package/packages/milestone-manager/dist/index.js +6 -0
  88. package/packages/milestone-manager/dist/index.js.map +1 -0
  89. package/packages/milestone-manager/dist/types.d.ts +25 -0
  90. package/packages/milestone-manager/dist/types.d.ts.map +1 -0
  91. package/packages/milestone-manager/dist/types.js +7 -0
  92. package/packages/milestone-manager/dist/types.js.map +1 -0
  93. package/packages/milestone-manager/src/engine.ts +105 -0
  94. package/packages/milestone-manager/src/index.ts +6 -0
  95. package/packages/milestone-manager/src/types.ts +28 -0
  96. package/packages/notification/dist/adapters/line-adapter.d.ts +17 -0
  97. package/packages/notification/dist/adapters/line-adapter.d.ts.map +1 -0
  98. package/packages/notification/dist/adapters/line-adapter.js +99 -0
  99. package/packages/notification/dist/adapters/line-adapter.js.map +1 -0
  100. package/packages/notification/dist/index.d.ts +1 -0
  101. package/packages/notification/dist/index.d.ts.map +1 -1
  102. package/packages/notification/dist/index.js +1 -0
  103. package/packages/notification/dist/index.js.map +1 -1
  104. package/packages/notification/dist/notification-center.d.ts.map +1 -1
  105. package/packages/notification/dist/notification-center.js +4 -0
  106. package/packages/notification/dist/notification-center.js.map +1 -1
  107. package/packages/notification/dist/types.d.ts +6 -2
  108. package/packages/notification/dist/types.d.ts.map +1 -1
  109. package/packages/notification/src/adapters/line-adapter.ts +119 -0
  110. package/packages/notification/src/index.ts +1 -0
  111. package/packages/notification/src/notification-center.ts +6 -0
  112. package/packages/notification/src/types.ts +7 -2
  113. package/packages/project-bootstrap/dist/bootstrap.d.ts +23 -0
  114. package/packages/project-bootstrap/dist/bootstrap.d.ts.map +1 -0
  115. package/packages/project-bootstrap/dist/bootstrap.js +128 -0
  116. package/packages/project-bootstrap/dist/bootstrap.js.map +1 -0
  117. package/packages/project-bootstrap/dist/frappe.d.ts +8 -0
  118. package/packages/project-bootstrap/dist/frappe.d.ts.map +1 -0
  119. package/packages/project-bootstrap/dist/frappe.js +82 -0
  120. package/packages/project-bootstrap/dist/frappe.js.map +1 -0
  121. package/packages/project-bootstrap/dist/index.d.ts +27 -0
  122. package/packages/project-bootstrap/dist/index.d.ts.map +1 -0
  123. package/packages/project-bootstrap/dist/index.js +27 -0
  124. package/packages/project-bootstrap/dist/index.js.map +1 -0
  125. package/packages/project-bootstrap/dist/minimal.d.ts +8 -0
  126. package/packages/project-bootstrap/dist/minimal.d.ts.map +1 -0
  127. package/packages/project-bootstrap/dist/minimal.js +35 -0
  128. package/packages/project-bootstrap/dist/minimal.js.map +1 -0
  129. package/packages/project-bootstrap/dist/types.d.ts +61 -0
  130. package/packages/project-bootstrap/dist/types.d.ts.map +1 -0
  131. package/packages/project-bootstrap/dist/types.js +7 -0
  132. package/packages/project-bootstrap/dist/types.js.map +1 -0
  133. package/packages/project-bootstrap/dist/typescript.d.ts +8 -0
  134. package/packages/project-bootstrap/dist/typescript.d.ts.map +1 -0
  135. package/packages/project-bootstrap/dist/typescript.js +103 -0
  136. package/packages/project-bootstrap/dist/typescript.js.map +1 -0
  137. package/packages/project-bootstrap/src/bootstrap.ts +166 -0
  138. package/packages/project-bootstrap/src/frappe.ts +97 -0
  139. package/packages/project-bootstrap/src/index.ts +27 -0
  140. package/packages/project-bootstrap/src/minimal.ts +45 -0
  141. package/packages/project-bootstrap/src/types.ts +66 -0
  142. package/packages/project-bootstrap/src/typescript.ts +114 -0
  143. package/packages/provider-router/dist/src/types.js +1 -1
  144. package/packages/write-review/src/index.ts +47 -0
  145. package/packages/write-review/src/injection.ts +21 -1
  146. package/prompts/agents/reviewer.md +57 -0
  147. package/prompts/agents/writer.md +42 -0
  148. package/prompts/slash-commands.md +40 -0
  149. package/prompts/write-review.md +50 -0
  150. package/scripts/copy-plugins.js +56 -0
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Clipboard Bridge Plugin — Main Entry
3
+ *
4
+ * Provides clipboard bridge functionality as a pi plugin.
5
+ * Integrates paste-from-bridge and paste shortcut registration.
6
+ */
7
+
8
+ import type { FrameworkExtension, ToolExtension } from "@pi/framework-plugin-sdk";
9
+ import { pasteFromBridge, registerPasteShortcut } from "@pi/clipboard";
10
+
11
+ export type { ClipboardBridgeConfig } from "./types.js";
12
+
13
+ /**
14
+ * Create a clipboard bridge tool plugin.
15
+ * Provides paste-from-bridge functionality.
16
+ */
17
+ export function createClipboardBridgeTool(): ToolExtension {
18
+ return {
19
+ capability: "tool",
20
+ name: "Clipboard Bridge",
21
+ tools: [
22
+ {
23
+ name: "paste-from-bridge",
24
+ description: "Read content from the clipboard bridge file (~/.herdr-clipboard)",
25
+ parameters: {
26
+ type: "object",
27
+ properties: {},
28
+ required: [],
29
+ },
30
+ execute: async () => {
31
+ const content = pasteFromBridge();
32
+ return { content };
33
+ },
34
+ },
35
+ ],
36
+ config: {},
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Create a clipboard bridge framework extension.
42
+ * For framework detection compatibility.
43
+ */
44
+ export function createClipboardBridgePlugin(): FrameworkExtension {
45
+ return {
46
+ capability: "framework",
47
+ name: "Clipboard Bridge Plugin",
48
+ detector: {
49
+ detect: async () => {
50
+ // This plugin doesn't detect frameworks, it's a utility plugin
51
+ return false;
52
+ },
53
+ signals: [],
54
+ },
55
+ config: {},
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Register the paste shortcut with pi runtime.
61
+ * Call this during plugin activation.
62
+ */
63
+ export function registerPasteShortcutHandler(
64
+ pi: unknown,
65
+ Key: unknown,
66
+ ): void {
67
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
68
+ registerPasteShortcut(pi as any, Key as any);
69
+ }
70
+
71
+ /**
72
+ * Default plugin export.
73
+ */
74
+ export default createClipboardBridgeTool;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Clipboard Bridge Plugin — Types
3
+ */
4
+
5
+ import type { ToolExtension } from "@pi/framework-plugin-sdk";
6
+
7
+ export interface ClipboardBridgeConfig {
8
+ /** Enable the paste shortcut (default: true) */
9
+ enablePasteShortcut?: boolean;
10
+ /** Bridge file path (default: ~/.herdr-clipboard) */
11
+ bridgePath?: string;
12
+ /** Auto-sync to clipboard (default: true) */
13
+ autoSync?: boolean;
14
+ }
15
+
16
+ export interface ClipboardBridgeTool extends ToolExtension {
17
+ capability: "tool";
18
+ name: "Clipboard Bridge";
19
+ tools: {
20
+ name: "paste-from-bridge";
21
+ description: "Read content from the clipboard bridge file and return it";
22
+ parameters: {
23
+ type: "object";
24
+ properties: {};
25
+ required: [];
26
+ };
27
+ execute: () => Promise<{ content: string | null }>;
28
+ }[];
29
+ config: ClipboardBridgeConfig;
30
+ }
@@ -1,39 +1,92 @@
1
- // packages/file-copy-helper/src/extension.ts
2
- var COPY_KEYWORDS = [
3
- "mimic",
4
- "copy from",
5
- "copy to",
6
- "clone from",
7
- "clone to",
8
- "replicate",
9
- "port from",
10
- "migrate from",
11
- "bring from",
12
- "move from"
1
+ // src/extension.ts
2
+ var COPY_KEYWORDS = ["mimic", "replicate", "clone"];
3
+ var COPY_DEST_CONTEXT = ["to ", "into ", "/dest/", "/target/", "over to"];
4
+ var COPY_SOURCE_CONTEXT = ["from ", "source", "/source/", "orig"];
5
+ var IMPORT_ERROR_TRIGGERS = [
6
+ "import error",
7
+ "import errors",
8
+ "cannot find module",
9
+ "cannot resolve",
10
+ "module not found",
11
+ "missing import",
12
+ "missing file",
13
+ "does not exist",
14
+ "@repo/",
15
+ "error one by one",
16
+ "fix errors one by one",
17
+ "fix imports one by one",
18
+ "roll back"
13
19
  ];
14
20
  var COPY_RULE = `
15
- ## File Copy Rule (CRITICAL)
21
+ ## COPY FILES DIRECTLY (CRITICAL)
16
22
 
17
- When copying files between folders, ALWAYS use \`cp\` or \`sudo cp\` instead of writing from scratch:
23
+ When asked to mimic, copy, clone, or replicate files:
18
24
 
19
- \`\`\`bash
20
- # Direct copy (if you have permission)
21
- cp /path/to/source/file.ts /path/to/dest/file.ts
25
+ 1. **DO NOT read the source files** - just copy them directly
26
+ 2. **Run \`sudo cp\` immediately** - no reading needed
27
+ 3. **If files have different owners**, use sudo
22
28
 
23
- # With sudo (if permission denied)
24
- sudo cp /path/to/source/file.ts /path/to/dest/file.ts
29
+ Examples:
30
+ \`\`\`bash
31
+ # Copy single file
32
+ sudo cp /source/file.ts /dest/file.ts
25
33
 
26
34
  # Copy entire folder
27
- sudo cp -r /path/to/source/folder /path/to/dest/
35
+ sudo cp -r /source/folder/ /dest/folder/
36
+
37
+ # Copy matching pattern
38
+ sudo cp /source/*.tsx /dest/
28
39
  \`\`\`
29
40
 
30
- **NEVER write files from scratch when copying is available** - it causes incomplete/imprecise code.
41
+ **STOP**: Do NOT use \`read\`, \`expand\`, or \`cat\` on source files. Just copy.
31
42
 
32
- The source files already exist. Just copy them directly.
43
+ **Why**: Reading wastes time. The source files are complete - copying preserves everything exactly.
33
44
  `;
34
- function shouldInject(text) {
45
+ var IMPORT_ERROR_RULE = `
46
+ ## IMPORT ERRORS? COPY THOSE FILES TOO!
47
+
48
+ When you see import/module errors after copying a file:
49
+
50
+ 1. **STOP fixing imports one by one**
51
+ 2. **STOP rolling back**
52
+ 3. **LIST all missing import files** for the user
53
+ 4. **Ask user which to copy**, then copy them via sudo cp
54
+
55
+ Example:
56
+ \`\`\`bash
57
+ # If error shows missing:
58
+ # - @repo/auth/server
59
+ # - @repo/design-system/components/ui/sidebar
60
+ # - ./components/notifications-provider
61
+
62
+ # Then copy these files the same way:
63
+ sudo cp /source/path/@repo/auth/server.ts /dest/path/@repo/auth/server.ts
64
+ sudo cp -r /source/path/@repo/design-system/components/ui/sidebar /dest/path/@repo/design-system/components/ui/sidebar
65
+ sudo cp /source/path/components/notifications-provider.tsx /dest/path/components/notifications-provider.tsx
66
+ \`\`\`
67
+
68
+ **NEVER fix imports one by one** - copy the missing files instead!
69
+ **NEVER roll back** - keep the copied file and copy its dependencies!
70
+ `;
71
+ function shouldInjectCopyRule(text) {
72
+ const lower = text.toLowerCase();
73
+ const hasIntent = COPY_KEYWORDS.some((keyword) => lower.includes(keyword));
74
+ if (!hasIntent) {
75
+ return false;
76
+ }
77
+ const hasDest = COPY_DEST_CONTEXT.some((ctx) => lower.includes(ctx));
78
+ if (!hasDest) {
79
+ return false;
80
+ }
81
+ const hasSource = COPY_SOURCE_CONTEXT.some((ctx) => lower.includes(ctx));
82
+ if (!hasSource) {
83
+ return false;
84
+ }
85
+ return true;
86
+ }
87
+ function shouldInjectImportErrorRule(text) {
35
88
  const lower = text.toLowerCase();
36
- return COPY_KEYWORDS.some((keyword) => lower.includes(keyword));
89
+ return IMPORT_ERROR_TRIGGERS.some((t) => lower.includes(t));
37
90
  }
38
91
  function registerFileCopyHelper(pi) {
39
92
  pi.on("input", (event) => {
@@ -44,12 +97,18 @@ function registerFileCopyHelper(pi) {
44
97
  if (!text || text.length < 10) {
45
98
  return;
46
99
  }
47
- if (shouldInject(text)) {
100
+ if (shouldInjectCopyRule(text)) {
48
101
  return {
49
102
  action: "transform",
50
103
  text: text + COPY_RULE
51
104
  };
52
105
  }
106
+ if (shouldInjectImportErrorRule(text)) {
107
+ return {
108
+ action: "transform",
109
+ text: text + IMPORT_ERROR_RULE
110
+ };
111
+ }
53
112
  return;
54
113
  });
55
114
  }
@@ -0,0 +1,226 @@
1
+ /**
2
+ * file-copy-helper extension (DEBUG VERSION)
3
+ *
4
+ * Injects "use cp instead of writing from scratch" reminder when user
5
+ * asks to mimic, copy, clone, or replicate files/folders between locations.
6
+ *
7
+ * DEBUG: All events and rule evaluations logged to /tmp/file-copy-helper-debug.log
8
+ */
9
+
10
+ import type {
11
+ ExtensionAPI,
12
+ InputEvent,
13
+ InputEventResult,
14
+ } from "@earendil-works/pi-coding-agent";
15
+ import * as fs from "fs";
16
+
17
+ // DEBUG: Write to file instead of TUI
18
+ const DEBUG_LOG = "/tmp/file-copy-helper-debug.log";
19
+
20
+ function debugLog(label: string, data: unknown): void {
21
+ const timestamp = new Date().toISOString();
22
+ const entry = `[${timestamp}] ${label}: ${JSON.stringify(data, null, 2)}\n`;
23
+ fs.appendFileSync(DEBUG_LOG, entry);
24
+ }
25
+
26
+ // Keywords that trigger the copy rule injection
27
+ // MUST have explicit mimic/copy/clone/replicate intent + source/dest context
28
+ const COPY_KEYWORDS = ["mimic", "replicate", "clone"];
29
+
30
+ // Must have destination context to trigger (avoids false positives)
31
+ const COPY_DEST_CONTEXT = ["to ", "into ", "/dest/", "/target/", "over to"];
32
+
33
+ // Must have source context to trigger
34
+ const COPY_SOURCE_CONTEXT = ["from ", "source", "/source/", "orig"];
35
+
36
+ // When agent sees import/module errors (needs to copy those files too)
37
+ const IMPORT_ERROR_TRIGGERS = [
38
+ "import error",
39
+ "import errors",
40
+ "cannot find module",
41
+ "cannot resolve",
42
+ "module not found",
43
+ "missing import",
44
+ "missing file",
45
+ "does not exist",
46
+ "@repo/",
47
+ "error one by one",
48
+ "fix errors one by one",
49
+ "fix imports one by one",
50
+ "roll back",
51
+ ];
52
+
53
+ // Extract source and destination from user request
54
+ const COPY_RULE = `
55
+ ## COPY FILES DIRECTLY (CRITICAL)
56
+
57
+ When asked to mimic, copy, clone, or replicate files:
58
+
59
+ 1. **DO NOT read the source files** - just copy them directly
60
+ 2. **Run \`sudo cp\` immediately** - no reading needed
61
+ 3. **If files have different owners**, use sudo
62
+
63
+ Examples:
64
+ \`\`\`bash
65
+ # Copy single file
66
+ sudo cp /source/file.ts /dest/file.ts
67
+
68
+ # Copy entire folder
69
+ sudo cp -r /source/folder/ /dest/folder/
70
+
71
+ # Copy matching pattern
72
+ sudo cp /source/*.tsx /dest/
73
+ \`\`\`
74
+
75
+ **STOP**: Do NOT use \`read\`, \`expand\`, or \`cat\` on source files. Just copy.
76
+
77
+ **Why**: Reading wastes time. The source files are complete - copying preserves everything exactly.
78
+ `;
79
+
80
+ // When agent sees import errors - copy those files too!
81
+ const IMPORT_ERROR_RULE = `
82
+ ## IMPORT ERRORS? COPY THOSE FILES TOO!
83
+
84
+ When you see import/module errors after copying a file:
85
+
86
+ 1. **STOP fixing imports one by one**
87
+ 2. **STOP rolling back**
88
+ 3. **LIST all missing import files** for the user
89
+ 4. **Ask user which to copy**, then copy them via sudo cp
90
+
91
+ Example:
92
+ \`\`\`bash
93
+ # If error shows missing:
94
+ # - @repo/auth/server
95
+ # - @repo/design-system/components/ui/sidebar
96
+ # - ./components/notifications-provider
97
+
98
+ # Then copy these files the same way:
99
+ sudo cp /source/path/@repo/auth/server.ts /dest/path/@repo/auth/server.ts
100
+ sudo cp -r /source/path/@repo/design-system/components/ui/sidebar /dest/path/@repo/design-system/components/ui/sidebar
101
+ sudo cp /source/path/components/notifications-provider.tsx /dest/path/components/notifications-provider.tsx
102
+ \`\`\`
103
+
104
+ **NEVER fix imports one by one** - copy the missing files instead!
105
+ **NEVER roll back** - keep the copied file and copy its dependencies!
106
+ `;
107
+
108
+ /**
109
+ * Check if text contains copy-related keywords
110
+ * Requires: explicit intent (mimic/copy/clone/replicate) AND destination context
111
+ */
112
+ function shouldInjectCopyRule(text: string): { triggered: boolean; reason: string } {
113
+ const lower = text.toLowerCase();
114
+
115
+ // Must have explicit mimic/copy/clone/replicate intent
116
+ const hasIntent = COPY_KEYWORDS.some((keyword) => lower.includes(keyword));
117
+ if (!hasIntent) {
118
+ return { triggered: false, reason: `No intent keyword found. Keywords: ${COPY_KEYWORDS.join(", ")}` };
119
+ }
120
+
121
+ // Must have destination context (avoids false positives on "copy this code" etc)
122
+ const hasDest = COPY_DEST_CONTEXT.some((ctx) => lower.includes(ctx));
123
+ if (!hasDest) {
124
+ return { triggered: false, reason: `No destination context found. Dest contexts: ${COPY_DEST_CONTEXT.join(", ")}` };
125
+ }
126
+
127
+ // Should have source context for full mimic behavior
128
+ const hasSource = COPY_SOURCE_CONTEXT.some((ctx) => lower.includes(ctx));
129
+ if (!hasSource) {
130
+ return { triggered: false, reason: `No source context found. Source contexts: ${COPY_SOURCE_CONTEXT.join(", ")}` };
131
+ }
132
+
133
+ return { triggered: true, reason: `All conditions met: hasIntent=${hasIntent}, hasDest=${hasDest}, hasSource=${hasSource}` };
134
+ }
135
+
136
+ /**
137
+ * Check if text contains import error triggers
138
+ */
139
+ function shouldInjectImportErrorRule(text: string): { triggered: boolean; reason: string } {
140
+ const lower = text.toLowerCase();
141
+ const matchingTriggers = IMPORT_ERROR_TRIGGERS.filter((t) => lower.includes(t));
142
+
143
+ if (matchingTriggers.length === 0) {
144
+ return { triggered: false, reason: "No import error triggers found" };
145
+ }
146
+
147
+ return { triggered: true, reason: `Matched triggers: ${matchingTriggers.join(", ")}` };
148
+ }
149
+
150
+ /**
151
+ * Register the file-copy-helper extension
152
+ */
153
+ export function registerFileCopyHelper(pi: ExtensionAPI): void {
154
+ // Clear debug log on startup
155
+ fs.writeFileSync(DEBUG_LOG, `[${new Date().toISOString()}] === DEBUG SESSION STARTED ===\n`);
156
+
157
+ pi.on("input", (event: InputEvent): InputEventResult | undefined => {
158
+ // DEBUG: Log what we receive
159
+ debugLog("INPUT_EVENT", {
160
+ source: event.source,
161
+ textLength: event.text?.length,
162
+ textPreview: event.text?.substring(0, 200),
163
+ fullText: event.text,
164
+ });
165
+
166
+ // Only process user input (not from extensions or RPC)
167
+ if (event.source !== "interactive") {
168
+ debugLog("SKIP", "not interactive input - skipping");
169
+ return;
170
+ }
171
+
172
+ const text = event.text.trim();
173
+ debugLog("PROCESSING_TEXT", {
174
+ text,
175
+ length: text.length,
176
+ hasMinLength: text.length >= 10,
177
+ });
178
+
179
+ if (!text || text.length < 10) {
180
+ debugLog("SKIP", "text too short (< 10 chars)");
181
+ return;
182
+ }
183
+
184
+ // Check if this is a copy/mimic request
185
+ const copyCheck = shouldInjectCopyRule(text);
186
+ debugLog("COPY_RULE_CHECK", {
187
+ triggered: copyCheck.triggered,
188
+ reason: copyCheck.reason,
189
+ matchingKeywords: COPY_KEYWORDS.filter(k => text.toLowerCase().includes(k)),
190
+ matchingDest: COPY_DEST_CONTEXT.filter(ctx => text.toLowerCase().includes(ctx)),
191
+ matchingSource: COPY_SOURCE_CONTEXT.filter(ctx => text.toLowerCase().includes(ctx)),
192
+ });
193
+
194
+ if (copyCheck.triggered) {
195
+ debugLog("INJECT", { rule: "COPY_RULE", reason: copyCheck.reason });
196
+ return {
197
+ action: "transform",
198
+ text: text + COPY_RULE,
199
+ };
200
+ }
201
+
202
+ // Inject import error rule when agent sees missing imports
203
+ const importCheck = shouldInjectImportErrorRule(text);
204
+ debugLog("IMPORT_ERROR_CHECK", {
205
+ triggered: importCheck.triggered,
206
+ reason: importCheck.reason,
207
+ matchingTriggers: IMPORT_ERROR_TRIGGERS.filter(t => text.toLowerCase().includes(t)),
208
+ });
209
+
210
+ if (importCheck.triggered) {
211
+ debugLog("INJECT", { rule: "IMPORT_ERROR_RULE", reason: importCheck.reason });
212
+ return {
213
+ action: "transform",
214
+ text: text + IMPORT_ERROR_RULE,
215
+ };
216
+ }
217
+
218
+ debugLog("NO_INJECTION", "no rule matched - passing through");
219
+ return;
220
+ });
221
+ }
222
+
223
+ // Default export for pi extension loading
224
+ export default function fileCopyHelperExtension(pi: ExtensionAPI): void {
225
+ registerFileCopyHelper(pi);
226
+ }
@@ -1,8 +1,10 @@
1
1
  /**
2
- * file-copy-helper extension
2
+ * file-copy-helper extension (DEBUG VERSION)
3
3
  *
4
4
  * Injects "use cp instead of writing from scratch" reminder when user
5
5
  * asks to mimic, copy, clone, or replicate files/folders between locations.
6
+ *
7
+ * DEBUG: All events and rule evaluations logged to /tmp/file-copy-helper-debug.log
6
8
  */
7
9
 
8
10
  import type {
@@ -10,32 +12,26 @@ import type {
10
12
  InputEvent,
11
13
  InputEventResult,
12
14
  } from "@earendil-works/pi-coding-agent";
15
+ import * as fs from "fs";
16
+
17
+ // DEBUG: Write to file instead of TUI
18
+ const DEBUG_LOG = "/tmp/file-copy-helper-debug.log";
19
+
20
+ function debugLog(label: string, data: unknown): void {
21
+ const timestamp = new Date().toISOString();
22
+ const entry = `[${timestamp}] ${label}: ${JSON.stringify(data, null, 2)}\n`;
23
+ fs.appendFileSync(DEBUG_LOG, entry);
24
+ }
13
25
 
14
26
  // Keywords that trigger the copy rule injection
15
- const COPY_KEYWORDS = [
16
- "mimic",
17
- "copy from",
18
- "copy to",
19
- "clone from",
20
- "clone to",
21
- "replicate",
22
- "port from",
23
- "migrate from",
24
- "bring from",
25
- "move from",
26
- ];
27
+ // MUST have explicit mimic/copy/clone/replicate intent + source/dest context
28
+ const COPY_KEYWORDS = ["mimic", "replicate", "clone"];
27
29
 
28
- // When agent starts reading source files (bad behavior to break)
29
- const READ_SOURCE_TRIGGERS = [
30
- "expand",
31
- "read ",
32
- "view ",
33
- "cat ",
34
- "catting",
35
- "reading the file",
36
- "reading file",
37
- "lets read",
38
- ];
30
+ // Must have destination context to trigger (avoids false positives)
31
+ const COPY_DEST_CONTEXT = ["to ", "into ", "/dest/", "/target/", "over to"];
32
+
33
+ // Must have source context to trigger
34
+ const COPY_SOURCE_CONTEXT = ["from ", "source", "/source/", "orig"];
39
35
 
40
36
  // When agent sees import/module errors (needs to copy those files too)
41
37
  const IMPORT_ERROR_TRIGGERS = [
@@ -81,17 +77,6 @@ sudo cp /source/*.tsx /dest/
81
77
  **Why**: Reading wastes time. The source files are complete - copying preserves everything exactly.
82
78
  `;
83
79
 
84
- // Lighter reminder when agent starts reading source
85
- const READ_WARNING = `
86
- ## STOP READING - JUST COPY
87
-
88
- You are about to read a source file. DO NOT read it.
89
-
90
- Just run: sudo cp /path/to/source /path/to/dest
91
-
92
- Copying is instant and preserves exact code. Reading is unnecessary.
93
- `;
94
-
95
80
  // When agent sees import errors - copy those files too!
96
81
  const IMPORT_ERROR_RULE = `
97
82
  ## IMPORT ERRORS? COPY THOSE FILES TOO!
@@ -122,70 +107,115 @@ sudo cp /source/path/components/notifications-provider.tsx /dest/path/components
122
107
 
123
108
  /**
124
109
  * Check if text contains copy-related keywords
110
+ * Requires: explicit intent (mimic/copy/clone/replicate) AND destination context
125
111
  */
126
- function shouldInjectCopyRule(text: string): boolean {
112
+ function shouldInjectCopyRule(text: string): { triggered: boolean; reason: string } {
127
113
  const lower = text.toLowerCase();
128
- return COPY_KEYWORDS.some((keyword) => lower.includes(keyword));
129
- }
130
114
 
131
- /**
132
- * Check if text contains source file reading triggers
133
- */
134
- function shouldInjectReadWarning(text: string): boolean {
135
- const lower = text.toLowerCase();
136
- // Check if this looks like reading source files before copying
137
- const hasSourcePath = /(source|auto-|from-|orig)/i.test(text);
138
- const hasReadIntent = READ_SOURCE_TRIGGERS.some((t) => lower.includes(t));
139
- return hasSourcePath && hasReadIntent;
115
+ // Must have explicit mimic/copy/clone/replicate intent
116
+ const hasIntent = COPY_KEYWORDS.some((keyword) => lower.includes(keyword));
117
+ if (!hasIntent) {
118
+ return { triggered: false, reason: `No intent keyword found. Keywords: ${COPY_KEYWORDS.join(", ")}` };
119
+ }
120
+
121
+ // Must have destination context (avoids false positives on "copy this code" etc)
122
+ const hasDest = COPY_DEST_CONTEXT.some((ctx) => lower.includes(ctx));
123
+ if (!hasDest) {
124
+ return { triggered: false, reason: `No destination context found. Dest contexts: ${COPY_DEST_CONTEXT.join(", ")}` };
125
+ }
126
+
127
+ // Should have source context for full mimic behavior
128
+ const hasSource = COPY_SOURCE_CONTEXT.some((ctx) => lower.includes(ctx));
129
+ if (!hasSource) {
130
+ return { triggered: false, reason: `No source context found. Source contexts: ${COPY_SOURCE_CONTEXT.join(", ")}` };
131
+ }
132
+
133
+ return { triggered: true, reason: `All conditions met: hasIntent=${hasIntent}, hasDest=${hasDest}, hasSource=${hasSource}` };
140
134
  }
141
135
 
142
136
  /**
143
137
  * Check if text contains import error triggers
144
138
  */
145
- function shouldInjectImportErrorRule(text: string): boolean {
139
+ function shouldInjectImportErrorRule(text: string): { triggered: boolean; reason: string } {
146
140
  const lower = text.toLowerCase();
147
- return IMPORT_ERROR_TRIGGERS.some((t) => lower.includes(t));
141
+ const matchingTriggers = IMPORT_ERROR_TRIGGERS.filter((t) => lower.includes(t));
142
+
143
+ if (matchingTriggers.length === 0) {
144
+ return { triggered: false, reason: "No import error triggers found" };
145
+ }
146
+
147
+ return { triggered: true, reason: `Matched triggers: ${matchingTriggers.join(", ")}` };
148
148
  }
149
149
 
150
150
  /**
151
151
  * Register the file-copy-helper extension
152
152
  */
153
153
  export function registerFileCopyHelper(pi: ExtensionAPI): void {
154
+ // Clear debug log on startup
155
+ fs.writeFileSync(DEBUG_LOG, `[${new Date().toISOString()}] === DEBUG SESSION STARTED ===\n`);
156
+
154
157
  pi.on("input", (event: InputEvent): InputEventResult | undefined => {
158
+ // DEBUG: Log what we receive
159
+ debugLog("INPUT_EVENT", {
160
+ source: event.source,
161
+ textLength: event.text?.length,
162
+ textPreview: event.text?.substring(0, 200),
163
+ fullText: event.text,
164
+ });
165
+
155
166
  // Only process user input (not from extensions or RPC)
156
167
  if (event.source !== "interactive") {
168
+ debugLog("SKIP", "not interactive input - skipping");
157
169
  return;
158
170
  }
159
171
 
160
172
  const text = event.text.trim();
173
+ debugLog("PROCESSING_TEXT", {
174
+ text,
175
+ length: text.length,
176
+ hasMinLength: text.length >= 10,
177
+ });
178
+
161
179
  if (!text || text.length < 10) {
180
+ debugLog("SKIP", "text too short (< 10 chars)");
162
181
  return;
163
182
  }
164
183
 
165
184
  // Check if this is a copy/mimic request
166
- if (shouldInjectCopyRule(text)) {
185
+ const copyCheck = shouldInjectCopyRule(text);
186
+ debugLog("COPY_RULE_CHECK", {
187
+ triggered: copyCheck.triggered,
188
+ reason: copyCheck.reason,
189
+ matchingKeywords: COPY_KEYWORDS.filter(k => text.toLowerCase().includes(k)),
190
+ matchingDest: COPY_DEST_CONTEXT.filter(ctx => text.toLowerCase().includes(ctx)),
191
+ matchingSource: COPY_SOURCE_CONTEXT.filter(ctx => text.toLowerCase().includes(ctx)),
192
+ });
193
+
194
+ if (copyCheck.triggered) {
195
+ debugLog("INJECT", { rule: "COPY_RULE", reason: copyCheck.reason });
167
196
  return {
168
197
  action: "transform",
169
198
  text: text + COPY_RULE,
170
199
  };
171
200
  }
172
201
 
173
- // Warn if agent is about to read source files unnecessarily
174
- if (shouldInjectReadWarning(text)) {
175
- return {
176
- action: "transform",
177
- text: text + READ_WARNING,
178
- };
179
- }
180
-
181
202
  // Inject import error rule when agent sees missing imports
182
- if (shouldInjectImportErrorRule(text)) {
203
+ const importCheck = shouldInjectImportErrorRule(text);
204
+ debugLog("IMPORT_ERROR_CHECK", {
205
+ triggered: importCheck.triggered,
206
+ reason: importCheck.reason,
207
+ matchingTriggers: IMPORT_ERROR_TRIGGERS.filter(t => text.toLowerCase().includes(t)),
208
+ });
209
+
210
+ if (importCheck.triggered) {
211
+ debugLog("INJECT", { rule: "IMPORT_ERROR_RULE", reason: importCheck.reason });
183
212
  return {
184
213
  action: "transform",
185
214
  text: text + IMPORT_ERROR_RULE,
186
215
  };
187
216
  }
188
217
 
218
+ debugLog("NO_INJECTION", "no rule matched - passing through");
189
219
  return;
190
220
  });
191
221
  }