iranti 0.3.0 → 0.3.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/README.md +48 -44
- package/dist/scripts/claude-code-memory-hook.js +42 -153
- package/dist/scripts/codex-setup.js +1 -1
- package/dist/scripts/iranti-cli.js +161 -17
- package/dist/scripts/iranti-mcp.js +80 -8
- package/dist/scripts/seed.js +1 -1
- package/dist/src/api/middleware/validation.d.ts.map +1 -1
- package/dist/src/api/middleware/validation.js +13 -1
- package/dist/src/api/middleware/validation.js.map +1 -1
- package/dist/src/api/routes/knowledge.d.ts.map +1 -1
- package/dist/src/api/routes/knowledge.js +3 -0
- package/dist/src/api/routes/knowledge.js.map +1 -1
- package/dist/src/api/routes/memory.d.ts.map +1 -1
- package/dist/src/api/routes/memory.js +3 -0
- package/dist/src/api/routes/memory.js.map +1 -1
- package/dist/src/api/server.js +1 -1
- package/dist/src/attendant/AttendantInstance.d.ts +44 -1
- package/dist/src/attendant/AttendantInstance.d.ts.map +1 -1
- package/dist/src/attendant/AttendantInstance.js +475 -41
- package/dist/src/attendant/AttendantInstance.js.map +1 -1
- package/dist/src/attendant/index.d.ts +1 -1
- package/dist/src/attendant/index.d.ts.map +1 -1
- package/dist/src/attendant/index.js.map +1 -1
- package/dist/src/chat/index.d.ts +2 -0
- package/dist/src/chat/index.d.ts.map +1 -1
- package/dist/src/chat/index.js +56 -22
- package/dist/src/chat/index.js.map +1 -1
- package/dist/src/lib/assistantCheckpoint.d.ts +21 -0
- package/dist/src/lib/assistantCheckpoint.d.ts.map +1 -0
- package/dist/src/lib/assistantCheckpoint.js +143 -0
- package/dist/src/lib/assistantCheckpoint.js.map +1 -0
- package/dist/src/lib/cliHelpCatalog.d.ts.map +1 -1
- package/dist/src/lib/cliHelpCatalog.js +6 -5
- package/dist/src/lib/cliHelpCatalog.js.map +1 -1
- package/dist/src/lib/hostMemoryFormatting.d.ts +25 -0
- package/dist/src/lib/hostMemoryFormatting.d.ts.map +1 -0
- package/dist/src/lib/hostMemoryFormatting.js +56 -0
- package/dist/src/lib/hostMemoryFormatting.js.map +1 -0
- package/dist/src/lib/llm.d.ts.map +1 -1
- package/dist/src/lib/llm.js +3 -1
- package/dist/src/lib/llm.js.map +1 -1
- package/dist/src/lib/projectLearning.d.ts +21 -0
- package/dist/src/lib/projectLearning.d.ts.map +1 -0
- package/dist/src/lib/projectLearning.js +357 -0
- package/dist/src/lib/projectLearning.js.map +1 -0
- package/dist/src/lib/protocolEnforcement.d.ts +3 -1
- package/dist/src/lib/protocolEnforcement.d.ts.map +1 -1
- package/dist/src/lib/protocolEnforcement.js +28 -2
- package/dist/src/lib/protocolEnforcement.js.map +1 -1
- package/dist/src/lib/sessionLedger.d.ts +18 -0
- package/dist/src/lib/sessionLedger.d.ts.map +1 -1
- package/dist/src/lib/sessionLedger.js +78 -0
- package/dist/src/lib/sessionLedger.js.map +1 -1
- package/dist/src/librarian/index.d.ts.map +1 -1
- package/dist/src/librarian/index.js +51 -0
- package/dist/src/librarian/index.js.map +1 -1
- package/dist/src/library/client.d.ts.map +1 -1
- package/dist/src/library/client.js +0 -1
- package/dist/src/library/client.js.map +1 -1
- package/dist/src/sdk/index.d.ts +2 -0
- package/dist/src/sdk/index.d.ts.map +1 -1
- package/dist/src/sdk/index.js +39 -2
- package/dist/src/sdk/index.js.map +1 -1
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
Iranti gives agents persistent, identity-based memory. Facts written by one agent are retrievable by any other agent through exact entity+key lookup. Iranti also supports hybrid search (lexical + vector) when exact keys are unknown. Memory persists across sessions and survives context window limits.
|
|
11
11
|
|
|
12
|
-
**Repo version:** `0.
|
|
13
|
-
**Latest published release:** [`v0.2.51`](https://github.com/nfemmanuel/iranti/releases/tag/v0.2.51)
|
|
12
|
+
**Repo version:** `0.3.0`
|
|
14
13
|
Published packages:
|
|
15
|
-
- `iranti@0.
|
|
16
|
-
- `@iranti/sdk@0.
|
|
14
|
+
- npm `iranti@0.3.0`
|
|
15
|
+
- npm `@iranti/sdk@0.3.0`
|
|
16
|
+
- PyPI `iranti==0.3.0`
|
|
17
17
|
|
|
18
18
|
---
|
|
19
19
|
|
|
@@ -53,27 +53,29 @@ Vector databases answer "what's similar to X?" Iranti answers "what do we know a
|
|
|
53
53
|
|
|
54
54
|
## Benchmark Summary
|
|
55
55
|
|
|
56
|
-
Iranti now has current rerun evidence across exact retrieval, process continuity, upgrade durability, relationships, bounded conflict handling, and bounded recovery behavior. The current picture is stronger and narrower than the early validation story: exact, durable, shared memory is validated; broad semantic-memory and autonomous-recovery claims still need explicit limits.
|
|
56
|
+
Iranti now has current rerun evidence across exact retrieval, process continuity, upgrade durability, relationships, bounded conflict handling, and bounded recovery behavior. The current picture is stronger and narrower than the early validation story: exact, durable, shared memory is validated; broad semantic-memory and autonomous-recovery claims still need explicit limits.
|
|
57
57
|
|
|
58
58
|
### Confirmed Strengths
|
|
59
59
|
|
|
60
|
-
- **Exact lookup (`iranti_query`)**: The current
|
|
61
|
-
- **Persistence across sessions and processes**: Facts survive context-window loss and genuine process boundaries, with current reruns and validation passes covering process-isolated reads plus cross-process invalidation.
|
|
62
|
-
- **Upgrade durability**: A fresh continuity chain now passes across public npm `0.2.49 -> 0.2.50 -> 0.2.51` plus a final
|
|
60
|
+
- **Exact lookup (`iranti_query`)**: The prepublish candidate for the current `0.3.0` line still scored `10/10` on the canonical B1 exact-retrieval arm.
|
|
61
|
+
- **Persistence across sessions and processes**: Facts survive context-window loss and genuine process boundaries, with current reruns and validation passes covering process-isolated reads plus cross-process invalidation.
|
|
62
|
+
- **Upgrade durability**: A fresh continuity chain now passes across public npm `0.2.49 -> 0.2.50 -> 0.2.51` plus a final prepublish candidate hop on the `0.3.0` line.
|
|
63
63
|
- **Conflict handling**: Reliable when confidence differentials are large and explicit.
|
|
64
|
-
- **Relationship traversal and multi-agent coordination**: Relationship writes plus one-hop/deep traversal work, and agents can share memory across genuine subprocess boundaries with zero shared conversational context.
|
|
64
|
+
- **Relationship traversal and multi-agent coordination**: Relationship writes plus one-hop/deep traversal work, and agents can share memory across genuine subprocess boundaries with zero shared conversational context.
|
|
65
65
|
- **Provenance on writes**: Write-side attribution through stored source metadata is working and benchmark-confirmed.
|
|
66
|
-
- **Ingest**: Prose extraction is accurate on clean entities in the bounded rerun surfaces already documented.
|
|
67
|
-
- **Observe with hints / explicit recovery**: `iranti_observe` with hints and explicit query-based recovery are meaningfully useful today.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
66
|
+
- **Ingest**: Prose extraction is accurate on clean entities in the bounded rerun surfaces already documented.
|
|
67
|
+
- **Observe with hints / explicit recovery**: `iranti_observe` with hints and explicit query-based recovery are meaningfully useful today.
|
|
68
|
+
- **Protocol enforcement turn-gate**: Strict turn-cycle enforcement (handshake → attend → discover → post-response) is validated by focused route and MCP regression tests. In `strict` mode, KB discovery routes return HTTP 428 with a structured violation payload before an agent that skipped attend can silently read stale memory. Injected facts now include a `lastUpdated` timestamp so the calling agent can judge freshness at the point of injection.
|
|
69
|
+
|
|
70
|
+
Recent fixes since the last rerun:
|
|
71
|
+
- **Search now bridges semantic hits across relationships for filtered retrieval.** A strong incident or issue hit can now propagate back onto the owning filtered entity over up to two hops, so filtered project search no longer depends on direct lexical overlap alone.
|
|
72
|
+
- **`iranti_attend` now safe-defaults parse failures toward memory on non-greeting turns.** Terse prompts such as `help`, `why?`, or `how?` no longer silently fall through to `classification_parse_failed_default_false`.
|
|
73
|
+
- **Hybrid search now indexes entity addresses directly.** Addressed queries such as `project/<id> status` no longer rely on value-summary overlap alone.
|
|
74
|
+
|
|
75
|
+
### Current Limits
|
|
76
|
+
|
|
76
77
|
- **Upgrade durability is now test-covered.** The `v0.2.21` upgrade reinitialized the instance under test — a one-time operator incident, not a systemic design flaw. All Prisma migrations are additive (no DROP or TRUNCATE); Staff Namespace seeding is guarded by `isSeeded()`; `seed-codebase.ts` upserts to `codebase/*` entities only. A KB preservation test in `tests/runtime-lifecycle/run_setup_upgrade_tests.ts` now confirms user KB data written before a `setup` re-run survives intact.
|
|
78
|
+
- **Project learning is now only a bounded bind-time snapshot.** When you bind a project, Iranti derives a stable `IRANTI_CODEBASE_ENTITY` and writes a small snapshot from authoritative files such as `package.json`, `README.md`, `tsconfig.json`, `pyproject.toml`, `prisma/schema.prisma`, and common source directories. It does not yet crawl the repo continuously or build autonomous whole-project understanding.
|
|
77
79
|
- **Relationship and provenance reflection surfaces remain partially permission-gated in benchmark sessions.** The rerun did not prove `iranti_relate`, `iranti_related`, `iranti_related_deep`, or `iranti_who_knows` end-to-end under the benchmark session policy.
|
|
78
80
|
|
|
79
81
|
### Practical Position
|
|
@@ -81,9 +83,10 @@ Recent fixes since the last rerun:
|
|
|
81
83
|
Iranti is strongest today as **structured memory infrastructure for multi-agent systems**:
|
|
82
84
|
- exact entity/key lookup
|
|
83
85
|
- durable shared memory
|
|
84
|
-
- provenance-aware writes
|
|
86
|
+
- provenance-aware writes with source attribution and freshness timestamps
|
|
85
87
|
- conflict-aware storage
|
|
86
88
|
- session-aware recovery
|
|
89
|
+
- protocol-enforced turn discipline for trustworthy injection (opt-in strict mode)
|
|
87
90
|
|
|
88
91
|
It should not yet be described as a fully general semantic-memory, semantic-search, or autonomous-memory-injection system.
|
|
89
92
|
|
|
@@ -468,6 +471,7 @@ iranti project init . --instance local --agent-id chatbot_main
|
|
|
468
471
|
```
|
|
469
472
|
|
|
470
473
|
This writes `.env.iranti` in the project with the correct `IRANTI_URL`, `IRANTI_API_KEY`, and default agent identity.
|
|
474
|
+
It also writes a derived `IRANTI_CODEBASE_ENTITY` and a bounded initial project snapshot for that bound repo.
|
|
471
475
|
|
|
472
476
|
Later changes use the same surface:
|
|
473
477
|
|
|
@@ -608,19 +612,19 @@ for item in matches:
|
|
|
608
612
|
print(item["entity"], item["key"], item["score"])
|
|
609
613
|
```
|
|
610
614
|
|
|
611
|
-
### Context Persistence (attend)
|
|
612
|
-
|
|
613
|
-
`handshake()` establishes the session contract. Discovery surfaces such as `query`, `search`, `get_related`, and `who_knows` are now fail-closed until the host has run `handshake()` for the session and `attend()` for the current turn. Starting a new `attend(phase="pre-response")` turn without first closing the previous one with `attend(phase="post-response")` is now blocked as a protocol violation, and `inspectSession()` / `listSessions()` expose structured lifecycle compliance state (`healthy`, `degraded`, `non_compliant`) so hosts and operators can see when breadcrumb discipline is slipping.
|
|
614
|
-
|
|
615
|
-
```python
|
|
616
|
-
# Before each LLM call, let Attendant decide if memory is needed
|
|
617
|
-
result = client.attend(
|
|
618
|
-
agent_id="research_agent_001",
|
|
619
|
-
latest_message="What's Jane Smith's current affiliation?",
|
|
620
|
-
current_context="User: What's Jane Smith's current affiliation?\nAssistant: Let me check...",
|
|
621
|
-
max_facts=5,
|
|
622
|
-
phase="pre-response"
|
|
623
|
-
)
|
|
615
|
+
### Context Persistence (attend)
|
|
616
|
+
|
|
617
|
+
`handshake()` establishes the session contract. Discovery surfaces such as `query`, `search`, `get_related`, and `who_knows` are now fail-closed until the host has run `handshake()` for the session and `attend()` for the current turn. Starting a new `attend(phase="pre-response")` turn without first closing the previous one with `attend(phase="post-response")` is now blocked as a protocol violation, and `inspectSession()` / `listSessions()` expose structured lifecycle compliance state (`healthy`, `degraded`, `non_compliant`) so hosts and operators can see when breadcrumb discipline is slipping.
|
|
618
|
+
|
|
619
|
+
```python
|
|
620
|
+
# Before each LLM call, let Attendant decide if memory is needed
|
|
621
|
+
result = client.attend(
|
|
622
|
+
agent_id="research_agent_001",
|
|
623
|
+
latest_message="What's Jane Smith's current affiliation?",
|
|
624
|
+
current_context="User: What's Jane Smith's current affiliation?\nAssistant: Let me check...",
|
|
625
|
+
max_facts=5,
|
|
626
|
+
phase="pre-response"
|
|
627
|
+
)
|
|
624
628
|
|
|
625
629
|
if result["shouldInject"]:
|
|
626
630
|
for fact in result['facts']:
|
|
@@ -731,11 +735,11 @@ middleware.after_receive(
|
|
|
731
735
|
)
|
|
732
736
|
```
|
|
733
737
|
|
|
734
|
-
**How it works**:
|
|
735
|
-
1. `before_send()` calls `attend()` with conversation context
|
|
736
|
-
2. Forgotten facts are prepended as `[MEMORY: ...]`
|
|
737
|
-
3. `after_receive()` extracts new facts and saves them (best-effort)
|
|
738
|
-
4. If the host skips `handshake()` or `attend()`, discovery calls block instead of returning warn-only metadata
|
|
738
|
+
**How it works**:
|
|
739
|
+
1. `before_send()` calls `attend()` with conversation context
|
|
740
|
+
2. Forgotten facts are prepended as `[MEMORY: ...]`
|
|
741
|
+
3. `after_receive()` extracts new facts and saves them (best-effort)
|
|
742
|
+
4. If the host skips `handshake()` or `attend()`, discovery calls block instead of returning warn-only metadata
|
|
739
743
|
|
|
740
744
|
**Note**: Browser extensions are blocked by ChatGPT and Claude's Content Security Policy. Use API-based middleware instead.
|
|
741
745
|
|
|
@@ -762,10 +766,10 @@ Express server on port 3001 with endpoints:
|
|
|
762
766
|
- `POST /kb/write` - Write atomic fact
|
|
763
767
|
- `POST /kb/ingest` - Ingest raw text for one entity, auto-chunk into facts with per-fact confidence and per-fact write outcomes
|
|
764
768
|
- `GET /kb/query/:entityType/:entityId/:key` - Query specific fact
|
|
765
|
-
- `GET /kb/query/:entityType/:entityId` - Query all facts for entity
|
|
766
|
-
- `GET /kb/search` - Hybrid search across facts
|
|
767
|
-
- `POST /memory/attend` - Decide whether to inject memory for this turn and unlock one discovery step for the current turn
|
|
768
|
-
- `POST /memory/observe` - Context persistence (inject missing facts)
|
|
769
|
+
- `GET /kb/query/:entityType/:entityId` - Query all facts for entity
|
|
770
|
+
- `GET /kb/search` - Hybrid search across facts
|
|
771
|
+
- `POST /memory/attend` - Decide whether to inject memory for this turn and unlock one discovery step for the current turn
|
|
772
|
+
- `POST /memory/observe` - Context persistence (inject missing facts)
|
|
769
773
|
- `POST /memory/handshake` - Working memory brief for agent session
|
|
770
774
|
- `GET /memory/ledger` - Read structured session ledger events from `staff_events`
|
|
771
775
|
- `GET /memory/sessions` - List persisted operator-visible session checkpoints across agents, with optional operator filters/sorting
|
|
@@ -774,8 +778,8 @@ Express server on port 3001 with endpoints:
|
|
|
774
778
|
- `GET /kb/related/:entityType/:entityId` - Get related entities
|
|
775
779
|
- `POST /agents/register` - Register agent in registry
|
|
776
780
|
|
|
777
|
-
All endpoints require `X-Iranti-Key` header for authentication.
|
|
778
|
-
Discovery endpoints return `428 Precondition Required` when a host skips the required session `handshake` or current-turn `attend`.
|
|
781
|
+
All endpoints require `X-Iranti-Key` header for authentication.
|
|
782
|
+
Discovery endpoints return `428 Precondition Required` when a host skips the required session `handshake` or current-turn `attend`.
|
|
779
783
|
|
|
780
784
|
---
|
|
781
785
|
|
|
@@ -11,6 +11,8 @@ const runtimeEnv_1 = require("../src/lib/runtimeEnv");
|
|
|
11
11
|
const staffEventRegistry_1 = require("../src/lib/staffEventRegistry");
|
|
12
12
|
const client_1 = require("../src/library/client");
|
|
13
13
|
const autoRemember_1 = require("../src/lib/autoRemember");
|
|
14
|
+
const assistantCheckpoint_1 = require("../src/lib/assistantCheckpoint");
|
|
15
|
+
const hostMemoryFormatting_1 = require("../src/lib/hostMemoryFormatting");
|
|
14
16
|
const MEMORY_NEED_POSITIVE_PATTERNS = [
|
|
15
17
|
/\bwhat(?:'s| is| was)?\s+my\b/i,
|
|
16
18
|
/\bdo you remember\b/i,
|
|
@@ -41,7 +43,7 @@ function printHelp() {
|
|
|
41
43
|
'',
|
|
42
44
|
'Reads Claude Code hook JSON from stdin and returns hookSpecificOutput.additionalContext on stdout.',
|
|
43
45
|
'This helper retrieves working memory; durable KB writes still require explicit iranti_write/ingest calls.',
|
|
44
|
-
'Set IRANTI_AUTO_REMEMBER=true to auto-save
|
|
46
|
+
'Set IRANTI_AUTO_REMEMBER=true to auto-save prompt-side personal/project facts. Assistant-response continuity facts and shared checkpoints are captured on Stop regardless.',
|
|
45
47
|
].join('\n'));
|
|
46
48
|
}
|
|
47
49
|
function parseArgs(argv) {
|
|
@@ -229,19 +231,29 @@ function getMaxFacts() {
|
|
|
229
231
|
return Math.min(12, Math.trunc(raw));
|
|
230
232
|
}
|
|
231
233
|
function formatSessionContext(facts, cwd) {
|
|
232
|
-
const limited = facts.slice(0, getMaxFacts())
|
|
234
|
+
const limited = (0, hostMemoryFormatting_1.assignStructuredFactIds)(facts.slice(0, getMaxFacts()).map((fact) => ({
|
|
235
|
+
...fact,
|
|
236
|
+
entityKey: `${fact.entity}/${fact.key}`,
|
|
237
|
+
})));
|
|
233
238
|
const lines = [
|
|
234
239
|
'[Iranti Session Memory]',
|
|
235
240
|
`Project: ${path_1.default.basename(cwd)}`,
|
|
236
241
|
'REQUIRED: Call mcp__iranti__iranti_handshake before responding to the first user message.',
|
|
237
|
-
'REQUIRED: Call mcp__iranti__iranti_attend before every
|
|
242
|
+
'REQUIRED: Call mcp__iranti__iranti_attend(phase=\'pre-response\') before every reply and before factual discovery.',
|
|
243
|
+
'REQUIRED: After every response, call mcp__iranti__iranti_attend(phase=\'post-response\').',
|
|
244
|
+
'REQUIRED: Prefer injected Iranti facts before re-inferring project state.',
|
|
245
|
+
'REQUIRED: Call mcp__iranti__iranti_write after every file edit, confirmed finding, system state discovery, and subagent completion — write what changed, why, and what it means.',
|
|
238
246
|
];
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
const block = (0, hostMemoryFormatting_1.formatStructuredFactBlock)(limited, {
|
|
248
|
+
title: 'Iranti Session Facts',
|
|
249
|
+
introLines: [
|
|
250
|
+
'Use these loaded facts as the starting working-memory frame for this session.',
|
|
251
|
+
'Prefer them before re-inferring project state.',
|
|
252
|
+
'Fact IDs are stable only within this block.',
|
|
253
|
+
],
|
|
254
|
+
});
|
|
255
|
+
if (block)
|
|
256
|
+
lines.push(block);
|
|
245
257
|
return lines.join('\n');
|
|
246
258
|
}
|
|
247
259
|
function formatPreCompactContext() {
|
|
@@ -270,154 +282,22 @@ function extractSelfMemoryQueryKey(prompt) {
|
|
|
270
282
|
function formatPromptContext(facts, prompt) {
|
|
271
283
|
if (facts.length === 0)
|
|
272
284
|
return '';
|
|
273
|
-
const
|
|
285
|
+
const structuredFacts = (0, hostMemoryFormatting_1.assignStructuredFactIds)(facts.map((fact) => ({
|
|
286
|
+
...fact,
|
|
287
|
+
entityKey: `${fact.entity}/${fact.key}`,
|
|
288
|
+
})));
|
|
289
|
+
const lines = [];
|
|
274
290
|
const targetKey = prompt ? extractSelfMemoryQueryKey(prompt) : null;
|
|
275
291
|
if (targetKey) {
|
|
276
|
-
const answerCandidate =
|
|
292
|
+
const answerCandidate = structuredFacts.find((fact) => (0, autoRemember_1.canonicalizeMemoryKey)(fact.entityKey.split('/').slice(2).join('/')) === targetKey);
|
|
277
293
|
if (answerCandidate) {
|
|
278
|
-
lines.push(`Direct
|
|
279
|
-
lines.push(
|
|
294
|
+
lines.push(`[Iranti Direct Answer]`);
|
|
295
|
+
lines.push(`Use ${answerCandidate.factId} directly if it fully answers the user question: ${answerCandidate.summary}.`);
|
|
280
296
|
}
|
|
281
297
|
}
|
|
282
|
-
|
|
283
|
-
lines.push(`- ${fact.entity}/${fact.key}: ${fact.summary}`);
|
|
284
|
-
}
|
|
298
|
+
lines.push((0, hostMemoryFormatting_1.formatStructuredFactBlock)(structuredFacts, { title: 'Iranti Retrieved Memory' }));
|
|
285
299
|
return lines.join('\n');
|
|
286
300
|
}
|
|
287
|
-
function readTextField(value, preferredKey) {
|
|
288
|
-
if (typeof value !== 'object' || value === null)
|
|
289
|
-
return undefined;
|
|
290
|
-
const record = value;
|
|
291
|
-
const raw = record[preferredKey];
|
|
292
|
-
return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
|
|
293
|
-
}
|
|
294
|
-
function readItems(value) {
|
|
295
|
-
if (typeof value !== 'object' || value === null)
|
|
296
|
-
return [];
|
|
297
|
-
const record = value;
|
|
298
|
-
const items = Array.isArray(record.items) ? record.items : [];
|
|
299
|
-
return items.map((item) => String(item ?? '').trim()).filter(Boolean);
|
|
300
|
-
}
|
|
301
|
-
function readFileChangeOutputs(value) {
|
|
302
|
-
if (typeof value !== 'object' || value === null)
|
|
303
|
-
return [];
|
|
304
|
-
const record = value;
|
|
305
|
-
const items = Array.isArray(record.items) ? record.items : [];
|
|
306
|
-
return items.map((item) => {
|
|
307
|
-
if (typeof item !== 'object' || item === null)
|
|
308
|
-
return '';
|
|
309
|
-
const change = item;
|
|
310
|
-
const action = String(change.action ?? 'updated').trim();
|
|
311
|
-
const targetPath = String(change.path ?? '').trim();
|
|
312
|
-
const toPath = String(change.toPath ?? '').trim();
|
|
313
|
-
if (!targetPath)
|
|
314
|
-
return '';
|
|
315
|
-
return toPath ? `${action} ${targetPath} -> ${toPath}` : `${action} ${targetPath}`;
|
|
316
|
-
}).filter(Boolean);
|
|
317
|
-
}
|
|
318
|
-
function readCheckpointActions(value) {
|
|
319
|
-
if (typeof value !== 'object' || value === null)
|
|
320
|
-
return [];
|
|
321
|
-
const record = value;
|
|
322
|
-
const items = Array.isArray(record.items) ? record.items : [];
|
|
323
|
-
return items
|
|
324
|
-
.map((item) => {
|
|
325
|
-
if (typeof item !== 'object' || item === null)
|
|
326
|
-
return null;
|
|
327
|
-
const action = item;
|
|
328
|
-
const summary = String(action.summary ?? '').trim();
|
|
329
|
-
if (!summary)
|
|
330
|
-
return null;
|
|
331
|
-
const kind = String(action.kind ?? 'action').trim() || 'action';
|
|
332
|
-
return {
|
|
333
|
-
kind,
|
|
334
|
-
summary,
|
|
335
|
-
...(typeof action.status === 'string' && action.status.trim() ? { status: action.status.trim() } : {}),
|
|
336
|
-
...(typeof action.target === 'string' && action.target.trim() ? { target: action.target.trim() } : {}),
|
|
337
|
-
...(typeof action.detail === 'string' && action.detail.trim() ? { detail: action.detail.trim() } : {}),
|
|
338
|
-
};
|
|
339
|
-
})
|
|
340
|
-
.filter((item) => Boolean(item));
|
|
341
|
-
}
|
|
342
|
-
function readActionOutputs(value) {
|
|
343
|
-
if (typeof value !== 'object' || value === null)
|
|
344
|
-
return [];
|
|
345
|
-
const record = value;
|
|
346
|
-
const items = Array.isArray(record.items) ? record.items : [];
|
|
347
|
-
return items.map((item) => {
|
|
348
|
-
if (typeof item !== 'object' || item === null)
|
|
349
|
-
return '';
|
|
350
|
-
const action = item;
|
|
351
|
-
const kind = String(action.kind ?? 'action').trim() || 'action';
|
|
352
|
-
const summary = String(action.summary ?? '').trim();
|
|
353
|
-
const status = String(action.status ?? '').trim();
|
|
354
|
-
if (!summary)
|
|
355
|
-
return '';
|
|
356
|
-
return status ? `[${status}] ${kind}: ${summary}` : `${kind}: ${summary}`;
|
|
357
|
-
}).filter(Boolean);
|
|
358
|
-
}
|
|
359
|
-
function extractHookCheckpointPayload(response) {
|
|
360
|
-
const facts = (0, autoRemember_1.extractExplicitAssistantMemory)(response).filter((fact) => fact.scope === 'project');
|
|
361
|
-
if (facts.length === 0) {
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
const checkpoint = {};
|
|
365
|
-
const outputs = [];
|
|
366
|
-
for (const fact of facts) {
|
|
367
|
-
if (fact.key === 'current_step') {
|
|
368
|
-
checkpoint.currentStep = readTextField(fact.value, 'text');
|
|
369
|
-
continue;
|
|
370
|
-
}
|
|
371
|
-
if (fact.key === 'next_step') {
|
|
372
|
-
checkpoint.nextStep = readTextField(fact.value, 'instruction') ?? readTextField(fact.value, 'text');
|
|
373
|
-
continue;
|
|
374
|
-
}
|
|
375
|
-
if (fact.key === 'open_risks') {
|
|
376
|
-
checkpoint.openRisks = readItems(fact.value);
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
if (fact.key === 'important_artifacts') {
|
|
380
|
-
outputs.push(...readItems(fact.value));
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
if (fact.key === 'recent_file_changes') {
|
|
384
|
-
const fileChanges = typeof fact.value === 'object' && fact.value !== null && Array.isArray(fact.value.items)
|
|
385
|
-
? fact.value.items
|
|
386
|
-
.filter((item) => item && typeof item === 'object')
|
|
387
|
-
.map((item) => ({
|
|
388
|
-
action: String(item.action ?? 'updated').trim() || 'updated',
|
|
389
|
-
path: String(item.path ?? '').trim(),
|
|
390
|
-
...(typeof item.toPath === 'string' && item.toPath.trim() ? { toPath: String(item.toPath).trim() } : {}),
|
|
391
|
-
...(typeof item.purpose === 'string' && item.purpose.trim() ? { purpose: String(item.purpose).trim() } : {}),
|
|
392
|
-
}))
|
|
393
|
-
.filter((item) => item.path)
|
|
394
|
-
: [];
|
|
395
|
-
if (fileChanges.length > 0) {
|
|
396
|
-
checkpoint.fileChanges = [...(checkpoint.fileChanges ?? []), ...fileChanges];
|
|
397
|
-
}
|
|
398
|
-
outputs.push(...readFileChangeOutputs(fact.value));
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
if (fact.key === 'recent_actions') {
|
|
402
|
-
const actions = readCheckpointActions(fact.value);
|
|
403
|
-
if (actions.length > 0) {
|
|
404
|
-
checkpoint.actions = [...(checkpoint.actions ?? []), ...actions];
|
|
405
|
-
}
|
|
406
|
-
outputs.push(...readActionOutputs(fact.value));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
if (outputs.length > 0) {
|
|
410
|
-
checkpoint.recentOutputs = outputs;
|
|
411
|
-
}
|
|
412
|
-
return checkpoint.currentStep
|
|
413
|
-
|| checkpoint.nextStep
|
|
414
|
-
|| (checkpoint.openRisks && checkpoint.openRisks.length > 0)
|
|
415
|
-
|| (checkpoint.recentOutputs && checkpoint.recentOutputs.length > 0)
|
|
416
|
-
|| (checkpoint.actions && checkpoint.actions.length > 0)
|
|
417
|
-
|| (checkpoint.fileChanges && checkpoint.fileChanges.length > 0)
|
|
418
|
-
? checkpoint
|
|
419
|
-
: null;
|
|
420
|
-
}
|
|
421
301
|
function emitHookContext(event, additionalContext) {
|
|
422
302
|
const payload = {
|
|
423
303
|
hookSpecificOutput: {
|
|
@@ -523,8 +403,8 @@ async function buildHookAdditionalContext(options) {
|
|
|
523
403
|
}
|
|
524
404
|
if (event === 'Stop') {
|
|
525
405
|
const response = getLastAssistantMessage(payload);
|
|
526
|
-
if (response
|
|
527
|
-
await (0, autoRemember_1.
|
|
406
|
+
if (response) {
|
|
407
|
+
await (0, autoRemember_1.rememberAssistantResponseFacts)({
|
|
528
408
|
iranti,
|
|
529
409
|
response,
|
|
530
410
|
agent,
|
|
@@ -534,7 +414,7 @@ async function buildHookAdditionalContext(options) {
|
|
|
534
414
|
host: 'claude_code',
|
|
535
415
|
},
|
|
536
416
|
});
|
|
537
|
-
const checkpoint =
|
|
417
|
+
const checkpoint = (0, assistantCheckpoint_1.extractAssistantCheckpointPayload)(response);
|
|
538
418
|
const projectEntity = (0, autoRemember_1.getProjectMemoryEntity)();
|
|
539
419
|
if (checkpoint && projectEntity && typeof iranti.checkpoint === 'function') {
|
|
540
420
|
await iranti.checkpoint({
|
|
@@ -547,6 +427,14 @@ async function buildHookAdditionalContext(options) {
|
|
|
547
427
|
},
|
|
548
428
|
});
|
|
549
429
|
}
|
|
430
|
+
await iranti.attend({
|
|
431
|
+
agent,
|
|
432
|
+
latestMessage: response,
|
|
433
|
+
currentContext: response,
|
|
434
|
+
entityHints,
|
|
435
|
+
maxFacts: getMaxFacts(),
|
|
436
|
+
phase: 'post-response',
|
|
437
|
+
});
|
|
550
438
|
}
|
|
551
439
|
return '';
|
|
552
440
|
}
|
|
@@ -575,6 +463,7 @@ async function buildHookAdditionalContext(options) {
|
|
|
575
463
|
currentContext: buildCurrentContext(payload, prompt),
|
|
576
464
|
entityHints,
|
|
577
465
|
maxFacts: getMaxFacts(),
|
|
466
|
+
phase: 'pre-response',
|
|
578
467
|
});
|
|
579
468
|
const facts = attend.facts.map((fact) => ({
|
|
580
469
|
entity: fact.entityKey.split('/').slice(0, 2).join('/'),
|
|
@@ -225,7 +225,7 @@ function buildCodexAgentsBlock() {
|
|
|
225
225
|
'- Call `mcp__iranti__iranti_checkpoint` at natural pauses, before stepping away from long work, when interrupted, and when completing a useful slice.',
|
|
226
226
|
'- When useful actions happen, record them in the checkpoint `actions` field so later sessions can see important commands, tests, searches, validations, and decisions without rerunning them blindly.',
|
|
227
227
|
'- Do not treat durable writes as a substitute for checkpoints. A checkpoint not written means the next session has to reconstruct state.',
|
|
228
|
-
'- Under-logged runs are non-compliant for this repo. When applicable,
|
|
228
|
+
'- Under-logged runs are non-compliant for this repo. When applicable, call iranti_write with what you found, what worked, what failed, what changed, and what happens next — not a broad summary, but specific durable facts.',
|
|
229
229
|
'',
|
|
230
230
|
'## Host setup check',
|
|
231
231
|
'- If this block was missing at session start, rerun `iranti codex-setup` from the bound project root.',
|