@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.
- package/package.json +15 -1
- package/src/brain/dream-pipeline.js +77 -14
- package/src/brain/dump-ingest.js +32 -0
- package/src/brain/entity-collapse.js +2 -2
- package/src/brain/export.js +60 -6
- package/src/brain/extractors/markdown.js +28 -2
- package/src/brain/layout-sentinel.js +19 -14
- package/src/brain/path-guard.js +17 -0
- package/src/brain/wiki-compiler.js +35 -39
- package/src/codex-agents.js +25 -2
- package/src/cross-orchestrator-cli.js +176 -18
- package/src/dashboard-server.js +36 -3
- package/src/dispatch/override.js +18 -2
- package/src/dispatch/signer-cli.js +14 -9
- package/src/dream/stage-runner.js +17 -0
- package/src/dream/state-file.js +15 -1
- package/src/extension-installer.js +91 -2
- package/src/extension-registry.js +15 -4
- package/src/handlers/brain-handler.js +44 -5
- package/src/lib/atomic-io.js +69 -12
- package/src/lib/shasum-verify.js +46 -22
- package/src/lib/ui-review-runner.js +7 -2
- package/src/lib/uispec-drift.js +8 -3
- package/src/lib/uispec-intake.js +5 -2
- package/src/memory/layout-migrations/001-visible-layer.js +71 -7
- package/src/memory/reader.js +111 -58
- package/src/orchestrator/merge-block-aware.js +75 -37
- package/src/orchestrator/post-done-runner.js +6 -1
- package/src/orchestrator/state-sdk.js +242 -14
- package/src/orchestrator/wave-state.js +22 -69
- package/src/recovery/checkpoint.js +30 -6
- package/src/recovery/code-fixer.js +52 -7
- package/src/runtime-mediator.js +2 -2
- package/src/server.js +57 -8
- package/src/swarm/planner.js +46 -1
- package/src/update-apply.js +27 -35
- 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
|
|
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
|
-
|
|
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
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
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({
|
package/src/swarm/planner.js
CHANGED
|
@@ -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 };
|
package/src/update-apply.js
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Internal helper: ijfwUpdateApply (sentinel writer).
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
|
14
|
-
//
|
|
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
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* the
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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).
|
package/src/update-check.js
CHANGED
|
@@ -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 = '
|
|
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
|
-
|
|
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) {
|