@sporhq/spor 0.2.7 → 0.2.9
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/API.md +112 -12
- package/GRAPH.md +92 -5
- package/QUEUE.md +81 -15
- package/README.md +109 -3
- package/adapters/README.md +32 -0
- package/adapters/codex/README.md +7 -3
- package/adapters/copilot/README.md +3 -2
- package/adapters/cursor/README.md +3 -2
- package/adapters/gemini/README.md +3 -0
- package/adapters/opencode/README.md +3 -0
- package/bin/spor-hook +26 -2
- package/bin/spor-hook.js +18 -0
- package/bin/spor.js +534 -20
- package/lib/config.js +113 -5
- package/lib/graph.js +3 -0
- package/lib/kernel/graph.js +45 -33
- package/lib/kernel/queue.js +199 -29
- package/lib/kernel/registry.js +118 -1
- package/lib/kernel/resolution.js +11 -2
- package/lib/queue.js +64 -2
- package/lib/seed/schema-artifact.md +53 -3
- package/lib/seed/schema-decision.md +14 -2
- package/lib/seed/schema-edge-assigned.md +3 -1
- package/lib/seed/schema-edge-stewards.md +3 -1
- package/lib/seed/schema-person.md +10 -1
- package/lib/seed/schema-task.md +37 -10
- package/package.json +5 -1
- package/scripts/engines/debounce-watcher.js +13 -0
- package/scripts/engines/distill.js +45 -8
- package/scripts/engines/doctor.js +188 -0
- package/scripts/engines/post-tool.js +166 -4
- package/scripts/engines/session-start.js +92 -10
- package/scripts/engines/util.js +109 -4
- package/skills/next/SKILL.md +5 -2
package/adapters/README.md
CHANGED
|
@@ -56,3 +56,35 @@ export SPOR_DISTILL_CMD="$HOME/repos/spor/scripts/distill-gemini.sh"
|
|
|
56
56
|
|
|
57
57
|
The journal records the backend per call (`journal/llm-calls/`), so distill
|
|
58
58
|
quality stays observable across backends through the same eval loop.
|
|
59
|
+
|
|
60
|
+
## Capture-nudge backend
|
|
61
|
+
|
|
62
|
+
The post-tool capture nudge (a Write/Edit of substantial prose to a `.md`
|
|
63
|
+
outside the graph runs a classifier and, if it finds capturable facts, injects
|
|
64
|
+
a capture-or-dismiss nudge) uses the **same backend contract** as the
|
|
65
|
+
distiller — prompt on stdin, response on stdout — but a separate variable so
|
|
66
|
+
you can point the two at different models:
|
|
67
|
+
|
|
68
|
+
- `SPOR_NUDGE_CMD` — custom classifier command (defaults to `claude -p --model
|
|
69
|
+
haiku`, same as the distiller). Because the contract is identical,
|
|
70
|
+
`scripts/distill-gemini.sh` doubles as a nudge backend (it keys off
|
|
71
|
+
`SPOR_DISTILL_MODEL`, default `gemini-3.5-flash`):
|
|
72
|
+
|
|
73
|
+
```sh
|
|
74
|
+
export SPOR_NUDGE_CMD="$HOME/repos/spor/scripts/distill-gemini.sh"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This is the recommended latency fix: the nudge runs **synchronously** in the
|
|
78
|
+
tool loop, and Gemini Flash returns in ~2–7s versus ~17s for a `claude -p`
|
|
79
|
+
cold boot, with no quality regression (dec-cc-nudge-flash-latency).
|
|
80
|
+
- `SPOR_NUDGE=0` — disable the nudge entirely.
|
|
81
|
+
- `SPOR_NUDGE_MAX` — per-session ceiling on classifier calls (default 20),
|
|
82
|
+
bounding spend/latency in a docs-heavy session where many `.md` files each
|
|
83
|
+
classify to nothing. (A separate cap stops after 3 *fired* nudges.)
|
|
84
|
+
- `SPOR_NUDGE_TIMEOUT` — milliseconds before a hung classifier is killed
|
|
85
|
+
(default 30000), so a wedged backend can't block the tool loop.
|
|
86
|
+
|
|
87
|
+
(Legacy `SUBSTRATE_*` spellings are still read for all four.) The same
|
|
88
|
+
`SPOR_DISTILLING=1` recursion guard is exported around the call, and the
|
|
89
|
+
journal records each nudge call under `journal/llm-calls/` (source `nudge`)
|
|
90
|
+
through the same eval loop as the distiller.
|
package/adapters/codex/README.md
CHANGED
|
@@ -27,11 +27,15 @@ is just a manifest over `bin/spor-hook`.
|
|
|
27
27
|
# Distiller backend — Codex hosts usually won't have the claude CLI.
|
|
28
28
|
# Contract: prompt on stdin, response on stdout.
|
|
29
29
|
export SPOR_DISTILL_CMD='codex exec -'
|
|
30
|
+
# Capture-nudge backend — same contract, but it runs synchronously in the
|
|
31
|
+
# tool loop, so prefer a fast model. SPOR_NUDGE=0 disables it.
|
|
32
|
+
export SPOR_NUDGE_CMD='codex exec -'
|
|
30
33
|
```
|
|
31
34
|
|
|
32
|
-
(Legacy `SUBSTRATE_*` names are still read.) Without `SPOR_DISTILL_CMD`
|
|
33
|
-
|
|
34
|
-
claude CLI is installed.
|
|
35
|
+
(Legacy `SUBSTRATE_*` names are still read.) Without `SPOR_DISTILL_CMD` /
|
|
36
|
+
`SPOR_NUDGE_CMD` both default to `claude -p --model haiku`, which is fine if
|
|
37
|
+
the claude CLI is installed. See [adapters/README.md](../README.md) for the
|
|
38
|
+
nudge bounds (`SPOR_NUDGE_MAX`, `SPOR_NUDGE_TIMEOUT`).
|
|
35
39
|
|
|
36
40
|
## Event mapping
|
|
37
41
|
|
|
@@ -20,8 +20,9 @@ sed "s|__SPOR_ROOT__|$SPOR_ROOT|g" \
|
|
|
20
20
|
(Per-repo installs go in `.github/hooks/spor.json` — that is also the
|
|
21
21
|
only location the Copilot cloud coding agent loads.) Environment is the
|
|
22
22
|
usual set: `SPOR_SERVER`, `SPOR_TOKEN`, and `SPOR_DISTILL_CMD`
|
|
23
|
-
(e.g. `copilot -p "$(cat)"`) if the claude CLI isn't installed
|
|
24
|
-
`
|
|
23
|
+
(e.g. `copilot -p "$(cat)"`) if the claude CLI isn't installed — set
|
|
24
|
+
`SPOR_NUDGE_CMD` to the same value for the capture nudge, or `SPOR_NUDGE=0`
|
|
25
|
+
to skip it; legacy `SUBSTRATE_*` names are still read.
|
|
25
26
|
|
|
26
27
|
## Event mapping
|
|
27
28
|
|
|
@@ -17,8 +17,9 @@ sed "s|__SPOR_ROOT__|$SPOR_ROOT|g" \
|
|
|
17
17
|
(Or merge into an existing `~/.cursor/hooks.json` / project
|
|
18
18
|
`.cursor/hooks.json`.) Environment is the usual set: `SPOR_SERVER`,
|
|
19
19
|
`SPOR_TOKEN`, and `SPOR_DISTILL_CMD` if the claude CLI isn't installed
|
|
20
|
-
(`cursor-agent -p` works: prompt on stdin, response on stdout)
|
|
21
|
-
`
|
|
20
|
+
(`cursor-agent -p` works: prompt on stdin, response on stdout) — set
|
|
21
|
+
`SPOR_NUDGE_CMD` to the same value for the capture nudge, or `SPOR_NUDGE=0`
|
|
22
|
+
to skip it; legacy `SUBSTRATE_*` names are still read.
|
|
22
23
|
|
|
23
24
|
## Event mapping
|
|
24
25
|
|
|
@@ -31,6 +31,9 @@ export SPOR_SERVER=https://spor.example.com # remote mode
|
|
|
31
31
|
export SPOR_TOKEN=spor_pat_...
|
|
32
32
|
# Distiller backend (prompt on stdin, response on stdout):
|
|
33
33
|
export SPOR_DISTILL_CMD='gemini --model gemini-2.5-flash'
|
|
34
|
+
# Capture-nudge backend (same contract; runs synchronously, so pick a fast
|
|
35
|
+
# model). SPOR_NUDGE=0 disables it; see adapters/README.md for the bounds.
|
|
36
|
+
export SPOR_NUDGE_CMD='gemini --model gemini-2.5-flash'
|
|
34
37
|
```
|
|
35
38
|
|
|
36
39
|
(Legacy `SUBSTRATE_*` names are still read.)
|
|
@@ -24,6 +24,9 @@ export SPOR_TOKEN=spor_pat_...
|
|
|
24
24
|
# Distiller backend (prompt on stdin -> response on stdout); without the
|
|
25
25
|
# claude CLI installed, route through opencode itself:
|
|
26
26
|
export SPOR_DISTILL_CMD='opencode run "$(cat)"'
|
|
27
|
+
# Capture-nudge backend — same contract, runs synchronously, so prefer a fast
|
|
28
|
+
# model. SPOR_NUDGE=0 disables it; see adapters/README.md for the bounds.
|
|
29
|
+
export SPOR_NUDGE_CMD='opencode run "$(cat)"'
|
|
27
30
|
```
|
|
28
31
|
|
|
29
32
|
## Behavior mapping
|
package/bin/spor-hook
CHANGED
|
@@ -3,5 +3,29 @@
|
|
|
3
3
|
# kept so existing hooks configs and adapter manifests that exec this path
|
|
4
4
|
# keep working unchanged. Windows hosts invoke spor-hook.js via node
|
|
5
5
|
# (or spor-hook.cmd) instead. Fail-open: no node, no output, exit 0.
|
|
6
|
-
command -v node >/dev/null 2>&1
|
|
7
|
-
exec node "$(dirname "$0")/spor-hook.js" "$@"
|
|
6
|
+
if command -v node >/dev/null 2>&1; then
|
|
7
|
+
exec node "$(dirname "$0")/spor-hook.js" "$@"
|
|
8
|
+
fi
|
|
9
|
+
# No Node interpreter — the hook can do nothing (the client is zero-dep *Node*).
|
|
10
|
+
# Stay fail-open (exit 0, no output, no latency on the working path above), but
|
|
11
|
+
# drop a ONE-TIME greppable breadcrumb so the silence is diagnosable instead of
|
|
12
|
+
# invisible (issue-spor-onboarding-no-node-silent-fail-open). Best-effort: every
|
|
13
|
+
# step is guarded and we exit 0 regardless, so this can never break fail-open.
|
|
14
|
+
# Resolve the graph home the way lib/shell/home.js does: $SPOR_HOME, then the
|
|
15
|
+
# legacy $SUBSTRATE_HOME, else ~/.spor (or ~/.substrate if that legacy dir
|
|
16
|
+
# exists). The breadcrumb is written at most once (guarded by its own marker).
|
|
17
|
+
_spor_home="${SPOR_HOME:-${SUBSTRATE_HOME:-}}"
|
|
18
|
+
if [ -z "$_spor_home" ]; then
|
|
19
|
+
if [ -d "$HOME/.spor" ]; then _spor_home="$HOME/.spor"
|
|
20
|
+
elif [ -d "$HOME/.substrate" ]; then _spor_home="$HOME/.substrate"
|
|
21
|
+
else _spor_home="$HOME/.spor"
|
|
22
|
+
fi
|
|
23
|
+
fi
|
|
24
|
+
_spor_warn="$_spor_home/journal/no-node.warn"
|
|
25
|
+
if [ -n "$HOME" ] && [ ! -f "$_spor_warn" ]; then
|
|
26
|
+
if mkdir -p "$_spor_home/journal" 2>/dev/null; then
|
|
27
|
+
printf '%s spor-hook: node not found on PATH — every hook is a silent no-op. Spor needs a Node interpreter (see package.json engines; `spor status` reports the floor). Install Node, then restart your session.\n' \
|
|
28
|
+
"$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo now)" >"$_spor_warn" 2>/dev/null || true
|
|
29
|
+
fi
|
|
30
|
+
fi
|
|
31
|
+
exit 0
|
package/bin/spor-hook.js
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// spor-hook post-tool [--host ...]
|
|
16
16
|
// spor-hook distill [--host ...] [--debounce SECONDS]
|
|
17
17
|
// spor-hook agents-md [--cwd DIR] # AGENTS.md floor; no stdin
|
|
18
|
+
// spor-hook doctor [--cwd DIR] # client health report; no stdin
|
|
18
19
|
//
|
|
19
20
|
// Fail-open contract (dec-cc-fail-open-hooks): any failure exits 0 with no
|
|
20
21
|
// output — a Spor problem never costs the user their session.
|
|
@@ -114,6 +115,23 @@ async function main() {
|
|
|
114
115
|
return;
|
|
115
116
|
}
|
|
116
117
|
|
|
118
|
+
// `spor-hook doctor` (task-cc-client-hook-operability-diagnostics piece 3):
|
|
119
|
+
// an operator-run diagnostic, not a host hook — it takes no stdin and prints a
|
|
120
|
+
// human-readable health report. It runs even when the plugin is disabled for
|
|
121
|
+
// the repo (a disabled plugin is exactly what you'd want doctor to tell you).
|
|
122
|
+
// Write the report with fs.writeSync(1) so the full body flushes before the
|
|
123
|
+
// crash handler's process.exit(0) can truncate a piped stdout.
|
|
124
|
+
if (event === "doctor") {
|
|
125
|
+
const { doctor } = require("../scripts/engines/doctor");
|
|
126
|
+
let dCwd = process.cwd();
|
|
127
|
+
const ci = args.indexOf("--cwd");
|
|
128
|
+
if (ci >= 0 && args[ci + 1]) dCwd = args[ci + 1];
|
|
129
|
+
u.useConfig({ cwd: dCwd });
|
|
130
|
+
const report = await doctor();
|
|
131
|
+
fs.writeSync(1, report);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
117
135
|
if (!(event in ENGINES)) {
|
|
118
136
|
process.stderr.write(`spor-hook: unknown event '${event}'\n`);
|
|
119
137
|
return;
|