@bradygaster/squad-cli 0.9.6-insider.2 → 0.10.0-insider.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 (188) hide show
  1. package/CHANGELOG.md +555 -0
  2. package/README.md +5 -5
  3. package/dist/cli/commands/build.js +3 -3
  4. package/dist/cli/commands/build.js.map +1 -1
  5. package/dist/cli/commands/copilot-bridge.d.ts.map +1 -1
  6. package/dist/cli/commands/copilot-bridge.js +5 -1
  7. package/dist/cli/commands/copilot-bridge.js.map +1 -1
  8. package/dist/cli/commands/cross-squad.d.ts +15 -2
  9. package/dist/cli/commands/cross-squad.d.ts.map +1 -1
  10. package/dist/cli/commands/cross-squad.js +78 -4
  11. package/dist/cli/commands/cross-squad.js.map +1 -1
  12. package/dist/cli/commands/doctor.d.ts +6 -0
  13. package/dist/cli/commands/doctor.d.ts.map +1 -1
  14. package/dist/cli/commands/doctor.js +120 -5
  15. package/dist/cli/commands/doctor.js.map +1 -1
  16. package/dist/cli/commands/export.d.ts +7 -3
  17. package/dist/cli/commands/export.d.ts.map +1 -1
  18. package/dist/cli/commands/export.js +68 -16
  19. package/dist/cli/commands/export.js.map +1 -1
  20. package/dist/cli/commands/import.d.ts +7 -3
  21. package/dist/cli/commands/import.d.ts.map +1 -1
  22. package/dist/cli/commands/import.js +140 -42
  23. package/dist/cli/commands/import.js.map +1 -1
  24. package/dist/cli/commands/install-hooks.d.ts.map +1 -1
  25. package/dist/cli/commands/install-hooks.js +50 -5
  26. package/dist/cli/commands/install-hooks.js.map +1 -1
  27. package/dist/cli/commands/link.d.ts.map +1 -1
  28. package/dist/cli/commands/link.js +7 -1
  29. package/dist/cli/commands/link.js.map +1 -1
  30. package/dist/cli/commands/loop.d.ts.map +1 -1
  31. package/dist/cli/commands/loop.js +7 -5
  32. package/dist/cli/commands/loop.js.map +1 -1
  33. package/dist/cli/commands/memory.d.ts +2 -0
  34. package/dist/cli/commands/memory.d.ts.map +1 -0
  35. package/dist/cli/commands/memory.js +304 -0
  36. package/dist/cli/commands/memory.js.map +1 -0
  37. package/dist/cli/commands/migrate-backend.d.ts +36 -5
  38. package/dist/cli/commands/migrate-backend.d.ts.map +1 -1
  39. package/dist/cli/commands/migrate-backend.js +250 -40
  40. package/dist/cli/commands/migrate-backend.js.map +1 -1
  41. package/dist/cli/commands/notes.d.ts +27 -0
  42. package/dist/cli/commands/notes.d.ts.map +1 -0
  43. package/dist/cli/commands/notes.js +222 -0
  44. package/dist/cli/commands/notes.js.map +1 -0
  45. package/dist/cli/commands/plugin.d.ts.map +1 -1
  46. package/dist/cli/commands/plugin.js +423 -8
  47. package/dist/cli/commands/plugin.js.map +1 -1
  48. package/dist/cli/commands/skill.d.ts +31 -0
  49. package/dist/cli/commands/skill.d.ts.map +1 -0
  50. package/dist/cli/commands/skill.js +498 -0
  51. package/dist/cli/commands/skill.js.map +1 -0
  52. package/dist/cli/commands/start.d.ts.map +1 -1
  53. package/dist/cli/commands/start.js +9 -1
  54. package/dist/cli/commands/start.js.map +1 -1
  55. package/dist/cli/commands/state-mcp.d.ts +25 -0
  56. package/dist/cli/commands/state-mcp.d.ts.map +1 -0
  57. package/dist/cli/commands/state-mcp.js +174 -0
  58. package/dist/cli/commands/state-mcp.js.map +1 -0
  59. package/dist/cli/commands/watch/agent-spawn.d.ts +62 -0
  60. package/dist/cli/commands/watch/agent-spawn.d.ts.map +1 -0
  61. package/dist/cli/commands/watch/agent-spawn.js +127 -0
  62. package/dist/cli/commands/watch/agent-spawn.js.map +1 -0
  63. package/dist/cli/commands/watch/capabilities/board.d.ts.map +1 -1
  64. package/dist/cli/commands/watch/capabilities/board.js +2 -1
  65. package/dist/cli/commands/watch/capabilities/board.js.map +1 -1
  66. package/dist/cli/commands/watch/capabilities/decision-hygiene.d.ts.map +1 -1
  67. package/dist/cli/commands/watch/capabilities/decision-hygiene.js +3 -2
  68. package/dist/cli/commands/watch/capabilities/decision-hygiene.js.map +1 -1
  69. package/dist/cli/commands/watch/capabilities/execute.d.ts.map +1 -1
  70. package/dist/cli/commands/watch/capabilities/execute.js +14 -2
  71. package/dist/cli/commands/watch/capabilities/execute.js.map +1 -1
  72. package/dist/cli/commands/watch/capabilities/index.d.ts.map +1 -1
  73. package/dist/cli/commands/watch/capabilities/index.js +2 -0
  74. package/dist/cli/commands/watch/capabilities/index.js.map +1 -1
  75. package/dist/cli/commands/watch/capabilities/monitor-email.d.ts +1 -1
  76. package/dist/cli/commands/watch/capabilities/monitor-email.d.ts.map +1 -1
  77. package/dist/cli/commands/watch/capabilities/monitor-email.js +21 -4
  78. package/dist/cli/commands/watch/capabilities/monitor-email.js.map +1 -1
  79. package/dist/cli/commands/watch/capabilities/monitor-teams.d.ts +1 -1
  80. package/dist/cli/commands/watch/capabilities/monitor-teams.d.ts.map +1 -1
  81. package/dist/cli/commands/watch/capabilities/monitor-teams.js +21 -5
  82. package/dist/cli/commands/watch/capabilities/monitor-teams.js.map +1 -1
  83. package/dist/cli/commands/watch/capabilities/notes-promote.d.ts +11 -0
  84. package/dist/cli/commands/watch/capabilities/notes-promote.d.ts.map +1 -0
  85. package/dist/cli/commands/watch/capabilities/notes-promote.js +124 -0
  86. package/dist/cli/commands/watch/capabilities/notes-promote.js.map +1 -0
  87. package/dist/cli/commands/watch/capabilities/retro.d.ts.map +1 -1
  88. package/dist/cli/commands/watch/capabilities/retro.js +3 -2
  89. package/dist/cli/commands/watch/capabilities/retro.js.map +1 -1
  90. package/dist/cli/commands/watch/capabilities/wave-dispatch.d.ts.map +1 -1
  91. package/dist/cli/commands/watch/capabilities/wave-dispatch.js +2 -1
  92. package/dist/cli/commands/watch/capabilities/wave-dispatch.js.map +1 -1
  93. package/dist/cli/commands/watch/index.d.ts.map +1 -1
  94. package/dist/cli/commands/watch/index.js +16 -12
  95. package/dist/cli/commands/watch/index.js.map +1 -1
  96. package/dist/cli/core/cast.d.ts.map +1 -1
  97. package/dist/cli/core/cast.js +216 -1
  98. package/dist/cli/core/cast.js.map +1 -1
  99. package/dist/cli/core/command-help.d.ts +44 -0
  100. package/dist/cli/core/command-help.d.ts.map +1 -0
  101. package/dist/cli/core/command-help.js +401 -0
  102. package/dist/cli/core/command-help.js.map +1 -0
  103. package/dist/cli/core/copilot-invocation.d.ts +57 -0
  104. package/dist/cli/core/copilot-invocation.d.ts.map +1 -0
  105. package/dist/cli/core/copilot-invocation.js +84 -0
  106. package/dist/cli/core/copilot-invocation.js.map +1 -0
  107. package/dist/cli/core/effective-squad-dir.d.ts +33 -0
  108. package/dist/cli/core/effective-squad-dir.d.ts.map +1 -0
  109. package/dist/cli/core/effective-squad-dir.js +37 -0
  110. package/dist/cli/core/effective-squad-dir.js.map +1 -0
  111. package/dist/cli/core/init.d.ts +2 -0
  112. package/dist/cli/core/init.d.ts.map +1 -1
  113. package/dist/cli/core/init.js +130 -1
  114. package/dist/cli/core/init.js.map +1 -1
  115. package/dist/cli/core/mcp-root.d.ts +73 -0
  116. package/dist/cli/core/mcp-root.d.ts.map +1 -0
  117. package/dist/cli/core/mcp-root.js +195 -0
  118. package/dist/cli/core/mcp-root.js.map +1 -0
  119. package/dist/cli/core/mcp-spec.d.ts +48 -0
  120. package/dist/cli/core/mcp-spec.d.ts.map +1 -0
  121. package/dist/cli/core/mcp-spec.js +62 -0
  122. package/dist/cli/core/mcp-spec.js.map +1 -0
  123. package/dist/cli/core/npm-registry.d.ts +25 -0
  124. package/dist/cli/core/npm-registry.d.ts.map +1 -0
  125. package/dist/cli/core/npm-registry.js +65 -0
  126. package/dist/cli/core/npm-registry.js.map +1 -0
  127. package/dist/cli/core/templates.d.ts.map +1 -1
  128. package/dist/cli/core/templates.js +102 -24
  129. package/dist/cli/core/templates.js.map +1 -1
  130. package/dist/cli/core/upgrade.d.ts +29 -0
  131. package/dist/cli/core/upgrade.d.ts.map +1 -1
  132. package/dist/cli/core/upgrade.js +413 -15
  133. package/dist/cli/core/upgrade.js.map +1 -1
  134. package/dist/cli/index.d.ts +1 -0
  135. package/dist/cli/index.d.ts.map +1 -1
  136. package/dist/cli/index.js +1 -0
  137. package/dist/cli/index.js.map +1 -1
  138. package/dist/cli/shell/components/App.js +1 -1
  139. package/dist/cli/shell/components/App.js.map +1 -1
  140. package/dist/cli/shell/components/MessageStream.js +2 -2
  141. package/dist/cli/shell/components/MessageStream.js.map +1 -1
  142. package/dist/cli/shell/coordinator.d.ts.map +1 -1
  143. package/dist/cli/shell/coordinator.js +11 -5
  144. package/dist/cli/shell/coordinator.js.map +1 -1
  145. package/dist/cli/shell/index.d.ts.map +1 -1
  146. package/dist/cli/shell/index.js +14 -7
  147. package/dist/cli/shell/index.js.map +1 -1
  148. package/dist/cli/shell/lifecycle.d.ts.map +1 -1
  149. package/dist/cli/shell/lifecycle.js +12 -7
  150. package/dist/cli/shell/lifecycle.js.map +1 -1
  151. package/dist/cli/shell/session-store.d.ts +4 -4
  152. package/dist/cli/shell/session-store.d.ts.map +1 -1
  153. package/dist/cli/shell/session-store.js +11 -11
  154. package/dist/cli/shell/session-store.js.map +1 -1
  155. package/dist/cli-entry.js +171 -50
  156. package/dist/cli-entry.js.map +1 -1
  157. package/package.json +9 -4
  158. package/scripts/patch-esm-imports.mjs +3 -1
  159. package/templates/after-agent-reference.md +64 -0
  160. package/templates/ceremony-reference.md +82 -0
  161. package/templates/client-compatibility-reference.md +46 -0
  162. package/templates/copilot-agent.md +96 -0
  163. package/templates/copilot-instructions.md +14 -0
  164. package/templates/fact-checker-policy.md +104 -0
  165. package/templates/model-selection-reference.md +101 -0
  166. package/templates/prd-intake.md +105 -0
  167. package/templates/rai-charter.md +110 -0
  168. package/templates/rai-policy.md +103 -0
  169. package/templates/ralph-reference.md +141 -0
  170. package/templates/routing.md +1 -0
  171. package/templates/scribe-charter.md +18 -151
  172. package/templates/session-init-reference.md +199 -0
  173. package/templates/skills/cross-squad/SKILL.md +66 -6
  174. package/templates/skills/cross-squad-communication/SKILL.md +399 -0
  175. package/templates/skills/e2e-template-testing/SKILL.md +557 -0
  176. package/templates/skills/fact-checking/SKILL.md +61 -0
  177. package/templates/skills/release-process/SKILL.md +2 -2
  178. package/templates/skills/squad/SKILL.md +299 -0
  179. package/templates/skills/squad-help/SKILL.md +97 -0
  180. package/templates/skills/squad-version-check/SKILL.md +169 -0
  181. package/templates/skills/tiered-memory/SKILL.md +31 -44
  182. package/templates/spawn-reference.md +131 -0
  183. package/templates/squad.agent.md.template +306 -618
  184. package/templates/workflow-wiring-appendix-a-code-reviewer.md +131 -0
  185. package/templates/workflow-wiring-appendix-b-documenter.md +140 -0
  186. package/templates/workflow-wiring-guide.md +276 -0
  187. package/templates/workflows/squad-heartbeat.yml +167 -167
  188. package/templates/worktree-reference.md +126 -0
