@mmnto/cli 1.14.13 → 1.14.15
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/dist/assets/universal-lessons.d.ts +1 -1
- package/dist/assets/universal-lessons.d.ts.map +1 -1
- package/dist/assets/universal-lessons.js +1 -1
- package/dist/commands/compile-templates.d.ts +1 -1
- package/dist/commands/compile-templates.d.ts.map +1 -1
- package/dist/commands/compile-templates.js +29 -7
- package/dist/commands/compile-templates.js.map +1 -1
- package/dist/commands/init-templates.d.ts +2 -2
- package/dist/commands/init-templates.d.ts.map +1 -1
- package/dist/commands/init-templates.js +6 -8
- package/dist/commands/init-templates.js.map +1 -1
- package/dist/commands/shield.js +1 -1
- package/dist/commands/shield.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +2 -3
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
- package/dist/commands/audit-templates.d.ts +0 -16
- package/dist/commands/audit-templates.d.ts.map +0 -1
- package/dist/commands/audit-templates.js +0 -57
- package/dist/commands/audit-templates.js.map +0 -1
- package/dist/commands/audit.d.ts +0 -41
- package/dist/commands/audit.d.ts.map +0 -1
- package/dist/commands/audit.js +0 -359
- package/dist/commands/audit.js.map +0 -1
- package/dist/commands/audit.test.d.ts +0 -2
- package/dist/commands/audit.test.d.ts.map +0 -1
- package/dist/commands/audit.test.js +0 -268
- package/dist/commands/audit.test.js.map +0 -1
- package/dist/commands/bridge.d.ts +0 -7
- package/dist/commands/bridge.d.ts.map +0 -1
- package/dist/commands/bridge.js +0 -50
- package/dist/commands/bridge.js.map +0 -1
- package/dist/commands/bridge.test.d.ts +0 -2
- package/dist/commands/bridge.test.d.ts.map +0 -1
- package/dist/commands/bridge.test.js +0 -50
- package/dist/commands/bridge.test.js.map +0 -1
- package/dist/commands/briefing.d.ts +0 -18
- package/dist/commands/briefing.d.ts.map +0 -1
- package/dist/commands/briefing.js +0 -146
- package/dist/commands/briefing.js.map +0 -1
- package/dist/commands/briefing.test.d.ts +0 -2
- package/dist/commands/briefing.test.d.ts.map +0 -1
- package/dist/commands/briefing.test.js +0 -59
- package/dist/commands/briefing.test.js.map +0 -1
package/dist/commands/audit.js
DELETED
|
@@ -1,359 +0,0 @@
|
|
|
1
|
-
import * as crypto from 'node:crypto';
|
|
2
|
-
import * as fs from 'node:fs';
|
|
3
|
-
import * as os from 'node:os';
|
|
4
|
-
import * as path from 'node:path';
|
|
5
|
-
import { sanitize, TotemConfigError, TotemParseError } from '@mmnto/totem';
|
|
6
|
-
import { ghExec } from '../adapters/gh-utils.js';
|
|
7
|
-
import { log } from '../ui.js';
|
|
8
|
-
import { ACTION_LABELS, GH_ISSUE_LIMIT, MAX_STRATEGIC_CONTEXT_CHARS, STRATEGY_DIRS, STRATEGY_DOCS, SYSTEM_PROMPT, TAG, VALID_ACTIONS, VALID_TIERS, } from './audit-templates.js';
|
|
9
|
-
export { MAX_STRATEGIC_CONTEXT_CHARS } from './audit-templates.js';
|
|
10
|
-
// ─── Strategic context loading ──────────────────────────
|
|
11
|
-
export function loadStrategicDocs(cwd) {
|
|
12
|
-
const sections = [];
|
|
13
|
-
// Load root-level *.md files from each strategy directory (skip subdirs)
|
|
14
|
-
for (const rel of STRATEGY_DIRS) {
|
|
15
|
-
const dir = path.join(cwd, rel);
|
|
16
|
-
if (!fs.existsSync(dir))
|
|
17
|
-
continue;
|
|
18
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
19
|
-
for (const entry of entries) {
|
|
20
|
-
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
21
|
-
const content = fs.readFileSync(path.join(dir, entry.name), 'utf-8');
|
|
22
|
-
sections.push(`### ${entry.name}\n${content}`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// Load individual strategic docs (roadmap, active_work, etc.)
|
|
27
|
-
for (const rel of STRATEGY_DOCS) {
|
|
28
|
-
const docPath = path.join(cwd, rel);
|
|
29
|
-
if (!fs.existsSync(docPath))
|
|
30
|
-
continue;
|
|
31
|
-
const content = fs.readFileSync(docPath, 'utf-8');
|
|
32
|
-
const name = path.basename(docPath);
|
|
33
|
-
sections.push(`### ${name}\n${content}`);
|
|
34
|
-
}
|
|
35
|
-
if (sections.length === 0) {
|
|
36
|
-
log.dim(TAG, 'No strategic context files found — skipping.');
|
|
37
|
-
return '';
|
|
38
|
-
}
|
|
39
|
-
const combined = sections.join('\n\n---\n\n');
|
|
40
|
-
if (combined.length > MAX_STRATEGIC_CONTEXT_CHARS) {
|
|
41
|
-
log.warn(TAG, `Strategic context truncated from ${(combined.length / 1024).toFixed(0)}KB to ${(MAX_STRATEGIC_CONTEXT_CHARS / 1024).toFixed(0)}KB to stay within LLM limits.`);
|
|
42
|
-
return combined.slice(0, MAX_STRATEGIC_CONTEXT_CHARS);
|
|
43
|
-
}
|
|
44
|
-
return combined;
|
|
45
|
-
}
|
|
46
|
-
// ─── Response parsing ───────────────────────────────────
|
|
47
|
-
export function parseAuditResponse(content) {
|
|
48
|
-
const match = content.match(/<audit_proposals>([\s\S]*?)<\/audit_proposals>/);
|
|
49
|
-
if (!match) {
|
|
50
|
-
throw new TotemParseError('LLM response missing <audit_proposals> wrapper.', 'Re-run the command or check the audit prompt template.');
|
|
51
|
-
}
|
|
52
|
-
let parsed;
|
|
53
|
-
try {
|
|
54
|
-
parsed = JSON.parse(match[1]);
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
throw new TotemParseError('Failed to parse audit proposals as JSON.', 'Re-run the command. The LLM may have produced malformed output.');
|
|
58
|
-
}
|
|
59
|
-
if (!Array.isArray(parsed)) {
|
|
60
|
-
throw new TotemParseError('Audit proposals must be a JSON array.', 'Re-run the command. The LLM returned an object instead of an array.');
|
|
61
|
-
}
|
|
62
|
-
return parsed.map((item, i) => {
|
|
63
|
-
if (typeof item.number !== 'number') {
|
|
64
|
-
throw new TotemParseError(`Invalid or missing "number" for proposal ${i}.`, 'Re-run the command. Each proposal must include a numeric "number" field.');
|
|
65
|
-
}
|
|
66
|
-
const action = String(item.action ?? '').toUpperCase();
|
|
67
|
-
if (!VALID_ACTIONS.includes(action)) {
|
|
68
|
-
throw new TotemParseError(`Invalid action "${item.action}" for proposal ${i}. Must be one of: ${VALID_ACTIONS.join(', ')}`, 'Re-run the command. The LLM produced an unrecognized action value.');
|
|
69
|
-
}
|
|
70
|
-
if (action === 'MERGE' && typeof item.mergeInto !== 'number') {
|
|
71
|
-
throw new TotemParseError(`Invalid or missing "mergeInto" for MERGE proposal ${i}.`, 'Re-run the command. MERGE proposals must include a numeric "mergeInto" field.');
|
|
72
|
-
}
|
|
73
|
-
if (action === 'REPRIORITIZE' &&
|
|
74
|
-
(typeof item.newTier !== 'string' || !VALID_TIERS.includes(item.newTier))) {
|
|
75
|
-
throw new TotemParseError(`Invalid "newTier" for REPRIORITIZE proposal ${i}. Must be one of: ${VALID_TIERS.join(', ')}.`, 'Re-run the command. REPRIORITIZE proposals must include a valid "newTier" value.');
|
|
76
|
-
}
|
|
77
|
-
return {
|
|
78
|
-
number: item.number,
|
|
79
|
-
title: String(item.title ?? ''),
|
|
80
|
-
action,
|
|
81
|
-
newTier: item.newTier,
|
|
82
|
-
mergeInto: item.mergeInto,
|
|
83
|
-
rationale: String(item.rationale ?? ''),
|
|
84
|
-
};
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
// ─── Proposal display ───────────────────────────────────
|
|
88
|
-
export function formatProposalTable(proposals) {
|
|
89
|
-
const rows = proposals.map((p) => {
|
|
90
|
-
let detail = '';
|
|
91
|
-
if (p.action === 'REPRIORITIZE' && p.newTier)
|
|
92
|
-
detail = ` → ${p.newTier}`;
|
|
93
|
-
if (p.action === 'MERGE' && p.mergeInto)
|
|
94
|
-
detail = ` → #${p.mergeInto}`;
|
|
95
|
-
return `| #${p.number} | ${sanitize(p.title)} | ${ACTION_LABELS[p.action]}${detail} | ${sanitize(p.rationale)} |`;
|
|
96
|
-
});
|
|
97
|
-
return ['| Issue | Title | Action | Rationale |', '|---|---|---|---|', ...rows].join('\n');
|
|
98
|
-
}
|
|
99
|
-
// ─── Interactive approval ───────────────────────────────
|
|
100
|
-
/** Filter proposals to only actionable ones (not KEEP). */
|
|
101
|
-
function getActionableProposals(proposals) {
|
|
102
|
-
return proposals.filter((p) => p.action !== 'KEEP');
|
|
103
|
-
}
|
|
104
|
-
export async function selectProposals(proposals, opts) {
|
|
105
|
-
const { isCancel, multiselect } = await import('@clack/prompts');
|
|
106
|
-
const actionable = getActionableProposals(proposals);
|
|
107
|
-
if (actionable.length === 0) {
|
|
108
|
-
log.info(TAG, 'No actionable proposals (all KEEP). Nothing to execute.');
|
|
109
|
-
return [];
|
|
110
|
-
}
|
|
111
|
-
if (opts.yes) {
|
|
112
|
-
return actionable;
|
|
113
|
-
}
|
|
114
|
-
if (!opts.isTTY) {
|
|
115
|
-
throw new TotemConfigError('Refusing to modify issues in non-interactive mode.', 'Use --yes to bypass confirmation, or run in an interactive terminal.', 'CONFIG_INVALID');
|
|
116
|
-
}
|
|
117
|
-
const result = await multiselect({
|
|
118
|
-
message: `Select proposals to execute (${actionable.length} actionable):`,
|
|
119
|
-
options: actionable.map((p, i) => {
|
|
120
|
-
let label = `#${p.number} — ${ACTION_LABELS[p.action]}`;
|
|
121
|
-
if (p.action === 'REPRIORITIZE' && p.newTier)
|
|
122
|
-
label += ` → ${p.newTier}`;
|
|
123
|
-
if (p.action === 'MERGE' && p.mergeInto)
|
|
124
|
-
label += ` → #${p.mergeInto}`;
|
|
125
|
-
return {
|
|
126
|
-
value: i,
|
|
127
|
-
label,
|
|
128
|
-
hint: sanitize(p.rationale),
|
|
129
|
-
};
|
|
130
|
-
}),
|
|
131
|
-
initialValues: actionable.map((_, i) => i),
|
|
132
|
-
required: false,
|
|
133
|
-
});
|
|
134
|
-
if (isCancel(result)) {
|
|
135
|
-
log.warn(TAG, 'Cancelled. No changes made.');
|
|
136
|
-
return [];
|
|
137
|
-
}
|
|
138
|
-
return result.map((i) => actionable[i]);
|
|
139
|
-
}
|
|
140
|
-
// ─── Execution ──────────────────────────────────────────
|
|
141
|
-
/**
|
|
142
|
-
* Post a comment on an issue using --body-file to avoid shell injection.
|
|
143
|
-
* LLM-generated rationale text could contain shell metacharacters; writing
|
|
144
|
-
* to a temp file and using --body-file sidesteps this entirely.
|
|
145
|
-
*/
|
|
146
|
-
function ghComment(issueNumber, body, cwd) {
|
|
147
|
-
const tmpFile = path.join(os.tmpdir(), `totem-audit-comment-${crypto.randomUUID()}.md`); // totem-ignore — ephemeral temp file, deleted in finally block
|
|
148
|
-
try {
|
|
149
|
-
fs.writeFileSync(tmpFile, body, 'utf-8');
|
|
150
|
-
ghExec(['issue', 'comment', String(issueNumber), '--body-file', tmpFile], cwd);
|
|
151
|
-
}
|
|
152
|
-
finally {
|
|
153
|
-
try {
|
|
154
|
-
fs.unlinkSync(tmpFile);
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
// Best-effort cleanup
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* Validate that all MERGE targets reference issues that exist in the backlog.
|
|
163
|
-
*/
|
|
164
|
-
export function validateMergeTargets(proposals, issueNumbers) {
|
|
165
|
-
const errors = [];
|
|
166
|
-
for (const p of proposals) {
|
|
167
|
-
if (p.action === 'MERGE' && p.mergeInto && !issueNumbers.has(p.mergeInto)) {
|
|
168
|
-
errors.push(`#${p.number} proposes MERGE into #${p.mergeInto} which is not in the backlog`);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return errors;
|
|
172
|
-
}
|
|
173
|
-
export function executeProposals(proposals, cwd) {
|
|
174
|
-
const result = { succeeded: 0, failed: 0, errors: [] };
|
|
175
|
-
for (const p of proposals) {
|
|
176
|
-
try {
|
|
177
|
-
switch (p.action) {
|
|
178
|
-
case 'CLOSE': {
|
|
179
|
-
ghComment(p.number, `Closed via \`totem audit\` — ${p.rationale}`, cwd);
|
|
180
|
-
ghExec(['issue', 'close', String(p.number)], cwd);
|
|
181
|
-
log.success(TAG, `Closed #${p.number}`);
|
|
182
|
-
result.succeeded++;
|
|
183
|
-
break;
|
|
184
|
-
}
|
|
185
|
-
case 'REPRIORITIZE': {
|
|
186
|
-
if (!p.newTier) {
|
|
187
|
-
const msg = `REPRIORITIZE proposal for #${p.number} missing newTier`;
|
|
188
|
-
log.warn(TAG, msg);
|
|
189
|
-
result.failed++;
|
|
190
|
-
result.errors.push(msg);
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
const removals = ['tier-1', 'tier-2', 'tier-3']
|
|
194
|
-
.filter((t) => t !== p.newTier)
|
|
195
|
-
.flatMap((t) => ['--remove-label', t]);
|
|
196
|
-
ghExec(['issue', 'edit', String(p.number), '--add-label', p.newTier, ...removals], cwd);
|
|
197
|
-
ghComment(p.number, `Reprioritized to ${p.newTier} via \`totem audit\` — ${p.rationale}`, cwd);
|
|
198
|
-
log.success(TAG, `Reprioritized #${p.number} → ${p.newTier}`);
|
|
199
|
-
result.succeeded++;
|
|
200
|
-
break;
|
|
201
|
-
}
|
|
202
|
-
case 'MERGE': {
|
|
203
|
-
if (!p.mergeInto) {
|
|
204
|
-
const msg = `MERGE proposal for #${p.number} missing mergeInto`;
|
|
205
|
-
log.warn(TAG, msg);
|
|
206
|
-
result.failed++;
|
|
207
|
-
result.errors.push(msg);
|
|
208
|
-
break;
|
|
209
|
-
}
|
|
210
|
-
ghComment(p.number, `Merged into #${p.mergeInto} via \`totem audit\` — ${p.rationale}`, cwd);
|
|
211
|
-
ghExec(['issue', 'close', String(p.number), '--reason', 'not planned'], cwd);
|
|
212
|
-
ghComment(p.mergeInto, `#${p.number} merged into this issue via \`totem audit\`.`, cwd);
|
|
213
|
-
log.success(TAG, `Merged #${p.number} → #${p.mergeInto}`);
|
|
214
|
-
result.succeeded++;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
case 'KEEP':
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
catch (err) {
|
|
222
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
223
|
-
log.error('Totem Error', `Failed to execute proposal for #${p.number}: ${sanitize(msg)}`); // totem-ignore — sanitize wraps error
|
|
224
|
-
result.failed++;
|
|
225
|
-
result.errors.push(`#${p.number}: ${msg}`);
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return result;
|
|
229
|
-
}
|
|
230
|
-
// ─── Prompt assembly ────────────────────────────────────
|
|
231
|
-
export function formatIssueInventory(issues) {
|
|
232
|
-
const rows = issues.map((i) => {
|
|
233
|
-
const labels = i.labels.join(', ') || '(none)';
|
|
234
|
-
const updated = i.updatedAt.slice(0, 10);
|
|
235
|
-
const id = i.repo ? `${i.repo}#${i.number}` : `#${i.number}`;
|
|
236
|
-
return `| ${id} | ${i.title} | ${labels} | ${updated} |`;
|
|
237
|
-
});
|
|
238
|
-
return ['| Issue | Title | Labels | Updated |', '|---|---|---|---|', ...rows].join('\n');
|
|
239
|
-
}
|
|
240
|
-
function assemblePrompt(issues, strategicContext, systemPrompt, wrapXml, userContext) {
|
|
241
|
-
const sections = [systemPrompt];
|
|
242
|
-
// Issue inventory
|
|
243
|
-
sections.push('=== OPEN ISSUES ===');
|
|
244
|
-
sections.push(`Total: ${issues.length} open issues\n`);
|
|
245
|
-
sections.push(wrapXml('issue_list', formatIssueInventory(issues)));
|
|
246
|
-
// Strategic context
|
|
247
|
-
if (strategicContext) {
|
|
248
|
-
sections.push('\n=== STRATEGIC CONTEXT ===');
|
|
249
|
-
sections.push(wrapXml('strategic_docs', strategicContext));
|
|
250
|
-
}
|
|
251
|
-
// User-supplied context (--context flag)
|
|
252
|
-
if (userContext) {
|
|
253
|
-
sections.push('\n=== AUDIT LENS ===');
|
|
254
|
-
sections.push('The user has provided the following strategic lens for this audit. Weight your proposals accordingly:');
|
|
255
|
-
sections.push(wrapXml('user_context', userContext));
|
|
256
|
-
}
|
|
257
|
-
return sections.join('\n');
|
|
258
|
-
}
|
|
259
|
-
// ─── Main command ───────────────────────────────────────
|
|
260
|
-
export async function auditCommand(options) {
|
|
261
|
-
const { getSystemPrompt, loadConfig, loadEnv, resolveConfigPath, runOrchestrator, wrapXml, writeOutput, } = await import('../utils.js');
|
|
262
|
-
const cwd = process.cwd();
|
|
263
|
-
const configPath = resolveConfigPath(cwd);
|
|
264
|
-
loadEnv(cwd);
|
|
265
|
-
const config = await loadConfig(configPath);
|
|
266
|
-
// Fetch open issues
|
|
267
|
-
log.info(TAG, 'Fetching open issues...');
|
|
268
|
-
const { createIssueAdapter } = await import('../adapters/create-issue-adapter.js');
|
|
269
|
-
const adapter = await createIssueAdapter(cwd, config);
|
|
270
|
-
const issues = adapter.fetchOpenIssues(GH_ISSUE_LIMIT);
|
|
271
|
-
if (issues.length === 0) {
|
|
272
|
-
log.warn(TAG, 'No open issues found. Nothing to audit.');
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
log.info(TAG, `Found ${issues.length} open issues.`);
|
|
276
|
-
// Load strategic context
|
|
277
|
-
log.info(TAG, 'Loading strategic context...');
|
|
278
|
-
const strategicContext = loadStrategicDocs(cwd);
|
|
279
|
-
log.dim(TAG, `Strategic context: ${(strategicContext.length / 1024).toFixed(0)}KB`);
|
|
280
|
-
// Resolve system prompt (allow .totem/prompts/audit.md override)
|
|
281
|
-
const systemPrompt = getSystemPrompt('audit', SYSTEM_PROMPT, cwd, config.totemDir);
|
|
282
|
-
// Assemble prompt
|
|
283
|
-
const prompt = assemblePrompt(issues, strategicContext, systemPrompt, wrapXml, options.context);
|
|
284
|
-
log.dim(TAG, `Prompt: ${(prompt.length / 1024).toFixed(0)}KB`);
|
|
285
|
-
const content = await runOrchestrator({
|
|
286
|
-
prompt,
|
|
287
|
-
tag: TAG,
|
|
288
|
-
options,
|
|
289
|
-
config,
|
|
290
|
-
cwd,
|
|
291
|
-
totalResults: issues.length,
|
|
292
|
-
});
|
|
293
|
-
if (content == null)
|
|
294
|
-
return;
|
|
295
|
-
// --raw or --out without interactive: just write and exit
|
|
296
|
-
if (options.out && !options.dryRun) {
|
|
297
|
-
writeOutput(content, options.out);
|
|
298
|
-
log.success(TAG, `Written to ${options.out}`);
|
|
299
|
-
}
|
|
300
|
-
// Parse proposals
|
|
301
|
-
let proposals;
|
|
302
|
-
try {
|
|
303
|
-
proposals = parseAuditResponse(content);
|
|
304
|
-
}
|
|
305
|
-
catch (err) {
|
|
306
|
-
// If parsing fails, output raw content so user can see what the LLM produced
|
|
307
|
-
writeOutput(content);
|
|
308
|
-
throw err;
|
|
309
|
-
}
|
|
310
|
-
log.info(TAG, `${proposals.length} proposals parsed.`);
|
|
311
|
-
// Display table
|
|
312
|
-
const table = formatProposalTable(proposals);
|
|
313
|
-
writeOutput(`\n${table}\n`);
|
|
314
|
-
const actionable = getActionableProposals(proposals);
|
|
315
|
-
const closes = proposals.filter((p) => p.action === 'CLOSE').length;
|
|
316
|
-
const repris = proposals.filter((p) => p.action === 'REPRIORITIZE').length;
|
|
317
|
-
const merges = proposals.filter((p) => p.action === 'MERGE').length;
|
|
318
|
-
const keeps = proposals.filter((p) => p.action === 'KEEP').length;
|
|
319
|
-
log.info(TAG, `Summary: ${keeps} KEEP, ${closes} CLOSE, ${repris} REPRIORITIZE, ${merges} MERGE`);
|
|
320
|
-
// Dry-run: stop here
|
|
321
|
-
if (options.dryRun) {
|
|
322
|
-
log.info(TAG, 'Dry run — no changes made.');
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
if (actionable.length === 0) {
|
|
326
|
-
log.info(TAG, 'No actionable proposals. Backlog looks healthy.');
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
// Interactive approval
|
|
330
|
-
let selected = await selectProposals(proposals, {
|
|
331
|
-
yes: options.yes,
|
|
332
|
-
isTTY: !!process.stdin.isTTY,
|
|
333
|
-
});
|
|
334
|
-
if (selected.length === 0) {
|
|
335
|
-
log.info(TAG, 'No proposals selected. No changes made.');
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
// Validate merge targets — filter out invalid ones
|
|
339
|
-
const issueNumbers = new Set(issues.map((i) => i.number));
|
|
340
|
-
const mergeErrors = validateMergeTargets(selected, issueNumbers);
|
|
341
|
-
if (mergeErrors.length > 0) {
|
|
342
|
-
for (const err of mergeErrors) {
|
|
343
|
-
log.warn(TAG, `Invalid merge target: ${err}`); // totem-ignore — err is our own validation string, not an Error object
|
|
344
|
-
}
|
|
345
|
-
selected = selected.filter((p) => !(p.action === 'MERGE' && p.mergeInto && !issueNumbers.has(p.mergeInto)));
|
|
346
|
-
if (selected.length === 0) {
|
|
347
|
-
log.info(TAG, 'No valid proposals remaining after filtering. No changes made.');
|
|
348
|
-
return;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
// Execute
|
|
352
|
-
log.info(TAG, `Executing ${selected.length} proposal(s)...`);
|
|
353
|
-
const result = executeProposals(selected, cwd);
|
|
354
|
-
if (result.failed > 0) {
|
|
355
|
-
log.warn(TAG, `${result.failed} proposal(s) failed. See errors above.`); // totem-ignore — result is our own counter
|
|
356
|
-
}
|
|
357
|
-
log.success(TAG, `Done — ${result.succeeded} issue(s) updated.`); // totem-ignore — result is our own counter
|
|
358
|
-
}
|
|
359
|
-
//# sourceMappingURL=audit.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"audit.test.d.ts","sourceRoot":"","sources":["../../src/commands/audit.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as os from 'node:os';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import { cleanTmpDir } from '../test-utils.js';
|
|
6
|
-
import { executeProposals, formatProposalTable, loadStrategicDocs, MAX_STRATEGIC_CONTEXT_CHARS, parseAuditResponse, selectProposals, validateMergeTargets, } from './audit.js';
|
|
7
|
-
// ─── parseAuditResponse ─────────────────────────────────
|
|
8
|
-
describe('parseAuditResponse', () => {
|
|
9
|
-
it('parses valid proposals from XML-wrapped JSON', () => {
|
|
10
|
-
const content = `Here are my proposals:
|
|
11
|
-
<audit_proposals>
|
|
12
|
-
[
|
|
13
|
-
{ "number": 42, "title": "Widget support", "action": "KEEP", "rationale": "Aligns with roadmap." },
|
|
14
|
-
{ "number": 99, "title": "Legacy auth", "action": "CLOSE", "rationale": "Superseded by #150." }
|
|
15
|
-
]
|
|
16
|
-
</audit_proposals>`;
|
|
17
|
-
const proposals = parseAuditResponse(content);
|
|
18
|
-
expect(proposals).toHaveLength(2);
|
|
19
|
-
expect(proposals[0]).toEqual({
|
|
20
|
-
number: 42,
|
|
21
|
-
title: 'Widget support',
|
|
22
|
-
action: 'KEEP',
|
|
23
|
-
newTier: undefined,
|
|
24
|
-
mergeInto: undefined,
|
|
25
|
-
rationale: 'Aligns with roadmap.',
|
|
26
|
-
});
|
|
27
|
-
expect(proposals[1].action).toBe('CLOSE');
|
|
28
|
-
});
|
|
29
|
-
it('parses REPRIORITIZE with newTier', () => {
|
|
30
|
-
const content = `<audit_proposals>
|
|
31
|
-
[{ "number": 55, "title": "Perf", "action": "REPRIORITIZE", "newTier": "tier-3", "rationale": "Defer." }]
|
|
32
|
-
</audit_proposals>`;
|
|
33
|
-
const proposals = parseAuditResponse(content);
|
|
34
|
-
expect(proposals[0].newTier).toBe('tier-3');
|
|
35
|
-
});
|
|
36
|
-
it('parses MERGE with mergeInto', () => {
|
|
37
|
-
const content = `<audit_proposals>
|
|
38
|
-
[{ "number": 88, "title": "Colors", "action": "MERGE", "mergeInto": 42, "rationale": "Subset of #42." }]
|
|
39
|
-
</audit_proposals>`;
|
|
40
|
-
const proposals = parseAuditResponse(content);
|
|
41
|
-
expect(proposals[0].mergeInto).toBe(42);
|
|
42
|
-
});
|
|
43
|
-
it('normalizes action case', () => {
|
|
44
|
-
const content = `<audit_proposals>
|
|
45
|
-
[{ "number": 1, "title": "Test", "action": "close", "rationale": "Done." }]
|
|
46
|
-
</audit_proposals>`;
|
|
47
|
-
const proposals = parseAuditResponse(content);
|
|
48
|
-
expect(proposals[0].action).toBe('CLOSE');
|
|
49
|
-
});
|
|
50
|
-
it('throws on missing XML wrapper', () => {
|
|
51
|
-
expect(() => parseAuditResponse('Just some text without tags')).toThrow('missing <audit_proposals> wrapper');
|
|
52
|
-
});
|
|
53
|
-
it('throws on invalid JSON inside tags', () => {
|
|
54
|
-
const content = '<audit_proposals>not json</audit_proposals>';
|
|
55
|
-
expect(() => parseAuditResponse(content)).toThrow('Failed to parse');
|
|
56
|
-
});
|
|
57
|
-
it('throws on non-array JSON', () => {
|
|
58
|
-
const content = '<audit_proposals>{"number": 1}</audit_proposals>';
|
|
59
|
-
expect(() => parseAuditResponse(content)).toThrow('must be a JSON array');
|
|
60
|
-
});
|
|
61
|
-
it('throws on invalid action', () => {
|
|
62
|
-
const content = `<audit_proposals>
|
|
63
|
-
[{ "number": 1, "title": "Test", "action": "DELETE", "rationale": "Bad." }]
|
|
64
|
-
</audit_proposals>`;
|
|
65
|
-
expect(() => parseAuditResponse(content)).toThrow('Invalid action "DELETE"');
|
|
66
|
-
});
|
|
67
|
-
it('throws on missing issue number', () => {
|
|
68
|
-
const content = `<audit_proposals>
|
|
69
|
-
[{ "title": "Test", "action": "KEEP", "rationale": "Good." }]
|
|
70
|
-
</audit_proposals>`;
|
|
71
|
-
expect(() => parseAuditResponse(content)).toThrow('Invalid or missing "number"');
|
|
72
|
-
});
|
|
73
|
-
it('throws on MERGE without mergeInto', () => {
|
|
74
|
-
const content = `<audit_proposals>
|
|
75
|
-
[{ "number": 88, "title": "Colors", "action": "MERGE", "rationale": "Subset." }]
|
|
76
|
-
</audit_proposals>`;
|
|
77
|
-
expect(() => parseAuditResponse(content)).toThrow('Invalid or missing "mergeInto"');
|
|
78
|
-
});
|
|
79
|
-
it('throws on REPRIORITIZE with invalid tier', () => {
|
|
80
|
-
const content = `<audit_proposals>
|
|
81
|
-
[{ "number": 55, "title": "Perf", "action": "REPRIORITIZE", "newTier": "urgent", "rationale": "Defer." }]
|
|
82
|
-
</audit_proposals>`;
|
|
83
|
-
expect(() => parseAuditResponse(content)).toThrow('Invalid "newTier"');
|
|
84
|
-
});
|
|
85
|
-
it('throws on REPRIORITIZE without newTier', () => {
|
|
86
|
-
const content = `<audit_proposals>
|
|
87
|
-
[{ "number": 55, "title": "Perf", "action": "REPRIORITIZE", "rationale": "Defer." }]
|
|
88
|
-
</audit_proposals>`;
|
|
89
|
-
expect(() => parseAuditResponse(content)).toThrow('Invalid "newTier"');
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
// ─── formatProposalTable ────────────────────────────────
|
|
93
|
-
describe('formatProposalTable', () => {
|
|
94
|
-
const proposals = [
|
|
95
|
-
{ number: 42, title: 'Widget support', action: 'KEEP', rationale: 'Good.' },
|
|
96
|
-
{
|
|
97
|
-
number: 55,
|
|
98
|
-
title: 'Perf',
|
|
99
|
-
action: 'REPRIORITIZE',
|
|
100
|
-
newTier: 'tier-3',
|
|
101
|
-
rationale: 'Defer.',
|
|
102
|
-
},
|
|
103
|
-
{ number: 88, title: 'Colors', action: 'MERGE', mergeInto: 42, rationale: 'Subset.' },
|
|
104
|
-
{ number: 99, title: 'Legacy', action: 'CLOSE', rationale: 'Obsolete.' },
|
|
105
|
-
];
|
|
106
|
-
it('generates a markdown table with all proposals', () => {
|
|
107
|
-
const table = formatProposalTable(proposals);
|
|
108
|
-
expect(table).toContain('| Issue | Title | Action | Rationale |');
|
|
109
|
-
expect(table).toContain('#42');
|
|
110
|
-
expect(table).toContain('#99');
|
|
111
|
-
});
|
|
112
|
-
it('shows tier for REPRIORITIZE', () => {
|
|
113
|
-
const table = formatProposalTable(proposals);
|
|
114
|
-
expect(table).toContain('REPRI → tier-3');
|
|
115
|
-
});
|
|
116
|
-
it('shows merge target for MERGE', () => {
|
|
117
|
-
const table = formatProposalTable(proposals);
|
|
118
|
-
expect(table).toContain('MERGE → #42');
|
|
119
|
-
});
|
|
120
|
-
it('handles empty array', () => {
|
|
121
|
-
const table = formatProposalTable([]);
|
|
122
|
-
const lines = table.split('\n');
|
|
123
|
-
expect(lines).toHaveLength(2); // header + separator only
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
// ─── validateMergeTargets ───────────────────────────────
|
|
127
|
-
describe('validateMergeTargets', () => {
|
|
128
|
-
it('returns empty array when all merge targets are valid', () => {
|
|
129
|
-
const proposals = [
|
|
130
|
-
{ number: 88, title: 'Colors', action: 'MERGE', mergeInto: 42, rationale: 'Subset.' },
|
|
131
|
-
];
|
|
132
|
-
const errors = validateMergeTargets(proposals, new Set([42, 88, 99]));
|
|
133
|
-
expect(errors).toHaveLength(0);
|
|
134
|
-
});
|
|
135
|
-
it('returns errors for invalid merge targets', () => {
|
|
136
|
-
const proposals = [
|
|
137
|
-
{ number: 88, title: 'Colors', action: 'MERGE', mergeInto: 999, rationale: 'Bad.' },
|
|
138
|
-
];
|
|
139
|
-
const errors = validateMergeTargets(proposals, new Set([42, 88]));
|
|
140
|
-
expect(errors).toHaveLength(1);
|
|
141
|
-
expect(errors[0]).toContain('#999');
|
|
142
|
-
expect(errors[0]).toContain('not in the backlog');
|
|
143
|
-
});
|
|
144
|
-
it('ignores non-MERGE proposals', () => {
|
|
145
|
-
const proposals = [
|
|
146
|
-
{ number: 42, title: 'Widget', action: 'KEEP', rationale: 'Good.' },
|
|
147
|
-
{ number: 99, title: 'Legacy', action: 'CLOSE', rationale: 'Old.' },
|
|
148
|
-
];
|
|
149
|
-
const errors = validateMergeTargets(proposals, new Set([42]));
|
|
150
|
-
expect(errors).toHaveLength(0);
|
|
151
|
-
});
|
|
152
|
-
});
|
|
153
|
-
// ─── selectProposals ────────────────────────────────────
|
|
154
|
-
describe('selectProposals', () => {
|
|
155
|
-
const proposals = [
|
|
156
|
-
{ number: 42, title: 'Widget', action: 'KEEP', rationale: 'Good.' },
|
|
157
|
-
{ number: 99, title: 'Legacy', action: 'CLOSE', rationale: 'Obsolete.' },
|
|
158
|
-
{ number: 55, title: 'Perf', action: 'REPRIORITIZE', newTier: 'tier-3', rationale: 'Defer.' },
|
|
159
|
-
];
|
|
160
|
-
it('returns all actionable proposals in --yes mode', async () => {
|
|
161
|
-
const selected = await selectProposals(proposals, { yes: true, isTTY: false });
|
|
162
|
-
expect(selected).toHaveLength(2); // CLOSE + REPRIORITIZE, not KEEP
|
|
163
|
-
expect(selected.map((p) => p.number)).toEqual([99, 55]);
|
|
164
|
-
});
|
|
165
|
-
it('returns empty array when all proposals are KEEP', async () => {
|
|
166
|
-
const keepOnly = [
|
|
167
|
-
{ number: 1, title: 'A', action: 'KEEP', rationale: 'Good.' },
|
|
168
|
-
{ number: 2, title: 'B', action: 'KEEP', rationale: 'Good.' },
|
|
169
|
-
];
|
|
170
|
-
const selected = await selectProposals(keepOnly, { yes: true, isTTY: false });
|
|
171
|
-
expect(selected).toHaveLength(0);
|
|
172
|
-
});
|
|
173
|
-
it('throws in non-TTY without --yes', async () => {
|
|
174
|
-
await expect(selectProposals(proposals, { yes: false, isTTY: false })).rejects.toThrow('non-interactive mode');
|
|
175
|
-
});
|
|
176
|
-
});
|
|
177
|
-
// ─── executeProposals ───────────────────────────────────
|
|
178
|
-
vi.mock('../adapters/gh-utils.js', () => ({
|
|
179
|
-
ghExec: vi.fn(),
|
|
180
|
-
}));
|
|
181
|
-
const { ghExec: mockGhExec } = await vi.importMock('../adapters/gh-utils.js');
|
|
182
|
-
describe('executeProposals', () => {
|
|
183
|
-
beforeEach(() => {
|
|
184
|
-
vi.mocked(mockGhExec).mockReset();
|
|
185
|
-
});
|
|
186
|
-
it('closes issues with comment and close commands', () => {
|
|
187
|
-
const proposals = [
|
|
188
|
-
{ number: 99, title: 'Legacy', action: 'CLOSE', rationale: 'Done.' },
|
|
189
|
-
];
|
|
190
|
-
const result = executeProposals(proposals, '/tmp/test');
|
|
191
|
-
expect(result.succeeded).toBe(1);
|
|
192
|
-
expect(result.failed).toBe(0);
|
|
193
|
-
// ghExec called for: comment (--body-file) + close
|
|
194
|
-
expect(vi.mocked(mockGhExec)).toHaveBeenCalledTimes(2);
|
|
195
|
-
});
|
|
196
|
-
it('continues on failure and reports errors', () => {
|
|
197
|
-
vi.mocked(mockGhExec).mockImplementationOnce(() => {
|
|
198
|
-
throw new Error('rate limit');
|
|
199
|
-
});
|
|
200
|
-
const proposals = [
|
|
201
|
-
{ number: 99, title: 'Legacy', action: 'CLOSE', rationale: 'Done.' },
|
|
202
|
-
{ number: 55, title: 'Perf', action: 'CLOSE', rationale: 'Old.' },
|
|
203
|
-
];
|
|
204
|
-
const result = executeProposals(proposals, '/tmp/test');
|
|
205
|
-
expect(result.failed).toBe(1);
|
|
206
|
-
expect(result.succeeded).toBe(1);
|
|
207
|
-
expect(result.errors).toHaveLength(1);
|
|
208
|
-
expect(result.errors[0]).toContain('#99');
|
|
209
|
-
});
|
|
210
|
-
it('skips KEEP proposals', () => {
|
|
211
|
-
const proposals = [
|
|
212
|
-
{ number: 42, title: 'Widget', action: 'KEEP', rationale: 'Good.' },
|
|
213
|
-
];
|
|
214
|
-
const result = executeProposals(proposals, '/tmp/test');
|
|
215
|
-
expect(result.succeeded).toBe(0);
|
|
216
|
-
expect(result.failed).toBe(0);
|
|
217
|
-
expect(vi.mocked(mockGhExec)).not.toHaveBeenCalled();
|
|
218
|
-
});
|
|
219
|
-
it('reports error when REPRIORITIZE missing newTier (defense-in-depth)', () => {
|
|
220
|
-
// parseAuditResponse catches this at parse time, but executeProposals
|
|
221
|
-
// still guards against it as defense-in-depth
|
|
222
|
-
const proposals = [
|
|
223
|
-
{ number: 55, title: 'Perf', action: 'REPRIORITIZE', rationale: 'Defer.' },
|
|
224
|
-
];
|
|
225
|
-
const result = executeProposals(proposals, '/tmp/test');
|
|
226
|
-
expect(result.failed).toBe(1);
|
|
227
|
-
expect(result.succeeded).toBe(0);
|
|
228
|
-
expect(result.errors[0]).toContain('missing newTier');
|
|
229
|
-
});
|
|
230
|
-
it('reports error when MERGE missing mergeInto (defense-in-depth)', () => {
|
|
231
|
-
const proposals = [
|
|
232
|
-
{ number: 88, title: 'Colors', action: 'MERGE', rationale: 'Subset.' },
|
|
233
|
-
];
|
|
234
|
-
const result = executeProposals(proposals, '/tmp/test');
|
|
235
|
-
expect(result.failed).toBe(1);
|
|
236
|
-
expect(result.succeeded).toBe(0);
|
|
237
|
-
expect(result.errors[0]).toContain('missing mergeInto');
|
|
238
|
-
});
|
|
239
|
-
});
|
|
240
|
-
// ─── loadStrategicDocs ──────────────────────────────────
|
|
241
|
-
describe('loadStrategicDocs', () => {
|
|
242
|
-
it('truncates when content exceeds MAX_STRATEGIC_CONTEXT_CHARS', () => {
|
|
243
|
-
// Create a temp dir with a massive file
|
|
244
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-audit-test-'));
|
|
245
|
-
const strategyDir = path.join(tmpDir, '.strategy');
|
|
246
|
-
fs.mkdirSync(strategyDir, { recursive: true });
|
|
247
|
-
const bigContent = 'x'.repeat(MAX_STRATEGIC_CONTEXT_CHARS + 10_000);
|
|
248
|
-
fs.writeFileSync(path.join(strategyDir, 'big.md'), bigContent);
|
|
249
|
-
try {
|
|
250
|
-
const result = loadStrategicDocs(tmpDir);
|
|
251
|
-
expect(result.length).toBeLessThanOrEqual(MAX_STRATEGIC_CONTEXT_CHARS);
|
|
252
|
-
}
|
|
253
|
-
finally {
|
|
254
|
-
cleanTmpDir(tmpDir);
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
it('returns empty string when no strategic docs exist', () => {
|
|
258
|
-
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'totem-audit-test-'));
|
|
259
|
-
try {
|
|
260
|
-
const result = loadStrategicDocs(tmpDir);
|
|
261
|
-
expect(result).toBe('');
|
|
262
|
-
}
|
|
263
|
-
finally {
|
|
264
|
-
cleanTmpDir(tmpDir);
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
});
|
|
268
|
-
//# sourceMappingURL=audit.test.js.map
|