link-agents 0.9.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 (184) hide show
  1. package/AGENTS.md +127 -0
  2. package/README.md +93 -0
  3. package/cursor-rules-notes.md +23 -0
  4. package/dist/cli/interactive.d.ts +9 -0
  5. package/dist/cli/interactive.d.ts.map +1 -0
  6. package/dist/cli/interactive.js +1176 -0
  7. package/dist/cli/interactive.js.map +1 -0
  8. package/dist/cli/options.d.ts +3 -0
  9. package/dist/cli/options.d.ts.map +1 -0
  10. package/dist/cli/options.js +107 -0
  11. package/dist/cli/options.js.map +1 -0
  12. package/dist/cli/options.spec.d.ts +2 -0
  13. package/dist/cli/options.spec.d.ts.map +1 -0
  14. package/dist/cli/options.spec.js +74 -0
  15. package/dist/cli/options.spec.js.map +1 -0
  16. package/dist/clients/definitions.d.ts +5 -0
  17. package/dist/clients/definitions.d.ts.map +1 -0
  18. package/dist/clients/definitions.js +82 -0
  19. package/dist/clients/definitions.js.map +1 -0
  20. package/dist/clients/definitions.spec.d.ts +2 -0
  21. package/dist/clients/definitions.spec.d.ts.map +1 -0
  22. package/dist/clients/definitions.spec.js +135 -0
  23. package/dist/clients/definitions.spec.js.map +1 -0
  24. package/dist/commands/doctor.d.ts +3 -0
  25. package/dist/commands/doctor.d.ts.map +1 -0
  26. package/dist/commands/doctor.js +81 -0
  27. package/dist/commands/doctor.js.map +1 -0
  28. package/dist/commands/restore.d.ts +3 -0
  29. package/dist/commands/restore.d.ts.map +1 -0
  30. package/dist/commands/restore.js +36 -0
  31. package/dist/commands/restore.js.map +1 -0
  32. package/dist/commands/sync.d.ts +3 -0
  33. package/dist/commands/sync.d.ts.map +1 -0
  34. package/dist/commands/sync.js +193 -0
  35. package/dist/commands/sync.js.map +1 -0
  36. package/dist/index.d.ts +3 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +30 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/types/index.d.ts +98 -0
  41. package/dist/types/index.d.ts.map +1 -0
  42. package/dist/types/index.js +2 -0
  43. package/dist/types/index.js.map +1 -0
  44. package/dist/utils/apply.d.ts +22 -0
  45. package/dist/utils/apply.d.ts.map +1 -0
  46. package/dist/utils/apply.js +215 -0
  47. package/dist/utils/apply.js.map +1 -0
  48. package/dist/utils/bootstrap.d.ts +18 -0
  49. package/dist/utils/bootstrap.d.ts.map +1 -0
  50. package/dist/utils/bootstrap.js +31 -0
  51. package/dist/utils/bootstrap.js.map +1 -0
  52. package/dist/utils/bootstrap.spec.d.ts +2 -0
  53. package/dist/utils/bootstrap.spec.d.ts.map +1 -0
  54. package/dist/utils/bootstrap.spec.js +92 -0
  55. package/dist/utils/bootstrap.spec.js.map +1 -0
  56. package/dist/utils/canonical.d.ts +17 -0
  57. package/dist/utils/canonical.d.ts.map +1 -0
  58. package/dist/utils/canonical.js +136 -0
  59. package/dist/utils/canonical.js.map +1 -0
  60. package/dist/utils/canonicalState.d.ts +19 -0
  61. package/dist/utils/canonicalState.d.ts.map +1 -0
  62. package/dist/utils/canonicalState.js +21 -0
  63. package/dist/utils/canonicalState.js.map +1 -0
  64. package/dist/utils/cursorHistory.d.ts +7 -0
  65. package/dist/utils/cursorHistory.d.ts.map +1 -0
  66. package/dist/utils/cursorHistory.js +54 -0
  67. package/dist/utils/cursorHistory.js.map +1 -0
  68. package/dist/utils/cursorPaths.d.ts +3 -0
  69. package/dist/utils/cursorPaths.d.ts.map +1 -0
  70. package/dist/utils/cursorPaths.js +17 -0
  71. package/dist/utils/cursorPaths.js.map +1 -0
  72. package/dist/utils/discovery.d.ts +8 -0
  73. package/dist/utils/discovery.d.ts.map +1 -0
  74. package/dist/utils/discovery.js +93 -0
  75. package/dist/utils/discovery.js.map +1 -0
  76. package/dist/utils/frontmatter.d.ts +32 -0
  77. package/dist/utils/frontmatter.d.ts.map +1 -0
  78. package/dist/utils/frontmatter.js +263 -0
  79. package/dist/utils/frontmatter.js.map +1 -0
  80. package/dist/utils/frontmatter.spec.d.ts +2 -0
  81. package/dist/utils/frontmatter.spec.d.ts.map +1 -0
  82. package/dist/utils/frontmatter.spec.js +264 -0
  83. package/dist/utils/frontmatter.spec.js.map +1 -0
  84. package/dist/utils/fs.d.ts +27 -0
  85. package/dist/utils/fs.d.ts.map +1 -0
  86. package/dist/utils/fs.js +137 -0
  87. package/dist/utils/fs.js.map +1 -0
  88. package/dist/utils/fs.spec.d.ts +2 -0
  89. package/dist/utils/fs.spec.d.ts.map +1 -0
  90. package/dist/utils/fs.spec.js +73 -0
  91. package/dist/utils/fs.spec.js.map +1 -0
  92. package/dist/utils/gitignore.d.ts +10 -0
  93. package/dist/utils/gitignore.d.ts.map +1 -0
  94. package/dist/utils/gitignore.js +63 -0
  95. package/dist/utils/gitignore.js.map +1 -0
  96. package/dist/utils/manifest.d.ts +28 -0
  97. package/dist/utils/manifest.d.ts.map +1 -0
  98. package/dist/utils/manifest.js +89 -0
  99. package/dist/utils/manifest.js.map +1 -0
  100. package/dist/utils/mcp.d.ts +73 -0
  101. package/dist/utils/mcp.d.ts.map +1 -0
  102. package/dist/utils/mcp.js +529 -0
  103. package/dist/utils/mcp.js.map +1 -0
  104. package/dist/utils/mcp.spec.d.ts +2 -0
  105. package/dist/utils/mcp.spec.d.ts.map +1 -0
  106. package/dist/utils/mcp.spec.js +488 -0
  107. package/dist/utils/mcp.spec.js.map +1 -0
  108. package/dist/utils/merge.d.ts +17 -0
  109. package/dist/utils/merge.d.ts.map +1 -0
  110. package/dist/utils/merge.js +45 -0
  111. package/dist/utils/merge.js.map +1 -0
  112. package/dist/utils/merge.spec.d.ts +2 -0
  113. package/dist/utils/merge.spec.d.ts.map +1 -0
  114. package/dist/utils/merge.spec.js +134 -0
  115. package/dist/utils/merge.spec.js.map +1 -0
  116. package/dist/utils/paths.d.ts +11 -0
  117. package/dist/utils/paths.d.ts.map +1 -0
  118. package/dist/utils/paths.js +164 -0
  119. package/dist/utils/paths.js.map +1 -0
  120. package/dist/utils/paths.spec.d.ts +2 -0
  121. package/dist/utils/paths.spec.d.ts.map +1 -0
  122. package/dist/utils/paths.spec.js +282 -0
  123. package/dist/utils/paths.spec.js.map +1 -0
  124. package/dist/utils/plan.d.ts +7 -0
  125. package/dist/utils/plan.d.ts.map +1 -0
  126. package/dist/utils/plan.js +118 -0
  127. package/dist/utils/plan.js.map +1 -0
  128. package/dist/utils/plan.spec.d.ts +2 -0
  129. package/dist/utils/plan.spec.d.ts.map +1 -0
  130. package/dist/utils/plan.spec.js +420 -0
  131. package/dist/utils/plan.spec.js.map +1 -0
  132. package/dist/utils/reporting.d.ts +21 -0
  133. package/dist/utils/reporting.d.ts.map +1 -0
  134. package/dist/utils/reporting.js +82 -0
  135. package/dist/utils/reporting.js.map +1 -0
  136. package/dist/utils/reporting.spec.d.ts +2 -0
  137. package/dist/utils/reporting.spec.d.ts.map +1 -0
  138. package/dist/utils/reporting.spec.js +78 -0
  139. package/dist/utils/reporting.spec.js.map +1 -0
  140. package/dist/utils/reset.d.ts +14 -0
  141. package/dist/utils/reset.d.ts.map +1 -0
  142. package/dist/utils/reset.js +81 -0
  143. package/dist/utils/reset.js.map +1 -0
  144. package/dist/utils/revert.d.ts +30 -0
  145. package/dist/utils/revert.d.ts.map +1 -0
  146. package/dist/utils/revert.js +89 -0
  147. package/dist/utils/revert.js.map +1 -0
  148. package/dist/utils/revert.spec.d.ts +2 -0
  149. package/dist/utils/revert.spec.d.ts.map +1 -0
  150. package/dist/utils/revert.spec.js +102 -0
  151. package/dist/utils/revert.spec.js.map +1 -0
  152. package/dist/utils/similarity.d.ts +14 -0
  153. package/dist/utils/similarity.d.ts.map +1 -0
  154. package/dist/utils/similarity.js +70 -0
  155. package/dist/utils/similarity.js.map +1 -0
  156. package/dist/utils/similarity.spec.d.ts +2 -0
  157. package/dist/utils/similarity.spec.d.ts.map +1 -0
  158. package/dist/utils/similarity.spec.js +62 -0
  159. package/dist/utils/similarity.spec.js.map +1 -0
  160. package/dist/utils/snapshots.d.ts +21 -0
  161. package/dist/utils/snapshots.d.ts.map +1 -0
  162. package/dist/utils/snapshots.js +81 -0
  163. package/dist/utils/snapshots.js.map +1 -0
  164. package/dist/utils/snapshots.spec.d.ts +2 -0
  165. package/dist/utils/snapshots.spec.d.ts.map +1 -0
  166. package/dist/utils/snapshots.spec.js +56 -0
  167. package/dist/utils/snapshots.spec.js.map +1 -0
  168. package/dist/utils/syncFilters.d.ts +3 -0
  169. package/dist/utils/syncFilters.d.ts.map +1 -0
  170. package/dist/utils/syncFilters.js +8 -0
  171. package/dist/utils/syncFilters.js.map +1 -0
  172. package/dist/utils/syncRuntime.d.ts +3 -0
  173. package/dist/utils/syncRuntime.d.ts.map +1 -0
  174. package/dist/utils/syncRuntime.js +31 -0
  175. package/dist/utils/syncRuntime.js.map +1 -0
  176. package/dist/utils/validation.d.ts +3 -0
  177. package/dist/utils/validation.d.ts.map +1 -0
  178. package/dist/utils/validation.js +19 -0
  179. package/dist/utils/validation.js.map +1 -0
  180. package/dist/utils/validation.spec.d.ts +2 -0
  181. package/dist/utils/validation.spec.d.ts.map +1 -0
  182. package/dist/utils/validation.spec.js +36 -0
  183. package/dist/utils/validation.spec.js.map +1 -0
  184. package/package.json +63 -0
