@aligndottech/cli 0.63.0 → 0.64.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 (318) hide show
  1. package/README.md +3 -2
  2. package/dist/cli.d.ts +2 -2
  3. package/dist/cli.js +6 -6
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/adjudicate.d.ts +3 -3
  6. package/dist/commands/capture.js +2 -2
  7. package/dist/commands/capture.js.map +1 -1
  8. package/dist/commands/check.d.ts +1 -1
  9. package/dist/commands/check.js +30 -30
  10. package/dist/commands/check.js.map +1 -1
  11. package/dist/commands/connect-agents.d.ts +4 -2
  12. package/dist/commands/connect-agents.d.ts.map +1 -1
  13. package/dist/commands/connect-agents.js +7 -7
  14. package/dist/commands/connect-agents.js.map +1 -1
  15. package/dist/commands/connect.d.ts +1 -1
  16. package/dist/commands/connector/index.js +1 -1
  17. package/dist/commands/connector/index.js.map +1 -1
  18. package/dist/commands/context.d.ts +4 -4
  19. package/dist/commands/context.js +7 -7
  20. package/dist/commands/context.js.map +1 -1
  21. package/dist/commands/decisions/index.js +4 -4
  22. package/dist/commands/decisions/index.js.map +1 -1
  23. package/dist/commands/default-action.d.ts.map +1 -1
  24. package/dist/commands/default-action.js +8 -5
  25. package/dist/commands/default-action.js.map +1 -1
  26. package/dist/commands/glossary.d.ts +1 -1
  27. package/dist/commands/import/confluence.js +1 -1
  28. package/dist/commands/import/confluence.js.map +1 -1
  29. package/dist/commands/import/git.js +4 -4
  30. package/dist/commands/import/git.js.map +1 -1
  31. package/dist/commands/import/jira.js +1 -1
  32. package/dist/commands/import/jira.js.map +1 -1
  33. package/dist/commands/import/sessions.d.ts +6 -6
  34. package/dist/commands/import/sessions.js +10 -10
  35. package/dist/commands/import/sessions.js.map +1 -1
  36. package/dist/commands/import.d.ts +2 -2
  37. package/dist/commands/import.js +2 -2
  38. package/dist/commands/invite.d.ts +1 -1
  39. package/dist/commands/invite.js +1 -1
  40. package/dist/commands/invite.js.map +1 -1
  41. package/dist/commands/local.js +3 -3
  42. package/dist/commands/local.js.map +1 -1
  43. package/dist/commands/mcp.d.ts +4 -4
  44. package/dist/commands/mcp.d.ts.map +1 -1
  45. package/dist/commands/mcp.js +34 -35
  46. package/dist/commands/mcp.js.map +1 -1
  47. package/dist/commands/ratify.d.ts +1 -1
  48. package/dist/commands/ratify.js +1 -1
  49. package/dist/commands/ratify.js.map +1 -1
  50. package/dist/commands/registry.d.ts +2 -2
  51. package/dist/commands/registry.js +3 -3
  52. package/dist/commands/registry.js.map +1 -1
  53. package/dist/commands/search.js +2 -2
  54. package/dist/commands/search.js.map +1 -1
  55. package/dist/commands/setup.d.ts +14 -10
  56. package/dist/commands/setup.d.ts.map +1 -1
  57. package/dist/commands/setup.js +133 -75
  58. package/dist/commands/setup.js.map +1 -1
  59. package/dist/commands/status.js +2 -2
  60. package/dist/commands/status.js.map +1 -1
  61. package/dist/commands/telemetry.d.ts +2 -2
  62. package/dist/commands/telemetry.js +3 -3
  63. package/dist/commands/telemetry.js.map +1 -1
  64. package/dist/commands/why.js +31 -31
  65. package/dist/commands/why.js.map +1 -1
  66. package/dist/detector/local-model-default.d.ts +1 -1
  67. package/dist/detector/local-model-default.js +1 -1
  68. package/dist/index.js +2 -2
  69. package/dist/index.js.map +1 -1
  70. package/dist/lib/access-capability.d.ts +1 -1
  71. package/dist/lib/access-capability.js +1 -1
  72. package/dist/lib/advisory-verdict.d.ts +3 -3
  73. package/dist/lib/advisory-verdict.js +3 -3
  74. package/dist/lib/agent-rules.d.ts +9 -0
  75. package/dist/lib/agent-rules.d.ts.map +1 -1
  76. package/dist/lib/agent-rules.js +55 -39
  77. package/dist/lib/agent-rules.js.map +1 -1
  78. package/dist/lib/approve-suggestions.d.ts +1 -1
  79. package/dist/lib/approve-suggestions.js +1 -1
  80. package/dist/lib/as-of.d.ts +6 -6
  81. package/dist/lib/as-of.js +6 -6
  82. package/dist/lib/ask-evidence.js +1 -1
  83. package/dist/lib/ask-key-offer.js +1 -1
  84. package/dist/lib/ask-listing.js +1 -1
  85. package/dist/lib/brand.d.ts +2 -2
  86. package/dist/lib/brand.js +3 -3
  87. package/dist/lib/capture-report.d.ts +1 -1
  88. package/dist/lib/capture-report.d.ts.map +1 -1
  89. package/dist/lib/capture-report.js +3 -3
  90. package/dist/lib/capture-report.js.map +1 -1
  91. package/dist/lib/capture-sources.d.ts +1 -1
  92. package/dist/lib/capture-sources.js +1 -1
  93. package/dist/lib/check-depth.d.ts +1 -1
  94. package/dist/lib/check-depth.js +1 -1
  95. package/dist/lib/claude-presence.d.ts +1 -1
  96. package/dist/lib/claude-presence.js +1 -1
  97. package/dist/lib/cli-oauth.js +1 -1
  98. package/dist/lib/cli-oauth.js.map +1 -1
  99. package/dist/lib/commands-doc.d.ts +1 -1
  100. package/dist/lib/commands-doc.js +1 -1
  101. package/dist/lib/commit-cite.js +2 -2
  102. package/dist/lib/commit-shape.d.ts +1 -1
  103. package/dist/lib/commit-shape.js +1 -1
  104. package/dist/lib/config.d.ts +6 -6
  105. package/dist/lib/config.js +7 -7
  106. package/dist/lib/config.js.map +1 -1
  107. package/dist/lib/connect-prompt.d.ts +1 -1
  108. package/dist/lib/connect-prompt.js +1 -1
  109. package/dist/lib/decider-kind.d.ts +4 -4
  110. package/dist/lib/decider-kind.js +4 -4
  111. package/dist/lib/decision-links.d.ts +7 -8
  112. package/dist/lib/decision-links.d.ts.map +1 -1
  113. package/dist/lib/decision-links.js +8 -9
  114. package/dist/lib/decision-links.js.map +1 -1
  115. package/dist/lib/decision-refs.d.ts +3 -3
  116. package/dist/lib/decision-refs.js +4 -4
  117. package/dist/lib/decision-refs.js.map +1 -1
  118. package/dist/lib/decision-relations.d.ts +4 -4
  119. package/dist/lib/decision-relations.js +4 -4
  120. package/dist/lib/decision-render.d.ts +2 -2
  121. package/dist/lib/decision-render.d.ts.map +1 -1
  122. package/dist/lib/decision-render.js +5 -5
  123. package/dist/lib/decision-render.js.map +1 -1
  124. package/dist/lib/decisions-context.d.ts +4 -4
  125. package/dist/lib/decisions-context.js +5 -5
  126. package/dist/lib/decisions-context.js.map +1 -1
  127. package/dist/lib/doctor-mcp.js +1 -1
  128. package/dist/lib/doctor.d.ts +2 -2
  129. package/dist/lib/embedding-assets.js +1 -1
  130. package/dist/lib/embedding-runtime.js +1 -1
  131. package/dist/lib/fetchers/capture.d.ts +1 -1
  132. package/dist/lib/fetchers/docs.d.ts +1 -1
  133. package/dist/lib/fetchers/docs.js +5 -5
  134. package/dist/lib/fetchers/docs.js.map +1 -1
  135. package/dist/lib/fetchers/git.d.ts +1 -1
  136. package/dist/lib/fetchers/git.js +1 -1
  137. package/dist/lib/fetchers/github.d.ts +2 -2
  138. package/dist/lib/fetchers/github.js +2 -2
  139. package/dist/lib/fetchers/linear.js +1 -1
  140. package/dist/lib/fetchers/slack.d.ts +2 -2
  141. package/dist/lib/fetchers/slack.js +2 -2
  142. package/dist/lib/first-decision.d.ts +1 -1
  143. package/dist/lib/first-decision.js +1 -1
  144. package/dist/lib/first-run-pr-import.d.ts +1 -1
  145. package/dist/lib/format-date.d.ts +1 -1
  146. package/dist/lib/format-date.js +1 -1
  147. package/dist/lib/found-summary.js +4 -4
  148. package/dist/lib/found-summary.js.map +1 -1
  149. package/dist/lib/gateway-client.d.ts +27 -27
  150. package/dist/lib/gateway-client.d.ts.map +1 -1
  151. package/dist/lib/gateway-client.js +30 -30
  152. package/dist/lib/gateway-client.js.map +1 -1
  153. package/dist/lib/git.d.ts +10 -10
  154. package/dist/lib/git.d.ts.map +1 -1
  155. package/dist/lib/git.js +12 -12
  156. package/dist/lib/git.js.map +1 -1
  157. package/dist/lib/hook-context.js +1 -1
  158. package/dist/lib/hook-payload.d.ts +1 -1
  159. package/dist/lib/hybrid-rank.d.ts +3 -3
  160. package/dist/lib/hybrid-rank.d.ts.map +1 -1
  161. package/dist/lib/hybrid-rank.js +3 -3
  162. package/dist/lib/hybrid-rank.js.map +1 -1
  163. package/dist/lib/import-defaults.d.ts +5 -5
  164. package/dist/lib/import-defaults.js +3 -3
  165. package/dist/lib/inline-rules.d.ts +1 -1
  166. package/dist/lib/inline-rules.js +1 -1
  167. package/dist/lib/invite-prompt.d.ts +1 -1
  168. package/dist/lib/invite-prompt.js +1 -1
  169. package/dist/lib/launch/mcp-child-env.js +1 -1
  170. package/dist/lib/launch/mcp-child-env.js.map +1 -1
  171. package/dist/lib/launch/strict-entry.js +2 -2
  172. package/dist/lib/launch/strict-entry.js.map +1 -1
  173. package/dist/lib/lexical-store.d.ts +1 -1
  174. package/dist/lib/lexical-store.js +1 -1
  175. package/dist/lib/lexical-terms.d.ts +1 -1
  176. package/dist/lib/lexical-terms.js +1 -1
  177. package/dist/lib/lexical-text.d.ts +2 -2
  178. package/dist/lib/lexical-text.js +2 -2
  179. package/dist/lib/llm-providers.d.ts +1 -1
  180. package/dist/lib/llm-providers.js +2 -2
  181. package/dist/lib/llm-providers.js.map +1 -1
  182. package/dist/lib/llm-trace.d.ts +1 -1
  183. package/dist/lib/llm-trace.js +1 -1
  184. package/dist/lib/llm-trace.js.map +1 -1
  185. package/dist/lib/local-candidate-classify.d.ts +1 -1
  186. package/dist/lib/local-candidate-classify.js +3 -3
  187. package/dist/lib/local-candidate-classify.js.map +1 -1
  188. package/dist/lib/local-db-migrate.d.ts +6 -6
  189. package/dist/lib/local-db-migrate.js +9 -9
  190. package/dist/lib/local-db-migrate.js.map +1 -1
  191. package/dist/lib/local-db-v11.d.ts +1 -1
  192. package/dist/lib/local-db.d.ts +33 -33
  193. package/dist/lib/local-db.d.ts.map +1 -1
  194. package/dist/lib/local-db.js +27 -27
  195. package/dist/lib/local-db.js.map +1 -1
  196. package/dist/lib/local-embeddings-native.js +1 -1
  197. package/dist/lib/local-embeddings.d.ts +2 -2
  198. package/dist/lib/local-embeddings.js +2 -2
  199. package/dist/lib/local-embeddings.js.map +1 -1
  200. package/dist/lib/local-gateway-client.d.ts +5 -5
  201. package/dist/lib/local-gateway-client.js +40 -40
  202. package/dist/lib/local-gateway-client.js.map +1 -1
  203. package/dist/lib/local-ingest.d.ts +3 -3
  204. package/dist/lib/local-ingest.js +3 -3
  205. package/dist/lib/local-link-pass.js +1 -1
  206. package/dist/lib/local-link-pass.js.map +1 -1
  207. package/dist/lib/local-llm.d.ts +21 -22
  208. package/dist/lib/local-llm.d.ts.map +1 -1
  209. package/dist/lib/local-llm.js +28 -30
  210. package/dist/lib/local-llm.js.map +1 -1
  211. package/dist/lib/local-mode.d.ts +1 -1
  212. package/dist/lib/local-mode.js +2 -2
  213. package/dist/lib/local-mode.js.map +1 -1
  214. package/dist/lib/local-provenance.d.ts +1 -1
  215. package/dist/lib/local-provenance.js +1 -1
  216. package/dist/lib/local-relationship-classifier.d.ts +6 -6
  217. package/dist/lib/local-relationship-classifier.js +7 -7
  218. package/dist/lib/local-relationship-classifier.js.map +1 -1
  219. package/dist/lib/local-thresholds.d.ts +3 -3
  220. package/dist/lib/local-thresholds.js +4 -4
  221. package/dist/lib/local-thresholds.js.map +1 -1
  222. package/dist/lib/mcp/share-status-tool.js +1 -1
  223. package/dist/lib/mcp/share-tool.js +1 -1
  224. package/dist/lib/mcp-setup.d.ts +11 -4
  225. package/dist/lib/mcp-setup.d.ts.map +1 -1
  226. package/dist/lib/mcp-setup.js +22 -12
  227. package/dist/lib/mcp-setup.js.map +1 -1
  228. package/dist/lib/mcp-timeline-tools.d.ts +5 -5
  229. package/dist/lib/mcp-timeline-tools.d.ts.map +1 -1
  230. package/dist/lib/mcp-timeline-tools.js +22 -23
  231. package/dist/lib/mcp-timeline-tools.js.map +1 -1
  232. package/dist/lib/next-step.d.ts +11 -5
  233. package/dist/lib/next-step.d.ts.map +1 -1
  234. package/dist/lib/next-step.js +12 -4
  235. package/dist/lib/next-step.js.map +1 -1
  236. package/dist/lib/personal-import.d.ts +3 -3
  237. package/dist/lib/personal-import.d.ts.map +1 -1
  238. package/dist/lib/personal-import.js +4 -4
  239. package/dist/lib/personal-import.js.map +1 -1
  240. package/dist/lib/personal-oauth.js +3 -3
  241. package/dist/lib/personal-oauth.js.map +1 -1
  242. package/dist/lib/query-expand.d.ts +1 -1
  243. package/dist/lib/query-expand.js +1 -1
  244. package/dist/lib/read-value-rollup.d.ts +2 -2
  245. package/dist/lib/read-value-rollup.js +3 -3
  246. package/dist/lib/read-value-rollup.js.map +1 -1
  247. package/dist/lib/reason-extract.d.ts +1 -1
  248. package/dist/lib/reason-extract.js +1 -1
  249. package/dist/lib/repo-identity.d.ts +2 -2
  250. package/dist/lib/repo-identity.js +3 -3
  251. package/dist/lib/resolve-env.d.ts +1 -1
  252. package/dist/lib/resolve-env.js +1 -1
  253. package/dist/lib/scope-defaults.js +1 -1
  254. package/dist/lib/scope-real.d.ts.map +1 -1
  255. package/dist/lib/scope-real.js +1 -1
  256. package/dist/lib/scope-real.js.map +1 -1
  257. package/dist/lib/search-query.d.ts +2 -2
  258. package/dist/lib/search-query.js +2 -2
  259. package/dist/lib/search-rerank.d.ts +1 -1
  260. package/dist/lib/search-rerank.js +1 -1
  261. package/dist/lib/sessions/confirm-freetext.d.ts +3 -3
  262. package/dist/lib/sessions/confirm-freetext.d.ts.map +1 -1
  263. package/dist/lib/sessions/confirm-freetext.js +2 -2
  264. package/dist/lib/sessions/extract-freetext.d.ts +3 -3
  265. package/dist/lib/sessions/extract-freetext.d.ts.map +1 -1
  266. package/dist/lib/sessions/import-summary.d.ts +1 -1
  267. package/dist/lib/sessions/import-summary.js +1 -1
  268. package/dist/lib/sessions/memory-files.d.ts +1 -1
  269. package/dist/lib/sessions/memory-files.js +5 -5
  270. package/dist/lib/sessions/source-url.d.ts +2 -2
  271. package/dist/lib/sessions/source-url.js +2 -2
  272. package/dist/lib/sessions/types.d.ts +6 -6
  273. package/dist/lib/sessions/types.js +4 -4
  274. package/dist/lib/setup-funnel.d.ts +2 -2
  275. package/dist/lib/setup-ux.d.ts +2 -2
  276. package/dist/lib/setup-ux.js +3 -3
  277. package/dist/lib/setup-ux.js.map +1 -1
  278. package/dist/lib/share/approval.js +1 -1
  279. package/dist/lib/share/browser-flow.d.ts +1 -1
  280. package/dist/lib/share/browser-flow.js +1 -1
  281. package/dist/lib/share/command.js +1 -1
  282. package/dist/lib/share/envelope.js +1 -1
  283. package/dist/lib/share/pending.d.ts +1 -1
  284. package/dist/lib/share/requests-client.d.ts +1 -1
  285. package/dist/lib/share/run.d.ts +1 -1
  286. package/dist/lib/sparse-graph.d.ts +20 -0
  287. package/dist/lib/sparse-graph.d.ts.map +1 -0
  288. package/dist/lib/sparse-graph.js +39 -0
  289. package/dist/lib/sparse-graph.js.map +1 -0
  290. package/dist/lib/suppress-sqlite-warning.d.ts +1 -1
  291. package/dist/lib/sync/real-env.d.ts.map +1 -1
  292. package/dist/lib/telemetry-env.d.ts +3 -3
  293. package/dist/lib/telemetry-env.js +1 -1
  294. package/dist/lib/token-estimate.d.ts +1 -1
  295. package/dist/lib/token-estimate.js +3 -3
  296. package/dist/lib/token-estimate.js.map +1 -1
  297. package/dist/lib/token-scope-gate.d.ts +1 -1
  298. package/dist/lib/token-scope-gate.js +1 -1
  299. package/dist/lib/trusted-align-bin.d.ts +2 -2
  300. package/dist/lib/trusted-align-bin.d.ts.map +1 -1
  301. package/dist/lib/trusted-align-bin.js +2 -2
  302. package/dist/lib/trusted-align-bin.js.map +1 -1
  303. package/dist/lib/usage-telemetry.d.ts +17 -17
  304. package/dist/lib/usage-telemetry.js +24 -24
  305. package/dist/lib/usage-telemetry.js.map +1 -1
  306. package/dist/lib/user-hooks.d.ts +1 -1
  307. package/dist/lib/user-hooks.d.ts.map +1 -1
  308. package/dist/lib/user-hooks.js +3 -3
  309. package/dist/lib/user-hooks.js.map +1 -1
  310. package/dist/lib/value-rollup.d.ts +7 -7
  311. package/dist/lib/value-rollup.d.ts.map +1 -1
  312. package/dist/lib/value-rollup.js +12 -12
  313. package/dist/lib/value-rollup.js.map +1 -1
  314. package/dist/lib/write-confirm.d.ts +53 -0
  315. package/dist/lib/write-confirm.d.ts.map +1 -0
  316. package/dist/lib/write-confirm.js +70 -0
  317. package/dist/lib/write-confirm.js.map +1 -0
  318. package/package.json +7 -7
