@aligndottech/cli 0.1.3 → 0.2.0

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 (122) hide show
  1. package/README.md +151 -54
  2. package/dist/commands/check.d.ts.map +1 -1
  3. package/dist/commands/check.js +108 -0
  4. package/dist/commands/check.js.map +1 -1
  5. package/dist/commands/links.js +1 -1
  6. package/dist/commands/links.js.map +1 -1
  7. package/dist/commands/local.d.ts +3 -0
  8. package/dist/commands/local.d.ts.map +1 -0
  9. package/dist/commands/local.js +71 -0
  10. package/dist/commands/local.js.map +1 -0
  11. package/dist/commands/login.d.ts.map +1 -1
  12. package/dist/commands/login.js +4 -54
  13. package/dist/commands/login.js.map +1 -1
  14. package/dist/commands/mcp.d.ts +2 -0
  15. package/dist/commands/mcp.d.ts.map +1 -1
  16. package/dist/commands/mcp.js +32 -11
  17. package/dist/commands/mcp.js.map +1 -1
  18. package/dist/commands/setup.d.ts.map +1 -1
  19. package/dist/commands/setup.js +583 -199
  20. package/dist/commands/setup.js.map +1 -1
  21. package/dist/commands/why.d.ts.map +1 -1
  22. package/dist/commands/why.js +46 -4
  23. package/dist/commands/why.js.map +1 -1
  24. package/dist/index.js +3 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/lib/advisory-dedup.d.ts +3 -0
  27. package/dist/lib/advisory-dedup.d.ts.map +1 -0
  28. package/dist/lib/advisory-dedup.js +44 -0
  29. package/dist/lib/advisory-dedup.js.map +1 -0
  30. package/dist/lib/agent-rules.d.ts +10 -0
  31. package/dist/lib/agent-rules.d.ts.map +1 -0
  32. package/dist/lib/agent-rules.js +137 -0
  33. package/dist/lib/agent-rules.js.map +1 -0
  34. package/dist/lib/config.d.ts +4 -1
  35. package/dist/lib/config.d.ts.map +1 -1
  36. package/dist/lib/config.js +11 -0
  37. package/dist/lib/config.js.map +1 -1
  38. package/dist/lib/fetchers/confluence.d.ts +2 -0
  39. package/dist/lib/fetchers/confluence.d.ts.map +1 -1
  40. package/dist/lib/fetchers/confluence.js +10 -32
  41. package/dist/lib/fetchers/confluence.js.map +1 -1
  42. package/dist/lib/fetchers/git.d.ts +2 -0
  43. package/dist/lib/fetchers/git.d.ts.map +1 -1
  44. package/dist/lib/fetchers/git.js +5 -12
  45. package/dist/lib/fetchers/git.js.map +1 -1
  46. package/dist/lib/fetchers/github.d.ts +1 -0
  47. package/dist/lib/fetchers/github.d.ts.map +1 -1
  48. package/dist/lib/fetchers/github.js +3 -40
  49. package/dist/lib/fetchers/github.js.map +1 -1
  50. package/dist/lib/fetchers/gitlab.d.ts +1 -0
  51. package/dist/lib/fetchers/gitlab.d.ts.map +1 -1
  52. package/dist/lib/fetchers/gitlab.js +3 -21
  53. package/dist/lib/fetchers/gitlab.js.map +1 -1
  54. package/dist/lib/fetchers/jira.d.ts +2 -0
  55. package/dist/lib/fetchers/jira.d.ts.map +1 -1
  56. package/dist/lib/fetchers/jira.js +10 -51
  57. package/dist/lib/fetchers/jira.js.map +1 -1
  58. package/dist/lib/fetchers/linear.d.ts +1 -0
  59. package/dist/lib/fetchers/linear.d.ts.map +1 -1
  60. package/dist/lib/fetchers/linear.js +3 -55
  61. package/dist/lib/fetchers/linear.js.map +1 -1
  62. package/dist/lib/fetchers/notion.d.ts +1 -0
  63. package/dist/lib/fetchers/notion.d.ts.map +1 -1
  64. package/dist/lib/fetchers/notion.js +3 -44
  65. package/dist/lib/fetchers/notion.js.map +1 -1
  66. package/dist/lib/fetchers/slack.d.ts +1 -0
  67. package/dist/lib/fetchers/slack.d.ts.map +1 -1
  68. package/dist/lib/fetchers/slack.js +3 -59
  69. package/dist/lib/fetchers/slack.js.map +1 -1
  70. package/dist/lib/fetchers/teams.d.ts +1 -0
  71. package/dist/lib/fetchers/teams.d.ts.map +1 -1
  72. package/dist/lib/fetchers/teams.js +3 -59
  73. package/dist/lib/fetchers/teams.js.map +1 -1
  74. package/dist/lib/fetchers/zoom.d.ts +1 -0
  75. package/dist/lib/fetchers/zoom.d.ts.map +1 -1
  76. package/dist/lib/fetchers/zoom.js +3 -57
  77. package/dist/lib/fetchers/zoom.js.map +1 -1
  78. package/dist/lib/format-date.d.ts +7 -0
  79. package/dist/lib/format-date.d.ts.map +1 -0
  80. package/dist/lib/format-date.js +26 -0
  81. package/dist/lib/format-date.js.map +1 -0
  82. package/dist/lib/gateway-client.d.ts +9 -8
  83. package/dist/lib/gateway-client.d.ts.map +1 -1
  84. package/dist/lib/gateway-client.js +28 -3
  85. package/dist/lib/gateway-client.js.map +1 -1
  86. package/dist/lib/hook-payload.d.ts +20 -0
  87. package/dist/lib/hook-payload.d.ts.map +1 -0
  88. package/dist/lib/hook-payload.js +30 -0
  89. package/dist/lib/hook-payload.js.map +1 -0
  90. package/dist/lib/local-db.d.ts +51 -0
  91. package/dist/lib/local-db.d.ts.map +1 -0
  92. package/dist/lib/local-db.js +89 -0
  93. package/dist/lib/local-db.js.map +1 -0
  94. package/dist/lib/local-embeddings.d.ts +3 -0
  95. package/dist/lib/local-embeddings.d.ts.map +1 -0
  96. package/dist/lib/local-embeddings.js +20 -0
  97. package/dist/lib/local-embeddings.js.map +1 -0
  98. package/dist/lib/local-gateway-client.d.ts +98 -0
  99. package/dist/lib/local-gateway-client.d.ts.map +1 -0
  100. package/dist/lib/local-gateway-client.js +151 -0
  101. package/dist/lib/local-gateway-client.js.map +1 -0
  102. package/dist/lib/local-mode.d.ts +7 -0
  103. package/dist/lib/local-mode.d.ts.map +1 -0
  104. package/dist/lib/local-mode.js +39 -0
  105. package/dist/lib/local-mode.js.map +1 -0
  106. package/dist/lib/local-relationship-classifier.d.ts +14 -0
  107. package/dist/lib/local-relationship-classifier.d.ts.map +1 -0
  108. package/dist/lib/local-relationship-classifier.js +100 -0
  109. package/dist/lib/local-relationship-classifier.js.map +1 -0
  110. package/dist/lib/login-flow.d.ts +5 -0
  111. package/dist/lib/login-flow.d.ts.map +1 -0
  112. package/dist/lib/login-flow.js +64 -0
  113. package/dist/lib/login-flow.js.map +1 -0
  114. package/dist/lib/personal-import.d.ts +4 -0
  115. package/dist/lib/personal-import.d.ts.map +1 -1
  116. package/dist/lib/personal-import.js +133 -27
  117. package/dist/lib/personal-import.js.map +1 -1
  118. package/package.json +27 -15
  119. package/dist/lib/why-normalise.d.ts +0 -2
  120. package/dist/lib/why-normalise.d.ts.map +0 -1
  121. package/dist/lib/why-normalise.js +0 -39
  122. package/dist/lib/why-normalise.js.map +0 -1
