@hraness/sponge 0.3.1 → 0.3.3
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/annotations/quote.d.ts +2 -0
- package/dist/src/annotations/quote.js +29 -1
- package/dist/src/cli/commands.js +1 -1
- package/dist/src/cli/main.js +2 -2
- package/dist/src/digest/pipeline.js +8 -6
- package/dist/src/errors.d.ts +5 -0
- package/dist/src/errors.js +7 -0
- package/dist/src/portability/files.js +12 -2
- package/native/runtime-manifest.json +4 -4
- package/package.json +1 -1
|
@@ -7,6 +7,8 @@ export interface TextQuote {
|
|
|
7
7
|
start: number;
|
|
8
8
|
end: number;
|
|
9
9
|
}
|
|
10
|
+
/** Spoken text without transcript cues, whitespace collapsed: what a reader quotes from a transcript. */
|
|
11
|
+
export declare function stripCues(raw: string): string;
|
|
10
12
|
export declare function foldText(value: string): string;
|
|
11
13
|
/** Every occurrence of `quote` in `source`, compared after folding. */
|
|
12
14
|
export declare function locateAll(source: string, quote: string): {
|
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
const context = 32;
|
|
2
2
|
const graphemes = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
|
|
3
3
|
const escapable = /^[\\`*_{}[\]()#+\-.!|>~<"']$/;
|
|
4
|
+
/** A transcript cue at a line start: `- [00:02] ` (clipped captions) or `[00:00:02] ` (Sponge transcripts). */
|
|
5
|
+
const cue = /^[ \t]*(?:[-*+][ \t]+)?\[(?:\d{1,2}:)?\d{1,2}:\d{2}\][ \t]+/;
|
|
6
|
+
/** Spoken text without transcript cues, whitespace collapsed: what a reader quotes from a transcript. */
|
|
7
|
+
export function stripCues(raw) {
|
|
8
|
+
return raw
|
|
9
|
+
.split('\n')
|
|
10
|
+
.map((line) => line.replace(cue, ''))
|
|
11
|
+
.join('\n')
|
|
12
|
+
.replace(/\s+/g, ' ')
|
|
13
|
+
.trim();
|
|
14
|
+
}
|
|
4
15
|
/**
|
|
5
16
|
* Fold the differences a reader cannot see: Markdown escapes, curly quotes, dashes, runs of
|
|
6
|
-
* whitespace and Unicode normalization. Folding works on grapheme clusters, each NFC-normalized;
|
|
17
|
+
* whitespace, line-start transcript cues and Unicode normalization. Folding works on grapheme clusters, each NFC-normalized;
|
|
7
18
|
* every folded code unit of a cluster maps to the cluster's raw bounds, so `map[i]` and `ends[i]`
|
|
8
19
|
* bound the raw text of folded unit i and matches map back exactly.
|
|
9
20
|
*/
|
|
@@ -19,6 +30,17 @@ function fold(raw) {
|
|
|
19
30
|
};
|
|
20
31
|
for (let c = 0; c < clusters.length; c++) {
|
|
21
32
|
const { segment, index } = clusters[c];
|
|
33
|
+
// A transcript cue reads as the space between caption lines, so a quote of the speech spans them.
|
|
34
|
+
const marker = (index === 0 || raw[index - 1] === '\n') && cue.exec(raw.slice(index, index + 32));
|
|
35
|
+
if (marker) {
|
|
36
|
+
const stop = index + marker[0].length;
|
|
37
|
+
if (!space && text.length)
|
|
38
|
+
emit(' ', index, stop);
|
|
39
|
+
space = true;
|
|
40
|
+
while (c + 1 < clusters.length && clusters[c + 1].index < stop)
|
|
41
|
+
c++;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
22
44
|
// An escaped character maps to its backslash, so a match starting there keeps the escape.
|
|
23
45
|
let ch = segment, end = index + segment.length;
|
|
24
46
|
if (segment === '\\' && c + 1 < clusters.length && escapable.test(clusters[c + 1].segment)) {
|
|
@@ -98,6 +120,12 @@ function rendered(raw) {
|
|
|
98
120
|
for (let i = 0; i < raw.length; i++) {
|
|
99
121
|
let ch = raw[i], at = i;
|
|
100
122
|
if (lineStart) {
|
|
123
|
+
const c = cue.exec(raw.slice(i, i + 32));
|
|
124
|
+
if (c) {
|
|
125
|
+
i += c[0].length - 1;
|
|
126
|
+
lineStart = false;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
101
129
|
const m = /^[ \t]*(?:>[ \t]?|#{1,6}[ \t]+|[-*+][ \t]+|\d{1,3}[.)][ \t]+)*/.exec(raw.slice(i, i + 64));
|
|
102
130
|
if (m[0].length) {
|
|
103
131
|
i += m[0].length - 1;
|
package/dist/src/cli/commands.js
CHANGED
|
@@ -791,5 +791,5 @@ export const usageOf = (name) => command(name)
|
|
|
791
791
|
.join('\n');
|
|
792
792
|
export function commandHelp(name) {
|
|
793
793
|
const c = command(name), flags = Object.keys(c.flags);
|
|
794
|
-
return `sponge ${c.name} — ${c.summary}\n\nUsage:\n${c.usage.map(row).join('')
|
|
794
|
+
return `sponge ${c.name} — ${c.summary}\n\nUsage:\n${c.usage.map(([synopsis, description]) => row([`sponge ${synopsis}`, description])).join('')}${flags.length ? `\nFlags: ${flags.map((f) => '--' + f).join(' ')}\n` : ''}\nGlobal flags: --library /absolute/path --json --debug\n`;
|
|
795
795
|
}
|
package/dist/src/cli/main.js
CHANGED
|
@@ -8,7 +8,7 @@ import { command, usageOf } from './commands.js';
|
|
|
8
8
|
import { openLibrary } from '../store/index.js';
|
|
9
9
|
import { defaultCapturePins } from '../acquisition/index.js';
|
|
10
10
|
import { failure } from '../acquisition/errors.js';
|
|
11
|
-
import { SpongeError, codeOf, publicCode, exitFor, hints, debugChain } from '../errors.js';
|
|
11
|
+
import { SpongeError, codeOf, publicCode, exitFor, hints, detailHints, debugChain, } from '../errors.js';
|
|
12
12
|
/** Failures print one categorical JSON line: the public code, its specific `reason` when narrower, and a fixed hint.
|
|
13
13
|
* Causes, details, paths and upstream messages appear only with --debug or SPONGE_DEBUG=1. */
|
|
14
14
|
function report(error, requestId, debug) {
|
|
@@ -17,7 +17,7 @@ function report(error, requestId, debug) {
|
|
|
17
17
|
process.stderr.write(JSON.stringify({ issues }) + '\n');
|
|
18
18
|
const hint = code === 'invalid-input' && typeof usage === 'string' && command(usage)
|
|
19
19
|
? `Usage: ${usageOf(usage).replace(/\n/g, ' | ')}`
|
|
20
|
-
: hints[code];
|
|
20
|
+
: (detailHints[reason] ?? hints[code]);
|
|
21
21
|
process.stderr.write(JSON.stringify({ ...failure(requestId, code), ...(reason !== code ? { reason } : {}), hint }) + '\n');
|
|
22
22
|
if (debug)
|
|
23
23
|
process.stderr.write(debugChain(error));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { foldText, locateAll, locateLoose, words } from '../annotations/quote.js';
|
|
1
|
+
import { foldText, locateAll, locateLoose, stripCues, words } from '../annotations/quote.js';
|
|
2
2
|
import { checkDigest, digestLimits as L, forms, parseDigest, } from './schema.js';
|
|
3
3
|
import { SpongeError } from '../errors.js';
|
|
4
4
|
import { sha256 } from '../util/hash.js';
|
|
@@ -97,7 +97,9 @@ function lastSentenceEnd(window) {
|
|
|
97
97
|
best = m.index + m[0].length;
|
|
98
98
|
return best;
|
|
99
99
|
}
|
|
100
|
-
const display = (p) => `[${p.id}] ${
|
|
100
|
+
const display = (p) => `[${p.id}] ${stripCues(p.text)}`;
|
|
101
|
+
/** A raw source span as a quote: transcript cues inside it are dropped, so the quote reads as the speech. */
|
|
102
|
+
const spoken = (raw) => (raw.includes('\n') && stripCues(raw) !== collapse(raw) ? stripCues(raw) : raw);
|
|
101
103
|
/** Pack passages into sections of at most `budget` display characters, preferring heading boundaries past half a budget. */
|
|
102
104
|
export function sectionsOf(list, budget) {
|
|
103
105
|
const out = [];
|
|
@@ -295,7 +297,7 @@ export function snapQuote(source, text, within) {
|
|
|
295
297
|
const folded = locateAll(source, text);
|
|
296
298
|
if (folded.length) {
|
|
297
299
|
const m = pick(folded);
|
|
298
|
-
return { text: m ? source.slice(m.start, m.end) : text, match: 'folded' };
|
|
300
|
+
return { text: m ? spoken(source.slice(m.start, m.end)) : text, match: 'folded' };
|
|
299
301
|
}
|
|
300
302
|
const loose = (within && pick(locateLoose(source, text, within))) || pick(locateLoose(source, text));
|
|
301
303
|
if (!loose)
|
|
@@ -306,7 +308,7 @@ export function snapQuote(source, text, within) {
|
|
|
306
308
|
start--;
|
|
307
309
|
while (end < source.length && /[*_`]/.test(source[end]))
|
|
308
310
|
end++;
|
|
309
|
-
return { text: source.slice(start, end), match: 'snapped' };
|
|
311
|
+
return { text: spoken(source.slice(start, end)), match: 'snapped' };
|
|
310
312
|
}
|
|
311
313
|
/**
|
|
312
314
|
* A located quote over the word limit keeps its leading words: a prefix of a source span is still verbatim. Trailing
|
|
@@ -608,7 +610,7 @@ export async function draftDigest(input) {
|
|
|
608
610
|
result = await attempt(await call([...messages, { role: 'assistant', content: first }, { role: 'user', content: repairPrompt(result.issues) }], schema));
|
|
609
611
|
}
|
|
610
612
|
if (!result.draft)
|
|
611
|
-
throw new SpongeError('
|
|
613
|
+
throw new SpongeError('draft-rejected', { issues: result.issues });
|
|
612
614
|
let issues = result.issues;
|
|
613
615
|
if (issues.length) {
|
|
614
616
|
trace.dropped = prune(result.draft, issues, source, options);
|
|
@@ -616,7 +618,7 @@ export async function draftDigest(input) {
|
|
|
616
618
|
if (result.draft.digest.ideas.length < L.minIdeas)
|
|
617
619
|
issues = [...issues.filter((i) => !(i.path === 'ideas' && i.code === 'count')), { path: 'ideas', code: 'count' }];
|
|
618
620
|
if (issues.length)
|
|
619
|
-
throw new SpongeError('
|
|
621
|
+
throw new SpongeError('draft-rejected', { issues });
|
|
620
622
|
}
|
|
621
623
|
const producer = {
|
|
622
624
|
pipeline: 'sponge-digest',
|
package/dist/src/errors.d.ts
CHANGED
|
@@ -16,6 +16,9 @@ declare const parents: {
|
|
|
16
16
|
readonly 'not-digest': "unsupported";
|
|
17
17
|
readonly malformed: "invalid-input";
|
|
18
18
|
readonly 'no-capture': "invalid-input";
|
|
19
|
+
readonly 'draft-rejected': "unavailable";
|
|
20
|
+
readonly 'parent-missing': "invalid-input";
|
|
21
|
+
readonly 'target-exists': "invalid-input";
|
|
19
22
|
};
|
|
20
23
|
export type DetailCode = keyof typeof parents;
|
|
21
24
|
export type SpongeCode = ErrorCode | DetailCode;
|
|
@@ -25,6 +28,8 @@ export declare const isSpongeCode: (value: unknown) => value is SpongeCode;
|
|
|
25
28
|
export declare const exitStatus: Readonly<Record<ErrorCode, number>>;
|
|
26
29
|
/** The one code→user-facing hint table. Fixed text only: never paths, sources, credentials or upstream prose. */
|
|
27
30
|
export declare const hints: Readonly<Record<ErrorCode, string>>;
|
|
31
|
+
/** Hints for detail codes whose public code's hint would mislead. Fixed text only, like `hints`. */
|
|
32
|
+
export declare const detailHints: Readonly<Partial<Record<DetailCode, string>>>;
|
|
28
33
|
export interface SpongeErrorOptions {
|
|
29
34
|
detail?: Record<string, unknown>;
|
|
30
35
|
cause?: unknown;
|
package/dist/src/errors.js
CHANGED
|
@@ -30,6 +30,9 @@ const parents = {
|
|
|
30
30
|
'not-digest': 'unsupported',
|
|
31
31
|
malformed: 'invalid-input',
|
|
32
32
|
'no-capture': 'invalid-input',
|
|
33
|
+
'draft-rejected': 'unavailable',
|
|
34
|
+
'parent-missing': 'invalid-input',
|
|
35
|
+
'target-exists': 'invalid-input',
|
|
33
36
|
};
|
|
34
37
|
export const indexCodes = [
|
|
35
38
|
'invalid-index',
|
|
@@ -75,6 +78,10 @@ export const hints = {
|
|
|
75
78
|
'schema-newer': 'This library was written by a newer Sponge. Upgrade Sponge.',
|
|
76
79
|
'owner-busy': 'Another Sponge process owns the library. Wait for it, or run `sponge queue recover` after it stops.',
|
|
77
80
|
};
|
|
81
|
+
/** Hints for detail codes whose public code's hint would mislead. Fixed text only, like `hints`. */
|
|
82
|
+
export const detailHints = {
|
|
83
|
+
'draft-rejected': 'The model draft failed the digest checks listed above, even after one repair. Run it again; drafts vary.',
|
|
84
|
+
};
|
|
78
85
|
/** The one error type. `message` is the code, so bare `String(error)` never carries detail, paths or causes. */
|
|
79
86
|
export class SpongeError extends Error {
|
|
80
87
|
code;
|
|
@@ -112,8 +112,18 @@ export function copyVerified(source, target, record) {
|
|
|
112
112
|
export function newDirectory(path) {
|
|
113
113
|
if (resolve(path) !== path)
|
|
114
114
|
throw new SpongeError('invalid-input');
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
try {
|
|
116
|
+
safeDirectory(dirname(path));
|
|
117
|
+
mkdirSync(path, { mode: 0o700 });
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const code = error.code;
|
|
121
|
+
if (code === 'ENOENT')
|
|
122
|
+
throw new SpongeError('parent-missing', { cause: error });
|
|
123
|
+
if (code === 'EEXIST')
|
|
124
|
+
throw new SpongeError('target-exists', { cause: error });
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
117
127
|
const stat = lstatSync(path);
|
|
118
128
|
return { root: path, device: stat.dev, inode: stat.ino };
|
|
119
129
|
}
|
|
@@ -27,14 +27,14 @@
|
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
29
|
"build": {
|
|
30
|
-
"asrSourceSha256": "
|
|
30
|
+
"asrSourceSha256": "e2e38c4beb276bf8e625e89f4694dcf6a89526621b0a056b590325bc7fb92314",
|
|
31
31
|
"clangVersion": "Apple clang version 21.0.0 (clang-2100.1.1.101)",
|
|
32
|
-
"embeddingSourceSha256": "
|
|
32
|
+
"embeddingSourceSha256": "38ba64989463d38bf6100636a2b1569e43cc32fbe640da6cc4bf0d8b24409c8a",
|
|
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.3",
|
|
37
37
|
"platform": "darwin",
|
|
38
38
|
"schemaVersion": 1,
|
|
39
|
-
"sourceCommit": "
|
|
39
|
+
"sourceCommit": "b1de42fa724be73e352c34357d42a1ca3946d5b5"
|
|
40
40
|
}
|