threadnote 0.7.11 → 1.0.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/manager/app.js CHANGED
@@ -34731,6 +34731,7 @@
34731
34731
  var SIDEBAR_WIDTH_MIN = 260;
34732
34732
  var SIDEBAR_WIDTH_MAX = 560;
34733
34733
  var token = typeof window === "undefined" ? "" : new URLSearchParams(window.location.search).get("token") ?? "";
34734
+ var EMPTY_SELECTED_URIS = /* @__PURE__ */ new Set();
34734
34735
  function clampSidebarWidth(width) {
34735
34736
  return Math.min(SIDEBAR_WIDTH_MAX, Math.max(SIDEBAR_WIDTH_MIN, Math.round(width)));
34736
34737
  }
@@ -34742,6 +34743,7 @@
34742
34743
  const [panel, setPanel] = (0, import_react2.useState)("doctor");
34743
34744
  const [state, setState] = (0, import_react2.useState)();
34744
34745
  const [tree, setTree] = (0, import_react2.useState)();
34746
+ const [resourceTree, setResourceTree] = (0, import_react2.useState)();
34745
34747
  const [shares, setShares] = (0, import_react2.useState)([]);
34746
34748
  const [doctor, setDoctor] = (0, import_react2.useState)([]);
34747
34749
  const [doctorOutput, setDoctorOutput] = (0, import_react2.useState)("");
@@ -34754,6 +34756,7 @@
34754
34756
  const [openSelect, setOpenSelect] = (0, import_react2.useState)();
34755
34757
  const [filter, setFilter] = (0, import_react2.useState)("");
34756
34758
  const [showSystem, setShowSystem] = (0, import_react2.useState)(false);
34759
+ const [navTreeTab, setNavTreeTab] = (0, import_react2.useState)("memories");
34757
34760
  const [toast, setToast] = (0, import_react2.useState)("");
34758
34761
  const [output, setOutput] = (0, import_react2.useState)("");
34759
34762
  const [recallQuery, setRecallQuery] = (0, import_react2.useState)("");
@@ -34801,7 +34804,7 @@
34801
34804
  setMemoryViewMode("edit");
34802
34805
  return;
34803
34806
  }
34804
- const node2 = tree ? findNode(tree, selectedUri) : void 0;
34807
+ const node2 = findNodeInTrees([tree, resourceTree], selectedUri);
34805
34808
  if (node2?.isDir) {
34806
34809
  setMemory(void 0);
34807
34810
  setContent("");
@@ -34809,8 +34812,12 @@
34809
34812
  setTarget({ kind: "durable", project: "", status: "active", team: node2.sharedTeam ?? "", topic: "" });
34810
34813
  return;
34811
34814
  }
34815
+ if (isResourceUri(selectedUri)) {
34816
+ void loadResource(selectedUri);
34817
+ return;
34818
+ }
34812
34819
  void loadMemory(selectedUri);
34813
- }, [selectedUri, tree]);
34820
+ }, [resourceTree, selectedUri, tree]);
34814
34821
  (0, import_react2.useEffect)(() => {
34815
34822
  const firstAvailable = state?.agents.find((item) => item.available && (item.id === "codex" || item.id === "claude"));
34816
34823
  if (firstAvailable) {
@@ -34818,12 +34825,12 @@
34818
34825
  }
34819
34826
  }, [state]);
34820
34827
  const selectedNode = (0, import_react2.useMemo)(
34821
- () => tree && selectedUri ? findNode(tree, selectedUri) : void 0,
34822
- [tree, selectedUri]
34828
+ () => selectedUri ? findNodeInTrees([tree, resourceTree], selectedUri) : void 0,
34829
+ [resourceTree, selectedUri, tree]
34823
34830
  );
34824
34831
  const visibleSelectedUris = (0, import_react2.useMemo)(
34825
- () => pruneSelectedMemoryUris(selectedUris, tree, { filter, showSystem }),
34826
- [filter, selectedUris, showSystem, tree]
34832
+ () => navTreeTab === "memories" ? pruneSelectedMemoryUris(selectedUris, tree, { filter, showSystem }) : EMPTY_SELECTED_URIS,
34833
+ [filter, navTreeTab, selectedUris, showSystem, tree]
34827
34834
  );
34828
34835
  const selectedList = (0, import_react2.useMemo)(() => [...visibleSelectedUris], [visibleSelectedUris]);
34829
34836
  const outputUris = (0, import_react2.useMemo)(() => vikingUrisFromText(output), [output]);
