@bradygaster/squad-sdk 0.9.1 → 0.9.2-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 (285) hide show
  1. package/README.md +340 -296
  2. package/dist/agents/history-shadow.d.ts +7 -5
  3. package/dist/agents/history-shadow.d.ts.map +1 -1
  4. package/dist/agents/history-shadow.js +69 -78
  5. package/dist/agents/history-shadow.js.map +1 -1
  6. package/dist/agents/index.d.ts +12 -1
  7. package/dist/agents/index.d.ts.map +1 -1
  8. package/dist/agents/index.js +62 -9
  9. package/dist/agents/index.js.map +1 -1
  10. package/dist/agents/lifecycle.d.ts +4 -0
  11. package/dist/agents/lifecycle.d.ts.map +1 -1
  12. package/dist/agents/lifecycle.js +6 -7
  13. package/dist/agents/lifecycle.js.map +1 -1
  14. package/dist/agents/onboarding.d.ts +4 -2
  15. package/dist/agents/onboarding.d.ts.map +1 -1
  16. package/dist/agents/onboarding.js +26 -16
  17. package/dist/agents/onboarding.js.map +1 -1
  18. package/dist/agents/personal.d.ts +2 -1
  19. package/dist/agents/personal.d.ts.map +1 -1
  20. package/dist/agents/personal.js +11 -12
  21. package/dist/agents/personal.js.map +1 -1
  22. package/dist/build/bundle.d.ts.map +1 -1
  23. package/dist/build/bundle.js +6 -6
  24. package/dist/build/bundle.js.map +1 -1
  25. package/dist/build/github-dist.js +42 -42
  26. package/dist/build/release.d.ts.map +1 -1
  27. package/dist/build/release.js +7 -5
  28. package/dist/build/release.js.map +1 -1
  29. package/dist/casting/index.d.ts.map +1 -1
  30. package/dist/casting/index.js +4 -3
  31. package/dist/casting/index.js.map +1 -1
  32. package/dist/config/agent-source.d.ts +5 -1
  33. package/dist/config/agent-source.d.ts.map +1 -1
  34. package/dist/config/agent-source.js +85 -41
  35. package/dist/config/agent-source.js.map +1 -1
  36. package/dist/config/init.d.ts +4 -3
  37. package/dist/config/init.d.ts.map +1 -1
  38. package/dist/config/init.js +257 -236
  39. package/dist/config/init.js.map +1 -1
  40. package/dist/config/legacy-fallback.d.ts +3 -2
  41. package/dist/config/legacy-fallback.d.ts.map +1 -1
  42. package/dist/config/legacy-fallback.js +16 -14
  43. package/dist/config/legacy-fallback.js.map +1 -1
  44. package/dist/config/models.d.ts +9 -6
  45. package/dist/config/models.d.ts.map +1 -1
  46. package/dist/config/models.js +35 -25
  47. package/dist/config/models.js.map +1 -1
  48. package/dist/index.d.ts +5 -1
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +14 -1
  51. package/dist/index.js.map +1 -1
  52. package/dist/marketplace/packaging.d.ts.map +1 -1
  53. package/dist/marketplace/packaging.js +18 -16
  54. package/dist/marketplace/packaging.js.map +1 -1
  55. package/dist/multi-squad.d.ts.map +1 -1
  56. package/dist/multi-squad.js +10 -9
  57. package/dist/multi-squad.js.map +1 -1
  58. package/dist/platform/comms-file-log.d.ts.map +1 -1
  59. package/dist/platform/comms-file-log.js +7 -6
  60. package/dist/platform/comms-file-log.js.map +1 -1
  61. package/dist/platform/comms.d.ts.map +1 -1
  62. package/dist/platform/comms.js +6 -5
  63. package/dist/platform/comms.js.map +1 -1
  64. package/dist/platform/index.d.ts.map +1 -1
  65. package/dist/platform/index.js +4 -3
  66. package/dist/platform/index.js.map +1 -1
  67. package/dist/ralph/capabilities.d.ts +30 -1
  68. package/dist/ralph/capabilities.d.ts.map +1 -1
  69. package/dist/ralph/capabilities.js +51 -6
  70. package/dist/ralph/capabilities.js.map +1 -1
  71. package/dist/ralph/index.d.ts +1 -1
  72. package/dist/ralph/index.d.ts.map +1 -1
  73. package/dist/ralph/index.js +4 -3
  74. package/dist/ralph/index.js.map +1 -1
  75. package/dist/ralph/rate-limiting.d.ts.map +1 -1
  76. package/dist/ralph/rate-limiting.js +4 -4
  77. package/dist/ralph/rate-limiting.js.map +1 -1
  78. package/dist/remote/bridge.d.ts.map +1 -1
  79. package/dist/remote/bridge.js +2 -2
  80. package/dist/remote/bridge.js.map +1 -1
  81. package/dist/resolution.d.ts +9 -0
  82. package/dist/resolution.d.ts.map +1 -1
  83. package/dist/resolution.js +39 -16
  84. package/dist/resolution.js.map +1 -1
  85. package/dist/roles/catalog.d.ts +1 -1
  86. package/dist/runtime/config.d.ts.map +1 -1
  87. package/dist/runtime/config.js +8 -7
  88. package/dist/runtime/config.js.map +1 -1
  89. package/dist/runtime/cross-squad.d.ts.map +1 -1
  90. package/dist/runtime/cross-squad.js +8 -7
  91. package/dist/runtime/cross-squad.js.map +1 -1
  92. package/dist/runtime/scheduler.d.ts.map +1 -1
  93. package/dist/runtime/scheduler.js +8 -8
  94. package/dist/runtime/scheduler.js.map +1 -1
  95. package/dist/runtime/squad-observer.d.ts.map +1 -1
  96. package/dist/runtime/squad-observer.js +7 -4
  97. package/dist/runtime/squad-observer.js.map +1 -1
  98. package/dist/sharing/consult.d.ts +1 -1
  99. package/dist/sharing/consult.d.ts.map +1 -1
  100. package/dist/sharing/consult.js +144 -142
  101. package/dist/sharing/consult.js.map +1 -1
  102. package/dist/sharing/export.d.ts.map +1 -1
  103. package/dist/sharing/export.js +16 -16
  104. package/dist/sharing/export.js.map +1 -1
  105. package/dist/sharing/import.d.ts.map +1 -1
  106. package/dist/sharing/import.js +13 -12
  107. package/dist/sharing/import.js.map +1 -1
  108. package/dist/skills/skill-loader.d.ts.map +1 -1
  109. package/dist/skills/skill-loader.js +10 -9
  110. package/dist/skills/skill-loader.js.map +1 -1
  111. package/dist/skills/skill-script-loader.d.ts.map +1 -1
  112. package/dist/skills/skill-script-loader.js +6 -4
  113. package/dist/skills/skill-script-loader.js.map +1 -1
  114. package/dist/skills/skill-source.d.ts +3 -1
  115. package/dist/skills/skill-source.d.ts.map +1 -1
  116. package/dist/skills/skill-source.js +18 -16
  117. package/dist/skills/skill-source.js.map +1 -1
  118. package/dist/state/collection-map.d.ts +43 -0
  119. package/dist/state/collection-map.d.ts.map +1 -0
  120. package/dist/state/collection-map.js +9 -0
  121. package/dist/state/collection-map.js.map +1 -0
  122. package/dist/state/collections.d.ts +102 -0
  123. package/dist/state/collections.d.ts.map +1 -0
  124. package/dist/state/collections.js +317 -0
  125. package/dist/state/collections.js.map +1 -0
  126. package/dist/state/domain-types.d.ts +122 -0
  127. package/dist/state/domain-types.d.ts.map +1 -0
  128. package/dist/state/domain-types.js +54 -0
  129. package/dist/state/domain-types.js.map +1 -0
  130. package/dist/state/handles.d.ts +16 -0
  131. package/dist/state/handles.d.ts.map +1 -0
  132. package/dist/state/handles.js +161 -0
  133. package/dist/state/handles.js.map +1 -0
  134. package/dist/state/index.d.ts +17 -0
  135. package/dist/state/index.d.ts.map +1 -0
  136. package/dist/state/index.js +15 -0
  137. package/dist/state/index.js.map +1 -0
  138. package/dist/state/io/charter-io.d.ts +28 -0
  139. package/dist/state/io/charter-io.d.ts.map +1 -0
  140. package/dist/state/io/charter-io.js +94 -0
  141. package/dist/state/io/charter-io.js.map +1 -0
  142. package/dist/state/io/decisions-io.d.ts +42 -0
  143. package/dist/state/io/decisions-io.d.ts.map +1 -0
  144. package/dist/state/io/decisions-io.js +66 -0
  145. package/dist/state/io/decisions-io.js.map +1 -0
  146. package/dist/state/io/history-io.d.ts +37 -0
  147. package/dist/state/io/history-io.d.ts.map +1 -0
  148. package/dist/state/io/history-io.js +102 -0
  149. package/dist/state/io/history-io.js.map +1 -0
  150. package/dist/state/io/index.d.ts +19 -0
  151. package/dist/state/io/index.d.ts.map +1 -0
  152. package/dist/state/io/index.js +19 -0
  153. package/dist/state/io/index.js.map +1 -0
  154. package/dist/state/io/routing-io.d.ts +37 -0
  155. package/dist/state/io/routing-io.d.ts.map +1 -0
  156. package/dist/state/io/routing-io.js +99 -0
  157. package/dist/state/io/routing-io.js.map +1 -0
  158. package/dist/state/io/team-io.d.ts +46 -0
  159. package/dist/state/io/team-io.d.ts.map +1 -0
  160. package/dist/state/io/team-io.js +82 -0
  161. package/dist/state/io/team-io.js.map +1 -0
  162. package/dist/state/schema.d.ts +24 -0
  163. package/dist/state/schema.d.ts.map +1 -0
  164. package/dist/state/schema.js +41 -0
  165. package/dist/state/schema.js.map +1 -0
  166. package/dist/state/squad-state.d.ts +42 -0
  167. package/dist/state/squad-state.d.ts.map +1 -0
  168. package/dist/state/squad-state.js +68 -0
  169. package/dist/state/squad-state.js.map +1 -0
  170. package/dist/storage/fs-storage-provider.d.ts +60 -0
  171. package/dist/storage/fs-storage-provider.d.ts.map +1 -0
  172. package/dist/storage/fs-storage-provider.js +377 -0
  173. package/dist/storage/fs-storage-provider.js.map +1 -0
  174. package/dist/storage/in-memory-storage-provider.d.ts +46 -0
  175. package/dist/storage/in-memory-storage-provider.d.ts.map +1 -0
  176. package/dist/storage/in-memory-storage-provider.js +264 -0
  177. package/dist/storage/in-memory-storage-provider.js.map +1 -0
  178. package/dist/storage/index.d.ts +6 -0
  179. package/dist/storage/index.d.ts.map +1 -0
  180. package/dist/storage/index.js +5 -0
  181. package/dist/storage/index.js.map +1 -0
  182. package/dist/storage/sqlite-storage-provider.d.ts +95 -0
  183. package/dist/storage/sqlite-storage-provider.d.ts.map +1 -0
  184. package/dist/storage/sqlite-storage-provider.js +383 -0
  185. package/dist/storage/sqlite-storage-provider.js.map +1 -0
  186. package/dist/storage/storage-error.d.ts +28 -0
  187. package/dist/storage/storage-error.d.ts.map +1 -0
  188. package/dist/storage/storage-error.js +35 -0
  189. package/dist/storage/storage-error.js.map +1 -0
  190. package/dist/storage/storage-provider.d.ts +161 -0
  191. package/dist/storage/storage-provider.d.ts.map +1 -0
  192. package/dist/storage/storage-provider.js +18 -0
  193. package/dist/storage/storage-provider.js.map +1 -0
  194. package/dist/streams/resolver.d.ts.map +1 -1
  195. package/dist/streams/resolver.js +6 -5
  196. package/dist/streams/resolver.js.map +1 -1
  197. package/dist/tools/index.d.ts +5 -1
  198. package/dist/tools/index.d.ts.map +1 -1
  199. package/dist/tools/index.js +54 -15
  200. package/dist/tools/index.js.map +1 -1
  201. package/dist/upstream/resolver.d.ts +3 -2
  202. package/dist/upstream/resolver.d.ts.map +1 -1
  203. package/dist/upstream/resolver.js +33 -32
  204. package/dist/upstream/resolver.js.map +1 -1
  205. package/package.json +33 -1
  206. package/templates/casting/Futurama.json +9 -9
  207. package/templates/casting-history.json +4 -4
  208. package/templates/casting-policy.json +37 -37
  209. package/templates/casting-reference.md +104 -104
  210. package/templates/casting-registry.json +3 -3
  211. package/templates/ceremonies.md +41 -41
  212. package/templates/charter.md +53 -53
  213. package/templates/constraint-tracking.md +38 -38
  214. package/templates/cooperative-rate-limiting.md +229 -229
  215. package/templates/copilot-instructions.md +46 -46
  216. package/templates/history.md +10 -10
  217. package/templates/identity/now.md +9 -9
  218. package/templates/identity/wisdom.md +15 -15
  219. package/templates/issue-lifecycle.md +412 -412
  220. package/templates/keda-scaler.md +164 -164
  221. package/templates/machine-capabilities.md +74 -74
  222. package/templates/mcp-config.md +90 -90
  223. package/templates/multi-agent-format.md +28 -28
  224. package/templates/plugin-marketplace.md +49 -49
  225. package/templates/ralph-circuit-breaker.md +313 -313
  226. package/templates/raw-agent-output.md +37 -37
  227. package/templates/roster.md +60 -60
  228. package/templates/routing.md +39 -39
  229. package/templates/run-output.md +50 -50
  230. package/templates/schedule.json +19 -19
  231. package/templates/scribe-charter.md +123 -119
  232. package/templates/skill.md +24 -24
  233. package/templates/skills/agent-collaboration/SKILL.md +42 -42
  234. package/templates/skills/agent-conduct/SKILL.md +24 -24
  235. package/templates/skills/architectural-proposals/SKILL.md +151 -151
  236. package/templates/skills/ci-validation-gates/SKILL.md +84 -84
  237. package/templates/skills/cli-wiring/SKILL.md +47 -47
  238. package/templates/skills/client-compatibility/SKILL.md +89 -89
  239. package/templates/skills/cross-machine-coordination/SKILL.md +434 -0
  240. package/templates/skills/cross-squad/SKILL.md +114 -114
  241. package/templates/skills/distributed-mesh/SKILL.md +287 -287
  242. package/templates/skills/distributed-mesh/mesh.json.example +30 -30
  243. package/templates/skills/distributed-mesh/sync-mesh.ps1 +111 -111
  244. package/templates/skills/distributed-mesh/sync-mesh.sh +104 -104
  245. package/templates/skills/docs-standards/SKILL.md +71 -71
  246. package/templates/skills/economy-mode/SKILL.md +114 -114
  247. package/templates/skills/error-recovery/SKILL.md +99 -0
  248. package/templates/skills/external-comms/SKILL.md +329 -329
  249. package/templates/skills/gh-auth-isolation/SKILL.md +183 -183
  250. package/templates/skills/git-workflow/SKILL.md +204 -204
  251. package/templates/skills/github-multi-account/SKILL.md +95 -95
  252. package/templates/skills/history-hygiene/SKILL.md +36 -36
  253. package/templates/skills/humanizer/SKILL.md +105 -105
  254. package/templates/skills/init-mode/SKILL.md +102 -102
  255. package/templates/skills/iterative-retrieval/SKILL.md +165 -0
  256. package/templates/skills/model-selection/SKILL.md +117 -117
  257. package/templates/skills/nap/SKILL.md +24 -24
  258. package/templates/skills/notification-routing/SKILL.md +105 -0
  259. package/templates/skills/personal-squad/SKILL.md +57 -57
  260. package/templates/skills/pr-screenshots/SKILL.md +149 -0
  261. package/templates/skills/project-conventions/SKILL.md +56 -56
  262. package/templates/skills/ralph-two-pass-scan/SKILL.md +35 -0
  263. package/templates/skills/reflect/SKILL.md +229 -0
  264. package/templates/skills/release-process/SKILL.md +131 -423
  265. package/templates/skills/reskill/SKILL.md +92 -92
  266. package/templates/skills/retro-enforcement/SKILL.md +148 -0
  267. package/templates/skills/reviewer-protocol/SKILL.md +79 -79
  268. package/templates/skills/secret-handling/SKILL.md +200 -200
  269. package/templates/skills/session-recovery/SKILL.md +155 -155
  270. package/templates/skills/squad-conventions/SKILL.md +69 -69
  271. package/templates/skills/test-discipline/SKILL.md +37 -37
  272. package/templates/skills/tiered-memory/SKILL.md +234 -0
  273. package/templates/skills/windows-compatibility/SKILL.md +98 -74
  274. package/templates/{squad.agent.md → squad.agent.md.template} +57 -28
  275. package/templates/workflows/squad-ci.yml +24 -24
  276. package/templates/workflows/squad-docs.yml +54 -54
  277. package/templates/workflows/squad-heartbeat.yml +167 -171
  278. package/templates/workflows/squad-insider-release.yml +61 -61
  279. package/templates/workflows/squad-issue-assign.yml +161 -161
  280. package/templates/workflows/squad-label-enforce.yml +181 -181
  281. package/templates/workflows/squad-preview.yml +55 -55
  282. package/templates/workflows/squad-promote.yml +120 -120
  283. package/templates/workflows/squad-release.yml +77 -77
  284. package/templates/workflows/squad-triage.yml +260 -260
  285. package/templates/workflows/sync-squad-labels.yml +169 -169
