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,3053 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dna workflow — draft-only workflow builder.
|
|
3
|
+
*/
|
|
4
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { AUTHORING_PACKET_SCHEMA_VERSION, buildAuthoringPacket, createWorkflowDraft, createWorkflowEditPlan, createWorkflowEditPlanFromWorkflow, buildWorkflowDesignPacket, workflowDraftExamples, workflowDraftToTemplateYaml, appendSuggestionReviewReceipt, buildSuggestionReviewReceipt, findTerminalSuggestionReceipt, normalizeReviewDecision, sha256Canonical, suggestionBindingDigest, } from "../../workflow/index.js";
|
|
7
|
+
import { parseYAML } from "../../schema/yaml-parser.js";
|
|
8
|
+
import { findTemplateCatalogEntry } from "../../templates/catalog.js";
|
|
9
|
+
import { TEMPLATE_MATCH_METADATA, WORKFLOW_ASSET_IDS, WORKFLOW_SUGGESTION_IDS, } from "../../templates/metadata.js";
|
|
10
|
+
import { activeEvidenceFollowupReviewRoute, buildWorkflowAssetCandidateBacklog, buildWorkflowAssetEvidenceIndex, findWorkflowAsset, listWorkflowAssets, getWorkflowAssetMetadata, } from "../../assets/index.js";
|
|
11
|
+
import { buildAssetSuggestions, runAssets } from "./assets.js";
|
|
12
|
+
import { readOrganizationProfile, reviewOrganization } from "../../organization/index.js";
|
|
13
|
+
const EXAMPLE_IDS = ["dev_pipeline", "evidence_review", "rewrite_governance"];
|
|
14
|
+
const EDIT_OPERATIONS = ["insert_step", "add_verifier", "tighten_boundary", "split_step", "replace_role"];
|
|
15
|
+
function normalizeExample(value) {
|
|
16
|
+
return EXAMPLE_IDS.includes(value) ? value : "rewrite_governance";
|
|
17
|
+
}
|
|
18
|
+
function normalizeStrictness(value) {
|
|
19
|
+
return value === "high" ? "high" : "standard";
|
|
20
|
+
}
|
|
21
|
+
function normalizeOperation(value) {
|
|
22
|
+
return EDIT_OPERATIONS.includes(value) ? value : "insert_step";
|
|
23
|
+
}
|
|
24
|
+
function inferEditOperation(instruction) {
|
|
25
|
+
const lower = instruction.toLowerCase();
|
|
26
|
+
if (/(verify|verifier|validation|validate|equivalence|test|check|证明|验证|校验)/i.test(lower)) {
|
|
27
|
+
return { operation: "add_verifier", reason: "instruction asks for verification or evidence checking", confidence: "high" };
|
|
28
|
+
}
|
|
29
|
+
if (/(boundary|guard|constraint|strict|tighten|scope|limit|gate|block|边界|约束|收紧|限制)/i.test(lower)) {
|
|
30
|
+
return { operation: "tighten_boundary", reason: "instruction asks to tighten a boundary or gate", confidence: "high" };
|
|
31
|
+
}
|
|
32
|
+
if (/(split|separate|divide|break into|拆分|分成)/i.test(lower)) {
|
|
33
|
+
return { operation: "split_step", reason: "instruction asks to split a step", confidence: "high" };
|
|
34
|
+
}
|
|
35
|
+
if (/(replace role|change role|switch role|改成.*role|角色.*替换|替换.*角色)/i.test(lower)) {
|
|
36
|
+
return { operation: "replace_role", reason: "instruction asks to replace a role", confidence: "medium" };
|
|
37
|
+
}
|
|
38
|
+
return { operation: "insert_step", reason: "default edit is inserting a new workflow step", confidence: "low" };
|
|
39
|
+
}
|
|
40
|
+
function workflowForExample(example) {
|
|
41
|
+
const exampleId = normalizeExample(example);
|
|
42
|
+
return workflowDraftExamples().find((entry) => entry.id === exampleId)?.workflow
|
|
43
|
+
?? workflowDraftExamples()[0].workflow;
|
|
44
|
+
}
|
|
45
|
+
function termsFromText(value) {
|
|
46
|
+
return value
|
|
47
|
+
.toLowerCase()
|
|
48
|
+
.split(/[^a-z0-9]+/)
|
|
49
|
+
.map((term) => term.trim())
|
|
50
|
+
.filter((term) => term.length >= 3)
|
|
51
|
+
.filter((term) => !["the", "and", "for", "with", "after", "before", "into", "from"].includes(term));
|
|
52
|
+
}
|
|
53
|
+
function stepMatchScore(instruction, step) {
|
|
54
|
+
const lower = instruction.toLowerCase();
|
|
55
|
+
const stepId = step.id.toLowerCase();
|
|
56
|
+
const stepIdPhrase = stepId.replace(/[-_]+/g, " ");
|
|
57
|
+
let score = 0;
|
|
58
|
+
if (keywordMatchesDescription(lower, stepId))
|
|
59
|
+
score += 8;
|
|
60
|
+
if (stepIdPhrase !== stepId && keywordMatchesDescription(lower, stepIdPhrase))
|
|
61
|
+
score += 8;
|
|
62
|
+
for (const term of termsFromText(step.id)) {
|
|
63
|
+
if (keywordMatchesDescription(lower, term))
|
|
64
|
+
score += 4;
|
|
65
|
+
}
|
|
66
|
+
for (const term of termsFromText(step.role)) {
|
|
67
|
+
if (keywordMatchesDescription(lower, term))
|
|
68
|
+
score += 2;
|
|
69
|
+
}
|
|
70
|
+
for (const term of termsFromText(`${step.description ?? ""} ${step.prompt ?? ""}`)) {
|
|
71
|
+
if (keywordMatchesDescription(lower, term))
|
|
72
|
+
score += 1;
|
|
73
|
+
}
|
|
74
|
+
return score;
|
|
75
|
+
}
|
|
76
|
+
function inferTargetStep(instruction, steps) {
|
|
77
|
+
const lower = instruction.toLowerCase();
|
|
78
|
+
const after = lower.match(/\b(?:after|before)\s+([a-z0-9_-]+)/);
|
|
79
|
+
if (after?.[1]) {
|
|
80
|
+
const referenced = steps.find((step) => step.id.toLowerCase() === after[1]);
|
|
81
|
+
if (referenced)
|
|
82
|
+
return { targetStep: referenced.id, reason: `instruction references ${after[0]}`, confidence: "high" };
|
|
83
|
+
}
|
|
84
|
+
const scored = steps
|
|
85
|
+
.map((step) => ({ step, score: stepMatchScore(instruction, step) }))
|
|
86
|
+
.filter((entry) => entry.score >= 3)
|
|
87
|
+
.sort((a, b) => b.score - a.score || steps.indexOf(a.step) - steps.indexOf(b.step));
|
|
88
|
+
if (scored[0]) {
|
|
89
|
+
return {
|
|
90
|
+
targetStep: scored[0].step.id,
|
|
91
|
+
reason: `instruction matches current workflow step ${scored[0].step.id}`,
|
|
92
|
+
confidence: scored[0].score >= 8 ? "high" : "medium",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const fallback = steps.at(-1);
|
|
96
|
+
return fallback
|
|
97
|
+
? { targetStep: fallback.id, reason: `no direct step match; defaulting to last workflow step ${fallback.id}`, confidence: "low" }
|
|
98
|
+
: { confidence: "low" };
|
|
99
|
+
}
|
|
100
|
+
function inferRole(operation, instruction) {
|
|
101
|
+
const lower = instruction.toLowerCase();
|
|
102
|
+
const explicitRole = lower.match(/\b(?:role|as|to)\s+([a-z][a-z0-9_-]{2,})\b/);
|
|
103
|
+
if (operation === "replace_role" && explicitRole?.[1]) {
|
|
104
|
+
return { role: explicitRole[1], reason: `instruction names role ${explicitRole[1]}`, confidence: "high" };
|
|
105
|
+
}
|
|
106
|
+
if (operation === "add_verifier")
|
|
107
|
+
return { role: "verifier", reason: "verifier edit uses verifier role", confidence: "high" };
|
|
108
|
+
if (operation === "tighten_boundary")
|
|
109
|
+
return { role: "architect", reason: "boundary edits usually belong to architecture/governance role", confidence: "medium" };
|
|
110
|
+
if (/(review|审查|复核)/i.test(lower))
|
|
111
|
+
return { role: "reviewer", reason: "instruction references review", confidence: "medium" };
|
|
112
|
+
if (/(test|qa|验证|测试)/i.test(lower))
|
|
113
|
+
return { role: "test-engineer", reason: "instruction references tests", confidence: "medium" };
|
|
114
|
+
if (/(plan|intent|需求|计划)/i.test(lower))
|
|
115
|
+
return { role: "planner", reason: "instruction references planning or intent", confidence: "medium" };
|
|
116
|
+
return { confidence: "low" };
|
|
117
|
+
}
|
|
118
|
+
function mergeConfidence(values) {
|
|
119
|
+
if (values.includes("low"))
|
|
120
|
+
return "low";
|
|
121
|
+
if (values.includes("medium"))
|
|
122
|
+
return "medium";
|
|
123
|
+
return "high";
|
|
124
|
+
}
|
|
125
|
+
function buildEditInput(params) {
|
|
126
|
+
const operationInferred = inferEditOperation(params.instruction);
|
|
127
|
+
const operation = params.operation ? normalizeOperation(params.operation) : operationInferred.operation;
|
|
128
|
+
const targetWorkflow = params.workflow ?? workflowForExample(params.example);
|
|
129
|
+
const targetInferred = inferTargetStep(params.instruction, targetWorkflow.steps);
|
|
130
|
+
const roleInferred = inferRole(operation, params.instruction);
|
|
131
|
+
const reasons = [];
|
|
132
|
+
if (!params.operation)
|
|
133
|
+
reasons.push(`operation inferred: ${operationInferred.reason}`);
|
|
134
|
+
if (!params.targetStep && targetInferred.reason)
|
|
135
|
+
reasons.push(`target step inferred: ${targetInferred.reason}`);
|
|
136
|
+
if (!params.role && roleInferred.reason)
|
|
137
|
+
reasons.push(`role inferred: ${roleInferred.reason}`);
|
|
138
|
+
return {
|
|
139
|
+
exampleId: normalizeExample(params.example),
|
|
140
|
+
goal: params.instruction,
|
|
141
|
+
strictness: normalizeStrictness(params.strictness),
|
|
142
|
+
operation,
|
|
143
|
+
instruction: params.instruction,
|
|
144
|
+
targetStep: params.targetStep ?? targetInferred.targetStep,
|
|
145
|
+
role: params.role ?? roleInferred.role,
|
|
146
|
+
source: params.fromTemplate ? `template:${params.fromTemplate}` : undefined,
|
|
147
|
+
inference: {
|
|
148
|
+
operation: params.operation ? "explicit" : "inferred",
|
|
149
|
+
targetStep: params.targetStep ? "explicit" : targetInferred.targetStep ? "inferred" : "default",
|
|
150
|
+
role: params.role ? "explicit" : roleInferred.role ? "inferred" : "default",
|
|
151
|
+
confidence: mergeConfidence([
|
|
152
|
+
params.operation ? "high" : operationInferred.confidence,
|
|
153
|
+
params.targetStep ? "high" : targetInferred.confidence,
|
|
154
|
+
params.role ? "high" : roleInferred.confidence,
|
|
155
|
+
]),
|
|
156
|
+
reasons,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function normalizeSuggestionId(value) {
|
|
161
|
+
return value?.trim() || "template-model-verifier";
|
|
162
|
+
}
|
|
163
|
+
function normalizeTemplateName(value) {
|
|
164
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "workflow-draft";
|
|
165
|
+
}
|
|
166
|
+
function templateIdForAsset(assetName) {
|
|
167
|
+
return `template_${assetName.replaceAll("-", "_")}`;
|
|
168
|
+
}
|
|
169
|
+
function commandQuote(value) {
|
|
170
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
171
|
+
}
|
|
172
|
+
function suggestedDraftTemplateName(value) {
|
|
173
|
+
const normalized = normalizeTemplateName(value);
|
|
174
|
+
const truncated = normalized.length > 48 ? normalized.slice(0, 48).replace(/-+$/g, "") : normalized;
|
|
175
|
+
return truncated || "workflow-draft";
|
|
176
|
+
}
|
|
177
|
+
function defaultSyncCommand(target) {
|
|
178
|
+
const normalized = target.toLowerCase();
|
|
179
|
+
if (normalized === "codex")
|
|
180
|
+
return "dna sync --codex";
|
|
181
|
+
if (normalized === "claude" || normalized === "claude-code")
|
|
182
|
+
return "dna sync";
|
|
183
|
+
return `dna sync --target ${normalized}`;
|
|
184
|
+
}
|
|
185
|
+
function templateNameFromPlanSource(source) {
|
|
186
|
+
if (!source)
|
|
187
|
+
return undefined;
|
|
188
|
+
const parts = source.split(":");
|
|
189
|
+
if ((parts[0] === "template" || parts[0] === "suggestions") && parts[1])
|
|
190
|
+
return parts[1];
|
|
191
|
+
return source;
|
|
192
|
+
}
|
|
193
|
+
function editOperationUseWhen(operation) {
|
|
194
|
+
switch (operation) {
|
|
195
|
+
case "insert_step": return "add a new human or agent step into the workflow";
|
|
196
|
+
case "add_verifier": return "add proof, test, equivalence, or evidence checking";
|
|
197
|
+
case "tighten_boundary": return "make a step stricter with a blocking checkpoint";
|
|
198
|
+
case "split_step": return "separate one broad step into prepare and execute steps";
|
|
199
|
+
case "replace_role": return "change who owns an existing step";
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function routeEditTargetStep(description, selectedAsset) {
|
|
203
|
+
const steps = selectedAsset?.workflows.flatMap((workflow) => workflow.steps) ?? [];
|
|
204
|
+
return inferTargetStep(description, steps).targetStep ?? "STEP_ID";
|
|
205
|
+
}
|
|
206
|
+
function buildWorkflowRouteEditOperations(params) {
|
|
207
|
+
const targetStep = routeEditTargetStep(params.description, params.selectedAsset);
|
|
208
|
+
return EDIT_OPERATIONS.map((operation) => ({
|
|
209
|
+
operation,
|
|
210
|
+
useWhen: editOperationUseWhen(operation),
|
|
211
|
+
targetStep,
|
|
212
|
+
command: `dna workflow edit-plan "Describe the reviewed workflow change" --from-template ${params.selectedAssetName} --operation ${operation} --target-step ${targetStep} --save-template ${params.nextVersion}`,
|
|
213
|
+
writes: true,
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
function stepIds(workflow) {
|
|
217
|
+
return workflow.steps.map((step) => step.id);
|
|
218
|
+
}
|
|
219
|
+
function successors(workflow, targetStep) {
|
|
220
|
+
return workflow.steps
|
|
221
|
+
.filter((step) => (step.depends_on ?? []).includes(targetStep))
|
|
222
|
+
.map((step) => step.id);
|
|
223
|
+
}
|
|
224
|
+
function buildChangePreview(plan, params) {
|
|
225
|
+
const baseIds = new Set(stepIds(plan.base_workflow));
|
|
226
|
+
const proposedIds = new Set(stepIds(plan.proposed_workflow));
|
|
227
|
+
const target = plan.operations[0]?.target ?? plan.input.targetStep ?? "workflow";
|
|
228
|
+
const addedSteps = plan.proposed_workflow.steps
|
|
229
|
+
.filter((step) => !baseIds.has(step.id))
|
|
230
|
+
.map((step) => ({
|
|
231
|
+
id: step.id,
|
|
232
|
+
role: step.role,
|
|
233
|
+
after: step.depends_on ?? [],
|
|
234
|
+
}));
|
|
235
|
+
const removedSteps = plan.base_workflow.steps
|
|
236
|
+
.filter((step) => !proposedIds.has(step.id))
|
|
237
|
+
.map((step) => step.id);
|
|
238
|
+
return {
|
|
239
|
+
baseStepCount: plan.base_workflow.steps.length,
|
|
240
|
+
proposedStepCount: plan.proposed_workflow.steps.length,
|
|
241
|
+
addedSteps,
|
|
242
|
+
removedSteps,
|
|
243
|
+
targetBefore: successors(plan.base_workflow, target),
|
|
244
|
+
targetAfter: successors(plan.proposed_workflow, target),
|
|
245
|
+
suggestedNext: params.saveTemplate
|
|
246
|
+
? "Review the saved draft diff, then run asset adoption and organization binding only after manager approval."
|
|
247
|
+
: "Run the save command if this preview is correct, then review the asset diff before adoption or sync.",
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
function buildEditGuidance(plan, params) {
|
|
251
|
+
const sourceTemplate = params.fromTemplate ?? templateNameFromPlanSource(plan.input.source) ?? "<template>";
|
|
252
|
+
const saveTemplate = params.saveTemplate;
|
|
253
|
+
const targetStep = plan.operations[0]?.target ?? plan.input.targetStep ?? plan.proposed_workflow.steps.at(-1)?.id ?? "workflow";
|
|
254
|
+
const instruction = commandQuote(plan.input.instruction);
|
|
255
|
+
const targetChoices = plan.base_workflow.steps.map((step) => step.id);
|
|
256
|
+
const operationChoices = EDIT_OPERATIONS.map((operation) => ({
|
|
257
|
+
operation,
|
|
258
|
+
useWhen: editOperationUseWhen(operation),
|
|
259
|
+
command: `dna workflow edit-plan ${instruction} --from-template ${sourceTemplate} --operation ${operation} --target-step ${targetStep}`,
|
|
260
|
+
}));
|
|
261
|
+
const reviewBase = saveTemplate ?? `${sourceTemplate}-v2`;
|
|
262
|
+
const reviewPath = [
|
|
263
|
+
saveTemplate
|
|
264
|
+
? `dna assets diff ${sourceTemplate} ${saveTemplate}`
|
|
265
|
+
: `dna workflow edit-plan ${instruction} --from-template ${sourceTemplate} --save-template ${reviewBase}`,
|
|
266
|
+
`dna assets inspect ${reviewBase} --evidence`,
|
|
267
|
+
`dna assets adoption ${WORKFLOW_ASSET_IDS.verificationCommandRouting}`,
|
|
268
|
+
`record explicit manager rationale for dna assets diff ${sourceTemplate} ${reviewBase}`,
|
|
269
|
+
`dna assets adoption ${reviewBase}`,
|
|
270
|
+
`dna org bind --asset ${reviewBase} --status active --sync-target codex`,
|
|
271
|
+
"dna org rollout",
|
|
272
|
+
];
|
|
273
|
+
return {
|
|
274
|
+
summary: `Interpreted as ${plan.input.operation} on ${targetStep}. Review the YAML preview before saving, adopting, or syncing.`,
|
|
275
|
+
changePreview: buildChangePreview(plan, params),
|
|
276
|
+
inferred: {
|
|
277
|
+
operation: plan.input.operation,
|
|
278
|
+
targetStep,
|
|
279
|
+
role: plan.input.role,
|
|
280
|
+
confidence: plan.input.inference?.confidence ?? "low",
|
|
281
|
+
reasons: plan.input.inference?.reasons ?? [],
|
|
282
|
+
},
|
|
283
|
+
operationChoices,
|
|
284
|
+
targetChoices,
|
|
285
|
+
reviewPath,
|
|
286
|
+
saveCommand: saveTemplate ? undefined : `dna workflow edit-plan ${instruction} --from-template ${sourceTemplate} --save-template ${reviewBase}`,
|
|
287
|
+
mutationBoundary: "Edit guidance is read-only. It does not save a template, adopt an asset, bind an organization, sync a harness, or deploy a surface unless an explicit follow-up command is run.",
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function escapeRegExp(value) {
|
|
291
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
292
|
+
}
|
|
293
|
+
function keywordMatchesDescription(description, keyword) {
|
|
294
|
+
const normalizedKeyword = keyword.toLowerCase().trim();
|
|
295
|
+
if (!normalizedKeyword)
|
|
296
|
+
return false;
|
|
297
|
+
if (/^[a-z0-9 ]+$/.test(normalizedKeyword)) {
|
|
298
|
+
const pattern = normalizedKeyword.split(/\s+/).map(escapeRegExp).join("\\s+");
|
|
299
|
+
return new RegExp(`(^|[^a-z0-9])${pattern}($|[^a-z0-9])`).test(description);
|
|
300
|
+
}
|
|
301
|
+
return description.includes(normalizedKeyword);
|
|
302
|
+
}
|
|
303
|
+
function inferExample(description) {
|
|
304
|
+
const lower = description.toLowerCase();
|
|
305
|
+
if (["rewrite", "refactor", "migration", "migrate", "flutter", "logic equivalence", "重构", "迁移", "逻辑对等"].some((keyword) => lower.includes(keyword))) {
|
|
306
|
+
return "rewrite_governance";
|
|
307
|
+
}
|
|
308
|
+
if (["evidence", "review", "audit", "verify", "verification", "证据", "审查", "验证"].some((keyword) => lower.includes(keyword))) {
|
|
309
|
+
return "evidence_review";
|
|
310
|
+
}
|
|
311
|
+
return "dev_pipeline";
|
|
312
|
+
}
|
|
313
|
+
function metadataIntentSignals(description, template) {
|
|
314
|
+
const lower = description.toLowerCase();
|
|
315
|
+
return (template.intent_signals ?? [])
|
|
316
|
+
.filter((intentSignal) => intentSignal.keywords.some((keyword) => keywordMatchesDescription(lower, keyword)))
|
|
317
|
+
.map((intentSignal) => ({
|
|
318
|
+
signal: intentSignal.signal,
|
|
319
|
+
weight: intentSignal.weight,
|
|
320
|
+
}));
|
|
321
|
+
}
|
|
322
|
+
function assetRankingSignals(asset) {
|
|
323
|
+
if (!asset)
|
|
324
|
+
return [];
|
|
325
|
+
return [
|
|
326
|
+
asset.status === "dogfood_ready" ? "dogfood-ready" : undefined,
|
|
327
|
+
asset.evidence !== "unlinked" ? "evidence-linked" : undefined,
|
|
328
|
+
asset.cascade === "declared" ? "cascade-declared" : undefined,
|
|
329
|
+
asset.evolution === "preview_supported" ? "preview-evolution" : undefined,
|
|
330
|
+
asset.workflowCount > 1 ? "multi-workflow" : undefined,
|
|
331
|
+
asset.controllerCount > 0 ? "controller-backed" : undefined,
|
|
332
|
+
].filter((signal) => signal !== undefined);
|
|
333
|
+
}
|
|
334
|
+
function intakeMatchScore(matchedKeywords, intentSignals, asset) {
|
|
335
|
+
let score = matchedKeywords.length;
|
|
336
|
+
if (asset?.status === "dogfood_ready")
|
|
337
|
+
score += 6;
|
|
338
|
+
if (asset?.evidence !== "unlinked")
|
|
339
|
+
score += 3;
|
|
340
|
+
if (asset?.cascade === "declared")
|
|
341
|
+
score += 1;
|
|
342
|
+
if (asset?.evolution === "preview_supported")
|
|
343
|
+
score += 1;
|
|
344
|
+
if ((asset?.workflowCount ?? 0) > 1)
|
|
345
|
+
score += 1;
|
|
346
|
+
if ((asset?.controllerCount ?? 0) > 0)
|
|
347
|
+
score += 1;
|
|
348
|
+
for (const signal of intentSignals)
|
|
349
|
+
score += signal.weight;
|
|
350
|
+
return score;
|
|
351
|
+
}
|
|
352
|
+
function workflowIntakeAsset(asset) {
|
|
353
|
+
const metadata = getWorkflowAssetMetadata(asset.name);
|
|
354
|
+
return {
|
|
355
|
+
name: asset.name,
|
|
356
|
+
source: asset.source,
|
|
357
|
+
status: asset.status.managementStatus,
|
|
358
|
+
evidence: asset.status.evidencePosture,
|
|
359
|
+
cascade: asset.status.cascadePosture,
|
|
360
|
+
evolution: asset.status.evolutionPosture,
|
|
361
|
+
configured: asset.status.configured,
|
|
362
|
+
workflowCount: asset.workflows.length,
|
|
363
|
+
controllerCount: asset.controllers.length,
|
|
364
|
+
positioning: asset.positioning ?? metadata?.positioning,
|
|
365
|
+
dogfood: metadata?.dogfood !== undefined,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
async function matchWorkflowAssets(description, projectDir) {
|
|
369
|
+
const lower = description.toLowerCase();
|
|
370
|
+
const matches = [];
|
|
371
|
+
for (const template of TEMPLATE_MATCH_METADATA) {
|
|
372
|
+
const matchedKeywords = template.keywords.filter((keyword) => keywordMatchesDescription(lower, keyword));
|
|
373
|
+
if (matchedKeywords.length === 0)
|
|
374
|
+
continue;
|
|
375
|
+
const asset = await findWorkflowAsset(template.name, { projectDir });
|
|
376
|
+
const intakeAsset = asset ? workflowIntakeAsset(asset) : undefined;
|
|
377
|
+
const intentSignals = metadataIntentSignals(description, template);
|
|
378
|
+
const rankingSignals = [
|
|
379
|
+
...intentSignals.map((signal) => signal.signal),
|
|
380
|
+
...assetRankingSignals(intakeAsset),
|
|
381
|
+
];
|
|
382
|
+
matches.push({
|
|
383
|
+
name: template.name,
|
|
384
|
+
displayName: template.name.replace(/-/g, " "),
|
|
385
|
+
score: intakeMatchScore(matchedKeywords, intentSignals, intakeAsset),
|
|
386
|
+
matchedKeywords,
|
|
387
|
+
rankingSignals,
|
|
388
|
+
asset: intakeAsset,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return matches.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
|
|
392
|
+
}
|
|
393
|
+
function reusableAsset(match) {
|
|
394
|
+
return match?.asset?.status === "dogfood_ready" || match?.asset?.status === "asset_ready";
|
|
395
|
+
}
|
|
396
|
+
async function buildWorkflowIntake(description, projectDir) {
|
|
397
|
+
const matches = await matchWorkflowAssets(description, projectDir);
|
|
398
|
+
const best = matches[0];
|
|
399
|
+
const path = reusableAsset(best) ? "reuse_asset" : "draft_new_asset";
|
|
400
|
+
const example = inferExample(description);
|
|
401
|
+
const saveTemplate = suggestedDraftTemplateName(description);
|
|
402
|
+
const authoringPacketCommand = `dna workflow authoring-packet ${commandQuote(description)} --json`;
|
|
403
|
+
const draftCommand = `dna workflow draft ${commandQuote(description)} --example ${example} --strictness high --save-template ${saveTemplate}`;
|
|
404
|
+
const bestEvidence = best?.asset?.evidence === "dogfood_linked"
|
|
405
|
+
? await buildWorkflowAssetEvidenceIndex(best.name, { projectDir })
|
|
406
|
+
: undefined;
|
|
407
|
+
const evidenceOpen = bestEvidence
|
|
408
|
+
? bestEvidence.totals.continue > 0 || bestEvidence.totals.blocked > 0 || bestEvidence.totals.requestChanges > 0 || bestEvidence.totals.remaining > 0
|
|
409
|
+
: false;
|
|
410
|
+
const activeFollowup = best?.asset && evidenceOpen
|
|
411
|
+
? activeEvidenceFollowupReviewRoute(best.name, await listWorkflowAssets({ projectDir }), bestEvidence)
|
|
412
|
+
: undefined;
|
|
413
|
+
const reuse = best?.asset
|
|
414
|
+
? activeFollowup
|
|
415
|
+
? [...new Set([
|
|
416
|
+
"dna assets library",
|
|
417
|
+
`dna assets lifecycle ${best.name}`,
|
|
418
|
+
activeFollowup.currentCommand,
|
|
419
|
+
activeFollowup.diffCommand,
|
|
420
|
+
activeFollowup.adoptionCommand,
|
|
421
|
+
])]
|
|
422
|
+
: [
|
|
423
|
+
"dna assets library",
|
|
424
|
+
`dna assets lineage ${best.name}`,
|
|
425
|
+
`dna assets inspect ${best.name} --evidence`,
|
|
426
|
+
`dna assets governance ${best.name}`,
|
|
427
|
+
`dna assets adoption ${best.name}`,
|
|
428
|
+
]
|
|
429
|
+
: ["dna assets library"];
|
|
430
|
+
const draft = [
|
|
431
|
+
authoringPacketCommand,
|
|
432
|
+
draftCommand,
|
|
433
|
+
`dna workflow edit-plan "Describe the reviewed workflow change" --from-template ${saveTemplate} --save-template ${saveTemplate}-v2`,
|
|
434
|
+
`dna assets inspect ${saveTemplate} --evidence`,
|
|
435
|
+
];
|
|
436
|
+
return {
|
|
437
|
+
mode: "workflow_intake",
|
|
438
|
+
input: { description },
|
|
439
|
+
recommendation: {
|
|
440
|
+
path,
|
|
441
|
+
asset: path === "reuse_asset" ? best?.name : undefined,
|
|
442
|
+
reason: path === "reuse_asset"
|
|
443
|
+
? evidenceOpen
|
|
444
|
+
? `${best?.name} already looks like a managed workflow asset, but dogfood evidence is open; use lifecycle to save and review unfinished work before adoption or sync.`
|
|
445
|
+
: `${best?.name} already looks like a managed workflow asset; inspect lineage and governance before adoption or sync.`
|
|
446
|
+
: "No reusable managed workflow asset was strong enough; use the authoring-packet agent loop to create a real C1 workflow asset. The old draft command is only an example-clone scaffold fallback.",
|
|
447
|
+
},
|
|
448
|
+
matches: matches.slice(0, 5),
|
|
449
|
+
draft: {
|
|
450
|
+
example,
|
|
451
|
+
strictness: "high",
|
|
452
|
+
saveTemplate,
|
|
453
|
+
command: draftCommand,
|
|
454
|
+
scaffold: true,
|
|
455
|
+
deprecatedPrimaryCreationPath: "dna workflow authoring-packet",
|
|
456
|
+
note: "Example-clone scaffold only; structure is not derived from the goal.",
|
|
457
|
+
},
|
|
458
|
+
authoringPacket: {
|
|
459
|
+
schemaVersion: AUTHORING_PACKET_SCHEMA_VERSION,
|
|
460
|
+
command: authoringPacketCommand,
|
|
461
|
+
primary: true,
|
|
462
|
+
boundary: "Read-only packet generation; dna treats the goal as opaque context and performs no goal modeling.",
|
|
463
|
+
},
|
|
464
|
+
reviewPath: {
|
|
465
|
+
reuse,
|
|
466
|
+
draft,
|
|
467
|
+
},
|
|
468
|
+
mutationBoundary: "Read-only intake; no DNA config, organization binding, runtime sync, dashboard state, or policy truth is changed.",
|
|
469
|
+
nextCommands: [...new Set([
|
|
470
|
+
...(path === "reuse_asset" ? reuse : []),
|
|
471
|
+
...(path === "draft_new_asset" ? [authoringPacketCommand] : []),
|
|
472
|
+
draftCommand,
|
|
473
|
+
])],
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function workflowEvidenceOpen(evidence) {
|
|
477
|
+
return evidence
|
|
478
|
+
? evidence.totals.continue > 0 ||
|
|
479
|
+
evidence.totals.blocked > 0 ||
|
|
480
|
+
evidence.totals.requestChanges > 0 ||
|
|
481
|
+
evidence.totals.remaining > 0
|
|
482
|
+
: false;
|
|
483
|
+
}
|
|
484
|
+
function projectAssetBindingStatus(review, projectName, asset) {
|
|
485
|
+
if (!review)
|
|
486
|
+
return "missing";
|
|
487
|
+
const project = review.profile.projects.find((entry) => entry.name === projectName || entry.id === projectName) ?? review.profile.projects[0];
|
|
488
|
+
return project?.assets.find((binding) => binding.asset === asset)?.status ?? "missing";
|
|
489
|
+
}
|
|
490
|
+
function projectSyncTargets(review, projectName) {
|
|
491
|
+
if (!review)
|
|
492
|
+
return [];
|
|
493
|
+
const project = review.profile.projects.find((entry) => entry.name === projectName || entry.id === projectName) ?? review.profile.projects[0];
|
|
494
|
+
return project?.syncTargets ?? [];
|
|
495
|
+
}
|
|
496
|
+
function routePhase(phases, id) {
|
|
497
|
+
return phases.find((phase) => phase.id === id) ?? phases[0];
|
|
498
|
+
}
|
|
499
|
+
function buildWorkflowRouteGovernance(params) {
|
|
500
|
+
const selected = params.selectedAsset;
|
|
501
|
+
const next = params.nextAsset;
|
|
502
|
+
const previewAsset = next ?? selected;
|
|
503
|
+
const routeAsset = params.routeAsset ?? params.nextVersion;
|
|
504
|
+
return {
|
|
505
|
+
selectedAsset: {
|
|
506
|
+
name: params.selected,
|
|
507
|
+
found: selected !== undefined,
|
|
508
|
+
cascade: selected?.status.cascadePosture ?? "unknown",
|
|
509
|
+
evolution: selected?.status.evolutionPosture ?? "unknown",
|
|
510
|
+
evidence: selected?.status.evidencePosture ?? "unknown",
|
|
511
|
+
configured: selected?.status.configured === true,
|
|
512
|
+
},
|
|
513
|
+
nextAsset: {
|
|
514
|
+
name: routeAsset,
|
|
515
|
+
found: next !== undefined,
|
|
516
|
+
cascade: next?.status.cascadePosture ?? "unknown",
|
|
517
|
+
evolution: next?.status.evolutionPosture ?? "unknown",
|
|
518
|
+
evidence: next?.status.evidencePosture ?? "unknown",
|
|
519
|
+
configured: next?.status.configured === true,
|
|
520
|
+
},
|
|
521
|
+
cascadeCommand: `dna assets governance ${params.selected}`,
|
|
522
|
+
evolutionPreviewCommand: params.routeAsset
|
|
523
|
+
? `dna evolve ${templateIdForAsset(params.routeAsset)}`
|
|
524
|
+
: previewAsset?.evolution.previewCommand ?? `dna evolve ${templateIdForAsset(params.nextVersion)}`,
|
|
525
|
+
improvementCommand: `dna assets suggestions ${routeAsset}`,
|
|
526
|
+
principle: "Cascade and evolution are governed in dna core: cascade composes inherited policy before sync, and evolution stays preview-first until evidence is reviewed.",
|
|
527
|
+
mutationBoundary: "Route governance is read-only. It does not apply markers, change inheritance, sync a harness, update dashboard state, or write Obsidian/wiki.",
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
function routeNeedsBehaviorLock(description, selected, selectedAsset) {
|
|
531
|
+
const haystack = [
|
|
532
|
+
description,
|
|
533
|
+
selected,
|
|
534
|
+
selectedAsset?.displayName,
|
|
535
|
+
selectedAsset?.positioning,
|
|
536
|
+
selectedAsset?.workflows.map((workflow) => `${workflow.name} ${workflow.description ?? ""}`).join(" "),
|
|
537
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
538
|
+
return /(rewrite|refactor|migration|migrate|flutter|logic equivalence|behavior lock|behavior-preserving|重构|迁移|逻辑对等|行为锁定|行为保持|精准改写)/i.test(haystack);
|
|
539
|
+
}
|
|
540
|
+
function buildWorkflowRouteCurrent(params) {
|
|
541
|
+
const completed = ["describe"];
|
|
542
|
+
const blockers = [];
|
|
543
|
+
if (params.source === "new_draft" && !params.selectedAsset) {
|
|
544
|
+
const phase = routePhase(params.phases, "asset");
|
|
545
|
+
blockers.push("No saved validated workflow asset exists yet for the selected route.");
|
|
546
|
+
return {
|
|
547
|
+
stage: phase.id,
|
|
548
|
+
command: phase.command,
|
|
549
|
+
writes: phase.writes,
|
|
550
|
+
reason: "Run the authoring-packet loop and save a validated workflow asset before planning edits, diff, organization binding, or runtime sync.",
|
|
551
|
+
completed,
|
|
552
|
+
blockers,
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
if (params.behaviorLockRequired)
|
|
556
|
+
completed.push("lock");
|
|
557
|
+
completed.push("asset");
|
|
558
|
+
completed.push("cascade");
|
|
559
|
+
if (params.activeFollowup) {
|
|
560
|
+
blockers.push(`Active dogfood evidence must be reviewed as saved unfinished work in ${params.activeFollowup.candidateAssetName} before generic version editing or sync; the source asset remains unchanged.`);
|
|
561
|
+
if (params.activeFollowup.status === "draft_reviewable") {
|
|
562
|
+
completed.push("edit");
|
|
563
|
+
return {
|
|
564
|
+
stage: "review",
|
|
565
|
+
command: params.activeFollowup.currentCommand,
|
|
566
|
+
writes: params.activeFollowup.currentWrites,
|
|
567
|
+
reason: `${params.activeFollowup.reason} Review the saved unfinished-work draft before generic version editing, rollout, sync, or evolution.`,
|
|
568
|
+
completed,
|
|
569
|
+
blockers,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const phase = routePhase(params.phases, "edit");
|
|
573
|
+
return {
|
|
574
|
+
stage: phase.id,
|
|
575
|
+
command: params.activeFollowup.currentCommand,
|
|
576
|
+
writes: params.activeFollowup.currentWrites,
|
|
577
|
+
reason: `${params.activeFollowup.reason} Handle the saved unfinished-work draft ${params.activeFollowup.candidateAssetName} before generic version editing, rollout, sync, or evolution.`,
|
|
578
|
+
completed,
|
|
579
|
+
blockers,
|
|
580
|
+
};
|
|
581
|
+
}
|
|
582
|
+
if (!params.nextAsset) {
|
|
583
|
+
const phase = routePhase(params.phases, "edit");
|
|
584
|
+
blockers.push("No next asset version exists yet for manager review.");
|
|
585
|
+
return {
|
|
586
|
+
stage: phase.id,
|
|
587
|
+
command: phase.command,
|
|
588
|
+
writes: phase.writes,
|
|
589
|
+
reason: "Create the reviewable next version before diff, adoption, org binding, or sync.",
|
|
590
|
+
completed,
|
|
591
|
+
blockers,
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
completed.push("edit");
|
|
595
|
+
if (params.bindingStatus !== "active") {
|
|
596
|
+
const phase = routePhase(params.phases, "review");
|
|
597
|
+
blockers.push("Next asset version exists but has not been accepted into the organization route.");
|
|
598
|
+
return {
|
|
599
|
+
stage: phase.id,
|
|
600
|
+
command: phase.command,
|
|
601
|
+
writes: phase.writes,
|
|
602
|
+
reason: "Review the asset version diff before organization binding, rollout, or runtime sync.",
|
|
603
|
+
completed,
|
|
604
|
+
blockers,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
completed.push("review");
|
|
608
|
+
if (!params.hasOrganization) {
|
|
609
|
+
const phase = routePhase(params.phases, "organize");
|
|
610
|
+
blockers.push("No .dna/org.yaml profile exists for organization-managed rollout.");
|
|
611
|
+
return {
|
|
612
|
+
stage: phase.id,
|
|
613
|
+
command: phase.command,
|
|
614
|
+
writes: phase.writes,
|
|
615
|
+
reason: "Plan or create the organization/project binding before rollout and sync.",
|
|
616
|
+
completed,
|
|
617
|
+
blockers,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
if (params.targets.length === 0) {
|
|
621
|
+
const phase = routePhase(params.phases, "organize");
|
|
622
|
+
blockers.push("No sync target is declared for the project.");
|
|
623
|
+
return {
|
|
624
|
+
stage: phase.id,
|
|
625
|
+
command: phase.command,
|
|
626
|
+
writes: phase.writes,
|
|
627
|
+
reason: "Bind the accepted asset version as active with a sync target before rollout.",
|
|
628
|
+
completed,
|
|
629
|
+
blockers,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
completed.push("organize");
|
|
633
|
+
if (!params.configured) {
|
|
634
|
+
const phase = routePhase(params.phases, "configure");
|
|
635
|
+
blockers.push("The accepted asset version is organization-bound but missing project DNA config.");
|
|
636
|
+
return {
|
|
637
|
+
stage: phase.id,
|
|
638
|
+
command: phase.command,
|
|
639
|
+
writes: phase.writes,
|
|
640
|
+
reason: "Configure declared organization assets before rollout or runtime sync.",
|
|
641
|
+
completed,
|
|
642
|
+
blockers,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
completed.push("configure");
|
|
646
|
+
const rollout = routePhase(params.phases, "rollout");
|
|
647
|
+
return {
|
|
648
|
+
stage: rollout.id,
|
|
649
|
+
command: rollout.command,
|
|
650
|
+
writes: rollout.writes,
|
|
651
|
+
reason: "The accepted asset version is active in the organization route; review rollout readiness before runtime sync.",
|
|
652
|
+
completed,
|
|
653
|
+
blockers,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
function workflowRouteNextCommands(params) {
|
|
657
|
+
if (params.activeFollowup) {
|
|
658
|
+
return [...new Set([
|
|
659
|
+
params.current.command,
|
|
660
|
+
...params.activeFollowup.reviewCommands,
|
|
661
|
+
])].slice(0, 8);
|
|
662
|
+
}
|
|
663
|
+
return [...new Set([
|
|
664
|
+
params.current.command,
|
|
665
|
+
...params.phases
|
|
666
|
+
.filter((phase) => phase.command !== params.current.command)
|
|
667
|
+
.filter((phase) => phase.id !== "lock" || params.behaviorLockRequired)
|
|
668
|
+
.map((phase) => phase.command),
|
|
669
|
+
])].slice(0, 5);
|
|
670
|
+
}
|
|
671
|
+
function workflowRouteRisk(current) {
|
|
672
|
+
if (current.stage === "asset")
|
|
673
|
+
return "needs_draft";
|
|
674
|
+
if (current.stage === "edit")
|
|
675
|
+
return "needs_version";
|
|
676
|
+
if (current.stage === "review")
|
|
677
|
+
return "needs_manager_review";
|
|
678
|
+
if (current.stage === "organize") {
|
|
679
|
+
return current.blockers.some((blocker) => blocker.toLowerCase().includes("sync target"))
|
|
680
|
+
? "needs_sync_target"
|
|
681
|
+
: "needs_organization";
|
|
682
|
+
}
|
|
683
|
+
if (current.stage === "configure")
|
|
684
|
+
return "needs_configuration";
|
|
685
|
+
if (current.stage === "rollout")
|
|
686
|
+
return "ready_for_rollout";
|
|
687
|
+
return "unknown";
|
|
688
|
+
}
|
|
689
|
+
function workflowRouteAllowedNow(current) {
|
|
690
|
+
if (current.stage === "asset") {
|
|
691
|
+
return [
|
|
692
|
+
`Start the authoring loop: ${current.command}.`,
|
|
693
|
+
"Use an external agent to generate YAML, then run dna validate before saving the asset.",
|
|
694
|
+
"Keep organization binding and runtime sync blocked until the asset exists.",
|
|
695
|
+
];
|
|
696
|
+
}
|
|
697
|
+
if (current.stage === "edit") {
|
|
698
|
+
const activeFollowup = current.blockers.some((blocker) => blocker.includes("saved unfinished work"));
|
|
699
|
+
const createLabel = activeFollowup ? "Save the unfinished-work draft" : "Create the reviewable next version";
|
|
700
|
+
return [
|
|
701
|
+
`${createLabel}: ${current.command}.`,
|
|
702
|
+
"Review the version diff before adoption.",
|
|
703
|
+
"Keep organization binding and runtime sync blocked until the version exists.",
|
|
704
|
+
];
|
|
705
|
+
}
|
|
706
|
+
if (current.stage === "review") {
|
|
707
|
+
return [
|
|
708
|
+
`Review the asset diff: ${current.command}.`,
|
|
709
|
+
"Adopt or bind only after the manager accepts the version.",
|
|
710
|
+
"Keep runtime sync blocked until the accepted version is organization-bound and configured.",
|
|
711
|
+
];
|
|
712
|
+
}
|
|
713
|
+
if (current.stage === "organize") {
|
|
714
|
+
return [
|
|
715
|
+
`Plan the organization binding: ${current.command}.`,
|
|
716
|
+
"Make the accepted asset and sync target explicit in organization state.",
|
|
717
|
+
"Return to dna workflow route or dna org review before rollout.",
|
|
718
|
+
];
|
|
719
|
+
}
|
|
720
|
+
if (current.stage === "configure") {
|
|
721
|
+
return [
|
|
722
|
+
`Review missing project configuration: ${current.command}.`,
|
|
723
|
+
"Apply configuration only after the plan is acceptable.",
|
|
724
|
+
"Return to rollout review before runtime sync.",
|
|
725
|
+
];
|
|
726
|
+
}
|
|
727
|
+
if (current.stage === "rollout") {
|
|
728
|
+
return [
|
|
729
|
+
`Review rollout readiness: ${current.command}.`,
|
|
730
|
+
"Run the target sync command only after rollout stays ready.",
|
|
731
|
+
"Run dna release deploy-plan before any release claim; execute the printed Vercel deploy/readiness/URL verification commands only when it returns deployment_required or a confirmed deployment_conditional.",
|
|
732
|
+
];
|
|
733
|
+
}
|
|
734
|
+
return [`Run the current route command: ${current.command}.`];
|
|
735
|
+
}
|
|
736
|
+
function workflowRouteBlockedNow(current) {
|
|
737
|
+
const blocked = [...current.blockers];
|
|
738
|
+
if (current.stage !== "rollout") {
|
|
739
|
+
blocked.push("Runtime sync is blocked until the route reaches rollout readiness.");
|
|
740
|
+
blocked.push("Vercel deployment is not a substitute for workflow review, organization rollout, or runtime sync.");
|
|
741
|
+
}
|
|
742
|
+
return blocked.length > 0
|
|
743
|
+
? [...new Set(blocked)]
|
|
744
|
+
: ["No route-level blockers detected; still verify org rollout before runtime sync."];
|
|
745
|
+
}
|
|
746
|
+
function buildWorkflowRouteDecisionPacket(current) {
|
|
747
|
+
return {
|
|
748
|
+
currentState: `${current.stage}; completed=${current.completed.join(", ") || "none"}; blockers=${current.blockers.length}`,
|
|
749
|
+
primaryAction: current.command,
|
|
750
|
+
risk: workflowRouteRisk(current),
|
|
751
|
+
reason: current.reason,
|
|
752
|
+
allowedNow: workflowRouteAllowedNow(current),
|
|
753
|
+
blockedNow: workflowRouteBlockedNow(current),
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function workflowRouteStatusSummary(current, phase) {
|
|
757
|
+
if (current.stage === "rollout") {
|
|
758
|
+
return `Current step: ${phase.label}. Run ${current.command}; only then should runtime sync, deploy-plan, improvement review, or evolution preview continue.`;
|
|
759
|
+
}
|
|
760
|
+
if (current.stage === "sync") {
|
|
761
|
+
return `Current step: ${phase.label}. Run ${current.command} only after organization rollout stays clear.`;
|
|
762
|
+
}
|
|
763
|
+
return `Current step: ${phase.label}. Run ${current.command} before moving to organization rollout, runtime sync, deployment, or evolution.`;
|
|
764
|
+
}
|
|
765
|
+
function buildWorkflowRouteStatus(params) {
|
|
766
|
+
const currentPhase = routePhase(params.phases, params.current.stage);
|
|
767
|
+
const blockedCommands = params.phases
|
|
768
|
+
.filter((phase) => phase.id !== params.current.stage && !params.current.completed.includes(phase.id))
|
|
769
|
+
.filter((phase) => phase.id === "sync" || phase.id === "deploy" || phase.id === "evolve")
|
|
770
|
+
.map((phase) => `${phase.label}: ${phase.command}`);
|
|
771
|
+
return {
|
|
772
|
+
readiness: params.decisionPacket.risk,
|
|
773
|
+
plainSummary: workflowRouteStatusSummary(params.current, currentPhase),
|
|
774
|
+
currentStep: {
|
|
775
|
+
id: currentPhase.id,
|
|
776
|
+
label: currentPhase.label,
|
|
777
|
+
owner: currentPhase.owner,
|
|
778
|
+
writes: params.current.writes,
|
|
779
|
+
command: params.current.command,
|
|
780
|
+
},
|
|
781
|
+
completed: params.current.completed,
|
|
782
|
+
blockers: params.current.blockers,
|
|
783
|
+
now: params.decisionPacket.allowedNow,
|
|
784
|
+
notYet: blockedCommands.length > 0
|
|
785
|
+
? blockedCommands
|
|
786
|
+
: workflowRouteBlockedNow(params.current).filter((item) => item.toLowerCase().includes("blocked")),
|
|
787
|
+
evidenceLoop: [
|
|
788
|
+
params.improvementCommand,
|
|
789
|
+
params.evolutionPreviewCommand,
|
|
790
|
+
"Accepted improvement still returns through diff, adoption, organization rollout, runtime sync, and deploy-plan gates.",
|
|
791
|
+
],
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
function buildWorkflowAuthoringChoices(params) {
|
|
795
|
+
const hasSelectedAsset = params.selectedAsset !== undefined;
|
|
796
|
+
const activeFollowup = params.activeFollowup;
|
|
797
|
+
const choices = [];
|
|
798
|
+
if (activeFollowup) {
|
|
799
|
+
const activeFollowupBoundary = `${activeFollowup.candidateAssetName} is a manager/dna_core review draft only; it does not replace, delete, auto-adopt, execute, create org_private visibility, or grant runtime authority for ${params.selectedAssetName}.`;
|
|
800
|
+
choices.push({
|
|
801
|
+
id: "active_followup",
|
|
802
|
+
label: activeFollowup.status === "draft_reviewable"
|
|
803
|
+
? "Review the unfinished-work draft"
|
|
804
|
+
: activeFollowup.status === "draft_structural_drift"
|
|
805
|
+
? "Repair the unfinished-work draft"
|
|
806
|
+
: activeFollowup.status === "draft_incomplete"
|
|
807
|
+
? "Inspect the unfinished-work draft"
|
|
808
|
+
: "Save active dogfood evidence",
|
|
809
|
+
command: activeFollowup.currentCommand,
|
|
810
|
+
purpose: "Use the candidate-backed route when real dogfood evidence is still open, instead of drafting a generic version.",
|
|
811
|
+
writes: activeFollowup.currentWrites,
|
|
812
|
+
status: "recommended",
|
|
813
|
+
reason: `Open evidence on ${params.selectedAssetName} should be reviewed as ${activeFollowup.candidateAssetName} before adoption, sync, or evolution. ${activeFollowup.reason} ${activeFollowupBoundary}`,
|
|
814
|
+
next: activeFollowup.status === "draft_reviewable"
|
|
815
|
+
? `Record manager rationale for ${activeFollowup.diffCommand}, then run ${activeFollowup.adoptionCommand}; organization binding and rollout stay later. ${activeFollowupBoundary}`
|
|
816
|
+
: `Review with ${activeFollowup.diffCommand}, record manager rationale, then continue to adoption and organization rollout. ${activeFollowupBoundary}`,
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
choices.push({
|
|
820
|
+
id: "reuse_asset",
|
|
821
|
+
label: "Reuse an existing workflow asset",
|
|
822
|
+
command: hasSelectedAsset ? `dna assets lifecycle ${params.selectedAssetName}` : "dna assets library",
|
|
823
|
+
purpose: "Inspect lineage, cascade, evidence, candidates, and adoption posture before creating or editing anything.",
|
|
824
|
+
writes: false,
|
|
825
|
+
status: activeFollowup ? "blocked" : hasSelectedAsset && params.source === "reused_asset" ? "available" : "available",
|
|
826
|
+
reason: activeFollowup
|
|
827
|
+
? "Direct reuse is blocked by active dogfood evidence; inspect the asset but handle the follow-up first."
|
|
828
|
+
: hasSelectedAsset
|
|
829
|
+
? `${params.selectedAssetName} is available as the base workflow asset.`
|
|
830
|
+
: "No strong reusable asset is selected; browse the library before drafting if unsure.",
|
|
831
|
+
next: activeFollowup ? activeFollowup.currentCommand : params.editCommand,
|
|
832
|
+
});
|
|
833
|
+
choices.push({
|
|
834
|
+
id: "draft_new_asset",
|
|
835
|
+
label: "Author a new workflow asset",
|
|
836
|
+
command: params.newDraftCommand,
|
|
837
|
+
purpose: "Use the C3 authoring packet so an external agent can interview, generate YAML, and loop on dna validate without dna pretending to model intent.",
|
|
838
|
+
writes: false,
|
|
839
|
+
status: params.source === "new_draft" ? "recommended" : "available",
|
|
840
|
+
reason: params.source === "new_draft"
|
|
841
|
+
? "No reusable managed workflow asset was strong enough; start the authoring-packet loop before any edit, review, organization binding, or sync."
|
|
842
|
+
: "Use this only when the reusable asset is not the right work method. The old draft command is an example-clone scaffold fallback.",
|
|
843
|
+
next: "Generate YAML with the external agent, run dna validate until clean, save under .dna/templates, then inspect the asset before adoption.",
|
|
844
|
+
});
|
|
845
|
+
choices.push({
|
|
846
|
+
id: "edit_existing_asset",
|
|
847
|
+
label: "Edit the selected workflow asset",
|
|
848
|
+
command: params.editCommand,
|
|
849
|
+
purpose: "Adjust steps, roles, boundaries, handoffs, or verifiers through a reviewable edit plan.",
|
|
850
|
+
writes: true,
|
|
851
|
+
status: params.current.stage === "edit" && !activeFollowup ? "recommended" : hasSelectedAsset ? "available" : "blocked",
|
|
852
|
+
reason: hasSelectedAsset
|
|
853
|
+
? "The selected asset can be edited into a reviewable next version without mutating the original."
|
|
854
|
+
: "Save or choose a workflow asset before editing it.",
|
|
855
|
+
next: "Run asset diff, adoption review, organization binding, rollout, then sync only after gates pass.",
|
|
856
|
+
});
|
|
857
|
+
return choices;
|
|
858
|
+
}
|
|
859
|
+
function userPathStatus(params) {
|
|
860
|
+
if (params.phaseIds.includes(params.current.stage))
|
|
861
|
+
return "current";
|
|
862
|
+
if (params.phaseIds.every((id) => params.current.completed.includes(id)))
|
|
863
|
+
return "done";
|
|
864
|
+
return "later";
|
|
865
|
+
}
|
|
866
|
+
function buildWorkflowRouteUserPath(params) {
|
|
867
|
+
const phase = (id) => routePhase(params.phases, id);
|
|
868
|
+
const shapeCommand = params.activeFollowup?.currentCommand ?? phase("edit").command;
|
|
869
|
+
const reviewAsset = params.activeFollowup?.candidateAssetName ?? params.nextVersion;
|
|
870
|
+
const syncPhase = phase("sync");
|
|
871
|
+
const releasePhase = phase("deploy");
|
|
872
|
+
const improvementPhase = phase("improve");
|
|
873
|
+
const improvementCommand = params.activeFollowup
|
|
874
|
+
? `dna assets suggestions ${params.activeFollowup.candidateAssetName}`
|
|
875
|
+
: improvementPhase.command;
|
|
876
|
+
const distributeCommand = params.activeFollowup
|
|
877
|
+
? params.activeFollowup.bindCommand
|
|
878
|
+
: params.current.stage === "rollout"
|
|
879
|
+
? phase("rollout").command
|
|
880
|
+
: params.current.stage === "configure"
|
|
881
|
+
? phase("configure").command
|
|
882
|
+
: phase("organize").command;
|
|
883
|
+
const distributeWrites = params.activeFollowup !== undefined;
|
|
884
|
+
const selectPhaseIds = params.behaviorLockRequired
|
|
885
|
+
? ["lock", "asset"]
|
|
886
|
+
: ["asset"];
|
|
887
|
+
const distributePhaseIds = ["organize", "configure", "rollout", "sync", "deploy"];
|
|
888
|
+
return [
|
|
889
|
+
{
|
|
890
|
+
id: "describe",
|
|
891
|
+
label: "Describe the workflow",
|
|
892
|
+
command: phase("describe").command,
|
|
893
|
+
purpose: "Start from natural language and let dna recommend reuse or the external-agent authoring path; no canvas is required.",
|
|
894
|
+
owner: "user",
|
|
895
|
+
writes: false,
|
|
896
|
+
status: userPathStatus({ current: params.current, phaseIds: ["describe"] }),
|
|
897
|
+
phaseIds: ["describe"],
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
id: "select_asset",
|
|
901
|
+
label: params.source === "new_draft" ? "Create a reviewable asset" : "Select a reusable asset",
|
|
902
|
+
command: phase("asset").command,
|
|
903
|
+
purpose: params.source === "new_draft"
|
|
904
|
+
? "Run the authoring packet, validate the external agent's YAML, and save only after explicit human confirmation."
|
|
905
|
+
: "Inspect the matched asset and its evidence before changing or distributing it.",
|
|
906
|
+
owner: "dna_core",
|
|
907
|
+
writes: phase("asset").writes,
|
|
908
|
+
status: userPathStatus({ current: params.current, phaseIds: selectPhaseIds }),
|
|
909
|
+
phaseIds: selectPhaseIds,
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
id: "govern_asset",
|
|
913
|
+
label: "Review governance",
|
|
914
|
+
command: phase("cascade").command,
|
|
915
|
+
purpose: "Check cascade inheritance, evidence posture, and preview-first evolution before edits or adoption.",
|
|
916
|
+
owner: "dna_core",
|
|
917
|
+
writes: false,
|
|
918
|
+
status: userPathStatus({ current: params.current, phaseIds: ["cascade"] }),
|
|
919
|
+
phaseIds: ["cascade"],
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
id: "shape_asset",
|
|
923
|
+
label: params.activeFollowup
|
|
924
|
+
? params.activeFollowup.status === "draft_reviewable"
|
|
925
|
+
? "Review unfinished-work draft"
|
|
926
|
+
: params.activeFollowup.status === "draft_structural_drift"
|
|
927
|
+
? "Repair unfinished-work draft"
|
|
928
|
+
: "Save unfinished-work draft"
|
|
929
|
+
: "Shape the next asset version",
|
|
930
|
+
command: shapeCommand,
|
|
931
|
+
purpose: params.activeFollowup
|
|
932
|
+
? `Handle the saved unfinished-work draft ${params.activeFollowup.candidateAssetName} through its current review route before generic edits, rollout, sync, or evolution.`
|
|
933
|
+
: "Adjust steps, roles, boundaries, handoffs, or verifiers through a reviewable edit plan.",
|
|
934
|
+
owner: "dna_core",
|
|
935
|
+
writes: params.activeFollowup?.currentWrites ?? true,
|
|
936
|
+
status: userPathStatus({ current: params.current, phaseIds: ["edit"] }),
|
|
937
|
+
phaseIds: ["edit"],
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
id: "manager_review",
|
|
941
|
+
label: "Manager reviews the asset",
|
|
942
|
+
command: params.activeFollowup
|
|
943
|
+
? params.activeFollowup.diffCommand
|
|
944
|
+
: phase("review").command,
|
|
945
|
+
purpose: "Review the asset diff/adoption posture before accepting it into the organization route.",
|
|
946
|
+
owner: "manager",
|
|
947
|
+
writes: false,
|
|
948
|
+
status: userPathStatus({ current: params.current, phaseIds: ["review"] }),
|
|
949
|
+
phaseIds: ["review"],
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
id: "distribute",
|
|
953
|
+
label: "Roll out and sync",
|
|
954
|
+
command: distributeCommand,
|
|
955
|
+
purpose: `Bind the accepted asset, configure declared assets, review rollout, then sync to ${params.syncTarget}; run ${params.releaseCommand} only for web/public surface changes.`,
|
|
956
|
+
owner: params.current.stage === "configure" ? "dna_core" : "manager",
|
|
957
|
+
writes: distributeWrites,
|
|
958
|
+
status: userPathStatus({ current: params.current, phaseIds: distributePhaseIds }),
|
|
959
|
+
phaseIds: distributePhaseIds,
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
id: "improve",
|
|
963
|
+
label: "Improve from run evidence",
|
|
964
|
+
command: improvementCommand,
|
|
965
|
+
purpose: `After runtime use, convert evidence into reviewed suggestions and preview evolution; ${releasePhase.command} and ${syncPhase.command} stay separate gates.`,
|
|
966
|
+
owner: "dna_core",
|
|
967
|
+
writes: false,
|
|
968
|
+
status: userPathStatus({ current: params.current, phaseIds: ["improve", "evolve"] }),
|
|
969
|
+
phaseIds: ["improve", "evolve"],
|
|
970
|
+
},
|
|
971
|
+
];
|
|
972
|
+
}
|
|
973
|
+
function personalPhaseStatus(current, phase) {
|
|
974
|
+
if (phase === "describe")
|
|
975
|
+
return "done";
|
|
976
|
+
if (phase === "draft") {
|
|
977
|
+
if (current.stage === "asset")
|
|
978
|
+
return "current";
|
|
979
|
+
return current.completed.includes("asset") ? "done" : "later";
|
|
980
|
+
}
|
|
981
|
+
if (phase === "validate") {
|
|
982
|
+
if (["cascade", "edit", "review", "verify"].includes(current.stage))
|
|
983
|
+
return "current";
|
|
984
|
+
if (current.completed.includes("review"))
|
|
985
|
+
return "done";
|
|
986
|
+
return current.completed.includes("asset") ? "current" : "later";
|
|
987
|
+
}
|
|
988
|
+
if (current.stage === "sync" || current.stage === "run" || current.stage === "rollout")
|
|
989
|
+
return "current";
|
|
990
|
+
return current.completed.includes("review") ? "current" : "later";
|
|
991
|
+
}
|
|
992
|
+
function buildPersonalRoutePhases(params) {
|
|
993
|
+
return [
|
|
994
|
+
{
|
|
995
|
+
id: "describe",
|
|
996
|
+
label: "Describe",
|
|
997
|
+
command: `dna workflow intake ${commandQuote(params.description)}`,
|
|
998
|
+
purpose: "Describe the working method and get a reuse-or-author recommendation.",
|
|
999
|
+
writes: false,
|
|
1000
|
+
owner: "user",
|
|
1001
|
+
},
|
|
1002
|
+
{
|
|
1003
|
+
id: "draft",
|
|
1004
|
+
label: "Author asset",
|
|
1005
|
+
command: params.draftCommand,
|
|
1006
|
+
purpose: "Use the C3 authoring packet and validate loop to create or select a workflow asset in the shared C1 format.",
|
|
1007
|
+
writes: false,
|
|
1008
|
+
owner: "dna_core",
|
|
1009
|
+
},
|
|
1010
|
+
{
|
|
1011
|
+
id: "validate",
|
|
1012
|
+
label: "Validate",
|
|
1013
|
+
command: params.validateCommand,
|
|
1014
|
+
purpose: "Inspect evidence, governance, or diff output before treating the asset as usable.",
|
|
1015
|
+
writes: false,
|
|
1016
|
+
owner: "dna_core",
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
id: "run",
|
|
1020
|
+
label: "Run",
|
|
1021
|
+
command: params.syncCommand,
|
|
1022
|
+
purpose: "Sync the selected runtime surface only after the local asset is validated.",
|
|
1023
|
+
writes: true,
|
|
1024
|
+
owner: "runtime",
|
|
1025
|
+
},
|
|
1026
|
+
];
|
|
1027
|
+
}
|
|
1028
|
+
function buildPersonalRouteUserPath(params) {
|
|
1029
|
+
return params.phases.map((phase) => ({
|
|
1030
|
+
id: phase.id,
|
|
1031
|
+
label: phase.label,
|
|
1032
|
+
command: phase.command,
|
|
1033
|
+
purpose: phase.purpose,
|
|
1034
|
+
owner: phase.owner,
|
|
1035
|
+
writes: phase.writes,
|
|
1036
|
+
status: personalPhaseStatus(params.current, phase.id),
|
|
1037
|
+
phaseIds: [phase.id],
|
|
1038
|
+
}));
|
|
1039
|
+
}
|
|
1040
|
+
function buildWorkflowRouteReviewPath(params) {
|
|
1041
|
+
if (params.activeFollowup) {
|
|
1042
|
+
const phaseById = new Map(params.phases.map((phase) => [phase.id, phase]));
|
|
1043
|
+
const maybeLock = params.behaviorLockRequired ? [phaseById.get("lock")?.command] : [];
|
|
1044
|
+
return [...new Set([
|
|
1045
|
+
params.current.command,
|
|
1046
|
+
phaseById.get("describe")?.command,
|
|
1047
|
+
...maybeLock,
|
|
1048
|
+
phaseById.get("asset")?.command,
|
|
1049
|
+
phaseById.get("cascade")?.command,
|
|
1050
|
+
params.activeFollowup.diffCommand,
|
|
1051
|
+
phaseById.get("verify")?.command,
|
|
1052
|
+
`record explicit manager rationale for ${params.activeFollowup.diffCommand}`,
|
|
1053
|
+
params.activeFollowup.adoptionCommand,
|
|
1054
|
+
params.activeFollowup.bindCommand,
|
|
1055
|
+
params.activeFollowup.rolloutCommand,
|
|
1056
|
+
phaseById.get("sync")?.command,
|
|
1057
|
+
phaseById.get("deploy")?.command,
|
|
1058
|
+
`dna assets suggestions ${params.activeFollowup.candidateAssetName}`,
|
|
1059
|
+
phaseById.get("evolve")?.command,
|
|
1060
|
+
].filter((command) => command !== undefined))];
|
|
1061
|
+
}
|
|
1062
|
+
const phaseOrder = [
|
|
1063
|
+
"describe",
|
|
1064
|
+
"lock",
|
|
1065
|
+
"asset",
|
|
1066
|
+
"cascade",
|
|
1067
|
+
"edit",
|
|
1068
|
+
"review",
|
|
1069
|
+
"verify",
|
|
1070
|
+
"organize",
|
|
1071
|
+
"configure",
|
|
1072
|
+
"rollout",
|
|
1073
|
+
"sync",
|
|
1074
|
+
"deploy",
|
|
1075
|
+
"improve",
|
|
1076
|
+
"evolve",
|
|
1077
|
+
];
|
|
1078
|
+
const includePhase = (phase) => phase.id !== "lock" || params.behaviorLockRequired;
|
|
1079
|
+
const phaseById = new Map(params.phases.map((phase) => [phase.id, phase]));
|
|
1080
|
+
const ordered = phaseOrder
|
|
1081
|
+
.map((id) => phaseById.get(id))
|
|
1082
|
+
.filter((phase) => phase !== undefined)
|
|
1083
|
+
.filter(includePhase)
|
|
1084
|
+
.map((phase) => phase.command);
|
|
1085
|
+
return [...new Set([params.current.command, ...ordered])];
|
|
1086
|
+
}
|
|
1087
|
+
function buildWorkflowAlternateEditPath(params) {
|
|
1088
|
+
if (!params.activeFollowup)
|
|
1089
|
+
return [];
|
|
1090
|
+
const ids = ["edit", "review", "verify", "organize"];
|
|
1091
|
+
const phaseById = new Map(params.phases.map((phase) => [phase.id, phase]));
|
|
1092
|
+
return ids
|
|
1093
|
+
.map((id) => phaseById.get(id)?.command)
|
|
1094
|
+
.filter((command) => command !== undefined);
|
|
1095
|
+
}
|
|
1096
|
+
async function buildWorkflowAuthoringRoute(opts) {
|
|
1097
|
+
const description = opts.goal?.trim() ?? "";
|
|
1098
|
+
const intake = await buildWorkflowIntake(description, opts.projectDir);
|
|
1099
|
+
const projectDir = opts.projectDir ?? process.cwd();
|
|
1100
|
+
const organization = opts.name?.trim() || "My Org";
|
|
1101
|
+
const project = opts.project?.trim() || "Workflow Project";
|
|
1102
|
+
const syncTarget = opts.syncTarget?.trim() || "codex";
|
|
1103
|
+
const forcedAsset = opts.fromTemplate?.trim() || undefined;
|
|
1104
|
+
const reusable = intake.recommendation.path === "reuse_asset" && intake.recommendation.asset;
|
|
1105
|
+
const source = forcedAsset || reusable ? "reused_asset" : "new_draft";
|
|
1106
|
+
const draftTemplate = normalizeTemplateName(opts.saveTemplate || (source === "reused_asset" ? `${forcedAsset ?? intake.recommendation.asset}-v2` : intake.draft.saveTemplate));
|
|
1107
|
+
const selected = source === "reused_asset" ? forcedAsset ?? intake.recommendation.asset ?? draftTemplate : draftTemplate;
|
|
1108
|
+
const nextVersion = source === "reused_asset" ? draftTemplate : `${draftTemplate}-v2`;
|
|
1109
|
+
const orgDescription = commandQuote(project);
|
|
1110
|
+
const orgName = commandQuote(organization);
|
|
1111
|
+
const projectName = commandQuote(project);
|
|
1112
|
+
const selectedAssetName = selected;
|
|
1113
|
+
const syncCommand = defaultSyncCommand(syncTarget);
|
|
1114
|
+
const authoringPacketCommand = `dna workflow authoring-packet ${commandQuote(description)} --json`;
|
|
1115
|
+
const scaffoldCommand = `dna workflow draft ${commandQuote(description)} --example ${intake.draft.example} --strictness high --save-template ${draftTemplate}`;
|
|
1116
|
+
const draftCommand = source === "new_draft"
|
|
1117
|
+
? authoringPacketCommand
|
|
1118
|
+
: `dna assets lineage ${selectedAssetName}`;
|
|
1119
|
+
const behaviorLockRequired = routeNeedsBehaviorLock(description, selectedAssetName);
|
|
1120
|
+
const editCommand = `dna workflow edit-plan "Describe the reviewed workflow change" --from-template ${selectedAssetName} --save-template ${nextVersion}`;
|
|
1121
|
+
const reviewCommand = `dna assets diff ${selectedAssetName} ${nextVersion}`;
|
|
1122
|
+
const bindAsset = nextVersion;
|
|
1123
|
+
const orgPhases = [
|
|
1124
|
+
{
|
|
1125
|
+
id: "describe",
|
|
1126
|
+
label: "Describe the work",
|
|
1127
|
+
command: `dna workflow intake ${commandQuote(description)}`,
|
|
1128
|
+
purpose: "Turn a natural-language working method into a reuse-or-author recommendation without drawing a canvas.",
|
|
1129
|
+
writes: false,
|
|
1130
|
+
owner: "user",
|
|
1131
|
+
},
|
|
1132
|
+
{
|
|
1133
|
+
id: "lock",
|
|
1134
|
+
label: "Lock behavior as evidence",
|
|
1135
|
+
command: "dna assets adoption flutter-behavior-lock",
|
|
1136
|
+
purpose: "For risky rewrite or refactor work, capture source behavior as docs/tests before changing workflow structure or runtime state.",
|
|
1137
|
+
writes: false,
|
|
1138
|
+
owner: "dna_core",
|
|
1139
|
+
requiredWhen: "risky_rewrite_or_refactor",
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
id: "asset",
|
|
1143
|
+
label: source === "reused_asset" ? "Inspect reusable asset" : "Author workflow asset",
|
|
1144
|
+
command: draftCommand,
|
|
1145
|
+
purpose: source === "reused_asset"
|
|
1146
|
+
? "Understand the selected asset lineage, cascade, evidence, and evolution route before editing or adoption."
|
|
1147
|
+
: "Create a real workflow asset through the C3 authoring packet, external agent generation, dna validate loop, and explicit human save.",
|
|
1148
|
+
writes: false,
|
|
1149
|
+
owner: "dna_core",
|
|
1150
|
+
},
|
|
1151
|
+
{
|
|
1152
|
+
id: "cascade",
|
|
1153
|
+
label: "Review cascade governance",
|
|
1154
|
+
command: `dna assets governance ${selectedAssetName}`,
|
|
1155
|
+
purpose: "Check inherited layers, evidence posture, and preview-first evolution before edit, diff, organization binding, or sync.",
|
|
1156
|
+
writes: false,
|
|
1157
|
+
owner: "dna_core",
|
|
1158
|
+
},
|
|
1159
|
+
{
|
|
1160
|
+
id: "edit",
|
|
1161
|
+
label: "Plan edits",
|
|
1162
|
+
command: editCommand,
|
|
1163
|
+
purpose: "Adjust steps, roles, boundaries, handoffs, and verifiers through an operation-based edit plan.",
|
|
1164
|
+
writes: true,
|
|
1165
|
+
owner: "dna_core",
|
|
1166
|
+
},
|
|
1167
|
+
{
|
|
1168
|
+
id: "review",
|
|
1169
|
+
label: "Review asset version",
|
|
1170
|
+
command: reviewCommand,
|
|
1171
|
+
purpose: "Let a manager review the draft or version diff before it becomes an organization binding.",
|
|
1172
|
+
writes: false,
|
|
1173
|
+
owner: "manager",
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
id: "verify",
|
|
1177
|
+
label: "Route verification command",
|
|
1178
|
+
command: `dna assets adoption ${WORKFLOW_ASSET_IDS.verificationCommandRouting}`,
|
|
1179
|
+
purpose: "Plan and review the authoritative command root/scope before accepting verification evidence for completion, rollout, or sync.",
|
|
1180
|
+
writes: false,
|
|
1181
|
+
owner: "dna_core",
|
|
1182
|
+
requiredWhen: "verification_root_or_scope_is_ambiguous",
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
id: "organize",
|
|
1186
|
+
label: "Bind to organization",
|
|
1187
|
+
command: `dna org intake ${orgDescription} --name ${orgName} --project ${projectName} --asset ${bindAsset} --sync-target ${syncTarget}`,
|
|
1188
|
+
purpose: "Plan organization/project binding before writing .dna/org.yaml or changing rollout state.",
|
|
1189
|
+
writes: false,
|
|
1190
|
+
owner: "manager",
|
|
1191
|
+
},
|
|
1192
|
+
{
|
|
1193
|
+
id: "configure",
|
|
1194
|
+
label: "Configure declared assets",
|
|
1195
|
+
command: "dna org configure-plan",
|
|
1196
|
+
purpose: "List or apply explicit dna init commands for organization-declared assets missing project config before rollout.",
|
|
1197
|
+
writes: false,
|
|
1198
|
+
owner: "dna_core",
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
id: "rollout",
|
|
1202
|
+
label: "Review rollout",
|
|
1203
|
+
command: "dna org rollout",
|
|
1204
|
+
purpose: "Check configured assets, evidence gates, blockers, and allowed sync commands before distribution.",
|
|
1205
|
+
writes: false,
|
|
1206
|
+
owner: "manager",
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
id: "sync",
|
|
1210
|
+
label: "Sync runtime surface",
|
|
1211
|
+
command: syncCommand,
|
|
1212
|
+
purpose: "Compile and install the accepted governance asset into the selected harness surface.",
|
|
1213
|
+
writes: true,
|
|
1214
|
+
owner: "runtime",
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
id: "deploy",
|
|
1218
|
+
label: "Deploy if public surface changed",
|
|
1219
|
+
command: "dna release deploy-plan",
|
|
1220
|
+
purpose: "Decide whether release needs Vercel CLI deployment; run the printed deploy, readiness wait, and deployed URL verification commands only for deployment_required or a confirmed deployment_conditional.",
|
|
1221
|
+
writes: false,
|
|
1222
|
+
owner: "release",
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
id: "improve",
|
|
1226
|
+
label: "Improve from evidence",
|
|
1227
|
+
command: `dna assets suggestions ${bindAsset}`,
|
|
1228
|
+
purpose: "Convert dogfood/runtime evidence into human-reviewed improvement suggestions instead of automatic mutation.",
|
|
1229
|
+
writes: false,
|
|
1230
|
+
owner: "dna_core",
|
|
1231
|
+
},
|
|
1232
|
+
{
|
|
1233
|
+
id: "evolve",
|
|
1234
|
+
label: "Preview self-evolution",
|
|
1235
|
+
command: `dna evolve ${templateIdForAsset(bindAsset)}`,
|
|
1236
|
+
purpose: "Preview outcome-derived marker changes; mutation remains explicit through dna sync --evolve after review.",
|
|
1237
|
+
writes: false,
|
|
1238
|
+
owner: "dna_core",
|
|
1239
|
+
requiredWhen: "execution_outcomes_recorded",
|
|
1240
|
+
},
|
|
1241
|
+
];
|
|
1242
|
+
const [selectedAsset, profile, selectedEvidence, assets] = await Promise.all([
|
|
1243
|
+
findWorkflowAsset(selectedAssetName, { projectDir }),
|
|
1244
|
+
readOrganizationProfile({ projectDir }),
|
|
1245
|
+
buildWorkflowAssetEvidenceIndex(selectedAssetName, { projectDir }),
|
|
1246
|
+
listWorkflowAssets({ projectDir }),
|
|
1247
|
+
]);
|
|
1248
|
+
const orgReview = profile.profile ? await reviewOrganization({ projectDir }) : undefined;
|
|
1249
|
+
const activeFollowup = workflowEvidenceOpen(selectedEvidence)
|
|
1250
|
+
? activeEvidenceFollowupReviewRoute(selectedAssetName, assets, selectedEvidence)
|
|
1251
|
+
: undefined;
|
|
1252
|
+
const routeAssetName = activeFollowup?.candidateAssetName ?? bindAsset;
|
|
1253
|
+
const nextAsset = activeFollowup?.candidateAsset ?? await findWorkflowAsset(bindAsset, { projectDir });
|
|
1254
|
+
const finalBehaviorLockRequired = routeNeedsBehaviorLock(description, selectedAssetName, selectedAsset) || behaviorLockRequired;
|
|
1255
|
+
const governance = buildWorkflowRouteGovernance({
|
|
1256
|
+
selected: selectedAssetName,
|
|
1257
|
+
nextVersion,
|
|
1258
|
+
selectedAsset,
|
|
1259
|
+
nextAsset,
|
|
1260
|
+
routeAsset: activeFollowup?.candidateAssetName,
|
|
1261
|
+
});
|
|
1262
|
+
const evolvePhase = orgPhases.find((phase) => phase.id === "evolve");
|
|
1263
|
+
if (evolvePhase)
|
|
1264
|
+
evolvePhase.command = governance.evolutionPreviewCommand;
|
|
1265
|
+
const improvePhase = orgPhases.find((phase) => phase.id === "improve");
|
|
1266
|
+
if (improvePhase)
|
|
1267
|
+
improvePhase.command = governance.improvementCommand;
|
|
1268
|
+
const current = buildWorkflowRouteCurrent({
|
|
1269
|
+
phases: orgPhases,
|
|
1270
|
+
source,
|
|
1271
|
+
selectedAsset,
|
|
1272
|
+
nextAsset,
|
|
1273
|
+
activeFollowup,
|
|
1274
|
+
hasOrganization: profile.profile !== undefined,
|
|
1275
|
+
bindingStatus: projectAssetBindingStatus(orgReview, project, routeAssetName),
|
|
1276
|
+
configured: nextAsset?.status.configured === true,
|
|
1277
|
+
targets: projectSyncTargets(orgReview, project),
|
|
1278
|
+
behaviorLockRequired: finalBehaviorLockRequired,
|
|
1279
|
+
});
|
|
1280
|
+
const decisionPacket = buildWorkflowRouteDecisionPacket(current);
|
|
1281
|
+
const status = buildWorkflowRouteStatus({
|
|
1282
|
+
current,
|
|
1283
|
+
phases: orgPhases,
|
|
1284
|
+
decisionPacket,
|
|
1285
|
+
improvementCommand: governance.improvementCommand,
|
|
1286
|
+
evolutionPreviewCommand: governance.evolutionPreviewCommand,
|
|
1287
|
+
});
|
|
1288
|
+
const orgSpine = ["describe", "lock", "asset", "cascade", "edit", "review", "verify", "organize", "configure", "rollout", "sync", "deploy", "improve", "evolve"];
|
|
1289
|
+
const personalSpine = ["describe", "draft", "validate", "run"];
|
|
1290
|
+
const validateCommand = source === "new_draft"
|
|
1291
|
+
? `dna validate ${join(projectDir, ".dna", "templates", `${selectedAssetName}.dna.yaml`)}`
|
|
1292
|
+
: current.stage === "review" || current.stage === "edit" || current.stage === "verify"
|
|
1293
|
+
? current.command
|
|
1294
|
+
: `dna assets inspect ${routeAssetName} --evidence`;
|
|
1295
|
+
const personalPhases = buildPersonalRoutePhases({
|
|
1296
|
+
description,
|
|
1297
|
+
draftCommand,
|
|
1298
|
+
validateCommand,
|
|
1299
|
+
syncCommand,
|
|
1300
|
+
});
|
|
1301
|
+
const orgPath = buildWorkflowRouteUserPath({
|
|
1302
|
+
current,
|
|
1303
|
+
phases: orgPhases,
|
|
1304
|
+
source,
|
|
1305
|
+
selectedAssetName,
|
|
1306
|
+
nextVersion,
|
|
1307
|
+
activeFollowup,
|
|
1308
|
+
behaviorLockRequired: finalBehaviorLockRequired,
|
|
1309
|
+
syncTarget,
|
|
1310
|
+
releaseCommand: "dna release deploy-plan",
|
|
1311
|
+
});
|
|
1312
|
+
const personalPath = buildPersonalRouteUserPath({ current, phases: personalPhases });
|
|
1313
|
+
const orgView = opts.org === true;
|
|
1314
|
+
const orgNextCommands = workflowRouteNextCommands({
|
|
1315
|
+
current,
|
|
1316
|
+
phases: orgPhases,
|
|
1317
|
+
behaviorLockRequired: finalBehaviorLockRequired,
|
|
1318
|
+
activeFollowup,
|
|
1319
|
+
});
|
|
1320
|
+
const personalNextCommands = [...new Set([
|
|
1321
|
+
current.command,
|
|
1322
|
+
...(source === "new_draft" ? [scaffoldCommand] : []),
|
|
1323
|
+
`dna workflow route ${commandQuote(description)} --org --name ${orgName} --project ${projectName} --sync-target ${syncTarget}`,
|
|
1324
|
+
])].slice(0, 4);
|
|
1325
|
+
return {
|
|
1326
|
+
mode: "workflow_authoring_route",
|
|
1327
|
+
scope: orgView ? "organization" : "personal",
|
|
1328
|
+
input: {
|
|
1329
|
+
description,
|
|
1330
|
+
organization,
|
|
1331
|
+
project,
|
|
1332
|
+
syncTarget,
|
|
1333
|
+
forcedAsset,
|
|
1334
|
+
},
|
|
1335
|
+
recommendation: intake.recommendation,
|
|
1336
|
+
asset: {
|
|
1337
|
+
selected: selectedAssetName,
|
|
1338
|
+
source,
|
|
1339
|
+
draftTemplate,
|
|
1340
|
+
nextVersion,
|
|
1341
|
+
},
|
|
1342
|
+
choices: buildWorkflowAuthoringChoices({
|
|
1343
|
+
source,
|
|
1344
|
+
selectedAssetName,
|
|
1345
|
+
selectedAsset,
|
|
1346
|
+
newDraftCommand: authoringPacketCommand,
|
|
1347
|
+
editCommand,
|
|
1348
|
+
activeFollowup,
|
|
1349
|
+
current,
|
|
1350
|
+
}),
|
|
1351
|
+
userPath: orgView ? orgPath : personalPath,
|
|
1352
|
+
orgPath,
|
|
1353
|
+
governance,
|
|
1354
|
+
spine: orgView ? orgSpine : personalSpine,
|
|
1355
|
+
orgSpine,
|
|
1356
|
+
phases: orgView ? orgPhases : personalPhases,
|
|
1357
|
+
orgPhases,
|
|
1358
|
+
current,
|
|
1359
|
+
status,
|
|
1360
|
+
decisionPacket,
|
|
1361
|
+
reviewPath: buildWorkflowRouteReviewPath({
|
|
1362
|
+
current,
|
|
1363
|
+
phases: orgPhases,
|
|
1364
|
+
behaviorLockRequired: finalBehaviorLockRequired,
|
|
1365
|
+
selectedAssetName,
|
|
1366
|
+
activeFollowup,
|
|
1367
|
+
syncTarget,
|
|
1368
|
+
}),
|
|
1369
|
+
alternateEditPath: buildWorkflowAlternateEditPath({
|
|
1370
|
+
activeFollowup,
|
|
1371
|
+
phases: orgPhases,
|
|
1372
|
+
}),
|
|
1373
|
+
editOperations: buildWorkflowRouteEditOperations({
|
|
1374
|
+
description,
|
|
1375
|
+
selectedAssetName,
|
|
1376
|
+
nextVersion,
|
|
1377
|
+
selectedAsset,
|
|
1378
|
+
}),
|
|
1379
|
+
checkpoints: [
|
|
1380
|
+
"No canvas is required; the user describes the workflow in natural language.",
|
|
1381
|
+
"Risky rewrite and refactor routes start by locking behavior evidence before workflow edits.",
|
|
1382
|
+
"Drafting and edit planning remain review-only until an explicit save/apply command runs.",
|
|
1383
|
+
"Organization binding happens before runtime sync.",
|
|
1384
|
+
"Project DNA config is explicit; organization-bound assets go through configure-plan before rollout.",
|
|
1385
|
+
"Verification routing confirms command root, scope, and machine evidence before evidence review, completion, rollout, or sync.",
|
|
1386
|
+
"Cascade and evolution are visible route gates, not hidden harness behavior.",
|
|
1387
|
+
"Evolution is preview-first; marker mutation requires explicit dna sync --evolve after review.",
|
|
1388
|
+
"Dashboard remains a read-only projection of this route.",
|
|
1389
|
+
"Obsidian/wiki remains internal project memory, not product runtime truth.",
|
|
1390
|
+
"Vercel deployment and deployed URL verification run only when dna release deploy-plan returns deployment_required, or deployment_conditional is explicitly confirmed for external review.",
|
|
1391
|
+
],
|
|
1392
|
+
mutationBoundary: "Read-only authoring route; it does not create templates, bind organizations, sync runtimes, deploy web surfaces, mutate evolution markers, or write dashboard state.",
|
|
1393
|
+
nextCommands: orgView ? orgNextCommands : personalNextCommands,
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function isRecord(value) {
|
|
1397
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1398
|
+
}
|
|
1399
|
+
function parseWorkflow(value) {
|
|
1400
|
+
if (!isRecord(value) || typeof value.name !== "string" || !Array.isArray(value.steps))
|
|
1401
|
+
return undefined;
|
|
1402
|
+
return value;
|
|
1403
|
+
}
|
|
1404
|
+
function workflowMapFromTemplateYaml(parsed) {
|
|
1405
|
+
const workflows = {};
|
|
1406
|
+
if (!isRecord(parsed))
|
|
1407
|
+
return workflows;
|
|
1408
|
+
const singular = parseWorkflow(parsed.workflow);
|
|
1409
|
+
if (singular)
|
|
1410
|
+
workflows.workflow = singular;
|
|
1411
|
+
if (isRecord(parsed.workflows)) {
|
|
1412
|
+
for (const [key, value] of Object.entries(parsed.workflows)) {
|
|
1413
|
+
const workflow = parseWorkflow(value);
|
|
1414
|
+
if (workflow)
|
|
1415
|
+
workflows[key] = workflow;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
return workflows;
|
|
1419
|
+
}
|
|
1420
|
+
function selectWorkflow(params) {
|
|
1421
|
+
const entries = Object.entries(params.workflows);
|
|
1422
|
+
if (entries.length === 0)
|
|
1423
|
+
return {};
|
|
1424
|
+
const requested = params.requested?.trim();
|
|
1425
|
+
if (requested && params.workflows[requested])
|
|
1426
|
+
return { workflow: params.workflows[requested], workflowId: requested };
|
|
1427
|
+
if (requested)
|
|
1428
|
+
return { requestedMissing: requested };
|
|
1429
|
+
if (params.targetStep) {
|
|
1430
|
+
const matched = entries.find(([, workflow]) => workflow.steps.some((step) => step.id === params.targetStep));
|
|
1431
|
+
if (matched)
|
|
1432
|
+
return { workflow: matched[1], workflowId: matched[0] };
|
|
1433
|
+
}
|
|
1434
|
+
return { workflow: entries[0][1], workflowId: entries[0][0], fallback: true };
|
|
1435
|
+
}
|
|
1436
|
+
function sourceWithWorkflow(prefix, templateName, workflowId, suffix) {
|
|
1437
|
+
const parts = [prefix, templateName];
|
|
1438
|
+
if (workflowId && workflowId !== "workflow")
|
|
1439
|
+
parts.push(workflowId);
|
|
1440
|
+
if (suffix)
|
|
1441
|
+
parts.push(suffix);
|
|
1442
|
+
return parts.join(":");
|
|
1443
|
+
}
|
|
1444
|
+
async function currentSuggestionReviewSnapshot(params) {
|
|
1445
|
+
const asset = await findWorkflowAsset(params.assetName, { projectDir: params.projectDir });
|
|
1446
|
+
if (!asset)
|
|
1447
|
+
return undefined;
|
|
1448
|
+
const evidence = await buildWorkflowAssetEvidenceIndex(asset.name, { projectDir: params.projectDir });
|
|
1449
|
+
if (!evidence)
|
|
1450
|
+
return undefined;
|
|
1451
|
+
const assets = await listWorkflowAssets({ projectDir: params.projectDir });
|
|
1452
|
+
const review = buildAssetSuggestions(asset, evidence, assets);
|
|
1453
|
+
const suggestion = review.suggestions.find((entry) => entry.id === params.suggestionId);
|
|
1454
|
+
if (!suggestion)
|
|
1455
|
+
return undefined;
|
|
1456
|
+
return {
|
|
1457
|
+
asset: asset.name,
|
|
1458
|
+
workflowId: params.workflowId,
|
|
1459
|
+
suggestion,
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
function writeSuggestionReviewReceipt(receipt, path) {
|
|
1463
|
+
process.stderr.write("Workflow suggestion review receipt\n");
|
|
1464
|
+
process.stderr.write(`Decision: ${receipt.decision}\n`);
|
|
1465
|
+
process.stderr.write(`Asset: ${receipt.asset}\n`);
|
|
1466
|
+
process.stderr.write(`Workflow: ${receipt.workflow_id}\n`);
|
|
1467
|
+
process.stderr.write(`Suggestion: ${receipt.suggestion_id}\n`);
|
|
1468
|
+
process.stderr.write(`Suggestion digest: ${receipt.suggestion_digest}\n`);
|
|
1469
|
+
process.stderr.write(`Binding digest: ${receipt.binding_digest}\n`);
|
|
1470
|
+
process.stderr.write(`Receipt path: ${path}\n`);
|
|
1471
|
+
process.stderr.write(`Mutation: writes this local review receipt only; it does not edit workflow assets, mutate policy, adopt assets, bind organizations, or sync runtimes. ${receipt.decision === "accept" ? "Acceptance authorizes the matching improve-from-suggestions route only" : "This decision blocks improve-from-suggestions for this binding"}.\n`);
|
|
1472
|
+
}
|
|
1473
|
+
function recordValue(value, key) {
|
|
1474
|
+
if (!isRecord(value))
|
|
1475
|
+
return undefined;
|
|
1476
|
+
const child = value[key];
|
|
1477
|
+
return isRecord(child) ? child : undefined;
|
|
1478
|
+
}
|
|
1479
|
+
function workflowRoles(workflow) {
|
|
1480
|
+
return new Set((workflow?.steps ?? []).map((step) => step.role).filter((role) => role.length > 0));
|
|
1481
|
+
}
|
|
1482
|
+
function controllerMatchesWorkflow(controller, workflowId) {
|
|
1483
|
+
return controller.workflow === workflowId ||
|
|
1484
|
+
controller.fix_workflow === workflowId ||
|
|
1485
|
+
controller.diagnosis_workflow === workflowId;
|
|
1486
|
+
}
|
|
1487
|
+
function controllerWorkflowIds(controller) {
|
|
1488
|
+
return [controller.workflow, controller.fix_workflow, controller.diagnosis_workflow]
|
|
1489
|
+
.filter((value) => typeof value === "string" && value.length > 0);
|
|
1490
|
+
}
|
|
1491
|
+
function controllerRoleIds(controller) {
|
|
1492
|
+
const roles = recordValue(controller, "roles");
|
|
1493
|
+
if (!roles)
|
|
1494
|
+
return [];
|
|
1495
|
+
return Object.values(roles).filter((value) => typeof value === "string" && value.length > 0);
|
|
1496
|
+
}
|
|
1497
|
+
function defaultRoleDefinition(roleId) {
|
|
1498
|
+
if (roleId === "verifier") {
|
|
1499
|
+
return {
|
|
1500
|
+
description: "Verifies workflow evidence before downstream review or completion claims.",
|
|
1501
|
+
tool_permissions: {
|
|
1502
|
+
allow: ["Read", "Grep", "Glob", "Bash", "Write"],
|
|
1503
|
+
deny: ["Edit", "NotebookEdit"],
|
|
1504
|
+
},
|
|
1505
|
+
scope: {
|
|
1506
|
+
read: ["**/*", ".dna/**", "docs/**"],
|
|
1507
|
+
write: [".dna/fix-progress/**", ".dna/evidence-review/**"],
|
|
1508
|
+
},
|
|
1509
|
+
instructions: [
|
|
1510
|
+
"Run the smallest meaningful verification command for the current workflow step.",
|
|
1511
|
+
"Record command, result, evidence paths, remaining failures, and blockers.",
|
|
1512
|
+
"Do not claim completion without fresh evidence.",
|
|
1513
|
+
],
|
|
1514
|
+
};
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
description: `Generated role placeholder for ${roleId}; review and tighten before adoption.`,
|
|
1518
|
+
tool_permissions: {
|
|
1519
|
+
allow: ["Read", "Grep", "Glob", "Bash"],
|
|
1520
|
+
deny: ["Edit", "Write", "NotebookEdit"],
|
|
1521
|
+
},
|
|
1522
|
+
scope: {
|
|
1523
|
+
read: ["**/*"],
|
|
1524
|
+
write: [],
|
|
1525
|
+
},
|
|
1526
|
+
instructions: [
|
|
1527
|
+
"Review this generated role before runtime sync.",
|
|
1528
|
+
"Keep permissions minimal until the workflow asset is adopted.",
|
|
1529
|
+
],
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
function templateContextFromParsed(parsed, workflowId, proposedWorkflow, opts = {}) {
|
|
1533
|
+
if (!isRecord(parsed) || !workflowId)
|
|
1534
|
+
return undefined;
|
|
1535
|
+
const sourceWorkflows = recordValue(parsed, "workflows");
|
|
1536
|
+
const sourceRoles = recordValue(parsed, "roles");
|
|
1537
|
+
const sourceControllers = recordValue(parsed, "controllers");
|
|
1538
|
+
if (!sourceWorkflows && !sourceRoles && !sourceControllers)
|
|
1539
|
+
return undefined;
|
|
1540
|
+
const workflowIds = new Set(opts.preserveAllWorkflows && sourceWorkflows ? Object.keys(sourceWorkflows) : [workflowId]);
|
|
1541
|
+
workflowIds.add(workflowId);
|
|
1542
|
+
const controllers = {};
|
|
1543
|
+
if (sourceControllers) {
|
|
1544
|
+
for (const [controllerId, controller] of Object.entries(sourceControllers)) {
|
|
1545
|
+
if (!isRecord(controller))
|
|
1546
|
+
continue;
|
|
1547
|
+
if (!opts.preserveAllWorkflows && !controllerMatchesWorkflow(controller, workflowId))
|
|
1548
|
+
continue;
|
|
1549
|
+
controllers[controllerId] = controller;
|
|
1550
|
+
for (const id of controllerWorkflowIds(controller))
|
|
1551
|
+
workflowIds.add(id);
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
const workflows = {};
|
|
1555
|
+
for (const id of workflowIds) {
|
|
1556
|
+
const sourceWorkflow = sourceWorkflows?.[id];
|
|
1557
|
+
const parsedWorkflow = id === workflowId ? proposedWorkflow : parseWorkflow(sourceWorkflow);
|
|
1558
|
+
if (parsedWorkflow)
|
|
1559
|
+
workflows[id] = parsedWorkflow;
|
|
1560
|
+
}
|
|
1561
|
+
if (Object.keys(workflows).length === 0)
|
|
1562
|
+
workflows[workflowId] = proposedWorkflow;
|
|
1563
|
+
const roleIds = new Set();
|
|
1564
|
+
for (const workflow of Object.values(workflows)) {
|
|
1565
|
+
for (const role of workflowRoles(workflow))
|
|
1566
|
+
roleIds.add(role);
|
|
1567
|
+
}
|
|
1568
|
+
for (const controller of Object.values(controllers)) {
|
|
1569
|
+
if (!isRecord(controller))
|
|
1570
|
+
continue;
|
|
1571
|
+
for (const role of controllerRoleIds(controller))
|
|
1572
|
+
roleIds.add(role);
|
|
1573
|
+
}
|
|
1574
|
+
const roles = {};
|
|
1575
|
+
if (sourceRoles) {
|
|
1576
|
+
for (const roleId of roleIds) {
|
|
1577
|
+
if (sourceRoles[roleId] !== undefined)
|
|
1578
|
+
roles[roleId] = sourceRoles[roleId];
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
for (const roleId of roleIds) {
|
|
1582
|
+
if (roles[roleId] === undefined)
|
|
1583
|
+
roles[roleId] = defaultRoleDefinition(roleId);
|
|
1584
|
+
}
|
|
1585
|
+
const extraSections = {};
|
|
1586
|
+
for (const key of ["variables", "context_files", "verifier_policy"]) {
|
|
1587
|
+
if (parsed[key] !== undefined)
|
|
1588
|
+
extraSections[key] = parsed[key];
|
|
1589
|
+
}
|
|
1590
|
+
return {
|
|
1591
|
+
workflowId,
|
|
1592
|
+
workflows,
|
|
1593
|
+
roles: Object.keys(roles).length > 0 ? roles : undefined,
|
|
1594
|
+
controllers: Object.keys(controllers).length > 0 ? controllers : undefined,
|
|
1595
|
+
extraSections: Object.keys(extraSections).length > 0 ? extraSections : undefined,
|
|
1596
|
+
};
|
|
1597
|
+
}
|
|
1598
|
+
async function loadWorkflowFromTemplate(name, projectDir) {
|
|
1599
|
+
const template = await findTemplateCatalogEntry(name, { projectDir });
|
|
1600
|
+
if (!template)
|
|
1601
|
+
return { templateName: name, workflows: {} };
|
|
1602
|
+
const parsed = parseYAML(await readFile(template.path, "utf-8"));
|
|
1603
|
+
return {
|
|
1604
|
+
templateName: template.name,
|
|
1605
|
+
workflows: workflowMapFromTemplateYaml(parsed),
|
|
1606
|
+
path: template.path,
|
|
1607
|
+
parsed,
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
function writeExamples() {
|
|
1611
|
+
process.stderr.write("Workflow draft examples:\n");
|
|
1612
|
+
for (const example of workflowDraftExamples()) {
|
|
1613
|
+
process.stderr.write(` - ${example.id}: ${example.label} — ${example.summary}\n`);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
function writeWorkflowIntake(packet) {
|
|
1617
|
+
process.stderr.write("Workflow intake\n");
|
|
1618
|
+
process.stderr.write(`Mode: ${packet.mode}\n`);
|
|
1619
|
+
process.stderr.write(`Goal: ${packet.input.description}\n`);
|
|
1620
|
+
process.stderr.write(`Recommended path: ${packet.recommendation.path}\n`);
|
|
1621
|
+
if (packet.recommendation.asset)
|
|
1622
|
+
process.stderr.write(`Recommended asset: ${packet.recommendation.asset}\n`);
|
|
1623
|
+
process.stderr.write(`Reason: ${packet.recommendation.reason}\n`);
|
|
1624
|
+
process.stderr.write(`Mutation boundary: ${packet.mutationBoundary}\n`);
|
|
1625
|
+
process.stderr.write("\nBest matches:\n");
|
|
1626
|
+
if (packet.matches.length === 0) {
|
|
1627
|
+
process.stderr.write(" - none\n");
|
|
1628
|
+
}
|
|
1629
|
+
else {
|
|
1630
|
+
for (const match of packet.matches) {
|
|
1631
|
+
process.stderr.write(` - ${match.name} score=${match.score} matched=${match.matchedKeywords.join(", ")}\n`);
|
|
1632
|
+
if (match.rankingSignals.length > 0)
|
|
1633
|
+
process.stderr.write(` ranking: ${match.rankingSignals.join(", ")}\n`);
|
|
1634
|
+
if (match.asset) {
|
|
1635
|
+
process.stderr.write(` asset: ${match.asset.status}, evidence=${match.asset.evidence}, cascade=${match.asset.cascade}, evolution=${match.asset.evolution}${match.asset.dogfood ? ", dogfood" : ""}\n`);
|
|
1636
|
+
if (match.asset.positioning)
|
|
1637
|
+
process.stderr.write(` why: ${match.asset.positioning}\n`);
|
|
1638
|
+
}
|
|
1639
|
+
else {
|
|
1640
|
+
process.stderr.write(" asset: not found in current template catalog\n");
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
process.stderr.write("\nReuse review path:\n");
|
|
1645
|
+
for (const command of packet.reviewPath.reuse)
|
|
1646
|
+
process.stderr.write(` - ${command}\n`);
|
|
1647
|
+
process.stderr.write("\nAuthoring path:\n");
|
|
1648
|
+
process.stderr.write(` - primary: ${packet.authoringPacket.command}\n`);
|
|
1649
|
+
process.stderr.write(` - packet schema: ${packet.authoringPacket.schemaVersion}\n`);
|
|
1650
|
+
process.stderr.write(` - boundary: ${packet.authoringPacket.boundary}\n`);
|
|
1651
|
+
process.stderr.write(" - scaffold fallback only:\n");
|
|
1652
|
+
process.stderr.write(` - example: ${packet.draft.example}\n`);
|
|
1653
|
+
process.stderr.write(` - save template: ${packet.draft.saveTemplate}\n`);
|
|
1654
|
+
process.stderr.write(` - note: ${packet.draft.note}\n`);
|
|
1655
|
+
for (const command of packet.reviewPath.draft)
|
|
1656
|
+
process.stderr.write(` - ${command}\n`);
|
|
1657
|
+
process.stderr.write("\nNext commands:\n");
|
|
1658
|
+
for (const command of packet.nextCommands)
|
|
1659
|
+
process.stderr.write(` - ${command}\n`);
|
|
1660
|
+
}
|
|
1661
|
+
function writeWorkflowAuthoringRoute(route) {
|
|
1662
|
+
process.stderr.write("Workflow authoring route\n");
|
|
1663
|
+
process.stderr.write(`Mode: ${route.mode}\n`);
|
|
1664
|
+
process.stderr.write(`Scope: ${route.scope}\n`);
|
|
1665
|
+
process.stderr.write(`Goal: ${route.input.description}\n`);
|
|
1666
|
+
process.stderr.write(`Sync target: ${route.input.syncTarget}\n`);
|
|
1667
|
+
process.stderr.write(`Recommended path: ${route.recommendation.path}\n`);
|
|
1668
|
+
if (route.recommendation.asset)
|
|
1669
|
+
process.stderr.write(`Recommended asset: ${route.recommendation.asset}\n`);
|
|
1670
|
+
process.stderr.write(`Selected asset: ${route.asset.selected} (${route.asset.source})\n`);
|
|
1671
|
+
process.stderr.write(`Current stage: ${route.current.stage}\n`);
|
|
1672
|
+
process.stderr.write(`Current command: ${route.current.command}\n`);
|
|
1673
|
+
process.stderr.write(`Mutation boundary: ${route.mutationBoundary}\n`);
|
|
1674
|
+
if (route.scope === "personal") {
|
|
1675
|
+
process.stderr.write("\nPersonal path:\n");
|
|
1676
|
+
process.stderr.write(` ${route.userPath.map((step) => step.label).join(" -> ")}\n`);
|
|
1677
|
+
for (const step of route.userPath) {
|
|
1678
|
+
process.stderr.write(` - ${step.id}: ${step.status}; ${step.command}\n`);
|
|
1679
|
+
}
|
|
1680
|
+
if (route.current.blockers.length > 0) {
|
|
1681
|
+
process.stderr.write("\nCurrent blockers:\n");
|
|
1682
|
+
for (const blocker of route.current.blockers.slice(0, 3))
|
|
1683
|
+
process.stderr.write(` - ${blocker}\n`);
|
|
1684
|
+
}
|
|
1685
|
+
process.stderr.write("\nNext commands:\n");
|
|
1686
|
+
for (const command of route.nextCommands)
|
|
1687
|
+
process.stderr.write(` - ${command}\n`);
|
|
1688
|
+
process.stderr.write(" - rerun with --org for organization rollout, configure, deploy, improve, and evolution gates\n");
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
process.stderr.write(`Organization: ${route.input.organization}\n`);
|
|
1692
|
+
process.stderr.write(`Project: ${route.input.project}\n`);
|
|
1693
|
+
process.stderr.write(`Draft template: ${route.asset.draftTemplate}\n`);
|
|
1694
|
+
process.stderr.write(`Next version: ${route.asset.nextVersion}\n`);
|
|
1695
|
+
process.stderr.write(`Cascade: ${route.governance.selectedAsset.cascade} -> ${route.governance.nextAsset.cascade}\n`);
|
|
1696
|
+
process.stderr.write(`Evolution: ${route.governance.selectedAsset.evolution} -> ${route.governance.nextAsset.evolution}\n`);
|
|
1697
|
+
process.stderr.write(`Evidence: ${route.governance.selectedAsset.evidence} -> ${route.governance.nextAsset.evidence}\n`);
|
|
1698
|
+
process.stderr.write(`Current reason: ${route.current.reason}\n`);
|
|
1699
|
+
process.stderr.write(`Current writes: ${route.current.writes ? "yes" : "no"}\n`);
|
|
1700
|
+
process.stderr.write("\nAuthoring choices:\n");
|
|
1701
|
+
for (const choice of route.choices) {
|
|
1702
|
+
process.stderr.write(` - ${choice.id}: ${choice.status} writes=${choice.writes ? "yes" : "no"}\n`);
|
|
1703
|
+
process.stderr.write(` ${choice.command}\n`);
|
|
1704
|
+
process.stderr.write(` why: ${choice.reason}\n`);
|
|
1705
|
+
process.stderr.write(` next: ${choice.next}\n`);
|
|
1706
|
+
}
|
|
1707
|
+
process.stderr.write("\nUser path:\n");
|
|
1708
|
+
for (const step of route.userPath) {
|
|
1709
|
+
process.stderr.write(` - ${step.id}: ${step.status} owner=${step.owner} writes=${step.writes ? "yes" : "no"}\n`);
|
|
1710
|
+
process.stderr.write(` ${step.command}\n`);
|
|
1711
|
+
process.stderr.write(` why: ${step.purpose}\n`);
|
|
1712
|
+
}
|
|
1713
|
+
process.stderr.write("\nStatus:\n");
|
|
1714
|
+
process.stderr.write(` summary: ${route.status.plainSummary}\n`);
|
|
1715
|
+
process.stderr.write(` readiness: ${route.status.readiness}\n`);
|
|
1716
|
+
process.stderr.write(` current step: ${route.status.currentStep.id} (${route.status.currentStep.owner}; writes: ${route.status.currentStep.writes ? "yes" : "no"})\n`);
|
|
1717
|
+
process.stderr.write(" do now:\n");
|
|
1718
|
+
for (const item of route.status.now)
|
|
1719
|
+
process.stderr.write(` - ${item}\n`);
|
|
1720
|
+
process.stderr.write(" not yet:\n");
|
|
1721
|
+
for (const item of route.status.notYet)
|
|
1722
|
+
process.stderr.write(` - ${item}\n`);
|
|
1723
|
+
process.stderr.write(" evidence loop:\n");
|
|
1724
|
+
for (const item of route.status.evidenceLoop)
|
|
1725
|
+
process.stderr.write(` - ${item}\n`);
|
|
1726
|
+
process.stderr.write("\nDecision packet:\n");
|
|
1727
|
+
process.stderr.write(` current: ${route.decisionPacket.currentState}\n`);
|
|
1728
|
+
process.stderr.write(` primary action: ${route.decisionPacket.primaryAction}\n`);
|
|
1729
|
+
process.stderr.write(` risk: ${route.decisionPacket.risk}\n`);
|
|
1730
|
+
process.stderr.write(` reason: ${route.decisionPacket.reason}\n`);
|
|
1731
|
+
process.stderr.write(" allowed now:\n");
|
|
1732
|
+
for (const item of route.decisionPacket.allowedNow)
|
|
1733
|
+
process.stderr.write(` - ${item}\n`);
|
|
1734
|
+
process.stderr.write(" blocked now:\n");
|
|
1735
|
+
for (const item of route.decisionPacket.blockedNow)
|
|
1736
|
+
process.stderr.write(` - ${item}\n`);
|
|
1737
|
+
process.stderr.write("\nReview path:\n");
|
|
1738
|
+
for (const command of route.reviewPath)
|
|
1739
|
+
process.stderr.write(` - ${command}\n`);
|
|
1740
|
+
if (route.alternateEditPath.length > 0) {
|
|
1741
|
+
process.stderr.write("\nAlternate edit path:\n");
|
|
1742
|
+
for (const command of route.alternateEditPath)
|
|
1743
|
+
process.stderr.write(` - ${command}\n`);
|
|
1744
|
+
}
|
|
1745
|
+
process.stderr.write("\nEdit operations:\n");
|
|
1746
|
+
for (const choice of route.editOperations) {
|
|
1747
|
+
process.stderr.write(` - ${choice.operation}: ${choice.useWhen} (target: ${choice.targetStep}; writes: ${choice.writes ? "yes" : "no"})\n`);
|
|
1748
|
+
process.stderr.write(` ${choice.command}\n`);
|
|
1749
|
+
}
|
|
1750
|
+
process.stderr.write("\nSpine:\n");
|
|
1751
|
+
process.stderr.write(` ${route.spine.join(" -> ")}\n`);
|
|
1752
|
+
process.stderr.write("\nPhases:\n");
|
|
1753
|
+
for (const phase of route.phases) {
|
|
1754
|
+
process.stderr.write(` - ${phase.id}. ${phase.label}\n`);
|
|
1755
|
+
process.stderr.write(` command: ${phase.command}\n`);
|
|
1756
|
+
process.stderr.write(` owner: ${phase.owner}; writes: ${phase.writes ? "yes" : "no"}\n`);
|
|
1757
|
+
process.stderr.write(` why: ${phase.purpose}\n`);
|
|
1758
|
+
}
|
|
1759
|
+
process.stderr.write("\nCheckpoints:\n");
|
|
1760
|
+
for (const checkpoint of route.checkpoints)
|
|
1761
|
+
process.stderr.write(` - ${checkpoint}\n`);
|
|
1762
|
+
process.stderr.write("\nGovernance route:\n");
|
|
1763
|
+
process.stderr.write(` - principle: ${route.governance.principle}\n`);
|
|
1764
|
+
process.stderr.write(` - cascade review: ${route.governance.cascadeCommand}\n`);
|
|
1765
|
+
process.stderr.write(` - evolution preview: ${route.governance.evolutionPreviewCommand}\n`);
|
|
1766
|
+
process.stderr.write(` - improvement review: ${route.governance.improvementCommand}\n`);
|
|
1767
|
+
process.stderr.write(` - boundary: ${route.governance.mutationBoundary}\n`);
|
|
1768
|
+
if (route.current.blockers.length > 0) {
|
|
1769
|
+
process.stderr.write("\nCurrent blockers:\n");
|
|
1770
|
+
for (const blocker of route.current.blockers)
|
|
1771
|
+
process.stderr.write(` - ${blocker}\n`);
|
|
1772
|
+
}
|
|
1773
|
+
process.stderr.write("\nNext commands:\n");
|
|
1774
|
+
for (const command of route.nextCommands)
|
|
1775
|
+
process.stderr.write(` - ${command}\n`);
|
|
1776
|
+
}
|
|
1777
|
+
function writeAuthoringFields(label, fields) {
|
|
1778
|
+
process.stderr.write(` ${label}:\n`);
|
|
1779
|
+
for (const field of fields) {
|
|
1780
|
+
const allowed = field.allowed_values?.length ? `; allowed=${field.allowed_values.join("|")}` : "";
|
|
1781
|
+
const notes = field.notes ? `; ${field.notes}` : "";
|
|
1782
|
+
process.stderr.write(` - ${field.name}: ${field.type}; ${field.required ? "required" : "optional"}${allowed}${notes}\n`);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
function writeAuthoringPacket(packet) {
|
|
1786
|
+
process.stderr.write("Workflow authoring packet\n");
|
|
1787
|
+
process.stderr.write(`Schema: ${packet.schema_version}\n`);
|
|
1788
|
+
process.stderr.write(`Project root: ${packet.project_context.root}\n`);
|
|
1789
|
+
if (packet.goal_context) {
|
|
1790
|
+
process.stderr.write(`Goal context: ${packet.goal_context.raw}\n`);
|
|
1791
|
+
process.stderr.write("Goal handling: opaque context only; no goal modeling.\n");
|
|
1792
|
+
}
|
|
1793
|
+
process.stderr.write(`Runtime authority: ${packet.runtime_authority}\n`);
|
|
1794
|
+
process.stderr.write(`Mutation performed: ${packet.mutation_performed}\n`);
|
|
1795
|
+
process.stderr.write(`Network performed: ${packet.network_performed}\n`);
|
|
1796
|
+
process.stderr.write("\nAsset contract:\n");
|
|
1797
|
+
process.stderr.write(` - source: ${packet.asset_contract.source}\n`);
|
|
1798
|
+
process.stderr.write(` - required: ${packet.asset_contract.required_fields.join(", ")}\n`);
|
|
1799
|
+
process.stderr.write(` - recommended: ${packet.asset_contract.recommended_fields.join(", ")}\n`);
|
|
1800
|
+
process.stderr.write(` - validation shell required: ${packet.asset_contract.validation_required_fields.join(", ")}\n`);
|
|
1801
|
+
process.stderr.write(` - project templates: ${packet.asset_contract.directory_protocol.project_templates}\n`);
|
|
1802
|
+
process.stderr.write(` - candidate staging: ${packet.asset_contract.directory_protocol.candidate_staging}\n`);
|
|
1803
|
+
for (const note of packet.asset_contract.validation_notes)
|
|
1804
|
+
process.stderr.write(` - validation note: ${note}\n`);
|
|
1805
|
+
process.stderr.write("\nSchema constraints:\n");
|
|
1806
|
+
process.stderr.write(` - source: ${packet.schema_constraints.source}\n`);
|
|
1807
|
+
writeAuthoringFields("workflow fields", packet.schema_constraints.workflow);
|
|
1808
|
+
writeAuthoringFields("step fields", packet.schema_constraints.step);
|
|
1809
|
+
writeAuthoringFields("role fields", packet.schema_constraints.role);
|
|
1810
|
+
writeAuthoringFields("role tool_permissions fields", packet.schema_constraints.tool_permissions);
|
|
1811
|
+
writeAuthoringFields("role scope fields", packet.schema_constraints.scope);
|
|
1812
|
+
writeAuthoringFields("handoff artifact fields", packet.schema_constraints.handoff_artifact.fields);
|
|
1813
|
+
writeAuthoringFields("checkpoint fields", packet.schema_constraints.checkpoint.fields);
|
|
1814
|
+
writeAuthoringFields("transition fields", packet.schema_constraints.transition);
|
|
1815
|
+
writeAuthoringFields("retry_policy fields", packet.schema_constraints.retry_policy);
|
|
1816
|
+
process.stderr.write("\nRed lines:\n");
|
|
1817
|
+
for (const line of packet.red_lines)
|
|
1818
|
+
process.stderr.write(` - ${line}\n`);
|
|
1819
|
+
process.stderr.write("\nInterview protocol:\n");
|
|
1820
|
+
for (const item of packet.interview_protocol) {
|
|
1821
|
+
process.stderr.write(` - ${item.id}: ${item.question}\n`);
|
|
1822
|
+
process.stderr.write(` output: ${item.required_output}\n`);
|
|
1823
|
+
}
|
|
1824
|
+
process.stderr.write("\nValidate loop:\n");
|
|
1825
|
+
for (const step of packet.validate_loop) {
|
|
1826
|
+
process.stderr.write(` - ${step.owner} writes=${step.writes ? "yes" : "no"}: ${step.step}\n`);
|
|
1827
|
+
if (step.command)
|
|
1828
|
+
process.stderr.write(` command: ${step.command}\n`);
|
|
1829
|
+
if (step.repeat_until)
|
|
1830
|
+
process.stderr.write(` until: ${step.repeat_until}\n`);
|
|
1831
|
+
}
|
|
1832
|
+
process.stderr.write("\nHandoff boundary:\n");
|
|
1833
|
+
for (const boundary of packet.handoff_boundary)
|
|
1834
|
+
process.stderr.write(` - ${boundary}\n`);
|
|
1835
|
+
process.stderr.write("\nExamples loaded from real templates:\n");
|
|
1836
|
+
for (const example of packet.examples) {
|
|
1837
|
+
process.stderr.write(` - ${example.name} (${example.content_hash})\n`);
|
|
1838
|
+
process.stderr.write(` source: ${example.source_path}\n`);
|
|
1839
|
+
process.stderr.write(` annotation: ${example.annotation}\n`);
|
|
1840
|
+
process.stderr.write(` workflows: ${example.workflow_keys.join(", ") || "none"}; roles: ${example.role_keys.join(", ") || "none"}\n`);
|
|
1841
|
+
process.stderr.write(" complete YAML:\n");
|
|
1842
|
+
for (const line of example.yaml.trimEnd().split("\n"))
|
|
1843
|
+
process.stderr.write(` ${line}\n`);
|
|
1844
|
+
}
|
|
1845
|
+
process.stderr.write("\nNext commands:\n");
|
|
1846
|
+
for (const command of packet.next_commands)
|
|
1847
|
+
process.stderr.write(` - ${command}\n`);
|
|
1848
|
+
process.stderr.write("\nThe --json mode carries the same complete contract as structured data.\n");
|
|
1849
|
+
}
|
|
1850
|
+
function writeDraft(draft) {
|
|
1851
|
+
process.stderr.write("Workflow draft\n");
|
|
1852
|
+
process.stderr.write(`Mode: ${draft.mode}\n`);
|
|
1853
|
+
process.stderr.write("Positioning: example-clone scaffold; structure is not derived from the goal.\n");
|
|
1854
|
+
process.stderr.write("Primary creation path: dna workflow authoring-packet\n");
|
|
1855
|
+
process.stderr.write(`Example: ${draft.input.exampleId}\n`);
|
|
1856
|
+
process.stderr.write(`Goal: ${draft.input.goal}\n`);
|
|
1857
|
+
process.stderr.write(`Strictness: ${draft.input.strictness}\n`);
|
|
1858
|
+
process.stderr.write(`Workflow: ${draft.workflow.name}\n`);
|
|
1859
|
+
process.stderr.write(`Steps: ${draft.validation.preview.step_count}\n`);
|
|
1860
|
+
process.stderr.write(`Retry max: ${draft.validation.preview.retry_max}\n`);
|
|
1861
|
+
process.stderr.write(`Write path: ${draft.validation.preview.write_path}\n`);
|
|
1862
|
+
process.stderr.write(`Validation: ${draft.validation.ok ? "ok" : "failed"}\n`);
|
|
1863
|
+
writeDesignPacket(draft.design_packet);
|
|
1864
|
+
process.stderr.write("\nSteps:\n");
|
|
1865
|
+
for (const step of draft.workflow.steps) {
|
|
1866
|
+
const deps = step.depends_on && step.depends_on.length > 0 ? ` after=${step.depends_on.join(",")}` : "";
|
|
1867
|
+
process.stderr.write(` - ${step.id} -> ${step.role}${deps}`);
|
|
1868
|
+
if (step.description)
|
|
1869
|
+
process.stderr.write(` — ${step.description}`);
|
|
1870
|
+
process.stderr.write("\n");
|
|
1871
|
+
}
|
|
1872
|
+
process.stderr.write("\nValidation messages:\n");
|
|
1873
|
+
for (const message of draft.validation.messages) {
|
|
1874
|
+
process.stderr.write(` - [${message.severity}] ${message.path}: ${message.message}\n`);
|
|
1875
|
+
}
|
|
1876
|
+
process.stderr.write("\nSafe flow:\n");
|
|
1877
|
+
for (const item of draft.safe_flow)
|
|
1878
|
+
process.stderr.write(` - ${item}\n`);
|
|
1879
|
+
process.stderr.write("\nForbidden:\n");
|
|
1880
|
+
for (const item of draft.forbidden)
|
|
1881
|
+
process.stderr.write(` - ${item}\n`);
|
|
1882
|
+
process.stderr.write("\nYAML preview:\n");
|
|
1883
|
+
process.stderr.write(draft.yaml);
|
|
1884
|
+
}
|
|
1885
|
+
function writeDesignPacket(packet) {
|
|
1886
|
+
process.stderr.write("\nWorkflow design packet:\n");
|
|
1887
|
+
process.stderr.write(` - workflow: ${packet.workflow}\n`);
|
|
1888
|
+
process.stderr.write(` - step count: ${packet.stepCount}\n`);
|
|
1889
|
+
process.stderr.write(` - editable operations: ${packet.editableOperations.join(", ")}\n`);
|
|
1890
|
+
process.stderr.write(` - mutation boundary: ${packet.mutationBoundary}\n`);
|
|
1891
|
+
process.stderr.write(` - prompt: ${packet.userPrompt}\n`);
|
|
1892
|
+
process.stderr.write(" - review commands:\n");
|
|
1893
|
+
for (const command of packet.reviewCommands)
|
|
1894
|
+
process.stderr.write(` - ${command}\n`);
|
|
1895
|
+
process.stderr.write(" - adoption commands:\n");
|
|
1896
|
+
for (const command of packet.adoptionCommands)
|
|
1897
|
+
process.stderr.write(` - ${command}\n`);
|
|
1898
|
+
}
|
|
1899
|
+
async function saveDraftTemplate(params) {
|
|
1900
|
+
const projectDir = params.projectDir ?? process.cwd();
|
|
1901
|
+
const templateName = normalizeTemplateName(params.name);
|
|
1902
|
+
const templatesDir = join(projectDir, ".dna", "templates");
|
|
1903
|
+
const path = join(templatesDir, `${templateName}.dna.yaml`);
|
|
1904
|
+
const yaml = workflowDraftToTemplateYaml({
|
|
1905
|
+
templateName,
|
|
1906
|
+
workflow: params.draft.workflow,
|
|
1907
|
+
generatedAt: params.draft.generated_at,
|
|
1908
|
+
lineage: params.lineage,
|
|
1909
|
+
evidence: params.evidence,
|
|
1910
|
+
templateContext: params.templateContext,
|
|
1911
|
+
});
|
|
1912
|
+
await mkdir(templatesDir, { recursive: true });
|
|
1913
|
+
await writeFile(path, yaml, { encoding: "utf-8", flag: params.force ? "w" : "wx" });
|
|
1914
|
+
return { templateName, path, yaml };
|
|
1915
|
+
}
|
|
1916
|
+
function evidenceLinkKey(link) {
|
|
1917
|
+
return `${link.kind}:${link.path}`;
|
|
1918
|
+
}
|
|
1919
|
+
function uniqueTemplateEvidenceLinks(links) {
|
|
1920
|
+
const seen = new Set();
|
|
1921
|
+
const unique = [];
|
|
1922
|
+
for (const link of links) {
|
|
1923
|
+
const key = evidenceLinkKey(link);
|
|
1924
|
+
if (seen.has(key))
|
|
1925
|
+
continue;
|
|
1926
|
+
seen.add(key);
|
|
1927
|
+
unique.push(link);
|
|
1928
|
+
}
|
|
1929
|
+
return unique;
|
|
1930
|
+
}
|
|
1931
|
+
function toTemplateEvidenceLink(link) {
|
|
1932
|
+
return {
|
|
1933
|
+
label: link.label,
|
|
1934
|
+
path: link.path,
|
|
1935
|
+
kind: link.kind,
|
|
1936
|
+
description: link.description,
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
function candidateTemplateLineage(candidate) {
|
|
1940
|
+
return {
|
|
1941
|
+
generatedFrom: "workflow_candidate",
|
|
1942
|
+
sourceAsset: candidate.sourceAsset,
|
|
1943
|
+
sourceCandidate: candidate.id,
|
|
1944
|
+
sourceEvidence: candidate.evidence,
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
function candidateTemplateEvidence(candidate) {
|
|
1948
|
+
const metadata = getWorkflowAssetMetadata(candidate.sourceAsset);
|
|
1949
|
+
const links = [];
|
|
1950
|
+
if (metadata?.dogfood) {
|
|
1951
|
+
links.push({
|
|
1952
|
+
label: `Active dogfood evidence: ${candidate.sourceAsset}`,
|
|
1953
|
+
path: join(metadata.dogfood.projectPath, metadata.dogfood.progressDir ?? ".dna/fix-progress"),
|
|
1954
|
+
kind: "dogfood",
|
|
1955
|
+
description: candidate.type === "active_evidence_followup"
|
|
1956
|
+
? `Saved unfinished-work draft from ${candidate.sourceAsset}: ${candidate.evidence}`
|
|
1957
|
+
: `Candidate ${candidate.id}: ${candidate.evidence}`,
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
links.push(...(metadata?.evidence ?? []).map(toTemplateEvidenceLink));
|
|
1961
|
+
return uniqueTemplateEvidenceLinks(links);
|
|
1962
|
+
}
|
|
1963
|
+
function suggestionTemplateLineage(params) {
|
|
1964
|
+
return {
|
|
1965
|
+
generatedFrom: "workflow_suggestion",
|
|
1966
|
+
sourceAsset: params.sourceAsset,
|
|
1967
|
+
sourceSuggestion: params.suggestionId,
|
|
1968
|
+
sourceEvidence: `suggestion:${params.suggestionId}`,
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
function suggestionTemplateEvidence(params) {
|
|
1972
|
+
if (params.suggestionId !== WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts)
|
|
1973
|
+
return [];
|
|
1974
|
+
return [{
|
|
1975
|
+
label: `C5 captured run facts: ${params.sourceAsset}`,
|
|
1976
|
+
path: ".dna/evidence/capture",
|
|
1977
|
+
kind: "reference",
|
|
1978
|
+
description: "Review local C5 JSONL facts with dna assets evidence before adopting this draft.",
|
|
1979
|
+
}];
|
|
1980
|
+
}
|
|
1981
|
+
function savedDesignPacket(templateName, workflow, goal) {
|
|
1982
|
+
return buildWorkflowDesignPacket({ templateName, workflow, goal });
|
|
1983
|
+
}
|
|
1984
|
+
function writeSavedTemplate(result, packet, options) {
|
|
1985
|
+
process.stderr.write(`Saved workflow asset draft: ${result.path}\n`);
|
|
1986
|
+
if (options?.scaffold) {
|
|
1987
|
+
process.stderr.write("Positioning: example-clone scaffold; structure is not derived from the goal.\n");
|
|
1988
|
+
process.stderr.write("Primary creation path: dna workflow authoring-packet\n");
|
|
1989
|
+
}
|
|
1990
|
+
process.stderr.write("Review before runtime use.\n");
|
|
1991
|
+
if (packet)
|
|
1992
|
+
writeDesignPacket(packet);
|
|
1993
|
+
process.stderr.write("\nNext commands:\n");
|
|
1994
|
+
process.stderr.write(` - dna assets inspect ${result.templateName} --evidence\n`);
|
|
1995
|
+
process.stderr.write(` - dna init --template ${result.templateName}\n`);
|
|
1996
|
+
process.stderr.write(" - dna assets review\n");
|
|
1997
|
+
}
|
|
1998
|
+
function writeExistingDraftRoute(templateName, sourceTemplate) {
|
|
1999
|
+
const normalized = normalizeTemplateName(templateName);
|
|
2000
|
+
process.stderr.write(`Workflow asset draft already exists: ${normalized}\n`);
|
|
2001
|
+
process.stderr.write("Review the existing draft before overwriting it.\n");
|
|
2002
|
+
process.stderr.write("\nReview path:\n");
|
|
2003
|
+
process.stderr.write(` - dna assets inspect ${normalized} --evidence\n`);
|
|
2004
|
+
if (sourceTemplate && sourceTemplate !== normalized) {
|
|
2005
|
+
process.stderr.write(` - dna assets diff ${sourceTemplate} ${normalized}\n`);
|
|
2006
|
+
}
|
|
2007
|
+
process.stderr.write(" - record explicit manager rationale for the diff before adoption or organization binding\n");
|
|
2008
|
+
process.stderr.write(` - dna assets adoption ${normalized}\n`);
|
|
2009
|
+
process.stderr.write(` - dna org bind --asset ${normalized} --status active --sync-target codex\n`);
|
|
2010
|
+
process.stderr.write("\nOverwrite only after review:\n");
|
|
2011
|
+
process.stderr.write(` - add --force to the save command if this draft should be replaced\n`);
|
|
2012
|
+
}
|
|
2013
|
+
function writeMissingWorkflowSelection(params) {
|
|
2014
|
+
process.stderr.write(`Workflow not found in template: ${params.requested}\n`);
|
|
2015
|
+
process.stderr.write(`Template: ${params.templatePath}\n`);
|
|
2016
|
+
process.stderr.write(`Available workflows: ${params.available.length > 0 ? params.available.join(", ") : "none"}\n`);
|
|
2017
|
+
}
|
|
2018
|
+
function suggestionToEditInput(params) {
|
|
2019
|
+
if (params.suggestionId === WORKFLOW_SUGGESTION_IDS.modelVerifier) {
|
|
2020
|
+
return {
|
|
2021
|
+
operation: "add_verifier",
|
|
2022
|
+
targetStep: "fix_bugs",
|
|
2023
|
+
role: "verifier",
|
|
2024
|
+
instruction: "Add a verifier for model behavior locks and placeholder assertion replacement.",
|
|
2025
|
+
};
|
|
2026
|
+
}
|
|
2027
|
+
if (params.suggestionId === WORKFLOW_SUGGESTION_IDS.blockedTriage) {
|
|
2028
|
+
return {
|
|
2029
|
+
operation: "tighten_boundary",
|
|
2030
|
+
targetStep: "review_changes",
|
|
2031
|
+
role: "fix_reviewer",
|
|
2032
|
+
instruction: "Require explicit blocked, skipped, or intentionally deferred scope triage before review completion.",
|
|
2033
|
+
};
|
|
2034
|
+
}
|
|
2035
|
+
if (params.suggestionId === WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts) {
|
|
2036
|
+
return {
|
|
2037
|
+
operation: "tighten_boundary",
|
|
2038
|
+
targetStep: "verify_report",
|
|
2039
|
+
role: "fix_reviewer",
|
|
2040
|
+
instruction: "Require explicit C5 captured run fact review, evidence refs, and manager rationale before changing the workflow asset.",
|
|
2041
|
+
};
|
|
2042
|
+
}
|
|
2043
|
+
return {
|
|
2044
|
+
operation: "add_verifier",
|
|
2045
|
+
targetStep: "fix_bugs",
|
|
2046
|
+
role: "verifier",
|
|
2047
|
+
instruction: params.fallbackInstruction || `Review suggestion ${params.suggestionId} for ${params.assetName} before changing the workflow asset.`,
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
function writeSuggestionEditPlan(params) {
|
|
2051
|
+
process.stderr.write("Workflow improvement from suggestions\n");
|
|
2052
|
+
process.stderr.write(`Mode: ${params.plan.mode}\n`);
|
|
2053
|
+
process.stderr.write(`Source asset: ${params.fromTemplate}\n`);
|
|
2054
|
+
if (params.workflowId)
|
|
2055
|
+
process.stderr.write(`Selected workflow: ${params.workflowId}\n`);
|
|
2056
|
+
process.stderr.write(`Suggestion: ${params.suggestionId}\n`);
|
|
2057
|
+
process.stderr.write("Mutation: none; this is a reviewable edit plan only.\n");
|
|
2058
|
+
writeEditPlan(params.plan);
|
|
2059
|
+
}
|
|
2060
|
+
function writeCandidateReviewOnly(candidate) {
|
|
2061
|
+
process.stderr.write("Workflow candidate action\n");
|
|
2062
|
+
process.stderr.write(`Candidate: ${candidate.id}\n`);
|
|
2063
|
+
process.stderr.write(`Title: ${candidate.title}\n`);
|
|
2064
|
+
process.stderr.write(`Source asset: ${candidate.sourceAsset}\n`);
|
|
2065
|
+
process.stderr.write(`Candidate asset: ${candidate.candidateAssetName}\n`);
|
|
2066
|
+
process.stderr.write(`Action: ${candidate.action.kind}\n`);
|
|
2067
|
+
process.stderr.write(`Reason: ${candidate.rationale}\n`);
|
|
2068
|
+
process.stderr.write(`Evidence: ${candidate.evidence}\n`);
|
|
2069
|
+
process.stderr.write(`Mutation boundary: ${candidate.mutationBoundary}\n`);
|
|
2070
|
+
if (candidate.continuityBoundary)
|
|
2071
|
+
process.stderr.write(`Continuity boundary: ${candidate.continuityBoundary}\n`);
|
|
2072
|
+
process.stderr.write("\nThis candidate does not create a new workflow draft.\n");
|
|
2073
|
+
if (candidate.action.kind === "review") {
|
|
2074
|
+
process.stderr.write(`Review command: ${candidate.action.command}\n`);
|
|
2075
|
+
process.stderr.write(`Review reason: ${candidate.action.reason}\n`);
|
|
2076
|
+
}
|
|
2077
|
+
process.stderr.write("\nNext commands:\n");
|
|
2078
|
+
for (const command of candidate.reviewCommands)
|
|
2079
|
+
process.stderr.write(` - ${command}\n`);
|
|
2080
|
+
}
|
|
2081
|
+
function candidateReviewPacket(params) {
|
|
2082
|
+
const inspect = `dna assets inspect ${params.templateName} --evidence`;
|
|
2083
|
+
const diff = `dna assets diff ${params.candidate.sourceAsset} ${params.templateName}`;
|
|
2084
|
+
const adoption = `dna assets adoption ${params.templateName}`;
|
|
2085
|
+
const bind = `dna org bind --asset ${params.templateName} --status active --sync-target codex`;
|
|
2086
|
+
const rollout = "dna org rollout";
|
|
2087
|
+
return {
|
|
2088
|
+
currentState: params.savedPath
|
|
2089
|
+
? `draft_saved:${params.templateName}`
|
|
2090
|
+
: `draft_preview:${params.templateName}`,
|
|
2091
|
+
primaryAction: inspect,
|
|
2092
|
+
reason: params.candidate.type === "active_evidence_followup"
|
|
2093
|
+
? "The candidate is now a saved unfinished-work draft. It preserves the source asset and must be inspected and diffed before adoption, organization binding, rollout, runtime sync, or evolution."
|
|
2094
|
+
: "The candidate is now a reviewable workflow asset draft. Inspect and diff it before adoption, organization binding, rollout, runtime sync, or evolution.",
|
|
2095
|
+
continuityBoundary: params.candidate.continuityBoundary,
|
|
2096
|
+
allowedNow: [
|
|
2097
|
+
params.candidate.type === "active_evidence_followup"
|
|
2098
|
+
? `Inspect the saved unfinished-work draft: ${inspect}.`
|
|
2099
|
+
: `Inspect the saved draft: ${inspect}.`,
|
|
2100
|
+
`Compare it against the source asset: ${diff}.`,
|
|
2101
|
+
`Record explicit manager rationale for the diff before adoption or organization binding.`,
|
|
2102
|
+
`Run adoption review only after the diff is acceptable: ${adoption}.`,
|
|
2103
|
+
],
|
|
2104
|
+
blockedNow: [
|
|
2105
|
+
"Runtime sync is blocked until the draft is reviewed, adopted, organization-bound, configured if needed, and rollout-ready.",
|
|
2106
|
+
"Evolution marker mutation is blocked while active dogfood evidence remains open.",
|
|
2107
|
+
"Vercel deployment is not a substitute for workflow review, organization rollout, or runtime sync.",
|
|
2108
|
+
],
|
|
2109
|
+
reviewPath: [inspect, diff, "record explicit manager rationale for the diff before adoption or organization binding", adoption, bind, rollout],
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
function suggestionReviewPacket(params) {
|
|
2113
|
+
const evidence = `dna assets evidence ${params.sourceAsset} --json`;
|
|
2114
|
+
const inspect = `dna assets inspect ${params.templateName} --evidence`;
|
|
2115
|
+
const diff = `dna assets diff ${params.sourceAsset} ${params.templateName}`;
|
|
2116
|
+
const adoption = `dna assets adoption ${params.templateName}`;
|
|
2117
|
+
const bind = `dna org bind --asset ${params.templateName} --status active --sync-target codex`;
|
|
2118
|
+
const rollout = "dna org rollout";
|
|
2119
|
+
const c5 = params.suggestionId === WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts;
|
|
2120
|
+
return {
|
|
2121
|
+
currentState: params.savedPath
|
|
2122
|
+
? `draft_saved:${params.templateName}`
|
|
2123
|
+
: `draft_preview:${params.templateName}`,
|
|
2124
|
+
primaryAction: evidence,
|
|
2125
|
+
reason: c5
|
|
2126
|
+
? "The C5 suggestion is now a saved workflow draft. Review captured local run facts, inspect the draft, and diff it against the source asset before adoption, organization binding, rollout, or runtime sync."
|
|
2127
|
+
: "The suggestion is now a saved workflow draft. Review source evidence, inspect the draft, and diff it against the source asset before adoption, organization binding, rollout, or runtime sync.",
|
|
2128
|
+
allowedNow: [
|
|
2129
|
+
`Review source evidence: ${evidence}.`,
|
|
2130
|
+
`Inspect the saved draft: ${inspect}.`,
|
|
2131
|
+
`Compare it against the source asset: ${diff}.`,
|
|
2132
|
+
"Record explicit manager rationale for the diff before adoption or organization binding.",
|
|
2133
|
+
`Run adoption review only after the diff is acceptable: ${adoption}.`,
|
|
2134
|
+
],
|
|
2135
|
+
blockedNow: [
|
|
2136
|
+
"Runtime sync is blocked until the draft is reviewed, adopted, organization-bound, configured if needed, and rollout-ready.",
|
|
2137
|
+
"Suggestion evidence cannot directly promote, adopt, bind, sync, upload, or mutate the workflow asset.",
|
|
2138
|
+
"Online, marketplace, and Supabase can project or distribute assets only; they do not perform runtime allow/warn/block/validate.",
|
|
2139
|
+
],
|
|
2140
|
+
reviewPath: [evidence, inspect, diff, "record explicit manager rationale for the diff before adoption or organization binding", adoption, bind, rollout],
|
|
2141
|
+
};
|
|
2142
|
+
}
|
|
2143
|
+
function candidateSavedDesignPacket(params) {
|
|
2144
|
+
const base = savedDesignPacket(params.templateName, params.workflow, params.goal);
|
|
2145
|
+
const reviewPacket = candidateReviewPacket({
|
|
2146
|
+
candidate: params.candidate,
|
|
2147
|
+
templateName: params.templateName,
|
|
2148
|
+
});
|
|
2149
|
+
return {
|
|
2150
|
+
...base,
|
|
2151
|
+
reviewCommands: reviewPacket.reviewPath.slice(0, 4),
|
|
2152
|
+
adoptionCommands: [...reviewPacket.reviewPath.slice(4), "dna sync --codex"],
|
|
2153
|
+
mutationBoundary: [
|
|
2154
|
+
base.mutationBoundary,
|
|
2155
|
+
reviewPacket.reason,
|
|
2156
|
+
reviewPacket.continuityBoundary,
|
|
2157
|
+
].filter(Boolean).join(" "),
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
function suggestionSavedDesignPacket(params) {
|
|
2161
|
+
const base = savedDesignPacket(params.templateName, params.workflow, params.goal);
|
|
2162
|
+
const reviewPacket = suggestionReviewPacket({
|
|
2163
|
+
sourceAsset: params.sourceAsset,
|
|
2164
|
+
suggestionId: params.suggestionId,
|
|
2165
|
+
templateName: params.templateName,
|
|
2166
|
+
});
|
|
2167
|
+
return {
|
|
2168
|
+
...base,
|
|
2169
|
+
reviewCommands: reviewPacket.reviewPath.slice(0, 5),
|
|
2170
|
+
adoptionCommands: [...reviewPacket.reviewPath.slice(5), "dna sync --codex"],
|
|
2171
|
+
mutationBoundary: [
|
|
2172
|
+
base.mutationBoundary,
|
|
2173
|
+
reviewPacket.reason,
|
|
2174
|
+
"The saved draft is not adopted, organization-bound, synced, uploaded, or treated as runtime policy truth by this command.",
|
|
2175
|
+
].join(" "),
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
function writeCandidateReviewPacket(packet) {
|
|
2179
|
+
process.stderr.write("\nCandidate review packet:\n");
|
|
2180
|
+
process.stderr.write(` current: ${packet.currentState}\n`);
|
|
2181
|
+
process.stderr.write(` primary action: ${packet.primaryAction}\n`);
|
|
2182
|
+
process.stderr.write(` reason: ${packet.reason}\n`);
|
|
2183
|
+
if (packet.continuityBoundary)
|
|
2184
|
+
process.stderr.write(` continuity boundary: ${packet.continuityBoundary}\n`);
|
|
2185
|
+
process.stderr.write(" allowed now:\n");
|
|
2186
|
+
for (const item of packet.allowedNow)
|
|
2187
|
+
process.stderr.write(` - ${item}\n`);
|
|
2188
|
+
process.stderr.write(" blocked now:\n");
|
|
2189
|
+
for (const item of packet.blockedNow)
|
|
2190
|
+
process.stderr.write(` - ${item}\n`);
|
|
2191
|
+
process.stderr.write(" review path:\n");
|
|
2192
|
+
for (const command of packet.reviewPath)
|
|
2193
|
+
process.stderr.write(` - ${command}\n`);
|
|
2194
|
+
}
|
|
2195
|
+
function writeSuggestionReviewPacket(packet) {
|
|
2196
|
+
process.stderr.write("\nSuggestion review packet:\n");
|
|
2197
|
+
process.stderr.write(` current: ${packet.currentState}\n`);
|
|
2198
|
+
process.stderr.write(` primary action: ${packet.primaryAction}\n`);
|
|
2199
|
+
process.stderr.write(` reason: ${packet.reason}\n`);
|
|
2200
|
+
process.stderr.write(" allowed now:\n");
|
|
2201
|
+
for (const item of packet.allowedNow)
|
|
2202
|
+
process.stderr.write(` - ${item}\n`);
|
|
2203
|
+
process.stderr.write(" blocked now:\n");
|
|
2204
|
+
for (const item of packet.blockedNow)
|
|
2205
|
+
process.stderr.write(` - ${item}\n`);
|
|
2206
|
+
process.stderr.write(" review path:\n");
|
|
2207
|
+
for (const command of packet.reviewPath)
|
|
2208
|
+
process.stderr.write(` - ${command}\n`);
|
|
2209
|
+
}
|
|
2210
|
+
function writeSuggestionSavedTemplate(params) {
|
|
2211
|
+
process.stderr.write(`Saved workflow asset draft: ${params.saved.path}\n`);
|
|
2212
|
+
process.stderr.write("Review before runtime use.\n");
|
|
2213
|
+
writeDesignPacket(suggestionSavedDesignPacket({
|
|
2214
|
+
sourceAsset: params.sourceAsset,
|
|
2215
|
+
suggestionId: params.suggestionId,
|
|
2216
|
+
templateName: params.saved.templateName,
|
|
2217
|
+
workflow: params.workflow,
|
|
2218
|
+
goal: params.goal,
|
|
2219
|
+
}));
|
|
2220
|
+
const reviewPacket = suggestionReviewPacket({
|
|
2221
|
+
sourceAsset: params.sourceAsset,
|
|
2222
|
+
suggestionId: params.suggestionId,
|
|
2223
|
+
templateName: params.saved.templateName,
|
|
2224
|
+
savedPath: params.saved.path,
|
|
2225
|
+
});
|
|
2226
|
+
writeSuggestionReviewPacket(reviewPacket);
|
|
2227
|
+
process.stderr.write("\nNext commands:\n");
|
|
2228
|
+
for (const command of reviewPacket.reviewPath)
|
|
2229
|
+
process.stderr.write(` - ${command}\n`);
|
|
2230
|
+
}
|
|
2231
|
+
function writeCandidateDraft(params) {
|
|
2232
|
+
process.stderr.write("Workflow from candidate\n");
|
|
2233
|
+
process.stderr.write(`Candidate: ${params.candidate.id}\n`);
|
|
2234
|
+
process.stderr.write(`Title: ${params.candidate.title}\n`);
|
|
2235
|
+
process.stderr.write(`Source asset: ${params.candidate.sourceAsset}\n`);
|
|
2236
|
+
process.stderr.write(`Candidate asset: ${params.candidate.candidateAssetName}\n`);
|
|
2237
|
+
process.stderr.write(`Action: ${params.candidate.action.kind}\n`);
|
|
2238
|
+
process.stderr.write(`Evidence: ${params.candidate.evidence}\n`);
|
|
2239
|
+
process.stderr.write(`Rationale: ${params.candidate.rationale}\n`);
|
|
2240
|
+
process.stderr.write(`Mutation boundary: ${params.candidate.mutationBoundary}\n`);
|
|
2241
|
+
if (params.saved) {
|
|
2242
|
+
process.stderr.write(`\nSaved workflow asset draft: ${params.saved.path}\n`);
|
|
2243
|
+
process.stderr.write("Review before runtime use.\n");
|
|
2244
|
+
const workflow = params.plan?.proposed_workflow ?? params.draft?.workflow;
|
|
2245
|
+
const goal = params.plan?.input.goal ?? params.draft?.input.goal;
|
|
2246
|
+
if (workflow && goal) {
|
|
2247
|
+
writeDesignPacket(candidateSavedDesignPacket({
|
|
2248
|
+
candidate: params.candidate,
|
|
2249
|
+
templateName: params.saved.templateName,
|
|
2250
|
+
workflow,
|
|
2251
|
+
goal,
|
|
2252
|
+
}));
|
|
2253
|
+
}
|
|
2254
|
+
writeCandidateReviewPacket(candidateReviewPacket({
|
|
2255
|
+
candidate: params.candidate,
|
|
2256
|
+
templateName: params.saved.templateName,
|
|
2257
|
+
savedPath: params.saved.path,
|
|
2258
|
+
}));
|
|
2259
|
+
}
|
|
2260
|
+
else if (params.plan) {
|
|
2261
|
+
writeEditPlan(params.plan);
|
|
2262
|
+
}
|
|
2263
|
+
else if (params.draft) {
|
|
2264
|
+
writeDraft(params.draft);
|
|
2265
|
+
}
|
|
2266
|
+
process.stderr.write("\nNext commands:\n");
|
|
2267
|
+
const nextCommands = params.saved
|
|
2268
|
+
? candidateReviewPacket({
|
|
2269
|
+
candidate: params.candidate,
|
|
2270
|
+
templateName: params.saved.templateName,
|
|
2271
|
+
savedPath: params.saved.path,
|
|
2272
|
+
}).reviewPath
|
|
2273
|
+
: params.candidate.reviewCommands;
|
|
2274
|
+
for (const command of nextCommands)
|
|
2275
|
+
process.stderr.write(` - ${command}\n`);
|
|
2276
|
+
}
|
|
2277
|
+
function writeEditPlan(plan) {
|
|
2278
|
+
process.stderr.write("Workflow edit plan\n");
|
|
2279
|
+
process.stderr.write(`Mode: ${plan.mode}\n`);
|
|
2280
|
+
process.stderr.write(`Example: ${plan.input.exampleId}\n`);
|
|
2281
|
+
if (plan.input.source)
|
|
2282
|
+
process.stderr.write(`Source: ${plan.input.source}\n`);
|
|
2283
|
+
process.stderr.write(`Goal: ${plan.input.goal}\n`);
|
|
2284
|
+
process.stderr.write(`Operation: ${plan.input.operation}\n`);
|
|
2285
|
+
const resolvedTarget = plan.operations[0]?.target;
|
|
2286
|
+
if (plan.input.targetStep)
|
|
2287
|
+
process.stderr.write(`Requested target step: ${plan.input.targetStep}\n`);
|
|
2288
|
+
if (resolvedTarget && resolvedTarget !== plan.input.targetStep)
|
|
2289
|
+
process.stderr.write(`Resolved target step: ${resolvedTarget}\n`);
|
|
2290
|
+
if (plan.input.inference) {
|
|
2291
|
+
process.stderr.write(`Inference: confidence=${plan.input.inference.confidence} operation=${plan.input.inference.operation} target=${plan.input.inference.targetStep} role=${plan.input.inference.role}\n`);
|
|
2292
|
+
for (const reason of plan.input.inference.reasons)
|
|
2293
|
+
process.stderr.write(` - ${reason}\n`);
|
|
2294
|
+
}
|
|
2295
|
+
process.stderr.write(`Instruction: ${plan.input.instruction}\n`);
|
|
2296
|
+
process.stderr.write(`Validation: ${plan.validation.ok ? "ok" : "failed"}\n`);
|
|
2297
|
+
writeDesignPacket(plan.design_packet);
|
|
2298
|
+
writeEditGuidance(buildEditGuidance(plan, {
|
|
2299
|
+
fromTemplate: templateNameFromPlanSource(plan.input.source),
|
|
2300
|
+
}));
|
|
2301
|
+
process.stderr.write("\nOperations:\n");
|
|
2302
|
+
for (const operation of plan.operations) {
|
|
2303
|
+
process.stderr.write(` - ${operation.operation} on ${operation.target}: ${operation.summary}\n`);
|
|
2304
|
+
}
|
|
2305
|
+
process.stderr.write("\nProposed steps:\n");
|
|
2306
|
+
for (const step of plan.proposed_workflow.steps) {
|
|
2307
|
+
const deps = step.depends_on && step.depends_on.length > 0 ? ` after=${step.depends_on.join(",")}` : "";
|
|
2308
|
+
process.stderr.write(` - ${step.id} -> ${step.role}${deps}`);
|
|
2309
|
+
if (step.description)
|
|
2310
|
+
process.stderr.write(` — ${step.description}`);
|
|
2311
|
+
process.stderr.write("\n");
|
|
2312
|
+
}
|
|
2313
|
+
process.stderr.write("\nValidation messages:\n");
|
|
2314
|
+
for (const message of plan.validation.messages) {
|
|
2315
|
+
process.stderr.write(` - [${message.severity}] ${message.path}: ${message.message}\n`);
|
|
2316
|
+
}
|
|
2317
|
+
process.stderr.write("\nSafe flow:\n");
|
|
2318
|
+
for (const item of plan.safe_flow)
|
|
2319
|
+
process.stderr.write(` - ${item}\n`);
|
|
2320
|
+
process.stderr.write("\nForbidden:\n");
|
|
2321
|
+
for (const item of plan.forbidden)
|
|
2322
|
+
process.stderr.write(` - ${item}\n`);
|
|
2323
|
+
process.stderr.write("\nYAML preview:\n");
|
|
2324
|
+
process.stderr.write(plan.yaml);
|
|
2325
|
+
}
|
|
2326
|
+
function writeEditGuidance(guidance) {
|
|
2327
|
+
process.stderr.write("\nEdit guidance:\n");
|
|
2328
|
+
process.stderr.write(` - summary: ${guidance.summary}\n`);
|
|
2329
|
+
process.stderr.write(" - change preview:\n");
|
|
2330
|
+
process.stderr.write(` - steps: ${guidance.changePreview.baseStepCount} -> ${guidance.changePreview.proposedStepCount}\n`);
|
|
2331
|
+
process.stderr.write(` - added steps: ${guidance.changePreview.addedSteps.length > 0 ? guidance.changePreview.addedSteps.map((step) => `${step.id}(${step.role}) after=${step.after.join(",") || "none"}`).join("; ") : "none"}\n`);
|
|
2332
|
+
process.stderr.write(` - removed steps: ${guidance.changePreview.removedSteps.length > 0 ? guidance.changePreview.removedSteps.join(", ") : "none"}\n`);
|
|
2333
|
+
process.stderr.write(` - target successors: ${guidance.changePreview.targetBefore.length > 0 ? guidance.changePreview.targetBefore.join(",") : "none"} -> ${guidance.changePreview.targetAfter.length > 0 ? guidance.changePreview.targetAfter.join(",") : "none"}\n`);
|
|
2334
|
+
process.stderr.write(` - suggested next: ${guidance.changePreview.suggestedNext}\n`);
|
|
2335
|
+
process.stderr.write(` - inferred: operation=${guidance.inferred.operation} target=${guidance.inferred.targetStep}${guidance.inferred.role ? ` role=${guidance.inferred.role}` : ""} confidence=${guidance.inferred.confidence}\n`);
|
|
2336
|
+
for (const reason of guidance.inferred.reasons)
|
|
2337
|
+
process.stderr.write(` - ${reason}\n`);
|
|
2338
|
+
process.stderr.write(` - mutation boundary: ${guidance.mutationBoundary}\n`);
|
|
2339
|
+
process.stderr.write(" - target choices:\n");
|
|
2340
|
+
for (const target of guidance.targetChoices)
|
|
2341
|
+
process.stderr.write(` - ${target}\n`);
|
|
2342
|
+
process.stderr.write(" - operation choices:\n");
|
|
2343
|
+
for (const choice of guidance.operationChoices) {
|
|
2344
|
+
process.stderr.write(` - ${choice.operation}: ${choice.useWhen}\n`);
|
|
2345
|
+
process.stderr.write(` ${choice.command}\n`);
|
|
2346
|
+
}
|
|
2347
|
+
if (guidance.saveCommand)
|
|
2348
|
+
process.stderr.write(` - save command: ${guidance.saveCommand}\n`);
|
|
2349
|
+
process.stderr.write(" - review path:\n");
|
|
2350
|
+
for (const command of guidance.reviewPath)
|
|
2351
|
+
process.stderr.write(` - ${command}\n`);
|
|
2352
|
+
}
|
|
2353
|
+
function planWithGuidance(plan, opts) {
|
|
2354
|
+
return {
|
|
2355
|
+
...plan,
|
|
2356
|
+
guidance: buildEditGuidance(plan, opts),
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
export async function runWorkflow(opts) {
|
|
2360
|
+
const subcommand = opts.subcommand || "draft";
|
|
2361
|
+
if (subcommand === "examples") {
|
|
2362
|
+
if (opts.json) {
|
|
2363
|
+
process.stdout.write(JSON.stringify(workflowDraftExamples(), null, 2) + "\n");
|
|
2364
|
+
return 0;
|
|
2365
|
+
}
|
|
2366
|
+
writeExamples();
|
|
2367
|
+
return 0;
|
|
2368
|
+
}
|
|
2369
|
+
if (subcommand === "authoring-packet") {
|
|
2370
|
+
const packet = await buildAuthoringPacket({
|
|
2371
|
+
goal: opts.goal,
|
|
2372
|
+
projectDir: opts.projectDir,
|
|
2373
|
+
});
|
|
2374
|
+
if (opts.json) {
|
|
2375
|
+
process.stdout.write(JSON.stringify(packet, null, 2) + "\n");
|
|
2376
|
+
return 0;
|
|
2377
|
+
}
|
|
2378
|
+
writeAuthoringPacket(packet);
|
|
2379
|
+
return 0;
|
|
2380
|
+
}
|
|
2381
|
+
if (subcommand === "intake") {
|
|
2382
|
+
const goal = opts.goal?.trim() ?? "";
|
|
2383
|
+
if (!goal) {
|
|
2384
|
+
process.stderr.write("Usage: dna workflow intake <description> [--json]\n");
|
|
2385
|
+
return 2;
|
|
2386
|
+
}
|
|
2387
|
+
const packet = await buildWorkflowIntake(goal, opts.projectDir);
|
|
2388
|
+
if (opts.json) {
|
|
2389
|
+
process.stdout.write(JSON.stringify(packet, null, 2) + "\n");
|
|
2390
|
+
return 0;
|
|
2391
|
+
}
|
|
2392
|
+
writeWorkflowIntake(packet);
|
|
2393
|
+
return 0;
|
|
2394
|
+
}
|
|
2395
|
+
if (subcommand === "route") {
|
|
2396
|
+
const goal = opts.goal?.trim() ?? "";
|
|
2397
|
+
if (!goal) {
|
|
2398
|
+
process.stderr.write("Usage: dna workflow route <description> [--org] [--name <org>] [--project <project>] [--sync-target <target>] [--from-template <asset>] [--save-template <name>] [--json]\n");
|
|
2399
|
+
return 2;
|
|
2400
|
+
}
|
|
2401
|
+
const route = await buildWorkflowAuthoringRoute({ ...opts, goal });
|
|
2402
|
+
if (opts.json) {
|
|
2403
|
+
process.stdout.write(JSON.stringify(route, null, 2) + "\n");
|
|
2404
|
+
return 0;
|
|
2405
|
+
}
|
|
2406
|
+
writeWorkflowAuthoringRoute(route);
|
|
2407
|
+
return 0;
|
|
2408
|
+
}
|
|
2409
|
+
if (subcommand === "suggestions") {
|
|
2410
|
+
const assetName = opts.goal?.trim() || opts.fromTemplate?.trim();
|
|
2411
|
+
if (!assetName) {
|
|
2412
|
+
process.stderr.write("Usage: dna workflow suggestions <asset> [--json]\n");
|
|
2413
|
+
return 2;
|
|
2414
|
+
}
|
|
2415
|
+
return runAssets({
|
|
2416
|
+
subcommand: "suggestions",
|
|
2417
|
+
name: assetName,
|
|
2418
|
+
projectDir: opts.projectDir,
|
|
2419
|
+
json: opts.json,
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
if (subcommand === "mine") {
|
|
2423
|
+
const assetName = opts.goal?.trim() || opts.fromTemplate?.trim();
|
|
2424
|
+
return runAssets({
|
|
2425
|
+
subcommand: "candidates",
|
|
2426
|
+
name: assetName,
|
|
2427
|
+
projectDir: opts.projectDir,
|
|
2428
|
+
json: opts.json,
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
if (subcommand === "review-suggestion") {
|
|
2432
|
+
const projectDir = opts.projectDir ?? process.cwd();
|
|
2433
|
+
const fromTemplate = opts.fromTemplate?.trim();
|
|
2434
|
+
const decision = normalizeReviewDecision(opts.decision ?? opts.goal);
|
|
2435
|
+
const rawSuggestionId = opts.suggestion?.trim();
|
|
2436
|
+
const suggestionId = rawSuggestionId ? normalizeSuggestionId(rawSuggestionId) : undefined;
|
|
2437
|
+
const workflowId = opts.workflow?.trim();
|
|
2438
|
+
if (!fromTemplate || !decision || !suggestionId) {
|
|
2439
|
+
process.stderr.write("Usage: dna workflow review-suggestion --from-template <asset> --workflow <id> --suggestion <id> --decision accept|reject|defer [--json]\n");
|
|
2440
|
+
return 2;
|
|
2441
|
+
}
|
|
2442
|
+
if (!workflowId) {
|
|
2443
|
+
process.stderr.write("Usage: dna workflow review-suggestion --from-template <asset> --workflow <id> --suggestion <id> --decision accept|reject|defer [--json]\n");
|
|
2444
|
+
return 2;
|
|
2445
|
+
}
|
|
2446
|
+
const loaded = await loadWorkflowFromTemplate(fromTemplate, projectDir);
|
|
2447
|
+
if (!loaded.path) {
|
|
2448
|
+
process.stderr.write(`Unknown workflow asset template: ${fromTemplate}\n`);
|
|
2449
|
+
return 2;
|
|
2450
|
+
}
|
|
2451
|
+
if (!loaded.workflows[workflowId]) {
|
|
2452
|
+
writeMissingWorkflowSelection({
|
|
2453
|
+
requested: workflowId,
|
|
2454
|
+
available: Object.keys(loaded.workflows),
|
|
2455
|
+
templatePath: loaded.path,
|
|
2456
|
+
});
|
|
2457
|
+
return 2;
|
|
2458
|
+
}
|
|
2459
|
+
const snapshot = await currentSuggestionReviewSnapshot({
|
|
2460
|
+
assetName: fromTemplate,
|
|
2461
|
+
workflowId,
|
|
2462
|
+
suggestionId,
|
|
2463
|
+
projectDir,
|
|
2464
|
+
});
|
|
2465
|
+
if (!snapshot) {
|
|
2466
|
+
process.stderr.write(`Suggestion not found for review: ${fromTemplate}/${workflowId}/${suggestionId}\n`);
|
|
2467
|
+
return 2;
|
|
2468
|
+
}
|
|
2469
|
+
const receipt = buildSuggestionReviewReceipt({ snapshot, decision });
|
|
2470
|
+
const persisted = await appendSuggestionReviewReceipt({ projectDir, receipt });
|
|
2471
|
+
const effective = persisted.existing ?? receipt;
|
|
2472
|
+
if (persisted.existing && persisted.existing.decision !== decision) {
|
|
2473
|
+
process.stderr.write(`Suggestion review already terminal: ${persisted.existing.decision}\n`);
|
|
2474
|
+
process.stderr.write(`Binding digest: ${persisted.existing.binding_digest}\n`);
|
|
2475
|
+
return 2;
|
|
2476
|
+
}
|
|
2477
|
+
if (opts.json) {
|
|
2478
|
+
process.stdout.write(JSON.stringify({
|
|
2479
|
+
mode: "workflow_suggestion_review_receipt",
|
|
2480
|
+
path: persisted.path,
|
|
2481
|
+
receipt: effective,
|
|
2482
|
+
existing: Boolean(persisted.existing),
|
|
2483
|
+
nextCommand: effective.decision === "accept"
|
|
2484
|
+
? `dna workflow improve-from-suggestions --from-template ${effective.asset} --workflow ${effective.workflow_id} --suggestion ${effective.suggestion_id}`
|
|
2485
|
+
: undefined,
|
|
2486
|
+
}, null, 2) + "\n");
|
|
2487
|
+
return 0;
|
|
2488
|
+
}
|
|
2489
|
+
writeSuggestionReviewReceipt(effective, persisted.path);
|
|
2490
|
+
return 0;
|
|
2491
|
+
}
|
|
2492
|
+
if (subcommand === "draft") {
|
|
2493
|
+
const goal = opts.goal?.trim() ?? "";
|
|
2494
|
+
if (!goal) {
|
|
2495
|
+
process.stderr.write("Usage: dna workflow draft <description> [--example rewrite_governance|dev_pipeline|evidence_review] [--strictness standard|high] [--json|--yaml]\n");
|
|
2496
|
+
return 2;
|
|
2497
|
+
}
|
|
2498
|
+
const draft = createWorkflowDraft({
|
|
2499
|
+
exampleId: normalizeExample(opts.example),
|
|
2500
|
+
goal,
|
|
2501
|
+
strictness: normalizeStrictness(opts.strictness),
|
|
2502
|
+
});
|
|
2503
|
+
if (opts.saveTemplate) {
|
|
2504
|
+
try {
|
|
2505
|
+
const result = await saveDraftTemplate({
|
|
2506
|
+
draft,
|
|
2507
|
+
name: opts.saveTemplate,
|
|
2508
|
+
projectDir: opts.projectDir,
|
|
2509
|
+
force: opts.force,
|
|
2510
|
+
});
|
|
2511
|
+
if (opts.json) {
|
|
2512
|
+
process.stdout.write(JSON.stringify({
|
|
2513
|
+
mode: "saved_workflow_asset_draft",
|
|
2514
|
+
templateName: result.templateName,
|
|
2515
|
+
path: result.path,
|
|
2516
|
+
designPacket: savedDesignPacket(result.templateName, draft.workflow, draft.input.goal),
|
|
2517
|
+
scaffold: draft.scaffold,
|
|
2518
|
+
draft,
|
|
2519
|
+
}, null, 2) + "\n");
|
|
2520
|
+
return 0;
|
|
2521
|
+
}
|
|
2522
|
+
writeSavedTemplate(result, savedDesignPacket(result.templateName, draft.workflow, draft.input.goal), { scaffold: true });
|
|
2523
|
+
return 0;
|
|
2524
|
+
}
|
|
2525
|
+
catch (err) {
|
|
2526
|
+
if (err && typeof err === "object" && "code" in err && err.code === "EEXIST") {
|
|
2527
|
+
writeExistingDraftRoute(opts.saveTemplate);
|
|
2528
|
+
return 2;
|
|
2529
|
+
}
|
|
2530
|
+
throw err;
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
if (opts.json) {
|
|
2534
|
+
process.stdout.write(JSON.stringify(draft, null, 2) + "\n");
|
|
2535
|
+
return 0;
|
|
2536
|
+
}
|
|
2537
|
+
if (opts.yaml) {
|
|
2538
|
+
process.stdout.write([
|
|
2539
|
+
"# example-clone scaffold; structure is not derived from the goal",
|
|
2540
|
+
"# primary creation path: dna workflow authoring-packet",
|
|
2541
|
+
draft.yaml,
|
|
2542
|
+
].join("\n"));
|
|
2543
|
+
return 0;
|
|
2544
|
+
}
|
|
2545
|
+
writeDraft(draft);
|
|
2546
|
+
return 0;
|
|
2547
|
+
}
|
|
2548
|
+
if (subcommand === "edit-plan") {
|
|
2549
|
+
const instruction = opts.goal?.trim() ?? "";
|
|
2550
|
+
if (!instruction) {
|
|
2551
|
+
process.stderr.write("Usage: dna workflow edit-plan <instruction> [--operation <override>] [--target-step <override>] [--role <override>] [--example <id>] [--json|--yaml]\n");
|
|
2552
|
+
process.stderr.write("The CLI infers common edit operation, target step, and role from natural language; explicit options override inference.\n");
|
|
2553
|
+
return 2;
|
|
2554
|
+
}
|
|
2555
|
+
const input = buildEditInput({
|
|
2556
|
+
instruction,
|
|
2557
|
+
example: opts.example,
|
|
2558
|
+
strictness: opts.strictness,
|
|
2559
|
+
operation: opts.operation,
|
|
2560
|
+
targetStep: opts.targetStep,
|
|
2561
|
+
role: opts.role,
|
|
2562
|
+
fromTemplate: opts.fromTemplate,
|
|
2563
|
+
});
|
|
2564
|
+
let plan;
|
|
2565
|
+
let templateContext;
|
|
2566
|
+
if (opts.fromTemplate) {
|
|
2567
|
+
const loaded = await loadWorkflowFromTemplate(opts.fromTemplate, opts.projectDir);
|
|
2568
|
+
if (!loaded.path) {
|
|
2569
|
+
process.stderr.write(`Unknown workflow asset template: ${opts.fromTemplate}\n`);
|
|
2570
|
+
return 2;
|
|
2571
|
+
}
|
|
2572
|
+
if (Object.keys(loaded.workflows).length === 0) {
|
|
2573
|
+
process.stderr.write(`Template has no workflow to edit: ${loaded.path}\n`);
|
|
2574
|
+
return 2;
|
|
2575
|
+
}
|
|
2576
|
+
const selected = selectWorkflow({
|
|
2577
|
+
workflows: loaded.workflows,
|
|
2578
|
+
requested: opts.workflow,
|
|
2579
|
+
targetStep: input.targetStep,
|
|
2580
|
+
});
|
|
2581
|
+
if (selected.requestedMissing) {
|
|
2582
|
+
writeMissingWorkflowSelection({
|
|
2583
|
+
requested: selected.requestedMissing,
|
|
2584
|
+
available: Object.keys(loaded.workflows),
|
|
2585
|
+
templatePath: loaded.path,
|
|
2586
|
+
});
|
|
2587
|
+
return 2;
|
|
2588
|
+
}
|
|
2589
|
+
if (!selected.workflow) {
|
|
2590
|
+
process.stderr.write(`Template has no workflow to edit: ${loaded.path}\n`);
|
|
2591
|
+
return 2;
|
|
2592
|
+
}
|
|
2593
|
+
const selectedInput = buildEditInput({
|
|
2594
|
+
instruction,
|
|
2595
|
+
example: opts.example,
|
|
2596
|
+
strictness: opts.strictness,
|
|
2597
|
+
operation: opts.operation,
|
|
2598
|
+
targetStep: opts.targetStep,
|
|
2599
|
+
role: opts.role,
|
|
2600
|
+
fromTemplate: opts.fromTemplate,
|
|
2601
|
+
workflow: selected.workflow,
|
|
2602
|
+
});
|
|
2603
|
+
plan = createWorkflowEditPlanFromWorkflow(selected.workflow, {
|
|
2604
|
+
...selectedInput,
|
|
2605
|
+
source: sourceWithWorkflow("template", loaded.templateName, selected.workflowId),
|
|
2606
|
+
});
|
|
2607
|
+
templateContext = templateContextFromParsed(loaded.parsed, selected.workflowId, plan.proposed_workflow);
|
|
2608
|
+
}
|
|
2609
|
+
else {
|
|
2610
|
+
plan = createWorkflowEditPlan(input);
|
|
2611
|
+
}
|
|
2612
|
+
if (opts.saveTemplate) {
|
|
2613
|
+
try {
|
|
2614
|
+
const result = await saveDraftTemplate({
|
|
2615
|
+
draft: {
|
|
2616
|
+
mode: "draft_only",
|
|
2617
|
+
generated_at: plan.generated_at,
|
|
2618
|
+
input: {
|
|
2619
|
+
exampleId: plan.input.exampleId,
|
|
2620
|
+
goal: plan.input.goal,
|
|
2621
|
+
strictness: plan.input.strictness,
|
|
2622
|
+
},
|
|
2623
|
+
workflow: plan.proposed_workflow,
|
|
2624
|
+
design_packet: plan.design_packet,
|
|
2625
|
+
yaml: plan.yaml,
|
|
2626
|
+
validation: plan.validation,
|
|
2627
|
+
safe_flow: plan.safe_flow,
|
|
2628
|
+
forbidden: plan.forbidden,
|
|
2629
|
+
},
|
|
2630
|
+
name: opts.saveTemplate,
|
|
2631
|
+
projectDir: opts.projectDir,
|
|
2632
|
+
force: opts.force,
|
|
2633
|
+
templateContext,
|
|
2634
|
+
});
|
|
2635
|
+
if (opts.json) {
|
|
2636
|
+
process.stdout.write(JSON.stringify({
|
|
2637
|
+
mode: "saved_workflow_edit_plan",
|
|
2638
|
+
templateName: result.templateName,
|
|
2639
|
+
path: result.path,
|
|
2640
|
+
designPacket: savedDesignPacket(result.templateName, plan.proposed_workflow, plan.input.goal),
|
|
2641
|
+
plan: planWithGuidance(plan, { fromTemplate: opts.fromTemplate, saveTemplate: result.templateName }),
|
|
2642
|
+
}, null, 2) + "\n");
|
|
2643
|
+
return 0;
|
|
2644
|
+
}
|
|
2645
|
+
writeSavedTemplate(result, savedDesignPacket(result.templateName, plan.proposed_workflow, plan.input.goal));
|
|
2646
|
+
return 0;
|
|
2647
|
+
}
|
|
2648
|
+
catch (err) {
|
|
2649
|
+
if (err && typeof err === "object" && "code" in err && err.code === "EEXIST") {
|
|
2650
|
+
writeExistingDraftRoute(opts.saveTemplate, opts.fromTemplate);
|
|
2651
|
+
return 2;
|
|
2652
|
+
}
|
|
2653
|
+
throw err;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
if (opts.json) {
|
|
2657
|
+
process.stdout.write(JSON.stringify(planWithGuidance(plan, { fromTemplate: opts.fromTemplate, saveTemplate: opts.saveTemplate }), null, 2) + "\n");
|
|
2658
|
+
return 0;
|
|
2659
|
+
}
|
|
2660
|
+
if (opts.yaml) {
|
|
2661
|
+
process.stdout.write(plan.yaml);
|
|
2662
|
+
return 0;
|
|
2663
|
+
}
|
|
2664
|
+
writeEditPlan(plan);
|
|
2665
|
+
return 0;
|
|
2666
|
+
}
|
|
2667
|
+
if (subcommand === "improve-from-suggestions") {
|
|
2668
|
+
const fromTemplate = opts.fromTemplate?.trim();
|
|
2669
|
+
if (!fromTemplate) {
|
|
2670
|
+
process.stderr.write("Usage: dna workflow improve-from-suggestions --from-template <asset> [--suggestion <id>] [--save-template <name>] [--json|--yaml]\n");
|
|
2671
|
+
return 2;
|
|
2672
|
+
}
|
|
2673
|
+
const loaded = await loadWorkflowFromTemplate(fromTemplate, opts.projectDir);
|
|
2674
|
+
if (!loaded.path) {
|
|
2675
|
+
process.stderr.write(`Unknown workflow asset template: ${fromTemplate}\n`);
|
|
2676
|
+
return 2;
|
|
2677
|
+
}
|
|
2678
|
+
if (Object.keys(loaded.workflows).length === 0) {
|
|
2679
|
+
process.stderr.write(`Template has no workflow to edit: ${loaded.path}\n`);
|
|
2680
|
+
return 2;
|
|
2681
|
+
}
|
|
2682
|
+
const evidence = await buildWorkflowAssetEvidenceIndex(loaded.templateName, { projectDir: opts.projectDir });
|
|
2683
|
+
const suggestionId = normalizeSuggestionId(opts.suggestion ?? opts.goal);
|
|
2684
|
+
const activeFocus = evidence?.modules.find((module) => module.verdict !== "PASS" ||
|
|
2685
|
+
!module.moduleComplete ||
|
|
2686
|
+
module.remainingCount > 0 ||
|
|
2687
|
+
module.newRedCount > 0);
|
|
2688
|
+
const inputShape = suggestionToEditInput({
|
|
2689
|
+
suggestionId,
|
|
2690
|
+
assetName: loaded.templateName,
|
|
2691
|
+
fallbackInstruction: activeFocus?.nextRoundFocus ?? activeFocus?.failureReason ?? "",
|
|
2692
|
+
});
|
|
2693
|
+
const selected = selectWorkflow({
|
|
2694
|
+
workflows: loaded.workflows,
|
|
2695
|
+
requested: opts.workflow,
|
|
2696
|
+
targetStep: inputShape.targetStep,
|
|
2697
|
+
});
|
|
2698
|
+
if (selected.requestedMissing) {
|
|
2699
|
+
writeMissingWorkflowSelection({
|
|
2700
|
+
requested: selected.requestedMissing,
|
|
2701
|
+
available: Object.keys(loaded.workflows),
|
|
2702
|
+
templatePath: loaded.path,
|
|
2703
|
+
});
|
|
2704
|
+
return 2;
|
|
2705
|
+
}
|
|
2706
|
+
if (!selected.workflow) {
|
|
2707
|
+
process.stderr.write(`Template has no workflow to edit: ${loaded.path}\n`);
|
|
2708
|
+
return 2;
|
|
2709
|
+
}
|
|
2710
|
+
const projectDir = opts.projectDir ?? process.cwd();
|
|
2711
|
+
const snapshot = await currentSuggestionReviewSnapshot({
|
|
2712
|
+
assetName: loaded.templateName,
|
|
2713
|
+
workflowId: selected.workflowId ?? "workflow",
|
|
2714
|
+
suggestionId,
|
|
2715
|
+
projectDir,
|
|
2716
|
+
});
|
|
2717
|
+
if (!snapshot) {
|
|
2718
|
+
process.stderr.write(`Suggestion not found or no longer current: ${loaded.templateName}/${selected.workflowId ?? "workflow"}/${suggestionId}\n`);
|
|
2719
|
+
process.stderr.write("Run dna workflow suggestions and record a fresh accept receipt before improving from suggestions.\n");
|
|
2720
|
+
return 2;
|
|
2721
|
+
}
|
|
2722
|
+
const suggestionDigest = sha256Canonical(snapshot.suggestion);
|
|
2723
|
+
const bindingDigest = suggestionBindingDigest({
|
|
2724
|
+
asset: snapshot.asset,
|
|
2725
|
+
workflowId: snapshot.workflowId,
|
|
2726
|
+
suggestionId,
|
|
2727
|
+
suggestionDigest,
|
|
2728
|
+
evidenceRefs: snapshot.suggestion.evidenceRefs ?? [],
|
|
2729
|
+
});
|
|
2730
|
+
const terminalReceipt = await findTerminalSuggestionReceipt({
|
|
2731
|
+
projectDir,
|
|
2732
|
+
asset: snapshot.asset,
|
|
2733
|
+
workflowId: snapshot.workflowId,
|
|
2734
|
+
suggestionId,
|
|
2735
|
+
bindingDigest,
|
|
2736
|
+
});
|
|
2737
|
+
if (!terminalReceipt) {
|
|
2738
|
+
process.stderr.write(`Missing K4 accept receipt for suggestion: ${snapshot.asset}/${snapshot.workflowId}/${suggestionId}\n`);
|
|
2739
|
+
process.stderr.write(`Required binding digest: ${bindingDigest}\n`);
|
|
2740
|
+
process.stderr.write(`Review first: dna workflow review-suggestion --from-template ${snapshot.asset} --workflow ${snapshot.workflowId} --suggestion ${suggestionId} --decision accept\n`);
|
|
2741
|
+
return 2;
|
|
2742
|
+
}
|
|
2743
|
+
if (terminalReceipt.decision !== "accept") {
|
|
2744
|
+
process.stderr.write(`Suggestion review decision blocks improvement: ${terminalReceipt.decision}\n`);
|
|
2745
|
+
process.stderr.write(`Binding digest: ${bindingDigest}\n`);
|
|
2746
|
+
return 2;
|
|
2747
|
+
}
|
|
2748
|
+
const source = sourceWithWorkflow("suggestions", loaded.templateName, selected.workflowId, suggestionId);
|
|
2749
|
+
const plan = createWorkflowEditPlanFromWorkflow(selected.workflow, {
|
|
2750
|
+
exampleId: "rewrite_governance",
|
|
2751
|
+
goal: `Apply reviewed suggestion ${suggestionId} to ${loaded.templateName}`,
|
|
2752
|
+
strictness: "high",
|
|
2753
|
+
operation: inputShape.operation,
|
|
2754
|
+
instruction: inputShape.instruction,
|
|
2755
|
+
targetStep: inputShape.targetStep,
|
|
2756
|
+
role: inputShape.role,
|
|
2757
|
+
source,
|
|
2758
|
+
});
|
|
2759
|
+
const templateContext = templateContextFromParsed(loaded.parsed, selected.workflowId, plan.proposed_workflow);
|
|
2760
|
+
if (opts.saveTemplate) {
|
|
2761
|
+
try {
|
|
2762
|
+
const result = await saveDraftTemplate({
|
|
2763
|
+
draft: {
|
|
2764
|
+
mode: "draft_only",
|
|
2765
|
+
generated_at: plan.generated_at,
|
|
2766
|
+
input: {
|
|
2767
|
+
exampleId: plan.input.exampleId,
|
|
2768
|
+
goal: plan.input.goal,
|
|
2769
|
+
strictness: plan.input.strictness,
|
|
2770
|
+
},
|
|
2771
|
+
workflow: plan.proposed_workflow,
|
|
2772
|
+
design_packet: plan.design_packet,
|
|
2773
|
+
yaml: plan.yaml,
|
|
2774
|
+
validation: plan.validation,
|
|
2775
|
+
safe_flow: plan.safe_flow,
|
|
2776
|
+
forbidden: plan.forbidden,
|
|
2777
|
+
},
|
|
2778
|
+
name: opts.saveTemplate,
|
|
2779
|
+
projectDir: opts.projectDir,
|
|
2780
|
+
force: opts.force,
|
|
2781
|
+
lineage: suggestionTemplateLineage({
|
|
2782
|
+
sourceAsset: loaded.templateName,
|
|
2783
|
+
suggestionId,
|
|
2784
|
+
}),
|
|
2785
|
+
evidence: suggestionTemplateEvidence({
|
|
2786
|
+
sourceAsset: loaded.templateName,
|
|
2787
|
+
suggestionId,
|
|
2788
|
+
}),
|
|
2789
|
+
templateContext,
|
|
2790
|
+
});
|
|
2791
|
+
const designPacket = suggestionSavedDesignPacket({
|
|
2792
|
+
sourceAsset: loaded.templateName,
|
|
2793
|
+
suggestionId,
|
|
2794
|
+
templateName: result.templateName,
|
|
2795
|
+
workflow: plan.proposed_workflow,
|
|
2796
|
+
goal: plan.input.goal,
|
|
2797
|
+
});
|
|
2798
|
+
const reviewPacket = suggestionReviewPacket({
|
|
2799
|
+
sourceAsset: loaded.templateName,
|
|
2800
|
+
suggestionId,
|
|
2801
|
+
templateName: result.templateName,
|
|
2802
|
+
savedPath: result.path,
|
|
2803
|
+
});
|
|
2804
|
+
if (opts.json) {
|
|
2805
|
+
process.stdout.write(JSON.stringify({
|
|
2806
|
+
mode: "saved_workflow_suggestion_edit_plan",
|
|
2807
|
+
suggestionId,
|
|
2808
|
+
workflowId: selected.workflowId,
|
|
2809
|
+
templateName: result.templateName,
|
|
2810
|
+
path: result.path,
|
|
2811
|
+
designPacket,
|
|
2812
|
+
reviewPacket,
|
|
2813
|
+
nextCommands: reviewPacket.reviewPath,
|
|
2814
|
+
plan,
|
|
2815
|
+
}, null, 2) + "\n");
|
|
2816
|
+
return 0;
|
|
2817
|
+
}
|
|
2818
|
+
writeSuggestionSavedTemplate({
|
|
2819
|
+
sourceAsset: loaded.templateName,
|
|
2820
|
+
suggestionId,
|
|
2821
|
+
workflow: plan.proposed_workflow,
|
|
2822
|
+
goal: plan.input.goal,
|
|
2823
|
+
saved: result,
|
|
2824
|
+
});
|
|
2825
|
+
return 0;
|
|
2826
|
+
}
|
|
2827
|
+
catch (err) {
|
|
2828
|
+
if (err && typeof err === "object" && "code" in err && err.code === "EEXIST") {
|
|
2829
|
+
writeExistingDraftRoute(opts.saveTemplate, fromTemplate);
|
|
2830
|
+
return 2;
|
|
2831
|
+
}
|
|
2832
|
+
throw err;
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
if (opts.json) {
|
|
2836
|
+
process.stdout.write(JSON.stringify({ mode: "workflow_suggestion_edit_plan", suggestionId, workflowId: selected.workflowId, plan }, null, 2) + "\n");
|
|
2837
|
+
return 0;
|
|
2838
|
+
}
|
|
2839
|
+
if (opts.yaml) {
|
|
2840
|
+
process.stdout.write(plan.yaml);
|
|
2841
|
+
return 0;
|
|
2842
|
+
}
|
|
2843
|
+
writeSuggestionEditPlan({ plan, suggestionId, fromTemplate: loaded.templateName, workflowId: selected.workflowId });
|
|
2844
|
+
return 0;
|
|
2845
|
+
}
|
|
2846
|
+
if (subcommand === "from-candidate") {
|
|
2847
|
+
const candidateId = opts.goal?.trim() ?? opts.suggestion?.trim() ?? "";
|
|
2848
|
+
const fromTemplate = opts.fromTemplate?.trim();
|
|
2849
|
+
if (!candidateId || !fromTemplate) {
|
|
2850
|
+
process.stderr.write("Usage: dna workflow from-candidate <candidate-id> --from-template <asset> [--save-template <name>] [--json|--yaml]\n");
|
|
2851
|
+
return 2;
|
|
2852
|
+
}
|
|
2853
|
+
const backlog = await buildWorkflowAssetCandidateBacklog({ name: fromTemplate, projectDir: opts.projectDir });
|
|
2854
|
+
if (!backlog) {
|
|
2855
|
+
process.stderr.write(`Unknown workflow asset: ${fromTemplate}\n`);
|
|
2856
|
+
return 2;
|
|
2857
|
+
}
|
|
2858
|
+
const candidate = backlog.candidates.find((entry) => entry.id === candidateId);
|
|
2859
|
+
if (!candidate) {
|
|
2860
|
+
process.stderr.write(`Unknown workflow candidate: ${candidateId}\n`);
|
|
2861
|
+
process.stderr.write("Available candidates:\n");
|
|
2862
|
+
for (const entry of backlog.candidates)
|
|
2863
|
+
process.stderr.write(` - ${entry.id}: ${entry.title}\n`);
|
|
2864
|
+
return 2;
|
|
2865
|
+
}
|
|
2866
|
+
const saveTemplate = opts.saveTemplate?.trim() || undefined;
|
|
2867
|
+
if (candidate.action.kind === "review") {
|
|
2868
|
+
if (opts.json) {
|
|
2869
|
+
process.stdout.write(JSON.stringify({
|
|
2870
|
+
mode: "workflow_candidate_review_only",
|
|
2871
|
+
candidate,
|
|
2872
|
+
nextCommands: candidate.reviewCommands,
|
|
2873
|
+
mutation: {
|
|
2874
|
+
writes: false,
|
|
2875
|
+
note: "This candidate requires review/adoption first and does not generate a workflow draft.",
|
|
2876
|
+
},
|
|
2877
|
+
}, null, 2) + "\n");
|
|
2878
|
+
return 0;
|
|
2879
|
+
}
|
|
2880
|
+
writeCandidateReviewOnly(candidate);
|
|
2881
|
+
return 0;
|
|
2882
|
+
}
|
|
2883
|
+
if (candidate.action.kind === "draft") {
|
|
2884
|
+
const draft = createWorkflowDraft({
|
|
2885
|
+
exampleId: candidate.action.example,
|
|
2886
|
+
goal: candidate.action.goal,
|
|
2887
|
+
strictness: candidate.action.strictness,
|
|
2888
|
+
});
|
|
2889
|
+
const templateName = saveTemplate ?? candidate.action.saveTemplate;
|
|
2890
|
+
if (saveTemplate) {
|
|
2891
|
+
try {
|
|
2892
|
+
const result = await saveDraftTemplate({
|
|
2893
|
+
draft,
|
|
2894
|
+
name: templateName,
|
|
2895
|
+
projectDir: opts.projectDir,
|
|
2896
|
+
force: opts.force,
|
|
2897
|
+
lineage: candidateTemplateLineage(candidate),
|
|
2898
|
+
evidence: candidateTemplateEvidence(candidate),
|
|
2899
|
+
});
|
|
2900
|
+
if (opts.json) {
|
|
2901
|
+
const reviewPacket = candidateReviewPacket({
|
|
2902
|
+
candidate,
|
|
2903
|
+
templateName: result.templateName,
|
|
2904
|
+
savedPath: result.path,
|
|
2905
|
+
});
|
|
2906
|
+
process.stdout.write(JSON.stringify({
|
|
2907
|
+
mode: "saved_workflow_candidate_draft",
|
|
2908
|
+
candidate,
|
|
2909
|
+
templateName: result.templateName,
|
|
2910
|
+
path: result.path,
|
|
2911
|
+
designPacket: candidateSavedDesignPacket({
|
|
2912
|
+
candidate,
|
|
2913
|
+
templateName: result.templateName,
|
|
2914
|
+
workflow: draft.workflow,
|
|
2915
|
+
goal: draft.input.goal,
|
|
2916
|
+
}),
|
|
2917
|
+
reviewPacket,
|
|
2918
|
+
nextCommands: reviewPacket.reviewPath,
|
|
2919
|
+
draft,
|
|
2920
|
+
}, null, 2) + "\n");
|
|
2921
|
+
return 0;
|
|
2922
|
+
}
|
|
2923
|
+
writeCandidateDraft({ candidate, draft, saved: result });
|
|
2924
|
+
return 0;
|
|
2925
|
+
}
|
|
2926
|
+
catch (err) {
|
|
2927
|
+
if (err && typeof err === "object" && "code" in err && err.code === "EEXIST") {
|
|
2928
|
+
writeExistingDraftRoute(templateName, fromTemplate);
|
|
2929
|
+
return 2;
|
|
2930
|
+
}
|
|
2931
|
+
throw err;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
if (opts.json) {
|
|
2935
|
+
process.stdout.write(JSON.stringify({ mode: "workflow_candidate_draft", candidate, draft }, null, 2) + "\n");
|
|
2936
|
+
return 0;
|
|
2937
|
+
}
|
|
2938
|
+
if (opts.yaml) {
|
|
2939
|
+
process.stdout.write(draft.yaml);
|
|
2940
|
+
return 0;
|
|
2941
|
+
}
|
|
2942
|
+
writeCandidateDraft({ candidate, draft });
|
|
2943
|
+
return 0;
|
|
2944
|
+
}
|
|
2945
|
+
const loaded = await loadWorkflowFromTemplate(candidate.action.fromTemplate, opts.projectDir);
|
|
2946
|
+
if (!loaded.path) {
|
|
2947
|
+
process.stderr.write(`Unknown workflow asset template: ${candidate.action.fromTemplate}\n`);
|
|
2948
|
+
return 2;
|
|
2949
|
+
}
|
|
2950
|
+
const selected = selectWorkflow({
|
|
2951
|
+
workflows: loaded.workflows,
|
|
2952
|
+
requested: candidate.action.workflow,
|
|
2953
|
+
targetStep: candidate.action.targetStep,
|
|
2954
|
+
});
|
|
2955
|
+
if (selected.requestedMissing) {
|
|
2956
|
+
writeMissingWorkflowSelection({
|
|
2957
|
+
requested: selected.requestedMissing,
|
|
2958
|
+
available: Object.keys(loaded.workflows),
|
|
2959
|
+
templatePath: loaded.path,
|
|
2960
|
+
});
|
|
2961
|
+
return 2;
|
|
2962
|
+
}
|
|
2963
|
+
if (!selected.workflow) {
|
|
2964
|
+
process.stderr.write(`Template has no workflow to edit: ${loaded.path}\n`);
|
|
2965
|
+
return 2;
|
|
2966
|
+
}
|
|
2967
|
+
const plan = createWorkflowEditPlanFromWorkflow(selected.workflow, {
|
|
2968
|
+
exampleId: "rewrite_governance",
|
|
2969
|
+
goal: `Apply workflow candidate ${candidate.id} from ${candidate.sourceAsset}`,
|
|
2970
|
+
strictness: "high",
|
|
2971
|
+
operation: candidate.action.operation,
|
|
2972
|
+
instruction: candidate.action.instruction,
|
|
2973
|
+
targetStep: candidate.action.targetStep,
|
|
2974
|
+
source: sourceWithWorkflow("candidate", loaded.templateName, selected.workflowId, candidate.id),
|
|
2975
|
+
});
|
|
2976
|
+
const templateContext = templateContextFromParsed(loaded.parsed, selected.workflowId, plan.proposed_workflow, {
|
|
2977
|
+
preserveAllWorkflows: candidate.type === "active_evidence_followup",
|
|
2978
|
+
});
|
|
2979
|
+
const templateName = saveTemplate ?? candidate.action.saveTemplate;
|
|
2980
|
+
if (saveTemplate) {
|
|
2981
|
+
try {
|
|
2982
|
+
const result = await saveDraftTemplate({
|
|
2983
|
+
draft: {
|
|
2984
|
+
mode: "draft_only",
|
|
2985
|
+
generated_at: plan.generated_at,
|
|
2986
|
+
input: {
|
|
2987
|
+
exampleId: plan.input.exampleId,
|
|
2988
|
+
goal: plan.input.goal,
|
|
2989
|
+
strictness: plan.input.strictness,
|
|
2990
|
+
},
|
|
2991
|
+
workflow: plan.proposed_workflow,
|
|
2992
|
+
design_packet: plan.design_packet,
|
|
2993
|
+
yaml: plan.yaml,
|
|
2994
|
+
validation: plan.validation,
|
|
2995
|
+
safe_flow: plan.safe_flow,
|
|
2996
|
+
forbidden: plan.forbidden,
|
|
2997
|
+
},
|
|
2998
|
+
name: templateName,
|
|
2999
|
+
projectDir: opts.projectDir,
|
|
3000
|
+
force: opts.force,
|
|
3001
|
+
lineage: candidateTemplateLineage(candidate),
|
|
3002
|
+
evidence: candidateTemplateEvidence(candidate),
|
|
3003
|
+
templateContext,
|
|
3004
|
+
});
|
|
3005
|
+
if (opts.json) {
|
|
3006
|
+
const reviewPacket = candidateReviewPacket({
|
|
3007
|
+
candidate,
|
|
3008
|
+
templateName: result.templateName,
|
|
3009
|
+
savedPath: result.path,
|
|
3010
|
+
});
|
|
3011
|
+
process.stdout.write(JSON.stringify({
|
|
3012
|
+
mode: "saved_workflow_candidate_edit_plan",
|
|
3013
|
+
candidate,
|
|
3014
|
+
workflowId: selected.workflowId,
|
|
3015
|
+
templateName: result.templateName,
|
|
3016
|
+
path: result.path,
|
|
3017
|
+
designPacket: candidateSavedDesignPacket({
|
|
3018
|
+
candidate,
|
|
3019
|
+
templateName: result.templateName,
|
|
3020
|
+
workflow: plan.proposed_workflow,
|
|
3021
|
+
goal: plan.input.goal,
|
|
3022
|
+
}),
|
|
3023
|
+
reviewPacket,
|
|
3024
|
+
nextCommands: reviewPacket.reviewPath,
|
|
3025
|
+
plan,
|
|
3026
|
+
}, null, 2) + "\n");
|
|
3027
|
+
return 0;
|
|
3028
|
+
}
|
|
3029
|
+
writeCandidateDraft({ candidate, plan, saved: result });
|
|
3030
|
+
return 0;
|
|
3031
|
+
}
|
|
3032
|
+
catch (err) {
|
|
3033
|
+
if (err && typeof err === "object" && "code" in err && err.code === "EEXIST") {
|
|
3034
|
+
writeExistingDraftRoute(templateName, candidate.action.fromTemplate);
|
|
3035
|
+
return 2;
|
|
3036
|
+
}
|
|
3037
|
+
throw err;
|
|
3038
|
+
}
|
|
3039
|
+
}
|
|
3040
|
+
if (opts.json) {
|
|
3041
|
+
process.stdout.write(JSON.stringify({ mode: "workflow_candidate_edit_plan", candidate, workflowId: selected.workflowId, plan }, null, 2) + "\n");
|
|
3042
|
+
return 0;
|
|
3043
|
+
}
|
|
3044
|
+
if (opts.yaml) {
|
|
3045
|
+
process.stdout.write(plan.yaml);
|
|
3046
|
+
return 0;
|
|
3047
|
+
}
|
|
3048
|
+
writeCandidateDraft({ candidate, plan });
|
|
3049
|
+
return 0;
|
|
3050
|
+
}
|
|
3051
|
+
process.stderr.write("Usage: dna workflow <examples|authoring-packet|intake|route|suggestions|mine|draft|edit-plan|review-suggestion|improve-from-suggestions|from-candidate> [description] [--json|--yaml]\n");
|
|
3052
|
+
return 2;
|
|
3053
|
+
}
|