asljs-rq-cli 0.1.1 → 0.1.2

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/LICENSE.md CHANGED
@@ -1,20 +1,51 @@
1
- # LICENSE
1
+ # Permissive No-Derivatives License 1.0
2
2
 
3
- Copyright (c) 2026 Alexandrite Software Ltd
3
+ Copyright (c) 2026 Alexandrite Software Ltd.
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
- this software and associated documentation files (the "Software"), to deal in
7
- the Software without restriction, including without limitation the rights to
8
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
- the Software, and to permit persons to whom the Software is furnished to do so,
10
- subject to the following conditions:
6
+ this software (the "Software") to:
11
7
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
8
+ 1. Use the Software for any purpose, including commercial purposes.
9
+ 2. Copy and redistribute the Software, including as part of another application,
10
+ library, or package.
11
+ 3. Modify the Software for private or internal use.
12
+
13
+ Subject to the following conditions:
14
+
15
+ ## 1. No Distribution of Modified Versions
16
+
17
+ Modified versions of the Software, or substantial portions thereof, must not be
18
+ distributed without prior written permission from the copyright holder.
19
+
20
+ Routine build transformations, including minification, transpilation, and
21
+ bundling, are permitted for distribution, provided they do not intentionally
22
+ alter the Software's functionality.
23
+
24
+ ## 2. Licence and Copyright Preservation
25
+
26
+ All distributed copies of the Software must retain this copyright notice and
27
+ licence.
28
+
29
+ The Software itself, including copies incorporated into other applications,
30
+ libraries, or packages, remains subject to this licence.
31
+
32
+ The Software must not be sublicensed or redistributed under a different licence.
33
+
34
+ ## 3. Use in Other Software
35
+
36
+ Applications and libraries that merely use, import, link to, or bundle the
37
+ Software are not considered modified versions of the Software and may be
38
+ distributed under any licence.
39
+
40
+ Such use, incorporation, or bundling does not grant permission to relicense the
41
+ Software or distribute modified versions of it.
42
+
43
+ ### 4. No Warranty
14
44
 
15
45
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
46
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
47
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
48
+
49
+ IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR
50
+ OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS
51
+ USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -49,9 +49,10 @@ covered by them.
49
49
  writes the text, and changes the structure with the commands.
50
50
 
51
51
  The model and the commands themselves are functions of the [rq library][RQL].
52
- This package is the `rq` command, the `rq-mcp` server and `rq view` over them;
53
- an application that wants the functions without a command line imports
54
- `asljs-rq` instead.
52
+ This package is the `rq` command. The [rq-mcp][MCP] server, `asljs-rq-mcp`,
53
+ serves the same commands as MCP tools, and `rq view` runs the web server of
54
+ [asljs-rq-srv][SRV]. An application that wants the functions without a command
55
+ line imports `asljs-rq` instead.
55
56
 
56
57
  ## Scope
57
58
 
@@ -67,8 +68,8 @@ an application that wants the functions without a command line imports
67
68
  - Steps run in order, in the test's folder, until the first that fails. An
68
69
  instruction step needs an AI agent, `claude` or `copilot`; the first one
69
70
  installed is used unless `--ai` names one.
70
- - The view loads Mermaid from a CDN to draw the diagram; the documents and their
71
- links work without it.
71
+ - The view draws the graph with `asljs-ui-diagram`, served by `rq view` itself,
72
+ so it needs no network; it has a search over the nodes' titles.
72
73
 
73
74
  ## Installation
74
75
 
@@ -197,6 +198,8 @@ Questions and bugs: [asljs/issues][IS].
197
198
  ## Related packages
198
199
 
199
200
  - [rq library][RQL] - the requirements model and the commands as functions.
201
+ - [asljs-rq-mcp][MCP] - the `rq-mcp` MCP server.
202
+ - [asljs-rq-srv][SRV] - the `rq view` web server.
200
203
  - `asljs-part` defines project artefacts in markdown and checks them with rules.
201
204
 
202
205
  ## License
@@ -210,11 +213,13 @@ MIT
210
213
  [CK]: <docs/rq check.md>
211
214
  [CV]: <docs/rq coverage.md>
212
215
  [IS]: https://github.com/AlexandriteSoftware/asljs/issues
213
- [MC]: docs/rq-mcp.md
216
+ [MC]: ../rq-mcp/docs/rq-mcp.md
217
+ [MCP]: ../rq-mcp/README.md
214
218
  [QG]: <docs/Querying the graph.md>
215
- [RF]: <../../libs/rq/docs/Requirement files.md>
216
- [RM]: ../../libs/rq/docs/Requirements.md
217
- [RQL]: ../../libs/rq/README.md
219
+ [RF]: <../rq/docs/Requirement files.md>
220
+ [RM]: ../rq/docs/Requirements.md
221
+ [RQL]: ../rq/README.md
218
222
  [SK]: skills
223
+ [SRV]: ../rq-srv/README.md
219
224
  [TS]: <docs/rq test.md>
220
- [VW]: <docs/rq view.md>
225
+ [VW]: <../rq-srv/docs/rq view.md>
package/dist/cli.js CHANGED
@@ -2,10 +2,10 @@ import { AI_AGENTS, parseAgentSpec } from 'asljs-agents';
2
2
  import { createLoggerProvider, readLoggerOptions } from 'asljs-logging';
3
3
  import { postProcess, takeWritten, untilStopped } from 'asljs-mdcli';
4
4
  import { execAdd, execAgents, execBacklinks, execCheck, execCoverage, execLink, execLinks, execList, execLog, execMove, execRemove, execTest, execToJson, execUnlink, getLogger, resolveFolders } from 'asljs-rq';
5
+ import { execView } from 'asljs-rq-srv';
5
6
  import { Command, CommanderError } from 'commander';
6
7
  import path from 'node:path';
7
8
  import { execConfig } from './config.js';
8
- import { execView } from './view.js';
9
9
  const WORKING_DIR = 'The folder to work in: paths resolve against it, rq.json is looked for from it up, and its .rq folder holds the results; the current directory by default';
10
10
  const REQUIREMENTS_DIR = 'The folder of the requirements: the default path, and where ids and .md names are searched for; requirementsDir of rq.json, else the working folder';
