@ijfw/memory-server 1.5.4 → 1.5.6

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.
Files changed (37) hide show
  1. package/package.json +15 -1
  2. package/src/brain/dream-pipeline.js +77 -14
  3. package/src/brain/dump-ingest.js +32 -0
  4. package/src/brain/entity-collapse.js +2 -2
  5. package/src/brain/export.js +60 -6
  6. package/src/brain/extractors/markdown.js +28 -2
  7. package/src/brain/layout-sentinel.js +19 -14
  8. package/src/brain/path-guard.js +17 -0
  9. package/src/brain/wiki-compiler.js +35 -39
  10. package/src/codex-agents.js +25 -2
  11. package/src/cross-orchestrator-cli.js +176 -18
  12. package/src/dashboard-server.js +36 -3
  13. package/src/dispatch/override.js +18 -2
  14. package/src/dispatch/signer-cli.js +14 -9
  15. package/src/dream/stage-runner.js +17 -0
  16. package/src/dream/state-file.js +15 -1
  17. package/src/extension-installer.js +91 -2
  18. package/src/extension-registry.js +15 -4
  19. package/src/handlers/brain-handler.js +44 -5
  20. package/src/lib/atomic-io.js +69 -12
  21. package/src/lib/shasum-verify.js +46 -22
  22. package/src/lib/ui-review-runner.js +7 -2
  23. package/src/lib/uispec-drift.js +8 -3
  24. package/src/lib/uispec-intake.js +5 -2
  25. package/src/memory/layout-migrations/001-visible-layer.js +71 -7
  26. package/src/memory/reader.js +111 -58
  27. package/src/orchestrator/merge-block-aware.js +75 -37
  28. package/src/orchestrator/post-done-runner.js +6 -1
  29. package/src/orchestrator/state-sdk.js +242 -14
  30. package/src/orchestrator/wave-state.js +22 -69
  31. package/src/recovery/checkpoint.js +30 -6
  32. package/src/recovery/code-fixer.js +52 -7
  33. package/src/runtime-mediator.js +2 -2
  34. package/src/server.js +57 -8
  35. package/src/swarm/planner.js +46 -1
  36. package/src/update-apply.js +27 -35
  37. package/src/update-check.js +6 -2
