@ariso-ai/ivan 1.0.23 → 1.0.25

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 (185) hide show
  1. package/.github/workflows/ivanagent.yml +1 -1
  2. package/README.md +29 -0
  3. package/dist/__tests__/cli.test.d.ts +2 -0
  4. package/dist/__tests__/cli.test.d.ts.map +1 -0
  5. package/dist/__tests__/cli.test.js +100 -0
  6. package/dist/__tests__/cli.test.js.map +1 -0
  7. package/dist/database/migration.d.ts +3 -2
  8. package/dist/database/migration.d.ts.map +1 -1
  9. package/dist/database/migration.js +4 -2
  10. package/dist/database/migration.js.map +1 -1
  11. package/dist/database/migrations/015_create_learnings_tables.d.ts +3 -0
  12. package/dist/database/migrations/015_create_learnings_tables.d.ts.map +1 -0
  13. package/dist/database/migrations/015_create_learnings_tables.js +32 -0
  14. package/dist/database/migrations/015_create_learnings_tables.js.map +1 -0
  15. package/dist/database/migrations/index.d.ts +1 -0
  16. package/dist/database/migrations/index.d.ts.map +1 -1
  17. package/dist/database/migrations/index.js +2 -0
  18. package/dist/database/migrations/index.js.map +1 -1
  19. package/dist/database/types.d.ts +1 -1
  20. package/dist/database/types.d.ts.map +1 -1
  21. package/dist/database.d.ts +1 -1
  22. package/dist/database.d.ts.map +1 -1
  23. package/dist/index.js +21 -4
  24. package/dist/index.js.map +1 -1
  25. package/dist/learnings/builder.d.ts +25 -0
  26. package/dist/learnings/builder.d.ts.map +1 -0
  27. package/dist/learnings/builder.js +247 -0
  28. package/dist/learnings/builder.js.map +1 -0
  29. package/dist/learnings/database.d.ts +45 -0
  30. package/dist/learnings/database.d.ts.map +1 -0
  31. package/dist/learnings/database.js +63 -0
  32. package/dist/learnings/database.js.map +1 -0
  33. package/dist/learnings/embeddings.d.ts +20 -0
  34. package/dist/learnings/embeddings.d.ts.map +1 -0
  35. package/dist/learnings/embeddings.js +54 -0
  36. package/dist/learnings/embeddings.js.map +1 -0
  37. package/dist/learnings/evidence-writer.d.ts +13 -0
  38. package/dist/learnings/evidence-writer.d.ts.map +1 -0
  39. package/dist/learnings/evidence-writer.js +182 -0
  40. package/dist/learnings/evidence-writer.js.map +1 -0
  41. package/dist/learnings/extractor.d.ts +32 -0
  42. package/dist/learnings/extractor.d.ts.map +1 -0
  43. package/dist/learnings/extractor.js +265 -0
  44. package/dist/learnings/extractor.js.map +1 -0
  45. package/dist/learnings/github-evidence.d.ts +89 -0
  46. package/dist/learnings/github-evidence.d.ts.map +1 -0
  47. package/dist/learnings/github-evidence.js +255 -0
  48. package/dist/learnings/github-evidence.js.map +1 -0
  49. package/dist/learnings/github-ingestion.d.ts +18 -0
  50. package/dist/learnings/github-ingestion.d.ts.map +1 -0
  51. package/dist/learnings/github-ingestion.js +29 -0
  52. package/dist/learnings/github-ingestion.js.map +1 -0
  53. package/dist/learnings/heuristics.d.ts +17 -0
  54. package/dist/learnings/heuristics.d.ts.map +1 -0
  55. package/dist/learnings/heuristics.js +52 -0
  56. package/dist/learnings/heuristics.js.map +1 -0
  57. package/dist/learnings/id.d.ts +11 -0
  58. package/dist/learnings/id.d.ts.map +1 -0
  59. package/dist/learnings/id.js +36 -0
  60. package/dist/learnings/id.js.map +1 -0
  61. package/dist/learnings/index.d.ts +9 -0
  62. package/dist/learnings/index.d.ts.map +1 -0
  63. package/dist/learnings/index.js +55 -0
  64. package/dist/learnings/index.js.map +1 -0
  65. package/dist/learnings/ingest-pr-command.d.ts +8 -0
  66. package/dist/learnings/ingest-pr-command.d.ts.map +1 -0
  67. package/dist/learnings/ingest-pr-command.js +15 -0
  68. package/dist/learnings/ingest-pr-command.js.map +1 -0
  69. package/dist/learnings/ingest-repo-command.d.ts +9 -0
  70. package/dist/learnings/ingest-repo-command.d.ts.map +1 -0
  71. package/dist/learnings/ingest-repo-command.js +62 -0
  72. package/dist/learnings/ingest-repo-command.js.map +1 -0
  73. package/dist/learnings/init-command.d.ts +15 -0
  74. package/dist/learnings/init-command.d.ts.map +1 -0
  75. package/dist/learnings/init-command.js +36 -0
  76. package/dist/learnings/init-command.js.map +1 -0
  77. package/dist/learnings/install-hooks-command.d.ts +17 -0
  78. package/dist/learnings/install-hooks-command.d.ts.map +1 -0
  79. package/dist/learnings/install-hooks-command.js +241 -0
  80. package/dist/learnings/install-hooks-command.js.map +1 -0
  81. package/dist/learnings/learning-writer.d.ts +6 -0
  82. package/dist/learnings/learning-writer.d.ts.map +1 -0
  83. package/dist/learnings/learning-writer.js +42 -0
  84. package/dist/learnings/learning-writer.js.map +1 -0
  85. package/dist/learnings/parser.d.ts +12 -0
  86. package/dist/learnings/parser.d.ts.map +1 -0
  87. package/dist/learnings/parser.js +133 -0
  88. package/dist/learnings/parser.js.map +1 -0
  89. package/dist/learnings/paths.d.ts +6 -0
  90. package/dist/learnings/paths.d.ts.map +1 -0
  91. package/dist/learnings/paths.js +11 -0
  92. package/dist/learnings/paths.js.map +1 -0
  93. package/dist/learnings/query-command.d.ts +9 -0
  94. package/dist/learnings/query-command.d.ts.map +1 -0
  95. package/dist/learnings/query-command.js +37 -0
  96. package/dist/learnings/query-command.js.map +1 -0
  97. package/dist/learnings/query.d.ts +7 -0
  98. package/dist/learnings/query.d.ts.map +1 -0
  99. package/dist/learnings/query.js +65 -0
  100. package/dist/learnings/query.js.map +1 -0
  101. package/dist/learnings/rebuild-command.d.ts +8 -0
  102. package/dist/learnings/rebuild-command.d.ts.map +1 -0
  103. package/dist/learnings/rebuild-command.js +16 -0
  104. package/dist/learnings/rebuild-command.js.map +1 -0
  105. package/dist/learnings/record-types.d.ts +98 -0
  106. package/dist/learnings/record-types.d.ts.map +1 -0
  107. package/dist/learnings/record-types.js +5 -0
  108. package/dist/learnings/record-types.js.map +1 -0
  109. package/dist/learnings/repository.d.ts +18 -0
  110. package/dist/learnings/repository.d.ts.map +1 -0
  111. package/dist/learnings/repository.js +73 -0
  112. package/dist/learnings/repository.js.map +1 -0
  113. package/dist/learnings/types.d.ts +17 -0
  114. package/dist/learnings/types.d.ts.map +1 -0
  115. package/dist/learnings/types.js +4 -0
  116. package/dist/learnings/types.js.map +1 -0
  117. package/dist/learnings/validator.d.ts +14 -0
  118. package/dist/learnings/validator.d.ts.map +1 -0
  119. package/dist/learnings/validator.js +44 -0
  120. package/dist/learnings/validator.js.map +1 -0
  121. package/dist/learnings/weighting.d.ts +33 -0
  122. package/dist/learnings/weighting.d.ts.map +1 -0
  123. package/dist/learnings/weighting.js +106 -0
  124. package/dist/learnings/weighting.js.map +1 -0
  125. package/dist/services/address-executor.d.ts +0 -1
  126. package/dist/services/address-executor.d.ts.map +1 -1
  127. package/dist/services/address-executor.js +1 -4
  128. package/dist/services/address-executor.js.map +1 -1
  129. package/dist/services/address-task-executor.d.ts +0 -1
  130. package/dist/services/address-task-executor.d.ts.map +1 -1
  131. package/dist/services/address-task-executor.js +18 -14
  132. package/dist/services/address-task-executor.js.map +1 -1
  133. package/dist/services/claude-cli-executor.d.ts +1 -1
  134. package/dist/services/claude-cli-executor.d.ts.map +1 -1
  135. package/dist/services/claude-executor.d.ts +1 -1
  136. package/dist/services/claude-executor.d.ts.map +1 -1
  137. package/dist/services/claude-executor.js +2 -2
  138. package/dist/services/claude-executor.js.map +1 -1
  139. package/dist/services/git-interfaces.d.ts +3 -3
  140. package/dist/services/git-interfaces.d.ts.map +1 -1
  141. package/dist/services/git-manager-cli.d.ts +1 -1
  142. package/dist/services/git-manager-cli.d.ts.map +1 -1
  143. package/dist/services/git-manager-cli.js +111 -49
  144. package/dist/services/git-manager-cli.js.map +1 -1
  145. package/dist/services/git-manager-pat.d.ts +2 -2
  146. package/dist/services/git-manager-pat.d.ts.map +1 -1
  147. package/dist/services/git-manager-pat.js +31 -8
  148. package/dist/services/git-manager-pat.js.map +1 -1
  149. package/dist/services/github-api-client.d.ts +39 -3
  150. package/dist/services/github-api-client.d.ts.map +1 -1
  151. package/dist/services/github-api-client.js +76 -8
  152. package/dist/services/github-api-client.js.map +1 -1
  153. package/dist/services/index.d.ts +2 -1
  154. package/dist/services/index.d.ts.map +1 -1
  155. package/dist/services/index.js.map +1 -1
  156. package/dist/services/job-manager.d.ts +1 -1
  157. package/dist/services/job-manager.d.ts.map +1 -1
  158. package/dist/services/job-manager.js.map +1 -1
  159. package/dist/services/openai-service.d.ts +1 -0
  160. package/dist/services/openai-service.d.ts.map +1 -1
  161. package/dist/services/openai-service.js +54 -0
  162. package/dist/services/openai-service.js.map +1 -1
  163. package/dist/services/pr-service-cli.d.ts +1 -1
  164. package/dist/services/pr-service-cli.d.ts.map +1 -1
  165. package/dist/services/pr-service-pat.d.ts +1 -1
  166. package/dist/services/pr-service-pat.d.ts.map +1 -1
  167. package/dist/services/pr-service-pat.js +2 -2
  168. package/dist/services/pr-service-pat.js.map +1 -1
  169. package/dist/services/repository-manager-cli.d.ts +1 -1
  170. package/dist/services/repository-manager-cli.d.ts.map +1 -1
  171. package/dist/services/repository-manager-cli.js.map +1 -1
  172. package/dist/services/repository-manager-pat.d.ts +3 -3
  173. package/dist/services/repository-manager-pat.d.ts.map +1 -1
  174. package/dist/services/repository-manager-pat.js.map +1 -1
  175. package/dist/services/service-factory.d.ts +1 -1
  176. package/dist/services/service-factory.d.ts.map +1 -1
  177. package/dist/services/task-executor.d.ts +3 -2
  178. package/dist/services/task-executor.d.ts.map +1 -1
  179. package/dist/services/task-executor.js +54 -13
  180. package/dist/services/task-executor.js.map +1 -1
  181. package/dist/types/non-interactive-config.d.ts +5 -0
  182. package/dist/types/non-interactive-config.d.ts.map +1 -1
  183. package/dist/web-server.d.ts.map +1 -1
  184. package/dist/web-server.js.map +1 -1
  185. package/package.json +7 -5
