synomem 0.4.0 → 0.5.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 (47) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +50 -10
  3. package/dist/cli.d.ts +6 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +179 -16
  6. package/dist/cli.js.map +1 -1
  7. package/dist/client.d.ts +2 -1
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +37 -1
  10. package/dist/client.js.map +1 -1
  11. package/dist/configure.d.ts.map +1 -1
  12. package/dist/configure.js +31 -15
  13. package/dist/configure.js.map +1 -1
  14. package/dist/credentials.d.ts.map +1 -1
  15. package/dist/credentials.js +9 -1
  16. package/dist/credentials.js.map +1 -1
  17. package/dist/discover.d.ts +48 -0
  18. package/dist/discover.d.ts.map +1 -0
  19. package/dist/discover.js +106 -0
  20. package/dist/discover.js.map +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/projections.d.ts.map +1 -1
  26. package/dist/projections.js +15 -7
  27. package/dist/projections.js.map +1 -1
  28. package/dist/service.d.ts +2 -1
  29. package/dist/service.d.ts.map +1 -1
  30. package/dist/storage.d.ts +5 -0
  31. package/dist/storage.d.ts.map +1 -1
  32. package/dist/storage.js +6 -0
  33. package/dist/storage.js.map +1 -1
  34. package/dist/types.d.ts +26 -0
  35. package/dist/types.d.ts.map +1 -1
  36. package/docs/cli.md +67 -3
  37. package/package.json +1 -1
  38. package/src/cli.ts +228 -20
  39. package/src/client.ts +41 -1
  40. package/src/configure.ts +30 -14
  41. package/src/credentials.ts +9 -1
  42. package/src/discover.ts +155 -0
  43. package/src/index.ts +2 -0
  44. package/src/projections.ts +17 -7
  45. package/src/service.ts +8 -0
  46. package/src/storage.ts +9 -0
  47. package/src/types.ts +21 -0
@@ -749,6 +749,14 @@ export class ProjectionManager implements ProjectionWriter {
749
749
  return { generated: generated.sort(), removed: removed.sort() };
750
750
  }
751
751
 
