@bradygaster/squad-cli 0.9.0 → 0.9.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.
Files changed (74) hide show
  1. package/README.md +329 -329
  2. package/dist/cli/commands/personal.js +45 -45
  3. package/dist/cli/core/team-md.js +34 -34
  4. package/package.json +2 -2
  5. package/scripts/patch-esm-imports.mjs +105 -105
  6. package/scripts/patch-ink-rendering.mjs +115 -115
  7. package/templates/casting/Futurama.json +9 -9
  8. package/templates/casting-history.json +4 -4
  9. package/templates/casting-policy.json +37 -37
  10. package/templates/casting-reference.md +104 -104
  11. package/templates/casting-registry.json +3 -3
  12. package/templates/ceremonies.md +41 -41
  13. package/templates/charter.md +53 -53
  14. package/templates/constraint-tracking.md +38 -38
  15. package/templates/cooperative-rate-limiting.md +229 -229
  16. package/templates/copilot-instructions.md +46 -46
  17. package/templates/history.md +10 -10
  18. package/templates/identity/now.md +9 -9
  19. package/templates/identity/wisdom.md +15 -15
  20. package/templates/issue-lifecycle.md +412 -412
  21. package/templates/keda-scaler.md +164 -164
  22. package/templates/machine-capabilities.md +74 -74
  23. package/templates/mcp-config.md +90 -90
  24. package/templates/multi-agent-format.md +28 -28
  25. package/templates/orchestration-log.md +27 -27
  26. package/templates/plugin-marketplace.md +49 -49
  27. package/templates/ralph-circuit-breaker.md +313 -313
  28. package/templates/raw-agent-output.md +37 -37
  29. package/templates/roster.md +60 -60
  30. package/templates/routing.md +39 -39
  31. package/templates/run-output.md +50 -50
  32. package/templates/scribe-charter.md +119 -119
  33. package/templates/skill.md +24 -24
  34. package/templates/skills/agent-collaboration/SKILL.md +42 -42
  35. package/templates/skills/agent-conduct/SKILL.md +24 -24
  36. package/templates/skills/architectural-proposals/SKILL.md +151 -151
  37. package/templates/skills/ci-validation-gates/SKILL.md +84 -84
  38. package/templates/skills/cli-wiring/SKILL.md +47 -47
  39. package/templates/skills/client-compatibility/SKILL.md +89 -89
  40. package/templates/skills/cross-squad/SKILL.md +114 -114
  41. package/templates/skills/distributed-mesh/SKILL.md +287 -287
  42. package/templates/skills/distributed-mesh/mesh.json.example +30 -30
  43. package/templates/skills/distributed-mesh/sync-mesh.ps1 +111 -111
  44. package/templates/skills/distributed-mesh/sync-mesh.sh +104 -104
  45. package/templates/skills/docs-standards/SKILL.md +71 -71
  46. package/templates/skills/economy-mode/SKILL.md +114 -114
  47. package/templates/skills/external-comms/SKILL.md +329 -329
  48. package/templates/skills/gh-auth-isolation/SKILL.md +183 -183
  49. package/templates/skills/git-workflow/SKILL.md +204 -204
  50. package/templates/skills/github-multi-account/SKILL.md +95 -95
  51. package/templates/skills/history-hygiene/SKILL.md +36 -36
  52. package/templates/skills/humanizer/SKILL.md +105 -105
  53. package/templates/skills/init-mode/SKILL.md +102 -102
  54. package/templates/skills/model-selection/SKILL.md +117 -117
  55. package/templates/skills/nap/SKILL.md +24 -24
  56. package/templates/skills/personal-squad/SKILL.md +57 -57
  57. package/templates/skills/release-process/SKILL.md +423 -423
  58. package/templates/skills/reskill/SKILL.md +92 -92
  59. package/templates/skills/reviewer-protocol/SKILL.md +79 -79
  60. package/templates/skills/secret-handling/SKILL.md +200 -200
  61. package/templates/skills/session-recovery/SKILL.md +155 -155
  62. package/templates/skills/squad-conventions/SKILL.md +69 -69
  63. package/templates/skills/test-discipline/SKILL.md +37 -37
  64. package/templates/skills/windows-compatibility/SKILL.md +74 -74
  65. package/templates/workflows/squad-ci.yml +24 -24
  66. package/templates/workflows/squad-docs.yml +54 -54
  67. package/templates/workflows/squad-insider-release.yml +61 -61
  68. package/templates/workflows/squad-issue-assign.yml +161 -161
  69. package/templates/workflows/squad-label-enforce.yml +181 -181
  70. package/templates/workflows/squad-preview.yml +55 -55
  71. package/templates/workflows/squad-promote.yml +120 -120
  72. package/templates/workflows/squad-release.yml +77 -77
  73. package/templates/workflows/squad-triage.yml +260 -260
  74. package/templates/workflows/sync-squad-labels.yml +169 -169