@@ -4,6 +4,7 @@
4
4
  * @module cli/core/upgrade
5
5
  */
6
6
  import path from 'node:path';
7
+ import { execFileSync } from 'node:child_process';
7
8
  import { FSStorageProvider } from '@bradygaster/squad-sdk';
8
9
  import { success, warn, info, dim } from './output.js';
9
10
  import { fatal } from './errors.js';
@@ -11,7 +12,105 @@ import { detectSquadDir } from './detect-squad-dir.js';
11
12
  import { TEMPLATE_MANIFEST, getTemplatesDir } from './templates.js';
12
13
  import { runMigrations } from './migrations.js';
13
14
  import { getPackageVersion, stampVersion, readInstalledVersion } from './version.js';
15
+ import { resolveSquadStateMcpSpec } from './mcp-spec.js';
16
+ export { resolveSquadStateMcpSpec } from './mcp-spec.js';
17
+ import { ensureSquadStateMcpInRoot, tombstoneStaleSquadStateInProjectMcp } from './mcp-root.js';
14
18
  const storage = new FSStorageProvider();
19
+ /**
20
+ * Returns true if the version looks like a local dev build or unpublished
21
+ * pre-release that cannot be resolved from the public npm registry.
22
+ * Guards against writing unresolvable version strings into MCP config
23
+ * (see #1204).
24
+ */
25
+ export function isLocalOrUnpublishedVersion(version) {
26
+ if (!version || version === '0.0.0')
27
+ return true;
28
+ // Local linked builds often have 0.0.0-development or similar sentinel
29
+ if (/^0\.0\.0/.test(version))
30
+ return true;
31
+ // Versions with `+` build metadata (e.g. 0.10.0+local.1234) are not
32
+ // publishable to npm — they indicate a local build.
33
+ if (version.includes('+'))
34
+ return true;
35
+ return false;
36
+ }
37
+ function buildMcpServerSpecs(isGitHub, cliVersion) {
38
+ // Pin the squad-cli package to the currently-installed CLI version so that
39
+ // `npx -y @bradygaster/squad-cli state-mcp` does NOT silently resolve to the
40
+ // npm `latest` dist-tag (which may predate the `state-mcp` command and thus
41
+ // expose zero tools to Copilot — see MCP-BRIDGE-BROKEN root cause).
42
+ //
43
+ // #1204: When the CLI is a local dev build or unpublished pre-release, fall
44
+ // back to the @insider dist-tag to avoid writing an unresolvable version
45
+ // string that breaks npx resolution at session start.
46
+ let pkgSpec;
47
+ if (!cliVersion || isLocalOrUnpublishedVersion(cliVersion)) {
48
+ pkgSpec = '@bradygaster/squad-cli@insider';
49
+ }
50
+ else {
51
+ pkgSpec = `@bradygaster/squad-cli@${cliVersion}`;
52
+ }
53
+ const servers = [
54
+ {
55
+ name: 'squad_state',
56
+ command: 'npx',
57
+ args: ['-y', pkgSpec, 'state-mcp'],
58
+ },
59
+ ];
60
+ servers.push(isGitHub
61
+ ? {
62
+ name: 'EXAMPLE-github',
63
+ command: 'npx',
64
+ args: ['-y', '@anthropic/github-mcp-server'],
65
+ env: { GITHUB_TOKEN: '${GITHUB_TOKEN}' },
66
+ }
67
+ : {
68
+ name: 'EXAMPLE-azure-devops',
69
+ command: 'npx',
70
+ args: ['-y', '@azure/devops-mcp-server'],
71
+ env: {
72
+ AZURE_DEVOPS_ORG: '${AZURE_DEVOPS_ORG}',
73
+ AZURE_DEVOPS_PAT: '${AZURE_DEVOPS_PAT}',
74
+ },
75
+ });
76
+ return servers;
77
+ }
78
+ function yamlSingleQuoted(value) {
79
+ return `'${value.replace(/'/g, "''")}'`;
80
+ }
81
+ function yamlEnvValue(value) {
82
+ if (/^\$\{[A-Z0-9_]+\}$/.test(value)) {
83
+ return value;
84
+ }
85
+ return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
86
+ }
87
+ function buildMcpFrontmatterBlock(isGitHub, cliVersion) {
88
+ const lines = ['mcp-servers:'];
89
+ for (const server of buildMcpServerSpecs(isGitHub, cliVersion)) {
90
+ lines.push(` ${server.name}:`);
91
+ lines.push(' type: local');
92
+ lines.push(` command: ${server.command}`);
93
+ lines.push(` args: [${server.args.map(yamlSingleQuoted).join(', ')}]`);
94
+ lines.push(' tools: ["*"]');
95
+ if (server.env && Object.keys(server.env).length > 0) {
96
+ lines.push(' env:');
97
+ for (const [key, value] of Object.entries(server.env)) {
98
+ lines.push(` ${key}: ${yamlEnvValue(value)}`);
99
+ }
100
+ }
101
+ }
102
+ return lines.join('\n');
103
+ }
104
+ function injectMcpFrontmatter(content, isGitHub, cliVersion) {
105
+ const closingStart = content.indexOf('\n---', 4);
106
+ if (!content.startsWith('---') || closingStart === -1) {
107
+ return content;
108
+ }
109
+ return content.slice(0, closingStart)
110
+ + '\n'
111
+ + buildMcpFrontmatterBlock(isGitHub, cliVersion)
112
+ + content.slice(closingStart);
113
+ }
15
114
  function copyDirRecursive(src, dest, force = true) {
16
115
  storage.mkdirSync(dest, { recursive: true });
17
116
  for (const entry of storage.listSync(src)) {
@@ -49,6 +148,64 @@ function compareSemver(a, b) {
49
148
  return a < b ? -1 : a > b ? 1 : 0;
50
149
  return 0;
51
150
  }
151
+ function readSquadConfig(squadDir) {
152
+ const configPath = path.join(squadDir, 'config.json');
153
+ if (!storage.existsSync(configPath))
154
+ return {};
155
+ try {
156
+ const raw = storage.readSync(configPath);
157
+ if (!raw)
158
+ return {};
159
+ const parsed = JSON.parse(raw);
160
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
161
+ return parsed;
162
+ }
163
+ }
164
+ catch {
165
+ // Ignore malformed config for upgrade compatibility.
166
+ }
167
+ return {};
168
+ }
169
+ function readMcpConfigMode(config) {
170
+ return config['mcpConfigMode'] === 'agent-frontmatter' ? 'agent-frontmatter' : 'copilot-file';
171
+ }
172
+ function detectMcpConfigMode(config, agentDest) {
173
+ const configuredMode = readMcpConfigMode(config);
174
+ if (configuredMode === 'agent-frontmatter')
175
+ return configuredMode;
176
+ if (storage.existsSync(agentDest)) {
177
+ const existingAgent = storage.readSync(agentDest) ?? '';
178
+ const frontmatterEnd = existingAgent.indexOf('\n---', 4);
179
+ const frontmatter = frontmatterEnd === -1 ? '' : existingAgent.slice(0, frontmatterEnd);
180
+ if (frontmatter.includes('mcp-servers:')) {
181
+ return 'agent-frontmatter';
182
+ }
183
+ }
184
+ return configuredMode;
185
+ }
186
+ function detectIsGitHubForMcp(dest, config) {
187
+ if (config['platform'] === 'azure-devops')
188
+ return false;
189
+ try {
190
+ const remoteUrl = execFileSync('git', ['remote', 'get-url', 'origin'], { cwd: dest, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
191
+ const remoteUrlLower = remoteUrl.toLowerCase();
192
+ if (remoteUrlLower.includes('dev.azure.com') || remoteUrlLower.includes('visualstudio.com') || remoteUrlLower.includes('ssh.dev.azure.com')) {
193
+ return false;
194
+ }
195
+ }
196
+ catch {
197
+ // No git remote — assume GitHub, matching init behavior.
198
+ }
199
+ return true;
200
+ }
201
+ function writeAgentTemplate(agentSrc, agentDest, cliVersion, mcpConfigMode, isGitHub) {
202
+ let agentContent = storage.readSync(agentSrc) ?? '';
203
+ if (mcpConfigMode === 'agent-frontmatter') {
204
+ agentContent = injectMcpFrontmatter(agentContent, isGitHub, cliVersion);
205
+ }
206
+ storage.writeSync(agentDest, agentContent);
207
+ stampVersion(agentDest, cliVersion);
208
+ }
52
209
  /**
53
210
  * Detect project type by checking marker files
54
211
  */
@@ -252,6 +409,7 @@ const GITIGNORE_ENTRIES = [
252
409
  '.squad/log/',
253
410
  '.squad/decisions/inbox/',
254
411
  '.squad/sessions/',
412
+ '.squad/.cache/',
255
413
  '.squad-workstream',
256
414
  ];
257
415
  const ENSURE_DIRECTORIES = [
@@ -262,7 +420,7 @@ const ENSURE_DIRECTORIES = [
262
420
  '.squad/decisions/inbox',
263
421
  '.squad/casting',
264
422
  '.squad/agents',
265
- '.copilot/skills',
423
+ '.github/skills',
266
424
  ];
267
425
  /**
268
426
  * Ensure .gitattributes has required merge=union rules (idempotent)
@@ -417,7 +575,74 @@ function warnIfSkillCustomized(srcPath, destPath, sourceName) {
417
575
  }
418
576
  }
419
577
  /**
420
- * Sync manifest-declared skills to .copilot/skills/, respecting overwriteOnUpgrade.
578
+ * Migrate skills from the legacy `.copilot/skills/<name>/` location to the
579
+ * canonical `.github/skills/<name>/`. Only migrates skills that appear in
580
+ * `TEMPLATE_MANIFEST` (manifest-curated Squad skills) — does NOT touch
581
+ * user-added skills in `.copilot/skills/`. After successful migration,
582
+ * removes the now-empty `.copilot/skills/<name>/` directories.
583
+ *
584
+ * Idempotent: when a skill already exists at the new location the legacy
585
+ * copy is tombstoned without comparing content — this protects any
586
+ * in-place customization the user made at `.github/skills/<name>/` from
587
+ * being clobbered, and means re-running upgrade is a no-op on the new
588
+ * location. If only the legacy copy exists, it is moved over.
589
+ *
590
+ * See bradygaster/squad#1126 (canonical issue) — this is the migration
591
+ * piece of that fix; #1304 is the PR that implements it.
592
+ */
593
+ function migrateLegacyCopilotSkills(dest) {
594
+ const legacyDir = path.join(dest, '.copilot', 'skills');
595
+ if (!storage.existsSync(legacyDir))
596
+ return { migrated: [], tombstoned: [] };
597
+ const manifestSkills = new Set(TEMPLATE_MANIFEST
598
+ .filter(f => f.source.startsWith('skills/'))
599
+ .map(f => f.source.split('/')[1]) // 'skills/foo/SKILL.md' -> 'foo'
600
+ .filter((s) => Boolean(s)));
601
+ const newDir = path.join(dest, '.github', 'skills');
602
+ const migrated = [];
603
+ const tombstoned = [];
604
+ for (const entry of storage.listSync(legacyDir)) {
605
+ if (!manifestSkills.has(entry))
606
+ continue; // leave user-added skills alone
607
+ const legacySkillDir = path.join(legacyDir, entry);
608
+ if (!storage.isDirectorySync(legacySkillDir))
609
+ continue;
610
+ const newSkillDir = path.join(newDir, entry);
611
+ if (storage.existsSync(newSkillDir)) {
612
+ // New location already has it — drop the legacy copy without overwriting.
613
+ try {
614
+ storage.deleteDirSync(legacySkillDir);
615
+ tombstoned.push(path.posix.join('.copilot/skills', entry));
616
+ }
617
+ catch {
618
+ // best-effort; leave the legacy dir if we can't remove it
619
+ }
620
+ continue;
621
+ }
622
+ try {
623
+ storage.mkdirSync(newDir, { recursive: true });
624
+ copyDirRecursive(legacySkillDir, newSkillDir);
625
+ storage.deleteDirSync(legacySkillDir);
626
+ migrated.push(path.posix.join('.github/skills', entry));
627
+ }
628
+ catch {
629
+ // best-effort; leave the legacy dir if anything fails
630
+ }
631
+ }
632
+ // If the legacy `.copilot/skills/` directory is now empty, remove it too.
633
+ try {
634
+ if (storage.existsSync(legacyDir) && storage.listSync(legacyDir).length === 0) {
635
+ storage.deleteDirSync(legacyDir);
636
+ tombstoned.push('.copilot/skills');
637
+ }
638
+ }
639
+ catch {
640
+ // best-effort
641
+ }
642
+ return { migrated, tombstoned };
643
+ }
644
+ /**
645
+ * Sync manifest-declared skills to .github/skills/, respecting overwriteOnUpgrade.
421
646
  * Only skills listed in TEMPLATE_MANIFEST are installed — not the entire templates/skills/ dir.
422
647
  */
423
648
  function syncAllSkills(dest, templatesDir) {
@@ -464,7 +689,7 @@ function refreshSquadTemplatesDir(dest, templatesDir) {
464
689
  /**
465
690
  * Run all ensure* checks and skill/template sync — shared by both code paths
466
691
  */
467
- function runEnsureChecks(dest, templatesDir, filesUpdated) {
692
+ async function runEnsureChecks(dest, templatesDir, filesUpdated) {
468
693
  const attrAdded = ensureGitattributes(dest);
469
694
  if (attrAdded.length > 0) {
470
695
  success(`ensured .gitattributes (${attrAdded.length} rules added)`);
@@ -485,14 +710,170 @@ function runEnsureChecks(dest, templatesDir, filesUpdated) {
485
710
  success(`scaffolded ${castingFiles.length} default casting files`);
486
711
  filesUpdated.push(...castingFiles);
487
712
  }
713
+ const memoryFiles = ensureMemoryGovernanceUpgradeDefaults(dest);
714
+ if (memoryFiles.length > 0) {
715
+ success(`scaffolded memory governance defaults (${memoryFiles.length} files/directories)`);
716
+ filesUpdated.push(...memoryFiles);
717
+ }
718
+ const builtinAgents = ensureBuiltinAgents(dest, templatesDir);
719
+ if (builtinAgents.length > 0) {
720
+ const uniqueAgentNames = Array.from(new Set(builtinAgents.map(p => path.basename(path.dirname(p)))));
721
+ success(`scaffolded ${uniqueAgentNames.length} built-in agent(s): ${uniqueAgentNames.join(', ')}`);
722
+ filesUpdated.push(...builtinAgents);
723
+ }
724
+ const skillMigration = migrateLegacyCopilotSkills(dest);
725
+ if (skillMigration.migrated.length > 0) {
726
+ success(`migrated ${skillMigration.migrated.length} skill(s) from .copilot/skills/ → .github/skills/`);
727
+ filesUpdated.push(...skillMigration.migrated);
728
+ }
729
+ if (skillMigration.tombstoned.length > 0) {
730
+ success(`removed ${skillMigration.tombstoned.length} stale .copilot/skills entries (now live at .github/skills/)`);
731
+ }
488
732
  const skillCount = syncAllSkills(dest, templatesDir);
489
733
  if (skillCount > 0) {
490
- success(`synced ${skillCount} skills to .copilot/skills/`);
734
+ success(`synced ${skillCount} skills to .github/skills/`);
491
735
  filesUpdated.push(`skills (${skillCount})`);
492
736
  }
493
737
  refreshSquadTemplatesDir(dest, templatesDir);
494
738
  success('refreshed .squad/templates/');
495
739
  filesUpdated.push('.squad/templates/');
740
+ // iter-8: write squad_state MCP entry to repo-root `.mcp.json`
741
+ // (auto-loaded by Copilot CLI ≥1.0.59, which walks up from cwd to the
742
+ // git root looking for .mcp.json) and tombstone any stale project-level
743
+ // entry left by older Squad versions in `.copilot/mcp-config.json`.
744
+ // No HOME modifications.
745
+ const pinnedSpec = await resolveSquadStateMcpSpec(getPackageVersion());
746
+ try {
747
+ const rootResult = ensureSquadStateMcpInRoot(dest, getPackageVersion(), pinnedSpec);
748
+ if (rootResult.written) {
749
+ success(`installed squad_state MCP server to .mcp.json (${describeMcpSpec(pinnedSpec)}) — Copilot CLI will auto-load on next invocation`);
750
+ filesUpdated.push('.mcp.json');
751
+ }
752
+ }
753
+ catch (err) {
754
+ warn(`Could not write .mcp.json: ${err instanceof Error ? err.message : err}`);
755
+ }
756
+ // iter-8: do NOT write to ~/.copilot/mcp-config.json on upgrade. The
757
+ // repo-root .mcp.json write above is sufficient for Copilot CLI ≥1.0.59
758
+ // (walks from cwd up looking for .mcp.json) AND for `copilot -p` from
759
+ // the project root. Out-of-tree `copilot -p` should use
760
+ // `--additional-mcp-config @.mcp.json`. See bradygaster/squad#1296.
761
+ const tomb = tombstoneStaleSquadStateInProjectMcp(dest);
762
+ if (tomb.removed) {
763
+ success(`removed stale squad_state from ${tomb.path} (now lives in .mcp.json)`);
764
+ filesUpdated.push('.copilot/mcp-config.json (tombstoned)');
765
+ }
766
+ }
767
+ /** Human-readable single-line description of an McpSpec for success() messages. */
768
+ export function describeMcpSpec(spec) {
769
+ // After iter-7 all specs are `npx -y <pkg@version-or-tag> state-mcp`.
770
+ const pkg = spec.args[1] ?? '<unknown>';
771
+ return spec.source === 'insider' ? `${pkg} (@insider fallback)` : pkg;
772
+ }
773
+ export function ensureMemoryGovernanceUpgradeDefaults(dest) {
774
+ const memoryDir = path.join(dest, '.squad', 'memory');
775
+ const created = [];
776
+ for (const dir of ['local', 'policy-inbox', 'semantic-inbox', 'tombstones']) {
777
+ const fullPath = path.join(memoryDir, dir);
778
+ if (!storage.existsSync(fullPath)) {
779
+ storage.mkdirSync(fullPath, { recursive: true });
780
+ created.push(path.join('.squad', 'memory', dir));
781
+ }
782
+ }
783
+ const defaults = {
784
+ 'config.json': JSON.stringify({
785
+ version: 1,
786
+ defaultProvider: 'local',
787
+ promptOnlyFallback: true,
788
+ externalProviders: {
789
+ hostInjectedCopilotAdapter: {
790
+ enabled: false,
791
+ requireApproval: true,
792
+ },
793
+ },
794
+ policy: {
795
+ rejectForbidden: true,
796
+ rejectTransientDurableWrites: true,
797
+ auditContent: false,
798
+ },
799
+ }, null, 2) + '\n',
800
+ 'index.json': '[]\n',
801
+ 'audit.jsonl': '',
802
+ };
803
+ for (const [file, content] of Object.entries(defaults)) {
804
+ const fullPath = path.join(memoryDir, file);
805
+ if (!storage.existsSync(fullPath)) {
806
+ storage.writeSync(fullPath, content);
807
+ created.push(path.join('.squad', 'memory', file));
808
+ }
809
+ }
810
+ return created;
811
+ }
812
+ /**
813
+ * Scaffold always-on built-in agent charters (Rai, Fact Checker) that ship
814
+ * as templates but may be missing from older squads. Idempotent — only writes
815
+ * when the agent directory is absent. Never overwrites existing charters or
816
+ * history. Sources charter content from the shipped `templates/{name}-charter.md`
817
+ * files when available, falling back to a minimal placeholder otherwise.
818
+ *
819
+ * Scribe and Ralph are intentionally NOT scaffolded here — they should already
820
+ * exist in any squad that ran a prior init, and their charters are inlined in
821
+ * cast.ts (no shipped template file). Adding them here would risk overwriting
822
+ * customized versions on legacy squads.
823
+ *
824
+ * @param dest Root directory containing .squad/
825
+ * @param templatesDir Directory containing shipped charter templates
826
+ * @returns Paths (relative to dest) of created agent files
827
+ */
828
+ export function ensureBuiltinAgents(dest, templatesDir) {
829
+ const agentsDir = path.join(dest, '.squad', 'agents');
830
+ if (!storage.existsSync(agentsDir)) {
831
+ storage.mkdirSync(agentsDir, { recursive: true });
832
+ }
833
+ // Built-in agents that ship as charter templates. Each entry maps to:
834
+ // - dirName: case-preserving directory name under .squad/agents/
835
+ // - templateFile: filename under templatesDir (charter template)
836
+ // - displayName: shown in history.md header
837
+ const builtins = [
838
+ { dirName: 'Rai', templateFile: 'Rai-charter.md', displayName: 'Rai' },
839
+ { dirName: 'fact-checker', templateFile: 'fact-checker-charter.md', displayName: 'Fact Checker' },
840
+ ];
841
+ const created = [];
842
+ for (const agent of builtins) {
843
+ const agentDir = path.join(agentsDir, agent.dirName);
844
+ const charterPath = path.join(agentDir, 'charter.md');
845
+ const historyPath = path.join(agentDir, 'history.md');
846
+ // Idempotent: skip if agent directory already exists. Never overwrite
847
+ // existing charters or history files (preserves user customization).
848
+ if (storage.existsSync(agentDir))
849
+ continue;
850
+ // Source charter content from the shipped template; fall back to a
851
+ // minimal placeholder if the template file is missing (defensive — should
852
+ // not happen in a well-formed install, but better than crashing upgrade).
853
+ const tplPath = path.join(templatesDir, agent.templateFile);
854
+ let charterContent;
855
+ if (storage.existsSync(tplPath)) {
856
+ charterContent = storage.readSync(tplPath) ?? '';
857
+ }
858
+ else {
859
+ charterContent = `# ${agent.displayName}\n\n> Charter template not found in shipped templates. Run \`squad upgrade\` after reinstalling the CLI to repair.\n`;
860
+ }
861
+ try {
862
+ storage.mkdirSync(agentDir, { recursive: true });
863
+ storage.writeSync(charterPath, charterContent);
864
+ created.push(path.join('.squad', 'agents', agent.dirName, 'charter.md'));
865
+ storage.writeSync(historyPath, `# ${agent.displayName} — History\n\n## Learnings\n\nInitial scaffold via \`squad upgrade\`. Ready for work.\n`);
866
+ created.push(path.join('.squad', 'agents', agent.dirName, 'history.md'));
867
+ }
868
+ catch (err) {
869
+ if (err instanceof Error && 'code' in err && ['EPERM', 'EACCES'].includes(err.code ?? '')) {
870
+ warn(`Could not scaffold built-in agent ${agent.displayName} (read-only). Create .squad/agents/${agent.dirName}/ manually.`);
871
+ continue;
872
+ }
873
+ throw err;
874
+ }
875
+ }
876
+ return created;
496
877
  }
497
878
  /**
498
879
  * Run the upgrade command
@@ -513,6 +894,9 @@ export async function runUpgrade(dest, options = {}) {
513
894
  }
514
895
  const agentDest = path.join(dest, '.github', 'agents', 'squad.agent.md');
515
896
  const oldVersion = readInstalledVersion(agentDest) ?? '0.0.0';
897
+ const squadConfig = readSquadConfig(squadDirInfo.path);
898
+ const mcpConfigMode = detectMcpConfigMode(squadConfig, agentDest);
899
+ const isGitHubForMcp = detectIsGitHubForMcp(dest, squadConfig);
516
900
  // Check if already current
517
901
  const isAlreadyCurrent = !options.force && oldVersion && oldVersion !== '0.0.0' && compareSemver(oldVersion, cliVersion) === 0;
518
902
  const projectType = detectProjectType(dest);
@@ -537,8 +921,7 @@ export async function runUpgrade(dest, options = {}) {
537
921
  const agentSrc = path.join(templatesDir, 'squad.agent.md.template');
538
922
  if (storage.existsSync(agentSrc)) {
539
923
  storage.mkdirSync(path.dirname(agentDest), { recursive: true });
540
- storage.copySync(agentSrc, agentDest);
541
- stampVersion(agentDest, cliVersion);
924
+ writeAgentTemplate(agentSrc, agentDest, cliVersion, mcpConfigMode, isGitHubForMcp);
542
925
  success('upgraded squad.agent.md');
543
926
  filesUpdated.push('squad.agent.md');
544
927
  }
@@ -546,7 +929,7 @@ export async function runUpgrade(dest, options = {}) {
546
929
  warn('squad.agent.md.template not found — squad.agent.md was not refreshed. Reinstall or repair the CLI to restore the missing template.');
547
930
  }
548
931
  // Run infrastructure ensure checks even when already current
549
- runEnsureChecks(dest, templatesDir, filesUpdated);
932
+ await runEnsureChecks(dest, templatesDir, filesUpdated);
550
933
  return {
551
934
  fromVersion: oldVersion,
552
935
  toVersion: cliVersion,
@@ -561,8 +944,7 @@ export async function runUpgrade(dest, options = {}) {
561
944
  fatal('squad.agent.md.template not found in templates — installation may be corrupted');
562
945
  }
563
946
  storage.mkdirSync(path.dirname(agentDest), { recursive: true });
564
- storage.copySync(agentSrc, agentDest);
565
- stampVersion(agentDest, cliVersion);
947
+ writeAgentTemplate(agentSrc, agentDest, cliVersion, mcpConfigMode, isGitHubForMcp);
566
948
  const fromLabel = oldVersion === '0.0.0' || !oldVersion ? 'unknown' : oldVersion;
567
949
  success(`upgraded coordinator from ${fromLabel} to ${cliVersion}`);
568
950
  filesUpdated.push('squad.agent.md');
@@ -613,7 +995,7 @@ export async function runUpgrade(dest, options = {}) {
613
995
  }
614
996
  }
615
997
  // Run infrastructure ensure checks
616
- runEnsureChecks(dest, templatesDir, filesUpdated);
998
+ await runEnsureChecks(dest, templatesDir, filesUpdated);
617
999
  console.log();
618
1000
  info(`Upgrade complete: v${fromLabel} → v${cliVersion}`);
619
1001
  if (migrationsApplied.some(m => m.toLowerCase().includes('scrub email'))) {
@@ -677,15 +1059,31 @@ export async function selfUpgradeCli(options = {}) {
677
1059
  execSync(cmd, { stdio: 'inherit' });
678
1060
  }
679
1061
  catch (err) {
680
- const isPermission = err instanceof Error &&
681
- 'code' in err &&
682
- err.code === 'EACCES';
1062
+ // UPGRADE-EPERM-FALSE-SUCCESS fix: do NOT swallow self-upgrade failures.
1063
+ // Previously this only printed a warning and returned, causing the caller
1064
+ // (cli-entry.ts) to then unconditionally print "✅ Upgraded" and exit 0.
1065
+ // Now we surface the failure as a thrown error so the caller can exit non-zero
1066
+ // and avoid the contradictory success message.
1067
+ const errMsg = err instanceof Error ? err.message : String(err);
1068
+ const code = err instanceof Error && 'code' in err
1069
+ ? (err.code ?? '')
1070
+ : '';
1071
+ const isPermission = code === 'EACCES' || code === 'EPERM' || /EACCES|EPERM|permission denied/i.test(errMsg);
1072
+ const isBusy = code === 'EBUSY' || /EBUSY|in use|cannot access|being used by another process/i.test(errMsg);
1073
+ let hint;
683
1074
  if (isPermission) {
684
- warn(`Permission denied. Try: sudo ${cmd}`);
1075
+ hint = `Permission denied. Try: sudo ${cmd}`;
1076
+ }
1077
+ else if (isBusy) {
1078
+ hint = `A file is in use (likely another squad shell is running). Close other squad CLI processes and retry: ${cmd}`;
685
1079
  }
686
1080
  else {
687
- warn(`Upgrade failed. Try running manually: ${cmd}`);
1081
+ hint = `Upgrade failed. Try running manually: ${cmd}`;
688
1082
  }
1083
+ warn(hint);
1084
+ const failure = new Error(`Self-upgrade failed: ${hint}`);
1085
+ failure.code = code || undefined;
1086
+ throw failure;
689
1087
  }
690
1088
  }
691
1089
  //# sourceMappingURL=upgrade.js.map