@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.
- package/.claude-plugin/plugin.json +1 -1
- package/bin/spor.js +33 -16
- package/lib/query.js +15 -8
- package/package.json +1 -1
|
@@ -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.
|
|
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
|
|
479
|
-
if (
|
|
480
|
-
err(`offline — could not reach server (${
|
|
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 (
|
|
504
|
+
if (b.status === 404) {
|
|
484
505
|
err(`no such node: ${root}`);
|
|
485
506
|
return 1;
|
|
486
507
|
}
|
|
487
|
-
if (!
|
|
488
|
-
err(`error ${
|
|
508
|
+
if (!b.ok) {
|
|
509
|
+
err(`error ${b.status}`);
|
|
489
510
|
return 1;
|
|
490
511
|
}
|
|
491
|
-
|
|
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
|
-
|
|
1539
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|