intentdna 1.7.4 → 1.8.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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/LICENSE +201 -0
- package/README.md +143 -194
- package/dist/assets/candidates.d.ts +124 -0
- package/dist/assets/candidates.js +1249 -0
- package/dist/assets/catalog.d.ts +101 -0
- package/dist/assets/catalog.js +311 -0
- package/dist/assets/evidence.d.ts +93 -0
- package/dist/assets/evidence.js +391 -0
- package/dist/assets/index.d.ts +4 -0
- package/dist/assets/index.js +4 -0
- package/dist/assets/metadata.d.ts +19 -0
- package/dist/assets/metadata.js +227 -0
- package/dist/cli/commands/assets.d.ts +65 -0
- package/dist/cli/commands/assets.js +2871 -0
- package/dist/cli/commands/auth.d.ts +322 -0
- package/dist/cli/commands/auth.js +1592 -0
- package/dist/cli/commands/cloud-auth-guidance.d.ts +2 -0
- package/dist/cli/commands/cloud-auth-guidance.js +4 -0
- package/dist/cli/commands/cloud.d.ts +1359 -0
- package/dist/cli/commands/cloud.js +8364 -0
- package/dist/cli/commands/evolve.js +18 -2
- package/dist/cli/commands/generate.js +25 -2
- package/dist/cli/commands/guard.d.ts +1 -1
- package/dist/cli/commands/guard.js +1 -1
- package/dist/cli/commands/guide.d.ts +15 -0
- package/dist/cli/commands/guide.js +1747 -0
- package/dist/cli/commands/init.d.ts +12 -0
- package/dist/cli/commands/init.js +95 -60
- package/dist/cli/commands/org.d.ts +16 -0
- package/dist/cli/commands/org.js +2393 -0
- package/dist/cli/commands/release.d.ts +59 -0
- package/dist/cli/commands/release.js +716 -0
- package/dist/cli/commands/report.d.ts +82 -0
- package/dist/cli/commands/report.js +1516 -0
- package/dist/cli/commands/run.d.ts +6 -1
- package/dist/cli/commands/run.js +95 -26
- package/dist/cli/commands/sync.d.ts +8 -1
- package/dist/cli/commands/sync.js +436 -141
- package/dist/cli/commands/templates.d.ts +64 -0
- package/dist/cli/commands/templates.js +2151 -1
- package/dist/cli/commands/workflow.d.ts +26 -0
- package/dist/cli/commands/workflow.js +3053 -0
- package/dist/cli/index.js +719 -42
- package/dist/compiler/cascade.js +4 -9
- package/dist/compiler/compile.js +1 -0
- package/dist/governance/evidence-capture-attribution.d.ts +12 -0
- package/dist/governance/evidence-capture-attribution.js +46 -0
- package/dist/governance/index.d.ts +2 -0
- package/dist/governance/index.js +1 -0
- package/dist/hooks/cli.d.ts +6 -0
- package/dist/hooks/cli.js +168 -12
- package/dist/hooks/enforce.d.ts +1 -0
- package/dist/hooks/protocol.d.ts +1 -1
- package/dist/hooks/protocol.js +1 -1
- package/dist/hooks/state.d.ts +41 -0
- package/dist/hooks/state.js +126 -2
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp/tools-state.js +2 -0
- package/dist/organization/index.d.ts +121 -0
- package/dist/organization/index.js +413 -0
- package/dist/runtime/claude-sync-target.d.ts +49 -0
- package/dist/runtime/claude-sync-target.js +280 -0
- package/dist/runtime/codex-adapter.d.ts +254 -0
- package/dist/runtime/codex-adapter.js +1204 -0
- package/dist/runtime/codex-sync-target.d.ts +33 -0
- package/dist/runtime/codex-sync-target.js +241 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/output-artifact-registry.d.ts +1 -1
- package/dist/runtime/output-artifact-registry.js +2 -0
- package/dist/runtime/settings-adapter.d.ts +1 -1
- package/dist/runtime/settings-adapter.js +1 -1
- package/dist/runtime/skill-adapter.d.ts +5 -1
- package/dist/runtime/skill-adapter.js +120 -34
- package/dist/runtime/sync-artifact-registry.d.ts +99 -0
- package/dist/runtime/sync-artifact-registry.js +194 -0
- package/dist/runtime/sync-target-plan.d.ts +45 -0
- package/dist/runtime/sync-target-plan.js +19 -0
- package/dist/schema/controller-registry.d.ts +8 -1
- package/dist/schema/controller-registry.js +8 -0
- package/dist/schema/types.d.ts +35 -23
- package/dist/schema/types.js +10 -1
- package/dist/schema/validate.js +22 -22
- package/dist/schema/validators/controllers.js +38 -20
- package/dist/schema/workflow-authoring-contract.d.ts +29 -0
- package/dist/schema/workflow-authoring-contract.js +109 -0
- package/dist/schema/yaml-parser.js +18 -0
- package/dist/templates/api-backend.dna.yaml +19 -0
- package/dist/templates/brainstorming-first.dna.yaml +19 -0
- package/dist/templates/code-cleanup.dna.yaml +19 -0
- package/dist/templates/code-review-pipeline.dna.yaml +18 -0
- package/dist/templates/documentation-writer.dna.yaml +19 -0
- package/dist/templates/flutter-behavior-lock.dna.yaml +19 -0
- package/dist/templates/flutter-refactoring-rescue.dna.yaml +19 -0
- package/dist/templates/flutter-rewrite-diagnosis-review.dna.yaml +210 -0
- package/dist/templates/flutter-rewrite-fix-review-loop.dna.yaml +260 -0
- package/dist/templates/flutter-rewrite-new.dna.yaml +506 -0
- package/dist/templates/flutter-rewrite.dna.yaml +20 -0
- package/dist/templates/frontend-quality.dna.yaml +19 -0
- package/dist/templates/full-pipeline.dna.yaml +20 -0
- package/dist/templates/marketplace.d.ts +92 -0
- package/dist/templates/marketplace.js +502 -0
- package/dist/templates/metadata.d.ts +25 -0
- package/dist/templates/metadata.js +136 -3
- package/dist/templates/mobile-dev.dna.yaml +19 -0
- package/dist/templates/multi-agent-handoff-coordination.dna.yaml +156 -0
- package/dist/templates/multi-perspective-review.dna.yaml +19 -0
- package/dist/templates/persistent-executor.dna.yaml +19 -0
- package/dist/templates/qa-loop.dna.yaml +18 -0
- package/dist/templates/release-evidence-gate.dna.yaml +131 -0
- package/dist/templates/requirements-gate.dna.yaml +19 -0
- package/dist/templates/research-orchestration.dna.yaml +19 -0
- package/dist/templates/root-cause-analysis.dna.yaml +19 -0
- package/dist/templates/safe-refactoring.dna.yaml +321 -0
- package/dist/templates/secure-dev.dna.yaml +19 -0
- package/dist/templates/subagent-parallel.dna.yaml +19 -0
- package/dist/templates/systematic-debugging.dna.yaml +19 -0
- package/dist/templates/tdd-strict.dna.yaml +19 -0
- package/dist/templates/ui-visual-regression-qa.dna.yaml +133 -0
- package/dist/templates/workflow-author.dna.yaml +302 -0
- package/dist/templates/workflow-blocked-scope-triage.dna.yaml +242 -0
- package/dist/templates/workflow-completion-audit.dna.yaml +254 -0
- package/dist/templates/workflow-cross-boundary-resource-approval.dna.yaml +249 -0
- package/dist/templates/workflow-evidence-review.dna.yaml +198 -0
- package/dist/templates/workflow-module-inventory.dna.yaml +257 -0
- package/dist/templates/workflow-runtime-smoke-review.dna.yaml +252 -0
- package/dist/templates/workflow-verification-command-routing.dna.yaml +278 -0
- package/dist/workflow/authoring-packet.d.ts +67 -0
- package/dist/workflow/authoring-packet.js +218 -0
- package/dist/workflow/draft.d.ts +159 -0
- package/dist/workflow/draft.js +800 -0
- package/dist/workflow/index.d.ts +3 -0
- package/dist/workflow/index.js +3 -0
- package/dist/workflow/suggestion-review.d.ts +65 -0
- package/dist/workflow/suggestion-review.js +245 -0
- package/package.json +1 -1
- package/spec/README.md +15 -48
- package/spec/codex-adapter-contract.md +176 -0
- package/spec/sync-target-plan.md +158 -0
- package/spec/workflow-capability-ir.md +153 -0
- package/spec/agent-dispatch-fix.md +0 -116
- package/spec/control-plane-convergence-handoff-2026-04-24.md +0 -432
- package/spec/flutter-rewrite-template-optimization.md +0 -301
- package/spec/foundation-hardening.md +0 -178
- package/spec/hooks-infra-harness-hardening.md +0 -741
- package/spec/multi-config.md +0 -172
- package/spec/parallel-isolation.md +0 -268
- package/spec/template-version-namespace-fix.md +0 -653
- package/spec/workflow-pipeline.md +0 -101
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { SyncOutputArtifactId } from "./output-artifact-registry.js";
|
|
2
|
+
import type { PlannedArtifact, SyncTarget } from "./sync-target-plan.js";
|
|
3
|
+
export declare const SYNC_ARTIFACT_REGISTRY_PATH = ".dna/sync/registry.json";
|
|
4
|
+
export declare const SYNC_ARTIFACT_REGISTRY_SCHEMA = "intentdna.sync_artifact_registry.v1";
|
|
5
|
+
export type SyncArtifactOwnership = PlannedArtifact["ownership"] | "unknown";
|
|
6
|
+
export type SyncArtifactStatus = "ok" | "missing" | "drifted" | "untracked";
|
|
7
|
+
export interface SyncArtifactRegistryTemplate {
|
|
8
|
+
sourceDnaIds: string[];
|
|
9
|
+
configFiles: string[];
|
|
10
|
+
compiledIrHash?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SyncArtifactRegistryEntry {
|
|
13
|
+
artifact: SyncOutputArtifactId;
|
|
14
|
+
kind: string;
|
|
15
|
+
path: string;
|
|
16
|
+
relativePath: string;
|
|
17
|
+
target: SyncTarget | "markdown" | "generic";
|
|
18
|
+
adapter?: SyncTarget;
|
|
19
|
+
installMode?: string;
|
|
20
|
+
ownership: SyncArtifactOwnership;
|
|
21
|
+
trackedByLock: boolean;
|
|
22
|
+
sha256?: string;
|
|
23
|
+
missingAtSync?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface SyncArtifactRegistry {
|
|
26
|
+
schema: typeof SYNC_ARTIFACT_REGISTRY_SCHEMA;
|
|
27
|
+
syncedAt: string;
|
|
28
|
+
projectDir: string;
|
|
29
|
+
dnaVersion?: string;
|
|
30
|
+
mode: "write";
|
|
31
|
+
template: SyncArtifactRegistryTemplate;
|
|
32
|
+
artifacts: SyncArtifactRegistryEntry[];
|
|
33
|
+
}
|
|
34
|
+
export interface SyncArtifactRegistryInputEntry {
|
|
35
|
+
artifact: SyncOutputArtifactId;
|
|
36
|
+
path: string;
|
|
37
|
+
kind?: string;
|
|
38
|
+
target?: SyncArtifactRegistryEntry["target"];
|
|
39
|
+
adapter?: SyncTarget;
|
|
40
|
+
installMode?: string;
|
|
41
|
+
ownership?: SyncArtifactOwnership;
|
|
42
|
+
trackedByLock?: boolean;
|
|
43
|
+
}
|
|
44
|
+
export interface WriteSyncArtifactRegistryOptions {
|
|
45
|
+
projectDir: string;
|
|
46
|
+
dnaVersion?: string;
|
|
47
|
+
template: SyncArtifactRegistryTemplate;
|
|
48
|
+
artifacts: SyncArtifactRegistryInputEntry[];
|
|
49
|
+
}
|
|
50
|
+
export interface SyncArtifactInspection extends SyncArtifactRegistryEntry {
|
|
51
|
+
status: SyncArtifactStatus;
|
|
52
|
+
currentSha256?: string;
|
|
53
|
+
reason: string;
|
|
54
|
+
}
|
|
55
|
+
export interface SyncArtifactRegistryInspection {
|
|
56
|
+
registry: SyncArtifactRegistry | null;
|
|
57
|
+
registryPath: string;
|
|
58
|
+
artifacts: SyncArtifactInspection[];
|
|
59
|
+
totals: {
|
|
60
|
+
artifacts: number;
|
|
61
|
+
ok: number;
|
|
62
|
+
missing: number;
|
|
63
|
+
drifted: number;
|
|
64
|
+
untracked: number;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
export interface RemoveSyncArtifactsOptions {
|
|
68
|
+
projectDir?: string;
|
|
69
|
+
dryRun?: boolean;
|
|
70
|
+
}
|
|
71
|
+
export interface RemoveSyncArtifactsResult {
|
|
72
|
+
schema_version: "intentdna.sync_remove_result.v1";
|
|
73
|
+
mode: "sync_registry_remove";
|
|
74
|
+
registryPath: string;
|
|
75
|
+
removed: string[];
|
|
76
|
+
skipped: Array<{
|
|
77
|
+
path: string;
|
|
78
|
+
reason: string;
|
|
79
|
+
}>;
|
|
80
|
+
preservation: {
|
|
81
|
+
note: string;
|
|
82
|
+
kept: Array<{
|
|
83
|
+
path: string;
|
|
84
|
+
reason: string;
|
|
85
|
+
explanation: string;
|
|
86
|
+
}>;
|
|
87
|
+
nextCommands: string[];
|
|
88
|
+
};
|
|
89
|
+
dryRun: boolean;
|
|
90
|
+
totals: {
|
|
91
|
+
removable: number;
|
|
92
|
+
skipped: number;
|
|
93
|
+
};
|
|
94
|
+
boundary: string;
|
|
95
|
+
}
|
|
96
|
+
export declare function writeSyncArtifactRegistry(options: WriteSyncArtifactRegistryOptions): Promise<SyncArtifactRegistry>;
|
|
97
|
+
export declare function readSyncArtifactRegistry(projectDir?: string): Promise<SyncArtifactRegistry | null>;
|
|
98
|
+
export declare function inspectSyncArtifactRegistry(projectDir?: string): Promise<SyncArtifactRegistryInspection>;
|
|
99
|
+
export declare function removeSyncArtifacts(options?: RemoveSyncArtifactsOptions): Promise<RemoveSyncArtifactsResult>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, realpath, rm, stat, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, relative, resolve } from "node:path";
|
|
4
|
+
export const SYNC_ARTIFACT_REGISTRY_PATH = ".dna/sync/registry.json";
|
|
5
|
+
export const SYNC_ARTIFACT_REGISTRY_SCHEMA = "intentdna.sync_artifact_registry.v1";
|
|
6
|
+
function fileSha256(content) {
|
|
7
|
+
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
8
|
+
}
|
|
9
|
+
async function computeFileHash(path) {
|
|
10
|
+
try {
|
|
11
|
+
return fileSha256(await readFile(path, "utf-8"));
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function registryPath(projectDir) {
|
|
18
|
+
return resolve(projectDir, SYNC_ARTIFACT_REGISTRY_PATH);
|
|
19
|
+
}
|
|
20
|
+
async function canonicalPath(path) {
|
|
21
|
+
return realpath(path).catch(() => resolve(path));
|
|
22
|
+
}
|
|
23
|
+
async function normalizeEntry(projectDir, entry) {
|
|
24
|
+
const canonicalProjectDir = await canonicalPath(projectDir);
|
|
25
|
+
const absolutePath = resolve(projectDir, entry.path);
|
|
26
|
+
const canonicalArtifactPath = await canonicalPath(absolutePath);
|
|
27
|
+
return {
|
|
28
|
+
artifact: entry.artifact,
|
|
29
|
+
kind: entry.kind ?? entry.artifact,
|
|
30
|
+
path: canonicalArtifactPath,
|
|
31
|
+
relativePath: relative(canonicalProjectDir, canonicalArtifactPath) || ".",
|
|
32
|
+
target: entry.target ?? entry.adapter ?? "generic",
|
|
33
|
+
adapter: entry.adapter,
|
|
34
|
+
installMode: entry.installMode,
|
|
35
|
+
ownership: entry.ownership ?? "unknown",
|
|
36
|
+
trackedByLock: entry.trackedByLock ?? true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function uniqueArtifacts(artifacts) {
|
|
40
|
+
const byKey = new Map();
|
|
41
|
+
for (const artifact of artifacts) {
|
|
42
|
+
byKey.set(`${artifact.artifact}:${artifact.path}:${artifact.kind}`, artifact);
|
|
43
|
+
}
|
|
44
|
+
return [...byKey.values()].sort((a, b) => a.target.localeCompare(b.target) ||
|
|
45
|
+
a.relativePath.localeCompare(b.relativePath) ||
|
|
46
|
+
a.kind.localeCompare(b.kind));
|
|
47
|
+
}
|
|
48
|
+
export async function writeSyncArtifactRegistry(options) {
|
|
49
|
+
const projectDir = await canonicalPath(options.projectDir);
|
|
50
|
+
const artifacts = uniqueArtifacts(await Promise.all(options.artifacts.map((entry) => normalizeEntry(projectDir, entry))));
|
|
51
|
+
const withHashes = [];
|
|
52
|
+
for (const artifact of artifacts) {
|
|
53
|
+
const sha256 = await computeFileHash(artifact.path);
|
|
54
|
+
withHashes.push(sha256 ? { ...artifact, sha256 } : { ...artifact, missingAtSync: true });
|
|
55
|
+
}
|
|
56
|
+
const registry = {
|
|
57
|
+
schema: SYNC_ARTIFACT_REGISTRY_SCHEMA,
|
|
58
|
+
syncedAt: new Date().toISOString(),
|
|
59
|
+
projectDir,
|
|
60
|
+
dnaVersion: options.dnaVersion,
|
|
61
|
+
mode: "write",
|
|
62
|
+
template: options.template,
|
|
63
|
+
artifacts: withHashes,
|
|
64
|
+
};
|
|
65
|
+
const path = registryPath(options.projectDir);
|
|
66
|
+
await mkdir(dirname(path), { recursive: true });
|
|
67
|
+
await writeFile(path, JSON.stringify(registry, null, 2) + "\n", "utf-8");
|
|
68
|
+
return registry;
|
|
69
|
+
}
|
|
70
|
+
export async function readSyncArtifactRegistry(projectDir = process.cwd()) {
|
|
71
|
+
try {
|
|
72
|
+
const raw = await readFile(registryPath(projectDir), "utf-8");
|
|
73
|
+
const parsed = JSON.parse(raw);
|
|
74
|
+
if (parsed.schema !== SYNC_ARTIFACT_REGISTRY_SCHEMA || !Array.isArray(parsed.artifacts))
|
|
75
|
+
return null;
|
|
76
|
+
return parsed;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function inspectEntry(entry) {
|
|
83
|
+
if (!entry.sha256) {
|
|
84
|
+
const exists = await stat(entry.path).then(() => true, () => false);
|
|
85
|
+
return {
|
|
86
|
+
...entry,
|
|
87
|
+
status: exists ? "untracked" : "missing",
|
|
88
|
+
reason: exists ? "No sync-time hash recorded for this artifact." : "Artifact did not exist when inspected.",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const currentSha256 = await computeFileHash(entry.path);
|
|
92
|
+
if (!currentSha256) {
|
|
93
|
+
return { ...entry, status: "missing", reason: "Artifact path is missing." };
|
|
94
|
+
}
|
|
95
|
+
if (currentSha256 !== entry.sha256) {
|
|
96
|
+
return { ...entry, status: "drifted", currentSha256, reason: "Artifact hash differs from last sync." };
|
|
97
|
+
}
|
|
98
|
+
return { ...entry, status: "ok", currentSha256, reason: "Artifact matches last sync." };
|
|
99
|
+
}
|
|
100
|
+
export async function inspectSyncArtifactRegistry(projectDir = process.cwd()) {
|
|
101
|
+
const registry = await readSyncArtifactRegistry(projectDir);
|
|
102
|
+
const artifacts = registry ? await Promise.all(registry.artifacts.map(inspectEntry)) : [];
|
|
103
|
+
const totals = {
|
|
104
|
+
artifacts: artifacts.length,
|
|
105
|
+
ok: artifacts.filter((entry) => entry.status === "ok").length,
|
|
106
|
+
missing: artifacts.filter((entry) => entry.status === "missing").length,
|
|
107
|
+
drifted: artifacts.filter((entry) => entry.status === "drifted").length,
|
|
108
|
+
untracked: artifacts.filter((entry) => entry.status === "untracked").length,
|
|
109
|
+
};
|
|
110
|
+
return { registry, registryPath: registryPath(projectDir), artifacts, totals };
|
|
111
|
+
}
|
|
112
|
+
function isSafeToRemove(entry) {
|
|
113
|
+
if (entry.status !== "ok")
|
|
114
|
+
return false;
|
|
115
|
+
if (!entry.sha256 || entry.currentSha256 !== entry.sha256)
|
|
116
|
+
return false;
|
|
117
|
+
return entry.ownership === "intentdna_owned" || entry.ownership === "diagnostic";
|
|
118
|
+
}
|
|
119
|
+
function removalSkipExplanation(entry) {
|
|
120
|
+
if (entry.status === "drifted") {
|
|
121
|
+
return "Preserved because the file changed after the last sync; review the diff or rerun dna sync before cleanup.";
|
|
122
|
+
}
|
|
123
|
+
if (entry.status === "missing") {
|
|
124
|
+
return "No file was deleted because the registry path is already missing.";
|
|
125
|
+
}
|
|
126
|
+
if (entry.status === "untracked") {
|
|
127
|
+
return "Preserved because no sync-time hash proves IntentDNA still owns the current file.";
|
|
128
|
+
}
|
|
129
|
+
if (entry.ownership === "merge_preserve_user") {
|
|
130
|
+
return "Preserved because the artifact is a merge-preserve user configuration surface.";
|
|
131
|
+
}
|
|
132
|
+
if (entry.ownership === "owned_block") {
|
|
133
|
+
return "Preserved because IntentDNA owns only an injected block inside this user file.";
|
|
134
|
+
}
|
|
135
|
+
if (entry.ownership === "unknown") {
|
|
136
|
+
return "Preserved because ownership is unknown.";
|
|
137
|
+
}
|
|
138
|
+
return "Preserved because this artifact is outside the safe remove set.";
|
|
139
|
+
}
|
|
140
|
+
export async function removeSyncArtifacts(options = {}) {
|
|
141
|
+
const projectDir = options.projectDir ?? process.cwd();
|
|
142
|
+
const inspection = await inspectSyncArtifactRegistry(projectDir);
|
|
143
|
+
const removed = [];
|
|
144
|
+
const skipped = [];
|
|
145
|
+
const kept = [];
|
|
146
|
+
for (const artifact of inspection.artifacts) {
|
|
147
|
+
if (!isSafeToRemove(artifact)) {
|
|
148
|
+
const reason = `${artifact.ownership}/${artifact.status}`;
|
|
149
|
+
skipped.push({ path: artifact.path, reason });
|
|
150
|
+
kept.push({ path: artifact.path, reason, explanation: removalSkipExplanation(artifact) });
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (!options.dryRun)
|
|
154
|
+
await rm(artifact.path, { recursive: true, force: true });
|
|
155
|
+
removed.push(artifact.path);
|
|
156
|
+
}
|
|
157
|
+
if (inspection.registry && !options.dryRun && skipped.length === 0) {
|
|
158
|
+
await rm(inspection.registryPath, { force: true });
|
|
159
|
+
removed.push(inspection.registryPath);
|
|
160
|
+
}
|
|
161
|
+
else if (inspection.registry && !options.dryRun) {
|
|
162
|
+
const keptPaths = new Set(skipped.map((entry) => entry.path));
|
|
163
|
+
const preservedRegistry = {
|
|
164
|
+
...inspection.registry,
|
|
165
|
+
artifacts: inspection.registry.artifacts.filter((artifact) => keptPaths.has(artifact.path)),
|
|
166
|
+
};
|
|
167
|
+
await writeFile(inspection.registryPath, JSON.stringify(preservedRegistry, null, 2) + "\n", "utf-8");
|
|
168
|
+
}
|
|
169
|
+
else if (inspection.registry && skipped.length === 0) {
|
|
170
|
+
removed.push(inspection.registryPath);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
schema_version: "intentdna.sync_remove_result.v1",
|
|
174
|
+
mode: "sync_registry_remove",
|
|
175
|
+
registryPath: inspection.registryPath,
|
|
176
|
+
removed,
|
|
177
|
+
skipped,
|
|
178
|
+
preservation: {
|
|
179
|
+
note: "Safe remove preserves user-owned, merge-preserve, owned-block, drifted, missing, untracked, and unknown-ownership registry entries.",
|
|
180
|
+
kept,
|
|
181
|
+
nextCommands: [
|
|
182
|
+
"dna sync status",
|
|
183
|
+
"dna sync doctor",
|
|
184
|
+
"dna sync remove --dry-run",
|
|
185
|
+
],
|
|
186
|
+
},
|
|
187
|
+
dryRun: options.dryRun === true,
|
|
188
|
+
totals: {
|
|
189
|
+
removable: removed.length,
|
|
190
|
+
skipped: skipped.length,
|
|
191
|
+
},
|
|
192
|
+
boundary: "remove only deletes hash-matching IntentDNA-owned/diagnostic artifacts from the sync registry; merge-preserve/user-owned files are skipped",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { SyncOutputArtifactId } from "./output-artifact-registry.js";
|
|
2
|
+
export type SyncTarget = "claude_code" | "codex_cli";
|
|
3
|
+
export type SyncTargetMode = "write" | "remove" | "dry_run";
|
|
4
|
+
export type CapabilitySupportStatus = "supported" | "partial" | "unknown" | "unsupported";
|
|
5
|
+
export type EvidenceClassification = "enterprise_evidence" | "diagnostic_only" | "unsupported";
|
|
6
|
+
export interface PlannedArtifact {
|
|
7
|
+
kind: string;
|
|
8
|
+
path: string;
|
|
9
|
+
ownership: "intentdna_owned" | "owned_block" | "merge_preserve_user" | "diagnostic";
|
|
10
|
+
trackedByLock: boolean;
|
|
11
|
+
outputArtifact?: SyncOutputArtifactId;
|
|
12
|
+
}
|
|
13
|
+
export interface CapabilityReportEntry {
|
|
14
|
+
status: CapabilitySupportStatus;
|
|
15
|
+
surfaces: string[];
|
|
16
|
+
evidenceRefs: string[];
|
|
17
|
+
notes: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface AdapterCapabilityReport {
|
|
20
|
+
adapter: SyncTarget;
|
|
21
|
+
runtime?: string;
|
|
22
|
+
capabilities: Record<string, CapabilityReportEntry>;
|
|
23
|
+
unsupported: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface EvidencePlan {
|
|
26
|
+
primary: "RuntimeDecisionEvent" | "none";
|
|
27
|
+
diagnosticArtifacts: string[];
|
|
28
|
+
classification: EvidenceClassification;
|
|
29
|
+
notes: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface SyncTargetPlan {
|
|
32
|
+
target: SyncTarget;
|
|
33
|
+
mode?: SyncTargetMode;
|
|
34
|
+
plannedArtifacts: PlannedArtifact[];
|
|
35
|
+
capabilityReport?: AdapterCapabilityReport;
|
|
36
|
+
evidencePlan: EvidencePlan;
|
|
37
|
+
warnings: string[];
|
|
38
|
+
unsupported: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface SyncTargetTrackedOutput {
|
|
41
|
+
artifact: SyncOutputArtifactId;
|
|
42
|
+
path: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function trackedOutputsForSyncTargetPlan(plan: SyncTargetPlan): SyncTargetTrackedOutput[];
|
|
45
|
+
export declare function formatSyncTargetPlanDiagnostics(plan: SyncTargetPlan): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function trackedOutputsForSyncTargetPlan(plan) {
|
|
2
|
+
return plan.plannedArtifacts
|
|
3
|
+
.filter((artifact) => typeof artifact.outputArtifact === "string" && artifact.outputArtifact.length > 0)
|
|
4
|
+
.map((artifact) => ({ artifact: artifact.outputArtifact, path: artifact.path }));
|
|
5
|
+
}
|
|
6
|
+
export function formatSyncTargetPlanDiagnostics(plan) {
|
|
7
|
+
const lines = [];
|
|
8
|
+
if (plan.warnings.length > 0) {
|
|
9
|
+
lines.push(`${plan.target} warnings:`);
|
|
10
|
+
for (const warning of plan.warnings)
|
|
11
|
+
lines.push(` - ${warning}`);
|
|
12
|
+
}
|
|
13
|
+
if (plan.unsupported.length > 0) {
|
|
14
|
+
lines.push(`${plan.target} unsupported gaps:`);
|
|
15
|
+
for (const gap of plan.unsupported)
|
|
16
|
+
lines.push(` - ${gap}`);
|
|
17
|
+
}
|
|
18
|
+
return lines.length > 0 ? `${lines.join("\n")}\n` : "";
|
|
19
|
+
}
|
|
@@ -5,7 +5,7 @@ export interface ControllerKindMetadata {
|
|
|
5
5
|
kind: ControllerKind;
|
|
6
6
|
label: string;
|
|
7
7
|
supportsSkillGeneration: boolean;
|
|
8
|
-
requiredRoleKeys: readonly
|
|
8
|
+
requiredRoleKeys: readonly string[];
|
|
9
9
|
diagnosisRoleKeys: readonly ControllerRoleKey[];
|
|
10
10
|
fixRoleKeys: readonly ControllerRoleKey[];
|
|
11
11
|
}
|
|
@@ -23,6 +23,13 @@ export declare const CONTROLLER_KIND_REGISTRY: readonly [{
|
|
|
23
23
|
readonly requiredRoleKeys: readonly ["analyzer", "analysis_reviewer", "surgeon", "fix_reviewer", "test_runner"];
|
|
24
24
|
readonly diagnosisRoleKeys: readonly ["analyzer", "analysis_reviewer"];
|
|
25
25
|
readonly fixRoleKeys: readonly ["surgeon", "fix_reviewer", "test_runner"];
|
|
26
|
+
}, {
|
|
27
|
+
readonly kind: "sequential";
|
|
28
|
+
readonly label: "Sequential workflow gate";
|
|
29
|
+
readonly supportsSkillGeneration: false;
|
|
30
|
+
readonly requiredRoleKeys: readonly [];
|
|
31
|
+
readonly diagnosisRoleKeys: readonly [];
|
|
32
|
+
readonly fixRoleKeys: readonly [];
|
|
26
33
|
}];
|
|
27
34
|
export declare function getControllerKindMetadata(kind: unknown): ControllerKindMetadata | undefined;
|
|
28
35
|
export declare function isSupportedControllerKind(kind: unknown): kind is ControllerKind;
|
|
@@ -22,6 +22,14 @@ export const CONTROLLER_KIND_REGISTRY = [
|
|
|
22
22
|
diagnosisRoleKeys: ["analyzer", "analysis_reviewer"],
|
|
23
23
|
fixRoleKeys: ["surgeon", "fix_reviewer", "test_runner"],
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
kind: "sequential",
|
|
27
|
+
label: "Sequential workflow gate",
|
|
28
|
+
supportsSkillGeneration: false,
|
|
29
|
+
requiredRoleKeys: [],
|
|
30
|
+
diagnosisRoleKeys: [],
|
|
31
|
+
fixRoleKeys: [],
|
|
32
|
+
},
|
|
25
33
|
];
|
|
26
34
|
const CONTROLLER_KIND_BY_ID = new Map(CONTROLLER_KIND_REGISTRY.map((entry) => [entry.kind, entry]));
|
|
27
35
|
export function getControllerKindMetadata(kind) {
|
package/dist/schema/types.d.ts
CHANGED
|
@@ -96,7 +96,8 @@ export interface VerifierCommandPolicy {
|
|
|
96
96
|
allow_builtin_asserts?: string[];
|
|
97
97
|
}
|
|
98
98
|
/** Handoff artifact type — what kind of artifact is passed between steps */
|
|
99
|
-
export
|
|
99
|
+
export declare const HANDOFF_TYPE_VALUES: readonly ["file", "directory", "test_result", "git_commit", "summary", "state"];
|
|
100
|
+
export type HandoffType = typeof HANDOFF_TYPE_VALUES[number];
|
|
100
101
|
/** A single artifact consumed or produced by a workflow step */
|
|
101
102
|
export interface HandoffArtifact {
|
|
102
103
|
type: HandoffType;
|
|
@@ -142,14 +143,18 @@ export interface StepHandoff {
|
|
|
142
143
|
export interface TransitionDef {
|
|
143
144
|
from: string;
|
|
144
145
|
to: string;
|
|
145
|
-
condition:
|
|
146
|
+
condition: TransitionCondition;
|
|
146
147
|
}
|
|
148
|
+
export declare const TRANSITION_CONDITION_VALUES: readonly ["pass", "fail", "always", "error"];
|
|
149
|
+
export type TransitionCondition = typeof TRANSITION_CONDITION_VALUES[number];
|
|
147
150
|
/** Retry policy for the workflow */
|
|
148
151
|
export interface RetryPolicy {
|
|
149
152
|
max_retries: number;
|
|
150
153
|
retry_from?: string;
|
|
151
|
-
backoff?:
|
|
154
|
+
backoff?: RetryBackoff;
|
|
152
155
|
}
|
|
156
|
+
export declare const RETRY_BACKOFF_VALUES: readonly ["none", "linear", "exponential"];
|
|
157
|
+
export type RetryBackoff = typeof RETRY_BACKOFF_VALUES[number];
|
|
153
158
|
/** Predefined checkpoint assertion types — compiler knows how to translate each to shell */
|
|
154
159
|
export type CheckpointAssertType = "clean_working_tree" | "no_test_regression" | "lint_passing" | "build_passing";
|
|
155
160
|
/** Step checkpoint — condition that must be met before transitioning to next step */
|
|
@@ -157,8 +162,18 @@ export interface StepCheckpoint {
|
|
|
157
162
|
assert: CheckpointAssertType | string;
|
|
158
163
|
command?: string;
|
|
159
164
|
message: string;
|
|
160
|
-
action?:
|
|
161
|
-
}
|
|
165
|
+
action?: CheckpointAction;
|
|
166
|
+
}
|
|
167
|
+
export declare const CHECKPOINT_ACTION_VALUES: readonly ["block", "warn"];
|
|
168
|
+
export type CheckpointAction = typeof CHECKPOINT_ACTION_VALUES[number];
|
|
169
|
+
export declare const WORKFLOW_ISOLATION_VALUES: readonly ["none", "worktree", "auto"];
|
|
170
|
+
export type WorkflowIsolation = typeof WORKFLOW_ISOLATION_VALUES[number];
|
|
171
|
+
export declare const WORKFLOW_STEP_FAILURE_VALUES: readonly ["retry_with_feedback", "handoff", "skip"];
|
|
172
|
+
export type WorkflowStepFailureAction = typeof WORKFLOW_STEP_FAILURE_VALUES[number];
|
|
173
|
+
export declare const WORKFLOW_HANDOFF_EXHAUSTED_VALUES: readonly ["skip", "stop"];
|
|
174
|
+
export type WorkflowHandoffExhaustedAction = typeof WORKFLOW_HANDOFF_EXHAUSTED_VALUES[number];
|
|
175
|
+
export declare const WORKFLOW_MERGE_STRATEGY_VALUES: readonly ["escalate"];
|
|
176
|
+
export type WorkflowMergeStrategy = typeof WORKFLOW_MERGE_STRATEGY_VALUES[number];
|
|
162
177
|
/** Completion check for a workflow step */
|
|
163
178
|
export interface CompletionCheck {
|
|
164
179
|
file_exists?: string;
|
|
@@ -181,17 +196,17 @@ export interface WorkflowStepDef {
|
|
|
181
196
|
completion?: CompletionCheck[];
|
|
182
197
|
checkpoints?: StepCheckpoint[];
|
|
183
198
|
handoff?: StepHandoff;
|
|
184
|
-
isolation?:
|
|
199
|
+
isolation?: WorkflowIsolation;
|
|
185
200
|
enforce?: {
|
|
186
201
|
read_only?: boolean;
|
|
187
202
|
relax_after_iteration?: number;
|
|
188
203
|
additional_write_paths?: string[];
|
|
189
204
|
};
|
|
190
205
|
max_attempts?: number;
|
|
191
|
-
on_fail?:
|
|
206
|
+
on_fail?: WorkflowStepFailureAction;
|
|
192
207
|
handoff_to?: string;
|
|
193
208
|
max_handoffs?: number;
|
|
194
|
-
on_handoff_exhausted?:
|
|
209
|
+
on_handoff_exhausted?: WorkflowHandoffExhaustedAction;
|
|
195
210
|
blocked_items_path?: string;
|
|
196
211
|
}
|
|
197
212
|
/** Top-level workflow definition */
|
|
@@ -204,10 +219,10 @@ export interface WorkflowDef {
|
|
|
204
219
|
max_rounds?: number;
|
|
205
220
|
produces?: HandoffArtifact[];
|
|
206
221
|
consumes?: HandoffArtifact[];
|
|
207
|
-
default_isolation?:
|
|
208
|
-
merge_strategy?:
|
|
222
|
+
default_isolation?: WorkflowIsolation;
|
|
223
|
+
merge_strategy?: WorkflowMergeStrategy;
|
|
209
224
|
}
|
|
210
|
-
export type ControllerKind = "fix_loop" | "flutter_rewrite_fix_loop";
|
|
225
|
+
export type ControllerKind = "fix_loop" | "flutter_rewrite_fix_loop" | "sequential";
|
|
211
226
|
export interface ControllerPolicyDef {
|
|
212
227
|
diagnosis_reuse_criteria?: string[];
|
|
213
228
|
progress_provenance_checks?: string[];
|
|
@@ -223,18 +238,13 @@ export interface ControllerDef {
|
|
|
223
238
|
kind: ControllerKind;
|
|
224
239
|
max_rounds: number;
|
|
225
240
|
progress_artifact_path: string;
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
analysis_reviewer: string;
|
|
234
|
-
surgeon: string;
|
|
235
|
-
fix_reviewer: string;
|
|
236
|
-
test_runner: string;
|
|
237
|
-
};
|
|
241
|
+
workflow?: string;
|
|
242
|
+
diagnosis_workflow?: string;
|
|
243
|
+
fix_workflow?: string;
|
|
244
|
+
diagnosis_artifact_path?: string;
|
|
245
|
+
diagnosis_review_artifact_path?: string;
|
|
246
|
+
review_artifact_path?: string;
|
|
247
|
+
roles: Record<string, string>;
|
|
238
248
|
stop_format: "configured_stop_report" | "zh_three_part";
|
|
239
249
|
policy?: ControllerPolicyDef;
|
|
240
250
|
}
|
|
@@ -479,6 +489,7 @@ export interface StepEnforceRule {
|
|
|
479
489
|
export interface WorkflowIR {
|
|
480
490
|
workflow_name: string;
|
|
481
491
|
namespace: string;
|
|
492
|
+
step_ids?: string[];
|
|
482
493
|
step_checkpoints: StepCheckpointIR[];
|
|
483
494
|
active_roles: string[];
|
|
484
495
|
handoff_chain: HandoffChainEntry[];
|
|
@@ -502,6 +513,7 @@ export interface CompletedArtifactEntry {
|
|
|
502
513
|
/** Runtime workflow state written to .dna/state/workflow.json */
|
|
503
514
|
export interface WorkflowState {
|
|
504
515
|
active: boolean;
|
|
516
|
+
workflow_asset?: string;
|
|
505
517
|
workflow: string;
|
|
506
518
|
current_step: string;
|
|
507
519
|
current_role: string;
|
package/dist/schema/types.js
CHANGED
|
@@ -7,4 +7,13 @@
|
|
|
7
7
|
* Layer 3: Regulatory Regions (context-dependent expression)
|
|
8
8
|
* Layer 4: Epigenetic Markers (experience-driven modulation)
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
// ── Workflow System ─────────────────────────────────────────
|
|
11
|
+
/** Handoff artifact type — what kind of artifact is passed between steps */
|
|
12
|
+
export const HANDOFF_TYPE_VALUES = ["file", "directory", "test_result", "git_commit", "summary", "state"];
|
|
13
|
+
export const TRANSITION_CONDITION_VALUES = ["pass", "fail", "always", "error"];
|
|
14
|
+
export const RETRY_BACKOFF_VALUES = ["none", "linear", "exponential"];
|
|
15
|
+
export const CHECKPOINT_ACTION_VALUES = ["block", "warn"];
|
|
16
|
+
export const WORKFLOW_ISOLATION_VALUES = ["none", "worktree", "auto"];
|
|
17
|
+
export const WORKFLOW_STEP_FAILURE_VALUES = ["retry_with_feedback", "handoff", "skip"];
|
|
18
|
+
export const WORKFLOW_HANDOFF_EXHAUSTED_VALUES = ["skip", "stop"];
|
|
19
|
+
export const WORKFLOW_MERGE_STRATEGY_VALUES = ["escalate"];
|
package/dist/schema/validate.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - No circular inheritance
|
|
9
9
|
*/
|
|
10
10
|
import { isAbsolute } from "node:path";
|
|
11
|
+
import { CHECKPOINT_ACTION_VALUES, HANDOFF_TYPE_VALUES, RETRY_BACKOFF_VALUES, TRANSITION_CONDITION_VALUES, WORKFLOW_HANDOFF_EXHAUSTED_VALUES, WORKFLOW_ISOLATION_VALUES, WORKFLOW_MERGE_STRATEGY_VALUES, WORKFLOW_STEP_FAILURE_VALUES, } from "./types.js";
|
|
11
12
|
import { validateController } from "./validators/controllers.js";
|
|
12
13
|
const SAFE_GENERATED_IDENTIFIER = /^[A-Za-z0-9_-]+$/;
|
|
13
14
|
const SAFE_GENERATED_DISPLAY_NAME = /^[A-Za-z0-9][A-Za-z0-9 _-]*$/;
|
|
@@ -503,25 +504,22 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
|
|
|
503
504
|
}
|
|
504
505
|
// on_fail: must be a valid enum value
|
|
505
506
|
if (step.on_fail !== undefined) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
errors.push({ path: `${stepPath}.on_fail`, message: `must be one of: ${validOnFail.join(", ")}` });
|
|
507
|
+
if (!WORKFLOW_STEP_FAILURE_VALUES.includes(step.on_fail)) {
|
|
508
|
+
errors.push({ path: `${stepPath}.on_fail`, message: `must be one of: ${WORKFLOW_STEP_FAILURE_VALUES.join(", ")}` });
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
511
|
// on_handoff_exhausted: must be a valid enum value
|
|
512
512
|
if (step.on_handoff_exhausted !== undefined) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
errors.push({ path: `${stepPath}.on_handoff_exhausted`, message: `must be one of: ${validExhausted.join(", ")}` });
|
|
513
|
+
if (!WORKFLOW_HANDOFF_EXHAUSTED_VALUES.includes(step.on_handoff_exhausted)) {
|
|
514
|
+
errors.push({ path: `${stepPath}.on_handoff_exhausted`, message: `must be one of: ${WORKFLOW_HANDOFF_EXHAUSTED_VALUES.join(", ")}` });
|
|
516
515
|
}
|
|
517
516
|
}
|
|
518
517
|
// isolation: must be a valid enum value
|
|
519
518
|
if (step.isolation !== undefined) {
|
|
520
|
-
|
|
521
|
-
if (!validIsolation.includes(step.isolation)) {
|
|
519
|
+
if (!WORKFLOW_ISOLATION_VALUES.includes(step.isolation)) {
|
|
522
520
|
errors.push({
|
|
523
521
|
path: `${stepPath}.isolation`,
|
|
524
|
-
message: `must be one of: ${
|
|
522
|
+
message: `must be one of: ${WORKFLOW_ISOLATION_VALUES.join(", ")}`,
|
|
525
523
|
});
|
|
526
524
|
}
|
|
527
525
|
}
|
|
@@ -540,6 +538,12 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
|
|
|
540
538
|
for (let j = 0; j < artifacts.length; j++) {
|
|
541
539
|
const artifact = artifacts[j];
|
|
542
540
|
const artifactPath = `${stepPath}.handoff.${direction}[${j}]`;
|
|
541
|
+
if (!HANDOFF_TYPE_VALUES.includes(artifact.type)) {
|
|
542
|
+
errors.push({ path: `${artifactPath}.type`, message: `must be one of: ${HANDOFF_TYPE_VALUES.join(", ")}` });
|
|
543
|
+
}
|
|
544
|
+
if (!artifact.description) {
|
|
545
|
+
errors.push({ path: `${artifactPath}.description`, message: "handoff artifact requires 'description'" });
|
|
546
|
+
}
|
|
543
547
|
if (artifact.required !== false && artifact.type !== "git_commit" && !artifact.path && !artifact.name && !artifact.artifact_id) {
|
|
544
548
|
errors.push({ path: artifactPath, message: "required handoff artifacts must define path, name, or artifact_id" });
|
|
545
549
|
}
|
|
@@ -567,7 +571,7 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
|
|
|
567
571
|
if (!cp.message) {
|
|
568
572
|
errors.push({ path: `${cpPath}.message`, message: "checkpoint requires 'message'" });
|
|
569
573
|
}
|
|
570
|
-
if (cp.action &&
|
|
574
|
+
if (cp.action && !CHECKPOINT_ACTION_VALUES.includes(cp.action)) {
|
|
571
575
|
errors.push({ path: `${cpPath}.action`, message: "checkpoint action must be 'block' or 'warn'" });
|
|
572
576
|
}
|
|
573
577
|
}
|
|
@@ -607,26 +611,23 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
|
|
|
607
611
|
}
|
|
608
612
|
// Validate default_isolation
|
|
609
613
|
if (workflow.default_isolation !== undefined) {
|
|
610
|
-
|
|
611
|
-
if (!validIsolation.includes(workflow.default_isolation)) {
|
|
614
|
+
if (!WORKFLOW_ISOLATION_VALUES.includes(workflow.default_isolation)) {
|
|
612
615
|
errors.push({
|
|
613
616
|
path: `${path}.default_isolation`,
|
|
614
|
-
message: `must be one of: ${
|
|
617
|
+
message: `must be one of: ${WORKFLOW_ISOLATION_VALUES.join(", ")}`,
|
|
615
618
|
});
|
|
616
619
|
}
|
|
617
620
|
}
|
|
618
621
|
// Validate merge_strategy
|
|
619
622
|
if (workflow.merge_strategy !== undefined) {
|
|
620
|
-
|
|
621
|
-
if (!validStrategies.includes(workflow.merge_strategy)) {
|
|
623
|
+
if (!WORKFLOW_MERGE_STRATEGY_VALUES.includes(workflow.merge_strategy)) {
|
|
622
624
|
errors.push({
|
|
623
625
|
path: `${path}.merge_strategy`,
|
|
624
|
-
message: `must be one of: ${
|
|
626
|
+
message: `must be one of: ${WORKFLOW_MERGE_STRATEGY_VALUES.join(", ")}`,
|
|
625
627
|
});
|
|
626
628
|
}
|
|
627
629
|
}
|
|
628
630
|
// Validate transitions
|
|
629
|
-
const validConditions = ["pass", "fail", "always", "error"];
|
|
630
631
|
for (let i = 0; i < (workflow.transitions?.length ?? 0); i++) {
|
|
631
632
|
const t = workflow.transitions[i];
|
|
632
633
|
const tPath = `${path}.transitions[${i}]`;
|
|
@@ -642,10 +643,10 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
|
|
|
642
643
|
else if (!stepIds.has(t.to)) {
|
|
643
644
|
errors.push({ path: `${tPath}.to`, message: `transition references unknown step '${t.to}'` });
|
|
644
645
|
}
|
|
645
|
-
if (!t.condition || !
|
|
646
|
+
if (!t.condition || !TRANSITION_CONDITION_VALUES.includes(t.condition)) {
|
|
646
647
|
errors.push({
|
|
647
648
|
path: `${tPath}.condition`,
|
|
648
|
-
message: `transition condition must be one of: ${
|
|
649
|
+
message: `transition condition must be one of: ${TRANSITION_CONDITION_VALUES.join(", ")}`,
|
|
649
650
|
});
|
|
650
651
|
}
|
|
651
652
|
}
|
|
@@ -665,11 +666,10 @@ function validateWorkflow(workflow, roleNames, pathPrefix) {
|
|
|
665
666
|
message: `retry_from references unknown step '${rp.retry_from}'`,
|
|
666
667
|
});
|
|
667
668
|
}
|
|
668
|
-
|
|
669
|
-
if (rp.backoff && !validBackoff.includes(rp.backoff)) {
|
|
669
|
+
if (rp.backoff && !RETRY_BACKOFF_VALUES.includes(rp.backoff)) {
|
|
670
670
|
errors.push({
|
|
671
671
|
path: `${rpPath}.backoff`,
|
|
672
|
-
message: `backoff must be one of: ${
|
|
672
|
+
message: `backoff must be one of: ${RETRY_BACKOFF_VALUES.join(", ")}`,
|
|
673
673
|
});
|
|
674
674
|
}
|
|
675
675
|
}
|