opencode-swarm 7.109.2 → 7.109.4

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.
Files changed (39) hide show
  1. package/.opencode/skills/ci-failure-batching/SKILL.md +39 -0
  2. package/.opencode/skills/gate-attribution/SKILL.md +31 -0
  3. package/.opencode/skills/merge-queue-readiness/SKILL.md +37 -0
  4. package/.opencode/skills/skill-edit-validation/SKILL.md +29 -0
  5. package/.opencode/skills/worktree-retry-cleanup/SKILL.md +24 -0
  6. package/dist/cli/{config-doctor-q354tb2w.js → config-doctor-9vjyj9m3.js} +2 -2
  7. package/dist/cli/{curator-llm-factory-wqbm9mwr.js → curator-llm-factory-3hf9xpnd.js} +9 -9
  8. package/dist/cli/{curator-6p9tpprq.js → curator-zn1kmwmz.js} +9 -9
  9. package/dist/cli/{guardrail-explain-ynge4ww3.js → guardrail-explain-hjz1k6w0.js} +10 -10
  10. package/dist/cli/{guardrail-log-8mqqsssq.js → guardrail-log-5240bawg.js} +3 -3
  11. package/dist/cli/{hive-promoter-qh2rh8x2.js → hive-promoter-j81yj97d.js} +9 -9
  12. package/dist/cli/{index-h4c4rrwx.js → index-18690p15.js} +23 -2
  13. package/dist/cli/{index-fz1jgbvw.js → index-41fgssqr.js} +22 -2
  14. package/dist/cli/{index-wg0665ft.js → index-8f10r7ay.js} +2 -2
  15. package/dist/cli/{index-9qxntjtx.js → index-b15qbvph.js} +190 -41
  16. package/dist/cli/{index-wj4jeavn.js → index-d3txrx5q.js} +1 -1
  17. package/dist/cli/{index-5gnp8fyw.js → index-mtzjbaaa.js} +3 -3
  18. package/dist/cli/{index-3qwaydq1.js → index-nvmy7xmj.js} +11 -11
  19. package/dist/cli/{index-vx76tcxe.js → index-p5vm9gy9.js} +1 -1
  20. package/dist/cli/{index-cqbqynzp.js → index-y3nx3ss0.js} +710 -557
  21. package/dist/cli/{index-mmny81g1.js → index-y5z2qdvk.js} +1 -1
  22. package/dist/cli/{index-8a8b5wjf.js → index-zh3qh6m7.js} +2 -2
  23. package/dist/cli/{index-fm7xz1ne.js → index-zjh648z4.js} +81 -1
  24. package/dist/cli/index.js +9 -9
  25. package/dist/cli/{knowledge-escalator-6v7sjprx.js → knowledge-escalator-9ksrgq7h.js} +3 -3
  26. package/dist/cli/{knowledge-events-6n3x3he2.js → knowledge-events-ymysgtrr.js} +1 -1
  27. package/dist/cli/{knowledge-store-exkw6yb1.js → knowledge-store-562fefjy.js} +1 -1
  28. package/dist/cli/{knowledge-validator-exymfcb9.js → knowledge-validator-b9nvmzjc.js} +4 -2
  29. package/dist/cli/{schema-x17zmd03.js → schema-5vmb5dh8.js} +3 -1
  30. package/dist/cli/{skill-generator-mx9br4ty.js → skill-generator-mjfqvnn0.js} +15 -5
  31. package/dist/config/bundled-skills.d.ts +1 -1
  32. package/dist/config/schema.d.ts +12 -4
  33. package/dist/hooks/knowledge-types.d.ts +6 -0
  34. package/dist/hooks/knowledge-validator.d.ts +5 -0
  35. package/dist/hooks/skill-invalidator.d.ts +48 -0
  36. package/dist/index.js +217 -216
  37. package/dist/services/skill-generator.d.ts +47 -2
  38. package/dist/tools/knowledge-remove.d.ts +3 -8
  39. package/package.json +6 -1
