buildanything 2.0.0 → 2.1.1
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +9 -1
- package/README.md +57 -61
- package/agents/a11y-architect.md +2 -0
- package/agents/briefing-officer.md +172 -0
- package/agents/business-model.md +14 -12
- package/agents/code-architect.md +6 -1
- package/agents/code-reviewer.md +3 -2
- package/agents/code-simplifier.md +12 -4
- package/agents/design-brand-guardian.md +19 -0
- package/agents/design-critic.md +16 -11
- package/agents/design-inclusive-visuals-specialist.md +2 -0
- package/agents/design-ui-designer.md +17 -0
- package/agents/design-ux-architect.md +15 -0
- package/agents/design-ux-researcher.md +102 -7
- package/agents/engineering-ai-engineer.md +2 -0
- package/agents/engineering-backend-architect.md +2 -0
- package/agents/engineering-data-engineer.md +2 -0
- package/agents/engineering-devops-automator.md +2 -0
- package/agents/engineering-frontend-developer.md +13 -0
- package/agents/engineering-mobile-app-builder.md +2 -0
- package/agents/engineering-rapid-prototyper.md +15 -2
- package/agents/engineering-security-engineer.md +2 -0
- package/agents/engineering-senior-developer.md +13 -0
- package/agents/engineering-sre.md +2 -0
- package/agents/engineering-technical-writer.md +2 -0
- package/agents/feature-intel.md +8 -7
- package/agents/ios-app-review-guardian.md +2 -0
- package/agents/ios-foundation-models-specialist.md +2 -0
- package/agents/ios-product-reality-auditor.md +292 -0
- package/agents/ios-storekit-specialist.md +2 -0
- package/agents/ios-swift-architect.md +1 -0
- package/agents/ios-swift-search.md +1 -0
- package/agents/ios-swift-ui-design.md +7 -4
- package/agents/marketing-app-store-optimizer.md +2 -0
- package/agents/planner.md +6 -1
- package/agents/pr-test-analyzer.md +3 -2
- package/agents/product-feedback-synthesizer.md +62 -0
- package/agents/product-owner.md +163 -0
- package/agents/product-reality-auditor.md +216 -0
- package/agents/product-spec-writer.md +176 -0
- package/agents/refactor-cleaner.md +9 -1
- package/agents/security-reviewer.md +2 -1
- package/agents/silent-failure-hunter.md +2 -1
- package/agents/swift-build-resolver.md +2 -0
- package/agents/swift-reviewer.md +2 -1
- package/agents/tech-feasibility.md +5 -3
- package/agents/testing-api-tester.md +2 -0
- package/agents/testing-evidence-collector.md +24 -0
- package/agents/testing-performance-benchmarker.md +2 -0
- package/agents/testing-reality-checker.md +2 -1
- package/agents/visual-research.md +7 -5
- package/bin/adapters/scribe-tool.ts +4 -2
- package/bin/adapters/write-lease-tool.ts +1 -1
- package/bin/buildanything-runtime.ts +20 -107
- package/bin/graph-index.js +24 -0
- package/bin/graph-index.ts +340 -0
- package/bin/mcp-servers/graph-mcp.js +26 -0
- package/bin/mcp-servers/graph-mcp.ts +481 -0
- package/bin/mcp-servers/orchestrator-mcp.js +26 -0
- package/bin/mcp-servers/orchestrator-mcp.ts +361 -0
- package/bin/setup.js +272 -111
- package/commands/build.md +371 -158
- package/commands/idea-sweep.md +2 -2
- package/commands/setup.md +15 -4
- package/commands/ux-review.md +3 -3
- package/commands/verify.md +3 -0
- package/docs/migration/phase-graph.yaml +573 -157
- package/hooks/design-md-lint +4 -0
- package/hooks/design-md-lint.ts +295 -0
- package/hooks/pre-tool-use.ts +37 -6
- package/hooks/record-mode-transitions.ts +63 -6
- package/hooks/subagent-start.ts +3 -2
- package/package.json +3 -1
- package/protocols/agent-prompt-authoring.md +165 -0
- package/protocols/architecture-schema.md +10 -3
- package/protocols/cleanup.md +4 -0
- package/protocols/decision-log.md +8 -4
- package/protocols/design-md-authoring.md +520 -0
- package/protocols/design-md-spec.md +362 -0
- package/protocols/fake-data-detector.md +1 -1
- package/protocols/ios-fake-data-detector.md +65 -0
- package/protocols/ios-phase-branches.md +112 -27
- package/protocols/launch-readiness.md +9 -5
- package/protocols/metric-loop.md +1 -1
- package/protocols/page-spec-schema.md +234 -0
- package/protocols/product-spec-schema.md +354 -0
- package/protocols/sprint-tasks-schema.md +53 -0
- package/protocols/state-schema.json +38 -3
- package/protocols/state-schema.md +32 -2
- package/protocols/verify.md +29 -1
- package/protocols/web-phase-branches.md +234 -64
- package/skills/ios/ios-bootstrap/SKILL.md +1 -1
- package/src/graph/ids.ts +86 -0
- package/src/graph/index.ts +32 -0
- package/src/graph/parser/architecture.ts +603 -0
- package/src/graph/parser/component-manifest.ts +268 -0
- package/src/graph/parser/decisions-jsonl.ts +407 -0
- package/src/graph/parser/design-md-pass2.ts +253 -0
- package/src/graph/parser/design-md.ts +477 -0
- package/src/graph/parser/page-spec.ts +496 -0
- package/src/graph/parser/product-spec.ts +930 -0
- package/src/graph/parser/screenshot.ts +342 -0
- package/src/graph/parser/sprint-tasks.ts +317 -0
- package/src/graph/storage/index.ts +1154 -0
- package/src/graph/types.ts +432 -0
- package/src/graph/util/dhash.ts +84 -0
- package/src/lrr/aggregator.ts +105 -10
- package/src/orchestrator/hooks/context-header.ts +34 -10
- package/src/orchestrator/hooks/token-accounting.ts +25 -14
- package/src/orchestrator/mcp/cycle-counter.ts +2 -1
- package/src/orchestrator/mcp/scribe.ts +27 -16
- package/src/orchestrator/mcp/write-lease.ts +30 -13
- package/src/orchestrator/phase4-shared-context.ts +20 -4
- package/protocols/visual-dna.md +0 -185
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env tsx
|
|
2
|
+
/**
|
|
3
|
+
* Orchestrator stdio MCP server.
|
|
4
|
+
*
|
|
5
|
+
* This is the stdio bridge that Claude-Code-hosted sessions auto-start via
|
|
6
|
+
* plugin.json's `mcpServers` block. It exposes the four orchestrator
|
|
7
|
+
* toolsets — state_save, write_lease, cycle_counter, scribe — as a single
|
|
8
|
+
* MCP server with 10 tools total.
|
|
9
|
+
*
|
|
10
|
+
* The Agent SDK code path (bin/buildanything-runtime.ts) registers the same
|
|
11
|
+
* underlying handlers in-process via createSdkMcpServer; this file owns the
|
|
12
|
+
* stdio path. Both import the same handler implementations from
|
|
13
|
+
* src/orchestrator/mcp/*.ts — the bifurcation is only at the transport layer.
|
|
14
|
+
*
|
|
15
|
+
* Tools exposed:
|
|
16
|
+
* state-save: state_save, state_read, verify_integrity
|
|
17
|
+
* write-lease: acquire_write_lease, release_write_lease, list_write_leases
|
|
18
|
+
* cycle-counter: cycle_counter_check, clear_in_flight_edge, handle_stale_edge
|
|
19
|
+
* scribe: scribe_decision
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { resolve } from "node:path";
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
stateSave,
|
|
29
|
+
stateRead,
|
|
30
|
+
verifyIntegrity,
|
|
31
|
+
} from "../../src/orchestrator/mcp/state-save.js";
|
|
32
|
+
import {
|
|
33
|
+
acquireWriteLease,
|
|
34
|
+
releaseLease,
|
|
35
|
+
getActiveLeases,
|
|
36
|
+
init as initWriteLease,
|
|
37
|
+
} from "../../src/orchestrator/mcp/write-lease.js";
|
|
38
|
+
import {
|
|
39
|
+
cycleCounterCheck,
|
|
40
|
+
clearInFlightEdge,
|
|
41
|
+
handleStaleEdge,
|
|
42
|
+
type CounterState,
|
|
43
|
+
} from "../../src/orchestrator/mcp/cycle-counter.js";
|
|
44
|
+
import { scribeDecision, loadCounters } from "../../src/orchestrator/mcp/scribe.js";
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Helpers
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
function okResult(payload: unknown) {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text" as const, text: JSON.stringify(payload) }],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function errResult(toolName: string, err: unknown) {
|
|
57
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
58
|
+
return {
|
|
59
|
+
isError: true,
|
|
60
|
+
content: [
|
|
61
|
+
{ type: "text" as const, text: `${toolName} error: ${message}` },
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Zod input shapes (mirror bin/adapters/*-tool.ts verbatim)
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
const stateSaveShape = {
|
|
71
|
+
path: z.string().min(1),
|
|
72
|
+
state: z.record(z.string(), z.unknown()),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const stateReadShape = {
|
|
76
|
+
path: z.string().min(1),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const verifyIntegrityShape = {
|
|
80
|
+
path: z.string().min(1),
|
|
81
|
+
expected_sha256: z.string().min(1),
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const acquireLeaseShape = {
|
|
85
|
+
task_id: z.string().min(1),
|
|
86
|
+
file_paths: z.array(z.string().min(1)).min(1),
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const releaseLeaseShape = {
|
|
90
|
+
task_id: z.string().min(1),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const listLeasesShape = {} as const;
|
|
94
|
+
|
|
95
|
+
const inFlightEdgeSchema = z.object({
|
|
96
|
+
decision_id: z.string().min(1),
|
|
97
|
+
target_phase: z.string().min(1),
|
|
98
|
+
counter_value: z.number().int().nonnegative(),
|
|
99
|
+
started_at: z.string().min(1),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const counterStateSchema = z.object({
|
|
103
|
+
backward_routing_count: z.record(z.string(), z.number().int().nonnegative()),
|
|
104
|
+
backward_routing_count_by_target_phase: z.record(z.string(), z.number().int().nonnegative()),
|
|
105
|
+
in_flight_backward_edge: inFlightEdgeSchema.optional(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const cycleCheckShape = {
|
|
109
|
+
state: counterStateSchema,
|
|
110
|
+
input: z.object({
|
|
111
|
+
decision_id: z.string().min(1),
|
|
112
|
+
target_phase: z.string().min(1),
|
|
113
|
+
}),
|
|
114
|
+
max_cycles: z.number().int().positive().optional(),
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const clearEdgeShape = {
|
|
118
|
+
state: counterStateSchema,
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const staleEdgeShape = {
|
|
122
|
+
state: counterStateSchema,
|
|
123
|
+
threshold_ms: z.number().int().positive().optional(),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const rejectedAlternativeSchema = z.object({
|
|
127
|
+
approach: z.string().min(1),
|
|
128
|
+
reason: z.string().min(1),
|
|
129
|
+
revisit_criterion: z.string().min(1),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const scribeShape = {
|
|
133
|
+
phase: z.string().min(1),
|
|
134
|
+
summary: z.string().min(1),
|
|
135
|
+
decided_by: z.string().min(1),
|
|
136
|
+
impact_level: z.enum(["low", "medium", "high", "critical"]),
|
|
137
|
+
chosen_approach: z.string().min(1),
|
|
138
|
+
rejected_alternatives: z.array(rejectedAlternativeSchema).max(3).optional(),
|
|
139
|
+
ref: z
|
|
140
|
+
.string()
|
|
141
|
+
.min(1)
|
|
142
|
+
.regex(/^[a-zA-Z0-9_\-./]+\.(md|json|jsonl|yaml|yml)(#[a-zA-Z0-9_\-/.]+)?$/),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Server registration
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
const BUILD_STATE_PATH_REL = "docs/plans/.build-state.json";
|
|
150
|
+
const DECISIONS_PATH_REL = "docs/plans/decisions.jsonl";
|
|
151
|
+
|
|
152
|
+
function registerTools(server: McpServer, decisionsPath: string): void {
|
|
153
|
+
// --- state_save toolset ------------------------------------------------
|
|
154
|
+
|
|
155
|
+
server.registerTool(
|
|
156
|
+
"state_save",
|
|
157
|
+
{
|
|
158
|
+
description:
|
|
159
|
+
"Atomically persist state to a JSON file via write-to-.tmp + fsync + rename. Returns {success, path, sha256, bytesWritten}. Single writer for .build-state.json.",
|
|
160
|
+
inputSchema: stateSaveShape,
|
|
161
|
+
},
|
|
162
|
+
async (args) => {
|
|
163
|
+
try {
|
|
164
|
+
const result = stateSave(args.path, args.state as Record<string, unknown>);
|
|
165
|
+
return okResult(result);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
return errResult("state_save", err);
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
server.registerTool(
|
|
173
|
+
"state_read",
|
|
174
|
+
{
|
|
175
|
+
description:
|
|
176
|
+
"Read state from a JSON file and compute its SHA-256 checksum. Returns {state, sha256}.",
|
|
177
|
+
inputSchema: stateReadShape,
|
|
178
|
+
},
|
|
179
|
+
async (args) => {
|
|
180
|
+
try {
|
|
181
|
+
return okResult(stateRead(args.path));
|
|
182
|
+
} catch (err) {
|
|
183
|
+
return errResult("state_read", err);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
server.registerTool(
|
|
189
|
+
"verify_integrity",
|
|
190
|
+
{
|
|
191
|
+
description:
|
|
192
|
+
"Verify a state file's SHA-256 matches the expected checksum. Returns {matches:boolean}.",
|
|
193
|
+
inputSchema: verifyIntegrityShape,
|
|
194
|
+
},
|
|
195
|
+
async (args) => {
|
|
196
|
+
try {
|
|
197
|
+
const matches = verifyIntegrity(args.path, args.expected_sha256);
|
|
198
|
+
return okResult({ matches });
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return errResult("verify_integrity", err);
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// --- write_lease toolset ----------------------------------------------
|
|
206
|
+
|
|
207
|
+
server.registerTool(
|
|
208
|
+
"acquire_write_lease",
|
|
209
|
+
{
|
|
210
|
+
description:
|
|
211
|
+
"Acquire an exclusive write lease on one or more file paths for a task. Returns granted:true with the lease, or granted:false with conflict details when another task holds an overlapping lease.",
|
|
212
|
+
inputSchema: acquireLeaseShape,
|
|
213
|
+
},
|
|
214
|
+
async (args) => {
|
|
215
|
+
try {
|
|
216
|
+
const result = await acquireWriteLease(args.task_id, args.file_paths);
|
|
217
|
+
return okResult(result);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
return errResult("acquire_write_lease", err);
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
server.registerTool(
|
|
225
|
+
"release_write_lease",
|
|
226
|
+
{
|
|
227
|
+
description:
|
|
228
|
+
"Release all write leases held by a task. Called by SubagentStop hook on dispatch return. Returns released:true if a lease existed, false otherwise.",
|
|
229
|
+
inputSchema: releaseLeaseShape,
|
|
230
|
+
},
|
|
231
|
+
async (args) => {
|
|
232
|
+
try {
|
|
233
|
+
const released = releaseLease(args.task_id);
|
|
234
|
+
return okResult({ released });
|
|
235
|
+
} catch (err) {
|
|
236
|
+
return errResult("release_write_lease", err);
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
server.registerTool(
|
|
242
|
+
"list_write_leases",
|
|
243
|
+
{
|
|
244
|
+
description:
|
|
245
|
+
"List all active write leases (holder, paths, acquired_at). Read-only; intended for orchestrator diagnostics and .build-state.json persistence snapshots.",
|
|
246
|
+
inputSchema: listLeasesShape,
|
|
247
|
+
},
|
|
248
|
+
async () => {
|
|
249
|
+
try {
|
|
250
|
+
const leases = getActiveLeases();
|
|
251
|
+
return okResult({ leases });
|
|
252
|
+
} catch (err) {
|
|
253
|
+
return errResult("list_write_leases", err);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// --- cycle_counter toolset --------------------------------------------
|
|
259
|
+
|
|
260
|
+
server.registerTool(
|
|
261
|
+
"cycle_counter_check",
|
|
262
|
+
{
|
|
263
|
+
description:
|
|
264
|
+
"Check and increment backward-routing counters (per-decision + per-target-phase). Returns {action: allow|escalate_to_user, decision_count, phase_count, state} where state is the mutated CounterState to persist via state_save.",
|
|
265
|
+
inputSchema: cycleCheckShape,
|
|
266
|
+
},
|
|
267
|
+
async (args) => {
|
|
268
|
+
try {
|
|
269
|
+
const state = args.state as CounterState;
|
|
270
|
+
const result = cycleCounterCheck(state, args.input, args.max_cycles);
|
|
271
|
+
return okResult({ ...result, state });
|
|
272
|
+
} catch (err) {
|
|
273
|
+
return errResult("cycle_counter_check", err);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
server.registerTool(
|
|
279
|
+
"clear_in_flight_edge",
|
|
280
|
+
{
|
|
281
|
+
description:
|
|
282
|
+
"Clear the in_flight_backward_edge field (called by target phase on re-entry). Returns {state} for the caller to persist via state_save.",
|
|
283
|
+
inputSchema: clearEdgeShape,
|
|
284
|
+
},
|
|
285
|
+
async (args) => {
|
|
286
|
+
try {
|
|
287
|
+
const state = args.state as CounterState;
|
|
288
|
+
clearInFlightEdge(state);
|
|
289
|
+
return okResult({ state });
|
|
290
|
+
} catch (err) {
|
|
291
|
+
return errResult("clear_in_flight_edge", err);
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
server.registerTool(
|
|
297
|
+
"handle_stale_edge",
|
|
298
|
+
{
|
|
299
|
+
description:
|
|
300
|
+
"On --resume, if in_flight_backward_edge is older than threshold_ms (default 60000), decrement both counters and clear the edge. Returns {cleaned:boolean, state}.",
|
|
301
|
+
inputSchema: staleEdgeShape,
|
|
302
|
+
},
|
|
303
|
+
async (args) => {
|
|
304
|
+
try {
|
|
305
|
+
const state = args.state as CounterState;
|
|
306
|
+
const cleaned = handleStaleEdge(state, args.threshold_ms);
|
|
307
|
+
return okResult({ cleaned, state });
|
|
308
|
+
} catch (err) {
|
|
309
|
+
return errResult("handle_stale_edge", err);
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
// --- scribe toolset ----------------------------------------------------
|
|
315
|
+
|
|
316
|
+
server.registerTool(
|
|
317
|
+
"scribe_decision",
|
|
318
|
+
{
|
|
319
|
+
description:
|
|
320
|
+
"Append a decision row to docs/plans/decisions.jsonl. Single-writer per migration plan; validates against decisions.schema.json.",
|
|
321
|
+
inputSchema: scribeShape,
|
|
322
|
+
},
|
|
323
|
+
async (args) => {
|
|
324
|
+
try {
|
|
325
|
+
const row = scribeDecision(args, decisionsPath);
|
|
326
|
+
return okResult(row);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
return errResult("scribe_decision", err);
|
|
329
|
+
}
|
|
330
|
+
},
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function main(): Promise<void> {
|
|
335
|
+
const cwd = process.cwd();
|
|
336
|
+
const buildStatePath = resolve(cwd, BUILD_STATE_PATH_REL);
|
|
337
|
+
const decisionsPath = resolve(cwd, DECISIONS_PATH_REL);
|
|
338
|
+
|
|
339
|
+
// Hydrate in-memory stores from disk so they match persisted state across
|
|
340
|
+
// restarts. Mirrors bin/buildanything-runtime.ts:235 (write-lease) and
|
|
341
|
+
// bin/adapters/scribe-tool.ts:40 (scribe counters).
|
|
342
|
+
initWriteLease(buildStatePath);
|
|
343
|
+
loadCounters(decisionsPath);
|
|
344
|
+
|
|
345
|
+
const server = new McpServer(
|
|
346
|
+
{ name: "buildanything-orchestrator", version: "0.1.0" },
|
|
347
|
+
{ capabilities: { tools: {} } },
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
registerTools(server, decisionsPath);
|
|
351
|
+
|
|
352
|
+
const transport = new StdioServerTransport();
|
|
353
|
+
await server.connect(transport);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
main().catch((err) => {
|
|
357
|
+
process.stderr.write(
|
|
358
|
+
`[orchestrator-mcp] fatal: ${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`,
|
|
359
|
+
);
|
|
360
|
+
process.exit(1);
|
|
361
|
+
});
|