@aligndottech/cli 0.37.1 → 0.38.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 (179) hide show
  1. package/README.md +16 -10
  2. package/dist/cli.d.ts +28 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +98 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/check.d.ts +1 -1
  7. package/dist/commands/check.d.ts.map +1 -1
  8. package/dist/commands/check.js +44 -2
  9. package/dist/commands/check.js.map +1 -1
  10. package/dist/commands/connect-agents.d.ts +1 -0
  11. package/dist/commands/connect-agents.d.ts.map +1 -1
  12. package/dist/commands/connect-agents.js +10 -6
  13. package/dist/commands/connect-agents.js.map +1 -1
  14. package/dist/commands/connect.d.ts +31 -0
  15. package/dist/commands/connect.d.ts.map +1 -0
  16. package/dist/commands/connect.js +92 -0
  17. package/dist/commands/connect.js.map +1 -0
  18. package/dist/commands/context.d.ts.map +1 -1
  19. package/dist/commands/context.js +52 -10
  20. package/dist/commands/context.js.map +1 -1
  21. package/dist/commands/decisions/index.d.ts.map +1 -1
  22. package/dist/commands/decisions/index.js +5 -0
  23. package/dist/commands/decisions/index.js.map +1 -1
  24. package/dist/commands/default-action.d.ts +3 -1
  25. package/dist/commands/default-action.d.ts.map +1 -1
  26. package/dist/commands/default-action.js +29 -27
  27. package/dist/commands/default-action.js.map +1 -1
  28. package/dist/commands/export.d.ts.map +1 -1
  29. package/dist/commands/export.js +3 -0
  30. package/dist/commands/export.js.map +1 -1
  31. package/dist/commands/import/sessions.d.ts +10 -2
  32. package/dist/commands/import/sessions.d.ts.map +1 -1
  33. package/dist/commands/import/sessions.js +98 -3
  34. package/dist/commands/import/sessions.js.map +1 -1
  35. package/dist/commands/import.d.ts +7 -0
  36. package/dist/commands/import.d.ts.map +1 -1
  37. package/dist/commands/import.js +42 -2
  38. package/dist/commands/import.js.map +1 -1
  39. package/dist/commands/invite.d.ts +25 -0
  40. package/dist/commands/invite.d.ts.map +1 -0
  41. package/dist/commands/invite.js +68 -0
  42. package/dist/commands/invite.js.map +1 -0
  43. package/dist/commands/links.d.ts.map +1 -1
  44. package/dist/commands/links.js +5 -0
  45. package/dist/commands/links.js.map +1 -1
  46. package/dist/commands/mcp.d.ts +64 -35
  47. package/dist/commands/mcp.d.ts.map +1 -1
  48. package/dist/commands/mcp.js +94 -41
  49. package/dist/commands/mcp.js.map +1 -1
  50. package/dist/commands/ratify.d.ts.map +1 -1
  51. package/dist/commands/ratify.js +21 -0
  52. package/dist/commands/ratify.js.map +1 -1
  53. package/dist/commands/registry.d.ts +30 -0
  54. package/dist/commands/registry.d.ts.map +1 -0
  55. package/dist/commands/registry.js +70 -0
  56. package/dist/commands/registry.js.map +1 -0
  57. package/dist/commands/setup.d.ts +45 -1
  58. package/dist/commands/setup.d.ts.map +1 -1
  59. package/dist/commands/setup.js +197 -88
  60. package/dist/commands/setup.js.map +1 -1
  61. package/dist/commands/status.d.ts.map +1 -1
  62. package/dist/commands/status.js +10 -22
  63. package/dist/commands/status.js.map +1 -1
  64. package/dist/commands/telemetry.d.ts +7 -3
  65. package/dist/commands/telemetry.d.ts.map +1 -1
  66. package/dist/commands/telemetry.js +12 -7
  67. package/dist/commands/telemetry.js.map +1 -1
  68. package/dist/commands/why.d.ts.map +1 -1
  69. package/dist/commands/why.js +52 -10
  70. package/dist/commands/why.js.map +1 -1
  71. package/dist/index.js +4 -90
  72. package/dist/index.js.map +1 -1
  73. package/dist/lib/agent-rules.d.ts +1 -0
  74. package/dist/lib/agent-rules.d.ts.map +1 -1
  75. package/dist/lib/agent-rules.js +60 -0
  76. package/dist/lib/agent-rules.js.map +1 -1
  77. package/dist/lib/commands-doc.d.ts +15 -0
  78. package/dist/lib/commands-doc.d.ts.map +1 -0
  79. package/dist/lib/commands-doc.js +85 -0
  80. package/dist/lib/commands-doc.js.map +1 -0
  81. package/dist/lib/config.d.ts +7 -2
  82. package/dist/lib/config.d.ts.map +1 -1
  83. package/dist/lib/config.js.map +1 -1
  84. package/dist/lib/connect-prompt.d.ts +1 -1
  85. package/dist/lib/connect-prompt.js +2 -2
  86. package/dist/lib/connect-prompt.js.map +1 -1
  87. package/dist/lib/decision-links.d.ts +39 -0
  88. package/dist/lib/decision-links.d.ts.map +1 -1
  89. package/dist/lib/decision-links.js +51 -0
  90. package/dist/lib/decision-links.js.map +1 -1
  91. package/dist/lib/first-decision.d.ts +22 -0
  92. package/dist/lib/first-decision.d.ts.map +1 -0
  93. package/dist/lib/first-decision.js +22 -0
  94. package/dist/lib/first-decision.js.map +1 -0
  95. package/dist/lib/gateway-client.d.ts +14 -1
  96. package/dist/lib/gateway-client.d.ts.map +1 -1
  97. package/dist/lib/gateway-client.js +14 -0
  98. package/dist/lib/gateway-client.js.map +1 -1
  99. package/dist/lib/git.d.ts +28 -0
  100. package/dist/lib/git.d.ts.map +1 -1
  101. package/dist/lib/git.js +42 -0
  102. package/dist/lib/git.js.map +1 -1
  103. package/dist/lib/hook-context.d.ts +7 -0
  104. package/dist/lib/hook-context.d.ts.map +1 -0
  105. package/dist/lib/hook-context.js +37 -0
  106. package/dist/lib/hook-context.js.map +1 -0
  107. package/dist/lib/hook-payload.d.ts +7 -0
  108. package/dist/lib/hook-payload.d.ts.map +1 -1
  109. package/dist/lib/hook-payload.js +56 -8
  110. package/dist/lib/hook-payload.js.map +1 -1
  111. package/dist/lib/invite-prompt.d.ts +46 -0
  112. package/dist/lib/invite-prompt.d.ts.map +1 -0
  113. package/dist/lib/invite-prompt.js +63 -0
  114. package/dist/lib/invite-prompt.js.map +1 -0
  115. package/dist/lib/local-gateway-client.d.ts +0 -1
  116. package/dist/lib/local-gateway-client.d.ts.map +1 -1
  117. package/dist/lib/local-gateway-client.js +5 -2
  118. package/dist/lib/local-gateway-client.js.map +1 -1
  119. package/dist/lib/mcp-instructions.shared.d.ts +40 -0
  120. package/dist/lib/mcp-instructions.shared.d.ts.map +1 -0
  121. package/dist/lib/mcp-instructions.shared.js +56 -0
  122. package/dist/lib/mcp-instructions.shared.js.map +1 -0
  123. package/dist/lib/mcp-setup.d.ts +32 -2
  124. package/dist/lib/mcp-setup.d.ts.map +1 -1
  125. package/dist/lib/mcp-setup.js +92 -6
  126. package/dist/lib/mcp-setup.js.map +1 -1
  127. package/dist/lib/next-step.d.ts +92 -0
  128. package/dist/lib/next-step.d.ts.map +1 -0
  129. package/dist/lib/next-step.js +116 -0
  130. package/dist/lib/next-step.js.map +1 -0
  131. package/dist/lib/personal-email-domains.d.ts +8 -0
  132. package/dist/lib/personal-email-domains.d.ts.map +1 -0
  133. package/dist/lib/personal-email-domains.js +39 -0
  134. package/dist/lib/personal-email-domains.js.map +1 -0
  135. package/dist/lib/personal-import.d.ts +2 -0
  136. package/dist/lib/personal-import.d.ts.map +1 -1
  137. package/dist/lib/personal-import.js +4 -1
  138. package/dist/lib/personal-import.js.map +1 -1
  139. package/dist/lib/read-value-rollup.d.ts +18 -0
  140. package/dist/lib/read-value-rollup.d.ts.map +1 -0
  141. package/dist/lib/read-value-rollup.js +33 -0
  142. package/dist/lib/read-value-rollup.js.map +1 -0
  143. package/dist/lib/sessions/import-summary.d.ts +39 -0
  144. package/dist/lib/sessions/import-summary.d.ts.map +1 -0
  145. package/dist/lib/sessions/import-summary.js +41 -0
  146. package/dist/lib/sessions/import-summary.js.map +1 -0
  147. package/dist/lib/sessions/memory-files.d.ts +85 -0
  148. package/dist/lib/sessions/memory-files.d.ts.map +1 -0
  149. package/dist/lib/sessions/memory-files.js +235 -0
  150. package/dist/lib/sessions/memory-files.js.map +1 -0
  151. package/dist/lib/sessions/source-url.d.ts +11 -0
  152. package/dist/lib/sessions/source-url.d.ts.map +1 -1
  153. package/dist/lib/sessions/source-url.js +15 -0
  154. package/dist/lib/sessions/source-url.js.map +1 -1
  155. package/dist/lib/setup-funnel.d.ts +41 -0
  156. package/dist/lib/setup-funnel.d.ts.map +1 -0
  157. package/dist/lib/setup-funnel.js +28 -0
  158. package/dist/lib/setup-funnel.js.map +1 -0
  159. package/dist/lib/telemetry-consent.d.ts +17 -8
  160. package/dist/lib/telemetry-consent.d.ts.map +1 -1
  161. package/dist/lib/telemetry-consent.js +21 -6
  162. package/dist/lib/telemetry-consent.js.map +1 -1
  163. package/dist/lib/telemetry-env.d.ts +19 -0
  164. package/dist/lib/telemetry-env.d.ts.map +1 -0
  165. package/dist/lib/telemetry-env.js +13 -0
  166. package/dist/lib/telemetry-env.js.map +1 -0
  167. package/dist/lib/usage-telemetry.d.ts +108 -13
  168. package/dist/lib/usage-telemetry.d.ts.map +1 -1
  169. package/dist/lib/usage-telemetry.js +227 -55
  170. package/dist/lib/usage-telemetry.js.map +1 -1
  171. package/dist/lib/user-hooks.d.ts +15 -0
  172. package/dist/lib/user-hooks.d.ts.map +1 -0
  173. package/dist/lib/user-hooks.js +144 -0
  174. package/dist/lib/user-hooks.js.map +1 -0
  175. package/dist/lib/value-rollup.d.ts +10 -3
  176. package/dist/lib/value-rollup.d.ts.map +1 -1
  177. package/dist/lib/value-rollup.js +11 -4
  178. package/dist/lib/value-rollup.js.map +1 -1
  179. package/package.json +2 -1