package/src/server.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  /**
4
4
  * IJFW Memory Server -- Cross-platform MCP memory for AI coding agents
5
- * By Sean Donahoe | "It Just Fucking Works"
5
+ * By Ferrox Labs | "It Just Fucking Works"
6
6
  *
7
7
  * 4 tools: recall, store, search, status
8
8
  * Storage: append-only markdown (hot layer, zero dependencies)
@@ -72,7 +72,10 @@ import {
72
72
  // 1.1.6: update tools (cap 8 -> 10) -- token-issuance + OOB terminal confirm.
73
73
  // Per CLAUDE.md policy: future growth triggers retirement review, not raise.
74
74
  import { ijfwUpdateCheck, TOOL_DEF as UPDATE_CHECK_TOOL } from './update-check.js';
75
- import { ijfwUpdateApply, TOOL_DEF as UPDATE_APPLY_TOOL } from './update-apply.js';
75
+ // V155-017 (v1.5.5): ijfw_update_apply retired the verb has been @deprecated
76
+ // since v1.5.0 and was contradicted by live runtime. The CLI `ijfw update`
77
+ // path (cross-orchestrator-cli.js) is the supported flow. Frees one slot from
78
+ // the 14-tool cap for v1.6.0 brain growth.
76
79
  // ijfw_run: sandbox large command output to disk, return terse summary to context.
77
80
  import { runCommand, detectDomain, summarize, writeToSandbox, readFromSandbox, purgeSandboxOld, stripAnsi } from './sandbox.js';
78
81
  // W1B (1.3.0-alpha) -- colon-syntax dispatcher. Extends ijfw_run + ijfw_memory_search
@@ -518,18 +521,30 @@ function appendToJournal(entry) {
518
521
  // Structured append for decisions/patterns -- produces a richer frontmatter block
519
522
  // similar to Claude's native auto-memory format: YAML frontmatter plus a body with
520
523
  // Why / How-to-apply sections. This is the format users retrieve well from.
524
+ //
525
+ // V155-021 (v1.5.5): content-hash dedup — sibling `appendKnowledge` in
526
+ // importers/cli.js uses `<!-- hash:{sha12(content)} -->` so re-stores of
527
+ // identical content are silently deduplicated regardless of caller-side
528
+ // semantic-dedup config. The MCP `memory_store` writer was the lone
529
+ // exception. Same pattern adopted here for parity. Returns
530
+ // `{ok:true, deduped:true}` so callers can distinguish skipped writes
531
+ // from new appends without breaking the existing `ok:true` contract.
521
532
  function appendStructuredToKnowledge({ type, summary, content, why, howToApply, tags }) {
522
533
  const filepath = join(paths().memoryDir, 'knowledge.md');
523
534
  const ts = new Date().toISOString();
524
535
  const tagLine = tags && tags.length ? tags.join(', ') : '';
536
+ const hash = createHash('sha256').update(String(content || '')).digest('hex').slice(0, 12);
537
+ const hashSentinel = `<!-- hash:${hash} -->`;
525
538
  const block = [
526
539
  '',
527
540
  '---',
528
541
  `type: ${type}`,
529
542
  `summary: ${summary}`,
530
543
  `stored: ${ts}`,
544
+ `hash: ${hash}`,
531
545
  tagLine ? `tags: [${tagLine}]` : '',
532
546
  '---',
547
+ hashSentinel,
533
548
  content,
534
549
  why ? `\n**Why:** ${why}` : '',
535
550
  howToApply ? `\n**How to apply:** ${howToApply}` : '',
@@ -542,6 +557,16 @@ function appendStructuredToKnowledge({ type, summary, content, why, howToApply,
542
557
  return atomicWrite(filepath, seed);
543
558
  }
544
559
  try { ensureSchemaHeader(filepath); } catch { /* best-effort */ }
560
+ // V155-021: hash-precheck — skip append if an identical content block
561
+ // already exists. Bounded by knowledge.md size which is itself rotated
562
+ // elsewhere; for the typical <1 MB file this is a single fs read +
563
+ // substring scan.
564
+ try {
565
+ const existing = readFileSync(filepath, 'utf8');
566
+ if (existing.includes(hashSentinel)) {
567
+ return { ok: true, deduped: true };
568
+ }
569
+ } catch { /* fall through to append; missing-read is non-fatal */ }
545
570
  appendFileSync(filepath, block);
546
571
  return { ok: true };
547
572
  } catch (err) {
@@ -1115,7 +1140,8 @@ const TOOLS = [
1115
1140
  }
1116
1141
  },
1117
1142
  UPDATE_CHECK_TOOL,
1118
- UPDATE_APPLY_TOOL,
1143
+ // V155-017 (v1.5.5): UPDATE_APPLY_TOOL retired. See cross-orchestrator-cli.js
1144
+ // for the supported `ijfw update` flow.
1119
1145
  {
1120
1146
  name: 'ijfw_run',
1121
1147
  description: 'Run a shell command. For commands likely to produce large output (builds, test suites, grep -r, log tails), use this instead of Bash -- full output is sandboxed to disk and a smart summary is returned to context. For git/nav/quick ops, use Bash directly. Also accepts colon-namespaced commands instead of a shell line: "compute:python", "compute:js", "index:<source>", "detect:project_type".',
@@ -2146,11 +2172,11 @@ function handleMessage(msg) {
2146
2172
  }
2147
2173
  break;
2148
2174
  }