@@ -0,0 +1,62 @@
1
+ // CLI handler for `ivan learnings ingest-repo`.
2
+ // Fetches all PRs for a repo, builds signals in memory, then runs a single extract + rebuild pass.
3
+ import chalk from 'chalk';
4
+ import { extractLearningsFromEvidence } from './extractor.js';
5
+ import { fetchAllPullRequestNumbers } from './github-evidence.js';
6
+ import { fetchPullRequestSignals } from './github-ingestion.js';
7
+ import { ensureLearningsDirectories, resolveLearningsRepositoryContext } from './repository.js';
8
+ /** Commander action handler: lists all PRs, fetches signals for each, then runs a single extract + rebuild. */
9
+ export async function runIngestRepoCommand(options) {
10
+ const limit = options.limit ? parseInt(options.limit, 10) : 100;
11
+ if (Number.isNaN(limit) || limit <= 0) {
12
+ throw new Error('Limit must be a positive integer');
13
+ }
14
+ const rawState = options.state ?? 'merged';
15
+ if (rawState !== 'open' &&
16
+ rawState !== 'closed' &&
17
+ rawState !== 'merged' &&
18
+ rawState !== 'all') {
19
+ throw new Error('State must be one of: open, closed, merged, all');
20
+ }
21
+ const state = rawState;
22
+ const context = resolveLearningsRepositoryContext(options.repo);
23
+ ensureLearningsDirectories(context);
24
+ console.log(chalk.gray(`Fetching PR list (state=${state}, limit=${limit})...`));
25
+ const prNumbers = await fetchAllPullRequestNumbers(options.repo, {
26
+ state,
27
+ limit
28
+ });
29
+ if (prNumbers.length === 0) {
30
+ console.log(chalk.yellow('No PRs found.'));
31
+ return;
32
+ }
33
+ console.log(chalk.gray(`Found ${prNumbers.length} PRs. Fetching signals...`));
34
+ const allSignals = [];
35
+ const mergedCache = new Map();
36
+ let failed = 0;
37
+ for (let i = 0; i < prNumbers.length; i++) {
38
+ const prNumber = prNumbers[i];
39
+ process.stdout.write(chalk.gray(` [${i + 1}/${prNumbers.length}] PR #${prNumber}... `));
40
+ try {
41
+ const { signals, contextCache } = await fetchPullRequestSignals(options.repo, prNumber);
42
+ allSignals.push(...signals);
43
+ for (const [id, ctx] of contextCache) {
44
+ mergedCache.set(id, ctx);
45
+ }
46
+ process.stdout.write(chalk.green(`${signals.length} signals\n`));
47
+ }
48
+ catch (err) {
49
+ failed++;
50
+ process.stdout.write(chalk.red(`failed (${err.message})\n`));
51
+ }
52
+ }
53
+ if (failed > 0) {
54
+ console.log(chalk.yellow(`${failed} PRs failed and were skipped.`));
55
+ }
56
+ console.log(chalk.gray('Extracting learnings...'));
57
+ const extraction = await extractLearningsFromEvidence(options.repo, allSignals, mergedCache);
58
+ console.log(chalk.green('✅ Repo ingestion complete'));
59
+ console.log(chalk.gray(`Learnings: ${extraction.writtenLearningCount}`));
60
+ console.log(chalk.gray(`DB: ${extraction.rebuild.dbPath}`));
61
+ }
62
+ //# sourceMappingURL=ingest-repo-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest-repo-command.js","sourceRoot":"","sources":["../../src/learnings/ingest-repo-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ interface InitCommandOptions {
2
+ repo: string;
3
+ }
4
+ /**
5
+ * Initialises the learnings store for `repoPath`: creates the `.ivan/` directory and canonical files.
6
+ */
7
+ export declare function initLearningsStore(repoPath: string): {
8
+ createdDirectories: string[];
9
+ createdFiles: string[];
10
+ gitignoreUpdated: boolean;
11
+ };
12
+ /** Commander action handler: calls `initLearningsStore` and prints a formatted summary. */
13
+ export declare function runInitCommand(options: InitCommandOptions): Promise<void>;
14
+ export {};
15
+ //# sourceMappingURL=init-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-command.d.ts","sourceRoot":"","sources":["../../src/learnings/init-command.ts"],"names":[],"mappings":"AAWA,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACpD,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAWA;AAED,2FAA2F;AAC3F,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -0,0 +1,36 @@
1
+ // CLI handler for `ivan learnings init`.
2
+ // Sets up the repo-local `.ivan/` structure. The derived SQLite file is tracked in git.
3
+ import chalk from 'chalk';
4
+ import { ensureCanonicalJsonlFiles, ensureGitignoreCoverage, ensureLearningsDirectories, resolveLearningsRepositoryContext } from './repository.js';
5
+ /**
6
+ * Initialises the learnings store for `repoPath`: creates the `.ivan/` directory and canonical files.
7
+ */
8
+ export function initLearningsStore(repoPath) {
9
+ const context = resolveLearningsRepositoryContext(repoPath);
10
+ const createdDirectories = ensureLearningsDirectories(context);
11
+ const createdFiles = ensureCanonicalJsonlFiles(context.repoPath);
12
+ const gitignoreUpdated = ensureGitignoreCoverage(context.repoPath);
13
+ return {
14
+ createdDirectories,
15
+ createdFiles,
16
+ gitignoreUpdated
17
+ };
18
+ }
19
+ /** Commander action handler: calls `initLearningsStore` and prints a formatted summary. */
20
+ export async function runInitCommand(options) {
21
+ const result = initLearningsStore(options.repo);
22
+ console.log(chalk.green('✅ Learnings store initialized'));
23
+ if (result.createdDirectories.length > 0) {
24
+ console.log(chalk.gray('Created directories:'));
25
+ for (const directory of result.createdDirectories) {
26
+ console.log(chalk.gray(` - ${directory}`));
27
+ }
28
+ }
29
+ if (result.createdFiles.length > 0) {
30
+ console.log(chalk.gray('Created files:'));
31
+ for (const filePath of result.createdFiles) {
32
+ console.log(chalk.gray(` - ${filePath}`));
33
+ }
34
+ }
35
+ }
36
+ //# sourceMappingURL=init-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-command.js","sourceRoot":"","sources":["../../src/learnings/init-command.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,wFAAwF;AAExF,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,0BAA0B,EAC1B,iCAAiC,EAClC,MAAM,iBAAiB,CAAC;AAMzB;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB;IAKjD,MAAM,OAAO,GAAG,iCAAiC,CAAC,QAAQ,CAAC,CAAC;IAC5D,MAAM,kBAAkB,GAAG,0BAA0B,CAAC,OAAO,CAAC,CAAC;IAC/D,MAAM,YAAY,GAAG,yBAAyB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACjE,MAAM,gBAAgB,GAAG,uBAAuB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAEnE,OAAO;QACL,kBAAkB;QAClB,YAAY;QACZ,gBAAgB;KACjB,CAAC;AACJ,CAAC;AAED,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAA2B;IAE3B,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC,CAAC;IAE1D,IAAI,MAAM,CAAC,kBAAkB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,CAAC;QAChD,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,SAAS,EAAE,CAAC,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;QAC1C,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC7C,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ interface InstallHooksCommandOptions {
2
+ repo: string;
3
+ }
4
+ /**
5
+ * Writes the two hook bash scripts, marks them executable, and upserts the hook entries
6
+ * into `.claude/settings.json`. Safe to re-run; existing scripts and settings are replaced.
7
+ */
8
+ export declare function installLearningsHooks(repoPath: string): {
9
+ settingsPath: string;
10
+ createdScripts: string[];
11
+ updatedScripts: boolean;
12
+ updatedSettings: boolean;
13
+ };
14
+ /** Commander action handler: calls `installLearningsHooks` and prints a formatted summary. */
15
+ export declare function runInstallHooksCommand(options: InstallHooksCommandOptions): Promise<void>;
16
+ export {};
17
+ //# sourceMappingURL=install-hooks-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-hooks-command.d.ts","sourceRoot":"","sources":["../../src/learnings/install-hooks-command.ts"],"names":[],"mappings":"AASA,UAAU,0BAA0B;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAqBD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAmCA;AAED,8FAA8F;AAC9F,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
@@ -0,0 +1,241 @@
1
+ // CLI handler for `ivan learnings install-hooks`.
2
+ // Writes two bash scripts and wires them into `.claude/settings.json` so that
3
+ // learnings are surfaced automatically on each prompt submission and tool use.
4
+ import chalk from 'chalk';
5
+ import { createHash } from 'crypto';
6
+ import fs from 'fs';
7
+ import path from 'path';
8
+ /**
9
+ * Writes the two hook bash scripts, marks them executable, and upserts the hook entries
10
+ * into `.claude/settings.json`. Safe to re-run; existing scripts and settings are replaced.
11
+ */
12
+ export function installLearningsHooks(repoPath) {
13
+ const resolvedRepoPath = path.resolve(repoPath);
14
+ assertDirectoryExists(resolvedRepoPath);
15
+ const claudeDir = path.join(resolvedRepoPath, '.claude');
16
+ const hooksDir = path.join(claudeDir, 'hooks');
17
+ const logsDir = path.join(hooksDir, 'logs');
18
+ fs.mkdirSync(logsDir, { recursive: true });
19
+ const userPromptScriptPath = path.join(hooksDir, 'ivan-learnings-user-prompt.sh');
20
+ const postEditScriptPath = path.join(hooksDir, 'ivan-learnings-post-edit.sh');
21
+ const updatedScripts = writeScriptIfChanged(userPromptScriptPath, buildUserPromptScript());
22
+ writeScriptIfChanged(postEditScriptPath, buildPostEditScript());
23
+ const settingsPath = path.join(claudeDir, 'settings.json');
24
+ removeStaleHookFiles(hooksDir);
25
+ const updatedSettings = upsertClaudeSettings(settingsPath, resolvedRepoPath, {
26
+ userPromptScriptPath,
27
+ postEditScriptPath
28
+ });
29
+ return {
30
+ settingsPath,
31
+ createdScripts: [userPromptScriptPath, postEditScriptPath],
32
+ updatedScripts,
33
+ updatedSettings
34
+ };
35
+ }
36
+ /** Commander action handler: calls `installLearningsHooks` and prints a formatted summary. */
37
+ export async function runInstallHooksCommand(options) {
38
+ const result = installLearningsHooks(options.repo);
39
+ console.log(chalk.green('✅ Claude hook integration installed'));
40
+ console.log(chalk.gray('Installed hooks: UserPromptSubmit, PostToolUse(Edit|Write|MultiEdit)'));
41
+ console.log(chalk.gray(`Settings file: ${result.settingsPath}`));
42
+ console.log(chalk.gray('Hook scripts:'));
43
+ for (const scriptPath of result.createdScripts) {
44
+ console.log(chalk.gray(` - ${scriptPath}`));
45
+ }
46
+ console.log(chalk.gray(result.updatedScripts
47
+ ? 'Updated hook scripts'
48
+ : 'Hook scripts already up to date'));
49
+ console.log(chalk.gray(result.updatedSettings
50
+ ? 'Updated .claude/settings.json with Ivan learnings hooks'
51
+ : '.claude/settings.json already matched the Ivan learnings hooks'));
52
+ }
53
+ /**
54
+ * Reads `.claude/settings.json`, merges the two hook entries (replacing any existing
55
+ * ivan-learnings hooks by script name), and writes the file only if the content changed.
56
+ * Returns true when the file was written.
57
+ */
58
+ /**
59
+ * Writes `content` to `scriptPath` only when the file is absent or its content
60
+ * differs. Marks the file executable. Returns true if the file was written.
61
+ */
62
+ function sha256(content) {
63
+ return createHash('sha256').update(content, 'utf8').digest('hex');
64
+ }
65
+ function writeScriptIfChanged(scriptPath, content) {
66
+ if (fs.existsSync(scriptPath)) {
67
+ const existingHash = sha256(fs.readFileSync(scriptPath, 'utf8'));
68
+ if (existingHash === sha256(content)) {
69
+ return false;
70
+ }
71
+ }
72
+ fs.writeFileSync(scriptPath, content, 'utf8');
73
+ fs.chmodSync(scriptPath, 0o755);
74
+ return true;
75
+ }
76
+ function upsertClaudeSettings(settingsPath, repoPath, scriptPaths) {
77
+ const existingSettings = readClaudeSettings(settingsPath);
78
+ const existingHooks = existingSettings.hooks ?? {};
79
+ const nextSettings = {
80
+ ...existingSettings,
81
+ hooks: { ...existingHooks }
82
+ };
83
+ const userPromptCommand = buildHookCommand(repoPath, scriptPaths.userPromptScriptPath);
84
+ const postEditCommand = buildHookCommand(repoPath, scriptPaths.postEditScriptPath);
85
+ const updatedHooks = {
86
+ ...existingHooks,
87
+ UserPromptSubmit: upsertHookEntry(existingHooks.UserPromptSubmit ?? [], {
88
+ hooks: [{ type: 'command', command: userPromptCommand, timeout: 10 }]
89
+ }, ['ivan-learnings-user-prompt.sh']),
90
+ PostToolUse: upsertHookEntry(existingHooks.PostToolUse ?? [], {
91
+ matcher: 'Edit|Write|MultiEdit',
92
+ hooks: [{ type: 'command', command: postEditCommand, timeout: 10 }]
93
+ }, ['ivan-learnings-post-edit.sh'])
94
+ };
95
+ // Remove any stale Stop hook entries that reference ivan-learnings
96
+ if (updatedHooks.Stop) {
97
+ const filtered = updatedHooks.Stop.filter((entry) => !entry.hooks.some((hook) => hook.command.includes('ivan-learnings')));
98
+ if (filtered.length === 0) {
99
+ delete updatedHooks.Stop;
100
+ }
101
+ else {
102
+ updatedHooks.Stop = filtered;
103
+ }
104
+ }
105
+ nextSettings.hooks = updatedHooks;
106
+ const previous = JSON.stringify(existingSettings, null, 2);
107
+ const next = `${JSON.stringify(nextSettings, null, 2)}\n`;
108
+ if (previous === next.trimEnd()) {
109
+ return false;
110
+ }
111
+ fs.writeFileSync(settingsPath, next, 'utf8');
112
+ return true;
113
+ }
114
+ /**
115
+ * Removes any existing hook entries whose command contains one of `scriptNameMarkers`,
116
+ * then appends `nextEntry`. This ensures exactly one ivan-learnings entry per hook type.
117
+ */
118
+ function upsertHookEntry(existingEntries, nextEntry, scriptNameMarkers) {
119
+ const filtered = existingEntries.filter((entry) => !entry.hooks.some((hook) => scriptNameMarkers.some((marker) => hook.command.includes(marker))));
120
+ filtered.push(nextEntry);
121
+ return filtered;
122
+ }
123
+ /** Reads and parses `.claude/settings.json`; returns an empty object if the file is absent or empty. */
124
+ function readClaudeSettings(settingsPath) {
125
+ if (!fs.existsSync(settingsPath)) {
126
+ return {};
127
+ }
128
+ const raw = fs.readFileSync(settingsPath, 'utf8').trim();
129
+ if (!raw) {
130
+ return {};
131
+ }
132
+ const parsed = JSON.parse(raw);
133
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
134
+ throw new Error(`Expected ${settingsPath} to contain a JSON object`);
135
+ }
136
+ return parsed;
137
+ }
138
+ /**
139
+ * Builds the hook command string using a `$CLAUDE_PROJECT_DIR`-relative path so that
140
+ * the committed settings.json works on any machine without hardcoded absolute paths.
141
+ */
142
+ function buildHookCommand(repoPath, scriptPath) {
143
+ const relative = path.relative(repoPath, scriptPath);
144
+ return `bash "$CLAUDE_PROJECT_DIR/${relative}"`;
145
+ }
146
+ /** Generates the `UserPromptSubmit` hook script that queries learnings using the user's prompt text. */
147
+ function buildUserPromptScript() {
148
+ return `#!/usr/bin/env bash
149
+ set -uo pipefail
150
+
151
+ ivan_entry="$(command -v ivan || true)"
152
+
153
+ payload="$(mktemp)"
154
+ trap 'rm -f "$payload"' EXIT
155
+ cat > "$payload"
156
+
157
+ project_dir="\${CLAUDE_PROJECT_DIR:-$(jq -r '.cwd // empty' "$payload")}"
158
+ if [[ -z "$project_dir" ]]; then
159
+ exit 0
160
+ fi
161
+ if [[ -z "$ivan_entry" ]]; then
162
+ exit 0
163
+ fi
164
+ log_dir="$project_dir/.claude/hooks/logs"
165
+ mkdir -p "$log_dir"
166
+
167
+ repo="$(jq -r '.cwd // empty' "$payload")"
168
+ prompt="$(jq -r '.prompt // empty' "$payload")"
169
+
170
+ if [[ -z "$repo" || -z "$prompt" ]]; then
171
+ exit 0
172
+ fi
173
+
174
+ output="$("$ivan_entry" learnings query --repo "$repo" --text "$prompt" --limit 3 2>>"$log_dir/query.stderr")" || true
175
+
176
+ if [[ -z "$output" || "$output" == *"No learnings matched that query."* ]]; then
177
+ exit 0
178
+ fi
179
+
180
+ printf 'Local learnings relevant to this prompt:\\n%s\\n' "$output"
181
+ `;
182
+ }
183
+ /** Generates the `PostToolUse` hook script that queries learnings after each Edit/Write/MultiEdit tool call. */
184
+ function buildPostEditScript() {
185
+ return `#!/usr/bin/env bash
186
+ set -uo pipefail
187
+
188
+ ivan_entry="$(command -v ivan || true)"
189
+
190
+ payload="$(mktemp)"
191
+ trap 'rm -f "$payload"' EXIT
192
+ cat > "$payload"
193
+
194
+ project_dir="\${CLAUDE_PROJECT_DIR:-$(jq -r '.cwd // empty' "$payload")}"
195
+ if [[ -z "$project_dir" ]]; then
196
+ exit 0
197
+ fi
198
+ if [[ -z "$ivan_entry" ]]; then
199
+ exit 0
200
+ fi
201
+ log_dir="$project_dir/.claude/hooks/logs"
202
+ mkdir -p "$log_dir"
203
+
204
+ repo="$(jq -r '.cwd // empty' "$payload")"
205
+ tool_name="$(jq -r '.tool_name // empty' "$payload")"
206
+ tool_input="$(jq -c '.tool_input // {}' "$payload")"
207
+
208
+ if [[ -z "$repo" || -z "$tool_name" ]]; then
209
+ exit 0
210
+ fi
211
+
212
+ query_text="recent file changes after tool: $tool_name; input: $tool_input"
213
+
214
+ output="$("$ivan_entry" learnings query --repo "$repo" --text "$query_text" --limit 3 2>>"$log_dir/query.stderr")" || true
215
+
216
+ if [[ -z "$output" || "$output" == *"No learnings matched that query."* ]]; then
217
+ exit 0
218
+ fi
219
+
220
+ printf 'Local learnings relevant after edit:\\n%s\\n' "$output"
221
+ `;
222
+ }
223
+ /** Deletes stale hook files that are no longer managed by install-hooks (e.g. ivan-learnings-stop.sh). */
224
+ function removeStaleHookFiles(hooksDir) {
225
+ const stale = ['ivan-learnings-stop.sh'];
226
+ for (const name of stale) {
227
+ const filePath = path.join(hooksDir, name);
228
+ if (fs.existsSync(filePath)) {
229
+ fs.unlinkSync(filePath);
230
+ }
231
+ }
232
+ }
233
+ function assertDirectoryExists(repoPath) {
234
+ if (!fs.existsSync(repoPath)) {
235
+ throw new Error(`Repository path does not exist: ${repoPath}`);
236
+ }
237
+ if (!fs.statSync(repoPath).isDirectory()) {
238
+ throw new Error(`Repository path is not a directory: ${repoPath}`);
239
+ }
240
+ }
241
+ //# sourceMappingURL=install-hooks-command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-hooks-command.js","sourceRoot":"","sources":["../../src/learnings/install-hooks-command.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { LearningRecord } from './record-types.js';
2
+ /**
3
+ * Replaces this repository's records inside `.ivan/lessons.jsonl` and returns the `{file}#L{n}` paths.
4
+ */
5
+ export declare function writeLearningRecords(repoPath: string, records: LearningRecord[]): string[];
6
+ //# sourceMappingURL=learning-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learning-writer.d.ts","sourceRoot":"","sources":["../../src/learnings/learning-writer.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,EAAE,GACxB,MAAM,EAAE,CAYV"}
@@ -0,0 +1,42 @@
1
+ // Persists extracted LearningRecord objects to `.ivan/lessons.jsonl`.
2
+ // This is a full-replace write: the entire file is rewritten on each call.
3
+ import fs from 'fs';
4
+ import { LESSONS_JSONL_RELATIVE_PATH, resolveCanonicalLearningsPath } from './paths.js';
5
+ /**
6
+ * Replaces this repository's records inside `.ivan/lessons.jsonl` and returns the `{file}#L{n}` paths.
7
+ */
8
+ export function writeLearningRecords(repoPath, records) {
9
+ const filePath = resolveCanonicalLearningsPath(repoPath, 'lessons.jsonl');
10
+ const normalizedRecords = [...records]
11
+ .sort((left, right) => left.id.localeCompare(right.id))
12
+ .map((record) => ({ ...record, sourcePath: learningSourcePath() }));
13
+ const nextContent = normalizedRecords
14
+ .map((record) => `${JSON.stringify(serializeLearningRecord(record))}\n`)
15
+ .join('');
16
+ fs.writeFileSync(filePath, nextContent, 'utf8');
17
+ return normalizedRecords.map((_, index) => `${filePath}#L${index + 1}`);
18
+ }
19
+ /** Produces the plain-object form of a `LearningRecord` for JSON serialization, omitting `type` and `sourcePath`. */
20
+ function serializeLearningRecord(record) {
21
+ return {
22
+ id: record.id,
23
+ kind: record.kind,
24
+ statement: record.statement,
25
+ status: record.status,
26
+ created_at: record.created_at,
27
+ updated_at: record.updated_at,
28
+ source_type: record.source_type,
29
+ source_url: record.source_url,
30
+ title: record.title,
31
+ rationale: record.rationale,
32
+ applicability: record.applicability,
33
+ confidence: record.confidence,
34
+ embedding: record.embedding,
35
+ embeddingInputHash: record.embeddingInputHash
36
+ };
37
+ }
38
+ /** Returns the canonical relative path for the lessons JSONL file (without a line number). */
39
+ function learningSourcePath() {
40
+ return LESSONS_JSONL_RELATIVE_PATH;
41
+ }
42
+ //# sourceMappingURL=learning-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"learning-writer.js","sourceRoot":"","sources":["../../src/learnings/learning-writer.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,2EAA2E;AAE3E,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EACL,2BAA2B,EAC3B,6BAA6B,EAC9B,MAAM,YAAY,CAAC;AAGpB;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAAgB,EAChB,OAAyB;IAEzB,MAAM,QAAQ,GAAG,6BAA6B,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;IAC1E,MAAM,iBAAiB,GAAG,CAAC,GAAG,OAAO,CAAC;SACnC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;SACtD,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,kBAAkB,EAAE,EAAE,CAAC,CAAC,CAAC;IAEtE,MAAM,WAAW,GAAG,iBAAiB;SAClC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,uBAAuB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC;SACvE,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IAEhD,OAAO,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,QAAQ,KAAK,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;AAC1E,CAAC;AAED,qHAAqH;AACrH,SAAS,uBAAuB,CAC9B,MAAsB;IAEtB,OAAO;QACL,EAAE,EAAE,MAAM,CAAC,EAAE;QACb,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;KAC9C,CAAC;AACJ,CAAC;AAED,8FAA8F;AAC9F,SAAS,kBAAkB;IACzB,OAAO,2BAA2B,CAAC;AACrC,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { LearningsDataset } from './record-types.js';
2
+ /** Reads and sorts the canonical lessons JSONL for `repoPath`, returning a `LearningsDataset`. */
3
+ export declare function loadCanonicalRecords(repoPath: string): LearningsDataset;
4
+ /** Comparator that orders by `sourcePath` first, then `id`, both lexicographically. */
5
+ export declare function sortByPathThenId(left: {
6
+ sourcePath: string;
7
+ id: string;
8
+ }, right: {
9
+ sourcePath: string;
10
+ id: string;
11
+ }): number;
12
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/learnings/parser.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,mBAAmB,CAAC;AAI1E,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAKvE;AAqJD,uFAAuF;AACvF,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EACxC,KAAK,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACxC,MAAM,CAKR"}
@@ -0,0 +1,133 @@
1
+ // Reads the canonical JSONL file from a repository's `.ivan/` directory and
2
+ // deserializes it into typed in-memory records. The parser is additive: missing
3
+ // optional fields are silently dropped rather than treated as errors.
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { resolveCanonicalLearningsPath } from './paths.js';
7
+ /** Reads and sorts the canonical lessons JSONL for `repoPath`, returning a `LearningsDataset`. */
8
+ export function loadCanonicalRecords(repoPath) {
9
+ const resolvedRepoPath = path.resolve(repoPath);
10
+ return {
11
+ learnings: readLearningRecords(resolvedRepoPath).sort(sortByPathThenId)
12
+ };
13
+ }
14
+ /** Reads `.ivan/lessons.jsonl` and parses it into `LearningRecord[]`. */
15
+ function readLearningRecords(repoPath) {
16
+ const filePath = resolveCanonicalLearningsPath(repoPath, 'lessons.jsonl');
17
+ return readJsonlFile(filePath, repoPath, (sourcePath, record) => ({
18
+ type: 'learning',
19
+ sourcePath,
20
+ id: getRequiredString(record, 'id', sourcePath),
21
+ kind: getRequiredString(record, 'kind', sourcePath),
22
+ statement: getRequiredString(record, 'statement', sourcePath),
23
+ status: getOptionalString(record, 'status') ?? 'active',
24
+ tags: getStringArray(record, 'tags'),
25
+ created_at: getRequiredString(record, 'created_at', sourcePath),
26
+ updated_at: getRequiredString(record, 'updated_at', sourcePath),
27
+ ...omitUndefined({
28
+ source_type: getOptionalString(record, 'source_type'),
29
+ source_url: getOptionalString(record, 'source_url'),
30
+ title: getOptionalString(record, 'title'),
31
+ rationale: getOptionalString(record, 'rationale'),
32
+ applicability: getOptionalString(record, 'applicability'),
33
+ confidence: getOptionalNumber(record, 'confidence'),
34
+ embedding: getOptionalNumberArray(record, 'embedding'),
35
+ embeddingInputHash: getOptionalString(record, 'embeddingInputHash')
36
+ })
37
+ }));
38
+ }
39
+ /**
40
+ * Reads a JSONL file line by line, calls `parser` with a `{file}#L{n}` source path per line,
41
+ * and accumulates results. Empty lines and absent files are silently skipped.
42
+ */
43
+ function readJsonlFile(filePath, repoPath, parser) {
44
+ if (!fs.existsSync(filePath)) {
45
+ return [];
46
+ }
47
+ const sourceFile = toCanonicalPath(repoPath, filePath);
48
+ const lines = fs.readFileSync(filePath, 'utf8').split('\n');
49
+ const records = [];
50
+ for (let index = 0; index < lines.length; index += 1) {
51
+ const rawLine = lines[index].trim();
52
+ if (!rawLine) {
53
+ continue;
54
+ }
55
+ const parsed = JSON.parse(rawLine);
56
+ records.push(parser(`${sourceFile}#L${index + 1}`, parsed));
57
+ }
58
+ return records;
59
+ }
60
+ /** Reads a string field that must be present and non-empty; throws with `sourcePath` context if missing. */
61
+ function getRequiredString(record, key, sourcePath) {
62
+ const value = getOptionalString(record, key);
63
+ if (!value) {
64
+ throw new Error(`Missing required field "${key}" in ${sourcePath}`);
65
+ }
66
+ return value;
67
+ }
68
+ /** Returns a trimmed string value or `undefined` when the field is absent, null, an array, or blank. */
69
+ function getOptionalString(record, key) {
70
+ const value = record[key];
71
+ if (value === undefined || value === null || Array.isArray(value)) {
72
+ return undefined;
73
+ }
74
+ return String(value).trim() || undefined;
75
+ }
76
+ /** Coerces a JSON value to a number; returns `undefined` for absent, null, blank, or non-finite values. */
77
+ function getOptionalNumber(record, key) {
78
+ const value = record[key];
79
+ if (value === undefined || value === null || Array.isArray(value)) {
80
+ return undefined;
81
+ }
82
+ if (typeof value === 'number') {
83
+ return Number.isFinite(value) ? value : undefined;
84
+ }
85
+ const str = String(value).trim();
86
+ if (str === '') {
87
+ return undefined;
88
+ }
89
+ const parsed = Number(str);
90
+ return Number.isFinite(parsed) ? parsed : undefined;
91
+ }
92
+ /** Returns a `number[]` from a JSON field, or `undefined` when the field is absent or null. */
93
+ function getOptionalNumberArray(record, key) {
94
+ const value = record[key];
95
+ if (value === undefined || value === null) {
96
+ return undefined;
97
+ }
98
+ if (!Array.isArray(value)) {
99
+ return undefined;
100
+ }
101
+ return value.map((item) => (typeof item === 'number' ? item : Number(item)));
102
+ }
103
+ /** Returns a `string[]` from a JSON field, gracefully handling missing, scalar, or array values. */
104
+ function getStringArray(record, key) {
105
+ const value = record[key];
106
+ if (value === undefined || value === null || value === '') {
107
+ return [];
108
+ }
109
+ if (!Array.isArray(value)) {
110
+ return [String(value).trim()].filter(Boolean);
111
+ }
112
+ return value.map((item) => String(item).trim()).filter(Boolean);
113
+ }
114
+ /** Converts an absolute file path to a forward-slash relative path from `repoPath`. */
115
+ function toCanonicalPath(repoPath, filePath) {
116
+ return path.relative(repoPath, filePath).split(path.sep).join('/');
117
+ }
118
+ /** Comparator that orders by `sourcePath` first, then `id`, both lexicographically. */
119
+ export function sortByPathThenId(left, right) {
120
+ return (left.sourcePath.localeCompare(right.sourcePath) ||
121
+ left.id.localeCompare(right.id));
122
+ }
123
+ /** Returns a copy of `obj` with all `undefined` values removed. */
124
+ function omitUndefined(obj) {
125
+ const result = {};
126
+ for (const [key, value] of Object.entries(obj)) {
127
+ if (value !== undefined) {
128
+ result[key] = value;
129
+ }
130
+ }
131
+ return result;
132
+ }
133
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/learnings/parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export declare const CANONICAL_LEARNINGS_ROOT = ".ivan";
2
+ export declare const LESSONS_JSONL_RELATIVE_PATH = ".ivan/lessons.jsonl";
3
+ export declare const LEARNINGS_DB_RELATIVE_PATH = ".ivan/db.sqlite";
4
+ export declare function canonicalLearningsPath(...segments: string[]): string;
5
+ export declare function resolveCanonicalLearningsPath(repoPath: string, ...segments: string[]): string;
6
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/learnings/paths.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAChD,eAAO,MAAM,2BAA2B,wBAA8C,CAAC;AACvF,eAAO,MAAM,0BAA0B,oBAA0C,CAAC;AAElF,wBAAgB,sBAAsB,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAEpE;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,MAAM,EAChB,GAAG,QAAQ,EAAE,MAAM,EAAE,GACpB,MAAM,CAER"}