spexcode 0.6.7 → 0.7.0-next.0
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 +12 -7
- package/node_modules/@spexcode/session-application/dist/index.d.ts +17 -0
- package/node_modules/@spexcode/session-application/dist/index.js +35 -0
- package/node_modules/@spexcode/session-application/dist/migration.d.ts +51 -0
- package/node_modules/@spexcode/session-application/dist/migration.js +694 -0
- package/node_modules/@spexcode/session-application/dist/production.d.ts +109 -0
- package/node_modules/@spexcode/session-application/dist/production.js +438 -0
- package/node_modules/@spexcode/session-application/dist/schema.d.ts +2 -0
- package/node_modules/@spexcode/session-application/dist/schema.js +39 -0
- package/node_modules/@spexcode/session-application/package.json +29 -0
- package/node_modules/@spexcode/session-events/dist/errors.d.ts +6 -0
- package/node_modules/@spexcode/session-events/dist/errors.js +11 -0
- package/node_modules/@spexcode/session-events/dist/index.d.ts +46 -0
- package/node_modules/@spexcode/session-events/dist/index.js +245 -0
- package/node_modules/@spexcode/session-events/dist/schema.d.ts +3 -0
- package/node_modules/@spexcode/session-events/dist/schema.js +44 -0
- package/node_modules/@spexcode/session-events/package.json +24 -0
- package/node_modules/@spexcode/session-protocol/dist/canonical.d.ts +36 -0
- package/node_modules/@spexcode/session-protocol/dist/canonical.js +139 -0
- package/node_modules/@spexcode/session-protocol/dist/engine.d.ts +22 -0
- package/node_modules/@spexcode/session-protocol/dist/engine.js +478 -0
- package/node_modules/@spexcode/session-protocol/dist/errors.d.ts +8 -0
- package/node_modules/@spexcode/session-protocol/dist/errors.js +39 -0
- package/node_modules/@spexcode/session-protocol/dist/index.d.ts +60 -0
- package/node_modules/@spexcode/session-protocol/dist/index.js +4 -0
- package/node_modules/@spexcode/session-protocol/dist/schema.d.ts +36 -0
- package/node_modules/@spexcode/session-protocol/dist/schema.js +192 -0
- package/node_modules/@spexcode/{session-core → session-protocol}/package.json +4 -8
- package/node_modules/@spexcode/session-runtime/dist/errors.d.ts +6 -0
- package/node_modules/@spexcode/session-runtime/dist/errors.js +11 -0
- package/node_modules/@spexcode/session-runtime/dist/index.d.ts +33 -0
- package/node_modules/@spexcode/session-runtime/dist/index.js +193 -0
- package/node_modules/@spexcode/session-runtime/dist/schema.d.ts +2 -0
- package/node_modules/@spexcode/session-runtime/dist/schema.js +37 -0
- package/node_modules/@spexcode/session-runtime/package.json +24 -0
- package/node_modules/@spexcode/session-selflaunch/bin/spex-session.mjs +4 -0
- package/node_modules/@spexcode/session-selflaunch/dist/cli.d.ts +25 -0
- package/node_modules/@spexcode/session-selflaunch/dist/cli.js +156 -0
- package/node_modules/@spexcode/session-selflaunch/dist/index.d.ts +28 -0
- package/node_modules/@spexcode/session-selflaunch/dist/index.js +25 -0
- package/node_modules/@spexcode/session-selflaunch/dist/locality.d.ts +31 -0
- package/node_modules/@spexcode/session-selflaunch/dist/locality.js +135 -0
- package/node_modules/@spexcode/session-selflaunch/dist/path.d.ts +13 -0
- package/node_modules/@spexcode/session-selflaunch/dist/path.js +45 -0
- package/node_modules/@spexcode/session-selflaunch/package.json +35 -0
- package/node_modules/@spexcode/session-topology/dist/errors.d.ts +6 -0
- package/node_modules/@spexcode/session-topology/dist/errors.js +11 -0
- package/node_modules/@spexcode/session-topology/dist/index.d.ts +24 -0
- package/node_modules/@spexcode/session-topology/dist/index.js +173 -0
- package/node_modules/@spexcode/session-topology/dist/schema.d.ts +3 -0
- package/node_modules/@spexcode/session-topology/dist/schema.js +27 -0
- package/node_modules/@spexcode/session-topology/package.json +24 -0
- package/node_modules/@spexcode/spec-cli/bin/spex.mjs +175 -21
- package/node_modules/@spexcode/spec-cli/dist/cli.js +121 -59
- package/node_modules/@spexcode/spec-cli/dist/client.d.ts +1 -3
- package/node_modules/@spexcode/spec-cli/dist/client.js +49 -30
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.d.ts +11 -0
- package/node_modules/@spexcode/spec-cli/dist/codex-runtime-generations.js +46 -9
- package/node_modules/@spexcode/spec-cli/dist/delivery-lock.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/delivery-lock.js +58 -0
- package/node_modules/@spexcode/spec-cli/dist/doctor.js +53 -11
- package/node_modules/@spexcode/spec-cli/dist/file-write.js +14 -2
- package/node_modules/@spexcode/spec-cli/dist/gateway-hub.js +2 -1
- package/node_modules/@spexcode/spec-cli/dist/gateway.js +6 -3
- package/node_modules/@spexcode/spec-cli/dist/graphCache.js +32 -2
- package/node_modules/@spexcode/spec-cli/dist/graphSnapshot.js +57 -2
- package/node_modules/@spexcode/spec-cli/dist/graphStream.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/graphStream.js +83 -3
- package/node_modules/@spexcode/spec-cli/dist/guide.js +29 -18
- package/node_modules/@spexcode/spec-cli/dist/harness-select.js +16 -3
- package/node_modules/@spexcode/spec-cli/dist/harness.d.ts +24 -6
- package/node_modules/@spexcode/spec-cli/dist/harness.js +510 -72
- package/node_modules/@spexcode/spec-cli/dist/help.js +11 -8
- package/node_modules/@spexcode/spec-cli/dist/hook-prompts.js +8 -0
- package/node_modules/@spexcode/spec-cli/dist/host-resources.js +29 -8
- package/node_modules/@spexcode/spec-cli/dist/host.d.ts +7 -0
- package/node_modules/@spexcode/spec-cli/dist/host.js +98 -1
- package/node_modules/@spexcode/spec-cli/dist/index.js +294 -36
- package/node_modules/@spexcode/spec-cli/dist/init.js +1 -1
- package/node_modules/@spexcode/spec-cli/dist/lint.js +70 -35
- package/node_modules/@spexcode/spec-cli/dist/listen.d.ts +3 -2
- package/node_modules/@spexcode/spec-cli/dist/listen.js +14 -2
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.d.ts +2 -1
- package/node_modules/@spexcode/spec-cli/dist/machine-peer.js +46 -15
- package/node_modules/@spexcode/spec-cli/dist/materialize.d.ts +2 -2
- package/node_modules/@spexcode/spec-cli/dist/materialize.js +176 -35
- package/node_modules/@spexcode/spec-cli/dist/opencode.js +10 -2
- package/node_modules/@spexcode/spec-cli/dist/pi-harness.js +18 -5
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.d.ts +2 -0
- package/node_modules/@spexcode/spec-cli/dist/pi-headless.js +27 -0
- package/node_modules/@spexcode/spec-cli/dist/pty-bridge.js +14 -14
- package/node_modules/@spexcode/spec-cli/dist/reviews.js +12 -7
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.d.ts +11 -0
- package/node_modules/@spexcode/spec-cli/dist/runtime-ownership.js +79 -1
- package/node_modules/@spexcode/spec-cli/dist/session-application.d.ts +23 -0
- package/node_modules/@spexcode/spec-cli/dist/session-application.js +189 -0
- package/node_modules/@spexcode/spec-cli/dist/session-declarations.js +13 -1
- package/node_modules/@spexcode/spec-cli/dist/session-files.d.ts +6 -0
- package/node_modules/@spexcode/spec-cli/dist/session-files.js +13 -1
- package/node_modules/@spexcode/spec-cli/dist/session-follow.js +39 -22
- package/node_modules/@spexcode/{session-core/dist/record-lock.d.ts → spec-cli/dist/session-record-lock.d.ts} +0 -4
- package/node_modules/@spexcode/spec-cli/dist/session-record-lock.js +94 -0
- package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.d.ts +44 -0
- package/node_modules/@spexcode/spec-cli/dist/session-runtime-adapter.js +37 -0
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.d.ts +25 -2
- package/node_modules/@spexcode/spec-cli/dist/session-timeline.js +68 -11
- package/node_modules/@spexcode/spec-cli/dist/session-transcript.d.ts +57 -0
- package/node_modules/@spexcode/spec-cli/dist/session-transcript.js +137 -0
- package/node_modules/@spexcode/spec-cli/dist/session-web.js +4 -4
- package/node_modules/@spexcode/spec-cli/dist/sessions.d.ts +112 -15
- package/node_modules/@spexcode/spec-cli/dist/sessions.js +1553 -764
- package/node_modules/@spexcode/spec-cli/dist/shim-runtime.js +14 -0
- package/node_modules/@spexcode/spec-cli/dist/source-list.d.ts +13 -0
- package/node_modules/@spexcode/spec-cli/dist/source-list.js +99 -0
- package/node_modules/@spexcode/spec-cli/dist/source-read.d.ts +16 -0
- package/node_modules/@spexcode/spec-cli/dist/source-read.js +84 -0
- package/node_modules/@spexcode/spec-cli/dist/spec-attachments.d.ts +7 -0
- package/node_modules/@spexcode/spec-cli/dist/spec-attachments.js +89 -0
- package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.d.ts +23 -0
- package/node_modules/@spexcode/spec-cli/dist/spec-body-edit.js +138 -0
- package/node_modules/@spexcode/spec-cli/dist/supervise.js +15 -6
- package/node_modules/@spexcode/spec-cli/hooks/dispatch.sh +19 -31
- package/node_modules/@spexcode/spec-cli/hooks/harness.sh +6 -6
- package/node_modules/@spexcode/spec-cli/package.json +8 -6
- package/node_modules/@spexcode/spec-cli/templates/hooks/post-checkout +4 -2
- package/node_modules/@spexcode/spec-cli/templates/hooks/post-merge +2 -1
- package/node_modules/@spexcode/spec-cli/templates/hooks/pre-commit +5 -3
- package/node_modules/@spexcode/spec-cli/templates/hooks/reference-transaction +5 -3
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/commands/spec.md +2 -7
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/idle.sh +4 -10
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/idle/spec.md +1 -1
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/mark-active.sh +22 -24
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md +10 -2
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/fail.sh +8 -7
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md +3 -1
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/session-listen.sh +133 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/spec.md +36 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/spec.md +2 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md +1 -1
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh +17 -20
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/merge/spec.md +33 -0
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/skills/spec.md +2 -6
- package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/spec.md +7 -0
- package/node_modules/@spexcode/spec-core/dist/anchors.d.ts +13 -2
- package/node_modules/@spexcode/spec-core/dist/anchors.js +311 -15
- package/node_modules/@spexcode/spec-core/dist/git.d.ts +4 -0
- package/node_modules/@spexcode/spec-core/dist/git.js +9 -9
- package/node_modules/@spexcode/spec-core/dist/layout.d.ts +12 -1
- package/node_modules/@spexcode/spec-core/dist/layout.js +4 -2
- package/node_modules/@spexcode/spec-core/dist/resilience.d.ts +1 -0
- package/node_modules/@spexcode/spec-core/dist/resilience.js +8 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.d.ts +1 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewFilters.js +7 -3
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.d.ts +1 -0
- package/node_modules/@spexcode/spec-core/dist/review/reviewQuery.js +4 -0
- package/node_modules/@spexcode/spec-core/dist/specs.d.ts +2 -0
- package/node_modules/@spexcode/spec-core/dist/specs.js +30 -0
- package/node_modules/@spexcode/spec-core/package.json +4 -1
- package/node_modules/@spexcode/spec-core/templates/spexcode.json +36 -13
- package/node_modules/@spexcode/spec-eval/dist/cli.js +1 -1
- package/node_modules/@spexcode/spec-eval/dist/evaltab.d.ts +1 -0
- package/node_modules/@spexcode/spec-eval/dist/evaltab.js +6 -1
- package/node_modules/@spexcode/spec-eval/dist/freshness.d.ts +1 -1
- package/node_modules/@spexcode/spec-eval/dist/freshness.js +12 -12
- package/node_modules/@spexcode/spec-eval/dist/host.d.ts +2 -1
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.d.ts +2 -1
- package/node_modules/@spexcode/spec-eval/dist/sessioneval.js +20 -3
- package/node_modules/@spexcode/spec-eval/package.json +2 -2
- package/node_modules/@spexcode/spec-forge/package.json +2 -2
- package/node_modules/@spexcode/transcript/dist/frames.d.ts +44 -0
- package/node_modules/@spexcode/transcript/dist/frames.js +85 -0
- package/node_modules/@spexcode/transcript/dist/index.d.ts +5 -0
- package/node_modules/@spexcode/transcript/dist/index.js +5 -0
- package/node_modules/@spexcode/transcript/dist/live.d.ts +16 -0
- package/node_modules/@spexcode/transcript/dist/live.js +65 -0
- package/node_modules/@spexcode/transcript/dist/parsers.d.ts +47 -0
- package/node_modules/@spexcode/transcript/dist/parsers.js +285 -0
- package/node_modules/@spexcode/transcript/dist/readers.d.ts +10 -0
- package/node_modules/@spexcode/transcript/dist/readers.js +296 -0
- package/node_modules/@spexcode/transcript/dist/turns.d.ts +42 -0
- package/node_modules/@spexcode/transcript/dist/turns.js +11 -0
- package/node_modules/@spexcode/transcript/package.json +30 -0
- package/node_modules/@vscode/tree-sitter-wasm/LICENSE +21 -0
- package/node_modules/@vscode/tree-sitter-wasm/README.md +36 -0
- package/node_modules/@vscode/tree-sitter-wasm/SECURITY.md +41 -0
- package/node_modules/@vscode/tree-sitter-wasm/cgmanifest.json +16 -0
- package/node_modules/@vscode/tree-sitter-wasm/package.json +42 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-bash.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-c-sharp.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-cpp.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-css.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-go.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ini.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-java.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-javascript.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-php.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-powershell.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-python.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-regex.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ruby.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-rust.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-tsx.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-typescript.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.js +4075 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts +1027 -0
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +418 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +127 -0
- package/node_modules/smol-toml/dist/error.d.ts +38 -0
- package/node_modules/smol-toml/dist/error.js +63 -0
- package/node_modules/smol-toml/dist/extract.js +69 -0
- package/node_modules/smol-toml/dist/index.cjs +734 -0
- package/node_modules/smol-toml/dist/index.d.ts +43 -0
- package/node_modules/smol-toml/dist/index.js +33 -0
- package/node_modules/smol-toml/dist/parse.d.ts +36 -0
- package/node_modules/smol-toml/dist/parse.js +149 -0
- package/node_modules/smol-toml/dist/primitive.js +238 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
- package/node_modules/smol-toml/dist/stringify.js +181 -0
- package/node_modules/smol-toml/dist/struct.js +179 -0
- package/node_modules/smol-toml/dist/util.d.ts +38 -0
- package/node_modules/smol-toml/dist/util.js +89 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +9 -3
- package/node_modules/@spexcode/session-core/dist/delivery-queue.d.ts +0 -24
- package/node_modules/@spexcode/session-core/dist/delivery-queue.js +0 -206
- package/node_modules/@spexcode/session-core/dist/index.d.ts +0 -5
- package/node_modules/@spexcode/session-core/dist/index.js +0 -5
- package/node_modules/@spexcode/session-core/dist/internal.d.ts +0 -3
- package/node_modules/@spexcode/session-core/dist/internal.js +0 -3
- package/node_modules/@spexcode/session-core/dist/message.d.ts +0 -22
- package/node_modules/@spexcode/session-core/dist/message.js +0 -53
- package/node_modules/@spexcode/session-core/dist/record-lock.js +0 -152
- package/node_modules/@spexcode/session-core/dist/runtime-session.d.ts +0 -62
- package/node_modules/@spexcode/session-core/dist/runtime-session.js +0 -326
- package/node_modules/@spexcode/session-core/dist/session-cursors.d.ts +0 -14
- package/node_modules/@spexcode/session-core/dist/session-cursors.js +0 -82
- package/node_modules/@spexcode/session-core/dist/session-timeline.d.ts +0 -47
- package/node_modules/@spexcode/session-core/dist/session-timeline.js +0 -216
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.d.ts +0 -26
- package/node_modules/@spexcode/spec-cli/dist/execution-trace.js +0 -475
- package/node_modules/@spexcode/spec-cli/dist/session-execution.d.ts +0 -10
- package/node_modules/@spexcode/spec-cli/dist/session-execution.js +0 -70
- package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-0.5.2-eef1.fixture +0 -53
- package/node_modules/@spexcode/spec-cli/hooks/compat/mark-active-sed-v0.fixture +0 -46
|
@@ -16,27 +16,23 @@
|
|
|
16
16
|
# session-worktree creation, and the pre-commit/post-checkout/post-merge hooks). .plugins edits are
|
|
17
17
|
# git-transactional: they take effect at the commit/checkout/merge that carries them, like any other source.
|
|
18
18
|
set -u
|
|
19
|
-
# args: `<harness> <Event>`.
|
|
20
|
-
#
|
|
21
|
-
# keeps working. `plugin` is the bundle form ([[plugin-harness]]), `opencode` the generated event-bus plugin
|
|
19
|
+
# args: `<harness> <Event>`. The harness id is explicit. `plugin` is the bundle form ([[plugin-harness]]),
|
|
20
|
+
# `opencode` the generated event-bus plugin
|
|
22
21
|
# ([[opencode-harness]]), `pi` the generated extension ([[pi-harness]]), and `zcode` the native adapter: all four
|
|
23
22
|
# carry Claude-shaped payloads (Claude tool names + file_path), so they join the claude branch in harness.sh via
|
|
24
23
|
# the default case — no parse arm of their own.
|
|
25
24
|
harness=claude
|
|
26
|
-
case "${1:-}" in claude|codex|opencode|pi|zcode|plugin) harness="$1"; shift ;;
|
|
25
|
+
case "${1:-}" in claude|codex|opencode|pi|zcode|plugin) harness="$1"; shift ;; *)
|
|
26
|
+
printf 'dispatch.sh: missing or unknown harness id\n' >&2
|
|
27
|
+
exit 64
|
|
28
|
+
;; esac
|
|
27
29
|
event="${1:?usage: dispatch.sh <harness> <Event>}"
|
|
28
30
|
export SPEXCODE_HARNESS="$harness"
|
|
29
31
|
# the harness.sh path (the adapter's shell mirror) — sibling of this script; hook handlers source it, and we
|
|
30
32
|
# source it here too for hp_runtime_dir (the per-project store dir).
|
|
31
33
|
hook_root="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
-
tool_root="$(cd "$hook_root/.." && pwd)"
|
|
33
34
|
export SPEXCODE_HARNESS_LIB="$hook_root/harness.sh"
|
|
34
35
|
. "$SPEXCODE_HARNESS_LIB"
|
|
35
|
-
if [ -n "${SPEX:-}" ]; then
|
|
36
|
-
read -r -a spex_cmd <<< "$SPEX"
|
|
37
|
-
else
|
|
38
|
-
spex_cmd=("$tool_root/bin/spex.mjs")
|
|
39
|
-
fi
|
|
40
36
|
proj="${CLAUDE_PROJECT_DIR:-$PWD}"
|
|
41
37
|
# the manifest lives in THIS tree's materialize slot of the GLOBAL per-project store (mirrors layout.treeSlotDir),
|
|
42
38
|
# NOT the worktree — and per tree, so a dispatch can only read the manifest of the tree it fires in
|
|
@@ -46,8 +42,7 @@ rt="$(cd "$proj" 2>/dev/null && hp_runtime_dir)" || rt=""
|
|
|
46
42
|
slot="$(cd "$proj" 2>/dev/null && hp_tree_dir)" || slot=""
|
|
47
43
|
|
|
48
44
|
# A project transport can outlive the tree that installed it. The current tree's last successful materialize
|
|
49
|
-
# is the authority for whether its events are active.
|
|
50
|
-
# shape; afterwards absence means this tree never successfully selected a harness and dispatch stays inert.
|
|
45
|
+
# is the authority for whether its events are active. A tree without a published selection is inert.
|
|
51
46
|
allowed="$slot/harnesses"
|
|
52
47
|
if [ -f "$allowed" ]; then
|
|
53
48
|
grep -Fxq "$harness" "$allowed" || exit 0
|
|
@@ -56,17 +51,8 @@ elif [ -f "$rt/harness-selection-v1" ]; then
|
|
|
56
51
|
fi
|
|
57
52
|
|
|
58
53
|
# --- dispatch ---------------------------------------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
else
|
|
62
|
-
# migration window: a tree last materialized by a pre-slot toolchain has no slot until its next git-native
|
|
63
|
-
# anchor — fall back to the legacy global manifest (its exact pre-migration behavior) so no hook (the
|
|
64
|
-
# Stop gate included) silently no-ops. The legacy file is never written again; the next anchor plants the
|
|
65
|
-
# slot and this branch goes dead.
|
|
66
|
-
manifest="$slot/hooks-manifest"
|
|
67
|
-
[ -f "$manifest" ] || manifest="$rt/hooks-manifest"
|
|
68
|
-
fi
|
|
69
|
-
[ -f "$manifest" ] || exit 0 # no manifest yet (materialize never ran) → nothing to dispatch
|
|
54
|
+
manifest="${SPEX_HOOK_MANIFEST:-$slot/hooks-manifest}"
|
|
55
|
+
[ -f "$manifest" ] || { printf 'dispatch.sh: current tree has no hook manifest\n' >&2; exit 78; }
|
|
70
56
|
input="$(cat 2>/dev/null || true)" # capture stdin ONCE; each handler gets its own copy
|
|
71
57
|
err="/tmp/.spex-hook-$$.err" # per-dispatch (pid-unique) stderr capture; no cross-session race
|
|
72
58
|
cleanup() { rm -f "$err"; }
|
|
@@ -78,14 +64,6 @@ rc=0
|
|
|
78
64
|
while IFS=$'\t' read -r ev order block script; do
|
|
79
65
|
[ "$ev" = "$event" ] || continue
|
|
80
66
|
handler="$proj/$script"
|
|
81
|
-
# A seeded core hook is tracked project source, so package replacement cannot safely overwrite it. These
|
|
82
|
-
# byte-exact default revisions compose an ask note into JSON with sed; route only them to the package
|
|
83
|
-
# implementation. `cmp` makes a user-modified hook ineligible without a platform-specific hash utility.
|
|
84
|
-
if [ "$script" = '.spec/project/.plugins/core/mark-active/mark-active.sh' ] &&
|
|
85
|
-
{ cmp -s "$handler" "$hook_root/compat/mark-active-sed-v0.fixture" ||
|
|
86
|
-
cmp -s "$handler" "$hook_root/compat/mark-active-0.5.2-eef1.fixture"; }; then
|
|
87
|
-
handler="$tool_root/templates/spec/project/.plugins/core/mark-active/mark-active.sh"
|
|
88
|
-
fi
|
|
89
67
|
out="$(printf '%s' "$input" | bash "$handler" 2>"$err")"; code=$?
|
|
90
68
|
[ -n "$out" ] && printf '%s' "$out"
|
|
91
69
|
if [ "$block" = "true" ] && { [ "$code" = "2" ] || printf '%s' "$out" | grep -q '"decision"[[:space:]]*:[[:space:]]*"block"'; }; then
|
|
@@ -101,5 +79,15 @@ while IFS=$'\t' read -r ev order block script; do
|
|
|
101
79
|
fi
|
|
102
80
|
rc=2
|
|
103
81
|
fi
|
|
82
|
+
# FAIL LOUD. A non-blocking handler's failure used to vanish completely: its exit code was dropped and its
|
|
83
|
+
# stderr was overwritten by the next handler and deleted on exit, so a lifecycle hook that could not write
|
|
84
|
+
# left NO trace anywhere — the board kept whatever state it last held and the reader had to guess whether
|
|
85
|
+
# the hook had run at all. That silence is what let a whole fleet's mark-active and stop-gate die unnoticed.
|
|
86
|
+
# Reporting is all this does: a non-blocking hook must not change the dispatch verdict, so `rc` stays the
|
|
87
|
+
# blocking handlers' to set, and a noisy hook can never turn into a gate.
|
|
88
|
+
if [ "$code" != 0 ] && [ "$block" != "true" ]; then
|
|
89
|
+
printf 'dispatch.sh: %s handler %s exited %s\n' "$event" "$script" "$code" >&2
|
|
90
|
+
[ -s "$err" ] && cat "$err" >&2
|
|
91
|
+
fi
|
|
104
92
|
done < "$manifest"
|
|
105
93
|
exit "$rc"
|
|
@@ -84,8 +84,8 @@ hp_session_id() {
|
|
|
84
84
|
codex) hp_field "$1" session_id ;;
|
|
85
85
|
*) pid=$(hp_field "$1" session_id)
|
|
86
86
|
if [ -n "$pid" ] && [ -n "$SPEXCODE_SESSION_ID" ] && [ "$pid" != "$SPEXCODE_SESSION_ID" ] \
|
|
87
|
-
&& [ ! -e "$(hp_store_dir "$pid")/
|
|
88
|
-
&& [ -e "$(hp_store_dir "$SPEXCODE_SESSION_ID")/
|
|
87
|
+
&& [ ! -e "$(hp_store_dir "$pid")/runtime.json" ] \
|
|
88
|
+
&& [ -e "$(hp_store_dir "$SPEXCODE_SESSION_ID")/runtime.json" ]; then
|
|
89
89
|
printf '%s' "$SPEXCODE_SESSION_ID"
|
|
90
90
|
else
|
|
91
91
|
printf '%s' "${pid:-$SPEXCODE_SESSION_ID}"
|
|
@@ -139,19 +139,19 @@ hp_tree_dir() {
|
|
|
139
139
|
# payload session_id on codex: the codex THREAD id, NOT the SpexCode record id the dir is keyed by. So when no
|
|
140
140
|
# record sits at <id> directly, find the one record that captured this id as `harness_session_id` (the backend
|
|
141
141
|
# stored it at thread/start, before the first tool turn).
|
|
142
|
-
# A grep over the few
|
|
142
|
+
# A grep over the few runtime.json files — no jq on the hot path; the trailing quote anchors the value so a
|
|
143
143
|
# thread id can't match a longer one as a prefix. Direct hit wins; a miss with no alias echoes the direct path
|
|
144
144
|
# unchanged, so the caller's `[ -e "$rec" ]` still no-ops gracefully. Mirrors layout.ts `readAliasedRawRecord`.
|
|
145
145
|
hp_store_dir() {
|
|
146
146
|
local rd; rd=$(hp_runtime_dir) || return 1
|
|
147
147
|
local direct="$rd/sessions/$1"
|
|
148
|
-
if [ -e "$direct/
|
|
148
|
+
if [ -e "$direct/runtime.json" ]; then printf '%s' "$direct"; return 0; fi
|
|
149
149
|
# same two-halves-of-absence rule as layout.ts's readAliasedRecordEntry: an id owning a store dir is already
|
|
150
150
|
# one of ours, so its emptiness is settled and the alias grep is the wrong question (and a whole-store scan).
|
|
151
151
|
if [ -d "$direct" ]; then printf '%s' "$direct"; return 0; fi
|
|
152
152
|
local hit
|
|
153
|
-
hit=$(grep -lF "\"harness_session_id\": \"$1\"" "$rd"/sessions/*/
|
|
154
|
-
[ -n "$hit" ] && { printf '%s' "${hit%/
|
|
153
|
+
hit=$(grep -lF "\"harness_session_id\": \"$1\"" "$rd"/sessions/*/runtime.json 2>/dev/null | head -1)
|
|
154
|
+
[ -n "$hit" ] && { printf '%s' "${hit%/runtime.json}"; return 0; }
|
|
155
155
|
printf '%s' "$direct"
|
|
156
156
|
}
|
|
157
157
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spexcode/spec-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0-next.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SpexCode CLI + server. The root spexcode package delegates to this compiled package; dashboard assets live in @spexcode/spec-dashboard.",
|
|
6
6
|
"bin": {
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
],
|
|
22
22
|
"bundledDependencies": [
|
|
23
23
|
"@spexcode/spec-core",
|
|
24
|
-
"@spexcode/session-core",
|
|
25
24
|
"@spexcode/spec-eval",
|
|
26
25
|
"@spexcode/spec-forge"
|
|
27
26
|
],
|
|
@@ -36,10 +35,13 @@
|
|
|
36
35
|
"test": "tsx --import ../scripts/test-home.mjs --test src/*.test.ts"
|
|
37
36
|
},
|
|
38
37
|
"dependencies": {
|
|
39
|
-
"@spexcode/session-
|
|
40
|
-
"@spexcode/
|
|
41
|
-
"@spexcode/spec-
|
|
42
|
-
"@spexcode/spec-
|
|
38
|
+
"@spexcode/session-application": "0.7.0-next.0",
|
|
39
|
+
"@spexcode/session-selflaunch": "0.7.0-next.0",
|
|
40
|
+
"@spexcode/spec-core": "0.7.0-next.0",
|
|
41
|
+
"@spexcode/spec-eval": "0.7.0-next.0",
|
|
42
|
+
"@spexcode/spec-forge": "0.7.0-next.0",
|
|
43
|
+
"@spexcode/transcript": "0.7.0-next.0",
|
|
44
|
+
"smol-toml": "^1.8.0"
|
|
43
45
|
},
|
|
44
46
|
"devDependencies": {
|
|
45
47
|
"@types/node": "^20.16.0",
|
|
@@ -15,10 +15,12 @@
|
|
|
15
15
|
[ "${SPEXCODE_DEFER_FOOTPRINT_REFRESH:-}" = "session-create" ] && exit 0
|
|
16
16
|
main_root=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)")
|
|
17
17
|
repo_root=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
18
|
-
if
|
|
19
|
-
spex internal refresh-footprint >/dev/null 2>&1 || true
|
|
18
|
+
if [ -x "$repo_root/spec-cli/bin/spex.mjs" ]; then
|
|
19
|
+
"$repo_root/spec-cli/bin/spex.mjs" internal refresh-footprint >/dev/null 2>&1 || true
|
|
20
20
|
elif [ -x "$repo_root/node_modules/.bin/spex" ]; then
|
|
21
21
|
"$repo_root/node_modules/.bin/spex" internal refresh-footprint >/dev/null 2>&1 || true
|
|
22
|
+
elif command -v spex >/dev/null 2>&1; then
|
|
23
|
+
spex internal refresh-footprint >/dev/null 2>&1 || true
|
|
22
24
|
elif [ -x "$main_root/spec-cli/bin/spex.mjs" ]; then
|
|
23
25
|
"$main_root/spec-cli/bin/spex.mjs" internal refresh-footprint >/dev/null 2>&1 || true
|
|
24
26
|
fi
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
main_root=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)")
|
|
14
14
|
repo_root=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
15
15
|
spex_run() {
|
|
16
|
-
if
|
|
16
|
+
if [ -x "$repo_root/spec-cli/bin/spex.mjs" ]; then "$repo_root/spec-cli/bin/spex.mjs" "$@"
|
|
17
17
|
elif [ -x "$repo_root/node_modules/.bin/spex" ]; then "$repo_root/node_modules/.bin/spex" "$@"
|
|
18
|
+
elif command -v spex >/dev/null 2>&1; then spex "$@"
|
|
18
19
|
elif [ -x "$main_root/spec-cli/bin/spex.mjs" ]; then "$main_root/spec-cli/bin/spex.mjs" "$@"
|
|
19
20
|
else return 127; fi
|
|
20
21
|
}
|
|
@@ -12,10 +12,12 @@
|
|
|
12
12
|
main_root=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)")
|
|
13
13
|
repo_root=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
14
14
|
spex_kind=
|
|
15
|
-
if
|
|
16
|
-
spex_kind=
|
|
15
|
+
if [ -x "$repo_root/spec-cli/bin/spex.mjs" ]; then
|
|
16
|
+
spex_kind=pkg
|
|
17
17
|
elif [ -x "$repo_root/node_modules/.bin/spex" ]; then
|
|
18
18
|
spex_kind=local
|
|
19
|
+
elif command -v spex >/dev/null 2>&1; then
|
|
20
|
+
spex_kind=path
|
|
19
21
|
elif [ -x "$main_root/spec-cli/bin/spex.mjs" ]; then
|
|
20
22
|
spex_kind=pkg
|
|
21
23
|
fi
|
|
@@ -23,7 +25,7 @@ spex_cli() {
|
|
|
23
25
|
case "$spex_kind" in
|
|
24
26
|
path) spex "$@" ;;
|
|
25
27
|
local) "$repo_root/node_modules/.bin/spex" "$@" ;;
|
|
26
|
-
pkg) "$main_root/spec-cli/bin/spex.mjs" "$@" ;;
|
|
28
|
+
pkg) if [ -x "$repo_root/spec-cli/bin/spex.mjs" ]; then "$repo_root/spec-cli/bin/spex.mjs" "$@"; else "$main_root/spec-cli/bin/spex.mjs" "$@"; fi ;;
|
|
27
29
|
*) return 127 ;;
|
|
28
30
|
esac
|
|
29
31
|
}
|
|
@@ -65,10 +65,12 @@ fi
|
|
|
65
65
|
main_root=$(dirname "$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)")
|
|
66
66
|
repo_root=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
67
67
|
spex_kind=
|
|
68
|
-
if
|
|
69
|
-
spex_kind=
|
|
68
|
+
if [ -x "$repo_root/spec-cli/bin/spex.mjs" ]; then
|
|
69
|
+
spex_kind=pkg
|
|
70
70
|
elif [ -x "$repo_root/node_modules/.bin/spex" ]; then
|
|
71
71
|
spex_kind=local
|
|
72
|
+
elif command -v spex >/dev/null 2>&1; then
|
|
73
|
+
spex_kind=path
|
|
72
74
|
elif [ -x "$main_root/spec-cli/bin/spex.mjs" ]; then
|
|
73
75
|
spex_kind=pkg
|
|
74
76
|
fi
|
|
@@ -76,7 +78,7 @@ spex_cli() {
|
|
|
76
78
|
case "$spex_kind" in
|
|
77
79
|
path) spex "$@" ;;
|
|
78
80
|
local) "$repo_root/node_modules/.bin/spex" "$@" ;;
|
|
79
|
-
pkg) "$main_root/spec-cli/bin/spex.mjs" "$@" ;;
|
|
81
|
+
pkg) if [ -x "$repo_root/spec-cli/bin/spex.mjs" ]; then "$repo_root/spec-cli/bin/spex.mjs" "$@"; else "$main_root/spec-cli/bin/spex.mjs" "$@"; fi ;;
|
|
80
82
|
*) return 127 ;;
|
|
81
83
|
esac
|
|
82
84
|
}
|
|
@@ -21,10 +21,5 @@ resolution. A preset with `{{targets}}` always receives the resolved target bloc
|
|
|
21
21
|
gets a target block only when the invocation actually names a target, so a targetless utility remains a small
|
|
22
22
|
prompt. An unknown leading `/name` stays ordinary prompt text and is never swallowed or guessed.
|
|
23
23
|
|
|
24
|
-
This node is a **shelf, not a surface
|
|
25
|
-
|
|
26
|
-
exactly as it would at the root — the gather set is path-independent, so shelving a command changes
|
|
27
|
-
nothing about what `/api/plugins` and the launcher offer. A plugin that serves BOTH surfaces (e.g.
|
|
28
|
-
[[distill]], skill and command) shelves once by its primary identity, never duplicated. The init
|
|
29
|
-
templates mirror this layout. The shelf stays pure presentation: moving a resident beneath it changes
|
|
30
|
-
neither that plugin's identity nor the surfaces gathered from its frontmatter.
|
|
24
|
+
This node is a **shelf, not a surface**. Its routing and relocation invariant is owned once by
|
|
25
|
+
[[.plugins]]'s shelf invariant; this node only describes command-specific discovery and invocation.
|
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# On an idle_prompt notification,
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# project — see hp_store_dir); the id is passed to the cli via `--session` so it writes the right record
|
|
7
|
-
# without depending on the worktree (which no longer holds any session file). NOTE the Notification event is
|
|
8
|
-
# Claude-only ([[harness-adapter]]: Codex fires no Notification), so this never runs under Codex.
|
|
2
|
+
# On an idle_prompt notification, ask the canonical lifecycle writer to infer idle. The writer owns both the
|
|
3
|
+
# governed-session boundary and the active-only compare-and-set; this hook only decodes the native event and
|
|
4
|
+
# passes its acting session id. NOTE the Notification event is Claude-only ([[harness-adapter]]: Codex fires no
|
|
5
|
+
# Notification), so this never runs under Codex.
|
|
9
6
|
. "${SPEXCODE_HARNESS_LIB:?harness.sh not exported by dispatch.sh}"
|
|
10
7
|
payload=$(cat 2>/dev/null)
|
|
11
8
|
sid=$(hp_session_id "$payload"); [ -n "$sid" ] || exit 0
|
|
12
|
-
sdir=$(hp_store_dir "$sid") || exit 0
|
|
13
|
-
rec="$sdir/session.json"
|
|
14
|
-
grep -q '"governed"[[:space:]]*:[[:space:]]*true' "$rec" 2>/dev/null || exit 0
|
|
15
9
|
[ "$(hp_notification_type "$payload")" = idle_prompt ] && exec ${SPEX:-spex} internal session-idle --session "$sid"
|
|
@@ -10,4 +10,4 @@ block: false
|
|
|
10
10
|
---
|
|
11
11
|
Catches the undeclared stop the [[stop-gate]] misses. When the harness signals — via an idle-prompt notification — that the agent is simply sitting idle at its prompt rather than working, this hook marks the session `idle`, so a session that quietly ran out of things to do is not left reading as active on the board.
|
|
12
12
|
|
|
13
|
-
It acts only on the idle-prompt notification, ignoring every other notification kind. As a board-lifecycle hook it
|
|
13
|
+
It acts only on the idle-prompt notification, ignoring every other notification kind. As a board-lifecycle hook it passes the payload's acting `session_id` to `spex internal session-idle --session <id>`; the canonical writer owns governed-session resolution and returns a no-op for self-launched or missing records. It is guarded so it never clobbers a deliberate declaration: marking idle applies only to a session still in the undeclared `active` state, leaving any considered `awaiting`, `asking`, `parked`, or `error` claim untouched. Together with [[stop-gate]] and [[session-fail]] it closes the last gap where a session could stop without its true state reaching the board.
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
# request_user_input tool) — read via hp_is_ask, so this hook never names a harness tool.
|
|
14
14
|
# Fires BEFORE the tool runs, so a `spex session done` declaration (itself a tool) lands AFTER this and wins;
|
|
15
15
|
# the next real tool flips back to active, forcing a fresh Stop-gate declaration.
|
|
16
|
-
# @@@
|
|
17
|
-
#
|
|
18
|
-
# is the
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
16
|
+
# @@@ one writer - this hook is on the hot path (every tool call), but it must not inspect runtime.json to
|
|
17
|
+
# decide whether a transition is needed. That file is only a runtime/worktree envelope; the canonical
|
|
18
|
+
# session application is the lifecycle authority. The structured writer is idempotent for an unchanged state,
|
|
19
|
+
# so every eligible event goes through the same writer and cannot short-circuit on a second fact. It never
|
|
20
|
+
# edits runtime.json itself: an asking note is arbitrary prose, and shell substitution is not a record writer
|
|
21
|
+
# ([[sessions-core]]).
|
|
22
|
+
# @@@ global store - the lifecycle state lives in the canonical session application, keyed by the harness
|
|
23
|
+
# session_id, grouped per-project (see hp_store_dir). The sibling runtime.json is only the runtime/worktree
|
|
24
|
+
# envelope. GATED on `governed`: a user-self-launched
|
|
25
25
|
# (non-governed) session has no board to feed, so this no-ops on it. cwd = the session worktree.
|
|
26
26
|
. "${SPEXCODE_HARNESS_LIB:?harness.sh not exported by dispatch.sh}"
|
|
27
27
|
payload=$(cat 2>/dev/null)
|
|
@@ -32,20 +32,20 @@ payload=$(cat 2>/dev/null)
|
|
|
32
32
|
# discriminator is the payload's own top-level agent_id stamp (hp_is_subagent) — deterministic, never a
|
|
33
33
|
# timing window.
|
|
34
34
|
[ -n "$(hp_is_subagent "$payload")" ] && exit 0
|
|
35
|
+
# Managed watch deliveries are supervision messages, not work performed by this session. They arrive through
|
|
36
|
+
# the harness's ordinary UserPromptSubmit seam, so the freshness hook must recognize the protocol's exact
|
|
37
|
+
# prefix before treating that seam as a human re-entry. This is deliberately a prefix check, not a broad
|
|
38
|
+
# text heuristic: only the canonical `[spex watch] ` wire form is exempt; ordinary prompts and all tools still
|
|
39
|
+
# mark active.
|
|
40
|
+
if [ "$(hp_field "$payload" hook_event_name)" = "UserPromptSubmit" ]; then
|
|
41
|
+
case "$(hp_field "$payload" prompt)" in
|
|
42
|
+
"[spex watch] "*) exit 0 ;;
|
|
43
|
+
esac
|
|
44
|
+
fi
|
|
35
45
|
sid=$(hp_session_id "$payload"); [ -n "$sid" ] || exit 0
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
grep -q '^[[:space:]]*"governed"[[:space:]]*:[[:space:]]*true,\?$' "$rec" 2>/dev/null || exit 0
|
|
40
|
-
|
|
41
|
-
# does FIELD's line hold exactly VALUE? The record is written one-field-per-line by the single writer
|
|
42
|
-
# (sessions.ts writeRecord), so a whole-line match is exact — and, unlike a value regex, it cannot be fooled
|
|
43
|
-
# by an escaped quote inside a neighbouring note.
|
|
44
|
-
jline_is() { grep -q "^[[:space:]]*\"$1\"[[:space:]]*:[[:space:]]*\"$2\",\?$" "$rec" 2>/dev/null; }
|
|
45
|
-
|
|
46
|
-
# The writer's own stdout is a human confirmation, not hook output — swallow it so a PreToolUse handler never
|
|
47
|
-
# emits a decision-shaped line; its stderr (a refusal — a corrupt or retired record) still surfaces. We always
|
|
48
|
-
# exit 0: this hook observes freshness, it is not a gate on the tool that triggered it.
|
|
46
|
+
# The canonical writer owns governed/lifecycle validation. The hook must not inspect runtime.json: that file is
|
|
47
|
+
# a runtime envelope, and using it as a gate is how old/missing envelopes silently disabled mark-active.
|
|
48
|
+
# The writer's stdout is a human confirmation, not hook output; stderr remains visible for real refusals.
|
|
49
49
|
if [ -n "$(hp_is_ask "$payload")" ]; then
|
|
50
50
|
# first question's text → the note (best-effort). It is passed as ONE argv word to the writer, so quotes,
|
|
51
51
|
# backslashes, newlines, and non-ASCII reach the record intact — no shell ever composes the JSON.
|
|
@@ -53,7 +53,5 @@ if [ -n "$(hp_is_ask "$payload")" ]; then
|
|
|
53
53
|
exit 0
|
|
54
54
|
fi
|
|
55
55
|
|
|
56
|
-
# cheap path: already active with nothing stale to clear → no-op (the common every-tool case), no spawn.
|
|
57
|
-
jline_is status active && jline_is proposal '' && jline_is note '' && exit 0
|
|
58
56
|
${SPEX:-spex} internal session-state active --session "$sid" >/dev/null
|
|
59
57
|
exit 0
|
package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/mark-active/spec.md
CHANGED
|
@@ -15,8 +15,16 @@ The state is read from ONE structured field in the hook payload, never sniffed f
|
|
|
15
15
|
|
|
16
16
|
The one activity that does NOT count as the session acting is an IN-PROCESS SUBAGENT's tool call (the harness's Task tool — a sub-conversation inside the same process). Such a call fires the parent's hooks carrying the parent's `session_id`, so without a discriminator a supervising parent could never hold a declared state: its own subagents erased every park/ask within seconds and raced the stop-gate into "undeclared stop". The harness stamps subagent-executed calls with a top-level `agent_id` field the parent's own calls never carry; `hp_is_subagent` reads that stamp deterministically (scanning only the pre-`tool_input` payload prefix, where a tool parameter or file content can never fake an unescaped key), and this hook skips the flip entirely. A subagent working is its parent supervising, not the parent moving on — the parent's own next tool call still flips as before.
|
|
17
17
|
|
|
18
|
-
It is a board-lifecycle hook, so it acts only on a GOVERNED (dashboard-launched) session — it resolves that session's
|
|
18
|
+
It is a board-lifecycle hook, so it acts only on a GOVERNED (dashboard-launched) session — it resolves that session's runtime envelope in the global per-session store from the payload's `session_id` and no-ops unless `governed: true`. Lifecycle state is written to the canonical session application; `runtime.json` is not a lifecycle authority. The hook never edits that envelope itself: it hands every lifecycle write to `spex internal session-state`, the one structured writer the CLI declarations use — an asking note is arbitrary prose, and a shell that substitutes prose into existing JSON eventually writes a record nothing can parse.
|
|
19
19
|
|
|
20
|
-
This hook carries no conversation. A message addressed to the session reaches its agent as an ordinary prompt through the harness adapter (delivery-queue), which is the only way anything enters a turn
|
|
20
|
+
This hook carries no conversation. A message addressed to the session reaches its agent as an ordinary prompt through the harness adapter (delivery-queue), which is the only way anything enters a turn. The one protocol exception is a managed watch notice: the canonical `[spex watch] ` prefix identifies a supervision snapshot, not work performed by the receiving agent, so UserPromptSubmit for that exact wire form does not flip the receiver to `active`. Other inter-agent prompts remain ordinary prompt activity. A hook that also injected mail delivered every message a second time and made the agent's context depend on which of two paths won a race; a freshness signal reports a fact about the session and hands nothing over.
|
|
21
21
|
|
|
22
22
|
This is the freshness half of the [[core]] discipline: it keeps the board honest about whether a session is working, waiting, or asking, so the gates and the dashboard read a true present state rather than a stale one.
|
|
23
|
+
|
|
24
|
+
## legacy handler migration
|
|
25
|
+
|
|
26
|
+
For the two known pre-structured `mark-active` source blobs still tracked by existing projects, dispatch performs
|
|
27
|
+
a bounded migration at the adapter boundary: it executes the current package-owned structured implementation,
|
|
28
|
+
and the next materialize replaces the old tracked handler with that implementation. This is a migration of a
|
|
29
|
+
legacy source identity, not a second lifecycle protocol or a permanent backward-compatibility path. The old
|
|
30
|
+
handler's envelope writes are never allowed to author current lifecycle state.
|
package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/fail.sh
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Mark the session errored when a turn ends on an API failure (StopFailure).
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# so it writes the right record without depending on the worktree.
|
|
2
|
+
# Mark the session errored when a turn ends on an API failure (StopFailure). The canonical lifecycle writer owns
|
|
3
|
+
# the governed-session boundary and active-only compare-and-set; this hook only decodes the native event and
|
|
4
|
+
# passes its acting session id.
|
|
6
5
|
. "${SPEXCODE_HARNESS_LIB:?harness.sh not exported by dispatch.sh}"
|
|
7
6
|
payload=$(cat 2>/dev/null)
|
|
7
|
+
# an IN-PROCESS SUBAGENT's failed turn (Claude's Task tool) fires the parent's hooks with the PARENT's
|
|
8
|
+
# session_id, so without this the parent's own record would be flipped to `error` by a helper it spawned —
|
|
9
|
+
# the same defect already fixed for mark-active, and the same discriminator fixes it: the payload's own
|
|
10
|
+
# top-level agent_id stamp. A subagent's turn dying is not THIS session's turn dying.
|
|
11
|
+
[ -n "$(hp_is_subagent "$payload")" ] && exit 0
|
|
8
12
|
sid=$(hp_session_id "$payload"); [ -n "$sid" ] || exit 0
|
|
9
|
-
sdir=$(hp_store_dir "$sid") || exit 0
|
|
10
|
-
rec="$sdir/session.json"
|
|
11
|
-
grep -q '"governed"[[:space:]]*:[[:space:]]*true' "$rec" 2>/dev/null || exit 0
|
|
12
13
|
exec ${SPEX:-spex} internal session-fail --session "$sid"
|
package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-fail/spec.md
CHANGED
|
@@ -12,4 +12,6 @@ code:
|
|
|
12
12
|
---
|
|
13
13
|
When a turn ends not because the agent declared but because the API itself failed, this hook structurally marks the session `error`. A failed turn is a real outcome the board must show, and without this signal the session would freeze under whatever state it last held — reading as "active" or "awaiting" long after it actually died.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
**A SUBAGENT'S FAILED TURN IS NOT THIS SESSION'S FAILED TURN.** An in-process subagent (Claude's Task tool) fires the parent's hooks carrying the PARENT's `session_id`, so a helper the session spawned could flip the session that spawned it to `error` — a supervising parent marked dead by a delegate it is still supervising. The discriminator is the payload's own top-level `agent_id` stamp, the deterministic one [[mark-active]] already uses for the same reason; it is not a timing window. This is one defect class, so it gets one answer at both hooks rather than a second idea of what "this session acted" means.
|
|
16
|
+
|
|
17
|
+
It is non-blocking on the failure event: the failure already happened, so the only job is to report it truthfully. As a board-lifecycle hook it passes the payload's acting `session_id` to `spex internal session-fail --session <id>`; the canonical writer, not shell parsing of `runtime.json`, resolves the governed record and owns the live-active compare-and-set. Only an undeclared, non-stopped `active` record becomes `error`. A declaration, explicit stop, or archive that landed first remains authoritative; a late native failure never rewrites it. This one writer keeps the [[stop-gate]] family's invariant intact for every harness while each adapter retains only its native failure signal.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -u
|
|
3
|
+
|
|
4
|
+
# A self-launch project opts in only by configuring the adopter database. No configured database means no listener.
|
|
5
|
+
if [ -z "${SPEX_SESSION_DATABASE_PATH+x}" ] && [ -z "${SPEX_SESSION_CONFIG+x}" ]; then
|
|
6
|
+
exit 0
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
# The explicit command is one executable path; an invalid explicit value must not silently fall through to PATH.
|
|
10
|
+
cli="${SPEX_SESSION_CLI:-}"
|
|
11
|
+
if [ -z "$cli" ]; then
|
|
12
|
+
cli=$(command -v spex-session 2>/dev/null || true)
|
|
13
|
+
fi
|
|
14
|
+
if [ -z "$cli" ] || [ ! -x "$cli" ]; then
|
|
15
|
+
printf '%s\n' 'session-listen: spex-session CLI not found; install @spexcode/session-selflaunch (npm install) or set SPEX_SESSION_CLI to its executable path' >&2
|
|
16
|
+
exit 2
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
for tool in awk base64 iconv od grep tail sed mktemp cat tr rm; do
|
|
20
|
+
if ! command -v "$tool" >/dev/null 2>&1; then
|
|
21
|
+
printf "session-listen: required delivery tool '%s' is missing; install it or repair PATH before retrying\n" "$tool" >&2
|
|
22
|
+
exit 2
|
|
23
|
+
fi
|
|
24
|
+
done
|
|
25
|
+
|
|
26
|
+
capability_error() {
|
|
27
|
+
printf "session-listen: required delivery capability '%s' is unavailable; install compatible tooling or repair PATH before retrying\n" "$1" >&2
|
|
28
|
+
exit 2
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
escape_awk='
|
|
32
|
+
BEGIN { ORS = "" }
|
|
33
|
+
NR > 1 { printf "\\n" }
|
|
34
|
+
{
|
|
35
|
+
for (i = 1; i <= length($0); i++) {
|
|
36
|
+
c = substr($0, i, 1)
|
|
37
|
+
if (c == "\\") printf "\\\\"
|
|
38
|
+
else if (c == "\"") printf "\\\""
|
|
39
|
+
else if (c == "\t") printf "\\t"
|
|
40
|
+
else if (c == "\r") printf "\\r"
|
|
41
|
+
else if (c == "\b") printf "\\b"
|
|
42
|
+
else if (c == "\f") printf "\\f"
|
|
43
|
+
else printf "%s", c
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
'
|
|
47
|
+
control_pattern='(^|[[:space:]])(00|01|02|03|04|05|06|07|08|0b|0c|0e|0f|1[0-9a-f])([[:space:]]|$)'
|
|
48
|
+
body64_sed='s/.*"bodyBase64":"\([^"]*\)".*/\1/p'
|
|
49
|
+
|
|
50
|
+
# Existence is not capability. These fixed vectors prove every non-default operation before at-most-once dequeue.
|
|
51
|
+
[ "$(printf ' A ' | tr -d '[:space:]')" = A ] || capability_error 'tr -d character classes'
|
|
52
|
+
[ "$(printf A | od -An -v -tx1 | tr -d '[:space:]')" = 41 ] || capability_error 'od hexadecimal bytes'
|
|
53
|
+
[ "$(printf QQ== | base64 -d 2>/dev/null | od -An -v -tx1 | tr -d '[:space:]')" = 41 ] \
|
|
54
|
+
|| capability_error 'base64 -d exact decoding'
|
|
55
|
+
[ "$(printf '\303\251' | iconv -f UTF-8 -t UTF-8 2>/dev/null | od -An -v -tx1 | tr -d '[:space:]')" = c3a9 ] \
|
|
56
|
+
|| capability_error 'iconv UTF-8 validation'
|
|
57
|
+
printf ' 00 ' | grep -Eq "$control_pattern" || capability_error 'grep extended quiet match'
|
|
58
|
+
if printf ' 0a ' | grep -Eq "$control_pattern"; then capability_error 'grep extended quiet exclusion'; fi
|
|
59
|
+
[ "$(printf AB | tail -c 1)" = B ] || capability_error 'tail byte selection'
|
|
60
|
+
[ "$(printf '%s' '{"bodyBase64":"QQ=="}' | sed -n "$body64_sed")" = QQ== ] \
|
|
61
|
+
|| capability_error 'sed JSON field extraction'
|
|
62
|
+
[ "$(printf 'A\nB\n' | awk "$escape_awk")" = 'A\nB' ] || capability_error 'awk JSON text escaping'
|
|
63
|
+
[ "$(printf A | cat)" = A ] || capability_error 'cat byte emission'
|
|
64
|
+
|
|
65
|
+
. "${SPEXCODE_HARNESS_LIB:?harness.sh not exported by dispatch.sh}"
|
|
66
|
+
payload=$(cat 2>/dev/null || true)
|
|
67
|
+
sid=$(hp_field "$payload" session_id)
|
|
68
|
+
[ -n "$sid" ] || exit 0
|
|
69
|
+
event=$(hp_field "$payload" hook_event_name)
|
|
70
|
+
|
|
71
|
+
case "$event" in
|
|
72
|
+
SessionStart)
|
|
73
|
+
"$cli" initialize --session-id "$sid" >/dev/null || exit 2
|
|
74
|
+
;;
|
|
75
|
+
UserPromptSubmit)
|
|
76
|
+
decoded=$(mktemp "${TMPDIR:-/tmp}/spex-session-listen.XXXXXX") || {
|
|
77
|
+
printf '%s\n' 'session-listen: could not allocate a temporary body file; repair TMPDIR and retry' >&2
|
|
78
|
+
exit 2
|
|
79
|
+
}
|
|
80
|
+
escaped_file="$decoded.escaped"
|
|
81
|
+
output_file="$decoded.output"
|
|
82
|
+
if ! : >"$escaped_file" || ! : >"$output_file"; then
|
|
83
|
+
rm -f "$decoded"
|
|
84
|
+
rm -f "$escaped_file" "$output_file"
|
|
85
|
+
printf '%s\n' 'session-listen: could not prepare temporary delivery files; repair TMPDIR and retry' >&2
|
|
86
|
+
exit 2
|
|
87
|
+
fi
|
|
88
|
+
cleanup() { rm -f "$decoded" "$escaped_file" "$output_file"; }
|
|
89
|
+
trap cleanup EXIT
|
|
90
|
+
raw=$("$cli" dequeue --session-id "$sid") || exit 2
|
|
91
|
+
[ "$raw" = 'null' ] && exit 0
|
|
92
|
+
body64=$(printf '%s' "$raw" | sed -n "$body64_sed")
|
|
93
|
+
message_id=$(printf '%s' "$raw" | sed -n 's/.*"messageId":"\([^"]*\)".*/\1/p')
|
|
94
|
+
[ -n "$body64" ] || {
|
|
95
|
+
printf '%s\n' 'session-listen: spex-session dequeue returned invalid JSON (missing bodyBase64)' >&2
|
|
96
|
+
exit 2
|
|
97
|
+
}
|
|
98
|
+
if ! printf '%s' "$body64" | base64 -d >"$decoded" 2>/dev/null; then
|
|
99
|
+
printf '%s\n' 'session-listen: spex-session dequeue returned invalid bodyBase64' >&2
|
|
100
|
+
exit 2
|
|
101
|
+
fi
|
|
102
|
+
if ! iconv -f UTF-8 -t UTF-8 "$decoded" >/dev/null 2>/dev/null; then
|
|
103
|
+
printf 'session-listen: refusing non-UTF-8 body; messageId=%s bodyBase64=%s\n' "$message_id" "$body64" >&2
|
|
104
|
+
exit 2
|
|
105
|
+
fi
|
|
106
|
+
if od -An -v -tx1 "$decoded" | grep -Eq "$control_pattern"; then
|
|
107
|
+
printf 'session-listen: refusing control-byte body; messageId=%s bodyBase64=%s\n' "$message_id" "$body64" >&2
|
|
108
|
+
exit 2
|
|
109
|
+
fi
|
|
110
|
+
if ! awk "$escape_awk" "$decoded" >"$escaped_file"; then
|
|
111
|
+
printf 'session-listen: could not encode body for harness input; messageId=%s bodyBase64=%s\n' "$message_id" "$body64" >&2
|
|
112
|
+
exit 2
|
|
113
|
+
fi
|
|
114
|
+
if [ -s "$decoded" ] && [ ! -s "$escaped_file" ]; then
|
|
115
|
+
printf 'session-listen: non-empty body encoded to empty additionalContext; messageId=%s bodyBase64=%s\n' "$message_id" "$body64" >&2
|
|
116
|
+
exit 2
|
|
117
|
+
fi
|
|
118
|
+
last_byte=$(tail -c 1 "$decoded" | od -An -tx1 | tr -d '[:space:]')
|
|
119
|
+
if ! {
|
|
120
|
+
printf '{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit","additionalContext":"'
|
|
121
|
+
cat "$escaped_file"
|
|
122
|
+
[ "$last_byte" = 0a ] && printf '\\n'
|
|
123
|
+
printf '"}}\n'
|
|
124
|
+
} >"$output_file"; then
|
|
125
|
+
printf 'session-listen: could not assemble harness input; messageId=%s bodyBase64=%s\n' "$message_id" "$body64" >&2
|
|
126
|
+
exit 2
|
|
127
|
+
fi
|
|
128
|
+
if ! cat "$output_file"; then
|
|
129
|
+
printf 'session-listen: could not emit harness input; messageId=%s bodyBase64=%s\n' "$message_id" "$body64" >&2
|
|
130
|
+
exit 2
|
|
131
|
+
fi
|
|
132
|
+
;;
|
|
133
|
+
esac
|
package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/session-listen/spec.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: session-listen
|
|
3
|
+
surface: hook
|
|
4
|
+
status: active
|
|
5
|
+
hue: 280
|
|
6
|
+
events:
|
|
7
|
+
- SessionStart
|
|
8
|
+
- UserPromptSubmit
|
|
9
|
+
order: 20
|
|
10
|
+
block: true
|
|
11
|
+
---
|
|
12
|
+
The backend-free self-launch message loop. On `SessionStart`, it initializes the harness's native session id in the
|
|
13
|
+
adopter protocol database; initialization is idempotent and creates no governed lifecycle record. On each
|
|
14
|
+
`UserPromptSubmit`, it performs exactly one at-most-once `spex-session dequeue` for that same native id. A message
|
|
15
|
+
body is decoded and emitted as the harness's `hookSpecificOutput.additionalContext` JSON, so the harness input seam
|
|
16
|
+
receives it without a resident process or wake-hint dependency. An empty queue is a successful no-op and emits no
|
|
17
|
+
stdout. The hook never derives a database path: the adopter CLI owns path resolution and locality checks.
|
|
18
|
+
|
|
19
|
+
Every external delivery tool and each non-default operation it supplies is proven with a fixed result vector before
|
|
20
|
+
`dequeue`, so a binary that exists but lacks or misimplements the required flag cannot consume a message. In
|
|
21
|
+
particular, `base64 -d` must decode `QQ==` to exactly the one byte `0x41`; checking only its exit status would admit a
|
|
22
|
+
shim that merely copies input. `-d` is used because GNU base64 on Linux and base64 on both fleet Macs running macOS
|
|
23
|
+
15.6.1 were measured to decode that vector correctly; this is an observed common capability, not a platform legend.
|
|
24
|
+
The opaque body
|
|
25
|
+
is decoded to a temporary file, validated as UTF-8, and rejected loudly (with its `messageId` and original
|
|
26
|
+
`bodyBase64`) when it contains NUL or other JSON-hostile control bytes. Clean text is escaped with the existing awk
|
|
27
|
+
toolchain without command substitution, preserving embedded and trailing newlines; a non-empty body can never become
|
|
28
|
+
an empty additionalContext success.
|
|
29
|
+
|
|
30
|
+
The CLI is resolved at runtime through one explicit seam: a non-empty `SPEX_SESSION_CLI` wins, otherwise PATH is
|
|
31
|
+
searched for `spex-session`. If either protocol database environment variable is configured and no CLI can be
|
|
32
|
+
resolved, the hook fails loudly with an installation or `SPEX_SESSION_CLI` repair entrypoint. With neither database
|
|
33
|
+
variable configured, the project has not adopted this capability, so the hook exits silently without trying another
|
|
34
|
+
delivery path. Only these two events are bound: startup establishes the address, and prompt submission is the natural
|
|
35
|
+
harness input seam; a configured-but-broken adopter is a blocking hook failure so dispatch cannot hide it. There is no
|
|
36
|
+
daemon, polling, retry loop, observer, governed record, or compatibility path.
|
|
@@ -10,6 +10,8 @@ Use noun-first CLI commands; `spex help` is the authoritative command map.
|
|
|
10
10
|
|
|
11
11
|
Anything a human needs to inspect, whether a file or a local webpage, goes out through `spex session files add` or
|
|
12
12
|
`spex session web add`: never paste an absolute path or `host:port`, and never start a static server yourself.
|
|
13
|
+
Put raw file evidence in a persistent directory outside the product repository by default, then run
|
|
14
|
+
`spex session files ls` before review and repair or retract every `INVALID` handoff.
|
|
13
15
|
|
|
14
16
|
When this session has a clearly running child session (`active` or `parked`), the parent is supervising rather
|
|
15
17
|
than finished: declare `park`, not `done`/`awaiting`, until the child reports a settled state.
|