@@ -0,0 +1,151 @@
1
+ import { createLocalDb } from './local-db.js';
2
+ import { cosineSimilarity, getEmbedding } from './local-embeddings.js';
3
+ import { classifyRelationship } from './local-relationship-classifier.js';
4
+ export const CONFLICT_THRESHOLD = 0.65;
5
+ // Embeddings flag a decision as a related CANDIDATE at/above this score; the
6
+ // relationship type is then assigned lazily by the LLM classifier.
7
+ export const RELATES_THRESHOLD = 0.45;
8
+ // Below this similarity between a decision and new content, the content is
9
+ // considered to have drifted from the decision.
10
+ export const DRIFT_THRESHOLD = 0.5;
11
+ export function createLocalGatewayClient(dbPath) {
12
+ const db = createLocalDb(dbPath);
13
+ async function findSimilar(embedding, topK, threshold = 0.0, excludeId) {
14
+ const all = db.getAllEmbeddings();
15
+ return all
16
+ .filter(e => e.decisionId !== excludeId)
17
+ .map(e => ({ decisionId: e.decisionId, score: cosineSimilarity(embedding, e.embedding) }))
18
+ .filter(e => e.score >= threshold)
19
+ .sort((a, b) => b.score - a.score)
20
+ .slice(0, topK);
21
+ }
22
+ // Shared ingest path: insert, embed (title + summary), detect conflicts.
23
+ // Used by both captureDecision (single, may parse a URL) and ingestBatch.
24
+ async function ingestOne(input, platform, opts = {}) {
25
+ let title = input.slice(0, 80);
26
+ let summary = input;
27
+ let sourceUrl = opts.sourceUrlOverride ?? null;
28
+ if (opts.sourceUrlOverride === undefined) {
29
+ try {
30
+ const url = new URL(input);
31
+ sourceUrl = url.href;
32
+ title = url.pathname.split('/').filter(Boolean).pop() ?? url.hostname;
33
+ summary = `Captured from ${url.hostname}`;
34
+ }
35
+ catch { /* plain text - use as-is */ }
36
+ }
37
+ if (opts.titleOverride)
38
+ title = opts.titleOverride.slice(0, 80);
39
+ const id = db.insertDecision({ title, summary, sourceUrl, platform });
40
+ // Embed title + summary so URL captures (whose summary is just "Captured
41
+ // from <host>") still carry the path-derived title's semantic content.
42
+ const embedText = title === summary ? summary : `${title}. ${summary}`;
43
+ const embedding = await getEmbedding(embedText);
44
+ db.setEmbedding(id, embedding);
45
+ const candidates = await findSimilar(embedding, 10, CONFLICT_THRESHOLD, id);
46
+ for (const c of candidates) {
47
+ db.insertLink({ sourceId: id, targetId: c.decisionId, relation: 'conflicts_with', confidence: c.score });
48
+ }
49
+ return { id, title, summary, sourceUrl, platform, conflicts: candidates };
50
+ }
51
+ return {
52
+ async whoami() {
53
+ return { email: 'local', tenantId: 'local', mode: 'local-embedded' };
54
+ },
55
+ async captureDecision(input, platform = 'cli') {
56
+ const r = await ingestOne(input, platform);
57
+ return { id: r.id, title: r.title, summary: r.summary, sourceUrl: r.sourceUrl, platform: r.platform, conflicts: r.conflicts.map(c => c.decisionId) };
58
+ },
59
+ async ingestBatch(items) {
60
+ const snapshots = [];
61
+ for (const item of items) {
62
+ const r = await ingestOne(item.raw_text, item.platform ?? 'cli', {
63
+ titleOverride: item.title,
64
+ sourceUrlOverride: item.source_url ?? null,
65
+ });
66
+ snapshots.push({
67
+ id: r.id,
68
+ title: r.title,
69
+ summary: r.summary,
70
+ analysis: {
71
+ relatedDecisions: r.conflicts.map(c => ({
72
+ id: c.decisionId,
73
+ title: db.getDecisionById(c.decisionId)?.title ?? '',
74
+ relationship: 'conflicts_with',
75
+ confidence: c.score,
76
+ })),
77
+ },
78
+ });
79
+ }
80
+ return { snapshots };
81
+ },
82
+ async searchDecisions(query, limit = 10) {
83
+ const embedding = await getEmbedding(query);
84
+ const similar = await findSimilar(embedding, limit, 0.1);
85
+ const decisions = similar
86
+ .map(s => {
87
+ const row = db.getDecisionById(s.decisionId);
88
+ return row ? { ...row, score: s.score } : null;
89
+ })
90
+ .filter((d) => d !== null);
91
+ return { decisions };
92
+ },
93
+ async checkAlignment(diff, _context) {
94
+ // Stage 1: embeddings find candidate related decisions (free, local).
95
+ const embedding = await getEmbedding(diff);
96
+ const similar = await findSimilar(embedding, 5, RELATES_THRESHOLD);
97
+ const candidates = similar
98
+ .map(s => {
99
+ const row = db.getDecisionById(s.decisionId);
100
+ return row ? { ...row, score: s.score } : null;
101
+ })
102
+ .filter((d) => d !== null);
103
+ if (!candidates.length) {
104
+ return { status: 'no_context', conflicting_decisions: [], relevant_decisions: [], message: 'No related decisions found in your local graph.' };
105
+ }
106
+ // Stage 2: type each candidate against the proposed change (LLM, user's key,
107
+ // lazy - only the few candidates we surface here). Degrades to untyped.
108
+ const subject = { title: 'Proposed change', summary: diff.slice(0, 2000) };
109
+ const relevant_decisions = [];
110
+ for (const c of candidates) {
111
+ const rel = await classifyRelationship(subject, { title: c.title, summary: c.summary });
112
+ relevant_decisions.push({
113
+ id: c.id,
114
+ title: c.title,
115
+ platform: c.platform,
116
+ source_url: c.sourceUrl,
117
+ relationship: rel?.type ?? 'relates_to',
118
+ confidence: rel?.confidence ?? c.score,
119
+ typed: rel !== null,
120
+ ...(rel?.reason ? { reason: rel.reason } : {}),
121
+ });
122
+ }
123
+ const conflicting_decisions = relevant_decisions.filter(d => d.relationship === 'conflicts_with' || d.relationship === 'contradicts');
124
+ const status = conflicting_decisions.length ? 'conflict' : 'related';
125
+ const anyTyped = relevant_decisions.some(d => d.typed);
126
+ const message = status === 'conflict'
127
+ ? `This change conflicts with ${conflicting_decisions.length} existing decision(s) across your tools - review before proceeding.`
128
+ : `Found ${relevant_decisions.length} related decision(s) to review${anyTyped ? '' : ' (set ANTHROPIC_API_KEY or OPENAI_API_KEY to type these relationships)'}.`;
129
+ return { status, conflicting_decisions, relevant_decisions, message };
130
+ },
131
+ async checkDrift(decisionId, content, _sourceType) {
132
+ const decisionEmbedding = db.getEmbedding(decisionId);
133
+ if (!decisionEmbedding)
134
+ return { decisionId, score: null, drifted: null, note: 'Decision not found or not yet embedded.' };
135
+ const contentEmbedding = await getEmbedding(content);
136
+ const score = cosineSimilarity(decisionEmbedding, contentEmbedding);
137
+ return { decisionId, score, drifted: score < DRIFT_THRESHOLD };
138
+ },
139
+ async getImpact(decisionId) {
140
+ const allLinks = db.listLinks({ decisionId });
141
+ const upstream = allLinks.filter(l => l.targetId === decisionId);
142
+ const downstream = allLinks.filter(l => l.sourceId === decisionId);
143
+ return { upstream, downstream };
144
+ },
145
+ async getConflicts() {
146
+ const links = db.listLinks({ relation: 'conflicts_with' });
147
+ return { links };
148
+ },
149
+ };
150
+ }
151
+ //# sourceMappingURL=local-gateway-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-gateway-client.js","sourceRoot":"","sources":["../../src/lib/local-gateway-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare function getLocalDbPath(): string;
2
+ export declare function initLocalMode(opts?: {
3
+ quiet?: boolean;
4
+ }): Promise<{
5
+ dbPath: string;
6
+ }>;
7
+ //# sourceMappingURL=local-mode.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-mode.d.ts","sourceRoot":"","sources":["../../src/lib/local-mode.ts"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,IAAI,MAAM,CAUvC;AAED,wBAAsB,aAAa,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO;;GAsBjE"}
@@ -0,0 +1,39 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import { createConfigStore } from './config.js';
4
+ import { createLocalDb } from './local-db.js';
5
+ import { detectEditors, writeMcpConfig } from './mcp-setup.js';
6
+ export function getLocalDbPath() {
7
+ let configDir;
8
+ if (process.platform === 'darwin') {
9
+ configDir = path.join(os.homedir(), 'Library', 'Preferences', 'align-cli');
10
+ }
11
+ else if (process.platform === 'win32') {
12
+ configDir = path.join(process.env['APPDATA'] ?? os.homedir(), 'align-cli');
13
+ }
14
+ else {
15
+ configDir = path.join(os.homedir(), '.config', 'align-cli');
16
+ }
17
+ return path.join(configDir, 'local.db');
18
+ }
19
+ export async function initLocalMode(opts = {}) {
20
+ const dbPath = getLocalDbPath();
21
+ const config = createConfigStore();
22
+ config.setLocalMode(dbPath);
23
+ // Do NOT flip the global default env to 'local'. The MCP server is wired to
24
+ // local mode via the '--env local' flag written into each editor's MCP config
25
+ // (see writeMcpConfig below), so the agent uses local mode without hijacking
26
+ // every other `align` command - those would hit a local client that does not
27
+ // implement cloud-only methods and crash.
28
+ // Initialize schema (idempotent)
29
+ const db = createLocalDb(dbPath);
30
+ db.close();
31
+ if (!opts.quiet) {
32
+ const editors = detectEditors();
33
+ for (const target of editors) {
34
+ writeMcpConfig(target, 'local');
35
+ }
36
+ }
37
+ return { dbPath };
38
+ }
39
+ //# sourceMappingURL=local-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-mode.js","sourceRoot":"","sources":["../../src/lib/local-mode.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE/D,MAAM,UAAU,cAAc;IAC5B,IAAI,SAAiB,CAAC;IACtB,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAClC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;IAC7E,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACxC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;IAC7E,CAAC;SAAM,CAAC;QACN,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA4B,EAAE;IAChE,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;IACnC,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;IAC5B,4EAA4E;IAC5E,8EAA8E;IAC9E,6EAA6E;IAC7E,6EAA6E;IAC7E,0CAA0C;IAE1C,iCAAiC;IACjC,MAAM,EAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,EAAE,CAAC,KAAK,EAAE,CAAC;IAEX,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAChB,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;QAChC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC"}
@@ -0,0 +1,14 @@
1
+ export declare const RELATIONSHIP_TYPES: readonly ["supersedes", "conflicts_with", "contradicts", "duplicates", "refines", "implements", "depends_on", "relates_to"];
2
+ export type RelationshipType = typeof RELATIONSHIP_TYPES[number];
3
+ export interface ClassifiedRelationship {
4
+ type: RelationshipType;
5
+ confidence: number;
6
+ reason?: string;
7
+ }
8
+ interface DecisionLite {
9
+ title: string;
10
+ summary: string;
11
+ }
12
+ export declare function classifyRelationship(subject: DecisionLite, candidate: DecisionLite): Promise<ClassifiedRelationship | null>;
13
+ export {};
14
+ //# sourceMappingURL=local-relationship-classifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-relationship-classifier.d.ts","sourceRoot":"","sources":["../../src/lib/local-relationship-classifier.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,kBAAkB,6HASrB,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,OAAO,kBAAkB,CAAC,MAAM,CAAC,CAAC;AAEjE,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,gBAAgB,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAYD,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,YAAY,EACrB,SAAS,EAAE,YAAY,GACtB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAIxC"}
@@ -0,0 +1,100 @@
1
+ // Stage 2 of local relationship detection: type a candidate edge (found cheaply
2
+ // via embeddings) using an LLM with the user's OWN key. Embedding similarity says
3
+ // two decisions are related; only an LLM reading both can say HOW (the taxonomy).
4
+ // Returns null when no key is configured, so callers degrade to an untyped edge.
5
+ export const RELATIONSHIP_TYPES = [
6
+ 'supersedes',
7
+ 'conflicts_with',
8
+ 'contradicts',
9
+ 'duplicates',
10
+ 'refines',
11
+ 'implements',
12
+ 'depends_on',
13
+ 'relates_to',
14
+ ];
15
+ const SYSTEM_PROMPT = "You classify how decision B relates to decision A in a software team's decision graph. " +
16
+ `Respond ONLY with compact JSON: {"type": one of [${RELATIONSHIP_TYPES.join(', ')}], "confidence": number 0-1, "reason": short string}. ` +
17
+ 'Use "conflicts_with" or "contradicts" ONLY when B genuinely opposes A - high textual similarity alone is NOT a conflict ' +
18
+ '(two decisions about the same topic often agree). Use "supersedes" when B replaces A, "relates_to" when merely related.';
19
+ function buildUserPrompt(a, b) {
20
+ return `Decision A: ${a.title}. ${a.summary}\n\nDecision B: ${b.title}. ${b.summary}`;
21
+ }
22
+ export async function classifyRelationship(subject, candidate) {
23
+ const raw = await callLlm(SYSTEM_PROMPT, buildUserPrompt(subject, candidate));
24
+ if (!raw)
25
+ return null;
26
+ return parseRelationship(raw);
27
+ }
28
+ function parseRelationship(text) {
29
+ const match = text.match(/\{[\s\S]*\}/);
30
+ if (!match)
31
+ return null;
32
+ try {
33
+ const obj = JSON.parse(match[0]);
34
+ if (typeof obj.type !== 'string' || !RELATIONSHIP_TYPES.includes(obj.type))
35
+ return null;
36
+ const confidence = typeof obj.confidence === 'number' ? Math.max(0, Math.min(1, obj.confidence)) : 0.5;
37
+ const result = { type: obj.type, confidence };
38
+ if (typeof obj.reason === 'string' && obj.reason.trim())
39
+ result.reason = obj.reason.trim();
40
+ return result;
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ // Minimal provider call: Anthropic (Haiku) preferred, then OpenAI-compatible.
47
+ // Reuses the env-key convention of local-llm.ts; returns null on any failure.
48
+ async function callLlm(system, user) {
49
+ const anthropicKey = process.env['ANTHROPIC_API_KEY'];
50
+ if (anthropicKey)
51
+ return callAnthropic(system, user, anthropicKey);
52
+ const openaiKey = process.env['OPENAI_API_KEY'];
53
+ if (openaiKey)
54
+ return callOpenAi(system, user, openaiKey);
55
+ return null;
56
+ }
57
+ async function callAnthropic(system, user, key) {
58
+ try {
59
+ const res = await fetch('https://api.anthropic.com/v1/messages', {
60
+ method: 'POST',
61
+ headers: { 'Content-Type': 'application/json', 'x-api-key': key, 'anthropic-version': '2023-06-01' },
62
+ body: JSON.stringify({
63
+ model: process.env['ALIGN_ANTHROPIC_MODEL'] ?? 'claude-haiku-4-5-20251001',
64
+ max_tokens: 256,
65
+ system,
66
+ messages: [{ role: 'user', content: user }],
67
+ }),
68
+ signal: AbortSignal.timeout(15000),
69
+ });
70
+ if (!res.ok)
71
+ return null;
72
+ const data = await res.json();
73
+ return data.content?.[0]?.text?.trim() ?? null;
74
+ }
75
+ catch {
76
+ return null;
77
+ }
78
+ }
79
+ async function callOpenAi(system, user, key) {
80
+ try {
81
+ const res = await fetch('https://api.openai.com/v1/chat/completions', {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` },
84
+ body: JSON.stringify({
85
+ model: process.env['ALIGN_OPENAI_MODEL'] ?? 'gpt-4o-mini',
86
+ max_tokens: 256,
87
+ messages: [{ role: 'system', content: system }, { role: 'user', content: user }],
88
+ }),
89
+ signal: AbortSignal.timeout(15000),
90
+ });
91
+ if (!res.ok)
92
+ return null;
93
+ const data = await res.json();
94
+ return data.choices?.[0]?.message?.content?.trim() ?? null;
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ //# sourceMappingURL=local-relationship-classifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-relationship-classifier.js","sourceRoot":"","sources":["../../src/lib/local-relationship-classifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import type { createConfigStore, EnvironmentConfig, EnvName } from './config.js';
2
+ type ConfigStore = ReturnType<typeof createConfigStore>;
3
+ export declare function loginInteractive(env: EnvironmentConfig, envName: EnvName, config: ConfigStore): Promise<boolean>;
4
+ export {};
5
+ //# sourceMappingURL=login-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login-flow.d.ts","sourceRoot":"","sources":["../../src/lib/login-flow.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAEjF,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAMxD,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,iBAAiB,EACtB,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,OAAO,CAAC,CAoDlB"}
@@ -0,0 +1,64 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import open from 'open';
4
+ import { createGatewayClient } from './gateway-client.js';
5
+ import { CLI_CALLBACK_PORTS, waitForCallback } from './cli-oauth.js';
6
+ // Browser-based CLI login. Starts a local callback server, opens the branded
7
+ // login page, exchanges the returned token, verifies it, and persists the
8
+ // token + tenant id. Returns true on success. Shared by `align login` and the
9
+ // inline login on the cloud `align setup` path.
10
+ export async function loginInteractive(env, envName, config) {
11
+ const spinner = p.spinner();
12
+ spinner.start('Opening browser for login...');
13
+ let loginUrl = '';
14
+ const callbackPromise = waitForCallback({
15
+ ports: CLI_CALLBACK_PORTS,
16
+ timeoutMs: 120_000,
17
+ onBound: async (port, nonce) => {
18
+ try {
19
+ const res = await fetch(`${env.gatewayUrl}/auth/cli-init?port=${port}&nonce=${nonce}`);
20
+ if (!res.ok)
21
+ throw new Error(`Gateway returned ${res.status}`);
22
+ const body = await res.json();
23
+ loginUrl = body.url;
24
+ await open(loginUrl).catch(() => { });
25
+ spinner.stop(`Browser opened. If nothing happened, visit:\n ${chalk.bold(loginUrl)}`);
26
+ p.log.info('Waiting for you to log in (2 min timeout)...');
27
+ }
28
+ catch (e) {
29
+ spinner.stop(`Could not open browser: ${e.message}`);
30
+ }
31
+ },
32
+ });
33
+ let result;
34
+ try {
35
+ result = await callbackPromise;
36
+ }
37
+ catch (e) {
38
+ p.log.error(`Login failed: ${e.message}`);
39
+ return false;
40
+ }
41
+ const token = result.data['token'];
42
+ if (!token) {
43
+ p.log.error('No token received. Try again or use: align login --token <token>');
44
+ return false;
45
+ }
46
+ const verifySpinner = p.spinner();
47
+ verifySpinner.start('Verifying token...');
48
+ try {
49
+ const client = createGatewayClient({ ...env, authToken: token });
50
+ const me = await client.whoami();
51
+ config.setAuthToken(envName, token);
52
+ if (me.tenant?.id)
53
+ config.setTenantId(envName, me.tenant.id);
54
+ verifySpinner.stop(`Logged in as ${me.user.email} (${me.tenant.name}) [${envName}]`);
55
+ return true;
56
+ }
57
+ catch {
58
+ verifySpinner.stop('Token saved (gateway unreachable for verification)');
59
+ config.setAuthToken(envName, token);
60
+ p.log.warn('Check the gateway is reachable with: align dev status');
61
+ return true;
62
+ }
63
+ }
64
+ //# sourceMappingURL=login-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login-flow.js","sourceRoot":"","sources":["../../src/lib/login-flow.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,12 @@
1
1
  import type { BatchIngestItem, createGatewayClient } from './gateway-client.js';
2
2
  export type PersonalImportItem = BatchIngestItem;
3
+ export declare function runWithConcurrency<T>(tasks: Array<() => Promise<T>>, concurrency: number): Promise<PromiseSettledResult<T>[]>;
3
4
  export declare function runPersonalImport(items: PersonalImportItem[], client: ReturnType<typeof createGatewayClient>, opts: {
4
5
  label: string;
5
6
  approve?: boolean;
6
7
  appUrl: string;
8
+ quiet?: boolean;
9
+ deferEnrichment?: boolean;
10
+ local?: boolean;
7
11
  }): Promise<number>;
8
12
  //# sourceMappingURL=personal-import.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"personal-import.d.ts","sourceRoot":"","sources":["../../src/lib/personal-import.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAIjD,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,kBAAkB,EAAE,EAC3B,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAC9C,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACzD,OAAO,CAAC,MAAM,CAAC,CA+DjB"}
1
+ {"version":3,"file":"personal-import.d.ts","sourceRoot":"","sources":["../../src/lib/personal-import.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,MAAM,MAAM,kBAAkB,GAAG,eAAe,CAAC;AAoDjD,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,KAAK,EAAE,KAAK,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,EAC9B,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,CAAC,CAYpC;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,kBAAkB,EAAE,EAC3B,MAAM,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,EAC9C,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACtH,OAAO,CAAC,MAAM,CAAC,CA0GjB"}
@@ -2,26 +2,95 @@ import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import * as p from '@clack/prompts';
4
4
  import { renderTable } from './table.js';
5
+ import { GatewayError } from './gateway-client.js';
5
6
  const BATCH_SIZE = 20;
7
+ const BATCH_CONCURRENCY = 3;
8
+ // Cap TOTAL concurrent /ingest/batch calls across all imports running in
9
+ // parallel (align setup imports several connectors at once). Without this,
10
+ // IMPORT_CONCURRENCY x BATCH_CONCURRENCY swamps the gateway's DB pool and
11
+ // surfaces as 500 "timeout exceeded when trying to connect" (ALI-110).
12
+ const GLOBAL_INGEST_CONCURRENCY = Number(process.env['ALIGN_INGEST_CONCURRENCY']) || 6;
13
+ const INGEST_MAX_ATTEMPTS = 3;
14
+ const INGEST_BACKOFF_MS = 250;
15
+ let activeIngests = 0;
16
+ const ingestWaiters = [];
17
+ // Acquire one of the global ingest slots, run fn, release (waking the next
18
+ // waiter). Shared across every concurrent runPersonalImport.
19
+ async function withIngestSlot(fn) {
20
+ if (activeIngests >= GLOBAL_INGEST_CONCURRENCY) {
21
+ await new Promise((resolve) => ingestWaiters.push(resolve));
22
+ }
23
+ activeIngests++;
24
+ try {
25
+ return await fn();
26
+ }
27
+ finally {
28
+ activeIngests--;
29
+ ingestWaiters.shift()?.();
30
+ }
31
+ }
32
+ // A pool-connection timeout shows up as a 5xx; network failures as status 0.
33
+ // Both are transient - retrying after a short backoff usually succeeds. 4xx
34
+ // (bad request / auth) is not retried.
35
+ function isTransientGatewayError(err) {
36
+ return err instanceof GatewayError && (err.statusCode >= 500 || err.statusCode === 0);
37
+ }
38
+ async function ingestBatchResilient(fn) {
39
+ let lastErr;
40
+ for (let attempt = 1; attempt <= INGEST_MAX_ATTEMPTS; attempt++) {
41
+ try {
42
+ return await withIngestSlot(fn);
43
+ }
44
+ catch (err) {
45
+ lastErr = err;
46
+ if (!isTransientGatewayError(err) || attempt === INGEST_MAX_ATTEMPTS)
47
+ throw err;
48
+ await new Promise((r) => setTimeout(r, INGEST_BACKOFF_MS * 2 ** (attempt - 1)));
49
+ }
50
+ }
51
+ throw lastErr;
52
+ }
53
+ export async function runWithConcurrency(tasks, concurrency) {
54
+ const results = new Array(tasks.length);
55
+ let next = 0;
56
+ async function worker() {
57
+ while (next < tasks.length) {
58
+ const i = next++;
59
+ try {
60
+ results[i] = { status: 'fulfilled', value: await tasks[i]() };
61
+ }
62
+ catch (reason) {
63
+ results[i] = { status: 'rejected', reason };
64
+ }
65
+ }
66
+ }
67
+ await Promise.all(Array.from({ length: Math.min(concurrency, tasks.length) }, worker));
68
+ return results;
69
+ }
6
70
  export async function runPersonalImport(items, client, opts) {
7
71
  if (!items.length) {
8
72
  p.log.warn(`No items found from ${opts.label}.`);
9
73
  return 0;
10
74
  }
11
- const preview = items.slice(0, 10);
12
- const more = items.length - preview.length;
13
- console.log(chalk.bold(`\nFound ${items.length} items from ${opts.label}\n`));
14
- renderTable([
15
- { header: 'SOURCE', width: 48 },
16
- { header: 'TITLE', width: 50 },
17
- ], preview.map(i => [
18
- i.source_url.replace(/https?:\/\//, '').slice(0, 46),
19
- (i.title ?? i.raw_text.split('\n')[0]).slice(0, 48),
20
- ]));
21
- if (more > 0)
22
- console.log(chalk.dim(` ...and ${more} more\n`));
23
- else
24
- console.log('');
75
+ // Quiet mode (concurrent setup imports): skip the preview table, the animated
76
+ // spinner, and the multi-line footer - they clash when several imports run at
77
+ // once. A single compact completion line is printed at the end instead.
78
+ if (!opts.quiet) {
79
+ const preview = items.slice(0, 10);
80
+ const more = items.length - preview.length;
81
+ console.log(chalk.bold(`\nFound ${items.length} items from ${opts.label}\n`));
82
+ renderTable([
83
+ { header: 'SOURCE', width: 48 },
84
+ { header: 'TITLE', width: 50 },
85
+ ], preview.map(i => [
86
+ i.source_url.replace(/https?:\/\//, '').slice(0, 46),
87
+ (i.title ?? i.raw_text.split('\n')[0]).slice(0, 48),
88
+ ]));
89
+ if (more > 0)
90
+ console.log(chalk.dim(` ...and ${more} more\n`));
91
+ else
92
+ console.log('');
93
+ }
25
94
  if (!opts.approve) {
26
95
  const confirmed = await p.confirm({
27
96
  message: `Import ${items.length} items to your decision graph?`,
@@ -37,27 +106,64 @@ export async function runPersonalImport(items, client, opts) {
37
106
  }
38
107
  let total = 0;
39
108
  let relatedCount = 0;
40
- for (let i = 0; i < batches.length; i++) {
41
- const spinner = ora(`Importing batch ${i + 1}/${batches.length}...`).start();
109
+ let done = 0;
110
+ const failures = [];
111
+ const spinner = opts.quiet ? null : ora(`Importing 0/${batches.length} batches...`).start();
112
+ const results = await runWithConcurrency(batches.map((batch) => async () => {
42
113
  try {
43
- const result = await client.ingestBatch(batches[i]);
44
- total += result.snapshots.length;
45
- for (const s of result.snapshots) {
114
+ return await ingestBatchResilient(() => client.ingestBatch(batch, { deferEnrichment: opts.deferEnrichment }));
115
+ }
116
+ finally {
117
+ done++;
118
+ if (spinner)
119
+ spinner.text = `Importing ${done}/${batches.length} batches...`;
120
+ }
121
+ }), BATCH_CONCURRENCY);
122
+ for (let i = 0; i < results.length; i++) {
123
+ const r = results[i];
124
+ if (r.status === 'fulfilled') {
125
+ total += r.value.snapshots.length;
126
+ for (const s of r.value.snapshots)
46
127
  relatedCount += s.analysis?.relatedDecisions?.length ?? 0;
47
- }
48
- spinner.succeed(`Batch ${i + 1}/${batches.length} done (${result.snapshots.length} decisions)`);
49
128
  }
50
- catch (err) {
51
- spinner.fail(`Batch ${i + 1} failed: ${err.message}`);
129
+ else {
130
+ failures.push(`Batch ${i + 1}: ${r.reason.message}`);
52
131
  }
53
132
  }
54
- console.log('');
55
- console.log(chalk.green(`${total} decisions captured from ${opts.label}.`));
133
+ // Quiet mode: one compact completion line; the shared footer is printed once
134
+ // by the caller after all concurrent imports finish.
135
+ if (opts.quiet) {
136
+ const conn = relatedCount > 0 ? `, ${relatedCount} connection${relatedCount === 1 ? '' : 's'}` : '';
137
+ const failNote = failures.length
138
+ ? chalk.yellow(` (${failures.length} batch${failures.length > 1 ? 'es' : ''} failed)`)
139
+ : '';
140
+ console.log(` ${chalk.green('✓')} ${opts.label}: ${total} decision${total === 1 ? '' : 's'}${conn}${failNote}`);
141
+ for (const f of failures)
142
+ p.log.warn(f);
143
+ return total;
144
+ }
145
+ if (failures.length === 0) {
146
+ spinner.succeed(`Imported ${total} decisions from ${opts.label}`);
147
+ }
148
+ else {
149
+ spinner.warn(`Imported ${total} decisions (${failures.length} batch${failures.length > 1 ? 'es' : ''} failed)`);
150
+ for (const f of failures)
151
+ p.log.warn(f);
152
+ }
56
153
  if (relatedCount > 0) {
57
154
  console.log(chalk.cyan(`${relatedCount} connections found with existing decisions in your graph.`));
58
155
  }
59
- console.log(chalk.dim('Relationships across all your imported tools are detected automatically in the background.'));
60
- console.log(chalk.dim(`View at: ${opts.appUrl}/decisions`));
156
+ if (opts.local) {
157
+ // Local mode: on-device SQLite, no web UI and relationships are computed
158
+ // synchronously - don't claim a background job or a browser link.
159
+ console.log(chalk.dim('Stored locally - run `align local status` to inspect your graph.'));
160
+ }
161
+ else {
162
+ // Cloud personal: CLI/MCP-native. Don't push the web UI (a team surface);
163
+ // point to the CLI instead.
164
+ console.log(chalk.dim('Relationships across all your imported tools are detected automatically in the background.'));
165
+ console.log(chalk.dim('Query your graph: align ask "..." or align decisions list'));
166
+ }
61
167
  console.log(chalk.dim('Tip: import more tools to build a richer cross-tool decision graph.'));
62
168
  console.log('');
63
169
  return total;