threadnote 0.7.9 → 0.7.11

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/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  # Threadnote
6
6
 
7
+ [![npm version](https://img.shields.io/npm/v/threadnote.svg)](https://www.npmjs.com/package/threadnote) [![CI](https://img.shields.io/github/actions/workflow/status/Kashkovsky/threadnote/ci.yml?branch=main&label=CI)](https://github.com/Kashkovsky/threadnote/actions/workflows/ci.yml) [![npm downloads](https://img.shields.io/npm/dm/threadnote.svg)](https://www.npmjs.com/package/threadnote) [![license](https://img.shields.io/npm/l/threadnote.svg)](./LICENSE) ![node version](https://img.shields.io/node/v/threadnote.svg)
8
+
7
9
  `threadnote` is a safe local workflow for using [OpenViking](https://openviking.ai/) as shared, agent-neutral context for development work.
8
10
  It is intentionally scoped to curated docs, memories, skills, and handoffs. It is not a source-navigation replacement,
9
11
  and it does not index whole repositories by default.
@@ -36148,7 +36148,6 @@ function exactMemoryScopes(config2, includeArchived) {
36148
36148
  `${userBase}/durable/projects`,
36149
36149
  `${userBase}/handoffs/active`,
36150
36150
  `${userBase}/incidents/active`,
36151
- `${userBase}/events`,
36152
36151
  `${userBase}/shared`,
36153
36152
  `viking://agent/${uriSegment2(config2.agentId)}/memories`,
36154
36153
  // Seeded project resources live outside the user/memories tree. Include
@@ -3868,6 +3868,22 @@ async function gitValue(args, cwd = getInvocationCwd()) {
3868
3868
  }
3869
3869
  return result.stdout.trim();
3870
3870
  }
3871
+ async function resolveRepoName(cwd = getInvocationCwd()) {
3872
+ const repoRoot = await gitValue(["rev-parse", "--show-toplevel"], cwd);
3873
+ if (!repoRoot) {
3874
+ return void 0;
3875
+ }
3876
+ const commonDir = await gitValue(["rev-parse", "--git-common-dir"], repoRoot);
3877
+ if (commonDir) {
3878
+ const absoluteCommonDir = (0, import_node_path.isAbsolute)(commonDir) ? commonDir : (0, import_node_path.resolve)(repoRoot, commonDir);
3879
+ const primaryRoot = (0, import_node_path.basename)(absoluteCommonDir) === ".git" ? (0, import_node_path.dirname)(absoluteCommonDir) : absoluteCommonDir;
3880
+ const name = (0, import_node_path.basename)(primaryRoot).replace(/\.git$/, "");
3881
+ if (name && name !== ".") {
3882
+ return name;
3883
+ }
3884
+ }
3885
+ return (0, import_node_path.basename)(repoRoot);
3886
+ }
3871
3887
  async function runInteractive(executable, args) {
3872
3888
  return new Promise((resolvePromise) => {
3873
3889
  const child = (0, import_node_child_process.spawn)(executable, args, { stdio: "inherit" });
@@ -9547,6 +9563,40 @@ async function runCompact(config, options) {
9547
9563
  await runForget(config, action.uri, { dryRun: false });
9548
9564
  }
9549
9565
  }
9566
+ async function runCompactDiagnostics(config, options) {
9567
+ const project = normalizeOptionalMetadata2(options.project);
9568
+ if (!project) {
9569
+ throw new Error("Provide --project for scoped memory hygiene.");
9570
+ }
9571
+ const topic = normalizeOptionalMetadata2(options.topic);
9572
+ const records = await scopedCompactRecords(config, {
9573
+ kind: options.kind,
9574
+ project
9575
+ });
9576
+ const activeRecords = records.filter((record) => record.metadata.status === "active");
9577
+ const matchingRecords = activeRecords.filter((record) => topic === void 0 || topicForRecord(record) === topic);
9578
+ const counts = /* @__PURE__ */ new Map();
9579
+ for (const record of matchingRecords) {
9580
+ const kind = record.metadata.kind;
9581
+ if (kind === "durable" || kind === "handoff" || kind === "incident") {
9582
+ counts.set(kind, (counts.get(kind) ?? 0) + 1);
9583
+ }
9584
+ }
9585
+ console.log(
9586
+ [
9587
+ "Scope summary:",
9588
+ `- project: ${project}`,
9589
+ `- topic: ${topic ?? "(all)"}`,
9590
+ `- kind: ${options.kind ?? "(handoff, durable, incident)"}`,
9591
+ `- stable records read: ${records.length}`,
9592
+ `- active records in project: ${activeRecords.length}`,
9593
+ `- active records matching topic: ${matchingRecords.length}`,
9594
+ `- matching by kind: ${formatKindCounts(counts)}`,
9595
+ "- skipped by design: archived memories, shared memories, preferences, smoke records, seeded resources, and non-stable timestamped/global paths",
9596
+ ""
9597
+ ].join("\n")
9598
+ );
9599
+ }
9550
9600
  async function scopedCompactRecords(config, options) {
9551
9601
  const kinds = options.kind ? [options.kind] : ["handoff", "durable", "incident"];
9552
9602
  const records = [];
@@ -9575,6 +9625,9 @@ async function scopedCompactRecords(config, options) {
9575
9625
  }
9576
9626
  return records;
9577
9627
  }
9628
+ function formatKindCounts(counts) {
9629
+ return ["handoff", "durable", "incident"].map((kind) => `${kind} ${counts.get(kind) ?? 0}`).join(", ");
9630
+ }
9578
9631
  async function readMemoryRecordsByUri(config, uris) {
9579
9632
  const records = [];
9580
9633
  for (const uri of uris) {
@@ -9975,7 +10028,6 @@ function exactMemoryScopes(config, includeArchived) {
9975
10028
  `${userBase}/durable/projects`,
9976
10029
  `${userBase}/handoffs/active`,
9977
10030
  `${userBase}/incidents/active`,
9978
- `${userBase}/events`,
9979
10031
  `${userBase}/shared`,
9980
10032
  `viking://agent/${uriSegment(config.agentId)}/memories`,
9981
10033
  // Seeded project resources (READMEs, AGENTS.md, SKILL.md, docs/**) live
@@ -10278,7 +10330,7 @@ async function buildHandoff(options) {
10278
10330
  const status = await gitValue(["status", "--short"], repoRoot) ?? "";
10279
10331
  const diffStat = await gitValue(["diff", "--stat", "HEAD"], repoRoot) ?? "";
10280
10332
  const touchedFiles = await gitTouchedFiles(repoRoot);
10281
- const repoName = (0, import_node_path6.basename)(repoRoot);
10333
+ const repoName = await resolveRepoName(repoRoot) ?? (0, import_node_path6.basename)(repoRoot);
10282
10334
  const topicBranch = branch && branch !== "unknown" ? branch : "current";
10283
10335
  const metadata = {
10284
10336
  kind: "handoff",
@@ -10607,8 +10659,7 @@ async function hasManagedClaudeHooks() {
10607
10659
  }
10608
10660
  async function runPreCompactHook(config, options = {}) {
10609
10661
  try {
10610
- const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
10611
- const project = repoRoot ? (0, import_node_path9.basename)(repoRoot) : "general";
10662
+ const project = await resolveRepoName() ?? "general";
10612
10663
  await runHandoff(config, {
10613
10664
  blockers: "- none recorded",
10614
10665
  dryRun: options.dryRun === true,
@@ -10628,11 +10679,10 @@ async function runPreCompactHook(config, options = {}) {
10628
10679
  }
10629
10680
  async function runSessionStartHook(config, options = {}) {
10630
10681
  try {
10631
- const repoRoot = await gitValue(["rev-parse", "--show-toplevel"]);
10632
- if (!repoRoot) {
10682
+ const project = await resolveRepoName();
10683
+ if (!project) {
10633
10684
  return;
10634
10685
  }
10635
- const project = (0, import_node_path9.basename)(repoRoot);
10636
10686
  await emitUpdateBannerIfOutdated(config);
10637
10687
  process.stdout.write(`## Threadnote \u2014 latest context for ${project}
10638
10688
 
@@ -12810,7 +12860,12 @@ var STATIC_FILES = {
12810
12860
  "/index.html": { contentType: "text/html; charset=utf-8", path: "index.html" },
12811
12861
  "/app.css": { contentType: "text/css; charset=utf-8", path: "app.css" },
12812
12862
  "/app.js": { contentType: "text/javascript; charset=utf-8", path: "app.js" },
12813
- "/threadnote-logo.svg": { contentType: "image/svg+xml; charset=utf-8", path: "threadnote-logo.svg", root: "docs" }
12863
+ "/threadnote-logo.svg": { contentType: "image/svg+xml; charset=utf-8", path: "threadnote-logo.svg", root: "docs" },
12864
+ "/threadnote-logo-inverted.svg": {
12865
+ contentType: "image/svg+xml; charset=utf-8",
12866
+ path: "threadnote-logo-inverted.svg",
12867
+ root: "docs"
12868
+ }
12814
12869
  };
12815
12870
  async function runManage(config, options) {
12816
12871
  const token = (0, import_node_crypto2.randomBytes)(24).toString("base64url");
@@ -13018,7 +13073,14 @@ async function handleRequest(context, request, response) {
13018
13073
  if (body.apply === true) {
13019
13074
  requireConfirm(body);
13020
13075
  }
13021
- writeJson(response, 200, await runCaptured(() => runCompact(context.config, body)));
13076
+ writeJson(
13077
+ response,
13078
+ 200,
13079
+ await runCaptured(async () => {
13080
+ await runCompactDiagnostics(context.config, body);
13081
+ await runCompact(context.config, body);
13082
+ })
13083
+ );
13022
13084
  return;
13023
13085
  case "/api/recall":
13024
13086
  writeJson(
package/docs/index.html CHANGED
@@ -1415,7 +1415,7 @@ threadnote remember \
1415
1415
  <div class="v-say">Continue where we left off on this branch.</div>
1416
1416
  <pre class="v-call">recall_context({query: "&lt;branch&gt; latest handoff durable feature memory"})</pre>
1417
1417
  <pre class="v-response">
1418
- → viking://user/you/memories/handoffs/active/&lt;branch&gt;/auto-precompact.md
1418
+ → viking://user/you/memories/handoffs/active/&lt;repo&gt;/auto-precompact.md
1419
1419
  → viking://user/you/memories/durable/projects/&lt;repo&gt;/&lt;feature&gt;.md</pre
1420
1420
  >
1421
1421
  <div class="v-answer">
package/manager/app.css CHANGED
@@ -196,9 +196,9 @@ body.is-resizing-sidebar * {
196
196
 
197
197
  .brand-logo {
198
198
  flex: 0 0 auto;
199
- height: 42px;
199
+ height: 86px;
200
200
  object-fit: contain;
201
- width: 42px;
201
+ width: 86px;
202
202
  }
203
203
 
204
204
  .brand h1,
@@ -264,7 +264,7 @@ body.is-resizing-sidebar * {
264
264
 
265
265
  .tree summary {
266
266
  cursor: pointer;
267
- grid-template-columns: 14px minmax(0, 1fr);
267
+ grid-template-columns: 18px 14px minmax(0, 1fr);
268
268
  list-style: none;
269
269
  }
270
270
 
@@ -380,6 +380,10 @@ body.is-resizing-sidebar * {
380
380
  height: 100%;
381
381
  }
382
382
 
383
+ .health-panel.panel.is-active {
384
+ overflow: auto;
385
+ }
386
+
383
387
  .content-grid,
384
388
  .split {
385
389
  display: grid;
@@ -766,6 +770,34 @@ dd {
766
770
  overflow: auto;
767
771
  }
768
772
 
773
+ .loading-row {
774
+ align-items: center;
775
+ background: var(--panel-alt);
776
+ border: 1px solid var(--accent-line);
777
+ border-radius: 8px;
778
+ color: var(--text);
779
+ display: inline-flex;
780
+ gap: 8px;
781
+ margin-top: 12px;
782
+ padding: 9px 12px;
783
+ }
784
+
785
+ .spinner {
786
+ animation: spin 0.8s linear infinite;
787
+ border: 2px solid var(--accent-line);
788
+ border-top-color: var(--accent);
789
+ border-radius: 50%;
790
+ display: inline-block;
791
+ height: 14px;
792
+ width: 14px;
793
+ }
794
+
795
+ @keyframes spin {
796
+ to {
797
+ transform: rotate(360deg);
798
+ }
799
+ }
800
+
769
801
  .output {
770
802
  background: var(--code-bg);
771
803
  border-radius: var(--radius);
@@ -779,7 +811,9 @@ dd {
779
811
 
780
812
  .doctor-output {
781
813
  margin: 12px 0 0;
814
+ max-height: min(34dvh, 360px);
782
815
  min-height: 120px;
816
+ overflow: auto;
783
817
  }
784
818
 
785
819
  .uri-list {
@@ -819,6 +853,24 @@ dd {
819
853
  z-index: 10;
820
854
  }
821
855
 
856
+ .busy-overlay {
857
+ align-items: center;
858
+ background: rgba(5, 5, 8, 0.34);
859
+ display: flex;
860
+ inset: 0;
861
+ justify-content: center;
862
+ position: fixed;
863
+ z-index: 20;
864
+ }
865
+
866
+ .busy-panel {
867
+ background: #050508;
868
+ border: 1px solid var(--accent-line);
869
+ border-radius: 8px;
870
+ color: var(--text);
871
+ padding: 12px 16px;
872
+ }
873
+
822
874
  .danger-text {
823
875
  color: var(--danger);
824
876
  }