memgineering 0.21.0 → 0.22.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/CHANGELOG.md +45 -0
- package/assets/MEMGINEERING.md +1 -1
- package/assets/memgineering-rules/SKILL.md +17 -6
- package/assets/memgineering-writing/SKILL.md +36 -8
- package/dist/index.js +250 -63
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,51 @@ language the reader wants. The bilingual rule the monorepo applies to
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.22.0] — 2026-09-09
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **`consolidate` now asks whether a rule is a rule.** It already proposed
|
|
19
|
+
raising a binding memory to a decision; it never proposed the other
|
|
20
|
+
direction. A memory marked as binding but classified as a fact or a
|
|
21
|
+
procedure is now surfaced as a candidate to stop binding — audited against a
|
|
22
|
+
real brain, 13 of 54 standing rules were a fact, a status, or a
|
|
23
|
+
troubleshooting procedure rather than anything an agent should obey.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- **`rules` and the before-edit hook now fill two budgets, not one list.**
|
|
28
|
+
Rules that hold everywhere and rules that hold only in this project are drawn
|
|
29
|
+
separately and labelled, so a project's own rules can no longer be pushed out
|
|
30
|
+
by more-used general ones. Measured before the change on a brain with 54
|
|
31
|
+
rules: not one project-scoped rule reached the hook in its own project.
|
|
32
|
+
- **A rule write has to say what it binds.** `remember --rule` now records a
|
|
33
|
+
scope. The CLI fills it from the project you are in, so a rule written at a
|
|
34
|
+
terminal is unaffected; a caller that sends neither a scope nor a project —
|
|
35
|
+
an MCP tool call, a direct API write — is refused and told to send the
|
|
36
|
+
project name, or `global` for a rule that must hold everywhere. `global` is
|
|
37
|
+
stored as no scope at all, which is what such a rule already looked like.
|
|
38
|
+
Ordinary `remember` is unchanged: an observation with no scope is a normal
|
|
39
|
+
observation.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **The before-edit hook no longer pays for the network on every tool call.**
|
|
44
|
+
It claims the session's turn before asking the server rather than after, so
|
|
45
|
+
the second and later calls in a session return without a round-trip. It
|
|
46
|
+
spoke once per session already; it now costs once per session too.
|
|
47
|
+
- **`init` scaffolds the folder `remember` actually writes to.** New brains
|
|
48
|
+
were laid out with `20_TIMELINE/observations/` while writes went to
|
|
49
|
+
`_observations/`, leaving the scaffolded folder empty and a second one beside
|
|
50
|
+
it. Existing notes are not moved.
|
|
51
|
+
|
|
52
|
+
### Agent guidance
|
|
53
|
+
|
|
54
|
+
The bundled hub and sub-skills changed: the hub now hands the install job to
|
|
55
|
+
`memgineering registry pull` outright instead of describing it, and the rules
|
|
56
|
+
skill covers the two budgets and the scope a rule now carries. Restart your
|
|
57
|
+
agent session after upgrading so it re-reads them.
|
|
58
|
+
|
|
14
59
|
## [0.21.0] — 2026-09-08
|
|
15
60
|
|
|
16
61
|
### Added
|
package/assets/MEMGINEERING.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: memgineering
|
|
3
3
|
description: Use whenever the user refers to something they told you before, asks what was decided, tells you something worth keeping, or settles something that should hold next time. The memory lives in their own folder and outlives this session; check it before answering from guesswork, and write to it when you learn something durable.
|
|
4
4
|
type: skill
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.22.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# memgineering
|
|
@@ -85,12 +85,23 @@ alone earned — not that the top card is a rule, and not that it never could be
|
|
|
85
85
|
since the engine noticing a note is decision-shaped is weaker evidence than its
|
|
86
86
|
author saying it binds.
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
**Rules arrive in two budgets, not one list.** What a session is shown is the
|
|
89
|
+
strongest few that hold everywhere, plus the strongest few scoped to the project
|
|
90
|
+
it is standing in — ten in total, and each half gives its unused room to the
|
|
91
|
+
other, so a brain with nothing scoped still fills the list. Rules scoped to a
|
|
92
|
+
DIFFERENT project are not shown to an agent working here at all; they bind
|
|
93
|
+
there. `memgineering rules` lists them separately under "scoped to other
|
|
94
|
+
projects", which is the one place to notice a scope with a typo in it — such a
|
|
95
|
+
rule binds nowhere and would otherwise appear nowhere.
|
|
96
|
+
|
|
97
|
+
Before the split there was one list ranked by use, and it did not work: measured
|
|
98
|
+
in a real repository, every slot went to rules about other things and a rule
|
|
99
|
+
written that morning about that project's own layout ranked 49th of 54.
|
|
100
|
+
|
|
101
|
+
The before-edit hook and `memgineering rules` read the same brain — the hosted
|
|
102
|
+
one when this machine is pointed at it, the folder on disk otherwise — so what
|
|
103
|
+
the hook will deliver is what the command shows. Add `--local` to read the
|
|
104
|
+
folder on this disk specifically.
|
|
94
105
|
|
|
95
106
|
## What the user's agent actually sees
|
|
96
107
|
|
|
@@ -33,14 +33,34 @@ memgineering remember "we always squash before merging" --rule --no-scope \
|
|
|
33
33
|
--reason "they said from now on, about every repo — not this one"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
- `--no-scope` for
|
|
37
|
-
rule
|
|
38
|
-
|
|
36
|
+
- `--no-scope` for a decision that genuinely holds in every project — a
|
|
37
|
+
security rule, a way of writing commits, something about the person rather
|
|
38
|
+
than the work. It is a real answer, not the safe one: use it when it is true.
|
|
39
39
|
- `--scope <name>` when the note is ABOUT a project you are not standing in.
|
|
40
40
|
|
|
41
|
+
**Prefer the project scope for a rule that is about one project.** `--no-scope`
|
|
42
|
+
used to be the advice for rules in general, on the reasoning that a scoped rule
|
|
43
|
+
drops out of other projects' answers — it does, and that is now the point. Rules
|
|
44
|
+
reach a session in two budgets, one for what holds everywhere and one for the
|
|
45
|
+
project in front of it, so a scoped rule is no longer competing with every other
|
|
46
|
+
rule in the brain for the same few slots. Measured before the split: in a real
|
|
47
|
+
repository all the slots went to rules about other things, and a rule written
|
|
48
|
+
that morning about that project's own layout ranked 49th of 54 — present,
|
|
49
|
+
enforced by nothing, invisible to the session it existed to stop.
|
|
50
|
+
|
|
41
51
|
`--scope` and `--no-scope` together are refused — they answer the same
|
|
42
52
|
question both ways.
|
|
43
53
|
|
|
54
|
+
**A rule has to say what it binds, and one of the two is required.** A `--rule`
|
|
55
|
+
write is refused when nothing names a scope and nothing can be derived — a
|
|
56
|
+
folder that is not a repository and has no bound project. Add `--scope "<the
|
|
57
|
+
project>"` or `--no-scope`, and retry; nothing was written. On the hosted API
|
|
58
|
+
(`brain_remember`, or `POST /remember` directly) the same refusal applies with
|
|
59
|
+
no derivation at all: send `scope`, or `"scope": "global"` for one that really
|
|
60
|
+
does hold everywhere. `global` is reserved and is stored as no scope, which is
|
|
61
|
+
what a machine-wide rule already looks like — so there is only ever one
|
|
62
|
+
spelling of it to search for.
|
|
63
|
+
|
|
44
64
|
**`--reason` on every write.** It goes in the ledger and is the only part of the
|
|
45
65
|
record that still means anything six months later. Every write verb takes it:
|
|
46
66
|
`remember`, `revise`, `retire`, `exclude`, `undo`. The one on `undo` matters
|
|
@@ -215,11 +235,19 @@ it is about without opening them. It does not carry their claims, so deciding
|
|
|
215
235
|
which of a pair is right still means reading them: two notes can share a title
|
|
216
236
|
and hold opposite conclusions, which is exactly the case a proposal exists for.
|
|
217
237
|
It returns them with the evidence that caught them and the action it would
|
|
218
|
-
suggest, under
|
|
219
|
-
`contradiction`, a disagreement nobody settled; `semantic_similarity`,
|
|
220
|
-
meaning agreeing reciprocally; `shared_context`, one scope naming the
|
|
221
|
-
thing; `similar_title`; and
|
|
222
|
-
|
|
238
|
+
suggest, under seven rules: `declared_supersession`, a replaced note still
|
|
239
|
+
active; `contradiction`, a disagreement nobody settled; `semantic_similarity`,
|
|
240
|
+
indexed meaning agreeing reciprocally; `shared_context`, one scope naming the
|
|
241
|
+
same thing; `similar_title`; and the two directions of one disagreement between
|
|
242
|
+
a note's `binding` flag and its `kind` — `kind_promotion`, a binding note with
|
|
243
|
+
no kind at all, where the marking was deliberate and the classification never
|
|
244
|
+
happened; and `rule_demotion`, a binding note the author classified as a fact,
|
|
245
|
+
a procedure, an episode or a goal. That second one is two deliberate fields
|
|
246
|
+
contradicting each other, and the marking is the one to doubt: measured on a
|
|
247
|
+
real brain, 13 of 54 binding notes were a fact, a piece of state or a procedure,
|
|
248
|
+
each standing over every session as a decision nobody made. Its suggestion is
|
|
249
|
+
`memgineering revise <id> --no-rule --reason "…"`, which already exists — there
|
|
250
|
+
is no new verb for taking a marking back. Semantic detection reuses a complete, calibrated hosted
|
|
223
251
|
vector generation and never calls a model during consolidation. Authored
|
|
224
252
|
scope/entity context permits reciprocal neighbours; without it, evidence must
|
|
225
253
|
form a stricter three-note semantic triangle.
|
package/dist/index.js
CHANGED
|
@@ -277,13 +277,17 @@ var init_errors = __esm({
|
|
|
277
277
|
|
|
278
278
|
// ../../packages/memory-engine/src/schema.ts
|
|
279
279
|
import { z } from "zod";
|
|
280
|
-
|
|
280
|
+
function isGlobalScope(scope) {
|
|
281
|
+
return scope != null && scope.trim().toLowerCase() === GLOBAL_SCOPE;
|
|
282
|
+
}
|
|
283
|
+
var MappingModeSchema, ScopeSourceSchema, SCOPE_LIMIT, GLOBAL_SCOPE, TELEMETRY_ERROR_KINDS, TELEMETRY_MESSAGE_LIMIT, TelemetryErrorPayloadSchema, ProvenanceSchema, MemoryStatusSchema, ClaimConfidenceSchema, MemoryKindSchema, ClaimSchema, MemorySchema, RelationTypeSchema, RelationSourceSchema, RelationSchema;
|
|
281
284
|
var init_schema = __esm({
|
|
282
285
|
"../../packages/memory-engine/src/schema.ts"() {
|
|
283
286
|
"use strict";
|
|
284
287
|
MappingModeSchema = z.enum(["explicit", "heuristic"]);
|
|
285
288
|
ScopeSourceSchema = z.enum(["explicit", "path", "none"]);
|
|
286
289
|
SCOPE_LIMIT = 128;
|
|
290
|
+
GLOBAL_SCOPE = "global";
|
|
287
291
|
TELEMETRY_ERROR_KINDS = [
|
|
288
292
|
"network_unreachable",
|
|
289
293
|
"server_error",
|
|
@@ -444,6 +448,55 @@ var init_schema = __esm({
|
|
|
444
448
|
}
|
|
445
449
|
});
|
|
446
450
|
|
|
451
|
+
// ../../packages/memory-engine/src/layout.ts
|
|
452
|
+
var OBSERVATIONS_DIR;
|
|
453
|
+
var init_layout = __esm({
|
|
454
|
+
"../../packages/memory-engine/src/layout.ts"() {
|
|
455
|
+
"use strict";
|
|
456
|
+
OBSERVATIONS_DIR = "_observations";
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// ../../packages/memory-engine/src/rule-budget.ts
|
|
461
|
+
function ruleLane(scope, project) {
|
|
462
|
+
if (scope == null || scope.trim() === "" || isGlobalScope(scope)) return "global";
|
|
463
|
+
if (project === null) return "elsewhere";
|
|
464
|
+
return scope.trim().toLowerCase() === project.trim().toLowerCase() ? "project" : "elsewhere";
|
|
465
|
+
}
|
|
466
|
+
function declaredScopeOf(memory) {
|
|
467
|
+
return memory.provenance.scopeSource === "explicit" ? memory.scope : null;
|
|
468
|
+
}
|
|
469
|
+
function splitRuleBudget(ranked, scopeOf, project, budget = DEFAULT_RULE_BUDGET) {
|
|
470
|
+
const globalLane = [];
|
|
471
|
+
const projectLane = [];
|
|
472
|
+
const elsewhere = [];
|
|
473
|
+
for (const item of ranked) {
|
|
474
|
+
const lane = ruleLane(scopeOf(item), project);
|
|
475
|
+
if (lane === "global") globalLane.push(item);
|
|
476
|
+
else if (lane === "project") projectLane.push(item);
|
|
477
|
+
else elsewhere.push(item);
|
|
478
|
+
}
|
|
479
|
+
const total = budget.global + budget.project;
|
|
480
|
+
const globalRoom = Math.max(budget.global, total - Math.min(projectLane.length, budget.project));
|
|
481
|
+
const projectRoom = Math.max(budget.project, total - Math.min(globalLane.length, budget.global));
|
|
482
|
+
return {
|
|
483
|
+
global: globalLane.slice(0, globalRoom),
|
|
484
|
+
project: projectLane.slice(0, projectRoom),
|
|
485
|
+
elsewhere,
|
|
486
|
+
// Everything either budget COULD have shown, so "5 of 12" is honest about
|
|
487
|
+
// the cap without counting another project's rules as things withheld.
|
|
488
|
+
eligible: globalLane.length + projectLane.length
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
var DEFAULT_RULE_BUDGET;
|
|
492
|
+
var init_rule_budget = __esm({
|
|
493
|
+
"../../packages/memory-engine/src/rule-budget.ts"() {
|
|
494
|
+
"use strict";
|
|
495
|
+
init_schema();
|
|
496
|
+
DEFAULT_RULE_BUDGET = { global: 5, project: 5 };
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
|
|
447
500
|
// ../../packages/memory-engine/src/boundary.ts
|
|
448
501
|
function normalizeForMatch(value) {
|
|
449
502
|
return value.normalize("NFC").replace(ZERO_WIDTH, "").replace(WHITESPACE_RUN, " ").trim();
|
|
@@ -2796,19 +2849,38 @@ function kindPromotions(entries) {
|
|
|
2796
2849
|
const out = [];
|
|
2797
2850
|
for (const entry of entries) {
|
|
2798
2851
|
if (entry.memory.binding !== true) continue;
|
|
2799
|
-
|
|
2852
|
+
const kind = entry.memory.kind;
|
|
2853
|
+
if (kind !== void 0 && RULE_SHAPED_KINDS.has(kind)) continue;
|
|
2854
|
+
if (kind === void 0) {
|
|
2855
|
+
out.push({
|
|
2856
|
+
candidate: {
|
|
2857
|
+
rule: "kind_promotion",
|
|
2858
|
+
memory_id: entry.memory.id,
|
|
2859
|
+
path: entry.path,
|
|
2860
|
+
title: entry.memory.title,
|
|
2861
|
+
other_id: null,
|
|
2862
|
+
other_path: null,
|
|
2863
|
+
other_title: null,
|
|
2864
|
+
evidence: "marked as binding but left unclassified \u2014 a memory that binds is a decision",
|
|
2865
|
+
suggested_action: "reclassify",
|
|
2866
|
+
suggested_kind: "decision"
|
|
2867
|
+
},
|
|
2868
|
+
confidence: 4
|
|
2869
|
+
});
|
|
2870
|
+
continue;
|
|
2871
|
+
}
|
|
2800
2872
|
out.push({
|
|
2801
2873
|
candidate: {
|
|
2802
|
-
rule: "
|
|
2874
|
+
rule: "rule_demotion",
|
|
2803
2875
|
memory_id: entry.memory.id,
|
|
2804
2876
|
path: entry.path,
|
|
2805
2877
|
title: entry.memory.title,
|
|
2806
2878
|
other_id: null,
|
|
2807
2879
|
other_path: null,
|
|
2808
2880
|
other_title: null,
|
|
2809
|
-
evidence:
|
|
2810
|
-
suggested_action: "
|
|
2811
|
-
suggested_kind:
|
|
2881
|
+
evidence: `marked as binding but classified as ${kind} \u2014 the author said it is not a decision, so it may not be a rule`,
|
|
2882
|
+
suggested_action: "unmark_rule",
|
|
2883
|
+
suggested_kind: null
|
|
2812
2884
|
},
|
|
2813
2885
|
confidence: 4
|
|
2814
2886
|
});
|
|
@@ -2915,7 +2987,7 @@ function* pairsFrom(buckets, budget) {
|
|
|
2915
2987
|
}
|
|
2916
2988
|
}
|
|
2917
2989
|
}
|
|
2918
|
-
var CONSOLIDATION_RULES, DEFAULT_LIMIT, DEFAULT_PAIR_BUDGET, SEMANTIC_PAIR_BUDGET, TITLE_OVERLAP, SAME_SCOPE_SEMANTIC_SIMILARITY, SHARED_ENTITY_SEMANTIC_SIMILARITY, GLOBAL_SEMANTIC_SIMILARITY, RECIPROCAL_NEIGHBOURS, CandidateComponents;
|
|
2990
|
+
var CONSOLIDATION_RULES, DEFAULT_LIMIT, DEFAULT_PAIR_BUDGET, SEMANTIC_PAIR_BUDGET, TITLE_OVERLAP, SAME_SCOPE_SEMANTIC_SIMILARITY, SHARED_ENTITY_SEMANTIC_SIMILARITY, GLOBAL_SEMANTIC_SIMILARITY, RECIPROCAL_NEIGHBOURS, RULE_SHAPED_KINDS, CandidateComponents;
|
|
2919
2991
|
var init_detect = __esm({
|
|
2920
2992
|
"../../packages/memory-engine/src/proposal/detect.ts"() {
|
|
2921
2993
|
"use strict";
|
|
@@ -2926,7 +2998,8 @@ var init_detect = __esm({
|
|
|
2926
2998
|
"semantic_similarity",
|
|
2927
2999
|
"shared_context",
|
|
2928
3000
|
"similar_title",
|
|
2929
|
-
"kind_promotion"
|
|
3001
|
+
"kind_promotion",
|
|
3002
|
+
"rule_demotion"
|
|
2930
3003
|
];
|
|
2931
3004
|
DEFAULT_LIMIT = 50;
|
|
2932
3005
|
DEFAULT_PAIR_BUDGET = 2e5;
|
|
@@ -2936,6 +3009,7 @@ var init_detect = __esm({
|
|
|
2936
3009
|
SHARED_ENTITY_SEMANTIC_SIMILARITY = 0.75;
|
|
2937
3010
|
GLOBAL_SEMANTIC_SIMILARITY = 0.72;
|
|
2938
3011
|
RECIPROCAL_NEIGHBOURS = 2;
|
|
3012
|
+
RULE_SHAPED_KINDS = /* @__PURE__ */ new Set(["decision", "preference"]);
|
|
2939
3013
|
CandidateComponents = class {
|
|
2940
3014
|
parent = /* @__PURE__ */ new Map();
|
|
2941
3015
|
root(id) {
|
|
@@ -4051,6 +4125,8 @@ var init_src = __esm({
|
|
|
4051
4125
|
"../../packages/memory-engine/src/index.ts"() {
|
|
4052
4126
|
"use strict";
|
|
4053
4127
|
init_schema();
|
|
4128
|
+
init_layout();
|
|
4129
|
+
init_rule_budget();
|
|
4054
4130
|
init_boundary();
|
|
4055
4131
|
init_markdown_mapping();
|
|
4056
4132
|
init_build();
|
|
@@ -4482,6 +4558,7 @@ var BrainSchema, ConfigSchema, EMPTY_CONFIG;
|
|
|
4482
4558
|
var init_config = __esm({
|
|
4483
4559
|
"src/lib/config.ts"() {
|
|
4484
4560
|
"use strict";
|
|
4561
|
+
init_src();
|
|
4485
4562
|
init_atomic();
|
|
4486
4563
|
init_brand();
|
|
4487
4564
|
BrainSchema = z8.object({
|
|
@@ -4495,8 +4572,11 @@ var init_config = __esm({
|
|
|
4495
4572
|
* place for observations in its layout, while a folder someone has kept
|
|
4496
4573
|
* notes in for years does not — and picking one for them is how a tool
|
|
4497
4574
|
* starts rearranging a vault it promised only to read.
|
|
4575
|
+
*
|
|
4576
|
+
* The DEFAULT is shared with everything else that writes an observation, and
|
|
4577
|
+
* is not a second opinion about where they go — see `OBSERVATIONS_DIR`.
|
|
4498
4578
|
*/
|
|
4499
|
-
observationsDir: z8.string().min(1).default(
|
|
4579
|
+
observationsDir: z8.string().min(1).default(OBSERVATIONS_DIR)
|
|
4500
4580
|
});
|
|
4501
4581
|
ConfigSchema = z8.object({
|
|
4502
4582
|
brain: z8.object({
|
|
@@ -4730,7 +4810,12 @@ var init_cloud_brain = __esm({
|
|
|
4730
4810
|
...askedFromField(args.askedFrom)
|
|
4731
4811
|
});
|
|
4732
4812
|
cloudRules = (brain, args = {}) => call(brain, "POST", "/rules", {
|
|
4733
|
-
...args.limit === void 0 ? {} : { limit: args.limit }
|
|
4813
|
+
...args.limit === void 0 ? {} : { limit: args.limit },
|
|
4814
|
+
// The server cannot see this machine's folders, so the project it is
|
|
4815
|
+
// standing in has to be told. Omitted rather than sent as null when there
|
|
4816
|
+
// is none: absent means "fill the global budget only", which is what a
|
|
4817
|
+
// folder that names no project should get.
|
|
4818
|
+
...args.project == null ? {} : { project: args.project }
|
|
4734
4819
|
});
|
|
4735
4820
|
cloudReindex = (brain, args = {}) => call(brain, "POST", "/reindex", {
|
|
4736
4821
|
...args.rebuild === true ? { rebuild: true } : {}
|
|
@@ -5542,14 +5627,14 @@ function initCommand() {
|
|
|
5542
5627
|
brain: canonicalRoot2,
|
|
5543
5628
|
folders: FOLDERS,
|
|
5544
5629
|
files: Object.keys(files(opts.name)),
|
|
5545
|
-
observations_dir:
|
|
5630
|
+
observations_dir: OBSERVATIONS_DIR2,
|
|
5546
5631
|
indexed
|
|
5547
5632
|
},
|
|
5548
5633
|
human: () => {
|
|
5549
5634
|
printHuman(`Created a brain at \`${canonicalRoot2}\`.
|
|
5550
5635
|
`);
|
|
5551
5636
|
printHuman(`- \`01_BASE/\` \u2014 five files your agent reads first`);
|
|
5552
|
-
printHuman(`- \`${
|
|
5637
|
+
printHuman(`- \`${OBSERVATIONS_DIR2}/\` \u2014 where \`remember\` writes`);
|
|
5553
5638
|
printHuman(`- nine more folders, ready when you need them
|
|
5554
5639
|
`);
|
|
5555
5640
|
printHuman(c.gray("The five base files are empty templates.\n"));
|
|
@@ -5589,7 +5674,7 @@ async function createBrain(path, name) {
|
|
|
5589
5674
|
brains.push({
|
|
5590
5675
|
root: canonicalRoot2,
|
|
5591
5676
|
linkedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5592
|
-
observationsDir:
|
|
5677
|
+
observationsDir: OBSERVATIONS_DIR2
|
|
5593
5678
|
});
|
|
5594
5679
|
await saveConfig({ ...cfg, brain: withDefaultBrain({ ...cfg.brain, brains }) });
|
|
5595
5680
|
return { root: canonicalRoot2, indexed: built.entries.length };
|
|
@@ -5634,7 +5719,7 @@ function files(name) {
|
|
|
5634
5719
|
"| Folder | What lives here |",
|
|
5635
5720
|
"| --- | --- |",
|
|
5636
5721
|
"| `10_ENTITIES/` | People, projects, systems \u2014 one note each |",
|
|
5637
|
-
"| `20_TIMELINE/` | What happened, when
|
|
5722
|
+
"| `20_TIMELINE/` | What happened, when |",
|
|
5638
5723
|
"| `30_ACTIONS/` | Open work and decisions waiting on something |",
|
|
5639
5724
|
"| `40_KNOWLEDGE/` | Conclusions worth keeping: how things work, why they were decided |",
|
|
5640
5725
|
"| `50_SKILLS/` | Procedures \u2014 the steps for doing a thing again |",
|
|
@@ -5642,6 +5727,10 @@ function files(name) {
|
|
|
5642
5727
|
"| `70_SOURCES/` | Where something came from: links, papers, transcripts |",
|
|
5643
5728
|
"| `80_SHAREABLE/` | Notes you would be willing to publish |",
|
|
5644
5729
|
"| `90_ARCHIVE/` | Kept, but no longer current |",
|
|
5730
|
+
`| \`${OBSERVATIONS_DIR2}/\` | Written by the agent \u2014 what \`remember\` records |`,
|
|
5731
|
+
"",
|
|
5732
|
+
"An underscore in front means the folder is the agent's: it is written",
|
|
5733
|
+
"and rewritten without asking. A number in front means it is yours.",
|
|
5645
5734
|
""
|
|
5646
5735
|
].join("\n"),
|
|
5647
5736
|
"01_BASE/CRITICAL_FACTS.md": base(
|
|
@@ -5701,30 +5790,33 @@ function files(name) {
|
|
|
5701
5790
|
)
|
|
5702
5791
|
};
|
|
5703
5792
|
}
|
|
5704
|
-
var
|
|
5793
|
+
var OBSERVATIONS_DIR2, FOLDERS;
|
|
5705
5794
|
var init_init = __esm({
|
|
5706
5795
|
"src/commands/init.ts"() {
|
|
5707
5796
|
"use strict";
|
|
5797
|
+
init_src();
|
|
5708
5798
|
init_adapter();
|
|
5709
5799
|
init_errors();
|
|
5710
5800
|
init_config();
|
|
5711
5801
|
init_index_build();
|
|
5712
5802
|
init_vault();
|
|
5713
5803
|
init_ui();
|
|
5714
|
-
|
|
5804
|
+
OBSERVATIONS_DIR2 = OBSERVATIONS_DIR;
|
|
5715
5805
|
FOLDERS = [
|
|
5716
5806
|
"00_HUB",
|
|
5717
5807
|
"01_BASE",
|
|
5718
5808
|
"10_ENTITIES",
|
|
5719
5809
|
"20_TIMELINE",
|
|
5720
|
-
"20_TIMELINE/observations",
|
|
5721
5810
|
"30_ACTIONS",
|
|
5722
5811
|
"40_KNOWLEDGE",
|
|
5723
5812
|
"50_SKILLS",
|
|
5724
5813
|
"60_TEMPLATES",
|
|
5725
5814
|
"70_SOURCES",
|
|
5726
5815
|
"80_SHAREABLE",
|
|
5727
|
-
"90_ARCHIVE"
|
|
5816
|
+
"90_ARCHIVE",
|
|
5817
|
+
// Last rather than in numeric order, because it is not in that order: the
|
|
5818
|
+
// numbered folders are the person's and this one is the agent's.
|
|
5819
|
+
OBSERVATIONS_DIR2
|
|
5728
5820
|
];
|
|
5729
5821
|
}
|
|
5730
5822
|
});
|
|
@@ -6163,7 +6255,7 @@ var init_link = __esm({
|
|
|
6163
6255
|
init_index_build();
|
|
6164
6256
|
init_vault();
|
|
6165
6257
|
init_ui();
|
|
6166
|
-
DEFAULT_OBSERVATIONS_DIR =
|
|
6258
|
+
DEFAULT_OBSERVATIONS_DIR = OBSERVATIONS_DIR;
|
|
6167
6259
|
}
|
|
6168
6260
|
});
|
|
6169
6261
|
|
|
@@ -12539,6 +12631,14 @@ function rememberCommand() {
|
|
|
12539
12631
|
const neverStore = parseNeverStore(opts.neverStore);
|
|
12540
12632
|
const binding = opts.rule === true || neverStore.length > 0;
|
|
12541
12633
|
const scope = await scopeToWrite(opts.scope);
|
|
12634
|
+
if (binding && scope === void 0 && !said.noScope) {
|
|
12635
|
+
throw memgError(
|
|
12636
|
+
"invalid_input",
|
|
12637
|
+
"a rule needs a scope, and this folder does not suggest one",
|
|
12638
|
+
'Nothing was written. This folder is not a repository and has no bound project name, so there is nothing to stamp. Add `--scope "<the project>"` for a decision about one thing, or `--no-scope` for one that has to hold everywhere.'
|
|
12639
|
+
);
|
|
12640
|
+
}
|
|
12641
|
+
const wireScope = scope ?? (binding ? GLOBAL_SCOPE : void 0);
|
|
12542
12642
|
const target = await resolveTarget({
|
|
12543
12643
|
...opts.local === void 0 ? {} : { local: opts.local },
|
|
12544
12644
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
@@ -12547,7 +12647,7 @@ function rememberCommand() {
|
|
|
12547
12647
|
const cloud = await openCloudBrain(target);
|
|
12548
12648
|
const written = await cloudRemember(cloud, {
|
|
12549
12649
|
text: body,
|
|
12550
|
-
...
|
|
12650
|
+
...wireScope ? { scope: wireScope } : {},
|
|
12551
12651
|
...rationale ? { reason: rationale } : {},
|
|
12552
12652
|
...binding ? { rule: true } : {},
|
|
12553
12653
|
...neverStore.length > 0 ? { neverStore } : {},
|
|
@@ -13770,8 +13870,11 @@ function consolidateCommand() {
|
|
|
13770
13870
|
" semantic_similarity calibrated indexed meaning agrees reciprocally",
|
|
13771
13871
|
" shared_context two notes in one scope naming the same thing",
|
|
13772
13872
|
" similar_title two notes titled with mostly the same words",
|
|
13773
|
-
" kind_promotion a note marked as binding
|
|
13774
|
-
"
|
|
13873
|
+
" kind_promotion a note marked as binding with no kind at all \u2014 the",
|
|
13874
|
+
" marking is deliberate, the classification never happened",
|
|
13875
|
+
" rule_demotion a note marked as binding but classified as a fact, a",
|
|
13876
|
+
" procedure, an episode or a goal \u2014 the author said it is",
|
|
13877
|
+
" not a decision, so the MARKING is the field to doubt",
|
|
13775
13878
|
"",
|
|
13776
13879
|
"Nothing here becomes a proposal on its own. Read a candidate, decide what it",
|
|
13777
13880
|
"means, then say so:",
|
|
@@ -13854,7 +13957,12 @@ function renderCandidate(candidate) {
|
|
|
13854
13957
|
);
|
|
13855
13958
|
}
|
|
13856
13959
|
lines2.push(
|
|
13857
|
-
|
|
13960
|
+
// `unmark_rule` is not a proposal action and deliberately is not one: taking
|
|
13961
|
+
// a marking BACK is `revise --no-rule`, which already exists, and inventing
|
|
13962
|
+
// a second door to the same field would give a brain two ways to record one
|
|
13963
|
+
// change. So the suggestion names the command rather than a verb this CLI
|
|
13964
|
+
// does not have.
|
|
13965
|
+
candidate.suggested_action === "unmark_rule" ? c.gray(` suggested: memgineering revise ${candidate.memory_id} --no-rule --reason "\u2026"`) : candidate.suggested_kind === null ? c.gray(` suggested: ${candidate.suggested_action}`) : c.gray(` suggested: ${candidate.suggested_action} --kind ${candidate.suggested_kind}`)
|
|
13858
13966
|
);
|
|
13859
13967
|
return lines2.join("\n");
|
|
13860
13968
|
}
|
|
@@ -15475,7 +15583,7 @@ init_src();
|
|
|
15475
15583
|
init_index_build();
|
|
15476
15584
|
init_ui();
|
|
15477
15585
|
init_vault();
|
|
15478
|
-
var RULE_LIMIT =
|
|
15586
|
+
var RULE_LIMIT = DEFAULT_RULE_BUDGET.global + DEFAULT_RULE_BUDGET.project;
|
|
15479
15587
|
async function collectRules(vault, contextDir, now = /* @__PURE__ */ new Date()) {
|
|
15480
15588
|
const { brain, unreachableDefault } = await resolveBrain(vault, contextDir);
|
|
15481
15589
|
const adapter = await openBrainOrExplain(brain.root);
|
|
@@ -15493,11 +15601,21 @@ async function collectRules(vault, contextDir, now = /* @__PURE__ */ new Date())
|
|
|
15493
15601
|
here: (event) => isInside(event.context_dir, contextDir)
|
|
15494
15602
|
}).map((r) => [r.entry.memory.id, r.score])
|
|
15495
15603
|
);
|
|
15496
|
-
const
|
|
15604
|
+
const ranked = declared.map((entry) => ({ entry, score: order.get(entry.memory.id) ?? 0 })).sort((a, b) => b.score - a.score || a.entry.memory.id.localeCompare(b.entry.memory.id));
|
|
15605
|
+
const project = await projectOf(contextDir).then((p) => p.name).catch(() => null);
|
|
15606
|
+
const split = splitRuleBudget(ranked, (r) => declaredScopeOf(r.entry.memory), project);
|
|
15497
15607
|
return {
|
|
15498
15608
|
brainRoot: brain.root,
|
|
15499
|
-
|
|
15500
|
-
|
|
15609
|
+
// Global first: they hold here too, and they are the ones a reader is most
|
|
15610
|
+
// likely to already know, so the project's own decisions land last and
|
|
15611
|
+
// closest to the work.
|
|
15612
|
+
rules: [
|
|
15613
|
+
...split.global.map((rule) => ({ ...rule, lane: "global" })),
|
|
15614
|
+
...split.project.map((rule) => ({ ...rule, lane: "project" }))
|
|
15615
|
+
],
|
|
15616
|
+
elsewhere: split.elsewhere.map((rule) => ({ ...rule, lane: "elsewhere" })),
|
|
15617
|
+
total: ranked.length,
|
|
15618
|
+
project,
|
|
15501
15619
|
...unreachableDefault ? { unreachableDefault } : {}
|
|
15502
15620
|
};
|
|
15503
15621
|
}
|
|
@@ -15508,6 +15626,17 @@ function renderRuleLine(text, width = RULE_WIDTH) {
|
|
|
15508
15626
|
function renderRuleLines(rules) {
|
|
15509
15627
|
return rules.map(({ entry }) => renderRuleLine(entry.memory.summary ?? entry.memory.title));
|
|
15510
15628
|
}
|
|
15629
|
+
function renderRuleGroups(rules, project) {
|
|
15630
|
+
const globals = rules.filter((rule) => rule.lane === "global");
|
|
15631
|
+
const scoped = rules.filter((rule) => rule.lane === "project");
|
|
15632
|
+
const out = [];
|
|
15633
|
+
if (globals.length > 0 && scoped.length > 0) {
|
|
15634
|
+
out.push("Everywhere:", ...renderRuleLines(globals), "");
|
|
15635
|
+
out.push(`In ${oneLine(project ?? "this project", WIDTH.title)}:`, ...renderRuleLines(scoped));
|
|
15636
|
+
return out;
|
|
15637
|
+
}
|
|
15638
|
+
return renderRuleLines(rules);
|
|
15639
|
+
}
|
|
15511
15640
|
|
|
15512
15641
|
// src/commands/guard.ts
|
|
15513
15642
|
init_deadline();
|
|
@@ -15576,11 +15705,16 @@ async function runStopGuard(payload, contextDirFlag) {
|
|
|
15576
15705
|
});
|
|
15577
15706
|
}
|
|
15578
15707
|
async function claimRules(sessionId, contextDir) {
|
|
15579
|
-
const
|
|
15580
|
-
if (
|
|
15581
|
-
if (
|
|
15582
|
-
|
|
15583
|
-
|
|
15708
|
+
const where = await resolveTarget({}).catch(() => null);
|
|
15709
|
+
if (where?.kind === "cloud") {
|
|
15710
|
+
if (!await claimSession(sessionId, where.brainId)) return null;
|
|
15711
|
+
const hosted = await hostedRuleLines(where, contextDir).catch(() => null);
|
|
15712
|
+
if (hosted !== null) {
|
|
15713
|
+
if (hosted.lines.length > 0) return { lines: hosted.lines, total: hosted.total };
|
|
15714
|
+
await releaseSession(sessionId, where.brainId);
|
|
15715
|
+
return null;
|
|
15716
|
+
}
|
|
15717
|
+
await releaseSession(sessionId, where.brainId);
|
|
15584
15718
|
}
|
|
15585
15719
|
const collected = await collectRules(void 0, contextDir).catch(() => null);
|
|
15586
15720
|
if (collected === null) return null;
|
|
@@ -15599,7 +15733,7 @@ async function claimRules(sessionId, contextDir) {
|
|
|
15599
15733
|
limit: RULE_LIMIT,
|
|
15600
15734
|
returnedIds: rules.map((r) => r.entry.memory.id)
|
|
15601
15735
|
});
|
|
15602
|
-
return { lines:
|
|
15736
|
+
return { lines: renderRuleGroups(rules, collected.project), total };
|
|
15603
15737
|
}
|
|
15604
15738
|
function contextFor(lines2, total) {
|
|
15605
15739
|
return `${rulesQuoted(lines2, total)}
|
|
@@ -15662,11 +15796,14 @@ async function misdeliveryNotice(toolName, filePath, sessionId) {
|
|
|
15662
15796
|
if (!await claimSession(sessionId, "\0misdelivery")) return null;
|
|
15663
15797
|
return 'This file is this harness\u2019s own memory \u2014 no other tool the user works with will ever read it. Durable facts about the user belong in their brain, which every tool shares: memgineering remember "<the fact>" --reason "<why>" (add --rule if they stated a standing rule). Write it there instead, or in addition.';
|
|
15664
15798
|
}
|
|
15665
|
-
|
|
15666
|
-
|
|
15799
|
+
function markerFor(sessionId, brain) {
|
|
15800
|
+
return join18(
|
|
15667
15801
|
guardDir(),
|
|
15668
15802
|
`${createHash7("sha256").update(`${sessionId}\0${brain}`).digest("hex").slice(0, 32)}`
|
|
15669
15803
|
);
|
|
15804
|
+
}
|
|
15805
|
+
async function claimSession(sessionId, brain) {
|
|
15806
|
+
const marker = markerFor(sessionId, brain);
|
|
15670
15807
|
try {
|
|
15671
15808
|
await mkdir11(guardDir(), { recursive: true, mode: 448 });
|
|
15672
15809
|
await writeFile9(marker, "", { flag: "wx", mode: 384 });
|
|
@@ -15676,6 +15813,9 @@ async function claimSession(sessionId, brain) {
|
|
|
15676
15813
|
void pruneOldMarkers();
|
|
15677
15814
|
return true;
|
|
15678
15815
|
}
|
|
15816
|
+
async function releaseSession(sessionId, brain) {
|
|
15817
|
+
await unlink3(markerFor(sessionId, brain)).catch(() => void 0);
|
|
15818
|
+
}
|
|
15679
15819
|
var MARKER_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
15680
15820
|
async function pruneOldMarkers() {
|
|
15681
15821
|
try {
|
|
@@ -15690,15 +15830,24 @@ async function pruneOldMarkers() {
|
|
|
15690
15830
|
} catch {
|
|
15691
15831
|
}
|
|
15692
15832
|
}
|
|
15693
|
-
async function hostedRuleLines() {
|
|
15694
|
-
const
|
|
15695
|
-
|
|
15696
|
-
const
|
|
15697
|
-
return {
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15833
|
+
async function hostedRuleLines(where, contextDir) {
|
|
15834
|
+
const project = await projectOf(contextDir).then((p) => p.name).catch(() => null);
|
|
15835
|
+
const { rules, total } = await cloudRules(await openCloudBrain(where), { project });
|
|
15836
|
+
const grouped = groupHostedLines(rules, project);
|
|
15837
|
+
return { lines: grouped, total };
|
|
15838
|
+
}
|
|
15839
|
+
function groupHostedLines(rules, project) {
|
|
15840
|
+
const line = (r) => renderRuleLine(r.summary ?? r.title);
|
|
15841
|
+
const globals = rules.filter((r) => r.lane === "global");
|
|
15842
|
+
const scoped = rules.filter((r) => r.lane === "project");
|
|
15843
|
+
if (globals.length === 0 || scoped.length === 0) return rules.map(line);
|
|
15844
|
+
return [
|
|
15845
|
+
"Everywhere:",
|
|
15846
|
+
...globals.map(line),
|
|
15847
|
+
"",
|
|
15848
|
+
`In ${project ?? "this project"}:`,
|
|
15849
|
+
...scoped.map(line)
|
|
15850
|
+
];
|
|
15702
15851
|
}
|
|
15703
15852
|
|
|
15704
15853
|
// src/program.ts
|
|
@@ -21118,9 +21267,13 @@ function rulesCommand() {
|
|
|
21118
21267
|
...opts.vault === void 0 ? {} : { vault: opts.vault }
|
|
21119
21268
|
});
|
|
21120
21269
|
if (where.kind === "cloud") {
|
|
21121
|
-
const
|
|
21270
|
+
const project2 = await projectOf().then((p) => p.name).catch(() => null);
|
|
21271
|
+
const { rules: rules2, total: total2, eligible } = await cloudRules(await openCloudBrain(where), {
|
|
21272
|
+
project: project2
|
|
21273
|
+
});
|
|
21274
|
+
const shownOf = eligible ?? rules2.length;
|
|
21122
21275
|
printDual({
|
|
21123
|
-
json: { brain: where.name, hosted: true, rules: rules2, total: total2 },
|
|
21276
|
+
json: { brain: where.name, hosted: true, rules: rules2, total: total2, project: project2, eligible: shownOf },
|
|
21124
21277
|
human: () => {
|
|
21125
21278
|
printHuman(`## rules \u2014 ${oneLine(where.name, WIDTH.title)} (${total2})
|
|
21126
21279
|
`);
|
|
@@ -21135,11 +21288,23 @@ function rulesCommand() {
|
|
|
21135
21288
|
}
|
|
21136
21289
|
for (const rule of rules2) {
|
|
21137
21290
|
printHuman(`- ${oneLine(rule.summary ?? rule.title, WIDTH.text)}`);
|
|
21138
|
-
|
|
21291
|
+
const lane = rule.lane === "project" ? oneLine(project2 ?? "this project", WIDTH.title) : "global";
|
|
21292
|
+
printHuman(
|
|
21293
|
+
c.gray(
|
|
21294
|
+
` \`open: ${oneLine(rule.handle, WIDTH.handle)}\`${rule.lane ? ` \xB7 ${lane}` : ""}`
|
|
21295
|
+
)
|
|
21296
|
+
);
|
|
21139
21297
|
}
|
|
21140
|
-
if (
|
|
21298
|
+
if (shownOf > rules2.length) {
|
|
21141
21299
|
printHuman(c.gray(`
|
|
21142
|
-
${
|
|
21300
|
+
${shownOf - rules2.length} more not shown.`));
|
|
21301
|
+
}
|
|
21302
|
+
if (total2 > shownOf) {
|
|
21303
|
+
printHuman(
|
|
21304
|
+
c.gray(
|
|
21305
|
+
`${total2 - shownOf} more belong to other projects and are not shown here \u2014 they bind there.`
|
|
21306
|
+
)
|
|
21307
|
+
);
|
|
21143
21308
|
}
|
|
21144
21309
|
printHuman(
|
|
21145
21310
|
"\nThese are decisions, not suggestions. Follow them without being asked, and say\nso if what you are about to do goes against one."
|
|
@@ -21148,12 +21313,9 @@ ${total2 - rules2.length} more not shown.`));
|
|
|
21148
21313
|
});
|
|
21149
21314
|
return;
|
|
21150
21315
|
}
|
|
21151
|
-
const { rules, total, unreachableDefault, brainRoot } = await collectRules(
|
|
21152
|
-
|
|
21153
|
-
|
|
21154
|
-
);
|
|
21155
|
-
const allIds = rules.map((r) => r.entry.memory.id);
|
|
21156
|
-
if (rules.length === 0) {
|
|
21316
|
+
const { rules, elsewhere, total, project, unreachableDefault, brainRoot } = await collectRules(opts.vault, process.cwd());
|
|
21317
|
+
const allIds = [...rules, ...elsewhere].map((r) => r.entry.memory.id);
|
|
21318
|
+
if (rules.length === 0 && elsewhere.length === 0) {
|
|
21157
21319
|
const hint = unreachableDefault ? `Your default brain is not on disk right now: ${unreachableDefault}
|
|
21158
21320
|
This answered from \`${brainRoot}\` instead, so rules you wrote in the missing one are not shown \u2014 they are not lost. If it moved, \`memgineering use --default <new path>\`. If it is gone for good, \`memgineering unlink ${unreachableDefault}\`.` : 'Nothing here binds you yet. When the user decides something that should hold \u2014 a colour they will not use, a step never to skip \u2014 record it with `memgineering remember "<the decision>" --rule --reason "<why>"`, and it will be shown before the next edit.';
|
|
21159
21321
|
printDual({
|
|
@@ -21178,27 +21340,52 @@ This answered from \`${brainRoot}\` instead, so rules you wrote in the missing o
|
|
|
21178
21340
|
scope: entry.memory.scope,
|
|
21179
21341
|
path: entry.path
|
|
21180
21342
|
})),
|
|
21343
|
+
elsewhere: elsewhere.map(({ entry }) => ({
|
|
21344
|
+
id: entry.memory.id,
|
|
21345
|
+
handle: displayHandle(entry.memory.id, allIds),
|
|
21346
|
+
title: entry.memory.title,
|
|
21347
|
+
summary: entry.memory.summary,
|
|
21348
|
+
scope: entry.memory.scope,
|
|
21349
|
+
path: entry.path
|
|
21350
|
+
})),
|
|
21181
21351
|
total,
|
|
21182
|
-
|
|
21183
|
-
|
|
21352
|
+
project,
|
|
21353
|
+
...total > rules.length ? {
|
|
21354
|
+
hint: `${total} rules are declared and ${rules.length} are shown${project === null ? "" : ` for \`${project}\``}. Up to ${RULE_LIMIT} fit at once, split between rules that hold everywhere and rules scoped to this project; anything scoped to a DIFFERENT project is not shown here and binds there instead. If the number is high because old decisions never got retired, \`memgineering retire <handle>\`.`
|
|
21184
21355
|
} : {}
|
|
21185
21356
|
},
|
|
21186
21357
|
human: () => {
|
|
21187
|
-
printHuman(
|
|
21188
|
-
`)
|
|
21189
|
-
|
|
21358
|
+
printHuman(
|
|
21359
|
+
`## rules (${rules.length}${total > rules.length ? ` of ${total}` : ""}${project === null ? "" : ` \xB7 ${oneLine(project, WIDTH.title)}`})
|
|
21360
|
+
`
|
|
21361
|
+
);
|
|
21362
|
+
for (const { entry, lane } of rules) {
|
|
21190
21363
|
printHuman(`- ${oneLine(entry.memory.summary ?? entry.memory.title, WIDTH.text)}`);
|
|
21191
21364
|
printHuman(
|
|
21192
21365
|
c.gray(
|
|
21193
|
-
` \`open: ${displayHandle(entry.memory.id, allIds)}
|
|
21366
|
+
` \`open: ${displayHandle(entry.memory.id, allIds)}\` \xB7 ${lane === "project" ? oneLine(project ?? "this project", WIDTH.title) : "global"}`
|
|
21194
21367
|
)
|
|
21195
21368
|
);
|
|
21196
21369
|
}
|
|
21370
|
+
if (elsewhere.length > 0) {
|
|
21371
|
+
printHuman(`
|
|
21372
|
+
### scoped to other projects (${elsewhere.length})
|
|
21373
|
+
`);
|
|
21374
|
+
printHuman(c.gray("Not shown to an agent working here. They bind where they say.\n"));
|
|
21375
|
+
for (const { entry } of elsewhere) {
|
|
21376
|
+
printHuman(`- ${oneLine(entry.memory.summary ?? entry.memory.title, WIDTH.text)}`);
|
|
21377
|
+
printHuman(
|
|
21378
|
+
c.gray(
|
|
21379
|
+
` \`open: ${displayHandle(entry.memory.id, allIds)}\` \xB7 ${oneLine(entry.memory.scope ?? "", WIDTH.title)}`
|
|
21380
|
+
)
|
|
21381
|
+
);
|
|
21382
|
+
}
|
|
21383
|
+
}
|
|
21197
21384
|
printHuman("");
|
|
21198
21385
|
printHuman(
|
|
21199
21386
|
c.gray(
|
|
21200
|
-
total >
|
|
21201
|
-
|
|
21387
|
+
total > rules.length ? `${total} declared, ${rules.length} shown here. Rules scoped to another project bind there, not here.
|
|
21388
|
+
If the rest are decisions that no longer decide anything: \`memgineering retire <handle>\`.` : "These are decisions, not suggestions. Follow them without being asked, and say\nso if what you are about to do goes against one."
|
|
21202
21389
|
)
|
|
21203
21390
|
);
|
|
21204
21391
|
}
|
package/package.json
CHANGED