@hraness/sponge 0.3.0 → 0.3.1
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/dist/src/cli/commands.js +1 -1
- package/dist/src/digest/pipeline.d.ts +1 -1
- package/dist/src/digest/pipeline.js +33 -9
- package/dist/src/importers/jungle-store.d.ts +6 -2
- package/dist/src/importers/jungle-store.js +6 -4
- package/dist/src/importers/jungle.d.ts +2 -2
- package/dist/src/importers/jungle.js +8 -2
- package/native/runtime-manifest.json +4 -4
- package/package.json +1 -1
package/dist/src/cli/commands.js
CHANGED
|
@@ -591,7 +591,7 @@ export const commands = [
|
|
|
591
591
|
summary: "Set or reset an item's title",
|
|
592
592
|
usage: [
|
|
593
593
|
['title set ITEM TEXT', 'Use TEXT as the title; refresh and import never change it'],
|
|
594
|
-
['title reset ITEM', 'Restore the default: the
|
|
594
|
+
['title reset ITEM', 'Restore the default: the captured title (a local scan takes its import note title)'],
|
|
595
595
|
],
|
|
596
596
|
positional: (p) => (p[0] === 'set' && p.length === 3) || (p[0] === 'reset' && p.length === 2),
|
|
597
597
|
flags: {},
|
|
@@ -4,7 +4,7 @@ import type { Derivation, DigestCandidate } from './derivation.js';
|
|
|
4
4
|
* Model-agnostic Digest v2 drafting. The model sees numbered passages and cites a passage id plus a short exact
|
|
5
5
|
* span; code snaps, prunes and checks. Nothing leaves this module except through the injected model.
|
|
6
6
|
*/
|
|
7
|
-
export declare const PROMPT_VERSION = "sponge-digest-prompt-
|
|
7
|
+
export declare const PROMPT_VERSION = "sponge-digest-prompt-v5";
|
|
8
8
|
export type ChatMessage = {
|
|
9
9
|
role: 'system' | 'user' | 'assistant';
|
|
10
10
|
content: string;
|
|
@@ -6,7 +6,7 @@ import { sha256 } from '../util/hash.js';
|
|
|
6
6
|
* Model-agnostic Digest v2 drafting. The model sees numbered passages and cites a passage id plus a short exact
|
|
7
7
|
* span; code snaps, prunes and checks. Nothing leaves this module except through the injected model.
|
|
8
8
|
*/
|
|
9
|
-
export const PROMPT_VERSION = 'sponge-digest-prompt-
|
|
9
|
+
export const PROMPT_VERSION = 'sponge-digest-prompt-v5';
|
|
10
10
|
/** 24k characters is roughly 6k tokens of passages, leaving room for instructions and output in an 8k context on an 8 GB Mac. */
|
|
11
11
|
const defaultDigestParams = {
|
|
12
12
|
temperature: 0,
|
|
@@ -148,7 +148,7 @@ export function draftSchema(passageCount, vocabulary) {
|
|
|
148
148
|
return {
|
|
149
149
|
type: 'object',
|
|
150
150
|
additionalProperties: false,
|
|
151
|
-
required: ['lead', 'ideas', 'tags'],
|
|
151
|
+
required: ['lead', 'context', 'ideas', 'tags'],
|
|
152
152
|
properties: {
|
|
153
153
|
lead: str(L.leadChars),
|
|
154
154
|
context: str(600),
|
|
@@ -177,16 +177,36 @@ function notesSchema(first, last) {
|
|
|
177
177
|
},
|
|
178
178
|
};
|
|
179
179
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
/**
|
|
181
|
+
* Style examples are invented sources shaped like reviewed digests (short verdict claims, dense details); no library
|
|
182
|
+
* text. They show voice only: the model must still draw every claim and number from the source it is given.
|
|
183
|
+
*/
|
|
184
|
+
const styleExamples = `Style examples (invented sources; copy the voice, never the content):
|
|
185
|
+
Source: a database company's post on moving its write path to object storage.
|
|
186
|
+
lead: Acme moves Ledger's write path onto an append-only log in S3 and cuts p99 commit latency from 40 ms to 9 ms.
|
|
187
|
+
context: Postgres becomes a read replica rebuilt from the log. Batching makes object storage affordable; single-row workloads still get slower.
|
|
188
|
+
claim: The log is the database. | detail: Every write appends one immutable segment; Postgres is rebuilt from the log and serves reads only.
|
|
189
|
+
claim: Batching pays for the bucket. | detail: Commits wait up to 5 ms, so one PUT carries about 300 writes and request cost falls 40-fold.
|
|
190
|
+
claim: Small writers lose. | detail: Under ten writes per second, latency is higher than before the move.
|
|
191
|
+
Source: an essay arguing that taste is a skill.
|
|
192
|
+
lead: Mara Lind argues taste is trained by making and comparing many small things, not inherited, and that teams can schedule it.
|
|
193
|
+
claim: Taste is trained. | detail: Lind's students improved most after drawing fifty quick versions, not after studying masters.
|
|
194
|
+
claim: Volume beats polish early. | detail: Side-by-side comparison of many rough drafts sharpens judgment faster than finishing one.
|
|
195
|
+
Bad claim: The essay explores how practice over time shapes a person's sense of taste.
|
|
196
|
+
Good claim: Taste is trained.`;
|
|
197
|
+
const system = (budget, vocabulary) => `You write a Digest v2 of one source for a private reading library, in the owner's voice: terse, declarative, specific. Reply with JSON only, matching the schema.
|
|
198
|
+
- lead: one sentence of at most ${L.leadChars} characters stating this source's specific thesis or result: who (author, organization or product by name) shows or argues what, with the number, name or mechanism that makes it this source. Do not restate the title, describe the source ("This article discusses"), or tease.
|
|
199
|
+
- context: 1 or 2 sentences, at most 45 words: the evidence, method, numbers or limits the lead could not hold. Lead and context together stay within ${L.gistWords} words and ${L.gistSentences} sentences.
|
|
183
200
|
- ideas: ${L.minIdeas} to ${L.maxIdeas} (prefer ${L.maxIdeas} when the source has them), in the order the author makes them, covering the whole source including its caveats and limits. Each idea is a claim with detail:
|
|
184
|
-
- claim:
|
|
185
|
-
- detail:
|
|
201
|
+
- claim: a verdict of 4 to 8 words (never more than 12): subject, verb, point, naming this source's own subject ("Batching pays for the bucket", not "Costs drop"). Something a reader could disagree with; never a topic label or a description of the source.
|
|
202
|
+
- detail: one or two sentences, 15 to 30 words, dense with the source's own names, numbers, mechanism or limits behind the claim. Do not repeat the claim.
|
|
186
203
|
- quotes: 0 to ${L.maxQuotesPerIdea} that support this idea, each a contiguous span copied character for character from one numbered passage, ${minQuoteWords + 2} to 20 words, with that passage number. A quote is a fragment, not a whole long sentence: when a sentence runs past 20 words, copy only its strongest part. No added quotation marks or ellipses. Give attribution (who said it) when it is not the author. Prefer reader highlights.
|
|
187
204
|
- Attach at least ${L.minQuotes} and at most ${L.maxQuotes} quotes in all; ${L.minQuotes + 1} to 3 is typical. All quotes together stay within ${budget} words and never overlap.
|
|
188
205
|
- tags: 0 to ${L.maxTags} ${vocabulary?.length ? `from: ${vocabulary.join(', ')}` : 'lowercase kebab-case topics'}; usually one subject tag.
|
|
189
|
-
|
|
206
|
+
Plain words. Name things and use numbers. No hedges or filler: may, might, potentially, various, significantly, key, crucial, notably, highlights, explores, discusses, "the author argues", "the article".
|
|
207
|
+
Use only what the source says.
|
|
208
|
+
|
|
209
|
+
${styleExamples}`;
|
|
190
210
|
function sourceBlock(title, form, list, highlights) {
|
|
191
211
|
let heading;
|
|
192
212
|
const lines = [];
|
|
@@ -332,7 +352,11 @@ const pruneable = new Set(['not-verbatim', 'overlap', 'unsupported', 'too-long',
|
|
|
332
352
|
* budget (second quotes first, longest first), never the last quote. Quoteless ideas stay: v2 allows them.
|
|
333
353
|
*/
|
|
334
354
|
function prune(draft, issues, source, options) {
|
|
335
|
-
const dropped = { quotes: 0, ideas: 0, tags: 0 }, d = draft.digest
|
|
355
|
+
const dropped = { quotes: 0, ideas: 0, tags: 0 }, d = draft.digest;
|
|
356
|
+
// Context is optional in Digest v2: an over-long gist loses its context rather than the draft.
|
|
357
|
+
if (d.gist.context !== undefined && issues.some((i) => i.path === 'gist' || i.path === 'gist.context'))
|
|
358
|
+
delete d.gist.context;
|
|
359
|
+
const bad = new Set(issues
|
|
336
360
|
.filter((i) => /^ideas\.\d+\.quotes\.\d+(?:\.attribution)?$/.test(i.path) && pruneable.has(i.code))
|
|
337
361
|
.map((i) => i.path.replace(/\.attribution$/, '')));
|
|
338
362
|
d.ideas.forEach((idea, i) => {
|
|
@@ -12,8 +12,12 @@ export type DigestAction = 'new' | 'unchanged' | 'not-convertible' | 'not-digest
|
|
|
12
12
|
export type VisibilityAction = 'set-public' | 'set-private' | 'restore-default' | 'unchanged' | 'kept-user-edit' | 'guarded' | 'default-policy';
|
|
13
13
|
/** Local captures only (no web URI of their own): `uri` records the note's `source_url`. Later notes on one capture leave it to the first (`other-note`). */
|
|
14
14
|
export type UriAction = 'new' | 'unchanged' | 'web-capture' | 'other-note' | 'no-source-url' | 'capability';
|
|
15
|
-
/**
|
|
16
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Local captures only, like `uri`: the first note's title becomes the default title (`noteTitle`) of a capture whose own
|
|
17
|
+
* title is a file name; `kept-user-edit` records it while the user's override stays shown. A web capture keeps its
|
|
18
|
+
* captured title (`web-capture`).
|
|
19
|
+
*/
|
|
20
|
+
export type TitleAction = 'set' | 'unchanged' | 'web-capture' | 'other-note' | 'kept-user-edit';
|
|
17
21
|
/** `firstNote` is false for a later note on a capture an earlier note in the same run already used; `firstNoteUri` is the URI that note records (a dry run has not written it). */
|
|
18
22
|
export interface JungleNoteInput {
|
|
19
23
|
itemId: string;
|
|
@@ -45,9 +45,11 @@ export function uriAction(local, firstNote, sourceUrl, recorded) {
|
|
|
45
45
|
const { uri, refused } = noteUri(sourceUrl);
|
|
46
46
|
return uri ? { action: uri === recorded ? 'unchanged' : 'new', uri } : { action: refused };
|
|
47
47
|
}
|
|
48
|
-
function titleAction(db, input) {
|
|
48
|
+
function titleAction(db, input, local) {
|
|
49
49
|
if (input.firstNote === false)
|
|
50
50
|
return 'other-note';
|
|
51
|
+
if (!local)
|
|
52
|
+
return 'web-capture';
|
|
51
53
|
const title = input.editorial.title.trim(), item = db.prepare('SELECT title_note,title_override FROM items WHERE id=?').get(input.itemId);
|
|
52
54
|
if (item?.title_override !== null && item?.title_override !== undefined)
|
|
53
55
|
return 'kept-user-edit';
|
|
@@ -138,7 +140,7 @@ export function planJungleNote(db, input) {
|
|
|
138
140
|
...(digestId ? { digestId } : {}),
|
|
139
141
|
digest: action,
|
|
140
142
|
uri: uri.action,
|
|
141
|
-
title: titleAction(db, input),
|
|
143
|
+
title: titleAction(db, input, local),
|
|
142
144
|
visibility,
|
|
143
145
|
};
|
|
144
146
|
}
|
|
@@ -162,9 +164,9 @@ export function applyJungleNote(db, input) {
|
|
|
162
164
|
// A local capture takes the note's web address and title before visibility is decided, so the published state applies to it.
|
|
163
165
|
if (plan.uri === 'new')
|
|
164
166
|
setMetadataUri(db, input.itemId, uriAction(true, true, input.editorial.sourceUrl).uri);
|
|
165
|
-
//
|
|
167
|
+
// A local capture's default title is the note's; a refresh changes only the captured title, and the user's override wins over both.
|
|
166
168
|
const title = input.editorial.title.trim();
|
|
167
|
-
if (
|
|
169
|
+
if ((plan.title === 'set' || plan.title === 'kept-user-edit') &&
|
|
168
170
|
title &&
|
|
169
171
|
Buffer.byteLength(title) <= 4096 &&
|
|
170
172
|
title !== db.prepare('SELECT title_note FROM items WHERE id=?').get(input.itemId)?.title_note)
|
|
@@ -6,8 +6,8 @@ import type { BundleVerifier } from './inspect.js';
|
|
|
6
6
|
* `sponge import jungle DIR`: every Jungle Reading note (`kb/notes/reading/<slug>.md`) onto the item holding its capture.
|
|
7
7
|
* The capture goes through the verified bundle import (always as a private association); the note becomes an accepted
|
|
8
8
|
* editorial derivation, plus an accepted Digest v2 when it converts without altering owner text (non-verbatim, short and
|
|
9
|
-
* overflowing quotes are dropped and reported);
|
|
10
|
-
*
|
|
9
|
+
* overflowing quotes are dropped and reported); a local capture (no web URI of its own) takes the note's `source_url` and its
|
|
10
|
+
* title as the default title (a user override still wins), while a web capture keeps its captured title;
|
|
11
11
|
* `reading.status` becomes the item's visibility. Jungle is only read. A dry run reads the notes, captures and library and writes nothing.
|
|
12
12
|
*/
|
|
13
13
|
export interface JungleImportOptions {
|
|
@@ -139,7 +139,7 @@ export async function importJungle(store, jungle, options = {}) {
|
|
|
139
139
|
throw entry;
|
|
140
140
|
}
|
|
141
141
|
// Two notes on one capture share its item, in a dry run too.
|
|
142
|
-
// The first note on a capture decides
|
|
142
|
+
// The first note on a capture decides a local capture's URI and default title; later notes see the URI it records.
|
|
143
143
|
const firstNote = !seen.has(link), firstNoteUri = seen.get(link);
|
|
144
144
|
entry.item = prior || !firstNote ? 'existing' : 'new';
|
|
145
145
|
if (firstNote)
|
|
@@ -174,7 +174,13 @@ export async function importJungle(store, jungle, options = {}) {
|
|
|
174
174
|
...(issues.length ? { digestIssues: issues } : {}),
|
|
175
175
|
...(quotes ? { quotes } : {}),
|
|
176
176
|
uri: uri.action,
|
|
177
|
-
title: !firstNote
|
|
177
|
+
title: !firstNote
|
|
178
|
+
? 'other-note'
|
|
179
|
+
: !local
|
|
180
|
+
? 'web-capture'
|
|
181
|
+
: noteTitle && Buffer.byteLength(noteTitle) <= 4096
|
|
182
|
+
? 'set'
|
|
183
|
+
: 'unchanged',
|
|
178
184
|
visibility: visibilityAction({ access, default: defaultVisibility(access, 'private'), override: null }, editorial.status, false).action,
|
|
179
185
|
});
|
|
180
186
|
}
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
29
|
"build": {
|
|
30
|
-
"asrSourceSha256": "
|
|
30
|
+
"asrSourceSha256": "94b9303afcf9304e695c37411cef065e9cc45a31e0e51cd171819cbfc9ee3955",
|
|
31
31
|
"clangVersion": "Apple clang version 21.0.0 (clang-2100.1.1.101)",
|
|
32
|
-
"embeddingSourceSha256": "
|
|
32
|
+
"embeddingSourceSha256": "eed7e110bc888af99e0d6e614275cea7a7dbaa56f88d3de9bdd04cc9ff4cfa45",
|
|
33
33
|
"swiftVersion": "Apple Swift version 6.3.3 (swiftlang-6.3.3.1.3 clang-2100.1.1.101)"
|
|
34
34
|
},
|
|
35
35
|
"minimumMacOS": "26.0",
|
|
36
|
-
"packageVersion": "0.3.
|
|
36
|
+
"packageVersion": "0.3.1",
|
|
37
37
|
"platform": "darwin",
|
|
38
38
|
"schemaVersion": 1,
|
|
39
|
-
"sourceCommit": "
|
|
39
|
+
"sourceCommit": "b8975aff1dc06e398c3f78811e76f4ff875729a1"
|
|
40
40
|
}
|