asljs-rq-cli 0.1.2 → 0.1.3

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 CHANGED
@@ -35,10 +35,6 @@ covered by them.
35
35
  `## Status` section; a failed test fails every requirement above it.
36
36
  - `rq coverage <target>...` asks an AI agent whether each requirement's
37
37
  sub-requirements and tests fully cover it, and records the verdict.
38
- - `rq view [path]` serves the graph as a clickable diagram, each border coloured
39
- by the result and drawn solid, dashed or dotted by the coverage, and each
40
- document of the documents folder rendered as HTML, including documents outside
41
- the requirements folder that the requirements link to.
42
38
  - `rq agents` prints the formats and the rules every change keeps, for an AI
43
39
  agent that edits the files without `rq`.
44
40
  - `rq config` prints the folders, `rq.json`, the agent and the log settings, and
@@ -50,8 +46,9 @@ covered by them.
50
46
 
51
47
  The model and the commands themselves are functions of the [rq library][RQL].
52
48
  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
49
+ serves the same commands as MCP tools, and the [rq-srv][SRV] web server,
50
+ `asljs-rq-srv`, serves the graph as a clickable diagram and the documents
51
+ rendered and editable. An application that wants the functions without a command
55
52
  line imports `asljs-rq` instead.
56
53
 
57
54
  ## Scope
@@ -68,8 +65,6 @@ line imports `asljs-rq` instead.
68
65
  - Steps run in order, in the test's folder, until the first that fails. An
69
66
  instruction step needs an AI agent, `claude` or `copilot`; the first one
70
67
  installed is used unless `--ai` names one.
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.
73
68
 
74
69
  ## Installation
75
70
 
@@ -80,8 +75,9 @@ npm install asljs-rq-cli
80
75
  ## Usage
81
76
 
82
77
  Every command takes `--loglevel <level>`, `--logfile <target>` and `--logformat
83
- <format>`, or `RQ_LOG_LEVEL`, `RQ_LOG_FILE` and `RQ_LOG_FORMAT`, to log what it
84
- does, e.g. `--loglevel debug --logfile stderr`; nothing is logged without them.
78
+ <format>`, or `RQ_LOG_LEVEL`, `RQ_LOG_FILE` and `RQ_LOG_FORMAT`, or `logLevel`,
79
+ `logFile` and `logFormat` of `rq.json`, to log what it does, e.g. `--loglevel
80
+ debug --logfile stderr`; nothing is logged without them.
85
81
 
86
82
  Build the graph with the commands, and write the statements, descriptions and
87
83
  steps as text:
@@ -156,14 +152,8 @@ Run a single requirement and everything below it by id, from any folder:
156
152
  npx rq test R1 --recurse --working-dir path/to/project
157
153
  ```
158
154
 
159
- Browse the graph and the documents:
160
-
161
- ```bash
162
- npx rq view requirements --port 8080
163
- ```
164
-
165
155
  With `rq.json` at the project root, the commands default to the requirements
166
- folder, and the view serves the whole project:
156
+ folder, and the documents folder is the whole project:
167
157
 
168
158
  ```json
169
159
  {
@@ -173,7 +163,7 @@ folder, and the view serves the whole project:
173
163
  ```
174
164
 
175
165
  ```bash
176
- npx rq view
166
+ npx rq check
177
167
  npx rq config
178
168
  ```
179
169
 
@@ -185,9 +175,8 @@ npx rq config
185
175
  built.
186
176
  - [Querying the graph][QG] and [Changing the graph][CG] - the structural
187
177
  commands.
188
- - [rq check][CK], [rq test][TS], [rq coverage][CV], [rq view][VW] and
189
- [rq config][CF] - checking, testing, checking coverage, viewing and the
190
- configuration.
178
+ - [rq check][CK], [rq test][TS], [rq coverage][CV] and [rq config][CF] -
179
+ checking, testing, checking coverage and the configuration.
191
180
  - [rq agents][AG] - the instructions for an AI agent that edits the files
