@joystyk/content 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/NOTICE +36 -0
- package/README.md +113 -2
- package/dist/banks.d.ts +14 -0
- package/dist/banks.js +55 -0
- package/dist/banks.js.map +1 -0
- package/dist/extract-json.d.ts +12 -0
- package/dist/extract-json.js +59 -0
- package/dist/extract-json.js.map +1 -0
- package/dist/filter.d.ts +56 -0
- package/dist/filter.js +127 -0
- package/dist/filter.js.map +1 -0
- package/dist/games.d.ts +9 -0
- package/dist/games.js +40 -0
- package/dist/games.js.map +1 -0
- package/dist/hash.d.ts +4 -0
- package/dist/hash.js +17 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +16 -0
- package/dist/log.d.ts +26 -0
- package/dist/log.js +58 -0
- package/dist/log.js.map +1 -0
- package/dist/model.d.ts +77 -0
- package/dist/model.js +268 -0
- package/dist/model.js.map +1 -0
- package/dist/packs.d.ts +144 -0
- package/dist/packs.js +466 -0
- package/dist/packs.js.map +1 -0
- package/dist/pipeline.d.ts +252 -0
- package/dist/pipeline.js +558 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/publish.d.ts +21 -0
- package/dist/publish.js +56 -0
- package/dist/publish.js.map +1 -0
- package/dist/ranked.d.ts +12 -0
- package/dist/ranked.js +40 -0
- package/dist/ranked.js.map +1 -0
- package/dist/screen.d.ts +25 -0
- package/dist/screen.js +46 -0
- package/dist/screen.js.map +1 -0
- package/dist/store.d.ts +136 -0
- package/dist/store.js +300 -0
- package/dist/store.js.map +1 -0
- package/dist/text.d.ts +31 -0
- package/dist/text.js +111 -0
- package/dist/text.js.map +1 -0
- package/dist/vocabulary.d.ts +16 -0
- package/dist/vocabulary.js +44 -0
- package/dist/vocabulary.js.map +1 -0
- package/lists/SCOWL-COPYRIGHT +252 -0
- package/lists/blocklist.txt +214 -0
- package/lists/common-extra.txt +13 -0
- package/lists/scowl-common.txt +70763 -0
- package/lists/scowl-ranked.txt +72076 -0
- package/lists/scowl-vocabulary.txt +55636 -0
- package/lists/sensitive.txt +280 -0
- package/package.json +53 -3
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { type Json, type Result, type StandardSchemaV1 } from '@joystyk/contracts';
|
|
2
|
+
import type { FieldText, FilterHit, WordFilter } from './filter.js';
|
|
3
|
+
import { type Logger } from './log.js';
|
|
4
|
+
import { type CallInfo, type CompletionFailure, type ModelClient } from './model.js';
|
|
5
|
+
import type { Publisher } from './publish.js';
|
|
6
|
+
import type { ContentStore, ItemRow, WithdrawReason } from './store.js';
|
|
7
|
+
export interface ScheduleContext {
|
|
8
|
+
readonly date: string;
|
|
9
|
+
readonly puzzleId: string;
|
|
10
|
+
/** Answers used or reserved within the requested history window, withdrawn ones included. */
|
|
11
|
+
readonly used: readonly {
|
|
12
|
+
readonly date: string;
|
|
13
|
+
readonly answer: string;
|
|
14
|
+
}[];
|
|
15
|
+
}
|
|
16
|
+
export interface Scheduled<Inputs extends Json> {
|
|
17
|
+
readonly inputs: Inputs;
|
|
18
|
+
readonly seed: string;
|
|
19
|
+
/** The answer key the inputs commit to, if the schedule picks it (Word of the Day's word). */
|
|
20
|
+
readonly answer: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface GenerateContext<Inputs extends Json> {
|
|
23
|
+
readonly date: string;
|
|
24
|
+
readonly puzzleId: string;
|
|
25
|
+
readonly inputs: Inputs;
|
|
26
|
+
readonly seed: string;
|
|
27
|
+
/** 1-based, within this run. */
|
|
28
|
+
readonly attempt: number;
|
|
29
|
+
/** What was wrong with the previous attempt, in words the prompt can repeat. Empty on the first. */
|
|
30
|
+
readonly feedback: readonly string[];
|
|
31
|
+
readonly model: ModelClient;
|
|
32
|
+
/** Normalized text keys used on nearby dates, withdrawn content included. */
|
|
33
|
+
readonly recentKeys?: readonly {
|
|
34
|
+
readonly date: string;
|
|
35
|
+
readonly key: string;
|
|
36
|
+
}[];
|
|
37
|
+
/** Published reusable catalogs for this game; each generator decodes its own schema. */
|
|
38
|
+
readonly catalogs?: readonly Json[];
|
|
39
|
+
readonly signal?: AbortSignal;
|
|
40
|
+
/**
|
|
41
|
+
* Keeps a game's own check results (for example Crossword's fact-check verdicts) in the
|
|
42
|
+
* attempt's report, under `checks[name]`, whether the attempt passes or fails. A later
|
|
43
|
+
* call with the same name replaces the earlier one. Absent when a test calls `generate`
|
|
44
|
+
* directly.
|
|
45
|
+
*/
|
|
46
|
+
readonly record?: (name: string, detail: Json) => void;
|
|
47
|
+
}
|
|
48
|
+
export type GenerateFailure =
|
|
49
|
+
/** A model call failed: the endpoint, or a reply that didn't parse or match its schema. */
|
|
50
|
+
{
|
|
51
|
+
readonly kind: 'model';
|
|
52
|
+
readonly error: CompletionFailure;
|
|
53
|
+
}
|
|
54
|
+
/** The game's own model checks refused the draft (for example a blind answer check). */
|
|
55
|
+
| {
|
|
56
|
+
readonly kind: 'rejected';
|
|
57
|
+
readonly issues: readonly string[];
|
|
58
|
+
};
|
|
59
|
+
export interface DailyGenerator<Config extends Json = Json, Inputs extends Json = Json> {
|
|
60
|
+
readonly gameId: string;
|
|
61
|
+
/** Recorded with every item, e.g. "wotd-v1". */
|
|
62
|
+
readonly promptVersion: string;
|
|
63
|
+
/** Scheduling history on either side of the date; defaults to 365 days. */
|
|
64
|
+
readonly historyDays?: number;
|
|
65
|
+
/** Numeric/card/grid configurations with no generated player-visible text. They can
|
|
66
|
+
* publish without a model endpoint; `texts` must be empty. */
|
|
67
|
+
readonly modelFree?: boolean;
|
|
68
|
+
/** Optional additional no-repeat rule, independent of the answer schedule. */
|
|
69
|
+
readonly repeatWindowDays?: number;
|
|
70
|
+
readonly reusesCatalogs?: boolean;
|
|
71
|
+
repeatKeys?(config: Config): readonly string[];
|
|
72
|
+
/** The game's `schemas.config`: every config is decoded through it before anything else. */
|
|
73
|
+
readonly configSchema: StandardSchemaV1<unknown, Config>;
|
|
74
|
+
/** Deterministic inputs for a slot: the same date and history always give the same inputs. */
|
|
75
|
+
schedule(context: ScheduleContext): Scheduled<Inputs>;
|
|
76
|
+
generate(context: GenerateContext<Inputs>): Promise<Result<Config, GenerateFailure>>;
|
|
77
|
+
/** The game validators: pure; an empty list means the config passes. */
|
|
78
|
+
validate(config: Config, context: {
|
|
79
|
+
readonly date: string;
|
|
80
|
+
readonly puzzleId: string;
|
|
81
|
+
readonly inputs: Inputs | null;
|
|
82
|
+
}): readonly string[];
|
|
83
|
+
/** Every player-visible text, for the all-ages filter and the model screen. */
|
|
84
|
+
texts(config: Config): readonly FieldText[];
|
|
85
|
+
/** The answer key for duplicate checks. */
|
|
86
|
+
answerOf(config: Config): string | null;
|
|
87
|
+
/** The reviewed fallback bank: `size` complete items, each turned into a config for a date. */
|
|
88
|
+
readonly bank: {
|
|
89
|
+
readonly size: number;
|
|
90
|
+
config(index: number, puzzleId: string): Config;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export interface CallReport {
|
|
94
|
+
readonly name: string;
|
|
95
|
+
readonly ok: boolean;
|
|
96
|
+
readonly error: string | null;
|
|
97
|
+
readonly latencyMs: number;
|
|
98
|
+
readonly requests: number;
|
|
99
|
+
readonly mode: string;
|
|
100
|
+
readonly promptTokens: number | null;
|
|
101
|
+
readonly completionTokens: number | null;
|
|
102
|
+
readonly totalTokens: number | null;
|
|
103
|
+
}
|
|
104
|
+
export type AttemptOutcome = 'validated' | 'endpoint' | 'reply' | 'schema' | 'rejected' | 'invalid' | 'duplicate' | 'filtered' | 'screened' | 'aborted';
|
|
105
|
+
export interface AttemptReport {
|
|
106
|
+
readonly attempt: number;
|
|
107
|
+
readonly outcome: AttemptOutcome;
|
|
108
|
+
readonly issues: readonly string[];
|
|
109
|
+
readonly calls: readonly CallReport[];
|
|
110
|
+
/** Null when the attempt never reached the filter. */
|
|
111
|
+
readonly filter: {
|
|
112
|
+
readonly ok: boolean;
|
|
113
|
+
readonly hits: readonly FilterHit[];
|
|
114
|
+
} | null;
|
|
115
|
+
/** Null when the attempt never reached the screen (or screening is off). */
|
|
116
|
+
readonly screen: {
|
|
117
|
+
readonly safe: boolean;
|
|
118
|
+
readonly reasons: readonly string[];
|
|
119
|
+
} | null;
|
|
120
|
+
/** What the game's own checks recorded (`GenerateContext.record`); absent when nothing was. */
|
|
121
|
+
readonly checks?: Readonly<Record<string, Json>>;
|
|
122
|
+
}
|
|
123
|
+
export type SlotAction = 'existing' | 'generated' | 'bank' | 'failed' | 'deferred' | 'aborted';
|
|
124
|
+
export interface SlotReport {
|
|
125
|
+
readonly gameId: string;
|
|
126
|
+
readonly date: string;
|
|
127
|
+
readonly puzzleId: string;
|
|
128
|
+
readonly action: SlotAction;
|
|
129
|
+
readonly status: string;
|
|
130
|
+
readonly source: string | null;
|
|
131
|
+
readonly answer: string | null;
|
|
132
|
+
/** The slot's scheduled inputs (a bank item may replace what they asked for). */
|
|
133
|
+
readonly inputs: Json | null;
|
|
134
|
+
readonly attempts: readonly AttemptReport[];
|
|
135
|
+
readonly contentHash: string | null;
|
|
136
|
+
readonly config: Json | null;
|
|
137
|
+
}
|
|
138
|
+
export interface PublishReport {
|
|
139
|
+
readonly gameId: string;
|
|
140
|
+
readonly date: string;
|
|
141
|
+
readonly file: string;
|
|
142
|
+
readonly hash: string;
|
|
143
|
+
readonly size: number;
|
|
144
|
+
}
|
|
145
|
+
export interface RunReport {
|
|
146
|
+
readonly runId: number;
|
|
147
|
+
readonly status: 'ok' | 'partial' | 'failed' | 'aborted';
|
|
148
|
+
readonly today: string;
|
|
149
|
+
readonly slots: readonly SlotReport[];
|
|
150
|
+
readonly published: readonly PublishReport[];
|
|
151
|
+
readonly breakerOpen: boolean;
|
|
152
|
+
}
|
|
153
|
+
export interface PipelineOptions {
|
|
154
|
+
readonly store: ContentStore;
|
|
155
|
+
readonly generators: readonly DailyGenerator[];
|
|
156
|
+
/** Null runs without a model: existing items publish and late slots take bank items. */
|
|
157
|
+
readonly model: ModelClient | null;
|
|
158
|
+
readonly filter: WordFilter;
|
|
159
|
+
/** Null for a dry run: nothing is written to the published-files folder. */
|
|
160
|
+
readonly publisher: Publisher | null;
|
|
161
|
+
readonly now?: () => number;
|
|
162
|
+
/** The dates to fill. Default: today and the next `daysAhead` days. */
|
|
163
|
+
readonly dates?: readonly string[];
|
|
164
|
+
/** Default 1: today and tomorrow (D-46). */
|
|
165
|
+
readonly daysAhead?: number;
|
|
166
|
+
/** Only these games. */
|
|
167
|
+
readonly games?: readonly string[];
|
|
168
|
+
readonly maxAttempts?: number;
|
|
169
|
+
/** Consecutive endpoint errors that end generation for the run. Default 5. */
|
|
170
|
+
readonly breakerThreshold?: number;
|
|
171
|
+
/** A slot not validated this long before its release takes a bank item. Default 48 hours. */
|
|
172
|
+
readonly deadlineHours?: number;
|
|
173
|
+
/** Give a late slot a bank item (default true). `content bank`, which grows the banks, turns it off. */
|
|
174
|
+
readonly bankFallback?: boolean;
|
|
175
|
+
/** The newest date published and listed, as days after today. Default 1 (D-46). */
|
|
176
|
+
readonly listAheadDays?: number;
|
|
177
|
+
/** How many past days the index keeps listing. Default 14. */
|
|
178
|
+
readonly indexBackDays?: number;
|
|
179
|
+
/** Run the model screen (layer 3). Default true. */
|
|
180
|
+
readonly screen?: boolean;
|
|
181
|
+
/** Pause before attempt n + 1 is `backoffMs * 2^(n-1)`. Default 2000. */
|
|
182
|
+
readonly backoffMs?: number;
|
|
183
|
+
readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
184
|
+
readonly signal?: AbortSignal;
|
|
185
|
+
readonly log?: Logger;
|
|
186
|
+
/** Recorded on the run: "schedule", "manual", "live-validation"… */
|
|
187
|
+
readonly trigger?: string;
|
|
188
|
+
}
|
|
189
|
+
/** @internal Shared with packs.ts. */
|
|
190
|
+
export declare const defaultSleep: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
191
|
+
/** @internal Shared with packs.ts. */
|
|
192
|
+
export declare const callReport: (call: CallInfo, error: CompletionFailure | null) => CallReport;
|
|
193
|
+
/** Wraps a model client so every call, successful or not, lands in `calls`. */
|
|
194
|
+
/** @internal Shared with packs.ts. */
|
|
195
|
+
export declare const recording: (model: ModelClient, calls: CallReport[]) => ModelClient;
|
|
196
|
+
/** @internal Shared with packs.ts. */
|
|
197
|
+
export declare const outcomeOf: (error: CompletionFailure) => AttemptOutcome;
|
|
198
|
+
/** @internal Shared with packs.ts. */
|
|
199
|
+
export declare const issuesOf: (error: CompletionFailure) => string[];
|
|
200
|
+
/** @internal Shared with packs.ts. */
|
|
201
|
+
export declare const decodeConfig: <Config>(schema: StandardSchemaV1<unknown, Config>, value: unknown) => Promise<Result<Config, string[]>>;
|
|
202
|
+
/** Nearby text keys decoded through the game schema; corrections on the same date may retain clues. */
|
|
203
|
+
export declare const recentContentKeys: (store: ContentStore, generator: DailyGenerator, date: string) => Promise<{
|
|
204
|
+
date: string;
|
|
205
|
+
key: string;
|
|
206
|
+
}[]>;
|
|
207
|
+
/**
|
|
208
|
+
* Checks a config the way every item is checked: the schema, the game validators, the
|
|
209
|
+
* duplicate rule and the word lists. The model screen is separate (it needs the endpoint).
|
|
210
|
+
*/
|
|
211
|
+
export declare const checkDailyConfig: (generator: DailyGenerator, value: unknown, context: {
|
|
212
|
+
date: string;
|
|
213
|
+
puzzleId: string;
|
|
214
|
+
inputs: Json | null;
|
|
215
|
+
used: readonly {
|
|
216
|
+
date: string;
|
|
217
|
+
answer: string;
|
|
218
|
+
}[];
|
|
219
|
+
filter: WordFilter;
|
|
220
|
+
recentKeys?: readonly {
|
|
221
|
+
date: string;
|
|
222
|
+
key: string;
|
|
223
|
+
}[];
|
|
224
|
+
}) => Promise<{
|
|
225
|
+
outcome: AttemptOutcome;
|
|
226
|
+
issues: string[];
|
|
227
|
+
config: Json | null;
|
|
228
|
+
filter: AttemptReport["filter"];
|
|
229
|
+
}>;
|
|
230
|
+
export declare const runPipeline: (options: PipelineOptions) => Promise<RunReport>;
|
|
231
|
+
/** Rewrites a game's index from the store: published items from `indexBackDays` ago through today + `listAheadDays`. */
|
|
232
|
+
export declare const rebuildIndex: (store: ContentStore, publisher: Publisher, gameId: string, today: string, { listAheadDays, indexBackDays }?: {
|
|
233
|
+
listAheadDays?: number;
|
|
234
|
+
indexBackDays?: number;
|
|
235
|
+
}) => void;
|
|
236
|
+
/**
|
|
237
|
+
* Takes an item down (content-pipeline.md, "Corrections"): the item is withdrawn with a
|
|
238
|
+
* reason code and actor, the game's index drops it at once, and the next run fills the slot
|
|
239
|
+
* again, from the model or the bank. The withdrawn answer counts as used, so the replacement
|
|
240
|
+
* gets a different one. Returns the withdrawn item, or null if no item held the puzzle.
|
|
241
|
+
*/
|
|
242
|
+
export declare const withdrawItem: (options: {
|
|
243
|
+
readonly store: ContentStore;
|
|
244
|
+
readonly publisher: Publisher | null;
|
|
245
|
+
readonly gameId: string;
|
|
246
|
+
readonly date: string;
|
|
247
|
+
readonly reason: WithdrawReason;
|
|
248
|
+
readonly actor: string;
|
|
249
|
+
readonly note?: string;
|
|
250
|
+
readonly now?: number;
|
|
251
|
+
readonly listAheadDays?: number;
|
|
252
|
+
}) => ItemRow | null;
|