@sporhq/spor 0.5.0 → 0.5.2

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.
@@ -2,6 +2,6 @@
2
2
  "name": "spor",
3
3
  "displayName": "Spor Context Compiler",
4
4
  "description": "Maintains a typed, versioned knowledge graph and compiles compact briefings from it: session-start injection, per-prompt relevance digests, capture at discovery, end-of-session distillation, decision queue.",
5
- "version": "0.5.0",
5
+ "version": "0.5.2",
6
6
  "author": { "name": "losthammer" }
7
7
  }
package/bin/spor.js CHANGED
@@ -450,6 +450,27 @@ async function cmdCompile(cfg, verb, args) {
450
450
  return passthrough("compile.js", compileArgs);
451
451
  }
452
452
 
453
+ // Compile a node's remote briefing the way the /spor:brief skill does: the raw
454
+ // node (GET /v1/nodes/<id>) plus a title/summary-seeded /v1/digest for its
455
+ // neighborhood, concatenated. Shared by compileRemote (brief / compile --root)
456
+ // and compileBriefing (dispatch) so the two can't drift — dispatch used to
457
+ // embed only the bare node, a thinner standing context than an interactive
458
+ // brief (issue-spor-dispatch-briefing-omits-neighborhood). Returns
459
+ // {transport,error} | {ok:false,status} | {ok:true,status,text}; the
460
+ // neighborhood is fail-soft (a failed/empty digest just yields the raw node).
461
+ async function remoteNodeBriefing(cfg, { root, project }) {
462
+ const r = await remote.get(cfg, `/v1/nodes/${encodeURIComponent(root)}`, { timeoutMs: 8000 });
463
+ if (r.transport) return { transport: true, error: r.error, text: "" };
464
+ if (!r.ok) return { ok: false, status: r.status, text: "" };
465
+ const raw = (r.json && r.json.raw) || r.text || "";
466
+ // Seed the neighborhood digest from the node's own title/summary (the REST
467
+ // /v1/digest is query-mode only — root compile is not exposed over REST).
468
+ const seed = (r.json && (r.json.title || r.json.summary)) || fmField(raw, "title") || fmField(raw, "summary") || root;
469
+ const d = await remote.post(cfg, "/v1/digest", project ? { query: seed, project } : { query: seed }, { timeoutMs: 8000 });
470
+ const neighborhood = d.ok && d.json && d.json.found !== false ? d.json.text || "" : "";
471
+ return { ok: true, status: r.status, text: neighborhood ? `${raw}\n\n${neighborhood}` : raw };
472
+ }
473
+
453
474
  // The remote arm of compile/brief. Mirrors the /spor:brief skill's remote
454
475
  // resolution: a node id -> the raw node plus a title/summary-seeded /v1/digest
455
476
  // for its neighborhood; free text -> POST /v1/digest. --skeleton has no server
