mmt-testlight 0.4.4 → 0.4.5
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 +1 -1
- package/dist/cli.js +26648 -9539
- package/dist/guides/agent-workflow.md +75 -0
- package/dist/guides/general.md +64 -0
- package/dist/guides/generate-api.md +221 -0
- package/dist/guides/generate-doc.md +103 -0
- package/dist/guides/generate-env.md +147 -0
- package/dist/guides/generate-loadtest.md +55 -0
- package/dist/guides/generate-suite.md +167 -0
- package/dist/guides/generate-test-skill.md +60 -0
- package/dist/guides/generate-test.md +335 -0
- package/dist/guides/generate.md +60 -0
- package/dist/guides/golden-smoke.md +52 -0
- package/dist/guides/min/api.md +29 -0
- package/dist/guides/min/constraints.md +9 -0
- package/dist/guides/min/doc.md +17 -0
- package/dist/guides/min/env.md +26 -0
- package/dist/guides/min/loadtest.md +17 -0
- package/dist/guides/min/overview.md +25 -0
- package/dist/guides/min/suite.md +18 -0
- package/dist/guides/min/test.md +42 -0
- package/dist/guides/min/workflow.md +16 -0
- package/dist/guides/offline-agent.md +46 -0
- package/esbuild.mjs +25 -0
- package/package.json +2 -1
- package/src/aiDocs.ts +88 -0
- package/src/cli.ts +283 -7
- package/src/pathNormalize.cjs +16 -0
- package/src/pathNormalize.test.ts +8 -0
- package/src/selfUpdate.test.ts +79 -0
- package/src/selfUpdate.ts +471 -0
- package/src/validateMmt.ts +45 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Constraints (min)
|
|
2
|
+
|
|
3
|
+
- Valid YAML only; first non-comment line is `type: …`
|
|
4
|
+
- No YAML comments (`#`) in generated `.mmt`
|
|
5
|
+
- Snake_case tokens: `e:`, `i:`, `r:`, `c:`
|
|
6
|
+
- New API tests: scaffold first, then minimal edits
|
|
7
|
+
- Modify: patch only; validate after every edit
|
|
8
|
+
- Never invent Postman/`pm.` syntax
|
|
9
|
+
- Prefer smoke coverage unless asked for more
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generate `type: doc` (min)
|
|
2
|
+
|
|
3
|
+
```yaml
|
|
4
|
+
type: doc
|
|
5
|
+
title: My API docs
|
|
6
|
+
sources:
|
|
7
|
+
- ./apis
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Essentials
|
|
11
|
+
|
|
12
|
+
- First line: `type: doc`
|
|
13
|
+
- `sources` lists API `.mmt` files or directories
|
|
14
|
+
- Optional: `description`, `logo`, `services`
|
|
15
|
+
- Build with `testlight doc <file.mmt>`
|
|
16
|
+
|
|
17
|
+
Request `pack: full` for multi-service layouts.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Generate `type: env` (min)
|
|
2
|
+
|
|
3
|
+
```yaml
|
|
4
|
+
type: env
|
|
5
|
+
variables:
|
|
6
|
+
api_url:
|
|
7
|
+
dev: http://localhost:8080
|
|
8
|
+
prod: https://test.mmt.dev
|
|
9
|
+
mode:
|
|
10
|
+
- debug
|
|
11
|
+
- info
|
|
12
|
+
presets:
|
|
13
|
+
runner:
|
|
14
|
+
dev:
|
|
15
|
+
api_url: http://localhost:8080
|
|
16
|
+
mode: debug
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Essentials
|
|
20
|
+
|
|
21
|
+
- First line: `type: env`
|
|
22
|
+
- `variables` required; each var is a map of choices or a list of allowed values
|
|
23
|
+
- `presets` optional; values must match variable choices
|
|
24
|
+
- APIs/tests consume via `e:api_url` / `<<e:api_url>>`
|
|
25
|
+
|
|
26
|
+
Request `pack: full` for multi-group presets and UI details.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Generate `type: loadtest` (min)
|
|
2
|
+
|
|
3
|
+
```yaml
|
|
4
|
+
type: loadtest
|
|
5
|
+
test: ./echo_test.mmt
|
|
6
|
+
threads: 10
|
|
7
|
+
repeat: 30s
|
|
8
|
+
rampup: 5s
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Essentials
|
|
12
|
+
|
|
13
|
+
- First line: `type: loadtest`
|
|
14
|
+
- Points at a `type: test` file
|
|
15
|
+
- `threads`, `repeat`, optional `rampup` / `duration`
|
|
16
|
+
|
|
17
|
+
Request `pack: full` for metrics and advanced timing.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Multimeter overview (min)
|
|
2
|
+
|
|
3
|
+
Prefer MCP tools when available. Offline: `testlight docs`, `testlight scaffold`, `testlight validate`.
|
|
4
|
+
|
|
5
|
+
## Types
|
|
6
|
+
|
|
7
|
+
- `api` — one endpoint
|
|
8
|
+
- `test` — call APIs/tests + assert
|
|
9
|
+
- `env` — variables/presets
|
|
10
|
+
- `suite` — group runnable files
|
|
11
|
+
- `doc` / `server` / `loadtest` / `report` — as named
|
|
12
|
+
|
|
13
|
+
## Tokens
|
|
14
|
+
|
|
15
|
+
- `e:name`, `i:name`, `r:uuid`, `c:epoch_ms`
|
|
16
|
+
- Embed with `<<e:api_url>>/path`
|
|
17
|
+
|
|
18
|
+
## Agent loop
|
|
19
|
+
|
|
20
|
+
1. New test from API → `scaffold_test` / `testlight scaffold test --from`
|
|
21
|
+
2. Patch only on modify
|
|
22
|
+
3. `validate` before finishing
|
|
23
|
+
4. Never web-search Multimeter syntax
|
|
24
|
+
|
|
25
|
+
Request `pack: full` only when min docs are not enough.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Generate `type: suite` (min)
|
|
2
|
+
|
|
3
|
+
```yaml
|
|
4
|
+
type: suite
|
|
5
|
+
title: Smoke suite
|
|
6
|
+
items:
|
|
7
|
+
- ./tests/login-smoke.mmt
|
|
8
|
+
- ./tests/echo-smoke.mmt
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Essentials
|
|
12
|
+
|
|
13
|
+
- First line: `type: suite`
|
|
14
|
+
- `items` lists `.mmt` paths (tests, apis, suites)
|
|
15
|
+
- Use `then` between groups for sequencing; same group runs in parallel
|
|
16
|
+
- Optional root-only: `servers`, `environment`, `export`
|
|
17
|
+
|
|
18
|
+
Request `pack: full` for nested suites, env export, and report options.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Generate `type: test` (min)
|
|
2
|
+
|
|
3
|
+
Prefer `scaffold_test` / `testlight scaffold test --from` for new API tests.
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
type: test
|
|
7
|
+
title: Login smoke
|
|
8
|
+
tags: [smoke]
|
|
9
|
+
import:
|
|
10
|
+
login: ../apis/login.mmt
|
|
11
|
+
inputs:
|
|
12
|
+
username: i:username
|
|
13
|
+
password: i:password
|
|
14
|
+
steps:
|
|
15
|
+
- call: login
|
|
16
|
+
id: iLogin
|
|
17
|
+
inputs:
|
|
18
|
+
username: i:username
|
|
19
|
+
password: i:password
|
|
20
|
+
expect:
|
|
21
|
+
status: 200
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Essentials
|
|
25
|
+
|
|
26
|
+
- First line: `type: test`
|
|
27
|
+
- Use `steps` **or** `stages`, not both
|
|
28
|
+
- Every `call` needs unique `id`
|
|
29
|
+
- Import aliases before calling them
|
|
30
|
+
- Assert with `expect` on call, or `assert` / `check` steps
|
|
31
|
+
- No YAML comments (`#`)
|
|
32
|
+
- Tokens: `e:`, `i:`, `r:`, `c:` (snake_case)
|
|
33
|
+
|
|
34
|
+
## Common steps
|
|
35
|
+
|
|
36
|
+
- `call: <alias>` — invoke imported API/test
|
|
37
|
+
- `http: <url>` — inline HTTP
|
|
38
|
+
- `assert: ${id.status} == 200`
|
|
39
|
+
- `check:` map of path → expected
|
|
40
|
+
- `js: |` — small helper script when needed
|
|
41
|
+
|
|
42
|
+
Request `pack: full` for stages, loops, CSV, cache, and advanced expect ops.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Agent workflow (min)
|
|
2
|
+
|
|
3
|
+
MCP-first when Multimeter MCP is registered.
|
|
4
|
+
|
|
5
|
+
| Intent | First tool | Then |
|
|
6
|
+
|--------|------------|------|
|
|
7
|
+
| Test from API | `scaffold_test` | Write yaml → **`validate` → `format`** |
|
|
8
|
+
| Few-shot | `list_examples` | Mirror `goldenSmoke` |
|
|
9
|
+
| Inspect API | `api_card` | Prefer card over full file dump |
|
|
10
|
+
| Tighten asserts | `suggest_assertions` | Patch → **`validate` → `format`** |
|
|
11
|
+
| Modify `.mmt` | **patch only** | **`validate` → `format`** (no full rewrite) |
|
|
12
|
+
| Run | `run` | tool JSON; then suggest_assertions if asked |
|
|
13
|
+
|
|
14
|
+
Offline: `testlight docs` → `scaffold` → edit → `validate`.
|
|
15
|
+
|
|
16
|
+
Hard rules: no web search; no blank-page invent; modify = patch only unless user asks to rewrite.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Offline / no-MCP agent profile
|
|
2
|
+
|
|
3
|
+
Use this when Multimeter MCP is **not** registered, or the host cannot call MCP tools.
|
|
4
|
+
|
|
5
|
+
## Prefer MCP when available
|
|
6
|
+
|
|
7
|
+
If Multimeter MCP tools exist (`scaffold_test`, `validate`, `run`, …), use those first. See `agent-workflow.md`.
|
|
8
|
+
|
|
9
|
+
## Offline loop
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
testlight docs <topic> # local syntax (pack min by default)
|
|
13
|
+
testlight scaffold test --from <api.mmt> [-o path]
|
|
14
|
+
testlight suggest asserts --from <api.mmt> [--body-file resp.json]
|
|
15
|
+
# patch the file if needed (never rewrite whole file unless asked)
|
|
16
|
+
testlight validate <file.mmt>
|
|
17
|
+
testlight run <file.mmt> # only when asked to execute
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Few-shot: mirror `examples/ai/golden_smoke/` (see `docs/AI/golden-smoke.md`).
|
|
21
|
+
|
|
22
|
+
### Topics
|
|
23
|
+
|
|
24
|
+
`overview` | `workflow` | `test` | `api` | `suite` | `env` | `doc` | `loadtest` | `constraints`
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
testlight docs test
|
|
28
|
+
testlight docs api --pack full
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Hard rules
|
|
32
|
+
|
|
33
|
+
1. Do **not** open mmt.dev / GitHub / web search for Multimeter YAML syntax.
|
|
34
|
+
2. New tests from an API: **scaffold first** — do not invent blank YAML.
|
|
35
|
+
3. Modify: **patch only** — do not rewrite the whole file.
|
|
36
|
+
4. Always **validate** before claiming done (format when using MCP).
|
|
37
|
+
5. Modify: **patch only** unless the user asks to rewrite.
|
|
38
|
+
6. Set `MMT_GUIDES_DIR` if guides are not next to the CLI binary.
|
|
39
|
+
|
|
40
|
+
## Guides location
|
|
41
|
+
|
|
42
|
+
`testlight docs` reads from, in order:
|
|
43
|
+
|
|
44
|
+
1. `MMT_GUIDES_DIR`
|
|
45
|
+
2. Guides bundled next to the CLI (`guides/` beside `cli.js`)
|
|
46
|
+
3. Repo `docs/AI` when developing from source
|
package/esbuild.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import * as esbuild from 'esbuild';
|
|
2
|
+
import fs from 'fs';
|
|
2
3
|
import path from 'path';
|
|
3
4
|
import {fileURLToPath} from 'url';
|
|
4
5
|
|
|
5
6
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
7
|
const coreDir = path.resolve(__dirname, '..', 'core', 'src');
|
|
8
|
+
const guidesSrc = path.resolve(__dirname, '..', 'docs', 'AI');
|
|
9
|
+
const guidesOut = path.join(__dirname, 'dist', 'guides');
|
|
7
10
|
|
|
8
11
|
// Plugin to resolve 'mmt-core' and 'mmt-core/<subpath>' to local core sources
|
|
9
12
|
const mmtCorePlugin = {
|
|
@@ -21,6 +24,26 @@ const mmtCorePlugin = {
|
|
|
21
24
|
},
|
|
22
25
|
};
|
|
23
26
|
|
|
27
|
+
function copyMarkdownTree(from, to) {
|
|
28
|
+
fs.mkdirSync(to, {recursive: true});
|
|
29
|
+
for (const entry of fs.readdirSync(from, {withFileTypes: true})) {
|
|
30
|
+
const src = path.join(from, entry.name);
|
|
31
|
+
const dest = path.join(to, entry.name);
|
|
32
|
+
if (entry.isDirectory()) {
|
|
33
|
+
copyMarkdownTree(src, dest);
|
|
34
|
+
} else if (entry.name.endsWith('.md')) {
|
|
35
|
+
fs.copyFileSync(src, dest);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function copyGuides() {
|
|
41
|
+
fs.rmSync(guidesOut, {recursive: true, force: true});
|
|
42
|
+
if (fs.existsSync(guidesSrc)) {
|
|
43
|
+
copyMarkdownTree(guidesSrc, guidesOut);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
await esbuild.build({
|
|
25
48
|
entryPoints: ['src/cli.ts'],
|
|
26
49
|
bundle: true,
|
|
@@ -43,3 +66,5 @@ await esbuild.build({
|
|
|
43
66
|
'unsupported-dynamic-import': 'silent',
|
|
44
67
|
},
|
|
45
68
|
});
|
|
69
|
+
|
|
70
|
+
copyGuides();
|
package/package.json
CHANGED
package/src/aiDocs.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export type CliDocTopic =
|
|
5
|
+
'overview'|'workflow'|'test'|'api'|'loadtest'|'suite'|'env'|'doc'|'constraints'|'all'|'offline';
|
|
6
|
+
|
|
7
|
+
export type CliDocPack = 'min'|'full';
|
|
8
|
+
|
|
9
|
+
const FULL_TOPIC_FILES: Record<Exclude<CliDocTopic, 'all'|'offline'>, string[]> = {
|
|
10
|
+
overview: ['agent-workflow.md', 'general.md', 'generate.md'],
|
|
11
|
+
workflow: ['agent-workflow.md'],
|
|
12
|
+
test: ['generate-test.md'],
|
|
13
|
+
api: ['generate-api.md'],
|
|
14
|
+
loadtest: ['generate-loadtest.md'],
|
|
15
|
+
suite: ['generate-suite.md'],
|
|
16
|
+
env: ['generate-env.md'],
|
|
17
|
+
doc: ['generate-doc.md'],
|
|
18
|
+
constraints: ['generate-test-skill.md'],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const MIN_TOPIC_FILES: Record<Exclude<CliDocTopic, 'all'|'offline'>, string[]> = {
|
|
22
|
+
overview: ['min/overview.md'],
|
|
23
|
+
workflow: ['min/workflow.md'],
|
|
24
|
+
test: ['min/test.md'],
|
|
25
|
+
api: ['min/api.md'],
|
|
26
|
+
loadtest: ['min/loadtest.md'],
|
|
27
|
+
suite: ['min/suite.md'],
|
|
28
|
+
env: ['min/env.md'],
|
|
29
|
+
doc: ['min/doc.md'],
|
|
30
|
+
constraints: ['min/constraints.md'],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function resolveCliGuidesDir(): string {
|
|
34
|
+
if (process.env.MMT_GUIDES_DIR && fs.existsSync(process.env.MMT_GUIDES_DIR)) {
|
|
35
|
+
return process.env.MMT_GUIDES_DIR;
|
|
36
|
+
}
|
|
37
|
+
const besideCli = path.join(__dirname, 'guides');
|
|
38
|
+
if (fs.existsSync(besideCli)) {
|
|
39
|
+
return besideCli;
|
|
40
|
+
}
|
|
41
|
+
const repoGuides = path.resolve(__dirname, '..', '..', 'docs', 'AI');
|
|
42
|
+
if (fs.existsSync(repoGuides)) {
|
|
43
|
+
return repoGuides;
|
|
44
|
+
}
|
|
45
|
+
// mmtcli/dist → ../../docs/AI from repo; when installed as package:
|
|
46
|
+
const pkgGuides = path.resolve(__dirname, '..', 'docs', 'AI');
|
|
47
|
+
if (fs.existsSync(pkgGuides)) {
|
|
48
|
+
return pkgGuides;
|
|
49
|
+
}
|
|
50
|
+
return besideCli;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readGuide(guidesDir: string, fileName: string): string {
|
|
54
|
+
const fullPath = path.join(guidesDir, fileName);
|
|
55
|
+
if (!fs.existsSync(fullPath)) {
|
|
56
|
+
throw new Error(`Guide not found: ${fullPath}`);
|
|
57
|
+
}
|
|
58
|
+
return fs.readFileSync(fullPath, 'utf8');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function formatCliDocs(topic: CliDocTopic, pack: CliDocPack = 'min'): string {
|
|
62
|
+
const guidesDir = resolveCliGuidesDir();
|
|
63
|
+
if (topic === 'offline') {
|
|
64
|
+
return readGuide(guidesDir, 'offline-agent.md');
|
|
65
|
+
}
|
|
66
|
+
const table = pack === 'full' ? FULL_TOPIC_FILES : MIN_TOPIC_FILES;
|
|
67
|
+
const files = topic === 'all' ?
|
|
68
|
+
Array.from(new Set(Object.values(table).flat())) :
|
|
69
|
+
table[topic];
|
|
70
|
+
const parts = files.map(fileName => {
|
|
71
|
+
const body = readGuide(guidesDir, fileName);
|
|
72
|
+
return `<!-- ${fileName} -->\n${body.trim()}\n`;
|
|
73
|
+
});
|
|
74
|
+
return [
|
|
75
|
+
`# Multimeter docs — topic=${topic} pack=${pack}`,
|
|
76
|
+
`guidesDir: ${guidesDir}`,
|
|
77
|
+
'',
|
|
78
|
+
...parts,
|
|
79
|
+
].join('\n');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function listCliDocTopics(): string[] {
|
|
83
|
+
return [
|
|
84
|
+
...Object.keys(MIN_TOPIC_FILES),
|
|
85
|
+
'all',
|
|
86
|
+
'offline',
|
|
87
|
+
];
|
|
88
|
+
}
|