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,2871 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dna assets — read-only workflow asset inventory.
|
|
3
|
+
*/
|
|
4
|
+
import { activeEvidenceFollowupReviewRoute, activeEvidenceFollowupRoute, buildWorkflowAssetEvidenceIndex, buildWorkflowAssetCandidateBacklog, findWorkflowAsset, listWorkflowAssets, } from "../../assets/index.js";
|
|
5
|
+
import { WORKFLOW_ASSET_IDS, WORKFLOW_SUGGESTION_IDS } from "../../templates/metadata.js";
|
|
6
|
+
function formatCounts(asset) {
|
|
7
|
+
return `workflows=${asset.workflows.length} controllers=${asset.controllers.length} roles=${asset.roles.length} evidence=${asset.evidence.length}`;
|
|
8
|
+
}
|
|
9
|
+
function stepKey(workflowId, stepId) {
|
|
10
|
+
return `${workflowId}/${stepId}`;
|
|
11
|
+
}
|
|
12
|
+
function compactStep(step) {
|
|
13
|
+
if (!step)
|
|
14
|
+
return "missing";
|
|
15
|
+
const deps = step.dependsOn.length > 0 ? ` after=${step.dependsOn.join(",")}` : "";
|
|
16
|
+
return `${step.id} -> ${step.role}${deps} handoff=${step.consumes}/${step.produces} checkpoints=${step.checkpoints}`;
|
|
17
|
+
}
|
|
18
|
+
function changedStepFields(from, to) {
|
|
19
|
+
const fields = [];
|
|
20
|
+
if (from.role !== to.role)
|
|
21
|
+
fields.push("role");
|
|
22
|
+
if (from.description !== to.description)
|
|
23
|
+
fields.push("description");
|
|
24
|
+
if (from.dependsOn.join("\0") !== to.dependsOn.join("\0"))
|
|
25
|
+
fields.push("depends_on");
|
|
26
|
+
if (from.consumes !== to.consumes || from.produces !== to.produces)
|
|
27
|
+
fields.push("handoff");
|
|
28
|
+
if (from.checkpoints !== to.checkpoints)
|
|
29
|
+
fields.push("checkpoints");
|
|
30
|
+
return fields;
|
|
31
|
+
}
|
|
32
|
+
function comparableGovernance(asset) {
|
|
33
|
+
return {
|
|
34
|
+
managementStatus: asset.status.managementStatus,
|
|
35
|
+
evidencePosture: asset.status.evidencePosture,
|
|
36
|
+
configurationPosture: asset.status.configurationPosture,
|
|
37
|
+
cascadePosture: asset.status.cascadePosture,
|
|
38
|
+
cascadeInherits: asset.cascade.inherits,
|
|
39
|
+
cascadePriority: asset.cascade.priority,
|
|
40
|
+
evolutionPosture: asset.status.evolutionPosture,
|
|
41
|
+
declaredMarkers: asset.evolution.declaredMarkers,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function assetDiffReceiptPath(from, to) {
|
|
45
|
+
const slug = `${from}-to-${to}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "asset-diff";
|
|
46
|
+
return `.dna/reports/asset-diff-${slug}.json`;
|
|
47
|
+
}
|
|
48
|
+
function assetDiffReviewEvidence(from, to) {
|
|
49
|
+
const receiptPathHint = assetDiffReceiptPath(from, to);
|
|
50
|
+
return {
|
|
51
|
+
mode: "local_asset_diff_review_evidence",
|
|
52
|
+
authority: "manager_review_handoff_only",
|
|
53
|
+
receiptCommand: `dna assets diff ${from} ${to} --json > ${receiptPathHint}`,
|
|
54
|
+
receiptPathHint,
|
|
55
|
+
boundaries: [
|
|
56
|
+
"Asset diff review evidence is local manager handoff only; it does not adopt, bind, rollout, sync, publish, or approve runtime behavior.",
|
|
57
|
+
"High-risk diffs still require explicit manager rationale before adoption or organization binding.",
|
|
58
|
+
"Runtime sync remains a separate local dna action after adoption, organization binding, and rollout review.",
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function assetDiffChangeExplanation(from, to, steps) {
|
|
63
|
+
const activeFollowupSource = to.name === `${from.name}-active-followup`;
|
|
64
|
+
if (!activeFollowupSource)
|
|
65
|
+
return [];
|
|
66
|
+
const modifiedCheckpointSteps = steps.filter((step) => step.change === "modified" &&
|
|
67
|
+
step.fields.length === 1 &&
|
|
68
|
+
step.fields[0] === "checkpoints" &&
|
|
69
|
+
step.from?.checkpoints !== step.to?.checkpoints);
|
|
70
|
+
const lines = [
|
|
71
|
+
`${to.name} is a saved unfinished-work draft for ${from.name}, not a deletion, upgrade, or replacement of the source asset.`,
|
|
72
|
+
"It exists because dogfood evidence still has open work, so baseline promotion, organization binding, rollout, runtime sync, and marker mutation must stop at review first.",
|
|
73
|
+
];
|
|
74
|
+
if (modifiedCheckpointSteps.length > 0) {
|
|
75
|
+
lines.push(`The concrete workflow improvement is ${modifiedCheckpointSteps.length} added evidence checkpoint(s): ${modifiedCheckpointSteps.map((step) => `${step.workflow}/${step.id} ${step.from?.checkpoints ?? 0}->${step.to?.checkpoints ?? 0}`).join(", ")}.`);
|
|
76
|
+
lines.push("That checkpoint makes the fix loop prove unfinished dogfood work before anyone claims the original workflow is fully stable.");
|
|
77
|
+
}
|
|
78
|
+
return lines;
|
|
79
|
+
}
|
|
80
|
+
function buildAssetDiff(from, to) {
|
|
81
|
+
const workflows = [];
|
|
82
|
+
const steps = [];
|
|
83
|
+
const fromWorkflows = new Map(from.workflows.map((workflow) => [workflow.id, workflow]));
|
|
84
|
+
const toWorkflows = new Map(to.workflows.map((workflow) => [workflow.id, workflow]));
|
|
85
|
+
const workflowIds = [...new Set([...fromWorkflows.keys(), ...toWorkflows.keys()])].sort();
|
|
86
|
+
for (const workflowId of workflowIds) {
|
|
87
|
+
const fromWorkflow = fromWorkflows.get(workflowId);
|
|
88
|
+
const toWorkflow = toWorkflows.get(workflowId);
|
|
89
|
+
if (!fromWorkflow && toWorkflow) {
|
|
90
|
+
workflows.push({ id: workflowId, change: "added", toStepCount: toWorkflow.stepCount });
|
|
91
|
+
for (const step of toWorkflow.steps)
|
|
92
|
+
steps.push({ id: step.id, workflow: workflowId, change: "added", to: step, fields: ["step"] });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (fromWorkflow && !toWorkflow) {
|
|
96
|
+
workflows.push({ id: workflowId, change: "removed", fromStepCount: fromWorkflow.stepCount });
|
|
97
|
+
for (const step of fromWorkflow.steps)
|
|
98
|
+
steps.push({ id: step.id, workflow: workflowId, change: "removed", from: step, fields: ["step"] });
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (!fromWorkflow || !toWorkflow)
|
|
102
|
+
continue;
|
|
103
|
+
const fromSteps = new Map(fromWorkflow.steps.map((step) => [stepKey(workflowId, step.id), step]));
|
|
104
|
+
const toSteps = new Map(toWorkflow.steps.map((step) => [stepKey(workflowId, step.id), step]));
|
|
105
|
+
const stepKeys = [...new Set([...fromSteps.keys(), ...toSteps.keys()])].sort();
|
|
106
|
+
let modified = false;
|
|
107
|
+
for (const key of stepKeys) {
|
|
108
|
+
const fromStep = fromSteps.get(key);
|
|
109
|
+
const toStep = toSteps.get(key);
|
|
110
|
+
if (!fromStep && toStep) {
|
|
111
|
+
modified = true;
|
|
112
|
+
steps.push({ id: toStep.id, workflow: workflowId, change: "added", to: toStep, fields: ["step"] });
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (fromStep && !toStep) {
|
|
116
|
+
modified = true;
|
|
117
|
+
steps.push({ id: fromStep.id, workflow: workflowId, change: "removed", from: fromStep, fields: ["step"] });
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!fromStep || !toStep)
|
|
121
|
+
continue;
|
|
122
|
+
const fields = changedStepFields(fromStep, toStep);
|
|
123
|
+
if (fields.length > 0) {
|
|
124
|
+
modified = true;
|
|
125
|
+
steps.push({ id: toStep.id, workflow: workflowId, change: "modified", from: fromStep, to: toStep, fields });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (modified || fromWorkflow.stepCount !== toWorkflow.stepCount || fromWorkflow.description !== toWorkflow.description) {
|
|
129
|
+
workflows.push({
|
|
130
|
+
id: workflowId,
|
|
131
|
+
change: "modified",
|
|
132
|
+
fromStepCount: fromWorkflow.stepCount,
|
|
133
|
+
toStepCount: toWorkflow.stepCount,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const fromGovernance = comparableGovernance(from);
|
|
138
|
+
const toGovernance = comparableGovernance(to);
|
|
139
|
+
const governance = Object.keys({ ...fromGovernance, ...toGovernance })
|
|
140
|
+
.sort()
|
|
141
|
+
.filter((field) => JSON.stringify(fromGovernance[field]) !== JSON.stringify(toGovernance[field]))
|
|
142
|
+
.map((field) => ({ field, from: fromGovernance[field], to: toGovernance[field] }));
|
|
143
|
+
const summary = {
|
|
144
|
+
workflowsAdded: workflows.filter((item) => item.change === "added").length,
|
|
145
|
+
workflowsRemoved: workflows.filter((item) => item.change === "removed").length,
|
|
146
|
+
workflowsModified: workflows.filter((item) => item.change === "modified").length,
|
|
147
|
+
stepsAdded: steps.filter((item) => item.change === "added").length,
|
|
148
|
+
stepsRemoved: steps.filter((item) => item.change === "removed").length,
|
|
149
|
+
stepsModified: steps.filter((item) => item.change === "modified").length,
|
|
150
|
+
governanceChanges: governance.length,
|
|
151
|
+
};
|
|
152
|
+
const riskNotes = [
|
|
153
|
+
...(summary.workflowsRemoved > 0 ? [`${summary.workflowsRemoved} workflow(s) removed; manager must confirm this does not drop required behavior before adoption.`] : []),
|
|
154
|
+
...(summary.stepsRemoved > 0 ? [`${summary.stepsRemoved} step(s) removed; compare removed handoffs/checkpoints against current runtime evidence.`] : []),
|
|
155
|
+
...(summary.governanceChanges > 0 ? [`${summary.governanceChanges} governance field(s) changed; inspect cascade/evolution posture before rollout.`] : []),
|
|
156
|
+
];
|
|
157
|
+
const riskLevel = summary.workflowsRemoved > 0 || summary.stepsRemoved > 0
|
|
158
|
+
? "high"
|
|
159
|
+
: summary.governanceChanges > 0 || summary.workflowsAdded > 0 || summary.stepsAdded > 0
|
|
160
|
+
? "medium"
|
|
161
|
+
: "low";
|
|
162
|
+
const reviewDecision = {
|
|
163
|
+
status: "manager_review_required",
|
|
164
|
+
riskLevel,
|
|
165
|
+
riskNotes,
|
|
166
|
+
reason: riskLevel === "high"
|
|
167
|
+
? "Structural removals require explicit manager acceptance before the proposed asset version can be adopted or bound."
|
|
168
|
+
: "Diff review is the manager gate before the proposed asset version can be accepted into an organization route.",
|
|
169
|
+
acceptanceCommand: `dna assets adoption ${to.name}`,
|
|
170
|
+
orgBindCommand: `dna org bind --asset ${to.name} --status active --sync-target codex`,
|
|
171
|
+
rolloutCommand: "dna org rollout",
|
|
172
|
+
syncCommand: "dna sync --codex",
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
from: from.name,
|
|
176
|
+
to: to.name,
|
|
177
|
+
summary,
|
|
178
|
+
workflows,
|
|
179
|
+
steps,
|
|
180
|
+
governance,
|
|
181
|
+
changeExplanation: assetDiffChangeExplanation(from, to, steps),
|
|
182
|
+
reviewDecision,
|
|
183
|
+
reviewEvidence: assetDiffReviewEvidence(from.name, to.name),
|
|
184
|
+
acceptanceChecklist: [
|
|
185
|
+
"The workflow change matches the stated intent and does not remove required behavior without an explicit reason.",
|
|
186
|
+
...(riskLevel === "high" ? ["High-risk removals have explicit manager rationale before adoption or organization binding."] : []),
|
|
187
|
+
"Evidence links are inspectable and strong enough for manager review.",
|
|
188
|
+
"Cascade inheritance and preview-first evolution remain visible before runtime sync.",
|
|
189
|
+
"Acceptance continues through adoption review, organization binding, rollout, and only then runtime sync.",
|
|
190
|
+
],
|
|
191
|
+
nextActions: [
|
|
192
|
+
`Review proposed asset details: dna assets inspect ${to.name} --evidence`,
|
|
193
|
+
`If manager accepts this diff, run adoption review: ${reviewDecision.acceptanceCommand}`,
|
|
194
|
+
`Record accepted version in the organization route: ${reviewDecision.orgBindCommand}`,
|
|
195
|
+
`Review rollout readiness before runtime sync: ${reviewDecision.rolloutCommand}`,
|
|
196
|
+
`Sync only after review, adoption, binding, and rollout: ${reviewDecision.syncCommand}`,
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function writeAssetDiff(diff) {
|
|
201
|
+
process.stderr.write(`Workflow asset diff: ${diff.from} -> ${diff.to}\n`);
|
|
202
|
+
process.stderr.write(`Workflows: +${diff.summary.workflowsAdded} -${diff.summary.workflowsRemoved} ~${diff.summary.workflowsModified}\n`);
|
|
203
|
+
process.stderr.write(`Steps: +${diff.summary.stepsAdded} -${diff.summary.stepsRemoved} ~${diff.summary.stepsModified}\n`);
|
|
204
|
+
process.stderr.write(`Governance changes: ${diff.summary.governanceChanges}\n`);
|
|
205
|
+
if (diff.changeExplanation.length > 0) {
|
|
206
|
+
process.stderr.write("\nWhat changed and why:\n");
|
|
207
|
+
for (const line of diff.changeExplanation)
|
|
208
|
+
process.stderr.write(` - ${line}\n`);
|
|
209
|
+
}
|
|
210
|
+
if (diff.workflows.length > 0) {
|
|
211
|
+
process.stderr.write("\nWorkflow changes:\n");
|
|
212
|
+
for (const workflow of diff.workflows) {
|
|
213
|
+
process.stderr.write(` - [${workflow.change}] ${workflow.id}`);
|
|
214
|
+
if (workflow.fromStepCount !== undefined || workflow.toStepCount !== undefined) {
|
|
215
|
+
process.stderr.write(` steps=${workflow.fromStepCount ?? "none"} -> ${workflow.toStepCount ?? "none"}`);
|
|
216
|
+
}
|
|
217
|
+
process.stderr.write("\n");
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (diff.steps.length > 0) {
|
|
221
|
+
process.stderr.write("\nStep changes:\n");
|
|
222
|
+
for (const step of diff.steps) {
|
|
223
|
+
process.stderr.write(` - [${step.change}] ${step.workflow}/${step.id}`);
|
|
224
|
+
if (step.fields.length > 0)
|
|
225
|
+
process.stderr.write(` fields=${step.fields.join(",")}`);
|
|
226
|
+
process.stderr.write("\n");
|
|
227
|
+
if (step.change === "modified") {
|
|
228
|
+
process.stderr.write(` from: ${compactStep(step.from)}\n`);
|
|
229
|
+
process.stderr.write(` to: ${compactStep(step.to)}\n`);
|
|
230
|
+
}
|
|
231
|
+
else if (step.to) {
|
|
232
|
+
process.stderr.write(` to: ${compactStep(step.to)}\n`);
|
|
233
|
+
}
|
|
234
|
+
else if (step.from) {
|
|
235
|
+
process.stderr.write(` from: ${compactStep(step.from)}\n`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (diff.governance.length > 0) {
|
|
240
|
+
process.stderr.write("\nGovernance changes:\n");
|
|
241
|
+
for (const change of diff.governance) {
|
|
242
|
+
process.stderr.write(` - ${change.field}: ${JSON.stringify(change.from)} -> ${JSON.stringify(change.to)}\n`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
process.stderr.write("\nReview decision:\n");
|
|
246
|
+
process.stderr.write(` - status: ${diff.reviewDecision.status}\n`);
|
|
247
|
+
process.stderr.write(` - risk: ${diff.reviewDecision.riskLevel}\n`);
|
|
248
|
+
for (const note of diff.reviewDecision.riskNotes)
|
|
249
|
+
process.stderr.write(` - ${note}\n`);
|
|
250
|
+
process.stderr.write(` - reason: ${diff.reviewDecision.reason}\n`);
|
|
251
|
+
process.stderr.write(` - adoption review: ${diff.reviewDecision.acceptanceCommand}\n`);
|
|
252
|
+
process.stderr.write(` - org bind: ${diff.reviewDecision.orgBindCommand}\n`);
|
|
253
|
+
process.stderr.write(` - rollout: ${diff.reviewDecision.rolloutCommand}\n`);
|
|
254
|
+
process.stderr.write(` - sync after rollout: ${diff.reviewDecision.syncCommand}\n`);
|
|
255
|
+
process.stderr.write("\nReview evidence:\n");
|
|
256
|
+
process.stderr.write(` - mode: ${diff.reviewEvidence.mode}\n`);
|
|
257
|
+
process.stderr.write(` - authority: ${diff.reviewEvidence.authority}\n`);
|
|
258
|
+
process.stderr.write(` - receipt command: ${diff.reviewEvidence.receiptCommand}\n`);
|
|
259
|
+
process.stderr.write(` - receipt path: ${diff.reviewEvidence.receiptPathHint}\n`);
|
|
260
|
+
for (const boundary of diff.reviewEvidence.boundaries)
|
|
261
|
+
process.stderr.write(` - ${boundary}\n`);
|
|
262
|
+
process.stderr.write("\nAcceptance checklist:\n");
|
|
263
|
+
for (const item of diff.acceptanceChecklist)
|
|
264
|
+
process.stderr.write(` - ${item}\n`);
|
|
265
|
+
process.stderr.write("\nNext actions:\n");
|
|
266
|
+
for (const action of diff.nextActions)
|
|
267
|
+
process.stderr.write(` - ${action}\n`);
|
|
268
|
+
}
|
|
269
|
+
function buildGovernanceReview(asset, evidence, assets = [asset], evidenceSourceAsset = asset) {
|
|
270
|
+
const nextActions = [];
|
|
271
|
+
if (!asset.status.hasCascade) {
|
|
272
|
+
nextActions.push("Declare cascade inheritance before distributing this asset across projects or teams.");
|
|
273
|
+
}
|
|
274
|
+
const evidenceGate = buildGovernanceEvidenceGate(asset, evidence, evidenceSourceAsset);
|
|
275
|
+
const followupRoute = evidenceGate.status === "open"
|
|
276
|
+
? activeEvidenceFollowupReviewRoute(evidenceSourceAsset.name, assets, evidence)
|
|
277
|
+
: undefined;
|
|
278
|
+
const routedEvidenceGate = followupRoute
|
|
279
|
+
? { ...evidenceGate, nextCommand: followupRoute.currentCommand }
|
|
280
|
+
: evidenceGate;
|
|
281
|
+
const draftCommand = evidenceSuggestionDraftCommand(asset, evidence, `${asset.name}-v2`);
|
|
282
|
+
const evidenceAllowsMutation = evidenceGate.status === "clear";
|
|
283
|
+
const decision = !asset.status.hasCascade
|
|
284
|
+
? "declare_cascade"
|
|
285
|
+
: !evidenceAllowsMutation
|
|
286
|
+
? "review_evidence_first"
|
|
287
|
+
: !asset.status.configured
|
|
288
|
+
? "configure_asset"
|
|
289
|
+
: "preview_evolution";
|
|
290
|
+
const nextCommand = decision === "declare_cascade"
|
|
291
|
+
? `dna assets inspect ${asset.name} --evidence`
|
|
292
|
+
: decision === "configure_asset"
|
|
293
|
+
? `dna init --template ${asset.name}`
|
|
294
|
+
: decision === "review_evidence_first"
|
|
295
|
+
? routedEvidenceGate.nextCommand ?? `dna assets suggestions ${asset.name}`
|
|
296
|
+
: asset.evolution.previewCommand;
|
|
297
|
+
if (evidenceGate.status === "open") {
|
|
298
|
+
nextActions.push(`Review evidence-backed follow-up before marker mutation: ${routedEvidenceGate.nextCommand}`);
|
|
299
|
+
if (followupRoute) {
|
|
300
|
+
nextActions.push(`Review candidate diff before adoption/sync: ${followupRoute.diffCommand}`);
|
|
301
|
+
}
|
|
302
|
+
else if (draftCommand) {
|
|
303
|
+
nextActions.push(`Review suggested workflow action before adoption/sync: ${draftCommand}`);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
nextActions.push(`No template improvement suggestion is ready yet; continue from evidence review: dna assets suggestions ${asset.name}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
if (!asset.status.configured) {
|
|
311
|
+
nextActions.push(`Configure this asset in the project: dna init --template ${asset.name}`);
|
|
312
|
+
}
|
|
313
|
+
nextActions.push(`Review marker changes before mutation: ${asset.evolution.previewCommand}`);
|
|
314
|
+
nextActions.push(`Apply evolution only through an explicit reviewed sync path: ${asset.evolution.mutatingCommand}`);
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
asset: asset.name,
|
|
318
|
+
displayName: asset.displayName,
|
|
319
|
+
managerGates: buildManagerGates({
|
|
320
|
+
asset,
|
|
321
|
+
evidenceStatus: evidenceGate.status,
|
|
322
|
+
evidenceNextCommand: routedEvidenceGate.nextCommand,
|
|
323
|
+
evidenceAllowsMutation,
|
|
324
|
+
}),
|
|
325
|
+
reviewPacket: {
|
|
326
|
+
cascadeReady: asset.status.hasCascade,
|
|
327
|
+
evolutionReady: asset.evolution.reviewMode === "preview_first",
|
|
328
|
+
evidenceAllowsMutation,
|
|
329
|
+
syncAllowed: asset.status.configured && evidenceAllowsMutation,
|
|
330
|
+
compilerEffect: asset.status.hasCascade
|
|
331
|
+
? `Compiler merges inherited layers (${asset.cascade.inherits.length > 0 ? asset.cascade.inherits.join(", ") : "none"}) before runtime artifacts are generated.`
|
|
332
|
+
: "Compiler treats this as a standalone asset until cascade inheritance is declared.",
|
|
333
|
+
mutationBoundary: evidenceAllowsMutation
|
|
334
|
+
? `Evolution remains preview-first; review ${asset.evolution.previewCommand} before explicit mutation through ${asset.evolution.mutatingCommand}.`
|
|
335
|
+
: "Evolution and runtime sync are blocked by active evidence; create a reviewed workflow draft before marker mutation.",
|
|
336
|
+
decision,
|
|
337
|
+
nextCommand,
|
|
338
|
+
},
|
|
339
|
+
evidenceGate: routedEvidenceGate,
|
|
340
|
+
cascade: {
|
|
341
|
+
posture: asset.status.cascadePosture,
|
|
342
|
+
declared: asset.cascade.declared,
|
|
343
|
+
layerType: asset.cascade.type,
|
|
344
|
+
priority: asset.cascade.priority,
|
|
345
|
+
inherits: asset.cascade.inherits,
|
|
346
|
+
explanation: asset.cascade.declared
|
|
347
|
+
? "Cascade is declared in the workflow asset. The compiler merges inherited DNA layers before runtime artifacts are generated."
|
|
348
|
+
: "Cascade is not declared; the asset behaves as a standalone template until an org/project layer is attached.",
|
|
349
|
+
},
|
|
350
|
+
evolution: {
|
|
351
|
+
posture: asset.status.evolutionPosture,
|
|
352
|
+
reviewMode: asset.evolution.reviewMode,
|
|
353
|
+
declaredMarkers: asset.evolution.declaredMarkers,
|
|
354
|
+
markerIds: asset.evolution.markerIds,
|
|
355
|
+
outcomeStore: asset.evolution.outcomeStore,
|
|
356
|
+
previewCommand: asset.evolution.previewCommand,
|
|
357
|
+
mutatingCommand: asset.evolution.mutatingCommand,
|
|
358
|
+
explanation: "Evolution is preview-first. Outcomes can suggest marker changes, but policy mutation requires human review and an explicit sync/evolve path.",
|
|
359
|
+
},
|
|
360
|
+
implementationRefs: [
|
|
361
|
+
{
|
|
362
|
+
layer: "asset_template",
|
|
363
|
+
path: asset.path,
|
|
364
|
+
role: "Declares workflows, cascade inheritance, evidence links, and preview-first evolution posture for this asset.",
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
layer: "cascade_compiler",
|
|
368
|
+
path: "src/compiler/cascade.ts",
|
|
369
|
+
role: "Merges inherited DNA layers before compile/sync so workflow governance is not duplicated inside harness adapters.",
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
layer: "evolution_engine",
|
|
373
|
+
path: "src/evolution/",
|
|
374
|
+
role: "Collects outcomes and produces previewable marker changes; mutation remains explicit and human-reviewed.",
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
layer: "dogfood_evidence",
|
|
378
|
+
path: evidence?.progressDir ?? "unavailable",
|
|
379
|
+
role: evidence
|
|
380
|
+
? "Provides module progress JSON used to gate suggestions, marker mutation, and runtime sync."
|
|
381
|
+
: "No evidence index is available yet; governance must stay preview-only.",
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
layer: "runtime_sync",
|
|
385
|
+
path: "src/cli/commands/sync.ts",
|
|
386
|
+
role: "Installs runtime artifacts after review; governance decisions stay in dna core/runtime rather than dashboard or Obsidian.",
|
|
387
|
+
},
|
|
388
|
+
],
|
|
389
|
+
nextActions,
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function buildGovernanceEvidenceGate(asset, evidence, evidenceSourceAsset = asset) {
|
|
393
|
+
if (!evidence) {
|
|
394
|
+
return {
|
|
395
|
+
status: "unknown",
|
|
396
|
+
modules: 0,
|
|
397
|
+
pass: 0,
|
|
398
|
+
continue: 0,
|
|
399
|
+
blocked: 0,
|
|
400
|
+
remaining: 0,
|
|
401
|
+
explanation: "No dogfood evidence index is available for this asset; keep evolution preview-only until evidence is reviewed.",
|
|
402
|
+
nextCommand: `dna assets evidence ${evidenceSourceAsset.name}`,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
const open = evidence.totals.continue > 0 || evidence.totals.blocked > 0 || evidence.totals.remaining > 0;
|
|
406
|
+
const sourcePrefix = evidenceSourceAsset.name === asset.name
|
|
407
|
+
? "Dogfood evidence"
|
|
408
|
+
: `Source dogfood evidence for ${evidenceSourceAsset.name}`;
|
|
409
|
+
return {
|
|
410
|
+
status: open ? "open" : "clear",
|
|
411
|
+
modules: evidence.totals.modules,
|
|
412
|
+
pass: evidence.totals.pass,
|
|
413
|
+
continue: evidence.totals.continue,
|
|
414
|
+
blocked: evidence.totals.blocked,
|
|
415
|
+
remaining: evidence.totals.remaining,
|
|
416
|
+
explanation: open
|
|
417
|
+
? `${sourcePrefix} still has active work; review suggestions and draft workflow changes before any marker mutation or runtime sync.`
|
|
418
|
+
: `${sourcePrefix} has no active remaining work; marker previews can be reviewed explicitly before mutation.`,
|
|
419
|
+
nextCommand: open ? `dna assets suggestions ${evidenceSourceAsset.name}` : asset.evolution.previewCommand,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
function describeCascadeInheritance(asset) {
|
|
423
|
+
const inheritance = asset.cascade.inherits.length > 0 ? `inherits ${asset.cascade.inherits.join(", ")}` : "inherits none";
|
|
424
|
+
return asset.cascade.priority !== undefined ? `${inheritance} at priority ${asset.cascade.priority}` : inheritance;
|
|
425
|
+
}
|
|
426
|
+
function findPredecessorAsset(asset, assets) {
|
|
427
|
+
const activeFollowupMatch = /^(.*)-active-followup$/.exec(asset.name);
|
|
428
|
+
if (activeFollowupMatch) {
|
|
429
|
+
return assets.find((candidate) => candidate.name === activeFollowupMatch[1]);
|
|
430
|
+
}
|
|
431
|
+
const versionMatch = /^(.*)-v\d+$/.exec(asset.name);
|
|
432
|
+
if (versionMatch) {
|
|
433
|
+
return assets.find((candidate) => candidate.name === versionMatch[1]);
|
|
434
|
+
}
|
|
435
|
+
return undefined;
|
|
436
|
+
}
|
|
437
|
+
function evidenceIndexOpen(evidence) {
|
|
438
|
+
return !!evidence && (evidence.totals.continue > 0 ||
|
|
439
|
+
evidence.totals.blocked > 0 ||
|
|
440
|
+
evidence.totals.requestChanges > 0 ||
|
|
441
|
+
evidence.totals.remaining > 0);
|
|
442
|
+
}
|
|
443
|
+
function buildAdoptionReview(asset, assets = [asset], predecessorEvidence) {
|
|
444
|
+
const predecessor = findPredecessorAsset(asset, assets);
|
|
445
|
+
const predecessorDiff = predecessor ? buildAssetDiff(predecessor, asset) : undefined;
|
|
446
|
+
const predecessorAcceptanceRequired = predecessorDiff ? predecessorDiff.reviewDecision.riskLevel !== "low" : false;
|
|
447
|
+
const predecessorEvidenceOpen = evidenceIndexOpen(predecessorEvidence);
|
|
448
|
+
const openPredecessorEvidence = predecessorEvidenceOpen ? predecessorEvidence : undefined;
|
|
449
|
+
const evidenceDescriptions = asset.evidence
|
|
450
|
+
.map((link) => link.description)
|
|
451
|
+
.filter((description) => typeof description === "string" && description.length > 0);
|
|
452
|
+
const checks = [
|
|
453
|
+
{
|
|
454
|
+
id: "workflow",
|
|
455
|
+
label: "Workflow",
|
|
456
|
+
status: asset.status.hasWorkflow ? "pass" : "warn",
|
|
457
|
+
detail: asset.status.hasWorkflow
|
|
458
|
+
? `${asset.workflows.length} workflow(s), ${asset.workflows.reduce((sum, workflow) => sum + workflow.stepCount, 0)} step(s)`
|
|
459
|
+
: "No workflow steps are declared yet.",
|
|
460
|
+
},
|
|
461
|
+
{
|
|
462
|
+
id: "controller",
|
|
463
|
+
label: "Controller",
|
|
464
|
+
status: asset.status.hasController ? "pass" : "warn",
|
|
465
|
+
detail: asset.status.hasController
|
|
466
|
+
? `${asset.controllers.length} controller(s) declared`
|
|
467
|
+
: "No controller is declared for repeatable execution.",
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "cascade",
|
|
471
|
+
label: "Cascade",
|
|
472
|
+
status: asset.status.hasCascade ? "pass" : "warn",
|
|
473
|
+
detail: asset.status.hasCascade
|
|
474
|
+
? describeCascadeInheritance(asset)
|
|
475
|
+
: "No inheritance layer is declared; keep this local until org/project inheritance is explicit.",
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "evolution",
|
|
479
|
+
label: "Evolution",
|
|
480
|
+
status: asset.evolution.reviewMode === "preview_first" ? "pass" : "warn",
|
|
481
|
+
detail: `review mode=${asset.evolution.reviewMode}; preview=${asset.evolution.previewCommand}`,
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
id: "evidence",
|
|
485
|
+
label: "Evidence",
|
|
486
|
+
status: asset.status.hasEvidence && !predecessorEvidenceOpen ? "pass" : "warn",
|
|
487
|
+
detail: openPredecessorEvidence && predecessor
|
|
488
|
+
? `Source evidence for ${predecessor.name} is still open: modules=${openPredecessorEvidence.totals.modules}; continue=${openPredecessorEvidence.totals.continue}; blocked=${openPredecessorEvidence.totals.blocked + openPredecessorEvidence.totals.requestChanges}; remaining=${openPredecessorEvidence.totals.remaining}. Keep ${asset.name} as a review draft until diff, rationale, adoption, organization binding, and rollout are explicitly accepted.`
|
|
489
|
+
: asset.status.hasEvidence
|
|
490
|
+
? `${asset.evidence.length} evidence/reference link(s); posture=${asset.status.evidencePosture}${evidenceDescriptions.length > 0 ? `; ${evidenceDescriptions.join("; ")}` : ""}`
|
|
491
|
+
: "No evidence is linked; do not present this asset as proven yet.",
|
|
492
|
+
},
|
|
493
|
+
...(predecessorDiff ? [{
|
|
494
|
+
id: "diff_acceptance",
|
|
495
|
+
label: "Diff acceptance",
|
|
496
|
+
status: predecessorAcceptanceRequired ? "warn" : "pass",
|
|
497
|
+
detail: predecessorAcceptanceRequired
|
|
498
|
+
? `${predecessorDiff.reviewDecision.riskLevel}-risk predecessor diff requires explicit manager rationale before adoption, organization binding, rollout, or sync.`
|
|
499
|
+
: "Predecessor diff is low risk; keep the receipt with the adoption review packet.",
|
|
500
|
+
}] : []),
|
|
501
|
+
{
|
|
502
|
+
id: "project_config",
|
|
503
|
+
label: "Project config",
|
|
504
|
+
status: asset.status.configured ? "pass" : "warn",
|
|
505
|
+
detail: asset.status.configured
|
|
506
|
+
? `configured at ${asset.status.configPaths.join(", ")}`
|
|
507
|
+
: "not configured in this project yet",
|
|
508
|
+
},
|
|
509
|
+
];
|
|
510
|
+
const hardWarnings = checks.filter((check) => check.status === "warn" && check.id !== "project_config");
|
|
511
|
+
const recommendation = hardWarnings.length === 0
|
|
512
|
+
? "ready_to_adopt"
|
|
513
|
+
: asset.status.hasWorkflow && asset.status.hasController
|
|
514
|
+
? "review_first"
|
|
515
|
+
: "not_ready";
|
|
516
|
+
return {
|
|
517
|
+
asset: asset.name,
|
|
518
|
+
displayName: asset.displayName,
|
|
519
|
+
recommendation,
|
|
520
|
+
summary: recommendation === "ready_to_adopt"
|
|
521
|
+
? "This asset has the minimum workflow, controller, cascade, evolution, and evidence signals needed for explicit project adoption."
|
|
522
|
+
: predecessorEvidenceOpen
|
|
523
|
+
? "This asset is structurally reviewable, but source dogfood evidence is still open; keep it as a review draft until manager rationale, adoption, organization binding, and rollout gates are explicitly accepted."
|
|
524
|
+
: predecessorAcceptanceRequired
|
|
525
|
+
? "This asset is structurally reviewable, but the predecessor diff requires explicit manager rationale before adoption, organization binding, rollout, or sync."
|
|
526
|
+
: recommendation === "review_first"
|
|
527
|
+
? "This asset can be reviewed, but at least one governance or evidence signal should be fixed before broad rollout."
|
|
528
|
+
: "This asset is not ready for adoption because the executable workflow shape is incomplete.",
|
|
529
|
+
predecessorDiff: predecessorDiff
|
|
530
|
+
? {
|
|
531
|
+
from: predecessorDiff.from,
|
|
532
|
+
command: `dna assets diff ${predecessorDiff.from} ${predecessorDiff.to}`,
|
|
533
|
+
riskLevel: predecessorDiff.reviewDecision.riskLevel,
|
|
534
|
+
riskNotes: predecessorDiff.reviewDecision.riskNotes,
|
|
535
|
+
acceptanceRequired: predecessorDiff.reviewDecision.riskLevel !== "low",
|
|
536
|
+
reason: predecessorDiff.reviewDecision.reason,
|
|
537
|
+
receiptCommand: predecessorDiff.reviewEvidence.receiptCommand,
|
|
538
|
+
receiptPathHint: predecessorDiff.reviewEvidence.receiptPathHint,
|
|
539
|
+
}
|
|
540
|
+
: undefined,
|
|
541
|
+
checks,
|
|
542
|
+
adoptionPath: [
|
|
543
|
+
...(predecessorDiff ? [{
|
|
544
|
+
id: "diff",
|
|
545
|
+
command: `dna assets diff ${predecessorDiff.from} ${predecessorDiff.to}`,
|
|
546
|
+
purpose: "Review predecessor changes and record manager rationale before adoption, organization binding, or sync.",
|
|
547
|
+
}] : []),
|
|
548
|
+
{
|
|
549
|
+
id: "inspect",
|
|
550
|
+
command: `dna assets inspect ${asset.name} --evidence`,
|
|
551
|
+
purpose: "Read the workflow steps, roles, controllers, cascade, and evidence links.",
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
id: "governance",
|
|
555
|
+
command: `dna assets governance ${asset.name}`,
|
|
556
|
+
purpose: "Confirm inheritance and preview-first evolution before any mutation.",
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
id: "evolution-preview",
|
|
560
|
+
command: asset.evolution.previewCommand,
|
|
561
|
+
purpose: "Preview marker changes; do not persist them from this review step.",
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
id: "org-bind",
|
|
565
|
+
command: `dna org bind --asset ${asset.name} --status active --sync-target codex`,
|
|
566
|
+
purpose: "Record manager adoption in the organization profile only after diff and adoption review are accepted.",
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
id: "configure",
|
|
570
|
+
command: `dna init --template ${asset.name}`,
|
|
571
|
+
purpose: "Generate project DNA config only after adoption is accepted.",
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: "sync",
|
|
575
|
+
command: "dna sync --codex",
|
|
576
|
+
purpose: "Install runtime artifacts after review, adoption, and configuration.",
|
|
577
|
+
},
|
|
578
|
+
],
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
function compactEvidenceModule(module) {
|
|
582
|
+
const remaining = module.remainingCount > 0 ? ` remaining=${module.remainingCount}` : "";
|
|
583
|
+
const blocked = module.blockedCount > 0 ? ` blocked=${module.blockedCount}` : "";
|
|
584
|
+
const red = module.newRedCount > 0 ? ` new_red=${module.newRedCount}` : "";
|
|
585
|
+
return `${module.module}: ${module.verdict}${module.moduleComplete ? " complete" : " incomplete"}${remaining}${blocked}${red}`;
|
|
586
|
+
}
|
|
587
|
+
function withCurrentActiveFollowupEvidence(asset, evidence) {
|
|
588
|
+
if (!evidence || !asset.name.endsWith("-active-followup"))
|
|
589
|
+
return asset;
|
|
590
|
+
const active = activeEvidenceModules(evidence);
|
|
591
|
+
if (active.length === 0)
|
|
592
|
+
return asset;
|
|
593
|
+
const description = `Candidate active-followup-${evidence.asset}: ${active.length} active module(s): ${active.slice(0, 5).map(compactEvidenceModule).join("; ")}`;
|
|
594
|
+
return {
|
|
595
|
+
...asset,
|
|
596
|
+
evidence: asset.evidence.map((link) => link.label === `Active dogfood evidence: ${evidence.asset}`
|
|
597
|
+
? { ...link, description }
|
|
598
|
+
: link),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
async function normalizeAssetForReview(asset, projectDir) {
|
|
602
|
+
const match = /^(.*)-active-followup$/.exec(asset.name);
|
|
603
|
+
if (!match)
|
|
604
|
+
return asset;
|
|
605
|
+
const evidence = await buildWorkflowAssetEvidenceIndex(match[1], { projectDir });
|
|
606
|
+
return withCurrentActiveFollowupEvidence(asset, evidence);
|
|
607
|
+
}
|
|
608
|
+
function suggestionId(value) {
|
|
609
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "suggestion";
|
|
610
|
+
}
|
|
611
|
+
function suggestionReviewCommand(params) {
|
|
612
|
+
return `dna workflow review-suggestion --from-template ${params.asset} --workflow ${params.workflow ?? "fix"} --suggestion ${params.suggestionId} --decision ${params.decision ?? "accept"}`;
|
|
613
|
+
}
|
|
614
|
+
function suggestionDraftCommand(params) {
|
|
615
|
+
return `dna workflow improve-from-suggestions --from-template ${params.asset} --workflow ${params.workflow ?? "fix"} --suggestion ${params.suggestionId} --save-template ${params.saveTemplate}`;
|
|
616
|
+
}
|
|
617
|
+
function suggestionAdoptionAsset(suggestion) {
|
|
618
|
+
if (suggestion.id === WORKFLOW_SUGGESTION_IDS.crossBoundaryResourceApproval)
|
|
619
|
+
return WORKFLOW_ASSET_IDS.crossBoundaryResourceApproval;
|
|
620
|
+
if (suggestion.id === WORKFLOW_SUGGESTION_IDS.blockedTriage)
|
|
621
|
+
return WORKFLOW_ASSET_IDS.blockedScopeTriage;
|
|
622
|
+
return undefined;
|
|
623
|
+
}
|
|
624
|
+
function suggestionAction(params) {
|
|
625
|
+
if (params.suggestion.id === WORKFLOW_SUGGESTION_IDS.activeFollowup) {
|
|
626
|
+
const route = activeEvidenceFollowupRoute(params.asset);
|
|
627
|
+
return {
|
|
628
|
+
kind: "draft",
|
|
629
|
+
command: route.command,
|
|
630
|
+
assetName: route.assetName,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
const adoptionAsset = suggestionAdoptionAsset(params.suggestion);
|
|
634
|
+
if (adoptionAsset) {
|
|
635
|
+
return {
|
|
636
|
+
kind: "adoption",
|
|
637
|
+
command: `dna assets adoption ${adoptionAsset}`,
|
|
638
|
+
assetName: adoptionAsset,
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
return {
|
|
642
|
+
kind: "draft",
|
|
643
|
+
command: suggestionDraftCommand({
|
|
644
|
+
asset: params.asset,
|
|
645
|
+
suggestionId: params.suggestion.id,
|
|
646
|
+
saveTemplate: params.saveTemplate,
|
|
647
|
+
workflow: params.workflow,
|
|
648
|
+
}),
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
function suggestionActionCommand(params) {
|
|
652
|
+
return suggestionAction(params).command;
|
|
653
|
+
}
|
|
654
|
+
function buildSuggestionImprovementRoute(params) {
|
|
655
|
+
const nextVersion = `${params.asset.name}-v2`;
|
|
656
|
+
const action = params.draftSuggestion
|
|
657
|
+
? suggestionAction({
|
|
658
|
+
asset: params.asset.name,
|
|
659
|
+
suggestion: params.draftSuggestion,
|
|
660
|
+
saveTemplate: nextVersion,
|
|
661
|
+
})
|
|
662
|
+
: undefined;
|
|
663
|
+
const reviewCommand = !params.activeFollowup
|
|
664
|
+
&& params.draftSuggestion
|
|
665
|
+
&& action?.kind === "draft"
|
|
666
|
+
&& params.draftSuggestion.id !== WORKFLOW_SUGGESTION_IDS.activeFollowup
|
|
667
|
+
? suggestionReviewCommand({
|
|
668
|
+
asset: params.asset.name,
|
|
669
|
+
suggestionId: params.draftSuggestion.id,
|
|
670
|
+
workflow: "fix",
|
|
671
|
+
decision: "accept",
|
|
672
|
+
})
|
|
673
|
+
: undefined;
|
|
674
|
+
const templateCommand = reviewCommand ??
|
|
675
|
+
params.activeFollowup?.currentCommand ??
|
|
676
|
+
action?.command ??
|
|
677
|
+
`dna workflow edit-plan "Describe the reviewed workflow change" --from-template ${params.asset.name} --save-template ${nextVersion}`;
|
|
678
|
+
const organizationCommand = params.activeFollowup?.bindCommand ??
|
|
679
|
+
`dna org bind --asset ${action?.assetName ?? nextVersion} --status active --sync-target codex`;
|
|
680
|
+
const templateWrites = reviewCommand ? true : (params.activeFollowup?.currentWrites ?? (action ? action.kind === "draft" : true));
|
|
681
|
+
return [
|
|
682
|
+
{
|
|
683
|
+
id: "evidence",
|
|
684
|
+
label: "Read dogfood evidence",
|
|
685
|
+
command: `dna assets evidence ${params.asset.name}`,
|
|
686
|
+
purpose: "Use canonical dogfood progress as the source of improvement instead of chat memory or loose notes.",
|
|
687
|
+
owner: "dna_core",
|
|
688
|
+
writes: false,
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
id: "template",
|
|
692
|
+
label: "Promote into workflow asset",
|
|
693
|
+
command: templateCommand,
|
|
694
|
+
purpose: "Turn concrete dogfood problems into a reviewed template version or reusable workflow asset before adoption.",
|
|
695
|
+
owner: "dna_core",
|
|
696
|
+
writes: templateWrites,
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
id: "infra",
|
|
700
|
+
label: "Escalate repeated gaps to dna infra",
|
|
701
|
+
command: `dna assets governance ${params.asset.name}`,
|
|
702
|
+
purpose: "If multiple templates need the same support, keep the capability in dna core rather than copy-pasting prompt logic into each workflow.",
|
|
703
|
+
owner: "dna_core",
|
|
704
|
+
writes: false,
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
id: "organization",
|
|
708
|
+
label: "Review organization rollout",
|
|
709
|
+
command: organizationCommand,
|
|
710
|
+
purpose: "Managers accept the reviewed asset version into the organization route before rollout and runtime sync.",
|
|
711
|
+
owner: "manager",
|
|
712
|
+
writes: true,
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
id: "evolution",
|
|
716
|
+
label: "Preview evolution only after review",
|
|
717
|
+
command: params.asset.evolution.previewCommand,
|
|
718
|
+
purpose: "Evolution is preview-first; marker mutation waits for reviewed evidence, adopted asset changes, and explicit sync/evolve.",
|
|
719
|
+
owner: "dna_core",
|
|
720
|
+
writes: false,
|
|
721
|
+
},
|
|
722
|
+
];
|
|
723
|
+
}
|
|
724
|
+
function firstDraftableSuggestion(suggestions) {
|
|
725
|
+
return suggestions.find((suggestion) => suggestion.type === "template_improvement" ||
|
|
726
|
+
suggestion.id === WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts);
|
|
727
|
+
}
|
|
728
|
+
function evidenceSuggestionDraftCommand(asset, evidence, saveTemplate) {
|
|
729
|
+
if (!evidence || asset.status.evidencePosture !== "dogfood_linked")
|
|
730
|
+
return undefined;
|
|
731
|
+
const suggestion = firstDraftableSuggestion(buildAssetSuggestions(asset, evidence).suggestions);
|
|
732
|
+
return suggestion ? suggestionActionCommand({ asset: asset.name, suggestion, saveTemplate }) : undefined;
|
|
733
|
+
}
|
|
734
|
+
function activeEvidenceModules(index) {
|
|
735
|
+
return index.modules.filter((module) => module.verdict !== "PASS" ||
|
|
736
|
+
!module.moduleComplete ||
|
|
737
|
+
module.remainingCount > 0 ||
|
|
738
|
+
module.newRedCount > 0);
|
|
739
|
+
}
|
|
740
|
+
function compactEvidenceCapture(capture) {
|
|
741
|
+
const result = capture.result ? `/${capture.result}` : "";
|
|
742
|
+
const scope = [capture.workflowId, capture.stepId].filter((value) => value && value !== "unknown").join(":");
|
|
743
|
+
const prefix = `${capture.capturedAt.slice(0, 10)} ${capture.fact}${result}${scope ? ` ${scope}` : ""}`;
|
|
744
|
+
return `${prefix}: ${capture.summary}`.slice(0, 240);
|
|
745
|
+
}
|
|
746
|
+
function c5GapCaptures(index) {
|
|
747
|
+
return index.captureFacts.filter((capture) => capture.fact === "blocker" ||
|
|
748
|
+
capture.fact === "manual_intervention" ||
|
|
749
|
+
capture.fact === "retry" ||
|
|
750
|
+
capture.result === "blocked" ||
|
|
751
|
+
capture.result === "failure" ||
|
|
752
|
+
capture.result === "unsupported");
|
|
753
|
+
}
|
|
754
|
+
function c5EvidenceRefs(captures) {
|
|
755
|
+
const refs = captures.flatMap((capture) => [
|
|
756
|
+
{ type: "capture_event", ref: capture.path },
|
|
757
|
+
...capture.evidenceRefs,
|
|
758
|
+
]);
|
|
759
|
+
const seen = new Set();
|
|
760
|
+
return refs.filter((ref) => {
|
|
761
|
+
const key = `${ref.type}:${ref.ref}`;
|
|
762
|
+
if (seen.has(key))
|
|
763
|
+
return false;
|
|
764
|
+
seen.add(key);
|
|
765
|
+
return true;
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
function c5CaptureBootstrapCommands(assetName) {
|
|
769
|
+
return [
|
|
770
|
+
`dna init --template ${assetName}`,
|
|
771
|
+
"dna sync --codex --dry-run",
|
|
772
|
+
"dna sync --codex",
|
|
773
|
+
`dna workflow suggestions ${assetName} --json`,
|
|
774
|
+
`dna workflow mine ${assetName} --json`,
|
|
775
|
+
];
|
|
776
|
+
}
|
|
777
|
+
function uniqueEvidenceRefs(refs, limit = 12) {
|
|
778
|
+
const seen = new Set();
|
|
779
|
+
const unique = [];
|
|
780
|
+
for (const ref of refs) {
|
|
781
|
+
const key = `${ref.type}:${ref.ref}`;
|
|
782
|
+
if (seen.has(key))
|
|
783
|
+
continue;
|
|
784
|
+
seen.add(key);
|
|
785
|
+
unique.push(ref);
|
|
786
|
+
if (unique.length >= limit)
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
return unique;
|
|
790
|
+
}
|
|
791
|
+
function dogfoodEvidenceRefs(modules) {
|
|
792
|
+
return uniqueEvidenceRefs(modules.flatMap((module) => [
|
|
793
|
+
{ type: "dogfood_progress", ref: module.progressJsonPath },
|
|
794
|
+
...module.evidencePaths.slice(0, 2).map((ref) => ({ type: "dogfood_evidence", ref })),
|
|
795
|
+
]));
|
|
796
|
+
}
|
|
797
|
+
function suggestionEvidenceRefs(suggestion, index) {
|
|
798
|
+
if (suggestion.evidenceRefs)
|
|
799
|
+
return suggestion.evidenceRefs;
|
|
800
|
+
if (suggestion.id === "c5-capture-empty-state") {
|
|
801
|
+
return [{ type: "local_evidence_store", ref: `${index.projectPath}/.dna/evidence/capture` }];
|
|
802
|
+
}
|
|
803
|
+
const modules = suggestion.sourceModules.length > 0
|
|
804
|
+
? index.modules.filter((module) => suggestion.sourceModules.includes(module.module))
|
|
805
|
+
: index.modules;
|
|
806
|
+
if (modules.length > 0)
|
|
807
|
+
return dogfoodEvidenceRefs(modules);
|
|
808
|
+
if (index.warnings.length > 0) {
|
|
809
|
+
return index.warnings.map((warning) => ({ type: "evidence_warning", ref: warning }));
|
|
810
|
+
}
|
|
811
|
+
return [{ type: "local_evidence_store", ref: `${index.projectPath}/.dna/evidence/capture` }];
|
|
812
|
+
}
|
|
813
|
+
function adoptionSuggestionCommands(sourceAsset, candidateAsset) {
|
|
814
|
+
return [
|
|
815
|
+
`dna assets lineage ${sourceAsset}`,
|
|
816
|
+
`dna assets evidence ${sourceAsset}`,
|
|
817
|
+
`dna assets inspect ${candidateAsset} --evidence`,
|
|
818
|
+
`dna assets governance ${candidateAsset}`,
|
|
819
|
+
`dna assets adoption ${candidateAsset}`,
|
|
820
|
+
];
|
|
821
|
+
}
|
|
822
|
+
function defaultSuggestionCommands(params) {
|
|
823
|
+
const adoptionAsset = suggestionAdoptionAsset(params.suggestion);
|
|
824
|
+
if (adoptionAsset)
|
|
825
|
+
return adoptionSuggestionCommands(params.asset.name, adoptionAsset);
|
|
826
|
+
if (params.suggestion.id === WORKFLOW_SUGGESTION_IDS.activeFollowup) {
|
|
827
|
+
const route = activeEvidenceFollowupRoute(params.asset.name);
|
|
828
|
+
return [
|
|
829
|
+
`dna assets evidence ${params.asset.name}`,
|
|
830
|
+
route.command,
|
|
831
|
+
`dna assets diff ${params.asset.name} ${route.assetName}`,
|
|
832
|
+
`dna assets adoption ${route.assetName}`,
|
|
833
|
+
];
|
|
834
|
+
}
|
|
835
|
+
if (params.suggestion.type === "template_improvement") {
|
|
836
|
+
const reviewCommand = suggestionReviewCommand({
|
|
837
|
+
asset: params.asset.name,
|
|
838
|
+
suggestionId: params.suggestion.id,
|
|
839
|
+
workflow: "fix",
|
|
840
|
+
decision: "accept",
|
|
841
|
+
});
|
|
842
|
+
return [
|
|
843
|
+
`dna assets evidence ${params.asset.name} --json`,
|
|
844
|
+
reviewCommand,
|
|
845
|
+
suggestionDraftCommand({
|
|
846
|
+
asset: params.asset.name,
|
|
847
|
+
suggestionId: params.suggestion.id,
|
|
848
|
+
saveTemplate: `${params.asset.name}-v2`,
|
|
849
|
+
workflow: "fix",
|
|
850
|
+
}),
|
|
851
|
+
`dna assets diff ${params.asset.name} ${params.asset.name}-v2`,
|
|
852
|
+
`dna assets adoption ${params.asset.name}-v2`,
|
|
853
|
+
];
|
|
854
|
+
}
|
|
855
|
+
if (params.suggestion.type === "continue_execution") {
|
|
856
|
+
return [
|
|
857
|
+
`dna assets evidence ${params.asset.name} --json`,
|
|
858
|
+
`continue governed workflow focus for ${params.suggestion.sourceModules[0] ?? params.asset.name}`,
|
|
859
|
+
];
|
|
860
|
+
}
|
|
861
|
+
if (params.suggestion.type === "governance_review") {
|
|
862
|
+
return [
|
|
863
|
+
`dna assets evidence ${params.asset.name}`,
|
|
864
|
+
`dna assets adoption ${params.asset.name}`,
|
|
865
|
+
`dna assets governance ${params.asset.name}`,
|
|
866
|
+
params.asset.evolution.previewCommand,
|
|
867
|
+
];
|
|
868
|
+
}
|
|
869
|
+
return [
|
|
870
|
+
`dna assets evidence ${params.asset.name} --json`,
|
|
871
|
+
`dna workflow suggestions ${params.asset.name} --json`,
|
|
872
|
+
];
|
|
873
|
+
}
|
|
874
|
+
function defaultMutationBoundary(suggestion) {
|
|
875
|
+
if (suggestion.mutationBoundary)
|
|
876
|
+
return suggestion.mutationBoundary;
|
|
877
|
+
if (suggestion.type === "continue_execution") {
|
|
878
|
+
return "Continue suggestions are operational guidance only; they do not edit workflow assets, mutate policy, adopt templates, bind organizations, sync runtime artifacts, upload reports, or create runtime authority.";
|
|
879
|
+
}
|
|
880
|
+
if (suggestion.type === "template_improvement") {
|
|
881
|
+
return "Template improvement suggestions are review paths only; drafts or adoption reviews require explicit follow-up commands and never auto-adopt, bind, sync, upload, or mutate runtime policy.";
|
|
882
|
+
}
|
|
883
|
+
if (suggestion.type === "governance_review") {
|
|
884
|
+
return "Governance suggestions are read-only review or preview steps; evolution marker mutation, organization binding, and runtime sync stay behind explicit human-reviewed commands.";
|
|
885
|
+
}
|
|
886
|
+
return "Evidence suggestions are read-only; evidence can inform reviewed changes but never edits workflow assets, mutates policy, adopts templates, binds organizations, syncs runtime artifacts, uploads reports, or creates runtime authority.";
|
|
887
|
+
}
|
|
888
|
+
function completeSuggestionReviewFields(params) {
|
|
889
|
+
return {
|
|
890
|
+
...params.suggestion,
|
|
891
|
+
evidenceRefs: suggestionEvidenceRefs(params.suggestion, params.index),
|
|
892
|
+
nextCommands: params.suggestion.nextCommands ?? defaultSuggestionCommands({
|
|
893
|
+
asset: params.asset,
|
|
894
|
+
suggestion: params.suggestion,
|
|
895
|
+
}),
|
|
896
|
+
mutationBoundary: defaultMutationBoundary(params.suggestion),
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
export function buildAssetSuggestions(asset, index, assets = [asset]) {
|
|
900
|
+
const suggestions = [];
|
|
901
|
+
const active = activeEvidenceModules(index);
|
|
902
|
+
const c5Gaps = c5GapCaptures(index);
|
|
903
|
+
for (const module of active.slice(0, 5)) {
|
|
904
|
+
suggestions.push({
|
|
905
|
+
id: `continue-${suggestionId(module.module)}`,
|
|
906
|
+
type: "continue_execution",
|
|
907
|
+
severity: module.verdict === "BLOCKED" || module.verdict === "REQUEST_CHANGES" ? "warn" : "info",
|
|
908
|
+
title: `Continue ${module.module} with the existing governed workflow`,
|
|
909
|
+
evidence: compactEvidenceModule(module),
|
|
910
|
+
rationale: module.nextRoundFocus ?? module.failureReason ?? "Dogfood evidence shows this module still has unresolved work.",
|
|
911
|
+
sourceModules: [module.module],
|
|
912
|
+
nextAction: `Run dna assets evidence ${asset.name} and continue the fix-loop focus for ${module.module}.`,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
if (active.length > 0) {
|
|
916
|
+
const route = activeEvidenceFollowupRoute(asset.name);
|
|
917
|
+
suggestions.push({
|
|
918
|
+
id: "template-active-followup",
|
|
919
|
+
type: "template_improvement",
|
|
920
|
+
severity: "warn",
|
|
921
|
+
title: "Draft a reviewed active-evidence follow-up workflow",
|
|
922
|
+
evidence: active.slice(0, 5).map(compactEvidenceModule).join("; "),
|
|
923
|
+
rationale: "Open dogfood evidence should become an explicit follow-up workflow asset before baseline promotion, organization binding, rollout, runtime sync, or marker mutation.",
|
|
924
|
+
sourceModules: active.map((module) => module.module),
|
|
925
|
+
nextAction: `Draft reviewed follow-up: ${route.command}`,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
if (c5Gaps.length > 0) {
|
|
929
|
+
const evidenceCommand = `dna assets evidence ${asset.name} --json`;
|
|
930
|
+
const suggestionsCommand = `dna workflow suggestions ${asset.name} --json`;
|
|
931
|
+
const reviewCommand = suggestionReviewCommand({
|
|
932
|
+
asset: asset.name,
|
|
933
|
+
suggestionId: WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts,
|
|
934
|
+
workflow: "fix",
|
|
935
|
+
decision: "accept",
|
|
936
|
+
});
|
|
937
|
+
const draftCommand = suggestionDraftCommand({
|
|
938
|
+
asset: asset.name,
|
|
939
|
+
suggestionId: WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts,
|
|
940
|
+
saveTemplate: `${asset.name}-v2`,
|
|
941
|
+
workflow: "fix",
|
|
942
|
+
});
|
|
943
|
+
suggestions.push({
|
|
944
|
+
id: WORKFLOW_SUGGESTION_IDS.c5CapturedRunFacts,
|
|
945
|
+
type: "evidence_improvement",
|
|
946
|
+
severity: "warn",
|
|
947
|
+
title: "Review captured local run facts before changing the workflow asset",
|
|
948
|
+
evidence: c5Gaps.slice(0, 5).map(compactEvidenceCapture).join("; "),
|
|
949
|
+
evidenceRefs: c5EvidenceRefs(c5Gaps),
|
|
950
|
+
rationale: "C5 hook evidence shows blocked, failed, retry, unsupported, or human-intervention facts. Evidence can suggest a reviewed change, but it must not mutate policy or templates automatically.",
|
|
951
|
+
sourceModules: [],
|
|
952
|
+
nextAction: `Review and accept the C5-backed suggestion before drafting: ${reviewCommand}`,
|
|
953
|
+
nextCommands: [
|
|
954
|
+
evidenceCommand,
|
|
955
|
+
suggestionsCommand,
|
|
956
|
+
reviewCommand,
|
|
957
|
+
draftCommand,
|
|
958
|
+
`record explicit manager rationale for any C5-derived change to ${asset.name}`,
|
|
959
|
+
],
|
|
960
|
+
mutationBoundary: "C5 captured facts are local review evidence only; they do not edit templates, mutate policy, adopt assets, bind organizations, sync runtime artifacts, upload cloud reports, or create runtime authority.",
|
|
961
|
+
});
|
|
962
|
+
}
|
|
963
|
+
if (index.totals.captureFacts === 0) {
|
|
964
|
+
const commands = c5CaptureBootstrapCommands(asset.name);
|
|
965
|
+
suggestions.push({
|
|
966
|
+
id: "c5-capture-empty-state",
|
|
967
|
+
type: "evidence_improvement",
|
|
968
|
+
severity: "info",
|
|
969
|
+
title: "Run the workflow locally to create C5 capture facts",
|
|
970
|
+
evidence: "No local C5 capture facts were found under .dna/evidence/capture for this asset.",
|
|
971
|
+
rationale: "P3 needs local run facts before the IMPROVE loop can suggest changes from actual usage. Start with local init/sync, run the workflow in the harness, then return to suggestions and mining for review-only output.",
|
|
972
|
+
sourceModules: [],
|
|
973
|
+
nextAction: `Create local capture evidence, then rerun dna workflow suggestions ${asset.name} --json.`,
|
|
974
|
+
nextCommands: commands,
|
|
975
|
+
mutationBoundary: "This suggestion is guidance only. init/sync may write local runtime artifacts, but evidence still creates suggestions only and never edits workflow assets, mutates policy, adopts templates, binds organizations, uploads reports, or creates runtime authority.",
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
const modelFocused = active.filter((module) => /model|behavior lock|placeholder|assert/i.test(`${module.nextRoundFocus ?? ""} ${module.failureReason ?? ""}`));
|
|
979
|
+
if (modelFocused.length > 0) {
|
|
980
|
+
suggestions.push({
|
|
981
|
+
id: WORKFLOW_SUGGESTION_IDS.modelVerifier,
|
|
982
|
+
type: "template_improvement",
|
|
983
|
+
severity: "info",
|
|
984
|
+
title: "Consider a reviewed verifier step for model behavior locks",
|
|
985
|
+
evidence: modelFocused.map(compactEvidenceModule).join("; "),
|
|
986
|
+
rationale: "Dogfood evidence points to remaining behavior-lock/model assertion work. This is a candidate workflow/template improvement, not an automatic mutation.",
|
|
987
|
+
sourceModules: modelFocused.map((module) => module.module),
|
|
988
|
+
nextAction: `Review and accept the model-verifier suggestion before drafting: ${suggestionReviewCommand({ asset: asset.name, workflow: "fix", suggestionId: WORKFLOW_SUGGESTION_IDS.modelVerifier, decision: "accept" })}`,
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
const crossBoundaryFocused = index.modules.filter((module) => /i18n|localization|translation|shared|cross[- ]boundary|outside[_ ]diagnosis|outside[_ ]scope/i.test(`${module.module} ${module.nextRoundFocus ?? ""} ${module.failureReason ?? ""}`));
|
|
992
|
+
if (crossBoundaryFocused.length > 0) {
|
|
993
|
+
suggestions.push({
|
|
994
|
+
id: WORKFLOW_SUGGESTION_IDS.crossBoundaryResourceApproval,
|
|
995
|
+
type: "template_improvement",
|
|
996
|
+
severity: "info",
|
|
997
|
+
title: "Adopt the reusable cross-boundary resource approval workflow asset",
|
|
998
|
+
evidence: crossBoundaryFocused.slice(0, 5).map(compactEvidenceModule).join("; "),
|
|
999
|
+
rationale: "Dogfood evidence includes shared-resource, localization, or outside-diagnosis work that should be routed through explicit approval before implementation, rollout, sync, or completion claims.",
|
|
1000
|
+
sourceModules: crossBoundaryFocused.map((module) => module.module),
|
|
1001
|
+
nextAction: `Review and adopt: dna assets adoption ${WORKFLOW_ASSET_IDS.crossBoundaryResourceApproval}`,
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
const historicalBlocked = index.modules.filter((module) => module.blockedCount > 0);
|
|
1005
|
+
if (historicalBlocked.length > 0) {
|
|
1006
|
+
suggestions.push({
|
|
1007
|
+
id: WORKFLOW_SUGGESTION_IDS.blockedTriage,
|
|
1008
|
+
type: "template_improvement",
|
|
1009
|
+
severity: "info",
|
|
1010
|
+
title: "Adopt the reusable blocked-scope triage workflow asset",
|
|
1011
|
+
evidence: historicalBlocked.slice(0, 5).map(compactEvidenceModule).join("; "),
|
|
1012
|
+
rationale: `Some dogfood modules recorded blocked items. This pattern has been extracted as ${WORKFLOW_ASSET_IDS.blockedScopeTriage} so teams can classify blocked, skipped, removed, infra, and deferred work before manager review or rollout.`,
|
|
1013
|
+
sourceModules: historicalBlocked.map((module) => module.module),
|
|
1014
|
+
nextAction: `Review and adopt: dna assets adoption ${WORKFLOW_ASSET_IDS.blockedScopeTriage}`,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
if (index.warnings.length > 0) {
|
|
1018
|
+
suggestions.push({
|
|
1019
|
+
id: "evidence-source-repair",
|
|
1020
|
+
type: "evidence_improvement",
|
|
1021
|
+
severity: "warn",
|
|
1022
|
+
title: "Repair or review dogfood evidence source registration",
|
|
1023
|
+
evidence: index.warnings.join("; "),
|
|
1024
|
+
rationale: "Evolution suggestions are only useful when evidence sources are readable and canonical.",
|
|
1025
|
+
sourceModules: [],
|
|
1026
|
+
nextAction: `Run dna assets evidence ${asset.name} and fix the registered dogfood source before using suggestions.`,
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
if (suggestions.length === 0 && index.totals.modules > 0) {
|
|
1030
|
+
suggestions.push({
|
|
1031
|
+
id: "capture-stable-pattern",
|
|
1032
|
+
type: "governance_review",
|
|
1033
|
+
severity: "info",
|
|
1034
|
+
title: "Capture the stable dogfood pattern as reviewed asset knowledge",
|
|
1035
|
+
evidence: `${index.totals.pass}/${index.totals.modules} PASS, remaining=${index.totals.remaining}`,
|
|
1036
|
+
rationale: "Dogfood evidence is stable enough to preserve the current behavior-lock, diagnosis, fix, and review structure as the baseline for future workflow versions.",
|
|
1037
|
+
sourceModules: index.modules.map((module) => module.module),
|
|
1038
|
+
nextAction: `Run dna assets adoption ${asset.name} before binding or syncing this asset in a project.`,
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
if (index.totals.remaining > 0) {
|
|
1042
|
+
suggestions.push({
|
|
1043
|
+
id: "do-not-auto-evolve",
|
|
1044
|
+
type: "governance_review",
|
|
1045
|
+
severity: "warn",
|
|
1046
|
+
title: "Do not apply evolution automatically while evidence remains active",
|
|
1047
|
+
evidence: `${index.totals.remaining} remaining item(s), continue=${index.totals.continue}, blocked=${index.totals.blocked + index.totals.requestChanges}`,
|
|
1048
|
+
rationale: "Remaining dogfood work should become reviewed workflow changes or next-run focus, not automatic policy mutation.",
|
|
1049
|
+
sourceModules: active.map((module) => module.module),
|
|
1050
|
+
nextAction: `Preview only: ${asset.evolution.previewCommand}. Use ${asset.evolution.mutatingCommand} only after human review.`,
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
const completedSuggestions = suggestions.map((suggestion) => completeSuggestionReviewFields({
|
|
1054
|
+
asset,
|
|
1055
|
+
index,
|
|
1056
|
+
suggestion,
|
|
1057
|
+
}));
|
|
1058
|
+
const activeFollowup = active.length > 0 ? activeEvidenceFollowupReviewRoute(asset.name, assets, index) : undefined;
|
|
1059
|
+
const draftSuggestion = firstDraftableSuggestion(completedSuggestions);
|
|
1060
|
+
const nextCommands = [
|
|
1061
|
+
`dna assets evidence ${asset.name}`,
|
|
1062
|
+
];
|
|
1063
|
+
if (activeFollowup) {
|
|
1064
|
+
nextCommands.push(activeFollowup.currentCommand);
|
|
1065
|
+
nextCommands.push(activeFollowup.diffCommand);
|
|
1066
|
+
nextCommands.push(activeFollowup.adoptionCommand);
|
|
1067
|
+
}
|
|
1068
|
+
else if (draftSuggestion) {
|
|
1069
|
+
const action = suggestionAction({
|
|
1070
|
+
asset: asset.name,
|
|
1071
|
+
suggestion: draftSuggestion,
|
|
1072
|
+
saveTemplate: `${asset.name}-v2`,
|
|
1073
|
+
});
|
|
1074
|
+
if (action.kind === "draft" && draftSuggestion.id !== WORKFLOW_SUGGESTION_IDS.activeFollowup) {
|
|
1075
|
+
nextCommands.push(suggestionReviewCommand({
|
|
1076
|
+
asset: asset.name,
|
|
1077
|
+
suggestionId: draftSuggestion.id,
|
|
1078
|
+
workflow: "fix",
|
|
1079
|
+
decision: "accept",
|
|
1080
|
+
}));
|
|
1081
|
+
}
|
|
1082
|
+
nextCommands.push(action.command);
|
|
1083
|
+
if (action.kind === "adoption" && action.assetName) {
|
|
1084
|
+
nextCommands.push(`dna assets governance ${action.assetName}`);
|
|
1085
|
+
nextCommands.push(`dna assets lineage ${action.assetName}`);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
nextCommands.push(`dna assets adoption ${asset.name}`);
|
|
1089
|
+
nextCommands.push(`dna assets governance ${asset.name}`);
|
|
1090
|
+
if (index.totals.captureFacts === 0) {
|
|
1091
|
+
nextCommands.push(...c5CaptureBootstrapCommands(asset.name));
|
|
1092
|
+
}
|
|
1093
|
+
const improvementRoute = buildSuggestionImprovementRoute({ asset, activeFollowup, draftSuggestion });
|
|
1094
|
+
return {
|
|
1095
|
+
asset: asset.name,
|
|
1096
|
+
displayName: asset.displayName,
|
|
1097
|
+
mode: "suggestions_only",
|
|
1098
|
+
evidence: {
|
|
1099
|
+
source: index.sourceDescription,
|
|
1100
|
+
modules: index.totals.modules,
|
|
1101
|
+
pass: index.totals.pass,
|
|
1102
|
+
continue: index.totals.continue,
|
|
1103
|
+
blocked: index.totals.blocked + index.totals.requestChanges,
|
|
1104
|
+
remaining: index.totals.remaining,
|
|
1105
|
+
captureFacts: index.totals.captureFacts,
|
|
1106
|
+
captureBlocked: index.totals.captureBlocked,
|
|
1107
|
+
captureFailures: index.totals.captureFailures,
|
|
1108
|
+
captureManualInterventions: index.totals.captureManualInterventions,
|
|
1109
|
+
captureRetries: index.totals.captureRetries,
|
|
1110
|
+
warnings: index.warnings,
|
|
1111
|
+
},
|
|
1112
|
+
suggestions: completedSuggestions,
|
|
1113
|
+
improvementRoute,
|
|
1114
|
+
nextCommands: [...new Set(nextCommands)],
|
|
1115
|
+
mutation: {
|
|
1116
|
+
writes: false,
|
|
1117
|
+
note: "Suggestions are read-only. They do not edit templates, org profiles, evolution markers, or runtime sync artifacts.",
|
|
1118
|
+
},
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function lifecycleEvidence(asset, evidence) {
|
|
1122
|
+
if (!evidence) {
|
|
1123
|
+
return {
|
|
1124
|
+
status: "unknown",
|
|
1125
|
+
modules: 0,
|
|
1126
|
+
pass: 0,
|
|
1127
|
+
blocked: 0,
|
|
1128
|
+
remaining: 0,
|
|
1129
|
+
nextCommand: `dna assets evidence ${asset.name}`,
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
const open = evidence.totals.continue > 0 || evidence.totals.blocked > 0 || evidence.totals.requestChanges > 0 || evidence.totals.remaining > 0;
|
|
1133
|
+
return {
|
|
1134
|
+
status: open ? "open" : "clear",
|
|
1135
|
+
source: evidence.sourceDescription,
|
|
1136
|
+
modules: evidence.totals.modules,
|
|
1137
|
+
pass: evidence.totals.pass,
|
|
1138
|
+
blocked: evidence.totals.blocked + evidence.totals.requestChanges,
|
|
1139
|
+
remaining: evidence.totals.remaining,
|
|
1140
|
+
nextCommand: open ? `dna assets suggestions ${asset.name}` : asset.evolution.previewCommand,
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
function buildManagerGates(params) {
|
|
1144
|
+
const { asset, evidenceStatus, evidenceNextCommand, evidenceAllowsMutation } = params;
|
|
1145
|
+
const evidenceCommand = evidenceNextCommand ?? `dna assets evidence ${asset.name}`;
|
|
1146
|
+
const syncAllowed = asset.status.configured && evidenceAllowsMutation;
|
|
1147
|
+
const syncDecision = !evidenceAllowsMutation
|
|
1148
|
+
? "blocked_by_evidence"
|
|
1149
|
+
: !asset.status.configured
|
|
1150
|
+
? "configure_asset"
|
|
1151
|
+
: "ready_to_sync";
|
|
1152
|
+
const syncNextCommand = !evidenceAllowsMutation
|
|
1153
|
+
? evidenceCommand
|
|
1154
|
+
: !asset.status.configured
|
|
1155
|
+
? `dna init --template ${asset.name}`
|
|
1156
|
+
: "dna sync --codex";
|
|
1157
|
+
return [
|
|
1158
|
+
{
|
|
1159
|
+
id: "cascade",
|
|
1160
|
+
label: "Cascade compile gate",
|
|
1161
|
+
status: asset.status.hasCascade ? "pass" : "blocked",
|
|
1162
|
+
owner: "dna_core",
|
|
1163
|
+
decision: asset.status.hasCascade ? "cascade_ready" : "declare_cascade",
|
|
1164
|
+
reason: asset.status.hasCascade
|
|
1165
|
+
? `Compiler can merge inherited layers (${asset.cascade.inherits.length > 0 ? asset.cascade.inherits.join(", ") : "none"}) before runtime artifacts are generated.`
|
|
1166
|
+
: "No cascade inheritance is declared, so this asset should stay local until its inherited layer is explicit.",
|
|
1167
|
+
nextCommand: asset.status.hasCascade ? `dna assets governance ${asset.name}` : `dna assets inspect ${asset.name} --evidence`,
|
|
1168
|
+
writes: false,
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
id: "evidence",
|
|
1172
|
+
label: "Evidence gate",
|
|
1173
|
+
status: evidenceStatus === "clear" ? "pass" : evidenceStatus === "open" ? "blocked" : "review",
|
|
1174
|
+
owner: "dna_core",
|
|
1175
|
+
decision: evidenceStatus === "clear" ? "evidence_clear" : evidenceStatus === "open" ? "review_evidence_first" : "collect_evidence",
|
|
1176
|
+
reason: evidenceStatus === "clear"
|
|
1177
|
+
? "Dogfood evidence has no active remaining work; evolution can be previewed before explicit mutation."
|
|
1178
|
+
: evidenceStatus === "open"
|
|
1179
|
+
? "Dogfood evidence is still active, so suggestions and reviewed drafts must come before marker mutation or runtime sync."
|
|
1180
|
+
: "No dogfood evidence index is available; keep this asset preview-only until evidence is reviewed.",
|
|
1181
|
+
nextCommand: evidenceCommand,
|
|
1182
|
+
writes: false,
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
id: "evolution",
|
|
1186
|
+
label: "Evolution gate",
|
|
1187
|
+
status: asset.evolution.reviewMode === "preview_first" ? "pass" : "review",
|
|
1188
|
+
owner: "dna_core",
|
|
1189
|
+
decision: asset.evolution.reviewMode === "preview_first" ? "preview_first" : "review_evolution_policy",
|
|
1190
|
+
reason: asset.evolution.reviewMode === "preview_first"
|
|
1191
|
+
? "Evolution can suggest marker changes, but mutation stays behind explicit human-reviewed sync."
|
|
1192
|
+
: "Evolution policy is not preview-first, so manager review is required before it can become an org asset.",
|
|
1193
|
+
nextCommand: asset.evolution.previewCommand,
|
|
1194
|
+
writes: false,
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
id: "sync",
|
|
1198
|
+
label: "Runtime sync gate",
|
|
1199
|
+
status: syncAllowed ? "pass" : "blocked",
|
|
1200
|
+
owner: "runtime",
|
|
1201
|
+
decision: syncDecision,
|
|
1202
|
+
reason: syncAllowed
|
|
1203
|
+
? "Project configuration and evidence gates allow runtime artifact sync."
|
|
1204
|
+
: !evidenceAllowsMutation
|
|
1205
|
+
? "Runtime sync is blocked until active evidence is resolved or converted into a reviewed draft."
|
|
1206
|
+
: "Runtime sync is blocked until the asset is configured in the project.",
|
|
1207
|
+
nextCommand: syncNextCommand,
|
|
1208
|
+
writes: syncAllowed,
|
|
1209
|
+
},
|
|
1210
|
+
{
|
|
1211
|
+
id: "deploy",
|
|
1212
|
+
label: "Public deploy gate",
|
|
1213
|
+
status: "conditional",
|
|
1214
|
+
owner: "release",
|
|
1215
|
+
decision: "only_for_web_public_changes",
|
|
1216
|
+
reason: "Run Vercel CLI deployment and deployed URL verification only when changed files affect web, dashboard, blog, or other public deployed surfaces.",
|
|
1217
|
+
nextCommand: "dna release deploy-plan",
|
|
1218
|
+
writes: false,
|
|
1219
|
+
},
|
|
1220
|
+
];
|
|
1221
|
+
}
|
|
1222
|
+
function buildAssetLifecyclePath(params) {
|
|
1223
|
+
const { asset, evidenceStatus, targetVersion, suggestionCommand, draftCommand, diffCommand, adoptionCommand, orgBindCommand, configureCommand, syncCommand, followupReviewable, evidenceActionWrites, evolutionPreviewCommand, releaseCommand, } = params;
|
|
1224
|
+
const evidenceOpen = evidenceStatus === "open";
|
|
1225
|
+
const evidenceClear = evidenceStatus === "clear";
|
|
1226
|
+
const evidenceKnown = evidenceOpen || evidenceClear;
|
|
1227
|
+
const canReviewVersion = evidenceClear || followupReviewable;
|
|
1228
|
+
const canBind = evidenceClear || followupReviewable;
|
|
1229
|
+
const canSync = evidenceClear && asset.status.configured;
|
|
1230
|
+
return [
|
|
1231
|
+
{
|
|
1232
|
+
id: "select_asset",
|
|
1233
|
+
label: "Select workflow asset",
|
|
1234
|
+
command: "dna assets library",
|
|
1235
|
+
purpose: "Choose the reusable workflow asset before creating a new workflow from scratch.",
|
|
1236
|
+
owner: "user",
|
|
1237
|
+
writes: false,
|
|
1238
|
+
status: "done",
|
|
1239
|
+
sourceIds: ["systemRoute.select"],
|
|
1240
|
+
next: `Review governance for ${asset.name}.`,
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
id: "review_governance",
|
|
1244
|
+
label: "Review governance",
|
|
1245
|
+
command: `dna assets governance ${asset.name}`,
|
|
1246
|
+
purpose: "Check cascade, evidence posture, evolution boundary, and runtime readiness before mutation.",
|
|
1247
|
+
owner: "dna_core",
|
|
1248
|
+
writes: false,
|
|
1249
|
+
status: asset.status.hasCascade ? "done" : "current",
|
|
1250
|
+
sourceIds: ["managerGates.cascade", "systemRoute.govern"],
|
|
1251
|
+
next: evidenceKnown ? "Handle dogfood evidence." : "Collect evidence before adoption or sync.",
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
id: "handle_evidence",
|
|
1255
|
+
label: "Handle evidence",
|
|
1256
|
+
command: evidenceOpen ? draftCommand : evidenceClear ? suggestionCommand : `dna assets evidence ${asset.name}`,
|
|
1257
|
+
purpose: evidenceOpen
|
|
1258
|
+
? "Save active dogfood evidence as a reviewed unfinished-work draft before promotion, rollout, or sync."
|
|
1259
|
+
: evidenceClear
|
|
1260
|
+
? "Evidence is clear; suggestions remain available as a read-only improvement source."
|
|
1261
|
+
: "Collect canonical dogfood evidence before shaping a managed asset version.",
|
|
1262
|
+
owner: "dna_core",
|
|
1263
|
+
writes: evidenceOpen ? evidenceActionWrites : false,
|
|
1264
|
+
status: evidenceOpen || !evidenceKnown ? "current" : "done",
|
|
1265
|
+
sourceIds: ["managerGates.evidence", "evidence", "stages.evidence", "stages.suggestions", "stages.draft"],
|
|
1266
|
+
next: evidenceOpen ? "Review the saved unfinished-work draft before manager adoption." : "Shape or review the target asset version.",
|
|
1267
|
+
},
|
|
1268
|
+
{
|
|
1269
|
+
id: "shape_version",
|
|
1270
|
+
label: "Shape version",
|
|
1271
|
+
command: diffCommand,
|
|
1272
|
+
purpose: `Make ${targetVersion} reviewable by comparing workflow and governance changes against ${asset.name}.`,
|
|
1273
|
+
owner: "dna_core",
|
|
1274
|
+
writes: false,
|
|
1275
|
+
status: canReviewVersion ? "available" : evidenceOpen ? "later" : "blocked",
|
|
1276
|
+
sourceIds: ["systemRoute.improve", "stages.diff"],
|
|
1277
|
+
next: canReviewVersion ? "Send the reviewed version to manager review." : "Resolve evidence before version review.",
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
id: "manager_review",
|
|
1281
|
+
label: "Manager review",
|
|
1282
|
+
command: adoptionCommand,
|
|
1283
|
+
purpose: "Review adoption readiness before organization binding, rollout, runtime sync, or release.",
|
|
1284
|
+
owner: "manager",
|
|
1285
|
+
writes: false,
|
|
1286
|
+
status: canReviewVersion ? "available" : "blocked",
|
|
1287
|
+
sourceIds: ["systemRoute.review", "stages.adoption"],
|
|
1288
|
+
next: canReviewVersion ? "Accept or reject the reviewed asset version." : "Wait for a reviewable asset version.",
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
id: "adopt_and_bind",
|
|
1292
|
+
label: "Adopt and bind",
|
|
1293
|
+
command: orgBindCommand,
|
|
1294
|
+
purpose: "Record the manager-approved asset version in the organization profile before downstream distribution.",
|
|
1295
|
+
owner: "manager",
|
|
1296
|
+
writes: canBind,
|
|
1297
|
+
status: canBind ? "available" : "blocked",
|
|
1298
|
+
sourceIds: ["systemRoute.bind", "stages.org-bind"],
|
|
1299
|
+
next: canBind ? `Configure ${targetVersion} for the project.` : "Manager review must pass before organization binding.",
|
|
1300
|
+
},
|
|
1301
|
+
{
|
|
1302
|
+
id: "rollout_and_sync",
|
|
1303
|
+
label: "Roll out and sync",
|
|
1304
|
+
command: canSync ? syncCommand : configureCommand,
|
|
1305
|
+
purpose: "Configure the accepted asset and sync runtime artifacts only after review and organization binding.",
|
|
1306
|
+
owner: "runtime",
|
|
1307
|
+
writes: canBind,
|
|
1308
|
+
status: canSync ? "available" : canBind ? "current" : "blocked",
|
|
1309
|
+
sourceIds: ["managerGates.sync", "systemRoute.rollout", "systemRoute.sync", "stages.configure", "stages.sync"],
|
|
1310
|
+
next: canSync ? "Run runtime sync, then evaluate release/deploy gate if public surfaces changed." : "Configure the accepted asset before runtime sync.",
|
|
1311
|
+
},
|
|
1312
|
+
{
|
|
1313
|
+
id: "evolve_or_release",
|
|
1314
|
+
label: "Evolve or release",
|
|
1315
|
+
command: evidenceClear ? evolutionPreviewCommand : releaseCommand,
|
|
1316
|
+
purpose: "Preview evolution changes and run the release deploy-plan gate; Vercel CLI deploy runs only when deploy-plan requires a deployed URL.",
|
|
1317
|
+
owner: "release",
|
|
1318
|
+
writes: false,
|
|
1319
|
+
status: evidenceClear ? "conditional" : "blocked",
|
|
1320
|
+
sourceIds: ["managerGates.evolution", "managerGates.deploy", "systemRoute.deploy", "evolution"],
|
|
1321
|
+
next: evidenceClear ? `${releaseCommand} <changed-files>; deploy with Vercel CLI only if the gate requires it.` : "Resolve evidence and adoption gates before evolution or release.",
|
|
1322
|
+
},
|
|
1323
|
+
];
|
|
1324
|
+
}
|
|
1325
|
+
function buildAssetLifecycle(asset, evidence, assets = [asset]) {
|
|
1326
|
+
const evidenceGate = lifecycleEvidence(asset, evidence);
|
|
1327
|
+
const evidenceOpen = evidenceGate.status === "open";
|
|
1328
|
+
const activeFollowup = evidenceOpen ? activeEvidenceFollowupReviewRoute(asset.name, assets, evidence) : undefined;
|
|
1329
|
+
const nextVersion = `${asset.name}-v2`;
|
|
1330
|
+
const targetVersion = activeFollowup?.candidateAssetName ?? nextVersion;
|
|
1331
|
+
const intakeCommand = `dna workflow intake "Describe the working method you want to govern"`;
|
|
1332
|
+
const suggestionCommand = `dna assets suggestions ${asset.name}`;
|
|
1333
|
+
const fallbackEditCommand = `dna workflow edit-plan "Describe the reviewed workflow change" --from-template ${asset.name} --save-template ${nextVersion}`;
|
|
1334
|
+
const suggestion = evidence && asset.status.evidencePosture === "dogfood_linked"
|
|
1335
|
+
? firstDraftableSuggestion(buildAssetSuggestions(asset, evidence).suggestions)
|
|
1336
|
+
: undefined;
|
|
1337
|
+
const improvementAction = suggestion
|
|
1338
|
+
? suggestionAction({ asset: asset.name, suggestion, saveTemplate: nextVersion })
|
|
1339
|
+
: { kind: "draft", command: fallbackEditCommand };
|
|
1340
|
+
const draftCommand = activeFollowup?.currentCommand ?? improvementAction.command;
|
|
1341
|
+
const diffCommand = `dna assets diff ${asset.name} ${targetVersion}`;
|
|
1342
|
+
const adoptionCommand = `dna assets adoption ${targetVersion}`;
|
|
1343
|
+
const orgBindCommand = `dna org bind --asset ${targetVersion} --status active --sync-target codex`;
|
|
1344
|
+
const configureCommand = `dna init --template ${targetVersion}`;
|
|
1345
|
+
const syncCommand = "dna sync --codex";
|
|
1346
|
+
const followupReviewable = activeFollowup?.status === "draft_reviewable";
|
|
1347
|
+
const evidenceActionWrites = activeFollowup
|
|
1348
|
+
? activeFollowup.currentWrites
|
|
1349
|
+
: evidenceOpen
|
|
1350
|
+
? improvementAction.kind === "draft"
|
|
1351
|
+
: false;
|
|
1352
|
+
const stages = [
|
|
1353
|
+
{
|
|
1354
|
+
id: "cascade",
|
|
1355
|
+
label: "Compile cascade",
|
|
1356
|
+
command: `dna assets governance ${asset.name}`,
|
|
1357
|
+
purpose: "Confirm inherited DNA layers and compiler effect before runtime artifacts are generated.",
|
|
1358
|
+
writes: false,
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
id: "evidence",
|
|
1362
|
+
label: "Read dogfood evidence",
|
|
1363
|
+
command: `dna assets evidence ${asset.name}`,
|
|
1364
|
+
purpose: "Inspect canonical progress evidence from dogfood instead of relying on chat memory.",
|
|
1365
|
+
writes: false,
|
|
1366
|
+
},
|
|
1367
|
+
{
|
|
1368
|
+
id: "suggestions",
|
|
1369
|
+
label: "Create improvement candidates",
|
|
1370
|
+
command: suggestionCommand,
|
|
1371
|
+
purpose: "Turn dogfood evidence into human-reviewed suggestions without editing templates or markers.",
|
|
1372
|
+
writes: false,
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
id: "draft",
|
|
1376
|
+
label: activeFollowup
|
|
1377
|
+
? activeFollowup.status === "draft_missing" ? "Save unfinished work" : "Review unfinished-work draft"
|
|
1378
|
+
: improvementAction.kind === "adoption" ? "Review extracted workflow asset" : "Draft next workflow asset",
|
|
1379
|
+
command: draftCommand,
|
|
1380
|
+
purpose: activeFollowup
|
|
1381
|
+
? activeFollowup.status === "draft_missing"
|
|
1382
|
+
? "Save active dogfood evidence as a reviewable unfinished-work draft before baseline promotion, marker mutation, rollout, or sync."
|
|
1383
|
+
: "Continue through the existing unfinished-work draft review route before baseline promotion, marker mutation, rollout, or sync."
|
|
1384
|
+
: improvementAction.kind === "adoption"
|
|
1385
|
+
? "Review and adopt the extracted reusable workflow asset instead of drafting a duplicate project template."
|
|
1386
|
+
: "Materialize an accepted suggestion as a reviewable project template version.",
|
|
1387
|
+
writes: activeFollowup ? activeFollowup.currentWrites : improvementAction.kind === "draft",
|
|
1388
|
+
},
|
|
1389
|
+
{
|
|
1390
|
+
id: "diff",
|
|
1391
|
+
label: "Review version diff",
|
|
1392
|
+
command: diffCommand,
|
|
1393
|
+
purpose: "Compare workflow and governance changes before adoption.",
|
|
1394
|
+
writes: false,
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
id: "adoption",
|
|
1398
|
+
label: "Review adoption readiness",
|
|
1399
|
+
command: adoptionCommand,
|
|
1400
|
+
purpose: "Check workflow, controller, cascade, evolution, evidence, and project configuration readiness.",
|
|
1401
|
+
writes: false,
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
id: "org-bind",
|
|
1405
|
+
label: "Bind accepted version",
|
|
1406
|
+
command: orgBindCommand,
|
|
1407
|
+
purpose: "Record manager adoption in the organization profile before runtime sync.",
|
|
1408
|
+
writes: true,
|
|
1409
|
+
},
|
|
1410
|
+
{
|
|
1411
|
+
id: "configure",
|
|
1412
|
+
label: "Configure project asset",
|
|
1413
|
+
command: configureCommand,
|
|
1414
|
+
purpose: "Generate project DNA config for the reviewed asset version.",
|
|
1415
|
+
writes: true,
|
|
1416
|
+
},
|
|
1417
|
+
{
|
|
1418
|
+
id: "sync",
|
|
1419
|
+
label: "Sync runtime surface",
|
|
1420
|
+
command: syncCommand,
|
|
1421
|
+
purpose: "Install runtime artifacts only after review, adoption, binding, and configuration.",
|
|
1422
|
+
writes: true,
|
|
1423
|
+
},
|
|
1424
|
+
];
|
|
1425
|
+
const currentGate = evidenceGate.status === "open"
|
|
1426
|
+
? {
|
|
1427
|
+
decision: "review_evidence",
|
|
1428
|
+
reason: followupReviewable
|
|
1429
|
+
? "Dogfood evidence still has active work; review the existing unfinished-work draft before baseline promotion, marker mutation, rollout, or sync."
|
|
1430
|
+
: "Dogfood evidence still has active work; save a reviewed unfinished-work draft before baseline promotion, marker mutation, rollout, or sync.",
|
|
1431
|
+
nextCommand: draftCommand,
|
|
1432
|
+
}
|
|
1433
|
+
: !asset.status.configured
|
|
1434
|
+
? {
|
|
1435
|
+
decision: "configure_asset",
|
|
1436
|
+
reason: "Evidence is clear, but this asset is not configured in the current project yet.",
|
|
1437
|
+
nextCommand: `dna init --template ${asset.name}`,
|
|
1438
|
+
}
|
|
1439
|
+
: {
|
|
1440
|
+
decision: "preview_evolution",
|
|
1441
|
+
reason: "Evidence is clear and the asset is configured; preview evolution before any explicit mutation.",
|
|
1442
|
+
nextCommand: asset.evolution.previewCommand,
|
|
1443
|
+
};
|
|
1444
|
+
const routedEvidenceGate = activeFollowup
|
|
1445
|
+
? { ...evidenceGate, nextCommand: activeFollowup.currentCommand }
|
|
1446
|
+
: evidenceGate;
|
|
1447
|
+
return {
|
|
1448
|
+
asset: asset.name,
|
|
1449
|
+
displayName: asset.displayName,
|
|
1450
|
+
mode: "workflow_asset_lifecycle",
|
|
1451
|
+
summary: "Cascade defines what gets compiled, evidence defines what should improve, and evolution stays preview-first until a reviewed asset version is adopted and synced.",
|
|
1452
|
+
managerGates: buildManagerGates({
|
|
1453
|
+
asset,
|
|
1454
|
+
evidenceStatus: evidenceGate.status,
|
|
1455
|
+
evidenceNextCommand: routedEvidenceGate.nextCommand,
|
|
1456
|
+
evidenceAllowsMutation: evidenceGate.status === "clear",
|
|
1457
|
+
}),
|
|
1458
|
+
assetPath: buildAssetLifecyclePath({
|
|
1459
|
+
asset,
|
|
1460
|
+
evidenceStatus: evidenceGate.status,
|
|
1461
|
+
targetVersion,
|
|
1462
|
+
suggestionCommand,
|
|
1463
|
+
draftCommand,
|
|
1464
|
+
diffCommand,
|
|
1465
|
+
adoptionCommand,
|
|
1466
|
+
orgBindCommand,
|
|
1467
|
+
configureCommand,
|
|
1468
|
+
syncCommand,
|
|
1469
|
+
followupReviewable,
|
|
1470
|
+
evidenceActionWrites,
|
|
1471
|
+
evolutionPreviewCommand: asset.evolution.previewCommand,
|
|
1472
|
+
releaseCommand: "dna release deploy-plan",
|
|
1473
|
+
}),
|
|
1474
|
+
systemRoute: {
|
|
1475
|
+
principle: "Users describe work in natural language; dna core turns it into reviewable workflow assets, manager gates, and explicit runtime sync.",
|
|
1476
|
+
phases: [
|
|
1477
|
+
{
|
|
1478
|
+
id: "describe",
|
|
1479
|
+
label: "Describe workflow",
|
|
1480
|
+
command: intakeCommand,
|
|
1481
|
+
purpose: "Start from the user's working method, not from a canvas or harness-specific setup.",
|
|
1482
|
+
writes: false,
|
|
1483
|
+
owner: "user",
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
id: "select",
|
|
1487
|
+
label: "Select or draft asset",
|
|
1488
|
+
command: "dna assets library",
|
|
1489
|
+
purpose: "Reuse a proven asset when possible; draft only when no managed asset fits.",
|
|
1490
|
+
writes: false,
|
|
1491
|
+
owner: "dna_core",
|
|
1492
|
+
},
|
|
1493
|
+
{
|
|
1494
|
+
id: "govern",
|
|
1495
|
+
label: "Inspect governance",
|
|
1496
|
+
command: `dna assets governance ${asset.name}`,
|
|
1497
|
+
purpose: "Make cascade, evidence gate, and preview-first evolution visible before adoption.",
|
|
1498
|
+
writes: false,
|
|
1499
|
+
owner: "dna_core",
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
id: "improve",
|
|
1503
|
+
label: activeFollowup
|
|
1504
|
+
? activeFollowup.status === "draft_missing" ? "Save unfinished work" : "Review unfinished-work draft"
|
|
1505
|
+
: improvementAction.kind === "adoption" ? "Review extracted asset" : "Draft improvement",
|
|
1506
|
+
command: draftCommand,
|
|
1507
|
+
purpose: activeFollowup
|
|
1508
|
+
? activeFollowup.status === "draft_missing"
|
|
1509
|
+
? "Save open dogfood evidence as a reviewable unfinished-work draft before organization binding or sync."
|
|
1510
|
+
: "Use the existing unfinished-work draft review route before organization binding or sync."
|
|
1511
|
+
: improvementAction.kind === "adoption"
|
|
1512
|
+
? "Use a built-in reusable asset that was extracted from dogfood evidence."
|
|
1513
|
+
: "Turn dogfood evidence or natural-language edits into a reviewable next asset version.",
|
|
1514
|
+
writes: activeFollowup ? activeFollowup.currentWrites : improvementAction.kind === "draft",
|
|
1515
|
+
owner: "dna_core",
|
|
1516
|
+
},
|
|
1517
|
+
{
|
|
1518
|
+
id: "review",
|
|
1519
|
+
label: "Manager review",
|
|
1520
|
+
command: diffCommand,
|
|
1521
|
+
purpose: "Review workflow and governance changes before adoption or rollout.",
|
|
1522
|
+
writes: false,
|
|
1523
|
+
owner: "manager",
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
id: "bind",
|
|
1527
|
+
label: "Bind accepted asset",
|
|
1528
|
+
command: orgBindCommand,
|
|
1529
|
+
purpose: "Record the manager-approved asset version in the organization profile.",
|
|
1530
|
+
writes: true,
|
|
1531
|
+
owner: "manager",
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
id: "rollout",
|
|
1535
|
+
label: "Check rollout readiness",
|
|
1536
|
+
command: "dna org rollout",
|
|
1537
|
+
purpose: "Confirm target-level readiness before downstream distribution.",
|
|
1538
|
+
writes: false,
|
|
1539
|
+
owner: "manager",
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
id: "sync",
|
|
1543
|
+
label: "Sync harness surface",
|
|
1544
|
+
command: syncCommand,
|
|
1545
|
+
purpose: "Generate runtime artifacts for Codex or Claude only after review gates pass.",
|
|
1546
|
+
writes: true,
|
|
1547
|
+
owner: "runtime",
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
id: "deploy",
|
|
1551
|
+
label: "Deploy public surface",
|
|
1552
|
+
command: "dna release deploy-plan",
|
|
1553
|
+
purpose: "Plan Vercel CLI deployment and deployed URL verification only when web, dashboard, blog, or other public deployed surfaces changed.",
|
|
1554
|
+
writes: false,
|
|
1555
|
+
owner: "release",
|
|
1556
|
+
},
|
|
1557
|
+
],
|
|
1558
|
+
boundaries: [
|
|
1559
|
+
"dna CLI/core/runtime owns product behavior and governance decisions.",
|
|
1560
|
+
"Claude Code and Codex are harness surfaces/adapters, not the governance source of truth.",
|
|
1561
|
+
"Obsidian/wiki is internal project memory, not product runtime or user-facing architecture.",
|
|
1562
|
+
"Dashboard is a projection surface; it must not become the workflow creation control plane.",
|
|
1563
|
+
"Vercel deployment is required only for changed web/public deployed surfaces, followed by deployed URL verification.",
|
|
1564
|
+
],
|
|
1565
|
+
},
|
|
1566
|
+
cascade: {
|
|
1567
|
+
layerType: asset.cascade.type,
|
|
1568
|
+
inherits: asset.cascade.inherits,
|
|
1569
|
+
priority: asset.cascade.priority,
|
|
1570
|
+
compilerEffect: asset.status.hasCascade
|
|
1571
|
+
? `Compiler merges inherited layers (${asset.cascade.inherits.length > 0 ? asset.cascade.inherits.join(", ") : "none"}) before runtime artifacts are generated.`
|
|
1572
|
+
: "No cascade inheritance is declared; the asset is standalone until inheritance is added.",
|
|
1573
|
+
},
|
|
1574
|
+
evidence: routedEvidenceGate,
|
|
1575
|
+
evolution: {
|
|
1576
|
+
reviewMode: asset.evolution.reviewMode,
|
|
1577
|
+
previewCommand: asset.evolution.previewCommand,
|
|
1578
|
+
mutatingCommand: asset.evolution.mutatingCommand,
|
|
1579
|
+
mutationBoundary: "Lifecycle is read-only. Evidence can produce suggestions and reviewed drafts; marker mutation and runtime sync remain explicit human-reviewed commands.",
|
|
1580
|
+
},
|
|
1581
|
+
stages,
|
|
1582
|
+
currentGate,
|
|
1583
|
+
nextCommands: [...new Set([currentGate.nextCommand, ...stages.slice(0, 6).map((stage) => stage.command)])],
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
function evidenceReviewSummary(index) {
|
|
1587
|
+
return {
|
|
1588
|
+
asset: index.asset,
|
|
1589
|
+
modules: index.totals.modules,
|
|
1590
|
+
pass: index.totals.pass,
|
|
1591
|
+
continue: index.totals.continue,
|
|
1592
|
+
requestChanges: index.totals.requestChanges,
|
|
1593
|
+
blocked: index.totals.blocked,
|
|
1594
|
+
complete: index.totals.complete,
|
|
1595
|
+
remaining: index.totals.remaining,
|
|
1596
|
+
remainingUnimplementedSkipped: index.totals.remainingUnimplementedSkipped,
|
|
1597
|
+
newRed: index.totals.newRed,
|
|
1598
|
+
nextFocus: index.modules
|
|
1599
|
+
.filter((module) => module.verdict !== "PASS" || !module.moduleComplete || module.remainingCount > 0)
|
|
1600
|
+
.slice(0, 5)
|
|
1601
|
+
.map((module) => {
|
|
1602
|
+
const remaining = module.remainingCount > 0 ? ` remaining=${module.remainingCount}` : "";
|
|
1603
|
+
const focus = module.nextRoundFocus ? ` next=${module.nextRoundFocus}` : "";
|
|
1604
|
+
return `${module.module}: ${module.verdict}${remaining}${focus}`;
|
|
1605
|
+
}),
|
|
1606
|
+
warnings: index.warnings,
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
function hasRegisteredDogfoodProgress(index) {
|
|
1610
|
+
return index.progressDir.length > 0;
|
|
1611
|
+
}
|
|
1612
|
+
function assetLibraryGroupId(asset) {
|
|
1613
|
+
if (asset.status.managementStatus === "dogfood_ready")
|
|
1614
|
+
return "dogfood_proven";
|
|
1615
|
+
if (asset.status.managementStatus === "asset_ready")
|
|
1616
|
+
return "ready_assets";
|
|
1617
|
+
return "draft_or_template";
|
|
1618
|
+
}
|
|
1619
|
+
function assetLibraryItem(asset) {
|
|
1620
|
+
const nextCommands = [
|
|
1621
|
+
`dna assets inspect ${asset.name} --evidence`,
|
|
1622
|
+
`dna assets governance ${asset.name}`,
|
|
1623
|
+
`dna assets adoption ${asset.name}`,
|
|
1624
|
+
];
|
|
1625
|
+
if (asset.status.evidencePosture === "dogfood_linked") {
|
|
1626
|
+
nextCommands.push(`dna assets suggestions ${asset.name}`);
|
|
1627
|
+
}
|
|
1628
|
+
if (!asset.status.configured) {
|
|
1629
|
+
nextCommands.push(`dna init --template ${asset.name}`);
|
|
1630
|
+
}
|
|
1631
|
+
return {
|
|
1632
|
+
asset: asset.name,
|
|
1633
|
+
displayName: asset.displayName,
|
|
1634
|
+
source: asset.source,
|
|
1635
|
+
purpose: asset.purpose,
|
|
1636
|
+
maturity: asset.maturity,
|
|
1637
|
+
fit: asset.fit,
|
|
1638
|
+
notFor: asset.notFor,
|
|
1639
|
+
inputs: asset.inputs,
|
|
1640
|
+
outputs: asset.outputs,
|
|
1641
|
+
lineage: asset.lineage,
|
|
1642
|
+
positioning: asset.positioning,
|
|
1643
|
+
status: asset.status.managementStatus,
|
|
1644
|
+
evidence: asset.status.evidencePosture,
|
|
1645
|
+
cascade: asset.status.cascadePosture,
|
|
1646
|
+
evolution: asset.status.evolutionPosture,
|
|
1647
|
+
configuration: asset.status.configurationPosture,
|
|
1648
|
+
configured: asset.status.configured,
|
|
1649
|
+
workflows: asset.workflows.length,
|
|
1650
|
+
controllers: asset.controllers.length,
|
|
1651
|
+
roles: asset.roles.length,
|
|
1652
|
+
nextCommands,
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1655
|
+
function assetLibrarySort(a, b) {
|
|
1656
|
+
const rank = {
|
|
1657
|
+
dogfood_ready: 0,
|
|
1658
|
+
asset_ready: 1,
|
|
1659
|
+
template_only: 2,
|
|
1660
|
+
};
|
|
1661
|
+
const byStatus = rank[a.status.managementStatus] - rank[b.status.managementStatus];
|
|
1662
|
+
if (byStatus !== 0)
|
|
1663
|
+
return byStatus;
|
|
1664
|
+
if (a.status.hasEvidence !== b.status.hasEvidence)
|
|
1665
|
+
return a.status.hasEvidence ? -1 : 1;
|
|
1666
|
+
return a.name.localeCompare(b.name);
|
|
1667
|
+
}
|
|
1668
|
+
function buildAssetLibrary(assets) {
|
|
1669
|
+
const definitions = [
|
|
1670
|
+
{
|
|
1671
|
+
id: "dogfood_proven",
|
|
1672
|
+
label: "Dogfood-proven",
|
|
1673
|
+
description: "Assets backed by real project evidence. Review evidence before broad sync or evolution.",
|
|
1674
|
+
},
|
|
1675
|
+
{
|
|
1676
|
+
id: "ready_assets",
|
|
1677
|
+
label: "Ready assets",
|
|
1678
|
+
description: "Assets with reusable workflow shape ready for adoption review.",
|
|
1679
|
+
},
|
|
1680
|
+
{
|
|
1681
|
+
id: "draft_or_template",
|
|
1682
|
+
label: "Draft/template assets",
|
|
1683
|
+
description: "Useful starting points that still need review, evidence, or governance hardening.",
|
|
1684
|
+
},
|
|
1685
|
+
];
|
|
1686
|
+
const groups = definitions.map((definition) => ({ ...definition, items: [] }));
|
|
1687
|
+
const byId = new Map(groups.map((group) => [group.id, group]));
|
|
1688
|
+
for (const asset of [...assets].sort(assetLibrarySort)) {
|
|
1689
|
+
byId.get(assetLibraryGroupId(asset))?.items.push(assetLibraryItem(asset));
|
|
1690
|
+
}
|
|
1691
|
+
return {
|
|
1692
|
+
mode: "workflow_asset_library",
|
|
1693
|
+
totals: {
|
|
1694
|
+
assets: assets.length,
|
|
1695
|
+
dogfoodReady: assets.filter((asset) => asset.status.managementStatus === "dogfood_ready").length,
|
|
1696
|
+
assetReady: assets.filter((asset) => asset.status.managementStatus === "asset_ready").length,
|
|
1697
|
+
templateOnly: assets.filter((asset) => asset.status.managementStatus === "template_only").length,
|
|
1698
|
+
evidenceLinked: assets.filter((asset) => asset.status.hasEvidence).length,
|
|
1699
|
+
configured: assets.filter((asset) => asset.status.configured).length,
|
|
1700
|
+
},
|
|
1701
|
+
governanceChain: {
|
|
1702
|
+
principle: "Dogfood problems become reusable workflow assets; workflow asset gaps become dna core governance gates before org rollout, sync, or deploy.",
|
|
1703
|
+
steps: [
|
|
1704
|
+
{
|
|
1705
|
+
id: "inventory",
|
|
1706
|
+
asset: "workflow-module-inventory",
|
|
1707
|
+
label: "Inventory before execution",
|
|
1708
|
+
command: "dna assets adoption workflow-module-inventory",
|
|
1709
|
+
purpose: "Scan modules, pages, actions, evidence gaps, and priorities before per-module behavior locks or fixes start.",
|
|
1710
|
+
},
|
|
1711
|
+
{
|
|
1712
|
+
id: "behavior-lock",
|
|
1713
|
+
asset: WORKFLOW_ASSET_IDS.behaviorLock,
|
|
1714
|
+
label: "Lock behavior first",
|
|
1715
|
+
command: `dna assets adoption ${WORKFLOW_ASSET_IDS.behaviorLock}`,
|
|
1716
|
+
purpose: "Capture source behavior as docs and tests before risky rewrite, refactor, diagnosis, or fix work starts.",
|
|
1717
|
+
},
|
|
1718
|
+
{
|
|
1719
|
+
id: "diagnosis",
|
|
1720
|
+
asset: WORKFLOW_ASSET_IDS.rewriteDiagnosisReview,
|
|
1721
|
+
label: "Classify before editing",
|
|
1722
|
+
command: `dna assets adoption ${WORKFLOW_ASSET_IDS.rewriteDiagnosisReview}`,
|
|
1723
|
+
purpose: "Require evidence-only diagnosis and independent review before implementation starts.",
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
id: "fix-review",
|
|
1727
|
+
asset: WORKFLOW_ASSET_IDS.rewriteFixReviewLoop,
|
|
1728
|
+
label: "Fix, review, verify",
|
|
1729
|
+
command: `dna assets adoption ${WORKFLOW_ASSET_IDS.rewriteFixReviewLoop}`,
|
|
1730
|
+
purpose: "Run the smallest diagnosis-approved fix with independent review and fresh verification.",
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
id: "verification-routing",
|
|
1734
|
+
asset: WORKFLOW_ASSET_IDS.verificationCommandRouting,
|
|
1735
|
+
label: "Route verification command",
|
|
1736
|
+
command: `dna assets adoption ${WORKFLOW_ASSET_IDS.verificationCommandRouting}`,
|
|
1737
|
+
purpose: "Choose the authoritative command root/scope and capture canonical machine evidence before completion or rollout decisions.",
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
id: "evidence",
|
|
1741
|
+
asset: "workflow-evidence-review",
|
|
1742
|
+
label: "Review evidence authority",
|
|
1743
|
+
command: "dna assets adoption workflow-evidence-review",
|
|
1744
|
+
purpose: "Separate canonical evidence from weak notes before adoption, evolution, rollout, or sync.",
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
id: "blocked-scope",
|
|
1748
|
+
asset: WORKFLOW_ASSET_IDS.blockedScopeTriage,
|
|
1749
|
+
label: "Triage blocked scope",
|
|
1750
|
+
command: `dna assets adoption ${WORKFLOW_ASSET_IDS.blockedScopeTriage}`,
|
|
1751
|
+
purpose: "Classify blocked, skipped, removed, infra, deferred, and remaining work before manager review.",
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
id: "completion",
|
|
1755
|
+
asset: "workflow-completion-audit",
|
|
1756
|
+
label: "Prove completion",
|
|
1757
|
+
command: "dna assets adoption workflow-completion-audit",
|
|
1758
|
+
purpose: "Prove PASS/CONTINUE/BLOCKED with diagnosis, current verification, red classification, skipped comparison, and progress evidence.",
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
id: "runtime-smoke",
|
|
1762
|
+
asset: "workflow-runtime-smoke-review",
|
|
1763
|
+
label: "Smoke runtime surface",
|
|
1764
|
+
command: "dna assets adoption workflow-runtime-smoke-review",
|
|
1765
|
+
purpose: "Prove the synced or distributed runtime surface is actually usable before wider rollout.",
|
|
1766
|
+
},
|
|
1767
|
+
].filter((step) => assets.some((asset) => asset.name === step.asset)),
|
|
1768
|
+
boundaries: [
|
|
1769
|
+
"Obsidian/wiki remains internal project memory; product governance uses workflow assets and machine-readable evidence.",
|
|
1770
|
+
"Dashboard remains a read-only projection of this chain, not a workflow builder or policy source.",
|
|
1771
|
+
"Runtime targets such as Codex, Claude, web, and mobile receive synced artifacts; dna core keeps governance truth.",
|
|
1772
|
+
],
|
|
1773
|
+
},
|
|
1774
|
+
groups,
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
function writeAssetLibrary(library) {
|
|
1778
|
+
process.stderr.write("Workflow asset library\n");
|
|
1779
|
+
process.stderr.write(`Assets: ${library.totals.assets}\n`);
|
|
1780
|
+
process.stderr.write(`Dogfood ready: ${library.totals.dogfoodReady}\n`);
|
|
1781
|
+
process.stderr.write(`Asset ready: ${library.totals.assetReady}\n`);
|
|
1782
|
+
process.stderr.write(`Template only: ${library.totals.templateOnly}\n`);
|
|
1783
|
+
process.stderr.write(`Evidence linked: ${library.totals.evidenceLinked}\n`);
|
|
1784
|
+
process.stderr.write(`Configured: ${library.totals.configured}\n`);
|
|
1785
|
+
process.stderr.write("\nWorkflow governance chain\n");
|
|
1786
|
+
process.stderr.write(`${library.governanceChain.principle}\n`);
|
|
1787
|
+
for (const step of library.governanceChain.steps) {
|
|
1788
|
+
process.stderr.write(` - ${step.id}. ${step.label}: ${step.asset}\n`);
|
|
1789
|
+
process.stderr.write(` ${step.command}\n`);
|
|
1790
|
+
process.stderr.write(` ${step.purpose}\n`);
|
|
1791
|
+
}
|
|
1792
|
+
process.stderr.write(" Boundaries:\n");
|
|
1793
|
+
for (const boundary of library.governanceChain.boundaries)
|
|
1794
|
+
process.stderr.write(` - ${boundary}\n`);
|
|
1795
|
+
for (const group of library.groups) {
|
|
1796
|
+
process.stderr.write(`\n${group.label} (${group.items.length})\n`);
|
|
1797
|
+
process.stderr.write(`${group.description}\n`);
|
|
1798
|
+
if (group.items.length === 0) {
|
|
1799
|
+
process.stderr.write(" - none\n");
|
|
1800
|
+
continue;
|
|
1801
|
+
}
|
|
1802
|
+
for (const item of group.items) {
|
|
1803
|
+
const label = item.displayName ? `${item.asset} (${item.displayName})` : item.asset;
|
|
1804
|
+
process.stderr.write(` - ${label}: ${item.status}, evidence=${item.evidence}, cascade=${item.cascade}, evolution=${item.evolution}, config=${item.configuration}, workflows=${item.workflows}, controllers=${item.controllers}\n`);
|
|
1805
|
+
if (item.positioning)
|
|
1806
|
+
process.stderr.write(` positioning: ${item.positioning}\n`);
|
|
1807
|
+
process.stderr.write(` maturity: ${item.maturity}; source=${item.source}\n`);
|
|
1808
|
+
if (item.purpose)
|
|
1809
|
+
process.stderr.write(` purpose: ${item.purpose}\n`);
|
|
1810
|
+
for (const command of item.nextCommands)
|
|
1811
|
+
process.stderr.write(` next: ${command}\n`);
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
function nextEvidenceFocus(index) {
|
|
1816
|
+
return index.modules
|
|
1817
|
+
.filter((module) => module.verdict !== "PASS" || !module.moduleComplete || module.remainingCount > 0 || module.newRedCount > 0)
|
|
1818
|
+
.slice(0, 5)
|
|
1819
|
+
.map((module) => {
|
|
1820
|
+
const remaining = module.remainingCount > 0 ? ` remaining=${module.remainingCount}` : "";
|
|
1821
|
+
const focus = module.nextRoundFocus ? ` next=${module.nextRoundFocus}` : "";
|
|
1822
|
+
return `${module.module}: ${module.verdict}${remaining}${focus}`;
|
|
1823
|
+
});
|
|
1824
|
+
}
|
|
1825
|
+
function buildLineageReview(asset, evidence) {
|
|
1826
|
+
const suggestedDraftName = `${asset.name}-v2`;
|
|
1827
|
+
const fallbackEditCommand = `dna workflow edit-plan "Describe the reviewed workflow change" --from-template ${asset.name} --save-template ${suggestedDraftName}`;
|
|
1828
|
+
const suggestion = evidence && asset.status.evidencePosture === "dogfood_linked"
|
|
1829
|
+
? firstDraftableSuggestion(buildAssetSuggestions(asset, evidence).suggestions)
|
|
1830
|
+
: undefined;
|
|
1831
|
+
const lineageAction = suggestion
|
|
1832
|
+
? suggestionAction({ asset: asset.name, suggestion, saveTemplate: suggestedDraftName })
|
|
1833
|
+
: { kind: "draft", command: fallbackEditCommand };
|
|
1834
|
+
const suggestedActionCommand = lineageAction.command;
|
|
1835
|
+
const versionReviewCommand = `dna assets diff ${asset.name} ${suggestedDraftName}`;
|
|
1836
|
+
const adoptionCommand = `dna assets adoption ${suggestedDraftName}`;
|
|
1837
|
+
const orgBindCommand = `dna org bind --asset ${suggestedDraftName} --status active --sync-target codex`;
|
|
1838
|
+
const syncCommand = "dna sync --codex";
|
|
1839
|
+
const dogfood = evidence && asset.status.evidencePosture === "dogfood_linked"
|
|
1840
|
+
? {
|
|
1841
|
+
source: evidence.sourceDescription,
|
|
1842
|
+
projectPath: evidence.projectPath,
|
|
1843
|
+
progressDir: evidence.progressDir,
|
|
1844
|
+
modules: evidence.totals.modules,
|
|
1845
|
+
pass: evidence.totals.pass,
|
|
1846
|
+
continue: evidence.totals.continue,
|
|
1847
|
+
blocked: evidence.totals.blocked + evidence.totals.requestChanges,
|
|
1848
|
+
remaining: evidence.totals.remaining,
|
|
1849
|
+
nextFocus: nextEvidenceFocus(evidence),
|
|
1850
|
+
warnings: evidence.warnings,
|
|
1851
|
+
}
|
|
1852
|
+
: undefined;
|
|
1853
|
+
return {
|
|
1854
|
+
asset: asset.name,
|
|
1855
|
+
displayName: asset.displayName,
|
|
1856
|
+
mode: "workflow_asset_lineage",
|
|
1857
|
+
origin: {
|
|
1858
|
+
source: asset.source,
|
|
1859
|
+
path: asset.path,
|
|
1860
|
+
dnaId: asset.dnaId,
|
|
1861
|
+
namespace: asset.namespace,
|
|
1862
|
+
version: asset.version,
|
|
1863
|
+
contentHash: asset.contentHash,
|
|
1864
|
+
positioning: asset.positioning,
|
|
1865
|
+
},
|
|
1866
|
+
capability: {
|
|
1867
|
+
workflows: asset.workflows.map((workflow) => ({ id: workflow.id, name: workflow.name, steps: workflow.stepCount })),
|
|
1868
|
+
controllers: asset.controllers.map((controller) => ({ id: controller.id, kind: controller.kind })),
|
|
1869
|
+
roles: asset.roles.map((role) => role.id),
|
|
1870
|
+
},
|
|
1871
|
+
cascade: {
|
|
1872
|
+
declared: asset.cascade.declared,
|
|
1873
|
+
layerType: asset.cascade.type,
|
|
1874
|
+
inherits: asset.cascade.inherits,
|
|
1875
|
+
priority: asset.cascade.priority,
|
|
1876
|
+
compileMeaning: asset.cascade.declared
|
|
1877
|
+
? `Compiler merges inherited layers (${asset.cascade.inherits.length > 0 ? asset.cascade.inherits.join(", ") : "none"}) before runtime artifacts are generated.`
|
|
1878
|
+
: "Compiler treats this as a standalone asset until cascade inheritance is declared.",
|
|
1879
|
+
},
|
|
1880
|
+
evidence: {
|
|
1881
|
+
posture: asset.status.evidencePosture,
|
|
1882
|
+
links: asset.evidence.map((link) => ({ label: link.label, kind: link.kind, path: link.path })),
|
|
1883
|
+
dogfood,
|
|
1884
|
+
},
|
|
1885
|
+
evolution: {
|
|
1886
|
+
posture: asset.status.evolutionPosture,
|
|
1887
|
+
reviewMode: asset.evolution.reviewMode,
|
|
1888
|
+
previewCommand: asset.evolution.previewCommand,
|
|
1889
|
+
mutatingCommand: asset.evolution.mutatingCommand,
|
|
1890
|
+
suggestedActionCommand,
|
|
1891
|
+
suggestedActionKind: lineageAction.kind,
|
|
1892
|
+
versionReviewCommand,
|
|
1893
|
+
adoptionCommand,
|
|
1894
|
+
orgBindCommand,
|
|
1895
|
+
syncCommand,
|
|
1896
|
+
mutationBoundary: "Lineage is read-only. Evidence can suggest a draft version, but policy/runtime mutation requires diff review, adoption review, org binding, and explicit sync.",
|
|
1897
|
+
},
|
|
1898
|
+
nextCommands: [
|
|
1899
|
+
`dna assets inspect ${asset.name} --evidence`,
|
|
1900
|
+
`dna assets governance ${asset.name}`,
|
|
1901
|
+
asset.status.evidencePosture === "dogfood_linked" ? `dna assets suggestions ${asset.name}` : `dna workflow edit-plan "Describe the reviewed workflow change" --from-template ${asset.name} --save-template ${suggestedDraftName}`,
|
|
1902
|
+
suggestedActionCommand,
|
|
1903
|
+
versionReviewCommand,
|
|
1904
|
+
adoptionCommand,
|
|
1905
|
+
orgBindCommand,
|
|
1906
|
+
syncCommand,
|
|
1907
|
+
],
|
|
1908
|
+
};
|
|
1909
|
+
}
|
|
1910
|
+
function writeLineageReview(review) {
|
|
1911
|
+
const label = review.displayName ? `${review.asset} (${review.displayName})` : review.asset;
|
|
1912
|
+
process.stderr.write(`Workflow asset lineage: ${label}\n`);
|
|
1913
|
+
process.stderr.write("\nOrigin:\n");
|
|
1914
|
+
process.stderr.write(` - source: ${review.origin.source}\n`);
|
|
1915
|
+
process.stderr.write(` - path: ${review.origin.path}\n`);
|
|
1916
|
+
if (review.origin.dnaId)
|
|
1917
|
+
process.stderr.write(` - dna id: ${review.origin.dnaId}\n`);
|
|
1918
|
+
if (review.origin.namespace)
|
|
1919
|
+
process.stderr.write(` - namespace: ${review.origin.namespace}\n`);
|
|
1920
|
+
if (review.origin.version)
|
|
1921
|
+
process.stderr.write(` - version: ${review.origin.version}\n`);
|
|
1922
|
+
process.stderr.write(` - content hash: ${review.origin.contentHash}\n`);
|
|
1923
|
+
if (review.origin.positioning)
|
|
1924
|
+
process.stderr.write(` - positioning: ${review.origin.positioning}\n`);
|
|
1925
|
+
process.stderr.write("\nCapability:\n");
|
|
1926
|
+
process.stderr.write(` - workflows: ${review.capability.workflows.length}\n`);
|
|
1927
|
+
for (const workflow of review.capability.workflows) {
|
|
1928
|
+
process.stderr.write(` - ${workflow.id}: ${workflow.name} (${workflow.steps} step(s))\n`);
|
|
1929
|
+
}
|
|
1930
|
+
process.stderr.write(` - controllers: ${review.capability.controllers.length}\n`);
|
|
1931
|
+
for (const controller of review.capability.controllers) {
|
|
1932
|
+
process.stderr.write(` - ${controller.id}: ${controller.kind}\n`);
|
|
1933
|
+
}
|
|
1934
|
+
process.stderr.write(` - roles: ${review.capability.roles.length > 0 ? review.capability.roles.join(", ") : "none"}\n`);
|
|
1935
|
+
process.stderr.write("\nCascade lineage:\n");
|
|
1936
|
+
process.stderr.write(` - declared: ${review.cascade.declared ? "yes" : "no"}\n`);
|
|
1937
|
+
if (review.cascade.layerType)
|
|
1938
|
+
process.stderr.write(` - layer type: ${review.cascade.layerType}\n`);
|
|
1939
|
+
if (review.cascade.priority !== undefined)
|
|
1940
|
+
process.stderr.write(` - priority: ${review.cascade.priority}\n`);
|
|
1941
|
+
process.stderr.write(` - inherits: ${review.cascade.inherits.length > 0 ? review.cascade.inherits.join(", ") : "none"}\n`);
|
|
1942
|
+
process.stderr.write(` - meaning: ${review.cascade.compileMeaning}\n`);
|
|
1943
|
+
process.stderr.write("\nEvidence lineage:\n");
|
|
1944
|
+
process.stderr.write(` - posture: ${review.evidence.posture}\n`);
|
|
1945
|
+
if (review.evidence.links.length === 0) {
|
|
1946
|
+
process.stderr.write(" - links: none\n");
|
|
1947
|
+
}
|
|
1948
|
+
else {
|
|
1949
|
+
for (const link of review.evidence.links)
|
|
1950
|
+
process.stderr.write(` - ${link.label} [${link.kind}]: ${link.path}\n`);
|
|
1951
|
+
}
|
|
1952
|
+
if (review.evidence.dogfood) {
|
|
1953
|
+
const dogfood = review.evidence.dogfood;
|
|
1954
|
+
if (dogfood.source)
|
|
1955
|
+
process.stderr.write(` - dogfood source: ${dogfood.source}\n`);
|
|
1956
|
+
process.stderr.write(` - dogfood project: ${dogfood.projectPath}\n`);
|
|
1957
|
+
process.stderr.write(` - progress dir: ${dogfood.progressDir}\n`);
|
|
1958
|
+
process.stderr.write(` - modules: ${dogfood.pass}/${dogfood.modules} PASS, continue=${dogfood.continue}, blocked=${dogfood.blocked}, remaining=${dogfood.remaining}\n`);
|
|
1959
|
+
for (const focus of dogfood.nextFocus)
|
|
1960
|
+
process.stderr.write(` next focus: ${focus}\n`);
|
|
1961
|
+
for (const warning of dogfood.warnings)
|
|
1962
|
+
process.stderr.write(` warning: ${warning}\n`);
|
|
1963
|
+
}
|
|
1964
|
+
process.stderr.write("\nEvolution route:\n");
|
|
1965
|
+
process.stderr.write(` - posture: ${review.evolution.posture}\n`);
|
|
1966
|
+
process.stderr.write(` - review mode: ${review.evolution.reviewMode}\n`);
|
|
1967
|
+
process.stderr.write(` - preview: ${review.evolution.previewCommand}\n`);
|
|
1968
|
+
process.stderr.write(` - explicit mutation: ${review.evolution.mutatingCommand}\n`);
|
|
1969
|
+
process.stderr.write(` - suggested action (${review.evolution.suggestedActionKind}): ${review.evolution.suggestedActionCommand}\n`);
|
|
1970
|
+
process.stderr.write(` - review diff: ${review.evolution.versionReviewCommand}\n`);
|
|
1971
|
+
process.stderr.write(` - adoption review: ${review.evolution.adoptionCommand}\n`);
|
|
1972
|
+
process.stderr.write(` - org bind: ${review.evolution.orgBindCommand}\n`);
|
|
1973
|
+
process.stderr.write(` - sync after review: ${review.evolution.syncCommand}\n`);
|
|
1974
|
+
process.stderr.write(` - boundary: ${review.evolution.mutationBoundary}\n`);
|
|
1975
|
+
process.stderr.write("\nNext commands:\n");
|
|
1976
|
+
for (const command of review.nextCommands)
|
|
1977
|
+
process.stderr.write(` - ${command}\n`);
|
|
1978
|
+
}
|
|
1979
|
+
async function buildAssetReview(assets, projectDir) {
|
|
1980
|
+
const evidence = [];
|
|
1981
|
+
for (const asset of assets.filter((candidate) => candidate.status.managementStatus === "dogfood_ready")) {
|
|
1982
|
+
const index = await buildWorkflowAssetEvidenceIndex(asset.name, { projectDir });
|
|
1983
|
+
if (index && hasRegisteredDogfoodProgress(index))
|
|
1984
|
+
evidence.push(evidenceReviewSummary(index));
|
|
1985
|
+
}
|
|
1986
|
+
const totals = {
|
|
1987
|
+
assets: assets.length,
|
|
1988
|
+
configured: assets.filter((asset) => asset.status.configured).length,
|
|
1989
|
+
available: assets.filter((asset) => !asset.status.configured).length,
|
|
1990
|
+
dogfoodReady: assets.filter((asset) => asset.status.managementStatus === "dogfood_ready").length,
|
|
1991
|
+
missingWorkflow: assets.filter((asset) => !asset.status.hasWorkflow).length,
|
|
1992
|
+
missingController: assets.filter((asset) => !asset.status.hasController).length,
|
|
1993
|
+
missingCascade: assets.filter((asset) => !asset.status.hasCascade).length,
|
|
1994
|
+
missingEvidence: assets.filter((asset) => !asset.status.hasEvidence).length,
|
|
1995
|
+
previewEvolution: assets.filter((asset) => asset.status.evolutionPosture === "preview_supported").length,
|
|
1996
|
+
dogfoodEvidenceModules: evidence.reduce((sum, summary) => sum + summary.modules, 0),
|
|
1997
|
+
dogfoodEvidencePass: evidence.reduce((sum, summary) => sum + summary.pass, 0),
|
|
1998
|
+
dogfoodEvidenceContinue: evidence.reduce((sum, summary) => sum + summary.continue, 0),
|
|
1999
|
+
dogfoodEvidenceBlocked: evidence.reduce((sum, summary) => sum + summary.blocked + summary.requestChanges, 0),
|
|
2000
|
+
dogfoodEvidenceRemaining: evidence.reduce((sum, summary) => sum + summary.remaining, 0),
|
|
2001
|
+
};
|
|
2002
|
+
const items = [];
|
|
2003
|
+
for (const asset of assets) {
|
|
2004
|
+
if (!asset.status.configured && (asset.status.hasEvidence || asset.status.managementStatus === "dogfood_ready")) {
|
|
2005
|
+
items.push({
|
|
2006
|
+
asset: asset.name,
|
|
2007
|
+
displayName: asset.displayName,
|
|
2008
|
+
severity: "info",
|
|
2009
|
+
message: "Evidence-linked asset is available but not configured in this project.",
|
|
2010
|
+
nextAction: `Run dna init --template ${asset.name} when this workflow should govern the project.`,
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
if (!asset.status.hasWorkflow) {
|
|
2014
|
+
items.push({
|
|
2015
|
+
asset: asset.name,
|
|
2016
|
+
displayName: asset.displayName,
|
|
2017
|
+
severity: "warn",
|
|
2018
|
+
message: "No workflow steps are defined.",
|
|
2019
|
+
nextAction: "Describe the repeatable work method as steps before treating this as a workflow asset.",
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
if (!asset.status.hasCascade) {
|
|
2023
|
+
items.push({
|
|
2024
|
+
asset: asset.name,
|
|
2025
|
+
displayName: asset.displayName,
|
|
2026
|
+
severity: "warn",
|
|
2027
|
+
message: "No cascade inheritance is declared.",
|
|
2028
|
+
nextAction: "Attach species/project/org inheritance before distributing this asset broadly.",
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
if (!asset.status.hasEvidence) {
|
|
2032
|
+
items.push({
|
|
2033
|
+
asset: asset.name,
|
|
2034
|
+
displayName: asset.displayName,
|
|
2035
|
+
severity: "warn",
|
|
2036
|
+
message: "No evidence/reference links are registered.",
|
|
2037
|
+
nextAction: "Link dogfood, reference, or decision evidence before presenting this as proven.",
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
for (const summary of evidence) {
|
|
2042
|
+
for (const warning of summary.warnings) {
|
|
2043
|
+
items.push({
|
|
2044
|
+
asset: summary.asset,
|
|
2045
|
+
severity: "warn",
|
|
2046
|
+
message: warning,
|
|
2047
|
+
nextAction: "Fix the registered dogfood progress source or remove the dogfood-ready claim.",
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
if (summary.continue > 0 || summary.requestChanges > 0 || summary.blocked > 0 || summary.remaining > 0) {
|
|
2051
|
+
items.push({
|
|
2052
|
+
asset: summary.asset,
|
|
2053
|
+
severity: "info",
|
|
2054
|
+
message: `Dogfood evidence is active: ${summary.pass}/${summary.modules} PASS, ${summary.remaining} remaining.`,
|
|
2055
|
+
nextAction: summary.nextFocus[0] ?? `Run dna assets evidence ${summary.asset} for module-level next focus.`,
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
return { totals, evidence, items };
|
|
2060
|
+
}
|
|
2061
|
+
function prioritizedAssets(assets) {
|
|
2062
|
+
return assets
|
|
2063
|
+
.filter((asset) => asset.status.managementStatus === "dogfood_ready" || asset.status.hasEvidence)
|
|
2064
|
+
.sort((a, b) => {
|
|
2065
|
+
if (a.status.managementStatus === "dogfood_ready" && b.status.managementStatus !== "dogfood_ready")
|
|
2066
|
+
return -1;
|
|
2067
|
+
if (b.status.managementStatus === "dogfood_ready" && a.status.managementStatus !== "dogfood_ready")
|
|
2068
|
+
return 1;
|
|
2069
|
+
return a.name.localeCompare(b.name);
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
function writeAssetReview(review, assets) {
|
|
2073
|
+
process.stderr.write("Workflow asset review\n");
|
|
2074
|
+
process.stderr.write(`Assets: ${review.totals.assets}\n`);
|
|
2075
|
+
process.stderr.write(`Configured: ${review.totals.configured}\n`);
|
|
2076
|
+
process.stderr.write(`Available only: ${review.totals.available}\n`);
|
|
2077
|
+
process.stderr.write(`Dogfood ready: ${review.totals.dogfoodReady}\n`);
|
|
2078
|
+
process.stderr.write(`Missing workflow: ${review.totals.missingWorkflow}\n`);
|
|
2079
|
+
process.stderr.write(`Missing controller: ${review.totals.missingController}\n`);
|
|
2080
|
+
process.stderr.write(`Missing cascade: ${review.totals.missingCascade}\n`);
|
|
2081
|
+
process.stderr.write(`Missing evidence: ${review.totals.missingEvidence}\n`);
|
|
2082
|
+
process.stderr.write(`Preview-first evolution: ${review.totals.previewEvolution}\n`);
|
|
2083
|
+
process.stderr.write(`Dogfood evidence modules: ${review.totals.dogfoodEvidenceModules}\n`);
|
|
2084
|
+
process.stderr.write(`Dogfood evidence PASS: ${review.totals.dogfoodEvidencePass}\n`);
|
|
2085
|
+
process.stderr.write(`Dogfood evidence CONTINUE: ${review.totals.dogfoodEvidenceContinue}\n`);
|
|
2086
|
+
process.stderr.write(`Dogfood evidence blocked/request changes: ${review.totals.dogfoodEvidenceBlocked}\n`);
|
|
2087
|
+
process.stderr.write(`Dogfood evidence remaining: ${review.totals.dogfoodEvidenceRemaining}\n`);
|
|
2088
|
+
const focusAssets = prioritizedAssets(assets);
|
|
2089
|
+
if (focusAssets.length > 0) {
|
|
2090
|
+
process.stderr.write("\nFocus assets:\n");
|
|
2091
|
+
for (const asset of focusAssets.slice(0, 5)) {
|
|
2092
|
+
const label = asset.displayName ? `${asset.name} (${asset.displayName})` : asset.name;
|
|
2093
|
+
process.stderr.write(` - ${label}: ${asset.status.managementStatus}, ${asset.status.configurationPosture}, ${asset.status.evidencePosture}, cascade=${asset.status.cascadePosture}, evolution=${asset.status.evolutionPosture}\n`);
|
|
2094
|
+
if (!asset.status.configured) {
|
|
2095
|
+
process.stderr.write(` next: Run dna init --template ${asset.name} when this workflow should govern the project.\n`);
|
|
2096
|
+
}
|
|
2097
|
+
else if (asset.evolution.reviewMode === "preview_first") {
|
|
2098
|
+
process.stderr.write(` next: Review ${asset.evolution.previewCommand} before applying any marker mutation.\n`);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
if (review.evidence.length > 0) {
|
|
2103
|
+
process.stderr.write("\nDogfood evidence:\n");
|
|
2104
|
+
for (const evidence of review.evidence) {
|
|
2105
|
+
process.stderr.write(` - ${evidence.asset}: ${evidence.pass}/${evidence.modules} PASS, continue=${evidence.continue}, blocked=${evidence.blocked + evidence.requestChanges}, remaining=${evidence.remaining}\n`);
|
|
2106
|
+
for (const focus of evidence.nextFocus.slice(0, 3)) {
|
|
2107
|
+
process.stderr.write(` next: ${focus}\n`);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
if (review.items.length === 0) {
|
|
2112
|
+
process.stderr.write("\nNo review items.\n");
|
|
2113
|
+
return;
|
|
2114
|
+
}
|
|
2115
|
+
const visibleItems = review.items.slice(0, 12);
|
|
2116
|
+
process.stderr.write("\nTop review items:\n");
|
|
2117
|
+
process.stderr.write(`Showing: ${visibleItems.length}/${review.items.length}\n`);
|
|
2118
|
+
for (const item of visibleItems) {
|
|
2119
|
+
const label = item.displayName ? `${item.asset} (${item.displayName})` : item.asset;
|
|
2120
|
+
process.stderr.write(` - [${item.severity}] ${label}: ${item.message}\n`);
|
|
2121
|
+
process.stderr.write(` next: ${item.nextAction}\n`);
|
|
2122
|
+
}
|
|
2123
|
+
if (visibleItems.length < review.items.length) {
|
|
2124
|
+
process.stderr.write(` ... ${review.items.length - visibleItems.length} more item(s). Use --json for the full review payload.\n`);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
function writeManagerGates(gates) {
|
|
2128
|
+
process.stderr.write("\nManager gates:\n");
|
|
2129
|
+
for (const gate of gates) {
|
|
2130
|
+
process.stderr.write(` - [${gate.status}] ${gate.id}: ${gate.decision}\n`);
|
|
2131
|
+
process.stderr.write(` owner: ${gate.owner}\n`);
|
|
2132
|
+
process.stderr.write(` writes: ${gate.writes ? "yes" : "no"}\n`);
|
|
2133
|
+
process.stderr.write(` next: ${gate.nextCommand}\n`);
|
|
2134
|
+
process.stderr.write(` reason: ${gate.reason}\n`);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
function writeGovernanceReview(review) {
|
|
2138
|
+
const label = review.displayName ? `${review.asset} (${review.displayName})` : review.asset;
|
|
2139
|
+
process.stderr.write(`Workflow asset governance: ${label}\n`);
|
|
2140
|
+
writeManagerGates(review.managerGates);
|
|
2141
|
+
process.stderr.write("\nGovernance review packet:\n");
|
|
2142
|
+
process.stderr.write(` - cascade ready: ${review.reviewPacket.cascadeReady ? "yes" : "no"}\n`);
|
|
2143
|
+
process.stderr.write(` - evolution ready: ${review.reviewPacket.evolutionReady ? "yes" : "no"}\n`);
|
|
2144
|
+
process.stderr.write(` - evidence allows mutation: ${review.reviewPacket.evidenceAllowsMutation ? "yes" : "no"}\n`);
|
|
2145
|
+
process.stderr.write(` - sync allowed: ${review.reviewPacket.syncAllowed ? "yes" : "no"}\n`);
|
|
2146
|
+
process.stderr.write(` - decision: ${review.reviewPacket.decision}\n`);
|
|
2147
|
+
process.stderr.write(` - next: ${review.reviewPacket.nextCommand}\n`);
|
|
2148
|
+
process.stderr.write(` - compiler effect: ${review.reviewPacket.compilerEffect}\n`);
|
|
2149
|
+
process.stderr.write(` - mutation boundary: ${review.reviewPacket.mutationBoundary}\n`);
|
|
2150
|
+
process.stderr.write("\nEvidence gate:\n");
|
|
2151
|
+
process.stderr.write(` - status: ${review.evidenceGate.status}\n`);
|
|
2152
|
+
process.stderr.write(` - modules: ${review.evidenceGate.modules}\n`);
|
|
2153
|
+
process.stderr.write(` - pass: ${review.evidenceGate.pass}\n`);
|
|
2154
|
+
process.stderr.write(` - continue: ${review.evidenceGate.continue}\n`);
|
|
2155
|
+
process.stderr.write(` - blocked: ${review.evidenceGate.blocked}\n`);
|
|
2156
|
+
process.stderr.write(` - remaining: ${review.evidenceGate.remaining}\n`);
|
|
2157
|
+
if (review.evidenceGate.nextCommand)
|
|
2158
|
+
process.stderr.write(` - next: ${review.evidenceGate.nextCommand}\n`);
|
|
2159
|
+
process.stderr.write(` - meaning: ${review.evidenceGate.explanation}\n`);
|
|
2160
|
+
process.stderr.write("\nCascade:\n");
|
|
2161
|
+
process.stderr.write(` - posture: ${review.cascade.posture}\n`);
|
|
2162
|
+
process.stderr.write(` - declared: ${review.cascade.declared ? "yes" : "no"}\n`);
|
|
2163
|
+
if (review.cascade.layerType)
|
|
2164
|
+
process.stderr.write(` - layer type: ${review.cascade.layerType}\n`);
|
|
2165
|
+
if (review.cascade.priority !== undefined)
|
|
2166
|
+
process.stderr.write(` - priority: ${review.cascade.priority}\n`);
|
|
2167
|
+
process.stderr.write(` - inherits: ${review.cascade.inherits.length > 0 ? review.cascade.inherits.join(", ") : "none"}\n`);
|
|
2168
|
+
process.stderr.write(` - meaning: ${review.cascade.explanation}\n`);
|
|
2169
|
+
process.stderr.write("\nEvolution:\n");
|
|
2170
|
+
process.stderr.write(` - posture: ${review.evolution.posture}\n`);
|
|
2171
|
+
process.stderr.write(` - review mode: ${review.evolution.reviewMode}\n`);
|
|
2172
|
+
process.stderr.write(` - declared markers: ${review.evolution.declaredMarkers}\n`);
|
|
2173
|
+
process.stderr.write(` - marker ids: ${review.evolution.markerIds.length > 0 ? review.evolution.markerIds.join(", ") : "none"}\n`);
|
|
2174
|
+
process.stderr.write(` - outcome store: ${review.evolution.outcomeStore}\n`);
|
|
2175
|
+
process.stderr.write(` - preview: ${review.evolution.previewCommand}\n`);
|
|
2176
|
+
process.stderr.write(` - explicit mutation: ${review.evolution.mutatingCommand}\n`);
|
|
2177
|
+
process.stderr.write(` - meaning: ${review.evolution.explanation}\n`);
|
|
2178
|
+
process.stderr.write("\nImplementation references:\n");
|
|
2179
|
+
for (const ref of review.implementationRefs) {
|
|
2180
|
+
process.stderr.write(` - ${ref.layer}: ${ref.path}\n`);
|
|
2181
|
+
process.stderr.write(` ${ref.role}\n`);
|
|
2182
|
+
}
|
|
2183
|
+
process.stderr.write("\nNext actions:\n");
|
|
2184
|
+
for (const action of review.nextActions)
|
|
2185
|
+
process.stderr.write(` - ${action}\n`);
|
|
2186
|
+
}
|
|
2187
|
+
function writeAdoptionReview(review) {
|
|
2188
|
+
const label = review.displayName ? `${review.asset} (${review.displayName})` : review.asset;
|
|
2189
|
+
process.stderr.write(`Workflow asset adoption review: ${label}\n`);
|
|
2190
|
+
process.stderr.write(`Recommendation: ${review.recommendation}\n`);
|
|
2191
|
+
process.stderr.write(`Summary: ${review.summary}\n`);
|
|
2192
|
+
if (review.predecessorDiff) {
|
|
2193
|
+
process.stderr.write("\nPredecessor diff gate:\n");
|
|
2194
|
+
process.stderr.write(` - from: ${review.predecessorDiff.from}\n`);
|
|
2195
|
+
process.stderr.write(` - command: ${review.predecessorDiff.command}\n`);
|
|
2196
|
+
process.stderr.write(` - risk: ${review.predecessorDiff.riskLevel}\n`);
|
|
2197
|
+
process.stderr.write(` - acceptance required: ${review.predecessorDiff.acceptanceRequired ? "yes" : "no"}\n`);
|
|
2198
|
+
process.stderr.write(` - receipt command: ${review.predecessorDiff.receiptCommand}\n`);
|
|
2199
|
+
process.stderr.write(` - receipt path: ${review.predecessorDiff.receiptPathHint}\n`);
|
|
2200
|
+
for (const note of review.predecessorDiff.riskNotes)
|
|
2201
|
+
process.stderr.write(` - ${note}\n`);
|
|
2202
|
+
process.stderr.write(` - reason: ${review.predecessorDiff.reason}\n`);
|
|
2203
|
+
}
|
|
2204
|
+
process.stderr.write("\nChecks:\n");
|
|
2205
|
+
for (const check of review.checks) {
|
|
2206
|
+
process.stderr.write(` - [${check.status}] ${check.label}: ${check.detail}\n`);
|
|
2207
|
+
}
|
|
2208
|
+
process.stderr.write("\nAdoption path:\n");
|
|
2209
|
+
for (const step of review.adoptionPath) {
|
|
2210
|
+
process.stderr.write(` - ${step.command}\n`);
|
|
2211
|
+
process.stderr.write(` ${step.purpose}\n`);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
function writeAssetSuggestions(review) {
|
|
2215
|
+
const label = review.displayName ? `${review.asset} (${review.displayName})` : review.asset;
|
|
2216
|
+
process.stderr.write(`Workflow asset suggestions: ${label}\n`);
|
|
2217
|
+
process.stderr.write(`Mode: ${review.mode}\n`);
|
|
2218
|
+
if (review.evidence.source)
|
|
2219
|
+
process.stderr.write(`Evidence source: ${review.evidence.source}\n`);
|
|
2220
|
+
process.stderr.write(`Dogfood evidence: ${review.evidence.pass}/${review.evidence.modules} PASS, continue=${review.evidence.continue}, blocked=${review.evidence.blocked}, remaining=${review.evidence.remaining}\n`);
|
|
2221
|
+
process.stderr.write(`C5 capture facts: ${review.evidence.captureFacts}, blocked=${review.evidence.captureBlocked}, failures=${review.evidence.captureFailures}, manual=${review.evidence.captureManualInterventions}, retries=${review.evidence.captureRetries}\n`);
|
|
2222
|
+
if (review.evidence.warnings.length > 0) {
|
|
2223
|
+
process.stderr.write("\nEvidence warnings:\n");
|
|
2224
|
+
for (const warning of review.evidence.warnings)
|
|
2225
|
+
process.stderr.write(` - ${warning}\n`);
|
|
2226
|
+
}
|
|
2227
|
+
process.stderr.write("\nSuggestions:\n");
|
|
2228
|
+
for (const suggestion of review.suggestions) {
|
|
2229
|
+
process.stderr.write(` - [${suggestion.severity}] ${suggestion.title}\n`);
|
|
2230
|
+
process.stderr.write(` type: ${suggestion.type}\n`);
|
|
2231
|
+
process.stderr.write(` evidence: ${suggestion.evidence}\n`);
|
|
2232
|
+
if (suggestion.evidenceRefs && suggestion.evidenceRefs.length > 0) {
|
|
2233
|
+
process.stderr.write(" evidence refs:\n");
|
|
2234
|
+
for (const ref of suggestion.evidenceRefs.slice(0, 5))
|
|
2235
|
+
process.stderr.write(` - ${ref.type}: ${ref.ref}\n`);
|
|
2236
|
+
if (suggestion.evidenceRefs.length > 5)
|
|
2237
|
+
process.stderr.write(` - ... ${suggestion.evidenceRefs.length - 5} more\n`);
|
|
2238
|
+
}
|
|
2239
|
+
process.stderr.write(` rationale: ${suggestion.rationale}\n`);
|
|
2240
|
+
process.stderr.write(` next: ${suggestion.nextAction}\n`);
|
|
2241
|
+
if (suggestion.nextCommands && suggestion.nextCommands.length > 0) {
|
|
2242
|
+
process.stderr.write(" next commands:\n");
|
|
2243
|
+
for (const command of suggestion.nextCommands)
|
|
2244
|
+
process.stderr.write(` - ${command}\n`);
|
|
2245
|
+
}
|
|
2246
|
+
if (suggestion.mutationBoundary)
|
|
2247
|
+
process.stderr.write(` mutation boundary: ${suggestion.mutationBoundary}\n`);
|
|
2248
|
+
}
|
|
2249
|
+
process.stderr.write("\nImprovement route:\n");
|
|
2250
|
+
for (const step of review.improvementRoute) {
|
|
2251
|
+
process.stderr.write(` - ${step.id}. ${step.label} (${step.owner}; writes: ${step.writes ? "yes" : "no"})\n`);
|
|
2252
|
+
process.stderr.write(` command: ${step.command}\n`);
|
|
2253
|
+
process.stderr.write(` why: ${step.purpose}\n`);
|
|
2254
|
+
}
|
|
2255
|
+
process.stderr.write("\nNext commands:\n");
|
|
2256
|
+
for (const command of review.nextCommands)
|
|
2257
|
+
process.stderr.write(` - ${command}\n`);
|
|
2258
|
+
process.stderr.write("\nMutation boundary:\n");
|
|
2259
|
+
process.stderr.write(` - writes: ${review.mutation.writes ? "yes" : "no"}\n`);
|
|
2260
|
+
process.stderr.write(` - ${review.mutation.note}\n`);
|
|
2261
|
+
}
|
|
2262
|
+
function writeAssetCandidateBacklog(backlog) {
|
|
2263
|
+
process.stderr.write("Workflow candidate backlog\n");
|
|
2264
|
+
process.stderr.write(`Mode: ${backlog.mode}\n`);
|
|
2265
|
+
process.stderr.write(`Scope: ${backlog.scope}\n`);
|
|
2266
|
+
if (backlog.focusAsset)
|
|
2267
|
+
process.stderr.write(`Focus asset: ${backlog.focusAsset}\n`);
|
|
2268
|
+
process.stderr.write(`Assets scanned: ${backlog.summary.assetsScanned}\n`);
|
|
2269
|
+
process.stderr.write(`Dogfood assets: ${backlog.summary.dogfoodAssets}\n`);
|
|
2270
|
+
process.stderr.write(`Evidence modules: ${backlog.summary.pass}/${backlog.summary.evidenceModules} PASS\n`);
|
|
2271
|
+
process.stderr.write(`Active modules: ${backlog.summary.activeModules}\n`);
|
|
2272
|
+
process.stderr.write(`C5 capture facts: ${backlog.summary.captureFacts} (gaps=${backlog.summary.captureGaps})\n`);
|
|
2273
|
+
process.stderr.write(`Candidates: ${backlog.summary.candidates}\n`);
|
|
2274
|
+
process.stderr.write("\nCandidate review packet:\n");
|
|
2275
|
+
process.stderr.write(` current: ${backlog.reviewPacket.currentState}\n`);
|
|
2276
|
+
process.stderr.write(` decision: ${backlog.reviewPacket.decision}\n`);
|
|
2277
|
+
process.stderr.write(` primary action: ${backlog.reviewPacket.primaryAction}\n`);
|
|
2278
|
+
process.stderr.write(` reason: ${backlog.reviewPacket.reason}\n`);
|
|
2279
|
+
process.stderr.write(" allowed now:\n");
|
|
2280
|
+
for (const item of backlog.reviewPacket.allowedNow)
|
|
2281
|
+
process.stderr.write(` - ${item}\n`);
|
|
2282
|
+
process.stderr.write(" blocked now:\n");
|
|
2283
|
+
for (const item of backlog.reviewPacket.blockedNow)
|
|
2284
|
+
process.stderr.write(` - ${item}\n`);
|
|
2285
|
+
process.stderr.write(" review path:\n");
|
|
2286
|
+
for (const command of backlog.reviewPacket.reviewPath)
|
|
2287
|
+
process.stderr.write(` - ${command}\n`);
|
|
2288
|
+
process.stderr.write("\nCandidates:\n");
|
|
2289
|
+
if (backlog.candidates.length === 0) {
|
|
2290
|
+
process.stderr.write(" - none\n");
|
|
2291
|
+
}
|
|
2292
|
+
else {
|
|
2293
|
+
for (const candidate of backlog.candidates) {
|
|
2294
|
+
process.stderr.write(` - [${candidate.priority}] ${candidate.title}\n`);
|
|
2295
|
+
process.stderr.write(` id: ${candidate.id}\n`);
|
|
2296
|
+
process.stderr.write(` type: ${candidate.type}\n`);
|
|
2297
|
+
process.stderr.write(candidate.type === "active_evidence_followup"
|
|
2298
|
+
? ` saved draft: ${candidate.candidateAssetName} (unfinished work; source asset is preserved)\n`
|
|
2299
|
+
: ` candidate asset: ${candidate.candidateAssetName}\n`);
|
|
2300
|
+
process.stderr.write(` source asset: ${candidate.sourceAsset}\n`);
|
|
2301
|
+
process.stderr.write(` source workflows: ${candidate.sourceWorkflows.length > 0 ? candidate.sourceWorkflows.join(", ") : "none"}\n`);
|
|
2302
|
+
process.stderr.write(` evidence: ${candidate.evidence}\n`);
|
|
2303
|
+
process.stderr.write(` rationale: ${candidate.rationale}\n`);
|
|
2304
|
+
if (candidate.plainLanguage) {
|
|
2305
|
+
process.stderr.write(" plain meaning:\n");
|
|
2306
|
+
process.stderr.write(` label: ${candidate.plainLanguage.label}\n`);
|
|
2307
|
+
process.stderr.write(` purpose: ${candidate.plainLanguage.purpose}\n`);
|
|
2308
|
+
process.stderr.write(` improvement: ${candidate.plainLanguage.workflowImprovement}\n`);
|
|
2309
|
+
if (candidate.type === "active_evidence_followup") {
|
|
2310
|
+
process.stderr.write(` why follow-up: ${candidate.plainLanguage.purpose}\n`);
|
|
2311
|
+
process.stderr.write(" not a deletion: the source asset stays available until an explicit diff, adoption, organization bind, and rollout review accept the draft.\n");
|
|
2312
|
+
}
|
|
2313
|
+
process.stderr.write(" preserves:\n");
|
|
2314
|
+
for (const item of candidate.plainLanguage.preserves)
|
|
2315
|
+
process.stderr.write(` - ${item}\n`);
|
|
2316
|
+
process.stderr.write(" required before use:\n");
|
|
2317
|
+
for (const item of candidate.plainLanguage.requiredBeforeUse)
|
|
2318
|
+
process.stderr.write(` - ${item}\n`);
|
|
2319
|
+
}
|
|
2320
|
+
process.stderr.write(` next: ${candidate.nextCommand}\n`);
|
|
2321
|
+
process.stderr.write(` boundary: ${candidate.mutationBoundary}\n`);
|
|
2322
|
+
if (candidate.continuityBoundary)
|
|
2323
|
+
process.stderr.write(` continuity: ${candidate.continuityBoundary}\n`);
|
|
2324
|
+
process.stderr.write(" action path:\n");
|
|
2325
|
+
for (const step of candidate.actionPath) {
|
|
2326
|
+
process.stderr.write(` - ${step.id}: ${step.status} writes=${step.writes ? "yes" : "no"} command=${step.command}\n`);
|
|
2327
|
+
process.stderr.write(` ${step.purpose}\n`);
|
|
2328
|
+
}
|
|
2329
|
+
for (const command of candidate.reviewCommands.slice(0, 4))
|
|
2330
|
+
process.stderr.write(` review: ${command}\n`);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
process.stderr.write("\nNext commands:\n");
|
|
2334
|
+
for (const command of backlog.nextCommands)
|
|
2335
|
+
process.stderr.write(` - ${command}\n`);
|
|
2336
|
+
process.stderr.write("\nMutation boundary:\n");
|
|
2337
|
+
process.stderr.write(` - writes: ${backlog.mutation.writes ? "yes" : "no"}\n`);
|
|
2338
|
+
process.stderr.write(` - ${backlog.mutation.note}\n`);
|
|
2339
|
+
}
|
|
2340
|
+
function writeAssetLifecycle(review) {
|
|
2341
|
+
const label = review.displayName ? `${review.asset} (${review.displayName})` : review.asset;
|
|
2342
|
+
process.stderr.write(`Workflow asset lifecycle: ${label}\n`);
|
|
2343
|
+
process.stderr.write(`Mode: ${review.mode}\n`);
|
|
2344
|
+
process.stderr.write(`Summary: ${review.summary}\n`);
|
|
2345
|
+
writeManagerGates(review.managerGates);
|
|
2346
|
+
process.stderr.write("\nSystem route:\n");
|
|
2347
|
+
process.stderr.write(` - principle: ${review.systemRoute.principle}\n`);
|
|
2348
|
+
for (const phase of review.systemRoute.phases) {
|
|
2349
|
+
process.stderr.write(` - ${phase.id}. ${phase.label}\n`);
|
|
2350
|
+
process.stderr.write(` command: ${phase.command}\n`);
|
|
2351
|
+
process.stderr.write(` owner: ${phase.owner}\n`);
|
|
2352
|
+
process.stderr.write(` writes: ${phase.writes ? "yes" : "no"}\n`);
|
|
2353
|
+
process.stderr.write(` purpose: ${phase.purpose}\n`);
|
|
2354
|
+
}
|
|
2355
|
+
process.stderr.write(" - boundaries:\n");
|
|
2356
|
+
for (const boundary of review.systemRoute.boundaries)
|
|
2357
|
+
process.stderr.write(` - ${boundary}\n`);
|
|
2358
|
+
process.stderr.write("\nAsset path:\n");
|
|
2359
|
+
for (const step of review.assetPath) {
|
|
2360
|
+
process.stderr.write(` - ${step.id}: ${step.status} owner=${step.owner} writes=${step.writes ? "yes" : "no"}\n`);
|
|
2361
|
+
process.stderr.write(` label: ${step.label}\n`);
|
|
2362
|
+
process.stderr.write(` command: ${step.command}\n`);
|
|
2363
|
+
process.stderr.write(` next: ${step.next}\n`);
|
|
2364
|
+
process.stderr.write(` purpose: ${step.purpose}\n`);
|
|
2365
|
+
process.stderr.write(` sources: ${step.sourceIds.join(", ")}\n`);
|
|
2366
|
+
}
|
|
2367
|
+
process.stderr.write("\nCascade stack:\n");
|
|
2368
|
+
if (review.cascade.layerType)
|
|
2369
|
+
process.stderr.write(` - layer type: ${review.cascade.layerType}\n`);
|
|
2370
|
+
if (review.cascade.priority !== undefined)
|
|
2371
|
+
process.stderr.write(` - priority: ${review.cascade.priority}\n`);
|
|
2372
|
+
process.stderr.write(` - inherits: ${review.cascade.inherits.length > 0 ? review.cascade.inherits.join(", ") : "none"}\n`);
|
|
2373
|
+
process.stderr.write(` - compiler effect: ${review.cascade.compilerEffect}\n`);
|
|
2374
|
+
process.stderr.write("\nEvidence loop:\n");
|
|
2375
|
+
process.stderr.write(` - status: ${review.evidence.status}\n`);
|
|
2376
|
+
if (review.evidence.source)
|
|
2377
|
+
process.stderr.write(` - source: ${review.evidence.source}\n`);
|
|
2378
|
+
process.stderr.write(` - modules: ${review.evidence.pass}/${review.evidence.modules} PASS\n`);
|
|
2379
|
+
process.stderr.write(` - blocked: ${review.evidence.blocked}\n`);
|
|
2380
|
+
process.stderr.write(` - remaining: ${review.evidence.remaining}\n`);
|
|
2381
|
+
process.stderr.write(` - next: ${review.evidence.nextCommand}\n`);
|
|
2382
|
+
process.stderr.write("\nEvolution boundary:\n");
|
|
2383
|
+
process.stderr.write(` - review mode: ${review.evolution.reviewMode}\n`);
|
|
2384
|
+
process.stderr.write(` - preview: ${review.evolution.previewCommand}\n`);
|
|
2385
|
+
process.stderr.write(` - explicit mutation: ${review.evolution.mutatingCommand}\n`);
|
|
2386
|
+
process.stderr.write(` - boundary: ${review.evolution.mutationBoundary}\n`);
|
|
2387
|
+
process.stderr.write("\nCurrent gate:\n");
|
|
2388
|
+
process.stderr.write(` - decision: ${review.currentGate.decision}\n`);
|
|
2389
|
+
process.stderr.write(` - reason: ${review.currentGate.reason}\n`);
|
|
2390
|
+
process.stderr.write(` - next: ${review.currentGate.nextCommand}\n`);
|
|
2391
|
+
process.stderr.write("\nLifecycle stages:\n");
|
|
2392
|
+
for (const stage of review.stages) {
|
|
2393
|
+
process.stderr.write(` - ${stage.id}. ${stage.label}\n`);
|
|
2394
|
+
process.stderr.write(` command: ${stage.command}\n`);
|
|
2395
|
+
process.stderr.write(` writes: ${stage.writes ? "yes" : "no"}\n`);
|
|
2396
|
+
process.stderr.write(` purpose: ${stage.purpose}\n`);
|
|
2397
|
+
}
|
|
2398
|
+
process.stderr.write("\nNext commands:\n");
|
|
2399
|
+
for (const command of review.nextCommands)
|
|
2400
|
+
process.stderr.write(` - ${command}\n`);
|
|
2401
|
+
}
|
|
2402
|
+
function writeEvidenceIndex(index) {
|
|
2403
|
+
process.stderr.write(`Workflow asset evidence: ${index.asset}\n`);
|
|
2404
|
+
if (index.sourceDescription)
|
|
2405
|
+
process.stderr.write(`Source: ${index.sourceDescription}\n`);
|
|
2406
|
+
if (index.projectPath)
|
|
2407
|
+
process.stderr.write(`Project: ${index.projectPath}\n`);
|
|
2408
|
+
if (index.progressDir)
|
|
2409
|
+
process.stderr.write(`Progress dir: ${index.progressDir}\n`);
|
|
2410
|
+
process.stderr.write(`Modules: ${index.totals.modules}\n`);
|
|
2411
|
+
process.stderr.write(`PASS: ${index.totals.pass}\n`);
|
|
2412
|
+
process.stderr.write(`CONTINUE: ${index.totals.continue}\n`);
|
|
2413
|
+
process.stderr.write(`REQUEST_CHANGES: ${index.totals.requestChanges}\n`);
|
|
2414
|
+
process.stderr.write(`BLOCKED: ${index.totals.blocked}\n`);
|
|
2415
|
+
process.stderr.write(`UNKNOWN: ${index.totals.unknown}\n`);
|
|
2416
|
+
process.stderr.write(`Complete: ${index.totals.complete}\n`);
|
|
2417
|
+
process.stderr.write(`Remaining: ${index.totals.remaining}\n`);
|
|
2418
|
+
process.stderr.write(`Remaining UNIMPLEMENTED skipped: ${index.totals.remainingUnimplementedSkipped}\n`);
|
|
2419
|
+
process.stderr.write(`New red: ${index.totals.newRed}\n`);
|
|
2420
|
+
process.stderr.write(`Runtime artifacts: ${index.totals.runtimeArtifacts}\n`);
|
|
2421
|
+
process.stderr.write(`Agent prompts: ${index.totals.agentPrompts}\n`);
|
|
2422
|
+
process.stderr.write(`Skill entrypoints: ${index.totals.skillEntrypoints}\n`);
|
|
2423
|
+
process.stderr.write(`C5 capture facts: ${index.totals.captureFacts}\n`);
|
|
2424
|
+
process.stderr.write(`C5 blocked facts: ${index.totals.captureBlocked}\n`);
|
|
2425
|
+
process.stderr.write(`C5 failure facts: ${index.totals.captureFailures}\n`);
|
|
2426
|
+
process.stderr.write(`C5 manual interventions: ${index.totals.captureManualInterventions}\n`);
|
|
2427
|
+
process.stderr.write(`C5 retries: ${index.totals.captureRetries}\n`);
|
|
2428
|
+
if (index.warnings.length > 0) {
|
|
2429
|
+
process.stderr.write("\nWarnings:\n");
|
|
2430
|
+
for (const warning of index.warnings)
|
|
2431
|
+
process.stderr.write(` - ${warning}\n`);
|
|
2432
|
+
}
|
|
2433
|
+
if (index.runtimeArtifacts.length > 0) {
|
|
2434
|
+
process.stderr.write("\nRuntime artifacts:\n");
|
|
2435
|
+
for (const artifact of index.runtimeArtifacts.slice(0, 20)) {
|
|
2436
|
+
const managed = artifact.managed ? "managed" : "unmanaged";
|
|
2437
|
+
const source = artifact.source ? ` source=${artifact.source}` : "";
|
|
2438
|
+
const roles = artifact.roles && artifact.roles.length > 0 ? ` roles=${artifact.roles.join(",")}` : "";
|
|
2439
|
+
const handoff = artifact.consumes !== undefined || artifact.produces !== undefined
|
|
2440
|
+
? ` handoff=${artifact.consumes ?? 0}/${artifact.produces ?? 0}`
|
|
2441
|
+
: "";
|
|
2442
|
+
process.stderr.write(` - ${artifact.target}/${artifact.kind}: ${artifact.name} ${managed}${source}${roles}${handoff}\n`);
|
|
2443
|
+
process.stderr.write(` path: ${artifact.path}\n`);
|
|
2444
|
+
if (artifact.bodyPath)
|
|
2445
|
+
process.stderr.write(` body: ${artifact.bodyPath}\n`);
|
|
2446
|
+
if (artifact.description)
|
|
2447
|
+
process.stderr.write(` description: ${artifact.description}\n`);
|
|
2448
|
+
}
|
|
2449
|
+
if (index.runtimeArtifacts.length > 20) {
|
|
2450
|
+
process.stderr.write(` ... ${index.runtimeArtifacts.length - 20} more runtime artifact(s). Use --json for full details.\n`);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
if (index.captureFacts.length > 0) {
|
|
2454
|
+
process.stderr.write("\nC5 capture facts:\n");
|
|
2455
|
+
for (const capture of index.captureFacts.slice(0, 20)) {
|
|
2456
|
+
const result = capture.result ? ` result=${capture.result}` : "";
|
|
2457
|
+
process.stderr.write(` - ${capture.capturedAt}: ${capture.fact}${result} workflow=${capture.workflowId} step=${capture.stepId}\n`);
|
|
2458
|
+
process.stderr.write(` summary: ${capture.summary}\n`);
|
|
2459
|
+
process.stderr.write(` path: ${capture.path}\n`);
|
|
2460
|
+
}
|
|
2461
|
+
if (index.captureFacts.length > 20) {
|
|
2462
|
+
process.stderr.write(` ... ${index.captureFacts.length - 20} more capture fact(s). Use --json for full details.\n`);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
if (index.modules.length > 0) {
|
|
2466
|
+
process.stderr.write("\nModules:\n");
|
|
2467
|
+
for (const module of index.modules.slice(0, 20)) {
|
|
2468
|
+
const complete = module.moduleComplete ? "complete" : "incomplete";
|
|
2469
|
+
const remaining = module.remainingCount > 0 ? ` remaining=${module.remainingCount}` : "";
|
|
2470
|
+
const skipped = module.remainingUnimplementedSkippedCount > 0 ? ` unimplemented_skipped=${module.remainingUnimplementedSkippedCount}` : "";
|
|
2471
|
+
process.stderr.write(` - ${module.module}: ${module.verdict} ${complete} round=${module.round ?? "unknown"} fixed=${module.fixedCount}${remaining}${skipped}\n`);
|
|
2472
|
+
if (module.nextRoundFocus)
|
|
2473
|
+
process.stderr.write(` next: ${module.nextRoundFocus}\n`);
|
|
2474
|
+
if (module.failureReason)
|
|
2475
|
+
process.stderr.write(` failure: ${module.failureReason}\n`);
|
|
2476
|
+
process.stderr.write(` progress: ${module.progressJsonPath}\n`);
|
|
2477
|
+
}
|
|
2478
|
+
if (index.modules.length > 20) {
|
|
2479
|
+
process.stderr.write(` ... ${index.modules.length - 20} more module(s). Use --json for full evidence paths.\n`);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
function writeAssetHeader(asset) {
|
|
2484
|
+
process.stderr.write(`Asset: ${asset.name}\n`);
|
|
2485
|
+
if (asset.dnaId)
|
|
2486
|
+
process.stderr.write(`DNA id: ${asset.dnaId}\n`);
|
|
2487
|
+
process.stderr.write(`Source: ${asset.source}\n`);
|
|
2488
|
+
process.stderr.write(`Path: ${asset.path}\n`);
|
|
2489
|
+
if (asset.displayName)
|
|
2490
|
+
process.stderr.write(`Name: ${asset.displayName}\n`);
|
|
2491
|
+
if (asset.namespace)
|
|
2492
|
+
process.stderr.write(`Namespace: ${asset.namespace}\n`);
|
|
2493
|
+
if (asset.version)
|
|
2494
|
+
process.stderr.write(`Version: ${asset.version}\n`);
|
|
2495
|
+
process.stderr.write(`Content hash: ${asset.contentHash}\n`);
|
|
2496
|
+
if (asset.positioning)
|
|
2497
|
+
process.stderr.write(`Positioning: ${asset.positioning}\n`);
|
|
2498
|
+
if (asset.keywords.length > 0)
|
|
2499
|
+
process.stderr.write(`Keywords: ${asset.keywords.join(", ")}\n`);
|
|
2500
|
+
process.stderr.write(`Status: ${asset.status.managementStatus} (${asset.status.evidencePosture})\n`);
|
|
2501
|
+
process.stderr.write(`Project config: ${asset.status.configurationPosture}\n`);
|
|
2502
|
+
process.stderr.write(`Cascade: ${asset.status.cascadePosture}\n`);
|
|
2503
|
+
process.stderr.write(`Evolution: ${asset.status.evolutionPosture}\n`);
|
|
2504
|
+
}
|
|
2505
|
+
function writeAssetStructure(asset) {
|
|
2506
|
+
process.stderr.write(`\nWorkflows (${asset.workflows.length}):\n`);
|
|
2507
|
+
for (const workflow of asset.workflows) {
|
|
2508
|
+
process.stderr.write(` - ${workflow.id}: ${workflow.name} (${workflow.stepCount} step(s))`);
|
|
2509
|
+
if (workflow.description)
|
|
2510
|
+
process.stderr.write(` — ${workflow.description}`);
|
|
2511
|
+
process.stderr.write("\n");
|
|
2512
|
+
}
|
|
2513
|
+
process.stderr.write(`\nControllers (${asset.controllers.length}):\n`);
|
|
2514
|
+
for (const controller of asset.controllers) {
|
|
2515
|
+
const rounds = controller.maxRounds !== undefined ? ` max_rounds=${controller.maxRounds}` : "";
|
|
2516
|
+
process.stderr.write(` - ${controller.id}: ${controller.kind}${rounds}`);
|
|
2517
|
+
if (controller.progressArtifactPath)
|
|
2518
|
+
process.stderr.write(` progress=${controller.progressArtifactPath}`);
|
|
2519
|
+
process.stderr.write("\n");
|
|
2520
|
+
}
|
|
2521
|
+
process.stderr.write(`\nRoles (${asset.roles.length}):\n`);
|
|
2522
|
+
for (const role of asset.roles) {
|
|
2523
|
+
const writes = role.writeScope.length > 0 ? role.writeScope.join(", ") : "none";
|
|
2524
|
+
process.stderr.write(` - ${role.id}: write=${writes}`);
|
|
2525
|
+
if (role.description)
|
|
2526
|
+
process.stderr.write(` — ${role.description}`);
|
|
2527
|
+
process.stderr.write("\n");
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
function writeAssetEvidence(asset) {
|
|
2531
|
+
process.stderr.write(`\nEvidence links (${asset.evidence.length}):\n`);
|
|
2532
|
+
if (asset.evidence.length === 0) {
|
|
2533
|
+
process.stderr.write(" - none registered\n");
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
for (const evidence of asset.evidence) {
|
|
2537
|
+
process.stderr.write(` - ${evidence.label} [${evidence.kind}]: ${evidence.path}`);
|
|
2538
|
+
if (evidence.description)
|
|
2539
|
+
process.stderr.write(` — ${evidence.description}`);
|
|
2540
|
+
process.stderr.write("\n");
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
function writeAssetGovernance(asset) {
|
|
2544
|
+
process.stderr.write("\nCascade:\n");
|
|
2545
|
+
process.stderr.write(` - posture: ${asset.status.cascadePosture}\n`);
|
|
2546
|
+
if (asset.cascade.type)
|
|
2547
|
+
process.stderr.write(` - layer type: ${asset.cascade.type}\n`);
|
|
2548
|
+
if (asset.cascade.priority !== undefined)
|
|
2549
|
+
process.stderr.write(` - priority: ${asset.cascade.priority}\n`);
|
|
2550
|
+
process.stderr.write(` - inherits: ${asset.cascade.inherits.length > 0 ? asset.cascade.inherits.join(", ") : "none"}\n`);
|
|
2551
|
+
process.stderr.write("\nEvolution:\n");
|
|
2552
|
+
process.stderr.write(` - posture: ${asset.status.evolutionPosture}\n`);
|
|
2553
|
+
process.stderr.write(` - review mode: ${asset.evolution.reviewMode}\n`);
|
|
2554
|
+
process.stderr.write(` - declared markers: ${asset.evolution.declaredMarkers}\n`);
|
|
2555
|
+
if (asset.evolution.markerIds.length > 0) {
|
|
2556
|
+
process.stderr.write(` - marker ids: ${asset.evolution.markerIds.join(", ")}\n`);
|
|
2557
|
+
}
|
|
2558
|
+
process.stderr.write(` - outcome store: ${asset.evolution.outcomeStore}\n`);
|
|
2559
|
+
process.stderr.write(` - preview: ${asset.evolution.previewCommand}\n`);
|
|
2560
|
+
process.stderr.write(` - explicit mutation: ${asset.evolution.mutatingCommand}\n`);
|
|
2561
|
+
}
|
|
2562
|
+
function writeAssetStatus(asset) {
|
|
2563
|
+
process.stderr.write(`Asset: ${asset.name}\n`);
|
|
2564
|
+
if (asset.displayName)
|
|
2565
|
+
process.stderr.write(`Name: ${asset.displayName}\n`);
|
|
2566
|
+
process.stderr.write(`Status: ${asset.status.managementStatus}\n`);
|
|
2567
|
+
process.stderr.write(`Evidence posture: ${asset.status.evidencePosture}\n`);
|
|
2568
|
+
process.stderr.write(`Project config: ${asset.status.configurationPosture}\n`);
|
|
2569
|
+
process.stderr.write(`Configured in project: ${asset.status.configured ? "yes" : "no"}\n`);
|
|
2570
|
+
if (asset.status.configPaths.length > 0) {
|
|
2571
|
+
process.stderr.write("Config paths:\n");
|
|
2572
|
+
for (const configPath of asset.status.configPaths)
|
|
2573
|
+
process.stderr.write(` - ${configPath}\n`);
|
|
2574
|
+
}
|
|
2575
|
+
process.stderr.write(`Workflow defined: ${asset.status.hasWorkflow ? "yes" : "no"}\n`);
|
|
2576
|
+
process.stderr.write(`Controller defined: ${asset.status.hasController ? "yes" : "no"}\n`);
|
|
2577
|
+
process.stderr.write(`Cascade declared: ${asset.status.hasCascade ? "yes" : "no"}\n`);
|
|
2578
|
+
process.stderr.write(`Evolution posture: ${asset.status.evolutionPosture}\n`);
|
|
2579
|
+
process.stderr.write(`Evidence linked: ${asset.status.hasEvidence ? "yes" : "no"}\n`);
|
|
2580
|
+
process.stderr.write(`Counts: ${formatCounts(asset)}\n`);
|
|
2581
|
+
if (asset.status.notes.length > 0) {
|
|
2582
|
+
process.stderr.write("Notes:\n");
|
|
2583
|
+
for (const note of asset.status.notes)
|
|
2584
|
+
process.stderr.write(` - ${note}\n`);
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
function writeAssetInspection(asset, includeEvidence) {
|
|
2588
|
+
writeAssetHeader(asset);
|
|
2589
|
+
writeAssetGovernance(asset);
|
|
2590
|
+
writeAssetStructure(asset);
|
|
2591
|
+
process.stderr.write("\nSteps:\n");
|
|
2592
|
+
for (const workflow of asset.workflows) {
|
|
2593
|
+
process.stderr.write(` ${workflow.id}:\n`);
|
|
2594
|
+
for (const step of workflow.steps) {
|
|
2595
|
+
const deps = step.dependsOn.length > 0 ? ` after=${step.dependsOn.join(",")}` : "";
|
|
2596
|
+
const handoff = step.consumes + step.produces > 0 ? ` handoff=${step.consumes}/${step.produces}` : "";
|
|
2597
|
+
const checkpoints = step.checkpoints > 0 ? ` checkpoints=${step.checkpoints}` : "";
|
|
2598
|
+
process.stderr.write(` - ${step.id} -> ${step.role}${deps}${handoff}${checkpoints}`);
|
|
2599
|
+
if (step.description)
|
|
2600
|
+
process.stderr.write(` — ${step.description}`);
|
|
2601
|
+
process.stderr.write("\n");
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
if (includeEvidence)
|
|
2605
|
+
writeAssetEvidence(asset);
|
|
2606
|
+
}
|
|
2607
|
+
async function loadAsset(name, projectDir) {
|
|
2608
|
+
if (!name)
|
|
2609
|
+
return undefined;
|
|
2610
|
+
return findWorkflowAsset(name, { projectDir });
|
|
2611
|
+
}
|
|
2612
|
+
export async function runAssets(opts) {
|
|
2613
|
+
const subcommand = opts.subcommand || "list";
|
|
2614
|
+
if (subcommand === "diff") {
|
|
2615
|
+
const from = await loadAsset(opts.name, opts.projectDir);
|
|
2616
|
+
const to = await loadAsset(opts.compareTo, opts.projectDir);
|
|
2617
|
+
if (!opts.name || !opts.compareTo) {
|
|
2618
|
+
process.stderr.write("Usage: dna assets diff <from> <to> [--json]\n");
|
|
2619
|
+
return 2;
|
|
2620
|
+
}
|
|
2621
|
+
if (!from) {
|
|
2622
|
+
process.stderr.write(`Unknown workflow asset: ${opts.name}\n`);
|
|
2623
|
+
return 2;
|
|
2624
|
+
}
|
|
2625
|
+
if (!to) {
|
|
2626
|
+
process.stderr.write(`Unknown workflow asset: ${opts.compareTo}\n`);
|
|
2627
|
+
return 2;
|
|
2628
|
+
}
|
|
2629
|
+
const diff = buildAssetDiff(from, to);
|
|
2630
|
+
if (opts.json) {
|
|
2631
|
+
process.stdout.write(JSON.stringify(diff, null, 2) + "\n");
|
|
2632
|
+
return 0;
|
|
2633
|
+
}
|
|
2634
|
+
writeAssetDiff(diff);
|
|
2635
|
+
return 0;
|
|
2636
|
+
}
|
|
2637
|
+
if (subcommand === "evidence") {
|
|
2638
|
+
if (!opts.name) {
|
|
2639
|
+
process.stderr.write("Usage: dna assets evidence <name> [--json]\n");
|
|
2640
|
+
return 2;
|
|
2641
|
+
}
|
|
2642
|
+
const index = await buildWorkflowAssetEvidenceIndex(opts.name, { projectDir: opts.projectDir });
|
|
2643
|
+
if (!index) {
|
|
2644
|
+
process.stderr.write(`Unknown workflow asset: ${opts.name}\n`);
|
|
2645
|
+
return 2;
|
|
2646
|
+
}
|
|
2647
|
+
if (opts.json) {
|
|
2648
|
+
process.stdout.write(JSON.stringify(index, null, 2) + "\n");
|
|
2649
|
+
return 0;
|
|
2650
|
+
}
|
|
2651
|
+
writeEvidenceIndex(index);
|
|
2652
|
+
return 0;
|
|
2653
|
+
}
|
|
2654
|
+
if (subcommand === "review") {
|
|
2655
|
+
const assets = await listWorkflowAssets({ projectDir: opts.projectDir });
|
|
2656
|
+
const review = await buildAssetReview(assets, opts.projectDir);
|
|
2657
|
+
if (opts.json) {
|
|
2658
|
+
process.stdout.write(JSON.stringify(review, null, 2) + "\n");
|
|
2659
|
+
return 0;
|
|
2660
|
+
}
|
|
2661
|
+
writeAssetReview(review, assets);
|
|
2662
|
+
return 0;
|
|
2663
|
+
}
|
|
2664
|
+
if (subcommand === "library") {
|
|
2665
|
+
const assets = await listWorkflowAssets({ projectDir: opts.projectDir });
|
|
2666
|
+
const library = buildAssetLibrary(assets);
|
|
2667
|
+
if (opts.json) {
|
|
2668
|
+
process.stdout.write(JSON.stringify(library, null, 2) + "\n");
|
|
2669
|
+
return 0;
|
|
2670
|
+
}
|
|
2671
|
+
writeAssetLibrary(library);
|
|
2672
|
+
return 0;
|
|
2673
|
+
}
|
|
2674
|
+
if (subcommand === "candidates") {
|
|
2675
|
+
const backlog = await buildWorkflowAssetCandidateBacklog({
|
|
2676
|
+
name: opts.name,
|
|
2677
|
+
projectDir: opts.projectDir,
|
|
2678
|
+
});
|
|
2679
|
+
if (!backlog) {
|
|
2680
|
+
process.stderr.write(`Unknown workflow asset: ${opts.name}\n`);
|
|
2681
|
+
return 2;
|
|
2682
|
+
}
|
|
2683
|
+
if (opts.json) {
|
|
2684
|
+
process.stdout.write(JSON.stringify(backlog, null, 2) + "\n");
|
|
2685
|
+
return 0;
|
|
2686
|
+
}
|
|
2687
|
+
writeAssetCandidateBacklog(backlog);
|
|
2688
|
+
return 0;
|
|
2689
|
+
}
|
|
2690
|
+
if (subcommand === "lineage") {
|
|
2691
|
+
const asset = await loadAsset(opts.name, opts.projectDir);
|
|
2692
|
+
if (!asset) {
|
|
2693
|
+
process.stderr.write(opts.name ? `Unknown workflow asset: ${opts.name}\n` : "Usage: dna assets lineage <name> [--json]\n");
|
|
2694
|
+
return 2;
|
|
2695
|
+
}
|
|
2696
|
+
const evidence = await buildWorkflowAssetEvidenceIndex(asset.name, { projectDir: opts.projectDir });
|
|
2697
|
+
const review = buildLineageReview(asset, evidence);
|
|
2698
|
+
if (opts.json) {
|
|
2699
|
+
process.stdout.write(JSON.stringify(review, null, 2) + "\n");
|
|
2700
|
+
return 0;
|
|
2701
|
+
}
|
|
2702
|
+
writeLineageReview(review);
|
|
2703
|
+
return 0;
|
|
2704
|
+
}
|
|
2705
|
+
if (subcommand === "governance") {
|
|
2706
|
+
const asset = await loadAsset(opts.name, opts.projectDir);
|
|
2707
|
+
if (!asset) {
|
|
2708
|
+
process.stderr.write(opts.name ? `Unknown workflow asset: ${opts.name}\n` : "Usage: dna assets governance <name> [--json]\n");
|
|
2709
|
+
return 2;
|
|
2710
|
+
}
|
|
2711
|
+
const evidence = await buildWorkflowAssetEvidenceIndex(asset.name, { projectDir: opts.projectDir });
|
|
2712
|
+
const assets = await listWorkflowAssets({ projectDir: opts.projectDir });
|
|
2713
|
+
const predecessor = findPredecessorAsset(asset, assets);
|
|
2714
|
+
const sourceEvidence = predecessor
|
|
2715
|
+
? await buildWorkflowAssetEvidenceIndex(predecessor.name, { projectDir: opts.projectDir })
|
|
2716
|
+
: evidence;
|
|
2717
|
+
const review = buildGovernanceReview(asset, sourceEvidence, assets, predecessor ?? asset);
|
|
2718
|
+
if (opts.json) {
|
|
2719
|
+
process.stdout.write(JSON.stringify(review, null, 2) + "\n");
|
|
2720
|
+
return 0;
|
|
2721
|
+
}
|
|
2722
|
+
writeGovernanceReview(review);
|
|
2723
|
+
return 0;
|
|
2724
|
+
}
|
|
2725
|
+
if (subcommand === "lifecycle") {
|
|
2726
|
+
const asset = await loadAsset(opts.name, opts.projectDir);
|
|
2727
|
+
if (!asset) {
|
|
2728
|
+
process.stderr.write(opts.name ? `Unknown workflow asset: ${opts.name}\n` : "Usage: dna assets lifecycle <name> [--json]\n");
|
|
2729
|
+
return 2;
|
|
2730
|
+
}
|
|
2731
|
+
const evidence = await buildWorkflowAssetEvidenceIndex(asset.name, { projectDir: opts.projectDir });
|
|
2732
|
+
const assets = await listWorkflowAssets({ projectDir: opts.projectDir });
|
|
2733
|
+
const review = buildAssetLifecycle(asset, evidence, assets);
|
|
2734
|
+
if (opts.json) {
|
|
2735
|
+
process.stdout.write(JSON.stringify(review, null, 2) + "\n");
|
|
2736
|
+
return 0;
|
|
2737
|
+
}
|
|
2738
|
+
writeAssetLifecycle(review);
|
|
2739
|
+
return 0;
|
|
2740
|
+
}
|
|
2741
|
+
if (subcommand === "adoption") {
|
|
2742
|
+
const loadedAsset = await loadAsset(opts.name, opts.projectDir);
|
|
2743
|
+
if (!loadedAsset) {
|
|
2744
|
+
process.stderr.write(opts.name ? `Unknown workflow asset: ${opts.name}\n` : "Usage: dna assets adoption <name> [--json]\n");
|
|
2745
|
+
return 2;
|
|
2746
|
+
}
|
|
2747
|
+
const asset = await normalizeAssetForReview(loadedAsset, opts.projectDir);
|
|
2748
|
+
const assets = await Promise.all((await listWorkflowAssets({ projectDir: opts.projectDir }))
|
|
2749
|
+
.map((entry) => normalizeAssetForReview(entry, opts.projectDir)));
|
|
2750
|
+
const predecessor = findPredecessorAsset(asset, assets);
|
|
2751
|
+
const predecessorEvidence = predecessor
|
|
2752
|
+
? await buildWorkflowAssetEvidenceIndex(predecessor.name, { projectDir: opts.projectDir })
|
|
2753
|
+
: undefined;
|
|
2754
|
+
const review = buildAdoptionReview(asset, assets, predecessorEvidence);
|
|
2755
|
+
if (opts.json) {
|
|
2756
|
+
process.stdout.write(JSON.stringify(review, null, 2) + "\n");
|
|
2757
|
+
return 0;
|
|
2758
|
+
}
|
|
2759
|
+
writeAdoptionReview(review);
|
|
2760
|
+
return 0;
|
|
2761
|
+
}
|
|
2762
|
+
if (subcommand === "suggestions") {
|
|
2763
|
+
const asset = await loadAsset(opts.name, opts.projectDir);
|
|
2764
|
+
if (!asset) {
|
|
2765
|
+
process.stderr.write(opts.name ? `Unknown workflow asset: ${opts.name}\n` : "Usage: dna assets suggestions <name> [--json]\n");
|
|
2766
|
+
return 2;
|
|
2767
|
+
}
|
|
2768
|
+
const index = await buildWorkflowAssetEvidenceIndex(asset.name, { projectDir: opts.projectDir });
|
|
2769
|
+
if (!index) {
|
|
2770
|
+
process.stderr.write(`Unknown workflow asset: ${asset.name}\n`);
|
|
2771
|
+
return 2;
|
|
2772
|
+
}
|
|
2773
|
+
const assets = await listWorkflowAssets({ projectDir: opts.projectDir });
|
|
2774
|
+
const review = buildAssetSuggestions(asset, index, assets);
|
|
2775
|
+
if (opts.json) {
|
|
2776
|
+
process.stdout.write(JSON.stringify(review, null, 2) + "\n");
|
|
2777
|
+
return 0;
|
|
2778
|
+
}
|
|
2779
|
+
writeAssetSuggestions(review);
|
|
2780
|
+
return 0;
|
|
2781
|
+
}
|
|
2782
|
+
if (subcommand === "list") {
|
|
2783
|
+
const assets = await listWorkflowAssets({ projectDir: opts.projectDir });
|
|
2784
|
+
if (opts.json) {
|
|
2785
|
+
process.stdout.write(JSON.stringify(assets, null, 2) + "\n");
|
|
2786
|
+
return 0;
|
|
2787
|
+
}
|
|
2788
|
+
process.stderr.write(`Workflow assets (${assets.length}):\n`);
|
|
2789
|
+
for (const asset of assets) {
|
|
2790
|
+
const ns = asset.namespace ? ` ns=${asset.namespace}` : "";
|
|
2791
|
+
const version = asset.version ? ` v${asset.version}` : "";
|
|
2792
|
+
process.stderr.write(` - ${asset.name} [${asset.source}]${ns}${version} ${asset.status.configurationPosture} ${formatCounts(asset)}`);
|
|
2793
|
+
if (asset.displayName)
|
|
2794
|
+
process.stderr.write(` — ${asset.displayName}`);
|
|
2795
|
+
process.stderr.write("\n");
|
|
2796
|
+
}
|
|
2797
|
+
return 0;
|
|
2798
|
+
}
|
|
2799
|
+
if (subcommand === "show") {
|
|
2800
|
+
const loadedAsset = await loadAsset(opts.name, opts.projectDir);
|
|
2801
|
+
if (!loadedAsset) {
|
|
2802
|
+
process.stderr.write(opts.name ? `Unknown workflow asset: ${opts.name}\n` : "Usage: dna assets show <name>\n");
|
|
2803
|
+
return 2;
|
|
2804
|
+
}
|
|
2805
|
+
const asset = await normalizeAssetForReview(loadedAsset, opts.projectDir);
|
|
2806
|
+
if (opts.json) {
|
|
2807
|
+
process.stdout.write(JSON.stringify(asset, null, 2) + "\n");
|
|
2808
|
+
return 0;
|
|
2809
|
+
}
|
|
2810
|
+
writeAssetHeader(asset);
|
|
2811
|
+
writeAssetGovernance(asset);
|
|
2812
|
+
writeAssetStructure(asset);
|
|
2813
|
+
if (opts.evidence)
|
|
2814
|
+
writeAssetEvidence(asset);
|
|
2815
|
+
return 0;
|
|
2816
|
+
}
|
|
2817
|
+
if (subcommand === "status") {
|
|
2818
|
+
if (opts.name) {
|
|
2819
|
+
const loadedAsset = await loadAsset(opts.name, opts.projectDir);
|
|
2820
|
+
if (!loadedAsset) {
|
|
2821
|
+
process.stderr.write(`Unknown workflow asset: ${opts.name}\n`);
|
|
2822
|
+
return 2;
|
|
2823
|
+
}
|
|
2824
|
+
const asset = await normalizeAssetForReview(loadedAsset, opts.projectDir);
|
|
2825
|
+
if (opts.json) {
|
|
2826
|
+
process.stdout.write(JSON.stringify({ asset: asset.name, status: asset.status }, null, 2) + "\n");
|
|
2827
|
+
return 0;
|
|
2828
|
+
}
|
|
2829
|
+
writeAssetStatus(asset);
|
|
2830
|
+
if (opts.evidence)
|
|
2831
|
+
writeAssetEvidence(asset);
|
|
2832
|
+
return 0;
|
|
2833
|
+
}
|
|
2834
|
+
const assets = await listWorkflowAssets({ projectDir: opts.projectDir });
|
|
2835
|
+
if (opts.json) {
|
|
2836
|
+
process.stdout.write(JSON.stringify(assets.map((asset) => ({
|
|
2837
|
+
asset: asset.name,
|
|
2838
|
+
displayName: asset.displayName,
|
|
2839
|
+
status: asset.status,
|
|
2840
|
+
counts: {
|
|
2841
|
+
workflows: asset.workflows.length,
|
|
2842
|
+
controllers: asset.controllers.length,
|
|
2843
|
+
roles: asset.roles.length,
|
|
2844
|
+
evidence: asset.evidence.length,
|
|
2845
|
+
},
|
|
2846
|
+
})), null, 2) + "\n");
|
|
2847
|
+
return 0;
|
|
2848
|
+
}
|
|
2849
|
+
process.stderr.write(`Workflow asset status (${assets.length}):\n`);
|
|
2850
|
+
for (const asset of assets) {
|
|
2851
|
+
process.stderr.write(` - ${asset.name}: ${asset.status.managementStatus} ${asset.status.evidencePosture} ${asset.status.configurationPosture} ${formatCounts(asset)}\n`);
|
|
2852
|
+
}
|
|
2853
|
+
return 0;
|
|
2854
|
+
}
|
|
2855
|
+
if (subcommand === "inspect") {
|
|
2856
|
+
const loadedAsset = await loadAsset(opts.name, opts.projectDir);
|
|
2857
|
+
if (!loadedAsset) {
|
|
2858
|
+
process.stderr.write(opts.name ? `Unknown workflow asset: ${opts.name}\n` : "Usage: dna assets inspect <name>\n");
|
|
2859
|
+
return 2;
|
|
2860
|
+
}
|
|
2861
|
+
const asset = await normalizeAssetForReview(loadedAsset, opts.projectDir);
|
|
2862
|
+
if (opts.json) {
|
|
2863
|
+
process.stdout.write(JSON.stringify(asset, null, 2) + "\n");
|
|
2864
|
+
return 0;
|
|
2865
|
+
}
|
|
2866
|
+
writeAssetInspection(asset, opts.evidence ?? false);
|
|
2867
|
+
return 0;
|
|
2868
|
+
}
|
|
2869
|
+
process.stderr.write("Usage: dna assets <list|library|candidates|lineage|show|inspect|status|review|governance|lifecycle|adoption|suggestions|diff|evidence> [name] [--evidence] [--json]\n");
|
|
2870
|
+
return 2;
|
|
2871
|
+
}
|