192
181
  without `rq`.
193
182
  - [rq-mcp][MC] - the MCP server with a tool per command.
@@ -199,7 +188,7 @@ Questions and bugs: [asljs/issues][IS].
199
188
 
200
189
  - [rq library][RQL] - the requirements model and the commands as functions.
201
190
  - [asljs-rq-mcp][MCP] - the `rq-mcp` MCP server.
202
- - [asljs-rq-srv][SRV] - the `rq view` web server.
191
+ - [asljs-rq-srv][SRV] - the `rq-srv` web server; see [rq-srv][VW].
203
192
  - `asljs-part` defines project artefacts in markdown and checks them with rules.
204
193
 
205
194
  ## License
@@ -222,4 +211,4 @@ MIT
222
211
  [SK]: skills
223
212
  [SRV]: ../rq-srv/README.md
224
213
  [TS]: <docs/rq test.md>
225
- [VW]: <../rq-srv/docs/rq view.md>
214
+ [VW]: ../rq-srv/docs/rq-srv.md
package/dist/cli.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { type Folders, Io } from 'asljs-rq';
2
2
  import { Command } from 'commander';
3
- import { Server } from 'node:http';
4
3
  /**
5
4
  * What a run of the command line leaves: its exit code, and the Io of the
6
5
  * command, with `--working-dir`, `--requirements-dir` and
@@ -19,20 +18,17 @@ export interface CliState {
19
18
  * The `rq` command line, not yet run: the program, and the state its
20
19
  * actions set.
21
20
  */