11
11
  const DOCUMENTS_DIR = 'The folder of every document, containing the requirements folder: what view serves, and where backlinks and links to rewrite are searched for; documentsDir of rq.json, else the requirements folder';
package/dist/index.d.ts CHANGED
@@ -1,3 +1 @@
1
1
  export { createCli, runCli, type CliState } from './cli.js';
2
- export { createTools, main as runMcpMain, runMcpServer, SERVER_NAME, serverInfo } from './mcp.js';
3
- export { execView, type ViewOptions } from './view.js';
package/dist/index.js CHANGED
@@ -1,3 +1 @@
1
1
  export { createCli, runCli } from './cli.js';
2
- export { createTools, main as runMcpMain, runMcpServer, SERVER_NAME, serverInfo } from './mcp.js';
3
- export { execView } from './view.js';
@@ -103,6 +103,6 @@ as [`rq test`][TS] does, with `--time` as the `Updated` of the test and of every
103
103
  requirement recalculated above it.
104
104
 
105
105
  [TS]: <rq test.md>
106
- [RM]: ../../../libs/rq/docs/Requirements.md
107
- [FD]: ../../../libs/rq/docs/Requirements.md#requirements-and-documents-folders
108
- [WF]: ../../../libs/rq/docs/Requirements.md#working-folder
106
+ [RM]: ../../rq/docs/Requirements.md
107
+ [FD]: ../../rq/docs/Requirements.md#requirements-and-documents-folders
108
+ [WF]: ../../rq/docs/Requirements.md#working-folder
@@ -67,5 +67,5 @@ as JSON:
67
67
  `body` (the text between the heading and the first section), `links` (the
68
68
  paths its `## Implementation` links to), `steps`, `result` and `coverage`.
69
69
 
70
- [RM]: ../../../libs/rq/docs/Requirements.md
71
- [FD]: ../../../libs/rq/docs/Requirements.md#requirements-and-documents-folders
70
+ [RM]: ../../rq/docs/Requirements.md
71
+ [FD]: ../../rq/docs/Requirements.md#requirements-and-documents-folders
@@ -25,5 +25,5 @@ Use it where `rq` is neither installed for the agent nor served to it by
25
25
  npx rq agents > .github/instructions/requirements.instructions.md
26
26
  ```
27
27
 
28
- [MC]: rq-mcp.md
29
- [RF]: <../../../libs/rq/docs/Requirement files.md>
28
+ [MC]: ../../rq-mcp/docs/rq-mcp.md
29
+ [RF]: <../../rq/docs/Requirement files.md>
@@ -27,6 +27,6 @@ OK 2 requirements, 2 tests
27
27
  Run it after editing documents by hand, and before `rq test`, which runs the
28
28
  steps.
29
29
 
30
- [RM]: ../../../libs/rq/docs/Requirements.md
31
- [FD]: ../../../libs/rq/docs/Requirements.md#requirements-and-documents-folders
32
- [WF]: ../../../libs/rq/docs/Requirements.md#working-folder
30
+ [RM]: ../../rq/docs/Requirements.md
31
+ [FD]: ../../rq/docs/Requirements.md#requirements-and-documents-folders
32
+ [WF]: ../../rq/docs/Requirements.md#working-folder
@@ -55,6 +55,6 @@ environment:
55
55
  requirements folder outside the documents folder is an error, here as in every
56
56
  command.
57
57
 
58
- [CF]: ../../../libs/rq/docs/Requirements.md#configuration
59
- [FD]: ../../../libs/rq/docs/Requirements.md#requirements-and-documents-folders
60
- [WF]: ../../../libs/rq/docs/Requirements.md#working-folder
58
+ [CF]: ../../rq/docs/Requirements.md#configuration
59
+ [FD]: ../../rq/docs/Requirements.md#requirements-and-documents-folders
60
+ [WF]: ../../rq/docs/Requirements.md#working-folder
@@ -75,6 +75,6 @@ only.
75
75
  default. No agent is an error. `RQ_AI_COMMAND` replaces the agent, as for
76
76
  [`rq test`][TS].
77
77
 
78
- [FD]: ../../../libs/rq/docs/Requirements.md#requirements-and-documents-folders
78
+ [FD]: ../../rq/docs/Requirements.md#requirements-and-documents-folders
79
79
  [TS]: <rq test.md>
80
- [WF]: ../../../libs/rq/docs/Requirements.md#working-folder
80
+ [WF]: ../../rq/docs/Requirements.md#working-folder
package/docs/rq test.md CHANGED
@@ -118,7 +118,7 @@ checked by [`rq coverage`][CV], not by `rq test`.
118
118
  `{"result":"OK"}` or `{"result":"Fail","message":"..."}`.
119
119
 
120
120
  [CV]: <rq coverage.md>
121
- [RM]: ../../../libs/rq/docs/Requirements.md
122
- [RT]: ../../../libs/rq/docs/Requirements.md#results
123
- [FD]: ../../../libs/rq/docs/Requirements.md#requirements-and-documents-folders
124
- [WF]: ../../../libs/rq/docs/Requirements.md#working-folder
121
+ [RM]: ../../rq/docs/Requirements.md
122
+ [RT]: ../../rq/docs/Requirements.md#results
123
+ [FD]: ../../rq/docs/Requirements.md#requirements-and-documents-folders
124
+ [WF]: ../../rq/docs/Requirements.md#working-folder
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "asljs-rq-cli",
3
- "version": "0.1.1",
4
- "description": "Requirements management tooling: the rq CLI, the rq-mcp MCP server and the rq view web server over asljs-rq.",
3
+ "version": "0.1.2",
4
+ "description": "The rq command line over asljs-rq: query, change, check, test and view requirements.",
5
5
  "type": "module",
6
+ "asljs": {
7
+ "type": "app"
8
+ },
6
9
  "main": "dist/index.js",
7
10
  "types": "dist/index.d.ts",
8
11
  "exports": {
@@ -14,28 +17,27 @@
14
17
  "files": [
15
18
  "dist/**",
16
19
  "bin/**",
17
- "docs/*.md",
20
+ "docs/**",
18
21
  "skills/*.md",
19
22
  "README.md",
20
23
  "LICENSE.md"
21
24
  ],
22
25
  "bin": {
23
- "rq": "bin/rq.js",
24
- "rq-mcp": "bin/rq-mcp.js"
26
+ "rq": "bin/rq.js"
25
27
  },
26
28
  "scripts": {
27
29
  "flint": "toolkit flint",
28
30
  "clean": "toolkit clean",
29
- "build": "tsc -p tsconfig.build.json",
30
- "build:dist": "tsc -p tsconfig.dist.json",
31
- "typecheck": "tsc -p tsconfig.build.json --noEmit",
31
+ "build": "npm run codegen --if-present && tsc -p tsconfig.build.json",
32
+ "build:dist": "npm run codegen --if-present && tsc -p tsconfig.dist.json",
33
+ "typecheck": "npm run codegen --if-present && tsc -p tsconfig.build.json --noEmit",
32
34
  "guard:clean-git": "toolkit ensure-clean-working-directory",
33
35
  "prepack": "npm run clean && npm run build:dist && npm run build && npm run test && npm run guard:clean-git",
34
36
  "postpublish": "toolkit tag-release-revision",
35
37
  "test": "node --test --enable-source-maps \"build/**/*.test.js\"",
36
38
  "test:watch": "node --watch --test \"build/**/*.test.js\"",
37
39
  "release:patch": "toolkit release-patch",
38
- "all": "npm run clean && npm run flint && npm run typecheck && npm run build:dist && npm run build && npm run test"
40
+ "all": "npm run clean && npm run build:dist && npm run flint && npm run typecheck && npm run build && npm run test"
39
41
  },
40
42
  "keywords": [
41
43
  "requirements",
@@ -43,7 +45,6 @@
43
45
  "test",
44
46
  "markdown",
45
47
  "ai",
46
- "mcp",
47
48
  "cli"
48
49
  ],
49
50
  "homepage": "https://github.com/AlexandriteSoftware/asljs#readme",
@@ -55,13 +56,13 @@
55
56
  "url": "git+https://github.com/AlexandriteSoftware/asljs.git"
56
57
  },
57
58
  "author": "\"Alex Netkachov\" <alex.netkachov@gmail.com>",
58
- "license": "MIT",
59
+ "license": "SEE LICENSE IN LICENSE.md",
59
60
  "dependencies": {
60
61
  "asljs-agents": "^0.1.0",
61
62
  "asljs-logging": "^0.1.2",
62
- "asljs-mcp": "^0.1.0",
63
- "asljs-mdcli": "^0.1.0",
64
- "asljs-rq": "^0.1.4",
63
+ "asljs-mdcli": "^0.1.3",
64
+ "asljs-rq": "^0.1.6",
65
+ "asljs-rq-srv": "^0.1.1",
65
66
  "commander": "^15.0.0"
66
67
  },
67
68
  "devDependencies": {
@@ -78,5 +78,5 @@ analysis of an incomplete requirement.
78
78
  [RC]: requirements-coverage.md
79
79
 
80
80
  [CG]: <../docs/Changing the graph.md>
81
- [RF]: <../../../libs/rq/docs/Requirement files.md>
82
- [RM]: ../../../libs/rq/docs/Requirements.md
81
+ [RF]: <../../rq/docs/Requirement files.md>
82
+ [RM]: ../../rq/docs/Requirements.md
@@ -47,5 +47,5 @@ analysis in the requirement's `## Coverage` section.
47
47
  [RC]: requirements-coverage.md
