mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-07 10:45:59 -10:00
docs(plugins): add native prompt author example
This commit is contained in:
1 parent
b66b2c34ff
commit
4e76141a41
6 files changed
+354
No files matched your search
@@ -384,6 +384,10 @@ New resource types:
|
||||
with the plugin;
|
||||
- any other file listed under `themes` is ignored.
|
||||
|
||||
The [native prompt example](../examples/work-note-kit/README.md) shows a
|
||||
template-only package, nested names, argument substitution, and the
|
||||
install/update/disable/remove lifecycle without a compiler or sidecar.
|
||||
|
||||
The `runtime` block declares a code extension — a sidecar process Reasonix
|
||||
launches and talks to over the Extension Protocol (JSON-RPC 2.0 over stdio;
|
||||
see `docs/EXTENSION_PROTOCOL.generated.md` for the method index and
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
---
|
||||
owner: "@esengine"
|
||||
backup: "@SivanCola"
|
||||
status: active
|
||||
reviewed: 2026-10-02
|
||||
---
|
||||
|
||||
# Native prompt template example
|
||||
|
||||
1. This native v2 package contributes two Markdown prompt templates. It has
|
||||
no runtime, hook, MCP server, compiler, or installation script.
|
||||
2. Templates become ordinary user turns when explicitly invoked. They supply
|
||||
model guidance; they do not run checks, limit tools, or grant permission.
|
||||
3. Use a normally configured model for the manual exercise. Its normal account
|
||||
access and usage charges apply. Model output quality needs manual evaluation.
|
||||
|
||||
## Install and discover
|
||||
|
||||
From the Reasonix repository root, preview and install a copy:
|
||||
|
||||
```sh
|
||||
reasonix plugin install ./examples/work-note-kit --dry-run
|
||||
reasonix plugin install ./examples/work-note-kit --yes
|
||||
reasonix plugin doctor work-note-kit
|
||||
reasonix plugin show work-note-kit
|
||||
```
|
||||
|
||||
1. Preview must leave the package uninstalled. The installed inventory must
|
||||
report two prompts and no runtime or executable contribution.
|
||||
2. `contributes.prompts` names the `prompts` directory, not an individual file.
|
||||
A template name comes from its path: `handoff.md` becomes `handoff`, and
|
||||
`checks/summarize.md` becomes `checks:summarize`.
|
||||
3. Start a new session after installation, or use `/reload` while idle. In
|
||||
Studio, use **Reload runtime** in **Settings → Extension → Installed**.
|
||||
The package prefix makes invocations distinct from other authors' templates.
|
||||
|
||||
## Run the exercise
|
||||
|
||||
Enter these in an interactive session:
|
||||
|
||||
```text
|
||||
/work-note-kit:handoff lint-passed typecheck-passed integration-not-run
|
||||
/work-note-kit:checks:summarize build unit-passed network-not-run
|
||||
```
|
||||
|
||||
1. The handoff should preserve both passing checks, identify integration as
|
||||
unverified, and avoid claiming deployment or an independent review.
|
||||
2. The check summary should use `build` as its label and preserve the full
|
||||
observations, including `network-not-run`. `$1` is the first whitespace
|
||||
separated argument; `$ARGUMENTS` includes all arguments, including the label.
|
||||
3. `$$` emits one literal dollar sign. Templates are text substitution, not
|
||||
shell commands: quotes do not group arguments and no expression is evaluated.
|
||||
4. Run `/work-note-kit:handoff` without arguments as a failure exercise. The
|
||||
template still reaches the model; a useful answer asks for missing facts.
|
||||
This is model behavior to assess, not a host-side input-validation guarantee.
|
||||
5. Use qualified names. A short-name compatibility alias may be absent when a
|
||||
project command or another package owns that name.
|
||||
|
||||
## Edit, replace and remove
|
||||
|
||||
1. Edit a source template. A copy installation keeps its own files; source
|
||||
edits do not change that installed copy. Preview and apply replacement,
|
||||
then start a new session or reload while idle:
|
||||
|
||||
```sh
|
||||
reasonix plugin install ./examples/work-note-kit --replace --dry-run
|
||||
reasonix plugin install ./examples/work-note-kit --replace --yes
|
||||
reasonix plugin disable work-note-kit
|
||||
reasonix plugin enable work-note-kit
|
||||
reasonix plugin remove work-note-kit --yes
|
||||
```
|
||||
|
||||
2. Disable preserves the installed copy but excludes its templates from newly
|
||||
built sessions. Enable restores discovery. Removal deletes the managed copy
|
||||
and registration while keeping the original source.
|
||||
3. For link-based authoring, remove the copy, preview using `--link --dry-run`,
|
||||
then install using `--link --yes`. Keep the linked source available and reload
|
||||
after edits. Removing a link preserves its external source directory.
|
||||
4. Disable and removal do not erase a template already sent in session history.
|
||||
Use a fresh session when checking that a contribution is absent.
|
||||
|
||||
## Verify the host path
|
||||
|
||||
```sh
|
||||
go test ./internal/assembly/boot/ -run '^TestEffectWorkNotePromptExampleLifecycle$' -count=1
|
||||
```
|
||||
|
||||
1. The effect test uses the real approved-plan installer and `boot.Build`. It
|
||||
checks copied templates, source-edit isolation, replacement preview/application,
|
||||
disable/enable/removal, qualified naming beside a project-owned short command,
|
||||
and rendered arguments in actual provider user messages.
|
||||
2. The provider is scripted. This proves host routing and substitution, not a
|
||||
live model's handoff quality or a public market publication.
|
||||
|
||||
| Observation | Check |
|
||||
| --- | --- |
|
||||
| No template in a fresh session | Check package enablement and the declared prompt directory; run `plugin show`. |
|
||||
| The short name runs a different command | Use the full `/work-note-kit:<name>` invocation. |
|
||||
| A source edit has no effect | Replace the installed copy, or use a link, then reload or build a fresh session. |
|
||||
| The answer invents a check result | Compare it with the supplied observations; prompt guidance does not enforce truthfulness. |
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
owner: "@esengine"
|
||||
backup: "@SivanCola"
|
||||
status: active
|
||||
reviewed: 2026-10-02
|
||||
description: Summarize supplied check evidence and its limits
|
||||
argument-hint: <label> <observations...>
|
||||
---
|
||||
|
||||
Summarize the supplied check evidence, keeping observation separate from inference.
|
||||
|
||||
Label: $1
|
||||
Observations: $ARGUMENTS
|
||||
A literal dollar sign is $$.
|
||||
|
||||
1. Name each reported check, its scope and observed result. Preserve failures
|
||||
and checks not run; do not infer success from the absence of a failure.
|
||||
2. If the label or observations are missing, identify the missing input. Do
|
||||
not fabricate logs, test counts, platforms, or a reviewer verdict.
|
||||
3. Explain what the evidence does not establish. Use the caller's language.
|
||||
This prompt does not execute a check or enforce an execution boundary.
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
owner: "@esengine"
|
||||
backup: "@SivanCola"
|
||||
status: active
|
||||
reviewed: 2026-10-02
|
||||
description: Draft a handoff from supplied task facts without inventing results
|
||||
argument-hint: <task facts...>
|
||||
---
|
||||
|
||||
Draft a short work handoff using only the facts supplied by the caller.
|
||||
|
||||
Facts supplied by the caller:
|
||||
$ARGUMENTS
|
||||
|
||||
1. Report the outcome, evidence of checks actually performed, and remaining
|
||||
work in separate sections. Preserve identifiers and qualifications.
|
||||
2. Treat a check marked not run as unverified. A passing unit test does not
|
||||
establish an integration result, deployment, or production readiness.
|
||||
3. If the input is empty or essential facts are absent, name the missing facts
|
||||
and ask for them instead of claiming completion.
|
||||
4. Keep the answer in the caller's requested language. This template supplies
|
||||
guidance; it grants no permission to edit files, run checks, or contact others.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"apiVersion": "reasonix.io/plugin/v2",
|
||||
"name": "work-note-kit",
|
||||
"version": "1.0.0",
|
||||
"description": "Prompt templates for an evidence-based handoff and check summary",
|
||||
"contributes": {
|
||||
"prompts": ["prompts"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package boot
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"reasonix/internal/contract/event"
|
||||
"reasonix/internal/contract/provider"
|
||||
"reasonix/internal/ext/installsource"
|
||||
"reasonix/internal/ext/pluginpkg"
|
||||
"reasonix/internal/runtime/agent/testutil"
|
||||
)
|
||||
|
||||
func TestEffectWorkNotePromptExampleLifecycle(t *testing.T) {
|
||||
registerBootTokenProfileTestProvider()
|
||||
example, err := filepath.Abs(filepath.Join("..", "..", "..", "examples", "work-note-kit"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
source := robustTempDir(t)
|
||||
if err := os.CopyFS(source, os.DirFS(example)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
home := isolateConfigHome(t)
|
||||
reasonixHome := filepath.Join(home, ".reasonix")
|
||||
t.Setenv("REASONIX_HOME", reasonixHome)
|
||||
workspace := robustTempDir(t)
|
||||
t.Chdir(workspace)
|
||||
writeFile(t, workspace, ".reasonix/commands/handoff.md", "PROJECT HANDOFF $ARGUMENTS")
|
||||
writeFile(t, workspace, "reasonix.toml", `
|
||||
default_model = "test-model"
|
||||
[agent]
|
||||
system_prompt = "PROMPT EXAMPLE BASE"
|
||||
[environment]
|
||||
enabled = false
|
||||
[codegraph]
|
||||
enabled = false
|
||||
[[providers]]
|
||||
name = "test-model"
|
||||
kind = "boot-token-profile-test"
|
||||
model = "x"
|
||||
`)
|
||||
approveWorkspace(t, workspace)
|
||||
installer := installsource.NewTool(installsource.Options{ProjectRoot: workspace, HomeDir: home, RequireApprovedPlan: true})
|
||||
type result struct {
|
||||
OK bool `json:"ok"`
|
||||
Applied bool `json:"applied"`
|
||||
Status string `json:"status"`
|
||||
PlanID string `json:"planId"`
|
||||
}
|
||||
install := func(args map[string]any) result {
|
||||
t.Helper()
|
||||
raw, err := json.Marshal(args)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out, err := installer.Execute(t.Context(), raw)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var r result
|
||||
if err := json.Unmarshal([]byte(out), &r); err != nil || !r.OK {
|
||||
t.Fatalf("install_source=%s, err=%v", out, err)
|
||||
}
|
||||
return r
|
||||
}
|
||||
check := func(t *testing.T, present, updated bool) {
|
||||
t.Helper()
|
||||
rec := testutil.NewMock("work-note-example", testutil.Turn{Text: "ok"}, testutil.Turn{Text: "ok"}, testutil.Turn{Text: "ok"})
|
||||
setBootTokenProfileTestProvider(t, rec)
|
||||
ctrl, err := Build(t.Context(), Options{Sink: event.Discard, SessionDir: filepath.Join(robustTempDir(t), "sessions")})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ctrl.Close()
|
||||
if got, found := ctrl.CustomCommand("/handoff local"); !found || got != "PROJECT HANDOFF local" {
|
||||
t.Fatalf("project short command=%q, %v", got, found)
|
||||
}
|
||||
for _, name := range []string{"handoff", "checks:summarize"} {
|
||||
if _, found := ctrl.CustomCommand("/work-note-kit:" + name); found != present {
|
||||
t.Fatalf("%s found=%v, want %v", name, found, present)
|
||||
}
|
||||
}
|
||||
if !present {
|
||||
if len(rec.Requests()) != 0 {
|
||||
t.Fatal("discovering an absent template sent a provider request")
|
||||
}
|
||||
return
|
||||
}
|
||||
ctrl.EnsureSessionPath()
|
||||
for _, input := range []string{
|
||||
"/work-note-kit:handoff lint-passed integration-not-run",
|
||||
"/work-note-kit:checks:summarize build unit-passed network-not-run",
|
||||
"/work-note-kit:handoff",
|
||||
} {
|
||||
ctrl.Submit(input)
|
||||
deadline := time.Now().Add(30 * time.Second)
|
||||
for ctrl.Running() {
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatal("prompt example turn did not finish")
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
}
|
||||
requests := agentRequests(rec.Requests())
|
||||
if len(requests) != 3 {
|
||||
t.Fatalf("provider requests=%d, want three", len(requests))
|
||||
}
|
||||
for i, req := range requests {
|
||||
var user string
|
||||
for _, msg := range req.Messages {
|
||||
if msg.Role == provider.RoleUser {
|
||||
user = msg.Content
|
||||
}
|
||||
}
|
||||
if strings.Contains(systemMessage(req.Messages), "Facts supplied by the caller:") || strings.Contains(systemMessage(req.Messages), "A literal dollar sign is") {
|
||||
t.Fatal("template body entered the stable system prefix")
|
||||
}
|
||||
if i == 1 {
|
||||
for _, want := range []string{"Label: build", "Observations: build unit-passed network-not-run", "A literal dollar sign is $."} {
|
||||
if !strings.Contains(user, want) {
|
||||
t.Fatalf("check template missing %q: %s", want, user)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !strings.Contains(user, "Draft a short work handoff") || !strings.Contains(user, "name the missing facts") {
|
||||
t.Fatalf("handoff instructions missing: %s", user)
|
||||
}
|
||||
if i == 0 && !strings.Contains(user, "Facts supplied by the caller:\nlint-passed integration-not-run") {
|
||||
t.Fatalf("handoff arguments missing: %s", user)
|
||||
}
|
||||
if strings.Contains(user, "SOURCE UPDATE MARKER") != updated {
|
||||
t.Fatalf("source update presence=%v, want %v", strings.Contains(user, "SOURCE UPDATE MARKER"), updated)
|
||||
}
|
||||
}
|
||||
if strings.Contains(user, "$ARGUMENTS") || strings.Contains(user, "Label: $1") {
|
||||
t.Fatal("unrendered template tokens reached the provider")
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Run("absent", func(t *testing.T) { check(t, false, false) })
|
||||
args := map[string]any{"source": source, "kind": "plugin", "mode": "copy", "scope": "global"}
|
||||
preview := install(args)
|
||||
if preview.Applied || preview.Status != "planned" || preview.PlanID == "" {
|
||||
t.Fatalf("preview=%+v", preview)
|
||||
}
|
||||
t.Run("preview", func(t *testing.T) { check(t, false, false) })
|
||||
args["apply"], args["planId"] = true, preview.PlanID
|
||||
if applied := install(args); !applied.Applied || applied.Status != "done" {
|
||||
t.Fatalf("applied=%+v", applied)
|
||||
}
|
||||
t.Run("copied", func(t *testing.T) { check(t, true, false) })
|
||||
path := filepath.Join(source, "prompts", "handoff.md")
|
||||
contents, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(path, append(contents, []byte("\nSOURCE UPDATE MARKER\n")...), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Run("source-edit", func(t *testing.T) { check(t, true, false) })
|
||||
args["replace"], args["apply"] = true, false
|
||||
delete(args, "planId")
|
||||
preview = install(args)
|
||||
if preview.Applied || preview.Status != "planned" {
|
||||
t.Fatalf("replacement preview=%+v", preview)
|
||||
}
|
||||
t.Run("replacement-preview", func(t *testing.T) { check(t, true, false) })
|
||||
args["apply"], args["planId"] = true, preview.PlanID
|
||||
if applied := install(args); !applied.Applied || applied.Status != "done" {
|
||||
t.Fatalf("replacement=%+v", applied)
|
||||
}
|
||||
t.Run("replaced", func(t *testing.T) { check(t, true, true) })
|
||||
for _, enabled := range []bool{false, true} {
|
||||
if err := pluginpkg.SetEnabled(reasonixHome, "work-note-kit", enabled); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if enabled {
|
||||
t.Run("reenabled", func(t *testing.T) { check(t, true, true) })
|
||||
} else {
|
||||
t.Run("disabled", func(t *testing.T) { check(t, false, false) })
|
||||
}
|
||||
}
|
||||
if removed := install(map[string]any{"op": "uninstall", "name": "work-note-kit", "scope": "global"}); !removed.Applied || removed.Status != "done" {
|
||||
t.Fatalf("removed=%+v", removed)
|
||||
}
|
||||
if _, err := os.Stat(pluginpkg.InstallRoot(reasonixHome, "work-note-kit")); !os.IsNotExist(err) {
|
||||
t.Fatalf("managed copy survives removal: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
t.Fatalf("removal deleted original source: %v", err)
|
||||
}
|
||||
t.Run("removed", func(t *testing.T) { check(t, false, false) })
|
||||
}
|
||||
Reference in new issue
Block a user