22
- export declare function createCli(args: string[], io: Io, servers?: Server[]): {
21
+ export declare function createCli(args: string[], io: Io): {
23
22
  cli: Command;
24
23
  state: CliState;
25
24
  };
26
25
  /**
27
- * Runs the `rq` command line and returns the exit code. `rq view`
28
- * returns once the server listens, and adds it to `servers`; it serves until
29
- * whoever passed `servers` closes it.
26
+ * Runs the `rq` command line and returns the exit code.
30
27
  */
31
- export declare function runCli(args: string[], io: Io, servers?: Server[]): Promise<number>;
28
+ export declare function runCli(args: string[], io: Io): Promise<number>;
32
29
  /**
33
30
  * The `rq` executable: runs the command line with a logger provider made
34
- * from `--loglevel`, `--logfile`, `--logformat` and the `RQ_LOG_`
35
- * variables, and disposes it before it returns the exit code. A server that
36
- * `view` started serves until SIGINT or SIGTERM first.
31
+ * from `--loglevel`, `--logfile`, `--logformat`, the `RQ_LOG_` variables and
32
+ * `rq.json` (`runMain`), and disposes it before it returns the exit code.
37
33
  */
38
34
  export declare function main(args: string[], io: Io): Promise<number>;
package/dist/cli.js CHANGED
@@ -1,19 +1,17 @@
1
1
  import { AI_AGENTS, parseAgentSpec } from 'asljs-agents';
2
- import { createLoggerProvider, readLoggerOptions } from 'asljs-logging';
3
- import { postProcess, takeWritten, untilStopped } from 'asljs-mdcli';
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';
2
+ import { commandLine, messageOf, postProcess, runMain, takeWritten } from 'asljs-appbase';
3
+ import { CONFIG_FILE, execAdd, execAgents, execBacklinks, execCheck, execCoverage, execLink, execLinks, execList, execLog, execMove, execRemove, execTest, execToJson, execUnlink, getLogger, resolveFolders } from 'asljs-rq';
6
4
  import { Command, CommanderError } from 'commander';
7
5
  import path from 'node:path';
8
6
  import { execConfig } from './config.js';
9
7
  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
8
  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
- 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';
9
+ const DOCUMENTS_DIR = 'The folder of every document, containing the requirements folder: what rq-srv serves, and where backlinks and links to rewrite are searched for; documentsDir of rq.json, else the requirements folder';
12
10
  /**
13
11
  * The `rq` command line, not yet run: the program, and the state its
14
12
  * actions set.
15
13
  */
16
- export function createCli(args, io, servers = []) {
14
+ export function createCli(args, io) {
17
15
  const state = { exitCode: 0,
18
16
  io,
19
17
  folders: null };
@@ -23,15 +21,9 @@ export function createCli(args, io, servers = []) {
23
21
  : JSON.stringify(arg))]
24
22
  .join(' ');
25
23
  const cli = new Command();
26
- cli.name('rq')
27
- .description('AI-assisted requirements management: test and view requirements and tests written in markdown.')
28
- .helpCommand(false)
29
- .configureOutput({ writeOut: value => io.stdout.write(value),
30
- writeErr: value => io.stderr.write(value) })
31
- .exitOverride()
32
- .option('--loglevel <level>', 'Log level: trace, debug, information, warning, error')
33
- .option('--logfile <target>', 'Where logs go: a file path, stdout or stderr')
34
- .option('--logformat <format>', 'Log format: auto, json, text or pretty')
24
+ commandLine(cli.name('rq')
25
+ .description('AI-assisted requirements management: test and check requirements and tests written in markdown.')
26
+ .helpCommand(false), io)
35
27
  .hook('preAction', async (_cli, action) => {
36
28
  const options = action.opts();
37
29
  const cwd = options.workingDir === undefined
@@ -40,8 +32,7 @@ export function createCli(args, io, servers = []) {
40
32
  state.folders =
41
33
  await resolveFolders(cwd, options);
42
34
  // A folder left to its default stays unset, so that a command can
43
- // tell it from one that was given; `view` serves the folder of its
44
- // path then.
35
+ // tell it from one that was given.
45
36
  const given = (setting) => setting.source === 'default'
46
37
  ? undefined
47
38
  : setting.value;
@@ -80,23 +71,6 @@ export function createCli(args, io, servers = []) {
80
71
  ? undefined
81
72
  : parseAgentSpec(options.ai) });
82
73
  });
83
- cli.command('view')
84
- .description('Serve the requirements graph and the rendered documents over HTTP')
85
- .argument('[path]', 'A requirement file, or a folder of requirements; the requirements folder by default')
86
- .option('--port <port>', 'Port to listen on, exactly; 0 picks a free one. By default the first free port from 3000 on')
87
- .action(async (target, options) => {
88
- const port = options.port === undefined
89
- ? undefined
90
- : Number(options.port);
91
- if (port !== undefined
92
- && (!Number.isInteger(port)
93
- || port < 0
94
- || port > 65535)) {
95
- throw new Error(`Invalid port: ${options.port}`);
96
- }
97
- servers.push(await execView(state.io, { target,
98
- port }));
99
- });
100
74
  cli.command('check')
101
75
  .description('Check the structure of the graph and its documents without running anything')
102
76
  .argument('[path]', 'A requirement file, or a folder of requirements; the requirements folder by default')
@@ -247,12 +221,10 @@ export function createCli(args, io, servers = []) {
247
221
  state };
248
222
  }
249
223
  /**
250
- * Runs the `rq` command line and returns the exit code. `rq view`
251
- * returns once the server listens, and adds it to `servers`; it serves until
252
- * whoever passed `servers` closes it.
224
+ * Runs the `rq` command line and returns the exit code.
253
225
  */
254
- export async function runCli(args, io, servers = []) {
255
- const { cli, state } = createCli(args, io, servers);
226
+ export async function runCli(args, io) {
227
+ const { cli, state } = createCli(args, io);
256
228
  if (args.length === 0) {
257
229
  cli.outputHelp();
258
230
  return 0;
@@ -267,14 +239,12 @@ export async function runCli(args, io, servers = []) {
267
239
  if (error instanceof CommanderError) {
268
240
  return error.exitCode;
269
241
  }
270
- io.stderr.write(`${error instanceof Error
271
- ? error.message
272
- : String(error)}\n`);
242
+ io.stderr.write(`${messageOf(error)}\n`);
273
243
  state.exitCode = 1;
274
244
  }
275
245
  try {
276
246
  const processed = await postProcess({ ...state.io,
277
- logger: getLogger(state.io, 'rq.post-process') }, takeWritten(), 'rq.json');
247
+ logger: getLogger(state.io, 'rq.post-process') }, takeWritten(), CONFIG_FILE);
278
248
  logger.debug({ exitCode: state.exitCode,
279
249
  postProcessing: processed }, 'command finished');
280
250
  return state.exitCode === 0
@@ -282,9 +252,7 @@ export async function runCli(args, io, servers = []) {
282
252
  : state.exitCode;
283
253
  }
284
254
  catch (error) {
285
- io.stderr.write(`${error instanceof Error
286
- ? error.message
287
- : String(error)}\n`);
255
+ io.stderr.write(`${messageOf(error)}\n`);
288
256
  return 1;
289
257
  }
290
258
  }
@@ -294,22 +262,11 @@ function collect(value, previous) {
294
262
  }
295
263
  /**
296
264
  * The `rq` executable: runs the command line with a logger provider made
297
- * from `--loglevel`, `--logfile`, `--logformat` and the `RQ_LOG_`
298
- * variables, and disposes it before it returns the exit code. A server that
299
- * `view` started serves until SIGINT or SIGTERM first.
265
+ * from `--loglevel`, `--logfile`, `--logformat`, the `RQ_LOG_` variables and
266
+ * `rq.json` (`runMain`), and disposes it before it returns the exit code.
300
267
  */
301
268
  export async function main(args, io) {
302
- const loggerProvider = createLoggerProvider('RQ_LOG_', readLoggerOptions(args));
303
- const servers = [];
304
- try {
305
- const exitCode = await runCli(args, { ...io,
306
- loggerProvider }, servers);
307
- for (const server of servers) {
308
- await untilStopped(server);
309
- }
310
- return exitCode;
311
- }
312
- finally {
313
- await loggerProvider.dispose();
314
- }
269
+ return await runMain(args, io, { prefix: 'RQ_LOG_',
270
+ configFile: CONFIG_FILE }, async (loggerProvider) => await runCli(args, { ...io,
271
+ loggerProvider }));
315
272
  }
package/dist/config.d.ts CHANGED
@@ -41,11 +41,11 @@ export interface Configuration {
41
41
  }
42
42
  /**
43
43
  * A log setting and where it comes from: its option, its `RQ_LOG_`
44
- * variable, or the default.
44
+ * variable, `rq.json`, or the default.
45
45
  */
46
46
  export interface LogSetting {
47
47
  value: string;
48
- source: 'option' | 'environment' | 'default';
48
+ source: 'option' | 'environment' | 'rq.json' | 'default';
49
49
  }
50
50
  /**
51
51
  * Prints the configuration: the folders and where they come from, the
package/dist/config.js CHANGED
@@ -1,8 +1,6 @@
1
1
  import { AI_AGENTS } from 'asljs-agents';
2
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';
3
+ import { configLogging, configString, packageVersion } from 'asljs-appbase';
6
4
  /**
7
5
  * The environment variables `rq` reads.
8
6
  */
@@ -26,33 +24,37 @@ export async function execConfig(io, options) {
26
24
  * The configuration `rq` runs with in the working folder of `io`.
27
25
  */
28
26
  export async function readConfiguration(io, folders, args) {
29
- const found = await findConfig(io.cwd, CONFIG_FILE);
30
27
  const environment = Object.fromEntries(ENVIRONMENT_VARIABLES.map(name => [name,
31
28
  io.env[name] || null]));
32
29
  const overrides = readLoggerOptions(args);
33
- const pick = (option, variable, fallback) => option !== undefined
30
+ const configured = configLogging(folders.config);
31
+ const pick = (option, variable, fromFile, fallback) => option !== undefined
34
32
  ? { value: option,
35
33
  source: 'option' }
36
34
  : environment[variable] !== null
37
35
  ? { value: environment[variable],
38
36
  source: 'environment' }
39
- : { value: fallback,
40
- source: 'default' };
41
- const file = pick(overrides.file, 'RQ_LOG_FILE', 'stdout');
42
- return { version: packageVersion(),
37
+ : fromFile !== undefined
38
+ ? { value: fromFile,
39
+ source: 'rq.json' }
40
+ : { value: fallback,
41
+ source: 'default' };
42
+ const file = pick(overrides.file, 'RQ_LOG_FILE', configured.file, 'stdout');
43
+ return { version: packageVersion(import.meta.url),
43
44
  workingDir: io.cwd,
44
- configFile: folders.configFile,
45
+ configFile: folders.config.file,
45
46
  requirementsDir: folders.requirementsDir,
46
47
  documentsDir: folders.documentsDir,
47
- markdownPostProcessing: found?.config.markdownPostProcessing?.trim()
48
+ markdownPostProcessing: configString(folders.config, 'markdownPostProcessing')
49
+ ?.trim()
48
50
  || null,
49
51
  agent: { command: environment.RQ_AI_COMMAND,
50
52
  agents: [...AI_AGENTS] },
51
- log: { level: pick(overrides.level, 'RQ_LOG_LEVEL', file.source === 'default'
53
+ log: { level: pick(overrides.level, 'RQ_LOG_LEVEL', configured.level, file.source === 'default'
52
54
  ? 'silent'
53
55
  : 'information'),
54
56
  file,
55
- format: pick(overrides.format, 'RQ_LOG_FORMAT', 'auto') },
57
+ format: pick(overrides.format, 'RQ_LOG_FORMAT', configured.format, 'auto') },
56
58
  environment };
57
59
  }
58
60
  function describe(configuration) {
@@ -73,6 +75,3 @@ function describe(configuration) {
73
75
  ...Object.entries(configuration.environment).map(([name, value]) => ` ${name}=${value ?? ''}`)];
74
76
  return `${lines.join('\n')}\n`;
75
77
  }
76
- function packageVersion() {
77
- return createRequire(import.meta.url)('../package.json').version;
78
- }
@@ -41,8 +41,8 @@ environment:
41
41
  needs one.
42
42
  - `logLevel`, `logFile`, `logFormat` - the log settings, and where each comes
43
43
  from: `option` (`--loglevel`, `--logfile`, `--logformat`), `environment`
44
- (`RQ_LOG_LEVEL`, `RQ_LOG_FILE`, `RQ_LOG_FORMAT`) or `default`. See the
45
- repository's logging rules.
44
+ (`RQ_LOG_LEVEL`, `RQ_LOG_FILE`, `RQ_LOG_FORMAT`), `rq.json` (`logLevel`,
45
+ `logFile`, `logFormat`) or `default`. See the repository's logging rules.
46
46
  - `environment` - each variable `rq` reads, and its value, empty when it is not
47
47
  set.
48
48
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "asljs-rq-cli",
3
- "version": "0.1.2",
4
- "description": "The rq command line over asljs-rq: query, change, check, test and view requirements.",
3
+ "version": "0.1.3",
4
+ "description": "The rq command line over asljs-rq: query, change, check and test requirements.",
5
5
  "type": "module",
6
6
  "asljs": {
7
7
  "type": "app"
@@ -60,9 +60,8 @@
60
60
  "dependencies": {
61
61
  "asljs-agents": "^0.1.0",
62
62
  "asljs-logging": "^0.1.2",
63
- "asljs-mdcli": "^0.1.3",
63
+ "asljs-appbase": "^0.1.3",
64
64
  "asljs-rq": "^0.1.6",
65
- "asljs-rq-srv": "^0.1.1",
66
65
  "commander": "^15.0.0"
67
66
  },
68
67
  "devDependencies": {