norn-cli 3.3.0 → 4.0.1
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/dist/cli.js +1396 -276
- package/dist/knowledgeIndexWorker.js +71 -35
- package/package.json +3 -3
- package/schemas/norn.config.schema.json +13 -0
- package/CLAUDE.md +0 -69
- package/NOW.md +0 -223
- package/demos/agent-workbench/README.md +0 -156
- package/demos/agent-workbench/agents.nornagent +0 -87
- package/demos/agent-workbench/contracts/domain-answer.schema.json +0 -28
- package/demos/agent-workbench/contracts/domain-question.schema.json +0 -34
- package/demos/agent-workbench/contracts/router-verdict.schema.json +0 -43
- package/demos/agent-workbench/contracts/ticket.schema.json +0 -29
- package/demos/agent-workbench/contracts/verdict.schema.json +0 -48
- package/demos/agent-workbench/fake-openai-server.js +0 -128
- package/demos/agent-workbench/fixtures/aligned-ticket.json +0 -10
- package/demos/agent-workbench/fixtures/cross-domain-ticket.json +0 -10
- package/demos/agent-workbench/fixtures/invalid-output-ticket.json +0 -8
- package/demos/agent-workbench/norn.config.json +0 -15
- package/demos/agent-workbench/prompts/ticket-router.md +0 -11
- package/demos/agent-workbench/workbench.norn +0 -32
- package/demos/knowledge-editor/README.md +0 -74
- package/demos/knowledge-editor/agents.nornagent +0 -16
- package/demos/knowledge-editor/api.nornapi +0 -10
- package/demos/knowledge-editor/handbook/ops/flow.svg +0 -1
- package/demos/knowledge-editor/handbook/ops/retired.md +0 -9
- package/demos/knowledge-editor/handbook/ops/runbook.md +0 -14
- package/demos/knowledge-editor/handbook/payments/charges.md +0 -25
- package/demos/knowledge-editor/handbook/payments/pricing-tiers.draft.md +0 -7
- package/demos/knowledge-editor/handbook/payments/refunds/policy.md +0 -12
- package/demos/knowledge-editor/support.norn +0 -10
- package/demos/mcp-ticket-testing/README.md +0 -114
- package/demos/mcp-ticket-testing/agents.nornagent +0 -77
- package/demos/mcp-ticket-testing/contracts/test-run.schema.json +0 -31
- package/demos/mcp-ticket-testing/expectations/proj-142.md +0 -12
- package/demos/mcp-ticket-testing/fixtures/proj-142.json +0 -13
- package/demos/mcp-ticket-testing/prompts/backend-tester.md +0 -12
- package/demos/mcp-ticket-testing/prompts/frontend-tester.md +0 -14
- package/demos/mcp-ticket-testing/prompts/reporter.md +0 -10
- package/demos/mcp-ticket-testing/servers/browser-server.js +0 -133
- package/demos/mcp-ticket-testing/servers/house-server.js +0 -125
- package/demos/mcp-ticket-testing/tickets.norn +0 -32
- package/demos/nornenv-region-refactor/README.md +0 -64
- package/demos/nornenv-showcase/README.md +0 -62
- package/demos/nornenv-showcase/norn.config.json +0 -16
- package/demos/nornenv-showcase/showcase.norn +0 -70
- package/demos/nornenv-showcase/showcase.nornapi +0 -26
- package/demos/nornenv-showcase/showcase.nornsql +0 -20
- package/demos/tests-showcase/01-single-requests.norn +0 -31
- package/demos/tests-showcase/02-sequences.norn +0 -54
- package/demos/tests-showcase/03-sidecars.norn +0 -42
- package/demos/tests-showcase/04-api-plus-sql.norn +0 -27
- package/demos/tests-showcase/db/testDb.nornsql +0 -12
- package/demos/tests-showcase/demo-api.nornapi +0 -17
- package/demos/tests-showcase/norn.config.json +0 -16
- package/playground/ai.norn +0 -15
- package/playground/ai_orchastration.nornagent +0 -34
- package/playground/knowedge_base/ACDC/testing_notes_new_clients.md +0 -11
- package/playground/knowedge_base/nexus_system_prompt.md +0 -1
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
# The knowledge editor
|
|
2
|
-
|
|
3
|
-
A knowledge base small enough to read in a minute, written to show every state the Stage 4
|
|
4
|
-
editor can be in. Open the files in [`handbook/`](./handbook) and the ordinary Markdown editor
|
|
5
|
-
gains chips, hovers, folded metadata, dimmed exclusions and underlined entities — no custom
|
|
6
|
-
editor, no preview pane, no new file type.
|
|
7
|
-
|
|
8
|
-
Membership comes from [`agents.nornagent`](./agents.nornagent) and nothing else:
|
|
9
|
-
|
|
10
|
-
```nornagent
|
|
11
|
-
knowledge Handbook
|
|
12
|
-
source ./handbook
|
|
13
|
-
exclude **/*.draft.md
|
|
14
|
-
end knowledge
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
The folder is called `handbook`, not `knowledge`. That is the point — a path convention never
|
|
18
|
-
activates the editor, and a frontmatter marker never activates it either. The declaration does.
|
|
19
|
-
|
|
20
|
-
## What each file demonstrates
|
|
21
|
-
|
|
22
|
-
| File | What to look at |
|
|
23
|
-
|---|---|
|
|
24
|
-
| `handbook/payments/charges.md` | H1 and H2 treatment, a multi-line `norn:meta` block folded behind its marker, a single-line one, an excluded section, and underlined entities that cross into `api.nornapi` and `.nornenv` |
|
|
25
|
-
| `handbook/payments/refunds/policy.md` | A sub-domain. `payments/refunds` is its own domain, shown separately from `payments` — loading the parent never loads the child |
|
|
26
|
-
| `handbook/ops/runbook.md` | No H1, so document feedback moves to a CodeLens at the top instead of chips on a title line |
|
|
27
|
-
| `handbook/ops/retired.md` | A whole-file exclusion: the document dims, stays editable, and raises no error |
|
|
28
|
-
| `handbook/ops/flow.svg` | A declared member whose extension produces no topics — inside the base, no editor surface |
|
|
29
|
-
| `handbook/payments/pricing-tiers.draft.md` | Excluded by the declaration's glob, so it is plain Markdown again |
|
|
30
|
-
|
|
31
|
-
## See the raw text
|
|
32
|
-
|
|
33
|
-
**Norn: Knowledge: Toggle Markup** puts the `##` markers and the `norn:meta` blocks back on screen
|
|
34
|
-
with the chips still in place. It is also how you check the surface still works without the
|
|
35
|
-
unsupported style injection that hides them.
|
|
36
|
-
|
|
37
|
-
## Try the authoring actions
|
|
38
|
-
|
|
39
|
-
On any topic heading, the lightbulb offers:
|
|
40
|
-
|
|
41
|
-
- **Change topic type…** — writes `type:` into the adjacent metadata block, creating the block if
|
|
42
|
-
there isn't one. The topic hash does not move, because metadata is stripped before hashing.
|
|
43
|
-
- **Add/Edit use_when…** — the sentence the router reads when no exact entity matches.
|
|
44
|
-
- **Exclude topic** / **Include topic** — the marker is placed below any metadata block, so the
|
|
45
|
-
metadata stays bound to its heading.
|
|
46
|
-
|
|
47
|
-
Inside a `norn:meta` block, completions offer `type` and `use_when`, and `task`/`reference`/
|
|
48
|
-
`concept` after `type:`.
|
|
49
|
-
|
|
50
|
-
## Recorded use
|
|
51
|
-
|
|
52
|
-
Every count in the editor is raw co-occurrence read back from Stage 3 run telemetry: these runs
|
|
53
|
-
loaded this text. Norn does not claim the text changed their outcome — that question belongs to
|
|
54
|
-
Stage 8. Until you run something, the honest state is what you'll see:
|
|
55
|
-
|
|
56
|
-
> No knowledge runs recorded yet.
|
|
57
|
-
|
|
58
|
-
To fill it in, point the `Support` model at a provider you have keys for and run the sequence:
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
node ./dist/cli.js demos/knowledge-editor/support.norn --env prelive
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Then reopen `handbook/payments/charges.md`: the title line gains a pill —
|
|
65
|
-
`Loaded by 1 run (1 succeeded · 0 failed)` — and each topic hover shows its own invocations and,
|
|
66
|
-
if you have since edited a topic, its earlier versions in a separate row.
|
|
67
|
-
|
|
68
|
-
## Inspect the same base from the CLI
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
node ./dist/cli.js knowledge ls --explain demos/knowledge-editor/agents.nornagent
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
Stage 4 adds no CLI command: the editor is a VS Code surface over the same index.
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
model Support
|
|
2
|
-
provider openai
|
|
3
|
-
name gpt-4.1-mini
|
|
4
|
-
end model
|
|
5
|
-
|
|
6
|
-
knowledge Handbook
|
|
7
|
-
source ./handbook
|
|
8
|
-
exclude **/*.draft.md
|
|
9
|
-
end knowledge
|
|
10
|
-
|
|
11
|
-
agent SupportEngineer
|
|
12
|
-
model Support
|
|
13
|
-
knowledge Handbook
|
|
14
|
-
describe "Answer payment support questions from the handbook."
|
|
15
|
-
system "You are a payments support engineer. Answer only from the handbook you were given."
|
|
16
|
-
end agent
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="40"><text x="4" y="24" font-size="14">charge flow</text></svg>
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
<!-- norn:exclude -->
|
|
2
|
-
|
|
3
|
-
# Retired: the old settlement runbook
|
|
4
|
-
|
|
5
|
-
Excluded as a whole file, so the entire document dims. It is still ordinary Markdown you can
|
|
6
|
-
read and edit — it simply contributes nothing to any run.
|
|
7
|
-
|
|
8
|
-
## Nightly settlement
|
|
9
|
-
We stopped running nightly settlement when the gateway moved to continuous capture.
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<!-- A file with no H1: the document feedback moves to a CodeLens at the top and the source
|
|
2
|
-
stays untouched. -->
|
|
3
|
-
|
|
4
|
-
## Gateway timeouts
|
|
5
|
-
<!-- norn:meta
|
|
6
|
-
type: task
|
|
7
|
-
use_when: the payment gateway is timing out
|
|
8
|
-
-->
|
|
9
|
-
1. Check the gateway dashboard before touching anything.
|
|
10
|
-
2. Retry once. A second timeout is an incident, not a retry.
|
|
11
|
-
3. HTTP status 504 from `payment-svc` means the gateway, not us.
|
|
12
|
-
|
|
13
|
-
## Who to wake
|
|
14
|
-
The on-call rota lives in the team handbook. Wake the payments on-call, not the platform one.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Charges
|
|
2
|
-
|
|
3
|
-
The charge lifecycle, written for a support engineer who has the customer on the line.
|
|
4
|
-
|
|
5
|
-
## Create a charge
|
|
6
|
-
<!-- norn:meta
|
|
7
|
-
type: task
|
|
8
|
-
use_when: creating a charge, or retrying one that failed
|
|
9
|
-
-->
|
|
10
|
-
1. Send `POST /v1/charges` with the amount in minor units.
|
|
11
|
-
2. Always send the header `Idempotency-Key`. A retry without one can charge twice.
|
|
12
|
-
3. A success is HTTP status 201 and a charge id in the body.
|
|
13
|
-
|
|
14
|
-
The service `payment-svc` owns this endpoint in the `prelive` environment, and the key comes
|
|
15
|
-
from {{$env.IDEMPOTENCY_KEY}}.
|
|
16
|
-
|
|
17
|
-
## What CARD_DECLINED means
|
|
18
|
-
<!-- norn:meta type: reference -->
|
|
19
|
-
The error code "CARD_DECLINED" is the issuer's answer, not ours. Nothing we retry will change
|
|
20
|
-
it inside the same hour, so the customer needs a different card or their bank.
|
|
21
|
-
|
|
22
|
-
## Legacy charge lookup
|
|
23
|
-
<!-- norn:exclude -->
|
|
24
|
-
The endpoint `GET /v1/charges/legacy` was retired in 2024. Kept for archaeology; excluded from
|
|
25
|
-
the knowledge base so no agent quotes it back to a customer.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Refunds
|
|
2
|
-
|
|
3
|
-
## When we refund without asking
|
|
4
|
-
<!-- norn:meta
|
|
5
|
-
type: reference
|
|
6
|
-
use_when: deciding whether a refund needs approval
|
|
7
|
-
-->
|
|
8
|
-
Under £50, refund it. The endpoint is `GET /v1/refunds/{id}` to check state first, and the
|
|
9
|
-
service is `payment-svc`.
|
|
10
|
-
|
|
11
|
-
## Partial refunds
|
|
12
|
-
Split the amount across the original charge. A partial refund still returns HTTP status 200.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
[CreateCharge]
|
|
2
|
-
POST {{BASE_URL}}/v1/charges
|
|
3
|
-
content-type: application/json
|
|
4
|
-
Idempotency-Key: {{$env.IDEMPOTENCY_KEY}}
|
|
5
|
-
|
|
6
|
-
{"amount": 1200, "currency": "gbp"}
|
|
7
|
-
|
|
8
|
-
sequence AnswerASupportQuestion
|
|
9
|
-
run SupportEngineer "A charge came back CARD_DECLINED. What should the customer do?"
|
|
10
|
-
end sequence
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
# Ticket testing with declared MCP servers
|
|
2
|
-
|
|
3
|
-
An agent graph that **does** something: a ticket arrives, one agent tests the product through a
|
|
4
|
-
browser, another tests it through the team's own API, a third writes the results up, and a
|
|
5
|
-
`judge` rules on whether the expected test cases were actually covered.
|
|
6
|
-
|
|
7
|
-
Everything it connects to is declared in [`agents.nornagent`](./agents.nornagent), beside the
|
|
8
|
-
model. There is **no `norn.config.json` in this folder** — the servers, their transports, and
|
|
9
|
-
their session lifetimes are all in the file that uses them.
|
|
10
|
-
|
|
11
|
-
## What to look at
|
|
12
|
-
|
|
13
|
-
```nornagent
|
|
14
|
-
mcp Browser
|
|
15
|
-
transport stdio
|
|
16
|
-
command node ./servers/browser-server.js
|
|
17
|
-
session agent
|
|
18
|
-
end mcp
|
|
19
|
-
|
|
20
|
-
agent FrontendTester
|
|
21
|
-
mcp Browser # the run of the browser
|
|
22
|
-
tools House.getFixtureUser, House.resetTenant # only what it needs from ours
|
|
23
|
-
end agent
|
|
24
|
-
|
|
25
|
-
agent BackendTester
|
|
26
|
-
tools House.callEndpoint, House.readAuditLog, House.queryDb
|
|
27
|
-
end agent
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
Three things this shows, and they are the point of the whole slice:
|
|
31
|
-
|
|
32
|
-
- **Two agents, one custom server, different tools each.** `BackendTester` gets three named
|
|
33
|
-
tools from `House` and cannot touch the browser at all. The access boundary is visible per
|
|
34
|
-
agent, in one file, in git.
|
|
35
|
-
- **`mcp Browser` and `tools House.callEndpoint` are visibly different statements.** Dropping
|
|
36
|
-
four characters must not silently widen access from one tool to twenty, so there is no
|
|
37
|
-
short form that means "all of it".
|
|
38
|
-
- **`session agent` on the browser.** Each agent invocation gets its own browser, closed when
|
|
39
|
-
that hop ends. The default, `session run`, shares one browser across every agent in the
|
|
40
|
-
sequence — right for a continuous flow, and wrong the moment a second agent is granted the
|
|
41
|
-
same server: it would inherit the first one's login and could pass a test it never
|
|
42
|
-
authenticated for, which is a false green and worse than a false red. Only `FrontendTester`
|
|
43
|
-
reaches the browser in this graph, so this is the lifetime worth authoring by default rather
|
|
44
|
-
than a bleed this demo would actually suffer.
|
|
45
|
-
|
|
46
|
-
## Run it
|
|
47
|
-
|
|
48
|
-
The servers are deterministic and ship with the demo, so nothing needs installing or starting.
|
|
49
|
-
Prove they are reachable **without spending anything on a model**:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npm run compile
|
|
53
|
-
node ./dist/cli.js demos/mcp-ticket-testing/tickets.norn --sequence ServersUp
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
That resolves the same aliases the agents use, so a failure points at the server rather than at
|
|
57
|
-
an agent. When you are building a server of your own, this is the loop to develop against.
|
|
58
|
-
|
|
59
|
-
Then the real thing, which does need a model:
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
export OPENAI_API="your-key"
|
|
63
|
-
node ./dist/cli.js demos/mcp-ticket-testing/tickets.norn --sequence TicketPROJ142
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
The variable name is not a convention Norn assumes — it is written in the model block as
|
|
67
|
-
`apiKey {{$env.OPENAI_API}}`, so any name works as long as the file and the environment
|
|
68
|
-
agree. A `.nornenv` value wins over the process environment; add `--env <name>` to select one.
|
|
69
|
-
|
|
70
|
-
## The browser here is a stand-in
|
|
71
|
-
|
|
72
|
-
[`servers/browser-server.js`](./servers/browser-server.js) is a deterministic scripted site, not
|
|
73
|
-
a real browser, so this demo runs anywhere `node` does — no browser download, no server to keep
|
|
74
|
-
alive. Its declaration is the **same shape** a real Playwright MCP block takes, and both real
|
|
75
|
-
forms are written out in the comments at the top of
|
|
76
|
-
[`agents.nornagent`](./agents.nornagent):
|
|
77
|
-
|
|
78
|
-
```nornagent
|
|
79
|
-
mcp Browser
|
|
80
|
-
transport stdio
|
|
81
|
-
command npx playwright-mcp --headless --isolated
|
|
82
|
-
session agent
|
|
83
|
-
end mcp
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
For CI with more than one sequence, run Playwright MCP as a service and dial it instead — that
|
|
87
|
-
is what `transport http` is for. Norn never starts an http server; it only connects to one, and
|
|
88
|
-
a refused connection fails before any model spend, naming the URL it tried.
|
|
89
|
-
|
|
90
|
-
## The judge, and the CI story
|
|
91
|
-
|
|
92
|
-
The last line of `TicketPROJ142` is the part that makes this a test rather than a demo:
|
|
93
|
-
|
|
94
|
-
```norn
|
|
95
|
-
judge report with Reviewer expects file expectations/proj-142.md
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Every expectation in [`expectations/proj-142.md`](./expectations/proj-142.md) must be met;
|
|
99
|
-
testing *more* than the list is not a failure. The judge is never asked for an overall verdict —
|
|
100
|
-
it rules on each expectation and quotes its evidence, and Norn computes the result.
|
|
101
|
-
|
|
102
|
-
Note the comment lines in that file. A line of prose left uncommented there **is** an
|
|
103
|
-
expectation, which is both Norn's comment rule and what makes Markdown headings work.
|
|
104
|
-
|
|
105
|
-
A live run records itself under `.norn-cache/runs/`. Replay it with no servers running at all:
|
|
106
|
-
|
|
107
|
-
```bash
|
|
108
|
-
node ./dist/cli.js replay .norn-cache/runs/<recording>.json
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
That is the CI answer for any expensive server: run it live where the servers exist, and gate
|
|
112
|
-
pull requests on the replay — no browser, no API, no model, and the graph and contracts still
|
|
113
|
-
checked. The recording keeps the tool names each granted server advertised, so a server that
|
|
114
|
-
quietly gains or loses a tool is reported the next time a live run sees it.
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Ticket testing — declared MCP servers, granted per agent.
|
|
2
|
-
#
|
|
3
|
-
# Every server this graph uses is declared here, beside the model, so the file says exactly
|
|
4
|
-
# what it connects to. Nothing is left to a norn.config.json the reader cannot see.
|
|
5
|
-
|
|
6
|
-
model Workbench
|
|
7
|
-
provider openai
|
|
8
|
-
name gpt-4o
|
|
9
|
-
apiKey {{$env.OPENAI_API}}
|
|
10
|
-
end model
|
|
11
|
-
|
|
12
|
-
# The browser. This demo ships a deterministic stand-in so it runs anywhere `node` does —
|
|
13
|
-
# the declaration is the same shape a real Playwright MCP server takes:
|
|
14
|
-
#
|
|
15
|
-
# mcp Browser
|
|
16
|
-
# transport stdio
|
|
17
|
-
# command npx playwright-mcp --headless --isolated
|
|
18
|
-
# session agent
|
|
19
|
-
# end mcp
|
|
20
|
-
#
|
|
21
|
-
# ...or, for a long-lived server you started yourself (a CI job with many sequences):
|
|
22
|
-
#
|
|
23
|
-
# mcp Browser
|
|
24
|
-
# transport http
|
|
25
|
-
# url {{$env.BROWSER_MCP_URL}} # e.g. http://localhost:8931/mcp
|
|
26
|
-
# timeout 120000 # browser work is slow; the default is not for this
|
|
27
|
-
# end mcp
|
|
28
|
-
mcp Browser
|
|
29
|
-
transport stdio
|
|
30
|
-
command node ./servers/browser-server.js
|
|
31
|
-
# One browser per agent invocation, closed when that hop ends. The default, `session run`,
|
|
32
|
-
# would share one browser across every agent in the sequence — which is what you want for a
|
|
33
|
-
# continuous flow, and what you do not want the moment a second agent is granted this server:
|
|
34
|
-
# it would inherit the first one's login and could pass a test it never authenticated for.
|
|
35
|
-
# Only FrontendTester reaches the browser here, so this is the safe default to author rather
|
|
36
|
-
# than a bleed this graph would actually suffer.
|
|
37
|
-
session agent
|
|
38
|
-
end mcp
|
|
39
|
-
|
|
40
|
-
# The team's own server: fixtures, endpoints, the audit log, the database.
|
|
41
|
-
mcp House
|
|
42
|
-
transport stdio
|
|
43
|
-
command node ./servers/house-server.js
|
|
44
|
-
end mcp
|
|
45
|
-
|
|
46
|
-
agent FrontendTester
|
|
47
|
-
model Workbench
|
|
48
|
-
# The run of the browser: this agent drives the site, and which tools that needs is not
|
|
49
|
-
# something the author should have to enumerate.
|
|
50
|
-
mcp Browser
|
|
51
|
-
# From our own server it gets only what it needs to set up — and cannot reach the
|
|
52
|
-
# database or the audit log at all.
|
|
53
|
-
tools House.getFixtureUser, House.resetTenant
|
|
54
|
-
returns contracts/test-run.schema.json
|
|
55
|
-
system file prompts/frontend-tester.md
|
|
56
|
-
end agent
|
|
57
|
-
|
|
58
|
-
agent BackendTester
|
|
59
|
-
model Workbench
|
|
60
|
-
# The same server, narrowed differently. Both grants are visible per agent, in one file.
|
|
61
|
-
tools House.callEndpoint, House.readAuditLog, House.queryDb
|
|
62
|
-
returns contracts/test-run.schema.json
|
|
63
|
-
system file prompts/backend-tester.md
|
|
64
|
-
end agent
|
|
65
|
-
|
|
66
|
-
agent Reporter
|
|
67
|
-
model Workbench
|
|
68
|
-
system file prompts/reporter.md
|
|
69
|
-
end agent
|
|
70
|
-
|
|
71
|
-
agent Reviewer
|
|
72
|
-
model Workbench
|
|
73
|
-
system "
|
|
74
|
-
You are reviewing a QA report against a checklist of expected test cases.
|
|
75
|
-
Rule on each expectation independently and quote the report as evidence.
|
|
76
|
-
"
|
|
77
|
-
end agent
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"type": "object",
|
|
3
|
-
"additionalProperties": false,
|
|
4
|
-
"properties": {
|
|
5
|
-
"surface": {
|
|
6
|
-
"type": "string",
|
|
7
|
-
"description": "Which side of the product was exercised: frontend or backend."
|
|
8
|
-
},
|
|
9
|
-
"cases": {
|
|
10
|
-
"type": "array",
|
|
11
|
-
"description": "One entry per test case actually carried out.",
|
|
12
|
-
"items": {
|
|
13
|
-
"type": "object",
|
|
14
|
-
"additionalProperties": false,
|
|
15
|
-
"properties": {
|
|
16
|
-
"name": { "type": "string" },
|
|
17
|
-
"steps": { "type": "array", "items": { "type": "string" } },
|
|
18
|
-
"outcome": { "type": "string", "enum": ["passed", "failed"] },
|
|
19
|
-
"evidence": { "type": "string" }
|
|
20
|
-
},
|
|
21
|
-
"required": ["name", "steps", "outcome", "evidence"]
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
"findings": {
|
|
25
|
-
"type": "array",
|
|
26
|
-
"description": "Anything that looked wrong. Empty when nothing did.",
|
|
27
|
-
"items": { "type": "string" }
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
"required": ["surface", "cases", "findings"]
|
|
31
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
# Expected test cases for PROJ-142
|
|
2
|
-
|
|
3
|
-
# Each line below is one expectation. All must be met; testing more than this is not a
|
|
4
|
-
# failure. Lines starting with # are comments — prose left uncommented here would itself
|
|
5
|
-
# become an expectation, which is both Norn's comment rule and what makes Markdown work.
|
|
6
|
-
|
|
7
|
-
- A test covers that a signed-out visitor opening /orders is sent to the sign-in page
|
|
8
|
-
- A test covers signing in and seeing the orders list
|
|
9
|
-
- A test covers opening order A-1001 and reading its total
|
|
10
|
-
- A test covers GET /orders/A-1001 returning 200 with the order
|
|
11
|
-
- A test covers GET /orders/A-9999 returning 404
|
|
12
|
-
- A test confirms endpoint calls reach the audit log
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"key": "PROJ-142",
|
|
3
|
-
"title": "Order detail page and order lookup endpoint",
|
|
4
|
-
"description": "A signed-in customer can open an order from the orders list and see its total. The API must return the order for a valid id and 404 for an unknown one.",
|
|
5
|
-
"acceptanceCriteria": [
|
|
6
|
-
"A signed-out visitor who opens /orders is sent to the sign-in page",
|
|
7
|
-
"After signing in, the orders list shows the customer's orders",
|
|
8
|
-
"Opening order A-1001 shows its total",
|
|
9
|
-
"GET /orders/A-1001 returns 200 with the order",
|
|
10
|
-
"GET /orders/A-9999 returns 404",
|
|
11
|
-
"Every endpoint call is written to the audit log"
|
|
12
|
-
]
|
|
13
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
You test the backend of a web product through its team's own MCP server.
|
|
2
|
-
|
|
3
|
-
Work only from the ticket you are given. For each acceptance criterion that is about an API,
|
|
4
|
-
a database row, or an audit entry, exercise it with the tools you have and record what came
|
|
5
|
-
back — never what you assume would come back.
|
|
6
|
-
|
|
7
|
-
`callEndpoint` calls the API. `queryDb` reads seeded tables. `readAuditLog` returns what this
|
|
8
|
-
run wrote. You have no browser, and you must not claim to have used one.
|
|
9
|
-
|
|
10
|
-
Return the required JSON only: one case per criterion you exercised, the steps you took, the
|
|
11
|
-
outcome, and a short quote of the evidence you saw. Put anything that looked wrong in
|
|
12
|
-
`findings`. Set `surface` to "backend".
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
You test the front end of a web product by driving a browser through an MCP server.
|
|
2
|
-
|
|
3
|
-
Work only from the ticket you are given. For each acceptance criterion that is about what a
|
|
4
|
-
person sees or does in the browser, carry out the steps yourself with the browser tools and
|
|
5
|
-
record what actually happened — never what you assume would happen.
|
|
6
|
-
|
|
7
|
-
The browser starts signed out. `getFixtureUser` gives you the test account; `resetTenant`
|
|
8
|
-
puts the tenant back to its seeded state before you start.
|
|
9
|
-
|
|
10
|
-
Snapshots are text. Read the elements listed in a snapshot rather than guessing at a page.
|
|
11
|
-
|
|
12
|
-
Return the required JSON only: one case per criterion you exercised, the steps you took, the
|
|
13
|
-
outcome, and a short quote of the evidence you saw. Put anything that looked wrong in
|
|
14
|
-
`findings`. Set `surface` to "frontend".
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
You turn two test runs into one short QA report for a ticket.
|
|
2
|
-
|
|
3
|
-
You are given the frontend run and the backend run. Write, in plain prose:
|
|
4
|
-
|
|
5
|
-
1. Which test cases were carried out, naming each one.
|
|
6
|
-
2. What the evidence was for each.
|
|
7
|
-
3. Any findings, or a single sentence saying there were none.
|
|
8
|
-
|
|
9
|
-
Report only what the runs contain. Do not invent a case that nobody ran, and do not soften a
|
|
10
|
-
failure. Someone will check this report against the list of expected test cases.
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A deterministic stand-in for Playwright MCP.
|
|
5
|
-
*
|
|
6
|
-
* The `mcp Browser` block in `agents.nornagent` is the *same shape* a real Playwright MCP
|
|
7
|
-
* declaration takes — see the commented block there — so this demo runs anywhere `node` does,
|
|
8
|
-
* with no browser download, while showing exactly what granting a browser looks like.
|
|
9
|
-
*
|
|
10
|
-
* It answers from a tiny scripted site, and its snapshots are text, which is what a browser
|
|
11
|
-
* agent should be working from anyway: image content never reaches a model's prompt.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
|
|
15
|
-
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
16
|
-
const { ListToolsRequestSchema, CallToolRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
|
|
17
|
-
|
|
18
|
-
const PAGES = {
|
|
19
|
-
'/login': {
|
|
20
|
-
title: 'Sign in',
|
|
21
|
-
elements: ['textbox "Email"', 'textbox "Password"', 'button "Sign in"']
|
|
22
|
-
},
|
|
23
|
-
'/orders': {
|
|
24
|
-
title: 'Your orders',
|
|
25
|
-
elements: ['link "A-1001"', 'link "A-1002"', 'button "New order"']
|
|
26
|
-
},
|
|
27
|
-
'/orders/A-1001': {
|
|
28
|
-
title: 'Order A-1001',
|
|
29
|
-
elements: ['heading "Order A-1001"', 'text "Total: 42.50"', 'button "Cancel order"']
|
|
30
|
-
}
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const state = { url: '/login', signedIn: false, filled: {} };
|
|
34
|
-
|
|
35
|
-
const TOOLS = [
|
|
36
|
-
{
|
|
37
|
-
name: 'browser_navigate',
|
|
38
|
-
description: 'Navigate to a path on the site under test.',
|
|
39
|
-
inputSchema: {
|
|
40
|
-
type: 'object',
|
|
41
|
-
additionalProperties: false,
|
|
42
|
-
properties: { path: { type: 'string', description: 'Path to open, e.g. /orders.' } },
|
|
43
|
-
required: ['path']
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
name: 'browser_fill',
|
|
48
|
-
description: 'Fill a named field on the current page.',
|
|
49
|
-
inputSchema: {
|
|
50
|
-
type: 'object',
|
|
51
|
-
additionalProperties: false,
|
|
52
|
-
properties: {
|
|
53
|
-
field: { type: 'string', description: 'Field label, e.g. Email.' },
|
|
54
|
-
value: { type: 'string', description: 'Value to type.' }
|
|
55
|
-
},
|
|
56
|
-
required: ['field', 'value']
|
|
57
|
-
}
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: 'browser_click',
|
|
61
|
-
description: 'Click a named control on the current page.',
|
|
62
|
-
inputSchema: {
|
|
63
|
-
type: 'object',
|
|
64
|
-
additionalProperties: false,
|
|
65
|
-
properties: { control: { type: 'string', description: 'Control label, e.g. Sign in.' } },
|
|
66
|
-
required: ['control']
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
name: 'browser_snapshot',
|
|
71
|
-
description: 'Return a text accessibility snapshot of the current page.',
|
|
72
|
-
inputSchema: { type: 'object', additionalProperties: false, properties: {} }
|
|
73
|
-
}
|
|
74
|
-
];
|
|
75
|
-
|
|
76
|
-
function structured(value) {
|
|
77
|
-
return { content: [{ type: 'text', text: JSON.stringify(value) }], structuredContent: value };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function snapshot() {
|
|
81
|
-
const page = PAGES[state.url] || { title: 'Not found', elements: [] };
|
|
82
|
-
return {
|
|
83
|
-
url: state.url,
|
|
84
|
-
title: page.title,
|
|
85
|
-
signedIn: state.signedIn,
|
|
86
|
-
elements: page.elements
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function main() {
|
|
91
|
-
const server = new Server(
|
|
92
|
-
{ name: 'norn-demo-browser', version: '1.0.0' },
|
|
93
|
-
{ capabilities: { tools: {} } }
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
97
|
-
|
|
98
|
-
server.setRequestHandler(CallToolRequestSchema, async request => {
|
|
99
|
-
const args = request.params.arguments || {};
|
|
100
|
-
switch (request.params.name) {
|
|
101
|
-
case 'browser_navigate':
|
|
102
|
-
state.url = String(args.path || '/');
|
|
103
|
-
if (state.url !== '/login' && !state.signedIn) {
|
|
104
|
-
// The whole point of a session: an unauthenticated visit lands on login.
|
|
105
|
-
state.url = '/login';
|
|
106
|
-
}
|
|
107
|
-
return structured(snapshot());
|
|
108
|
-
case 'browser_fill':
|
|
109
|
-
state.filled[String(args.field)] = String(args.value);
|
|
110
|
-
return structured({ filled: args.field, ...snapshot() });
|
|
111
|
-
case 'browser_click':
|
|
112
|
-
if (String(args.control) === 'Sign in' && state.filled.Email && state.filled.Password) {
|
|
113
|
-
state.signedIn = true;
|
|
114
|
-
state.url = '/orders';
|
|
115
|
-
}
|
|
116
|
-
return structured(snapshot());
|
|
117
|
-
case 'browser_snapshot':
|
|
118
|
-
return structured(snapshot());
|
|
119
|
-
default:
|
|
120
|
-
return {
|
|
121
|
-
isError: true,
|
|
122
|
-
content: [{ type: 'text', text: `Unknown tool: ${request.params.name}` }]
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
await server.connect(new StdioServerTransport());
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
main().catch(error => {
|
|
131
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
132
|
-
process.exit(1);
|
|
133
|
-
});
|