@@ -0,0 +1,39 @@
1
+ /**
2
+ * ALI-835: the line a human reads at the end of `align import sessions`, and the counts the
3
+ * funnel reports.
4
+ *
5
+ * The line is the product moment; the ping is the measurement. They are built from ONE object
6
+ * here so the two can never disagree - a summary saying 41 beside a ping saying 40 would make
7
+ * both unusable, and nothing downstream could tell which was wrong.
8
+ *
9
+ * The demo beat this exists for (Madhuri, 2026-08-25): run the import on the prospect's own
10
+ * repo, read the line aloud, ask who in the room knew.
11
+ */
12
+ export interface SessionImportCounts {
13
+ /** Session files read, across every detected agent. */
14
+ sessionsScanned: number;
15
+ /** Decision-shaped moments found, before review. */
16
+ candidatesFound: number;
17
+ /** How many of those a human confirmed into the graph this run. */
18
+ candidatesConfirmed: number;
19
+ /**
20
+ * How many of the confirmed claims already carry a human ratification.
21
+ *
22
+ * Structurally 0 at import: `align import sessions` writes claims, and ratifying is a
23
+ * separate human act (`align ratify`). It is reported anyway, because "0 have been ratified"
24
+ * is the sentence the demo turns on - the point is that nobody has stood behind any of them
25
+ * yet. The funnel's own decisions_ratified stage is emitted from `align ratify`, where the
26
+ * number actually moves, rather than from here where it could only ever be zero.
27
+ */
28
+ ratified: number;
29
+ }
30
+ /**
31
+ * The summary line.
32
+ *
33
+ * Renders honestly at zero rather than crashing or printing a line about nothing: "Found 0
34
+ * decisions in 3 sessions" is a different sentence from "Found 0 decisions across 0 sessions",
35
+ * and both are different from an exception. Singular and plural are handled for every count,
36
+ * because "1 sessions" in a demo is the kind of detail that costs more attention than it saves.
37
+ */
38
+ export declare function renderImportSummary(counts: SessionImportCounts): string;
39
+ //# sourceMappingURL=import-summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-summary.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/import-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,mBAAmB;IAClC,uDAAuD;IACvD,eAAe,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,eAAe,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,mBAAmB,EAAE,MAAM,CAAC;IAC5B;;;;;;;;OAQG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CAyBvE"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * ALI-835: the line a human reads at the end of `align import sessions`, and the counts the
3
+ * funnel reports.
4
+ *
5
+ * The line is the product moment; the ping is the measurement. They are built from ONE object
6
+ * here so the two can never disagree - a summary saying 41 beside a ping saying 40 would make
7
+ * both unusable, and nothing downstream could tell which was wrong.
8
+ *
9
+ * The demo beat this exists for (Madhuri, 2026-08-25): run the import on the prospect's own
10
+ * repo, read the line aloud, ask who in the room knew.
11
+ */
12
+ /**
13
+ * The summary line.
14
+ *
15
+ * Renders honestly at zero rather than crashing or printing a line about nothing: "Found 0
16
+ * decisions in 3 sessions" is a different sentence from "Found 0 decisions across 0 sessions",
17
+ * and both are different from an exception. Singular and plural are handled for every count,
18
+ * because "1 sessions" in a demo is the kind of detail that costs more attention than it saves.
19
+ */
20
+ export function renderImportSummary(counts) {
21
+ const { sessionsScanned, candidatesFound, candidatesConfirmed, ratified } = counts;
22
+ const sessions = `${sessionsScanned} session${sessionsScanned === 1 ? '' : 's'}`;
23
+ if (candidatesFound === 0) {
24
+ // The ticket's own wording for the empty case. A run that found nothing is a real result,
25
+ // not a failure, and it must not read like one.
26
+ return `Found 0 decisions in ${sessions}.`;
27
+ }
28
+ const decisions = `${candidatesFound} decision${candidatesFound === 1 ? '' : 's'}`;
29
+ const parts = [`Found ${decisions} across ${sessions}.`];
30
+ // Every candidate here came from an agent's own session, so "made by an agent" is the whole
31
+ // found set by construction rather than a second measurement. Stated as its own sentence
32
+ // because it is the one a prospect reacts to.
33
+ parts.push(`${candidatesFound} ${candidatesFound === 1 ? 'was' : 'were'} made by an agent.`);
34
+ parts.push(`${ratified} ${ratified === 1 ? 'has' : 'have'} been ratified.`);
35
+ if (candidatesConfirmed !== candidatesFound) {
36
+ // Only when it differs, so the common "reviewed them all" run keeps a short line.
37
+ parts.push(`${candidatesConfirmed} confirmed this run.`);
38
+ }
39
+ return parts.join(' ');
40
+ }
41
+ //# sourceMappingURL=import-summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-summary.js","sourceRoot":"","sources":["../../../src/lib/sessions/import-summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAqBH;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAA2B;IAC7D,MAAM,EAAE,eAAe,EAAE,eAAe,EAAE,mBAAmB,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IACnF,MAAM,QAAQ,GAAG,GAAG,eAAe,WAAW,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAEjF,IAAI,eAAe,KAAK,CAAC,EAAE,CAAC;QAC1B,0FAA0F;QAC1F,gDAAgD;QAChD,OAAO,wBAAwB,QAAQ,GAAG,CAAC;IAC7C,CAAC;IAED,MAAM,SAAS,GAAG,GAAG,eAAe,YAAY,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IACnF,MAAM,KAAK,GAAG,CAAC,SAAS,SAAS,WAAW,QAAQ,GAAG,CAAC,CAAC;IAEzD,4FAA4F;IAC5F,yFAAyF;IACzF,8CAA8C;IAC9C,KAAK,CAAC,IAAI,CAAC,GAAG,eAAe,IAAI,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,oBAAoB,CAAC,CAAC;IAC7F,KAAK,CAAC,IAAI,CAAC,GAAG,QAAQ,IAAI,QAAQ,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,iBAAiB,CAAC,CAAC;IAE5E,IAAI,mBAAmB,KAAK,eAAe,EAAE,CAAC;QAC5C,kFAAkF;QAClF,KAAK,CAAC,IAAI,CAAC,GAAG,mBAAmB,sBAAsB,CAAC,CAAC;IAC3D,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,85 @@
1
+ /** The four kinds Claude Code records, verbatim from the documentation. */
2
+ export declare const MEMORY_TYPES: readonly ["user", "feedback", "project", "reference"];
3
+ export type MemoryType = (typeof MEMORY_TYPES)[number];
4
+ /**
5
+ * The two kinds that are decision-shaped.
6
+ *
7
+ * `project` is "ongoing work, deadlines, and decisions that Claude can't derive from the code
8
+ * or git history" and `feedback` is "corrections you give Claude and approaches you confirm" -
9
+ * both are things somebody decided. `user` is a person's role and working preferences, and
10
+ * `reference` is where to find a dashboard. Neither is a decision, and importing them would
11
+ * put personal notes into a decision graph.
12
+ */
13
+ export declare const DECISION_MEMORY_TYPES: readonly MemoryType[];
14
+ /** The index file, never a memory in its own right. */
15
+ export declare const MEMORY_INDEX_FILENAME = "MEMORY.md";
16
+ export interface MemoryFile {
17
+ filePath: string;
18
+ /** Null when the file carries no frontmatter, or carries frontmatter with no `type`, or a
19
+ * `type` outside the four documented values. All three are "cannot classify", which is a
20
+ * different fact from "classified as something we skip" - see extractMemoryDecisions. */
21
+ type: MemoryType | null;
22
+ /** ISO-8601 from the `modified` field, or null when absent (the field only appears on files
23
+ * Claude has written since v2.1.214, and never on a file that has no frontmatter). */
24
+ modified: string | null;
25
+ /** Every other scalar frontmatter key, unparsed. Nothing depends on these; they exist so a
26
+ * later ticket can confirm `description`/`originSessionId` from real data without a reparse. */
27
+ extra: Record<string, string>;
28
+ /** The markdown below the frontmatter, trimmed. */
29
+ body: string;
30
+ }
31
+ export interface MemoryDecisionCandidate {
32
+ filePath: string;
33
+ type: MemoryType;
34
+ /** The file's first markdown heading, else its filename stem with separators as spaces. */
35
+ title: string;
36
+ body: string;
37
+ /** ISO-8601 or null, from `modified`. */
38
+ timestamp: string | null;
39
+ /** The memory directory this file came from, used to build a stable source_url. */
40
+ memoryDir: string;
41
+ }
42
+ /**
43
+ * Where this project's memory directory is, if anywhere.
44
+ *
45
+ * Resolution order, and the reason for it:
46
+ *
47
+ * 1. `autoMemoryDirectory` from `~/.claude/settings.json`. Reading it matters because the
48
+ * failure direction without it is silent: a user who relocated the directory would get an
49
+ * empty list, which is indistinguishable from "no memories yet".
50
+ * 2. Otherwise, the `memory/` sibling of this project's Claude Code session files. The docs
51
+ * say `<project>` is derived from the git repository, and the exact slug encoding is not
52
+ * documented - so rather than guess at it, this reuses the cwd matching ALI-808 already
53
+ * proved (each session file records its own `cwd`) and takes the directory those files
54
+ * sit in. That is correct for worktrees and subdirectories for free, because they share
55
+ * one project directory.
56
+ *
57
+ * Returns [] when the agent has never run here, which is the normal case and not an error.
58
+ */
59
+ export declare function locateMemoryDirs(cwd: string): string[];
60
+ /** Memory topic files for this project, newest first. `MEMORY.md` is excluded: it is an index
61
+ * of the others, so importing it would duplicate every memory as one more candidate. */
62
+ export declare function locateMemoryFiles(cwd: string): string[];
63
+ /**
64
+ * Parses one memory file.
65
+ *
66
+ * The frontmatter reader deliberately handles only `key: value` scalars on one line, which is
67
+ * every field the documentation describes. Anything else - a nested map, a list, a block
68
+ * scalar - is left in `extra` as its raw text if it is a simple scalar and otherwise ignored,
69
+ * rather than half-understood. js-yaml is a devDependency here, not a runtime one, so pulling
70
+ * a YAML parser into shipped CLI code would be a new dependency for two scalar fields.
71
+ *
72
+ * Returns null only for an unreadable file.
73
+ */
74
+ export declare function parseMemoryFile(filePath: string): MemoryFile | null;
75
+ /**
76
+ * The decision-shaped memories among these files.
77
+ *
78
+ * Three separate reasons a file is dropped, all deliberate:
79
+ * - `type` is `user` or `reference`: classified, and not a decision.
80
+ * - `type` is absent or unrecognised: NOT classified. Importing it would be a guess about
81
+ * personal notes, so it is skipped rather than defaulted into a decision.
82
+ * - the body is empty: nothing to review.
83
+ */
84
+ export declare function extractMemoryDecisions(filePaths: string[]): MemoryDecisionCandidate[];
85
+ //# sourceMappingURL=memory-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-files.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/memory-files.ts"],"names":[],"mappings":"AA4CA,2EAA2E;AAC3E,eAAO,MAAM,YAAY,uDAAwD,CAAC;AAClF,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,EAAE,SAAS,UAAU,EAA4B,CAAC;AAEpF,uDAAuD;AACvD,eAAO,MAAM,qBAAqB,cAAc,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB;;8FAE0F;IAC1F,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB;2FACuF;IACvF,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;qGACiG;IACjG,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,2FAA2F;IAC3F,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQtD;AAED;yFACyF;AACzF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAQvD;AAwBD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAmBnE;AAiDD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,uBAAuB,EAAE,CAyBrF"}
@@ -0,0 +1,235 @@
1
+ /**
2
+ * ALI-810: Claude Code's own auto-memory as a second, already-curated decision source.
3
+ *
4
+ * ALI-808 mines raw transcripts. This reads the notes the harness already decided were worth
5
+ * keeping: `~/.claude/projects/<project>/memory/*.md`, one fact per file, with a `type` in
6
+ * frontmatter. Higher precision per file and far cheaper to parse - no noise filtering, no
7
+ * repeat-loop exclusion, no plan-file resolution.
8
+ *
9
+ * ## What is verified, and what is not
10
+ *
11
+ * There is no captured memory-file fixture, because this environment has no memory directory
12
+ * at all (checked: `find ~/.claude -type d -name memory` is empty while `~/.claude/projects`
13
+ * exists). Under ALI-808's own gate - "the gate is the fixture, not the agent name" - that
14
+ * would normally mean shipping `locate` and refusing to parse, the way `gemini-cli` and
15
+ * `cursor` do.
16
+ *
17
+ * The difference here is that the format is DOCUMENTED by its producer rather than reverse
18
+ * engineered, so the fixtures are built from Anthropic's own published description rather
19
+ * than from a guess (https://code.claude.com/docs/en/memory, read 2026-09-09). Everything
20
+ * this module relies on is quoted from that page:
21
+ *
22
+ * - the directory is `~/.claude/projects/<project>/memory/`, `<project>` derived from the
23
+ * git repository, so all worktrees and subdirectories of one repo share it;
24
+ * - it holds a `MEMORY.md` index plus one topic file per memory, with arbitrary names;
25
+ * - Claude records the kind in a `type` frontmatter field with exactly four values:
26
+ * `user`, `feedback`, `project`, `reference`;
27
+ * - a `modified` frontmatter field carries an ISO-8601 write time;
28
+ * - "Claude Code never adds frontmatter to a file that has none", so a memory file may
29
+ * legitimately carry no frontmatter at all;
30
+ * - `autoMemoryDirectory` in settings.json relocates the directory.
31
+ *
32
+ * **The ALI-810 ticket also names `description` and `originSessionId` frontmatter fields.
33
+ * The documentation describes neither.** They may well exist - the ticket was written from a
34
+ * real machine and this one has no memory directory to check - but nothing here depends on
35
+ * them, because a field this module cannot confirm is a field it must not require. If they
36
+ * turn out to be real, `parseMemoryFile` already returns them under `extra` and a follow-up
37
+ * can promote them without touching any caller.
38
+ */
39
+ import { readFileSync } from 'node:fs';
40
+ import { homedir } from 'node:os';
41
+ import { basename, dirname, join } from 'node:path';
42
+ import { findFilesRecursive, sortByMtimeDesc } from './fs-scan.js';
43
+ import { claudeCodeAdapter } from './adapters/claude-code.js';
44
+ /** The four kinds Claude Code records, verbatim from the documentation. */
45
+ export const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'];
46
+ /**
47
+ * The two kinds that are decision-shaped.
48
+ *
49
+ * `project` is "ongoing work, deadlines, and decisions that Claude can't derive from the code
50
+ * or git history" and `feedback` is "corrections you give Claude and approaches you confirm" -
51
+ * both are things somebody decided. `user` is a person's role and working preferences, and
52
+ * `reference` is where to find a dashboard. Neither is a decision, and importing them would
53
+ * put personal notes into a decision graph.
54
+ */
55
+ export const DECISION_MEMORY_TYPES = ['project', 'feedback'];
56
+ /** The index file, never a memory in its own right. */
57
+ export const MEMORY_INDEX_FILENAME = 'MEMORY.md';
58
+ /**
59
+ * Where this project's memory directory is, if anywhere.
60
+ *
61
+ * Resolution order, and the reason for it:
62
+ *
63
+ * 1. `autoMemoryDirectory` from `~/.claude/settings.json`. Reading it matters because the
64
+ * failure direction without it is silent: a user who relocated the directory would get an
65
+ * empty list, which is indistinguishable from "no memories yet".
66
+ * 2. Otherwise, the `memory/` sibling of this project's Claude Code session files. The docs
67
+ * say `<project>` is derived from the git repository, and the exact slug encoding is not
68
+ * documented - so rather than guess at it, this reuses the cwd matching ALI-808 already
69
+ * proved (each session file records its own `cwd`) and takes the directory those files
70
+ * sit in. That is correct for worktrees and subdirectories for free, because they share
71
+ * one project directory.
72
+ *
73
+ * Returns [] when the agent has never run here, which is the normal case and not an error.
74
+ */
75
+ export function locateMemoryDirs(cwd) {
76
+ const configured = readAutoMemoryDirectory();
77
+ if (configured)
78
+ return [configured];
79
+ const sessionFiles = claudeCodeAdapter.locateSessionFiles(cwd);
80
+ const dirs = new Set();
81
+ for (const file of sessionFiles)
82
+ dirs.add(join(dirname(file), 'memory'));
83
+ return [...dirs];
84
+ }
85
+ /** Memory topic files for this project, newest first. `MEMORY.md` is excluded: it is an index
86
+ * of the others, so importing it would duplicate every memory as one more candidate. */
87
+ export function locateMemoryFiles(cwd) {
88
+ const found = [];
89
+ for (const dir of locateMemoryDirs(cwd)) {
90
+ for (const file of findFilesRecursive(dir, /\.md$/)) {
91
+ if (basename(file) !== MEMORY_INDEX_FILENAME)
92
+ found.push(file);
93
+ }
94
+ }
95
+ return sortByMtimeDesc(found);
96
+ }
97
+ function readAutoMemoryDirectory() {
98
+ let raw;
99
+ try {
100
+ raw = readFileSync(join(homedir(), '.claude', 'settings.json'), 'utf8');
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ let parsed;
106
+ try {
107
+ parsed = JSON.parse(raw);
108
+ }
109
+ catch {
110
+ // A settings file we cannot parse is not ours to repair, and guessing past it would be
111
+ // worse than falling back to the documented default location.
112
+ return null;
113
+ }
114
+ const value = parsed?.['autoMemoryDirectory'];
115
+ if (typeof value !== 'string' || value.length === 0)
116
+ return null;
117
+ // Documented constraint: "must be an absolute path or start with ~/".
118
+ if (value.startsWith('~/'))
119
+ return join(homedir(), value.slice(2));
120
+ return value;
121
+ }
122
+ /**
123
+ * Parses one memory file.
124
+ *
125
+ * The frontmatter reader deliberately handles only `key: value` scalars on one line, which is
126
+ * every field the documentation describes. Anything else - a nested map, a list, a block
127
+ * scalar - is left in `extra` as its raw text if it is a simple scalar and otherwise ignored,
128
+ * rather than half-understood. js-yaml is a devDependency here, not a runtime one, so pulling
129
+ * a YAML parser into shipped CLI code would be a new dependency for two scalar fields.
130
+ *
131
+ * Returns null only for an unreadable file.
132
+ */
133
+ export function parseMemoryFile(filePath) {
134
+ let text;
135
+ try {
136
+ text = readFileSync(filePath, 'utf8');
137
+ }
138
+ catch {
139
+ return null;
140
+ }
141
+ const { frontmatter, body } = splitFrontmatter(text);
142
+ const rawType = frontmatter['type'];
143
+ const type = isMemoryType(rawType) ? rawType : null;
144
+ const modified = frontmatter['modified'] ?? null;
145
+ const extra = {};
146
+ for (const [k, v] of Object.entries(frontmatter)) {
147
+ if (k !== 'type' && k !== 'modified')
148
+ extra[k] = v;
149
+ }
150
+ return { filePath, type, modified, extra, body };
151
+ }
152
+ function isMemoryType(value) {
153
+ return value !== undefined && MEMORY_TYPES.includes(value);
154
+ }
155
+ /**
156
+ * Splits `---\n...\n---\n` off the front. A file with no frontmatter is the documented normal
157
+ * case, not a malformed one, so it yields an empty map and the whole text as the body.
158
+ */
159
+ function splitFrontmatter(text) {
160
+ const normalized = text.replace(/\r\n/g, '\n');
161
+ if (!normalized.startsWith('---\n'))
162
+ return { frontmatter: {}, body: normalized.trim() };
163
+ const end = normalized.indexOf('\n---', 3);
164
+ if (end === -1) {
165
+ // An opening fence with no closing one is not frontmatter. Treating the rest of the file
166
+ // as key/value would silently swallow the content.
167
+ return { frontmatter: {}, body: normalized.trim() };
168
+ }
169
+ const block = normalized.slice(4, end);
170
+ const after = normalized.slice(end + 4);
171
+ const frontmatter = {};
172
+ for (const line of block.split('\n')) {
173
+ const colon = line.indexOf(':');
174
+ if (colon === -1)
175
+ continue;
176
+ const key = line.slice(0, colon).trim();
177
+ let value = line.slice(colon + 1).trim();
178
+ if (!key || !value)
179
+ continue;
180
+ // Strip one layer of matching quotes, which is how an ISO timestamp is often written.
181
+ if ((value.startsWith('"') && value.endsWith('"') && value.length > 1) ||
182
+ (value.startsWith("'") && value.endsWith("'") && value.length > 1)) {
183
+ value = value.slice(1, -1);
184
+ }
185
+ frontmatter[key] = value;
186
+ }
187
+ return { frontmatter, body: after.trim() };
188
+ }
189
+ /** First markdown heading, else the filename stem with `_`/`-` as spaces. */
190
+ function titleFor(file) {
191
+ const heading = file.body.split('\n').find(l => /^#{1,6}\s+\S/.test(l));
192
+ if (heading)
193
+ return heading.replace(/^#{1,6}\s+/, '').trim();
194
+ return basename(file.filePath).replace(/\.md$/, '').replace(/[_-]+/g, ' ').trim();
195
+ }
196
+ /**
197
+ * The decision-shaped memories among these files.
198
+ *
199
+ * Three separate reasons a file is dropped, all deliberate:
200
+ * - `type` is `user` or `reference`: classified, and not a decision.
201
+ * - `type` is absent or unrecognised: NOT classified. Importing it would be a guess about
202
+ * personal notes, so it is skipped rather than defaulted into a decision.
203
+ * - the body is empty: nothing to review.
204
+ */
205
+ export function extractMemoryDecisions(filePaths) {
206
+ const candidates = [];
207
+ for (const filePath of filePaths) {
208
+ const parsed = parseMemoryFile(filePath);
209
+ if (!parsed)
210
+ continue;
211
+ // This null check is RUNTIME-REDUNDANT and kept deliberately. Replacing it with a no-op
212
+ // leaves every test green, because DECISION_MEMORY_TYPES cannot contain null so the next
213
+ // line drops an unclassified file anyway. Deleting it does NOT leave the suite green:
214
+ // `tsc` then reports TS2345 and TS2322, because the narrowing to MemoryType is what lets
215
+ // the next line and the push below compile. The enforcing mechanism is the compiler
216
+ // rather than a test, which is the honest reason it survives a mutation run - so do not
217
+ // remove it as dead code.
218
+ if (parsed.type === null)
219
+ continue;
220
+ if (!DECISION_MEMORY_TYPES.includes(parsed.type))
221
+ continue;
222
+ if (parsed.body.length === 0)
223
+ continue;
224
+ candidates.push({
225
+ filePath,
226
+ type: parsed.type,
227
+ title: titleFor(parsed),
228
+ body: parsed.body,
229
+ timestamp: parsed.modified,
230
+ memoryDir: dirname(filePath),
231
+ });
232
+ }
233
+ return candidates;
234
+ }
235
+ //# sourceMappingURL=memory-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory-files.js","sourceRoot":"","sources":["../../../src/lib/sessions/memory-files.ts"],"names":[],"mappings":"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"}
@@ -7,4 +7,15 @@ export declare function parseSessionSourceUrl(url: string): {
7
7
  sessionId: string;
8
8
  messageId: string;
9
9
  } | null;
10
+ /**
11
+ * ALI-810: memory files are not sessions, so they get their own scheme rather than being
12
+ * squeezed into `<agent>-session://<sessionId>/<messageId>` with invented ids.
13
+ *
14
+ * Built from the project directory name and the topic file's stem, both of which are stable
15
+ * for a given memory across rewrites - Claude edits a topic file in place rather than making
16
+ * a new one. That stability is the point: the gateway upserts on `(tenant_id, source_url)`,
17
+ * so re-running the import after Claude has revised a memory updates the decision instead of
18
+ * adding a second copy of it.
19
+ */
20
+ export declare function buildMemorySourceUrl(memoryDir: string, filePath: string): string;
10
21
  //# sourceMappingURL=source-url.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-url.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C;6CAC6C;AAC7C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpG;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWpH"}
1
+ {"version":3,"file":"source-url.d.ts","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAI5C;6CAC6C;AAC7C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEpG;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAWpH;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIhF"}
@@ -17,4 +17,19 @@ export function parseSessionSourceUrl(url) {
17
17
  }
18
18
  return null;
19
19
  }
20
+ /**
21
+ * ALI-810: memory files are not sessions, so they get their own scheme rather than being
22
+ * squeezed into `<agent>-session://<sessionId>/<messageId>` with invented ids.
23
+ *
24
+ * Built from the project directory name and the topic file's stem, both of which are stable
25
+ * for a given memory across rewrites - Claude edits a topic file in place rather than making
26
+ * a new one. That stability is the point: the gateway upserts on `(tenant_id, source_url)`,
27
+ * so re-running the import after Claude has revised a memory updates the decision instead of
28
+ * adding a second copy of it.
29
+ */
30
+ export function buildMemorySourceUrl(memoryDir, filePath) {
31
+ const project = memoryDir.split(/[\\/]/).filter(Boolean).at(-2) ?? 'unknown-project';
32
+ const topic = (filePath.split(/[\\/]/).pop() ?? 'unknown').replace(/\.md$/, '');
33
+ return `claude-code-memory://${project}/${topic}`;
34
+ }
20
35
  //# sourceMappingURL=source-url.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"source-url.js","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAyB,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAExG;6CAC6C;AAC7C,MAAM,UAAU,qBAAqB,CAAC,KAAgB,EAAE,SAAiB,EAAE,SAAiB;IAC1F,OAAO,GAAG,KAAK,cAAc,SAAS,IAAI,SAAS,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,GAAG,KAAK,aAAa,CAAC;QACrC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC9B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACtF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"source-url.js","sourceRoot":"","sources":["../../../src/lib/sessions/source-url.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,GAAyB,CAAC,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;AAExG;6CAC6C;AAC7C,MAAM,UAAU,qBAAqB,CAAC,KAAgB,EAAE,SAAiB,EAAE,SAAiB;IAC1F,OAAO,GAAG,KAAK,cAAc,SAAS,IAAI,SAAS,EAAE,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,GAAG,KAAK,aAAa,CAAC;QACrC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;YAChC,IAAI,KAAK,KAAK,CAAC,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC9B,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAC;QACtF,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAAC,SAAiB,EAAE,QAAgB;IACtE,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC;IACrF,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,SAAS,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAChF,OAAO,wBAAwB,OAAO,IAAI,KAAK,EAAE,CAAC;AACpD,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { EnvironmentConfig } from './config.js';
2
+ /**
3
+ * ALI-949: the setup wizard's two funnel stages, carried through `runSetup` so bare
4
+ * `align` and `align setup` share one emitter (both run runSetup's body).
5
+ *
6
+ * `started` BUFFERS rather than sends. When a mode branch begins nothing may be sendable
7
+ * on a fresh install: cloud has no token until login, and local mode asks for consent
8
+ * mid-wizard (ALI-794 puts the found-decisions moment before any question, and that order
9
+ * is not negotiable here). So the wizard offers the current env at each checkpoint - the
10
+ * top of the local value phase, after consent, the top of cloud setup, after login - and
11
+ * this object sends at the first offer the emitter reports as sent. Sendability is
12
+ * recordFunnelStage's decision, not repeated here.
13
+ *
14
+ * Within one wizard run, "once" is per IDENTITY rather than once in total. A local-mode
15
+ * ping is keyed on the machine's installId; a cloud ping on the tenant. Nothing joins the
16
+ * two, so a fresh-install user who consents locally and then chooses "sync to the cloud"
17
+ * would otherwise leave `setup_started` under one identity and `setup_completed` under the
18
+ * other (fresh-context review on #279). A later checkpoint on the OTHER transport therefore
19
+ * sends again under its own identity; the same transport never repeats in the run. There is
20
+ * no cross-run memory here: one object is created per `runSetup` call, and a re-run of the
21
+ * wizard is a new setup_started, which is what the funnel wants (the reader counts an
22
+ * install once per stage regardless - `cli_activation_funnel.py`'s countIf(> 0)).
23
+ *
24
+ * What that means for the funnel: in local mode `setup_started` says "started, and got as
25
+ * far as consenting" rather than "typed the command". A user who declines consent is never
26
+ * counted at any stage, which is the consent model, not a gap.
27
+ *
28
+ * Offers are serialised through one promise chain because call sites fire them without
29
+ * awaiting (`void funnel.started(env)` - a slow gateway must not delay the wizard), so two
30
+ * offers can overlap; the second waits for the first's verdict before deciding to send.
31
+ * `completed` queues behind the started chain too (Copilot on #279): a started send still
32
+ * in flight when the wizard finishes must not be overtaken, or the install's two events
33
+ * reach the gateway out of order. By the outro the started send has long settled, so this
34
+ * costs nothing in practice and is the ordering guarantee when it has not.
35
+ */
36
+ export interface SetupFunnel {
37
+ started(env: EnvironmentConfig): Promise<void>;
38
+ completed(env: EnvironmentConfig): Promise<void>;
39
+ }
40
+ export declare function createSetupFunnel(): SetupFunnel;
41
+ //# sourceMappingURL=setup-funnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-funnel.d.ts","sourceRoot":"","sources":["../../src/lib/setup-funnel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,SAAS,CAAC,GAAG,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAClD;AAMD,wBAAgB,iBAAiB,IAAI,WAAW,CAuB/C"}
@@ -0,0 +1,28 @@
1
+ import { recordFunnelStage } from './usage-telemetry.js';
2
+ const transportOf = (env) => (env.mode === 'local-embedded' ? 'anonymous' : 'cloud');
3
+ export function createSetupFunnel() {
4
+ // The transports setup_started has been sent on so far.
5
+ let startedChain = Promise.resolve(new Set());
6
+ let completedChain = Promise.resolve(false);
7
+ return {
8
+ async started(env) {
9
+ startedChain = startedChain.then(async (sentOn) => {
10
+ const transport = transportOf(env);
11
+ if (sentOn.has(transport))
12
+ return sentOn;
13
+ const sent = await recordFunnelStage(env, 'setup_started', 'setup');
14
+ return sent ? new Set([...sentOn, transport]) : sentOn;
15
+ });
16
+ await startedChain;
17
+ },
18
+ async completed(env) {
19
+ // Capture the previous link BEFORE reassigning, or the new chain waits on itself.
20
+ const previous = completedChain;
21
+ completedChain = startedChain
22
+ .then(() => previous)
23
+ .then((sent) => (sent ? true : recordFunnelStage(env, 'setup_completed', 'setup')));
24
+ await completedChain;
25
+ },
26
+ };
27
+ }
28
+ //# sourceMappingURL=setup-funnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-funnel.js","sourceRoot":"","sources":["../../src/lib/setup-funnel.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AA2CzD,MAAM,WAAW,GAAG,CAAC,GAAsB,EAAa,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAEnH,MAAM,UAAU,iBAAiB;IAC/B,wDAAwD;IACxD,IAAI,YAAY,GAAoC,OAAO,CAAC,OAAO,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAC/E,IAAI,cAAc,GAAqB,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC9D,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,GAAG;YACf,YAAY,GAAG,YAAY,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE;gBAChD,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;gBACnC,IAAI,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;oBAAE,OAAO,MAAM,CAAC;gBACzC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;gBACpE,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACzD,CAAC,CAAC,CAAC;YACH,MAAM,YAAY,CAAC;QACrB,CAAC;QACD,KAAK,CAAC,SAAS,CAAC,GAAG;YACjB,kFAAkF;YAClF,MAAM,QAAQ,GAAG,cAAc,CAAC;YAChC,cAAc,GAAG,YAAY;iBAC1B,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC;iBACpB,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,iBAAiB,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;YACtF,MAAM,cAAc,CAAC;QACvB,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -3,18 +3,27 @@
3
3
  * carry every unrelated accessor `createConfigStore()` exposes.
4
4
  */
5
5
  export interface TelemetryConsentStore {
6
- getTelemetryConsent(): 'granted' | 'declined' | undefined;
7
- setTelemetryConsent(value: 'granted' | 'declined'): void;
6
+ getTelemetryConsent(): 'granted' | 'declined' | 'off' | undefined;
7
+ setTelemetryConsent(value: 'granted' | 'declined' | 'off'): void;
8
8
  }
9
9
  /**
10
10
  * ALI-618 D3: the one-time local-mode consent prompt, shown from `runLocalSetup()`.
11
11
  *
12
- * Never asks twice - a decision already on disk (granted OR declined) is left alone. Never
13
- * prompts without a TTY on both streams: a piped `setup --local` run hangs on a prompt it
14
- * cannot answer, and a closed stdin crashes clack's raw-mode init AFTER the real setup work has
15
- * already succeeded (the align-cli#118 lesson, `setup-local-non-tty.test.ts`). A non-interactive
16
- * run leaves consent UNSET rather than implicitly declined - a scripted first run may be a CI
17
- * smoke test, not a real user's choice, and it can still be asked on a later interactive run.
12
+ * Never asks twice - a decision already on disk (granted, declined, or `align telemetry off`)
13
+ * is left alone. Never prompts without a TTY on both streams: a piped `setup --local` run
14
+ * hangs on a prompt it cannot answer, and a closed stdin crashes clack's raw-mode init AFTER
15
+ * the real setup work has already succeeded (the align-cli#118 lesson,
16
+ * `setup-local-non-tty.test.ts`). A non-interactive run leaves consent UNSET rather than
17
+ * implicitly declined - a scripted first run may be a CI smoke test, not a real user's choice,
18
+ * and it can still be asked on a later interactive run.
19
+ *
20
+ * ALI-954: an env var that already turns everything off (`DO_NOT_TRACK=1`, `ALIGN_TELEMETRY=0`)
21
+ * skips the question with a one-line note - asking would imply the answer matters - and
22
+ * leaves the decision unset for the same reason as the non-interactive case.
23
+ *
24
+ * The question is about USAGE (which commands you run). The two anonymous counts the docs
25
+ * describe (install, setup completed) are not what is being asked about here and send either
26
+ * way; `align telemetry off` is what stops those (docs/telemetry.md).
18
27
  *
19
28
  * Default is No: anything other than an explicit yes - the default answer, or Ctrl-C - leaves
20
29
  * telemetry off (D3).
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-consent.d.ts","sourceRoot":"","sources":["../../src/lib/telemetry-consent.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,mBAAmB,IAAI,SAAS,GAAG,UAAU,GAAG,SAAS,CAAC;IAC1D,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,IAAI,CAAC;CAC1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,qBAAqB,EAC7B,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,IAAI,CAAC,CAYf"}
1
+ {"version":3,"file":"telemetry-consent.d.ts","sourceRoot":"","sources":["../../src/lib/telemetry-consent.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,mBAAmB,IAAI,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,SAAS,CAAC;IAClE,mBAAmB,CAAC,KAAK,EAAE,SAAS,GAAG,UAAU,GAAG,KAAK,GAAG,IAAI,CAAC;CAClE;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,4BAA4B,CAChD,MAAM,EAAE,qBAAqB,EAC7B,WAAW,EAAE,OAAO,GACnB,OAAO,CAAC,IAAI,CAAC,CAkBf"}