asljs-rq-cli 0.0.0-stage → 0.1.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/LICENSE.md +20 -0
- package/README.md +219 -2
- package/bin/rq-mcp.js +22 -0
- package/bin/rq.js +12 -0
- package/dist/cli.d.ts +38 -0
- package/dist/cli.js +315 -0
- package/dist/config.d.ts +63 -0
- package/dist/config.js +78 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp.d.ts +28 -0
- package/dist/mcp.js +70 -0
- package/dist/view-script.d.ts +41 -0
- package/dist/view-script.js +39 -0
- package/dist/view.d.ts +39 -0
- package/dist/view.js +321 -0
- package/docs/Changing the graph.md +108 -0
- package/docs/Querying the graph.md +71 -0
- package/docs/rq agents.md +29 -0
- package/docs/rq check.md +32 -0
- package/docs/rq config.md +60 -0
- package/docs/rq coverage.md +80 -0
- package/docs/rq test.md +124 -0
- package/docs/rq view.md +101 -0
- package/docs/rq-mcp.md +79 -0
- package/package.json +70 -4
- package/skills/requirements-authoring.md +82 -0
- package/skills/requirements-change.md +51 -0
- package/skills/requirements-coverage.md +48 -0
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type Folders, Io, type Setting } from 'asljs-rq';
|
|
2
|
+
/**
|
|
3
|
+
* The environment variables `rq` reads.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ENVIRONMENT_VARIABLES: readonly ["RQ_AI_COMMAND", "RQ_LOG_LEVEL", "RQ_LOG_FILE", "RQ_LOG_FORMAT"];
|
|
6
|
+
/**
|
|
7
|
+
* The configuration `rq` runs with, as `rq config` prints it.
|
|
8
|
+
*/
|
|
9
|
+
export interface Configuration {
|
|
10
|
+
version: string;
|
|
11
|
+
workingDir: string;
|
|
12
|
+
/**
|
|
13
|
+
* The `rq.json` in use; `null` when there is none.
|
|
14
|
+
*/
|
|
15
|
+
configFile: string | null;
|
|
16
|
+
requirementsDir: Setting;
|
|
17
|
+
documentsDir: Setting;
|
|
18
|
+
/**
|
|
19
|
+
* The command `rq.json` runs on the markdown files a command wrote; `null`
|
|
20
|
+
* when there is none.
|
|
21
|
+
*/
|
|
22
|
+
markdownPostProcessing: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* The agent of instruction steps and `rq coverage` when `--ai` names none:
|
|
25
|
+
* `RQ_AI_COMMAND`, or the first installed of `agents`, detected when a
|
|
26
|
+
* command needs it.
|
|
27
|
+
*/
|
|
28
|
+
agent: {
|
|
29
|
+
command: string | null;
|
|
30
|
+
agents: string[];
|
|
31
|
+
};
|
|
32
|
+
log: {
|
|
33
|
+
level: LogSetting;
|
|
34
|
+
file: LogSetting;
|
|
35
|
+
format: LogSetting;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Each variable `rq` reads, and its value; `null` when it is not set.
|
|
39
|
+
*/
|
|
40
|
+
environment: Record<string, string | null>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A log setting and where it comes from: its option, its `RQ_LOG_`
|
|
44
|
+
* variable, or the default.
|
|
45
|
+
*/
|
|
46
|
+
export interface LogSetting {
|
|
47
|
+
value: string;
|
|
48
|
+
source: 'option' | 'environment' | 'default';
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Prints the configuration: the folders and where they come from, the
|
|
52
|
+
* `rq.json` in use, the agent, the log settings, and the environment
|
|
53
|
+
* variables `rq` reads. `args` is the command line, for the log options.
|
|
54
|
+
*/
|
|
55
|
+
export declare function execConfig(io: Io, options: {
|
|
56
|
+
folders: Folders;
|
|
57
|
+
args: readonly string[];
|
|
58
|
+
json?: boolean;
|
|
59
|
+
}): Promise<number>;
|
|
60
|
+
/**
|
|
61
|
+
* The configuration `rq` runs with in the working folder of `io`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function readConfiguration(io: Io, folders: Folders, args: readonly string[]): Promise<Configuration>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AI_AGENTS } from 'asljs-agents';
|
|
2
|
+
import { readLoggerOptions } from 'asljs-logging';
|
|
3
|
+
import { findConfig } from 'asljs-mdcli';
|
|
4
|
+
import { CONFIG_FILE } from 'asljs-rq';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
|
+
/**
|
|
7
|
+
* The environment variables `rq` reads.
|
|
8
|
+
*/
|
|
9
|
+
export const ENVIRONMENT_VARIABLES = ['RQ_AI_COMMAND',
|
|
10
|
+
'RQ_LOG_LEVEL',
|
|
11
|
+
'RQ_LOG_FILE',
|
|
12
|
+
'RQ_LOG_FORMAT'];
|
|
13
|
+
/**
|
|
14
|
+
* Prints the configuration: the folders and where they come from, the
|
|
15
|
+
* `rq.json` in use, the agent, the log settings, and the environment
|
|
16
|
+
* variables `rq` reads. `args` is the command line, for the log options.
|
|
17
|
+
*/
|
|
18
|
+
export async function execConfig(io, options) {
|
|
19
|
+
const configuration = await readConfiguration(io, options.folders, options.args);
|
|
20
|
+
io.stdout.write(options.json
|
|
21
|
+
? `${JSON.stringify(configuration, null, 2)}\n`
|
|
22
|
+
: describe(configuration));
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The configuration `rq` runs with in the working folder of `io`.
|
|
27
|
+
*/
|
|
28
|
+
export async function readConfiguration(io, folders, args) {
|
|
29
|
+
const found = await findConfig(io.cwd, CONFIG_FILE);
|
|
30
|
+
const environment = Object.fromEntries(ENVIRONMENT_VARIABLES.map(name => [name,
|
|
31
|
+
io.env[name] || null]));
|
|
32
|
+
const overrides = readLoggerOptions(args);
|
|
33
|
+
const pick = (option, variable, fallback) => option !== undefined
|
|
34
|
+
? { value: option,
|
|
35
|
+
source: 'option' }
|
|
36
|
+
: environment[variable] !== null
|
|
37
|
+
? { value: environment[variable],
|
|
38
|
+
source: 'environment' }
|
|
39
|
+
: { value: fallback,
|
|
40
|
+
source: 'default' };
|
|
41
|
+
const file = pick(overrides.file, 'RQ_LOG_FILE', 'stdout');
|
|
42
|
+
return { version: packageVersion(),
|
|
43
|
+
workingDir: io.cwd,
|
|
44
|
+
configFile: folders.configFile,
|
|
45
|
+
requirementsDir: folders.requirementsDir,
|
|
46
|
+
documentsDir: folders.documentsDir,
|
|
47
|
+
markdownPostProcessing: found?.config.markdownPostProcessing?.trim()
|
|
48
|
+
|| null,
|
|
49
|
+
agent: { command: environment.RQ_AI_COMMAND,
|
|
50
|
+
agents: [...AI_AGENTS] },
|
|
51
|
+
log: { level: pick(overrides.level, 'RQ_LOG_LEVEL', file.source === 'default'
|
|
52
|
+
? 'silent'
|
|
53
|
+
: 'information'),
|
|
54
|
+
file,
|
|
55
|
+
format: pick(overrides.format, 'RQ_LOG_FORMAT', 'auto') },
|
|
56
|
+
environment };
|
|
57
|
+
}
|
|
58
|
+
function describe(configuration) {
|
|
59
|
+
const setting = (value) => `${value.value} (${value.source})`;
|
|
60
|
+
const lines = [`version: ${configuration.version}`,
|
|
61
|
+
`workingDir: ${configuration.workingDir}`,
|
|
62
|
+
`configFile: ${configuration.configFile ?? '(none)'}`,
|
|
63
|
+
`requirementsDir: ${setting(configuration.requirementsDir)}`,
|
|
64
|
+
`documentsDir: ${setting(configuration.documentsDir)}`,
|
|
65
|
+
`markdownPostProcessing: ${configuration.markdownPostProcessing ?? '(none)'}`,
|
|
66
|
+
`agent: ${configuration.agent.command === null
|
|
67
|
+
? `the first installed of ${configuration.agent.agents.join(', ')}, detected when needed`
|
|
68
|
+
: `${configuration.agent.command} (RQ_AI_COMMAND)`}`,
|
|
69
|
+
`logLevel: ${setting(configuration.log.level)}`,
|
|
70
|
+
`logFile: ${setting(configuration.log.file)}`,
|
|
71
|
+
`logFormat: ${setting(configuration.log.format)}`,
|
|
72
|
+
'environment:',
|
|
73
|
+
...Object.entries(configuration.environment).map(([name, value]) => ` ${name}=${value ?? ''}`)];
|
|
74
|
+
return `${lines.join('\n')}\n`;
|
|
75
|
+
}
|
|
76
|
+
function packageVersion() {
|
|
77
|
+
return createRequire(import.meta.url)('../package.json').version;
|
|
78
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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;
|
|
@@ -0,0 +1,39 @@
|
|
|
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
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
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>;
|