@forwardimpact/libwiki 0.2.7 → 0.2.9
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 +2 -2
- package/bin/fit-wiki.js +5 -4
- package/package.json +1 -1
- package/src/audit/rules.js +68 -55
- package/src/audit/scopes.js +6 -2
- package/src/commands/audit.js +14 -6
- package/src/commands/boot.js +2 -7
- package/src/index.js +1 -1
- package/src/audit/engine.js +0 -36
- package/src/audit/format.js +0 -39
package/README.md
CHANGED
|
@@ -53,8 +53,8 @@ line budget would be exceeded.
|
|
|
53
53
|
### `claim` / `release` — coordinate work
|
|
54
54
|
|
|
55
55
|
```sh
|
|
56
|
-
npx fit-wiki claim --agent X --target spec-
|
|
57
|
-
npx fit-wiki release --agent X --target spec-
|
|
56
|
+
npx fit-wiki claim --agent X --target spec-NNNN --branch claude/spec-NNNN
|
|
57
|
+
npx fit-wiki release --agent X --target spec-NNNN
|
|
58
58
|
npx fit-wiki release --agent X --expired
|
|
59
59
|
```
|
|
60
60
|
|
package/bin/fit-wiki.js
CHANGED
|
@@ -30,8 +30,9 @@ const wikiRootOpt = {
|
|
|
30
30
|
const agentOpt = {
|
|
31
31
|
agent: {
|
|
32
32
|
type: "string",
|
|
33
|
-
description:
|
|
34
|
-
|
|
33
|
+
description:
|
|
34
|
+
"Agent name (falls back to LIBEVAL_AGENT_PROFILE, then staff-engineer)",
|
|
35
|
+
default: process.env.LIBEVAL_AGENT_PROFILE || "staff-engineer",
|
|
35
36
|
},
|
|
36
37
|
};
|
|
37
38
|
|
|
@@ -222,8 +223,8 @@ const definition = {
|
|
|
222
223
|
examples: [
|
|
223
224
|
"fit-wiki boot --agent staff-engineer",
|
|
224
225
|
'fit-wiki log decision --agent staff-engineer --surveyed "..." --chosen "..." --rationale "..."',
|
|
225
|
-
"fit-wiki claim --agent staff-engineer --target spec-
|
|
226
|
-
"fit-wiki release --agent staff-engineer --target spec-
|
|
226
|
+
"fit-wiki claim --agent staff-engineer --target spec-NNNN --branch claude/...",
|
|
227
|
+
"fit-wiki release --agent staff-engineer --target spec-NNNN",
|
|
227
228
|
"fit-wiki inbox list --agent staff-engineer",
|
|
228
229
|
"fit-wiki rotate --agent staff-engineer",
|
|
229
230
|
"fit-wiki audit",
|
package/package.json
CHANGED
package/src/audit/rules.js
CHANGED
|
@@ -188,15 +188,16 @@ export const RULES = [
|
|
|
188
188
|
scope: "summary",
|
|
189
189
|
severity: "fail",
|
|
190
190
|
check: matches(/^\*\*Last run\*\*:/m),
|
|
191
|
-
message: (
|
|
191
|
+
message: () => "Missing '**Last run**:' line",
|
|
192
|
+
hint: "add a '**Last run**: <date> — <one-line state>' line directly after the H1",
|
|
192
193
|
},
|
|
193
194
|
{
|
|
194
195
|
id: "summary.first-h2-inbox",
|
|
195
196
|
scope: "summary",
|
|
196
197
|
severity: "fail",
|
|
197
198
|
check: firstH2Is("Message Inbox"),
|
|
198
|
-
message: (
|
|
199
|
-
|
|
199
|
+
message: (_s, r) => `First H2 is '${r.observed}', expected 'Message Inbox'`,
|
|
200
|
+
hint: "move '## Message Inbox' to be the first H2 in the file",
|
|
200
201
|
},
|
|
201
202
|
{
|
|
202
203
|
id: "summary.memo-inbox-marker",
|
|
@@ -204,31 +205,32 @@ export const RULES = [
|
|
|
204
205
|
severity: "fail",
|
|
205
206
|
when: (s) => s.h2s.includes("Message Inbox"),
|
|
206
207
|
check: containsLine(MEMO_INBOX_MARKER),
|
|
207
|
-
message: (
|
|
208
|
+
message: () => `Missing ${MEMO_INBOX_MARKER} marker`,
|
|
209
|
+
hint: "add the marker directly below the '## Message Inbox' heading so `fit-wiki memo` can find it",
|
|
208
210
|
},
|
|
209
211
|
{
|
|
210
212
|
id: "summary.open-blockers-last",
|
|
211
213
|
scope: "summary",
|
|
212
214
|
severity: "fail",
|
|
213
215
|
check: nothingAfterH2("Open Blockers"),
|
|
214
|
-
message: (
|
|
215
|
-
|
|
216
|
+
message: (_s, r) => `'${r.observed}' appears after 'Open Blockers'`,
|
|
217
|
+
hint: "move '## Open Blockers' to the end of the file",
|
|
216
218
|
},
|
|
217
219
|
{
|
|
218
220
|
id: "summary.line-budget",
|
|
219
221
|
scope: "summary",
|
|
220
222
|
severity: "fail",
|
|
221
223
|
check: lineBudget(SUMMARY_LINE_BUDGET),
|
|
222
|
-
message: (
|
|
223
|
-
|
|
224
|
+
message: (_s, r) => `${r.value} lines (limit ${SUMMARY_LINE_BUDGET})`,
|
|
225
|
+
hint: "trim history into the weekly log; the summary holds settled state, not history",
|
|
224
226
|
},
|
|
225
227
|
{
|
|
226
228
|
id: "summary.word-budget",
|
|
227
229
|
scope: "summary",
|
|
228
230
|
severity: "fail",
|
|
229
231
|
check: wordBudget(SUMMARY_WORD_BUDGET),
|
|
230
|
-
message: (
|
|
231
|
-
|
|
232
|
+
message: (_s, r) => `${r.value} words (limit ${SUMMARY_WORD_BUDGET})`,
|
|
233
|
+
hint: "trim history into the weekly log; the summary holds settled state, not history",
|
|
232
234
|
},
|
|
233
235
|
{
|
|
234
236
|
id: "summary.h1-agent-matches-filename",
|
|
@@ -236,7 +238,8 @@ export const RULES = [
|
|
|
236
238
|
severity: "fail",
|
|
237
239
|
check: summaryAgentMismatch,
|
|
238
240
|
message: (s, r) =>
|
|
239
|
-
`
|
|
241
|
+
`H1 title slug '${r.titleSlug}' does not match filename prefix '${s.agentPrefix}'`,
|
|
242
|
+
hint: "rename either the H1 ('# <agent> — Summary') or the file so they agree",
|
|
240
243
|
},
|
|
241
244
|
|
|
242
245
|
// -- Weekly logs (main) --
|
|
@@ -246,23 +249,24 @@ export const RULES = [
|
|
|
246
249
|
scope: "weekly-log-main",
|
|
247
250
|
severity: "fail",
|
|
248
251
|
check: firstLineMatches(WEEKLY_LOG_H1_RE),
|
|
249
|
-
message: (
|
|
252
|
+
message: () => "Missing valid H1 heading",
|
|
253
|
+
hint: "set the H1 to '# <agent> — YYYY-Www'",
|
|
250
254
|
},
|
|
251
255
|
{
|
|
252
256
|
id: "weekly-log.line-budget",
|
|
253
257
|
scope: "weekly-log-main",
|
|
254
258
|
severity: "fail",
|
|
255
259
|
check: lineBudget(WEEKLY_LOG_LINE_BUDGET),
|
|
256
|
-
message: (
|
|
257
|
-
|
|
260
|
+
message: (_s, r) => `${r.value} lines (limit ${WEEKLY_LOG_LINE_BUDGET})`,
|
|
261
|
+
hint: "run `bunx fit-wiki rotate` to seal this file as a sealed part and start a fresh weekly log",
|
|
258
262
|
},
|
|
259
263
|
{
|
|
260
264
|
id: "weekly-log.word-budget",
|
|
261
265
|
scope: "weekly-log-main",
|
|
262
266
|
severity: "fail",
|
|
263
267
|
check: wordBudget(WEEKLY_LOG_WORD_BUDGET),
|
|
264
|
-
message: (
|
|
265
|
-
|
|
268
|
+
message: (_s, r) => `${r.value} words (limit ${WEEKLY_LOG_WORD_BUDGET})`,
|
|
269
|
+
hint: "run `bunx fit-wiki rotate` to seal this file as a sealed part and start a fresh weekly log",
|
|
266
270
|
},
|
|
267
271
|
{
|
|
268
272
|
id: "weekly-log.h1-agent-matches-filename",
|
|
@@ -270,7 +274,8 @@ export const RULES = [
|
|
|
270
274
|
severity: "fail",
|
|
271
275
|
check: weeklyAgentMismatch,
|
|
272
276
|
message: (s, r) =>
|
|
273
|
-
`
|
|
277
|
+
`H1 title slug '${r.titleSlug}' does not match filename prefix '${s.agentPrefix}'`,
|
|
278
|
+
hint: "rename either the H1 or the file so they agree",
|
|
274
279
|
},
|
|
275
280
|
{
|
|
276
281
|
id: "decision-block.heading-within-5",
|
|
@@ -281,8 +286,8 @@ export const RULES = [
|
|
|
281
286
|
requiredLine: DECISION_HEADING,
|
|
282
287
|
stopRe: /^##\s/,
|
|
283
288
|
}),
|
|
284
|
-
message: (
|
|
285
|
-
|
|
289
|
+
message: () => "Entry lacks leading '### Decision'",
|
|
290
|
+
hint: "open each '## YYYY-MM-DD' entry with a '### Decision' subheading; use `bunx fit-wiki log decision` to do this mechanically",
|
|
286
291
|
},
|
|
287
292
|
|
|
288
293
|
// -- Weekly logs (sealed parts) --
|
|
@@ -292,23 +297,24 @@ export const RULES = [
|
|
|
292
297
|
scope: "weekly-log-part",
|
|
293
298
|
severity: "fail",
|
|
294
299
|
check: firstLineMatches(WEEKLY_LOG_H1_RE),
|
|
295
|
-
message: (
|
|
300
|
+
message: () => "Missing valid H1 heading",
|
|
301
|
+
hint: "set the H1 to '# <agent> — YYYY-Www (part N of M)'",
|
|
296
302
|
},
|
|
297
303
|
{
|
|
298
304
|
id: "weekly-log-part.line-budget",
|
|
299
305
|
scope: "weekly-log-part",
|
|
300
306
|
severity: "fail",
|
|
301
307
|
check: lineBudget(WEEKLY_LOG_LINE_BUDGET),
|
|
302
|
-
message: (
|
|
303
|
-
|
|
308
|
+
message: (_s, r) => `${r.value} lines (limit ${WEEKLY_LOG_LINE_BUDGET})`,
|
|
309
|
+
hint: "sealed parts should already be at-or-under the cap; if not, the rotation that produced this part needs investigation",
|
|
304
310
|
},
|
|
305
311
|
{
|
|
306
312
|
id: "weekly-log-part.word-budget",
|
|
307
313
|
scope: "weekly-log-part",
|
|
308
314
|
severity: "fail",
|
|
309
315
|
check: wordBudget(WEEKLY_LOG_WORD_BUDGET),
|
|
310
|
-
message: (
|
|
311
|
-
|
|
316
|
+
message: (_s, r) => `${r.value} words (limit ${WEEKLY_LOG_WORD_BUDGET})`,
|
|
317
|
+
hint: "sealed parts should already be at-or-under the cap; if not, the rotation that produced this part needs investigation",
|
|
312
318
|
},
|
|
313
319
|
{
|
|
314
320
|
id: "weekly-log-part.h1-agent-matches-filename",
|
|
@@ -316,7 +322,8 @@ export const RULES = [
|
|
|
316
322
|
severity: "fail",
|
|
317
323
|
check: weeklyAgentMismatch,
|
|
318
324
|
message: (s, r) =>
|
|
319
|
-
`
|
|
325
|
+
`H1 title slug '${r.titleSlug}' does not match filename prefix '${s.agentPrefix}'`,
|
|
326
|
+
hint: "rename either the H1 or the file so they agree",
|
|
320
327
|
},
|
|
321
328
|
|
|
322
329
|
// -- MEMORY.md --
|
|
@@ -326,7 +333,8 @@ export const RULES = [
|
|
|
326
333
|
scope: "memory",
|
|
327
334
|
severity: "fail",
|
|
328
335
|
check: exists,
|
|
329
|
-
message: (
|
|
336
|
+
message: () => "MEMORY.md not found",
|
|
337
|
+
hint: "run `bunx fit-wiki init` to scaffold the canonical sections",
|
|
330
338
|
},
|
|
331
339
|
{
|
|
332
340
|
id: "memory.priority-heading",
|
|
@@ -334,7 +342,8 @@ export const RULES = [
|
|
|
334
342
|
severity: "fail",
|
|
335
343
|
when: memoryExists,
|
|
336
344
|
check: matches(PRIORITY_INDEX_HEADING_RE),
|
|
337
|
-
message: () => `
|
|
345
|
+
message: () => `Missing '${PRIORITY_INDEX_HEADING}' heading`,
|
|
346
|
+
hint: "add the heading before the cross-cutting priorities table",
|
|
338
347
|
},
|
|
339
348
|
{
|
|
340
349
|
id: "memory.priority-table-header",
|
|
@@ -342,7 +351,8 @@ export const RULES = [
|
|
|
342
351
|
severity: "fail",
|
|
343
352
|
when: memoryExists,
|
|
344
353
|
check: matches(PRIORITY_HEADER_RE),
|
|
345
|
-
message: () => "
|
|
354
|
+
message: () => "Missing priority table header row",
|
|
355
|
+
hint: "add '| Item | Agents | Owner | Status | Added |' under the priority heading",
|
|
346
356
|
},
|
|
347
357
|
{
|
|
348
358
|
id: "memory.priority-separator-row",
|
|
@@ -350,8 +360,8 @@ export const RULES = [
|
|
|
350
360
|
severity: "fail",
|
|
351
361
|
when: memoryHasPriorityHeader,
|
|
352
362
|
check: matches(PRIORITY_SEPARATOR_RE),
|
|
353
|
-
message: () =>
|
|
354
|
-
|
|
363
|
+
message: () => "Missing priority table separator row",
|
|
364
|
+
hint: "add '| --- | --- | --- | --- | --- |' directly below the header row",
|
|
355
365
|
},
|
|
356
366
|
{
|
|
357
367
|
id: "memory.active-claims-table-header",
|
|
@@ -359,8 +369,8 @@ export const RULES = [
|
|
|
359
369
|
severity: "fail",
|
|
360
370
|
when: memoryHasClaimsHeading,
|
|
361
371
|
check: matches(CLAIMS_HEADER_RE),
|
|
362
|
-
message: () =>
|
|
363
|
-
|
|
372
|
+
message: () => `Active claims header mismatch`,
|
|
373
|
+
hint: `expected header row: '${ACTIVE_CLAIMS_TABLE_HEADER}'`,
|
|
364
374
|
},
|
|
365
375
|
{
|
|
366
376
|
id: "memory.active-claims-separator-row",
|
|
@@ -368,8 +378,8 @@ export const RULES = [
|
|
|
368
378
|
severity: "fail",
|
|
369
379
|
when: memoryHasClaimsHeader,
|
|
370
380
|
check: matches(CLAIMS_SEPARATOR_RE),
|
|
371
|
-
message: () =>
|
|
372
|
-
|
|
381
|
+
message: () => "Missing active-claims separator row",
|
|
382
|
+
hint: "add '| --- | --- | --- | --- | --- | --- |' directly below the claims header",
|
|
373
383
|
},
|
|
374
384
|
|
|
375
385
|
// -- Table rows --
|
|
@@ -379,32 +389,32 @@ export const RULES = [
|
|
|
379
389
|
scope: "priority-row",
|
|
380
390
|
severity: "fail",
|
|
381
391
|
check: columnCount(5),
|
|
382
|
-
message: (
|
|
383
|
-
|
|
392
|
+
message: (_s, r) => `${r.actual} cells (expected ${r.expected})`,
|
|
393
|
+
hint: "every priority row needs 5 cells: Item, Agents, Owner, Status, Added",
|
|
384
394
|
},
|
|
385
395
|
{
|
|
386
396
|
id: "claims-row.claimed-at-format",
|
|
387
397
|
scope: "claims-row",
|
|
388
398
|
severity: "fail",
|
|
389
399
|
check: fieldMatches("claimed_at", ISO_DATE_RE),
|
|
390
|
-
message: (s, r) =>
|
|
391
|
-
|
|
400
|
+
message: (s, r) => `Bad claimed_at '${r.value}' for ${s.agent}/${s.target}`,
|
|
401
|
+
hint: "claimed_at must be ISO YYYY-MM-DD",
|
|
392
402
|
},
|
|
393
403
|
{
|
|
394
404
|
id: "claims-row.expires-at-format",
|
|
395
405
|
scope: "claims-row",
|
|
396
406
|
severity: "fail",
|
|
397
407
|
check: fieldMatches("expires_at", ISO_DATE_RE),
|
|
398
|
-
message: (s, r) =>
|
|
399
|
-
|
|
408
|
+
message: (s, r) => `Bad expires_at '${r.value}' for ${s.agent}/${s.target}`,
|
|
409
|
+
hint: "expires_at must be ISO YYYY-MM-DD",
|
|
400
410
|
},
|
|
401
411
|
{
|
|
402
412
|
id: "expired-claim",
|
|
403
413
|
scope: "claims-row",
|
|
404
414
|
severity: "warn",
|
|
405
415
|
check: expired,
|
|
406
|
-
message: (s) =>
|
|
407
|
-
|
|
416
|
+
message: (s) => `${s.agent}/${s.target} expired ${s.expires_at}`,
|
|
417
|
+
hint: "run `bunx fit-wiki release --expired` to clear expired claims",
|
|
408
418
|
},
|
|
409
419
|
|
|
410
420
|
// -- Storyboards --
|
|
@@ -414,8 +424,8 @@ export const RULES = [
|
|
|
414
424
|
scope: "storyboard",
|
|
415
425
|
severity: "fail",
|
|
416
426
|
check: exists,
|
|
417
|
-
message: (s) =>
|
|
418
|
-
|
|
427
|
+
message: (s) => `Current-month storyboard (${s.yearMonth}) not found`,
|
|
428
|
+
hint: "create it from `.claude/skills/kata-session/references/storyboard-template.md`",
|
|
419
429
|
},
|
|
420
430
|
{
|
|
421
431
|
id: "storyboard.agent-h3-required",
|
|
@@ -423,7 +433,8 @@ export const RULES = [
|
|
|
423
433
|
severity: "fail",
|
|
424
434
|
when: storyboardExists,
|
|
425
435
|
check: allRequiredLines(AGENT_H3_REQUIREMENTS),
|
|
426
|
-
message: (
|
|
436
|
+
message: (_s, r) => `Missing '### ${r.label}' H3`,
|
|
437
|
+
hint: "every domain agent gets an H3 under '## Current Condition'",
|
|
427
438
|
},
|
|
428
439
|
{
|
|
429
440
|
id: "storyboard.line-budget",
|
|
@@ -431,8 +442,8 @@ export const RULES = [
|
|
|
431
442
|
severity: "fail",
|
|
432
443
|
when: storyboardExists,
|
|
433
444
|
check: lineBudget(STORYBOARD_LINE_BUDGET),
|
|
434
|
-
message: (
|
|
435
|
-
|
|
445
|
+
message: (_s, r) => `${r.value} lines (limit ${STORYBOARD_LINE_BUDGET})`,
|
|
446
|
+
hint: "see per-section word budgets in storyboard-template.md; retire prior-session Headlines/Notes/Next-review entries to weekly logs",
|
|
436
447
|
},
|
|
437
448
|
{
|
|
438
449
|
id: "storyboard.word-budget",
|
|
@@ -440,8 +451,8 @@ export const RULES = [
|
|
|
440
451
|
severity: "fail",
|
|
441
452
|
when: storyboardExists,
|
|
442
453
|
check: wordBudget(STORYBOARD_WORD_BUDGET),
|
|
443
|
-
message: (
|
|
444
|
-
|
|
454
|
+
message: (_s, r) => `${r.value} words (limit ${STORYBOARD_WORD_BUDGET})`,
|
|
455
|
+
hint: "see per-section word budgets in storyboard-template.md; retire prior-session Headlines/Notes/Next-review entries to weekly logs",
|
|
445
456
|
},
|
|
446
457
|
{
|
|
447
458
|
id: "storyboard.markers-balanced.xmr",
|
|
@@ -453,8 +464,9 @@ export const RULES = [
|
|
|
453
464
|
closeRe: XMR_CLOSE_RE,
|
|
454
465
|
label: "xmr",
|
|
455
466
|
}),
|
|
456
|
-
message: (
|
|
457
|
-
|
|
467
|
+
message: (_s, r) =>
|
|
468
|
+
`${r.reason} xmr marker${r.label ? ` (${r.label})` : ""}`,
|
|
469
|
+
hint: "every '<!-- xmr:metric:csv -->' needs a matching '<!-- /xmr -->'",
|
|
458
470
|
},
|
|
459
471
|
{
|
|
460
472
|
id: "storyboard.markers-balanced.issues",
|
|
@@ -466,8 +478,9 @@ export const RULES = [
|
|
|
466
478
|
closeRe: ISSUE_CLOSE_RE,
|
|
467
479
|
label: "issue-list",
|
|
468
480
|
}),
|
|
469
|
-
message: (
|
|
470
|
-
|
|
481
|
+
message: (_s, r) =>
|
|
482
|
+
`${r.reason} issue-list marker${r.label ? ` (${r.label})` : ""}`,
|
|
483
|
+
hint: "every '<!-- obstacles:* -->' or '<!-- experiments:* -->' needs a matching close marker",
|
|
471
484
|
},
|
|
472
485
|
|
|
473
486
|
// -- Stray files --
|
|
@@ -477,7 +490,7 @@ export const RULES = [
|
|
|
477
490
|
scope: "stray-file",
|
|
478
491
|
severity: "fail",
|
|
479
492
|
check: always,
|
|
480
|
-
message: (
|
|
481
|
-
|
|
493
|
+
message: () => "Does not match any known scope",
|
|
494
|
+
hint: "rename to a recognized scope (summary, weekly log, weekly-log part) or remove the file",
|
|
482
495
|
},
|
|
483
496
|
];
|
package/src/audit/scopes.js
CHANGED
|
@@ -161,8 +161,12 @@ const SCOPE_RESOLVERS = {
|
|
|
161
161
|
"weekly-log-part": (ctx) => ctx.subjects["weekly-log-part"],
|
|
162
162
|
memory: (ctx) => [ctx.memory],
|
|
163
163
|
"claims-row": (ctx) =>
|
|
164
|
-
parseClaims(ctx.memory.text).map((c) => ({ path:
|
|
165
|
-
"priority-row": (ctx) =>
|
|
164
|
+
parseClaims(ctx.memory.text).map((c) => ({ ...c, path: ctx.memory.path })),
|
|
165
|
+
"priority-row": (ctx) =>
|
|
166
|
+
parsePriorityRows(ctx.memory.text).map((r) => ({
|
|
167
|
+
...r,
|
|
168
|
+
path: ctx.memory.path,
|
|
169
|
+
})),
|
|
166
170
|
storyboard: (ctx) => [ctx.storyboard],
|
|
167
171
|
"stray-file": (ctx) => ctx.subjects.stray,
|
|
168
172
|
};
|
package/src/commands/audit.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import fsAsync from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
Finder,
|
|
5
|
+
emitFindingsJson,
|
|
6
|
+
emitFindingsText,
|
|
7
|
+
runRules,
|
|
8
|
+
} from "@forwardimpact/libutil";
|
|
5
9
|
import { RULES } from "../audit/rules.js";
|
|
6
|
-
import { buildContext } from "../audit/scopes.js";
|
|
7
|
-
import { emitJson, emitText } from "../audit/format.js";
|
|
10
|
+
import { buildContext, resolveScope } from "../audit/scopes.js";
|
|
8
11
|
|
|
9
12
|
/** Run the wiki audit and emit findings. JSON via --format json. */
|
|
10
13
|
export function runAuditCommand(values, _args, _cli) {
|
|
@@ -14,10 +17,15 @@ export function runAuditCommand(values, _args, _cli) {
|
|
|
14
17
|
const today = values.today || new Date().toISOString().slice(0, 10);
|
|
15
18
|
|
|
16
19
|
const ctx = buildContext({ wikiRoot, today });
|
|
17
|
-
const findings =
|
|
20
|
+
const findings = runRules(RULES, ctx, { resolveScope });
|
|
18
21
|
|
|
19
22
|
process.stdout.write(
|
|
20
|
-
values.format === "json"
|
|
23
|
+
values.format === "json"
|
|
24
|
+
? emitFindingsJson(findings)
|
|
25
|
+
: emitFindingsText(findings, {
|
|
26
|
+
cwd: projectRoot,
|
|
27
|
+
passMessage: "wiki audit passed",
|
|
28
|
+
}),
|
|
21
29
|
);
|
|
22
30
|
|
|
23
31
|
if (findings.some((f) => f.level === "fail")) process.exit(1);
|
package/src/commands/boot.js
CHANGED
|
@@ -42,13 +42,8 @@ function renderMarkdown(digest) {
|
|
|
42
42
|
|
|
43
43
|
/** Print the on-boot digest for the calling agent. JSON by default; --format markdown renders prose. */
|
|
44
44
|
export function runBootCommand(values, _args, cli) {
|
|
45
|
-
const agent =
|
|
46
|
-
|
|
47
|
-
cli.usageError(
|
|
48
|
-
"boot requires --agent <name> or LIBEVAL_AGENT_PROFILE env var",
|
|
49
|
-
);
|
|
50
|
-
process.exit(2);
|
|
51
|
-
}
|
|
45
|
+
const agent =
|
|
46
|
+
values.agent || process.env.LIBEVAL_AGENT_PROFILE || "staff-engineer";
|
|
52
47
|
|
|
53
48
|
const logger = { debug() {} };
|
|
54
49
|
const finder = new Finder(fsAsync, logger, process);
|
package/src/index.js
CHANGED
package/src/audit/engine.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { resolveScope } from "./scopes.js";
|
|
2
|
-
|
|
3
|
-
function groupByScope(rules) {
|
|
4
|
-
const groups = new Map();
|
|
5
|
-
for (const rule of rules) {
|
|
6
|
-
if (!groups.has(rule.scope)) groups.set(rule.scope, []);
|
|
7
|
-
groups.get(rule.scope).push(rule);
|
|
8
|
-
}
|
|
9
|
-
return groups;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function applyRule(rule, subject, ctx) {
|
|
13
|
-
if (rule.when && !rule.when(subject, ctx)) return [];
|
|
14
|
-
const result = rule.check(subject, ctx);
|
|
15
|
-
if (result == null) return [];
|
|
16
|
-
const items = Array.isArray(result) ? result : [result];
|
|
17
|
-
return items.map((item) => ({
|
|
18
|
-
id: rule.id,
|
|
19
|
-
level: rule.severity,
|
|
20
|
-
path: subject.path ?? null,
|
|
21
|
-
message: rule.message(subject, item, ctx),
|
|
22
|
-
}));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Apply the declarative rule catalogue to the wiki context. */
|
|
26
|
-
export function runAudit(rules, ctx) {
|
|
27
|
-
const findings = [];
|
|
28
|
-
for (const [scopeKey, scopeRules] of groupByScope(rules)) {
|
|
29
|
-
for (const subject of resolveScope(scopeKey, ctx)) {
|
|
30
|
-
for (const rule of scopeRules) {
|
|
31
|
-
findings.push(...applyRule(rule, subject, ctx));
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return findings;
|
|
36
|
-
}
|
package/src/audit/format.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
function partition(findings) {
|
|
2
|
-
const failures = [];
|
|
3
|
-
const warnings = [];
|
|
4
|
-
for (const f of findings) {
|
|
5
|
-
if (f.level === "warn") warnings.push(f);
|
|
6
|
-
else failures.push(f);
|
|
7
|
-
}
|
|
8
|
-
return { failures, warnings };
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Render findings as `WARN`/`FAIL` lines followed by a `RESULT:` trailer. */
|
|
12
|
-
export function emitText(findings) {
|
|
13
|
-
const { failures, warnings } = partition(findings);
|
|
14
|
-
const lines = [];
|
|
15
|
-
for (const w of warnings) lines.push(`WARN ${w.message}`);
|
|
16
|
-
for (const f of failures) lines.push(`FAIL ${f.message}`);
|
|
17
|
-
lines.push(
|
|
18
|
-
failures.length === 0
|
|
19
|
-
? "RESULT: pass"
|
|
20
|
-
: `RESULT: fail (${failures.length} checks failed)`,
|
|
21
|
-
);
|
|
22
|
-
return lines.join("\n") + "\n";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Render findings as a JSON document. */
|
|
26
|
-
export function emitJson(findings) {
|
|
27
|
-
const { failures, warnings } = partition(findings);
|
|
28
|
-
return (
|
|
29
|
-
JSON.stringify(
|
|
30
|
-
{
|
|
31
|
-
result: failures.length === 0 ? "pass" : "fail",
|
|
32
|
-
failures,
|
|
33
|
-
warnings,
|
|
34
|
-
},
|
|
35
|
-
null,
|
|
36
|
-
2,
|
|
37
|
-
) + "\n"
|
|
38
|
-
);
|
|
39
|
-
}
|