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.
@@ -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
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "mmt-testlight",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Multimeter CLI test runner",
5
+ "license": "Apache-2.0",
5
6
  "private": false,
6
7
  "bin": {
7
8
  "testlight": "dist/cli.js",
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
+ }