2149
- case 'ijfw_update_apply': {
2150
- const r = ijfwUpdateApply(args || {});
2151
- result = { text: JSON.stringify(r, null, 2), isError: r && r.status === 'error' };
2152
- break;
2153
- }
2175
+ // V155-017 (v1.5.5): 'ijfw_update_apply' case retired — handled by
2176
+ // the CLI flow via cross-orchestrator-cli.js. The MCP tool slot is
2177
+ // no longer advertised. Unknown-tool requests fall through to the
2178
+ // default branch below (handled identically to any other unsupported
2179
+ // verb name).
2154
2180
  case 'ijfw_cross_audit_converge': {
2155
2181
  // v1.5.0-major W12-C N03: Trident-as-a-service.
2156
2182
  const a = args || {};
@@ -2158,6 +2184,29 @@ function handleMessage(msg) {
2158
2184
  result = { text: JSON.stringify({ error: 'commitRange (string) is required' }), isError: true };
2159
2185
  break;
2160
2186
  }
2187
+ // V155-022 (v1.5.5): strict shape validation. `commitRange` is
2188
+ // passed downstream to `git rev-list` via execFile (no shell), but
2189
+ // git itself interprets `--upload-pack=cmd`, `--config=core.fsmonitor=cmd`
2190
+ // and similar option-style argv as command execution. Reject any
2191
+ // value that starts with `-`, contains shell metas (` ; | $ \` `),
2192
+ // whitespace, or wanders outside the SHA / SHA..SHA / SHA...SHA /
2193
+ // ref-name vocabulary. Cap at 200 chars to defang
2194
+ // resource-exhaustion via gigantic argv.
2195
+ const cr = a.commitRange;
2196
+ if (
2197
+ cr.length > 200
2198
+ || cr.startsWith('-')
2199
+ || /[\s;|`$\\]/.test(cr)
2200
+ || !/^[A-Za-z0-9_./@^~:-]+$/.test(cr)
2201
+ ) {
2202
+ result = {
2203
+ text: JSON.stringify({
2204
+ error: 'commitRange has invalid shape — expected SHA, SHA..SHA, SHA...SHA, or branch/tag ref',
2205
+ }),
2206
+ isError: true,
2207
+ };
2208
+ break;
2209
+ }
2161
2210
  const { runPhaseEConverge, defaultConvergeDispatch } = await import('./cross-orchestrator.js');
2162
2211
  try {
2163
2212
  const r = await runPhaseEConverge({
@@ -177,12 +177,56 @@ export function startSwarmTask(projectRoot, taskId, options = {}) {
177
177
  return { ok: updated.ok, task: updated.task, claims: claimResults, error: updated.error };
178
178
  }
179
179
 
180
+ // V155-006 (HIGH) — completeSwarmTask used to advance status:'done' with
181
+ // nothing but the caller's word. That's the v1.5.1 hallucination signature
182
+ // encoded into the state layer: a subagent claims DONE, blackboard records
183
+ // DONE, but there is no filesystem witness (no commit, no diff). Recovery
184
+ // then trusts the false-positive and the build silently drifts.
185
+ //
186
+ // Fix: require an `evidence` envelope with at least one concrete artifact:
187
+ // - evidence.commitSha — 7-40 hex chars (short or full SHA)
188
+ // - evidence.diffStats — { filesChanged: >=1, ... }
189
+ //
190
+ // Callers that genuinely cannot produce evidence (e.g., admin overrides,
191
+ // task-tracking-only flows) MUST set `options.skipEvidence: true` AND a
192
+ // reason; the completion still writes status:'done' but the blackboard
193
+ // event is tagged `task.completed-no-evidence` so audits can spot it.
194
+ function isValidEvidence(evidence) {
195
+ if (!evidence || typeof evidence !== 'object') return false;
196
+ const { commitSha, diffStats } = evidence;
197
+ if (typeof commitSha === 'string' && /^[a-f0-9]{7,40}$/.test(commitSha)) {
198
+ return true;
199
+ }
200
+ if (
201
+ diffStats &&
202
+ typeof diffStats === 'object' &&
203
+ typeof diffStats.filesChanged === 'number' &&
204
+ diffStats.filesChanged >= 1
205
+ ) {
206
+ return true;
207
+ }
208
+ return false;
209
+ }
210
+
180
211
  export function completeSwarmTask(projectRoot, taskId, options = {}) {
181
212
  const task = findTask(projectRoot, taskId);
182
213
  if (!task.ok) return task;
183
214
  if (!['in_progress', 'review'].includes(task.task.status)) {
184
215
  return { ok: false, error: 'task-not-in-progress', task: task.task };
185
216
  }
217
+ // V155-006 evidence gate. `skipEvidence:true` is the explicit escape
218
+ // hatch — callers that intentionally complete without filesystem witness
219
+ // (admin overrides, dry-run completion) opt in by name, and the event
220
+ // log records the bypass for downstream audits.
221
+ const evidenceOk = isValidEvidence(options.evidence);
222
+ if (!evidenceOk && options.skipEvidence !== true) {
223
+ return {
224
+ ok: false,
225
+ error: 'missing-evidence',
226
+ message: 'completeSwarmTask requires evidence.commitSha or evidence.diffStats (or set skipEvidence:true)',
227
+ task: task.task,
228
+ };
229
+ }
186
230
  const owner = options.owner || task.task.active_owner || task.task.owner;
187
231
  const releases = [];
188
232
  for (const artifactId of task.task.artifact_ids || []) {
@@ -195,11 +239,12 @@ export function completeSwarmTask(projectRoot, taskId, options = {}) {
195
239
  });
196
240
  if (updated.ok) {
197
241
  appendBlackboardEvent(projectRoot, {
198
- type: 'task.completed',
242
+ type: evidenceOk ? 'task.completed' : 'task.completed-no-evidence',
199
243
  actor: owner,
200
244
  task_id: taskId,
201
245
  artifact_ids: task.task.artifact_ids || [],
202
246
  message: options.message || `Completed ${taskId}`,
247
+ evidence: evidenceOk ? options.evidence : undefined,
203
248
  });
204
249
  }
205
250
  return { ok: updated.ok, task: updated.task, releases, error: updated.error };
@@ -1,28 +1,33 @@
1
- // MCP tool: ijfw_update_apply
1
+ // Internal helper: ijfwUpdateApply (sentinel writer).
2
2
  //
3
- // @deprecated since v1.5.0; will be removed in v1.6.0 (F-FUN-3 / v1.5.0 audit-MED-M7).
4
- // `ijfw_update_check` already issues a confirmation token whose instruction tells
5
- // the user to type `ijfw update --confirm <token>` in their terminal directly.
6
- // The intermediate `ijfw_update_apply` step writes a pending sentinel, but the
7
- // terminal CLI does not require the sentinel to confirm — the token itself is
8
- // authoritative. The tool is retained for v1.5.0 back-compat (older skills that
9
- // still call it work unchanged) and slated for retirement in v1.6.0 to free the
10
- // MCP-tool slot (see CLAUDE.md "MCP server: ≤14 tools" cap).
3
+ // V155-017 (v1.5.5): formerly exposed as the `ijfw_update_apply` MCP tool.
4
+ // The MCP registration was retired in v1.5.5 because `ijfw_update_check`
5
+ // already issues a confirmation token whose instruction tells the user to
6
+ // type `ijfw update --confirm <token>` in their terminal directly. The
7
+ // intermediate `ijfw_update_apply` MCP verb wrote a pending sentinel, but
8
+ // the terminal CLI does not require the sentinel to confirm — the token
9
+ // itself is authoritative.
10
+ //
11
+ // The function is kept INTERNAL-ONLY: still called from sentinel-write tests
12
+ // (test-1.1.6.js) which exercise validateToken + writePendingSentinel +
13
+ // target-mismatch semantics. It is NOT registered as an MCP tool and NOT
14
+ // referenced from user-facing CLI strings. If you find yourself wanting to
15
+ // re-expose it via MCP, check the ≤14 tool cap (mcp-server/TOOLS.md) and
16
+ // pick a tool to retire first.
11
17
  //
12
18
  // Does NOT execute the update. Validates the token, writes (or overwrites)
13
- // the pending sentinel, returns instruction telling the user to run the
14
- // terminal-side confirm command. Idempotent against a matching sentinel
15
- // already written by ijfw_update_check -- the sentinel + token are the
16
- // same artifact, so re-writing with the same values is a no-op.
17
- // Air-gaps the MCP path from actual code execution -- per v3 sec 16 blocker fix.
19
+ // the pending sentinel. Idempotent against a matching sentinel already
20
+ // written by ijfw_update_check.
18
21
 
19
22
  import { validateToken, writePendingSentinel } from './lib/token.js';
20
23
  import { isVersionStringValid } from './lib/npm-view.js';
21
24
 
22
25
  /**
23
- * @deprecated since v1.5.0; scheduled for removal in v1.6.0. Callers should
24
- * skip straight from `ijfw_update_check` to the terminal-side confirm command;
25
- * the intermediate sentinel write is redundant given the token contract.
26
+ * V155-017: internal sentinel-write helper. Was the `ijfw_update_apply`
27
+ * MCP tool through v1.5.4; retired from MCP surface in v1.5.5. Retained
28
+ * as in-process callable for the sentinel-write test surface and for any
29
+ * future flow that wants to write a sentinel without going through
30
+ * `ijfw_update_check` (no current production caller).
26
31
  */
27
32
  export function ijfwUpdateApply(args = {}) {
28
33
  const { target_version, confirmation_token } = args || {};
@@ -75,21 +80,8 @@ export function ijfwUpdateApply(args = {}) {
75
80
  };
76
81
  }
77
82
 
78
- export const TOOL_DEF = {
79
- name: 'ijfw_update_apply',
80
- description:
81
- '[DEPRECATED v1.5.0; removal in v1.6.0] Stage an IJFW update behind out-of-band terminal ' +
82
- 'confirmation. Writes a pending sentinel; actual update only runs when the user types ' +
83
- "'ijfw update --confirm <token>' in their terminal. This MCP tool NEVER executes the " +
84
- 'update directly. Prefer calling ijfw_update_check and forwarding the returned ' +
85
- "'ijfw update --confirm <token>' instruction directly to the user.",
86
- inputSchema: {
87
- type: 'object',
88
- required: ['target_version', 'confirmation_token'],
89
- properties: {
90
- target_version: { type: 'string', description: 'Target semver to install' },
91
- confirmation_token: { type: 'string', description: 'Token from ijfw_update_check' },
92
- session_id: { type: 'string', description: 'Session ID for token scoping (optional)' },
93
- },
94
- },
95
- };
83
+ // V155-017: TOOL_DEF removed in v1.5.5 — `ijfw_update_apply` is no longer
84
+ // an MCP tool. The streamlined update flow is `ijfw_update_check` → terminal
85
+ // `ijfw update --confirm <token>`. See server.js TOOLS array for the v1.5.5
86
+ // MCP tool surface; do not re-add this without retiring another tool first
87
+ // (≤14 tool cap, mcp-server/TOOLS.md).
@@ -14,7 +14,7 @@ import { npmView, compareSemver } from './lib/npm-view.js';
14
14
  import { issueToken, writePendingSentinel, readPendingSentinel } from './lib/token.js';
15
15
 
16
16
  const PKG = '@ijfw/install';
17
- const REPO = 'therealseandonahoe/ijfw';
17
+ const REPO = 'FerroxLabs/ijfw';
18
18
 
19
19
  function ijfwHome() {
20
20
  return process.env.IJFW_HOME || join(homedir(), '.ijfw');
@@ -90,7 +90,11 @@ export async function ijfwUpdateCheck(args = {}) {
90
90
  latest,
91
91
  available,
92
92
  reachable: true,
93
- changelog_url: `https://gitlab.com/${REPO}/-/releases/v${latest}`,
93
+ // GitHub uses `releases/tag/` (not `releases/v`). Until v1.5.5
94
+ // is published as a GitHub Release on FerroxLabs/ijfw the URL 404s;
95
+ // acceptable, since users on prior releases only see this URL after
96
+ // upgrading past v1.5.5.
97
+ changelog_url: `https://github.com/${REPO}/releases/tag/v${latest}`,
94
98
  };
95
99
 
96
100
  if (available) {