@lolkda/dsh-prompt-manager 3.0.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +606 -0
  3. package/client/client.js +2320 -0
  4. package/cordis.patch.yml +20 -0
  5. package/environment.md +24 -0
  6. package/lib/entries.js +303 -0
  7. package/lib/entries.js.map +1 -0
  8. package/lib/guard.js +134 -0
  9. package/lib/guard.js.map +1 -0
  10. package/lib/index.js +959 -0
  11. package/lib/index.js.map +1 -0
  12. package/lib/net.js +179 -0
  13. package/lib/net.js.map +1 -0
  14. package/lib/pack.js +327 -0
  15. package/lib/pack.js.map +1 -0
  16. package/lib/probe.js +251 -0
  17. package/lib/probe.js.map +1 -0
  18. package/lib/routes.js +718 -0
  19. package/lib/routes.js.map +1 -0
  20. package/lib/scripts.js +803 -0
  21. package/lib/scripts.js.map +1 -0
  22. package/lib/source.js +308 -0
  23. package/lib/source.js.map +1 -0
  24. package/lib/store.js +223 -0
  25. package/lib/store.js.map +1 -0
  26. package/lib/subscriptions.js +269 -0
  27. package/lib/subscriptions.js.map +1 -0
  28. package/lib/sync.js +646 -0
  29. package/lib/sync.js.map +1 -0
  30. package/lib/types/entries.d.ts +194 -0
  31. package/lib/types/entries.d.ts.map +1 -0
  32. package/lib/types/guard.d.ts +63 -0
  33. package/lib/types/guard.d.ts.map +1 -0
  34. package/lib/types/index.d.ts +176 -0
  35. package/lib/types/index.d.ts.map +1 -0
  36. package/lib/types/net.d.ts +81 -0
  37. package/lib/types/net.d.ts.map +1 -0
  38. package/lib/types/pack.d.ts +298 -0
  39. package/lib/types/pack.d.ts.map +1 -0
  40. package/lib/types/probe.d.ts +150 -0
  41. package/lib/types/probe.d.ts.map +1 -0
  42. package/lib/types/routes.d.ts +85 -0
  43. package/lib/types/routes.d.ts.map +1 -0
  44. package/lib/types/scripts.d.ts +455 -0
  45. package/lib/types/scripts.d.ts.map +1 -0
  46. package/lib/types/source.d.ts +194 -0
  47. package/lib/types/source.d.ts.map +1 -0
  48. package/lib/types/store.d.ts +140 -0
  49. package/lib/types/store.d.ts.map +1 -0
  50. package/lib/types/subscriptions.d.ts +204 -0
  51. package/lib/types/subscriptions.d.ts.map +1 -0
  52. package/lib/types/sync.d.ts +248 -0
  53. package/lib/types/sync.d.ts.map +1 -0
  54. package/package.json +100 -0
