@hilbras/omninode 2.0.0-alpha.10 → 2.0.0-alpha.12
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 +15 -2
- package/dist/cli/index.js +338 -87
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +197 -19
- package/dist/index.js +585 -349
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -166,7 +166,9 @@ var pipelineStepConfigSchema = z.object({
|
|
|
166
166
|
model: z.string().optional(),
|
|
167
167
|
depends_on: z.array(z.string()).optional(),
|
|
168
168
|
retries: z.number().int().min(0).max(10).optional(),
|
|
169
|
-
condition: z.enum(["always", "on-success", "on-failure"]).optional()
|
|
169
|
+
condition: z.enum(["always", "on-success", "on-failure"]).optional(),
|
|
170
|
+
/** Constraints passed to the planner for plan steps (§15). */
|
|
171
|
+
constraints: z.array(z.string()).optional()
|
|
170
172
|
}).strict();
|
|
171
173
|
var pipelineConfigSchema = z.object({
|
|
172
174
|
id: z.string().min(1).regex(/^[a-z0-9][a-z0-9-]*$/, "pipeline id must be kebab-case"),
|
|
@@ -296,7 +298,8 @@ function toPipelineDefinition(raw) {
|
|
|
296
298
|
model: step.model,
|
|
297
299
|
dependsOn: step.depends_on,
|
|
298
300
|
retries: step.retries,
|
|
299
|
-
condition: step.condition
|
|
301
|
+
condition: step.condition,
|
|
302
|
+
constraints: step.constraints
|
|
300
303
|
}))
|
|
301
304
|
};
|
|
302
305
|
}
|
|
@@ -1817,51 +1820,119 @@ function buildRoleRegistry(config) {
|
|
|
1817
1820
|
}
|
|
1818
1821
|
|
|
1819
1822
|
// src/persistence/json-file-store.ts
|
|
1820
|
-
import { mkdir, readFile, rename,
|
|
1823
|
+
import { open, mkdir, readFile, rename, stat } from "fs/promises";
|
|
1821
1824
|
import { dirname } from "path";
|
|
1825
|
+
var SCHEMA_VERSION = 2;
|
|
1826
|
+
async function writeFileAtomic(target, contents) {
|
|
1827
|
+
const tmp = `${target}.tmp`;
|
|
1828
|
+
await mkdir(dirname(target), { recursive: true });
|
|
1829
|
+
const handle = await open(tmp, "w");
|
|
1830
|
+
try {
|
|
1831
|
+
await handle.writeFile(contents, "utf8");
|
|
1832
|
+
await handle.sync();
|
|
1833
|
+
} finally {
|
|
1834
|
+
await handle.close();
|
|
1835
|
+
}
|
|
1836
|
+
await rename(tmp, target);
|
|
1837
|
+
try {
|
|
1838
|
+
const dir = await open(dirname(target), "r");
|
|
1839
|
+
try {
|
|
1840
|
+
await dir.sync();
|
|
1841
|
+
} finally {
|
|
1842
|
+
await dir.close();
|
|
1843
|
+
}
|
|
1844
|
+
} catch {
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
async function inspectDocument(path) {
|
|
1848
|
+
let raw;
|
|
1849
|
+
try {
|
|
1850
|
+
await stat(path);
|
|
1851
|
+
} catch {
|
|
1852
|
+
return { status: "missing" };
|
|
1853
|
+
}
|
|
1854
|
+
try {
|
|
1855
|
+
raw = await readFile(path, "utf8");
|
|
1856
|
+
} catch (error) {
|
|
1857
|
+
return { status: "corrupt", detail: `unreadable: ${error instanceof Error ? error.message : String(error)}` };
|
|
1858
|
+
}
|
|
1859
|
+
if (raw.trim().length === 0) return { status: "empty" };
|
|
1860
|
+
let parsed;
|
|
1861
|
+
try {
|
|
1862
|
+
parsed = JSON.parse(raw);
|
|
1863
|
+
} catch (error) {
|
|
1864
|
+
return { status: "corrupt", detail: error instanceof Error ? error.message : "invalid JSON" };
|
|
1865
|
+
}
|
|
1866
|
+
if (Array.isArray(parsed)) return { status: "v1-legacy" };
|
|
1867
|
+
if (parsed === null || typeof parsed !== "object") return { status: "corrupt", detail: "not an object" };
|
|
1868
|
+
const doc = parsed;
|
|
1869
|
+
if (!Array.isArray(doc.items)) return { status: "corrupt", detail: "missing items array" };
|
|
1870
|
+
const version = typeof doc.schemaVersion === "number" ? doc.schemaVersion : 1;
|
|
1871
|
+
if (version > SCHEMA_VERSION) return { status: "future", schemaVersion: version };
|
|
1872
|
+
if (version < SCHEMA_VERSION) return { status: "v1-legacy" };
|
|
1873
|
+
return { status: "current", schemaVersion: version, count: doc.items.length };
|
|
1874
|
+
}
|
|
1875
|
+
async function readDocument(path, onCorrupt, quarantine = true) {
|
|
1876
|
+
const state = await inspectDocument(path);
|
|
1877
|
+
if (state.status === "missing" || state.status === "empty") return [];
|
|
1878
|
+
if (state.status === "future") {
|
|
1879
|
+
const error = new Error(
|
|
1880
|
+
`${path} was written by a newer OmniNode (schema v${state.schemaVersion} > v${SCHEMA_VERSION})`
|
|
1881
|
+
);
|
|
1882
|
+
onCorrupt?.(path, error);
|
|
1883
|
+
return [];
|
|
1884
|
+
}
|
|
1885
|
+
let raw;
|
|
1886
|
+
try {
|
|
1887
|
+
raw = await readFile(path, "utf8");
|
|
1888
|
+
} catch (error) {
|
|
1889
|
+
onCorrupt?.(path, error);
|
|
1890
|
+
return [];
|
|
1891
|
+
}
|
|
1892
|
+
try {
|
|
1893
|
+
const parsed = JSON.parse(raw);
|
|
1894
|
+
if (Array.isArray(parsed)) return parsed;
|
|
1895
|
+
return parsed.items ?? [];
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
if (quarantine) {
|
|
1898
|
+
const quarantinePath = `${path}.corrupt-${Date.now()}`;
|
|
1899
|
+
try {
|
|
1900
|
+
await rename(path, quarantinePath);
|
|
1901
|
+
logger.child({ component: "persistence" }).warn(
|
|
1902
|
+
`Quarantined unreadable store file ${path} \u2192 ${quarantinePath}`
|
|
1903
|
+
);
|
|
1904
|
+
} catch {
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
onCorrupt?.(path, error);
|
|
1908
|
+
return [];
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1822
1911
|
var JsonFileStore = class {
|
|
1823
1912
|
filePath;
|
|
1824
1913
|
schemaVersion;
|
|
1825
1914
|
onCorrupt;
|
|
1915
|
+
quarantine;
|
|
1826
1916
|
log;
|
|
1827
1917
|
queue = Promise.resolve();
|
|
1828
1918
|
constructor(options) {
|
|
1829
1919
|
this.filePath = `${options.directory ?? `${process.cwd()}/.omninode`}/${options.fileName}`;
|
|
1830
|
-
this.schemaVersion = options.schemaVersion ??
|
|
1920
|
+
this.schemaVersion = options.schemaVersion ?? SCHEMA_VERSION;
|
|
1831
1921
|
this.onCorrupt = options.onCorrupt;
|
|
1922
|
+
this.quarantine = options.quarantine ?? true;
|
|
1832
1923
|
this.log = logger.child({ component: "persistence", file: options.fileName });
|
|
1833
1924
|
}
|
|
1834
1925
|
get path() {
|
|
1835
1926
|
return this.filePath;
|
|
1836
1927
|
}
|
|
1837
|
-
/** All items, or [] when missing/corrupt. */
|
|
1928
|
+
/** All items, or [] when missing/migrated-from-v1/corrupt. */
|
|
1838
1929
|
async readAll() {
|
|
1839
|
-
|
|
1840
|
-
try {
|
|
1841
|
-
raw = await readFile(this.filePath, "utf8");
|
|
1842
|
-
} catch {
|
|
1843
|
-
return [];
|
|
1844
|
-
}
|
|
1845
|
-
try {
|
|
1846
|
-
const parsed = JSON.parse(raw);
|
|
1847
|
-
if (Array.isArray(parsed)) return parsed;
|
|
1848
|
-
if (parsed !== null && typeof parsed === "object" && Array.isArray(parsed.items)) {
|
|
1849
|
-
return parsed.items;
|
|
1850
|
-
}
|
|
1851
|
-
throw new Error("persisted file has an unrecognized shape");
|
|
1852
|
-
} catch (error) {
|
|
1853
|
-
this.log.warn(`Treating ${this.filePath} as empty \u2014 it could not be parsed.`);
|
|
1854
|
-
this.onCorrupt?.(this.filePath, error);
|
|
1855
|
-
return [];
|
|
1856
|
-
}
|
|
1930
|
+
return readDocument(this.filePath, this.onCorrupt, this.quarantine);
|
|
1857
1931
|
}
|
|
1858
1932
|
async writeAll(items) {
|
|
1859
1933
|
const document = { schemaVersion: this.schemaVersion, items };
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
await writeFile(tmp, `${JSON.stringify(document, null, 2)}
|
|
1863
|
-
`, "utf8");
|
|
1864
|
-
await rename(tmp, this.filePath);
|
|
1934
|
+
await writeFileAtomic(this.filePath, `${JSON.stringify(document, null, 2)}
|
|
1935
|
+
`);
|
|
1865
1936
|
}
|
|
1866
1937
|
/** Serializes an async operation against this file. */
|
|
1867
1938
|
synchronized(operation) {
|
|
@@ -2164,7 +2235,7 @@ function sleep(ms) {
|
|
|
2164
2235
|
}
|
|
2165
2236
|
|
|
2166
2237
|
// src/audit/index.ts
|
|
2167
|
-
import {
|
|
2238
|
+
import { mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
2168
2239
|
import { dirname as dirname2 } from "path";
|
|
2169
2240
|
var FileAuditLog = class {
|
|
2170
2241
|
filePath;
|
|
@@ -2176,8 +2247,15 @@ var FileAuditLog = class {
|
|
|
2176
2247
|
async record(event) {
|
|
2177
2248
|
try {
|
|
2178
2249
|
await mkdir2(dirname2(this.filePath), { recursive: true });
|
|
2179
|
-
await
|
|
2250
|
+
const { open: open2 } = await import("fs/promises");
|
|
2251
|
+
const handle = await open2(this.filePath, "a");
|
|
2252
|
+
try {
|
|
2253
|
+
await handle.appendFile(`${JSON.stringify(event)}
|
|
2180
2254
|
`, "utf8");
|
|
2255
|
+
await handle.sync();
|
|
2256
|
+
} finally {
|
|
2257
|
+
await handle.close();
|
|
2258
|
+
}
|
|
2181
2259
|
} catch (error) {
|
|
2182
2260
|
this.log.warn(
|
|
2183
2261
|
`Audit write failed: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -2186,7 +2264,6 @@ var FileAuditLog = class {
|
|
|
2186
2264
|
}
|
|
2187
2265
|
/** Recent events, oldest first; used by the CLI. */
|
|
2188
2266
|
async recent(limit = 20) {
|
|
2189
|
-
const { readFile: readFile2 } = await import("fs/promises");
|
|
2190
2267
|
let raw;
|
|
2191
2268
|
try {
|
|
2192
2269
|
raw = await readFile2(this.filePath, "utf8");
|
|
@@ -2580,10 +2657,98 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
2580
2657
|
}
|
|
2581
2658
|
|
|
2582
2659
|
// src/pipelines/engine.ts
|
|
2583
|
-
import { randomBytes as
|
|
2660
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
2584
2661
|
|
|
2585
|
-
// src/
|
|
2662
|
+
// src/reports/aggregate.ts
|
|
2586
2663
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2664
|
+
|
|
2665
|
+
// src/reports/schema.ts
|
|
2666
|
+
import { z as z2 } from "zod";
|
|
2667
|
+
var evidenceSchema = z2.union([
|
|
2668
|
+
z2.string().transform((excerpt) => ({ kind: "observation", excerpt })),
|
|
2669
|
+
z2.object({
|
|
2670
|
+
kind: z2.enum(["file", "line", "url", "command", "observation", "artifact"]),
|
|
2671
|
+
ref: z2.string().optional(),
|
|
2672
|
+
line: z2.number().int().optional(),
|
|
2673
|
+
excerpt: z2.string().optional(),
|
|
2674
|
+
agent: z2.string().optional()
|
|
2675
|
+
})
|
|
2676
|
+
]);
|
|
2677
|
+
var findingSchema = z2.object({
|
|
2678
|
+
id: z2.string().min(1),
|
|
2679
|
+
title: z2.string().min(1),
|
|
2680
|
+
detail: z2.string().default(""),
|
|
2681
|
+
description: z2.string().optional(),
|
|
2682
|
+
severity: z2.enum(["info", "low", "medium", "high", "critical"]).optional(),
|
|
2683
|
+
confidence: z2.enum(["low", "medium", "high"]).optional(),
|
|
2684
|
+
severityBySource: z2.record(z2.enum(["info", "low", "medium", "high", "critical"])).optional(),
|
|
2685
|
+
confidenceBySource: z2.record(z2.enum(["low", "medium", "high"])).optional(),
|
|
2686
|
+
recommendations: z2.array(z2.string()).optional(),
|
|
2687
|
+
evidence: z2.array(evidenceSchema).optional(),
|
|
2688
|
+
location: z2.string().optional(),
|
|
2689
|
+
tags: z2.array(z2.string()).optional(),
|
|
2690
|
+
source: z2.object({ agent: z2.string().optional(), reportId: z2.string().optional(), pipelineRunId: z2.string().optional() }).optional(),
|
|
2691
|
+
recommendation: z2.string().optional()
|
|
2692
|
+
});
|
|
2693
|
+
var artifactSchema = z2.object({
|
|
2694
|
+
kind: z2.enum(["file", "patch", "log", "screenshot", "document", "data"]),
|
|
2695
|
+
ref: z2.string().min(1),
|
|
2696
|
+
description: z2.string().optional(),
|
|
2697
|
+
mediaType: z2.string().optional(),
|
|
2698
|
+
bytes: z2.number().int().optional()
|
|
2699
|
+
});
|
|
2700
|
+
var reportSchema = z2.object({
|
|
2701
|
+
id: z2.string().min(1),
|
|
2702
|
+
taskId: z2.string().min(1),
|
|
2703
|
+
agent: z2.string().min(1),
|
|
2704
|
+
agentId: z2.string().optional(),
|
|
2705
|
+
pipelineRunId: z2.string().optional(),
|
|
2706
|
+
model: z2.string().optional(),
|
|
2707
|
+
summary: z2.string(),
|
|
2708
|
+
findings: z2.array(findingSchema),
|
|
2709
|
+
recommendations: z2.array(z2.string()),
|
|
2710
|
+
confidence: z2.enum(["low", "medium", "high"]).optional(),
|
|
2711
|
+
evidence: z2.array(evidenceSchema).optional(),
|
|
2712
|
+
artifacts: z2.array(artifactSchema).optional(),
|
|
2713
|
+
createdAt: z2.string().min(1),
|
|
2714
|
+
metadata: z2.record(z2.unknown()).optional()
|
|
2715
|
+
});
|
|
2716
|
+
function validateReport(value) {
|
|
2717
|
+
const parsed = reportSchema.safeParse(value);
|
|
2718
|
+
if (!parsed.success) {
|
|
2719
|
+
const issues = parsed.error.issues.map((issue) => ({
|
|
2720
|
+
path: issue.path.join(".") || "(root)",
|
|
2721
|
+
message: issue.message
|
|
2722
|
+
}));
|
|
2723
|
+
return {
|
|
2724
|
+
valid: false,
|
|
2725
|
+
issues,
|
|
2726
|
+
reason: `report failed schema validation (${issues.length} issue(s))`
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
const report = parsed.data;
|
|
2730
|
+
report.findings = report.findings.map((finding) => ({
|
|
2731
|
+
...finding,
|
|
2732
|
+
description: finding.description ?? finding.detail,
|
|
2733
|
+
...finding.source === void 0 ? { source: { agent: report.agent, reportId: report.id, ...report.pipelineRunId !== void 0 ? { pipelineRunId: report.pipelineRunId } : {} } } : {}
|
|
2734
|
+
}));
|
|
2735
|
+
return { valid: true, report };
|
|
2736
|
+
}
|
|
2737
|
+
function normalizeEvidence(input) {
|
|
2738
|
+
if (input === void 0) return void 0;
|
|
2739
|
+
return input.map(
|
|
2740
|
+
(item) => typeof item === "string" ? { kind: "observation", excerpt: item } : item
|
|
2741
|
+
);
|
|
2742
|
+
}
|
|
2743
|
+
function normalizeFinding(finding) {
|
|
2744
|
+
return {
|
|
2745
|
+
...finding,
|
|
2746
|
+
description: finding.description ?? finding.detail,
|
|
2747
|
+
evidence: normalizeEvidence(finding.evidence)
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
|
|
2751
|
+
// src/reports/aggregate.ts
|
|
2587
2752
|
var SEVERITY_RANK = {
|
|
2588
2753
|
info: 0,
|
|
2589
2754
|
low: 1,
|
|
@@ -2591,23 +2756,197 @@ var SEVERITY_RANK = {
|
|
|
2591
2756
|
high: 3,
|
|
2592
2757
|
critical: 4
|
|
2593
2758
|
};
|
|
2759
|
+
function aggregateReports(reports, options = {}) {
|
|
2760
|
+
const threshold = options.similarityThreshold ?? 0.7;
|
|
2761
|
+
const findingGroups = [];
|
|
2762
|
+
const recommendationGroups = [];
|
|
2763
|
+
for (const report of reports) {
|
|
2764
|
+
for (const finding of report.findings) {
|
|
2765
|
+
mergeFinding(findingGroups, finding, report, threshold);
|
|
2766
|
+
}
|
|
2767
|
+
for (const text of report.recommendations) {
|
|
2768
|
+
mergeRecommendation(recommendationGroups, text, report, threshold);
|
|
2769
|
+
}
|
|
2770
|
+
}
|
|
2771
|
+
const sources = [...new Set(reports.map((r) => r.agent))].sort();
|
|
2772
|
+
const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
2773
|
+
const conflicts = detectConflicts(findingGroups);
|
|
2774
|
+
const agreements = findingGroups.filter(
|
|
2775
|
+
(group) => group.sources.length > 1 && group.severity !== void 0 && new Set(Object.values(group.severityBySource ?? {})).size === 1
|
|
2776
|
+
);
|
|
2777
|
+
const disputed = conflicts.length;
|
|
2778
|
+
const summary = `${reports.length} report(s) from ${sources.length > 0 ? sources.join(", ") : "no agents"}: ${findingGroups.length} unique finding group(s) (${totalRawFindings} raw), ${agreements.length} agreement(s), ${disputed} conflict(s), ${recommendationGroups.length} recommendation group(s).`;
|
|
2779
|
+
return {
|
|
2780
|
+
id: `combined-${Date.now().toString(36)}-${randomBytes2(3).toString("hex")}`,
|
|
2781
|
+
title: options.objective !== void 0 ? truncate(options.objective, 120) : "Combined report",
|
|
2782
|
+
...options.objective !== void 0 ? { objective: options.objective } : {},
|
|
2783
|
+
...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
|
|
2784
|
+
taskIds: options.taskIds ?? [],
|
|
2785
|
+
summary,
|
|
2786
|
+
findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
|
|
2787
|
+
agreements,
|
|
2788
|
+
conflicts,
|
|
2789
|
+
recommendations: recommendationGroups,
|
|
2790
|
+
sources,
|
|
2791
|
+
reportIds: reports.map((r) => r.id),
|
|
2792
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2793
|
+
metadata: {
|
|
2794
|
+
agents: sources.length,
|
|
2795
|
+
reports: reports.length,
|
|
2796
|
+
rawFindings: totalRawFindings,
|
|
2797
|
+
findingGroups: findingGroups.length,
|
|
2798
|
+
agreements: agreements.length,
|
|
2799
|
+
conflicts: conflicts.length
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
}
|
|
2803
|
+
function rankConfidence(confidence) {
|
|
2804
|
+
return confidence ? { low: 1, medium: 2, high: 3 }[confidence] : 0;
|
|
2805
|
+
}
|
|
2806
|
+
function detectConflicts(groups) {
|
|
2807
|
+
const conflicts = [];
|
|
2808
|
+
for (const group of groups) {
|
|
2809
|
+
if (group.sources.length < 2) continue;
|
|
2810
|
+
const severities = new Set(Object.values(group.severityBySource ?? {}));
|
|
2811
|
+
if (severities.size > 1) {
|
|
2812
|
+
conflicts.push({
|
|
2813
|
+
type: "severity",
|
|
2814
|
+
findingTitle: group.title,
|
|
2815
|
+
positions: group.sources.map((source) => position(source, group)),
|
|
2816
|
+
note: `Agents disagree on severity (${[...severities].sort((a, b) => SEVERITY_RANK[a] - SEVERITY_RANK[b]).join(", ")}); the highest severity is shown, every position is preserved.`
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
const recommendations = (group.recommendations ?? []).filter((r) => r.length > 0);
|
|
2820
|
+
if (recommendations.length > 1) {
|
|
2821
|
+
conflicts.push({
|
|
2822
|
+
type: "recommendation",
|
|
2823
|
+
findingTitle: group.title,
|
|
2824
|
+
positions: group.sources.map((source) => position(source, group)),
|
|
2825
|
+
note: `Agents proposed ${recommendations.length} different fixes; none was discarded.`
|
|
2826
|
+
});
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
return conflicts;
|
|
2830
|
+
}
|
|
2831
|
+
function position(source, group) {
|
|
2832
|
+
return {
|
|
2833
|
+
agent: source.agent,
|
|
2834
|
+
reportId: source.reportId,
|
|
2835
|
+
...group.severityBySource?.[source.agent] !== void 0 ? { severity: group.severityBySource[source.agent] } : {},
|
|
2836
|
+
...group.confidenceBySource?.[source.agent] !== void 0 ? { confidence: group.confidenceBySource[source.agent] } : {},
|
|
2837
|
+
...group.recommendations?.[0] !== void 0 ? { recommendation: group.recommendations[0] } : {}
|
|
2838
|
+
};
|
|
2839
|
+
}
|
|
2840
|
+
function rank(finding) {
|
|
2841
|
+
return finding.severity ? SEVERITY_RANK[finding.severity] : -1;
|
|
2842
|
+
}
|
|
2843
|
+
function mergeFinding(groups, finding, report, threshold) {
|
|
2844
|
+
const source = { agent: report.agent, reportId: report.id };
|
|
2845
|
+
const group = groups.find((g) => similar(g.title, finding.title, threshold));
|
|
2846
|
+
if (group) {
|
|
2847
|
+
group.occurrences += 1;
|
|
2848
|
+
group.details.push(finding.detail);
|
|
2849
|
+
group.sources.push(source);
|
|
2850
|
+
group.evidence.push(...normalizeEvidence(finding.evidence) ?? []);
|
|
2851
|
+
if (finding.severity && (!group.severity || SEVERITY_RANK[finding.severity] > SEVERITY_RANK[group.severity])) {
|
|
2852
|
+
group.severity = finding.severity;
|
|
2853
|
+
}
|
|
2854
|
+
if (finding.confidence && rankConfidence(finding.confidence) > rankConfidence(group.confidence)) {
|
|
2855
|
+
group.confidence = finding.confidence;
|
|
2856
|
+
}
|
|
2857
|
+
if (finding.severity) {
|
|
2858
|
+
group.severityBySource = { ...group.severityBySource, [report.agent]: finding.severity };
|
|
2859
|
+
}
|
|
2860
|
+
if (finding.confidence) {
|
|
2861
|
+
group.confidenceBySource = { ...group.confidenceBySource, [report.agent]: finding.confidence };
|
|
2862
|
+
}
|
|
2863
|
+
if (finding.recommendation && !group.recommendations?.includes(finding.recommendation)) {
|
|
2864
|
+
group.recommendations = [...group.recommendations ?? [], finding.recommendation];
|
|
2865
|
+
}
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
groups.push({
|
|
2869
|
+
title: finding.title,
|
|
2870
|
+
details: [finding.detail],
|
|
2871
|
+
sources: [source],
|
|
2872
|
+
occurrences: 1,
|
|
2873
|
+
...finding.severity ? { severity: finding.severity } : {},
|
|
2874
|
+
...finding.confidence ? { confidence: finding.confidence } : {},
|
|
2875
|
+
...finding.severity ? { severityBySource: { [report.agent]: finding.severity } } : {},
|
|
2876
|
+
...finding.confidence ? { confidenceBySource: { [report.agent]: finding.confidence } } : {},
|
|
2877
|
+
...finding.recommendation ? { recommendations: [finding.recommendation] } : {},
|
|
2878
|
+
evidence: [...normalizeEvidence(finding.evidence) ?? []]
|
|
2879
|
+
});
|
|
2880
|
+
}
|
|
2881
|
+
function mergeRecommendation(groups, text, report, threshold) {
|
|
2882
|
+
const source = { agent: report.agent, reportId: report.id };
|
|
2883
|
+
const group = groups.find((g) => similar(g.text, text, threshold));
|
|
2884
|
+
if (group) {
|
|
2885
|
+
group.occurrences += 1;
|
|
2886
|
+
group.sources.push(source);
|
|
2887
|
+
return;
|
|
2888
|
+
}
|
|
2889
|
+
groups.push({ text, sources: [source], occurrences: 1 });
|
|
2890
|
+
}
|
|
2891
|
+
function similar(a, b, threshold) {
|
|
2892
|
+
const left = tokens(a);
|
|
2893
|
+
const right = tokens(b);
|
|
2894
|
+
if (left.size === 0 || right.size === 0) return false;
|
|
2895
|
+
let intersection = 0;
|
|
2896
|
+
for (const token of left) {
|
|
2897
|
+
if (right.has(token)) intersection += 1;
|
|
2898
|
+
}
|
|
2899
|
+
const union = left.size + right.size - intersection;
|
|
2900
|
+
return intersection / union >= threshold;
|
|
2901
|
+
}
|
|
2902
|
+
function tokens(text) {
|
|
2903
|
+
return new Set(
|
|
2904
|
+
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3)
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2907
|
+
function truncate(text, max) {
|
|
2908
|
+
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
// src/planner/heuristic.ts
|
|
2912
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
2913
|
+
var SEVERITY_RANK2 = {
|
|
2914
|
+
info: 0,
|
|
2915
|
+
low: 1,
|
|
2916
|
+
medium: 2,
|
|
2917
|
+
high: 3,
|
|
2918
|
+
critical: 4
|
|
2919
|
+
};
|
|
2594
2920
|
var HeuristicPlanner = class {
|
|
2595
2921
|
name = "heuristic";
|
|
2596
2922
|
async plan(request) {
|
|
2597
2923
|
const steps = [];
|
|
2598
|
-
const findings = request.reports.flatMap(
|
|
2599
|
-
(report) => report.findings.map((finding) => ({ finding, agent: report.agent }))
|
|
2600
|
-
);
|
|
2601
2924
|
const recommendations = dedupe(request.reports.flatMap((r) => r.recommendations));
|
|
2602
|
-
for (const
|
|
2925
|
+
for (const text of recommendations) {
|
|
2603
2926
|
steps.push({
|
|
2604
2927
|
id: `step-${steps.length + 1}`,
|
|
2605
|
-
title:
|
|
2928
|
+
title: truncate2(text, 120),
|
|
2606
2929
|
order: steps.length + 1,
|
|
2607
|
-
...
|
|
2930
|
+
...steps.length > 0 ? { dependsOn: [`step-${steps.length}`] } : {}
|
|
2608
2931
|
});
|
|
2609
2932
|
}
|
|
2610
|
-
const
|
|
2933
|
+
const contributed = request.aggregated ? request.aggregated.findings.map((finding) => ({
|
|
2934
|
+
finding: {
|
|
2935
|
+
id: `agg-${slug(finding.title)}`,
|
|
2936
|
+
title: finding.title,
|
|
2937
|
+
detail: finding.details[0] ?? finding.title,
|
|
2938
|
+
...finding.severity ? { severity: finding.severity } : {},
|
|
2939
|
+
...finding.recommendations && finding.recommendations.length > 0 ? { recommendation: finding.recommendations[0] } : {}
|
|
2940
|
+
},
|
|
2941
|
+
agent: finding.sources.map((s) => s.agent).join("+")
|
|
2942
|
+
})) : request.reports.flatMap(
|
|
2943
|
+
(report) => report.findings.map((finding) => ({ finding, agent: report.agent }))
|
|
2944
|
+
);
|
|
2945
|
+
const significant = contributed.filter(
|
|
2946
|
+
({ finding }) => finding.severity ? SEVERITY_RANK2[finding.severity] >= SEVERITY_RANK2.high : false
|
|
2947
|
+
).sort(
|
|
2948
|
+
(a, b) => SEVERITY_RANK2[b.finding.severity ?? "info"] - SEVERITY_RANK2[a.finding.severity ?? "info"]
|
|
2949
|
+
);
|
|
2611
2950
|
for (const { finding } of significant) {
|
|
2612
2951
|
steps.push(remediationStep(finding, steps.length + 1));
|
|
2613
2952
|
}
|
|
@@ -2620,12 +2959,20 @@ var HeuristicPlanner = class {
|
|
|
2620
2959
|
});
|
|
2621
2960
|
}
|
|
2622
2961
|
const risks = significant.filter(({ finding }) => finding.severity === "critical").map(({ finding }) => `Critical finding unaddressed until its step completes: ${finding.title}`);
|
|
2962
|
+
const conflicts = request.aggregated?.conflicts ?? [];
|
|
2963
|
+
if (conflicts.length > 0) {
|
|
2964
|
+
risks.push(
|
|
2965
|
+
`${conflicts.length} unresolved agent disagreement(s): ${conflicts.map((c) => c.findingTitle).join("; ")}`
|
|
2966
|
+
);
|
|
2967
|
+
}
|
|
2623
2968
|
return {
|
|
2624
|
-
id: `plan-${Date.now().toString(36)}-${
|
|
2969
|
+
id: `plan-${Date.now().toString(36)}-${randomBytes3(3).toString("hex")}`,
|
|
2625
2970
|
objective: request.objective,
|
|
2971
|
+
goal: request.aggregated?.title ?? truncate2(request.objective, 120),
|
|
2626
2972
|
summary: `Derived from ${request.reports.length} report(s): ${recommendations.length} recommendation step(s), ${significant.length} remediation step(s).`,
|
|
2627
2973
|
steps,
|
|
2628
2974
|
...risks.length > 0 ? { risks } : {},
|
|
2975
|
+
...request.constraints !== void 0 && request.constraints.length > 0 ? { constraints: request.constraints } : {},
|
|
2629
2976
|
generatedBy: "heuristic",
|
|
2630
2977
|
...request.taskId !== void 0 ? { taskId: request.taskId } : {},
|
|
2631
2978
|
...request.pipelineRunId !== void 0 ? { pipelineRunId: request.pipelineRunId } : {},
|
|
@@ -2636,28 +2983,80 @@ var HeuristicPlanner = class {
|
|
|
2636
2983
|
function remediationStep(finding, order) {
|
|
2637
2984
|
return {
|
|
2638
2985
|
id: `step-${order}`,
|
|
2639
|
-
title: `Fix: ${
|
|
2986
|
+
title: `Fix: ${truncate2(finding.title, 100)}`,
|
|
2640
2987
|
description: finding.detail,
|
|
2641
2988
|
order,
|
|
2642
2989
|
dependsOn: order > 1 ? [`step-${order - 1}`] : [],
|
|
2643
|
-
sourceFindings: [finding.id]
|
|
2990
|
+
sourceFindings: [finding.id],
|
|
2991
|
+
...finding.recommendation ? { recommendation: finding.recommendation } : {}
|
|
2644
2992
|
};
|
|
2645
2993
|
}
|
|
2646
2994
|
function dedupe(texts) {
|
|
2647
2995
|
return [...new Set(texts.map((t) => t.trim()).filter((t) => t.length > 0))];
|
|
2648
2996
|
}
|
|
2649
|
-
function
|
|
2997
|
+
function slug(text) {
|
|
2998
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
2999
|
+
}
|
|
3000
|
+
function truncate2(text, max) {
|
|
2650
3001
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
2651
3002
|
}
|
|
2652
3003
|
|
|
2653
|
-
// src/planner/model.ts
|
|
2654
|
-
import { randomBytes as
|
|
2655
|
-
|
|
2656
|
-
|
|
3004
|
+
// src/planner/model.ts
|
|
3005
|
+
import { randomBytes as randomBytes4 } from "crypto";
|
|
3006
|
+
|
|
3007
|
+
// src/planner/schema.ts
|
|
3008
|
+
import { z as z3 } from "zod";
|
|
3009
|
+
var planStepSchema = z3.object({
|
|
3010
|
+
id: z3.string().min(1).optional(),
|
|
3011
|
+
title: z3.string().min(1),
|
|
3012
|
+
description: z3.string().optional(),
|
|
3013
|
+
order: z3.number().int().optional(),
|
|
3014
|
+
targets: z3.array(z3.string()).optional(),
|
|
3015
|
+
acceptance_criteria: z3.array(z3.string()).optional(),
|
|
3016
|
+
depends_on: z3.array(z3.string()).optional(),
|
|
3017
|
+
source_findings: z3.array(z3.string()).optional()
|
|
3018
|
+
});
|
|
3019
|
+
var planJsonSchema = z3.object({
|
|
3020
|
+
goal: z3.string().min(1),
|
|
3021
|
+
summary: z3.string().min(1),
|
|
3022
|
+
steps: z3.array(planStepSchema).min(1),
|
|
3023
|
+
risks: z3.array(z3.string()).optional(),
|
|
3024
|
+
notes: z3.array(z3.string()).optional()
|
|
3025
|
+
});
|
|
3026
|
+
function validatePlan(plan) {
|
|
3027
|
+
const issues = [];
|
|
3028
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
3029
|
+
for (const [index, step] of plan.steps.entries()) {
|
|
3030
|
+
if (stepIds.has(step.id)) {
|
|
3031
|
+
issues.push({ path: `steps.${index}.id`, message: `duplicate step id "${step.id}"` });
|
|
3032
|
+
}
|
|
3033
|
+
stepIds.add(step.id);
|
|
3034
|
+
if (!step.title || step.title.trim().length === 0) {
|
|
3035
|
+
issues.push({ path: `steps.${index}.title`, message: "title must not be empty" });
|
|
3036
|
+
}
|
|
3037
|
+
for (const dep of step.dependsOn ?? []) {
|
|
3038
|
+
if (!stepIds.has(dep) && !plan.steps.some((s) => s.id === dep)) {
|
|
3039
|
+
issues.push({ path: `steps.${index}.dependsOn`, message: `unknown step dependency "${dep}"` });
|
|
3040
|
+
}
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
if (plan.steps.length === 0) {
|
|
3044
|
+
issues.push({ path: "steps", message: "a plan needs at least one step" });
|
|
3045
|
+
}
|
|
3046
|
+
if (!plan.summary || plan.summary.trim().length === 0) {
|
|
3047
|
+
issues.push({ path: "summary", message: "summary must not be empty" });
|
|
3048
|
+
}
|
|
3049
|
+
return issues.length > 0 ? { valid: false, issues, reason: `plan failed validation (${issues.length} issue(s))` } : { valid: true, plan };
|
|
3050
|
+
}
|
|
3051
|
+
|
|
2657
3052
|
// src/planner/context.ts
|
|
2658
3053
|
function buildPlannerContext(request) {
|
|
2659
|
-
const parts = [`#
|
|
3054
|
+
const parts = [`# Task
|
|
2660
3055
|
${request.objective}`];
|
|
3056
|
+
if (request.projectContext) {
|
|
3057
|
+
parts.push(`# Project context
|
|
3058
|
+
${request.projectContext}`);
|
|
3059
|
+
}
|
|
2661
3060
|
if (request.role) {
|
|
2662
3061
|
const roleLines = [`# Role
|
|
2663
3062
|
${request.role.name} (${request.role.id})`];
|
|
@@ -2677,7 +3076,7 @@ ${request.role.name} (${request.role.id})`];
|
|
|
2677
3076
|
)
|
|
2678
3077
|
);
|
|
2679
3078
|
parts.push(
|
|
2680
|
-
`#
|
|
3079
|
+
`# Agent reports (${request.reports.length} by ${agents})
|
|
2681
3080
|
` + (findingLines.length > 0 ? findingLines.join("\n") : "(no findings reported)")
|
|
2682
3081
|
);
|
|
2683
3082
|
const recommendationLines = request.reports.flatMap(
|
|
@@ -2687,9 +3086,20 @@ ${request.role.name} (${request.role.id})`];
|
|
|
2687
3086
|
parts.push(`# Recommendations
|
|
2688
3087
|
${recommendationLines.join("\n")}`);
|
|
2689
3088
|
}
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
3089
|
+
}
|
|
3090
|
+
if (request.aggregated) {
|
|
3091
|
+
const aggregatedLines = [
|
|
3092
|
+
`- findings: ${request.aggregated.findings.length} (agreements: ${request.aggregated.agreements.length}, conflicts: ${request.aggregated.conflicts.length})`,
|
|
3093
|
+
...request.aggregated.findings.map(
|
|
3094
|
+
(finding) => `- [${finding.severity ?? "info"}] ${finding.title} (x${finding.occurrences})`
|
|
3095
|
+
),
|
|
3096
|
+
// Conflicts stay visible to the planner — it must plan around disagreement.
|
|
3097
|
+
...request.aggregated.conflicts.map(
|
|
3098
|
+
(conflict) => `- CONFLICT (${conflict.type}) on "${conflict.findingTitle}": ${conflict.note}`
|
|
3099
|
+
)
|
|
3100
|
+
];
|
|
3101
|
+
parts.push(`# Aggregated findings
|
|
3102
|
+
${aggregatedLines.join("\n")}`);
|
|
2693
3103
|
}
|
|
2694
3104
|
if (request.memory && request.memory.length > 0) {
|
|
2695
3105
|
parts.push(
|
|
@@ -2697,7 +3107,11 @@ ${summaryLines.join("\n")}`);
|
|
|
2697
3107
|
` + request.memory.map((entry) => `- [${entry.scope}] ${entry.content}`).join("\n")
|
|
2698
3108
|
);
|
|
2699
3109
|
}
|
|
2700
|
-
if (request.
|
|
3110
|
+
if (request.constraints && request.constraints.length > 0) {
|
|
3111
|
+
parts.push(`# Constraints
|
|
3112
|
+
${request.constraints.map((c) => `- ${c}`).join("\n")}`);
|
|
3113
|
+
}
|
|
3114
|
+
if (request.context) {
|
|
2701
3115
|
parts.push(`# Research context
|
|
2702
3116
|
${request.context}`);
|
|
2703
3117
|
}
|
|
@@ -2705,22 +3119,6 @@ ${request.context}`);
|
|
|
2705
3119
|
}
|
|
2706
3120
|
|
|
2707
3121
|
// src/planner/model.ts
|
|
2708
|
-
var planStepJsonSchema = z2.object({
|
|
2709
|
-
id: z2.string().optional(),
|
|
2710
|
-
title: z2.string().min(1),
|
|
2711
|
-
description: z2.string().optional(),
|
|
2712
|
-
order: z2.number().int().optional(),
|
|
2713
|
-
targets: z2.array(z2.string()).optional(),
|
|
2714
|
-
acceptance_criteria: z2.array(z2.string()).optional(),
|
|
2715
|
-
depends_on: z2.array(z2.string()).optional(),
|
|
2716
|
-
source_findings: z2.array(z2.string()).optional()
|
|
2717
|
-
}).strict();
|
|
2718
|
-
var planJsonSchema = z2.object({
|
|
2719
|
-
summary: z2.string().min(1),
|
|
2720
|
-
steps: z2.array(planStepJsonSchema).min(1),
|
|
2721
|
-
risks: z2.array(z2.string()).optional(),
|
|
2722
|
-
notes: z2.array(z2.string()).optional()
|
|
2723
|
-
}).strict();
|
|
2724
3122
|
var ModelPlanner = class {
|
|
2725
3123
|
constructor(options) {
|
|
2726
3124
|
this.options = options;
|
|
@@ -2734,7 +3132,7 @@ var ModelPlanner = class {
|
|
|
2734
3132
|
const messages = [
|
|
2735
3133
|
{
|
|
2736
3134
|
role: "system",
|
|
2737
|
-
content: 'You are the OmniNode planner. Given the objective and the combined research context, produce a final analysis and a prioritized, actionable implementation plan. Respond with JSON ONLY (no prose, no code fences) matching exactly this shape: {"summary": string, "steps": [{"id": string, "title": string, "description": string, "order": number, "targets": string[], "acceptance_criteria": string[], "source_findings": string[]}], "risks": string[], "notes": string[]}. ' + (this.options.instruction ?? "")
|
|
3135
|
+
content: 'You are the OmniNode planner. Given the objective and the combined research context, produce a final analysis and a prioritized, actionable implementation plan. Respond with JSON ONLY (no prose, no code fences) matching exactly this shape: {"goal": string, "summary": string, "steps": [{"id": string, "title": string, "description": string, "order": number, "targets": string[], "acceptance_criteria": string[], "source_findings": string[]}], "risks": string[], "notes": string[]}. ' + (this.options.instruction ?? "")
|
|
2738
3136
|
},
|
|
2739
3137
|
{ role: "user", content: context }
|
|
2740
3138
|
];
|
|
@@ -2745,60 +3143,86 @@ var ModelPlanner = class {
|
|
|
2745
3143
|
return this.degrade(request, `planner model call failed: ${describe(error)}`);
|
|
2746
3144
|
}
|
|
2747
3145
|
const parsed = parsePlanJson(responseText);
|
|
2748
|
-
if (!parsed) {
|
|
2749
|
-
return this.degrade(request,
|
|
3146
|
+
if (!parsed.ok) {
|
|
3147
|
+
return this.degrade(request, `planner model output rejected: ${parsed.reason}`, parsed.issues);
|
|
2750
3148
|
}
|
|
2751
3149
|
const plan = {
|
|
2752
|
-
id: `plan-${Date.now().toString(36)}-${
|
|
3150
|
+
id: `plan-${Date.now().toString(36)}-${randomBytes4(3).toString("hex")}`,
|
|
2753
3151
|
objective: request.objective,
|
|
3152
|
+
goal: parsed.goal,
|
|
2754
3153
|
summary: parsed.summary,
|
|
2755
3154
|
steps: parsed.steps,
|
|
2756
3155
|
...parsed.risks !== void 0 ? { risks: parsed.risks } : {},
|
|
2757
3156
|
...parsed.notes !== void 0 ? { notes: parsed.notes } : {},
|
|
3157
|
+
...request.constraints !== void 0 && request.constraints.length > 0 ? { constraints: request.constraints } : {},
|
|
2758
3158
|
generatedBy: `model:${this.options.model}`,
|
|
2759
3159
|
model: this.options.model,
|
|
2760
3160
|
...request.taskId !== void 0 ? { taskId: request.taskId } : {},
|
|
2761
3161
|
...request.pipelineRunId !== void 0 ? { pipelineRunId: request.pipelineRunId } : {},
|
|
2762
3162
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2763
3163
|
};
|
|
2764
|
-
|
|
3164
|
+
const validation = validatePlan(plan);
|
|
3165
|
+
if (!validation.valid) {
|
|
3166
|
+
return this.degrade(request, `generated plan rejected: ${validation.reason}`, validation.issues);
|
|
3167
|
+
}
|
|
3168
|
+
return validation.plan;
|
|
2765
3169
|
}
|
|
2766
|
-
async degrade(request, reason) {
|
|
3170
|
+
async degrade(request, reason, issues = []) {
|
|
2767
3171
|
if (this.options.fallback) {
|
|
2768
3172
|
this.log.warn(`${reason} \u2014 using the heuristic fallback planner.`);
|
|
2769
3173
|
return this.options.fallback.plan(request);
|
|
2770
3174
|
}
|
|
2771
|
-
throw new PlannerError("
|
|
3175
|
+
throw new PlannerError("PLAN_INVALID", `Planner failed: ${reason}.`, {
|
|
3176
|
+
details: { reason, issues }
|
|
3177
|
+
});
|
|
2772
3178
|
}
|
|
2773
3179
|
};
|
|
2774
3180
|
function parsePlanJson(text) {
|
|
2775
3181
|
const unfenced = text.replace(/```(?:json)?/gi, "");
|
|
2776
3182
|
const start = unfenced.indexOf("{");
|
|
2777
3183
|
const end = unfenced.lastIndexOf("}");
|
|
2778
|
-
if (start < 0 || end <= start)
|
|
3184
|
+
if (start < 0 || end <= start) {
|
|
3185
|
+
return { ok: false, reason: "planner output contained no JSON object", issues: [] };
|
|
3186
|
+
}
|
|
3187
|
+
let json;
|
|
2779
3188
|
try {
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
if (!parsed.success) return void 0;
|
|
2783
|
-
const steps = parsed.data.steps.map((step, index) => ({
|
|
2784
|
-
id: step.id ?? `step-${index + 1}`,
|
|
2785
|
-
title: step.title,
|
|
2786
|
-
description: step.description,
|
|
2787
|
-
order: step.order ?? index + 1,
|
|
2788
|
-
targets: step.targets,
|
|
2789
|
-
acceptanceCriteria: step.acceptance_criteria,
|
|
2790
|
-
dependsOn: step.depends_on,
|
|
2791
|
-
sourceFindings: step.source_findings
|
|
2792
|
-
}));
|
|
3189
|
+
json = JSON.parse(unfenced.slice(start, end + 1));
|
|
3190
|
+
} catch (error) {
|
|
2793
3191
|
return {
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
3192
|
+
ok: false,
|
|
3193
|
+
reason: `planner output was not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
|
|
3194
|
+
issues: []
|
|
3195
|
+
};
|
|
3196
|
+
}
|
|
3197
|
+
const parsed = planJsonSchema.safeParse(json);
|
|
3198
|
+
if (!parsed.success) {
|
|
3199
|
+
return {
|
|
3200
|
+
ok: false,
|
|
3201
|
+
reason: "planner output did not match the plan schema",
|
|
3202
|
+
issues: parsed.error.issues.map((issue) => ({
|
|
3203
|
+
path: issue.path.join(".") || "(root)",
|
|
3204
|
+
message: issue.message
|
|
3205
|
+
}))
|
|
2798
3206
|
};
|
|
2799
|
-
} catch {
|
|
2800
|
-
return void 0;
|
|
2801
3207
|
}
|
|
3208
|
+
const steps = parsed.data.steps.map((step, index) => ({
|
|
3209
|
+
id: step.id ?? `step-${index + 1}`,
|
|
3210
|
+
title: step.title,
|
|
3211
|
+
description: step.description,
|
|
3212
|
+
order: step.order ?? index + 1,
|
|
3213
|
+
targets: step.targets,
|
|
3214
|
+
acceptanceCriteria: step.acceptance_criteria,
|
|
3215
|
+
dependsOn: step.depends_on,
|
|
3216
|
+
sourceFindings: step.source_findings
|
|
3217
|
+
})).sort((a, b) => a.order - b.order);
|
|
3218
|
+
return {
|
|
3219
|
+
ok: true,
|
|
3220
|
+
goal: parsed.data.goal,
|
|
3221
|
+
summary: parsed.data.summary,
|
|
3222
|
+
steps,
|
|
3223
|
+
risks: parsed.data.risks,
|
|
3224
|
+
notes: parsed.data.notes
|
|
3225
|
+
};
|
|
2802
3226
|
}
|
|
2803
3227
|
function describe(error) {
|
|
2804
3228
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -2947,7 +3371,7 @@ var PipelineEngine = class {
|
|
|
2947
3371
|
}
|
|
2948
3372
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2949
3373
|
const run = {
|
|
2950
|
-
id: `run-${Date.now().toString(36)}-${
|
|
3374
|
+
id: `run-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
|
|
2951
3375
|
pipelineId: def.id,
|
|
2952
3376
|
attempt: 1,
|
|
2953
3377
|
objective,
|
|
@@ -3236,11 +3660,14 @@ ${contribution}`);
|
|
|
3236
3660
|
};
|
|
3237
3661
|
}
|
|
3238
3662
|
async runPlan(step, ctx) {
|
|
3663
|
+
const aggregated = ctx.reports.length > 0 ? aggregateReports(ctx.reports) : void 0;
|
|
3239
3664
|
const request = {
|
|
3240
3665
|
objective: ctx.objective,
|
|
3241
3666
|
reports: ctx.reports,
|
|
3667
|
+
...aggregated !== void 0 ? { aggregated } : {},
|
|
3242
3668
|
...ctx.combinedContext.length > 0 ? { context: ctx.combinedContext } : {},
|
|
3243
3669
|
...ctx.role ? { role: ctx.role } : {},
|
|
3670
|
+
...step.constraints !== void 0 && step.constraints.length > 0 ? { constraints: step.constraints } : {},
|
|
3244
3671
|
...ctx.pipelineRunId !== void 0 ? { pipelineRunId: ctx.pipelineRunId } : {}
|
|
3245
3672
|
};
|
|
3246
3673
|
let planner;
|
|
@@ -3399,255 +3826,6 @@ function buildPlanner(plannerConfig, chat, log = logger) {
|
|
|
3399
3826
|
});
|
|
3400
3827
|
}
|
|
3401
3828
|
|
|
3402
|
-
// src/reports/aggregate.ts
|
|
3403
|
-
import { randomBytes as randomBytes5 } from "crypto";
|
|
3404
|
-
|
|
3405
|
-
// src/reports/schema.ts
|
|
3406
|
-
import { z as z3 } from "zod";
|
|
3407
|
-
var evidenceSchema = z3.union([
|
|
3408
|
-
z3.string().transform((excerpt) => ({ kind: "observation", excerpt })),
|
|
3409
|
-
z3.object({
|
|
3410
|
-
kind: z3.enum(["file", "line", "url", "command", "observation", "artifact"]),
|
|
3411
|
-
ref: z3.string().optional(),
|
|
3412
|
-
line: z3.number().int().optional(),
|
|
3413
|
-
excerpt: z3.string().optional(),
|
|
3414
|
-
agent: z3.string().optional()
|
|
3415
|
-
})
|
|
3416
|
-
]);
|
|
3417
|
-
var findingSchema = z3.object({
|
|
3418
|
-
id: z3.string().min(1),
|
|
3419
|
-
title: z3.string().min(1),
|
|
3420
|
-
detail: z3.string().default(""),
|
|
3421
|
-
description: z3.string().optional(),
|
|
3422
|
-
severity: z3.enum(["info", "low", "medium", "high", "critical"]).optional(),
|
|
3423
|
-
confidence: z3.enum(["low", "medium", "high"]).optional(),
|
|
3424
|
-
severityBySource: z3.record(z3.enum(["info", "low", "medium", "high", "critical"])).optional(),
|
|
3425
|
-
confidenceBySource: z3.record(z3.enum(["low", "medium", "high"])).optional(),
|
|
3426
|
-
recommendations: z3.array(z3.string()).optional(),
|
|
3427
|
-
evidence: z3.array(evidenceSchema).optional(),
|
|
3428
|
-
location: z3.string().optional(),
|
|
3429
|
-
tags: z3.array(z3.string()).optional(),
|
|
3430
|
-
source: z3.object({ agent: z3.string().optional(), reportId: z3.string().optional(), pipelineRunId: z3.string().optional() }).optional(),
|
|
3431
|
-
recommendation: z3.string().optional()
|
|
3432
|
-
});
|
|
3433
|
-
var artifactSchema = z3.object({
|
|
3434
|
-
kind: z3.enum(["file", "patch", "log", "screenshot", "document", "data"]),
|
|
3435
|
-
ref: z3.string().min(1),
|
|
3436
|
-
description: z3.string().optional(),
|
|
3437
|
-
mediaType: z3.string().optional(),
|
|
3438
|
-
bytes: z3.number().int().optional()
|
|
3439
|
-
});
|
|
3440
|
-
var reportSchema = z3.object({
|
|
3441
|
-
id: z3.string().min(1),
|
|
3442
|
-
taskId: z3.string().min(1),
|
|
3443
|
-
agent: z3.string().min(1),
|
|
3444
|
-
agentId: z3.string().optional(),
|
|
3445
|
-
pipelineRunId: z3.string().optional(),
|
|
3446
|
-
model: z3.string().optional(),
|
|
3447
|
-
summary: z3.string(),
|
|
3448
|
-
findings: z3.array(findingSchema),
|
|
3449
|
-
recommendations: z3.array(z3.string()),
|
|
3450
|
-
confidence: z3.enum(["low", "medium", "high"]).optional(),
|
|
3451
|
-
evidence: z3.array(evidenceSchema).optional(),
|
|
3452
|
-
artifacts: z3.array(artifactSchema).optional(),
|
|
3453
|
-
createdAt: z3.string().min(1),
|
|
3454
|
-
metadata: z3.record(z3.unknown()).optional()
|
|
3455
|
-
});
|
|
3456
|
-
function validateReport(value) {
|
|
3457
|
-
const parsed = reportSchema.safeParse(value);
|
|
3458
|
-
if (!parsed.success) {
|
|
3459
|
-
const issues = parsed.error.issues.map((issue) => ({
|
|
3460
|
-
path: issue.path.join(".") || "(root)",
|
|
3461
|
-
message: issue.message
|
|
3462
|
-
}));
|
|
3463
|
-
return {
|
|
3464
|
-
valid: false,
|
|
3465
|
-
issues,
|
|
3466
|
-
reason: `report failed schema validation (${issues.length} issue(s))`
|
|
3467
|
-
};
|
|
3468
|
-
}
|
|
3469
|
-
const report = parsed.data;
|
|
3470
|
-
report.findings = report.findings.map((finding) => ({
|
|
3471
|
-
...finding,
|
|
3472
|
-
description: finding.description ?? finding.detail,
|
|
3473
|
-
...finding.source === void 0 ? { source: { agent: report.agent, reportId: report.id, ...report.pipelineRunId !== void 0 ? { pipelineRunId: report.pipelineRunId } : {} } } : {}
|
|
3474
|
-
}));
|
|
3475
|
-
return { valid: true, report };
|
|
3476
|
-
}
|
|
3477
|
-
function normalizeEvidence(input) {
|
|
3478
|
-
if (input === void 0) return void 0;
|
|
3479
|
-
return input.map(
|
|
3480
|
-
(item) => typeof item === "string" ? { kind: "observation", excerpt: item } : item
|
|
3481
|
-
);
|
|
3482
|
-
}
|
|
3483
|
-
function normalizeFinding(finding) {
|
|
3484
|
-
return {
|
|
3485
|
-
...finding,
|
|
3486
|
-
description: finding.description ?? finding.detail,
|
|
3487
|
-
evidence: normalizeEvidence(finding.evidence)
|
|
3488
|
-
};
|
|
3489
|
-
}
|
|
3490
|
-
|
|
3491
|
-
// src/reports/aggregate.ts
|
|
3492
|
-
var SEVERITY_RANK2 = {
|
|
3493
|
-
info: 0,
|
|
3494
|
-
low: 1,
|
|
3495
|
-
medium: 2,
|
|
3496
|
-
high: 3,
|
|
3497
|
-
critical: 4
|
|
3498
|
-
};
|
|
3499
|
-
function aggregateReports(reports, options = {}) {
|
|
3500
|
-
const threshold = options.similarityThreshold ?? 0.7;
|
|
3501
|
-
const findingGroups = [];
|
|
3502
|
-
const recommendationGroups = [];
|
|
3503
|
-
for (const report of reports) {
|
|
3504
|
-
for (const finding of report.findings) {
|
|
3505
|
-
mergeFinding(findingGroups, finding, report, threshold);
|
|
3506
|
-
}
|
|
3507
|
-
for (const text of report.recommendations) {
|
|
3508
|
-
mergeRecommendation(recommendationGroups, text, report, threshold);
|
|
3509
|
-
}
|
|
3510
|
-
}
|
|
3511
|
-
const sources = [...new Set(reports.map((r) => r.agent))].sort();
|
|
3512
|
-
const totalRawFindings = reports.reduce((sum, r) => sum + r.findings.length, 0);
|
|
3513
|
-
const conflicts = detectConflicts(findingGroups);
|
|
3514
|
-
const agreements = findingGroups.filter(
|
|
3515
|
-
(group) => group.sources.length > 1 && group.severity !== void 0 && new Set(Object.values(group.severityBySource ?? {})).size === 1
|
|
3516
|
-
);
|
|
3517
|
-
const disputed = conflicts.length;
|
|
3518
|
-
const summary = `${reports.length} report(s) from ${sources.length > 0 ? sources.join(", ") : "no agents"}: ${findingGroups.length} unique finding group(s) (${totalRawFindings} raw), ${agreements.length} agreement(s), ${disputed} conflict(s), ${recommendationGroups.length} recommendation group(s).`;
|
|
3519
|
-
return {
|
|
3520
|
-
id: `combined-${Date.now().toString(36)}-${randomBytes5(3).toString("hex")}`,
|
|
3521
|
-
title: options.objective !== void 0 ? truncate2(options.objective, 120) : "Combined report",
|
|
3522
|
-
...options.objective !== void 0 ? { objective: options.objective } : {},
|
|
3523
|
-
...options.pipelineRunId !== void 0 ? { pipelineRunId: options.pipelineRunId } : {},
|
|
3524
|
-
taskIds: options.taskIds ?? [],
|
|
3525
|
-
summary,
|
|
3526
|
-
findings: findingGroups.sort((a, b) => rank(b) - rank(a)),
|
|
3527
|
-
agreements,
|
|
3528
|
-
conflicts,
|
|
3529
|
-
recommendations: recommendationGroups,
|
|
3530
|
-
sources,
|
|
3531
|
-
reportIds: reports.map((r) => r.id),
|
|
3532
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3533
|
-
metadata: {
|
|
3534
|
-
agents: sources.length,
|
|
3535
|
-
reports: reports.length,
|
|
3536
|
-
rawFindings: totalRawFindings,
|
|
3537
|
-
findingGroups: findingGroups.length,
|
|
3538
|
-
agreements: agreements.length,
|
|
3539
|
-
conflicts: conflicts.length
|
|
3540
|
-
}
|
|
3541
|
-
};
|
|
3542
|
-
}
|
|
3543
|
-
function rankConfidence(confidence) {
|
|
3544
|
-
return confidence ? { low: 1, medium: 2, high: 3 }[confidence] : 0;
|
|
3545
|
-
}
|
|
3546
|
-
function detectConflicts(groups) {
|
|
3547
|
-
const conflicts = [];
|
|
3548
|
-
for (const group of groups) {
|
|
3549
|
-
if (group.sources.length < 2) continue;
|
|
3550
|
-
const severities = new Set(Object.values(group.severityBySource ?? {}));
|
|
3551
|
-
if (severities.size > 1) {
|
|
3552
|
-
conflicts.push({
|
|
3553
|
-
type: "severity",
|
|
3554
|
-
findingTitle: group.title,
|
|
3555
|
-
positions: group.sources.map((source) => position(source, group)),
|
|
3556
|
-
note: `Agents disagree on severity (${[...severities].sort((a, b) => SEVERITY_RANK2[a] - SEVERITY_RANK2[b]).join(", ")}); the highest severity is shown, every position is preserved.`
|
|
3557
|
-
});
|
|
3558
|
-
}
|
|
3559
|
-
const recommendations = (group.recommendations ?? []).filter((r) => r.length > 0);
|
|
3560
|
-
if (recommendations.length > 1) {
|
|
3561
|
-
conflicts.push({
|
|
3562
|
-
type: "recommendation",
|
|
3563
|
-
findingTitle: group.title,
|
|
3564
|
-
positions: group.sources.map((source) => position(source, group)),
|
|
3565
|
-
note: `Agents proposed ${recommendations.length} different fixes; none was discarded.`
|
|
3566
|
-
});
|
|
3567
|
-
}
|
|
3568
|
-
}
|
|
3569
|
-
return conflicts;
|
|
3570
|
-
}
|
|
3571
|
-
function position(source, group) {
|
|
3572
|
-
return {
|
|
3573
|
-
agent: source.agent,
|
|
3574
|
-
reportId: source.reportId,
|
|
3575
|
-
...group.severityBySource?.[source.agent] !== void 0 ? { severity: group.severityBySource[source.agent] } : {},
|
|
3576
|
-
...group.confidenceBySource?.[source.agent] !== void 0 ? { confidence: group.confidenceBySource[source.agent] } : {},
|
|
3577
|
-
...group.recommendations?.[0] !== void 0 ? { recommendation: group.recommendations[0] } : {}
|
|
3578
|
-
};
|
|
3579
|
-
}
|
|
3580
|
-
function rank(finding) {
|
|
3581
|
-
return finding.severity ? SEVERITY_RANK2[finding.severity] : -1;
|
|
3582
|
-
}
|
|
3583
|
-
function mergeFinding(groups, finding, report, threshold) {
|
|
3584
|
-
const source = { agent: report.agent, reportId: report.id };
|
|
3585
|
-
const group = groups.find((g) => similar(g.title, finding.title, threshold));
|
|
3586
|
-
if (group) {
|
|
3587
|
-
group.occurrences += 1;
|
|
3588
|
-
group.details.push(finding.detail);
|
|
3589
|
-
group.sources.push(source);
|
|
3590
|
-
group.evidence.push(...normalizeEvidence(finding.evidence) ?? []);
|
|
3591
|
-
if (finding.severity && (!group.severity || SEVERITY_RANK2[finding.severity] > SEVERITY_RANK2[group.severity])) {
|
|
3592
|
-
group.severity = finding.severity;
|
|
3593
|
-
}
|
|
3594
|
-
if (finding.confidence && rankConfidence(finding.confidence) > rankConfidence(group.confidence)) {
|
|
3595
|
-
group.confidence = finding.confidence;
|
|
3596
|
-
}
|
|
3597
|
-
if (finding.severity) {
|
|
3598
|
-
group.severityBySource = { ...group.severityBySource, [report.agent]: finding.severity };
|
|
3599
|
-
}
|
|
3600
|
-
if (finding.confidence) {
|
|
3601
|
-
group.confidenceBySource = { ...group.confidenceBySource, [report.agent]: finding.confidence };
|
|
3602
|
-
}
|
|
3603
|
-
if (finding.recommendation && !group.recommendations?.includes(finding.recommendation)) {
|
|
3604
|
-
group.recommendations = [...group.recommendations ?? [], finding.recommendation];
|
|
3605
|
-
}
|
|
3606
|
-
return;
|
|
3607
|
-
}
|
|
3608
|
-
groups.push({
|
|
3609
|
-
title: finding.title,
|
|
3610
|
-
details: [finding.detail],
|
|
3611
|
-
sources: [source],
|
|
3612
|
-
occurrences: 1,
|
|
3613
|
-
...finding.severity ? { severity: finding.severity } : {},
|
|
3614
|
-
...finding.confidence ? { confidence: finding.confidence } : {},
|
|
3615
|
-
...finding.severity ? { severityBySource: { [report.agent]: finding.severity } } : {},
|
|
3616
|
-
...finding.confidence ? { confidenceBySource: { [report.agent]: finding.confidence } } : {},
|
|
3617
|
-
...finding.recommendation ? { recommendations: [finding.recommendation] } : {},
|
|
3618
|
-
evidence: [...normalizeEvidence(finding.evidence) ?? []]
|
|
3619
|
-
});
|
|
3620
|
-
}
|
|
3621
|
-
function mergeRecommendation(groups, text, report, threshold) {
|
|
3622
|
-
const source = { agent: report.agent, reportId: report.id };
|
|
3623
|
-
const group = groups.find((g) => similar(g.text, text, threshold));
|
|
3624
|
-
if (group) {
|
|
3625
|
-
group.occurrences += 1;
|
|
3626
|
-
group.sources.push(source);
|
|
3627
|
-
return;
|
|
3628
|
-
}
|
|
3629
|
-
groups.push({ text, sources: [source], occurrences: 1 });
|
|
3630
|
-
}
|
|
3631
|
-
function similar(a, b, threshold) {
|
|
3632
|
-
const left = tokens(a);
|
|
3633
|
-
const right = tokens(b);
|
|
3634
|
-
if (left.size === 0 || right.size === 0) return false;
|
|
3635
|
-
let intersection = 0;
|
|
3636
|
-
for (const token of left) {
|
|
3637
|
-
if (right.has(token)) intersection += 1;
|
|
3638
|
-
}
|
|
3639
|
-
const union = left.size + right.size - intersection;
|
|
3640
|
-
return intersection / union >= threshold;
|
|
3641
|
-
}
|
|
3642
|
-
function tokens(text) {
|
|
3643
|
-
return new Set(
|
|
3644
|
-
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 3)
|
|
3645
|
-
);
|
|
3646
|
-
}
|
|
3647
|
-
function truncate2(text, max) {
|
|
3648
|
-
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
3829
|
// src/reports/extract.ts
|
|
3652
3830
|
var BULLET = /^(?:[-*•]|\d+[.)])\s+(.*)$/;
|
|
3653
3831
|
function parseSections(raw) {
|
|
@@ -3872,9 +4050,59 @@ var ProjectStores = class {
|
|
|
3872
4050
|
this.audit = new FileAuditLog(directory);
|
|
3873
4051
|
}
|
|
3874
4052
|
};
|
|
4053
|
+
var STORE_FILES = [
|
|
4054
|
+
"tasks.json",
|
|
4055
|
+
"pipelines.json",
|
|
4056
|
+
"reports.json",
|
|
4057
|
+
"plans.json",
|
|
4058
|
+
"memory.json"
|
|
4059
|
+
];
|
|
4060
|
+
async function migrateProjectStores(directory = `${process.cwd()}/.omninode`, options = {}) {
|
|
4061
|
+
const reports = [];
|
|
4062
|
+
for (const file of STORE_FILES) {
|
|
4063
|
+
const path = `${directory}/${file}`;
|
|
4064
|
+
const state = await inspectDocument(path);
|
|
4065
|
+
if (state.status === "missing") {
|
|
4066
|
+
reports.push({ file, state: state.status, migrated: false });
|
|
4067
|
+
continue;
|
|
4068
|
+
}
|
|
4069
|
+
if (state.status === "current") {
|
|
4070
|
+
reports.push({ file, state: state.status, before: state.count, after: state.count, migrated: false });
|
|
4071
|
+
continue;
|
|
4072
|
+
}
|
|
4073
|
+
if (state.status === "future") {
|
|
4074
|
+
reports.push({
|
|
4075
|
+
file,
|
|
4076
|
+
state: state.status,
|
|
4077
|
+
migrated: false,
|
|
4078
|
+
note: `written by a newer OmniNode (schema v${state.schemaVersion}); not touched`
|
|
4079
|
+
});
|
|
4080
|
+
continue;
|
|
4081
|
+
}
|
|
4082
|
+
if (state.status === "corrupt") {
|
|
4083
|
+
reports.push({ file, state: state.status, migrated: false, note: state.detail });
|
|
4084
|
+
continue;
|
|
4085
|
+
}
|
|
4086
|
+
const items = await readDocument(path, void 0, false);
|
|
4087
|
+
if (!options.checkOnly) {
|
|
4088
|
+
await writeFileAtomic(path, `${JSON.stringify({ schemaVersion: SCHEMA_VERSION, items }, null, 2)}
|
|
4089
|
+
`);
|
|
4090
|
+
}
|
|
4091
|
+
const beforeCount = items.length;
|
|
4092
|
+
reports.push({
|
|
4093
|
+
file,
|
|
4094
|
+
state: state.status,
|
|
4095
|
+
before: beforeCount,
|
|
4096
|
+
after: items.length,
|
|
4097
|
+
migrated: !options.checkOnly,
|
|
4098
|
+
...options.checkOnly ? { note: "would migrate" } : {}
|
|
4099
|
+
});
|
|
4100
|
+
}
|
|
4101
|
+
return reports;
|
|
4102
|
+
}
|
|
3875
4103
|
|
|
3876
4104
|
// src/version.ts
|
|
3877
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
4105
|
+
var OMNINODE_VERSION = "2.0.0-alpha.12";
|
|
3878
4106
|
export {
|
|
3879
4107
|
AgentAdapterRegistry,
|
|
3880
4108
|
AgentError,
|
|
@@ -3916,6 +4144,8 @@ export {
|
|
|
3916
4144
|
RememberaMemoryProvider,
|
|
3917
4145
|
ReportService,
|
|
3918
4146
|
RoleRegistry,
|
|
4147
|
+
SCHEMA_VERSION,
|
|
4148
|
+
STORE_FILES,
|
|
3919
4149
|
SUPPORTED_PROTOCOL_VERSIONS,
|
|
3920
4150
|
TERMINAL_TASK_STATUSES,
|
|
3921
4151
|
TaskEngine,
|
|
@@ -3951,6 +4181,7 @@ export {
|
|
|
3951
4181
|
extractMessage,
|
|
3952
4182
|
extractReportFromText,
|
|
3953
4183
|
findConfigFile,
|
|
4184
|
+
inspectDocument,
|
|
3954
4185
|
isNetworkError,
|
|
3955
4186
|
isOmniNodeError,
|
|
3956
4187
|
isRetryable,
|
|
@@ -3961,6 +4192,7 @@ export {
|
|
|
3961
4192
|
kindFromStatus,
|
|
3962
4193
|
loadConfig,
|
|
3963
4194
|
logger,
|
|
4195
|
+
migrateProjectStores,
|
|
3964
4196
|
newMessageId,
|
|
3965
4197
|
normalizeEvidence,
|
|
3966
4198
|
normalizeFinding,
|
|
@@ -3969,11 +4201,13 @@ export {
|
|
|
3969
4201
|
parseRetryAfter,
|
|
3970
4202
|
pipelineConfigSchema,
|
|
3971
4203
|
pipelineStepConfigSchema,
|
|
4204
|
+
planJsonSchema,
|
|
3972
4205
|
plannerConfigSchema,
|
|
3973
4206
|
projectConfigSchema,
|
|
3974
4207
|
providerConfigSchema,
|
|
3975
4208
|
providerHttpError,
|
|
3976
4209
|
providerTransportError,
|
|
4210
|
+
readDocument,
|
|
3977
4211
|
registryKey,
|
|
3978
4212
|
requestJson,
|
|
3979
4213
|
requireCompletion,
|
|
@@ -3983,7 +4217,9 @@ export {
|
|
|
3983
4217
|
sanitizeChunk,
|
|
3984
4218
|
similar,
|
|
3985
4219
|
validateEnvelope,
|
|
4220
|
+
validatePlan,
|
|
3986
4221
|
validateReport,
|
|
3987
|
-
validateWorkingDirectory
|
|
4222
|
+
validateWorkingDirectory,
|
|
4223
|
+
writeFileAtomic
|
|
3988
4224
|
};
|
|
3989
4225
|
//# sourceMappingURL=index.js.map
|