@@ -34835,6 +34842,7 @@
34835
34842
  ]);
34836
34843
  setState(nextState);
34837
34844
  setTree(nextTree.tree);
34845
+ setResourceTree(nextTree.resourcesTree);
34838
34846
  setShares(nextShares.shares);
34839
34847
  toastMessage("Refreshed");
34840
34848
  }
@@ -34854,6 +34862,15 @@
34854
34862
  topic: next.record?.metadata.topic ?? ""
34855
34863
  });
34856
34864
  }
34865
+ async function loadResource(uri) {
34866
+ const result = await api("/api/read", { uri });
34867
+ setMemory(void 0);
34868
+ setContent(result.content || result.output);
34869
+ setOutput(result.output || result.content);
34870
+ setReadUri(uri);
34871
+ setMemoryViewMode(isMarkdownUri(uri) ? "preview" : "edit");
34872
+ setTarget({ kind: "durable", project: "", status: "active", team: "", topic: "" });
34873
+ }
34857
34874
  async function readContext(uri) {
34858
34875
  const trimmed = uri.trim();
34859
34876
  if (!trimmed) {
@@ -34886,7 +34903,7 @@
34886
34903
  toastMessage(label);
34887
34904
  await refreshTreeOnly();
34888
34905
  if (selectedUri) {
34889
- await loadMemory(selectedUri).catch(() => void 0);
34906
+ await reloadSelected(selectedUri);
34890
34907
  }
34891
34908
  } catch (err) {
34892
34909
  toastMessage(errorMessage(err));
@@ -34911,6 +34928,14 @@
34911
34928
  async function refreshTreeOnly() {
34912
34929
  const next = await api("/api/tree");
34913
34930
  setTree(next.tree);
34931
+ setResourceTree(next.resourcesTree);
34932
+ }
34933
+ async function reloadSelected(uri) {
34934
+ if (isResourceUri(uri)) {
34935
+ await loadResource(uri).catch(() => void 0);
34936
+ } else {
34937
+ await loadMemory(uri).catch(() => void 0);
34938
+ }
34914
34939
  }
34915
34940
  async function saveCurrent() {
34916
34941
  await runAction(
@@ -35058,7 +35083,7 @@
35058
35083
  setSelectedUris(new Set(failedUris));
35059
35084
  if (currentSelectedUri && selectedList.includes(currentSelectedUri)) {
35060
35085
  if (failedUris.includes(currentSelectedUri)) {
35061
- await loadMemory(currentSelectedUri).catch(() => void 0);
35086
+ await reloadSelected(currentSelectedUri);
35062
35087
  } else {
35063
35088
  setSelectedUri(void 0);
35064
35089
  setMemory(void 0);
@@ -35066,7 +35091,7 @@
35066
35091
  setMemoryViewMode("edit");
35067
35092
  }
35068
35093
  } else if (currentSelectedUri) {
35069
- await loadMemory(currentSelectedUri).catch(() => void 0);
35094
+ await reloadSelected(currentSelectedUri);
35070
35095
  }
35071
35096
  await refreshTreeOnly();
35072
35097
  toastMessage(failedUris.length === 0 ? "Bulk action complete" : "Bulk action completed with failures");
@@ -35106,7 +35131,7 @@
35106
35131
  if (draftingConsolidation || applyingConsolidation) {
35107
35132
  return;
35108
35133
  }
35109
- const uris = selectedList.length > 0 ? selectedList : selectedUri ? [selectedUri] : [];
35134
+ const uris = selectedList.length > 0 ? selectedList : selectedUri && !isResourceUri(selectedUri) ? [selectedUri] : [];
35110
35135
  if (uris.length < 2) {
35111
35136
  toastMessage("Select at least two memories");
35112
35137
  return;
@@ -35172,7 +35197,7 @@
35172
35197
  setContent("");
35173
35198
  setMemoryViewMode("edit");
35174
35199
  } else if (currentSelectedUri) {
35175
- await loadMemory(currentSelectedUri).catch(() => void 0);
35200
+ await reloadSelected(currentSelectedUri);
35176
35201
  }
35177
35202
  await refreshTreeOnly();
35178
35203
  toastMessage("Applied consolidation");
@@ -35235,16 +35260,20 @@
35235
35260
  }
35236
35261
  }
35237
35262
  const selectedIsDir = selectedNode?.isDir === true;
35263
+ const selectedIsResource = selectedUri ? isResourceUri(selectedUri) : false;
35238
35264
  const selectedIsMarkdown = Boolean(selectedNode && isMarkdownNode(selectedNode));
35239
35265
  const markdownPreview = markdownBodyForPreview(content3);
35240
- const canMutate = Boolean(selectedUri && !selectedIsDir);
35241
- const canRemoveFolder = Boolean(selectedNode?.isDir && selectedNode.relativePath && !selectedNode.isShared);
35266
+ const canMutate = Boolean(selectedUri && !selectedIsDir && !selectedIsResource);
35267
+ const canRemoveFolder = Boolean(
35268
+ selectedNode?.isDir && selectedNode.relativePath && !selectedNode.isShared && !selectedIsResource
35269
+ );
35242
35270
  const consolidationBusy = draftingConsolidation || applyingConsolidation;
35271
+ const canDraftConsolidation = selectedList.length > 0 || !selectedIsResource;
35243
35272
  const doctorBusy = doctorAction !== void 0;
35244
35273
  const controlsBlocked = bulkAction !== void 0;
35245
35274
  const busyOverlayMessage = bulkAction ? `${actionProgressLabel(bulkAction)} ${selectedList.length} selected ${selectedList.length === 1 ? "memory" : "memories"}...` : "";
35246
35275
  const doctorBusyMessage = doctorAction ? `${doctorAction}...` : "";
35247
- const metadataFieldsDisabled = Boolean(memory || selectedIsDir);
35276
+ const metadataFieldsDisabled = Boolean(memory || selectedIsDir || selectedIsResource);
35248
35277
  const appStyle = { "--sidebar-width": `${sidebarWidth}px` };
35249
35278
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "app", style: appStyle, children: [
35250
35279
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("aside", { className: "sidebar", children: [
@@ -35274,7 +35303,7 @@
35274
35303
  disabled: controlsBlocked,
35275
35304
  value: filter,
35276
35305
  onChange: (event) => setFilter(event.target.value),
35277
- placeholder: "Filter memories",
35306
+ placeholder: "Filter memories and resources",
35278
35307
  type: "search"
35279
35308
  }
35280
35309
  ),
@@ -35290,7 +35319,39 @@
35290
35319
  ),
35291
35320
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Show system files" })
35292
35321
  ] }),
