@ijfw/install 1.4.4 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ijfw.js CHANGED
@@ -1583,7 +1583,7 @@ var init_project_type_detector = __esm({
1583
1583
  });
1584
1584
 
1585
1585
  // ../mcp-server/src/gate-result.js
1586
- import { mkdir, writeFile } from "node:fs/promises";
1586
+ import { mkdir, writeFile, readdir, stat, unlink, appendFile } from "node:fs/promises";
1587
1587
  import { basename, dirname as dirname2, join as join6 } from "node:path";
1588
1588
  async function emitGateResult(gateOpts, context = {}) {
1589
1589
  if (gateOpts === null || typeof gateOpts !== "object") {
@@ -1643,6 +1643,7 @@ async function makeReceipt(gateResult, opts = {}) {
1643
1643
  await mkdir(dirname2(receiptPath), { recursive: true });
1644
1644
  const body = JSON.stringify(gateResult, null, 2) + "\n";
1645
1645
  await writeFile(receiptPath, body, "utf8");
1646
+ await evictOldReceipts(dirname2(receiptPath));
1646
1647
  } catch (err) {
1647
1648
  const msg = err && err.message ? err.message : String(err);
1648
1649
  try {
@@ -1652,6 +1653,58 @@ async function makeReceipt(gateResult, opts = {}) {
1652
1653
  }
1653
1654
  }
1654
1655
  }
1656
+ async function evictOldReceipts(dir, opts = {}) {
1657
+ const keep = Number.isFinite(opts.keep) && opts.keep > 0 ? opts.keep | 0 : RECEIPTS_KEEP;
1658
+ try {
1659
+ let entries;
1660
+ try {
1661
+ entries = await readdir(dir);
1662
+ } catch {
1663
+ return { evicted: 0 };
1664
+ }
1665
+ const jsonFiles = entries.filter((f) => f.endsWith(".json"));
1666
+ if (jsonFiles.length <= keep) return { evicted: 0 };
1667
+ const stamped = [];
1668
+ for (const f of jsonFiles) {
1669
+ const full = join6(dir, f);
1670
+ try {
1671
+ const s = await stat(full);
1672
+ stamped.push({ full, name: f, mtimeMs: s.mtimeMs });
1673
+ } catch {
1674
+ stamped.push({ full, name: f, mtimeMs: 0 });
1675
+ }
1676
+ }
1677
+ stamped.sort((a, b2) => b2.mtimeMs - a.mtimeMs);
1678
+ const toEvict = stamped.slice(keep);
1679
+ if (toEvict.length === 0) return { evicted: 0 };
1680
+ const archivePath = join6(dir, RECEIPTS_ARCHIVE);
1681
+ let evicted = 0;
1682
+ for (const victim of toEvict) {
1683
+ try {
1684
+ let body;
1685
+ try {
1686
+ const { readFile } = await import("node:fs/promises");
1687
+ body = await readFile(victim.full, "utf8");
1688
+ } catch {
1689
+ continue;
1690
+ }
1691
+ let archiveLine;
1692
+ try {
1693
+ archiveLine = JSON.stringify(JSON.parse(body)) + "\n";
1694
+ } catch {
1695
+ archiveLine = JSON.stringify({ raw: body, evicted_from: victim.name }) + "\n";
1696
+ }
1697
+ await appendFile(archivePath, archiveLine, "utf8");
1698
+ await unlink(victim.full);
1699
+ evicted++;
1700
+ } catch {
1701
+ }
1702
+ }
1703
+ return { evicted };
1704
+ } catch {
1705
+ return { evicted: 0 };
1706
+ }
1707
+ }
1655
1708
  async function resolveProjectType(projectRoot) {
1656
1709
  try {
1657
1710
  const root = typeof projectRoot === "string" && projectRoot.length > 0 ? projectRoot : process.cwd();
@@ -1667,12 +1720,14 @@ async function resolveProjectType(projectRoot) {
1667
1720
  return "unknown";
1668
1721
  }
1669
1722
  }
1670
- var RECEIPT_GATE_ID_PATTERN;
1723
+ var RECEIPT_GATE_ID_PATTERN, RECEIPTS_KEEP, RECEIPTS_ARCHIVE;
1671
1724
  var init_gate_result = __esm({
1672
1725
  "../mcp-server/src/gate-result.js"() {
1673
1726
  init_gate_result_schema();
1674
1727
  init_project_type_detector();
1675
1728
  RECEIPT_GATE_ID_PATTERN = /^[a-z][a-z0-9-]+$/;
1729
+ RECEIPTS_KEEP = 1e3;
1730
+ RECEIPTS_ARCHIVE = ".archive.jsonl";
1676
1731
  }
1677
1732
  });
1678
1733
 
package/docs/GUIDE.md CHANGED
@@ -172,7 +172,7 @@ Run `/team setup` in Claude Code, or `ijfw team` from the shell, to see your cur
172
172
  |------|-------|--------------|
173
173
  | Hot | Plain markdown in `.ijfw/memory/` | Always on. Instant reads. Git friendly. |
174
174
  | Warm | BM25 ranked search | Always on. Scales to around 10,000 entries per project. |
175
- | Cold | Optional semantic vectors | Off by default. Requires a user-installed embedding provider. |
175
+ | Cold | Optional semantic vectors (hybrid BM25 + cosine rerank) | Off by default. Enable with `IJFW_VECTORS=on` and `npm i @xenova/transformers` (one-time ~23MB MiniLM model cached locally). Top-K BM25 candidates are reranked via cosine similarity with weights 0.6 BM25 / 0.4 vector. Pure no-op fallback to BM25 if disabled, the package isn't installed, or the model fails to load. |
176
176
 
177
177
  Every session also ends with an optional "dream cycle". Run `/consolidate` or "run a dream cycle" to have IJFW sweep the day's memory: promote observed patterns into your knowledge base, prune stale entries, reconcile contradictions, optionally lift winners into global memory so every future project benefits. Memory that grows sharper over time instead of heavier.
178
178
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ijfw/install",
3
- "version": "1.4.4",
3
+ "version": "1.5.0",
4
4
  "description": "One-command installer for IJFW -- the AI efficiency layer. One install, every AI coding agent, zero config.",
5
5
  "type": "module",
6
6
  "bin": {