752
+ /*
753
+ * The paths a rebuild would produce, for comparison against the manifest.
754
+ *
755
+ * Directories are named by HANDLE, matching what the writers above create,
756
+ * while the record filters match on the canonical agent ID, which is what
757
+ * stored events carry. Mixing the two up makes the comparison never agree,
758
+ * and `doctor` then reports every workspace's projections as stale forever.
759
+ */
752
760
  expectedPaths(): string[] {
753
761
  const profiles = this.storage.listAgents();
754
762
  const events = this.storage.getReadableEvents();
@@ -757,10 +765,12 @@ export class ProjectionManager implements ProjectionWriter {
757
765
  const tasks = taskRecordsFromEvents(events);
758
766
  const paths: string[] = [];
759
767
  for (const profile of profiles) {
760
- paths.push(`${profile.id}/profile.json`);
761
- if (this.storage.config.projection.writeWinsMarkdown) paths.push(`${profile.id}/WINS.md`);
762
- if (this.storage.config.projection.writeMemoryMarkdown) paths.push(`${profile.id}/MEMORY.md`);
763
- if (this.storage.config.projection.writeTasksMarkdown) paths.push(`${profile.id}/TASKS.md`);
768
+ paths.push(`${profile.handle}/profile.json`);
769
+ if (this.storage.config.projection.writeWinsMarkdown) paths.push(`${profile.handle}/WINS.md`);
770
+ if (this.storage.config.projection.writeMemoryMarkdown)
771
+ paths.push(`${profile.handle}/MEMORY.md`);
772
+ if (this.storage.config.projection.writeTasksMarkdown)
773
+ paths.push(`${profile.handle}/TASKS.md`);
764
774
  if (this.storage.config.projection.writeInboxEntries) {
765
775
  for (const record of records.filter(
766
776
  (item) =>
@@ -768,19 +778,19 @@ export class ProjectionManager implements ProjectionWriter {
768
778
  item.status === 'unacknowledged' &&
769
779
  item.revocationStatus === 'active',
770
780
  )) {
771
- paths.push(`${profile.id}/inbox/kudos/${record.event.id}.md`);
781
+ paths.push(`${profile.handle}/inbox/kudos/${record.event.id}.md`);
772
782
  }
773
783
  for (const record of memos.filter(
774
784
  (item) => item.event.recipientAgentId === profile.id && item.status === 'unread',
775
785
  )) {
776
- paths.push(`${profile.id}/inbox/memos/${record.event.id}.md`);
786
+ paths.push(`${profile.handle}/inbox/memos/${record.event.id}.md`);
777
787
  }
778
788
  for (const record of tasks.filter(
779
789
  (item) =>
780
790
  item.event.assigneeAgentId === profile.id &&
781
791
  (item.status === 'open' || item.status === 'assigned'),
782
792
  )) {
783
- paths.push(`${profile.id}/inbox/tasks/${record.event.id}.md`);
793
+ paths.push(`${profile.handle}/inbox/tasks/${record.event.id}.md`);
784
794
  }
785
795
  }
786
796
  }
package/src/service.ts CHANGED
@@ -19,6 +19,7 @@ import type {
19
19
  CreateTodoResult,
20
20
  CreateTaskResult,
21
21
  DoctorResult,
22
+ ProjectionStatus,
22
23
  GiveKudosInput,
23
24
  GiveKudosResult,
24
25
  ItemListInput,
@@ -213,6 +214,13 @@ export interface SynomemService extends SynomemDomainService {
213
214
  doctor(): Promise<DoctorResult>;
214
215
  export(format: 'json' | 'jsonl' | 'markdown'): Promise<string>;
215
216
  backup?(destination: string): Promise<string>;
217
+ /*
218
+ * Optional, because only a backend that writes projected files can report on
219
+ * them. The remote backend keeps no filesystem projections at all, and
220
+ * inventing an empty answer there would read as "nothing is stale" rather
221
+ * than "there is nothing to be stale".
222
+ */
223
+ projectionStatus?(): Promise<ProjectionStatus>;
216
224
  rebuild(): Promise<ProjectionRebuildResult>;
217
225
  capabilities(): Promise<SynomemServiceCapabilities>;
218
226
  info(): Promise<SynomemServiceInfo>;
package/src/storage.ts CHANGED
@@ -2041,6 +2041,15 @@ export class SynomemStorage implements SynomemRepository {
2041
2041
  ).map((row) => row.path);
2042
2042
  }
2043
2043
 
2044
+ /** The manifest with the time each path was written, newest first. */
2045
+ projectionManifestEntries(): { path: string; generatedAt: string }[] {
2046
+ return (
2047
+ this.db()
2048
+ .prepare('SELECT path, generated_at FROM projection_manifest ORDER BY generated_at DESC')
2049
+ .all() as unknown as { path: string; generated_at: string }[]
2050
+ ).map((row) => ({ path: row.path, generatedAt: row.generated_at }));
2051
+ }
2052
+
2044
2053
  integrityCheck(): string[] {
2045
2054
  const rows = this.db().prepare('PRAGMA integrity_check').all() as unknown as Record<
2046
2055
  string,
package/src/types.ts CHANGED
@@ -737,6 +737,27 @@ export interface DoctorResult {
737
737
  healthy: boolean;
738
738
  diagnostics: Diagnostic[];
739
739
  }
740
+ /**
741
+ * What the projected files on disk look like next to what they should be.
742
+ *
743
+ * Projections are derived, never canonical, so this reports drift rather than
744
+ * damage: `missing` and `unexpected` are both repaired by a rebuild, and
745
+ * neither means an event was lost.
746
+ */
747
+ export interface ProjectionStatus {
748
+ /** Where projected files live. Absent on a backend that projects nothing. */
749
+ directory?: string;
750
+ settings: SynomemConfig['projection'];
751
+ /** True when the manifest matches what a rebuild would produce. */
752
+ current: boolean;
753
+ /** Recorded by the manifest, not the filesystem; absent before any rebuild. */
754
+ lastRebuiltAt?: string;
755
+ counts: { expected: number; manifest: number; missing: number; unexpected: number };
756
+ /** Expected but not on disk. Capped, because a large workspace has many. */
757
+ missing: string[];
758
+ /** On disk and in the manifest, but no longer expected. Capped likewise. */
759
+ unexpected: string[];
760
+ }
740
761
 
741
762
  export type SynomemBackendConfig =
742
763
  { kind: 'local' } | { kind: 'remote'; baseUrl: string; workspaceId: string };