@agimon-ai/doompi-plan 0.0.1-alpha.37 → 0.0.1-alpha.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/README.md +1 -1
- package/package.json +21 -7
- package/web/PlanToolMessage.tsx +44 -0
- package/web/index.ts +26 -0
- package/web/planToolRender.ts +242 -0
- package/web/tsconfig.json +23 -0
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-plan",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.38",
|
|
4
4
|
"description": "Reviewable Pi planning mode that removes file-editing tools and persists implementation plans.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
"dist",
|
|
30
|
+
"web",
|
|
30
31
|
"llms.txt",
|
|
31
32
|
"src/prompts",
|
|
32
33
|
"README.md",
|
|
@@ -86,23 +87,36 @@
|
|
|
86
87
|
},
|
|
87
88
|
"dependencies": {
|
|
88
89
|
"@deepseek-ai/cordis": "4.0.1",
|
|
89
|
-
"@agimon-ai/doompi-config": "0.0.1-alpha.
|
|
90
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
91
|
-
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.
|
|
90
|
+
"@agimon-ai/doompi-config": "0.0.1-alpha.38",
|
|
91
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.38",
|
|
92
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.38",
|
|
93
|
+
"@agimon-ai/doompi-web-components": "0.0.0",
|
|
94
|
+
"@agimon-ai/doompi-web-contracts": "0.0.1-alpha.1"
|
|
92
95
|
},
|
|
93
96
|
"devDependencies": {
|
|
94
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
97
|
+
"@earendil-works/pi-coding-agent": "0.84.3",
|
|
98
|
+
"@tanstack/react-store": "0.11.1",
|
|
99
|
+
"@tanstack/store": "0.11.1",
|
|
95
100
|
"@types/node": "26.2.0",
|
|
101
|
+
"@types/react": "19.2.7",
|
|
102
|
+
"@types/react-dom": "19.2.3",
|
|
96
103
|
"@vitest/coverage-v8": "4.1.11",
|
|
104
|
+
"react": "19.2.8",
|
|
105
|
+
"react-dom": "19.2.8",
|
|
97
106
|
"typescript": "7.0.2",
|
|
98
107
|
"vitest": "4.1.11"
|
|
99
108
|
},
|
|
100
109
|
"peerDependencies": {
|
|
101
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
110
|
+
"@earendil-works/pi-coding-agent": "0.84.3"
|
|
102
111
|
},
|
|
103
112
|
"engines": {
|
|
104
113
|
"node": ">=22.19.0"
|
|
105
114
|
},
|
|
115
|
+
"doompiWeb": {
|
|
116
|
+
"pluginId": "plan",
|
|
117
|
+
"channels": [],
|
|
118
|
+
"client": "./web/index.ts"
|
|
119
|
+
},
|
|
106
120
|
"pi": {
|
|
107
121
|
"extensions": [
|
|
108
122
|
"./dist/extensions/pi.mjs"
|
|
@@ -111,7 +125,7 @@
|
|
|
111
125
|
"scripts": {
|
|
112
126
|
"build": "tsdown",
|
|
113
127
|
"test": "vitest --run",
|
|
114
|
-
"typecheck": "tsc --noEmit",
|
|
128
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p web/tsconfig.json",
|
|
115
129
|
"lint": "oxlint . && oxfmt . --check",
|
|
116
130
|
"fixcode": "oxlint . --fix && oxfmt ."
|
|
117
131
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MessageItem,
|
|
3
|
+
MessageItemBody,
|
|
4
|
+
MessageItemHeader,
|
|
5
|
+
MessageLines,
|
|
6
|
+
toolTone,
|
|
7
|
+
} from '@agimon-ai/doompi-web-components';
|
|
8
|
+
import type { ToolMessageRenderProps } from '@agimon-ai/doompi-web-contracts';
|
|
9
|
+
import { planCallSummary, planResultLines } from './planToolRender.ts';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The plan tools' timeline item: the plan action, the issue or decision it
|
|
13
|
+
* targets, and what the packet carries in the header; the outcome line, with
|
|
14
|
+
* the recorded evidence or the Fable draft once expanded, in the body.
|
|
15
|
+
*/
|
|
16
|
+
export function PlanToolMessage({ toolName, args, result, running, isError }: ToolMessageRenderProps) {
|
|
17
|
+
const summary = planCallSummary(toolName, args);
|
|
18
|
+
const collapsed = planResultLines(toolName, args, result, { expanded: false, isError, isPartial: running });
|
|
19
|
+
const full = planResultLines(toolName, args, result, { expanded: true, isError, isPartial: running });
|
|
20
|
+
return (
|
|
21
|
+
<MessageItem tone={toolTone({ running, isError })} expandable={full.length > collapsed.length}>
|
|
22
|
+
{({ expanded }) => (
|
|
23
|
+
<>
|
|
24
|
+
<MessageItemHeader title={toolName}>
|
|
25
|
+
<span data-testid={`tool-call-${toolName}`} className="flex min-w-0 flex-1 items-center gap-2">
|
|
26
|
+
<span className="font-bold text-doom-hi">{summary.action}</span>
|
|
27
|
+
{summary.detail ? <span className="min-w-0 truncate text-doom-faint">{summary.detail}</span> : null}
|
|
28
|
+
{summary.metadata.map((value) => (
|
|
29
|
+
<span key={value} className="shrink-0 text-doom-dim">
|
|
30
|
+
· {value}
|
|
31
|
+
</span>
|
|
32
|
+
))}
|
|
33
|
+
</span>
|
|
34
|
+
</MessageItemHeader>
|
|
35
|
+
{(expanded ? full : collapsed).length > 0 ? (
|
|
36
|
+
<MessageItemBody data-testid={`tool-result-${toolName}`}>
|
|
37
|
+
<MessageLines lines={expanded ? full : collapsed} />
|
|
38
|
+
</MessageItemBody>
|
|
39
|
+
) : null}
|
|
40
|
+
</>
|
|
41
|
+
)}
|
|
42
|
+
</MessageItem>
|
|
43
|
+
);
|
|
44
|
+
}
|
package/web/index.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
|
|
2
|
+
import { PlanToolMessage } from './PlanToolMessage.tsx';
|
|
3
|
+
import { PLAN_TOOL_NAMES } from './planToolRender.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This package's cockpit presence: pure metadata. The selection bar renders
|
|
7
|
+
* the minor-mode entry and folds in what the session reports through the 'plan-mode' footer status.
|
|
8
|
+
*/
|
|
9
|
+
export const webPlugin = defineWebPlugin({
|
|
10
|
+
id: 'plan',
|
|
11
|
+
minorModes: [{ name: 'plan', keys: 'p e', statusKey: 'plan-mode', order: 20 }],
|
|
12
|
+
// The plan tools' timeline cards; the TUI leaves these on Pi's default shell.
|
|
13
|
+
toolRenderers: [{ tools: [...PLAN_TOOL_NAMES], message: PlanToolMessage }],
|
|
14
|
+
// The TUI's SPC p e through /minor, which asks for the flavor; the direct
|
|
15
|
+
// debug (p d) and fable (p f) keys are leader actions the RPC road lacks.
|
|
16
|
+
leaderBindings: [
|
|
17
|
+
{
|
|
18
|
+
id: 'plan.toggle',
|
|
19
|
+
path: [
|
|
20
|
+
{ key: 'p', label: 'plan', detail: 'read-only planning modes' },
|
|
21
|
+
{ key: 'e', label: 'toggle', detail: 'enter or leave read-only planning' },
|
|
22
|
+
],
|
|
23
|
+
command: 'minor plan',
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
});
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type { MessageLine, MessageLineTone } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import type { ToolResultView } from '@agimon-ai/doompi-web-contracts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Purpose-first summaries for the plan-mode tools. The TUI leaves these on
|
|
6
|
+
* Pi's default shell, so the shapes here come straight from the tool
|
|
7
|
+
* definitions in src/services/planMode.ts: write_plan and complete_plan
|
|
8
|
+
* report through `details` (written, path, phase, exited), run_fable_plan
|
|
9
|
+
* carries the broker result, and record_debug_evidence only confirms.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const PLAN_TOOL_NAMES = ['record_debug_evidence', 'run_fable_plan', 'write_plan', 'complete_plan'] as const;
|
|
13
|
+
|
|
14
|
+
/** The toned lines the shared MessageLines draws; the vocabulary is the components package's. */
|
|
15
|
+
export type LineTone = MessageLineTone;
|
|
16
|
+
export type ToolLine = MessageLine;
|
|
17
|
+
|
|
18
|
+
export interface PlanCallSummary {
|
|
19
|
+
action: string;
|
|
20
|
+
detail?: string;
|
|
21
|
+
metadata: string[];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PlanRenderOptions {
|
|
25
|
+
expanded: boolean;
|
|
26
|
+
isError: boolean;
|
|
27
|
+
isPartial: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
type JsonRecord = Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
const COLLAPSED_BODY_LINES = 12;
|
|
33
|
+
const PREVIEW_LENGTH = 72;
|
|
34
|
+
|
|
35
|
+
const EVIDENCE_LISTS: ReadonlyArray<[key: string, label: string]> = [
|
|
36
|
+
['logs', 'log'],
|
|
37
|
+
['correlatedTraceEvidence', 'trace'],
|
|
38
|
+
['processOutput', 'process output'],
|
|
39
|
+
['browserConsoleEvidence', 'console entry'],
|
|
40
|
+
['correlationIds', 'correlation id'],
|
|
41
|
+
['timestamps', 'timestamp'],
|
|
42
|
+
['verifiedFacts', 'verified fact'],
|
|
43
|
+
['hypotheses', 'hypothesis'],
|
|
44
|
+
['unavailableEvidence', 'unavailable item'],
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const PACKET_LISTS: ReadonlyArray<[key: string, label: string]> = [
|
|
48
|
+
['goal', 'goal'],
|
|
49
|
+
['constraints', 'constraint'],
|
|
50
|
+
['decisions', 'decision'],
|
|
51
|
+
['verifiedFindings', 'verified finding'],
|
|
52
|
+
['inferredFindings', 'inferred finding'],
|
|
53
|
+
['unresolvedQuestions', 'open question'],
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
function asRecord(value: unknown): JsonRecord | undefined {
|
|
57
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value) ? (value as JsonRecord) : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function inlineText(value: string): string {
|
|
61
|
+
let normalized = '';
|
|
62
|
+
for (const character of value) {
|
|
63
|
+
const code = character.codePointAt(0) ?? 0;
|
|
64
|
+
normalized += code <= 31 || code === 127 ? ' ' : character;
|
|
65
|
+
}
|
|
66
|
+
return normalized.replace(/\s+/gu, ' ').trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readString(record: JsonRecord | undefined, key: string): string | undefined {
|
|
70
|
+
const value = record?.[key];
|
|
71
|
+
return typeof value === 'string' ? inlineText(value) || undefined : undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function readNumber(record: JsonRecord | undefined, key: string): number | undefined {
|
|
75
|
+
const value = record?.[key];
|
|
76
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function listLength(record: JsonRecord, key: string): number {
|
|
80
|
+
const value = record[key];
|
|
81
|
+
return Array.isArray(value) ? value.length : 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function plural(count: number, singular: string): string {
|
|
85
|
+
if (count === 1) return singular;
|
|
86
|
+
if (singular.endsWith('y')) return `${singular.slice(0, -1)}ies`;
|
|
87
|
+
return `${singular}s`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function preview(value: string | undefined): string | undefined {
|
|
91
|
+
if (!value) return undefined;
|
|
92
|
+
return value.length > PREVIEW_LENGTH ? `${value.slice(0, PREVIEW_LENGTH - 3)}…` : value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function countMetadata(args: JsonRecord, lists: ReadonlyArray<[string, string]>): string[] {
|
|
96
|
+
return lists.flatMap(([key, label]) => {
|
|
97
|
+
const count = listLength(args, key);
|
|
98
|
+
return count > 0 ? [`${count} ${plural(count, label)}`] : [];
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function line(text: string, tone: LineTone, extra: Omit<ToolLine, 'text' | 'tone'> = {}): ToolLine {
|
|
103
|
+
return { text, tone, ...extra };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function outputText(result: ToolResultView | null): string {
|
|
107
|
+
if (!result) return '';
|
|
108
|
+
return result.content
|
|
109
|
+
.flatMap((item) => {
|
|
110
|
+
const record = asRecord(item);
|
|
111
|
+
return record?.type === 'text' && typeof record.text === 'string' ? [record.text] : [];
|
|
112
|
+
})
|
|
113
|
+
.join('\n');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function bodyLines(text: string, expanded: boolean): ToolLine[] {
|
|
117
|
+
const all = text
|
|
118
|
+
.split('\n')
|
|
119
|
+
.map((entry) => entry.trimEnd())
|
|
120
|
+
.filter((entry) => entry.length > 0);
|
|
121
|
+
const shown = expanded ? all : all.slice(0, COLLAPSED_BODY_LINES);
|
|
122
|
+
const lines = shown.map((entry) => line(entry, 'text'));
|
|
123
|
+
if (all.length > shown.length) lines.push(line(`… ${all.length - shown.length} more lines`, 'dim'));
|
|
124
|
+
return lines;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The header beside the tool name: what the call is for and how much it carries. */
|
|
128
|
+
export function planCallSummary(toolName: string, args: JsonRecord): PlanCallSummary {
|
|
129
|
+
if (toolName === 'record_debug_evidence') {
|
|
130
|
+
const issue = preview(readString(args, 'issue'));
|
|
131
|
+
return {
|
|
132
|
+
action: 'record evidence',
|
|
133
|
+
...(issue ? { detail: issue } : {}),
|
|
134
|
+
metadata: countMetadata(args, EVIDENCE_LISTS),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (toolName === 'run_fable_plan') {
|
|
138
|
+
const metadata = countMetadata(args, PACKET_LISTS);
|
|
139
|
+
if (readString(args, 'currentPlan')) metadata.push('with current plan');
|
|
140
|
+
return { action: 'fable draft', metadata };
|
|
141
|
+
}
|
|
142
|
+
if (toolName === 'write_plan') return { action: 'save plan', metadata: [] };
|
|
143
|
+
const decision = readString(args, 'decision');
|
|
144
|
+
return { action: decision ? `decide ${decision}` : 'request approval', metadata: [] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function evidenceLines(args: JsonRecord, result: ToolResultView | null, options: PlanRenderOptions): ToolLine[] {
|
|
148
|
+
const recorded = asRecord(result?.details)?.recorded === true;
|
|
149
|
+
const lines = [
|
|
150
|
+
recorded
|
|
151
|
+
? line('✓ evidence recorded as planning context', 'success')
|
|
152
|
+
: line(`○ ${inlineText(outputText(result)) || 'not recorded'}`, 'muted'),
|
|
153
|
+
];
|
|
154
|
+
if (!options.expanded) return lines;
|
|
155
|
+
const fields: ReadonlyArray<[string, string]> = [
|
|
156
|
+
['issue', 'issue'],
|
|
157
|
+
['expectedBehavior', 'expected'],
|
|
158
|
+
['reproductionAttempt', 'reproduction'],
|
|
159
|
+
['actualBehavior', 'actual'],
|
|
160
|
+
];
|
|
161
|
+
for (const [key, label] of fields) {
|
|
162
|
+
const value = readString(args, key);
|
|
163
|
+
if (value) lines.push(line(`${label} ${value}`, 'text'));
|
|
164
|
+
}
|
|
165
|
+
for (const [key, label] of EVIDENCE_LISTS) {
|
|
166
|
+
const raw = args[key];
|
|
167
|
+
const items = Array.isArray(raw) ? raw.filter((item): item is string => typeof item === 'string') : [];
|
|
168
|
+
if (items.length === 0) continue;
|
|
169
|
+
lines.push(line(plural(items.length, label), 'hi', { bold: true }));
|
|
170
|
+
lines.push(...items.map((item) => line(inlineText(item), 'text', { indent: true })));
|
|
171
|
+
}
|
|
172
|
+
return lines;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function fableLines(result: ToolResultView | null, options: PlanRenderOptions): ToolLine[] {
|
|
176
|
+
const details = asRecord(result?.details);
|
|
177
|
+
const text = outputText(result);
|
|
178
|
+
if (details?.started !== true) {
|
|
179
|
+
const code = readString(details, 'errorCode');
|
|
180
|
+
return [line(`○ ${inlineText(text) || 'fable planning did not start'}${code ? ` · ${code}` : ''}`, 'muted')];
|
|
181
|
+
}
|
|
182
|
+
const status = readString(details, 'status') ?? 'unknown';
|
|
183
|
+
const draft = typeof details?.draft === 'string' ? details.draft : undefined;
|
|
184
|
+
const code = readString(details, 'errorCode');
|
|
185
|
+
if (!draft) return [line(`✗ fable planning ${status}${code ? ` · ${code}` : ''}`, 'error')];
|
|
186
|
+
const finished = status === 'completed';
|
|
187
|
+
return [
|
|
188
|
+
line(
|
|
189
|
+
`${finished ? '✓' : '!'} fable draft · ${status}${code ? ` · ${code}` : ''}`,
|
|
190
|
+
finished ? 'success' : 'warning',
|
|
191
|
+
{
|
|
192
|
+
bold: true,
|
|
193
|
+
},
|
|
194
|
+
),
|
|
195
|
+
...bodyLines(draft, options.expanded),
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function writeLines(result: ToolResultView | null, options: PlanRenderOptions): ToolLine[] {
|
|
200
|
+
const details = asRecord(result?.details);
|
|
201
|
+
const path = readString(details, 'path');
|
|
202
|
+
if (options.isPartial) {
|
|
203
|
+
const phase = readString(details, 'phase') ?? 'checking';
|
|
204
|
+
return [line(`◐ ${phase}${path ? ` ${path}` : ''}`, 'warning')];
|
|
205
|
+
}
|
|
206
|
+
const duration = readNumber(details, 'durationMs');
|
|
207
|
+
if (details?.written === true) {
|
|
208
|
+
return [line(`✓ wrote ${path ?? 'the plan'}${duration === undefined ? '' : ` · ${duration}ms`}`, 'success')];
|
|
209
|
+
}
|
|
210
|
+
const lines = [line(`○ ${inlineText(outputText(result)) || 'plan not written'}`, 'muted')];
|
|
211
|
+
if (path) lines.push(line(`path ${path}`, 'dim'));
|
|
212
|
+
return lines;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function completeLines(result: ToolResultView | null): ToolLine[] {
|
|
216
|
+
const text = inlineText(outputText(result));
|
|
217
|
+
if (asRecord(result?.details)?.exited === true) {
|
|
218
|
+
return [line(`✓ plan mode exited${text ? ` · ${text}` : ''}`, 'success')];
|
|
219
|
+
}
|
|
220
|
+
return [line(`○ staying in plan mode${text ? ` · ${text}` : ''}`, 'muted')];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** The body of a plan tool card: the outcome line, plus the carried detail once expanded. */
|
|
224
|
+
export function planResultLines(
|
|
225
|
+
toolName: string,
|
|
226
|
+
args: JsonRecord,
|
|
227
|
+
result: ToolResultView | null,
|
|
228
|
+
options: PlanRenderOptions,
|
|
229
|
+
): ToolLine[] {
|
|
230
|
+
if (options.isError) {
|
|
231
|
+
const text = inlineText(outputText(result));
|
|
232
|
+
return [line(`✗ ${text || `${toolName} failed`}`, 'error')];
|
|
233
|
+
}
|
|
234
|
+
if (toolName === 'record_debug_evidence') return evidenceLines(args, result, options);
|
|
235
|
+
if (toolName === 'run_fable_plan') {
|
|
236
|
+
if (options.isPartial) return [line('◐ drafting with the local Fable broker', 'warning')];
|
|
237
|
+
return fableLines(result, options);
|
|
238
|
+
}
|
|
239
|
+
if (toolName === 'write_plan') return writeLines(result, options);
|
|
240
|
+
if (options.isPartial) return [line('◐ waiting for the review decision', 'warning')];
|
|
241
|
+
return completeLines(result);
|
|
242
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitAny": true,
|
|
10
|
+
"noUnusedLocals": true,
|
|
11
|
+
"noUnusedParameters": true,
|
|
12
|
+
"noImplicitReturns": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"allowImportingTsExtensions": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"types": ["node"]
|
|
21
|
+
},
|
|
22
|
+
"include": [".", "../src/types", "../tests/web"]
|
|
23
|
+
}
|