@@ -0,0 +1,140 @@
1
+ /**
2
+ * The on-disk half of the prompt index: one markdown file per entry under a
3
+ * single directory, addressed by entry id.
4
+ *
5
+ * Everything a caller can name is validated before it reaches the filesystem —
6
+ * the id grammar is the only path segment this store ever accepts — and writes
7
+ * land through a temporary file plus a rename, so a crashed or interrupted
8
+ * write cannot leave a half-written body in the prompt.
9
+ *
10
+ * @module @lolkda/dsh-prompt-manager/store
11
+ */
12
+ /** Why a store operation was refused. */
13
+ export type PromptStoreErrorCode = 'invalid-id' | 'too-large' | 'conflict' | 'unwritable' | 'unreadable';
14
+ /** A store operation the caller should report, not retry blindly. */
15
+ export declare class PromptStoreError extends Error {
16
+ /** Machine-readable reason. */
17
+ readonly code: PromptStoreErrorCode;
18
+ /**
19
+ * @param code - machine-readable reason.
20
+ * @param message - human-facing detail.
21
+ */
22
+ constructor(code: PromptStoreErrorCode, message: string);
23
+ }
24
+ /** What a write expects to find on disk. */
25
+ export type WriteFence =
26
+ /** Overwrite whatever is there. */
27
+ {
28
+ readonly kind: 'any';
29
+ }
30
+ /** Refuse unless this entry has no body file yet. */
31
+ | {
32
+ readonly kind: 'absent';
33
+ }
34
+ /** Refuse unless the current body still hashes to `sha1`. */
35
+ | {
36
+ readonly kind: 'sha1';
37
+ readonly sha1: string;
38
+ };
39
+ /** One stored body plus the hash a later write can fence against. */
40
+ export interface StoredBody {
41
+ /** Exact UTF-8 contents. */
42
+ body: string;
43
+ /** sha1 of {@link body}, the fence value for the next write. */
44
+ sha1: string;
45
+ }
46
+ /** The store's current situation, as reported to the settings page. */
47
+ export interface StoreStatus {
48
+ /** Absolute directory holding the body files. */
49
+ dir: string;
50
+ /** Whether a body can be written right now. */
51
+ writable: boolean;
52
+ /** Ids that currently have a body file. */
53
+ ids: string[];
54
+ }
55
+ /**
56
+ * sha1 of one body, the value the settings page round-trips to fence writes.
57
+ * @param body - exact UTF-8 contents.
58
+ * @returns a lowercase hex digest.
59
+ */
60
+ export declare function bodyHash(body: string): string;
61
+ /** What one store holds: the file shape and the size a body may reach. */
62
+ export interface PromptStoreOptions {
63
+ /** File extension, leading dot included. Defaults to `.md`. */
64
+ extension?: string | undefined;
65
+ /** Largest accepted body, in bytes. Defaults to {@link MAX_BODY_BYTES}. */
66
+ maxBytes?: number | undefined;
67
+ }
68
+ /**
69
+ * Body files for prompt entries, confined to one directory.
70
+ *
71
+ * The same guards back the user-script directory, where `id` is a script name
72
+ * and the file is `<name>.js`: one id grammar, one fence, one atomic write.
73
+ */
74
+ export declare class PromptStore {
75
+ /** Absolute directory holding the body files. */
76
+ readonly dir: string;
77
+ /** File extension the store owns, leading dot included. */
78
+ private readonly extension;
79
+ /** Largest body this store accepts, in bytes. */
80
+ private readonly maxBytes;
81
+ /**
82
+ * @param dir - directory holding `<id><extension>`; created on first write.
83
+ * @param options - file extension and size cap; the prompt-body defaults are
84
+ * `.md` at 256 KiB, so the script store reuses every guard below unchanged.
85
+ */
86
+ constructor(dir: string, options?: PromptStoreOptions);
87
+ /**
88
+ * Absolute path of one entry's body file.
89
+ * @param id - entry id; validated against the id grammar.
90
+ * @returns the absolute file path.
91
+ * @throws {PromptStoreError} when the id is unusable or escapes {@link dir}.
92
+ */
93
+ bodyPath(id: string): string;
94
+ /**
95
+ * Whether a body file exists.
96
+ * @param id - entry id.
97
+ * @returns `true` when the file is present.
98
+ */
99
+ has(id: string): boolean;
100
+ /**
101
+ * Read one stored body.
102
+ * @param id - entry id.
103
+ * @returns the body and its hash, or `undefined` when no file exists.
104
+ * @throws {PromptStoreError} when the file exists but cannot be read.
105
+ */
106
+ read(id: string): StoredBody | undefined;
107
+ /**
108
+ * Write one body, refusing a stale editor.
109
+ *
110
+ * The fence is judged against the file as it stands when the write reaches
111
+ * the disk, so two editors holding the same draft cannot both land.
112
+ *
113
+ * @param id - entry id.
114
+ * @param body - exact UTF-8 contents to store.
115
+ * @param fence - what the caller expects to find; defaults to overwriting.
116
+ * @returns the hash of what was written.
117
+ * @throws {PromptStoreError} on an invalid id, oversized body, fence mismatch, or failed write.
118
+ */
119
+ write(id: string, body: string, fence?: WriteFence): StoredBody;
120
+ /**
121
+ * Remove one stored body, which restores the bundled default.
122
+ * @param id - entry id.
123
+ * @returns `true` when a file was removed.
124
+ * @throws {PromptStoreError} when the removal fails.
125
+ */
126
+ remove(id: string): boolean;
127
+ /**
128
+ * The directory's situation, for the settings page.
129
+ * @returns the directory, whether writing is possible, and the stored ids.
130
+ */
131
+ status(): StoreStatus;
132
+ /**
133
+ * Ids that currently have a body file.
134
+ * @returns the stored ids, in directory order; empty when the directory is absent.
135
+ */
136
+ ids(): string[];
137
+ /** Create {@link dir} when it is missing. */
138
+ private ensureDirectory;
139
+ }
140
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAoBH,yCAAyC;AACzC,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,WAAW,GAAG,UAAU,GAAG,YAAY,GAAG,YAAY,CAAA;AAExG,qEAAqE;AACrE,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAA;IAEnC;;;OAGG;gBACS,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM;CAKxD;AAED,4CAA4C;AAC5C,MAAM,MAAM,UAAU;AACpB,mCAAmC;AACjC;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;CAAE;AAC1B,qDAAqD;GACnD;IAAE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE;AAC7B,6DAA6D;GAC3D;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEpD,qEAAqE;AACrE,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,gEAAgE;IAChE,IAAI,EAAE,MAAM,CAAA;CACb;AAED,uEAAuE;AACvE,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAA;IACX,+CAA+C;IAC/C,QAAQ,EAAE,OAAO,CAAA;IACjB,2CAA2C;IAC3C,GAAG,EAAE,MAAM,EAAE,CAAA;CACd;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,iDAAiD;IACjD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IAEpB,2DAA2D;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAElC,iDAAiD;IACjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAQ;IAEjC;;;;OAIG;gBACS,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB;IAMzD;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM;IAO5B;;;;OAIG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAQxB;;;;;OAKG;IACH,IAAI,CAAC,EAAE,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAWxC;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,GAAE,UAA4B,GAAG,UAAU;IAgChF;;;;;OAKG;IACH,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAW3B;;;OAGG;IACH,MAAM,IAAI,WAAW;IAYrB;;;OAGG;IACH,GAAG,IAAI,MAAM,EAAE;IAaf,6CAA6C;IAC7C,OAAO,CAAC,eAAe;CAOxB"}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * The subscription engine: the one thing the HTTP routes call, the one thing the
3
+ * settings page reads, and the source of truth for the bodies of subscribed
4
+ * entries.
5
+ *
6
+ * It owns no global state of its own. Sources come from the resolved settings
7
+ * document, proxy and mirror come from the same place, and the index it rewrites
8
+ * is handed back to the plugin through {@link SubscriptionHost.setEntries}, so
9
+ * the plugin stays the only writer of its own namespace.
10
+ *
11
+ * @module @lolkda/dsh-prompt-manager/subscriptions
12
+ */
13
+ import { type ProxyConfig } from './net.js';
14
+ import { type PromptSource } from './source.js';
15
+ import { SourceWorkspace, type CheckOutcome, type PlannedChange } from './sync.js';
16
+ import type { PromptEntry } from './entries.js';
17
+ /** One source as the settings page sees it. */
18
+ export interface SourceSummary {
19
+ /** Slug. */
20
+ id: string;
21
+ /** `owner/name`. */
22
+ repo: string;
23
+ /** Branch, tag, or commit. */
24
+ ref: string;
25
+ /** Effective mirror, with the global default already folded in. */
26
+ mirror: string;
27
+ /** Whether the source takes part in a check, an apply, or a revert. */
28
+ enabled: boolean;
29
+ /** When its files were last applied. */
30
+ appliedAt?: string;
31
+ /** Commit in force. */
32
+ headSha?: string;
33
+ /** Bodies in force. */
34
+ files: number;
35
+ /** Files a previous check staged and nobody applied yet. */
36
+ pending: number;
37
+ }
38
+ /** Where a subscribed entry's body lives. */
39
+ export interface SubscriptionLocation {
40
+ /** Owning source. */
41
+ slug: string;
42
+ /** Repository-relative path. */
43
+ path: string;
44
+ }
45
+ /** What the engine needs from the plugin. */
46
+ export interface SubscriptionHost {
47
+ /** Sources in force, already narrowed from the settings document. */
48
+ sources(): PromptSource[];
49
+ /** Global proxy. */
50
+ proxy(): ProxyConfig;
51
+ /** Global mirror, used when a source does not set its own. */
52
+ mirror(): string;
53
+ /** The plugin's storage root. */
54
+ root(): string;
55
+ /** The index in force. */
56
+ entries(): PromptEntry[];
57
+ /** Replace the index's entry list. */
58
+ setEntries(entries: PromptEntry[]): Promise<void>;
59
+ /** The next free placement for an added entry. */
60
+ nextOrder(): number;
61
+ /** Report a non-fatal problem. */
62
+ warn(message: string): void;
63
+ }
64
+ /** The result of applying a staged plan. */
65
+ export interface ApplyOutcome {
66
+ /** Source slug. */
67
+ slug: string;
68
+ /** Changes that landed. */
69
+ applied: PlannedChange[];
70
+ /** The index after the apply. */
71
+ entries: PromptEntry[];
72
+ }
73
+ /** The subscription engine. */
74
+ export declare class Subscriptions {
75
+ private readonly host;
76
+ /** How one source's workspace is built; replaced in tests. */
77
+ private readonly workspaceOf;
78
+ /**
79
+ * Where the subscribed bodies were last found.
80
+ *
81
+ * Section text is resolved on every assembly, so `readBody` runs once per
82
+ * subscribed entry per model step. Rebuilding this map each time would re-read
83
+ * and re-parse every source's `state.json` in that hot path, so it is computed
84
+ * once and dropped whenever the files or the source list can have changed.
85
+ */
86
+ private cachedLocations;
87
+ /**
88
+ * @param host - the plugin side of the engine.
89
+ * @param options - workspace factory, for tests that count what the engine reads.
90
+ */
91
+ constructor(host: SubscriptionHost, options?: {
92
+ workspace?: (slug: string) => SourceWorkspace;
93
+ });
94
+ /**
95
+ * The workspace of one source.
96
+ * @param slug - source id.
97
+ * @returns its file workspace.
98
+ */
99
+ workspace(slug: string): SourceWorkspace;
100
+ /**
101
+ * Where every subscribed entry's body lives.
102
+ * @returns entry id → source and path, for the entries on disk.
103
+ */
104
+ locate(): Map<string, SubscriptionLocation>;
105
+ /**
106
+ * Forget the cached map and read the sources again.
107
+ *
108
+ * Called when the source list changed, or after this engine moved files, so
109
+ * the map never describes a snapshot that has already been replaced.
110
+ *
111
+ * @returns the freshly computed map.
112
+ */
113
+ refreshLocations(): Map<string, SubscriptionLocation>;
114
+ /**
115
+ * Read one subscribed entry's body.
116
+ * @param id - entry id.
117
+ * @returns the body, or `undefined` when it is not a subscribed entry.
118
+ */
119
+ readBody(id: string): string | undefined;
120
+ /** Build the location map from every source's bookkeeping. */
121
+ private computeLocations;
122
+ /**
123
+ * The configured sources with their on-disk situation.
124
+ * @returns one summary per source.
125
+ */
126
+ list(): SourceSummary[];
127
+ /**
128
+ * Check one source against its upstream and stage whatever changed.
129
+ * @param slug - source id.
130
+ * @returns the check outcome, tagged with its source.
131
+ * @throws {CheckError} when the source is unknown, switched off, or the check cannot conclude.
132
+ */
133
+ check(slug: string): Promise<CheckOutcome & {
134
+ slug: string;
135
+ }>;
136
+ /**
137
+ * Apply what a check staged.
138
+ * @param slug - source id.
139
+ * @param files - paths to apply; all staged changes when omitted.
140
+ * @returns what landed and the index that resulted.
141
+ * @throws {CheckError} when nothing is staged for this source.
142
+ */
143
+ apply(slug: string, files?: readonly string[]): Promise<ApplyOutcome>;
144
+ /**
145
+ * Put back the version the last apply replaced.
146
+ * @param slug - source id.
147
+ * @returns the paths that moved back and the index that resulted.
148
+ */
149
+ revert(slug: string): Promise<{
150
+ slug: string;
151
+ reverted: string[];
152
+ entries: PromptEntry[];
153
+ }>;
154
+ /**
155
+ * Forget one source: its files and its entries.
156
+ *
157
+ * A source that is switched off can still be forgotten: removing it is exactly
158
+ * what a person does with one they no longer want.
159
+ *
160
+ * @param slug - source id.
161
+ * @returns the index that resulted.
162
+ */
163
+ remove(slug: string): Promise<{
164
+ slug: string;
165
+ entries: PromptEntry[];
166
+ }>;
167
+ /**
168
+ * Rebuild the index's subscribed half from every source's state.
169
+ *
170
+ * Local entries are carried through untouched. A subscribed entry keeps the
171
+ * title, placement, and switch a person gave it; an entry seen for the first
172
+ * time arrives disabled, because remote prose must not reach the prompt before
173
+ * someone turns it on.
174
+ *
175
+ * @returns the index now in force.
176
+ */
177
+ syncEntries(): Promise<PromptEntry[]>;
178
+ /**
179
+ * The mirror actually used for one source.
180
+ * @param source - the source.
181
+ * @returns its own mirror, or the global one.
182
+ */
183
+ private effectiveMirror;
184
+ /**
185
+ * Look one source up.
186
+ * @param slug - source id.
187
+ * @returns the source.
188
+ * @throws {CheckError} when no such source is configured.
189
+ */
190
+ private source;
191
+ /**
192
+ * Look up a source that may be operated on.
193
+ *
194
+ * A switched-off source keeps the bodies it already applied — turning it off
195
+ * is not a way to erase entries that are in force — but it takes no new work
196
+ * from upstream until it is switched back on.
197
+ *
198
+ * @param slug - source id.
199
+ * @returns the source.
200
+ * @throws {CheckError} when no such source is configured, or it is switched off.
201
+ */
202
+ private writableSource;
203
+ }
204
+ //# sourceMappingURL=subscriptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../../src/subscriptions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,248 @@
1
+ /**
2
+ * One source's files on disk, and the three operations the settings page drives:
3
+ * check what changed upstream, apply it, undo the last apply.
4
+ *
5
+ * The layout is a three-slot rotation, so nothing is ever half-updated:
6
+ * `staging/` holds what a check downloaded, `current/` is what the prompt reads,
7
+ * and `previous/` keeps the version the last apply replaced so one click can put
8
+ * it back. `state.json` carries the bookkeeping: which commit is in force, which
9
+ * entry each file became, and the hashes and validators that make the next check
10
+ * cheap.
11
+ *
12
+ * @module @lolkda/dsh-prompt-manager/sync
13
+ */
14
+ import { type Fetcher } from './net.js';
15
+ import { type ManifestPrompt, type PromptSource } from './source.js';
16
+ /** One file's bookkeeping inside a source. */
17
+ export interface SourceFileState {
18
+ /** Local entry id this file became. */
19
+ id: string;
20
+ /**
21
+ * The id this file carried before the apply that recorded it, when that apply
22
+ * changed the id — a manifest that started declaring its own `id`, or a file
23
+ * whose name changed. The index reads it to move the title, placement, and
24
+ * switch a person chose onto the new id instead of starting the entry over.
25
+ * Dropped by the next apply of the same path, so it never outlives its use.
26
+ */
27
+ renamedFromId?: string;
28
+ /** Display title the manifest asked for, when it asked for one. */
29
+ title?: string;
30
+ /** Placement the manifest asked for, when it asked for one. */
31
+ order?: number;
32
+ /** Whether the manifest wanted it enabled; the importer overrides on import. */
33
+ enabled: boolean;
34
+ /** sha1 of the body in force. */
35
+ sha1: string;
36
+ /** `etag` the upstream sent for that body. */
37
+ etag?: string;
38
+ }
39
+ /** A source's bookkeeping, persisted beside its files. */
40
+ export interface SourceState {
41
+ /** Ref the snapshot was taken from. */
42
+ ref: string;
43
+ /** Commit in force, when the ref could be resolved to one. */
44
+ headSha?: string;
45
+ /** Commit the last apply replaced, for revert. */
46
+ headShaPrevious?: string;
47
+ /** sha1 of the manifest in force. */
48
+ manifestSha1?: string;
49
+ /** When the last apply ran, ISO-8601. */
50
+ appliedAt?: string;
51
+ /** One record per body file in force, keyed by repository path. */
52
+ files: Record<string, SourceFileState>;
53
+ /**
54
+ * The last apply's undo ledger: the record each touched path had before, or
55
+ * `null` when the path had no file at all. This is what makes revert exact —
56
+ * a removed file comes back with the title, placement, and switch it had.
57
+ */
58
+ undo?: Record<string, SourceFileState | null>;
59
+ }
60
+ /** One file the upstream check found a difference in. */
61
+ export interface PlannedChange {
62
+ /** Repository-relative path. */
63
+ path: string;
64
+ /** Local entry id. */
65
+ id: string;
66
+ /** Display title from the manifest, when present. */
67
+ title?: string;
68
+ /** Placement from the manifest, when present. */
69
+ order?: number;
70
+ /** Which way the file moved. */
71
+ kind: 'added' | 'changed' | 'removed';
72
+ /** Lines the new body has and the old one did not. */
73
+ added: number;
74
+ /** Lines the old body had and the new one does not. */
75
+ removed: number;
76
+ /**
77
+ * On an added file: the path this file was renamed from, when the check
78
+ * recognised it as a rename. The pair is applied together or not at all.
79
+ */
80
+ renamedFrom?: string;
81
+ /**
82
+ * On a removal: the path this file was renamed to. Present exactly when the
83
+ * matching added change carries {@link renamedFrom}.
84
+ */
85
+ renamedTo?: string;
86
+ }
87
+ /** What a check concluded. */
88
+ export interface CheckOutcome {
89
+ /** Whether upstream already matches what is in force. */
90
+ upToDate: boolean;
91
+ /** Commit the ref resolves to, when it could be resolved. */
92
+ headSha?: string;
93
+ /** Files that differ, newest manifest order first. */
94
+ changes: PlannedChange[];
95
+ /** Prompts the manifest declared, in order. */
96
+ prompts: ManifestPrompt[];
97
+ /** Non-fatal problems worth showing: an unreachable feed, a skipped file. */
98
+ warnings: string[];
99
+ }
100
+ /** What a check left staged, for a later apply to pick up. */
101
+ export interface StagedPlan {
102
+ /** Commit the check resolved. */
103
+ headSha?: string;
104
+ /** sha1 of the manifest the check read. */
105
+ manifestSha1: string;
106
+ /** Prompts the manifest declared. */
107
+ prompts: ManifestPrompt[];
108
+ /** Files the check staged. */
109
+ changes: PlannedChange[];
110
+ /** `etag` per staged path, the validator the next check sends. */
111
+ etags: Record<string, string>;
112
+ }
113
+ /** A check that could not conclude. */
114
+ export declare class CheckError extends Error {
115
+ /** Machine-readable reason. */
116
+ readonly reason: 'manifest' | 'network' | 'mirror' | 'too-large' | 'unknown-source' | 'nothing-staged' | 'disabled';
117
+ /**
118
+ * @param reason - machine-readable reason.
119
+ * @param message - human-facing detail.
120
+ */
121
+ constructor(reason: CheckError['reason'], message: string);
122
+ }
123
+ /** The files of one source, confined to one directory. */
124
+ export declare class SourceWorkspace {
125
+ /** Source slug. */
126
+ readonly slug: string;
127
+ /** `<root>/sources/<slug>`. */
128
+ readonly dir: string;
129
+ /**
130
+ * @param root - the plugin's storage root, e.g. `$DSH_HOME/prompt-manager`.
131
+ * @param slug - the source id.
132
+ */
133
+ constructor(root: string, slug: string);
134
+ /** Directory holding the version in force. */
135
+ get currentDir(): string;
136
+ /** Directory holding the version the last apply replaced. */
137
+ get previousDir(): string;
138
+ /** Directory holding what a check downloaded but nobody applied yet. */
139
+ get stagingDir(): string;
140
+ /** Path of the bookkeeping file. */
141
+ get statePath(): string;
142
+ /**
143
+ * Absolute path of one body file inside one slot.
144
+ * @param slot - which slot.
145
+ * @param path - repository-relative manifest path.
146
+ * @returns the absolute path; a traversal attempt resolves to `undefined`.
147
+ */
148
+ slotPath(slot: 'current' | 'previous' | 'staging', path: string): string | undefined;
149
+ /** Whether this source has any files on disk. */
150
+ exists(): boolean;
151
+ /**
152
+ * Read the bookkeeping, defaulting to an empty state.
153
+ * @returns the state; an unreadable or malformed file reads as empty.
154
+ */
155
+ readState(): SourceState;
156
+ /**
157
+ * Persist the bookkeeping.
158
+ * @param state - the state to write.
159
+ */
160
+ writeState(state: SourceState): void;
161
+ /** Read one body from a slot. */
162
+ read(slot: 'current' | 'previous' | 'staging', path: string): string | undefined;
163
+ /** Paths present in a slot, relative to that slot. */
164
+ list(slot: 'current' | 'previous' | 'staging'): string[];
165
+ /** Write one body into the staging slot. */
166
+ stage(path: string, text: string): void;
167
+ /** Empty the staging slot. */
168
+ clearStaging(): void;
169
+ /**
170
+ * Record what a check staged, so an apply can run without repeating the
171
+ * network round trip.
172
+ * @param plan - the staged plan.
173
+ */
174
+ writePlan(plan: StagedPlan): void;
175
+ /**
176
+ * Read the staged plan.
177
+ * @returns the plan, or `undefined` when nothing is staged.
178
+ */
179
+ readPlan(): StagedPlan | undefined;
180
+ /** Remove the whole source directory. */
181
+ remove(): void;
182
+ }
183
+ /**
184
+ * Line counts of what a change adds and removes, by longest common
185
+ * subsequence over lines. The dynamic table is skipped for pathologically large
186
+ * pairs, where the answer degrades to "roughly how many lines differ" rather
187
+ * than stalling the request.
188
+ *
189
+ * @param before - the body in force, or `''` for a new file.
190
+ * @param after - the checked body.
191
+ * @returns added and removed line counts.
192
+ */
193
+ export declare function diffCounts(before: string, after: string): {
194
+ added: number;
195
+ removed: number;
196
+ };
197
+ /**
198
+ * Check one source against its upstream and stage whatever changed.
199
+ *
200
+ * @param input - source, workspace, and the fetcher carrying the proxy and mirror.
201
+ * @returns what changed, or that nothing did.
202
+ * @throws {CheckError} when the check cannot conclude.
203
+ */
204
+ export declare function checkSource(input: {
205
+ source: PromptSource;
206
+ workspace: SourceWorkspace;
207
+ fetcher: Fetcher;
208
+ }): Promise<CheckOutcome>;
209
+ /**
210
+ * Apply staged changes into the version in force.
211
+ *
212
+ * @param input - workspace, current state, the staged plan, the source, and the
213
+ * paths the caller selected (all of them when omitted).
214
+ * @returns the new state and the changes that landed.
215
+ */
216
+ export declare function applyChanges(input: {
217
+ workspace: SourceWorkspace;
218
+ state: SourceState;
219
+ plan: StagedPlan;
220
+ source: PromptSource;
221
+ selected?: readonly string[];
222
+ nowIso: string;
223
+ }): {
224
+ state: SourceState;
225
+ applied: PlannedChange[];
226
+ };
227
+ /**
228
+ * Put the version the last apply replaced back in force, guided by the apply's
229
+ * undo ledger so a restored file also gets its title, placement, and switch back.
230
+ *
231
+ * @param input - workspace and current state.
232
+ * @returns the restored state and the paths that moved back.
233
+ */
234
+ export declare function revertChanges(input: {
235
+ workspace: SourceWorkspace;
236
+ state: SourceState;
237
+ }): {
238
+ state: SourceState;
239
+ reverted: string[];
240
+ };
241
+ /**
242
+ * The entry title a manifest file should get on import.
243
+ * @param source - owning source.
244
+ * @param prompt - the manifest entry.
245
+ * @returns a non-empty display title.
246
+ */
247
+ export declare function titleFor(source: PromptSource, prompt: ManifestPrompt): string;
248
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/sync.ts"],"names":[],"mappings":"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"}