@@ -14,11 +14,13 @@
14
14
  *
15
15
  * @module sharing/consult
16
16
  */
17
- import fs from 'node:fs';
17
+ import { cpSync } from 'node:fs'; // cpSync retained for recursive directory copy — StorageProvider.copySync is file-only
18
18
  import path from 'node:path';
19
19
  import { fileURLToPath } from 'node:url';
20
20
  import { execSync } from 'node:child_process';
21
+ import { FSStorageProvider } from '../storage/fs-storage-provider.js';
21
22
  import { resolveGlobalSquadPath } from '../resolution.js';
23
+ const storage = new FSStorageProvider();
22
24
  // ============================================================================
23
25
  // Typed Errors
24
26
  // ============================================================================
@@ -51,22 +53,22 @@ export class ExtractionDisabledError extends Error {
51
53
  * Consult mode preamble to inject after frontmatter in squad.agent.md.
52
54
  * This tells Squad it's in consult mode and should skip Init Mode.
53
55
  */
54
- const CONSULT_MODE_PREAMBLE = `
55
- <!-- consult-mode: true -->
56
-
57
- ## ⚡ Consult Mode Active
58
-
59
- This project is in **consult mode**. Your personal squad has been copied into \`.squad/\` for this session.
60
-
61
- **Key differences from normal mode:**
62
- - **Skip Init Mode** — The team already exists (copied from your personal squad)
63
- - **Isolated changes** — All changes stay local until you run \`squad extract\`
64
- - **Invisible to project** — Both \`.squad/\` and this agent file are in \`.git/info/exclude\`
65
-
66
- **When done:** Run \`squad extract\` to review learnings and merge generic ones back to your personal squad.
67
-
68
- ---
69
-
56
+ const CONSULT_MODE_PREAMBLE = `
57
+ <!-- consult-mode: true -->
58
+
59
+ ## ⚡ Consult Mode Active
60
+
61
+ This project is in **consult mode**. Your personal squad has been copied into \`.squad/\` for this session.
62
+
63
+ **Key differences from normal mode:**
64
+ - **Skip Init Mode** — The team already exists (copied from your personal squad)
65
+ - **Isolated changes** — All changes stay local until you run \`squad extract\`
66
+ - **Invisible to project** — Both \`.squad/\` and this agent file are in \`.git/info/exclude\`
67
+
68
+ **When done:** Run \`squad extract\` to review learnings and merge generic ones back to your personal squad.
69
+
70
+ ---
71
+
70
72
  `;
71
73
  /**
72
74
  * Get the full squad.agent.md template path.
@@ -76,13 +78,13 @@ function getSquadAgentTemplatePath() {
76
78
  // Use fileURLToPath for cross-platform compatibility (handles Windows drive letters, URL encoding)
77
79
  const currentDir = path.dirname(fileURLToPath(import.meta.url));
78
80
  // Try relative to this file (in dist/)
79
- const distPath = path.resolve(currentDir, '../../templates/squad.agent.md');
80
- if (fs.existsSync(distPath)) {
81
+ const distPath = path.resolve(currentDir, '../../templates/squad.agent.md.template');
82
+ if (storage.existsSync(distPath)) {
81
83
  return distPath;
82
84
  }
83
85
  // Try relative to package root
84
- const pkgPath = path.resolve(currentDir, '../../../templates/squad.agent.md');
85
- if (fs.existsSync(pkgPath)) {
86
+ const pkgPath = path.resolve(currentDir, '../../../templates/squad.agent.md.template');
87
+ if (storage.existsSync(pkgPath)) {
86
88
  return pkgPath;
87
89
  }
88
90
  return null;
@@ -119,8 +121,8 @@ function getGitRemoteUrl(projectRoot) {
119
121
  */
120
122
  function getConsultAgentContent(projectName) {
121
123
  const templatePath = getSquadAgentTemplatePath();
122
- if (templatePath && fs.existsSync(templatePath)) {
123
- const template = fs.readFileSync(templatePath, 'utf-8');
124
+ if (templatePath && storage.existsSync(templatePath)) {
125
+ const template = storage.readSync(templatePath) ?? '';
124
126
  // Find the end of frontmatter (second ---)
125
127
  const frontmatterEnd = template.indexOf('---', template.indexOf('---') + 3);
126
128
  if (frontmatterEnd !== -1) {
@@ -135,24 +137,24 @@ function getConsultAgentContent(projectName) {
135
137
  return template + '\n' + CONSULT_MODE_PREAMBLE;
136
138
  }
137
139
  // Fallback: minimal agent if template not found
138
- return `---
139
- name: Squad
140
- description: "Your AI team. Consulting on ${projectName} using your personal squad."
141
- ---
142
-
143
- ${CONSULT_MODE_PREAMBLE}
144
-
145
- You are **Squad (Consultant)** — working on **${projectName}** using a copy of your personal squad.
146
-
147
- ### Available Context (local copy in .squad/)
148
-
149
- - **Team:** \`.squad/team.md\` for roster and roles
150
- - **Routing:** \`.squad/routing.md\` for task routing rules
151
- - **Decisions:** \`.squad/decisions.md\` for your established patterns
152
- - **Skills:** \`.copilot/skills/\` for reusable capabilities
153
- - **Agents:** \`.squad/agents/\` for your squad agents
154
-
155
- Work as you would with your personal squad, but in this external codebase.
140
+ return `---
141
+ name: Squad
142
+ description: "Your AI team. Consulting on ${projectName} using your personal squad."
143
+ ---
144
+
145
+ ${CONSULT_MODE_PREAMBLE}
146
+
147
+ You are **Squad (Consultant)** — working on **${projectName}** using a copy of your personal squad.
148
+
149
+ ### Available Context (local copy in .squad/)
150
+
151
+ - **Team:** \`.squad/team.md\` for roster and roles
152
+ - **Routing:** \`.squad/routing.md\` for task routing rules
153
+ - **Decisions:** \`.squad/decisions.md\` for your established patterns
154
+ - **Skills:** \`.copilot/skills/\` for reusable capabilities
155
+ - **Agents:** \`.squad/agents/\` for your squad agents
156
+
157
+ Work as you would with your personal squad, but in this external codebase.
156
158
  `;
157
159
  }
158
160
  // ============================================================================
@@ -162,65 +164,65 @@ Work as you would with your personal squad, but in this external codebase.
162
164
  * Consult mode instructions to append to Scribe charter.
163
165
  * This enables Scribe to classify decisions as generic or project-specific.
164
166
  */
165
- const CONSULT_MODE_SCRIBE_PATCH = `
166
-
167
- ---
168
-
169
- ## Consult Mode Extraction
170
-
171
- **This squad is in consult mode.** When merging decisions from the inbox, also classify each decision:
172
-
173
- ### Classification
174
-
175
- For each decision in \`.squad/decisions/inbox/\`:
176
-
177
- 1. **Generic** (applies to any project) → Copy to \`.squad/extract/\` with the same filename
178
- - Signals: "always use", "never use", "prefer X over Y", "best practice", coding standards, patterns that work anywhere
179
- - These will be extracted to the personal squad via \`squad extract\`
180
-
181
- 2. **Project-specific** (only applies here) → Keep in local \`decisions.md\` only
182
- - Signals: Contains file paths from this project, references "this project/codebase/repo", mentions project-specific config/APIs/schemas
183
-
184
- Generic decisions go to BOTH \`.squad/decisions.md\` (for this session) AND \`.squad/extract/\` (for later extraction).
185
-
186
- ### Extract Directory
187
-
188
- \`\`\`
189
- .squad/extract/ # Generic learnings staged for personal squad
190
- ├── decision-1.md # Ready for extraction
191
- └── pattern-auth.md # Ready for extraction
192
- \`\`\`
193
-
194
- Run \`squad extract\` to review and merge these to your personal squad.
167
+ const CONSULT_MODE_SCRIBE_PATCH = `
168
+
169
+ ---
170
+
171
+ ## Consult Mode Extraction
172
+
173
+ **This squad is in consult mode.** When merging decisions from the inbox, also classify each decision:
174
+
175
+ ### Classification
176
+
177
+ For each decision in \`.squad/decisions/inbox/\`:
178
+
179
+ 1. **Generic** (applies to any project) → Copy to \`.squad/extract/\` with the same filename
180
+ - Signals: "always use", "never use", "prefer X over Y", "best practice", coding standards, patterns that work anywhere
181
+ - These will be extracted to the personal squad via \`squad extract\`
182
+
183
+ 2. **Project-specific** (only applies here) → Keep in local \`decisions.md\` only
184
+ - Signals: Contains file paths from this project, references "this project/codebase/repo", mentions project-specific config/APIs/schemas
185
+
186
+ Generic decisions go to BOTH \`.squad/decisions.md\` (for this session) AND \`.squad/extract/\` (for later extraction).
187
+
188
+ ### Extract Directory
189
+
190
+ \`\`\`
191
+ .squad/extract/ # Generic learnings staged for personal squad
192
+ ├── decision-1.md # Ready for extraction
193
+ └── pattern-auth.md # Ready for extraction
194
+ \`\`\`
195
+
196
+ Run \`squad extract\` to review and merge these to your personal squad.
195
197
  `;
196
198
  /**
197
199
  * Patch the Scribe charter in the copied squad with consult mode instructions.
198
200
  */
199
201
  function patchScribeCharterForConsultMode(squadDir) {
200
202
  const charterPath = path.join(squadDir, 'agents', 'scribe', 'charter.md');
201
- if (!fs.existsSync(charterPath)) {
203
+ if (!storage.existsSync(charterPath)) {
202
204
  // No scribe charter to patch — skip silently
203
205
  return;
204
206
  }
205
- const existing = fs.readFileSync(charterPath, 'utf-8');
207
+ const existing = storage.readSync(charterPath) ?? '';
206
208
  // Don't patch if already patched
207
209
  if (existing.includes('Consult Mode Extraction')) {
208
210
  return;
209
211
  }
210
- fs.appendFileSync(charterPath, CONSULT_MODE_SCRIBE_PATCH);
212
+ storage.appendSync(charterPath, CONSULT_MODE_SCRIBE_PATCH);
211
213
  }
212
214
  /**
213
215
  * List files recursively in a directory.
214
216
  */
215
217
  function listFilesInDir(dir, basePath = '') {
216
- if (!fs.existsSync(dir))
218
+ if (!storage.existsSync(dir))
217
219
  return [];
218
220
  const files = [];
219
- const entries = fs.readdirSync(dir, { withFileTypes: true });
221
+ const entries = storage.listSync(dir);
220
222
  for (const entry of entries) {
221
- const relativePath = basePath ? path.join(basePath, entry.name) : entry.name;
222
- if (entry.isDirectory()) {
223
- files.push(...listFilesInDir(path.join(dir, entry.name), relativePath));
223
+ const relativePath = basePath ? path.join(basePath, entry) : entry;
224
+ if (storage.isDirectorySync(path.join(dir, entry))) {
225
+ files.push(...listFilesInDir(path.join(dir, entry), relativePath));
224
226
  }
225
227
  else {
226
228
  files.push(relativePath);
@@ -230,10 +232,10 @@ function listFilesInDir(dir, basePath = '') {
230
232
  }
231
233
  /**
232
234
  * Get the personal squad root path.
233
- * Returns {globalSquadPath}/.squad/
235
+ * Returns {globalSquadPath}/personal-squad/
234
236
  */
235
237
  export function getPersonalSquadRoot() {
236
- return path.resolve(resolveGlobalSquadPath(), '.squad');
238
+ return path.resolve(resolveGlobalSquadPath(), 'personal-squad');
237
239
  }
238
240
  /**
239
241
  * Resolve the git exclude path using git rev-parse (handles worktrees/submodules).
@@ -272,7 +274,7 @@ export async function setupConsultMode(options = {}) {
272
274
  const agentFile = path.resolve(projectRoot, '.github', 'agents', 'squad.agent.md');
273
275
  // Check if we're in a git repository (handle worktrees/submodules where .git is a file)
274
276
  const gitPath = path.resolve(projectRoot, '.git');
275
- if (!fs.existsSync(gitPath)) {
277
+ if (!storage.existsSync(gitPath)) {
276
278
  throw new Error('Not a git repository. Consult mode requires git.');
277
279
  }
278
280
  // Resolve exclude path via git rev-parse (handles worktrees/submodules)
@@ -282,16 +284,16 @@ export async function setupConsultMode(options = {}) {
282
284
  return path.isAbsolute(excludePath) ? excludePath : path.resolve(projectRoot, excludePath);
283
285
  })();
284
286
  // Check if personal squad exists
285
- if (!fs.existsSync(personalSquadRoot)) {
287
+ if (!storage.existsSync(personalSquadRoot)) {
286
288
  throw new PersonalSquadNotFoundError();
287
289
  }
288
290
  // Read source squad's config to inherit extractionDisabled setting
289
291
  // Option takes precedence, then fall back to source config
290
292
  let extractionDisabled = options.extractionDisabled ?? false;
291
293
  const sourceConfigPath = path.join(personalSquadRoot, 'config.json');
292
- if (fs.existsSync(sourceConfigPath)) {
294
+ if (storage.existsSync(sourceConfigPath)) {
293
295
  try {
294
- const sourceConfig = JSON.parse(fs.readFileSync(sourceConfigPath, 'utf-8'));
296
+ const sourceConfig = JSON.parse(storage.readSync(sourceConfigPath) ?? '{}');
295
297
  // Inherit from source unless explicitly overridden in options
296
298
  if (options.extractionDisabled === undefined && sourceConfig.extractionDisabled) {
297
299
  extractionDisabled = true;
@@ -302,7 +304,7 @@ export async function setupConsultMode(options = {}) {
302
304
  }
303
305
  }
304
306
  // Check if project already has .squad/
305
- if (fs.existsSync(squadDir)) {
307
+ if (storage.existsSync(squadDir)) {
306
308
  throw new Error('This project already has a .squad/ directory. Cannot use consult mode on squadified projects.');
307
309
  }
308
310
  // List files in personal squad (for dry run preview or later count)
@@ -310,7 +312,7 @@ export async function setupConsultMode(options = {}) {
310
312
  if (!dryRun) {
311
313
  // Copy personal squad contents into project's .squad/
312
314
  // This isolates changes during the consult session
313
- fs.cpSync(personalSquadRoot, squadDir, { recursive: true });
315
+ cpSync(personalSquadRoot, squadDir, { recursive: true });
314
316
  // Write/overwrite config.json with consult: true
315
317
  // Include SquadDirConfig fields so loadDirConfig() can read it
316
318
  // Note: version must be numeric for loadDirConfig() compatibility
@@ -323,30 +325,30 @@ export async function setupConsultMode(options = {}) {
323
325
  createdAt: new Date().toISOString(),
324
326
  extractionDisabled,
325
327
  };
326
- fs.writeFileSync(path.join(squadDir, 'config.json'), JSON.stringify(config, null, 2), 'utf-8');
328
+ storage.writeSync(path.join(squadDir, 'config.json'), JSON.stringify(config, null, 2));
327
329
  // Create sessions directory for tracking (if not copied)
328
330
  const sessionsDir = path.join(squadDir, 'sessions');
329
- if (!fs.existsSync(sessionsDir)) {
330
- fs.mkdirSync(sessionsDir, { recursive: true });
331
+ if (!storage.existsSync(sessionsDir)) {
332
+ storage.mkdirSync(sessionsDir, { recursive: true });
331
333
  }
332
334
  // Create extract/ directory for staging generic learnings
333
335
  const extractDir = path.join(squadDir, 'extract');
334
- fs.mkdirSync(extractDir, { recursive: true });
336
+ storage.mkdirSync(extractDir, { recursive: true });
335
337
  // Patch scribe-charter.md with consult mode extraction instructions
336
338
  patchScribeCharterForConsultMode(squadDir);
337
339
  // Create .github/agents/squad.agent.md for `gh copilot --agent squad`
338
340
  const agentDir = path.dirname(agentFile);
339
- if (!fs.existsSync(agentDir)) {
340
- fs.mkdirSync(agentDir, { recursive: true });
341
+ if (!storage.existsSync(agentDir)) {
342
+ storage.mkdirSync(agentDir, { recursive: true });
341
343
  }
342
- fs.writeFileSync(agentFile, getConsultAgentContent(projectName), 'utf-8');
344
+ storage.writeSync(agentFile, getConsultAgentContent(projectName));
343
345
  // Add .squad/ and .github/agents/squad.agent.md to .git/info/exclude
344
346
  const excludeDir = path.dirname(gitExclude);
345
- if (!fs.existsSync(excludeDir)) {
346
- fs.mkdirSync(excludeDir, { recursive: true });
347
+ if (!storage.existsSync(excludeDir)) {
348
+ storage.mkdirSync(excludeDir, { recursive: true });
347
349
  }
348
- const excludeContent = fs.existsSync(gitExclude)
349
- ? fs.readFileSync(gitExclude, 'utf-8')
350
+ const excludeContent = storage.existsSync(gitExclude)
351
+ ? storage.readSync(gitExclude) ?? ''
350
352
  : '';
351
353
  const excludeLines = [];
352
354
  if (!excludeContent.includes('.squad/')) {
@@ -356,7 +358,7 @@ export async function setupConsultMode(options = {}) {
356
358
  excludeLines.push('.github/agents/squad.agent.md');
357
359
  }
358
360
  if (excludeLines.length > 0) {
359
- fs.appendFileSync(gitExclude, '\n# Squad consult mode (local only)\n' + excludeLines.join('\n') + '\n');
361
+ storage.appendSync(gitExclude, '\n# Squad consult mode (local only)\n' + excludeLines.join('\n') + '\n');
360
362
  }
361
363
  }
362
364
  // List files created (from squad dir after copy, or from source for dry run)
@@ -381,15 +383,15 @@ export async function setupConsultMode(options = {}) {
381
383
  export function loadSessionHistory(squadDir) {
382
384
  const sessionsDir = path.join(squadDir, 'sessions');
383
385
  const entries = [];
384
- if (!fs.existsSync(sessionsDir)) {
386
+ if (!storage.existsSync(sessionsDir)) {
385
387
  return { entries };
386
388
  }
387
- const files = fs.readdirSync(sessionsDir)
389
+ const files = storage.listSync(sessionsDir)
388
390
  .filter(f => f.endsWith('.json'))
389
391
  .sort();
390
392
  for (const file of files) {
391
393
  try {
392
- const content = fs.readFileSync(path.join(sessionsDir, file), 'utf-8');
394
+ const content = storage.readSync(path.join(sessionsDir, file)) ?? '';
393
395
  const session = JSON.parse(content);
394
396
  // Extract learnings from session data
395
397
  if (session.learnings && Array.isArray(session.learnings)) {
@@ -442,14 +444,14 @@ export async function extractLearnings(options = {}) {
442
444
  const squadDir = path.resolve(projectRoot, '.squad');
443
445
  const projectName = options.projectName || path.basename(projectRoot);
444
446
  // Check if we're in consult mode
445
- if (!fs.existsSync(squadDir)) {
447
+ if (!storage.existsSync(squadDir)) {
446
448
  throw new Error('Not in consult mode. No .squad/ directory found.');
447
449
  }
448
450
  const configPath = path.join(squadDir, 'config.json');
449
- if (!fs.existsSync(configPath)) {
451
+ if (!storage.existsSync(configPath)) {
450
452
  throw new Error('Invalid consult mode: missing config.json');
451
453
  }
452
- const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
454
+ const config = JSON.parse(storage.readSync(configPath) ?? '{}');
453
455
  if (!config.consult) {
454
456
  throw new Error('This project has a .squad/ but is not in consult mode. Use normal squad commands.');
455
457
  }
@@ -459,8 +461,8 @@ export async function extractLearnings(options = {}) {
459
461
  }
460
462
  // Detect license
461
463
  const licensePath = path.join(projectRoot, 'LICENSE');
462
- const licenseContent = fs.existsSync(licensePath)
463
- ? fs.readFileSync(licensePath, 'utf-8')
464
+ const licenseContent = storage.existsSync(licensePath)
465
+ ? storage.readSync(licensePath) ?? ''
464
466
  : '';
465
467
  const license = detectLicense(licenseContent);
466
468
  // Block copyleft extraction unless --accept-risks
@@ -514,12 +516,12 @@ export async function extractLearnings(options = {}) {
514
516
  consultationLogPath = await logConsultation(personalSquadRoot, result);
515
517
  // Remove extracted files from .squad/extract/
516
518
  for (const learning of staged) {
517
- fs.rmSync(learning.filepath, { force: true });
519
+ storage.deleteSync(learning.filepath);
518
520
  }
519
521
  }
520
522
  // Clean up entire .squad/ if requested
521
523
  if (clean && !dryRun) {
522
- fs.rmSync(squadDir, { recursive: true, force: true });
524
+ storage.deleteDirSync(squadDir);
523
525
  cleaned = true;
524
526
  }
525
527
  return {
@@ -615,14 +617,14 @@ export function detectLicense(licenseContent) {
615
617
  export function loadStagedLearnings(squadDir) {
616
618
  const extractDir = path.join(squadDir, 'extract');
617
619
  const learnings = [];
618
- if (!fs.existsSync(extractDir)) {
620
+ if (!storage.existsSync(extractDir)) {
619
621
  return learnings;
620
622
  }
621
- const files = fs.readdirSync(extractDir).filter(f => f.endsWith('.md'));
623
+ const files = storage.listSync(extractDir).filter(f => f.endsWith('.md'));
622
624
  for (const file of files) {
623
625
  const filepath = path.join(extractDir, file);
624
626
  try {
625
- const content = fs.readFileSync(filepath, 'utf-8');
627
+ const content = storage.readSync(filepath) ?? '';
626
628
  learnings.push({
627
629
  filename: file,
628
630
  filepath,
@@ -652,25 +654,25 @@ export async function logConsultation(personalSquadRoot, result) {
652
654
  const consultDir = path.join(personalSquadRoot, 'consultations');
653
655
  const logPath = path.join(consultDir, `${result.projectName}.md`);
654
656
  // Create consultations directory if needed
655
- if (!fs.existsSync(consultDir)) {
656
- fs.mkdirSync(consultDir, { recursive: true });
657
+ if (!storage.existsSync(consultDir)) {
658
+ storage.mkdirSync(consultDir, { recursive: true });
657
659
  }
658
660
  const today = result.timestamp.split('T')[0] ?? new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
659
- if (fs.existsSync(logPath)) {
661
+ if (storage.existsSync(logPath)) {
660
662
  // Append to existing log — update "Last session" and add new entry
661
- let content = fs.readFileSync(logPath, 'utf-8');
663
+ let content = storage.readSync(logPath) ?? '';
662
664
  // Update "Last session" date
663
665
  content = content.replace(/\*\*Last session:\*\* \d{4}-\d{2}-\d{2}/, `**Last session:** ${today}`);
664
666
  // Build session entry
665
667
  const sessionEntry = formatSessionEntry(result, today);
666
668
  // Append to file
667
- fs.writeFileSync(logPath, content + sessionEntry, 'utf-8');
669
+ storage.writeSync(logPath, content + sessionEntry);
668
670
  }
669
671
  else {
670
672
  // Create new consultation log with full header
671
673
  const header = formatLogHeader(result, today);
672
674
  const sessionEntry = formatSessionEntry(result, today);
673
- fs.writeFileSync(logPath, header + sessionEntry, 'utf-8');
675
+ storage.writeSync(logPath, header + sessionEntry);
674
676
  }
675
677
  return logPath;
676
678
  }
@@ -682,14 +684,14 @@ function formatLogHeader(result, date) {
682
684
  ? `**Repository:** ${result.repoUrl}\n`
683
685
  : '';
684
686
  const licenseName = result.license.spdxId || result.license.name || result.license.type;
685
- return `# ${result.projectName}
686
-
687
- ${repoLine}**First consulted:** ${date}
688
- **Last session:** ${date}
689
- **License:** ${licenseName}
690
-
691
- ## Extracted Learnings
692
-
687
+ return `# ${result.projectName}
688
+
689
+ ${repoLine}**First consulted:** ${date}
690
+ **Last session:** ${date}
691
+ **License:** ${licenseName}
692
+
693
+ ## Extracted Learnings
694
+
693
695
  `;
694
696
  }
695
697
  /**
@@ -697,16 +699,16 @@ ${repoLine}**First consulted:** ${date}
697
699
  */
698
700
  function formatSessionEntry(result, date) {
699
701
  if (result.extracted.length === 0) {
700
- return `### ${date}
701
- - No learnings extracted
702
-
702
+ return `### ${date}
703
+ - No learnings extracted
704
+
703
705
  `;
704
706
  }
705
707
  // Just list titles/filenames, not content
706
708
  const lines = result.extracted.map(l => `- ${l.filename}`);
707
- return `### ${date}
708
- ${lines.join('\n')}
709
-
709
+ return `### ${date}
710
+ ${lines.join('\n')}
711
+
710
712
  `;
711
713
  }
712
714
  // ============================================================================
@@ -763,20 +765,20 @@ export async function mergeToPersonalSquad(learnings, personalSquadRoot) {
763
765
  const skillName = extractSkillName(skill.content) || skill.filename.replace('.md', '');
764
766
  const skillDir = path.join(skillsDir, skillName);
765
767
  // Create skill directory if needed
766
- if (!fs.existsSync(skillDir)) {
767
- fs.mkdirSync(skillDir, { recursive: true });
768
+ if (!storage.existsSync(skillDir)) {
769
+ storage.mkdirSync(skillDir, { recursive: true });
768
770
  }
769
771
  const skillPath = path.join(skillDir, 'SKILL.md');
770
772
  // Write skill (overwrites if exists — newer extraction wins)
771
- fs.writeFileSync(skillPath, skill.content, 'utf-8');
773
+ storage.writeSync(skillPath, skill.content);
772
774
  skillsAdded++;
773
775
  }
774
776
  // Route decisions to personal squad directory at decisions.md
775
777
  if (decisions.length > 0) {
776
778
  const decisionsPath = path.join(personalSquadRoot, 'decisions.md');
777
779
  const newContent = decisions.map(d => d.content.trim()).join('\n\n');
778
- if (fs.existsSync(decisionsPath)) {
779
- const existing = fs.readFileSync(decisionsPath, 'utf-8');
780
+ if (storage.existsSync(decisionsPath)) {
781
+ const existing = storage.readSync(decisionsPath) ?? '';
780
782
  // Check if we already have an "Extracted from Consultations" section
781
783
  if (existing.includes('## Extracted from Consultations')) {
782
784
  // Append under the existing section (before any subsequent ## heading)
@@ -789,27 +791,27 @@ export async function mergeToPersonalSquad(learnings, personalSquadRoot) {
789
791
  // Insert before next section
790
792
  const sectionContent = afterSection.slice(0, nextSectionMatch.index);
791
793
  const rest = afterSection.slice(nextSectionMatch.index);
792
- fs.writeFileSync(decisionsPath, beforeSection +
794
+ storage.writeSync(decisionsPath, beforeSection +
793
795
  '## Extracted from Consultations' +
794
796
  sectionContent.trimEnd() +
795
797
  '\n\n' +
796
798
  newContent +
797
799
  '\n' +
798
- rest, 'utf-8');
800
+ rest);
799
801
  }
800
802
  else {
801
803
  // No next section — append to end
802
- fs.writeFileSync(decisionsPath, existing.trimEnd() + '\n\n' + newContent + '\n', 'utf-8');
804
+ storage.writeSync(decisionsPath, existing.trimEnd() + '\n\n' + newContent + '\n');
803
805
  }
804
806
  }
805
807
  else {
806
808
  // No extraction section yet — create one
807
- fs.writeFileSync(decisionsPath, existing.trimEnd() + '\n\n## Extracted from Consultations\n\n' + newContent + '\n', 'utf-8');
809
+ storage.writeSync(decisionsPath, existing.trimEnd() + '\n\n## Extracted from Consultations\n\n' + newContent + '\n');
808
810
  }
809
811
  }
810
812
  else {
811
813
  // Create new decisions file
812
- fs.writeFileSync(decisionsPath, `# Squad Decisions\n\n## Extracted from Consultations\n\n${newContent}\n`, 'utf-8');
814
+ storage.writeSync(decisionsPath, `# Squad Decisions\n\n## Extracted from Consultations\n\n${newContent}\n`);
813
815
  }
814
816
  decisionsAdded = decisions.length;
815
817
  }