@sporhq/spor 0.18.1 → 0.18.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/.codex-plugin/plugin.json +40 -0
- package/adapters/codex/README.md +22 -17
- package/bin/spor-hook.js +1 -1
- package/bin/spor.js +46 -5
- package/package.json +2 -1
- package/scripts/engines/distill.js +1 -1
- package/scripts/engines/post-tool.js +1 -1
- package/scripts/engines/util.js +12 -0
- package/skills/spor/SKILL.md +13 -1
|
@@ -2,7 +2,7 @@
|
|
|
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.18.
|
|
5
|
+
"version": "0.18.2",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "losthammer"
|
|
8
8
|
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "spor",
|
|
3
|
+
"version": "0.18.2",
|
|
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
|
+
"author": {
|
|
6
|
+
"name": "Spor",
|
|
7
|
+
"url": "https://github.com/sporhq/spor"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://sporhq.io",
|
|
10
|
+
"repository": "https://github.com/sporhq/spor",
|
|
11
|
+
"license": "Apache-2.0",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"knowledge-graph",
|
|
14
|
+
"context",
|
|
15
|
+
"memory",
|
|
16
|
+
"agents",
|
|
17
|
+
"spor",
|
|
18
|
+
"provenance"
|
|
19
|
+
],
|
|
20
|
+
"skills": "./skills/",
|
|
21
|
+
"interface": {
|
|
22
|
+
"displayName": "Spor Context Compiler",
|
|
23
|
+
"shortDescription": "Graph-backed briefings, captures, and queue triage for Codex.",
|
|
24
|
+
"longDescription": "Spor keeps durable project context in a typed knowledge graph, then helps Codex brief tasks, capture follow-up work, correct stale context, and work the decision queue.",
|
|
25
|
+
"developerName": "Spor",
|
|
26
|
+
"category": "Productivity",
|
|
27
|
+
"capabilities": [
|
|
28
|
+
"Knowledge graph",
|
|
29
|
+
"Task briefings",
|
|
30
|
+
"Queue triage"
|
|
31
|
+
],
|
|
32
|
+
"websiteURL": "https://sporhq.io",
|
|
33
|
+
"defaultPrompt": [
|
|
34
|
+
"Brief this Spor task before I start work.",
|
|
35
|
+
"Capture this follow-up in the graph.",
|
|
36
|
+
"Show me what is next in the Spor queue."
|
|
37
|
+
],
|
|
38
|
+
"brandColor": "#3B82F6"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/adapters/codex/README.md
CHANGED
|
@@ -7,7 +7,18 @@ is just a manifest over `bin/spor-hook.js`.
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
9
|
1. Clone this repo somewhere stable, e.g. `~/tools/spor`.
|
|
10
|
-
2.
|
|
10
|
+
2. Install the Codex plugin, hook manifest, and backfill custom agent:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
~/tools/spor/bin/spor install codex
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This registers this checkout as the `spor` Codex marketplace, runs
|
|
17
|
+
`codex plugin add spor@spor`, resolves the hook path placeholder, and keeps
|
|
18
|
+
the custom agent sourced from `agents/backfill.md`.
|
|
19
|
+
|
|
20
|
+
3. Manual hook-only install, if you are debugging the adapter without the Codex
|
|
21
|
+
plugin:
|
|
11
22
|
|
|
12
23
|
```sh
|
|
13
24
|
SPOR_ROOT=~/tools/spor
|
|
@@ -18,7 +29,8 @@ is just a manifest over `bin/spor-hook.js`.
|
|
|
18
29
|
(Or merge into an existing `~/.codex/hooks.json` / `[hooks]` table in
|
|
19
30
|
`~/.codex/config.toml`. Per-repo installs go in `<repo>/.codex/hooks.json`.)
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
4. Add the backfill custom agent manually, only if you skipped
|
|
33
|
+
`spor install codex`:
|
|
22
34
|
|
|
23
35
|
```sh
|
|
24
36
|
mkdir -p ~/.codex/agents
|
|
@@ -31,27 +43,20 @@ is just a manifest over `bin/spor-hook.js`.
|
|
|
31
43
|
} > ~/.codex/agents/spor-backfill.toml
|
|
32
44
|
```
|
|
33
45
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
4. Approve the hooks in Codex's `/hooks` trust prompt on first run.
|
|
38
|
-
5. Environment (the same variables on every host):
|
|
46
|
+
5. Approve the hooks in Codex's `/hooks` trust prompt on first run.
|
|
47
|
+
6. Environment (the same variables on every host):
|
|
39
48
|
|
|
40
49
|
```sh
|
|
41
50
|
export SPOR_SERVER=https://spor.example.com # remote mode
|
|
42
51
|
export SPOR_TOKEN=spor_pat_...
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
# Capture-nudge backend — same contract, but it runs synchronously in the
|
|
47
|
-
# tool loop, so prefer a fast model. SPOR_NUDGE=0 disables it.
|
|
48
|
-
export SPOR_NUDGE_CMD='codex exec -'
|
|
52
|
+
# Capture-nudge defaults to `codex exec --model gpt-5.4-mini -`; distill
|
|
53
|
+
# defaults to `codex exec -`. Set SPOR_NUDGE_CMD / SPOR_DISTILL_CMD only to
|
|
54
|
+
# override those defaults; SPOR_NUDGE=0 disables the nudge.
|
|
49
55
|
```
|
|
50
56
|
|
|
51
|
-
(Legacy `SUBSTRATE_*` names are still read.)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
nudge bounds (`SPOR_NUDGE_MAX`, `SPOR_NUDGE_TIMEOUT`).
|
|
57
|
+
(Legacy `SUBSTRATE_*` names are still read.) See
|
|
58
|
+
[adapters/README.md](../README.md) for the nudge bounds (`SPOR_NUDGE_MAX`,
|
|
59
|
+
`SPOR_NUDGE_TIMEOUT`).
|
|
55
60
|
|
|
56
61
|
## Event mapping
|
|
57
62
|
|
package/bin/spor-hook.js
CHANGED
|
@@ -153,7 +153,7 @@ async function main() {
|
|
|
153
153
|
// `enabled:false` / `mode:off` makes every hook a no-op (exit 0, no output)
|
|
154
154
|
// so an unrelated side project doesn't pollute the shared graph neighborhood;
|
|
155
155
|
// default-enabled, so a repo without config is byte-identical to before.
|
|
156
|
-
const cfg = u.useConfig({ cwd: payload.cwd || process.cwd() });
|
|
156
|
+
const cfg = u.useConfig({ cwd: payload.cwd || process.cwd(), host });
|
|
157
157
|
if (cfg.warnings.length) {
|
|
158
158
|
try {
|
|
159
159
|
const log = u.makeLogger(path.join(u.graphHome(), "journal", "remote.log"), "config: ");
|
package/bin/spor.js
CHANGED
|
@@ -4870,7 +4870,7 @@ function cmdLink(cfg, { positionals }) {
|
|
|
4870
4870
|
const HOSTS = {
|
|
4871
4871
|
claude: { kind: "claude", label: "Claude Code" },
|
|
4872
4872
|
codex: {
|
|
4873
|
-
kind: "
|
|
4873
|
+
kind: "codex",
|
|
4874
4874
|
label: "Codex CLI",
|
|
4875
4875
|
src: ["adapters", "codex", "hooks.json"],
|
|
4876
4876
|
user: [".codex", "hooks.json"],
|
|
@@ -4907,6 +4907,12 @@ function claudeCmd() {
|
|
|
4907
4907
|
return process.env.SPOR_CLAUDE_CMD || "claude";
|
|
4908
4908
|
}
|
|
4909
4909
|
|
|
4910
|
+
// The Codex CLI binary, overridable for tests. Codex owns plugin install state,
|
|
4911
|
+
// so all plugin shell-outs route through this seam instead of writing its cache.
|
|
4912
|
+
function codexCmd() {
|
|
4913
|
+
return process.env.SPOR_CODEX_CMD || "codex";
|
|
4914
|
+
}
|
|
4915
|
+
|
|
4910
4916
|
function spawnPortableSync(cmd, args, opts = {}) {
|
|
4911
4917
|
if (process.platform !== "win32" || opts.shell) return spawnSync(cmd, args, opts);
|
|
4912
4918
|
const resolved = u.whichSync(cmd) || cmd;
|
|
@@ -5264,6 +5270,37 @@ function installClaude(scope, dryRun) {
|
|
|
5264
5270
|
return 0;
|
|
5265
5271
|
}
|
|
5266
5272
|
|
|
5273
|
+
// Codex CLI: install the repo as a Codex plugin via its marketplace commands,
|
|
5274
|
+
// then keep the hook manifest + backfill custom agent installed. The plugin
|
|
5275
|
+
// manifest intentionally does not carry hooks (Codex plugin validation rejects
|
|
5276
|
+
// that field), so both halves matter.
|
|
5277
|
+
function installCodex(scope, dryRun) {
|
|
5278
|
+
const cmd = codexCmd();
|
|
5279
|
+
const mpArgs = ["plugin", "marketplace", "add", ROOT];
|
|
5280
|
+
const pluginArgs = ["plugin", "add", "spor@spor"];
|
|
5281
|
+
if (dryRun) {
|
|
5282
|
+
out(`would run: ${cmd} ${mpArgs.join(" ")}`);
|
|
5283
|
+
out(`would run: ${cmd} ${pluginArgs.join(" ")}`);
|
|
5284
|
+
return installHookHost(HOSTS.codex, scope, true);
|
|
5285
|
+
}
|
|
5286
|
+
if (cmd === "codex" && !hasCmd("codex")) {
|
|
5287
|
+
err("codex CLI not on PATH — install Codex, then re-run 'spor install codex'.");
|
|
5288
|
+
return 1;
|
|
5289
|
+
}
|
|
5290
|
+
const mp = spawnPortableSync(cmd, mpArgs, { encoding: "utf8" });
|
|
5291
|
+
if (mp.status !== 0 && !/already|exists|known/i.test((mp.stderr || "") + (mp.stdout || ""))) {
|
|
5292
|
+
err(`codex plugin marketplace add failed: ${(mp.stderr || mp.stdout || "").trim() || "unknown error"}`);
|
|
5293
|
+
return 1;
|
|
5294
|
+
}
|
|
5295
|
+
const plugin = spawnPortableSync(cmd, pluginArgs, { stdio: "inherit" });
|
|
5296
|
+
if (plugin.status !== 0) {
|
|
5297
|
+
err(`codex plugin add failed (exit ${plugin.status == null ? "?" : plugin.status})`);
|
|
5298
|
+
return 1;
|
|
5299
|
+
}
|
|
5300
|
+
out("installed spor@spor into Codex.");
|
|
5301
|
+
return installHookHost(HOSTS.codex, scope, false);
|
|
5302
|
+
}
|
|
5303
|
+
|
|
5267
5304
|
// JSON-hook hosts (codex/cursor/copilot/gemini): render + merge into the target.
|
|
5268
5305
|
function installHookHost(spec, scope, dryRun) {
|
|
5269
5306
|
const target = targetPath(spec, scope);
|
|
@@ -5366,6 +5403,7 @@ async function cmdInstall(cfg, { values, positionals: pos }) {
|
|
|
5366
5403
|
const spec = HOSTS[host];
|
|
5367
5404
|
let r;
|
|
5368
5405
|
if (spec.kind === "claude") r = installClaude(scope, dryRun);
|
|
5406
|
+
else if (spec.kind === "codex") r = installCodex(scope, dryRun);
|
|
5369
5407
|
else if (spec.kind === "plugin") r = installPluginHost(spec, scope, dryRun);
|
|
5370
5408
|
else r = installHookHost(spec, scope, dryRun);
|
|
5371
5409
|
if (r !== 0) rc = r;
|
|
@@ -5386,10 +5424,12 @@ async function cmdInstall(cfg, { values, positionals: pos }) {
|
|
|
5386
5424
|
// (issue-spor-upgrade-no-plugin-refresh) An npm bump updates the package on disk
|
|
5387
5425
|
// but NOT what an agent already loaded: Claude Code runs its OWN cached copy of
|
|
5388
5426
|
// the plugin, so it keeps running stale skills/hooks until 'plugin update' swaps
|
|
5389
|
-
// the copy.
|
|
5390
|
-
//
|
|
5391
|
-
//
|
|
5392
|
-
//
|
|
5427
|
+
// the copy. Codex also caches an installed plugin copy, while its hooks still
|
|
5428
|
+
// reference this checkout by absolute path. Re-running the idempotent install
|
|
5429
|
+
// refreshes Codex's marketplace/plugin cache and rewrites the hook path. The
|
|
5430
|
+
// other hook hosts reference the package by absolute path, so they only go stale
|
|
5431
|
+
// if the checkout MOVED. This verb does both in one step and tells the user to
|
|
5432
|
+
// restart the session.
|
|
5393
5433
|
|
|
5394
5434
|
// Refresh Claude Code's loaded plugin (marketplace add to register/repoint the
|
|
5395
5435
|
// source, then the shared marketplace+plugin update). Returns 0/1.
|
|
@@ -5466,6 +5506,7 @@ async function cmdUpgrade(cfg, { values, positionals: pos }) {
|
|
|
5466
5506
|
for (const host of hosts) {
|
|
5467
5507
|
let r;
|
|
5468
5508
|
if (host === "claude") r = upgradeClaude(scope, dryRun);
|
|
5509
|
+
else if (host === "codex") r = installCodex(scope, dryRun);
|
|
5469
5510
|
else {
|
|
5470
5511
|
// Re-running install refreshes the absolute __SPOR_ROOT__ path (a no-op
|
|
5471
5512
|
// when the path is unchanged; repairs a moved checkout when it is not).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sporhq/spor",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.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",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
29
|
".claude-plugin/",
|
|
30
|
+
".codex-plugin/",
|
|
30
31
|
"lib/",
|
|
31
32
|
"bin/",
|
|
32
33
|
"scripts/engines/",
|
|
@@ -252,7 +252,7 @@ async function distill(input) {
|
|
|
252
252
|
// runs async on SessionEnd, so it tolerates more than the nudge, but a wedged
|
|
253
253
|
// CLI should still not hang the SessionEnd hook indefinitely).
|
|
254
254
|
const timeoutMs = u.cfgNum("distill.timeoutMs", "DISTILL_TIMEOUT", 120000);
|
|
255
|
-
const distillCmd = u.cfgStr("distill.cmd", "DISTILL_CMD");
|
|
255
|
+
const distillCmd = u.cfgStr("distill.cmd", "DISTILL_CMD") || u.hostDefaultBackendCmd("distill");
|
|
256
256
|
if (distillCmd) {
|
|
257
257
|
backend = `cmd:${distillCmd}`;
|
|
258
258
|
response = u.runBackendCmd(distillCmd, prompt, { timeoutMs });
|
|
@@ -181,7 +181,7 @@ async function nudge({ input, graph, slug, session, file, remote }) {
|
|
|
181
181
|
// PostToolUse budget (nudge.timeoutMs / SPOR_NUDGE_TIMEOUT, default 30s — room
|
|
182
182
|
// for a ~17s claude -p haiku cold boot, well under the host's 60s).
|
|
183
183
|
const timeoutMs = u.cfgNum("nudge.timeoutMs", "NUDGE_TIMEOUT", 30000);
|
|
184
|
-
const nudgeCmd = u.cfgStr("nudge.cmd", "NUDGE_CMD");
|
|
184
|
+
const nudgeCmd = u.cfgStr("nudge.cmd", "NUDGE_CMD") || u.hostDefaultBackendCmd("nudge");
|
|
185
185
|
if (nudgeCmd) {
|
|
186
186
|
backend = `cmd:${nudgeCmd}`;
|
|
187
187
|
response = u.runBackendCmd(nudgeCmd, prompt, { timeoutMs });
|
package/scripts/engines/util.js
CHANGED
|
@@ -11,6 +11,7 @@ const crypto = require("crypto");
|
|
|
11
11
|
const { execFileSync, spawnSync, spawn } = require("child_process");
|
|
12
12
|
|
|
13
13
|
const ROOT = path.resolve(__dirname, "..", "..");
|
|
14
|
+
const CODEX_NUDGE_MODEL = "gpt-5.4-mini";
|
|
14
15
|
|
|
15
16
|
const home = require(path.join(ROOT, "lib", "shell", "home.js"));
|
|
16
17
|
// The harness vocabulary the capability probe emits — owned by the pure matcher
|
|
@@ -25,7 +26,9 @@ const { HARNESS_BINARIES, SPOR_MCP_NAME } = require(path.join(ROOT, "lib", "kern
|
|
|
25
26
|
// falls back to the exact env dual-read it replaced, so those paths stay
|
|
26
27
|
// byte-identical (norm-cc-byte-identical-refactor).
|
|
27
28
|
let _config = null;
|
|
29
|
+
let _host = null;
|
|
28
30
|
function useConfig(opts) {
|
|
31
|
+
_host = opts && opts.host ? opts.host : null;
|
|
29
32
|
_config = require(path.join(ROOT, "lib", "config.js")).loadConfig(opts);
|
|
30
33
|
return _config;
|
|
31
34
|
}
|
|
@@ -35,6 +38,7 @@ function useConfig(opts) {
|
|
|
35
38
|
// (serverBase/bearer/graphHome) honors a file-config or --org tenant instead of
|
|
36
39
|
// silently falling back to raw env.
|
|
37
40
|
function setConfig(cfg) {
|
|
41
|
+
_host = null;
|
|
38
42
|
_config = cfg;
|
|
39
43
|
return cfg;
|
|
40
44
|
}
|
|
@@ -42,6 +46,7 @@ function config() {
|
|
|
42
46
|
return _config;
|
|
43
47
|
}
|
|
44
48
|
function clearConfig() {
|
|
49
|
+
_host = null;
|
|
45
50
|
_config = null; // test hook
|
|
46
51
|
}
|
|
47
52
|
// Config-aware string read: the active cascade value, else env dual-read.
|
|
@@ -61,6 +66,12 @@ function cfgNum(keyPath, envName, fallback) {
|
|
|
61
66
|
return Number.isFinite(n) ? n : fallback;
|
|
62
67
|
}
|
|
63
68
|
|
|
69
|
+
function hostDefaultBackendCmd(kind) {
|
|
70
|
+
if (_host === "codex" && kind === "nudge") return `codex exec --model ${CODEX_NUDGE_MODEL} -`;
|
|
71
|
+
if (_host === "codex" && kind === "distill") return "codex exec -";
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
|
|
64
75
|
function graphHome() {
|
|
65
76
|
return _config ? _config.graphHome() : home.graphHome();
|
|
66
77
|
}
|
|
@@ -1068,6 +1079,7 @@ module.exports = {
|
|
|
1068
1079
|
clearConfig,
|
|
1069
1080
|
cfgStr,
|
|
1070
1081
|
cfgNum,
|
|
1082
|
+
hostDefaultBackendCmd,
|
|
1071
1083
|
jqNow,
|
|
1072
1084
|
isoMs,
|
|
1073
1085
|
isoSeconds,
|
package/skills/spor/SKILL.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: spor
|
|
3
|
-
description:
|
|
3
|
+
description: >
|
|
4
|
+
The operating manual for Spor — a knowledge-graph tool your training does not
|
|
5
|
+
cover. Load it before any Spor graph operation so you use the right CLI
|
|
6
|
+
syntax, node and edge format, MCP tools, and REST API instead of rediscovering
|
|
7
|
+
them: querying or searching the graph, reading or writing nodes, adding edges,
|
|
8
|
+
capturing or deferring work, running spor CLI commands or Spor MCP tools,
|
|
9
|
+
working in local vs remote mode, or defining a new node/edge type or schema.
|
|
10
|
+
It also routes you to the right /spor action skill (defer, brief, next,
|
|
11
|
+
correct, ask, backfill) for a specific operation, and explains the node/edge
|
|
12
|
+
mental model when you need it. For FIRST-TIME setup — installing, creating an
|
|
13
|
+
identity, joining a team graph, or "spor isn't doing anything" — use
|
|
14
|
+
/spor:onboard instead. When unsure how to operate Spor, consult this before
|
|
15
|
+
guessing.
|
|
4
16
|
---
|
|
5
17
|
|
|
6
18
|
# Operate Spor
|