@@ -8,10 +8,13 @@ import { clearScreenForPicker, CLI_TOKEN_SOURCES, cliTokenDecision, detectVerifi
8
8
  import { createConfigStore } from '../lib/config.js';
9
9
  import { createGatewayClient } from '../lib/gateway-client.js';
10
10
  import { runPersonalImport, runWithConcurrency } from '../lib/personal-import.js';
11
+ import { detectEditors } from '../lib/mcp-setup.js';
12
+ import { confirmWrites, planSetupWrites } from '../lib/write-confirm.js';
11
13
  import { connectDetectedAgents } from './connect-agents.js';
12
14
  import { setupAgentAlignment } from '../lib/agent-rules.js';
13
15
  import { isGitRepo } from '../lib/git.js';
14
16
  import { currentRepoIdentity } from '../lib/repo-identity.js';
17
+ import { sparseGraphHint } from '../lib/sparse-graph.js';
15
18
  import { fetchDocsItems } from '../lib/fetchers/docs.js';
16
19
  import { createLocalDb } from '../lib/local-db.js';
17
20
  import { buildFoundSummary, renderFoundSummary } from '../lib/found-summary.js';
@@ -34,7 +37,7 @@ import { printBanner } from '../lib/brand.js';
34
37
  import { guardedPrompt } from '../lib/prompt-guard.js';
35
38
  import { setupSummaryLine, unresolvedGaps } from '../lib/connect-prompt.js';
36
39
  import { createSetupFunnel } from '../lib/setup-funnel.js';
37
- import { agentAskLine, agentConnectedLine, firstQuestion, orderAgents, pickAskableTitle, projectAgentsFromWritten } from '../lib/next-step.js';
40
+ import { agentAskLine, agentConnectedLine, agentNotConnectedLine, firstQuestion, orderAgents, pickAskableTitle, projectAgentsFromWritten } from '../lib/next-step.js';
38
41
  import { claudeCodeExists } from '../lib/claude-presence.js';
39
42
  import { projectForeignNotice } from '../lib/foreign-env.js';
40
43
  import { PICK_CANCELLED, pickAgent } from '../lib/launch/pick-agent.js';
@@ -87,7 +90,7 @@ export function buildSources(gitAvailable) {
87
90
  fetch: async (t, w = parseSince(undefined), o = {}) => {
88
91
  const { fetchGitHubItems, resolveGitHubRepoScope } = await import('../lib/fetchers/github.js');
89
92
  // L4: when the caller has resolved the scope (`resolved`), its `repo` is the whole answer and the folder is not consulted.
90
- // Without it (cloud setup) this is ALI-917's auto-detect: the repo narrows the search, and team scope only when asked.
93
+ // Without it (cloud setup) this is the auto-detect: the repo narrows the search, and team scope only when asked.
91
94
  const repo = o.resolved ? o.repo : await resolveGitHubRepoScope({});
92
95
  return fetchGitHubItems({ token: t['token'], ...fetchWindow('github', w), ...(repo ? { repo, ...(o.team || o.resolved ? { scope: 'team' } : {}) } : {}) });
93
96
  },
@@ -97,7 +100,7 @@ export function buildSources(gitAvailable) {
97
100
  description: 'Your issues',
98
101
  tier: 'site',
99
102
  // Personal/CLI tier is read-only (no write:jira-work). The team/org
100
- // comment bot keeps write via the `jira` key. See ALI-94.
103
+ // comment bot keeps write via the `jira` key.
101
104
  oauthKey: 'jira-personal',
102
105
  // Local-mode token paste (read-only Atlassian API token + email + site).
103
106
  tokenLabel: 'API token',
@@ -116,7 +119,7 @@ export function buildSources(gitAvailable) {
116
119
  ...CAPTURE_SOURCES.confluence,
117
120
  description: 'Your pages and documentation',
118
121
  tier: 'site',
119
- // Read-only personal/CLI tier. See ALI-94.
122
+ // Read-only personal/CLI tier.
120
123
  oauthKey: 'confluence-personal',
121
124
  // Local-mode token paste (read-only Atlassian API token + email + site).
122
125
  tokenLabel: 'API token',
@@ -136,7 +139,7 @@ export function buildSources(gitAvailable) {
136
139
  description: 'Decision threads from your channels - may need workspace admin [experimental]',
137
140
  tier: 'workspace',
138
141
  // Read-only personal/CLI tier (no chat:write). The team/org bot keeps
139
- // chat:write via the `slack` key. See ALI-94.
142
+ // chat:write via the `slack` key.
140
143
  oauthKey: 'slack-personal',
141
144
  // Local-mode token paste: a Slack user token (xoxp-) with read scopes only.
142
145
  tokenLabel: 'User token (xoxp-...)',
@@ -181,14 +184,14 @@ export function buildSources(gitAvailable) {
181
184
  ...CAPTURE_SOURCES.gitlab,
182
185
  description: 'Your merge requests',
183
186
  tier: 'personal',
184
- // gitlab.com → read-only browser OAuth (scope read_api, ALI-102). A
187
+ // gitlab.com → read-only browser OAuth (scope read_api). A
185
188
  // self-managed host (custom domain) can't use the fixed gitlab.com OAuth
186
189
  // app, so it falls back to the read-only PAT path below.
187
190
  oauthKey: 'gitlab-personal',
188
191
  hostGatedOAuth: { field: 'domain' },
189
192
  tokenLabel: 'Personal access token',
190
193
  // Read-only tier: steer users to the read-only scope. `api` would grant
191
- // write; `read_api` is read-only and all Align's import needs. See ALI-98.
194
+ // write; `read_api` is read-only and all Align's import needs.
192
195
  // read_api arrives pre-selected via GitLab's documented ?name=&scopes= pre-fill,
193
196
  // which works on self-managed hosts too - the docs example is gitlab.example.com.
194
197
  tokenHint: 'read_api is pre-selected (read-only). Click Create, then copy the token here',
@@ -209,7 +212,7 @@ export function buildSources(gitAvailable) {
209
212
  description: 'Your issues and project discussions',
210
213
  tier: 'personal',
211
214
  // Read-only personal/CLI tier via browser OAuth (scope `read`), replacing the
212
- // full-access API-key paste. Requires the Linear OAuth app + sealed creds. See ALI-101.
215
+ // full-access API-key paste. Requires the Linear OAuth app + sealed creds.
213
216
  oauthKey: 'linear-personal',
214
217
  // Local-mode token paste: a Linear personal API key (read-only graph).
215
218
  tokenLabel: 'Personal API key (lin_api_...)',
@@ -231,12 +234,12 @@ export function buildSources(gitAvailable) {
231
234
  // Read-only personal/CLI tier via browser OAuth (public integration),
232
235
  // replacing the internal-integration-secret paste in cloud. Read-only is
233
236
  // governed by the integration's capabilities (Read content), not scopes.
234
- // Requires the Notion OAuth app + sealed creds. See ALI-104.
237
+ // Requires the Notion OAuth app + sealed creds.
235
238
  oauthKey: 'notion-personal',
236
239
  // Local-mode token paste: a read-only internal integration secret.
237
240
  tokenLabel: 'Integration secret (ntn_...)',
238
241
  // Read-only tier: Align only reads. Notion integration capabilities are set
239
- // at creation - keep it to "Read content" (no insert/update). See ALI-98.
242
+ // at creation - keep it to "Read content" (no insert/update).
240
243
  tokenHint: 'Create an integration with ONLY "Read content" capability (no insert/update), then copy its Internal Integration Secret',
241
244
  // The developer console's tokens page directly, not the legacy my-integrations
242
245
  // landing (both resolve; this one is where the secret actually lives - Tom,
@@ -272,7 +275,7 @@ async function collectTokens(source, seed = {}, opts = {}) {
272
275
  }
273
276
  // Main token. A seeded token skips the whole block - no browser open, no paste.
274
277
  if (source.tokenLabel && tokens['token'] === undefined) {
275
- // No OAuth here, by design (Tom, 2026-08-31, superseding ALI-778's local
278
+ // No OAuth here, by design (Tom, 2026-08-31, superseding the local
276
279
  // direction): local mode is the user's personal graph, so the credential is one
277
280
  // they mint, scope and revoke themselves. The device-flow/PKCE machinery this
278
281
  // block used to try first is deleted - recover it from the history of PR #196 if
@@ -285,7 +288,7 @@ async function collectTokens(source, seed = {}, opts = {}) {
285
288
  let refusal;
286
289
  if (cliSource) {
287
290
  // Detection verifies read-only-ness BEFORE the decision layer, so --approve can
288
- // never auto-accept a token that was not positively confirmed (ALI-98). A
291
+ // never auto-accept a token that was not positively confirmed. A
289
292
  // refusal is said out loud: a silent skip here reads as "gh not installed" when
290
293
  // the truth is "gh is installed and its token can write".
291
294
  const detected = await detectVerifiedCliToken(cliSource);
@@ -339,14 +342,14 @@ async function collectTokens(source, seed = {}, opts = {}) {
339
342
  return tokens;
340
343
  }
341
344
  // ---------------------------------------------------------------------------
342
- // Deterministic auto-alignment (ALI-121)
345
+ // Deterministic auto-alignment
343
346
  // ---------------------------------------------------------------------------
344
347
  // Write the project-local, committed agent-rules files (Claude Code PostToolUse hook +
345
348
  // CLAUDE.md nudge + Cursor rule) so alignment context fires regardless of model
346
349
  // discretion. Best-effort: a write failure (read-only dir, weird CWD) must never abort
347
350
  // onboarding, so we warn and continue.
348
351
  /**
349
- * The retry command printed when a connector import fails mid-setup (ALI-675).
352
+ * The retry command printed when a connector import fails mid-setup.
350
353
  *
351
354
  * It must be runnable AS PRINTED by the user this session belongs to. The bare
352
355
  * form resolved to the cloud default, so a --local user pasting our own hint
@@ -356,10 +359,33 @@ async function collectTokens(source, seed = {}, opts = {}) {
356
359
  export function importRetryHint(sourceId, envName) {
357
360
  return envName === 'prod' ? `align connect ${sourceId}` : `align connect ${sourceId} --env ${envName}`;
358
361
  }
362
+ /**
363
+ * Before setup writes anything: show the exact files it will create or modify, then ask
364
+ * (default yes). Covers the project's agent files and each detected agent's own config, the
365
+ * two writers below. Non-TTY or --approve prints the list and goes ahead. False means the
366
+ * person declined: the caller writes nothing.
367
+ */
368
+ async function confirmAgentWrites(o) {
369
+ // Detected once: the same agents are listed here and written by connectDetectedAgents.
370
+ const editors = detectEditors();
371
+ const files = planSetupWrites({ cwd: process.cwd(), editors });
372
+ console.log('');
373
+ const confirmed = await confirmWrites({
374
+ files,
375
+ approve: o.approve,
376
+ interactive: o.interactive,
377
+ print: (text) => p.log.info(text),
378
+ ask: async (message) => {
379
+ const answer = await p.confirm({ message, initialValue: true });
380
+ return p.isCancel(answer) ? undefined : answer;
381
+ },
382
+ });
383
+ return { confirmed, editors };
384
+ }
359
385
  /**
360
386
  * Returns the repo-relative files written (empty when the write failed), so the outro can
361
- * tell whether the project .mcp.json - the one that wires Claude Code - actually landed
362
- * (ALI-950). Naming an agent as connected off a write that threw would be false.
387
+ * tell whether the project .mcp.json - the one that wires Claude Code - actually landed.
388
+ * Naming an agent as connected off a write that threw would be false.
363
389
  */
364
390
  function writeAgentAlignment(envName) {
365
391
  try {
@@ -382,7 +408,7 @@ function writeAgentAlignment(envName) {
382
408
  // Saved AI provider keys
383
409
  // ---------------------------------------------------------------------------
384
410
  /**
385
- * The wizard asks for no AI-provider key. It used to end on ALI-1284's guided Groq + Gemini
411
+ * The wizard asks for no AI-provider key. It used to end on the guided Groq + Gemini
386
412
  * offer, immediately before bare `align` opened the coding agent - and read in that position
387
413
  * the keys looked like something the agent needed (a founder's own 0.48.0 run). They only
388
414
  * serve align's own terminal `align ask` prose; inside a wrapped agent the agent writes the
@@ -432,13 +458,13 @@ function nextStepLine(agent, launching, agents) {
432
458
  const label = agent ? agentByName(agent)?.label : undefined;
433
459
  if (label)
434
460
  return launching ? null : `Run ${chalk.bold('align')} to open ${label} with your graph.`;
435
- // ALI-1779: said once, and only when it is true. With a configured agent the ask line below names it.
461
+ // Said once, and only when it is true. With a configured agent the ask line below names it.
436
462
  if (agents.length > 0)
437
463
  return null;
438
464
  return `No coding agent was found, so none is connected. Install one from the list above, then run ${chalk.bold('align')}.`;
439
465
  }
440
466
  /**
441
- * ALI-1779: the project `.mcp.json` is written whether or not anyone can read it, so it only
467
+ * The project `.mcp.json` is written whether or not anyone can read it, so it only
442
468
  * counts as a connected agent when Claude Code (the agent that reads it) exists on this machine.
443
469
  */
444
470
  function connectedProjectAgents(written) {
@@ -469,11 +495,11 @@ async function runLocalValuePhase(opts) {
469
495
  const localEnv = config.getEnvironment('local');
470
496
  const localClient = createGatewayClient(localEnv);
471
497
  const capture = createCaptureCollector();
472
- // ALI-949: local setup has begun. Sends now on a re-run (consent already on disk); on a
498
+ // Local setup has begun. Sends now on a re-run (consent already on disk); on a
473
499
  // first run it cannot, and the post-consent offer below is the one that lands. Fire-and-
474
500
  // forget: a slow gateway never delays the value moment.
475
501
  void opts.funnel.started(localEnv);
476
- // ALI-794: value before questions. Git needs no credential and no consent, so it runs
502
+ // Value before questions. Git needs no credential and no consent, so it runs
477
503
  // before anything is asked - the found-decisions summary below is what "here is what I
478
504
  // found" means, and it has to exist before the agent-wiring/consent/connector steps that
479
505
  // used to come first for no reason other than that is the order they were written in.
@@ -511,7 +537,7 @@ async function runLocalValuePhase(opts) {
511
537
  if (inGitRepo && !repoKnown) {
512
538
  console.log('');
513
539
  p.log.info(chalk.dim('First import downloads a local embedding model (~23MB, huggingface.co) and, on npm installs, its runtime (~20MB, github.com), one time.'));
514
- // ALI-1749/ALI-1782: the reason behind a change usually lives in its PR, so a GitHub origin's merged PR
540
+ // The reason behind a change usually lives in its PR, so a GitHub origin's merged PR
515
541
  // descriptions are read WHILE git is scanned. Public, bounded, never a prompt, never fails first run.
516
542
  const prRead = fetchFirstRunPrs(repo);
517
543
  const gitSpinner = p.spinner();
@@ -561,7 +587,7 @@ async function runLocalValuePhase(opts) {
561
587
  if (prs.note)
562
588
  p.log.info(chalk.dim(prs.note));
563
589
  if (items.length || prs.items.length) {
564
- // The payoff (ALI-215/ALI-794): name real decisions instead of a bare count, so a
590
+ // The payoff: name real decisions instead of a bare count, so a
565
591
  // first-run user can check the summary against their own repo. Read straight back
566
592
  // from the db rather than trusting the import's own tally - see buildFoundSummary's
567
593
  // comment on why total/linked come from a COUNT, not from `items.length`.
@@ -591,14 +617,14 @@ async function runLocalValuePhase(opts) {
591
617
  }
592
618
  }
593
619
  // Deterministic auto-alignment files target the local graph (advisory check runs --env local).
594
- // Deferred until AFTER the value moment above (ALI-794 component 3): writing project files
620
+ // Deferred until AFTER the value moment above: writing project files
595
621
  // and asking about telemetry before the user has seen anything real is the footprint-before-
596
622
  // value ordering this ticket exists to invert.
597
- // ALI-793: ADRs + the user's own CLAUDE.md/AGENTS.md content, same zero-auth tier as
623
+ // ADRs + the user's own CLAUDE.md/AGENTS.md content, same zero-auth tier as
598
624
  // git above and independent of it - a repo can carry decision-shaped docs with no git
599
625
  // history worth mining, or vice versa. fetchDocsItems degrades gracefully with no git
600
626
  // remote (falls back to a stable git:// identifier), so this runs unconditionally, and
601
- // it belongs in the value phase for the same reason git does (ALI-794): it is
627
+ // it belongs in the value phase for the same reason git does: it is
602
628
  // zero-credential value, so it has to show before the connector/consent questions,
603
629
  // not after them.
604
630
  console.log('');
@@ -624,7 +650,7 @@ async function runLocalValuePhase(opts) {
624
650
  local: true,
625
651
  funnel: { env: localEnv, source: 'docs' },
626
652
  });
627
- // ALI-950: a repo can carry decision-shaped docs and no git history worth mining, and
653
+ // A repo can carry decision-shaped docs and no git history worth mining, and
628
654
  // the outro's question must name a decision the wizard FOUND, whichever source found
629
655
  // it. Read back the same way the git summary does, only when git left nothing to name.
630
656
  if (!firstFoundTitle) {
@@ -646,13 +672,18 @@ async function runLocalValuePhase(opts) {
646
672
  if (!(e instanceof SkipDocs))
647
673
  localDocsSpinner.stop(`Docs import skipped - ${e.message}`);
648
674
  }
649
- const projectAgents = connectedProjectAgents(projectAgentsFromWritten(writeAgentAlignment('local')));
675
+ const { confirmed: writesConfirmed, editors } = await confirmAgentWrites({ interactive: Boolean(interactive), approve: opts.approve });
676
+ if (!writesConfirmed)
677
+ p.log.info('Left your agent config alone. Run align again when you want it connected.');
678
+ const projectAgents = writesConfirmed ? connectedProjectAgents(projectAgentsFromWritten(writeAgentAlignment('local'))) : [];
650
679
  // The agents installed on this machine, not just the ones this project configures. Local
651
680
  // setup skipped this and cloud did not, which is backwards: local mode is the one whose
652
681
  // entire pitch is an agent on your own machine reading a graph that never leaves it.
653
682
  console.log('');
654
- const localAgents = await connectDetectedAgents('local', { verbose: opts.verbose });
655
- // ALI-950: whenever ANY agent was wired - the project .mcp.json counts. This used to print
683
+ const localAgents = writesConfirmed
684
+ ? await connectDetectedAgents('local', { verbose: opts.verbose, editors })
685
+ : { detected: 0, connected: 0, wired: [] };
686
+ // Whenever ANY agent was wired - the project .mcp.json counts. This used to print
656
687
  // only when a GLOBAL config was written, so a Claude-Code-only user, wired through the
657
688
  // project file a few lines up, never saw it. The question itself moves to the outro's
658
689
  // last line, where it is the one thing left to do.
@@ -662,11 +693,11 @@ async function runLocalValuePhase(opts) {
662
693
  console.log('');
663
694
  p.log.info(chalk.dim(connectedLine));
664
695
  }
665
- // ALI-949: the first checkpoint at which the local env is initialised, so a local-mode
696
+ // The first checkpoint at which the local env is initialised, so a local-mode
666
697
  // setup_started is offered against it here. (C6: there is no consent question any more - the
667
698
  // one-time notice in cli.ts's preAction is the disclosure local sends wait on.)
668
699
  void opts.funnel.started(localEnv);
669
- return { interactive, config, localEnv, localClient, dbPath, opts, capture, funnel: opts.funnel, agents, firstFoundTitle, agent };
700
+ return { interactive, config, localEnv, localClient, dbPath, opts, capture, funnel: opts.funnel, agents, firstFoundTitle, agent, writesDeclined: !writesConfirmed };
670
701
  }
671
702
  /** The ids `align connect --source` accepts: every local paste-token source, in picker order. */
672
703
  export function localConnectorIds() {
@@ -678,7 +709,7 @@ export function localConnectorIds() {
678
709
  /**
679
710
  * The picker-and-import tail of local setup: ask which read-only-token sources to connect,
680
711
  * collect every credential up front, then fetch and import each one. Extracted from
681
- * runLocalConnectorPhase for `align connect` (ALI-951), which is this same flow without the
712
+ * runLocalConnectorPhase for `align connect`, which is this same flow without the
682
713
  * wizard around it - one picker, one paste path, one save rule, in one place. The body is
683
714
  * the wizard's; `preselected`, `seedTokens` and `json` are the only additions, and each is
684
715
  * inert when absent.
@@ -695,7 +726,7 @@ export async function connectLocalSources(o) {
695
726
  // paragraph has now said three different things; the design statement printed to
696
727
  // the user below is the durable version.) Only sources with a tokenLabel are
697
728
  // pasteable: Teams pastes a Graph access token, Zoom has no personal token and is
698
- // excluded. See ALI-103.
729
+ // excluded.
699
730
  //
700
731
  // Asked BEFORE the git scan so every question lands on a clean screen and the rest of
701
732
  // setup then runs without stopping. The picker used to sit under a screenful of import
@@ -716,9 +747,9 @@ export async function connectLocalSources(o) {
716
747
  // the user was sent to a provider page to mint a token with no explanation, which reads
717
748
  // as the tool being clumsy rather than as the privacy trade they chose. The constraint
718
749
  // is the provider's, not ours - OAuth needs a client secret, and a secret inside a
719
- // distributed binary is not a secret. See ALI-778.
750
+ // distributed binary is not a secret.
720
751
  if (interactive && localConnectors.length > 0 && !quiet) {
721
- // ONE story, stated as the design it is (Tom, 2026-08-31, superseding ALI-778's
752
+ // ONE story, stated as the design it is (Tom, 2026-08-31, superseding the earlier
722
753
  // local-OAuth direction): this is the user's PERSONAL graph, so the credential is
723
754
  // one they mint, scope and can revoke themselves. Earlier versions blamed the
724
755
  // provider ("their sign-in requires a secret"), then blamed us ("the Align app is
@@ -728,7 +759,7 @@ export async function connectLocalSources(o) {
728
759
  ` saved on this machine, readable only by you, and only ever used to read.\n` +
729
760
  ` Remove them any time with \`align local forget\`.`));
730
761
  }
731
- // ALI-802: what a previous run already collected. Until this existed, setup asked for every
762
+ // What a previous run already collected. Until this existed, setup asked for every
732
763
  // token on every run - it gathered credentials, spent them on one fetch and never called the
733
764
  // store that was sitting there. Read before the picker so the picker can say what is saved.
734
765
  const savedTokens = new Map();
@@ -761,7 +792,7 @@ export async function connectLocalSources(o) {
761
792
  isCancel: (v) => p.isCancel(v),
762
793
  });
763
794
  // `interactive` computed once, by the caller - see runLocalValuePhase.
764
- // ALI-794: the found-summary above sits between this picker and the last clean screen,
795
+ // The found-summary above sits between this picker and the last clean screen,
765
796
  // which is the exact condition that used to corrupt clack's in-place redraw for an
766
797
  // outside tester (2026-08-30). Clear first so the picker gets its own canvas.
767
798
  const askPicker = interactive && !o.preselected;
@@ -809,7 +840,7 @@ export async function connectLocalSources(o) {
809
840
  if (isAtlassian && seed['token'] !== undefined && !quiet) {
810
841
  p.log.info(chalk.dim(' Using your Atlassian email, domain and API token from the previous connector.'));
811
842
  }
812
- // ALI-951: `--token` (and any other field a flag supplied) seeds every preselected
843
+ // `--token` (and any other field a flag supplied) seeds every preselected
813
844
  // source the way the Atlassian reuse above does - a seeded field is never asked for.
814
845
  if (o.seedTokens)
815
846
  seed = { ...seed, ...o.seedTokens };
@@ -828,7 +859,7 @@ export async function connectLocalSources(o) {
828
859
  seed = { ...seed, ...saved };
829
860
  reused = true;
830
861
  }
831
- // ALI-951: with no terminal there is nothing to paste into. Name the flag rather than
862
+ // With no terminal there is nothing to paste into. Name the flag rather than
832
863
  // letting clack crash on a closed stdin, which reads as the tool breaking (align-cli#118).
833
864
  if (!interactive) {
834
865
  const missing = (source.extraFields ?? []).map((f) => f.key).filter((k) => seed[k] === undefined);
@@ -853,7 +884,7 @@ export async function connectLocalSources(o) {
853
884
  }
854
885
  }
855
886
  // Git and repo docs already ran above, in runLocalValuePhase, before the mode/connector
856
- // questions (ALI-794) - both are zero-credential value and belong in the "here is what I
887
+ // questions - both are zero-credential value and belong in the "here is what I
857
888
  // found" moment, not stranded behind the questions this phase exists to ask. This phase
858
889
  // is only the automatic import for the paste-token connectors just collected.
859
890
  for (const { source, tokens, reused } of localReady) {
@@ -911,7 +942,7 @@ export async function connectLocalSources(o) {
911
942
  async function runLocalConnectorPhase(ctx) {
912
943
  const { interactive, config, localEnv, localClient, dbPath, opts, capture, funnel, agents, firstFoundTitle, agent } = ctx;
913
944
  await connectLocalSources({ interactive, config, localEnv, localClient, capture, approve: opts.approve ?? false });
914
- // ALI-827: what each source fetched and what it could not reach - once, after every
945
+ // What each source fetched and what it could not reach - once, after every
915
946
  // import has printed its own line, so the numbers sit together. A source whose fetch
916
947
  // THREW is not in it: its spinner line above already named the error, and a row of
917
948
  // zeros beside it would read as "fetched nothing" rather than "could not fetch". Empty
@@ -925,8 +956,8 @@ async function runLocalConnectorPhase(ctx) {
925
956
  // most local-favouring read preference is the truth: a no-account user is
926
957
  // redirected to local and there is nothing to say; a cloud token (or an
927
958
  // exported ALIGN_ENV) keeps bare commands on the cloud graph the user did
928
- // NOT just build. The default deliberately does not move - ALI-87 keeps
929
- // personal cloud the default and `align env set` its only writer; this
959
+ // NOT just build. The default deliberately does not move - personal cloud stays
960
+ // the default and `align env set` its only writer; this
930
961
  // warning exists because Session A (2026-08-25) showed the silence reads
931
962
  // as the product ignoring an explicit choice.
932
963
  const bareEnv = resolveEnv(undefined, { preferLocalEmbedded: true });
@@ -945,7 +976,7 @@ async function runLocalConnectorPhase(ctx) {
945
976
  p.log.warn(`Bare commands (align ask, align connect ...) use the ${bareEnv} cloud graph, not this local one, because ${cause}.\n` +
946
977
  `Add ${chalk.bold('--env local')} per command, or ${remedy}.`);
947
978
  }
948
- // ALI-796: the graph names its own gaps - a ref whose platform has no connected
979
+ // The graph names its own gaps - a ref whose platform has no connected
949
980
  // source. Read directly off decision_refs (the same query status.ts/local.ts use),
950
981
  // never gate-y: at most one line, and only when there is a real gap to name.
951
982
  const refsDb = createLocalDb(dbPath);
@@ -953,7 +984,18 @@ async function runLocalConnectorPhase(ctx) {
953
984
  const gaps = unresolvedGaps(refsDb.getAllRefs(), isConnected);
954
985
  refsDb.close();
955
986
  const gapLine = setupSummaryLine(gaps);
956
- // ALI-950: the last line is the next step, and it happens in the agent - named, with a
987
+ // An empty or thin graph gets a specific next step instead of silence. Read from the db's
988
+ // own COUNT, after every import above has run, so it is the graph the user will actually query.
989
+ const totalDb = createLocalDb(dbPath);
990
+ let totalDecisions;
991
+ try {
992
+ totalDecisions = totalDb.getStats().decisions;
993
+ }
994
+ finally {
995
+ totalDb.close();
996
+ }
997
+ const sparseHint = sparseGraphHint(totalDecisions, { bareReadsLocal: bareEnv === 'local' });
998
+ // The last line is the next step, and it happens in the agent - named, with a
957
999
  // question about a decision the wizard just found, and no CLI verb. This used to end on
958
1000
  // `align ask "why <a thing you decided>"`, a prompt verb on the one screen whose job is to
959
1001
  // send the user into their agent. The gap line, when there is one, sits above it.
@@ -961,30 +1003,35 @@ async function runLocalConnectorPhase(ctx) {
961
1003
  // Claude Code itself. When it is about to, the last line says so instead.
962
1004
  // Said before the outro, once, only when true: with no model the checks the graph exists for cannot run.
963
1005
  await runModelStep({ interactive });
964
- const launching = willLaunchAgent(agent, Boolean(opts.launchNext));
1006
+ // Declined writes: the agent has no align MCP or hook, so opening it "with your graph" would be false.
1007
+ const launching = !ctx.writesDeclined && willLaunchAgent(agent, Boolean(opts.launchNext));
965
1008
  const launchLabel = launching && agent ? agentByName(agent)?.label : undefined;
966
- const nextStep = nextStepLine(agent, launching, agents);
967
- const askLine = launchLabel
968
- ? `Opening ${launchLabel}. Ask it: ${firstQuestion(firstFoundTitle)}`
969
- : agents.length === 0 && agent === null
970
- ? `Once one is installed, open it in this repo and ask: ${firstQuestion(firstFoundTitle)}`
971
- : agentAskLine({ agents, firstTitle: firstFoundTitle, inRepo: true, envName: 'local' });
1009
+ const nextStep = ctx.writesDeclined ? null : nextStepLine(agent, launching, agents);
1010
+ const askLine = ctx.writesDeclined
1011
+ ? agentNotConnectedLine('local')
1012
+ : launchLabel
1013
+ ? `Opening ${launchLabel}. Ask it: ${firstQuestion(firstFoundTitle)}`
1014
+ : agents.length === 0 && agent === null
1015
+ ? `Once one is installed, open it in this repo and ask: ${firstQuestion(firstFoundTitle)}`
1016
+ : agentAskLine({ agents, firstTitle: firstFoundTitle, inRepo: true, envName: 'local' });
972
1017
  p.outro(`${chalk.green('You are set up in local mode.')}\n` +
973
1018
  ` Graph: ${chalk.dim(dbPath)}\n` +
974
1019
  `${nextStep ? ` ${nextStep}\n` : ''}` +
975
- `${gapLine ? `\n ${chalk.dim(gapLine)}\n` : ''}\n` +
1020
+ `${gapLine ? `\n ${chalk.dim(gapLine)}\n` : ''}` +
1021
+ `${sparseHint ? `\n${sparseHint.map((l) => ` ${l}`).join('\n')}\n` : ''}\n` +
976
1022
  ` ${chalk.bold(askLine)}`);
977
- // ALI-949: after the outro, never before it - telemetry must not delay what the user is
1023
+ // After the outro, never before it - telemetry must not delay what the user is
978
1024
  // waiting for. Awaited (unlike the other emitters) because this is the wizard's last act
979
1025
  // and the one row the funnel was blind to; there is nothing left for a 2s worst case to hold up.
980
1026
  await funnel.completed(localEnv);
981
1027
  }
982
1028
  // Local-embedded onboarding (opt-in via --local): no account, no cloud, no OAuth. Composes
983
- // the two phases above unchanged - this is exactly what ran before the ALI-794 split, just
1029
+ // the two phases above unchanged - this is exactly what ran before the split, just
984
1030
  // as two calls instead of one function body.
985
1031
  async function runLocalSetup(opts) {
986
1032
  const ctx = await runLocalValuePhase(opts);
987
1033
  await runLocalConnectorPhase(ctx);
1034
+ return { writesDeclined: ctx.writesDeclined };
988
1035
  }
989
1036
  /**
990
1037
  * The setup flags, on the bare `align` command (there is no `align setup` any more). Hidden from --help: a
@@ -1000,7 +1047,7 @@ export function addSetupOptions(cmd) {
1000
1047
  }
1001
1048
  /**
1002
1049
  * The onboarding flow, extracted from the `setup` action so `align` with no arguments can run
1003
- * exactly the same thing (ALI-773). A new user's first instinct is to type the tool's name,
1050
+ * exactly the same thing. A new user's first instinct is to type the tool's name,
1004
1051
  * and that printed a twenty-command help wall.
1005
1052
  *
1006
1053
  * A pure move: the body below is the action's, unchanged apart from indentation. Both callers
@@ -1039,7 +1086,7 @@ export async function runSetup(opts = {}) {
1039
1086
  printBanner({ version });
1040
1087
  p.intro(commandIntro('align'));
1041
1088
  await runCloudSetup({ opts, config, env, client: createGatewayClient(env), envName: route.env, funnel: createSetupFunnel() });
1042
- return;
1089
+ return undefined; // the cloud path never launches an agent
1043
1090
  }
1044
1091
  // The one place a full brand moment belongs: first run, before any questions.
1045
1092
  printBanner({ version });
@@ -1047,11 +1094,11 @@ export async function runSetup(opts = {}) {
1047
1094
  if (route.teamLoginUntouched) {
1048
1095
  p.log.info(`Your ${route.teamLoginUntouched} team login is untouched. This sets up the local graph only.`);
1049
1096
  }
1050
- // ALI-949: the wizard's setup_started / setup_completed emitter. NOT offered an env
1097
+ // The wizard's setup_started / setup_completed emitter. NOT offered an env
1051
1098
  // here, where the mode is still unknown: each branch offers its own env at its top -
1052
1099
  // runLocalValuePhase and runCloudSetup - and again once consent / login makes a send
1053
1100
  // possible.
1054
- await runLocalSetup({ approve: opts.approve, reset: opts.reset, launchNext: opts.launchNext, verbose: opts.verbose, funnel: createSetupFunnel() });
1101
+ return await runLocalSetup({ approve: opts.approve, reset: opts.reset, launchNext: opts.launchNext, verbose: opts.verbose, funnel: createSetupFunnel() });
1055
1102
  }
1056
1103
  // Cloud (team) onboarding: verify login, wire MCP, seed from git, then offer
1057
1104
  // personal-scoped connectors. Reached through routeSetup's team route: an explicit
@@ -1067,7 +1114,7 @@ async function runCloudSetup(ctx) {
1067
1114
  try {
1068
1115
  const me = await client.whoami();
1069
1116
  authSpinner.stop(`Logged in as ${me.user.email} (${me.tenant?.name ?? envName})`);
1070
- // ALI-949: cloud setup has begun, and the token is proven good. Offered only AFTER
1117
+ // Cloud setup has begun, and the token is proven good. Offered only AFTER
1071
1118
  // whoami (Copilot on #279): a stale stored token passes the emitter's token check, so an
1072
1119
  // earlier offer would send a request the gateway rejects, count as sent, and suppress the
1073
1120
  // post-login offer below that would have landed. Reached from every route into cloud
@@ -1090,7 +1137,7 @@ async function runCloudSetup(ctx) {
1090
1137
  }
1091
1138
  // Re-create the client so it carries the freshly stored token.
1092
1139
  client = createGatewayClient(config.getEnvironment(envName));
1093
- // ALI-949: the first checkpoint at which a fresh cloud install's setup_started CAN
1140
+ // The first checkpoint at which a fresh cloud install's setup_started CAN
1094
1141
  // send - the token exists now. `env` above is the pre-login snapshot, so re-read.
1095
1142
  void funnel.started(config.getEnvironment(envName));
1096
1143
  }
@@ -1203,18 +1250,29 @@ async function runCloudSetup(ctx) {
1203
1250
  }
1204
1251
  // ---- Step 5: MCP editor config (before import - this is the payoff) ----
1205
1252
  console.log('');
1206
- // Shared with the local path (ALI-776), which used to skip this entirely - so a local-only
1253
+ // Shared with the local path, which used to skip this entirely - so a local-only
1207
1254
  // user got LESS agent wiring than a cloud one, on the mode where an agent running on your
1208
1255
  // own machine is the whole point.
1209
1256
  //
1210
- // It does NOT ask: it wires every detected agent and discloses each file it touched, with
1211
- // `align mcp --remove` as the undo. This block used to write to a user-level config without
1212
- // a word when exactly one editor was detected and prompt only at two or more, and that
1213
- // multiselect was unguarded, so `align setup --approve` with two agents installed hung.
1214
- const globalAgents = await connectDetectedAgents(envName, { verbose: ctx.opts.verbose });
1257
+ // It wires every detected agent and discloses each file it touched, with `align mcp --remove`
1258
+ // as the undo. Since the confirmation below it asks first, once, for the whole run: the list
1259
+ // of files, then Continue? (default yes). --approve or no terminal prints the list and goes
1260
+ // on. This block used to write without a word when exactly one editor was detected and
1261
+ // prompt only at two or more, and that multiselect was unguarded, so `align setup --approve`
1262
+ // with two agents installed hung.
1263
+ const { confirmed: writesConfirmed, editors } = await confirmAgentWrites({
1264
+ interactive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
1265
+ approve: opts.approve,
1266
+ });
1267
+ const writesDeclined = !writesConfirmed;
1268
+ if (!writesConfirmed)
1269
+ p.log.info('Left your agent config alone. Run align again when you want it connected.');
1270
+ const globalAgents = writesConfirmed
1271
+ ? await connectDetectedAgents(envName, { verbose: ctx.opts.verbose, editors })
1272
+ : { detected: 0, connected: 0, wired: [] };
1215
1273
  // ---- Step 5b: Deterministic auto-alignment files (hook + nudges) ----
1216
- const projectAgents = connectedProjectAgents(projectAgentsFromWritten(writeAgentAlignment(envName)));
1217
- // ALI-950: project config first - it is the one the user is most likely sitting in.
1274
+ const projectAgents = writesConfirmed ? connectedProjectAgents(projectAgentsFromWritten(writeAgentAlignment(envName))) : [];
1275
+ // Project config first - it is the one the user is most likely sitting in.
1218
1276
  const agents = orderAgents({ project: projectAgents, global: globalAgents.wired });
1219
1277
  // ---- Step 6: Git auto-import (zero-auth baseline graph seed) ----
1220
1278
  let totalDecisions = 0;
@@ -1369,7 +1427,7 @@ async function runCloudSetup(ctx) {
1369
1427
  appUrl: resolveAppUrl(env),
1370
1428
  quiet: true,
1371
1429
  funnel: { env, source: source.id },
1372
- // Async ingest (ALI-114): return at DB-write speed; titles + links
1430
+ // Async ingest: return at DB-write speed; titles + links
1373
1431
  // enrich in the background. Connection counts show as 0 here and fill in later.
1374
1432
  deferEnrichment: true,
1375
1433
  });
@@ -1394,7 +1452,7 @@ async function runCloudSetup(ctx) {
1394
1452
  console.log(chalk.dim('Query your graph: align ask "..." or align decisions list'));
1395
1453
  console.log('');
1396
1454
  }
1397
- // ALI-827: the capture report, once, after every source has imported.
1455
+ // The capture report, once, after every source has imported.
1398
1456
  const captureText = capture.render();
1399
1457
  if (captureText) {
1400
1458
  console.log(captureText);
@@ -1407,7 +1465,7 @@ async function runCloudSetup(ctx) {
1407
1465
  const sourceLine = sourcesImported.length > 0
1408
1466
  ? `\n Sources: ${sourcesImported.join(', ')}`
1409
1467
  : '';
1410
- // ---- Step 8: the agent is connected, and the next step is in it (ALI-950) ----
1468
+ // ---- Step 8: the agent is connected, and the next step is in it ----
1411
1469
  // Only when something was actually WIRED, not merely detected - and the project .mcp.json
1412
1470
  // counts, which it did not before: this printed only for a global config, so a
1413
1471
  // Claude-Code-only user never saw it.
@@ -1430,10 +1488,10 @@ async function runCloudSetup(ctx) {
1430
1488
  chalk.dim('\n Upgrade by accepting a team invite - your decisions come with you'),
1431
1489
  chalk.dim('\n (you reconnect your connectors once in the team workspace).'),
1432
1490
  chalk.dim('\n https://app.align.tech/pricing'),
1433
- `\n\n ${chalk.bold(agentAskLine({ agents, firstTitle: firstFoundTitle, inRepo: true, envName }))}`,
1491
+ `\n\n ${chalk.bold(writesDeclined ? agentNotConnectedLine(envName) : agentAskLine({ agents, firstTitle: firstFoundTitle, inRepo: true, envName }))}`,
1434
1492
  ].join('');
1435
1493
  p.outro(outroText);
1436
- // ALI-949: after the outro, and against the env as it is NOW (the inline login above may
1494
+ // After the outro, and against the env as it is NOW (the inline login above may
1437
1495
  // have stored the token after `env` was read). See runLocalConnectorPhase for why awaited.
1438
1496
  await funnel.completed(config.getEnvironment(envName));
1439
1497
  }