@kaddo/cli 3.9.1 → 3.10.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/dist/index.js +177 -96
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4814,6 +4814,110 @@ function runIgnoreRemove(artifactId) {
|
|
|
4814
4814
|
import matter2 from "gray-matter";
|
|
4815
4815
|
import { parse as parseYaml8 } from "yaml";
|
|
4816
4816
|
|
|
4817
|
+
// src/core/delivery-phase.ts
|
|
4818
|
+
function layer(layers, name) {
|
|
4819
|
+
return layers.find((l) => l.layer === name)?.status ?? "Missing";
|
|
4820
|
+
}
|
|
4821
|
+
function baseComplete(layers) {
|
|
4822
|
+
return layer(layers, "Business") !== "Missing" && layer(layers, "Product") !== "Missing" && layer(layers, "Tech") !== "Missing";
|
|
4823
|
+
}
|
|
4824
|
+
function determinePhase(input) {
|
|
4825
|
+
const { roadmap, workItems } = input;
|
|
4826
|
+
const active = workItems.byState.draft + workItems.byState.ready + workItems.byState["in-progress"] + workItems.byState.blocked;
|
|
4827
|
+
if (!baseComplete(input.layers)) return "Discovery";
|
|
4828
|
+
if (!roadmap.present) return "Planning";
|
|
4829
|
+
if (workItems.total === 0) return "Delivery Preparation";
|
|
4830
|
+
if (active > 0) return "Active Delivery";
|
|
4831
|
+
return "Maintenance";
|
|
4832
|
+
}
|
|
4833
|
+
function ownershipPct(o) {
|
|
4834
|
+
if (o.workItemsTotal === 0) return 100;
|
|
4835
|
+
return Math.round(o.workItemsWithOwnership / o.workItemsTotal * 100);
|
|
4836
|
+
}
|
|
4837
|
+
function buildReasons(input) {
|
|
4838
|
+
const reasons = [];
|
|
4839
|
+
reasons.push(input.roadmap.present ? "Roadmap available" : "No roadmap yet");
|
|
4840
|
+
if (input.roadmap.present) {
|
|
4841
|
+
reasons.push(`${input.roadmap.materialized} materialized work item(s)`);
|
|
4842
|
+
if (input.roadmap.remaining > 0) reasons.push(`${input.roadmap.remaining} roadmap candidate(s) remaining`);
|
|
4843
|
+
}
|
|
4844
|
+
const active = LIFECYCLE_STATES.filter((s) => input.workItems.byState[s] > 0 && s !== "completed" && s !== "archived");
|
|
4845
|
+
if (active.length > 0) {
|
|
4846
|
+
reasons.push(active.map((s) => `${s}: ${input.workItems.byState[s]}`).join(", "));
|
|
4847
|
+
}
|
|
4848
|
+
if (input.workItems.total > 0) reasons.push(`Ownership coverage ${ownershipPct(input.ownership)}%`);
|
|
4849
|
+
return reasons;
|
|
4850
|
+
}
|
|
4851
|
+
function firstMissingLayerAgent(layers) {
|
|
4852
|
+
if (layer(layers, "Business") === "Missing")
|
|
4853
|
+
return { agent: "business-agent", step: "Use business-agent to create knowledge/business/business.md" };
|
|
4854
|
+
if (layer(layers, "Product") === "Missing")
|
|
4855
|
+
return { agent: "capability-agent", step: "Use capability-agent to create knowledge/product/capabilities.md" };
|
|
4856
|
+
return { agent: "architecture-agent", step: "Use architecture-agent to create knowledge/tech/current-state.md" };
|
|
4857
|
+
}
|
|
4858
|
+
function assessPhase(input) {
|
|
4859
|
+
const phase = determinePhase(input);
|
|
4860
|
+
const reasons = buildReasons(input);
|
|
4861
|
+
const bs = input.workItems.byState;
|
|
4862
|
+
const items = input.workItems.items;
|
|
4863
|
+
const firstOf = (s) => items.find((i) => i.lifecycle === s);
|
|
4864
|
+
let recommendedAgents2 = [];
|
|
4865
|
+
let nextStep = "";
|
|
4866
|
+
switch (phase) {
|
|
4867
|
+
case "Discovery": {
|
|
4868
|
+
const m = firstMissingLayerAgent(input.layers);
|
|
4869
|
+
recommendedAgents2 = [m.agent];
|
|
4870
|
+
nextStep = m.step;
|
|
4871
|
+
break;
|
|
4872
|
+
}
|
|
4873
|
+
case "Planning": {
|
|
4874
|
+
recommendedAgents2 = ["roadmap-agent"];
|
|
4875
|
+
nextStep = "Use roadmap-agent to create knowledge/delivery/roadmap.md";
|
|
4876
|
+
break;
|
|
4877
|
+
}
|
|
4878
|
+
case "Delivery Preparation": {
|
|
4879
|
+
recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
|
|
4880
|
+
const n = input.roadmap.remaining || input.roadmap.candidates;
|
|
4881
|
+
nextStep = `Run \`kaddo create --from roadmap\`${n ? ` (${n} candidate(s))` : ""}, then refine with work-item-agent`;
|
|
4882
|
+
break;
|
|
4883
|
+
}
|
|
4884
|
+
case "Active Delivery": {
|
|
4885
|
+
if (bs.ready > 0) {
|
|
4886
|
+
const wi = firstOf("ready");
|
|
4887
|
+
recommendedAgents2 = ["implementation-agent"];
|
|
4888
|
+
nextStep = wi ? `Start ${wi.id} \u2014 ${wi.title} (ready \u2192 in-progress)` : "Start a ready Work Item";
|
|
4889
|
+
} else if (bs["in-progress"] > 0) {
|
|
4890
|
+
const wi = firstOf("in-progress");
|
|
4891
|
+
recommendedAgents2 = ["implementation-agent", "kaddo scan", "kaddo owners suggest", "kaddo guard"];
|
|
4892
|
+
nextStep = wi ? `Continue ${wi.id} \u2014 ${wi.title}; then run kaddo scan, owners suggest, guard` : "Continue the in-progress Work Item; then scan, owners suggest, guard";
|
|
4893
|
+
} else if (bs.draft > 0) {
|
|
4894
|
+
const wi = firstOf("draft");
|
|
4895
|
+
recommendedAgents2 = ["work-item-agent"];
|
|
4896
|
+
nextStep = wi ? `Refine ${wi.id} from draft to ready` : "Refine a draft Work Item to ready";
|
|
4897
|
+
} else if (bs.blocked > 0) {
|
|
4898
|
+
const wi = firstOf("blocked");
|
|
4899
|
+
recommendedAgents2 = ["work-item-agent"];
|
|
4900
|
+
nextStep = wi ? `Resolve the blocker on ${wi.id} \u2014 ${wi.title}` : "Resolve the blockers on active work";
|
|
4901
|
+
}
|
|
4902
|
+
if (input.ownership.workItemsMissingOwnership > 0) {
|
|
4903
|
+
recommendedAgents2.push("kaddo owners suggest");
|
|
4904
|
+
}
|
|
4905
|
+
break;
|
|
4906
|
+
}
|
|
4907
|
+
case "Maintenance": {
|
|
4908
|
+
if (input.roadmap.remaining > 0) {
|
|
4909
|
+
recommendedAgents2 = ["kaddo create --from roadmap", "work-item-agent"];
|
|
4910
|
+
nextStep = `Materialize ${input.roadmap.remaining} remaining roadmap candidate(s)`;
|
|
4911
|
+
} else {
|
|
4912
|
+
recommendedAgents2 = ["roadmap-agent"];
|
|
4913
|
+
nextStep = "No active work \u2014 use roadmap-agent to plan the next initiative";
|
|
4914
|
+
}
|
|
4915
|
+
break;
|
|
4916
|
+
}
|
|
4917
|
+
}
|
|
4918
|
+
return { phase, reasons, recommendedAgents: recommendedAgents2, nextStep };
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4817
4921
|
// src/core/knowledge-discovery.ts
|
|
4818
4922
|
var KNOWLEDGE = "knowledge";
|
|
4819
4923
|
var CONSOLIDATED_TYPE = {
|
|
@@ -4877,29 +4981,29 @@ function discoverLayers(dir) {
|
|
|
4877
4981
|
const artifacts = exists(archDir) ? readArtifacts(archDir) : [];
|
|
4878
4982
|
for (const a of artifacts) {
|
|
4879
4983
|
const type = a.type;
|
|
4880
|
-
const
|
|
4881
|
-
if (!
|
|
4882
|
-
const slot = acc[
|
|
4984
|
+
const layer2 = layerForType(type) ?? layerFromPath(a.filePath);
|
|
4985
|
+
if (!layer2) continue;
|
|
4986
|
+
const slot = acc[layer2];
|
|
4883
4987
|
slot.detected.add(basename(a.filePath));
|
|
4884
|
-
if (
|
|
4988
|
+
if (layer2 === "Delivery") {
|
|
4885
4989
|
if (type === "roadmap" || basename(a.filePath) === "roadmap.md") slot.hasRoadmap = true;
|
|
4886
4990
|
if (WORK_ITEM_TYPES2.has(type) || a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) {
|
|
4887
4991
|
if (a.filePath.replace(/\\/g, "/").includes("/delivery/work-items/")) slot.hasWorkItem = true;
|
|
4888
4992
|
}
|
|
4889
4993
|
continue;
|
|
4890
4994
|
}
|
|
4891
|
-
if (type === CONSOLIDATED_TYPE[
|
|
4892
|
-
else if (STRUCTURED_TYPES[
|
|
4995
|
+
if (type === CONSOLIDATED_TYPE[layer2]) slot.consolidated = true;
|
|
4996
|
+
else if (STRUCTURED_TYPES[layer2].has(type)) slot.structured = true;
|
|
4893
4997
|
else if (!type) {
|
|
4894
4998
|
slot.consolidated = true;
|
|
4895
4999
|
}
|
|
4896
5000
|
if (type === "adr" || type === "decision") slot.hasDecision = true;
|
|
4897
5001
|
}
|
|
4898
5002
|
if (existsDirWithMd(join(dir, KNOWLEDGE, "tech", "decisions"))) acc.Tech.structured = true;
|
|
4899
|
-
return ["Business", "Product", "Tech", "Delivery"].map((
|
|
4900
|
-
layer,
|
|
4901
|
-
status: statusFor(
|
|
4902
|
-
detected: [...acc[
|
|
5003
|
+
return ["Business", "Product", "Tech", "Delivery"].map((layer2) => ({
|
|
5004
|
+
layer: layer2,
|
|
5005
|
+
status: statusFor(layer2, acc[layer2]),
|
|
5006
|
+
detected: [...acc[layer2].detected].sort()
|
|
4903
5007
|
}));
|
|
4904
5008
|
}
|
|
4905
5009
|
function blank() {
|
|
@@ -4920,8 +5024,8 @@ function existsDirWithMd(d) {
|
|
|
4920
5024
|
return false;
|
|
4921
5025
|
}
|
|
4922
5026
|
}
|
|
4923
|
-
function statusFor(
|
|
4924
|
-
if (
|
|
5027
|
+
function statusFor(layer2, a) {
|
|
5028
|
+
if (layer2 === "Delivery") {
|
|
4925
5029
|
if (a.hasWorkItem) return "Traceable";
|
|
4926
5030
|
if (a.hasRoadmap) return "Partial";
|
|
4927
5031
|
return "Missing";
|
|
@@ -4930,40 +5034,15 @@ function statusFor(layer, a) {
|
|
|
4930
5034
|
if (a.consolidated) return "Consolidated";
|
|
4931
5035
|
return "Missing";
|
|
4932
5036
|
}
|
|
4933
|
-
function roadmapHasUnmaterializedCandidates(dir) {
|
|
4934
|
-
const roadmap = join(dir, KNOWLEDGE, "delivery", "roadmap.md");
|
|
4935
|
-
if (!exists(roadmap)) return false;
|
|
4936
|
-
let hasCandidates = false;
|
|
4937
|
-
try {
|
|
4938
|
-
hasCandidates = /WI-[A-Z0-9-]*\d/i.test(readFile(roadmap));
|
|
4939
|
-
} catch {
|
|
4940
|
-
return false;
|
|
4941
|
-
}
|
|
4942
|
-
if (!hasCandidates) return false;
|
|
4943
|
-
const wiDir = join(dir, KNOWLEDGE, "delivery", "work-items");
|
|
4944
|
-
return !existsDirWithMd(wiDir);
|
|
4945
|
-
}
|
|
4946
5037
|
|
|
4947
5038
|
// src/core/layers.ts
|
|
4948
5039
|
function knowledgeLayers(dir) {
|
|
4949
5040
|
return discoverLayers(dir);
|
|
4950
5041
|
}
|
|
4951
|
-
var COMPLETE = {
|
|
4952
|
-
Business: (s) => s !== "Missing",
|
|
4953
|
-
Product: (s) => s !== "Missing",
|
|
4954
|
-
Tech: (s) => s !== "Missing",
|
|
4955
|
-
Delivery: (s) => s === "Traceable"
|
|
4956
|
-
};
|
|
4957
|
-
function currentPhase(layers) {
|
|
4958
|
-
for (const l of layers) {
|
|
4959
|
-
if (!COMPLETE[l.layer](l.status)) return l.layer;
|
|
4960
|
-
}
|
|
4961
|
-
return "Delivery";
|
|
4962
|
-
}
|
|
4963
5042
|
function renderLayersMarkdown(layers) {
|
|
4964
5043
|
const lines = [];
|
|
4965
|
-
for (const { layer, status, detected } of layers) {
|
|
4966
|
-
lines.push(`### ${
|
|
5044
|
+
for (const { layer: layer2, status, detected } of layers) {
|
|
5045
|
+
lines.push(`### ${layer2} \u2014 ${status}`);
|
|
4967
5046
|
if (detected.length > 0) for (const d of detected) lines.push(`- \u2713 ${d}`);
|
|
4968
5047
|
else lines.push("- \u2717 none");
|
|
4969
5048
|
lines.push("");
|
|
@@ -5271,6 +5350,15 @@ function renderExplanationHuman(exp) {
|
|
|
5271
5350
|
for (const m of exp.missingKnowledge) lines.push(`- ${m}`);
|
|
5272
5351
|
lines.push("");
|
|
5273
5352
|
}
|
|
5353
|
+
const assessment = assessPhase(exp);
|
|
5354
|
+
lines.push("## Phase");
|
|
5355
|
+
lines.push(`- Phase: ${assessment.phase}`);
|
|
5356
|
+
if (assessment.reasons.length > 0) {
|
|
5357
|
+
lines.push("- Reason:");
|
|
5358
|
+
for (const r of assessment.reasons) lines.push(` - ${r}`);
|
|
5359
|
+
}
|
|
5360
|
+
if (assessment.nextStep) lines.push(`- Next step: ${assessment.nextStep}`);
|
|
5361
|
+
lines.push("");
|
|
5274
5362
|
if (exp.suggestedNextSteps.length > 0) {
|
|
5275
5363
|
lines.push("## Suggested Next Steps");
|
|
5276
5364
|
exp.suggestedNextSteps.forEach((s, i) => lines.push(`${i + 1}. ${s}`));
|
|
@@ -5600,6 +5688,28 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5600
5688
|
}
|
|
5601
5689
|
const mappedModules = loadMappedModules(dir);
|
|
5602
5690
|
const layers = knowledgeLayers(dir);
|
|
5691
|
+
const allWorkItems = allArtifacts.filter((a) => a.isWorkItem);
|
|
5692
|
+
const wiWithOwnership = allWorkItems.filter((a) => a.codeGlobs.length > 0).length;
|
|
5693
|
+
const phase = assessPhase({
|
|
5694
|
+
layers,
|
|
5695
|
+
roadmap,
|
|
5696
|
+
workItems: {
|
|
5697
|
+
total: allWorkItems.length,
|
|
5698
|
+
byState: lifecycleCounts(
|
|
5699
|
+
allWorkItems.map((a) => lifecycleStateOf({ status: a.status, filePath: a.filePath }))
|
|
5700
|
+
),
|
|
5701
|
+
items: allWorkItems.map((a) => ({
|
|
5702
|
+
id: a.id || a.title,
|
|
5703
|
+
title: a.title,
|
|
5704
|
+
lifecycle: lifecycleStateOf({ status: a.status, filePath: a.filePath })
|
|
5705
|
+
}))
|
|
5706
|
+
},
|
|
5707
|
+
ownership: {
|
|
5708
|
+
workItemsTotal: allWorkItems.length,
|
|
5709
|
+
workItemsWithOwnership: wiWithOwnership,
|
|
5710
|
+
workItemsMissingOwnership: allWorkItems.length - wiWithOwnership
|
|
5711
|
+
}
|
|
5712
|
+
});
|
|
5603
5713
|
return {
|
|
5604
5714
|
version: CONTEXT_PACK_VERSION,
|
|
5605
5715
|
generatedAt: now.toISOString(),
|
|
@@ -5628,6 +5738,7 @@ function buildContextPack(dir, config, now = /* @__PURE__ */ new Date()) {
|
|
|
5628
5738
|
},
|
|
5629
5739
|
layers,
|
|
5630
5740
|
roadmap,
|
|
5741
|
+
phase,
|
|
5631
5742
|
deliveryMix,
|
|
5632
5743
|
mappedModules,
|
|
5633
5744
|
missing,
|
|
@@ -5665,6 +5776,21 @@ function renderContextPack(pack) {
|
|
|
5665
5776
|
`- Structure: ${project.structure}`
|
|
5666
5777
|
].join("\n") + "\n"
|
|
5667
5778
|
);
|
|
5779
|
+
parts.push("## Current Phase\n");
|
|
5780
|
+
parts.push(`Phase: ${pack.phase.phase}
|
|
5781
|
+
`);
|
|
5782
|
+
if (pack.phase.reasons.length > 0) {
|
|
5783
|
+
parts.push("Reason:\n");
|
|
5784
|
+
parts.push(pack.phase.reasons.map((r) => `- ${r}`).join("\n") + "\n");
|
|
5785
|
+
}
|
|
5786
|
+
if (pack.phase.recommendedAgents.length > 0) {
|
|
5787
|
+
parts.push(`Recommended next: ${pack.phase.recommendedAgents.join(", ")}
|
|
5788
|
+
`);
|
|
5789
|
+
}
|
|
5790
|
+
if (pack.phase.nextStep) {
|
|
5791
|
+
parts.push(`Next step: ${pack.phase.nextStep}
|
|
5792
|
+
`);
|
|
5793
|
+
}
|
|
5668
5794
|
parts.push("## Knowledge Layers\n");
|
|
5669
5795
|
parts.push(
|
|
5670
5796
|
"Project knowledge is organized in four layers: **Business \u2192 Product \u2192 Tech \u2192 Delivery**.\n"
|
|
@@ -6064,64 +6190,19 @@ function runUnderstand() {
|
|
|
6064
6190
|
);
|
|
6065
6191
|
}
|
|
6066
6192
|
console.log(renderUnderstandTerminal(plan));
|
|
6067
|
-
const
|
|
6068
|
-
const
|
|
6069
|
-
const groupAgents = (AGENT_GROUPS[group] ?? []).map((a) => a.replace(/\.md$/, ""));
|
|
6070
|
-
const NEXT_STEPS = {
|
|
6071
|
-
business: ["Use business-agent \u2192 refine knowledge/business/business.md"],
|
|
6072
|
-
product: [
|
|
6073
|
-
"Use bootstrap-agent \u2192 refine knowledge/product/product.md",
|
|
6074
|
-
"Use capability-agent \u2192 knowledge/product/capabilities.md"
|
|
6075
|
-
],
|
|
6076
|
-
tech: [
|
|
6077
|
-
"Use architecture-agent \u2192 knowledge/tech/current-state.md (reality)",
|
|
6078
|
-
"Use codebase-agent \u2192 knowledge/tech/codebase.md (intent)",
|
|
6079
|
-
"Record decisions as ADRs in knowledge/tech/decisions/"
|
|
6080
|
-
],
|
|
6081
|
-
delivery: [
|
|
6082
|
-
"Use roadmap-agent \u2192 knowledge/delivery/roadmap.md",
|
|
6083
|
-
"Run `kaddo create --from roadmap` (candidates \u2192 Work Items)",
|
|
6084
|
-
"Use work-item-agent \u2192 refine into knowledge/delivery/work-items/",
|
|
6085
|
-
"Use implementation-agent \u2192 implement, then `kaddo scan`, `kaddo owners suggest`, `kaddo guard`"
|
|
6086
|
-
],
|
|
6087
|
-
utilities: ["Use legacy-agent to surface risks and unknowns"]
|
|
6088
|
-
};
|
|
6089
|
-
if (roadmapHasUnmaterializedCandidates(dir)) {
|
|
6090
|
-
const { candidates, materialized, remaining } = pack.roadmap;
|
|
6091
|
-
console.log("");
|
|
6092
|
-
console.log(
|
|
6093
|
-
`The roadmap has ${remaining} unmaterialized Work Item candidate(s) (${candidates} candidate(s), ${materialized} materialized).`
|
|
6094
|
-
);
|
|
6095
|
-
console.log(" \u2192 Run `kaddo create --from roadmap`, or use the work-item-agent to");
|
|
6096
|
-
console.log(" materialize them into knowledge/delivery/work-items/.");
|
|
6097
|
-
}
|
|
6193
|
+
const exp = buildProjectExplanation(dir);
|
|
6194
|
+
const assessment = assessPhase(exp);
|
|
6098
6195
|
console.log("");
|
|
6099
|
-
console.log(`Current phase: ${phase}`);
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
let n = 3;
|
|
6104
|
-
for (const step of NEXT_STEPS[group] ?? []) console.log(` ${n++}. ${step}`);
|
|
6105
|
-
if (groupAgents.length > 0) {
|
|
6106
|
-
console.log(`Agents for this phase: ${groupAgents.join(", ")}`);
|
|
6196
|
+
console.log(`Current phase: ${assessment.phase}`);
|
|
6197
|
+
if (assessment.reasons.length > 0) {
|
|
6198
|
+
console.log("Reason:");
|
|
6199
|
+
for (const r of assessment.reasons) console.log(` - ${r}`);
|
|
6107
6200
|
}
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
console.log(
|
|
6113
|
-
console.log(` Draft: ${bs.draft} Ready: ${bs.ready} In Progress: ${bs["in-progress"]} Blocked: ${bs.blocked}`);
|
|
6114
|
-
const firstReady = exp.workItems.items.find((i) => i.lifecycle === "ready");
|
|
6115
|
-
const firstInProgress = exp.workItems.items.find((i) => i.lifecycle === "in-progress");
|
|
6116
|
-
if (firstInProgress) {
|
|
6117
|
-
console.log(` \u2192 Continue ${firstInProgress.id} \u2014 ${firstInProgress.title} (in progress).`);
|
|
6118
|
-
} else if (firstReady) {
|
|
6119
|
-
console.log(` \u2192 Recommended next step: start ${firstReady.id} \u2014 ${firstReady.title}.`);
|
|
6120
|
-
} else if (bs.draft > 0) {
|
|
6121
|
-
console.log(" \u2192 Refine a draft Work Item to `ready` (scope + acceptance defined).");
|
|
6122
|
-
} else if (bs.blocked > 0) {
|
|
6123
|
-
console.log(" \u2192 All active work is blocked. Resolve the blockers to move forward.");
|
|
6124
|
-
}
|
|
6201
|
+
if (assessment.recommendedAgents.length > 0) {
|
|
6202
|
+
console.log(`Recommended: ${assessment.recommendedAgents.join(", ")}`);
|
|
6203
|
+
}
|
|
6204
|
+
if (assessment.nextStep) {
|
|
6205
|
+
console.log(`Next step: ${assessment.nextStep}`);
|
|
6125
6206
|
}
|
|
6126
6207
|
const active = activeWorkItems(dir);
|
|
6127
6208
|
if (active.length > 0) {
|
|
@@ -8789,7 +8870,7 @@ async function runBootstrap(dir = cwd()) {
|
|
|
8789
8870
|
const result = bootstrap(dir);
|
|
8790
8871
|
console.log("");
|
|
8791
8872
|
console.log("Bootstrap layers:");
|
|
8792
|
-
for (const
|
|
8873
|
+
for (const layer2 of result.layers) console.log(` \u2713 ${layer2}`);
|
|
8793
8874
|
console.log("");
|
|
8794
8875
|
if (result.written.length > 0) {
|
|
8795
8876
|
console.log("Created:");
|