@@ -475,27 +496,20 @@ async function compileRemote(cfg, args) {
475
496
 
476
497
  let text = "";
477
498
  if (root) {
478
- const r = await remote.get(cfg, `/v1/nodes/${encodeURIComponent(root)}`, { timeoutMs: 8000 });
479
- if (r.transport) {
480
- err(`offline — could not reach server (${r.error})`);
499
+ const b = await remoteNodeBriefing(cfg, { root, project });
500
+ if (b.transport) {
501
+ err(`offline — could not reach server (${b.error})`);
481
502
  return 1;
482
503
  }
483
- if (r.status === 404) {
504
+ if (b.status === 404) {
484
505
  err(`no such node: ${root}`);
485
506
  return 1;
486
507
  }
487
- if (!r.ok) {
488
- err(`error ${r.status}`);
508
+ if (!b.ok) {
509
+ err(`error ${b.status}`);
489
510
  return 1;
490
511
  }
491
- const raw = (r.json && r.json.raw) || r.text || "";
492
- // Seed the neighborhood digest from the node's own title/summary, exactly as
493
- // the /spor:brief skill does (the REST /v1/digest is query-mode only — root
494
- // compile is not exposed over REST, only via the query_graph MCP tool).
495
- const seed = (r.json && (r.json.title || r.json.summary)) || fmField(raw, "title") || fmField(raw, "summary") || root;
496
- const d = await remote.post(cfg, "/v1/digest", project ? { query: seed, project } : { query: seed }, { timeoutMs: 8000 });
497
- const neighborhood = d.ok && d.json && d.json.found !== false ? d.json.text || "" : "";
498
- text = neighborhood ? `${raw}\n\n${neighborhood}` : raw;
512
+ text = b.text;
499
513
  } else {
500
514
  const body = { query };
501
515
  if (project) body.project = project;
@@ -1535,8 +1549,11 @@ async function resolveNode(cfg, id) {
1535
1549
  async function compileBriefing(cfg, { nodeId, query, full, project }) {
1536
1550
  if (cfg.mode() === "remote") {
1537
1551
  if (nodeId) {
1538
- const r = await remote.get(cfg, `/v1/nodes/${encodeURIComponent(nodeId)}`, { timeoutMs: 8000 });
1539
- return r.ok && r.json ? r.json.raw || r.text || "" : "";
1552
+ // Same raw-node + seeded-neighborhood resolution as `spor brief <id>`, so
1553
+ // a dispatched agent's standing context matches an interactive brief
1554
+ // rather than the bare node (issue-spor-dispatch-briefing-omits-neighborhood).
1555
+ const b = await remoteNodeBriefing(cfg, { root: nodeId, project });
1556
+ return b.ok ? b.text : "";
1540
1557
  }
1541
1558
  const r = await remote.post(cfg, "/v1/digest", project ? { query, project } : { query });
1542
1559
  return r.ok && r.json && r.json.found !== false ? r.json.text || "" : "";
package/lib/query.js CHANGED
@@ -145,14 +145,22 @@ if (require.main === module) {
145
145
  to: has("to") ? opt("to", null) : null,
146
146
  });
147
147
 
148
+ // Project a loaded node for JSON output: drop the parser's load-time `file`
149
+ // artifact, and drop pin/exclude when empty — the regex parser initializes
150
+ // both to [] on every node, so they are noise unless a briefing/correction
151
+ // actually populated them (a non-empty list is kept). Deletes from a shallow
152
+ // copy so the original key order is preserved.
153
+ const cleanNode = (n) => {
154
+ const out = { ...n };
155
+ delete out.file;
156
+ if (Array.isArray(out.pin) && !out.pin.length) delete out.pin;
157
+ if (Array.isArray(out.exclude) && !out.exclude.length) delete out.exclude;
158
+ return out;
159
+ };
160
+
148
161
  if (has("json")) {
149
162
  if (r.edges) process.stdout.write(JSON.stringify(r.edges, null, 2) + "\n");
150
- else {
151
- // Strip the parser's internal `file` field from JSON node output (it's a
152
- // load-time artifact, not graph data).
153
- const clean = r.nodes.map(({ file, ...rest }) => rest);
154
- process.stdout.write(JSON.stringify(clean, null, 2) + "\n");
155
- }
163
+ else process.stdout.write(JSON.stringify(r.nodes.map(cleanNode), null, 2) + "\n");
156
164
  process.exit(0);
157
165
  }
158
166
 
@@ -183,8 +191,7 @@ if (require.main === module) {
183
191
  if (raw != null) {
184
192
  process.stdout.write(raw.endsWith("\n") ? raw : raw + "\n");
185
193
  } else {
186
- const { file: _f, ...rest } = n; // drop the load-time artifact
187
- process.stdout.write(JSON.stringify(rest, null, 2) + "\n");
194
+ process.stdout.write(JSON.stringify(cleanNode(n), null, 2) + "\n"); // raw file unreadable: reconstructed view
188
195
  }
189
196
  console.log("---");
190
197
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sporhq/spor",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Spor — a shared memory substrate for teams and agents. Decisions, their reasons, and the traces they leave. Knowledge-graph context compiler: session-start briefings, per-prompt digests, capture at discovery, end-of-session distillation, decision queue.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Anthony Allen",