@multileaf/ai-workflow 0.1.0
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/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +32 -0
- package/dist/adapter-contract.d.ts +13 -0
- package/dist/adapter-contract.js +50 -0
- package/dist/adapter.d.ts +15 -0
- package/dist/adapter.js +62 -0
- package/dist/claude-layout.d.ts +9 -0
- package/dist/claude-layout.js +23 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +30 -0
- package/dist/codex-layout.d.ts +10 -0
- package/dist/codex-layout.js +24 -0
- package/dist/confirmation.d.ts +9 -0
- package/dist/confirmation.js +41 -0
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +10 -0
- package/dist/context-cache.d.ts +8 -0
- package/dist/context-cache.js +17 -0
- package/dist/context-quality.d.ts +11 -0
- package/dist/context-quality.js +27 -0
- package/dist/context-retrieval.d.ts +3 -0
- package/dist/context-retrieval.js +13 -0
- package/dist/context-store.d.ts +9 -0
- package/dist/context-store.js +18 -0
- package/dist/copilot-layout.d.ts +9 -0
- package/dist/copilot-layout.js +24 -0
- package/dist/cursor-layout.d.ts +9 -0
- package/dist/cursor-layout.js +25 -0
- package/dist/dashboard.d.ts +37 -0
- package/dist/dashboard.js +251 -0
- package/dist/drift.d.ts +2 -0
- package/dist/drift.js +10 -0
- package/dist/evidence.d.ts +20 -0
- package/dist/evidence.js +32 -0
- package/dist/files.d.ts +2 -0
- package/dist/files.js +58 -0
- package/dist/gemini-layout.d.ts +9 -0
- package/dist/gemini-layout.js +23 -0
- package/dist/generator.d.ts +3 -0
- package/dist/generator.js +16 -0
- package/dist/intelligence.d.ts +2 -0
- package/dist/intelligence.js +13 -0
- package/dist/interpreter.d.ts +23 -0
- package/dist/interpreter.js +117 -0
- package/dist/lockfile.d.ts +12 -0
- package/dist/lockfile.js +59 -0
- package/dist/manifest.d.ts +15 -0
- package/dist/manifest.js +35 -0
- package/dist/migration.d.ts +22 -0
- package/dist/migration.js +50 -0
- package/dist/orchestration.d.ts +30 -0
- package/dist/orchestration.js +172 -0
- package/dist/organization-policy.d.ts +14 -0
- package/dist/organization-policy.js +47 -0
- package/dist/package-audit.d.ts +19 -0
- package/dist/package-audit.js +28 -0
- package/dist/package-contract.d.ts +30 -0
- package/dist/package-contract.js +93 -0
- package/dist/package-integrity.d.ts +8 -0
- package/dist/package-integrity.js +15 -0
- package/dist/package-updates.d.ts +11 -0
- package/dist/package-updates.js +32 -0
- package/dist/plugin-sdk.d.ts +20 -0
- package/dist/plugin-sdk.js +243 -0
- package/dist/policy-gate.d.ts +6 -0
- package/dist/policy-gate.js +20 -0
- package/dist/profile.d.ts +23 -0
- package/dist/profile.js +123 -0
- package/dist/providers.d.ts +20 -0
- package/dist/providers.js +67 -0
- package/dist/quality-gates.d.ts +2 -0
- package/dist/quality-gates.js +40 -0
- package/dist/recommendations.d.ts +12 -0
- package/dist/recommendations.js +63 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +27 -0
- package/dist/scanner.d.ts +9 -0
- package/dist/scanner.js +83 -0
- package/dist/selection.d.ts +3 -0
- package/dist/selection.js +11 -0
- package/dist/self-validation.d.ts +9 -0
- package/dist/self-validation.js +9 -0
- package/dist/services.d.ts +42 -0
- package/dist/services.js +1 -0
- package/dist/team-configuration.d.ts +23 -0
- package/dist/team-configuration.js +104 -0
- package/dist/telemetry.d.ts +20 -0
- package/dist/telemetry.js +95 -0
- package/dist/token-usage.d.ts +9 -0
- package/dist/token-usage.js +38 -0
- package/dist/traceability.d.ts +23 -0
- package/dist/traceability.js +51 -0
- package/dist/types.d.ts +22 -0
- package/dist/types.js +1 -0
- package/dist/universal-layout.d.ts +9 -0
- package/dist/universal-layout.js +21 -0
- package/dist/vercel-skills.d.ts +17 -0
- package/dist/vercel-skills.js +49 -0
- package/dist/workflow.d.ts +4 -0
- package/dist/workflow.js +1130 -0
- package/docs/README.md +20 -0
- package/docs/adapter-model.md +16 -0
- package/docs/architecture.md +66 -0
- package/docs/backlog.md +93 -0
- package/docs/orchestration.md +50 -0
- package/docs/package-model.md +53 -0
- package/docs/plugin-authoring.md +31 -0
- package/docs/product-requirements.md +57 -0
- package/docs/product-vision.md +26 -0
- package/docs/project-intelligence.md +59 -0
- package/docs/quality.md +32 -0
- package/docs/release-backlog.md +7 -0
- package/docs/release-process.md +38 -0
- package/docs/security.md +18 -0
- package/docs/telemetry.md +28 -0
- package/docs/token-efficiency.md +16 -0
- package/package.json +76 -0
- package/resources/README.md +4 -0
- package/resources/agents/adapter-engineer.md +4 -0
- package/resources/agents/documentation-writer.md +4 -0
- package/resources/agents/quality-reviewer.md +4 -0
- package/resources/agents/requirements-analyst.md +4 -0
- package/resources/agents/security-reviewer.md +4 -0
- package/resources/agents/technical-architect.md +4 -0
- package/resources/agents/test-engineer.md +4 -0
- package/resources/agents/typescript-engineer.md +4 -0
- package/resources/hooks/post-change.md +4 -0
- package/resources/hooks/post-implementation.md +4 -0
- package/resources/hooks/pre-commit.md +4 -0
- package/resources/hooks/pre-implementation.md +4 -0
- package/resources/hooks/pre-release.md +4 -0
- package/resources/package.yaml +36 -0
- package/resources/rules/architecture-policy.md +4 -0
- package/resources/rules/completion-validation.md +19 -0
- package/resources/rules/dependency-policy.md +4 -0
- package/resources/rules/documentation-policy.md +4 -0
- package/resources/rules/generated-files.md +4 -0
- package/resources/rules/language-policy.md +4 -0
- package/resources/rules/quality-gates.md +4 -0
- package/resources/rules/security-policy.md +4 -0
- package/resources/rules/self-validation.md +12 -0
- package/resources/rules/tdd-policy.md +4 -0
- package/resources/skills/adapter-development.md +9 -0
- package/resources/skills/brainstorming.md +9 -0
- package/resources/skills/code-review.md +9 -0
- package/resources/skills/documentation-maintenance.md +9 -0
- package/resources/skills/implementation-planning.md +9 -0
- package/resources/skills/project-init.md +9 -0
- package/resources/skills/requirements-specification.md +9 -0
- package/resources/skills/security-review.md +9 -0
- package/resources/skills/skill-authoring.md +9 -0
- package/resources/skills/tdd-development.md +9 -0
- package/resources/skills/technical-design.md +9 -0
- package/resources/skills/verification.md +9 -0
- package/resources/templates/brainstorm.md +16 -0
- package/resources/templates/decision-record.md +12 -0
- package/resources/templates/implementation-plan.md +12 -0
- package/resources/templates/migration-report.md +14 -0
- package/resources/templates/security-review.md +14 -0
- package/resources/templates/specification.md +23 -0
- package/resources/templates/task.md +14 -0
- package/resources/templates/technical-design.md +14 -0
- package/resources/templates/test-plan.md +12 -0
- package/resources/templates/verification-report.md +12 -0
package/dist/lockfile.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function resolvePackage(pkg) {
|
|
2
|
+
return {
|
|
3
|
+
id: pkg.id,
|
|
4
|
+
version: pkg.version,
|
|
5
|
+
provider: pkg.provider,
|
|
6
|
+
source: pkg.source,
|
|
7
|
+
integrity: `sha256-${pkg.id}@${pkg.version}`,
|
|
8
|
+
permissions: [...pkg.permissions].sort(),
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function serializeLock(packages) {
|
|
12
|
+
const ordered = [...packages].sort((a, b) => a.id.localeCompare(b.id));
|
|
13
|
+
return `schema: 1\npackages:\n${ordered.map((pkg) => ` - id: ${pkg.id}\n version: ${pkg.version}\n provider: ${pkg.provider}\n source: ${pkg.source}\n integrity: ${pkg.integrity}\n permissions: [${[...(pkg.permissions ?? [])].sort().join(", ")}]\n`).join("")}`;
|
|
14
|
+
}
|
|
15
|
+
export function parseLock(content) {
|
|
16
|
+
const lines = content.split(/\r?\n/);
|
|
17
|
+
if (lines.at(-1) === "")
|
|
18
|
+
lines.pop();
|
|
19
|
+
if (lines[0] !== "schema: 1")
|
|
20
|
+
throw new Error("Lockfile schema must be version 1.");
|
|
21
|
+
if (lines[1] !== "packages:")
|
|
22
|
+
throw new Error("Lockfile packages section is required.");
|
|
23
|
+
const packages = [];
|
|
24
|
+
for (let index = 2; index < lines.length;) {
|
|
25
|
+
const id = requiredLockValue(lines[index], /^ {2}- id: (.+)$/, "id");
|
|
26
|
+
const version = requiredLockValue(lines[index + 1], /^ {4}version: (.+)$/, "version");
|
|
27
|
+
const provider = requiredLockValue(lines[index + 2], /^ {4}provider: (.+)$/, "provider");
|
|
28
|
+
const source = requiredLockValue(lines[index + 3], /^ {4}source: (.+)$/, "source");
|
|
29
|
+
const integrity = requiredLockValue(lines[index + 4], /^ {4}integrity: (.+)$/, "integrity");
|
|
30
|
+
index += 5;
|
|
31
|
+
let permissions;
|
|
32
|
+
if (lines[index]?.startsWith(" permissions:")) {
|
|
33
|
+
const value = lines[index].match(/^ {4}permissions: \[([^\]]*)\]$/)?.[1];
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
throw new Error(`Lockfile permissions are invalid: ${id}`);
|
|
36
|
+
permissions = value === "" ? [] : value.split(",").map((permission) => permission.trim());
|
|
37
|
+
if (permissions.some((permission) => permission.length === 0))
|
|
38
|
+
throw new Error(`Lockfile permissions are invalid: ${id}`);
|
|
39
|
+
index += 1;
|
|
40
|
+
}
|
|
41
|
+
packages.push({
|
|
42
|
+
id,
|
|
43
|
+
version,
|
|
44
|
+
provider,
|
|
45
|
+
source,
|
|
46
|
+
integrity,
|
|
47
|
+
...(permissions ? { permissions } : {}),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
if (new Set(packages.map(({ id }) => id)).size !== packages.length)
|
|
51
|
+
throw new Error("Lockfile package identifiers must be unique.");
|
|
52
|
+
return packages;
|
|
53
|
+
}
|
|
54
|
+
function requiredLockValue(line, pattern, field) {
|
|
55
|
+
const value = line?.match(pattern)?.[1]?.trim();
|
|
56
|
+
if (!value)
|
|
57
|
+
throw new Error(`Lockfile package field is invalid: ${field}`);
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Target } from "./types.js";
|
|
2
|
+
export type Manifest = {
|
|
3
|
+
schema: number;
|
|
4
|
+
project: {
|
|
5
|
+
name: string;
|
|
6
|
+
};
|
|
7
|
+
target: {
|
|
8
|
+
active: Target;
|
|
9
|
+
};
|
|
10
|
+
policies: {
|
|
11
|
+
artifactLanguage: string;
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
export declare function serializeManifest(manifest: Manifest): string;
|
|
15
|
+
export declare function parseManifest(content: string): Manifest;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TARGETS } from "./types.js";
|
|
2
|
+
export function serializeManifest(manifest) {
|
|
3
|
+
if (manifest.schema !== 1)
|
|
4
|
+
throw new Error("Manifest schema must be version 1.");
|
|
5
|
+
if (!manifest.project.name.trim())
|
|
6
|
+
throw new Error("Manifest project.name is required.");
|
|
7
|
+
if (!TARGETS.includes(manifest.target.active))
|
|
8
|
+
throw new Error(`Manifest target.active is unsupported: ${manifest.target.active}.`);
|
|
9
|
+
if (manifest.policies.artifactLanguage !== "en")
|
|
10
|
+
throw new Error("Manifest policies.artifact_language must be en.");
|
|
11
|
+
return `schema: 1\nproject:\n name: ${manifest.project.name}\ntarget:\n active: ${manifest.target.active}\npolicies:\n artifact_language: en\n`;
|
|
12
|
+
}
|
|
13
|
+
export function parseManifest(content) {
|
|
14
|
+
const schema = valueOf(content, "schema");
|
|
15
|
+
const name = valueOf(content, "name");
|
|
16
|
+
const target = valueOf(content, "active");
|
|
17
|
+
const language = valueOf(content, "artifact_language");
|
|
18
|
+
if (schema !== "1")
|
|
19
|
+
throw new Error("Manifest schema must be version 1.");
|
|
20
|
+
if (!name)
|
|
21
|
+
throw new Error("Manifest project.name is required.");
|
|
22
|
+
if (!TARGETS.includes(target))
|
|
23
|
+
throw new Error(`Manifest target.active is unsupported: ${target || "missing"}.`);
|
|
24
|
+
if (language !== "en")
|
|
25
|
+
throw new Error("Manifest policies.artifact_language must be en.");
|
|
26
|
+
return {
|
|
27
|
+
schema: 1,
|
|
28
|
+
project: { name },
|
|
29
|
+
target: { active: target },
|
|
30
|
+
policies: { artifactLanguage: language },
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function valueOf(content, key) {
|
|
34
|
+
return content.match(new RegExp(`^\\s*${key}:\\s*(.+)\\s*$`, "m"))?.[1]?.trim() ?? "";
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Target } from "./types.js";
|
|
2
|
+
export type ResourceMigration = {
|
|
3
|
+
source: string;
|
|
4
|
+
destination: string;
|
|
5
|
+
};
|
|
6
|
+
export type MigrationSnapshot = {
|
|
7
|
+
path: string;
|
|
8
|
+
existed: boolean;
|
|
9
|
+
contentBase64: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function planNeutralResourceMigration(neutralRoot: string, relativeFiles: string[], target: Target): ResourceMigration[];
|
|
12
|
+
export declare function serializeMigrationPreview(target: Target, migrations: ResourceMigration[], existingDestinations: string[]): string;
|
|
13
|
+
export declare function serializeMigrationSnapshots(snapshots: MigrationSnapshot[]): string;
|
|
14
|
+
export declare function parseMigrationSnapshots(encoded: string): MigrationSnapshot[];
|
|
15
|
+
export declare function readResourceBytes(fs: {
|
|
16
|
+
read(path: string): string;
|
|
17
|
+
readBytes?(path: string): Uint8Array;
|
|
18
|
+
}, path: string): Uint8Array;
|
|
19
|
+
export declare function writeResourceBytes(fs: {
|
|
20
|
+
write(path: string, content: string): void;
|
|
21
|
+
writeBytes?(path: string, content: Uint8Array): void;
|
|
22
|
+
}, path: string, content: Uint8Array): void;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { renderResourcePath } from "./adapter.js";
|
|
3
|
+
import { RESOURCE_TYPES } from "./package-contract.js";
|
|
4
|
+
export function planNeutralResourceMigration(neutralRoot, relativeFiles, target) {
|
|
5
|
+
return relativeFiles
|
|
6
|
+
.flatMap((relative) => {
|
|
7
|
+
const [candidateType, id, filename, extra] = relative.split("/");
|
|
8
|
+
if (extra || !RESOURCE_TYPES.includes(candidateType) || !id || !filename)
|
|
9
|
+
return [];
|
|
10
|
+
const type = candidateType;
|
|
11
|
+
const expected = type === "skills" ? "SKILL.md" : `${id}.md`;
|
|
12
|
+
if (filename !== expected)
|
|
13
|
+
return [];
|
|
14
|
+
return [
|
|
15
|
+
{ source: join(neutralRoot, relative), destination: renderResourcePath(target, type, id) },
|
|
16
|
+
];
|
|
17
|
+
})
|
|
18
|
+
.sort((left, right) => left.destination.localeCompare(right.destination));
|
|
19
|
+
}
|
|
20
|
+
export function serializeMigrationPreview(target, migrations, existingDestinations) {
|
|
21
|
+
const existing = new Set(existingDestinations);
|
|
22
|
+
const changes = migrations.map(({ destination }) => existing.has(destination) ? `update: ${destination}` : `add: ${destination}`);
|
|
23
|
+
return `Migration preview for ${target}:\n${changes.join("\n")}${changes.length ? "\n" : ""}remove: previous target ai-init\n`;
|
|
24
|
+
}
|
|
25
|
+
export function serializeMigrationSnapshots(snapshots) {
|
|
26
|
+
return Buffer.from(JSON.stringify(snapshots), "utf8").toString("base64");
|
|
27
|
+
}
|
|
28
|
+
export function parseMigrationSnapshots(encoded) {
|
|
29
|
+
const value = JSON.parse(Buffer.from(encoded, "base64").toString("utf8"));
|
|
30
|
+
if (!Array.isArray(value))
|
|
31
|
+
throw new Error("Migration resource snapshots are invalid.");
|
|
32
|
+
return value.map((snapshot) => {
|
|
33
|
+
if (typeof snapshot !== "object" ||
|
|
34
|
+
snapshot === null ||
|
|
35
|
+
typeof snapshot.path !== "string" ||
|
|
36
|
+
typeof snapshot.existed !== "boolean" ||
|
|
37
|
+
typeof snapshot.contentBase64 !== "string")
|
|
38
|
+
throw new Error("Migration resource snapshots are invalid.");
|
|
39
|
+
return snapshot;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
export function readResourceBytes(fs, path) {
|
|
43
|
+
return fs.readBytes?.(path) ?? Buffer.from(fs.read(path), "utf8");
|
|
44
|
+
}
|
|
45
|
+
export function writeResourceBytes(fs, path, content) {
|
|
46
|
+
if (fs.writeBytes)
|
|
47
|
+
fs.writeBytes(path, content);
|
|
48
|
+
else
|
|
49
|
+
fs.write(path, Buffer.from(content).toString("utf8"));
|
|
50
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const ORCHESTRATION_AGENTS: readonly ["codex", "claude", "gemini"];
|
|
2
|
+
export type OrchestrationAgent = (typeof ORCHESTRATION_AGENTS)[number];
|
|
3
|
+
export type OrchestrationTask = {
|
|
4
|
+
id: string;
|
|
5
|
+
agent: OrchestrationAgent;
|
|
6
|
+
prompt: string;
|
|
7
|
+
dependsOn: string[];
|
|
8
|
+
};
|
|
9
|
+
export type OrchestrationPlan = {
|
|
10
|
+
schema: 1;
|
|
11
|
+
id: string;
|
|
12
|
+
tasks: OrchestrationTask[];
|
|
13
|
+
};
|
|
14
|
+
export type OrchestrationTaskResult = {
|
|
15
|
+
id: string;
|
|
16
|
+
status: "passed" | "failed" | "blocked";
|
|
17
|
+
detail: string;
|
|
18
|
+
};
|
|
19
|
+
export interface AgentOrchestrator {
|
|
20
|
+
run(task: OrchestrationTask): Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
detail: string;
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export declare function parseOrchestrationPlan(content: string): OrchestrationPlan;
|
|
26
|
+
export declare function orchestrationWaves(plan: OrchestrationPlan): string[][];
|
|
27
|
+
export declare function executeOrchestrationPlan(plan: OrchestrationPlan, orchestrator: AgentOrchestrator, maxParallel: number): Promise<OrchestrationTaskResult[]>;
|
|
28
|
+
export declare function validateOrchestrationParallelism(value: number): number;
|
|
29
|
+
export declare function serializeOrchestrationPreview(plan: OrchestrationPlan): string;
|
|
30
|
+
export declare function serializeOrchestrationResults(plan: OrchestrationPlan, results: OrchestrationTaskResult[]): string;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
export const ORCHESTRATION_AGENTS = ["codex", "claude", "gemini"];
|
|
2
|
+
export function parseOrchestrationPlan(content) {
|
|
3
|
+
if (/\r(?!\n)/.test(content))
|
|
4
|
+
throw new Error("Orchestration plan contains unsupported characters.");
|
|
5
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
6
|
+
if ([...normalized].some((character) => character !== "\n" && isControl(character)))
|
|
7
|
+
throw new Error("Orchestration plan contains unsupported characters.");
|
|
8
|
+
const lines = normalized.split("\n").filter(Boolean);
|
|
9
|
+
if (lines.some((line) => !isPlanLine(line)))
|
|
10
|
+
throw new Error("Orchestration plan syntax is invalid.");
|
|
11
|
+
if (lines.length < 7 || lines[2] !== "tasks:" || (lines.length - 3) % 4 !== 0)
|
|
12
|
+
throw new Error("Orchestration plan structure is invalid.");
|
|
13
|
+
const schema = lines[0]?.match(/^schema:\s*(.+)$/)?.[1]?.trim();
|
|
14
|
+
if (schema !== "1")
|
|
15
|
+
throw new Error("Orchestration plan schema must be version 1.");
|
|
16
|
+
const id = lines[1]?.match(/^id:\s*(.+)$/)?.[1]?.trim() ?? "";
|
|
17
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(id))
|
|
18
|
+
throw new Error("Orchestration plan id must use lowercase letters, numbers, and hyphens.");
|
|
19
|
+
const taskCount = (lines.length - 3) / 4;
|
|
20
|
+
if (taskCount > 64)
|
|
21
|
+
throw new Error("Orchestration plan cannot exceed 64 tasks.");
|
|
22
|
+
const tasks = Array.from({ length: taskCount }, (_, index) => {
|
|
23
|
+
const offset = 3 + index * 4;
|
|
24
|
+
const taskId = lines[offset]?.match(/^\s{2}- id:\s*(.+)$/)?.[1]?.trim() ?? "";
|
|
25
|
+
const agent = orderedField(lines[offset + 1] ?? "", "agent");
|
|
26
|
+
const prompt = quotedValue(orderedField(lines[offset + 2] ?? "", "prompt"), "prompt");
|
|
27
|
+
const dependsOn = inlineList(orderedField(lines[offset + 3] ?? "", "depends_on"));
|
|
28
|
+
if (!/^TASK-\d+$/.test(taskId))
|
|
29
|
+
throw new Error(`Invalid orchestration task id: ${taskId}`);
|
|
30
|
+
if (!ORCHESTRATION_AGENTS.includes(agent))
|
|
31
|
+
throw new Error(`Unsupported orchestration agent for ${taskId}: ${agent}`);
|
|
32
|
+
if (!prompt.trim())
|
|
33
|
+
throw new Error(`Orchestration task prompt is required: ${taskId}`);
|
|
34
|
+
if ([...prompt].some(isControl))
|
|
35
|
+
throw new Error(`Orchestration task prompt contains unsupported characters: ${taskId}`);
|
|
36
|
+
if (prompt.length > 8000)
|
|
37
|
+
throw new Error(`Orchestration task prompt is too long: ${taskId}`);
|
|
38
|
+
return { id: taskId, agent: agent, prompt, dependsOn };
|
|
39
|
+
});
|
|
40
|
+
assertPlanGraph(tasks);
|
|
41
|
+
return { schema: 1, id, tasks };
|
|
42
|
+
}
|
|
43
|
+
export function orchestrationWaves(plan) {
|
|
44
|
+
const remaining = new Map(plan.tasks.map((task) => [task.id, task]));
|
|
45
|
+
const completed = new Set();
|
|
46
|
+
const waves = [];
|
|
47
|
+
while (remaining.size) {
|
|
48
|
+
const wave = [...remaining.values()]
|
|
49
|
+
.filter((task) => task.dependsOn.every((dependency) => completed.has(dependency)))
|
|
50
|
+
.map((task) => task.id)
|
|
51
|
+
.sort();
|
|
52
|
+
if (!wave.length)
|
|
53
|
+
throw new Error("Orchestration plan contains a dependency cycle.");
|
|
54
|
+
waves.push(wave);
|
|
55
|
+
wave.forEach((id) => {
|
|
56
|
+
remaining.delete(id);
|
|
57
|
+
completed.add(id);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return waves;
|
|
61
|
+
}
|
|
62
|
+
export async function executeOrchestrationPlan(plan, orchestrator, maxParallel) {
|
|
63
|
+
validateOrchestrationParallelism(maxParallel);
|
|
64
|
+
const tasks = new Map(plan.tasks.map((task) => [task.id, task]));
|
|
65
|
+
const results = new Map();
|
|
66
|
+
for (const wave of orchestrationWaves(plan)) {
|
|
67
|
+
for (let offset = 0; offset < wave.length; offset += maxParallel) {
|
|
68
|
+
const batch = wave.slice(offset, offset + maxParallel);
|
|
69
|
+
await Promise.all(batch.map(async (id) => {
|
|
70
|
+
const task = tasks.get(id);
|
|
71
|
+
if (!task)
|
|
72
|
+
throw new Error(`Unknown orchestration task: ${id}`);
|
|
73
|
+
if (task.dependsOn.some((dependency) => results.get(dependency)?.status !== "passed")) {
|
|
74
|
+
results.set(id, { id, status: "blocked", detail: "A dependency did not pass." });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const outcome = await orchestrator.run(task);
|
|
79
|
+
results.set(id, {
|
|
80
|
+
id,
|
|
81
|
+
status: outcome.success ? "passed" : "failed",
|
|
82
|
+
detail: safeDetail(outcome.detail),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
results.set(id, {
|
|
87
|
+
id,
|
|
88
|
+
status: "failed",
|
|
89
|
+
detail: safeDetail(error instanceof Error ? error.message : String(error)),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return plan.tasks.map(({ id }) => results.get(id) ?? { id, status: "blocked", detail: "Not run." });
|
|
96
|
+
}
|
|
97
|
+
export function validateOrchestrationParallelism(value) {
|
|
98
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 16)
|
|
99
|
+
throw new Error("Orchestration parallelism must be between 1 and 16.");
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
export function serializeOrchestrationPreview(plan) {
|
|
103
|
+
return `schema: 1\nplan: ${plan.id}\nwaves:\n${orchestrationWaves(plan)
|
|
104
|
+
.map((wave, index) => ` - index: ${index + 1}\n tasks: [${wave.join(", ")}]`)
|
|
105
|
+
.join("\n")}\n`;
|
|
106
|
+
}
|
|
107
|
+
export function serializeOrchestrationResults(plan, results) {
|
|
108
|
+
return `schema: 1\nplan: ${plan.id}\nresults:\n${results
|
|
109
|
+
.map((result) => ` - id: ${result.id}\n status: ${result.status}\n detail: ${JSON.stringify(result.detail)}`)
|
|
110
|
+
.join("\n")}\n`;
|
|
111
|
+
}
|
|
112
|
+
function assertPlanGraph(tasks) {
|
|
113
|
+
const ids = tasks.map(({ id }) => id);
|
|
114
|
+
if (new Set(ids).size !== ids.length)
|
|
115
|
+
throw new Error("Orchestration task ids must be unique.");
|
|
116
|
+
const known = new Set(ids);
|
|
117
|
+
for (const task of tasks) {
|
|
118
|
+
if (new Set(task.dependsOn).size !== task.dependsOn.length)
|
|
119
|
+
throw new Error(`${task.id} dependencies must be unique.`);
|
|
120
|
+
if (task.dependsOn.includes(task.id))
|
|
121
|
+
throw new Error(`${task.id} cannot depend on itself.`);
|
|
122
|
+
const unknown = task.dependsOn.find((dependency) => !known.has(dependency));
|
|
123
|
+
if (unknown)
|
|
124
|
+
throw new Error(`${task.id} has an unknown dependency: ${unknown}`);
|
|
125
|
+
}
|
|
126
|
+
orchestrationWaves({ schema: 1, id: "validation", tasks });
|
|
127
|
+
}
|
|
128
|
+
function orderedField(line, name) {
|
|
129
|
+
const value = line.match(new RegExp(`^\\s{4}${name}:\\s*(.*)$`))?.[1]?.trim();
|
|
130
|
+
if (value === undefined)
|
|
131
|
+
throw new Error(`Orchestration task field order is invalid: ${name}`);
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
function quotedValue(value, name) {
|
|
135
|
+
if (!value.startsWith('"') || !value.endsWith('"'))
|
|
136
|
+
throw new Error(`Orchestration field '${name}' must be a JSON string.`);
|
|
137
|
+
try {
|
|
138
|
+
const parsed = JSON.parse(value);
|
|
139
|
+
if (typeof parsed !== "string")
|
|
140
|
+
throw new Error();
|
|
141
|
+
return parsed;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
throw new Error(`Orchestration field '${name}' must be a valid JSON string.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function inlineList(value) {
|
|
148
|
+
if (!/^\[(?:TASK-\d+(?:,\s*TASK-\d+)*)?\]$/.test(value))
|
|
149
|
+
throw new Error("Orchestration dependencies must be an inline list of task ids.");
|
|
150
|
+
return value === "[]"
|
|
151
|
+
? []
|
|
152
|
+
: value
|
|
153
|
+
.slice(1, -1)
|
|
154
|
+
.split(",")
|
|
155
|
+
.map((item) => item.trim());
|
|
156
|
+
}
|
|
157
|
+
function safeDetail(value) {
|
|
158
|
+
return value
|
|
159
|
+
.replace(/[\r\n\u0085\u2028\u2029]+/g, " ")
|
|
160
|
+
.trim()
|
|
161
|
+
.slice(0, 500);
|
|
162
|
+
}
|
|
163
|
+
function isPlanLine(line) {
|
|
164
|
+
return (/^(?:schema|id): .+$/.test(line) ||
|
|
165
|
+
line === "tasks:" ||
|
|
166
|
+
/^\s{2}- id: .+$/.test(line) ||
|
|
167
|
+
/^\s{4}(?:agent|prompt|depends_on): .+$/.test(line));
|
|
168
|
+
}
|
|
169
|
+
function isControl(value) {
|
|
170
|
+
const code = value.codePointAt(0) ?? 0;
|
|
171
|
+
return code <= 31 || code === 127 || code === 133 || code === 8232 || code === 8233;
|
|
172
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type OrganizationPolicy = {
|
|
2
|
+
schema: 1;
|
|
3
|
+
name: string;
|
|
4
|
+
approvedSources: string[];
|
|
5
|
+
deniedPermissions: string[];
|
|
6
|
+
};
|
|
7
|
+
export type PackagePolicySubject = {
|
|
8
|
+
provider: string;
|
|
9
|
+
source: string;
|
|
10
|
+
permissions: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare function parseOrganizationPolicy(content: string): OrganizationPolicy;
|
|
13
|
+
export declare function serializeOrganizationPolicy(policy: OrganizationPolicy): string;
|
|
14
|
+
export declare function enforceOrganizationPolicy(policy: OrganizationPolicy, subject: PackagePolicySubject): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
export function parseOrganizationPolicy(content) {
|
|
2
|
+
const schema = scalar(content, "schema");
|
|
3
|
+
const name = scalar(content, "name");
|
|
4
|
+
if (schema !== "1")
|
|
5
|
+
throw new Error("Organization policy schema must be version 1.");
|
|
6
|
+
if (!name)
|
|
7
|
+
throw new Error("Organization policy name is required.");
|
|
8
|
+
const approvedSources = list(content, "approved_sources");
|
|
9
|
+
if (!approvedSources.length)
|
|
10
|
+
throw new Error("Organization policy approved_sources must not be empty.");
|
|
11
|
+
return {
|
|
12
|
+
schema: 1,
|
|
13
|
+
name,
|
|
14
|
+
approvedSources: [...new Set(approvedSources)].sort(),
|
|
15
|
+
deniedPermissions: [...new Set(list(content, "denied_permissions"))].sort(),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function serializeOrganizationPolicy(policy) {
|
|
19
|
+
return `schema: 1\nname: ${policy.name}\napproved_sources: [${policy.approvedSources.join(", ")}]\ndenied_permissions: [${policy.deniedPermissions.join(", ")}]\n`;
|
|
20
|
+
}
|
|
21
|
+
export function enforceOrganizationPolicy(policy, subject) {
|
|
22
|
+
const candidate = `${subject.provider}:${subject.source}`;
|
|
23
|
+
const approved = policy.approvedSources.some((pattern) => pattern.endsWith("*") ? candidate.startsWith(pattern.slice(0, -1)) : candidate === pattern);
|
|
24
|
+
if (!approved)
|
|
25
|
+
throw new Error(`Package source is not approved by ${policy.name}: ${candidate}`);
|
|
26
|
+
const denied = subject.permissions.filter((permission) => policy.deniedPermissions.includes(permission));
|
|
27
|
+
if (denied.length)
|
|
28
|
+
throw new Error(`Package permissions are denied by ${policy.name}: ${denied.join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
function scalar(content, key) {
|
|
31
|
+
const value = content.match(new RegExp(`^${key}:[ \\t]*([^\\r\\n]*)$`, "m"))?.[1]?.trim() ?? "";
|
|
32
|
+
if (value.length >= 2 &&
|
|
33
|
+
((value.startsWith('"') && value.endsWith('"')) ||
|
|
34
|
+
(value.startsWith("'") && value.endsWith("'"))))
|
|
35
|
+
return value.slice(1, -1).trim();
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
function list(content, key) {
|
|
39
|
+
const value = scalar(content, key);
|
|
40
|
+
if (!value.startsWith("[") || !value.endsWith("]"))
|
|
41
|
+
throw new Error(`Organization policy ${key} must be an inline list.`);
|
|
42
|
+
return value
|
|
43
|
+
.slice(1, -1)
|
|
44
|
+
.split(",")
|
|
45
|
+
.map((item) => item.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { PackageContract } from "./package-contract.js";
|
|
2
|
+
export type PermissionReview = {
|
|
3
|
+
allowed: boolean;
|
|
4
|
+
requested: string[];
|
|
5
|
+
unapproved: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare const PACKAGE_PERMISSIONS: {
|
|
8
|
+
readonly "read:repository": "low";
|
|
9
|
+
readonly "filesystem:read": "low";
|
|
10
|
+
readonly "filesystem:write": "high";
|
|
11
|
+
readonly "network:external": "high";
|
|
12
|
+
readonly "environment:read": "high";
|
|
13
|
+
readonly "process:execute": "critical";
|
|
14
|
+
};
|
|
15
|
+
export type PackagePermission = keyof typeof PACKAGE_PERMISSIONS;
|
|
16
|
+
export declare function assertKnownPermissions(permissions: string[]): void;
|
|
17
|
+
export declare function reviewPackagePermissions(pkg: PackageContract, approved: string[]): PermissionReview;
|
|
18
|
+
export declare function serializePermissionReview(pkg: PackageContract): string;
|
|
19
|
+
export declare function assertPackagePermissions(pkg: PackageContract, approved: string[]): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const PACKAGE_PERMISSIONS = {
|
|
2
|
+
"read:repository": "low",
|
|
3
|
+
"filesystem:read": "low",
|
|
4
|
+
"filesystem:write": "high",
|
|
5
|
+
"network:external": "high",
|
|
6
|
+
"environment:read": "high",
|
|
7
|
+
"process:execute": "critical",
|
|
8
|
+
};
|
|
9
|
+
export function assertKnownPermissions(permissions) {
|
|
10
|
+
const unknown = permissions.filter((permission) => !Object.prototype.hasOwnProperty.call(PACKAGE_PERMISSIONS, permission));
|
|
11
|
+
if (unknown.length)
|
|
12
|
+
throw new Error(`Unknown package permissions: ${unknown.join(", ")}`);
|
|
13
|
+
}
|
|
14
|
+
export function reviewPackagePermissions(pkg, approved) {
|
|
15
|
+
assertKnownPermissions(pkg.permissions);
|
|
16
|
+
const unapproved = pkg.permissions.filter((permission) => !approved.includes(permission));
|
|
17
|
+
return { allowed: unapproved.length === 0, requested: [...pkg.permissions], unapproved };
|
|
18
|
+
}
|
|
19
|
+
export function serializePermissionReview(pkg) {
|
|
20
|
+
reviewPackagePermissions(pkg, []);
|
|
21
|
+
const permissions = [...pkg.permissions].sort();
|
|
22
|
+
return `Package permission review: ${pkg.id}@${pkg.version}\n${permissions.length ? permissions.map((permission) => `- ${permission} (${PACKAGE_PERMISSIONS[permission]})`).join("\n") : "- none"}\nDecision: ${permissions.length ? "explicit approval required" : "no permissions requested"}\n`;
|
|
23
|
+
}
|
|
24
|
+
export function assertPackagePermissions(pkg, approved) {
|
|
25
|
+
const review = reviewPackagePermissions(pkg, approved);
|
|
26
|
+
if (!review.allowed)
|
|
27
|
+
throw new Error(`Package permissions require approval: ${review.unapproved.join(", ")}`);
|
|
28
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export declare const RESOURCE_TYPES: readonly ["skills", "rules", "agents", "hooks", "templates"];
|
|
2
|
+
export type ResourceType = (typeof RESOURCE_TYPES)[number];
|
|
3
|
+
export type PackageResource = {
|
|
4
|
+
id: string;
|
|
5
|
+
version: string;
|
|
6
|
+
path: string;
|
|
7
|
+
};
|
|
8
|
+
export type PackagePolicy = {
|
|
9
|
+
id: string;
|
|
10
|
+
value: string;
|
|
11
|
+
};
|
|
12
|
+
export type PackageContract = {
|
|
13
|
+
schema: number;
|
|
14
|
+
id: string;
|
|
15
|
+
version: string;
|
|
16
|
+
provider: string;
|
|
17
|
+
source: string;
|
|
18
|
+
resources: Record<ResourceType, PackageResource[]>;
|
|
19
|
+
dependencies: string[];
|
|
20
|
+
permissions: string[];
|
|
21
|
+
provenance: {
|
|
22
|
+
source: string;
|
|
23
|
+
checksum?: string;
|
|
24
|
+
};
|
|
25
|
+
policies?: PackagePolicy[];
|
|
26
|
+
engines?: Record<string, string>;
|
|
27
|
+
provides?: Record<ResourceType, string[]>;
|
|
28
|
+
targets?: string[];
|
|
29
|
+
};
|
|
30
|
+
export declare function validatePackageContract(content: string, resourceExists?: (path: string) => boolean): PackageContract;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export const RESOURCE_TYPES = ["skills", "rules", "agents", "hooks", "templates"];
|
|
2
|
+
export function validatePackageContract(content, resourceExists = () => true) {
|
|
3
|
+
const required = [
|
|
4
|
+
"schema",
|
|
5
|
+
"id",
|
|
6
|
+
"version",
|
|
7
|
+
"provider",
|
|
8
|
+
"source",
|
|
9
|
+
"resources",
|
|
10
|
+
"dependencies",
|
|
11
|
+
"permissions",
|
|
12
|
+
"provenance",
|
|
13
|
+
];
|
|
14
|
+
for (const field of required) {
|
|
15
|
+
if (!new RegExp(`^${field}:`, "m").test(content))
|
|
16
|
+
throw new Error(`Package field '${field}' is required.`);
|
|
17
|
+
}
|
|
18
|
+
const schema = scalar(content, "schema");
|
|
19
|
+
if (schema !== "1")
|
|
20
|
+
throw new Error("Package schema must be version 1.");
|
|
21
|
+
const resources = Object.fromEntries(RESOURCE_TYPES.map((type) => [type, parseResources(content, type)]));
|
|
22
|
+
for (const type of RESOURCE_TYPES) {
|
|
23
|
+
const section = resourceSection(content, type);
|
|
24
|
+
const entries = section.match(/-\s*\{[^}]*\}/g) ?? [];
|
|
25
|
+
if (entries.length !== resources[type].length)
|
|
26
|
+
throw new Error(`Package resource entries in '${type}' require id, version, and path.`);
|
|
27
|
+
for (const resource of resources[type]) {
|
|
28
|
+
if (!resourceExists(resource.path))
|
|
29
|
+
throw new Error(`Package resource path does not exist: ${resource.path}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (!resources.skills.length &&
|
|
33
|
+
!resources.rules.length &&
|
|
34
|
+
!resources.agents.length &&
|
|
35
|
+
!resources.hooks.length &&
|
|
36
|
+
!resources.templates.length)
|
|
37
|
+
throw new Error("Package resources must contain at least one resource.");
|
|
38
|
+
return {
|
|
39
|
+
schema: 1,
|
|
40
|
+
id: scalar(content, "id"),
|
|
41
|
+
version: scalar(content, "version"),
|
|
42
|
+
provider: scalar(content, "provider"),
|
|
43
|
+
source: scalar(content, "source"),
|
|
44
|
+
resources,
|
|
45
|
+
dependencies: list(content, "dependencies"),
|
|
46
|
+
permissions: list(content, "permissions"),
|
|
47
|
+
provenance: {
|
|
48
|
+
source: nestedScalar(content, "source") || scalar(content, "source"),
|
|
49
|
+
...(nestedScalar(content, "checksum") ? { checksum: nestedScalar(content, "checksum") } : {}),
|
|
50
|
+
},
|
|
51
|
+
policies: parsePolicies(content),
|
|
52
|
+
...(nestedScalar(content, "ai_workflow")
|
|
53
|
+
? { engines: { ai_workflow: nestedScalar(content, "ai_workflow") } }
|
|
54
|
+
: {}),
|
|
55
|
+
...(list(content, "targets").length ? { targets: list(content, "targets") } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function resourceSection(content, type) {
|
|
59
|
+
return content.match(new RegExp(`^ ${type}:\\n([\\s\\S]*?)(?=^\\w|\\s*$)`, "m"))?.[1] ?? "";
|
|
60
|
+
}
|
|
61
|
+
function parsePolicies(content) {
|
|
62
|
+
const section = content.match(/policies:\n((?:\s{2}- .*\n?)+)/)?.[1] ?? "";
|
|
63
|
+
return [...section.matchAll(/id:\s*([^,]+),\s*value:\s*([^ }]+)/g)].map((match) => ({
|
|
64
|
+
id: match[1].trim(),
|
|
65
|
+
value: match[2].trim(),
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
function scalar(content, key) {
|
|
69
|
+
return (content
|
|
70
|
+
.match(new RegExp(`^${key}:\\s*(.+)$`, "m"))?.[1]
|
|
71
|
+
?.replaceAll('"', "")
|
|
72
|
+
.trim() ?? "");
|
|
73
|
+
}
|
|
74
|
+
function nestedScalar(content, key) {
|
|
75
|
+
return (content
|
|
76
|
+
.match(new RegExp(`^\\s{2,}${key}:\\s*(.+)$`, "m"))?.[1]
|
|
77
|
+
?.replaceAll('"', "")
|
|
78
|
+
.trim() ?? "");
|
|
79
|
+
}
|
|
80
|
+
function list(content, key) {
|
|
81
|
+
const value = scalar(content, key);
|
|
82
|
+
return value === "[]"
|
|
83
|
+
? []
|
|
84
|
+
: value
|
|
85
|
+
.replace(/^\[|\]$/g, "")
|
|
86
|
+
.split(",")
|
|
87
|
+
.map((item) => item.trim())
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
function parseResources(content, type) {
|
|
91
|
+
const section = resourceSection(content, type);
|
|
92
|
+
return [...section.matchAll(/id:\s*([\w-]+), version:\s*([\d.]+), path:\s*([^ }]+)/g)].map((match) => ({ id: match[1], version: match[2], path: match[3] }));
|
|
93
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type PackageProvenance = {
|
|
2
|
+
checksum: string;
|
|
3
|
+
signature?: string;
|
|
4
|
+
signer?: string;
|
|
5
|
+
};
|
|
6
|
+
export type SignatureVerifier = (content: string, signature: string, signer?: string) => boolean;
|
|
7
|
+
export declare function checksumPackage(content: string): string;
|
|
8
|
+
export declare function verifyPackageProvenance(content: string, provenance: PackageProvenance, verifySignature?: SignatureVerifier): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export function checksumPackage(content) {
|
|
3
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
4
|
+
}
|
|
5
|
+
export function verifyPackageProvenance(content, provenance, verifySignature) {
|
|
6
|
+
const actual = checksumPackage(content);
|
|
7
|
+
if (actual !== provenance.checksum)
|
|
8
|
+
throw new Error(`Package checksum mismatch: expected ${provenance.checksum}, received ${actual}.`);
|
|
9
|
+
if (provenance.signature && !verifySignature)
|
|
10
|
+
throw new Error("Package signature requires a configured verifier.");
|
|
11
|
+
if (provenance.signature &&
|
|
12
|
+
verifySignature &&
|
|
13
|
+
!verifySignature(content, provenance.signature, provenance.signer))
|
|
14
|
+
throw new Error("Package signature verification failed.");
|
|
15
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PackageContract } from "./package-contract.js";
|
|
2
|
+
export type UpdateStatus = "update" | "current" | "incompatible" | "conflict";
|
|
3
|
+
export type UpdatePlan = {
|
|
4
|
+
status: UpdateStatus;
|
|
5
|
+
reason?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function compareVersions(left: string, right: string): number;
|
|
8
|
+
export declare function planPackageUpdate(current: {
|
|
9
|
+
id: string;
|
|
10
|
+
version: string;
|
|
11
|
+
} | undefined, candidate: PackageContract, target: string, lockedVersions?: Map<string, string>): UpdatePlan;
|