@mjasnikovs/pi-task 0.13.36 → 0.13.38
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/task/auto-orchestrator.js +17 -2
- package/dist/task/enforce-guidelines.d.ts +11 -4
- package/dist/task/enforce-guidelines.js +12 -5
- package/dist/workers/docs-core.js +55 -2
- package/dist/workers/docs-index.d.ts +1 -1
- package/dist/workers/docs-index.js +3 -2
- package/dist/workers/docs-resolve.d.ts +14 -0
- package/dist/workers/docs-resolve.js +112 -5
- package/package.json +1 -1
|
@@ -17,7 +17,7 @@ import { writeTaskFile, readTaskFile, updateTaskFrontMatter, taskFilePath, tasks
|
|
|
17
17
|
import { gitCommitAll } from './auto-commit.js';
|
|
18
18
|
import { runGuidelineEnforcement, classifyEnforceChildFailure } from './enforce-guidelines.js';
|
|
19
19
|
import { runWorker } from '../workers/pi-worker-core.js';
|
|
20
|
-
import { runPhaseChild, prependHint, USER_CANCELLED } from './child-runner.js';
|
|
20
|
+
import { runPhaseChild, prependHint, formatLoopHint, USER_CANCELLED } from './child-runner.js';
|
|
21
21
|
import { SessionUI, registerBridgeCommand } from '../remote/bridge.js';
|
|
22
22
|
import { pushNotify } from '../remote/push.js';
|
|
23
23
|
import { getConfig } from '../config/config.js';
|
|
@@ -362,7 +362,15 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
362
362
|
signal: sig,
|
|
363
363
|
tools,
|
|
364
364
|
timeoutMs: 0, // no wall-clock timeout — run to completion
|
|
365
|
-
|
|
365
|
+
// Exact-match loop guard only: pathThreshold Infinity
|
|
366
|
+
// disables the path-revisit heuristic, so revisiting one
|
|
367
|
+
// file (which IS this pass's job) never trips — only a
|
|
368
|
+
// literally-identical call repeated past threshold does
|
|
369
|
+
// (e.g. the same grep fired 900× in enforce-debug.log).
|
|
370
|
+
// A hit nudges via the normal restart-with-hint; a loop
|
|
371
|
+
// that survives the nudges is WARNED, not blocked (see
|
|
372
|
+
// r.loopHit handling below and classifyEnforceChildFailure).
|
|
373
|
+
loop: { pathThreshold: Number.POSITIVE_INFINITY },
|
|
366
374
|
onLine: line => {
|
|
367
375
|
lastLine = line;
|
|
368
376
|
logEnforce(line);
|
|
@@ -371,6 +379,13 @@ function defaultDeps(ctx, cwd, signal, title) {
|
|
|
371
379
|
contextUsage = resolveContextUsage(snapshot, contextUsage, parentContextWindow);
|
|
372
380
|
}
|
|
373
381
|
});
|
|
382
|
+
// A loop that survived the restart-with-hint nudges is a
|
|
383
|
+
// warning, not a failure: log it and tell the user, but let
|
|
384
|
+
// the verdict gate (below) be the only thing that can block.
|
|
385
|
+
if (r.loopHit) {
|
|
386
|
+
logEnforce(`=== enforce LOOP WARNING — ${formatLoopHint(r.loopHit)} ===`);
|
|
387
|
+
enforceCtx.ui.notify(`${taskTitle}: enforce worker looped past the nudges — continuing (not blocked).`, 'warning');
|
|
388
|
+
}
|
|
374
389
|
const failure = classifyEnforceChildFailure(r);
|
|
375
390
|
logEnforce(failure ?
|
|
376
391
|
`=== enforce end: FAIL — ${failure} ===`
|
|
@@ -56,10 +56,17 @@ export interface EnforceChildResult {
|
|
|
56
56
|
* when it finished cleanly enough to parse a verdict from its text.
|
|
57
57
|
*
|
|
58
58
|
* The order is load-bearing. A loop-kill AND a wall-clock timeout BOTH also set
|
|
59
|
-
* `aborted` — killProc flips it on every kill path — so the
|
|
60
|
-
* (timeout, loop, leaked tool call) must be
|
|
61
|
-
* `aborted → user-cancel`
|
|
62
|
-
* inline code did) mislabels a loop-killed enforcement
|
|
59
|
+
* `aborted` (and a non-zero exit) — killProc flips it on every kill path — so the
|
|
60
|
+
* specific causes (timeout, loop, leaked tool call) must be handled BEFORE the
|
|
61
|
+
* generic `aborted → user-cancel` and `exitCode` mappings. Checking `aborted`
|
|
62
|
+
* first (as the original inline code did) mislabels a loop-killed enforcement
|
|
63
|
+
* child as a user cancel.
|
|
64
|
+
*
|
|
65
|
+
* A loop is NOT fatal: enforce attaches the detector in nudge-then-warn mode, so a
|
|
66
|
+
* loop that survived its restart-with-hint nudges returns null here (the caller
|
|
67
|
+
* logs/notifies it as a warning) and the verdict gate alone decides the outcome.
|
|
68
|
+
* It still has to be matched before `aborted`/`exitCode` so the kill's side
|
|
69
|
+
* effects don't get re-classified as a user cancel or a crash.
|
|
63
70
|
*/
|
|
64
71
|
export declare function classifyEnforceChildFailure(r: EnforceChildResult): string | null;
|
|
65
72
|
/**
|
|
@@ -101,16 +101,23 @@ export function parseEnforceVerdict(text) {
|
|
|
101
101
|
* when it finished cleanly enough to parse a verdict from its text.
|
|
102
102
|
*
|
|
103
103
|
* The order is load-bearing. A loop-kill AND a wall-clock timeout BOTH also set
|
|
104
|
-
* `aborted` — killProc flips it on every kill path — so the
|
|
105
|
-
* (timeout, loop, leaked tool call) must be
|
|
106
|
-
* `aborted → user-cancel`
|
|
107
|
-
* inline code did) mislabels a loop-killed enforcement
|
|
104
|
+
* `aborted` (and a non-zero exit) — killProc flips it on every kill path — so the
|
|
105
|
+
* specific causes (timeout, loop, leaked tool call) must be handled BEFORE the
|
|
106
|
+
* generic `aborted → user-cancel` and `exitCode` mappings. Checking `aborted`
|
|
107
|
+
* first (as the original inline code did) mislabels a loop-killed enforcement
|
|
108
|
+
* child as a user cancel.
|
|
109
|
+
*
|
|
110
|
+
* A loop is NOT fatal: enforce attaches the detector in nudge-then-warn mode, so a
|
|
111
|
+
* loop that survived its restart-with-hint nudges returns null here (the caller
|
|
112
|
+
* logs/notifies it as a warning) and the verdict gate alone decides the outcome.
|
|
113
|
+
* It still has to be matched before `aborted`/`exitCode` so the kill's side
|
|
114
|
+
* effects don't get re-classified as a user cancel or a crash.
|
|
108
115
|
*/
|
|
109
116
|
export function classifyEnforceChildFailure(r) {
|
|
110
117
|
if (r.timedOut)
|
|
111
118
|
return 'enforcement child timed out';
|
|
112
119
|
if (r.loopHit)
|
|
113
|
-
return
|
|
120
|
+
return null; // looped past the nudges → warning, handled by caller
|
|
114
121
|
if (r.leakedToolCall)
|
|
115
122
|
return 'enforcement child leaked a tool call';
|
|
116
123
|
if (r.aborted)
|
|
@@ -4,7 +4,7 @@ import * as os from 'node:os';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import { openCache as defaultOpenCache } from './docs-cache.js';
|
|
6
6
|
import { ensureIndexed as defaultEnsureIndexed } from './docs-index.js';
|
|
7
|
-
import { resolvePackage as defaultResolvePackage, ResolveError } from './docs-resolve.js';
|
|
7
|
+
import { resolvePackage as defaultResolvePackage, ResolveError, detectTypesRedirect, typesPackageName, hasTypeFiles, isDtsFile } from './docs-resolve.js';
|
|
8
8
|
import { retrieveChunks as defaultRetrieveChunks } from './docs-retrieve.js';
|
|
9
9
|
import { npmVersionLookup as defaultNpmVersionLookup } from './npm-version.js';
|
|
10
10
|
import { getPiInvocation } from '../shared/pi-invocation.js';
|
|
@@ -44,6 +44,52 @@ export async function runAutoInstall(spawn, packageName, signal) {
|
|
|
44
44
|
}, installDir, signal, { mode: 'text', discardStdout: true });
|
|
45
45
|
return { success: result.exitCode === 0 && !result.aborted, installDir, stderr: result.stderr };
|
|
46
46
|
}
|
|
47
|
+
/** Resolve `name` from cwd; on `not_installed`, auto-install it and resolve from
|
|
48
|
+
* the install dir. Returns null on any failure (caller keeps its fallback). */
|
|
49
|
+
async function tryResolveOrInstall(name, cwd, spawn, resolvePackage, signal) {
|
|
50
|
+
try {
|
|
51
|
+
return resolvePackage(name, cwd);
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
if (!(err instanceof ResolveError) || err.kind !== 'not_installed')
|
|
55
|
+
return null;
|
|
56
|
+
const install = await runAutoInstall(spawn, extractParentPackage(name), signal);
|
|
57
|
+
if (!install.success)
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
return resolvePackage(name, install.installDir);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Follow the @types/<name> + triple-slash `<reference types>` redirect chain
|
|
68
|
+
* from a package that ships no usable types of its own to the one that actually
|
|
69
|
+
* holds the declarations (e.g. bun -> @types/bun -> bun-types). Bounded to a few
|
|
70
|
+
* hops; returns the original package if no better source is found. */
|
|
71
|
+
async function resolveTypeSource(pkg, requested, cwd, spawn, resolvePackage, signal) {
|
|
72
|
+
const visited = new Set([pkg.name, extractParentPackage(requested)]);
|
|
73
|
+
let cur = pkg;
|
|
74
|
+
for (let depth = 0; depth < 3; depth++) {
|
|
75
|
+
let next = detectTypesRedirect(cur);
|
|
76
|
+
if (next && visited.has(next))
|
|
77
|
+
next = null;
|
|
78
|
+
if (!next && !hasTypeFiles(cur.root)) {
|
|
79
|
+
const types = typesPackageName(cur.name);
|
|
80
|
+
if (types && !visited.has(types))
|
|
81
|
+
next = types;
|
|
82
|
+
}
|
|
83
|
+
if (!next)
|
|
84
|
+
break;
|
|
85
|
+
visited.add(next);
|
|
86
|
+
const resolved = await tryResolveOrInstall(next, cwd, spawn, resolvePackage, signal);
|
|
87
|
+
if (!resolved)
|
|
88
|
+
break;
|
|
89
|
+
cur = resolved;
|
|
90
|
+
}
|
|
91
|
+
return cur;
|
|
92
|
+
}
|
|
47
93
|
export async function docsRaw(input) {
|
|
48
94
|
const resolvePackage = input.resolvePackage ?? defaultResolvePackage;
|
|
49
95
|
const ensureIndexed = input.ensureIndexed ?? defaultEnsureIndexed;
|
|
@@ -114,6 +160,13 @@ export async function docsRaw(input) {
|
|
|
114
160
|
};
|
|
115
161
|
}
|
|
116
162
|
}
|
|
163
|
+
// Step 1b: if the resolved package ships no usable type declarations (e.g.
|
|
164
|
+
// the `bun` runtime launcher, which is just a binary + install README), or is
|
|
165
|
+
// a pure `@types/<name>` redirect stub, follow the conventional
|
|
166
|
+
// @types/<name> + triple-slash `<reference types>` chain to the package that
|
|
167
|
+
// actually holds the declarations (e.g. bun -> @types/bun -> bun-types).
|
|
168
|
+
// Best-effort: any failure leaves the original resolution untouched.
|
|
169
|
+
pkg = await resolveTypeSource(pkg, input.pkg, input.cwd, spawn, resolvePackage, input.signal);
|
|
117
170
|
// Step 2: open cache
|
|
118
171
|
let cache = null;
|
|
119
172
|
let cacheError;
|
|
@@ -257,7 +310,7 @@ function walkDtsAlpha(root) {
|
|
|
257
310
|
const full = path.join(dir, entry.name);
|
|
258
311
|
if (entry.isDirectory())
|
|
259
312
|
stack.push(full);
|
|
260
|
-
else if (entry.isFile() && entry.name
|
|
313
|
+
else if (entry.isFile() && isDtsFile(entry.name))
|
|
261
314
|
out.push(full);
|
|
262
315
|
}
|
|
263
316
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
import { isDtsFile } from './docs-resolve.js';
|
|
4
5
|
const MAX_CHUNK_BYTES = 8 * 1024;
|
|
5
6
|
const ZERO_SEP = Buffer.from([0]);
|
|
6
7
|
const DECL_SPLIT_RE = /^(?:export\s+|declare\s+)?(?:default\s+)?(?:async\s+)?(?:function|class|interface|type|namespace|module|const|let|var|enum)\s+/m;
|
|
@@ -48,13 +49,13 @@ function walkDts(root) {
|
|
|
48
49
|
const stat = fs.statSync(realPath);
|
|
49
50
|
if (stat.isDirectory())
|
|
50
51
|
stack.push(realPath);
|
|
51
|
-
else if (stat.isFile() && realPath
|
|
52
|
+
else if (stat.isFile() && isDtsFile(realPath))
|
|
52
53
|
out.push(realPath);
|
|
53
54
|
continue;
|
|
54
55
|
}
|
|
55
56
|
if (entry.isDirectory())
|
|
56
57
|
stack.push(full);
|
|
57
|
-
else if (entry.isFile() && entry.name
|
|
58
|
+
else if (entry.isFile() && isDtsFile(entry.name))
|
|
58
59
|
out.push(full);
|
|
59
60
|
}
|
|
60
61
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** True for TypeScript declaration files: .d.ts, .d.mts, .d.cts. */
|
|
2
|
+
export declare function isDtsFile(name: string): boolean;
|
|
1
3
|
export interface ResolvedPackage {
|
|
2
4
|
name: string;
|
|
3
5
|
version: string;
|
|
@@ -10,3 +12,15 @@ export declare class ResolveError extends Error {
|
|
|
10
12
|
constructor(kind: 'not_installed' | 'invalid_name', message: string);
|
|
11
13
|
}
|
|
12
14
|
export declare function resolvePackage(moduleName: string, cwd: string): ResolvedPackage;
|
|
15
|
+
/** Conventional DefinitelyTyped package for a runtime package that ships no
|
|
16
|
+
* types of its own. `bun` -> `@types/bun`, `@scope/x` -> `@types/scope__x`.
|
|
17
|
+
* Returns null for packages that are already under the `@types` scope. */
|
|
18
|
+
export declare function typesPackageName(moduleName: string): string | null;
|
|
19
|
+
/** True if the package ships at least one declaration file. */
|
|
20
|
+
export declare function hasTypeFiles(root: string): boolean;
|
|
21
|
+
/** When a package is a pure pointer to another types package — a single-file
|
|
22
|
+
* `/// <reference types="X" />` (the `@types/bun -> bun-types` shape) or a lone
|
|
23
|
+
* `export * from "X"` re-export — return the target package name. Returns null
|
|
24
|
+
* for packages that ship their own declarations (more than one .d.ts file, or a
|
|
25
|
+
* local `/// <reference path=... />` aggregator entry). */
|
|
26
|
+
export declare function detectTypesRedirect(pkg: ResolvedPackage): string | null;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
const DTS_RE = /\.d\.(?:ts|mts|cts)$/;
|
|
5
|
+
/** True for TypeScript declaration files: .d.ts, .d.mts, .d.cts. */
|
|
6
|
+
export function isDtsFile(name) {
|
|
7
|
+
return DTS_RE.test(name);
|
|
8
|
+
}
|
|
4
9
|
export class ResolveError extends Error {
|
|
5
10
|
kind;
|
|
6
11
|
constructor(kind, message) {
|
|
@@ -52,10 +57,18 @@ function findReadme(root) {
|
|
|
52
57
|
function resolveEntryDts(moduleName, parent, root, pkg) {
|
|
53
58
|
if (moduleName !== parent) {
|
|
54
59
|
const subpath = moduleName.slice(parent.length + 1);
|
|
55
|
-
const candidates = [
|
|
60
|
+
const candidates = [
|
|
61
|
+
`${subpath}.d.ts`,
|
|
62
|
+
`${subpath}.d.mts`,
|
|
63
|
+
`${subpath}.d.cts`,
|
|
64
|
+
`${subpath}/index.d.ts`,
|
|
65
|
+
`${subpath}/index.d.mts`,
|
|
66
|
+
`${subpath}/index.d.cts`,
|
|
67
|
+
subpath
|
|
68
|
+
];
|
|
56
69
|
for (const c of candidates) {
|
|
57
70
|
const abs = path.join(root, c);
|
|
58
|
-
if (fs.existsSync(abs) && abs
|
|
71
|
+
if (fs.existsSync(abs) && isDtsFile(abs))
|
|
59
72
|
return abs;
|
|
60
73
|
}
|
|
61
74
|
}
|
|
@@ -71,9 +84,11 @@ function resolveEntryDts(moduleName, parent, root, pkg) {
|
|
|
71
84
|
if (fs.existsSync(abs))
|
|
72
85
|
return abs;
|
|
73
86
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
87
|
+
for (const name of ['index.d.ts', 'index.d.mts', 'index.d.cts']) {
|
|
88
|
+
const fallback = path.join(root, name);
|
|
89
|
+
if (fs.existsSync(fallback))
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
77
92
|
return null;
|
|
78
93
|
}
|
|
79
94
|
export function resolvePackage(moduleName, cwd) {
|
|
@@ -124,3 +139,95 @@ function findPackageJsonInNodeModules(parent, startDir) {
|
|
|
124
139
|
dir = up;
|
|
125
140
|
}
|
|
126
141
|
}
|
|
142
|
+
/** Conventional DefinitelyTyped package for a runtime package that ships no
|
|
143
|
+
* types of its own. `bun` -> `@types/bun`, `@scope/x` -> `@types/scope__x`.
|
|
144
|
+
* Returns null for packages that are already under the `@types` scope. */
|
|
145
|
+
export function typesPackageName(moduleName) {
|
|
146
|
+
const parent = parentPackageName(moduleName);
|
|
147
|
+
if (parent.startsWith('@types/'))
|
|
148
|
+
return null;
|
|
149
|
+
if (parent.startsWith('@')) {
|
|
150
|
+
const [scope, name] = parent.slice(1).split('/');
|
|
151
|
+
if (!scope || !name)
|
|
152
|
+
return null;
|
|
153
|
+
return `@types/${scope}__${name}`;
|
|
154
|
+
}
|
|
155
|
+
return `@types/${parent}`;
|
|
156
|
+
}
|
|
157
|
+
/** Count declaration files under root (excluding nested node_modules), stopping
|
|
158
|
+
* once `cap` is reached. */
|
|
159
|
+
function countTypeFiles(root, cap) {
|
|
160
|
+
let n = 0;
|
|
161
|
+
const stack = [root];
|
|
162
|
+
while (stack.length) {
|
|
163
|
+
const dir = stack.pop();
|
|
164
|
+
let entries;
|
|
165
|
+
try {
|
|
166
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
if (entry.name === 'node_modules')
|
|
173
|
+
continue;
|
|
174
|
+
const full = path.join(dir, entry.name);
|
|
175
|
+
if (entry.isDirectory())
|
|
176
|
+
stack.push(full);
|
|
177
|
+
else if (entry.isFile() && isDtsFile(entry.name)) {
|
|
178
|
+
if (++n >= cap)
|
|
179
|
+
return n;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return n;
|
|
184
|
+
}
|
|
185
|
+
/** True if the package ships at least one declaration file. */
|
|
186
|
+
export function hasTypeFiles(root) {
|
|
187
|
+
return countTypeFiles(root, 1) > 0;
|
|
188
|
+
}
|
|
189
|
+
function findIndexDts(root) {
|
|
190
|
+
for (const name of ['index.d.ts', 'index.d.mts', 'index.d.cts']) {
|
|
191
|
+
const abs = path.join(root, name);
|
|
192
|
+
if (fs.existsSync(abs))
|
|
193
|
+
return abs;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
function isBareSpecifier(spec) {
|
|
198
|
+
return spec.length > 0 && !spec.startsWith('.') && !spec.startsWith('/');
|
|
199
|
+
}
|
|
200
|
+
const REFERENCE_TYPES_RE = /\/\/\/\s*<reference\s+types=["']([^"']+)["']\s*\/>/;
|
|
201
|
+
const REEXPORT_ALL_RE = /^\s*export\s+(?:type\s+)?\*\s+from\s+["']([^"']+)["'];?\s*$/m;
|
|
202
|
+
/** When a package is a pure pointer to another types package — a single-file
|
|
203
|
+
* `/// <reference types="X" />` (the `@types/bun -> bun-types` shape) or a lone
|
|
204
|
+
* `export * from "X"` re-export — return the target package name. Returns null
|
|
205
|
+
* for packages that ship their own declarations (more than one .d.ts file, or a
|
|
206
|
+
* local `/// <reference path=... />` aggregator entry). */
|
|
207
|
+
export function detectTypesRedirect(pkg) {
|
|
208
|
+
// A package that ships multiple declaration files is an aggregator, not a
|
|
209
|
+
// redirect stub — use its own types.
|
|
210
|
+
if (countTypeFiles(pkg.root, 2) > 1)
|
|
211
|
+
return null;
|
|
212
|
+
const entry = pkg.entryDts ?? findIndexDts(pkg.root);
|
|
213
|
+
if (!entry)
|
|
214
|
+
return null;
|
|
215
|
+
let content;
|
|
216
|
+
try {
|
|
217
|
+
content = fs.readFileSync(entry, 'utf8');
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
// A local `/// <reference path="..." />` means the entry aggregates sibling
|
|
223
|
+
// declarations (e.g. bun-types) — not a redirect to another package.
|
|
224
|
+
if (/\/\/\/\s*<reference\s+path=/.test(content))
|
|
225
|
+
return null;
|
|
226
|
+
const ref = REFERENCE_TYPES_RE.exec(content);
|
|
227
|
+
if (ref && isBareSpecifier(ref[1]))
|
|
228
|
+
return parentPackageName(ref[1]);
|
|
229
|
+
const rex = REEXPORT_ALL_RE.exec(content);
|
|
230
|
+
if (rex && isBareSpecifier(rex[1]))
|
|
231
|
+
return parentPackageName(rex[1]);
|
|
232
|
+
return null;
|
|
233
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.38",
|
|
4
4
|
"description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|