@mjasnikovs/pi-task 0.40.2 → 0.40.4
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.
|
@@ -37,16 +37,25 @@ export const README_SPLIT_RE = /^#{1,2} /m;
|
|
|
37
37
|
export function splitAtMatches(text, re) {
|
|
38
38
|
const parts = [];
|
|
39
39
|
let lastIndex = 0;
|
|
40
|
+
let acceptedEnd = 0;
|
|
40
41
|
let m;
|
|
41
42
|
while ((m = re.exec(text))) {
|
|
43
|
+
// Scan resumes ONE character in, not past the match: the regex's trailing
|
|
44
|
+
// `\s+` can span a newline, so a genuinely separate line-anchored
|
|
45
|
+
// declaration may start inside what this match consumed.
|
|
46
|
+
re.lastIndex = m.index + 1;
|
|
47
|
+
// But a match that starts inside the last ACCEPTED one is that match's own
|
|
48
|
+
// tail, not a new declaration, and cutting there severs a declaration from
|
|
49
|
+
// the modifiers and attributes that open it. `export\nfunction a(){}` is
|
|
50
|
+
// one declaration; so is a cargo `#[cfg(…)]` above its `impl`, which
|
|
51
|
+
// CARGO_DECL_SPLIT_RE absorbs on purpose and this used to hand back as a
|
|
52
|
+
// chunk holding nothing but the attribute.
|
|
53
|
+
if (m.index < acceptedEnd)
|
|
54
|
+
continue;
|
|
42
55
|
if (m.index > lastIndex)
|
|
43
56
|
parts.push(text.slice(lastIndex, m.index));
|
|
44
57
|
lastIndex = m.index;
|
|
45
|
-
|
|
46
|
-
// span a newline, so the next line-anchored declaration may start INSIDE
|
|
47
|
-
// what this match consumed: `export\nfunction a(){}` is two chunks here
|
|
48
|
-
// and one if the scan resumes past the match.
|
|
49
|
-
re.lastIndex = m.index + 1;
|
|
58
|
+
acceptedEnd = m.index + m[0].length;
|
|
50
59
|
}
|
|
51
60
|
if (lastIndex < text.length)
|
|
52
61
|
parts.push(text.slice(lastIndex));
|
|
@@ -7,4 +7,32 @@ export interface IndexResult {
|
|
|
7
7
|
chunksWritten: number;
|
|
8
8
|
contentHash: string;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The gate that decides whether a package needs re-indexing.
|
|
12
|
+
*
|
|
13
|
+
* The entry file goes in SURFACED, not raw. What is cached is the extractor's
|
|
14
|
+
* OUTPUT, so a build whose extractor changed has stale chunks even though every
|
|
15
|
+
* byte on disk is identical — a crate indexed before the braced-`use` fix keeps
|
|
16
|
+
* `pub use crate::runtime::;` forever, because name, version and file bytes all
|
|
17
|
+
* still match. Surfacing here costs one file and makes the hash answer the
|
|
18
|
+
* question actually being asked: would re-reading produce the same chunks?
|
|
19
|
+
*
|
|
20
|
+
* The CHUNKER counts too, for the same reason: the rows are chunks, not surface,
|
|
21
|
+
* so a fix to where a declaration is cut leaves stale rows behind on its own. So
|
|
22
|
+
* does WHICH FILES are read: dropping a package's duplicate `.d.cts` twins
|
|
23
|
+
* changes the rows without changing a byte on disk.
|
|
24
|
+
*
|
|
25
|
+
* It is not total. An extractor change that alters only files BELOW the entry
|
|
26
|
+
* goes unnoticed; deleting the cache is still the escape hatch for that.
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* The chunker's own source, so a cut-point fix invalidates every cached package.
|
|
30
|
+
*
|
|
31
|
+
* `declSplitRe.source` alone does not do it: the attribute-orphaning bug was in
|
|
32
|
+
* `splitAtMatches`, not in any profile's regex, so the fingerprint sat still while
|
|
33
|
+
* the rows it describes changed. A package indexed before that fix keeps its
|
|
34
|
+
* dangling `#[cfg(...)]` chunks forever, and the hash is the only thing that would
|
|
35
|
+
* have said so.
|
|
36
|
+
*/
|
|
37
|
+
export declare function chunkerFingerprint(): string;
|
|
10
38
|
export declare function ensureIndexed(cache: CacheHandle, pkg: ResolvedPackage, profile?: EcosystemProfile): IndexResult;
|
|
@@ -2,7 +2,7 @@ import { createHash } from 'node:crypto';
|
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import {} from './docs-resolve.js';
|
|
5
|
-
import { chunkDeclarations, chunkReadme } from './docs-chunk.js';
|
|
5
|
+
import { chunkDeclarations, chunkReadme, splitAtMatches } from './docs-chunk.js';
|
|
6
6
|
import { ECOSYSTEMS } from './docs-ecosystems.js';
|
|
7
7
|
const ZERO_SEP = Buffer.from([0]);
|
|
8
8
|
/**
|
|
@@ -23,12 +23,26 @@ const ZERO_SEP = Buffer.from([0]);
|
|
|
23
23
|
* It is not total. An extractor change that alters only files BELOW the entry
|
|
24
24
|
* goes unnoticed; deleting the cache is still the escape hatch for that.
|
|
25
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* The chunker's own source, so a cut-point fix invalidates every cached package.
|
|
28
|
+
*
|
|
29
|
+
* `declSplitRe.source` alone does not do it: the attribute-orphaning bug was in
|
|
30
|
+
* `splitAtMatches`, not in any profile's regex, so the fingerprint sat still while
|
|
31
|
+
* the rows it describes changed. A package indexed before that fix keeps its
|
|
32
|
+
* dangling `#[cfg(...)]` chunks forever, and the hash is the only thing that would
|
|
33
|
+
* have said so.
|
|
34
|
+
*/
|
|
35
|
+
export function chunkerFingerprint() {
|
|
36
|
+
return `${String(splitAtMatches)}\u0000${String(chunkDeclarations)}\u0000${String(chunkReadme)}`;
|
|
37
|
+
}
|
|
26
38
|
function computeContentHash(pkg, profile) {
|
|
27
39
|
const hash = createHash('sha256');
|
|
28
40
|
hash.update(Buffer.from(`${pkg.name}@${pkg.version}`, 'utf8'));
|
|
29
41
|
hash.update(ZERO_SEP);
|
|
30
42
|
hash.update(Buffer.from(`${profile.declSplitRe.source}\u0000${profile.commentPrefix}`, 'utf8'));
|
|
31
43
|
hash.update(ZERO_SEP);
|
|
44
|
+
hash.update(Buffer.from(chunkerFingerprint(), 'utf8'));
|
|
45
|
+
hash.update(ZERO_SEP);
|
|
32
46
|
// Source text, the same trick as `declSplitRe.source`: the fingerprint moves
|
|
33
47
|
// whenever the selection rule does, with nothing to remember to bump.
|
|
34
48
|
hash.update(Buffer.from(`${String(profile.isSurfaceFile)}\u0000${String(dropParallelDeclarations)}`
|
|
@@ -194,7 +194,7 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
194
194
|
const r = await lookup(projectCorpus(projectName), chunks);
|
|
195
195
|
if (r.kind === 'failed')
|
|
196
196
|
return docsFailureResult(r.extraction, baseDetails, '');
|
|
197
|
-
const { extraction, excerptVerified: verified, body: text } = r;
|
|
197
|
+
const { extraction, excerptVerified: verified, body: text, content: retrieved } = r;
|
|
198
198
|
// SAME instrumentation channel as the package path below. Both branches
|
|
199
199
|
// record, or "the last docs answer before the worker stopped" is
|
|
200
200
|
// unanswerable whenever the last answer came from the branch that does
|
|
@@ -213,7 +213,8 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
213
213
|
reason: 'project-source lookup — the type-only detector is not applied here',
|
|
214
214
|
excerptVerified: verified,
|
|
215
215
|
excerptCheck: extraction.excerptCheck,
|
|
216
|
-
toolText: text
|
|
216
|
+
toolText: text,
|
|
217
|
+
retrievedText: retrieved
|
|
217
218
|
});
|
|
218
219
|
return workerAnswer(text, {
|
|
219
220
|
...baseDetails,
|
|
@@ -346,7 +347,8 @@ export function registerPiWorkerDocs(pi, internals = {}) {
|
|
|
346
347
|
reason: typeOnly.reason,
|
|
347
348
|
excerptVerified: verified,
|
|
348
349
|
excerptCheck: extraction.excerptCheck,
|
|
349
|
-
toolText: text
|
|
350
|
+
toolText: text,
|
|
351
|
+
retrievedText: concatenated
|
|
350
352
|
});
|
|
351
353
|
return workerAnswer(text, {
|
|
352
354
|
...baseDetails,
|
|
@@ -31,6 +31,19 @@ export interface TypeOnlyLogRecord {
|
|
|
31
31
|
* Optional — older records still parse.
|
|
32
32
|
*/
|
|
33
33
|
excerptCheck?: ExcerptVerification;
|
|
34
|
+
/**
|
|
35
|
+
* The retrieved chunk text handed to the extraction child, before it wrote a word.
|
|
36
|
+
*
|
|
37
|
+
* `toolText` cannot stand in for it. The tool return embeds the child's own prose,
|
|
38
|
+
* so scoring that prose against it asks whether the answer contains itself — two
|
|
39
|
+
* full runs read 13/13, 12/12, 10/10, 13/13, 4/4 and 2/2 clean, 54 answers and not
|
|
40
|
+
* one miss, while one of them shipped `decodeFile`, which aeson 2 does not have.
|
|
41
|
+
* Removing the prose leaves only the child's own CITED excerpt, a line or two, and
|
|
42
|
+
* flags `from_str` and `Context` as invented. Neither corpus is the one the question
|
|
43
|
+
* needs; this is. Optional — records written before it still parse, and a scorer
|
|
44
|
+
* must say "not computable" rather than guess when it is absent.
|
|
45
|
+
*/
|
|
46
|
+
retrievedText?: string;
|
|
34
47
|
/**
|
|
35
48
|
* The tool's ENTIRE return text — version banner, npm header, the answer prose, the cited
|
|
36
49
|
* excerpt, and (when it fires) the type-only banner. Optional so logs written before this
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.40.
|
|
3
|
+
"version": "0.40.4",
|
|
4
4
|
"description": "Deterministic task planning and spec-orchestration for local models — crash-safe /task pipelines with verify/enforce gates, a real-time remote web view, and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|