@ijfw/memory-server 1.5.0 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +8 -4
- package/src/brain/budget-guard.js +86 -0
- package/src/brain/citation-resolver.js +41 -0
- package/src/brain/context-injection.js +69 -0
- package/src/brain/discovery.js +83 -0
- package/src/brain/dream-pipeline.js +324 -0
- package/src/brain/dump-ingest.js +88 -0
- package/src/brain/entity-collapse.js +28 -0
- package/src/brain/export.js +112 -0
- package/src/brain/extractors/index.js +24 -0
- package/src/brain/extractors/markdown.js +27 -0
- package/src/brain/extractors/pdf.js +31 -0
- package/src/brain/extractors/transcript.js +38 -0
- package/src/brain/first-run-scan.js +61 -0
- package/src/brain/index.js +1 -0
- package/src/brain/layout-sentinel.js +29 -0
- package/src/brain/migrate-facts-internal-once.js +87 -0
- package/src/brain/path-guard.js +103 -0
- package/src/brain/paths.js +26 -0
- package/src/brain/promotion-suggester.js +41 -0
- package/src/brain/stub-detector.js +33 -0
- package/src/brain/tiered-llm.js +83 -0
- package/src/brain/wiki-compiler.js +144 -0
- package/src/brain/wiki-sentinels.js +45 -0
- package/src/brain/wiki-templates.js +94 -0
- package/src/cross-orchestrator-cli.js +336 -150
- package/src/cross-orchestrator.js +52 -3
- package/src/dashboard-server.js +1 -1
- package/src/dispatch/extension.js +1 -1
- package/src/dream/runner.mjs +21 -0
- package/src/extension-registry.js +2 -2
- package/src/handlers/brain-handler.js +319 -0
- package/src/hardware-signer.js +4 -2
- package/src/lib/ui-review-runner.js +48 -7
- package/src/memory/auto-linker.js +121 -2
- package/src/memory/benchmark.js +4 -3
- package/src/memory/layout-migrations/001-visible-layer.js +131 -0
- package/src/memory/layout-migrations/index.js +50 -0
- package/src/memory/migration-runner.js +37 -3
- package/src/memory/migrations/009-obsidian-backfill.js +50 -0
- package/src/memory/obsidian-parser.js +65 -2
- package/src/memory/reader.js +2 -1
- package/src/memory/search.js +190 -41
- package/src/memory/temporal.js +40 -1
- package/src/orchestrator/agents-md-blackboard.js +114 -1
- package/src/orchestrator/debug-trident-trigger.js +374 -0
- package/src/orchestrator/discipline-selector.js +276 -0
- package/src/orchestrator/merge-block-aware.js +15 -5
- package/src/orchestrator/post-done-runner.js +36 -8
- package/src/orchestrator/state-sdk.js +216 -10
- package/src/orchestrator/subagent-telemetry.js +19 -0
- package/src/orchestrator/wave-state.js +38 -0
- package/src/override-resolver.js +5 -3
- package/src/recovery/code-fixer.js +311 -6
- package/src/runtime-mediator.js +0 -1
- package/src/server.js +486 -132
- 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;
|
|
@@ -441,6 +410,13 @@ function parseArgsInner(args) {
|
|
|
441
410
|
return { cmd: 'extension', sub: args[1] || 'list', rest: args.slice(2) };
|
|
442
411
|
}
|
|
443
412
|
|
|
413
|
+
if (args[0] === 'env') {
|
|
414
|
+
// v1.5.2 F6: `ijfw env` lists every IJFW_* env var with current value,
|
|
415
|
+
// default, and one-line description. Closes the configuration-sprawl
|
|
416
|
+
// discoverability gap surfaced in the v1.5.2 cross-audit.
|
|
417
|
+
return { cmd: 'env' };
|
|
418
|
+
}
|
|
419
|
+
|
|
444
420
|
if (args[0] === 'swarm') {
|
|
445
421
|
return { cmd: 'swarm', sub: args[1] || 'status' };
|
|
446
422
|
}
|
|
@@ -449,8 +425,25 @@ function parseArgsInner(args) {
|
|
|
449
425
|
return { cmd: 'codex', sub: args[1] || 'doctor' };
|
|
450
426
|
}
|
|
451
427
|
|
|
452
|
-
if (args[0] === 'memory'
|
|
453
|
-
|
|
428
|
+
if (args[0] === 'memory') {
|
|
429
|
+
// `ijfw memory` / `ijfw memory --help` / `ijfw memory -h` → namespace help
|
|
430
|
+
if (args.length === 1 || args[1] === '--help' || args[1] === '-h') {
|
|
431
|
+
return { cmd: 'memory-help' };
|
|
432
|
+
}
|
|
433
|
+
if (args[1] === 'checkpoint') {
|
|
434
|
+
return { cmd: 'memory-checkpoint', label: args[2] || 'manual' };
|
|
435
|
+
}
|
|
436
|
+
if (args[1] === 'reindex') {
|
|
437
|
+
// `ijfw memory reindex` -> M1 backfill (free, obsidian indexing)
|
|
438
|
+
// `ijfw memory reindex --m2` -> also run M2 A-Mem auto-link backfill
|
|
439
|
+
// (budget-gated; needs IJFW_AUTOLINK_*)
|
|
440
|
+
let m2 = false;
|
|
441
|
+
for (let i = 2; i < args.length; i++) {
|
|
442
|
+
if (args[i] === '--m2' || args[i] === '--autolink') m2 = true;
|
|
443
|
+
}
|
|
444
|
+
return { cmd: 'memory-reindex', m2 };
|
|
445
|
+
}
|
|
446
|
+
return { cmd: 'memory-unknown', sub: args[1] };
|
|
454
447
|
}
|
|
455
448
|
|
|
456
449
|
if (args[0] === 'recover') {
|
|
@@ -514,86 +507,67 @@ function parseArgsInner(args) {
|
|
|
514
507
|
// Commands
|
|
515
508
|
// ---------------------------------------------------------------------------
|
|
516
509
|
|
|
517
|
-
|
|
510
|
+
// v1.5.2 F6: every IJFW_* env var the brain + memory subsystems read at runtime.
|
|
511
|
+
// Order: most-likely-to-set first. `default` is the documented fallback when
|
|
512
|
+
// the var is unset; `description` is one short line for `ijfw env` output.
|
|
513
|
+
const IJFW_ENV_VARS = [
|
|
514
|
+
// Brain (v1.5.2)
|
|
515
|
+
{ name: 'IJFW_DREAM_BUDGET_USD', default: '0.50', description: 'Per-cycle USD cap for dream-cycle LLM extraction.' },
|
|
516
|
+
{ name: 'IJFW_DREAM_BUDGET_DAY_USD', default: '5.00', description: 'Per-day USD cap; cycle stops when reached.' },
|
|
517
|
+
{ name: 'IJFW_BRAIN_LOCAL_URL', default: '(unset)', description: 'Ollama-compatible local LLM endpoint to try first.' },
|
|
518
|
+
{ name: 'IJFW_BRAIN_EXTRACT_MODEL', default: 'claude-haiku-4-5-20251001', description: 'Cheap-tier model id for fact extraction.' },
|
|
519
|
+
{ name: 'IJFW_BRAIN_SYNTH_MODEL', default: 'claude-sonnet-4-6', description: 'Mid-tier model id for reconciliation + page synthesis.' },
|
|
520
|
+
{ name: 'IJFW_BRAIN_API_KEY', default: '(falls back to ANTHROPIC_API_KEY)', description: 'API key for the synth-tier Anthropic call.' },
|
|
521
|
+
{ name: 'IJFW_BRAIN_INJECT', default: 'never', description: '"auto"|"always" appends top-N wiki pages to handlePrelude.' },
|
|
522
|
+
// Memory-moat (v1.5.0 — A-Mem auto-linker)
|
|
523
|
+
{ name: 'IJFW_AUTOLINK_OFF', default: '(unset)', description: 'Set to disable the A-Mem auto-linker entirely.' },
|
|
524
|
+
{ name: 'IJFW_AUTOLINK_BUDGET_USD', default: '(unbounded if unset)', description: 'Per-write USD cap for auto-linker LLM calls.' },
|
|
525
|
+
{ name: 'IJFW_AUTOLINK_BACKFILL', default: '(unset)', description: 'Set to "1" to opt into M2 backfill during `memory reindex --m2`.' },
|
|
526
|
+
];
|
|
527
|
+
|
|
528
|
+
export function cmdEnv() {
|
|
529
|
+
const widthName = Math.max(...IJFW_ENV_VARS.map((v) => v.name.length)) + 2;
|
|
530
|
+
console.log('IJFW environment variables\n');
|
|
531
|
+
for (const v of IJFW_ENV_VARS) {
|
|
532
|
+
const current = process.env[v.name];
|
|
533
|
+
const shownValue = current !== undefined && current !== '' ? current : '(unset)';
|
|
534
|
+
const isOverridden = current !== undefined && current !== '';
|
|
535
|
+
const tag = isOverridden ? ' [SET]' : '';
|
|
536
|
+
console.log(` ${v.name.padEnd(widthName)} = ${shownValue}${tag}`);
|
|
537
|
+
console.log(` ${' '.repeat(widthName)} default: ${v.default}`);
|
|
538
|
+
console.log(` ${' '.repeat(widthName)} ${v.description}`);
|
|
539
|
+
console.log('');
|
|
540
|
+
}
|
|
541
|
+
console.log('Tip: variables tagged [SET] override their defaults. Unset values show the documented default in effect.');
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function printMemoryHelp() {
|
|
518
545
|
console.log(`
|
|
519
|
-
ijfw --
|
|
520
|
-
Fire 2-4 AIs at any target. Receipts logged. Cache hits tracked. Memory follows you.
|
|
546
|
+
ijfw memory -- project memory namespace
|
|
521
547
|
|
|
522
548
|
Usage:
|
|
523
|
-
ijfw
|
|
524
|
-
|
|
525
|
-
ijfw
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
ijfw
|
|
534
|
-
ijfw
|
|
535
|
-
ijfw
|
|
536
|
-
ijfw
|
|
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
|
|
549
|
+
ijfw memory checkpoint <label> Snapshot current swarm/memory state under <label>.
|
|
550
|
+
<label> defaults to "manual" if omitted.
|
|
551
|
+
ijfw memory reindex [--m2] Backfill M1 obsidian indexing (wikilinks,
|
|
552
|
+
#tags, [k:: v] metadata) over the whole
|
|
553
|
+
memory db. Free + idempotent. Add --m2 to
|
|
554
|
+
also run the A-Mem auto-link backfill --
|
|
555
|
+
budget-gated (set IJFW_AUTOLINK_BUDGET_USD
|
|
556
|
+
and IJFW_AUTOLINK_BACKFILL=1).
|
|
557
|
+
|
|
558
|
+
Related:
|
|
559
|
+
ijfw recover [status|latest] Inspect checkpoints and recovery state.
|
|
560
|
+
ijfw env List every IJFW_* env var, current value, default, description.
|
|
561
|
+
ijfw --help Top-level user-facing commands.
|
|
562
|
+
ijfw commands Full command surface (all verbs).
|
|
594
563
|
`.trim());
|
|
595
564
|
}
|
|
596
565
|
|
|
566
|
+
function printUnknownCommand(raw) {
|
|
567
|
+
console.error(`Unknown command: ${raw}`);
|
|
568
|
+
console.error('Run `ijfw --help` for the user-facing command list, or `ijfw commands` for the full surface.');
|
|
569
|
+
}
|
|
570
|
+
|
|
597
571
|
function cmdCommandAlias(alias) {
|
|
598
572
|
const info = COMMAND_ALIAS_HELP[alias];
|
|
599
573
|
if (!info) {
|
|
@@ -606,6 +580,41 @@ function cmdCommandAlias(alias) {
|
|
|
606
580
|
process.exit(0);
|
|
607
581
|
}
|
|
608
582
|
|
|
583
|
+
// v1.5.1 W2.H — `ijfw metrics --benchmark`: run the memory benchmark harness
|
|
584
|
+
// (T22) against IJFW's own 3-tier store and report recall@k, MRR/NDCG-style
|
|
585
|
+
// retrieval quality, throughput, and p50/p95/p99 latency. See
|
|
586
|
+
// docs/MEMORY-BENCHMARK.md for the axes and how to interpret the numbers.
|
|
587
|
+
async function cmdMetricsBenchmark(opts = {}) {
|
|
588
|
+
const results = await runBenchmark({
|
|
589
|
+
root: process.cwd(),
|
|
590
|
+
write: opts.write !== false,
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
if (wantsJson(opts)) {
|
|
594
|
+
emitJson(results);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const q = results.axes.query_warm_fts5;
|
|
599
|
+
const ing = results.axes.ingest;
|
|
600
|
+
const recallPairs = Object.entries(q.recall || {});
|
|
601
|
+
console.log('IJFW memory benchmark (T22)');
|
|
602
|
+
console.log('');
|
|
603
|
+
console.log(` corpus ${results.corpus.docs} docs / ${results.corpus.queries} queries / ${results.corpus.total_query_samples} timed samples`);
|
|
604
|
+
console.log(` ingest throughput ${ing.throughput_rps} rows/s`);
|
|
605
|
+
console.log(` ingest latency p50 ${ing.latency_ms.p50}ms p95 ${ing.latency_ms.p95}ms p99 ${ing.latency_ms.p99}ms`);
|
|
606
|
+
console.log(` query latency p50 ${q.latency_ms.p50}ms p95 ${q.latency_ms.p95}ms p99 ${q.latency_ms.p99}ms`);
|
|
607
|
+
console.log(` recall ${recallPairs.map(([k, v]) => `${k}=${v.toFixed(3)}`).join(' ')}`);
|
|
608
|
+
console.log(` storage ${results.axes.storage.bytes_per_memory} bytes/memory (${results.axes.storage.rows_indexed} rows)`);
|
|
609
|
+
console.log(` cold tier ${results.axes.query_cold_vector.available ? 'available' : 'reserved (no embedding model)'}`);
|
|
610
|
+
if (results.artifact_path) {
|
|
611
|
+
console.log('');
|
|
612
|
+
console.log(` artifact ${results.artifact_path}`);
|
|
613
|
+
}
|
|
614
|
+
console.log('');
|
|
615
|
+
console.log('Axes explained: docs/MEMORY-BENCHMARK.md');
|
|
616
|
+
}
|
|
617
|
+
|
|
609
618
|
async function cmdStatus(projectDir, opts = {}) {
|
|
610
619
|
const receipts = readReceipts(projectDir);
|
|
611
620
|
const last = receipts[receipts.length - 1];
|
|
@@ -696,8 +705,6 @@ async function cmdDemo() {
|
|
|
696
705
|
|
|
697
706
|
let result;
|
|
698
707
|
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
708
|
result = await runCrossOp({
|
|
702
709
|
mode: 'audit',
|
|
703
710
|
target,
|
|
@@ -1072,7 +1079,7 @@ async function cmdCross({ mode, target, only, confirm, expand, chunk }) {
|
|
|
1072
1079
|
const chunks = buildChunkedTargets(absPath, rawTarget);
|
|
1073
1080
|
console.log('');
|
|
1074
1081
|
console.log(`--chunk: splitting ${rawTarget} into ${chunks.length} chunks (≈${(CHUNKER_DEFAULTS.chunkSize / 1024).toFixed(0)} KB each, ${(CHUNKER_DEFAULTS.overlap / 1024).toFixed(0)} KB overlap).`);
|
|
1075
|
-
console.log(`Trident dispatches: ${chunks.length}
|
|
1082
|
+
console.log(`Trident dispatches: ${chunks.length} x per-chunk audit. Cost scales linearly.`);
|
|
1076
1083
|
|
|
1077
1084
|
const perChunkResults = [];
|
|
1078
1085
|
const auditorIds = new Set();
|
|
@@ -1107,7 +1114,7 @@ async function cmdCross({ mode, target, only, confirm, expand, chunk }) {
|
|
|
1107
1114
|
}
|
|
1108
1115
|
for (const f of merged) {
|
|
1109
1116
|
const sev = (f.severity || 'note').toUpperCase();
|
|
1110
|
-
const cluster = f.clusterSize > 1 ? ` [
|
|
1117
|
+
const cluster = f.clusterSize > 1 ? ` [x${f.clusterSize}]` : '';
|
|
1111
1118
|
const tgt = f.target ? ` ${f.target} —` : '';
|
|
1112
1119
|
// v1.5.0 wire-W4: widen field fallback to cover description/issue/
|
|
1113
1120
|
// detail/note/summary keys auditors emit. Closes the r19 "(no detail)"
|
|
@@ -2353,10 +2360,25 @@ if (isMainModule) {
|
|
|
2353
2360
|
}
|
|
2354
2361
|
|
|
2355
2362
|
if (parsed.cmd === 'help') {
|
|
2356
|
-
|
|
2363
|
+
// v1.5.1 W1.D+E: orchestrator-side help is handled by the installer
|
|
2364
|
+
// (`ijfw --help` for the primary surface, `ijfw commands` for full).
|
|
2365
|
+
// Print a pointer instead of the old stale Usage block.
|
|
2366
|
+
console.log('Run `ijfw --help` for the user-facing command list, or `ijfw commands` for the full surface.');
|
|
2357
2367
|
process.exit(0);
|
|
2358
2368
|
}
|
|
2359
2369
|
|
|
2370
|
+
if (parsed.cmd === 'memory-help') {
|
|
2371
|
+
printMemoryHelp();
|
|
2372
|
+
process.exit(0);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
if (parsed.cmd === 'memory-unknown') {
|
|
2376
|
+
console.error(`Unknown memory subcommand: ${parsed.sub}`);
|
|
2377
|
+
console.error('');
|
|
2378
|
+
printMemoryHelp();
|
|
2379
|
+
process.exit(1);
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2360
2382
|
if (parsed.cmd === 'status') {
|
|
2361
2383
|
cmdStatus(process.cwd(), parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2362
2384
|
} else if (parsed.cmd === 'demo') {
|
|
@@ -2367,6 +2389,8 @@ if (isMainModule) {
|
|
|
2367
2389
|
cmdCrossProjectAudit(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2368
2390
|
} else if (parsed.cmd === 'command-alias') {
|
|
2369
2391
|
cmdCommandAlias(parsed.alias);
|
|
2392
|
+
} else if (parsed.cmd === 'metrics-benchmark') {
|
|
2393
|
+
cmdMetricsBenchmark(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2370
2394
|
} else if (parsed.cmd === 'import') {
|
|
2371
2395
|
cmdImport(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2372
2396
|
} else if (parsed.cmd === 'doctor') {
|
|
@@ -2411,11 +2435,15 @@ if (isMainModule) {
|
|
|
2411
2435
|
cmdCodex(parsed.sub);
|
|
2412
2436
|
} else if (parsed.cmd === 'memory-checkpoint') {
|
|
2413
2437
|
cmdMemoryCheckpoint(parsed.label);
|
|
2438
|
+
} else if (parsed.cmd === 'memory-reindex') {
|
|
2439
|
+
cmdMemoryReindex(parsed).catch(err => { console.error(err.message); process.exit(1); });
|
|
2440
|
+
} else if (parsed.cmd === 'env') {
|
|
2441
|
+
cmdEnv();
|
|
2414
2442
|
} else if (parsed.cmd === 'recover') {
|
|
2415
2443
|
cmdRecover(parsed.sub);
|
|
2416
2444
|
} else {
|
|
2417
|
-
|
|
2418
|
-
|
|
2445
|
+
// v1.5.1 W1.D+E: clean unknown-command message; no stale usage dump.
|
|
2446
|
+
printUnknownCommand(parsed.raw);
|
|
2419
2447
|
process.exit(1);
|
|
2420
2448
|
}
|
|
2421
2449
|
}
|
|
@@ -2433,10 +2461,19 @@ function repoRootFromCli() {
|
|
|
2433
2461
|
return join(here, '..', '..');
|
|
2434
2462
|
}
|
|
2435
2463
|
function findCliAsset(...rel) {
|
|
2464
|
+
// F-C-4 (Lens 3): probe XDG_DATA_HOME and XDG_CONFIG_HOME in addition to
|
|
2465
|
+
// ~/.ijfw and IJFW_HOME. Users who installed via a distro-aware packager
|
|
2466
|
+
// (e.g. dotfiles repo, nix, distro RPM, or any wrapper that honours XDG
|
|
2467
|
+
// base-dir spec) land their ijfw tree under $XDG_DATA_HOME/ijfw rather
|
|
2468
|
+
// than ~/.ijfw, and were silently invisible to the doctor fallback.
|
|
2469
|
+
const xdgData = process.env.XDG_DATA_HOME;
|
|
2470
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME;
|
|
2436
2471
|
const candidates = [
|
|
2437
2472
|
join(repoRootFromCli(), ...rel),
|
|
2438
2473
|
process.env.IJFW_HOME ? join(process.env.IJFW_HOME, ...rel) : null,
|
|
2439
2474
|
join(homedir(), '.ijfw', ...rel),
|
|
2475
|
+
xdgData ? join(xdgData, 'ijfw', ...rel) : null,
|
|
2476
|
+
xdgConfig ? join(xdgConfig, 'ijfw', ...rel) : null,
|
|
2440
2477
|
].filter(Boolean);
|
|
2441
2478
|
return candidates.find(p => existsSync(p)) || null;
|
|
2442
2479
|
}
|
|
@@ -2935,6 +2972,42 @@ function cmdCodex(sub) {
|
|
|
2935
2972
|
process.exit(1);
|
|
2936
2973
|
}
|
|
2937
2974
|
|
|
2975
|
+
// F4.1/F4.2: resolve the canonical IJFW version with explicit source labelling
|
|
2976
|
+
// and a corrupt-vs-missing distinction. Exported so the codex-doctor tests can
|
|
2977
|
+
// exercise the fallback matrix without spawning the CLI against synthetic
|
|
2978
|
+
// repo trees. Inputs are absolute paths (or null) so callers can stub them.
|
|
2979
|
+
//
|
|
2980
|
+
// Returns: { canonicalVersion, canonicalSource, canonicalParseError } where
|
|
2981
|
+
// canonicalVersion : string | null (first source whose JSON parses)
|
|
2982
|
+
// canonicalSource : 'installer' | 'mcp-server' | null
|
|
2983
|
+
// canonicalParseError : string | null (only set if installer pkg corrupt)
|
|
2984
|
+
export function resolveCanonicalVersion({ installerPkg, selfPkg } = {}) {
|
|
2985
|
+
let canonicalVersion = null;
|
|
2986
|
+
let canonicalSource = null;
|
|
2987
|
+
let canonicalParseError = null;
|
|
2988
|
+
for (const [src, p] of [['installer', installerPkg], ['mcp-server', selfPkg]]) {
|
|
2989
|
+
if (!p || !existsSync(p)) continue;
|
|
2990
|
+
let raw;
|
|
2991
|
+
try {
|
|
2992
|
+
raw = readFileSync(p, 'utf8');
|
|
2993
|
+
} catch { continue; }
|
|
2994
|
+
try {
|
|
2995
|
+
canonicalVersion = JSON.parse(raw).version;
|
|
2996
|
+
canonicalSource = src;
|
|
2997
|
+
break;
|
|
2998
|
+
} catch (e) {
|
|
2999
|
+
// F-C-2 (Lens 3): capture parse error from whichever source was attempted
|
|
3000
|
+
// and label the source. Previously only installer parse errors surfaced,
|
|
3001
|
+
// so a corrupt mcp-server/package.json (e.g. partial pnpm install) caused
|
|
3002
|
+
// the doctor to render misleading "install @ijfw/install" fix text.
|
|
3003
|
+
if (canonicalParseError == null) {
|
|
3004
|
+
canonicalParseError = `${src}: ${e.message}`;
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
return { canonicalVersion, canonicalSource, canonicalParseError };
|
|
3009
|
+
}
|
|
3010
|
+
|
|
2938
3011
|
function codexDoctor(projectRoot) {
|
|
2939
3012
|
const root = resolve(projectRoot);
|
|
2940
3013
|
const checks = [];
|
|
@@ -2962,12 +3035,50 @@ function codexDoctor(projectRoot) {
|
|
|
2962
3035
|
const agentsMd = join(root, 'AGENTS.md');
|
|
2963
3036
|
|
|
2964
3037
|
const plugin = readJsonFile(pluginPath);
|
|
3038
|
+
// v1.5.2.1: read the canonical version dynamically from
|
|
3039
|
+
// installer/package.json instead of comparing against a hardcoded literal.
|
|
3040
|
+
// The previous hardcoded '1.3.2' check drifted out of sync on every
|
|
3041
|
+
// release (latest observed: project at 1.5.1, doctor still expecting
|
|
3042
|
+
// 1.3.2 → false-positive failures on every fresh install). Reading from
|
|
3043
|
+
// installer/package.json keeps the doctor honest as long as that file
|
|
3044
|
+
// and the codex plugin.json are bumped together at ship-gate.
|
|
3045
|
+
//
|
|
3046
|
+
// F4.1: standalone @ijfw/memory-server installs do not ship installer/.
|
|
3047
|
+
// F4.3: regressing against the established findCliAsset() convention. Reuse it.
|
|
3048
|
+
// F4.2: differentiate ENOENT (missing) from SyntaxError (corrupt).
|
|
3049
|
+
const { canonicalVersion, canonicalSource, canonicalParseError } =
|
|
3050
|
+
resolveCanonicalVersion({
|
|
3051
|
+
installerPkg: findCliAsset('installer', 'package.json'),
|
|
3052
|
+
// F-C-1 (Lens 3): IJFW_HOME fallback was previously installer-only; make
|
|
3053
|
+
// mcp-server resolution symmetric so a custom-checkout user with IJFW_HOME
|
|
3054
|
+
// pointed at a partial tree also gets a working fallback.
|
|
3055
|
+
selfPkg: findCliAsset('mcp-server', 'package.json')
|
|
3056
|
+
?? join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'),
|
|
3057
|
+
});
|
|
2965
3058
|
checks.push({
|
|
2966
3059
|
name: 'plugin metadata',
|
|
2967
|
-
ok: plugin
|
|
3060
|
+
ok: !!plugin && !!canonicalVersion && plugin.version === canonicalVersion,
|
|
2968
3061
|
required: true,
|
|
2969
|
-
message: plugin
|
|
2970
|
-
|
|
3062
|
+
message: plugin
|
|
3063
|
+
? (canonicalVersion
|
|
3064
|
+
? `version ${plugin.version}${plugin.version === canonicalVersion ? '' : ` (expected ${canonicalVersion} per ${canonicalSource}/package.json)`}`
|
|
3065
|
+
: canonicalParseError
|
|
3066
|
+
? `version ${plugin.version} (canonical source corrupt: ${canonicalParseError})`
|
|
3067
|
+
: `version ${plugin.version} (canonical version unreadable -- install @ijfw/install or set IJFW_HOME)`)
|
|
3068
|
+
: 'missing plugin.json',
|
|
3069
|
+
// F-C-9 (Lens 3): if plugin.json itself is missing, the fix text needs
|
|
3070
|
+
// to point at restoring plugin.json, NOT at the canonical-version dance.
|
|
3071
|
+
// canonicalParseError is now source-labelled by F-C-2 ("installer: ..."
|
|
3072
|
+
// or "mcp-server: ..."), so derive the specific package.json to restore.
|
|
3073
|
+
fix: !plugin
|
|
3074
|
+
? `restore codex/.codex-plugin/plugin.json (try \`git checkout codex/.codex-plugin/plugin.json\`)`
|
|
3075
|
+
: canonicalVersion
|
|
3076
|
+
? (plugin.version !== canonicalVersion
|
|
3077
|
+
? `update codex/.codex-plugin/plugin.json version to ${canonicalVersion}`
|
|
3078
|
+
: null)
|
|
3079
|
+
: canonicalParseError
|
|
3080
|
+
? `restore canonical source (try \`git checkout ${canonicalParseError.split(':')[0]}/package.json\`)`
|
|
3081
|
+
: `install @ijfw/install (npm i -g @ijfw/install), or set IJFW_HOME=<ijfw-repo-checkout>`,
|
|
2971
3082
|
});
|
|
2972
3083
|
|
|
2973
3084
|
const hooks = readJsonFile(hooksPath);
|
|
@@ -2981,11 +3092,23 @@ function codexDoctor(projectRoot) {
|
|
|
2981
3092
|
fix: 'restore codex/.codex/hooks.json and hook scripts',
|
|
2982
3093
|
});
|
|
2983
3094
|
|
|
3095
|
+
// C11 — the message MUST track the same condition `ok` does. Previously the
|
|
3096
|
+
// message said "ijfw-memory configured" whenever config.toml merely existed,
|
|
3097
|
+
// so a config.toml present-but-missing-the-ijfw-memory-block printed the
|
|
3098
|
+
// [ !! ] failure glyph (ok=false) next to success text. Branch all three
|
|
3099
|
+
// states: file absent, file present-but-unconfigured, file configured.
|
|
3100
|
+
const _codexConfigExists = existsSync(configPath);
|
|
3101
|
+
const _codexMemoryConfigured =
|
|
3102
|
+
_codexConfigExists && readFileSync(configPath, 'utf8').includes('ijfw-memory');
|
|
2984
3103
|
checks.push({
|
|
2985
3104
|
name: 'MCP config',
|
|
2986
|
-
ok:
|
|
3105
|
+
ok: _codexMemoryConfigured,
|
|
2987
3106
|
required: true,
|
|
2988
|
-
message:
|
|
3107
|
+
message: _codexMemoryConfigured
|
|
3108
|
+
? 'ijfw-memory configured'
|
|
3109
|
+
: _codexConfigExists
|
|
3110
|
+
? 'config.toml present but ijfw-memory not configured'
|
|
3111
|
+
: 'missing config.toml',
|
|
2989
3112
|
fix: 'run ijfw install or restore codex/.codex/config.toml',
|
|
2990
3113
|
});
|
|
2991
3114
|
|
|
@@ -3355,6 +3478,69 @@ function cmdMemoryCheckpoint(label) {
|
|
|
3355
3478
|
process.exit(0);
|
|
3356
3479
|
}
|
|
3357
3480
|
|
|
3481
|
+
// v1.5.1 R5-1.2 -- `ijfw memory reindex [--m2]`. Closes Trident r5 finding
|
|
3482
|
+
// 1.2: memory written during v1.5.0 (before Round-4 Fix-1 wired M1/M2 into
|
|
3483
|
+
// the production write path) has empty memory_links / memory_tags /
|
|
3484
|
+
// memory_meta. Migration 009 already backfills M1 once on upgrade; this verb
|
|
3485
|
+
// is the manual re-run path AND the only way to opt into the M2 (A-Mem
|
|
3486
|
+
// auto-link) backfill, which is budget-gated because it makes one LLM call
|
|
3487
|
+
// per row.
|
|
3488
|
+
async function cmdMemoryReindex(parsed) {
|
|
3489
|
+
const projectRoot = process.cwd();
|
|
3490
|
+
// Lazy import: better-sqlite3 is heavy; only pay for it on this verb.
|
|
3491
|
+
const { openDb, closeDb, dbPathFor } = await import('./memory/fts5.js');
|
|
3492
|
+
const { backfillObsidianIndex } = await import('./memory/obsidian-parser.js');
|
|
3493
|
+
const { backfillAutoLink } = await import('./memory/auto-linker.js');
|
|
3494
|
+
|
|
3495
|
+
let db;
|
|
3496
|
+
try {
|
|
3497
|
+
db = await openDb(projectRoot);
|
|
3498
|
+
} catch (e) {
|
|
3499
|
+
console.error(`Memory db unavailable: ${e.message}`);
|
|
3500
|
+
process.exit(1);
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
try {
|
|
3504
|
+
console.log(`Reindexing memory at ${dbPathFor(projectRoot)}`);
|
|
3505
|
+
// M1 -- always. Free + idempotent obsidian indexing.
|
|
3506
|
+
const m1 = backfillObsidianIndex(db);
|
|
3507
|
+
console.log(
|
|
3508
|
+
`M1 obsidian-index backfill: ${m1.rows} entries re-indexed ` +
|
|
3509
|
+
`(${m1.links} links, ${m1.tags} tags, ${m1.meta} meta` +
|
|
3510
|
+
`${m1.errors ? `, ${m1.errors} errors` : ''}).`,
|
|
3511
|
+
);
|
|
3512
|
+
|
|
3513
|
+
// M2 -- opt-in via --m2. Budget-gated; backfillAutoLink internally
|
|
3514
|
+
// forces past the IJFW_AUTOLINK_BACKFILL opt-in (the --m2 flag IS the
|
|
3515
|
+
// explicit opt-in) but still honours IJFW_AUTOLINK_OFF, the budget cap,
|
|
3516
|
+
// and the API-key requirement.
|
|
3517
|
+
if (parsed.m2) {
|
|
3518
|
+
const m2 = await backfillAutoLink(db, { force: true });
|
|
3519
|
+
if (m2.skipped) {
|
|
3520
|
+
console.log(
|
|
3521
|
+
`M2 auto-link backfill skipped (${m2.reason}). ` +
|
|
3522
|
+
`M2 backfill needs a positive IJFW_AUTOLINK_BUDGET_USD cap and an ` +
|
|
3523
|
+
`API key (IJFW_AUTOLINK_API_KEY or ANTHROPIC_API_KEY).`,
|
|
3524
|
+
);
|
|
3525
|
+
} else {
|
|
3526
|
+
console.log(
|
|
3527
|
+
`M2 auto-link backfill: ${m2.linked}/${m2.rows} entries linked ` +
|
|
3528
|
+
`(${m2.links_added} links, ${m2.neighbor_tags_added} neighbor tags)` +
|
|
3529
|
+
`${m2.stopped_early ? ' -- stopped early (budget / kill switch)' : ''}.`,
|
|
3530
|
+
);
|
|
3531
|
+
}
|
|
3532
|
+
} else {
|
|
3533
|
+
console.log(
|
|
3534
|
+
'M2 auto-link backfill not run. Re-run with --m2 (budget-gated) to ' +
|
|
3535
|
+
'auto-link old entries via the A-Mem LLM pass.',
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
} finally {
|
|
3539
|
+
closeDb(db);
|
|
3540
|
+
}
|
|
3541
|
+
process.exit(0);
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3358
3544
|
function cmdRecover(sub) {
|
|
3359
3545
|
if (sub === 'latest') {
|
|
3360
3546
|
const latest = latestCheckpoint(process.cwd());
|