@@ -0,0 +1,82 @@
1
+ const TYPE_ORDER = ["agents", "commands", "skills", "mcp"];
2
+ const TYPE_LABELS = {
3
+ agents: "agents",
4
+ commands: "commands",
5
+ skills: "skills",
6
+ mcp: "mcp",
7
+ };
8
+ export function buildSyncPreflightLines(input) {
9
+ const types = input.types?.length
10
+ ? input.types.join(", ")
11
+ : "agents, commands, skills, mcp";
12
+ const targets = input.targets.length > 0 ? input.targets.join(", ") : "none";
13
+ return [
14
+ `Mode: ${input.dryRun ? "dry-run" : "apply"}`,
15
+ `Write mode: ${input.writeMode}`,
16
+ `Canonical assets: ${input.canonicalCount}`,
17
+ `Bootstrap actions: ${input.bootstrapCount}`,
18
+ `Ignored legacy inputs: ${input.ignoredCount}`,
19
+ `Targets: ${targets}`,
20
+ `Managed types: ${types}`,
21
+ ];
22
+ }
23
+ export function buildSyncPlanSummaryLines(plan) {
24
+ const bootstrapEntries = plan.filter((entry) => entry.reason === "bootstrap");
25
+ const fanoutEntries = plan.filter((entry) => entry.reason !== "bootstrap");
26
+ const lines = [];
27
+ if (bootstrapEntries.length > 0) {
28
+ lines.push(`bootstrap ${formatEntrySummary(bootstrapEntries)}`);
29
+ }
30
+ const byClient = new Map();
31
+ for (const entry of fanoutEntries) {
32
+ const entries = byClient.get(entry.targetClient) ?? [];
33
+ entries.push(entry);
34
+ byClient.set(entry.targetClient, entries);
35
+ }
36
+ for (const [client, entries] of [...byClient.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
37
+ lines.push(`fanout ${client}: ${formatEntrySummary(entries)}`);
38
+ }
39
+ return lines;
40
+ }
41
+ export function buildDetailedPlanLines(plan) {
42
+ return plan.map((entry) => {
43
+ const phase = entry.reason === "bootstrap" ? "bootstrap" : "fanout";
44
+ return `${phase.padEnd(10)} ${entry.targetPath}`;
45
+ });
46
+ }
47
+ export function formatIssueSection(title, items) {
48
+ if (items.length === 0) {
49
+ return [];
50
+ }
51
+ return [
52
+ `${title} (${items.length})`,
53
+ ...items.map((item) => ` ${item}`),
54
+ "",
55
+ ];
56
+ }
57
+ export function formatSnapshotList(snapshots) {
58
+ return snapshots.map((snapshot) => `${snapshot.id} ${snapshot.createdAt} (${snapshot.entries.length} paths)`);
59
+ }
60
+ function formatEntrySummary(entries) {
61
+ const counts = new Map();
62
+ for (const type of TYPE_ORDER) {
63
+ counts.set(type, 0);
64
+ }
65
+ for (const entry of entries) {
66
+ if (isManagedAssetType(entry.asset.type)) {
67
+ counts.set(entry.asset.type, (counts.get(entry.asset.type) ?? 0) + 1);
68
+ }
69
+ }
70
+ const details = TYPE_ORDER.map((type) => {
71
+ const count = counts.get(type) ?? 0;
72
+ if (count === 0) {
73
+ return null;
74
+ }
75
+ return `${TYPE_LABELS[type]} ${count}`;
76
+ }).filter((item) => item !== null);
77
+ return `${entries.length} change${entries.length === 1 ? "" : "s"} (${details.join(", ")})`;
78
+ }
79
+ function isManagedAssetType(type) {
80
+ return TYPE_ORDER.includes(type);
81
+ }
82
+ //# sourceMappingURL=reporting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporting.js","sourceRoot":"","sources":["../../src/utils/reporting.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,GAAuB,CAAC,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AAC/E,MAAM,WAAW,GAAqC;IACpD,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;IACpB,MAAM,EAAE,QAAQ;IAChB,GAAG,EAAE,KAAK;CACX,CAAC;AAYF,MAAM,UAAU,uBAAuB,CAAC,KAAyB;IAC/D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,MAAM;QAC/B,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QACxB,CAAC,CAAC,+BAA+B,CAAC;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAE7E,OAAO;QACL,SAAS,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE;QAC7C,eAAe,KAAK,CAAC,SAAS,EAAE;QAChC,qBAAqB,KAAK,CAAC,cAAc,EAAE;QAC3C,sBAAsB,KAAK,CAAC,cAAc,EAAE;QAC5C,0BAA0B,KAAK,CAAC,YAAY,EAAE;QAC9C,YAAY,OAAO,EAAE;QACrB,kBAAkB,KAAK,EAAE;KAC1B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,IAAqB;IAC7D,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;IAC9E,MAAM,aAAa,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC;IAC3E,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,eAAe,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;IACpD,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACpE,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CACzB,EAAE,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,eAAe,MAAM,KAAK,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAqB;IAC1D,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,QAAQ,CAAC;QACpE,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAa,EAAE,KAAe;IAC/D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,OAAO;QACL,GAAG,KAAK,KAAK,KAAK,CAAC,MAAM,GAAG;QAC5B,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;QACnC,EAAE;KACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,SAAuE;IAEvE,OAAO,SAAS,CAAC,GAAG,CAClB,CAAC,QAAQ,EAAE,EAAE,CACX,GAAG,QAAQ,CAAC,EAAE,KAAK,QAAQ,CAAC,SAAS,MAAM,QAAQ,CAAC,OAAO,CAAC,MAAM,SAAS,CAC9E,CAAC;AACJ,CAAC;AAED,SAAS,kBAAkB,CAAC,OAAwB;IAClD,MAAM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IAEnD,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACtB,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,kBAAkB,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;IACzC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAEnD,OAAO,GAAG,OAAO,CAAC,MAAM,UAAU,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;AAC9F,CAAC;AAED,SAAS,kBAAkB,CAAC,IAAY;IACtC,OAAO,UAAU,CAAC,QAAQ,CAAC,IAAwB,CAAC,CAAC;AACvD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=reporting.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporting.spec.d.ts","sourceRoot":"","sources":["../../src/utils/reporting.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { hashContent } from "./fs.js";
3
+ import { buildDetailedPlanLines, buildSyncPlanSummaryLines, buildSyncPreflightLines, formatIssueSection, formatSnapshotList, } from "./reporting.js";
4
+ function makeEntry(type, targetClient, targetPath, reason) {
5
+ const canonicalPath = type === "mcp"
6
+ ? "mcp.json"
7
+ : type === "agents"
8
+ ? "AGENTS.md"
9
+ : `${type}/demo.md`;
10
+ return {
11
+ asset: {
12
+ client: "project",
13
+ type,
14
+ path: `/repo/.agents/${canonicalPath}`,
15
+ relativePath: canonicalPath,
16
+ canonicalPath,
17
+ name: canonicalPath,
18
+ content: canonicalPath,
19
+ hash: hashContent(canonicalPath),
20
+ },
21
+ targetClient,
22
+ targetPath,
23
+ action: "create",
24
+ reason,
25
+ };
26
+ }
27
+ describe("reporting", () => {
28
+ it("formats sync preflight lines", () => {
29
+ expect(buildSyncPreflightLines({
30
+ canonicalCount: 4,
31
+ bootstrapCount: 1,
32
+ ignoredCount: 2,
33
+ targets: ["claude", "cursor"],
34
+ writeMode: "copy",
35
+ dryRun: true,
36
+ types: ["agents", "commands"],
37
+ })).toEqual([
38
+ "Mode: dry-run",
39
+ "Write mode: copy",
40
+ "Canonical assets: 4",
41
+ "Bootstrap actions: 1",
42
+ "Ignored legacy inputs: 2",
43
+ "Targets: claude, cursor",
44
+ "Managed types: agents, commands",
45
+ ]);
46
+ });
47
+ it("groups sync plan lines by phase and client", () => {
48
+ const lines = buildSyncPlanSummaryLines([
49
+ makeEntry("agents", "project", "/repo/.agents/AGENTS.md", "bootstrap"),
50
+ makeEntry("commands", "codex", "/home/.codex/skills/commands/review/SKILL.md", "fanout"),
51
+ makeEntry("mcp", "cursor", "/home/.cursor/mcp.json", "fanout"),
52
+ makeEntry("agents", "cursor", "/home/.cursor/AGENTS.md", "fanout"),
53
+ ]);
54
+ expect(lines).toEqual([
55
+ "bootstrap 1 change (agents 1)",
56
+ "fanout codex: 1 change (commands 1)",
57
+ "fanout cursor: 2 changes (agents 1, mcp 1)",
58
+ ]);
59
+ });
60
+ it("formats detailed plan lines", () => {
61
+ expect(buildDetailedPlanLines([
62
+ makeEntry("agents", "project", "/repo/.agents/AGENTS.md", "bootstrap"),
63
+ makeEntry("commands", "claude", "/home/.claude/commands/review.md", "fanout"),
64
+ ])).toEqual([
65
+ "bootstrap /repo/.agents/AGENTS.md",
66
+ "fanout /home/.claude/commands/review.md",
67
+ ]);
68
+ });
69
+ it("formats issue sections with counts", () => {
70
+ expect(formatIssueSection("Ignored legacy inputs", ["/tmp/a", "/tmp/b"])).toEqual(["Ignored legacy inputs (2)", " /tmp/a", " /tmp/b", ""]);
71
+ });
72
+ it("formats snapshot lists with path counts", () => {
73
+ expect(formatSnapshotList([
74
+ { id: "snap-1", createdAt: "2026-03-13T11:00:00Z", entries: [{}, {}] },
75
+ ])).toEqual(["snap-1 2026-03-13T11:00:00Z (2 paths)"]);
76
+ });
77
+ });
78
+ //# sourceMappingURL=reporting.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reporting.spec.js","sourceRoot":"","sources":["../../src/utils/reporting.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ export interface ResetResult {
2
+ removedFiles: string[];
3
+ removedBackups: boolean;
4
+ cleanedGitignore: boolean;
5
+ clearedManifest: boolean;
6
+ }
7
+ /**
8
+ * Reset link-agents: remove all generated files, backups, and manifest.
9
+ */
10
+ export declare function performReset(projectRoot: string, options?: {
11
+ dryRun?: boolean;
12
+ verbose?: boolean;
13
+ }): Promise<ResetResult>;
14
+ //# sourceMappingURL=reset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.d.ts","sourceRoot":"","sources":["../../src/utils/reset.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,cAAc,EAAE,OAAO,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,GACpD,OAAO,CAAC,WAAW,CAAC,CA2EtB"}
@@ -0,0 +1,81 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
+ import chalk from "chalk";
5
+ import { fileExists } from "./fs.js";
6
+ import { getManifestFiles, clearManifest } from "./manifest.js";
7
+ import { cleanGitignore } from "./gitignore.js";
8
+ const SYNC_AGENTS_DIR = path.join(os.homedir(), ".link-agents");
9
+ /**
10
+ * Reset link-agents: remove all generated files, backups, and manifest.
11
+ */
12
+ export async function performReset(projectRoot, options = {}) {
13
+ const result = {
14
+ removedFiles: [],
15
+ removedBackups: false,
16
+ cleanedGitignore: false,
17
+ clearedManifest: false,
18
+ };
19
+ // Get files from manifest
20
+ const manifestFiles = await getManifestFiles();
21
+ console.log(chalk.yellow("Resetting link-agents..."));
22
+ // Remove generated files
23
+ for (const file of manifestFiles) {
24
+ if (await fileExists(file)) {
25
+ if (options.dryRun) {
26
+ console.log(chalk.dim(` would remove: ${file}`));
27
+ }
28
+ else {
29
+ try {
30
+ await fs.unlink(file);
31
+ if (options.verbose) {
32
+ console.log(chalk.dim(` removed: ${file}`));
33
+ }
34
+ result.removedFiles.push(file);
35
+ }
36
+ catch {
37
+ // File may have been manually deleted
38
+ }
39
+ }
40
+ }
41
+ }
42
+ // Remove .link-agents directory (includes backups and manifest)
43
+ if (await fileExists(SYNC_AGENTS_DIR)) {
44
+ if (options.dryRun) {
45
+ console.log(chalk.dim(` would remove: ${SYNC_AGENTS_DIR}`));
46
+ }
47
+ else {
48
+ try {
49
+ await fs.rm(SYNC_AGENTS_DIR, { recursive: true });
50
+ result.removedBackups = true;
51
+ result.clearedManifest = true;
52
+ if (options.verbose) {
53
+ console.log(chalk.dim(` removed: ${SYNC_AGENTS_DIR}`));
54
+ }
55
+ }
56
+ catch {
57
+ // Directory may not exist
58
+ }
59
+ }
60
+ }
61
+ // Clean .gitignore
62
+ if (options.dryRun) {
63
+ console.log(chalk.dim(` would clean: ${projectRoot}/.gitignore`));
64
+ }
65
+ else {
66
+ const cleaned = await cleanGitignore(projectRoot);
67
+ result.cleanedGitignore = cleaned;
68
+ if (cleaned && options.verbose) {
69
+ console.log(chalk.dim(` cleaned: ${projectRoot}/.gitignore`));
70
+ }
71
+ }
72
+ // Clear manifest (already done if we removed the directory)
73
+ if (!result.clearedManifest && !options.dryRun) {
74
+ await clearManifest();
75
+ result.clearedManifest = true;
76
+ }
77
+ const action = options.dryRun ? "Would remove" : "Removed";
78
+ console.log(chalk.green(`${action} ${result.removedFiles.length} file(s), backups, and manifest.`));
79
+ return result;
80
+ }
81
+ //# sourceMappingURL=reset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.js","sourceRoot":"","sources":["../../src/utils/reset.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ export interface BackupInfo {
2
+ originalPath: string;
3
+ backupPath: string;
4
+ exists: boolean;
5
+ }
6
+ export interface RevertResult {
7
+ restored: string[];
8
+ failed: string[];
9
+ }
10
+ /**
11
+ * Find all .bak files for files in the manifest.
12
+ */
13
+ export declare function listBackups(): Promise<BackupInfo[]>;
14
+ /**
15
+ * Find backups that actually exist (can be restored).
16
+ */
17
+ export declare function listAvailableBackups(): Promise<BackupInfo[]>;
18
+ /**
19
+ * Revert files from their .bak backups.
20
+ */
21
+ export declare function performRevert(options?: {
22
+ dryRun?: boolean;
23
+ verbose?: boolean;
24
+ files?: string[];
25
+ }): Promise<RevertResult>;
26
+ /**
27
+ * Show available backups.
28
+ */
29
+ export declare function showBackupStatus(): Promise<void>;
30
+ //# sourceMappingURL=revert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revert.d.ts","sourceRoot":"","sources":["../../src/utils/revert.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAYzD;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAGlE;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GACtE,OAAO,CAAC,YAAY,CAAC,CAmEvB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAatD"}
@@ -0,0 +1,89 @@
1
+ import chalk from "chalk";
2
+ import { fileExists, readFileSafe, writeFileSafe } from "./fs.js";
3
+ import { readManifest } from "./manifest.js";
4
+ /**
5
+ * Find all .bak files for files in the manifest.
6
+ */
7
+ export async function listBackups() {
8
+ const manifest = await readManifest();
9
+ const backups = await Promise.all(manifest.generatedFiles.map(async (filePath) => {
10
+ const backupPath = `${filePath}.bak`;
11
+ const exists = await fileExists(backupPath);
12
+ return { originalPath: filePath, backupPath, exists };
13
+ }));
14
+ return backups;
15
+ }
16
+ /**
17
+ * Find backups that actually exist (can be restored).
18
+ */
19
+ export async function listAvailableBackups() {
20
+ const all = await listBackups();
21
+ return all.filter((b) => b.exists);
22
+ }
23
+ /**
24
+ * Revert files from their .bak backups.
25
+ */
26
+ export async function performRevert(options = {}) {
27
+ const result = {
28
+ restored: [],
29
+ failed: [],
30
+ };
31
+ const available = await listAvailableBackups();
32
+ if (available.length === 0) {
33
+ console.log(chalk.yellow("No backups available to restore."));
34
+ return result;
35
+ }
36
+ // Filter to specific files if requested
37
+ const toRestore = options.files
38
+ ? available.filter((b) => options.files.some((f) => b.originalPath.includes(f) || b.backupPath.includes(f)))
39
+ : available;
40
+ if (toRestore.length === 0) {
41
+ console.log(chalk.yellow("No matching backups found."));
42
+ return result;
43
+ }
44
+ console.log(chalk.yellow(`Reverting ${toRestore.length} file(s)...`));
45
+ for (const backup of toRestore) {
46
+ if (options.dryRun) {
47
+ console.log(chalk.dim(` would restore: ${backup.originalPath}`));
48
+ result.restored.push(backup.originalPath);
49
+ continue;
50
+ }
51
+ try {
52
+ const backupContent = await readFileSafe(backup.backupPath);
53
+ if (!backupContent) {
54
+ result.failed.push(backup.originalPath);
55
+ continue;
56
+ }
57
+ await writeFileSafe(backup.originalPath, backupContent);
58
+ if (options.verbose) {
59
+ console.log(chalk.dim(` restored: ${backup.originalPath}`));
60
+ }
61
+ result.restored.push(backup.originalPath);
62
+ }
63
+ catch (err) {
64
+ console.log(chalk.red(` failed: ${backup.originalPath} - ${String(err)}`));
65
+ result.failed.push(backup.originalPath);
66
+ }
67
+ }
68
+ const action = options.dryRun ? "Would restore" : "Restored";
69
+ console.log(chalk.green(`${action} ${result.restored.length} file(s).`));
70
+ if (result.failed.length > 0) {
71
+ console.log(chalk.red(`Failed to restore ${result.failed.length} file(s).`));
72
+ }
73
+ return result;
74
+ }
75
+ /**
76
+ * Show available backups.
77
+ */
78
+ export async function showBackupStatus() {
79
+ const available = await listAvailableBackups();
80
+ if (available.length === 0) {
81
+ console.log(chalk.yellow("No backups available."));
82
+ return;
83
+ }
84
+ console.log(chalk.cyan(`Found ${available.length} backup(s):\n`));
85
+ for (const backup of available) {
86
+ console.log(` ${backup.originalPath}`);
87
+ }
88
+ }
89
+ //# sourceMappingURL=revert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revert.js","sourceRoot":"","sources":["../../src/utils/revert.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=revert.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revert.spec.d.ts","sourceRoot":"","sources":["../../src/utils/revert.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,102 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import os from "node:os";
5
+ import { listBackups, listAvailableBackups } from "./revert.js";
6
+ describe("revert", () => {
7
+ const testDir = path.join(os.tmpdir(), "link-agents-revert-test");
8
+ const manifestDir = path.join(os.homedir(), ".link-agents");
9
+ const manifestPath = path.join(manifestDir, "manifest.json");
10
+ let originalManifest = null;
11
+ beforeEach(async () => {
12
+ await fs.mkdir(testDir, { recursive: true });
13
+ // Save original manifest if it exists
14
+ try {
15
+ originalManifest = await fs.readFile(manifestPath, "utf8");
16
+ }
17
+ catch {
18
+ originalManifest = null;
19
+ }
20
+ });
21
+ afterEach(async () => {
22
+ await fs.rm(testDir, { recursive: true, force: true });
23
+ // Restore original manifest
24
+ if (originalManifest) {
25
+ await fs.writeFile(manifestPath, originalManifest, "utf8");
26
+ }
27
+ else {
28
+ try {
29
+ await fs.unlink(manifestPath);
30
+ }
31
+ catch {
32
+ // ignore
33
+ }
34
+ }
35
+ });
36
+ describe("listBackups", () => {
37
+ it("returns empty array when no manifest exists", async () => {
38
+ // Remove manifest if exists
39
+ try {
40
+ await fs.unlink(manifestPath);
41
+ }
42
+ catch {
43
+ // ignore
44
+ }
45
+ const backups = await listBackups();
46
+ expect(backups).toEqual([]);
47
+ });
48
+ it("returns backup info for files in manifest", async () => {
49
+ const testFile = path.join(testDir, "test.md");
50
+ const backupFile = `${testFile}.bak`;
51
+ // Create files
52
+ await fs.writeFile(testFile, "current content", "utf8");
53
+ await fs.writeFile(backupFile, "backup content", "utf8");
54
+ // Create manifest with test file
55
+ await fs.mkdir(manifestDir, { recursive: true });
56
+ await fs.writeFile(manifestPath, JSON.stringify({
57
+ version: 1,
58
+ lastSync: new Date().toISOString(),
59
+ generatedFiles: [testFile],
60
+ }), "utf8");
61
+ const backups = await listBackups();
62
+ expect(backups).toHaveLength(1);
63
+ expect(backups[0]).toEqual({
64
+ originalPath: testFile,
65
+ backupPath: backupFile,
66
+ exists: true,
67
+ });
68
+ });
69
+ it("marks backup as not existing when .bak file missing", async () => {
70
+ const testFile = path.join(testDir, "no-backup.md");
71
+ await fs.writeFile(testFile, "content", "utf8");
72
+ await fs.mkdir(manifestDir, { recursive: true });
73
+ await fs.writeFile(manifestPath, JSON.stringify({
74
+ version: 1,
75
+ lastSync: new Date().toISOString(),
76
+ generatedFiles: [testFile],
77
+ }), "utf8");
78
+ const backups = await listBackups();
79
+ expect(backups).toHaveLength(1);
80
+ expect(backups[0].exists).toBe(false);
81
+ });
82
+ });
83
+ describe("listAvailableBackups", () => {
84
+ it("filters to only existing backups", async () => {
85
+ const fileWithBackup = path.join(testDir, "has-backup.md");
86
+ const fileWithoutBackup = path.join(testDir, "no-backup.md");
87
+ await fs.writeFile(fileWithBackup, "content", "utf8");
88
+ await fs.writeFile(`${fileWithBackup}.bak`, "backup", "utf8");
89
+ await fs.writeFile(fileWithoutBackup, "content", "utf8");
90
+ await fs.mkdir(manifestDir, { recursive: true });
91
+ await fs.writeFile(manifestPath, JSON.stringify({
92
+ version: 1,
93
+ lastSync: new Date().toISOString(),
94
+ generatedFiles: [fileWithBackup, fileWithoutBackup],
95
+ }), "utf8");
96
+ const available = await listAvailableBackups();
97
+ expect(available).toHaveLength(1);
98
+ expect(available[0].originalPath).toBe(fileWithBackup);
99
+ });
100
+ });
101
+ });
102
+ //# sourceMappingURL=revert.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revert.spec.js","sourceRoot":"","sources":["../../src/utils/revert.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Calculate similarity between two strings using Jaccard similarity on word sets
3
+ * Returns a value between 0 (completely different) and 1 (identical)
4
+ */
5
+ export declare function calculateSimilarity(a: string, b: string): number;
6
+ /**
7
+ * Get human-readable similarity label
8
+ */
9
+ export declare function getSimilarityLabel(score: number): string;
10
+ /**
11
+ * Format relative time (e.g., "2 hours ago", "3 days ago")
12
+ */
13
+ export declare function formatRelativeTime(date: Date | undefined): string;
14
+ //# sourceMappingURL=similarity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarity.d.ts","sourceRoot":"","sources":["../../src/utils/similarity.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAchE;AAaD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,MAAM,CAiBjE"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Calculate similarity between two strings using Jaccard similarity on word sets
3
+ * Returns a value between 0 (completely different) and 1 (identical)
4
+ */
5
+ export function calculateSimilarity(a, b) {
6
+ if (a === b)
7
+ return 1;
8
+ if (!a || !b)
9
+ return 0;
10
+ const wordsA = new Set(tokenize(a));
11
+ const wordsB = new Set(tokenize(b));
12
+ if (wordsA.size === 0 && wordsB.size === 0)
13
+ return 1;
14
+ if (wordsA.size === 0 || wordsB.size === 0)
15
+ return 0;
16
+ const intersection = new Set([...wordsA].filter((w) => wordsB.has(w)));
17
+ const union = new Set([...wordsA, ...wordsB]);
18
+ return intersection.size / union.size;
19
+ }
20
+ /**
21
+ * Tokenize text into words (lowercase, alphanumeric only)
22
+ */
23
+ function tokenize(text) {
24
+ return text
25
+ .toLowerCase()
26
+ .split(/\s+/)
27
+ .map((w) => w.replace(/[^a-z0-9]/g, ""))
28
+ .filter((w) => w.length > 2);
29
+ }
30
+ /**
31
+ * Get human-readable similarity label
32
+ */
33
+ export function getSimilarityLabel(score) {
34
+ if (score >= 0.9)
35
+ return "nearly identical";
36
+ if (score >= 0.7)
37
+ return "very similar";
38
+ if (score >= 0.5)
39
+ return "similar";
40
+ if (score >= 0.3)
41
+ return "somewhat different";
42
+ return "very different";
43
+ }
44
+ /**
45
+ * Format relative time (e.g., "2 hours ago", "3 days ago")
46
+ */
47
+ export function formatRelativeTime(date) {
48
+ if (!date)
49
+ return "unknown";
50
+ const now = new Date();
51
+ const diffMs = now.getTime() - date.getTime();
52
+ const diffSecs = Math.floor(diffMs / 1000);
53
+ const diffMins = Math.floor(diffSecs / 60);
54
+ const diffHours = Math.floor(diffMins / 60);
55
+ const diffDays = Math.floor(diffHours / 24);
56
+ if (diffSecs < 60)
57
+ return "just now";
58
+ if (diffMins < 60)
59
+ return `${diffMins}m ago`;
60
+ if (diffHours < 24)
61
+ return `${diffHours}h ago`;
62
+ if (diffDays < 7)
63
+ return `${diffDays}d ago`;
64
+ if (diffDays < 30)
65
+ return `${Math.floor(diffDays / 7)}w ago`;
66
+ if (diffDays < 365)
67
+ return `${Math.floor(diffDays / 30)}mo ago`;
68
+ return `${Math.floor(diffDays / 365)}y ago`;
69
+ }
70
+ //# sourceMappingURL=similarity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarity.js","sourceRoot":"","sources":["../../src/utils/similarity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=similarity.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similarity.spec.d.ts","sourceRoot":"","sources":["../../src/utils/similarity.spec.ts"],"names":[],"mappings":""}