35293
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("nav", { className: "tree", "aria-label": "Memory tree", children: tree ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35322
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "nav-tree-tabs", "aria-label": "Navigation tree", children: [
35323
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35324
+ "button",
35325
+ {
35326
+ className: navTreeTab === "memories" ? "is-active" : void 0,
35327
+ disabled: controlsBlocked,
35328
+ onClick: () => setNavTreeTab("memories"),
35329
+ type: "button",
35330
+ children: "Memories"
35331
+ }
35332
+ ),
35333
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35334
+ "button",
35335
+ {
35336
+ className: navTreeTab === "resources" ? "is-active" : void 0,
35337
+ disabled: controlsBlocked,
35338
+ onClick: () => setNavTreeTab("resources"),
35339
+ type: "button",
35340
+ children: "Resources"
35341
+ }
35342
+ )
35343
+ ] }),
35344
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("nav", { className: "tree", "aria-label": "Context tree", children: navTreeTab === "resources" ? resourceTree ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35345
+ Tree,
35346
+ {
35347
+ filter,
35348
+ node: resourceTree,
35349
+ onSelect: selectTreeUri,
35350
+ selectable: false,
35351
+ selectedUri,
35352
+ showSystem
35353
+ }
35354
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "tree-empty", children: "No resources" }) : tree ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35294
35355
  Tree,
35295
35356
  {
35296
35357
  filter,
@@ -35312,7 +35373,7 @@
35312
35373
  selectionDisabled: bulkAction !== void 0,
35313
35374
  showSystem
35314
35375
  }
35315
- ) : null })
35376
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "tree-empty", children: "No memories" }) })
35316
35377
  ] }),
35317
35378
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35318
35379
  "div",
@@ -35374,7 +35435,7 @@
35374
35435
  "button",
35375
35436
  {
35376
35437
  className: memoryViewMode === "edit" ? "is-active" : void 0,
35377
- disabled: selectedIsDir || controlsBlocked,
35438
+ disabled: selectedIsDir || selectedIsResource || controlsBlocked,
35378
35439
  onClick: () => setMemoryViewMode("edit"),
35379
35440
  children: "Edit"
35380
35441
  }
@@ -35384,7 +35445,7 @@
35384
35445
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35385
35446
  "button",
