@mjasnikovs/pi-task 0.40.28 → 0.40.30

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.
@@ -0,0 +1,571 @@
1
+ /**
2
+ * go-surface — reducing Go source to the API a caller outside the package can
3
+ * reach.
4
+ *
5
+ * Go ships no declarations file, so this is the `.d.ts` a Go package does not
6
+ * have. It is the same job `rustSurface` does in eco-cargo.ts, and it needs the
7
+ * same care, but three things about Go make a straight port wrong.
8
+ *
9
+ * There are no semicolons. A declaration ends where Go's lexer would insert one,
10
+ * which is a property of the last token on the line, so the scanner has to track
11
+ * that token rather than look for a terminator.
12
+ *
13
+ * A signature can hold a balanced brace pair before its body:
14
+ * `func Bind(v interface{}) HandlerFunc {`. Splitting the head at the first `{`,
15
+ * the way the Rust version does, cuts that one at `interface`. The body is the
16
+ * group whose closer ENDS the declaration, not the first group that opens.
17
+ *
18
+ * And a doc comment has no marker at all. Adjacency is the marker: the comment
19
+ * paragraph on the lines directly above, with no blank line between. Every Go
20
+ * file also opens with a licence header, so "keep what came before" reprints the
21
+ * licence into every chunk of every file.
22
+ */
23
+ /** A Go identifier is Unicode, not `\w` — `Ünique` is a legal exported name. */
24
+ const ID = String.raw `[\p{L}_][\p{L}\p{Nd}_]*`;
25
+ const GO_ITEM_HEAD_RE = /^(package|import|const|var|type|func)\b/;
26
+ /** A func's name, and the base type of its receiver when it has one. */
27
+ const GO_FUNC_RE = new RegExp(String.raw `^func\s*(?:\(\s*(?:${ID}\s+)?\*?(${ID})(?:\[[^\]]*\])?\s*\)\s*)?(${ID})`, 'u');
28
+ const GO_DECL_NAME_RE = new RegExp(String.raw `^(?:type|const|var)\s+(${ID})`, 'u');
29
+ /** The names a member line declares, before its type or `=`. */
30
+ const GO_MEMBER_NAME_RE = new RegExp(String.raw `^(${ID}(?:\s*,\s*${ID})*)\s*(?:[^\s,=]|=|$)`, 'u');
31
+ /** A qualified name alone on a line: an embedded field or interface. */
32
+ const GO_EMBEDDED_RE = new RegExp(String.raw `^\*?(?:${ID}\.)?(${ID})\s*(?:\x60[^\x60]*\x60)?$`, 'u');
33
+ /**
34
+ * A named `struct`/`interface` head, for telling a type's BODY from a type whose
35
+ * definition merely ends in a brace: `type Set[T comparable] map[T]struct{}`.
36
+ */
37
+ const GO_TYPE_BODY_RE = new RegExp(String.raw `^type\s+${ID}(?:\[[\s\S]*\])?\s+(?:struct|interface)\s*$`, 'u');
38
+ /**
39
+ * Where a declaration begins. Column 0 only: `goSurface` indents struct fields
40
+ * and interface methods, and an `^\s*` anchor would cut every field into its own
41
+ * chunk. eco-cargo.ts records the same trap.
42
+ */
43
+ export const GO_DECL_SPLIT_RE = /^(?:package|import|const|var|type|func)\b/m;
44
+ /**
45
+ * The same heads indented, plus the keyword-less members only Go has: a struct
46
+ * field is `Name Type`, an interface method is `Name(args) ret`, a grouped const
47
+ * is `Name Type = value`. Reached only when one declaration does not fit a
48
+ * chunk, which `Context` and `IRoutes` in gin both fail to.
49
+ */
50
+ export const GO_MEMBER_SPLIT_RE = new RegExp(String.raw `^[ \t]+(?:(?:const|var|type|func)\b|${ID}(?:\s*,\s*${ID})*\s*(?:\(|=|[*\[]|${ID}))`, 'mu');
51
+ /** Go inserts a semicolon after a token of one of these shapes. */
52
+ const STMT_END_CHAR_RE = /[\p{L}\p{Nd}_"'\x60)\]}]/u;
53
+ /**
54
+ * Keywords that end a line without ending a declaration. Go's own rule lists
55
+ * only `break`, `continue`, `fallthrough` and `return`, which cannot appear at
56
+ * the top level; these six can, and a wrapped `type\n\tX int` would otherwise
57
+ * split into two items.
58
+ */
59
+ const CONTINUING_KEYWORDS = new Set(['package', 'import', 'const', 'var', 'type', 'func']);
60
+ /** A value longer than this is elided; below it the value IS the documentation. */
61
+ const INITIALIZER_LIMIT = 120;
62
+ const LICENCE_RE = /copyright|licen[sc]e|all rights reserved|SPDX-/i;
63
+ /** `//go:` directives that describe the declaration rather than the build. */
64
+ const KEPT_DIRECTIVES = new Set([
65
+ 'noescape',
66
+ 'linkname',
67
+ 'embed',
68
+ 'nosplit',
69
+ 'noinline',
70
+ 'uintptrescapes',
71
+ 'wasmimport'
72
+ ]);
73
+ /** Advance past whitespace and comments, to the next code character. */
74
+ function skipToCode(src, from) {
75
+ let i = from;
76
+ while (i < src.length) {
77
+ const c = src[i];
78
+ if (c === ' ' || c === '\t' || c === '\r' || c === '\n') {
79
+ i++;
80
+ }
81
+ else if (c === '/' && src[i + 1] === '/') {
82
+ const nl = src.indexOf('\n', i);
83
+ i = nl < 0 ? src.length : nl + 1;
84
+ }
85
+ else if (c === '/' && src[i + 1] === '*') {
86
+ const close = src.indexOf('*/', i + 2);
87
+ i = close < 0 ? src.length : close + 2;
88
+ }
89
+ else {
90
+ return i;
91
+ }
92
+ }
93
+ return src.length;
94
+ }
95
+ /** Past a string, rune or raw-string literal, given its opening quote. */
96
+ function skipLiteral(src, from) {
97
+ const quote = src[from];
98
+ let i = from + 1;
99
+ while (i < src.length) {
100
+ const c = src[i];
101
+ // A raw string has no escapes at all, so a lone backslash inside one is
102
+ // just a byte. `var x = `a\`` ends at the backtick, not after it.
103
+ if (c === '\\' && quote !== '`')
104
+ i += 2;
105
+ else if (c === quote)
106
+ return i + 1;
107
+ else
108
+ i++;
109
+ }
110
+ return src.length;
111
+ }
112
+ /**
113
+ * Where the declaration starting at `from` ends.
114
+ *
115
+ * Depth counts `{}`, `()` and `[]` together: a generic constraint list puts a
116
+ * brace group inside a bracket group, and a multi-line one must not terminate
117
+ * the declaration halfway through.
118
+ */
119
+ function scanItem(src, from) {
120
+ let depth = 0;
121
+ let word = '';
122
+ let lastChar = '';
123
+ let i = from;
124
+ while (i < src.length) {
125
+ const c = src[i];
126
+ if (c === '/' && src[i + 1] === '/') {
127
+ const nl = src.indexOf('\n', i);
128
+ i = nl < 0 ? src.length : nl;
129
+ continue;
130
+ }
131
+ if (c === '/' && src[i + 1] === '*') {
132
+ const close = src.indexOf('*/', i + 2);
133
+ i = close < 0 ? src.length : close + 2;
134
+ continue;
135
+ }
136
+ if (c === '"' || c === "'" || c === '`') {
137
+ i = skipLiteral(src, i);
138
+ lastChar = src[i - 1] ?? c;
139
+ word = '';
140
+ continue;
141
+ }
142
+ if (c === '\n') {
143
+ if (depth <= 0 && endsStatement(word, lastChar))
144
+ return i + 1;
145
+ i++;
146
+ continue;
147
+ }
148
+ if (c === '{' || c === '(' || c === '[')
149
+ depth++;
150
+ else if (c === '}' || c === ')' || c === ']')
151
+ depth--;
152
+ if (c !== ' ' && c !== '\t' && c !== '\r') {
153
+ lastChar = c;
154
+ word = /[\p{L}\p{Nd}_]/u.test(c) ? word + c : '';
155
+ }
156
+ i++;
157
+ }
158
+ return src.length;
159
+ }
160
+ function endsStatement(word, lastChar) {
161
+ if (word !== '')
162
+ return !CONTINUING_KEYWORDS.has(word);
163
+ return STMT_END_CHAR_RE.test(lastChar);
164
+ }
165
+ /**
166
+ * Split source into declarations. Works unchanged on a struct body or a const
167
+ * group, whose members obey the same semicolon rule with no keyword in front.
168
+ */
169
+ export function splitGoItems(src) {
170
+ const items = [];
171
+ let from = 0;
172
+ while (from < src.length) {
173
+ const start = skipToCode(src, from);
174
+ if (start >= src.length)
175
+ break;
176
+ const end = scanItem(src, start);
177
+ items.push({ pending: src.slice(from, start), text: src.slice(start, end).trimEnd() });
178
+ from = end;
179
+ }
180
+ return items;
181
+ }
182
+ /**
183
+ * The top-level group whose closer is the last character of `text` — the
184
+ * declaration's body, if it has one.
185
+ */
186
+ function trailingGroup(text) {
187
+ let depth = 0;
188
+ let open = -1;
189
+ let found = null;
190
+ let i = 0;
191
+ while (i < text.length) {
192
+ const c = text[i];
193
+ if (c === '/' && text[i + 1] === '/') {
194
+ const nl = text.indexOf('\n', i);
195
+ i = nl < 0 ? text.length : nl;
196
+ continue;
197
+ }
198
+ if (c === '/' && text[i + 1] === '*') {
199
+ const close = text.indexOf('*/', i + 2);
200
+ i = close < 0 ? text.length : close + 2;
201
+ continue;
202
+ }
203
+ if (c === '"' || c === "'" || c === '`') {
204
+ i = skipLiteral(text, i);
205
+ continue;
206
+ }
207
+ if (c === '{' || c === '(' || c === '[') {
208
+ if (depth === 0)
209
+ open = i;
210
+ depth++;
211
+ }
212
+ else if (c === '}' || c === ')' || c === ']') {
213
+ depth--;
214
+ if (depth === 0 && i === text.length - 1)
215
+ found = open;
216
+ }
217
+ i++;
218
+ }
219
+ return found;
220
+ }
221
+ /** True when the first rune of `name` is an uppercase letter. */
222
+ export function isExported(name) {
223
+ const first = name.codePointAt(0);
224
+ if (first === undefined)
225
+ return false;
226
+ const c = String.fromCodePoint(first);
227
+ return c !== c.toLowerCase() && c === c.toUpperCase();
228
+ }
229
+ /** The base type of a method's receiver, or null for a plain function. */
230
+ export function receiverType(text) {
231
+ return GO_FUNC_RE.exec(text)?.[1] ?? null;
232
+ }
233
+ const RULE_CHARS = '[*=#+\\-_~/]';
234
+ /**
235
+ * A decorative separator: a rule of punctuation, or a caption fenced by one on
236
+ * both sides. A line reading `***** CONTEXT CREATION ****` documents nothing.
237
+ */
238
+ function isBanner(line) {
239
+ const body = line
240
+ .trim()
241
+ .replace(/^\/\*|^\/\//, '')
242
+ .replace(/\*\/$/, '')
243
+ .trim();
244
+ if (body.length < 4)
245
+ return false;
246
+ return (new RegExp(`^${RULE_CHARS}+$`).test(body)
247
+ || new RegExp(`^${RULE_CHARS}{3,}.*${RULE_CHARS}{3,}$`).test(body));
248
+ }
249
+ /**
250
+ * The comment paragraph attached to this declaration, or nothing.
251
+ *
252
+ * Only the last paragraph, and only when it ends on the line directly above —
253
+ * the blank line before a licence header is what separates it from the code.
254
+ */
255
+ export function keptPreamble(pending) {
256
+ if (pending === '' || /\n[ \t]*\n[ \t]*$/.test(pending))
257
+ return '';
258
+ const text = pending.replace(/\s+$/, '');
259
+ if (text === '')
260
+ return '';
261
+ let raw;
262
+ if (text.endsWith('*/')) {
263
+ const open = text.lastIndexOf('/*');
264
+ if (open < 0)
265
+ return '';
266
+ raw = text.slice(open).split('\n');
267
+ }
268
+ else {
269
+ raw = [];
270
+ for (const line of text.split('\n').reverse()) {
271
+ if (!line.trim().startsWith('//'))
272
+ break;
273
+ raw.unshift(line.trim());
274
+ }
275
+ }
276
+ const kept = raw.filter(line => {
277
+ const directive = /^\/\/go:([a-z]+)/.exec(line.trim());
278
+ if (directive)
279
+ return KEPT_DIRECTIVES.has(directive[1]);
280
+ if (/^\/\/\s*nolint/i.test(line.trim()))
281
+ return false;
282
+ return !isBanner(line);
283
+ });
284
+ if (kept.length === 0 || kept.some(l => LICENCE_RE.test(l)))
285
+ return '';
286
+ return kept.join('\n');
287
+ }
288
+ /**
289
+ * The file's `//go:build` constraint, which applies to every declaration in it.
290
+ *
291
+ * `binding.go` and `binding_nomsgpack.go` declare the same twelve names under
292
+ * opposite constraints, so a reader shown one without the banner cannot tell
293
+ * which build they are reading.
294
+ */
295
+ export function buildConstraint(src) {
296
+ for (const line of src.split('\n')) {
297
+ const trimmed = line.trim();
298
+ if (/^\/\/go:build\s/.test(trimmed))
299
+ return trimmed;
300
+ if (GO_ITEM_HEAD_RE.test(trimmed))
301
+ return null;
302
+ }
303
+ return null;
304
+ }
305
+ function collapse(text) {
306
+ return text.replace(/\s+/g, ' ').trim();
307
+ }
308
+ function indent(lines) {
309
+ return lines.map(l => `\t${l}`).join('\n');
310
+ }
311
+ /** A member with its doc comment above it, ready to sit inside a body. */
312
+ function renderMember(item, body) {
313
+ const doc = keptPreamble(item.pending);
314
+ return doc ? [...doc.split('\n'), body] : [body];
315
+ }
316
+ /** Strip a trailing line comment, leaving the code that carries the names. */
317
+ function withoutTrailingComment(text) {
318
+ const at = text.indexOf('//');
319
+ if (at < 0)
320
+ return text;
321
+ // A `//` inside a struct tag or a string is code, not a comment.
322
+ const before = text.slice(0, at);
323
+ const backticks = (before.match(/`/g) ?? []).length;
324
+ const quotes = (before.match(/"/g) ?? []).length;
325
+ return backticks % 2 === 0 && quotes % 2 === 0 ? before.trimEnd() : text;
326
+ }
327
+ function memberNames(text) {
328
+ const code = withoutTrailingComment(text);
329
+ const embedded = GO_EMBEDDED_RE.exec(code);
330
+ if (embedded)
331
+ return { names: [embedded[1]], rest: '', embedded: true };
332
+ const named = GO_MEMBER_NAME_RE.exec(code);
333
+ if (!named)
334
+ return null;
335
+ const names = named[1].split(',').map(s => s.trim());
336
+ return { names, rest: code.slice(named[1].length).trim(), embedded: false };
337
+ }
338
+ /**
339
+ * Keep the exported fields of a struct.
340
+ *
341
+ * A field line can declare several names (`Key, Value string`), so a mixed line
342
+ * is rewritten to its exported half rather than kept or dropped whole.
343
+ */
344
+ function structMembers(body) {
345
+ const out = [];
346
+ let dropped = false;
347
+ for (const item of splitGoItems(body)) {
348
+ const parsed = memberNames(item.text);
349
+ if (!parsed)
350
+ continue;
351
+ const kept = parsed.names.filter(isExported);
352
+ if (kept.length === 0) {
353
+ dropped = true;
354
+ continue;
355
+ }
356
+ const text = kept.length === parsed.names.length ? item.text : `${kept.join(', ')} ${parsed.rest}`;
357
+ out.push(...renderMember(item, text));
358
+ }
359
+ // godoc's own phrase, and the type is still worth naming: an opaque struct is
360
+ // passed around by callers who never touch a field.
361
+ if (out.length === 0)
362
+ return dropped ? ['// contains filtered or unexported fields'] : [];
363
+ return out;
364
+ }
365
+ /**
366
+ * Every member of an interface, exported or not.
367
+ *
368
+ * The method set IS the contract, and an unexported method is the seal that
369
+ * makes the interface unimplementable from outside — which a caller has to be
370
+ * told about, not shielded from.
371
+ */
372
+ function interfaceMembers(body) {
373
+ const out = [];
374
+ for (const item of splitGoItems(body)) {
375
+ out.push(...renderMember(item, item.text));
376
+ }
377
+ return out;
378
+ }
379
+ function elideInitializer(text) {
380
+ if (text.length <= INITIALIZER_LIMIT && !text.includes('\n'))
381
+ return text;
382
+ const at = text.indexOf('=');
383
+ return at < 0 ? collapse(text) : `${text.slice(0, at).trimEnd()} = /* value elided */`;
384
+ }
385
+ /** `var _ Render = (*JSON)(nil)` — the only machine-readable "X implements Y". */
386
+ function isInterfaceAssertion(text) {
387
+ return new RegExp(String.raw `^(?:var\s+)?_\s+(${ID})\s*=\s*\(\*?(${ID})\)`, 'u').test(text);
388
+ }
389
+ /**
390
+ * Members of a `const` or `var` group.
391
+ *
392
+ * An `iota` group is all-or-nothing: its members without `=` take positional
393
+ * values, so dropping one silently renumbers every survivor.
394
+ */
395
+ function groupMembers(body) {
396
+ const items = splitGoItems(body);
397
+ const positional = /\biota\b/.test(body);
398
+ const exported = (item) => {
399
+ if (isInterfaceAssertion(item.text))
400
+ return true;
401
+ const parsed = memberNames(item.text);
402
+ return parsed !== null && parsed.names.some(isExported);
403
+ };
404
+ if (positional) {
405
+ return items.some(exported) ?
406
+ items.flatMap(item => renderMember(item, elideInitializer(item.text)))
407
+ : [];
408
+ }
409
+ return items.filter(exported).flatMap(item => renderMember(item, elideInitializer(item.text)));
410
+ }
411
+ function splitBody(text) {
412
+ const open = trailingGroup(text);
413
+ if (open === null)
414
+ return null;
415
+ return { head: text.slice(0, open + 1), inner: text.slice(open + 1, text.length - 1) };
416
+ }
417
+ function renderType(text) {
418
+ const name = GO_DECL_NAME_RE.exec(text)?.[1];
419
+ if (name === undefined || !isExported(name))
420
+ return null;
421
+ const body = splitBody(text);
422
+ if (!body || !GO_TYPE_BODY_RE.test(body.head.slice(0, -1)))
423
+ return elideInitializer(text);
424
+ const head = collapse(body.head);
425
+ // The keyword immediately before the brace, never a substring search: a
426
+ // generic constraint puts `interface` inside the type parameter list, and
427
+ // `type Number[T interface{ ~int }] struct` would then keep every private
428
+ // field an interface's members are exempt from.
429
+ const members = /\binterface\s*\{$/.test(head) ? interfaceMembers(body.inner) : structMembers(body.inner);
430
+ return members.length === 0 ? `${head}}` : `${head}\n${indent(members)}\n}`;
431
+ }
432
+ /** A `type ( … )` group: each member is a whole declaration missing its keyword. */
433
+ function renderTypeGroup(inner) {
434
+ return splitGoItems(inner).flatMap(item => {
435
+ const rendered = renderType(`type ${item.text}`);
436
+ return rendered === null ? [] : renderMember(item, rendered);
437
+ });
438
+ }
439
+ function renderFunc(text) {
440
+ const match = GO_FUNC_RE.exec(text);
441
+ if (!match)
442
+ return null;
443
+ const [, receiver, name] = match;
444
+ // A method on an unexported type cannot be called from outside the package,
445
+ // however uppercase its own name is. gin's `fs.go` publishes two such calls
446
+ // that do not compile, and `binding/` about thirty.
447
+ if (receiver !== undefined && !isExported(receiver))
448
+ return null;
449
+ if (!isExported(name))
450
+ return null;
451
+ const body = splitBody(text);
452
+ // No trailing `;`. Rust needs one to stay valid Rust; a bare Go signature is
453
+ // exactly what `go doc` prints.
454
+ return collapse(body ? text.slice(0, body.head.length - 1) : text);
455
+ }
456
+ function renderConstOrVar(text, kind) {
457
+ const body = splitBody(text);
458
+ if (body && body.head.trimEnd().endsWith('(')) {
459
+ const members = groupMembers(body.inner);
460
+ return members.length === 0 ? [] : [`${kind} (\n${indent(members)}\n)`];
461
+ }
462
+ if (isInterfaceAssertion(text))
463
+ return [elideInitializer(text)];
464
+ const name = GO_DECL_NAME_RE.exec(text)?.[1];
465
+ if (name === undefined || !isExported(name))
466
+ return [];
467
+ return [elideInitializer(text)];
468
+ }
469
+ /**
470
+ * One Go file reduced to the declarations a caller outside the package can use.
471
+ */
472
+ export function goSurface(src) {
473
+ const out = [];
474
+ const constraint = buildConstraint(src);
475
+ if (constraint)
476
+ out.push(constraint, '');
477
+ for (const item of splitGoItems(src)) {
478
+ const kind = GO_ITEM_HEAD_RE.exec(item.text)?.[1];
479
+ // Import paths are not API, and a file's own imports say nothing a caller
480
+ // of it can act on.
481
+ if (kind === undefined || kind === 'import')
482
+ continue;
483
+ const doc = keptPreamble(item.pending);
484
+ const push = (body) => {
485
+ if (doc)
486
+ out.push(doc);
487
+ out.push(body, '');
488
+ };
489
+ if (kind === 'package') {
490
+ push(item.text);
491
+ continue;
492
+ }
493
+ if (kind === 'func') {
494
+ const rendered = renderFunc(item.text);
495
+ if (rendered)
496
+ push(rendered);
497
+ continue;
498
+ }
499
+ if (kind === 'type') {
500
+ const body = splitBody(item.text);
501
+ if (body && /^type\s*\($/.test(body.head.trim())) {
502
+ for (const member of renderTypeGroup(body.inner))
503
+ out.push(member, '');
504
+ continue;
505
+ }
506
+ const rendered = renderType(item.text);
507
+ if (rendered)
508
+ push(rendered);
509
+ continue;
510
+ }
511
+ for (const rendered of renderConstOrVar(item.text, kind))
512
+ push(rendered);
513
+ }
514
+ return out
515
+ .join('\n')
516
+ .replace(/\n{3,}/g, '\n\n')
517
+ .trimEnd();
518
+ }
519
+ /**
520
+ * Everything reachable from {@link goSurface}, by source.
521
+ *
522
+ * `String(fn)` sees one level, and a helper left out of a list like this has
523
+ * three times frozen every cached package on the rule it replaced.
524
+ */
525
+ export function goContentFingerprint() {
526
+ return [
527
+ goSurface,
528
+ splitGoItems,
529
+ scanItem,
530
+ endsStatement,
531
+ skipToCode,
532
+ skipLiteral,
533
+ trailingGroup,
534
+ splitBody,
535
+ buildConstraint,
536
+ keptPreamble,
537
+ isBanner,
538
+ isExported,
539
+ receiverType,
540
+ memberNames,
541
+ withoutTrailingComment,
542
+ structMembers,
543
+ interfaceMembers,
544
+ groupMembers,
545
+ renderType,
546
+ renderTypeGroup,
547
+ renderFunc,
548
+ renderConstOrVar,
549
+ renderMember,
550
+ elideInitializer,
551
+ isInterfaceAssertion,
552
+ collapse,
553
+ indent
554
+ ]
555
+ .map(String)
556
+ .concat([
557
+ GO_ITEM_HEAD_RE.source,
558
+ GO_FUNC_RE.source,
559
+ GO_DECL_NAME_RE.source,
560
+ GO_MEMBER_NAME_RE.source,
561
+ GO_EMBEDDED_RE.source,
562
+ GO_TYPE_BODY_RE.source,
563
+ STMT_END_CHAR_RE.source,
564
+ LICENCE_RE.source,
565
+ RULE_CHARS,
566
+ [...CONTINUING_KEYWORDS].join(','),
567
+ [...KEPT_DIRECTIVES].join(','),
568
+ String(INITIALIZER_LIMIT)
569
+ ])
570
+ .join('');
571
+ }
@@ -26,7 +26,12 @@ const Params = Type.Object({
26
26
  query: Type.String({
27
27
  description: 'What to extract from the docs. The child pi reads ranked chunks and returns ONLY content answering this.'
28
28
  }),
29
- ecosystem: Type.Optional(Type.Union([Type.Literal('npm'), Type.Literal('cargo'), Type.Literal('hackage')], {
29
+ ecosystem: Type.Optional(Type.Union([
30
+ Type.Literal('npm'),
31
+ Type.Literal('cargo'),
32
+ Type.Literal('hackage'),
33
+ Type.Literal('go')
34
+ ], {
30
35
  description: 'Which registry to read. Only needed in a repo holding more than one package manifest; otherwise the manifest decides.'
31
36
  }))
32
37
  });
@@ -104,7 +109,9 @@ export function registerPiWorkerDocs(pi, internals = {}) {
104
109
  + 'truth and is version-pinned to what is actually installed (training-data '
105
110
  + 'versions and APIs are typically months stale).\n'
106
111
  + 'SUPPORTED ECOSYSTEMS: npm (package.json), cargo (Cargo.toml), hackage '
107
- + '(*.cabal). The MANIFEST in the working '
112
+ + '(*.cabal), go (go.mod). For go, pass the IMPORT PATH you would write in '
113
+ + 'source — "github.com/gin-gonic/gin/binding", "net/http" — not a bare '
114
+ + 'package name. The MANIFEST in the working '
108
115
  + 'directory decides which registry a name is looked up in — you do not. If '
109
116
  + 'the directory holds none of those manifests, this tool REFUSES and '
110
117
  + 'installs nothing; use `pi-worker-search` or `pi-worker-fetch` for that '
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mjasnikovs/pi-task",
3
- "version": "0.40.28",
3
+ "version": "0.40.30",
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",