@nemus-cli/nemus 0.5.0 → 0.9.0
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/CHANGELOG.md +46 -0
- package/README.md +52 -0
- package/dist/commands/config.js +195 -0
- package/dist/commands/reflect.js +141 -20
- package/dist/program.js +2 -0
- package/dist/utils/config-schema.js +145 -0
- package/dist/utils/config.js +4 -2
- package/dist/utils/editor.js +35 -0
- package/dist/utils/reflect.js +170 -1
- package/package.json +1 -1
- package/src/commands/config.ts +163 -0
- package/src/commands/reflect.ts +156 -20
- package/src/program.ts +2 -0
- package/src/utils/config-schema.test.ts +160 -0
- package/src/utils/config-schema.ts +177 -0
- package/src/utils/config.ts +4 -1
- package/src/utils/editor.test.ts +37 -0
- package/src/utils/editor.ts +48 -0
- package/src/utils/reflect.test.ts +129 -2
- package/src/utils/reflect.ts +201 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,52 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.0] - 2026-09-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`nemus config edit`** opens the config file in `$VISUAL`/`$EDITOR` (seeding
|
|
15
|
+
it with the current resolved config on first run) and re-validates the JSON
|
|
16
|
+
afterward, warning about unrecognized keys. Refuses to run without an
|
|
17
|
+
interactive terminal.
|
|
18
|
+
- **Environment-variable reference** in the README documenting every variable
|
|
19
|
+
Nemus reads (`NEMUS_*`, `WORKSPACE_*`, `NO_COLOR`/`FORCE_COLOR`,
|
|
20
|
+
`VISUAL`/`EDITOR`).
|
|
21
|
+
|
|
22
|
+
## [0.8.0] - 2026-09-01
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **`nemus reflect history`** and **`nemus reflect show [id]`** to review saved
|
|
27
|
+
reports (under `~/.nemus/reflect/`) without re-running the judge. `show`
|
|
28
|
+
defaults to the latest, accepts an id or id-prefix, and supports
|
|
29
|
+
`--json` / `--markdown` / `--group-by`.
|
|
30
|
+
- **`nemus reflect --group-by kind|priority`** (default `priority`) to control
|
|
31
|
+
how recommendations are grouped in both the human and Markdown output.
|
|
32
|
+
|
|
33
|
+
## [0.7.0] - 2026-09-01
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- **`nemus reflect --markdown`** — render the reflection report as clean,
|
|
38
|
+
severity-grouped Markdown to stdout (pipe into a file or an issue:
|
|
39
|
+
`nemus reflect --markdown > reflection.md`). Fenced example snippets are
|
|
40
|
+
escaped so they can't break out of their own code block.
|
|
41
|
+
- The human `reflect` report now prints a **severity summary line**
|
|
42
|
+
(e.g. `2 high · 1 medium`).
|
|
43
|
+
|
|
44
|
+
## [0.6.0] - 2026-09-01
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
|
|
48
|
+
- **`nemus config` command** for non-interactive configuration:
|
|
49
|
+
`config get [key]`, `set <key> <value>`, `unset <key>`, `list` (alias `ls`),
|
|
50
|
+
and `path`. Values are validated and coerced per field (booleans accept
|
|
51
|
+
`true/false/yes/no/on/off/1/0`; enums like `cloneProtocol` are checked), an
|
|
52
|
+
unknown key or invalid value exits non-zero with a clear message, and
|
|
53
|
+
`get`/`list` support `--json`. Complements the interactive `configure` wizard
|
|
54
|
+
and pairs well with `--quiet` for scripting.
|
|
55
|
+
|
|
10
56
|
## [0.5.0] - 2026-09-01
|
|
11
57
|
|
|
12
58
|
### Added
|
package/README.md
CHANGED
|
@@ -243,6 +243,42 @@ The workspace-scoped ones (`status`/`doctor`/`analyze-deps`) need an explicit
|
|
|
243
243
|
workspace name with `--json` (they never prompt). On failure, `--json` prints a
|
|
244
244
|
parseable `{ "ok": false, "error": … }` to stdout and exits non-zero.
|
|
245
245
|
|
|
246
|
+
### Configuration
|
|
247
|
+
|
|
248
|
+
Run `nemus configure` for the interactive wizard, or manage settings
|
|
249
|
+
non-interactively (handy for scripts and dotfiles):
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
nemus config list # all keys, values, and descriptions
|
|
253
|
+
nemus config get cloneProtocol # print one value (raw, for scripts)
|
|
254
|
+
nemus config set cloneProtocol https # validated + coerced per key
|
|
255
|
+
nemus config set autoReportBugs yes # booleans accept true/false/yes/no/on/off/1/0
|
|
256
|
+
nemus config unset githubOrg # reset a key to its default
|
|
257
|
+
nemus config path # print the config file location
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
`get`/`list` also accept `--json`. An unknown key or an invalid value exits
|
|
261
|
+
non-zero with a clear message (e.g. `cloneProtocol must be one of: ssh, https`).
|
|
262
|
+
`config edit` opens the file in `$VISUAL`/`$EDITOR` (seeding it with the current
|
|
263
|
+
resolved config first) and re-validates the JSON afterward.
|
|
264
|
+
|
|
265
|
+
### Environment variables
|
|
266
|
+
|
|
267
|
+
Everything Nemus reads from the environment (all optional):
|
|
268
|
+
|
|
269
|
+
| Variable | Effect |
|
|
270
|
+
| --- | --- |
|
|
271
|
+
| `NEMUS_DIR` | Override where workspaces are created (also `WORKSPACE_MANAGER_DIR`). |
|
|
272
|
+
| `NEMUS_CACHE_DIR` | Override the cache/config/state dir, default `~/.nemus` (also `WORKSPACE_MANAGER_CACHE_DIR`). |
|
|
273
|
+
| `NEMUS_JUDGE_MODEL` | Model for the `reflect` judge (overrides `--model`'s default). |
|
|
274
|
+
| `NEMUS_JUDGE_THINKING` | Thinking level for the `reflect` judge on pi (`off`…`max`). |
|
|
275
|
+
| `NEMUS_JUDGE_TIMEOUT_MS` | Timeout for the `reflect` judge call. |
|
|
276
|
+
| `NEMUS_BUG_REPORT_REPO` | Repo that `report-bug` files issues against. |
|
|
277
|
+
| `NEMUS_SKIP_CONFIGURE` | Skip the one-time post-install `configure` prompt. |
|
|
278
|
+
| `WORKSPACE_CLONE_TIMEOUT_MS` | Git clone timeout (default 15 min). |
|
|
279
|
+
| `NO_COLOR` / `FORCE_COLOR` | Disable / force ANSI color (see [Global flags](#global-flags)). |
|
|
280
|
+
| `VISUAL` / `EDITOR` | Editor launched by `nemus config edit`. |
|
|
281
|
+
|
|
246
282
|
### Global flags
|
|
247
283
|
|
|
248
284
|
- `--no-color` — disable ANSI color. Nemus also honors the standard
|
|
@@ -294,9 +330,25 @@ nemus snapshot restore <id> # (sr)
|
|
|
294
330
|
nemus reflect # (retro) analyze your last 10 workspaces' sessions
|
|
295
331
|
nemus reflect --limit 5 # narrow the window
|
|
296
332
|
nemus reflect --json # structured report for tooling
|
|
333
|
+
nemus reflect --markdown # Markdown report (grouped by severity) to paste/save
|
|
334
|
+
nemus reflect --group-by kind # group recommendations by kind instead of priority
|
|
297
335
|
nemus reflect --dry-run # show what the judge sees, without calling the agent
|
|
298
336
|
```
|
|
299
337
|
|
|
338
|
+
`--markdown` writes a clean, severity-grouped report to stdout — pipe it into a
|
|
339
|
+
file or an issue: `nemus reflect --markdown > reflection.md`. `--group-by
|
|
340
|
+
kind|priority` (default `priority`) controls how recommendations are grouped in
|
|
341
|
+
both the human and Markdown output.
|
|
342
|
+
|
|
343
|
+
Every run is saved under `~/.nemus/reflect/`. Review past reports without
|
|
344
|
+
re-running the judge:
|
|
345
|
+
|
|
346
|
+
```bash
|
|
347
|
+
nemus reflect history # list saved reports, newest first (--json)
|
|
348
|
+
nemus reflect show # print the latest saved report
|
|
349
|
+
nemus reflect show <id> # a specific one (--markdown / --json / --group-by)
|
|
350
|
+
```
|
|
351
|
+
|
|
300
352
|
`reflect` reads your recent agent **session transcripts** (Claude + pi), distills
|
|
301
353
|
the prompts you sent, the failures the agent hit, and the tools it used, then asks
|
|
302
354
|
**your own configured agent** (LLM-as-a-judge — no extra API key) to recommend
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.registerConfigCommand = registerConfigCommand;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const config_1 = require("../utils/config");
|
|
39
|
+
const editor_1 = require("../utils/editor");
|
|
40
|
+
const config_schema_1 = require("../utils/config-schema");
|
|
41
|
+
const output_1 = require("../utils/output");
|
|
42
|
+
const logger_1 = require("../utils/logger");
|
|
43
|
+
const colors_1 = require("../utils/colors");
|
|
44
|
+
/**
|
|
45
|
+
* Non-interactive config management: `nemus config get/set/unset/list/path`.
|
|
46
|
+
* Complements the interactive `configure` wizard and is script-friendly —
|
|
47
|
+
* `get`/`list` write DATA to stdout (raw value, or JSON with --json), logs go to
|
|
48
|
+
* stderr. Values are validated/coerced against config-schema.ts.
|
|
49
|
+
*/
|
|
50
|
+
function registerConfigCommand(parent) {
|
|
51
|
+
const config = parent.command('config').description('Get or set Nemus configuration');
|
|
52
|
+
config
|
|
53
|
+
.command('get')
|
|
54
|
+
.description('Print a config value (or all values with no key)')
|
|
55
|
+
.argument('[key]', 'Config key')
|
|
56
|
+
.option('--json', 'Output as JSON')
|
|
57
|
+
.action((key, opts) => {
|
|
58
|
+
const cfg = (0, config_1.getUserConfig)();
|
|
59
|
+
if (key === undefined) {
|
|
60
|
+
printAll(cfg, opts.json);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!(0, config_schema_1.isConfigKey)(key)) {
|
|
64
|
+
if (opts.json)
|
|
65
|
+
(0, output_1.outputJsonError)(`Unknown config key "${key}"`);
|
|
66
|
+
else
|
|
67
|
+
(0, logger_1.logError)(`Unknown config key "${key}". Run "nemus config list" to see valid keys.`);
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const value = cfg[key];
|
|
72
|
+
if (opts.json)
|
|
73
|
+
(0, output_1.outputJson)({ key, value });
|
|
74
|
+
else
|
|
75
|
+
process.stdout.write((0, config_schema_1.formatConfigValue)(value) + '\n');
|
|
76
|
+
});
|
|
77
|
+
config
|
|
78
|
+
.command('set')
|
|
79
|
+
.description('Set a config value')
|
|
80
|
+
.argument('<key>', 'Config key')
|
|
81
|
+
.argument('<value>', 'New value')
|
|
82
|
+
.option('--json', 'Output as JSON')
|
|
83
|
+
.action((key, value, opts) => {
|
|
84
|
+
const result = (0, config_schema_1.applyConfigSet)((0, config_1.getUserConfig)(), key, value);
|
|
85
|
+
if (!result.ok) {
|
|
86
|
+
if (opts.json)
|
|
87
|
+
(0, output_1.outputJsonError)(result.error);
|
|
88
|
+
else
|
|
89
|
+
(0, logger_1.logError)(result.error);
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
(0, config_1.saveUserConfig)(result.next);
|
|
94
|
+
if (opts.json)
|
|
95
|
+
(0, output_1.outputJson)({ ok: true, key, value: result.value });
|
|
96
|
+
else
|
|
97
|
+
(0, logger_1.logSuccess)(`Set ${(0, colors_1.colorize)(key, 'cyan')} = ${(0, config_schema_1.formatConfigValue)(result.value)}`);
|
|
98
|
+
});
|
|
99
|
+
config
|
|
100
|
+
.command('unset')
|
|
101
|
+
.description('Reset a config value to its default')
|
|
102
|
+
.argument('<key>', 'Config key')
|
|
103
|
+
.option('--json', 'Output as JSON')
|
|
104
|
+
.action((key, opts) => {
|
|
105
|
+
const result = (0, config_schema_1.applyConfigUnset)((0, config_1.getUserConfig)(), key);
|
|
106
|
+
if (!result.ok) {
|
|
107
|
+
if (opts.json)
|
|
108
|
+
(0, output_1.outputJsonError)(result.error);
|
|
109
|
+
else
|
|
110
|
+
(0, logger_1.logError)(result.error);
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
(0, config_1.saveUserConfig)(result.next);
|
|
115
|
+
if (opts.json)
|
|
116
|
+
(0, output_1.outputJson)({ ok: true, key, value: result.value });
|
|
117
|
+
else
|
|
118
|
+
(0, logger_1.logSuccess)(`Reset ${(0, colors_1.colorize)(key, 'cyan')} to default (${(0, config_schema_1.formatConfigValue)(result.value)})`);
|
|
119
|
+
});
|
|
120
|
+
config
|
|
121
|
+
.command('list')
|
|
122
|
+
.alias('ls')
|
|
123
|
+
.description('List all config keys and current values')
|
|
124
|
+
.option('--json', 'Output as JSON')
|
|
125
|
+
.action((opts) => printAll((0, config_1.getUserConfig)(), opts.json));
|
|
126
|
+
config
|
|
127
|
+
.command('path')
|
|
128
|
+
.description('Print the path to the config file')
|
|
129
|
+
.action(() => {
|
|
130
|
+
process.stdout.write(config_1.CONFIG_PATH + '\n');
|
|
131
|
+
});
|
|
132
|
+
config
|
|
133
|
+
.command('edit')
|
|
134
|
+
.description('Open the config file in $EDITOR (or $VISUAL)')
|
|
135
|
+
.action(() => {
|
|
136
|
+
handleEdit();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function handleEdit() {
|
|
140
|
+
if (!process.stdout.isTTY) {
|
|
141
|
+
(0, logger_1.logError)('`config edit` needs an interactive terminal. Use `config set <key> <value>` in scripts.');
|
|
142
|
+
process.exitCode = 1;
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Seed the file with the fully-resolved config so there's something complete
|
|
146
|
+
// to edit on a first run (getUserConfig merges defaults + any overrides).
|
|
147
|
+
if (!fs.existsSync(config_1.CONFIG_PATH))
|
|
148
|
+
(0, config_1.saveUserConfig)((0, config_1.getUserConfig)());
|
|
149
|
+
const result = (0, editor_1.openInEditor)(config_1.CONFIG_PATH);
|
|
150
|
+
if (!result.ok) {
|
|
151
|
+
(0, logger_1.logError)(result.error ?? `editor exited with code ${result.code}`);
|
|
152
|
+
process.exitCode = 1;
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Re-validate: a hand-edit can produce invalid JSON or invalid values, which
|
|
156
|
+
// getUserConfig would silently ignore (falling back to defaults). Surface that
|
|
157
|
+
// instead, using the SAME schema `config set` uses so both write paths agree.
|
|
158
|
+
const review = (0, config_schema_1.reviewConfigFileText)(fs.readFileSync(config_1.CONFIG_PATH, 'utf-8'));
|
|
159
|
+
if (review.parseError) {
|
|
160
|
+
(0, logger_1.logError)(`${config_1.CONFIG_PATH} is not valid JSON after editing — changes are kept, but Nemus will use defaults until it parses.`);
|
|
161
|
+
process.exitCode = 1;
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (review.notObject) {
|
|
165
|
+
(0, logger_1.logError)(`${config_1.CONFIG_PATH} must contain a JSON object — changes are kept, but Nemus will use defaults until it does.`);
|
|
166
|
+
process.exitCode = 1;
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
if (review.unknownKeys.length > 0)
|
|
170
|
+
(0, logger_1.logWarning)(`Ignoring unrecognized key(s): ${review.unknownKeys.join(', ')}`);
|
|
171
|
+
if (!review.ok) {
|
|
172
|
+
for (const e of review.invalid)
|
|
173
|
+
(0, logger_1.logWarning)(e);
|
|
174
|
+
(0, logger_1.logError)('Some values are invalid and will fall back to their defaults until fixed.');
|
|
175
|
+
process.exitCode = 1;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
(0, logger_1.logSuccess)('Config saved.');
|
|
179
|
+
}
|
|
180
|
+
function printAll(cfg, json) {
|
|
181
|
+
if (json) {
|
|
182
|
+
const values = {};
|
|
183
|
+
for (const key of config_schema_1.CONFIG_KEYS)
|
|
184
|
+
values[key] = cfg[key];
|
|
185
|
+
(0, output_1.outputJson)({ path: config_1.CONFIG_PATH, values });
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
const width = Math.max(...config_schema_1.CONFIG_KEYS.map((k) => k.length));
|
|
189
|
+
console.log((0, colors_1.colorize)('Nemus configuration', 'bright') + (0, colors_1.colorize)(` (${config_1.CONFIG_PATH})`, 'dim'));
|
|
190
|
+
for (const key of config_schema_1.CONFIG_KEYS) {
|
|
191
|
+
const val = (0, config_schema_1.formatConfigValue)(cfg[key]);
|
|
192
|
+
const shown = val === '' ? (0, colors_1.colorize)('(empty)', 'dim') : val;
|
|
193
|
+
console.log(` ${key.padEnd(width)} ${shown} ${(0, colors_1.colorize)(config_schema_1.CONFIG_SCHEMA[key].describe, 'dim')}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
package/dist/commands/reflect.js
CHANGED
|
@@ -11,22 +11,55 @@ function registerReflectCommand(parent) {
|
|
|
11
11
|
parent
|
|
12
12
|
.command('reflect')
|
|
13
13
|
.alias('retro')
|
|
14
|
-
.description('Analyze
|
|
14
|
+
.description('Analyze recent sessions for improvements, or review saved reports ("history"/"show")')
|
|
15
|
+
.argument('[subcommand]', '"history" or "show" — omit to run a new analysis')
|
|
16
|
+
.argument('[id]', 'report id when using "show" (default: latest)')
|
|
15
17
|
.option('-n, --limit <n>', 'How many recent workspaces to analyze', '10')
|
|
16
18
|
.option('-w, --workspace <name>', 'Analyze a single workspace by name (ignores --limit)')
|
|
17
19
|
.option('--model <model>', 'Judge model override (agent-native pattern/id)')
|
|
18
20
|
.option('--thinking <level>', 'Judge thinking level for pi: off|minimal|low|medium|high|xhigh|max')
|
|
19
21
|
.option('--json', 'Output the report as JSON')
|
|
22
|
+
.option('--markdown', 'Output the report as Markdown (paste into an issue/PR)')
|
|
23
|
+
.option('--group-by <how>', 'Group recommendations by: priority | kind', 'priority')
|
|
20
24
|
.option('--no-save', 'Do not save the report to ~/.nemus/reflect/')
|
|
21
25
|
.option('--dry-run', 'Print the assembled corpus + judge prompt without calling the agent')
|
|
22
|
-
.
|
|
23
|
-
|
|
26
|
+
.addHelpText('after', '\nSaved reports:\n nemus reflect history List saved reports (newest first)\n nemus reflect show [id] Print a saved report (default: latest)\n')
|
|
27
|
+
// history/show are positional (not commander subcommands) on purpose: as
|
|
28
|
+
// subcommands they would share --json/--markdown/--group-by with this parent
|
|
29
|
+
// command, and the only commander fix (enablePositionalOptions on the root)
|
|
30
|
+
// breaks global flags placed after a subcommand (e.g. `nemus list --quiet`).
|
|
31
|
+
.action(async (subcommand, id, opts) => {
|
|
32
|
+
if (subcommand === 'history')
|
|
33
|
+
return handleHistory(opts);
|
|
34
|
+
if (subcommand === 'show')
|
|
35
|
+
return handleShow(id, opts);
|
|
36
|
+
if (subcommand !== undefined) {
|
|
37
|
+
const msg = `Unknown reflect subcommand "${subcommand}" (expected "history" or "show").`;
|
|
38
|
+
if (opts.json)
|
|
39
|
+
(0, output_1.outputJsonError)(msg);
|
|
40
|
+
else
|
|
41
|
+
(0, logger_1.logError)(msg);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
return handleReflect(opts);
|
|
24
45
|
});
|
|
25
46
|
}
|
|
47
|
+
/** Validate --group-by; returns the value or exits non-zero with a clear error. */
|
|
48
|
+
function resolveGroupBy(raw, json) {
|
|
49
|
+
if (raw === undefined || raw === 'priority' || raw === 'kind')
|
|
50
|
+
return (raw ?? 'priority');
|
|
51
|
+
const msg = `--group-by must be "priority" or "kind"; got "${raw}"`;
|
|
52
|
+
if (json)
|
|
53
|
+
(0, output_1.outputJsonError)(msg);
|
|
54
|
+
else
|
|
55
|
+
(0, logger_1.logError)(msg);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
26
58
|
async function handleReflect(opts) {
|
|
27
59
|
const limit = Math.max(1, Number.parseInt(opts.limit ?? '10', 10) || 10);
|
|
60
|
+
const groupBy = resolveGroupBy(opts.groupBy, opts.json);
|
|
28
61
|
try {
|
|
29
|
-
const showProgress = !opts.json && !opts.dryRun;
|
|
62
|
+
const showProgress = !opts.json && !opts.markdown && !opts.dryRun;
|
|
30
63
|
if (showProgress) {
|
|
31
64
|
(0, logger_1.logStep)(opts.workspace
|
|
32
65
|
? `Analyzing workspace ${(0, colors_1.colorize)(opts.workspace, 'cyan')}…`
|
|
@@ -66,7 +99,7 @@ async function handleReflect(opts) {
|
|
|
66
99
|
const timeoutMs = Number.parseInt(process.env.NEMUS_JUDGE_TIMEOUT_MS ?? '', 10) || undefined;
|
|
67
100
|
const model = opts.model ?? process.env.NEMUS_JUDGE_MODEL ?? undefined;
|
|
68
101
|
const thinking = opts.thinking ?? process.env.NEMUS_JUDGE_THINKING ?? undefined;
|
|
69
|
-
const stopSpinner = opts.json
|
|
102
|
+
const stopSpinner = opts.json || opts.markdown
|
|
70
103
|
? () => { }
|
|
71
104
|
: startSpinner(`Judging ${withSessions} session(s) with your configured agent (this can take a minute)…`);
|
|
72
105
|
let parsed;
|
|
@@ -95,7 +128,20 @@ async function handleReflect(opts) {
|
|
|
95
128
|
(0, output_1.outputJson)({ analyzed: withSessions, workspaces: corpus.workspaces.length, ...report, savedTo });
|
|
96
129
|
return;
|
|
97
130
|
}
|
|
98
|
-
|
|
131
|
+
if (opts.markdown) {
|
|
132
|
+
// DATA channel: markdown to stdout, nothing else (a 'Saved report' note
|
|
133
|
+
// would corrupt a redirected .md file), so surface the path on stderr.
|
|
134
|
+
process.stdout.write((0, reflect_1.renderReportMarkdown)(report, {
|
|
135
|
+
analyzed: withSessions,
|
|
136
|
+
workspaces: corpus.workspaces.length,
|
|
137
|
+
workspace: opts.workspace,
|
|
138
|
+
generatedAt: new Date().toISOString(),
|
|
139
|
+
}, groupBy));
|
|
140
|
+
if (savedTo)
|
|
141
|
+
(0, logger_1.logInfo)(`Saved report to ${(0, colors_1.colorize)(savedTo, 'dim')}`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
printReport(report, corpus.workspaces.length, withSessions, groupBy);
|
|
99
145
|
if (savedTo)
|
|
100
146
|
(0, logger_1.logInfo)(`Saved report to ${(0, colors_1.colorize)(savedTo, 'dim')}`);
|
|
101
147
|
}
|
|
@@ -164,7 +210,7 @@ function priorityBadge(p) {
|
|
|
164
210
|
return (0, colors_1.colorize)('● med', 'yellow');
|
|
165
211
|
return (0, colors_1.colorize)('● low', 'gray');
|
|
166
212
|
}
|
|
167
|
-
function printReport(report, workspaces, analyzed) {
|
|
213
|
+
function printReport(report, workspaces, analyzed, groupBy = 'priority') {
|
|
168
214
|
console.log('');
|
|
169
215
|
console.log((0, colors_1.colorize)(' Reflection', 'bright') + (0, colors_1.colorize)(` (${analyzed} sessions across ${workspaces} workspaces)`, 'dim'));
|
|
170
216
|
console.log((0, colors_1.colorize)(' ' + '─'.repeat(56), 'dim'));
|
|
@@ -175,19 +221,94 @@ function printReport(report, workspaces, analyzed) {
|
|
|
175
221
|
console.log('\n ' + (0, colors_1.colorize)('No specific recommendations — looks solid.', 'green') + '\n');
|
|
176
222
|
return;
|
|
177
223
|
}
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
console.log(`
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
224
|
+
console.log('\n ' + (0, colors_1.colorize)((0, reflect_1.severitySummary)(report.recommendations), 'dim'));
|
|
225
|
+
for (const group of (0, reflect_1.groupRecommendations)(report.recommendations, groupBy)) {
|
|
226
|
+
console.log('\n ' + (0, colors_1.colorize)(group.heading, 'bright'));
|
|
227
|
+
for (const r of group.recs) {
|
|
228
|
+
const target = r.target ? (0, colors_1.colorize)(` [${r.target}]`, 'cyan') : '';
|
|
229
|
+
// Under a kind heading show the priority badge; under a priority heading
|
|
230
|
+
// show the kind label (the heading conveys the other axis).
|
|
231
|
+
const lead = groupBy === 'kind' ? priorityBadge(r.priority) : (0, colors_1.colorize)(KIND_LABEL[r.kind], 'bright');
|
|
232
|
+
console.log(` ${lead} ${r.title}${target}`);
|
|
233
|
+
if (r.detail)
|
|
234
|
+
console.log(` ${r.detail.replace(/\n/g, '\n ')}`);
|
|
235
|
+
if (r.example) {
|
|
236
|
+
console.log((0, colors_1.colorize)(' example:', 'dim'));
|
|
237
|
+
console.log((0, colors_1.colorize)(r.example.replace(/^/gm, ' '), 'dim'));
|
|
238
|
+
}
|
|
190
239
|
}
|
|
191
|
-
console.log('');
|
|
192
240
|
}
|
|
241
|
+
console.log('');
|
|
242
|
+
}
|
|
243
|
+
async function handleHistory(opts) {
|
|
244
|
+
const reports = await (0, reflect_1.listSavedReports)();
|
|
245
|
+
if (opts.json) {
|
|
246
|
+
(0, output_1.outputJson)({
|
|
247
|
+
count: reports.length,
|
|
248
|
+
reports: reports.map((r) => ({
|
|
249
|
+
id: r.id,
|
|
250
|
+
generatedAt: r.generatedAt,
|
|
251
|
+
analyzed: r.analyzed,
|
|
252
|
+
workspaces: r.workspaces,
|
|
253
|
+
workspace: r.workspace,
|
|
254
|
+
recommendations: r.report.recommendations.length,
|
|
255
|
+
severity: (0, reflect_1.severitySummary)(r.report.recommendations),
|
|
256
|
+
})),
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (reports.length === 0) {
|
|
261
|
+
(0, logger_1.logInfo)('No saved reflection reports yet. Run `nemus reflect` to create one.');
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log((0, colors_1.colorize)(' Saved reflections', 'bright') + (0, colors_1.colorize)(` (${reports.length})`, 'dim'));
|
|
266
|
+
console.log((0, colors_1.colorize)(' ' + '─'.repeat(56), 'dim'));
|
|
267
|
+
for (const r of reports) {
|
|
268
|
+
const when = r.generatedAt ? new Date(r.generatedAt).toLocaleString() : r.id;
|
|
269
|
+
const scope = r.workspace ? (0, colors_1.colorize)(` ${r.workspace}`, 'cyan') : (0, colors_1.colorize)(` ${r.analyzed} sessions`, 'dim');
|
|
270
|
+
const sev = r.report.recommendations.length
|
|
271
|
+
? (0, colors_1.colorize)(` ${(0, reflect_1.severitySummary)(r.report.recommendations)}`, 'dim')
|
|
272
|
+
: (0, colors_1.colorize)(' no recs', 'green');
|
|
273
|
+
console.log(` ${(0, colors_1.colorize)(r.id, 'bright')}${scope}${sev}`);
|
|
274
|
+
console.log((0, colors_1.colorize)(` ${when}`, 'dim'));
|
|
275
|
+
}
|
|
276
|
+
console.log('');
|
|
277
|
+
console.log((0, colors_1.colorize)(' nemus reflect show <id> (or `latest`)', 'dim'));
|
|
278
|
+
}
|
|
279
|
+
async function handleShow(id, opts) {
|
|
280
|
+
const groupBy = resolveGroupBy(opts.groupBy, opts.json);
|
|
281
|
+
const matches = (0, reflect_1.findSavedMatches)(await (0, reflect_1.listSavedReports)(), id);
|
|
282
|
+
const saved = matches[0];
|
|
283
|
+
if (!saved) {
|
|
284
|
+
const msg = id && id !== 'latest'
|
|
285
|
+
? `No saved report matching "${id}". Run "nemus reflect history" to list them.`
|
|
286
|
+
: 'No saved reflection reports yet. Run "nemus reflect" to create one.';
|
|
287
|
+
if (opts.json)
|
|
288
|
+
(0, output_1.outputJsonError)(msg);
|
|
289
|
+
else
|
|
290
|
+
(0, logger_1.logError)(msg);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
// An id-prefix that matches several reports resolves to the newest — say so
|
|
294
|
+
// (stderr only, so --json/--markdown stdout stays clean) rather than quietly
|
|
295
|
+
// showing a possibly-unintended report. An exact id / "latest" never multi-matches.
|
|
296
|
+
if (matches.length > 1 && !opts.json) {
|
|
297
|
+
(0, logger_1.logWarning)(`"${id}" matched ${matches.length} reports; showing the newest (${saved.id}). Use a longer id to disambiguate.`);
|
|
298
|
+
}
|
|
299
|
+
const meta = {
|
|
300
|
+
analyzed: saved.analyzed,
|
|
301
|
+
workspaces: saved.workspaces,
|
|
302
|
+
workspace: saved.workspace,
|
|
303
|
+
generatedAt: saved.generatedAt,
|
|
304
|
+
};
|
|
305
|
+
if (opts.json) {
|
|
306
|
+
(0, output_1.outputJson)({ id: saved.id, ...meta, ...saved.report });
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (opts.markdown) {
|
|
310
|
+
process.stdout.write((0, reflect_1.renderReportMarkdown)(saved.report, meta, groupBy));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
printReport(saved.report, saved.workspaces, saved.analyzed, groupBy);
|
|
193
314
|
}
|
package/dist/program.js
CHANGED
|
@@ -84,6 +84,7 @@ const archive_1 = require("./commands/archive");
|
|
|
84
84
|
const sessions_1 = require("./commands/sessions");
|
|
85
85
|
const generate_docs_1 = require("./commands/generate-docs");
|
|
86
86
|
const configure_1 = require("./commands/configure");
|
|
87
|
+
const config_1 = require("./commands/config");
|
|
87
88
|
const configure_claude_1 = require("./commands/configure-claude");
|
|
88
89
|
const ghq_status_1 = require("./commands/ghq-status");
|
|
89
90
|
const save_context_1 = require("./commands/save-context");
|
|
@@ -109,6 +110,7 @@ const reflect_1 = require("./commands/reflect");
|
|
|
109
110
|
(0, sessions_1.registerSessionsCommand)(exports.program);
|
|
110
111
|
(0, generate_docs_1.registerGenerateDocsCommand)(exports.program);
|
|
111
112
|
(0, configure_1.registerConfigureCommand)(exports.program);
|
|
113
|
+
(0, config_1.registerConfigCommand)(exports.program);
|
|
112
114
|
(0, configure_claude_1.registerConfigureClaudeCommand)(exports.program);
|
|
113
115
|
(0, ghq_status_1.registerGhqStatusCommand)(exports.program);
|
|
114
116
|
(0, save_context_1.registerSaveContextCommand)(exports.program);
|