35386
35447
  {
35387
- disabled: selectedIsDir || controlsBlocked,
35448
+ disabled: selectedIsDir || selectedIsResource || controlsBlocked,
35388
35449
  onClick: () => void (memory ? saveCurrent() : saveNew()),
35389
35450
  children: "Save"
35390
35451
  }
@@ -35431,9 +35492,9 @@
35431
35492
  memoryViewMode === "preview" && selectedIsMarkdown && !selectedIsDir ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MarkdownViewer, { markdown: markdownPreview }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35432
35493
  "textarea",
35433
35494
  {
35434
- disabled: selectedIsDir || controlsBlocked,
35495
+ disabled: selectedIsDir || selectedIsResource || controlsBlocked,
35435
35496
  onChange: (event) => setContent(event.target.value),
35436
- placeholder: selectedIsDir ? "Folder selected" : "Memory content",
35497
+ placeholder: selectedIsDir ? "Folder selected" : selectedIsResource ? "Resource content" : "Memory content",
35437
35498
  spellCheck: false,
35438
35499
  value: content3
35439
35500
  }
@@ -35471,7 +35532,14 @@
35471
35532
  value: agent
35472
35533
  }
35473
35534
  ),
35474
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { disabled: consolidationBusy || controlsBlocked, onClick: () => void draftConsolidation(), children: draftingConsolidation ? "Drafting..." : "Draft" })
35535
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35536
+ "button",
35537
+ {
35538
+ disabled: consolidationBusy || controlsBlocked || !canDraftConsolidation,
35539
+ onClick: () => void draftConsolidation(),
35540
+ children: draftingConsolidation ? "Drafting..." : "Draft"
35541
+ }
35542
+ )
35475
35543
  ] }),
35476
35544
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35477
35545
  "textarea",
@@ -35717,18 +35785,18 @@
35717
35785
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35718
35786
  "button",
35719
35787
  {
35720
- onClick: () => void runAction("Seed dry run complete", () => api("/api/seed", { confirm: true, dryRun: true })),
35721
- children: "Seed Dry Run"
35788
+ onClick: () => window.confirm("Run Threadnote seed and write resources?") ? void runAction("Seed complete", () => api("/api/seed", { confirm: true })) : void 0,
35789
+ children: "Seed"
35722
35790
  }
35723
35791
  ),
35724
35792
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35725
35793
  "button",
35726
35794
  {
35727
- onClick: () => void runAction(
35728
- "Seed skills dry run complete",
35729
- () => api("/api/seed", { confirm: true, dryRun: true, skills: true })
35730
- ),
35731
- children: "Seed Skills Dry Run"
35795
+ onClick: () => window.confirm("Run Threadnote seed-skills and write resources?") ? void runAction(
35796
+ "Seed skills complete",
35797
+ () => api("/api/seed", { confirm: true, skills: true })
35798
+ ) : void 0,
35799
+ children: "Seed Skills"
35732
35800
  }
35733
35801
  )
35734
35802
  ] })
@@ -35741,6 +35809,8 @@
35741
35809
  ] });
35742
35810
  }
35743
35811
  function Tree(props) {
35812
+ const selectable = props.selectable !== false;
35813
+ const selectedUris = props.selectedUris ?? EMPTY_SELECTED_URIS;
35744
35814
  if (!props.showSystem && props.node.isSystem) {
35745
35815
  return null;
35746
35816
  }
@@ -35748,45 +35818,39 @@
35748
35818
  return null;
35749
35819
  }
