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
package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/spec.md
CHANGED
|
@@ -18,6 +18,6 @@ The block text is where the declaration ritual is taught, so it is written to be
|
|
|
18
18
|
|
|
19
19
|
The clean-done eval nudge is advisory only and must never corrupt the Stop hook protocol. Claude-family hooks can receive it as `hookSpecificOutput.additionalContext`; Codex Stop allows are silent because Codex treats unsupported non-block stdout as invalid hook JSON. Blocking decisions stay shared across harnesses through `{"decision":"block"}` plus the dispatcher’s Codex stderr bridge.
|
|
20
20
|
|
|
21
|
-
Both gates act ONLY on a GOVERNED (dashboard-launched) session: the gate resolves the session's record in the global store from the payload's `session_id`, and on a non-governed (user-self-launched) record — or none — it exits 0 SILENTLY. A self-launched agent has no board to feed, so the declare-demand must never misfire on it.
|
|
21
|
+
Both gates act ONLY on a GOVERNED (dashboard-launched) session: the gate resolves the session's record in the global store from the payload's `session_id`, and on a non-governed (user-self-launched) record — or none — it exits 0 SILENTLY. A self-launched agent has no board to feed, so the declare-demand must never misfire on it. The forced-continuation fallback writes `asking` through `spex internal session-state --session <id>` — the canonical writer, with no delivery or build side effects — passing the id explicitly since there is no worktree file to read it from. Porcelain declarations remain agent-facing; the hook never invokes them to repair its own stop.
|
|
22
22
|
|
|
23
23
|
It is the enforcement edge of [[core]]: nothing leaves a session except as committed work under a truthful declaration. The freshness it reads is set by [[mark-active]].
|
package/node_modules/@spexcode/spec-cli/templates/spec/project/.plugins/core/stop-gate/stop-gate.sh
CHANGED
|
@@ -11,28 +11,20 @@
|
|
|
11
11
|
# without inventing a completion state.
|
|
12
12
|
# $SPEX is the PATH-independent CLI invocation (abs tsx + cli) injected by settingsArg, so the gate's own
|
|
13
13
|
# auto-default AND the command it shows the agent both work even when `spex` is absent from PATH.
|
|
14
|
-
# @@@
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
# misfiring on a self-launched codex/claude). cwd = the session worktree (resolves the project key + the
|
|
19
|
-
# commit-gate's git); state writes go through `$SPEX session … --session <id>` (TS owns the JSON).
|
|
14
|
+
# @@@ governed gate - the session id comes from the payload. The gate acts ONLY on a GOVERNED
|
|
15
|
+
# (dashboard-launched) session: a user-self-launched agent has no board to feed, so an undeclared stop is
|
|
16
|
+
# none of our business. Lifecycle status/proposal come from the canonical session application through one
|
|
17
|
+
# CLI read; this shell never treats runtime.json as a second lifecycle database.
|
|
20
18
|
. "${SPEXCODE_HARNESS_LIB:?harness.sh not exported by dispatch.sh}"
|
|
21
19
|
S="${SPEX:-spex}"
|
|
22
20
|
input=$(cat 2>/dev/null || true)
|
|
23
21
|
sid=$(hp_session_id "$input"); [ -n "$sid" ] || exit 0
|
|
24
22
|
sdir=$(hp_store_dir "$sid") || exit 0
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
# the single writer ([[sessions-core]]), so anchoring to the line start is what keeps a neighbouring note's
|
|
31
|
-
# escaped quote from being read as this field's value — the read half of the same rule that stops shell from
|
|
32
|
-
# ever composing the record.
|
|
33
|
-
jget() { sed -n "s/^[[:space:]]*\"$1\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\",\?$/\1/p" "$rec" 2>/dev/null | head -1; }
|
|
34
|
-
status=$(jget status)
|
|
35
|
-
proposal=$(jget proposal)
|
|
23
|
+
# non-governed (or no record) → silently let the stop through. THIS is the self-launch fix. The CLI response
|
|
24
|
+
# is governed<TAB>status<TAB>proposal; status/proposal are canonical, while governed is identity metadata.
|
|
25
|
+
hook_state=$($S internal session-hook-state --session "$sid" 2>/dev/null) || exit 0
|
|
26
|
+
IFS=$'\t' read -r governed status proposal <<< "$hook_state"
|
|
27
|
+
[ "$governed" = 1 ] || exit 0
|
|
36
28
|
|
|
37
29
|
# the value of the payload's structured `stop_hook_active` field (true on the hook-forced continuation),
|
|
38
30
|
# read by field name rather than substring-sniffing the JSON blob. ([a-z]* captures true/false portably —
|
|
@@ -91,7 +83,10 @@ if [ "${status:-active}" = awaiting ] && { [ "$proposal" = merge ] || [ "$propos
|
|
|
91
83
|
exit 0 # work is committed and ahead of main -> the proposal is honest, let it stop.
|
|
92
84
|
fi
|
|
93
85
|
if [ "$cont" = true ]; then
|
|
94
|
-
|
|
86
|
+
# The hook is a thin boundary. Do not call the porcelain declaration here: it may trigger
|
|
87
|
+
# delivery/build work and can remain running after the harness has already accepted the stop.
|
|
88
|
+
# The internal writer is the same canonical lifecycle path, with no dispatch side effects.
|
|
89
|
+
$S internal session-state asking --session "$sid" --note "stopped with uncommitted work — commit your spec+code on the node branch, then re-declare done" >/dev/null 2>&1 || true
|
|
95
90
|
exit 0
|
|
96
91
|
fi
|
|
97
92
|
reason=$($S internal hook-prompt stop-gate --variant commit --reason "$gatemsg" --cli "$S" --propose "$proposal") || exit 1
|
|
@@ -106,13 +101,15 @@ fi
|
|
|
106
101
|
if [ "$cont" = true ]; then
|
|
107
102
|
# The forced continuation also stopped without declaring. Escape into asking: no default may invent a
|
|
108
103
|
# completed lane, and the stopped agent now needs a human prompt to choose merge, close, ask, or park.
|
|
109
|
-
|
|
104
|
+
# Keep this fallback inside the hook/CLI boundary. The porcelain `session ask` command can wait on
|
|
105
|
+
# delivery and workspace builds; a stop hook must settle the canonical state independently of those paths.
|
|
106
|
+
$S internal session-state asking --session "$sid" --note "auto: stopped without declaring — choose merge, close, ask, or park; done --propose nothing records no state" >/dev/null 2>&1 || true
|
|
110
107
|
exit 0
|
|
111
108
|
fi
|
|
112
109
|
|
|
113
110
|
# first stop in an undeclared state -> block. The FULL teaching text prints ONCE per session; every later
|
|
114
111
|
# undeclared stop gets a ONE-LINE version (a heavy session hits this gate 15-20x a night — re-printing the
|
|
115
|
-
# full menu each time is pure token noise). The once-sentinel is a plain file beside
|
|
112
|
+
# full menu each time is pure token noise). The once-sentinel is a plain file beside runtime.json in the
|
|
116
113
|
# session's global store dir — the same per-session-sentinel mechanism as the CLI's note-echo-taught; $sdir
|
|
117
114
|
# is already alias-resolved here, so a codex thread id lands on the same file, and an unwritable dir just
|
|
118
115
|
# teaches again (never blocks the block). The terse line must stay SELF-EXPLANATORY: an agent whose context
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: merge
|
|
3
|
+
surface: skill, command
|
|
4
|
+
status: active
|
|
5
|
+
hue: 130
|
|
6
|
+
desc: Land this session's completed branch into the repository's source-of-truth branch as one verified no-ff merge, preserve unrelated dirty work, push it, and settle the session honestly. Use when the user says /merge, asks to merge or land this session, or when a supervisor dispatches a merge.
|
|
7
|
+
kind: mutating
|
|
8
|
+
---
|
|
9
|
+
# merge
|
|
10
|
+
|
|
11
|
+
Land the current SpexCode session's branch; do not dispatch another merge request back to yourself.
|
|
12
|
+
|
|
13
|
+
1. Inspect the current session, branch, worktree, source-of-truth checkout, and live Git status. Treat every
|
|
14
|
+
pre-existing dirty or untracked path as user-owned. Record its exact status and diff fingerprint; never
|
|
15
|
+
discard it, fold it into your commit, or hide it behind an unverified stash.
|
|
16
|
+
2. Commit this session's intended spec and code first. In this worktree, merge the latest source-of-truth
|
|
17
|
+
head into the session branch. Resolve conflicts here, then rerun the focused proof, build, `spex spec lint`,
|
|
18
|
+
and `spex eval lint --changed` required by the changed behavior.
|
|
19
|
+
3. Immediately before landing, verify
|
|
20
|
+
`git merge-base --is-ancestor <source-head> <session-head>`. If it fails, sync again. A clean textual merge
|
|
21
|
+
is not product proof.
|
|
22
|
+
4. In the source-of-truth checkout, make one `--no-ff` merge of the already-synced session tip. Do not resolve
|
|
23
|
+
conflicts there. If unrelated dirty work prevents the merge, preserve it byte-for-byte and report the exact
|
|
24
|
+
overlap rather than forcing, resetting, or committing it.
|
|
25
|
+
5. Verify the source checkout has no `MERGE_HEAD`, the session tip is its ancestor, unrelated dirty
|
|
26
|
+
fingerprints are unchanged, and the post-merge gates pass. Push the source-of-truth branch only after
|
|
27
|
+
those checks.
|
|
28
|
+
6. Stop test-owned processes and publish any evidence a human must inspect. If the work is fully landed and
|
|
29
|
+
no decision remains, run `spex session done --propose close` as the final action. Never close your own
|
|
30
|
+
session directly. If a real decision or external wake-up remains, declare the truthful alternative instead.
|
|
31
|
+
|
|
32
|
+
`spex session merge <SEL>` is the supervisor-facing dispatcher that sends this workflow to another session.
|
|
33
|
+
Inside the target session, execute the workflow above; do not call `spex session merge .` recursively.
|
|
@@ -11,9 +11,5 @@ where an agent invokes them on demand, each carrying `surface: skill`. Grouping
|
|
|
11
11
|
legible at a glance — the skill plugins on this shelf, the command presets on [[commands]], the auxiliary
|
|
12
12
|
system contracts on [[prompts]], with [[core]] a flat child beside them.
|
|
13
13
|
|
|
14
|
-
This node is a **shelf, not a surface
|
|
15
|
-
|
|
16
|
-
exactly as it would at the root. A plugin that serves BOTH surfaces — [[distill]] is skill and command —
|
|
17
|
-
shelves here by its primary (skill) identity and still gathers as a command through its field. The init
|
|
18
|
-
templates mirror this layout. The shelf stays pure presentation: moving a resident beneath it changes
|
|
19
|
-
neither that plugin's identity nor the surfaces gathered from its frontmatter.
|
|
14
|
+
This node is a **shelf, not a surface**. Its routing and relocation invariant is owned once by
|
|
15
|
+
[[.plugins]]'s shelf invariant; this node only describes skill-specific materialization and invocation.
|
|
@@ -21,3 +21,10 @@ materialized into the agent's contract.
|
|
|
21
21
|
Which plugins `spex init` ships is the init-preset rule. `seed: false` excludes a plugin subtree; shared
|
|
22
22
|
plugins have one body and one helper set — there is no separately authored adopter variant. Dogfood eval
|
|
23
23
|
scenarios/readings remain with the implementation and git history they measure.
|
|
24
|
+
|
|
25
|
+
## shelf invariant
|
|
26
|
+
|
|
27
|
+
Grouping shelves (`[[commands]]`, `[[skills]]`, `[[review]]`, and `[[prompts]]`) are presentation parents, not
|
|
28
|
+
surfaces. Discovery is recursive and field-driven, so moving a resident under a shelf changes neither its
|
|
29
|
+
identity nor the surfaces gathered from its frontmatter. Each shelf therefore states only its surface-specific
|
|
30
|
+
purpose; it must not copy this routing invariant.
|
|
@@ -9,8 +9,8 @@ export type Unit = {
|
|
|
9
9
|
export type Extractor = {
|
|
10
10
|
id: string;
|
|
11
11
|
claims(ext: string): boolean;
|
|
12
|
-
ready(): true | string
|
|
13
|
-
extract(content: string, filename: string): Unit[]
|
|
12
|
+
ready(): true | string | Promise<true | string>;
|
|
13
|
+
extract(content: string, filename: string): Unit[] | Promise<Unit[]>;
|
|
14
14
|
memoKey: (filename: string) => string;
|
|
15
15
|
};
|
|
16
16
|
export type CodeEntry = {
|
|
@@ -56,6 +56,16 @@ export type LangSpec = {
|
|
|
56
56
|
export declare function heuristicExtractor(spec: LangSpec): Extractor;
|
|
57
57
|
export declare const JS_LANG_R5B: LangSpec;
|
|
58
58
|
export declare const PYTHON_LANG: LangSpec;
|
|
59
|
+
type TreeSitterNode = any;
|
|
60
|
+
export type TreeSitterLanguageRow = {
|
|
61
|
+
id: string;
|
|
62
|
+
extensions: string[];
|
|
63
|
+
grammar: string;
|
|
64
|
+
schema: string;
|
|
65
|
+
units(root: TreeSitterNode): Unit[];
|
|
66
|
+
};
|
|
67
|
+
export declare const TREE_SITTER_ROWS: TreeSitterLanguageRow[];
|
|
68
|
+
export declare function treeSitterExtractor(row: TreeSitterLanguageRow): Extractor;
|
|
59
69
|
export declare function extractors(root: string): Extractor[];
|
|
60
70
|
export declare function extractorFor(regs: Extractor[], ext: string): Extractor | null;
|
|
61
71
|
export declare const extOf: (path: string) => string;
|
|
@@ -92,3 +102,4 @@ export type AnchorHitQuery = {
|
|
|
92
102
|
export declare function anchorHitQueries(root: string, queries: AnchorHitQuery[], regs: Extractor[]): Promise<AnchorHit[][]>;
|
|
93
103
|
export declare function anchorHitExists(root: string, queries: AnchorHitQuery[], regs: Extractor[]): Promise<boolean[]>;
|
|
94
104
|
export declare function anchorHitCommits(root: string, win: DriftPathEvent[], symbols: string[], regs: Extractor[]): Promise<AnchorHit[]>;
|
|
105
|
+
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { join } from 'node:path';
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { gitRequiredA, gitObjectFormat, isGitObjectId, batchRevisionOids, batchBlobTexts, combinedDiffOwnedChanges, driftPathWindow, readImmutableHunkFacts, persistImmutableHunkFacts, withEventLedgerBuild } from './git.js';
|
|
4
4
|
const RS = '\x1e';
|
|
@@ -50,7 +50,9 @@ export function parseRelation(raws, relation) {
|
|
|
50
50
|
}
|
|
51
51
|
return { entries: order.map((p) => ({ path: p, selectors: byPath.get(p).selectors })), problems };
|
|
52
52
|
}
|
|
53
|
-
// ---- extractor: ts-ast
|
|
53
|
+
// ---- legacy extractor: ts-ast ----
|
|
54
|
+
// Kept as a public compatibility helper only. The production language registry below routes every shipped
|
|
55
|
+
// language through Tree-sitter WASM and never selects this host-dependent path.
|
|
54
56
|
// Parse-only via the HOST project's own typescript, so the parse matches what the project itself compiles
|
|
55
57
|
// with. If it cannot resolve, ready() returns a loud unverified verdict and lint skips these anchors
|
|
56
58
|
// without crashing (no bundled compiler, regex fallback, or fake pass for JS).
|
|
@@ -310,9 +312,7 @@ export function heuristicExtractor(spec) {
|
|
|
310
312
|
},
|
|
311
313
|
};
|
|
312
314
|
}
|
|
313
|
-
//
|
|
314
|
-
// 41-file oracle) — NOT registered for JS (ts-ast is designated); kept as the engine's reference shape
|
|
315
|
-
// and the benchmark's scoring subject.
|
|
315
|
+
// Historical JS-family reference row retained for compatibility callers; not a production registry row.
|
|
316
316
|
export const JS_LANG_R5B = {
|
|
317
317
|
id: 'heuristic-js',
|
|
318
318
|
extensions: [...JS_EXTS],
|
|
@@ -330,9 +330,7 @@ export const JS_LANG_R5B = {
|
|
|
330
330
|
},
|
|
331
331
|
boundary: /^(?:[A-Za-z_$]|\/\/|\/\*)/,
|
|
332
332
|
};
|
|
333
|
-
// Python
|
|
334
|
-
// significant indentation supplies lexical qualification and ranges. It is intentionally structural,
|
|
335
|
-
// not a Python runtime or full grammar (the user-facing boundary is documented by [[code-anchor]]).
|
|
333
|
+
// Historical Python row retained for compatibility callers; Tree-sitter owns shipped Python anchors.
|
|
336
334
|
const PY_ID = String.raw `[\p{ID_Start}_][\p{ID_Continue}_]*`;
|
|
337
335
|
export const PYTHON_LANG = {
|
|
338
336
|
id: 'heuristic-python',
|
|
@@ -349,15 +347,313 @@ export const PYTHON_LANG = {
|
|
|
349
347
|
indentScopes: { decorator: /^\s*@/ },
|
|
350
348
|
boundary: /^\S/,
|
|
351
349
|
};
|
|
350
|
+
let treeSitterModulePromise;
|
|
351
|
+
let treeSitterInitPromise;
|
|
352
|
+
const treeSitterLanguagePromises = new Map();
|
|
353
|
+
function treeSitterRuntimePath() {
|
|
354
|
+
const require = createRequire(import.meta.url);
|
|
355
|
+
return require.resolve('@vscode/tree-sitter-wasm');
|
|
356
|
+
}
|
|
357
|
+
async function treeSitterModule() {
|
|
358
|
+
if (!treeSitterModulePromise)
|
|
359
|
+
treeSitterModulePromise = import(treeSitterRuntimePath()).then((m) => m.default ?? m);
|
|
360
|
+
return treeSitterModulePromise;
|
|
361
|
+
}
|
|
362
|
+
async function treeSitterLanguage(row) {
|
|
363
|
+
const existing = treeSitterLanguagePromises.get(row.grammar);
|
|
364
|
+
if (existing)
|
|
365
|
+
return existing;
|
|
366
|
+
const pending = (async () => {
|
|
367
|
+
const mod = await treeSitterModule();
|
|
368
|
+
if (!treeSitterInitPromise) {
|
|
369
|
+
const runtime = dirname(treeSitterRuntimePath());
|
|
370
|
+
treeSitterInitPromise = mod.Parser.init({ locateFile: (file) => join(runtime, file) }).then(() => mod);
|
|
371
|
+
}
|
|
372
|
+
await treeSitterInitPromise;
|
|
373
|
+
return mod.Language.load(join(dirname(treeSitterRuntimePath()), `tree-sitter-${row.grammar}.wasm`));
|
|
374
|
+
})();
|
|
375
|
+
treeSitterLanguagePromises.set(row.grammar, pending);
|
|
376
|
+
return pending;
|
|
377
|
+
}
|
|
378
|
+
const nodeField = (node, field) => node?.childForFieldName?.(field) ?? null;
|
|
379
|
+
const nodeChildren = (node) => (node?.namedChildren ?? []).filter(Boolean);
|
|
380
|
+
const nodeAllChildren = (node) => (node?.children ?? []).filter(Boolean);
|
|
381
|
+
const nodeText = (node) => node?.text ?? '';
|
|
382
|
+
const treeHasSyntaxError = (root) => {
|
|
383
|
+
if (root?.hasError)
|
|
384
|
+
return true;
|
|
385
|
+
const pending = [root];
|
|
386
|
+
while (pending.length) {
|
|
387
|
+
const node = pending.pop();
|
|
388
|
+
if (node.isError || node.isMissing)
|
|
389
|
+
return true;
|
|
390
|
+
pending.push(...nodeAllChildren(node));
|
|
391
|
+
}
|
|
392
|
+
return false;
|
|
393
|
+
};
|
|
394
|
+
const nodeLineRange = (node, start = node.startPosition.row + 1, end = node.endPosition.row + 1) => ({ start, end });
|
|
395
|
+
const simpleName = (node) => nodeText(node).replace(/^\s+|\s+$/g, '');
|
|
396
|
+
const lastTypeName = (text) => {
|
|
397
|
+
const clean = text.replace(/^\s*\(/, '').replace(/\)\s*$/, '').replace(/^\s*[*&]+/, '');
|
|
398
|
+
const base = clean.split(/[.[\]<>\s]/, 1)[0];
|
|
399
|
+
return (base.split('.').pop() ?? base).replace(/[^\p{L}\p{N}_$]/gu, '');
|
|
400
|
+
};
|
|
401
|
+
function tsTreeUnits(root) {
|
|
402
|
+
const units = [];
|
|
403
|
+
const add = (name, kind, node, typeOnly = false, start) => {
|
|
404
|
+
if (!name)
|
|
405
|
+
return;
|
|
406
|
+
units.push({ name, kind, ...nodeLineRange(node, start), ...(typeOnly ? { typeOnly: true } : {}) });
|
|
407
|
+
};
|
|
408
|
+
const methodUnits = (className, body) => {
|
|
409
|
+
for (const member of nodeChildren(body)) {
|
|
410
|
+
if (member.type !== 'method_definition' || !nodeField(member, 'body'))
|
|
411
|
+
continue;
|
|
412
|
+
const name = nodeText(nodeField(member, 'name')) || nodeText(member.namedChildren?.[0]) || '(computed)';
|
|
413
|
+
add(`${className}.${name === 'constructor' ? 'constructor' : name}`, 'method', member);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
for (const raw of nodeChildren(root)) {
|
|
417
|
+
const top = raw.type === 'export_statement' ? nodeChildren(raw) : [raw];
|
|
418
|
+
for (const node of top) {
|
|
419
|
+
if (node.type === 'function_declaration')
|
|
420
|
+
add(nodeText(nodeField(node, 'name')), 'function', node);
|
|
421
|
+
else if (node.type === 'class_declaration') {
|
|
422
|
+
const name = nodeText(nodeField(node, 'name'));
|
|
423
|
+
add(name, 'class', node);
|
|
424
|
+
methodUnits(name, nodeField(node, 'body'));
|
|
425
|
+
}
|
|
426
|
+
else if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
|
|
427
|
+
for (const decl of nodeChildren(node).filter((child) => child.type === 'variable_declarator')) {
|
|
428
|
+
const name = nodeText(nodeField(decl, 'name'));
|
|
429
|
+
if (!/^[A-Za-z_$][\w$]*$/u.test(name))
|
|
430
|
+
continue;
|
|
431
|
+
const value = nodeField(decl, 'value');
|
|
432
|
+
add(name, value?.type === 'arrow_function' || value?.type === 'function' ? 'const-fn' : 'const-data', node);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else if (node.type === 'enum_declaration')
|
|
436
|
+
add(nodeText(nodeField(node, 'name')), 'enum', node);
|
|
437
|
+
else if (node.type === 'interface_declaration')
|
|
438
|
+
add(nodeText(nodeField(node, 'name')), 'interface', node, true);
|
|
439
|
+
else if (node.type === 'type_alias_declaration')
|
|
440
|
+
add(nodeText(nodeField(node, 'name')), 'type', node, true);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return units;
|
|
444
|
+
}
|
|
445
|
+
function pythonTreeUnits(root) {
|
|
446
|
+
const units = [];
|
|
447
|
+
const definition = (node) => node.type === 'decorated_definition'
|
|
448
|
+
? nodeChildren(node).find((child) => child.type === 'function_definition' || child.type === 'class_definition') ?? null
|
|
449
|
+
: node;
|
|
450
|
+
const visitContainer = (container, scopes) => {
|
|
451
|
+
for (const child of nodeChildren(container)) {
|
|
452
|
+
const inner = definition(child);
|
|
453
|
+
if (!inner || (inner.type !== 'function_definition' && inner.type !== 'class_definition')) {
|
|
454
|
+
visitContainer(child, scopes);
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
const name = nodeText(nodeField(inner, 'name'));
|
|
458
|
+
const qualified = [...scopes.map((scope) => scope.name), name].filter(Boolean).join('.');
|
|
459
|
+
const wrapperStart = child.type === 'decorated_definition' ? child.startPosition.row + 1 : undefined;
|
|
460
|
+
const kind = inner.type === 'class_definition' ? 'class' : (scopes.at(-1)?.classLike ? 'method' : 'function');
|
|
461
|
+
units.push({ name: qualified, kind, ...nodeLineRange(inner, wrapperStart) });
|
|
462
|
+
const body = nodeField(inner, 'body');
|
|
463
|
+
if (body)
|
|
464
|
+
visitContainer(body, [...scopes, { name, classLike: inner.type === 'class_definition' }]);
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
visitContainer(root, []);
|
|
468
|
+
return units;
|
|
469
|
+
}
|
|
470
|
+
function goTreeUnits(root) {
|
|
471
|
+
const units = [];
|
|
472
|
+
for (const node of root.descendantsOfType?.(['const_declaration', 'function_declaration', 'method_declaration', 'type_declaration']) ?? []) {
|
|
473
|
+
if (node.type === 'const_declaration') {
|
|
474
|
+
for (const spec of nodeChildren(node).filter((child) => child.type === 'const_spec')) {
|
|
475
|
+
const name = nodeText(nodeField(spec, 'name')) || nodeText(nodeChildren(spec)[0]);
|
|
476
|
+
if (name)
|
|
477
|
+
units.push({ name, kind: 'const-data', ...nodeLineRange(spec) });
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
else if (node.type === 'function_declaration') {
|
|
481
|
+
units.push({ name: nodeText(nodeField(node, 'name')), kind: 'function', ...nodeLineRange(node) });
|
|
482
|
+
}
|
|
483
|
+
else if (node.type === 'method_declaration') {
|
|
484
|
+
const receiver = nodeField(node, 'receiver');
|
|
485
|
+
const parameter = nodeChildren(receiver).find((child) => child.type === 'parameter_declaration');
|
|
486
|
+
const receiverType = nodeField(parameter, 'type') ?? nodeChildren(parameter)[1];
|
|
487
|
+
const receiverName = lastTypeName(nodeText(receiverType));
|
|
488
|
+
const methodName = nodeText(nodeField(node, 'name'));
|
|
489
|
+
units.push({ name: receiverName ? `${receiverName}.${methodName}` : methodName, kind: 'method', ...nodeLineRange(node) });
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
for (const spec of nodeChildren(node).filter((child) => child.type === 'type_spec')) {
|
|
493
|
+
const name = nodeText(nodeField(spec, 'name')) || nodeText(nodeChildren(spec)[0]);
|
|
494
|
+
const shape = nodeChildren(spec).find((child) => ['struct_type', 'interface_type'].includes(child.type));
|
|
495
|
+
const kind = shape?.type === 'struct_type' ? 'struct' : shape?.type === 'interface_type' ? 'interface' : 'type';
|
|
496
|
+
if (name)
|
|
497
|
+
units.push({ name, kind, ...nodeLineRange(spec) });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return units;
|
|
502
|
+
}
|
|
503
|
+
function rustImplName(node) {
|
|
504
|
+
const header = nodeText(node).split('{', 1)[0];
|
|
505
|
+
const forMatch = header.match(/\bfor\s+([A-Za-z_][\w:]*)/u);
|
|
506
|
+
if (forMatch)
|
|
507
|
+
return forMatch[1].split('::').pop() ?? forMatch[1];
|
|
508
|
+
const names = nodeChildren(node).filter((child) => child.type === 'type_identifier' || child.type === 'scoped_type_identifier');
|
|
509
|
+
return lastTypeName(nodeText(names[0]));
|
|
510
|
+
}
|
|
511
|
+
function rustTreeUnits(root) {
|
|
512
|
+
const units = [];
|
|
513
|
+
for (const node of root.descendantsOfType?.(['const_item', 'function_item', 'struct_item', 'enum_item', 'trait_item']) ?? []) {
|
|
514
|
+
const name = nodeText(nodeField(node, 'name')) || nodeText(nodeChildren(node).find((child) => child.type === 'type_identifier'));
|
|
515
|
+
if (!name)
|
|
516
|
+
continue;
|
|
517
|
+
const parent = node.parent;
|
|
518
|
+
let impl = parent;
|
|
519
|
+
while (impl && impl.type !== 'impl_item' && impl.type !== 'source_file')
|
|
520
|
+
impl = impl.parent;
|
|
521
|
+
const implName = impl?.type === 'impl_item' ? rustImplName(impl) : '';
|
|
522
|
+
const kind = node.type === 'function_item' ? (implName ? 'method' : 'function')
|
|
523
|
+
: node.type === 'const_item' ? 'const-data'
|
|
524
|
+
: node.type === 'struct_item' ? 'struct'
|
|
525
|
+
: node.type === 'enum_item' ? 'enum' : 'trait';
|
|
526
|
+
units.push({ name: implName && node.type === 'function_item' ? `${implName}.${name}` : name, kind, ...nodeLineRange(node) });
|
|
527
|
+
}
|
|
528
|
+
return units;
|
|
529
|
+
}
|
|
530
|
+
function javaTreeUnits(root) {
|
|
531
|
+
const units = [];
|
|
532
|
+
const visitClass = (node, scopes) => {
|
|
533
|
+
const name = nodeText(nodeField(node, 'name')) || nodeText(nodeChildren(node).find((child) => child.type === 'identifier'));
|
|
534
|
+
if (!name)
|
|
535
|
+
return;
|
|
536
|
+
const qualified = [...scopes, name].join('.');
|
|
537
|
+
const kind = node.type === 'class_declaration' ? 'class' : node.type === 'interface_declaration' ? 'interface' : 'enum';
|
|
538
|
+
units.push({ name: qualified, kind, ...nodeLineRange(node) });
|
|
539
|
+
const body = nodeField(node, 'body');
|
|
540
|
+
for (const child of nodeChildren(body)) {
|
|
541
|
+
if (child.type === 'field_declaration' && /\bfinal\b/u.test(nodeText(child))) {
|
|
542
|
+
for (const variable of nodeChildren(child).filter((part) => part.type === 'variable_declarator')) {
|
|
543
|
+
const field = nodeText(nodeField(variable, 'name')) || nodeText(nodeChildren(variable).find((part) => part.type === 'identifier'));
|
|
544
|
+
if (field)
|
|
545
|
+
units.push({ name: `${qualified}.${field}`, kind: 'const-data', ...nodeLineRange(child) });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
else if (child.type === 'method_declaration' || child.type === 'constructor_declaration') {
|
|
549
|
+
const method = nodeText(nodeField(child, 'name')) || nodeText(nodeChildren(child).find((part) => part.type === 'identifier'));
|
|
550
|
+
if (method)
|
|
551
|
+
units.push({ name: `${qualified}.${child.type === 'constructor_declaration' ? 'constructor' : method}`, kind: 'method', ...nodeLineRange(child) });
|
|
552
|
+
}
|
|
553
|
+
else if (child.type.endsWith('_declaration') && ['class_declaration', 'interface_declaration', 'enum_declaration'].includes(child.type))
|
|
554
|
+
visitClass(child, [...scopes, name]);
|
|
555
|
+
}
|
|
556
|
+
};
|
|
557
|
+
const walk = (node) => {
|
|
558
|
+
for (const child of nodeChildren(node)) {
|
|
559
|
+
if (['class_declaration', 'interface_declaration', 'enum_declaration'].includes(child.type))
|
|
560
|
+
visitClass(child, []);
|
|
561
|
+
else
|
|
562
|
+
walk(child);
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
walk(root);
|
|
566
|
+
return units;
|
|
567
|
+
}
|
|
568
|
+
function rubyTreeUnits(root) {
|
|
569
|
+
const units = [];
|
|
570
|
+
const rubyName = (name) => name.replaceAll('::', '.');
|
|
571
|
+
const nameOf = (node) => rubyName(nodeText(nodeField(node, 'name')) || nodeText(nodeChildren(node).find((child) => ['constant', 'identifier'].includes(child.type))));
|
|
572
|
+
const visit = (container, scopes) => {
|
|
573
|
+
for (const child of nodeChildren(container)) {
|
|
574
|
+
if (child.type === 'class' || child.type === 'module') {
|
|
575
|
+
const name = nameOf(child);
|
|
576
|
+
if (!name)
|
|
577
|
+
continue;
|
|
578
|
+
const qualified = [...scopes.map((scope) => scope.name), name].join('.');
|
|
579
|
+
units.push({ name: qualified, kind: child.type === 'class' ? 'class' : 'module', ...nodeLineRange(child) });
|
|
580
|
+
visit(nodeField(child, 'body') ?? child, [...scopes, { name, kind: child.type }]);
|
|
581
|
+
}
|
|
582
|
+
else if (child.type === 'assignment' && nodeField(child, 'left')?.type === 'constant') {
|
|
583
|
+
const name = rubyName(nodeText(nodeField(child, 'left')));
|
|
584
|
+
if (name)
|
|
585
|
+
units.push({ name: [...scopes.map((scope) => scope.name), name].join('.'), kind: 'const-data', ...nodeLineRange(child) });
|
|
586
|
+
}
|
|
587
|
+
else if (child.type === 'method' || child.type === 'singleton_method') {
|
|
588
|
+
const name = nameOf(child);
|
|
589
|
+
if (!name)
|
|
590
|
+
continue;
|
|
591
|
+
const receiver = child.type === 'singleton_method' ? rubyName(nodeText(nodeField(child, 'object'))) : '';
|
|
592
|
+
const qualified = receiver && receiver !== 'self' ? `${receiver}.${name}` : [...scopes.map((scope) => scope.name), name].join('.');
|
|
593
|
+
units.push({ name: qualified, kind: child.type === 'singleton_method' || scopes.length ? 'method' : 'function', ...nodeLineRange(child) });
|
|
594
|
+
}
|
|
595
|
+
else
|
|
596
|
+
visit(child, scopes);
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
visit(root, []);
|
|
600
|
+
return units;
|
|
601
|
+
}
|
|
602
|
+
export const TREE_SITTER_ROWS = [
|
|
603
|
+
{ id: 'tree-sitter-typescript', extensions: ['ts', 'mts', 'cts'], grammar: 'typescript', schema: 'ts-v1', units: tsTreeUnits },
|
|
604
|
+
{ id: 'tree-sitter-tsx', extensions: ['tsx'], grammar: 'tsx', schema: 'tsx-v1', units: tsTreeUnits },
|
|
605
|
+
{ id: 'tree-sitter-javascript', extensions: ['js', 'jsx', 'mjs', 'cjs'], grammar: 'javascript', schema: 'js-v1', units: tsTreeUnits },
|
|
606
|
+
{ id: 'tree-sitter-python', extensions: ['py', 'pyi'], grammar: 'python', schema: 'python-v1', units: pythonTreeUnits },
|
|
607
|
+
{ id: 'tree-sitter-go', extensions: ['go'], grammar: 'go', schema: 'go-v1', units: goTreeUnits },
|
|
608
|
+
{ id: 'tree-sitter-rust', extensions: ['rs'], grammar: 'rust', schema: 'rust-v1', units: rustTreeUnits },
|
|
609
|
+
{ id: 'tree-sitter-java', extensions: ['java'], grammar: 'java', schema: 'java-v1', units: javaTreeUnits },
|
|
610
|
+
{ id: 'tree-sitter-ruby', extensions: ['rb'], grammar: 'ruby', schema: 'ruby-v1', units: rubyTreeUnits },
|
|
611
|
+
];
|
|
612
|
+
export function treeSitterExtractor(row) {
|
|
613
|
+
let readiness;
|
|
614
|
+
const getLanguage = () => treeSitterLanguage(row);
|
|
615
|
+
return {
|
|
616
|
+
id: row.id,
|
|
617
|
+
claims: (ext) => row.extensions.includes(ext),
|
|
618
|
+
async ready() {
|
|
619
|
+
if (readiness !== undefined)
|
|
620
|
+
return readiness;
|
|
621
|
+
try {
|
|
622
|
+
await getLanguage();
|
|
623
|
+
readiness = true;
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
readiness = `Tree-sitter extractor '${row.id}' cannot load grammar '${row.grammar}': ${error?.message ?? String(error)} — reinstall SpexCode or remove the #anchor`;
|
|
627
|
+
}
|
|
628
|
+
return readiness;
|
|
629
|
+
},
|
|
630
|
+
async extract(content, filename) {
|
|
631
|
+
const language = await getLanguage();
|
|
632
|
+
const mod = await treeSitterModule();
|
|
633
|
+
const parser = new mod.Parser().setLanguage(language);
|
|
634
|
+
let tree;
|
|
635
|
+
try {
|
|
636
|
+
tree = parser.parse(content);
|
|
637
|
+
if (!tree?.rootNode || treeHasSyntaxError(tree.rootNode))
|
|
638
|
+
throw new Error(`${filename} has Tree-sitter syntax errors`);
|
|
639
|
+
return row.units(tree.rootNode);
|
|
640
|
+
}
|
|
641
|
+
finally {
|
|
642
|
+
tree?.delete?.();
|
|
643
|
+
parser.delete?.();
|
|
644
|
+
}
|
|
645
|
+
},
|
|
646
|
+
memoKey(filename) { return JSON.stringify({ schema: 'tree-sitter-wasm-v1', row: row.id, grammar: row.grammar, rowSchema: row.schema, filename }); },
|
|
647
|
+
};
|
|
648
|
+
}
|
|
352
649
|
// ---- registry: extension -> its ONE designated extractor ----
|
|
353
|
-
// The registry
|
|
354
|
-
// heuristicExtractor(LangSpec) or a web-tree-sitter extractor carrying its own wasm-grammar/query
|
|
355
|
-
// config — whatever the implementation needs rides inside its own factory, never in the registry.
|
|
650
|
+
// The registry is one Tree-sitter language adapter: every shipped extension is a grammar DATA row.
|
|
356
651
|
export function extractors(root) {
|
|
357
|
-
|
|
652
|
+
void root;
|
|
653
|
+
return TREE_SITTER_ROWS.map(treeSitterExtractor);
|
|
358
654
|
}
|
|
359
655
|
// first claiming extractor IS the designation (the registry order defines it); null = no anchor support
|
|
360
|
-
// for this language yet (lint ERRORS — the remedy is a
|
|
656
|
+
// for this language yet (lint ERRORS — the remedy is a Tree-sitter language row, or dropping the anchor).
|
|
361
657
|
export function extractorFor(regs, ext) {
|
|
362
658
|
return regs.find((x) => x.claims(ext)) ?? null;
|
|
363
659
|
}
|
|
@@ -417,7 +713,7 @@ async function unitsAtFileRevision(commit, path, x, objectFormat, oid, text) {
|
|
|
417
713
|
throw new Error(`git cat-file --batch omitted object ${oid} for ${commit}:${path}`);
|
|
418
714
|
let result;
|
|
419
715
|
try {
|
|
420
|
-
result = { units: x.extract(text, path) };
|
|
716
|
+
result = { units: await x.extract(text, path) };
|
|
421
717
|
}
|
|
422
718
|
catch (e) {
|
|
423
719
|
result = { unparseable: e?.message ?? String(e) };
|
|
@@ -626,7 +922,7 @@ async function runAnchorQueriesInLedger(root, queries, regs, stopAtFirstHit) {
|
|
|
626
922
|
continue;
|
|
627
923
|
const ref = revisions.get(key), oid = oidByRef.get(key) ?? null;
|
|
628
924
|
const x = extractorFor(regs, extOf(ref.path));
|
|
629
|
-
const ready = x
|
|
925
|
+
const ready = x ? await x.ready() : undefined;
|
|
630
926
|
if (!x || ready !== true) {
|
|
631
927
|
units.set(key, { unparseable: !x ? `no designated extractor for ${ref.path}` : String(ready) });
|
|
632
928
|
continue;
|
|
@@ -153,6 +153,10 @@ export type ReviewDiffFile = {
|
|
|
153
153
|
additions: number;
|
|
154
154
|
deletions: number;
|
|
155
155
|
};
|
|
156
|
+
export declare function parseStatPath(token: string): {
|
|
157
|
+
from: string;
|
|
158
|
+
to: string;
|
|
159
|
+
};
|
|
156
160
|
export declare function mergeBaseDiff(wtPath: string, mainRef?: string, headRef?: string): Promise<ReviewDiffFile[]>;
|
|
157
161
|
export declare function mergeConflicts(wtPath: string, mainRef?: string, headRef?: string): Promise<boolean>;
|
|
158
162
|
type WorktreeSpecDemand = {
|
|
@@ -230,15 +230,15 @@ export async function batchRevisionOids(root, revisions, options = {}) {
|
|
|
230
230
|
});
|
|
231
231
|
}
|
|
232
232
|
export async function batchBlobTexts(root, oids) {
|
|
233
|
-
const
|
|
233
|
+
const objectIds = [...new Set(oids.filter(Boolean))];
|
|
234
234
|
const files = new Map();
|
|
235
|
-
if (!
|
|
235
|
+
if (!objectIds.length)
|
|
236
236
|
return files;
|
|
237
|
-
for (const oid of
|
|
237
|
+
for (const oid of objectIds)
|
|
238
238
|
if (!isGitObjectId(root, oid))
|
|
239
239
|
throw new Error(`invalid object id '${oid}'`);
|
|
240
|
-
for (let cursor = 0; cursor <
|
|
241
|
-
const chunk =
|
|
240
|
+
for (let cursor = 0; cursor < objectIds.length; cursor += BATCH_BLOB_CHUNK) {
|
|
241
|
+
const chunk = objectIds.slice(cursor, cursor + BATCH_BLOB_CHUNK);
|
|
242
242
|
const out = await batchBuffer(['-C', root, 'cat-file', '--batch'], chunk.join('\n') + '\n', BATCH_BLOB_MAX_BUFFER);
|
|
243
243
|
let offset = 0;
|
|
244
244
|
for (const oid of chunk) {
|
|
@@ -1609,12 +1609,12 @@ function canonicalPathProjector(renamesByFrom, topology) {
|
|
|
1609
1609
|
continue;
|
|
1610
1610
|
}
|
|
1611
1611
|
seen.add(candidate);
|
|
1612
|
-
const
|
|
1612
|
+
const applicableByIdentity = new Map();
|
|
1613
1613
|
for (const rename of renamesByFrom.get(candidate) ?? []) {
|
|
1614
1614
|
if (!precedes(rename.hash, event))
|
|
1615
|
-
|
|
1615
|
+
applicableByIdentity.set(`${rename.hash}\0${rename.to}`, rename);
|
|
1616
1616
|
}
|
|
1617
|
-
const applicable = [...
|
|
1617
|
+
const applicable = [...applicableByIdentity.values()];
|
|
1618
1618
|
if (!applicable.length) {
|
|
1619
1619
|
resolved.add(candidate);
|
|
1620
1620
|
continue;
|
|
@@ -2531,7 +2531,7 @@ function parseNameStatus(out) {
|
|
|
2531
2531
|
return rows;
|
|
2532
2532
|
}
|
|
2533
2533
|
const DIFF_STATUS = { A: 'added', M: 'modified', D: 'deleted', R: 'renamed', C: 'copied', T: 'type-changed' };
|
|
2534
|
-
function parseStatPath(token) {
|
|
2534
|
+
export function parseStatPath(token) {
|
|
2535
2535
|
const b = token.indexOf('{'), arrow = token.indexOf(' => ', b), close = token.indexOf('}', arrow);
|
|
2536
2536
|
if (b >= 0 && arrow > b && close > arrow) {
|
|
2537
2537
|
const pre = token.slice(0, b), post = token.slice(close + 1);
|