@mjasnikovs/pi-task 0.40.3 → 0.40.5

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.
@@ -22,10 +22,28 @@ const MIN_TOKEN_LEN = 2;
22
22
  * aliased members spend the whole budget on hops.
23
23
  */
24
24
  const MAX_ALIAS_HOPS = 3;
25
+ /**
26
+ * How many smallest-first candidates the value hop reads before giving up.
27
+ *
28
+ * A whole-word check cannot be pushed into SQL, so it runs over the shortest few.
29
+ * Only a name whose every shorter occurrence is a substring of a longer identifier
30
+ * needs more than a handful, and that name is not the one the query asked about.
31
+ */
32
+ const VALUE_CHUNK_CANDIDATES = 8;
25
33
  /** Backstop for a caller that names no ecosystem; every real one passes its own. */
26
34
  const DEFAULT_TYPE_KEYWORDS = ['interface', 'type', 'class', 'enum'];
27
35
  /** A member declared as a bare capitalised type: `get: HandlerInterface<…>`. */
28
36
  const MEMBER_TYPE_RE = /^\s*(?:readonly\s+)?([A-Za-z_$][\w$]*)\??\s*:\s*([A-Z][A-Za-z0-9_]*)\s*[<;,)|&]/gm;
37
+ /**
38
+ * A token that is a symbol rather than English: capitalised, or carrying an
39
+ * underscore or an internal capital.
40
+ *
41
+ * `/^[A-Z]/` alone was the whole rule, and it reached none of the 17 declarations
42
+ * the 2026-09-06 run named and never retrieved — `safeParse`, `from_str`,
43
+ * `into_make_service`, `parseJSON`. Widening to every token instead would hop on
44
+ * `signature` and `return`, spending a slot on whichever prose chunk is shortest.
45
+ */
46
+ const IDENTIFIER_SHAPED = /^(?:[A-Z][A-Za-z0-9_]{2,}|[a-z][A-Za-z0-9]*(?:_[A-Za-z0-9_]+|[A-Z][A-Za-z0-9_]*)[A-Za-z0-9_]*)$/;
29
47
  const TYPE_DECL_RE = /\b(?:interface|type|class|data|newtype|struct|trait|enum)\s+([A-Z][A-Za-z0-9_]*)/g;
30
48
  /** The `<E extends Env, BasePath extends string>` a declaration introduces itself. */
31
49
  const TYPE_PARAMS_RE = /<([^<>]*)>/g;
@@ -128,17 +146,21 @@ function hopNames(text, tokens) {
128
146
  }
129
147
  const asked = new Set(tokens.map(t => t.toLowerCase()));
130
148
  const out = [];
131
- // A capitalised name the QUERY itself asks about. scotty's seven failures were
132
- // all of this shape: `type ActionM = ActionT IO` sits in one chunk of 312
133
- // while 67 chunks USE the name, and a chunk carrying BOTH query terms
149
+ // A name the QUERY itself asks about. scotty's seven failures were all of this
150
+ // shape: `type ActionM = ActionT IO` sits in one chunk of 312 while 67 chunks
151
+ // USE the name, and a chunk carrying BOTH query terms
134
152
  // (`get :: RoutePattern -> ActionM () -> ScottyM ()`) outranks the definition
135
153
  // every time. Reading the ranked output, all eight slots went to uses.
154
+ // Not capped. MAX_ALIAS_HOPS bounds hops DERIVED from a chunk, where one chunk
155
+ // full of aliased members could generate them without end; a query names the
156
+ // handful of symbols it names, and that is the bound. Capping these at 3 as well
157
+ // cost 4 of the 6 recoveries this hop exists for — measured on the 2026-09-06
158
+ // run's own 35 named declarations: 17 missed uncapped-baseline, 15 at a cap of
159
+ // 3, 11 at a cap of 8. The content budget is what stops it running long.
136
160
  for (const t of tokens) {
137
- if (!/^[A-Z][A-Za-z0-9_]{2,}$/.test(t) || declared.has(t) || out.includes(t))
161
+ if (!IDENTIFIER_SHAPED.test(t) || declared.has(t) || out.includes(t))
138
162
  continue;
139
163
  out.push(t);
140
- if (out.length >= MAX_ALIAS_HOPS)
141
- return out;
142
164
  }
143
165
  for (const m of text.matchAll(MEMBER_TYPE_RE)) {
144
166
  const [, member, typeName] = m;
@@ -166,10 +188,45 @@ function definitionChunk(cache, opts, name) {
166
188
  AND (${where})
167
189
  ORDER BY length(content) LIMIT 1`)
168
190
  .get(opts.ecosystem, opts.name, opts.version, ...keywords.map(k => `*${k} ${name}[ <={(=]*`));
191
+ if (row) {
192
+ return {
193
+ filePath: row.file_path,
194
+ kind: row.kind,
195
+ content: row.content,
196
+ rank: row.rank
197
+ };
198
+ }
199
+ return valueChunk(cache, opts, name);
200
+ }
201
+ /**
202
+ * The smallest chunk declaring `name` where `name` is a VALUE, not a type.
203
+ *
204
+ * The keyword GLOB above finds `type ActionM` and `struct Config`; nothing it can
205
+ * spell finds `pub fn from_str<'a, T>` or `decodeValue :: String -> …`, and those
206
+ * were 17 of the 35 declarations the 2026-09-06 run named and never retrieved.
207
+ *
208
+ * Smallest-first is the same reasoning as the type path, and it is what makes this
209
+ * safe without a per-language declaration grammar: the surface extractor emits one
210
+ * declaration per chunk, so the short chunk carrying the name IS its declaration and
211
+ * the long ones are the prose that merely mentions it.
212
+ */
213
+ function valueChunk(cache, opts, name) {
214
+ const rows = cache.db
215
+ .prepare(`SELECT file_path, kind, content, 0 AS rank FROM chunks
216
+ WHERE ecosystem = ?1 AND name = ?2 AND version = ?3 AND content LIKE ?4
217
+ ORDER BY length(content) LIMIT ?5`)
218
+ .all(opts.ecosystem, opts.name, opts.version, `%${name}%`, VALUE_CHUNK_CANDIDATES);
219
+ // LIKE has no word boundary, so `decodeFile` matches `decodeFileStrict` — the
220
+ // wrong declaration, and the exact confusion these runs keep producing.
221
+ const whole = new RegExp(`(?<![A-Za-z0-9_])${escapeRe(name)}(?![A-Za-z0-9_])`);
222
+ const row = rows.find(r => whole.test(r.content));
169
223
  if (!row)
170
224
  return null;
171
225
  return { filePath: row.file_path, kind: row.kind, content: row.content, rank: row.rank };
172
226
  }
227
+ function escapeRe(s) {
228
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
229
+ }
173
230
  export function retrieveChunks(cache, opts) {
174
231
  const limit = opts.limit ?? DEFAULT_LIMIT;
175
232
  const budget = opts.contentBudget ?? DEFAULT_BUDGET;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.40.3",
3
+ "version": "0.40.5",
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",