35750
35820
  if (props.node.isDir) {
35751
- const selectableUris = selectableMemoryUris(props.node, { filter: props.filter, showSystem: props.showSystem });
35752
- const selectedCount = selectableUris.filter((uri) => props.selectedUris.has(uri)).length;
35821
+ const selectableUris = selectable ? selectableMemoryUris(props.node, { filter: props.filter, showSystem: props.showSystem }) : [];
35822
+ const selectedCount = selectableUris.filter((uri) => selectedUris.has(uri)).length;
35753
35823
  const checked = selectableUris.length > 0 && selectedCount === selectableUris.length;
35754
35824
  const indeterminate = selectedCount > 0 && selectedCount < selectableUris.length;
35825
+ const summaryClass = treeItemClass(props.selectedUri === props.node.uri, !selectable);
35755
35826
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("details", { open: props.node.relativePath.split("/").length < 3, children: [
35756
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
35757
- "summary",
35758
- {
35759
- className: props.selectedUri === props.node.uri ? "is-active" : void 0,
35760
- onClick: () => props.onSelect(props.node.uri),
35761
- title: props.node.uri,
35762
- children: [
35763
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35764
- TreeSelectionCheckbox,
35765
- {
35766
- checked,
35767
- disabled: props.selectionDisabled || selectableUris.length === 0,
35768
- indeterminate,
35769
- onChange: (checked2) => props.onToggleSelection(props.node, checked2)
35770
- }
35771
- ),
35772
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", className: "tree-caret" }),
35773
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "tree-name", children: props.node.name })
35774
- ]
35775
- }
35776
- ),
35827
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("summary", { className: summaryClass, onClick: () => props.onSelect(props.node.uri), title: props.node.uri, children: [
35828
+ selectable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35829
+ TreeSelectionCheckbox,
35830
+ {
35831
+ checked,
35832
+ disabled: props.selectionDisabled === true || selectableUris.length === 0,
35833
+ indeterminate,
35834
+ onChange: (checked2) => props.onToggleSelection?.(props.node, checked2)
35835
+ }
35836
+ ) : null,
35837
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "aria-hidden": "true", className: "tree-caret" }),
35838
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "tree-name", children: props.node.name })
35839
+ ] }),
35777
35840
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "tree-children", children: (props.node.children ?? []).map((child) => /* @__PURE__ */ (0, import_react3.createElement)(Tree, { ...props, key: child.uri, node: child })) })
35778
35841
  ] });
35779
35842
  }
35780
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: `tree-row ${props.selectedUri === props.node.uri ? "is-active" : ""}`, children: [
35781
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35843
+ const rowClass = treeItemClass(props.selectedUri === props.node.uri, !selectable, "tree-row");
35844
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: rowClass, children: [
35845
+ selectable ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
35782
35846
  "input",
35783
35847
  {
35784
- checked: props.selectedUris.has(props.node.uri),
35785
- disabled: props.selectionDisabled,
35786
- onChange: (event) => props.onToggleSelection(props.node, event.target.checked),
35848
+ checked: selectedUris.has(props.node.uri),
35849
+ disabled: props.selectionDisabled === true,
35850
+ onChange: (event) => props.onToggleSelection?.(props.node, event.target.checked),
35787
35851
  type: "checkbox"
35788
35852
  }
35789
- ),
35853
+ ) : null,
35790
35854
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { className: "tree-file", onClick: () => props.onSelect(props.node.uri), title: props.node.uri, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "tree-name", children: props.node.name }) })
35791
35855
  ] });
35792
35856
  }
@@ -36066,6 +36130,21 @@
36066
36130
  }
36067
36131
  return void 0;
36068
36132
  }
36133
+ function findNodeInTrees(trees, uri) {
36134
+ for (const tree of trees) {
36135
+ const node2 = tree ? findNode(tree, uri) : void 0;
36136
+ if (node2) {
36137
+ return node2;
36138
+ }
36139
+ }
36140
+ return void 0;
36141
+ }
36142
+ function treeItemClass(active, readOnly, base) {
36143
+ const classes = [base, active ? "is-active" : void 0, readOnly ? "is-readonly" : void 0].filter(
36144
+ (value) => typeof value === "string"
36145
+ );
36146
+ return classes.length > 0 ? classes.join(" ") : void 0;
36147
+ }
36069
36148
  function countFiles(node2) {
36070
36149
  if (!node2.isDir) {
36071
36150
  return 1;
@@ -36101,7 +36180,13 @@
36101
36180
  return changed ? next : selectedUris;
36102
36181
  }
36103
36182
  function isMarkdownNode(node2) {
36104
- return !node2.isDir && node2.name.toLowerCase().endsWith(".md");
36183
+ return !node2.isDir && isMarkdownUri(node2.name);
36184
+ }
36185
+ function isMarkdownUri(uri) {
36186
+ return uri.toLowerCase().endsWith(".md");
36187
+ }
36188
+ function isResourceUri(uri) {
36189
+ return uri === "viking://resources" || uri.startsWith("viking://resources/");
36105
36190
  }
36106
36191
  function markdownBodyForPreview(content3) {
36107
36192
  if (!content3.startsWith("MEMORY\n") && !content3.startsWith("HANDOFF\n")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "threadnote",
3
- "version": "0.7.11",
3
+ "version": "1.0.0",
4
4
  "type": "commonjs",
5
5
  "main": "dist/threadnote.cjs",
6
6
  "description": "Shared local context and handoffs for development agents",