@ijfw/memory-server 1.5.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/ijfw-memorize +14 -7
- package/fixtures/team/book.json +6 -6
- package/fixtures/team/business.json +146 -20
- package/fixtures/team/content.json +6 -6
- package/fixtures/team/design.json +148 -20
- package/fixtures/team/mixed.json +206 -27
- package/fixtures/team/research.json +146 -20
- package/fixtures/team/software.json +148 -20
- package/package.json +6 -3
- package/src/cross-orchestrator-cli.js +204 -145
- package/src/cross-orchestrator.js +50 -1
- package/src/dispatch/extension.js +1 -1
- package/src/hardware-signer.js +4 -2
- package/src/lib/ui-review-runner.js +48 -7
- package/src/memory/auto-linker.js +116 -1
- package/src/memory/migration-runner.js +6 -1
- package/src/memory/migrations/009-obsidian-backfill.js +50 -0
- package/src/memory/obsidian-parser.js +62 -1
- package/src/memory/search.js +46 -25
- package/src/orchestrator/debug-trident-trigger.js +374 -0
- package/src/orchestrator/post-done-runner.js +36 -8
- package/src/orchestrator/state-sdk.js +174 -6
- package/src/orchestrator/subagent-telemetry.js +19 -0
- package/src/override-resolver.js +5 -3
- package/src/recovery/code-fixer.js +310 -5
- package/src/runtime-mediator.js +0 -1
- package/src/server.js +198 -59
- package/src/swarm-config.js +30 -22
- package/src/team/domain-templates/business.json +4 -1
- package/src/team/domain-templates/research.json +4 -1
- package/src/team/generator.js +162 -0
- package/src/update-apply.js +1 -1
- package/src/dashboard-charts.js +0 -239
- package/src/orchestrator/runtime-loop.js +0 -430
|
@@ -62,6 +62,8 @@ import {
|
|
|
62
62
|
} from './swarm/worktree.js';
|
|
63
63
|
import { renderSwarmDispatchPrompt } from './swarm/dispatch-prompt.js';
|
|
64
64
|
import { syncCodexAgents } from './codex-agents.js';
|
|
65
|
+
// v1.5.1 W2.H — memory benchmark harness (T22). Surfaced via `ijfw metrics --benchmark`.
|
|
66
|
+
import { runBenchmark } from './memory/benchmark.js';
|
|
65
67
|
import {
|
|
66
68
|
DESIGN_ACTIONS,
|
|
67
69
|
auditDesignText,
|
|
@@ -69,6 +71,12 @@ import {
|
|
|
69
71
|
initialDesignMarkdown,
|
|
70
72
|
loadDesignContext,
|
|
71
73
|
} from './design-intelligence.js';
|
|
74
|
+
// v1.5.1 W3.A.4 — registry-driven usage + alias help.
|
|
75
|
+
// SINGLE SOURCE OF TRUTH lives at installer/src/command-registry.js;
|
|
76
|
+
// import sibling-package style, matching extension-installer.js precedent.
|
|
77
|
+
import {
|
|
78
|
+
COMMAND_REGISTRY,
|
|
79
|
+
} from '../../installer/src/command-registry.js';
|
|
72
80
|
|
|
73
81
|
// ---------------------------------------------------------------------------
|
|
74
82
|
// Auditor error translator (1.2.5)
|
|
@@ -219,68 +227,19 @@ function parseArgs(argv) {
|
|
|
219
227
|
return out;
|
|
220
228
|
}
|
|
221
229
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
consolidate: {
|
|
236
|
-
title: 'IJFW consolidate',
|
|
237
|
-
usage: 'Use the ijfw-handoff or ijfw-memory-audit skill to consolidate decisions into memory. For swarm state, run: ijfw memory checkpoint <label>.',
|
|
238
|
-
},
|
|
239
|
-
'ijfw-audit': {
|
|
240
|
-
title: 'IJFW audit',
|
|
241
|
-
usage: 'Run verification with: ijfw preflight. For multi-model review, run: ijfw cross audit <target>.',
|
|
242
|
-
},
|
|
243
|
-
'ijfw-execute': {
|
|
244
|
-
title: 'IJFW execute',
|
|
245
|
-
usage: 'Use ijfw-workflow in agents, then terminal helpers: ijfw team init, ijfw swarm plan, ijfw swarm prepare, ijfw swarm start <task-id>.',
|
|
246
|
-
},
|
|
247
|
-
'ijfw-help': {
|
|
248
|
-
title: 'IJFW help',
|
|
249
|
-
usage: 'Run: ijfw help. Add --browser for the rendered local guide.',
|
|
250
|
-
},
|
|
251
|
-
'ijfw-plan': {
|
|
252
|
-
title: 'IJFW plan',
|
|
253
|
-
usage: 'Use ijfw-workflow for planning. Terminal helpers: ijfw team init, ijfw swarm plan, ijfw swarm prepare --reviews.',
|
|
254
|
-
},
|
|
255
|
-
'ijfw-ship': {
|
|
256
|
-
title: 'IJFW ship',
|
|
257
|
-
usage: 'Run: ijfw preflight. Do not publish or tag until your release gate is explicitly cleared.',
|
|
258
|
-
},
|
|
259
|
-
'ijfw-verify': {
|
|
260
|
-
title: 'IJFW verify',
|
|
261
|
-
usage: 'Run: ijfw preflight. For focused review, run: ijfw cross audit <target>.',
|
|
262
|
-
},
|
|
263
|
-
'memory-audit': {
|
|
264
|
-
title: 'IJFW memory audit',
|
|
265
|
-
usage: 'Use the ijfw-memory-audit skill in agents. Terminal safety net: ijfw recover status and ijfw memory checkpoint <label>.',
|
|
266
|
-
},
|
|
267
|
-
'memory-consent': {
|
|
268
|
-
title: 'IJFW memory consent',
|
|
269
|
-
usage: 'Use IJFW memory tools only for explicit project memory. Terminal checkpoint: ijfw memory checkpoint <label>.',
|
|
270
|
-
},
|
|
271
|
-
'memory-why': {
|
|
272
|
-
title: 'IJFW memory why',
|
|
273
|
-
usage: 'Use ijfw-recall or ijfw-memory-audit in agents to inspect why memory exists. Terminal recovery state: ijfw recover latest.',
|
|
274
|
-
},
|
|
275
|
-
metrics: {
|
|
276
|
-
title: 'IJFW metrics',
|
|
277
|
-
usage: 'Open the dashboard with: ijfw dashboard start. Agent-side metrics are available through ijfw_metrics.',
|
|
278
|
-
},
|
|
279
|
-
mode: {
|
|
280
|
-
title: 'IJFW mode',
|
|
281
|
-
usage: 'Inspect configuration with: ijfw config --audit. Statusline mode helpers: ijfw statusline --status, --compose, or --disable.',
|
|
282
|
-
},
|
|
283
|
-
};
|
|
230
|
+
// v1.5.1 W3.A.4 — COMMAND_ALIAS_HELP is now derived from the command-registry
|
|
231
|
+
// (entries where tier === 'pointer-stub'). Each entry's deprecatedReason
|
|
232
|
+
// becomes the usage line; title is auto-derived from the canonical name.
|
|
233
|
+
// To change the help text or add a new pointer-stub: edit
|
|
234
|
+
// installer/src/command-registry.js.
|
|
235
|
+
const COMMAND_ALIAS_HELP = Object.freeze(Object.fromEntries(
|
|
236
|
+
COMMAND_REGISTRY
|
|
237
|
+
.filter(e => e.tier === 'pointer-stub')
|
|
238
|
+
.map(e => [e.name, {
|
|
239
|
+
title: `IJFW ${e.name}`,
|
|
240
|
+
usage: e.deprecatedReason,
|
|
241
|
+
}])
|
|
242
|
+
));
|
|
284
243
|
|
|
285
244
|
function parseCrossAlias(mode, args) {
|
|
286
245
|
let only = null;
|
|
@@ -313,6 +272,16 @@ function parseCommandAlias(args) {
|
|
|
313
272
|
return parseCrossAlias('research', args);
|
|
314
273
|
}
|
|
315
274
|
if (Object.prototype.hasOwnProperty.call(COMMAND_ALIAS_HELP, name)) {
|
|
275
|
+
// v1.5.1 W2.H — `ijfw metrics` is a deprecated pointer-stub, but
|
|
276
|
+
// `ijfw metrics --benchmark` surfaces the memory benchmark harness (T22).
|
|
277
|
+
// Bare `ijfw metrics` still falls through to the deprecation redirect.
|
|
278
|
+
if (name === 'metrics' && args.includes('--benchmark')) {
|
|
279
|
+
const opts = { cmd: 'metrics-benchmark', json: args.includes('--json'), write: true };
|
|
280
|
+
for (let i = 1; i < args.length; i++) {
|
|
281
|
+
if (args[i] === '--no-write') opts.write = false;
|
|
282
|
+
}
|
|
283
|
+
return opts;
|
|
284
|
+
}
|
|
316
285
|
return { cmd: 'command-alias', alias: name };
|
|
317
286
|
}
|
|
318
287
|
return null;
|
|
@@ -449,8 +418,25 @@ function parseArgsInner(args) {
|
|
|
449
418
|
return { cmd: 'codex', sub: args[1] || 'doctor' };
|
|
450
419
|
}
|
|
451
420
|
|
|
452
|
-
if (args[0] === 'memory'
|
|
453
|
-
|
|
421
|
+
if (args[0] === 'memory') {
|
|
422
|
+
// `ijfw memory` / `ijfw memory --help` / `ijfw memory -h` → namespace help
|
|
423
|
+
if (args.length === 1 || args[1] === '--help' || args[1] === '-h') {
|
|
424
|
+
return { cmd: 'memory-help' };
|
|
425
|
+
}
|
|
426
|
+
if (args[1] === 'checkpoint') {
|
|
427
|
+
return { cmd: 'memory-checkpoint', label: args[2] || 'manual' };
|
|
428
|
+
}
|
|
429
|
+
if (args[1] === 'reindex') {
|
|
430
|
+
// `ijfw memory reindex` -> M1 backfill (free, obsidian indexing)
|
|
431
|
+
// `ijfw memory reindex --m2` -> also run M2 A-Mem auto-link backfill
|
|
432
|
+
// (budget-gated; needs IJFW_AUTOLINK_*)
|
|
433
|
+
let m2 = false;
|
|
434
|
+
for (let i = 2; i < args.length; i++) {
|
|
435
|
+
if (args[i] === '--m2' || args[i] === '--autolink') m2 = true;
|
|
436
|
+
}
|
|
437
|
+
return { cmd: 'memory-reindex', m2 };
|
|
438
|
+
}
|
|
439
|
+
return { cmd: 'memory-unknown', sub: args[1] };
|
|
454
440
|
}
|
|
455
441
|
|
|
456
442
|
if (args[0] === 'recover') {
|
|
@@ -514,86 +500,32 @@ function parseArgsInner(args) {
|
|
|
514
500
|
// Commands
|
|
515
501
|
// ---------------------------------------------------------------------------
|
|
516
502
|
|
|
517
|
-
function
|
|
503
|
+
function printMemoryHelp() {
|
|
518
504
|
console.log(`
|
|
519
|
-
ijfw --
|
|
520
|
-
Fire 2-4 AIs at any target. Receipts logged. Cache hits tracked. Memory follows you.
|
|
505
|
+
ijfw memory -- project memory namespace
|
|
521
506
|
|
|
522
507
|
Usage:
|
|
523
|
-
ijfw
|
|
524
|
-
|
|
525
|
-
ijfw
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
ijfw
|
|
534
|
-
ijfw
|
|
535
|
-
ijfw
|
|
536
|
-
ijfw cross project-audit <rule-file> [--dry-run]
|
|
537
|
-
ijfw import <tool> [--all] [--dry-run] [--force] [--path <p>]
|
|
538
|
-
ijfw status
|
|
539
|
-
ijfw doctor
|
|
540
|
-
ijfw update
|
|
541
|
-
ijfw receipt last
|
|
542
|
-
ijfw --purge-receipts
|
|
543
|
-
ijfw --help
|
|
544
|
-
|
|
545
|
-
Commands:
|
|
546
|
-
install Install IJFW into your AI coding agents.
|
|
547
|
-
uninstall Remove IJFW and revert AI-agent configs. Same as: ijfw off
|
|
548
|
-
preflight Run the 11-gate quality pipeline (blocking + advisory).
|
|
549
|
-
dashboard Control the dashboard server (start, stop, status).
|
|
550
|
-
design Control the live visual design companion.
|
|
551
|
-
blackboard Coordinate project-local swarm state and artifact claims.
|
|
552
|
-
team Assemble project agents, charter, and workflow manifest.
|
|
553
|
-
swarm Plan artifact-aware parallel work from the team manifest.
|
|
554
|
-
recover Show the latest checkpoint and next recovery step.
|
|
555
|
-
demo 30-second live tour of the Trident (fires real auditors).
|
|
556
|
-
cross Fire external auditors at a target. Try: ijfw cross audit README.md
|
|
557
|
-
import Pull memory in from another tool. Try: ijfw import claude-mem --all
|
|
558
|
-
status Show recent cross-audit activity. Try: ijfw status
|
|
559
|
-
doctor Probe which CLIs and API keys are reachable. Try: ijfw doctor
|
|
560
|
-
update Pull latest IJFW + reinstall merge-safely. Try: ijfw update
|
|
561
|
-
update --check Non-invasive check. Exits 0 always; prints "update-available: <ver>" when an update exists (grep-safe).
|
|
562
|
-
receipt last Print a redacted, shareable block from the last Trident run.
|
|
563
|
-
--purge-receipts Clear the cross-runs receipt log. Try: ijfw --purge-receipts
|
|
564
|
-
|
|
565
|
-
Modes (for ijfw cross):
|
|
566
|
-
audit Adversarial review of a file, module, or path
|
|
567
|
-
research Multi-source research on a topic
|
|
568
|
-
critique Structured counter-argument generation
|
|
569
|
-
project-audit Run the same audit across every registered IJFW project
|
|
570
|
-
Usage: ijfw cross project-audit <rule-file> [--dry-run]
|
|
571
|
-
|
|
572
|
-
Options for ijfw cross:
|
|
573
|
-
--with <id> Force a specific auditor (comma-separated for multiple)
|
|
574
|
-
--confirm Prompt for confirmation before firing
|
|
575
|
-
--expand Include extended swarm when available
|
|
576
|
-
|
|
577
|
-
Global flags:
|
|
578
|
-
--json Emit JSON instead of human output. status and doctor auto-JSON
|
|
579
|
-
on non-TTY (gh-CLI convention); version stays one-line on pipe
|
|
580
|
-
and only JSON-ifies with explicit --json. Other commands ignore.
|
|
581
|
-
|
|
582
|
-
Environment:
|
|
583
|
-
IJFW_AUDIT_BUDGET_USD Session spend cap (default $2.00). First call is always
|
|
584
|
-
allowed (no cap). Cap enforced from the 2nd call on.
|
|
585
|
-
|
|
586
|
-
Examples:
|
|
587
|
-
ijfw demo
|
|
588
|
-
ijfw cross audit README.md
|
|
589
|
-
ijfw cross research "vector search approaches"
|
|
590
|
-
ijfw cross critique HEAD~3..HEAD
|
|
591
|
-
ijfw cross audit CLAUDE.md --with codex,gemini
|
|
592
|
-
ijfw status
|
|
593
|
-
ijfw doctor
|
|
508
|
+
ijfw memory checkpoint <label> Snapshot current swarm/memory state under <label>.
|
|
509
|
+
<label> defaults to "manual" if omitted.
|
|
510
|
+
ijfw memory reindex [--m2] Backfill M1 obsidian indexing (wikilinks,
|
|
511
|
+
#tags, [k:: v] metadata) over the whole
|
|
512
|
+
memory db. Free + idempotent. Add --m2 to
|
|
513
|
+
also run the A-Mem auto-link backfill --
|
|
514
|
+
budget-gated (set IJFW_AUTOLINK_BUDGET_USD
|
|
515
|
+
and IJFW_AUTOLINK_BACKFILL=1).
|
|
516
|
+
|
|
517
|
+
Related:
|
|
518
|
+
ijfw recover [status|latest] Inspect checkpoints and recovery state.
|
|
519
|
+
ijfw --help Top-level user-facing commands.
|
|
520
|
+
ijfw commands Full command surface (all verbs).
|
|
594
521
|
`.trim());
|
|
595
522
|
}
|
|
596
523
|
|
|
524
|
+
function printUnknownCommand(raw) {
|
|
525
|
+
console.error(`Unknown command: ${raw}`);
|
|
526
|
+
console.error('Run `ijfw --help` for the user-facing command list, or `ijfw commands` for the full surface.');
|
|
527
|
+
}
|
|
528
|
+
|
|
597
529
|
function cmdCommandAlias(alias) {
|
|
598
530
|
const info = COMMAND_ALIAS_HELP[alias];
|
|
599
531
|
if (!info) {
|
|
@@ -606,6 +538,41 @@ function cmdCommandAlias(alias) {
|
|
|
606
538
|
process.exit(0);
|
|
607
539
|
}
|
|
608
540
|
|
|
541
|
+
// v1.5.1 W2.H — `ijfw metrics --benchmark`: run the memory benchmark harness
|
|
542
|
+
// (T22) against IJFW's own 3-tier store and report recall@k, MRR/NDCG-style
|
|
543
|
+
// retrieval quality, throughput, and p50/p95/p99 latency. See
|
|
544
|
+
// docs/MEMORY-BENCHMARK.md for the axes and how to interpret the numbers.
|
|
545
|
+
async function cmdMetricsBenchmark(opts = {}) {
|
|
546
|
+
const results = await runBenchmark({
|
|
547
|
+
root: process.cwd(),
|
|
548
|
+
write: opts.write !== false,
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
if (wantsJson(opts)) {
|
|
552
|
+
emitJson(results);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const q = results.axes.query_warm_fts5;
|
|
557
|
+
const ing = results.axes.ingest;
|
|
558
|
+
const recallPairs = Object.entries(q.recall || {});
|
|
559
|
+
console.log('IJFW memory benchmark (T22)');
|
|
560
|
+
console.log('');
|
|
561
|
+
console.log(` corpus ${results.corpus.docs} docs / ${results.corpus.queries} queries / ${results.corpus.total_query_samples} timed samples`);
|
|
562
|
+
console.log(` ingest throughput ${ing.throughput_rps} rows/s`);
|
|
563
|
+
console.log(` ingest latency p50 ${ing.latency_ms.p50}ms p95 ${ing.latency_ms.p95}ms p99 ${ing.latency_ms.p99}ms`);
|
|
564
|
+
console.log(` query latency p50 ${q.latency_ms.p50}ms p95 ${q.latency_ms.p95}ms p99 ${q.latency_ms.p99}ms`);
|
|
565
|
+
console.log(` recall ${recallPairs.map(([k, v]) => `${k}=${v.toFixed(3)}`).join(' ')}`);
|
|
566
|
+
console.log(` storage ${results.axes.storage.bytes_per_memory} bytes/memory (${results.axes.storage.rows_indexed} rows)`);
|
|
567
|
+
console.log(` cold tier ${results.axes.query_cold_vector.available ? 'available' : 'reserved (no embedding model)'}`);
|
|
568
|
+
if (results.artifact_path) {
|
|
569
|
+
console.log('');
|
|
570
|
+
console.log(` artifact ${results.artifact_path}`);
|
|
571
|
+
}
|
|
572
|
+
console.log('');
|
|
573
|
+
console.log('Axes explained: docs/MEMORY-BENCHMARK.md');
|
|
574
|
+
}
|
|
575
|
+
|
|
609
576
|
async function cmdStatus(projectDir, opts = {}) {
|
|
610
577
|
const receipts = readReceipts(projectDir);
|
|
611
578
|
const last = receipts[receipts.length - 1];
|
|
@@ -696,8 +663,6 @@ async function cmdDemo() {
|
|
|
696
663
|
|
|
697
664
|
let result;
|
|
698
665
|
try {
|
|
699
|
-
// TODO post-merge: perAuditorTimeoutSec, minResponses, quiet are added by Item 2 agent.
|
|
700
|
-
// Passed through here; current orchestrator silently ignores unknown params.
|
|
701
666
|
result = await runCrossOp({
|
|
702
667
|
mode: 'audit',
|
|
703
668
|
target,
|
|
@@ -2353,10 +2318,25 @@ if (isMainModule) {
|
|
|
2353
2318
|
}
|
|
2354
2319
|
|
|
2355
2320
|
if (parsed.cmd === 'help') {
|
|
2356
|
-
|
|
2321
|
+
// v1.5.1 W1.D+E: orchestrator-side help is handled by the installer
|
|
2322
|
+
// (`ijfw --help` for the primary surface, `ijfw commands` for full).
|
|
2323
|
+
// Print a pointer instead of the old stale Usage block.
|
|
2324
|
+
console.log('Run `ijfw --help` for the user-facing command list, or `ijfw commands` for the full surface.');
|
|
2357
2325
|
process.exit(0);
|
|
2358
2326
|
}
|
|
2359
2327
|
|
|
2328
|
+
if (parsed.cmd === 'memory-help') {
|
|
2329
|
+
printMemoryHelp();
|
|
2330
|
+
process.exit(0);
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
if (parsed.cmd === 'memory-unknown') {
|
|
2334
|
+
console.error(`Unknown memory subcommand: ${parsed.sub}`);
|
|
2335
|
+
console.error('');
|
|
2336
|
+
printMemoryHelp();
|
|
2337
|
+
process.exit(1);
|
|
2338
|
+
}
|
|
2339
|
+
|
|
2360
2340
|
if (parsed.cmd === 'status') {
|
|
2361
2341
|
cmdStatus(process.cwd(), parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2362
2342
|
} else if (parsed.cmd === 'demo') {
|
|
@@ -2367,6 +2347,8 @@ if (isMainModule) {
|
|
|
2367
2347
|
cmdCrossProjectAudit(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2368
2348
|
} else if (parsed.cmd === 'command-alias') {
|
|
2369
2349
|
cmdCommandAlias(parsed.alias);
|
|
2350
|
+
} else if (parsed.cmd === 'metrics-benchmark') {
|
|
2351
|
+
cmdMetricsBenchmark(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2370
2352
|
} else if (parsed.cmd === 'import') {
|
|
2371
2353
|
cmdImport(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2372
2354
|
} else if (parsed.cmd === 'doctor') {
|
|
@@ -2411,11 +2393,13 @@ if (isMainModule) {
|
|
|
2411
2393
|
cmdCodex(parsed.sub);
|
|
2412
2394
|
} else if (parsed.cmd === 'memory-checkpoint') {
|
|
2413
2395
|
cmdMemoryCheckpoint(parsed.label);
|
|
2396
|
+
} else if (parsed.cmd === 'memory-reindex') {
|
|
2397
|
+
cmdMemoryReindex(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2414
2398
|
} else if (parsed.cmd === 'recover') {
|
|
2415
2399
|
cmdRecover(parsed.sub);
|
|
2416
2400
|
} else {
|
|
2417
|
-
|
|
2418
|
-
|
|
2401
|
+
// v1.5.1 W1.D+E: clean unknown-command message; no stale usage dump.
|
|
2402
|
+
printUnknownCommand(parsed.raw);
|
|
2419
2403
|
process.exit(1);
|
|
2420
2404
|
}
|
|
2421
2405
|
}
|
|
@@ -2981,11 +2965,23 @@ function codexDoctor(projectRoot) {
|
|
|
2981
2965
|
fix: 'restore codex/.codex/hooks.json and hook scripts',
|
|
2982
2966
|
});
|
|
2983
2967
|
|
|
2968
|
+
// C11 — the message MUST track the same condition `ok` does. Previously the
|
|
2969
|
+
// message said "ijfw-memory configured" whenever config.toml merely existed,
|
|
2970
|
+
// so a config.toml present-but-missing-the-ijfw-memory-block printed the
|
|
2971
|
+
// [ !! ] failure glyph (ok=false) next to success text. Branch all three
|
|
2972
|
+
// states: file absent, file present-but-unconfigured, file configured.
|
|
2973
|
+
const _codexConfigExists = existsSync(configPath);
|
|
2974
|
+
const _codexMemoryConfigured =
|
|
2975
|
+
_codexConfigExists && readFileSync(configPath, 'utf8').includes('ijfw-memory');
|
|
2984
2976
|
checks.push({
|
|
2985
2977
|
name: 'MCP config',
|
|
2986
|
-
ok:
|
|
2978
|
+
ok: _codexMemoryConfigured,
|
|
2987
2979
|
required: true,
|
|
2988
|
-
message:
|
|
2980
|
+
message: _codexMemoryConfigured
|
|
2981
|
+
? 'ijfw-memory configured'
|
|
2982
|
+
: _codexConfigExists
|
|
2983
|
+
? 'config.toml present but ijfw-memory not configured'
|
|
2984
|
+
: 'missing config.toml',
|
|
2989
2985
|
fix: 'run ijfw install or restore codex/.codex/config.toml',
|
|
2990
2986
|
});
|
|
2991
2987
|
|
|
@@ -3355,6 +3351,69 @@ function cmdMemoryCheckpoint(label) {
|
|
|
3355
3351
|
process.exit(0);
|
|
3356
3352
|
}
|
|
3357
3353
|
|
|
3354
|
+
// v1.5.1 R5-1.2 -- `ijfw memory reindex [--m2]`. Closes Trident r5 finding
|
|
3355
|
+
// 1.2: memory written during v1.5.0 (before Round-4 Fix-1 wired M1/M2 into
|
|
3356
|
+
// the production write path) has empty memory_links / memory_tags /
|
|
3357
|
+
// memory_meta. Migration 009 already backfills M1 once on upgrade; this verb
|
|
3358
|
+
// is the manual re-run path AND the only way to opt into the M2 (A-Mem
|
|
3359
|
+
// auto-link) backfill, which is budget-gated because it makes one LLM call
|
|
3360
|
+
// per row.
|
|
3361
|
+
async function cmdMemoryReindex(parsed) {
|
|
3362
|
+
const projectRoot = process.cwd();
|
|
3363
|
+
// Lazy import: better-sqlite3 is heavy; only pay for it on this verb.
|
|
3364
|
+
const { openDb, closeDb, dbPathFor } = await import('./memory/fts5.js');
|
|
3365
|
+
const { backfillObsidianIndex } = await import('./memory/obsidian-parser.js');
|
|
3366
|
+
const { backfillAutoLink } = await import('./memory/auto-linker.js');
|
|
3367
|
+
|
|
3368
|
+
let db;
|
|
3369
|
+
try {
|
|
3370
|
+
db = await openDb(projectRoot);
|
|
3371
|
+
} catch (e) {
|
|
3372
|
+
console.error(`Memory db unavailable: ${e.message}`);
|
|
3373
|
+
process.exit(1);
|
|
3374
|
+
}
|
|
3375
|
+
|
|
3376
|
+
try {
|
|
3377
|
+
console.log(`Reindexing memory at ${dbPathFor(projectRoot)}`);
|
|
3378
|
+
// M1 -- always. Free + idempotent obsidian indexing.
|
|
3379
|
+
const m1 = backfillObsidianIndex(db);
|
|
3380
|
+
console.log(
|
|
3381
|
+
`M1 obsidian-index backfill: ${m1.rows} entries re-indexed ` +
|
|
3382
|
+
`(${m1.links} links, ${m1.tags} tags, ${m1.meta} meta` +
|
|
3383
|
+
`${m1.errors ? `, ${m1.errors} errors` : ''}).`,
|
|
3384
|
+
);
|
|
3385
|
+
|
|
3386
|
+
// M2 -- opt-in via --m2. Budget-gated; backfillAutoLink internally
|
|
3387
|
+
// forces past the IJFW_AUTOLINK_BACKFILL opt-in (the --m2 flag IS the
|
|
3388
|
+
// explicit opt-in) but still honours IJFW_AUTOLINK_OFF, the budget cap,
|
|
3389
|
+
// and the API-key requirement.
|
|
3390
|
+
if (parsed.m2) {
|
|
3391
|
+
const m2 = await backfillAutoLink(db, { force: true });
|
|
3392
|
+
if (m2.skipped) {
|
|
3393
|
+
console.log(
|
|
3394
|
+
`M2 auto-link backfill skipped (${m2.reason}). ` +
|
|
3395
|
+
`M2 backfill needs a positive IJFW_AUTOLINK_BUDGET_USD cap and an ` +
|
|
3396
|
+
`API key (IJFW_AUTOLINK_API_KEY or ANTHROPIC_API_KEY).`,
|
|
3397
|
+
);
|
|
3398
|
+
} else {
|
|
3399
|
+
console.log(
|
|
3400
|
+
`M2 auto-link backfill: ${m2.linked}/${m2.rows} entries linked ` +
|
|
3401
|
+
`(${m2.links_added} links, ${m2.neighbor_tags_added} neighbor tags)` +
|
|
3402
|
+
`${m2.stopped_early ? ' -- stopped early (budget / kill switch)' : ''}.`,
|
|
3403
|
+
);
|
|
3404
|
+
}
|
|
3405
|
+
} else {
|
|
3406
|
+
console.log(
|
|
3407
|
+
'M2 auto-link backfill not run. Re-run with --m2 (budget-gated) to ' +
|
|
3408
|
+
'auto-link old entries via the A-Mem LLM pass.',
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
} finally {
|
|
3412
|
+
closeDb(db);
|
|
3413
|
+
}
|
|
3414
|
+
process.exit(0);
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3358
3417
|
function cmdRecover(sub) {
|
|
3359
3418
|
if (sub === 'latest') {
|
|
3360
3419
|
const latest = latestCheckpoint(process.cwd());
|
|
@@ -1184,9 +1184,22 @@ function buildCycleSummary(iteration, prior) {
|
|
|
1184
1184
|
// this convergence cycle. Aborts a lens once its
|
|
1185
1185
|
// cumulative cost in this run exceeds the cap. Defaults
|
|
1186
1186
|
// to env IJFW_AUDIT_BUDGET_USD_PER_LENS.
|
|
1187
|
+
// autoFix v1.5.1 C2 (T27) — opt-in. When truthy, after a non-PASS
|
|
1188
|
+
// convergence the consensus code-fixer (recovery/code-fixer.js)
|
|
1189
|
+
// fires on HIGH findings that 2+ lenses agreed on. `true`
|
|
1190
|
+
// uses defaults; an object is forwarded to runConsensusFix
|
|
1191
|
+
// (minLenses, dryRun, verifyCmd, maxAutoFixFiles, ...).
|
|
1192
|
+
// Mutates the working tree + writes per-finding atomic
|
|
1193
|
+
// commits — off by default.
|
|
1194
|
+
// SAFETY BOUNDARY (R5-1.10): the fixer can only modify files
|
|
1195
|
+
// inside `projectRoot` (path containment — out-of-root
|
|
1196
|
+
// findings are refused) and `maxAutoFixFiles` (default 10,
|
|
1197
|
+
// ceiling 50) caps the distinct files one run may touch;
|
|
1198
|
+
// beyond the cap it stops + reports rather than mass-rewrite.
|
|
1199
|
+
// `dryRun: true` reports what it WOULD fix without writing.
|
|
1187
1200
|
// Returns:
|
|
1188
1201
|
// { verdict, iterations, findings, divergence?, stalled?, perIteration,
|
|
1189
|
-
// timedOutTotal?, lensesOverBudget?, lensCosts }
|
|
1202
|
+
// timedOutTotal?, lensesOverBudget?, lensCosts, autoFix? }
|
|
1190
1203
|
export async function runPhaseEConverge({
|
|
1191
1204
|
commitRange,
|
|
1192
1205
|
lenses = DEFAULT_LENSES,
|
|
@@ -1198,6 +1211,11 @@ export async function runPhaseEConverge({
|
|
|
1198
1211
|
totalTimeoutMs, // v1.5.0 audit-MED-trident-M6 — cumulative timeout
|
|
1199
1212
|
perLensBudgetUsd, // v1.5.0 audit-MED-trident-M5 — per-lens USD cap
|
|
1200
1213
|
keepaliveOnTick, // v1.5.0 wire-W1.B — caller-supplied keepalive heartbeat
|
|
1214
|
+
autoFix = false, // v1.5.1 C2 (T27) — when truthy, fire the consensus
|
|
1215
|
+
// code-fixer on 2+-lens-agreed HIGH findings after a
|
|
1216
|
+
// non-PASS convergence. Accepts `true` (defaults) or an
|
|
1217
|
+
// options object { minLenses, dryRun, verifyCmd, ... }
|
|
1218
|
+
// forwarded to recovery/code-fixer.js#runConsensusFix.
|
|
1201
1219
|
env = process.env,
|
|
1202
1220
|
} = {}) {
|
|
1203
1221
|
if (typeof dispatch !== 'function') {
|
|
@@ -1488,6 +1506,37 @@ export async function runPhaseEConverge({
|
|
|
1488
1506
|
// break the orchestrator return value.
|
|
1489
1507
|
}
|
|
1490
1508
|
|
|
1509
|
+
// v1.5.1 C2 (T27) — consensus code-fixer wire-up. This is the call site
|
|
1510
|
+
// T27 was designed for: "when 2+ lenses agree on the same HIGH, the fixer
|
|
1511
|
+
// fires automatically." The convergence loop is the canonical Trident
|
|
1512
|
+
// path; once it settles on a non-PASS verdict, extract the consensus HIGH
|
|
1513
|
+
// findings from `perIteration` and run recovery/code-fixer.js's atomic
|
|
1514
|
+
// per-finding fix loop over them. Opt-in (`autoFix`) because it mutates
|
|
1515
|
+
// the working tree + writes commits — never the default for a read-only
|
|
1516
|
+
// audit. PASS verdicts are skipped (nothing to fix). Failure here is
|
|
1517
|
+
// surfaced on `enriched.autoFix` but NEVER changes the convergence
|
|
1518
|
+
// verdict — the fixer is a downstream remediation, not a gate.
|
|
1519
|
+
if (autoFix && enriched.verdict !== VERDICT_PASS) {
|
|
1520
|
+
try {
|
|
1521
|
+
const { runConsensusFix } = await import('./recovery/code-fixer.js');
|
|
1522
|
+
const fixOpts = (autoFix && typeof autoFix === 'object') ? autoFix : {};
|
|
1523
|
+
const fixResult = await runConsensusFix({
|
|
1524
|
+
perIteration,
|
|
1525
|
+
projectRoot: _resolvedProjectDir,
|
|
1526
|
+
dispatch,
|
|
1527
|
+
commitRange,
|
|
1528
|
+
lenses,
|
|
1529
|
+
...fixOpts,
|
|
1530
|
+
});
|
|
1531
|
+
enriched.autoFix = fixResult;
|
|
1532
|
+
} catch (err) {
|
|
1533
|
+
enriched.autoFix = {
|
|
1534
|
+
triggered: false,
|
|
1535
|
+
reason: `code-fixer error: ${err && err.message ? err.message : String(err)}`,
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1491
1540
|
return enriched;
|
|
1492
1541
|
}
|
|
1493
1542
|
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* session-start hook so org/user-scoped extensions
|
|
19
19
|
* become available in every project session.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
21
|
+
* DEFERRED (harness-dependency — IJFW_PARENT_PROJECT_ROOT env passthrough):
|
|
22
22
|
* The Agent({ isolation: 'worktree' }) spawn path lives in the Claude Code
|
|
23
23
|
* harness (Task tool / SDK), NOT in this MCP server's dispatch flow. When the
|
|
24
24
|
* harness eventually exposes a hook for env passthrough on worktree dispatch,
|
package/src/hardware-signer.js
CHANGED
|
@@ -14,8 +14,10 @@
|
|
|
14
14
|
*
|
|
15
15
|
* Backend resolution is FAIL-CLOSED (SEC-L-02): unknown backend names throw
|
|
16
16
|
* rather than silently fall through to software. This means a manifest with
|
|
17
|
-
* `publisher_key_backend: 'libfido2'` (
|
|
18
|
-
*
|
|
17
|
+
* `publisher_key_backend: 'libfido2'` (a direct-FIDO2 backend deferred to a
|
|
18
|
+
* future release — the ssh-agent backend already covers FIDO2 tokens via the
|
|
19
|
+
* agent socket) is a hard error at sign-time, not a quiet downgrade to a
|
|
20
|
+
* weaker backend.
|
|
19
21
|
*
|
|
20
22
|
* Identity selection (SEC-H-03): when the ssh-agent backend signs, the
|
|
21
23
|
* agent is asked to enumerate identities. The expected public-key blob is
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
// ui-review-runner.js -- v1.5.0 wire-W1.D + W1.E.
|
|
1
|
+
// ui-review-runner.js -- v1.5.0 wire-W1.D + W1.E (v1.5.1 W2.A: intake wired).
|
|
2
2
|
//
|
|
3
|
-
// Production wire-up for the
|
|
3
|
+
// Production wire-up for the 6 design libs (uispec-intake, uispec-drift,
|
|
4
4
|
// a11y-contract, lighthouse-pillar, playwright-baseline, sketches-gc) plus
|
|
5
5
|
// the 7-pillar visual audit declared in `claude/agents/ijfw-ui-auditor.md`.
|
|
6
|
+
// All 6 are imported below; the import list IS the canonical wiring count —
|
|
7
|
+
// docstring and imports must move together (v1.5.1 W2.A audit finding).
|
|
6
8
|
//
|
|
7
9
|
// Before W1.D these libraries shipped with isolated tests but ZERO callers.
|
|
8
10
|
// The auditor agent's "wave dispatch one subagent per pillar" was declared
|
|
@@ -38,6 +40,7 @@ import {
|
|
|
38
40
|
import { evaluateLighthouse, LIGHTHOUSE_THRESHOLDS } from './lighthouse-pillar.js';
|
|
39
41
|
import { compareToBaseline } from './playwright-baseline.js';
|
|
40
42
|
import { runSketchesGc } from './sketches-gc.js';
|
|
43
|
+
import { fromImage, fromFigma } from './uispec-intake.js';
|
|
41
44
|
|
|
42
45
|
// Pillar order is canonical -- the auditor agent spec enumerates them in
|
|
43
46
|
// this exact sequence. The runner emits per-pillar sections in the same
|
|
@@ -374,13 +377,16 @@ const GRADERS = Object.freeze({
|
|
|
374
377
|
* @param {object} [args.peerInputs] { axe, lighthouse, playwright } -- optional pre-computed peer-tool outputs
|
|
375
378
|
* @param {boolean} [args.write] when true, write UI-REVIEW.md (default true)
|
|
376
379
|
* @param {boolean} [args.gcSketches] when true, run sketches-gc as the finalizer (default false)
|
|
380
|
+
* @param {string} [args.fromImage] when set, run uispec-intake.fromImage and attach the stub to the result + UI-REVIEW.md "Intake" section.
|
|
381
|
+
* @param {string} [args.fromFigma] when set, run uispec-intake.fromFigma (same surfacing as fromImage).
|
|
377
382
|
* @returns {Promise<{
|
|
378
383
|
* topVerdict: 'PASS'|'FLAG'|'BLOCK',
|
|
379
384
|
* pillarVerdicts: Record<string, string>,
|
|
380
385
|
* verdicts: Array<{pillar, verdict, findings, startedAt, finishedAt}>,
|
|
381
386
|
* reviewPath: string|null,
|
|
382
387
|
* reviewMarkdown: string,
|
|
383
|
-
* parallel: { minStart: number, maxStart: number, minFinish: number, maxFinish: number, parallelism: number }
|
|
388
|
+
* parallel: { minStart: number, maxStart: number, minFinish: number, maxFinish: number, parallelism: number },
|
|
389
|
+
* intake: { kind: 'image'|'figma', ok: boolean, stub: object|null, error: string|null }|null
|
|
384
390
|
* }>}
|
|
385
391
|
*/
|
|
386
392
|
export async function runUiReview({
|
|
@@ -390,6 +396,8 @@ export async function runUiReview({
|
|
|
390
396
|
peerInputs = {},
|
|
391
397
|
write = true,
|
|
392
398
|
gcSketches = false,
|
|
399
|
+
fromImage: fromImagePath = null,
|
|
400
|
+
fromFigma: fromFigmaUrl = null,
|
|
393
401
|
} = {}) {
|
|
394
402
|
if (typeof uiSpecPath !== 'string' || uiSpecPath.length === 0) {
|
|
395
403
|
throw new TypeError('runUiReview: uiSpecPath is required');
|
|
@@ -410,6 +418,20 @@ export async function runUiReview({
|
|
|
410
418
|
const spec = parseUISpec(rawSpec);
|
|
411
419
|
spec.__rawText = rawSpec;
|
|
412
420
|
|
|
421
|
+
// v1.5.1 W2.A: optional uispec-intake pre-fill. When the caller supplies
|
|
422
|
+
// --from-image or --from-figma we run the intake helper and surface the
|
|
423
|
+
// resulting stub on the review (rendered into UI-REVIEW.md and returned
|
|
424
|
+
// structurally). This does NOT mutate the parsed spec used for grading —
|
|
425
|
+
// intake is purely a pre-fill hint for the user's next UI-SPEC edit.
|
|
426
|
+
let intake = null;
|
|
427
|
+
if (fromImagePath) {
|
|
428
|
+
const res = fromImage(fromImagePath, { projectRoot });
|
|
429
|
+
intake = { kind: 'image', ok: res.ok, stub: res.stub, error: res.error };
|
|
430
|
+
} else if (fromFigmaUrl) {
|
|
431
|
+
const res = await fromFigma(fromFigmaUrl);
|
|
432
|
+
intake = { kind: 'figma', ok: res.ok, stub: res.stub, error: res.error };
|
|
433
|
+
}
|
|
434
|
+
|
|
413
435
|
const files = walkSourceFiles(scopes, projectRoot);
|
|
414
436
|
|
|
415
437
|
// W1.E: 7 graders in parallel via Promise.all. Concurrency witness is a
|
|
@@ -475,6 +497,7 @@ export async function runUiReview({
|
|
|
475
497
|
sourceScope: scopes,
|
|
476
498
|
verdicts,
|
|
477
499
|
topVerdict,
|
|
500
|
+
intake,
|
|
478
501
|
});
|
|
479
502
|
|
|
480
503
|
let reviewPath = null;
|
|
@@ -488,7 +511,7 @@ export async function runUiReview({
|
|
|
488
511
|
try { runSketchesGc({ root: join(projectRoot, '.planning', 'sketches') }); } catch {}
|
|
489
512
|
}
|
|
490
513
|
|
|
491
|
-
return { topVerdict, pillarVerdicts, verdicts, reviewPath, reviewMarkdown, parallel };
|
|
514
|
+
return { topVerdict, pillarVerdicts, verdicts, reviewPath, reviewMarkdown, parallel, intake };
|
|
492
515
|
}
|
|
493
516
|
|
|
494
517
|
function computeTopVerdict(verdicts) {
|
|
@@ -503,7 +526,7 @@ function computeTopVerdict(verdicts) {
|
|
|
503
526
|
return top;
|
|
504
527
|
}
|
|
505
528
|
|
|
506
|
-
function renderReview({ uiSpecPath, sourceScope, verdicts, topVerdict }) {
|
|
529
|
+
function renderReview({ uiSpecPath, sourceScope, verdicts, topVerdict, intake = null }) {
|
|
507
530
|
const date = new Date().toISOString().slice(0, 10);
|
|
508
531
|
const scopeStr = Array.isArray(sourceScope) ? sourceScope.join(',') : String(sourceScope);
|
|
509
532
|
const lines = [
|
|
@@ -512,9 +535,27 @@ function renderReview({ uiSpecPath, sourceScope, verdicts, topVerdict }) {
|
|
|
512
535
|
`**Spec:** ${uiSpecPath} **Source scope:** ${scopeStr}`,
|
|
513
536
|
`**Top-level verdict:** ${topVerdict}`,
|
|
514
537
|
'',
|
|
515
|
-
'## Per-pillar verdicts',
|
|
516
|
-
'',
|
|
517
538
|
];
|
|
539
|
+
if (intake) {
|
|
540
|
+
lines.push('## Intake (uispec-intake)');
|
|
541
|
+
lines.push('');
|
|
542
|
+
lines.push(`- **Source kind:** ${intake.kind}`);
|
|
543
|
+
lines.push(`- **Status:** ${intake.ok ? 'ok' : 'error'}`);
|
|
544
|
+
if (intake.error) lines.push(`- **Error:** ${intake.error}`);
|
|
545
|
+
if (intake.stub && intake.stub.advisory) lines.push(`- **Advisory:** ${intake.stub.advisory}`);
|
|
546
|
+
if (intake.stub && intake.stub.source) {
|
|
547
|
+
const src = intake.stub.source;
|
|
548
|
+
if (src.path) lines.push(`- **Path:** ${src.path}`);
|
|
549
|
+
if (src.url) lines.push(`- **URL:** ${src.url}`);
|
|
550
|
+
if (src.bytes != null) lines.push(`- **Bytes:** ${src.bytes}`);
|
|
551
|
+
if (src.dimensions) lines.push(`- **Dimensions:** ${src.dimensions.width}x${src.dimensions.height}`);
|
|
552
|
+
if (src.fileKey) lines.push(`- **Figma file key:** ${src.fileKey}`);
|
|
553
|
+
if (src.name) lines.push(`- **Figma file name:** ${src.name}`);
|
|
554
|
+
}
|
|
555
|
+
lines.push('');
|
|
556
|
+
}
|
|
557
|
+
lines.push('## Per-pillar verdicts');
|
|
558
|
+
lines.push('');
|
|
518
559
|
for (const v of verdicts) {
|
|
519
560
|
const title = PILLAR_TITLES[v.pillar] || v.pillar;
|
|
520
561
|
lines.push(`### ${title} — ${v.verdict}`);
|