48
48
 
49
49
  [RA]: requirements-authoring.md
50
- [RF]: <../../../libs/rq/docs/Requirement files.md>
51
- [RM]: ../../../libs/rq/docs/Requirements.md
50
+ [RF]: <../../rq/docs/Requirement files.md>
51
+ [RM]: ../../rq/docs/Requirements.md
@@ -44,5 +44,5 @@ Use it to review the decomposition: a link that covers no statement can be
44
44
  unlinked, and a statement covered only by a vague test deserves a sharper one.
45
45
 
46
46
  [RA]: requirements-authoring.md
47
- [RF]: <../../../libs/rq/docs/Requirement files.md>
48
- [RM]: ../../../libs/rq/docs/Requirements.md
47
+ [RF]: <../../rq/docs/Requirement files.md>
48
+ [RM]: ../../rq/docs/Requirements.md
package/bin/rq-mcp.js DELETED
@@ -1,22 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { main }
4
- from '../dist/mcp.js';
5
-
6
- main(
7
- process.argv.slice(2),
8
- { cwd: process.cwd(),
9
- env: process.env,
10
- stdout: process.stderr,
11
- stderr: process.stderr },
12
- process.stdin,
13
- line => process.stdout.write(line))
14
- .catch(
15
- error => {
16
- console.error(
17
- error instanceof Error
18
- ? error.message
19
- : String(error));
20
-
21
- process.exitCode = 1;
22
- });
package/dist/mcp.d.ts DELETED
@@ -1,28 +0,0 @@
1
- import { type McpServerInfo, type McpTool } from 'asljs-mcp';
2
- import { Io } from 'asljs-rq';
3
- import { Server } from 'node:http';
4
- import { Readable } from 'node:stream';
5
- export declare const SERVER_NAME = "asljs-rq";
6
- /**
7
- * The tools of the MCP server: a tool per `rq` command, run as that command
8
- * line in `io.cwd`, or in its `workingDir`, with its output as the result.
9
- */
10
- export declare function createTools(io: Io, servers?: Server[]): McpTool[];
11
- /**
12
- * Serves the `rq` commands as MCP tools over a line-delimited JSON-RPC
13
- * stream, e.g. standard input and output. Resolves when the input ends,
14
- * and closes the servers `view` started.
15
- */
16
- export declare function runMcpServer(io: Io, input: Readable, write: (line: string) => void): Promise<void>;
17
- /**
18
- * The `rq-mcp` executable: serves the tools with a logger provider made
19
- * from `--loglevel`, `--logfile`, `--logformat` and the `RQ_LOG_`
20
- * variables, and disposes it once the input ends. Standard output carries the
21
- * protocol, so a log level without a log file other than stdout throws here,
22
- * before the first request is read.
23
- */
24
- export declare function main(args: string[], io: Io, input: Readable, write: (line: string) => void): Promise<void>;
25
- /**
26
- * The name and the version of the installed package.
27
- */
28
- export declare function serverInfo(): McpServerInfo;
package/dist/mcp.js DELETED
@@ -1,70 +0,0 @@
1
- import { createLoggerProvider, readLoggerOptions } from 'asljs-logging';
2
- import { commandTools, serveLines } from 'asljs-mcp';
3
- import { getLogger } from 'asljs-rq';
4
- import { createRequire } from 'node:module';
5
- import { createCli, runCli } from './cli.js';
6
- export const SERVER_NAME = 'asljs-rq';
7
- /**
8
- * The tools of the MCP server: a tool per `rq` command, run as that command
9
- * line in `io.cwd`, or in its `workingDir`, with its output as the result.
10
- */
11
- export function createTools(io, servers = []) {
12
- return commandTools(createCli([], io).cli, async (args) => {
13
- let stdout = '';
14
- let stderr = '';
15
- const exitCode = await runCli(args, { ...io,
16
- stdout: { write: (text) => {
17
- stdout += text;
18
- } },
19
- stderr: { write: (text) => {
20
- stderr += text;
21
- } } }, servers);
22
- return { exitCode,
23
- stdout,
24
- stderr };
25
- });
26
- }
27
- /**
28
- * Serves the `rq` commands as MCP tools over a line-delimited JSON-RPC
29
- * stream, e.g. standard input and output. Resolves when the input ends,
30
- * and closes the servers `view` started.
31
- */
32
- export async function runMcpServer(io, input, write) {
33
- const servers = [];
34
- try {
35
- await serveLines(input, write, createTools(io, servers), serverInfo(), { logger: getLogger(io, 'rq.mcp') });
36
- }
37
- finally {
38
- for (const server of servers) {
39
- await new Promise((resolve) => {
40
- server.close(resolve);
41
- server.closeAllConnections();
42
- });
43
- }
44
- }
45
- }
46
- /**
47
- * The `rq-mcp` executable: serves the tools with a logger provider made
48
- * from `--loglevel`, `--logfile`, `--logformat` and the `RQ_LOG_`
49
- * variables, and disposes it once the input ends. Standard output carries the
50
- * protocol, so a log level without a log file other than stdout throws here,
51
- * before the first request is read.
52
- */
53
- export async function main(args, io, input, write) {
54
- const loggerProvider = createLoggerProvider('RQ_LOG_', readLoggerOptions(args), { allowStdout: false });
55
- try {
56
- await runMcpServer({ ...io,
57
- loggerProvider }, input, write);
58
- }
59
- finally {
60
- await loggerProvider.dispose();
61
- }
62
- }
63
- /**
64
- * The name and the version of the installed package.
65
- */
66
- export function serverInfo() {
67
- const { version } = createRequire(import.meta.url)('../package.json');
68
- return { name: SERVER_NAME,
69
- version };
70
- }
@@ -1,41 +0,0 @@
1
- /**
2
- * The part of the diagram the window shows, in diagram coordinates: the SVG's
3
- * `viewBox`.
4
- */
5
- export interface ViewBox {
6
- x: number;
7
- y: number;
8
- width: number;
9
- height: number;
10
- }
11
- export interface Point {
12
- x: number;
13
- y: number;
14
- }
15
- /**
16
- * The view box zoomed by a wheel turn of `deltaY` around `point`, which stays
17
- * where it is on the screen: a turn down, a positive `deltaY`, zooms out.
18
- *
19
- * Its source is sent to the browser as part of the index page, so it uses
20
- * nothing but its arguments.
21
- */
22
- export declare function zoomAt(box: ViewBox, point: Point, deltaY: number): ViewBox;
23
- /**
24
- * The view box moved so that `grabbed`, the diagram point where a drag
25
- * started, is under the pointer, now over `point`.
26
- *
27
- * Its source is sent to the browser as part of the index page, so it uses
28
- * nothing but its arguments.
29
- */
30
- export declare function panTo(box: ViewBox, grabbed: Point, point: Point): ViewBox;
31
- /**
32
- * Whether pressing `button` on `target` starts a pan: the main button on
33
- * empty space, not on a node or a link, so that a click on a node still opens
34
- * its document.
35
- *
36
- * Its source is sent to the browser as part of the index page, so it uses
37
- * nothing but its arguments.
38
- */
39
- export declare function startsPan(button: number, target: {
40
- closest(selector: string): unknown;
41
- }): boolean;
@@ -1,39 +0,0 @@
1
- /**
2
- * The view box zoomed by a wheel turn of `deltaY` around `point`, which stays
3
- * where it is on the screen: a turn down, a positive `deltaY`, zooms out.
4
- *
5
- * Its source is sent to the browser as part of the index page, so it uses
6
- * nothing but its arguments.
7
- */
8
- export function zoomAt(box, point, deltaY) {
9
- const factor = Math.exp(deltaY * 0.0015);
10
- return { x: point.x - (point.x - box.x) * factor,
11
- y: point.y - (point.y - box.y) * factor,
12
- width: box.width * factor,
13
- height: box.height * factor };
14
- }
15
- /**
16
- * The view box moved so that `grabbed`, the diagram point where a drag
17
- * started, is under the pointer, now over `point`.
18
- *
19
- * Its source is sent to the browser as part of the index page, so it uses
20
- * nothing but its arguments.
21
- */
22
- export function panTo(box, grabbed, point) {
23
- return { x: box.x - (point.x - grabbed.x),
24
- y: box.y - (point.y - grabbed.y),
25
- width: box.width,
26
- height: box.height };
27
- }
28
- /**
29
- * Whether pressing `button` on `target` starts a pan: the main button on
30
- * empty space, not on a node or a link, so that a click on a node still opens
31
- * its document.
32
- *
33
- * Its source is sent to the browser as part of the index page, so it uses
34
- * nothing but its arguments.
35
- */
36
- export function startsPan(button, target) {
37
- return button === 0
38
- && !target.closest('.node, a');
39
- }
package/dist/view.d.ts DELETED
@@ -1,39 +0,0 @@
1
- import { type PostReply } from 'asljs-mdcli';
2
- import { Io } from 'asljs-rq';
3
- import { Server } from 'node:http';
4
- export { DEFAULT_PORT } from 'asljs-mdcli';
5
- export interface ViewOptions {
6
- /**
7
- * The requirement file or folder to view, relative to the working
8
- * directory, or an id or `.md` name; the requirements folder when absent.
9
- */
10
- target?: string;
11
- /**
12
- * The port to listen on, exactly; 0 picks a free one. When absent, the
13
- * first free port from `DEFAULT_PORT` on.
14
- */
15
- port?: number;
16
- host?: string;
17
- }
18
- /**
19
- * The body of a rendered document: a requirement or test starts with its
20
- * full path, which a click copies; any other document is its text alone.
21
- */
22
- export declare function renderDocument(file: string): Promise<string>;
23
- /**
24
- * Saves what the editor of a requirement or test sends to the document at
25
- * the URL's path in `folder`: its title and its text, unless the document
26
- * changed since the editor opened it; the ids of the requirements folder in
27
- * the text become links. Then the document is post-processed, as after a
28
- * command.
29
- */
30
- export declare function saveDocument(io: Io, folder: string, url: URL, body: unknown): Promise<PostReply>;
31
- /**
32
- * Starts a web server for a requirement file or folder. It serves the
33
- * documents folder, or the folder of the graph when none is given, which must
34
- * contain the graph's folder. `/` shows the graph, whose nodes open the
35
- * documents; a `.md` file of the served folder is shown rendered and any other
36
- * file as it is, so a document a requirement links to opens wherever it is in
37
- * the folder. The graph is reloaded on every request for `/`.
38
- */
39
- export declare function execView(io: Io, options: ViewOptions): Promise<Server>;
package/dist/view.js DELETED
@@ -1,321 +0,0 @@
1
- import { escapeHtml, markdownToHtml, page, postProcess, serverUrl, startServer, takeWritten, writeMarkdown } from 'asljs-mdcli';
2
- import { findTarget, getLogger, getNodeKind, getRequirementsDir, getResults, getVersion, isFile, isInside, loadGraph, readEditable, readIds, readImplementation, renderImplementationTable, toMermaid, writeEdited } from 'asljs-rq';
3
- import { readFile } from 'node:fs/promises';
4
- import path from 'node:path';
5
- import { panTo, startsPan, zoomAt } from './view-script.js';
6
- export { DEFAULT_PORT } from 'asljs-mdcli';
7
- const MERMAID_MODULE = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
8
- /**
9
- * The full path above a requirement or test, small and grey; a click copies
10
- * it.
11
- */
12
- const DOCUMENT_STYLE = `.rq-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin: 0 0 0.5rem; }
13
- .rq-path { margin: 0; font-family: ui-monospace, monospace; font-size: 0.8rem; color: #757575; cursor: copy; overflow-wrap: anywhere; }
14
- .rq-path.copied { color: #2e7d32; }
15
- .rq-title { display: flex; align-items: baseline; gap: 0.5rem; margin: 0.67em 0; font-size: 2em; font-weight: bold; }
16
- .rq-id { color: #757575; }
17
- #rq-title { flex: 1; min-width: 0; font: inherit; padding: 0 0.25rem; }
18
- #rq-editor { border: 1px solid #ddd; min-height: 300px; }
19
- .rq-note, #rq-message { color: #757575; font-size: 0.85rem; }
20
- .rq-implementation { border-collapse: collapse; }
21
- .rq-implementation th, .rq-implementation td { border-bottom: 1px solid #ddd; padding: 0.25rem 0.75rem 0.25rem 0; text-align: left; }
22
- .rq-pass { color: #2e7d32; }
23
- .rq-fail, .rq-missing { color: #c62828; }
24
- .rq-not-run { color: #757575; }`;
25
- /**
26
- * Stands for a requirement's Implementation list until the rest is rendered.
27
- */
28
- const TABLE_PLACEHOLDER = 'RQIMPLEMENTATIONTABLE';
29
- /**
30
- * The Milkdown editor, loaded when Edit is pressed: one module with its
31
- * dependencies, since separate modules can load ProseMirror twice, and its
32
- * theme.
33
- */
34
- const CREPE_MODULE = 'https://esm.sh/@milkdown/crepe@7.22.3?bundle';
35
- const CREPE_THEME = 'https://cdn.jsdelivr.net/npm/@milkdown/crepe@7.22.3/lib/theme';
36
- /**
37
- * Edit replaces the rendered document with a field for the title and a
38
- * Milkdown editor for the text, loaded only then; Save, or Ctrl+S, sends
39
- * both back with the version the page was made from, and reloads the page
40
- * once saved.
41
- */
42
- const EDIT_SCRIPT = `<script type="module">
43
- const data = JSON.parse(document.getElementById('rq-edit-data').textContent);
44
- const button = document.getElementById('rq-edit');
45
- const form = document.getElementById('rq-form');
46
- const title = document.getElementById('rq-title');
47
- const message = document.getElementById('rq-message');
48
- let crepe = null;
49
- button.addEventListener('click', async () => {
50
- button.disabled = true;
51
- button.textContent = 'Loading…';
52
- for (const sheet of [ 'common', 'frame' ]) {
53
- const link = document.createElement('link');
54
- link.rel = 'stylesheet';
55
- link.href = '${CREPE_THEME}/' + sheet + '/style.css';
56
- document.head.append(link);
57
- }
58
- try {
59
- const { Crepe } = await import('${CREPE_MODULE}');
60
- crepe = new Crepe({ root: document.getElementById('rq-editor'), defaultValue: data.body });
61
- await crepe.create();
62
- } catch (error) {
63
- button.disabled = false;
64
- button.textContent = 'Edit';
65
- message.textContent = 'The editor did not load: ' + error.message;
66
- return;
67
- }
68
- document.getElementById('rq-view').hidden = true;
69
- button.hidden = true;
70
- form.hidden = false;
71
- title.value = data.title;
72
- title.focus();
73
- });
74
- form.addEventListener('submit', async (event) => {
75
- event.preventDefault();
76
- message.textContent = 'Saving…';
77
- const response = await fetch(location.pathname, {
78
- method: 'POST',
79
- headers: { 'content-type': 'application/json' },
80
- body: JSON.stringify({ version: data.version, title: title.value, body: crepe.getMarkdown() })
81
- });
82
- const reply = await response.json().catch(() => ({ error: response.statusText }));
83
- if (response.ok) {
84
- location.reload();
85
- } else {
86
- message.textContent = reply.error;
87
- }
88
- });
89
- document.addEventListener('keydown', (event) => {
90
- if (!form.hidden && (event.ctrlKey || event.metaKey) && event.key === 's') {
91
- event.preventDefault();
92
- form.requestSubmit();
93
- }
94
- });
95
- document.getElementById('rq-cancel').addEventListener('click', () => location.reload());
96
- </script>`;
97
- /**
98
- * Copies the path a `.rq-path` holds when it is clicked, and shows it was
99
- * copied for a moment.
100
- */
101
- const COPY_SCRIPT = `<script>
102
- document.querySelector('.rq-path').addEventListener('click', async (event) => {
103
- const element = event.currentTarget;
104
- await navigator.clipboard.writeText(element.dataset.path);
105
- element.classList.add('copied');
106
- setTimeout(() => element.classList.remove('copied'), 1000);
107
- });
108
- </script>`;
109
- /**
110
- * The body of a rendered document: a requirement or test starts with its
111
- * full path, which a click copies; any other document is its text alone.
112
- */
113
- export async function renderDocument(file) {
114
- const text = await readFile(file, 'utf8');
115
- const kind = getNodeKind(file);
116
- if (kind === null) {
117
- return markdownToHtml(text);
118
- }
119
- // A requirement's Implementation list is shown as a table of what it
120
- // links to, with their coverage and result.
121
- const implementation = kind === 'requirement'
122
- ? await readImplementation(file, text, TABLE_PLACEHOLDER)
123
- : null;
124
- const html = implementation === null
125
- ? markdownToHtml(text)
126
- : markdownToHtml(implementation.text)
127
- .replace(`<p>${TABLE_PLACEHOLDER}</p>`, renderImplementationTable(implementation.rows));
128
- const editable = readEditable(file, text);
129
- // The editor's data, safe inside a script element.
130
- const data = JSON.stringify(editable)
131
- .replace(/</g, '\\u003c');
132
- return `<div class="rq-head">
133
- <p class="rq-path" data-path="${escapeHtml(file)}" title="Copy the path">${escapeHtml(file)}</p>
134
- <p><span id="rq-message"></span> <button type="button" id="rq-edit">Edit</button></p>
135
- </div>
136
- <div id="rq-view">
137
- ${html}
138
- </div>
139
- <form id="rq-form" hidden>
140
- <p class="rq-title"><span class="rq-id">${escapeHtml(editable.id)}</span><input id="rq-title" aria-label="Title"></p>
141
- <div id="rq-editor"></div>
142
- <p class="rq-note">Implementation, Coverage and Status are kept as they are, after the text. Ids such as R2 or T3 become links when saved.</p>
143
- <p><button type="submit">Save</button> <button type="button" id="rq-cancel">Cancel</button></p>
144
- </form>
145
- <script type="application/json" id="rq-edit-data">${data}</script>
146
- ${COPY_SCRIPT}
147
- ${EDIT_SCRIPT}`;
148
- }
149
- /**
150
- * Saves what the editor of a requirement or test sends to the document at
151
- * the URL's path in `folder`: its title and its text, unless the document
152
- * changed since the editor opened it; the ids of the requirements folder in
153
- * the text become links. Then the document is post-processed, as after a
154
- * command.
155
- */
156
- export async function saveDocument(io, folder, url, body) {
157
- const pathname = decodeURIComponent(url.pathname);
158
- const file = path.resolve(folder, `.${pathname}`);
159
- if (!isInside(folder, file)
160
- || getNodeKind(file)
161
- === null
162
- || !await isFile(file)) {
163
- return { status: 404,
164
- body: { error: `No requirement or test at ${pathname}.` } };
165
- }
166
- if (!isEdit(body)) {
167
- return { status: 400,
168
- body: { error: 'Send the version, the title and the text.' } };
169
- }
170
- const text = await readFile(file, 'utf8');
171
- if (getVersion(text)
172
- !== body.version) {
173
- return { status: 409,
174
- body: { error: 'The document changed since the editor opened it; copy your text, reload the page and edit again.' } };
175
- }
176
- await writeMarkdown(file, writeEdited(file, text, body, await readIds(getRequirementsDir(io))));
177
- const processed = await postProcess({ ...io,
178
- logger: getLogger(io, 'rq.post-process') }, takeWritten(), 'rq.json');
179
- return { status: 200,
180
- body: processed === 0
181
- ? { saved: true }
182
- : { saved: true,
183
- warning: 'markdownPostProcessing failed; see the server output.' } };
184
- }
185
- function isEdit(body) {
186
- return typeof body === 'object'
187
- && body !== null
188
- && typeof body.version === 'string'
189
- && typeof body.title === 'string'
190
- && typeof body.body === 'string';
191
- }
192
- /**
193
- * The index is the diagram alone, filling the window.
194
- */
195
- const INDEX_STYLE = `html, body { height: 100%; }
196
- body { max-width: none; margin: 0; padding: 0; overflow: hidden; }
197
- #diagram { position: fixed; inset: 0; cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
198
- #diagram.panning { cursor: grabbing; }
199
- #diagram > pre.mermaid { margin: 0; width: 100%; height: 100%; }
200
- #diagram svg { display: block; width: 100%; height: 100%; max-width: none !important; }
201
- #diagram .node { cursor: pointer; }
202
- #problems { position: fixed; top: 0.5rem; right: 0.5rem; max-width: 40%; max-height: 40%; overflow: auto; background: #fff8e1; border: 1px solid #ef8f00; padding: 0 0.75rem; font-size: 0.875rem; }
203
- #problems h2 { font-size: 1rem; }`;
204
- /**
205
- * Draws the diagram, then zooms it with the mouse wheel around the pointer and
206
- * pans it by dragging empty space. A click on a node opens its document, through
207
- * the `click` links of the diagram. The geometry is `zoomAt`, `panTo` and
208
- * `startsPan`, sent as their own source.
209
- */
210
- const SCRIPT = `import mermaid from '${MERMAID_MODULE}';
211
- ${zoomAt.toString()}
212
- ${panTo.toString()}
213
- ${startsPan.toString()}
214
- mermaid.initialize({ startOnLoad: false, securityLevel: 'loose' });
215
- await mermaid.run({ querySelector: '#diagram .mermaid' });
216
- const box = document.getElementById('diagram');
217
- const svg = box.querySelector('svg');
218
- svg.removeAttribute('width');
219
- svg.removeAttribute('height');
220
- const { x, y, width, height } = svg.viewBox.baseVal;
221
- let view = { x, y, width, height };
222
- const show = () =>
223
- svg.setAttribute('viewBox', \`\${view.x} \${view.y} \${view.width} \${view.height}\`);
224
- const toDiagram = (event) =>
225
- new DOMPoint(event.clientX, event.clientY).matrixTransform(svg.getScreenCTM().inverse());
226
- box.addEventListener('wheel', (event) => {
227
- event.preventDefault();
228
- view = zoomAt(view, toDiagram(event), event.deltaY);
229
- show();
230
- }, { passive: false });
231
- let drag = null;
232
- box.addEventListener('pointerdown', (event) => {
233
- if (!startsPan(event.button, event.target)) {
234
- return;
235
- }
236
- // Without this, the browser starts selecting the diagram's text.
237
- event.preventDefault();
238
- drag = { pointer: event.pointerId, grabbed: toDiagram(event) };
239
- box.setPointerCapture(event.pointerId);
240
- box.classList.add('panning');
241
- });
242
- box.addEventListener('pointermove', (event) => {
243
- if (drag?.pointer !== event.pointerId) {
244
- return;
245
- }
246
- view = panTo(view, drag.grabbed, toDiagram(event));
247
- show();
248
- });
249
- const stop = (event) => {
250
- if (drag?.pointer === event.pointerId) {
251
- drag = null;
252
- box.classList.remove('panning');
253
- }
254
- };
255
- box.addEventListener('pointerup', stop);
256
- box.addEventListener('pointercancel', stop);`;
257
- /**
258
- * Starts a web server for a requirement file or folder. It serves the
259
- * documents folder, or the folder of the graph when none is given, which must
260
- * contain the graph's folder. `/` shows the graph, whose nodes open the
261
- * documents; a `.md` file of the served folder is shown rendered and any other
262
- * file as it is, so a document a requirement links to opens wherever it is in
263
- * the folder. The graph is reloaded on every request for `/`.
264
- */
265
- export async function execView(io, options) {
266
- const target = await findTarget(io, options.target);
267
- const graphFolder = (await loadGraph(target)).folder;
268
- const folder = io.documentsDir ?? graphFolder;
269
- if (!isInside(folder, graphFolder)) {
270
- throw new Error(`${graphFolder} is not inside the documents folder ${folder}.`);
271
- }
272
- const server = await startServer({ folder,
273
- index: async () => {
274
- const graph = await loadGraph(target);
275
- return renderIndex(folder, graph, getResults(graph));
276
- },
277
- home: 'Requirements',
278
- style: DOCUMENT_STYLE,
279
- render: renderDocument,
280
- post: async (url, body) => saveDocument(io, folder, url, body),
281
- port: options.port,
282
- host: options.host,
283
- logger: getLogger(io, 'rq.view') });
284
- io.stdout.write(`Serving ${folder === graphFolder
285
- ? folder
286
- : `${graphFolder} in ${folder}`} at ${serverUrl(server)}\n`);
287
- return server;
288
- }
289
- function renderIndex(folder, graph, results) {
290
- const href = (file) => {
291
- if (!isInside(folder, file)) {
292
- return null;
293
- }
294
- return `/${encodeURI(path.relative(folder, file)
295
- .split(path.sep)
296
- .join('/'))}`;
297
- };
298
- const title = (graph.roots.length === 1
299
- ? graph.nodes.get(graph.roots[0])?.title
300
- : null)
301
- ?? 'Requirements';
302
- const problems = graph.errors.length === 0
303
- ? ''
304
- : `<aside id="problems">
305
- <h2>Problems</h2>
306
- <ul>
307
- ${graph.errors
308
- .map(error => `<li>${escapeHtml(error)}</li>`)
309
- .join('\n')}
310
- </ul>
311
- </aside>
312
- `;
313
- return page(title, `<main id="diagram">
314
- <pre class="mermaid">
315
- ${escapeHtml(toMermaid(graph, href, results))}
316
- </pre>
317
- </main>
318
- ${problems}<script type="module">
319
- ${SCRIPT}
320
- </script>`, INDEX_STYLE);
321
- }
package/docs/rq view.md DELETED
@@ -1,101 +0,0 @@
1
- # rq view
2
-
3
- Starts a web server for a requirement file or folder, and runs until stopped, by
4
- SIGINT (Ctrl+C) or SIGTERM.
5
-
6
- ```text
7
- rq view [path] [--port <port>] [--working-dir <folder>]
8
- [--requirements-dir <folder>] [--documents-dir <folder>]
9
- ```
10
-
11
- `[path]` is a requirement file, a folder, a `.md` name or an id; the
12
- requirements folder by default. The index shows its graph.
13
-
14
- The server serves the documents folder; see
15
- [Requirements and documents folders][FD]. When none is set, it serves the folder
16
- of the graph: `[path]` when it is a folder, the file's folder otherwise. The
17
- graph's folder must be inside the served folder; `rq view` reports an error
18
- otherwise. It listens on `127.0.0.1`.
19
-
20
- ```text
21
- rq view requirements --documents-dir .
22
- ```
23
-
24
- serves the whole project, so a requirement's link to `docs/design.md` opens
25
- rendered, and the index still shows the graph of `requirements`.
26
-
27
- - `/` - the index: the graph as a left-to-right Mermaid diagram of the
28
- requirements and tests and their `## Implementation` links, filling the
29
- window, and nothing else; the structure errors, when there are any, are listed
30
- in a box over its corner. Other documents are not drawn. Each node's border
31
- shows its state, from its `## Status` section:
32
- - colour, from `Result`, or for a requirement that records none the result
33
- that follows from its links - neutral grey for `NOT RUN`, green for `PASS`,
34
- red for a test that is `FAIL`, amber for a requirement that is `FAIL`
35
- because of what it links to;
36
- - style, from `Coverage`, which `rq coverage` writes - solid for a test and
37
- for a requirement that is `COMPLETE`, dashed for one that is `INCOMPLETE`,
38
- dotted for one without `Coverage`.
39
-
40
- The mouse wheel zooms in and out around the pointer, and dragging empty space
41
- pans. Clicking a node opens its document, at its path in the served folder,
42
- e.g. `/requirements/R1 Export.md`. All roots are in the one diagram; the page
43
- is titled with the root's heading when there is one root, `Requirements`
44
- otherwise. The graph is read again on every request, so a reload shows the
45
- current files.
46
- - `/<file>.md` - the document rendered as HTML. Its links to other `.md` files
47
- open them rendered. A requirement or test starts with its full path, small and
48
- grey; a click copies it to the clipboard. A requirement's `## Implementation`
49
- list is shown as a table of what it links to: each one's id and title, linking
50
- to it, the `Coverage` its `## Status` records, as Complete or Partial, and its
51
- `Result`, `NOT RUN` when it records none; an item whose document is missing
52
- says so.
53
- - any other file of the served folder is served as it is, e.g. an image a
54
- document shows.
55
-
56
- The server is for local use: it serves every file inside the served folder,
57
- including hidden ones, and renders the documents' HTML as it is. It takes a save
58
- only as JSON, sent to `localhost` or its address from its own pages, so another
59
- site cannot write through it.
60
-
61
- Nodes outside the served folder are drawn but do not open. The diagram is drawn
62
- by Mermaid, loaded from `cdn.jsdelivr.net`; without it the index shows the
63
- diagram's Mermaid text.
64
-
65
- ## Editing
66
-
67
- A requirement or test has an Edit button next to its path. It opens:
68
-
69
- - the title, in a field after the id, which stays as it is; the file keeps its
70
- name, which [`rq move`][MV] changes;
71
- - the rest of the text in a [Milkdown][MK] editor, without `## Implementation`,
72
- `## Coverage` and `## Status`, which the commands own. A link to a requirement
73
- or test by its id shows as the id alone, e.g. `R2`, and any other reference
74
- link as an inline link.
75
-
76
- Save, or Ctrl+S, writes the heading, the id and the title, then the text, then
77
- the definitions the owned sections use and the owned sections as they were. Each
78
- id of a requirement or test of the requirements folder in the text, outside
79
- code, becomes a reference link to it again, e.g. `[R2][]`, with its definition
80
- after the text. The document is then post-processed with
81
- `markdownPostProcessing` of `rq.json`, as after a command. A save is refused
82
- when the file changed since the editor opened it: reload the page and edit
83
- again. Cancel reloads the page. Milkdown is loaded from `esm.sh`, and its theme
84
- from `cdn.jsdelivr.net`, when Edit is pressed.
85
-
86
- ## Options
87
-
88
- - `--port <port>` - the port, exactly: one in use is an error, and 0 picks any
89
- free one. Without it, the first free port from 3000 on, up to 3099. The
90
- address is printed when the server starts.
91
- - `--working-dir <folder>` - the working folder, whose `.rq` folder holds the
92
- results; see [Working folder][WF]. The results are read again on every request
93
- for `/`.
94
- - `--requirements-dir <folder>`, `--documents-dir <folder>` - the requirements
95
- folder, the default `[path]`, and the documents folder, the served one; see
96
- [Requirements and documents folders][FD].
97
-
98
- [FD]: ../../../libs/rq/docs/Requirements.md#requirements-and-documents-folders
99
- [WF]: ../../../libs/rq/docs/Requirements.md#working-folder
100
- [MK]: https://milkdown.dev
101
- [MV]: <Changing the graph.md#rq-move>
package/docs/rq-mcp.md DELETED
@@ -1,79 +0,0 @@
1
- # rq-mcp
2
-
3
- `rq-mcp` is an MCP server with a tool per `rq` command, so that an AI agent can
4
- check, test and change requirements without a shell.
5
-
6
- ## Running it
7
-
8
- ```bash
9
- rq-mcp
10
- ```
11
-
12
- It reads JSON-RPC requests from standard input, one per line, and writes the
13
- responses to standard output, as the Model Context Protocol's stdio transport
14
- does; it stops when standard input ends. An MCP client starts it, e.g. in a
15
- Claude Code `.mcp.json`:
16
-
17
- ```json
18
- {
19
- "mcpServers": {
20
- "rq": {
21
- "command": "npx",
22
- "args": [ "rq-mcp" ]
23
- }
24
- }
25
- }
26
- ```
27
-
28
- ## Tools
29
-
30
- A tool per command, named by the command, with `_` between a command and its
31
- subcommand: `test`, `coverage`, `view`, `check`, `list`, `links`, `backlinks`,
32
- `tojson`, `agents`, `config`, `add_requirement`, `add_test`, `link`, `unlink`,
33
- `remove`, `move` and `log`. Each tool's arguments are the command's arguments
34
- and options, by name:
35
-
36
- ```json
37
- {
38
- "name": "test",
39
- "arguments": {
40
- "targets": [ "R1" ],
41
- "recurse": true,
42
- "workingDir": "dev/rq"
43
- }
44
- }
45
- ```
46
-
47
- - an argument that takes several values, such as `targets`, is an array;
48
- - an option without a value, such as `recurse`, is a boolean;
49
- - an option with an optional value, such as `ai`, takes a string, and an empty
50
- string gives the option without one: `"ai": ""` is `--ai`;
51
- - `workingDir` is the folder the command works in; by default the folder
52
- `rq-mcp` was started in. `requirementsDir` and `documentsDir` set the
53
- requirements and documents folders, as `--requirements-dir` and
54
- `--documents-dir` do.
55
-
56
- A call runs that command line, and the result is what it printed. A command that
57
- exits with a non-zero code - a failed test, a structure error - is an error
58
- result that ends with `Exit code: <n>`. Calls run one at a time.
59
-
60
- `test` with instruction steps and `coverage` run an AI agent, and can take
61
- minutes per test or requirement. `view` starts the server inside `rq-mcp`; it
62
- serves until standard input ends, when `rq-mcp` closes it.
63
-
64
- ## Logging
65
-
66
- `rq-mcp` logs nothing unless asked to, with `--loglevel`, `--logfile` and
67
- `--logformat`, or `RQ_LOG_LEVEL`, `RQ_LOG_FILE` and `RQ_LOG_FORMAT`. Standard
68
- output carries the protocol, so a level without a log file, or with `--logfile
69
- stdout`, is refused at startup; log to standard error, which MCP clients show,
70
- or to a file:
71
-
72
- ```bash
73
- rq-mcp --loglevel debug --logfile stderr
74
- ```
75
-
76
- At `debug` it logs each tool call and, as `rq` does, each command, test, step
77
- command, agent and post-processing run; at `trace` also each request, the
78
- agents' prompts and their output. A line that is not valid JSON is skipped and
79
- logged as a warning.