@@ -163,51 +163,51 @@ async function personalRemove(name) {
163
163
  * For now, we use a simplified inline template.
164
164
  */
165
165
  function generatePersonalCharterTemplate(name, role) {
166
- return `# ${name} — ${role}
167
-
168
- > Your one-line personality statement — what makes you tick
169
-
170
- ## Identity
171
-
172
- - **Name:** ${name}
173
- - **Role:** ${role}
174
- - **Expertise:** [Your 2-3 specific skills]
175
- - **Style:** [How you communicate — direct? thorough? opinionated?]
176
-
177
- ## What I Own
178
-
179
- - [Area of responsibility 1]
180
- - [Area of responsibility 2]
181
- - [Area of responsibility 3]
182
-
183
- ## How I Work
184
-
185
- - [Key approach or principle 1]
186
- - [Key approach or principle 2]
187
- - [Pattern or convention I follow]
188
-
189
- ## Boundaries
190
-
191
- **I handle:** [types of work this agent does]
192
-
193
- **I don't handle:** [types of work that belong to other team members]
194
-
195
- **When I'm unsure:** I say so and suggest who might know.
196
-
197
- ## Model
198
-
199
- - **Preferred:** auto
200
- - **Rationale:** Coordinator selects the best model based on task type
201
-
202
- ## Collaboration
203
-
204
- This is a personal agent — you're ambient across all projects.
205
- Ghost protocol is enforced in project contexts (observe, suggest, never modify).
206
-
207
- ## Voice
208
-
209
- [1-2 sentences describing personality. Be specific — you have opinions,
210
- preferences, and a style that's distinctly yours.]
166
+ return `# ${name} — ${role}
167
+
168
+ > Your one-line personality statement — what makes you tick
169
+
170
+ ## Identity
171
+
172
+ - **Name:** ${name}
173
+ - **Role:** ${role}
174
+ - **Expertise:** [Your 2-3 specific skills]
175
+ - **Style:** [How you communicate — direct? thorough? opinionated?]
176
+
177
+ ## What I Own
178
+
179
+ - [Area of responsibility 1]
180
+ - [Area of responsibility 2]
181
+ - [Area of responsibility 3]
182
+
183
+ ## How I Work
184
+
185
+ - [Key approach or principle 1]
186
+ - [Key approach or principle 2]
187
+ - [Pattern or convention I follow]
188
+
189
+ ## Boundaries
190
+
191
+ **I handle:** [types of work this agent does]
192
+
193
+ **I don't handle:** [types of work that belong to other team members]
194
+
195
+ **When I'm unsure:** I say so and suggest who might know.
196
+
197
+ ## Model
198
+
199
+ - **Preferred:** auto
200
+ - **Rationale:** Coordinator selects the best model based on task type
201
+
202
+ ## Collaboration
203
+
204
+ This is a personal agent — you're ambient across all projects.
205
+ Ghost protocol is enforced in project contexts (observe, suggest, never modify).
206
+
207
+ ## Voice
208
+
209
+ [1-2 sentences describing personality. Be specific — you have opinions,
210
+ preferences, and a style that's distinctly yours.]
211
211
  `;
212
212
  }
213
213
  //# sourceMappingURL=personal.js.map
@@ -31,40 +31,40 @@ export function hasCopilot(content) {
31
31
  */
32
32
  export function insertCopilotSection(content, autoAssign = false) {
33
33
  const autoAssignValue = autoAssign ? 'true' : 'false';
34
- const copilotSection = `
35
- ## Coding Agent
36
-
37
- <!-- copilot-auto-assign: ${autoAssignValue} -->
38
-
39
- | Name | Role | Charter | Status |
40
- |------|------|---------|--------|
41
- | @copilot | Coding Agent | — | 🤖 Coding Agent |
42
-
43
- ### Capabilities
44
-
45
- **🟢 Good fit — auto-route when enabled:**
46
- - Bug fixes with clear reproduction steps
47
- - Test coverage (adding missing tests, fixing flaky tests)
48
- - Lint/format fixes and code style cleanup
49
- - Dependency updates and version bumps
50
- - Small isolated features with clear specs
51
- - Boilerplate/scaffolding generation
52
- - Documentation fixes and README updates
53
-
54
- **🟡 Needs review — route to @copilot but flag for squad member PR review:**
55
- - Medium features with clear specs and acceptance criteria
56
- - Refactoring with existing test coverage
57
- - API endpoint additions following established patterns
58
- - Migration scripts with well-defined schemas
59
-
60
- **🔴 Not suitable — route to squad member instead:**
61
- - Architecture decisions and system design
62
- - Multi-system integration requiring coordination
63
- - Ambiguous requirements needing clarification
64
- - Security-critical changes (auth, encryption, access control)
65
- - Performance-critical paths requiring benchmarking
66
- - Changes requiring cross-team discussion
67
-
34
+ const copilotSection = `
35
+ ## Coding Agent
36
+
37
+ <!-- copilot-auto-assign: ${autoAssignValue} -->
38
+
39
+ | Name | Role | Charter | Status |
40
+ |------|------|---------|--------|
41
+ | @copilot | Coding Agent | — | 🤖 Coding Agent |
42
+
43
+ ### Capabilities
44
+
45
+ **🟢 Good fit — auto-route when enabled:**
46
+ - Bug fixes with clear reproduction steps
47
+ - Test coverage (adding missing tests, fixing flaky tests)
48
+ - Lint/format fixes and code style cleanup
49
+ - Dependency updates and version bumps
50
+ - Small isolated features with clear specs
51
+ - Boilerplate/scaffolding generation
52
+ - Documentation fixes and README updates
53
+
54
+ **🟡 Needs review — route to @copilot but flag for squad member PR review:**
55
+ - Medium features with clear specs and acceptance criteria
56
+ - Refactoring with existing test coverage
57
+ - API endpoint additions following established patterns
58
+ - Migration scripts with well-defined schemas
59
+
60
+ **🔴 Not suitable — route to squad member instead:**
61
+ - Architecture decisions and system design
62
+ - Multi-system integration requiring coordination
63
+ - Ambiguous requirements needing clarification
64
+ - Security-critical changes (auth, encryption, access control)
65
+ - Performance-critical paths requiring benchmarking
66
+ - Changes requiring cross-team discussion
67
+
68
68
  `;
69
69
  // Insert before "## Project Context" if it exists, otherwise append
70
70
  if (content.includes('## Project Context')) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bradygaster/squad-cli",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "Squad CLI — Command-line interface for the Squad multi-agent runtime",
5
5
  "type": "module",
6
6
  "bin": {
@@ -173,7 +173,7 @@
173
173
  "node": ">=22.5.0"
174
174
  },
175
175
  "dependencies": {
176
- "@bradygaster/squad-sdk": "file:../squad-sdk",
176
+ "@bradygaster/squad-sdk": ">=0.9.0",
177
177
  "ink": "^6.8.0",
178
178
  "react": "^19.2.4",
179
179
  "vscode-jsonrpc": "^8.2.1"
@@ -1,106 +1,106 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * ESM Import Patcher — dual-layer fix for Node 22/24+ compatibility
5
- *
6
- * Layer 1: Patch vscode-jsonrpc/package.json with `exports` field
7
- * vscode-jsonrpc@8.2.1 has no `exports` field. Node 22+ strict ESM
8
- * rejects subpath imports like 'vscode-jsonrpc/node' without it.
9
- * Injecting the exports map from v9.x fixes ALL subpath imports at once.
10
- *
11
- * Layer 2: Patch @github/copilot-sdk session.js (defense-in-depth)
12
- * copilot-sdk@0.1.32 imports 'vscode-jsonrpc/node' without .js extension.
13
- * This layer ensures the import works even if Layer 1 somehow fails.
14
- *
15
- * Issue: bradygaster/squad#449
16
- * Upstream: https://github.com/github/copilot-sdk/issues/707
17
- */
18
-
19
- import { readFileSync, writeFileSync, existsSync } from 'fs';
20
- import { join, dirname } from 'path';
21
- import { fileURLToPath } from 'url';
22
-
23
- const __dirname = dirname(fileURLToPath(import.meta.url));
24
-
25
- // Locations where npm workspaces / global install may place dependencies
26
- const SEARCH_ROOTS = [
27
- join(__dirname, '..', 'node_modules'), // squad-cli local
28
- join(__dirname, '..', '..', '..', 'node_modules'), // workspace root
29
- join(__dirname, '..', '..'), // global install (sibling)
30
- ];
31
-
32
- /**
33
- * Layer 1 — Inject `exports` field into vscode-jsonrpc/package.json.
34
- * This is the canonical fix: once the package has proper exports, Node's
35
- * ESM resolver handles every subpath ('vscode-jsonrpc/node', '/browser', etc.)
36
- * without needing per-file patches.
37
- */
38
- function patchVscodeJsonrpcExports() {
39
- const exportsField = {
40
- '.': { types: './lib/common/api.d.ts', default: './lib/node/main.js' },
41
- './node': { node: './lib/node/main.js', types: './lib/node/main.d.ts' },
42
- './node.js': { node: './lib/node/main.js', types: './lib/node/main.d.ts' },
43
- './browser': { types: './lib/browser/main.d.ts', browser: './lib/browser/main.js' },
44
- };
45
-
46
- for (const root of SEARCH_ROOTS) {
47
- const pkgPath = join(root, 'vscode-jsonrpc', 'package.json');
48
- if (!existsSync(pkgPath)) continue;
49
-
50
- try {
51
- const raw = readFileSync(pkgPath, 'utf8');
52
- const pkg = JSON.parse(raw);
53
-
54
- if (pkg.exports && pkg.exports['./node.js']) {
55
- console.log('⏭️ vscode-jsonrpc already has complete exports field — skipping');
56
- return false;
57
- }
58
-
59
- pkg.exports = exportsField;
60
- writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
61
- console.log('✅ Patched vscode-jsonrpc/package.json with exports field (Node 22/24+ ESM fix)');
62
- return true;
63
- } catch (err) {
64
- console.warn('⚠️ Failed to patch vscode-jsonrpc exports:', err.message);
65
- return false;
66
- }
67
- }
68
-
69
- return false;
70
- }
71
-
72
- /**
73
- * Layer 2 — Patch copilot-sdk session.js import (defense-in-depth).
74
- * Rewrites extensionless 'vscode-jsonrpc/node' to 'vscode-jsonrpc/node.js'.
75
- */
76
- function patchCopilotSdkSessionJs() {
77
- for (const root of SEARCH_ROOTS) {
78
- const sessionJsPath = join(root, '@github', 'copilot-sdk', 'dist', 'session.js');
79
- if (!existsSync(sessionJsPath)) continue;
80
-
81
- try {
82
- const content = readFileSync(sessionJsPath, 'utf8');
83
-
84
- const patched = content.replace(
85
- /from\s+["']vscode-jsonrpc\/node["']/g,
86
- 'from "vscode-jsonrpc/node.js"'
87
- );
88
-
89
- if (patched !== content) {
90
- writeFileSync(sessionJsPath, patched, 'utf8');
91
- console.log('✅ Patched @github/copilot-sdk session.js ESM imports');
92
- return true;
93
- }
94
- return false;
95
- } catch (err) {
96
- console.warn('⚠️ Failed to patch copilot-sdk session.js:', err.message);
97
- return false;
98
- }
99
- }
100
-
101
- return false;
102
- }
103
-
104
- // Run both layers
105
- patchVscodeJsonrpcExports();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * ESM Import Patcher — dual-layer fix for Node 22/24+ compatibility
5
+ *
6
+ * Layer 1: Patch vscode-jsonrpc/package.json with `exports` field
7
+ * vscode-jsonrpc@8.2.1 has no `exports` field. Node 22+ strict ESM
8
+ * rejects subpath imports like 'vscode-jsonrpc/node' without it.
9
+ * Injecting the exports map from v9.x fixes ALL subpath imports at once.
10
+ *
11
+ * Layer 2: Patch @github/copilot-sdk session.js (defense-in-depth)
12
+ * copilot-sdk@0.1.32 imports 'vscode-jsonrpc/node' without .js extension.
13
+ * This layer ensures the import works even if Layer 1 somehow fails.
14
+ *
15
+ * Issue: bradygaster/squad#449
16
+ * Upstream: https://github.com/github/copilot-sdk/issues/707
17
+ */
18
+
19
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
20
+ import { join, dirname } from 'path';
21
+ import { fileURLToPath } from 'url';
22
+
23
+ const __dirname = dirname(fileURLToPath(import.meta.url));
24
+
25
+ // Locations where npm workspaces / global install may place dependencies
26
+ const SEARCH_ROOTS = [
27
+ join(__dirname, '..', 'node_modules'), // squad-cli local
28
+ join(__dirname, '..', '..', '..', 'node_modules'), // workspace root
29
+ join(__dirname, '..', '..'), // global install (sibling)
30
+ ];
31
+
32
+ /**
33
+ * Layer 1 — Inject `exports` field into vscode-jsonrpc/package.json.
34
+ * This is the canonical fix: once the package has proper exports, Node's
35
+ * ESM resolver handles every subpath ('vscode-jsonrpc/node', '/browser', etc.)
36
+ * without needing per-file patches.
37
+ */
38
+ function patchVscodeJsonrpcExports() {
39
+ const exportsField = {
40
+ '.': { types: './lib/common/api.d.ts', default: './lib/node/main.js' },
41
+ './node': { node: './lib/node/main.js', types: './lib/node/main.d.ts' },
42
+ './node.js': { node: './lib/node/main.js', types: './lib/node/main.d.ts' },
43
+ './browser': { types: './lib/browser/main.d.ts', browser: './lib/browser/main.js' },
44
+ };
45
+
46
+ for (const root of SEARCH_ROOTS) {
47
+ const pkgPath = join(root, 'vscode-jsonrpc', 'package.json');
48
+ if (!existsSync(pkgPath)) continue;
49
+
50
+ try {
51
+ const raw = readFileSync(pkgPath, 'utf8');
52
+ const pkg = JSON.parse(raw);
53
+
54
+ if (pkg.exports && pkg.exports['./node.js']) {
55
+ console.log('⏭️ vscode-jsonrpc already has complete exports field — skipping');
56
+ return false;
57
+ }
58
+
59
+ pkg.exports = exportsField;
60
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
61
+ console.log('✅ Patched vscode-jsonrpc/package.json with exports field (Node 22/24+ ESM fix)');
62
+ return true;
63
+ } catch (err) {
64
+ console.warn('⚠️ Failed to patch vscode-jsonrpc exports:', err.message);
65
+ return false;
66
+ }
67
+ }
68
+
69
+ return false;
70
+ }
71
+
72
+ /**
73
+ * Layer 2 — Patch copilot-sdk session.js import (defense-in-depth).
74
+ * Rewrites extensionless 'vscode-jsonrpc/node' to 'vscode-jsonrpc/node.js'.
75
+ */
76
+ function patchCopilotSdkSessionJs() {
77
+ for (const root of SEARCH_ROOTS) {
78
+ const sessionJsPath = join(root, '@github', 'copilot-sdk', 'dist', 'session.js');
79
+ if (!existsSync(sessionJsPath)) continue;
80
+
81
+ try {
82
+ const content = readFileSync(sessionJsPath, 'utf8');
83
+
84
+ const patched = content.replace(
85
+ /from\s+["']vscode-jsonrpc\/node["']/g,
86
+ 'from "vscode-jsonrpc/node.js"'
87
+ );
88
+
89
+ if (patched !== content) {
90
+ writeFileSync(sessionJsPath, patched, 'utf8');
91
+ console.log('✅ Patched @github/copilot-sdk session.js ESM imports');
92
+ return true;
93
+ }
94
+ return false;
95
+ } catch (err) {
96
+ console.warn('⚠️ Failed to patch copilot-sdk session.js:', err.message);
97
+ return false;
98
+ }
99
+ }
100
+
101
+ return false;
102
+ }
103
+
104
+ // Run both layers
105
+ patchVscodeJsonrpcExports();
106
106
  patchCopilotSdkSessionJs();
@@ -1,115 +1,115 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Ink Rendering Patcher for Squad CLI
5
- *
6
- * Patches ink/build/ink.js to fix scroll flicker on Windows Terminal.
7
- * Three patches are applied:
8
- *
9
- * 1. Remove trailing newline — the extra '\n' appended to output causes
10
- * logUpdate's previousLineCount to be off by one, pushing the bottom of
11
- * the UI below the viewport.
12
- *
13
- * 2. Disable clearTerminal fullscreen path — when output fills the terminal,
14
- * Ink clears the entire screen, causing violent scroll-to-top flicker.
15
- * We force the condition to `false` so logUpdate's incremental
16
- * erase-and-rewrite is always used instead.
17
- *
18
- * 3. Verify incrementalRendering passthrough — confirms that Ink forwards
19
- * the incrementalRendering option to logUpdate.create(). No code change
20
- * needed if already wired up.
21
- *
22
- * All patches are idempotent (safe to run multiple times).
23
- */
24
-
25
- import { readFileSync, writeFileSync, existsSync } from 'fs';
26
- import { join, dirname } from 'path';
27
- import { fileURLToPath } from 'url';
28
-
29
- const __dirname = dirname(fileURLToPath(import.meta.url));
30
-
31
- function patchInkRendering() {
32
- // Try multiple possible locations (npm workspaces can hoist dependencies)
33
- const possiblePaths = [
34
- // squad-cli package node_modules
35
- join(__dirname, '..', 'node_modules', 'ink', 'build', 'ink.js'),
36
- // Workspace root node_modules (common with npm workspaces)
37
- join(__dirname, '..', '..', '..', 'node_modules', 'ink', 'build', 'ink.js'),
38
- // Global install location (node_modules at parent of package)
39
- join(__dirname, '..', '..', 'ink', 'build', 'ink.js'),
40
- ];
41
-
42
- const inkJsPath = possiblePaths.find(p => existsSync(p)) ?? null;
43
-
44
- if (!inkJsPath) {
45
- // ink not installed yet — exit silently
46
- return false;
47
- }
48
-
49
- try {
50
- let content = readFileSync(inkJsPath, 'utf8');
51
- let patchCount = 0;
52
-
53
- // --- Patch 1: Remove trailing newline ---
54
- // Original: const outputToRender = output + '\n';
55
- // Patched: const outputToRender = output;
56
- const trailingNewlineSearch = "const outputToRender = output + '\\n';";
57
- const trailingNewlineReplace = 'const outputToRender = output;';
58
- if (content.includes(trailingNewlineSearch)) {
59
- content = content.replace(trailingNewlineSearch, trailingNewlineReplace);
60
- console.log(' ✅ Patch 1/3: Removed trailing newline from outputToRender');
61
- patchCount++;
62
- } else if (content.includes(trailingNewlineReplace)) {
63
- console.log(' ⏭️ Patch 1/3: Trailing newline already removed');
64
- } else {
65
- console.warn(' ⚠️ Patch 1/3: Could not find outputToRender pattern — Ink version may have changed');
66
- }
67
-
68
- // --- Patch 2: Disable clearTerminal fullscreen path ---
69
- // Original: if (isFullscreen) {
70
- // const sync = shouldSynchronize(this.options.stdout);
71
- // ...
72
- // this.options.stdout.write(ansiEscapes.clearTerminal + ...
73
- // Patched: if (false) {
74
- //
75
- // We match `if (isFullscreen) {` only when followed by the clearTerminal
76
- // usage to avoid replacing unrelated isFullscreen references.
77
- const fullscreenSearch = /if \(isFullscreen\) \{\s*\n\s*const sync = shouldSynchronize/;
78
- const fullscreenAlreadyPatched = /if \(false\) \{\s*\n\s*const sync = shouldSynchronize/;
79
- if (fullscreenSearch.test(content)) {
80
- content = content.replace(
81
- /if \(isFullscreen\) (\{\s*\n\s*const sync = shouldSynchronize)/,
82
- 'if (false) $1'
83
- );
84
- console.log(' ✅ Patch 2/3: Disabled clearTerminal fullscreen path');
85
- patchCount++;
86
- } else if (fullscreenAlreadyPatched.test(content)) {
87
- console.log(' ⏭️ Patch 2/3: clearTerminal path already disabled');
88
- } else {
89
- console.warn(' ⚠️ Patch 2/3: Could not find isFullscreen pattern — Ink version may have changed');
90
- }
91
-
92
- // --- Patch 3: Verify incrementalRendering passthrough ---
93
- const incrementalPattern = 'incremental: options.incrementalRendering';
94
- if (content.includes(incrementalPattern)) {
95
- console.log(' ✅ Patch 3/3: incrementalRendering passthrough verified (no change needed)');
96
- } else {
97
- console.warn(' ⚠️ Patch 3/3: incrementalRendering passthrough not found — Ink version may have changed');
98
- }
99
-
100
- if (patchCount > 0) {
101
- writeFileSync(inkJsPath, content, 'utf8');
102
- console.log(`✅ Patched ink.js with ${patchCount} rendering fix(es) for scroll flicker`);
103
- return true;
104
- }
105
-
106
- return false;
107
- } catch (err) {
108
- console.warn('⚠️ Failed to patch ink.js rendering:', err.message);
109
- console.warn(' Scroll flicker may occur on Windows Terminal.');
110
- return false;
111
- }
112
- }
113
-
114
- // Run patch
115
- patchInkRendering();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Ink Rendering Patcher for Squad CLI
5
+ *
6
+ * Patches ink/build/ink.js to fix scroll flicker on Windows Terminal.
7
+ * Three patches are applied:
8
+ *
9
+ * 1. Remove trailing newline — the extra '\n' appended to output causes
10
+ * logUpdate's previousLineCount to be off by one, pushing the bottom of
11
+ * the UI below the viewport.
12
+ *
13
+ * 2. Disable clearTerminal fullscreen path — when output fills the terminal,
14
+ * Ink clears the entire screen, causing violent scroll-to-top flicker.
15
+ * We force the condition to `false` so logUpdate's incremental
16
+ * erase-and-rewrite is always used instead.
17
+ *
18
+ * 3. Verify incrementalRendering passthrough — confirms that Ink forwards
19
+ * the incrementalRendering option to logUpdate.create(). No code change
20
+ * needed if already wired up.
21
+ *
22
+ * All patches are idempotent (safe to run multiple times).
23
+ */
24
+
25
+ import { readFileSync, writeFileSync, existsSync } from 'fs';
26
+ import { join, dirname } from 'path';
27
+ import { fileURLToPath } from 'url';
28
+
29
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
+
31
+ function patchInkRendering() {
32
+ // Try multiple possible locations (npm workspaces can hoist dependencies)
33
+ const possiblePaths = [
34
+ // squad-cli package node_modules
35
+ join(__dirname, '..', 'node_modules', 'ink', 'build', 'ink.js'),
36
+ // Workspace root node_modules (common with npm workspaces)
37
+ join(__dirname, '..', '..', '..', 'node_modules', 'ink', 'build', 'ink.js'),
38
+ // Global install location (node_modules at parent of package)
39
+ join(__dirname, '..', '..', 'ink', 'build', 'ink.js'),
40
+ ];
41
+
42
+ const inkJsPath = possiblePaths.find(p => existsSync(p)) ?? null;
43
+
44
+ if (!inkJsPath) {
45
+ // ink not installed yet — exit silently
46
+ return false;
47
+ }
48
+
49
+ try {
50
+ let content = readFileSync(inkJsPath, 'utf8');
51
+ let patchCount = 0;
52
+
53
+ // --- Patch 1: Remove trailing newline ---
54
+ // Original: const outputToRender = output + '\n';
55
+ // Patched: const outputToRender = output;
56
+ const trailingNewlineSearch = "const outputToRender = output + '\\n';";
57
+ const trailingNewlineReplace = 'const outputToRender = output;';
58
+ if (content.includes(trailingNewlineSearch)) {
59
+ content = content.replace(trailingNewlineSearch, trailingNewlineReplace);
60
+ console.log(' ✅ Patch 1/3: Removed trailing newline from outputToRender');
61
+ patchCount++;
62
+ } else if (content.includes(trailingNewlineReplace)) {
63
+ console.log(' ⏭️ Patch 1/3: Trailing newline already removed');
64
+ } else {
65
+ console.warn(' ⚠️ Patch 1/3: Could not find outputToRender pattern — Ink version may have changed');
66
+ }
67
+
68
+ // --- Patch 2: Disable clearTerminal fullscreen path ---
69
+ // Original: if (isFullscreen) {
70
+ // const sync = shouldSynchronize(this.options.stdout);
71
+ // ...
72
+ // this.options.stdout.write(ansiEscapes.clearTerminal + ...
73
+ // Patched: if (false) {
74
+ //
75
+ // We match `if (isFullscreen) {` only when followed by the clearTerminal
76
+ // usage to avoid replacing unrelated isFullscreen references.
77
+ const fullscreenSearch = /if \(isFullscreen\) \{\s*\n\s*const sync = shouldSynchronize/;
78
+ const fullscreenAlreadyPatched = /if \(false\) \{\s*\n\s*const sync = shouldSynchronize/;
79
+ if (fullscreenSearch.test(content)) {
80
+ content = content.replace(
81
+ /if \(isFullscreen\) (\{\s*\n\s*const sync = shouldSynchronize)/,
82
+ 'if (false) $1'
83
+ );
84
+ console.log(' ✅ Patch 2/3: Disabled clearTerminal fullscreen path');
85
+ patchCount++;
86
+ } else if (fullscreenAlreadyPatched.test(content)) {
87
+ console.log(' ⏭️ Patch 2/3: clearTerminal path already disabled');
88
+ } else {
89
+ console.warn(' ⚠️ Patch 2/3: Could not find isFullscreen pattern — Ink version may have changed');
90
+ }
91
+
92
+ // --- Patch 3: Verify incrementalRendering passthrough ---
93
+ const incrementalPattern = 'incremental: options.incrementalRendering';
94
+ if (content.includes(incrementalPattern)) {
95
+ console.log(' ✅ Patch 3/3: incrementalRendering passthrough verified (no change needed)');
96
+ } else {
97
+ console.warn(' ⚠️ Patch 3/3: incrementalRendering passthrough not found — Ink version may have changed');
98
+ }
99
+
100
+ if (patchCount > 0) {
101
+ writeFileSync(inkJsPath, content, 'utf8');
102
+ console.log(`✅ Patched ink.js with ${patchCount} rendering fix(es) for scroll flicker`);
103
+ return true;
104
+ }
105
+
106
+ return false;
107
+ } catch (err) {
108
+ console.warn('⚠️ Failed to patch ink.js rendering:', err.message);
109
+ console.warn(' Scroll flicker may occur on Windows Terminal.');
110
+ return false;
111
+ }
112
+ }
113
+
114
+ // Run patch
115
+ patchInkRendering();
@@ -1,10 +1,10 @@
1
- [
2
- "Fry",
3
- "Leela",
4
- "Bender",
5
- "Farnsworth",
6
- "Zoidberg",
7
- "Amy",
8
- "Zapp",
9
- "Kif"
1
+ [
2
+ "Fry",
3
+ "Leela",
4
+ "Bender",
5
+ "Farnsworth",
6
+ "Zoidberg",
7
+ "Amy",
8
+ "Zapp",
9
+ "Kif"
10
10
  ]