@@ -2,18 +2,18 @@
2
2
  import {
3
3
  effectiveRetrievalOutcomes,
4
4
  readKnowledgeCounterRollups
5
- } from "./index-mmny81g1.js";
5
+ } from "./index-y5z2qdvk.js";
6
6
  import {
7
7
  ALLOWED_SKILL_PATH_PREFIXES,
8
8
  validateSkillPath
9
- } from "./index-fz1jgbvw.js";
9
+ } from "./index-41fgssqr.js";
10
10
  import {
11
11
  computeOutcomeSignal,
12
12
  readKnowledge,
13
13
  resolveHiveKnowledgePath,
14
14
  resolveSwarmKnowledgePath,
15
- rewriteKnowledge
16
- } from "./index-wg0665ft.js";
15
+ transactKnowledge
16
+ } from "./index-8f10r7ay.js";
17
17
  import {
18
18
  init_logger,
19
19
  warn
@@ -464,6 +464,43 @@ function activePath(directory, slug) {
464
464
  function activeRepoRelativePath(slug) {
465
465
  return `.opencode/skills/generated/${slug}/SKILL.md`;
466
466
  }
467
+ function proposalRepoRelativePath(slug) {
468
+ return `.swarm/skills/proposals/${slug}.md`;
469
+ }
470
+ function generateEvalStub(cluster) {
471
+ const required = uniqueStrings([
472
+ ...cluster.required_actions,
473
+ ...cluster.verification_checks
474
+ ]).slice(0, 40);
475
+ if (required.length === 0)
476
+ return [];
477
+ return [
478
+ {
479
+ id: "auto-stub",
480
+ task: `auto-generated from source directives for ${cluster.slug}`,
481
+ required_phrases: required.map((p) => p.slice(0, 160))
482
+ }
483
+ ];
484
+ }
485
+ async function writeEvalStub(directory, slug, cases) {
486
+ const stubPath = path3.join(directory, ".swarm", "skills", "evals", slug, "auto-stub.json");
487
+ if (cases.length === 0) {
488
+ return { written: false, path: stubPath, reason: "no directive fields" };
489
+ }
490
+ try {
491
+ await mkdir3(path3.dirname(stubPath), { recursive: true });
492
+ await atomicWrite2(stubPath, `${JSON.stringify({ cases })}
493
+ `);
494
+ return { written: true, path: stubPath };
495
+ } catch (err) {
496
+ warn(`[skill-generator] eval stub write failed for '${slug}': ${err instanceof Error ? err.message : String(err)}`);
497
+ return {
498
+ written: false,
499
+ path: stubPath,
500
+ reason: err instanceof Error ? err.message : String(err)
501
+ };
502
+ }
503
+ }
467
504
  var DEFAULT_SKILL_MIN_CONFIDENCE = 0.7;
468
505
  var DEFAULT_SKILL_MIN_CONFIRMATIONS = 2;
469
506
  var STRONG_SKILL_OUTCOME_COUNT = 3;
@@ -478,7 +515,7 @@ async function selectCandidateEntries(directory, opts) {
478
515
  for (const e of all) {
479
516
  if (e.status === "archived")
480
517
  continue;
481
- if (e.generated_skill_slug)
518
+ if (e.generated_skill_slug || e.draft_generated_skill_slug)
482
519
  continue;
483
520
  const outcomes = effectiveRetrievalOutcomes(e.retrieval_outcomes, counterRollups.get(e.id));
484
521
  if (!isSkillMaturityEligible(e, opts, outcomes))
@@ -754,6 +791,7 @@ async function generateSkills(req) {
754
791
  }
755
792
  }
756
793
  let content = renderSkillMarkdown(cluster, req.mode);
794
+ await writeEvalStub(req.directory, cluster.slug, generateEvalStub(cluster));
757
795
  if (await isRejectedSkillContent(req.directory, cluster.slug, content)) {
758
796
  result.skipped.push({
759
797
  slug: cluster.slug,
@@ -803,6 +841,9 @@ async function generateSkills(req) {
803
841
  if (missingSourceIds.length > 0) {
804
842
  content = injectMissingIdsIntoFrontmatter(content, missingSourceIds);
805
843
  }
844
+ } else if (req.mode === "draft") {
845
+ const idsToStamp = req.sourceKnowledgeIds?.length ? req.sourceKnowledgeIds : cluster.entries.map((e) => e.id);
846
+ await stampSourceEntries(req.directory, cluster.slug, idsToStamp, "draft");
806
847
  }
807
848
  await atomicWrite2(targetPath, content);
808
849
  result.written.push({
@@ -865,49 +906,56 @@ function injectMissingIdsIntoFrontmatter(content, missingIds) {
865
906
  const suffix = stripped.slice(closeStart);
866
907
  return prefix + newBody + suffix;
867
908
  }
868
- async function stampSourceEntries(directory, slug, ids) {
909
+ async function stampSourceEntries(directory, slug, ids, mode = "active") {
869
910
  if (!ids || ids.length === 0)
870
911
  return { stamped: [], missing: [] };
871
- const swarmPath = resolveSwarmKnowledgePath(directory);
872
- const swarm = await readKnowledge(swarmPath);
873
912
  const idSet = new Set(ids);
874
913
  const stamped = [];
875
- const missing = [];
876
914
  const found = new Set;
877
- const repoRel = activeRepoRelativePath(slug);
878
- for (const e of swarm) {
879
- if (!idSet.has(e.id))
880
- continue;
881
- found.add(e.id);
882
- e.generated_skill_slug = slug;
883
- e.generated_skill_path = repoRel;
915
+ const repoRel = mode === "draft" ? proposalRepoRelativePath(slug) : activeRepoRelativePath(slug);
916
+ const applyStamp = (e) => {
917
+ if (mode === "draft") {
918
+ e.draft_generated_skill_slug = slug;
919
+ e.draft_generated_skill_path = repoRel;
920
+ } else {
921
+ e.generated_skill_slug = slug;
922
+ e.generated_skill_path = repoRel;
923
+ e.draft_generated_skill_slug = undefined;
924
+ e.draft_generated_skill_path = undefined;
925
+ }
884
926
  e.updated_at = new Date().toISOString();
885
- }
886
- if (found.size > 0)
887
- await rewriteKnowledge(swarmPath, swarm);
927
+ };
928
+ const swarmPath = resolveSwarmKnowledgePath(directory);
929
+ await transactKnowledge(swarmPath, (entries) => {
930
+ let changed = false;
931
+ for (const e of entries) {
932
+ if (!idSet.has(e.id))
933
+ continue;
934
+ found.add(e.id);
935
+ applyStamp(e);
936
+ changed = true;
937
+ }
938
+ return changed ? entries : null;
939
+ });
888
940
  stamped.push(...found);
889
941
  const hivePath = resolveHiveKnowledgePath();
890
- if (!existsSync2(hivePath)) {
891
- for (const id of ids) {
892
- if (!found.has(id))
893
- missing.push(id);
894
- }
895
- return { stamped, missing };
896
- }
897
- const hive = await readKnowledge(hivePath);
898
942
  const foundHive = new Set;
899
- for (const e of hive) {
900
- if (!idSet.has(e.id))
901
- continue;
902
- foundHive.add(e.id);
903
- e.generated_skill_slug = slug;
904
- e.generated_skill_path = repoRel;
905
- e.updated_at = new Date().toISOString();
943
+ if (existsSync2(hivePath)) {
944
+ await transactKnowledge(hivePath, (entries) => {
945
+ let changed = false;
946
+ for (const e of entries) {
947
+ if (!idSet.has(e.id))
948
+ continue;
949
+ foundHive.add(e.id);
950
+ applyStamp(e);
951
+ changed = true;
952
+ }
953
+ return changed ? entries : null;
954
+ });
955
+ stamped.push(...foundHive);
906
956
  }
907
- if (foundHive.size > 0)
908
- await rewriteKnowledge(hivePath, hive);
909
- stamped.push(...foundHive);
910
957
  const allFound = new Set([...found, ...foundHive]);
958
+ const missing = [];
911
959
  for (const id of ids) {
912
960
  if (!allFound.has(id))
913
961
  missing.push(id);
@@ -1088,6 +1136,15 @@ async function activateProposal(directory, slug, force = false, options = {}) {
1088
1136
  incumbentContent,
1089
1137
  operation: options.operation ?? "skill_apply"
1090
1138
  });
1139
+ if (evaluation.status === "unevaluated" && !options.confirmUnevaluated) {
1140
+ return {
1141
+ activated: false,
1142
+ from,
1143
+ to,
1144
+ reason: "unevaluated: no eval set exists; pass confirmUnevaluated:true to activate anyway",
1145
+ evaluation
1146
+ };
1147
+ }
1091
1148
  if (!evaluation.passed) {
1092
1149
  await appendRejectedSkillEdit({
1093
1150
  directory,
@@ -1298,7 +1355,8 @@ async function autoApplyProposals(directory, llmDelegate) {
1298
1355
  if (verdict === "APPROVE") {
1299
1356
  const activation = await activateProposal(directory, proposal.slug, false, {
1300
1357
  evaluate: true,
1301
- operation: "skill_auto_apply"
1358
+ operation: "skill_auto_apply",
1359
+ confirmUnevaluated: true
1302
1360
  });
1303
1361
  if (activation.activated) {
1304
1362
  result.approved.push(proposal.slug);
@@ -1306,6 +1364,11 @@ async function autoApplyProposals(directory, llmDelegate) {
1306
1364
  result.skipped.push(proposal.slug);
1307
1365
  }
1308
1366
  } else if (verdict === "REJECT") {
1367
+ try {
1368
+ await clearDraftSkillLinks(directory, proposal.path, proposal.slug);
1369
+ } catch (clearErr) {
1370
+ warn(`[skill-generator] failed to clear draft links for rejected proposal ${proposal.slug}: ${clearErr instanceof Error ? clearErr.message : String(clearErr)}`);
1371
+ }
1309
1372
  try {
1310
1373
  _internals.unlinkSync(proposal.path);
1311
1374
  warn(`[skill-generator] auto-apply rejected proposal "${proposal.slug}"; deleted ${proposal.path}`);
@@ -1395,12 +1458,92 @@ async function retireSkill(directory, slug, reason) {
1395
1458
  });
1396
1459
  await mkdir3(markerDir, { recursive: true });
1397
1460
  await writeFile3(markerPath, markerContent, "utf-8");
1461
+ let clearedLinks = [];
1462
+ try {
1463
+ clearedLinks = await clearRetiredSkillLinks(directory, skillPath, cleanSlug);
1464
+ } catch (err) {
1465
+ warn(`[skill-generator] retireSkill link-clear failed for '${cleanSlug}': ${err instanceof Error ? err.message : String(err)}`);
1466
+ }
1398
1467
  return {
1399
1468
  retired: true,
1400
1469
  path: skillPath,
1401
1470
  markerPath,
1402
- reason
1471
+ reason,
1472
+ clearedLinks
1473
+ };
1474
+ }
1475
+ async function clearDraftSkillLinks(directory, proposalFilePath, slug) {
1476
+ return clearSkillLinks(directory, proposalFilePath, slug, "draft");
1477
+ }
1478
+ async function clearRetiredSkillLinks(directory, skillFilePath, slug) {
1479
+ return clearSkillLinks(directory, skillFilePath, slug, "retire");
1480
+ }
1481
+ async function clearSkillLinks(directory, skillFilePath, slug, mode) {
1482
+ let content;
1483
+ try {
1484
+ content = await readFile3(skillFilePath, "utf-8");
1485
+ } catch (err) {
1486
+ warn(`[skill-generator] clearSkillLinks: failed to read '${skillFilePath}' for slug '${slug}': ${err instanceof Error ? err.message : String(err)}`);
1487
+ return [];
1488
+ }
1489
+ const fm = parseDraftFrontmatter(content);
1490
+ const sourceIds = fm?.sourceKnowledgeIds ?? [];
1491
+ if (sourceIds.length === 0)
1492
+ return [];
1493
+ const idSet = new Set(sourceIds);
1494
+ const cleared = [];
1495
+ const now = new Date().toISOString();
1496
+ const clearOnEntry = (e) => {
1497
+ if (mode === "draft") {
1498
+ if (e.draft_generated_skill_slug !== slug)
1499
+ return false;
1500
+ e.draft_generated_skill_slug = undefined;
1501
+ e.draft_generated_skill_path = undefined;
1502
+ e.updated_at = now;
1503
+ return true;
1504
+ }
1505
+ if (e.generated_skill_slug !== slug)
1506
+ return false;
1507
+ e.generated_skill_slug = undefined;
1508
+ e.generated_skill_path = undefined;
1509
+ const history = Array.isArray(e.retired_skill_history) ? e.retired_skill_history.filter((s) => s !== slug) : [];
1510
+ history.push(slug);
1511
+ if (history.length > 50) {
1512
+ history.splice(0, history.length - 50);
1513
+ }
1514
+ e.retired_skill_history = history;
1515
+ e.updated_at = now;
1516
+ return true;
1403
1517
  };
1518
+ const swarmPath = resolveSwarmKnowledgePath(directory);
1519
+ await transactKnowledge(swarmPath, (entries) => {
1520
+ let changed = false;
1521
+ for (const e of entries) {
1522
+ if (!idSet.has(e.id))
1523
+ continue;
1524
+ if (clearOnEntry(e)) {
1525
+ cleared.push(e.id);
1526
+ changed = true;
1527
+ }
1528
+ }
1529
+ return changed ? entries : null;
1530
+ });
1531
+ const hivePath = resolveHiveKnowledgePath();
1532
+ if (existsSync2(hivePath)) {
1533
+ await transactKnowledge(hivePath, (entries) => {
1534
+ let changed = false;
1535
+ for (const e of entries) {
1536
+ if (!idSet.has(e.id))
1537
+ continue;
1538
+ if (clearOnEntry(e)) {
1539
+ cleared.push(e.id);
1540
+ changed = true;
1541
+ }
1542
+ }
1543
+ return changed ? entries : null;
1544
+ });
1545
+ }
1546
+ return cleared;
1404
1547
  }
1405
1548
  async function markSkillStale(skillDir, reason) {
1406
1549
  await mkdir3(skillDir, { recursive: true });
@@ -1580,6 +1723,7 @@ async function regenerateSkill(directory, slug, options = {}) {
1580
1723
  version: newVersion,
1581
1724
  skillOrigin: origin === "generated" || origin === "promoted_external" ? origin : "generated"
1582
1725
  });
1726
+ await writeEvalStub(directory, cleanSlug, generateEvalStub(cluster));
1583
1727
  let evaluation;
1584
1728
  if (options.evaluate) {
1585
1729
  evaluation = await evaluateSkillChange({
@@ -1654,7 +1798,12 @@ var _internals = {
1654
1798
  regenerateSkill,
1655
1799
  clearSkillStale,
1656
1800
  autoApplyProposals,
1657
- unlinkSync
1801
+ unlinkSync,
1802
+ proposalRepoRelativePath,
1803
+ generateEvalStub,
1804
+ writeEvalStub,
1805
+ clearDraftSkillLinks,
1806
+ clearRetiredSkillLinks
1658
1807
  };
1659
1808
 
1660
- export { appendSkillChangelog, evaluateSkillChange, appendRejectedSkillEdit, sanitizeSlug, isValidSlug2 as isValidSlug, proposalPath, activePath, activeRepoRelativePath, DEFAULT_SKILL_MIN_CONFIDENCE, DEFAULT_SKILL_MIN_CONFIRMATIONS, STRONG_SKILL_OUTCOME_COUNT, HIGH_PRIORITY_SKILL_MIN_CONFIDENCE, selectCandidateEntries, isSkillMaturityEligible, clusterEntries, renderSkillMarkdown, generateSkills, parseDraftFrontmatter, activateProposal, findSkillsBySourceKnowledgeId, findStaleSkillsBySourceKnowledgeId, listSkills, autoApplyProposals, inspectSkill, retireSkill, markSkillStale, clearSkillStale, retireOrMarkStale, regenerateSkill, _internals };
1809
+ export { appendSkillChangelog, evaluateSkillChange, appendRejectedSkillEdit, sanitizeSlug, isValidSlug2 as isValidSlug, proposalPath, activePath, activeRepoRelativePath, proposalRepoRelativePath, generateEvalStub, writeEvalStub, DEFAULT_SKILL_MIN_CONFIDENCE, DEFAULT_SKILL_MIN_CONFIRMATIONS, STRONG_SKILL_OUTCOME_COUNT, HIGH_PRIORITY_SKILL_MIN_CONFIDENCE, selectCandidateEntries, isSkillMaturityEligible, clusterEntries, renderSkillMarkdown, generateSkills, parseDraftFrontmatter, activateProposal, findSkillsBySourceKnowledgeId, findStaleSkillsBySourceKnowledgeId, listSkills, autoApplyProposals, inspectSkill, retireSkill, clearDraftSkillLinks, clearRetiredSkillLinks, markSkillStale, clearSkillStale, retireOrMarkStale, regenerateSkill, _internals };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  stripKnownSwarmPrefix
4
- } from "./index-h4c4rrwx.js";
4
+ } from "./index-18690p15.js";
5
5
  import {
6
6
  init_logger,
7
7
  warn
@@ -4,7 +4,7 @@ import {
4
4
  countEntryViolationsInWindow,
5
5
  readKnowledgeEvents,
6
6
  recordKnowledgeEvent
7
- } from "./index-mmny81g1.js";
7
+ } from "./index-y5z2qdvk.js";
8
8
  import {
9
9
  jaccardBigram,
10
10
  readKnowledge,
@@ -12,7 +12,7 @@ import {
12
12
  resolveSwarmKnowledgePath,
13
13
  transactKnowledge,
14
14
  wordBigrams
15
- } from "./index-wg0665ft.js";
15
+ } from "./index-8f10r7ay.js";
16
16
  import {
17
17
  isActiveStatus
18
18
  } from "./index-bfwt3abw.js";
@@ -188,7 +188,7 @@ async function maybeQuarantineOnContradiction(directory, entryId, threshold, win
188
188
  alreadyInactive: true
189
189
  };
190
190
  }
191
- const { quarantineEntry } = await import("./knowledge-validator-exymfcb9.js");
191
+ const { quarantineEntry } = await import("./knowledge-validator-b9nvmzjc.js");
192
192
  await quarantineEntry(directory, entryId, `repeat_contradiction: ${count} contradicted events in ${windowDays}d`, "auto");
193
193
  return { quarantined: true, entryId, contradictionsInWindow: count };
194
194
  } catch {
@@ -1,10 +1,10 @@
1
1
  // @bun
2
2
  import {
3
3
  handleGuardrailExplain
4
- } from "./index-8a8b5wjf.js";
4
+ } from "./index-zh3qh6m7.js";
5
5
  import {
6
6
  handleGuardrailLog
7
- } from "./index-vx76tcxe.js";
7
+ } from "./index-p5vm9gy9.js";
8
8
  import {
9
9
  COMMAND_REGISTRY,
10
10
  SWARM_COMMAND_TOOL_ALLOWLIST,
@@ -80,11 +80,11 @@ import {
80
80
  handleWriteRetroCommand,
81
81
  normalizeSwarmCommandInput,
82
82
  resolveCommand
83
- } from "./index-cqbqynzp.js";
84
- import"./index-wj4jeavn.js";
85
- import"./index-5gnp8fyw.js";
83
+ } from "./index-y3nx3ss0.js";
84
+ import"./index-d3txrx5q.js";
85
+ import"./index-mtzjbaaa.js";
86
86
  import"./index-c8s9a3zh.js";
87
- import"./index-fm7xz1ne.js";
87
+ import"./index-zjh648z4.js";
88
88
  import"./index-j2v3w1ds.js";
89
89
  import"./index-scww5b77.js";
90
90
  import"./index-yw8qpf51.js";
@@ -94,11 +94,11 @@ import {
94
94
  AGENT_TOOL_MAP,
95
95
  ORCHESTRATOR_NAME,
96
96
  stripKnownSwarmPrefix
97
- } from "./index-h4c4rrwx.js";
98
- import"./index-9qxntjtx.js";
99
- import"./index-mmny81g1.js";
100
- import"./index-fz1jgbvw.js";
101
- import"./index-wg0665ft.js";
97
+ } from "./index-18690p15.js";
98
+ import"./index-b15qbvph.js";
99
+ import"./index-y5z2qdvk.js";
100
+ import"./index-41fgssqr.js";
101
+ import"./index-8f10r7ay.js";
102
102
  import"./index-2nt4mq9n.js";
103
103
  import"./index-bfwt3abw.js";
104
104
  import"./index-wvrj16f0.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  redactPath,
4
4
  redactShellCommand
5
- } from "./index-wj4jeavn.js";
5
+ } from "./index-d3txrx5q.js";
6
6
 
7
7
  // src/services/guardrail-log-service.ts
8
8
  import * as fs from "fs/promises";