@agfpd/iapeer-memory 0.1.3 → 0.1.4

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agfpd/iapeer-memory",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "iapeer-memory — peer memory for the iapeer ecosystem: vault, memoryd (index/search/MCP-http), layer-5 context fragments, role doctrines. The package IS the system; the claude/codex plugins are thin session sockets (docs/10-distribution.md, ADR-009).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@agfpd/iapeer-memory-core": "0.1.3"
30
+ "@agfpd/iapeer-memory-core": "0.1.4"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/bun": "^1.2.0",
@@ -35,8 +35,10 @@ import { provisionVault, writeDefaultConfig } from "../provision.js";
35
35
  import { writeRolesManifest, type RoleEntry } from "../roles.js";
36
36
  import { writeSlot } from "../slot.js";
37
37
  import {
38
+ doctrineOwnership,
38
39
  guideText,
39
40
  materialiseTemplates,
41
+ rolePersonality,
40
42
  roleTemplatePath,
41
43
  ROLE_NAMES,
42
44
  } from "../templates/index.js";
@@ -61,6 +63,10 @@ type InitFlags = {
61
63
  skipDeps: boolean;
62
64
  skipEcosystem: boolean;
63
65
  skipBinary: boolean;
66
+ /** Skip the host-wide guide fragment (staged fleet rollout — cutover 10.06:
67
+ * the fleet still carries the MergeMind guide; ours lands by a separate
68
+ * decision after the plugin swap). */
69
+ skipGuide: boolean;
64
70
  iapeerBin: string;
65
71
  };
66
72
 
@@ -70,6 +76,7 @@ function parseFlags(argv: string[]): InitFlags | null {
70
76
  skipDeps: false,
71
77
  skipEcosystem: false,
72
78
  skipBinary: false,
79
+ skipGuide: false,
73
80
  iapeerBin: "iapeer",
74
81
  };
75
82
  for (let i = 0; i < argv.length; i++) {
@@ -85,6 +92,7 @@ function parseFlags(argv: string[]): InitFlags | null {
85
92
  case "--skip-deps": f.skipDeps = true; break;
86
93
  case "--skip-ecosystem": f.skipEcosystem = true; break;
87
94
  case "--skip-binary": f.skipBinary = true; break;
95
+ case "--skip-guide": f.skipGuide = true; break;
88
96
  case "--iapeer-bin": f.iapeerBin = take() ?? "iapeer"; break;
89
97
  default:
90
98
  console.error(`iapeer-memory init: unknown flag: ${a}`);
@@ -263,7 +271,9 @@ export async function cmdInit(argv: string[]): Promise<number> {
263
271
  const tmpl = materialiseTemplates({ templatesDir: paths.templatesDir, locale });
264
272
  step("templates", `${paths.templatesDir} (${tmpl.written.length} written, ${tmpl.identical.length} identical)`);
265
273
 
266
- // 6. role peers + doctrines + manifest
274
+ // 6. role peers + doctrines + manifest. Personalities are namespaced
275
+ // memory-<role> (collision-proof by design); the manifest keeps the
276
+ // CONCEPTUAL role keys.
267
277
  if (flags.skipEcosystem) {
268
278
  step("roles", "skipped (--skip-ecosystem)");
269
279
  } else {
@@ -271,26 +281,42 @@ export async function cmdInit(argv: string[]): Promise<number> {
271
281
  let rolesOk = true;
272
282
  let createdAny = false;
273
283
  for (const role of ROLE_NAMES) {
274
- const exists = (peers ?? []).some((p) => p.personality === role);
284
+ const personality = rolePersonality(role);
285
+ const exists = (peers ?? []).some((p) => p.personality === personality);
275
286
  if (!exists) {
276
- const created = run([flags.iapeerBin, "create", role]);
287
+ const created = run([flags.iapeerBin, "create", personality]);
277
288
  if (created.exitCode !== 0) {
278
289
  rolesOk = false;
279
- console.log(` roles create ${role} failed: ${created.stderr.trim()}`);
290
+ console.log(` roles create ${personality} failed: ${created.stderr.trim()}`);
280
291
  continue;
281
292
  }
282
293
  createdAny = true;
283
294
  }
284
295
  }
285
296
  // peerCwd: the registry FACT when the core exposes it (`cwd` in
286
- // `iapeer list --json` — iapeer 39de94b, next core release), otherwise
287
- // the core's DOCUMENTED create default (no --path — требование Артура;
288
- // IAPEER_ROOT-aware). Forward-compatible: switches to the fact
289
- // automatically once the deployed core ships the field.
297
+ // `iapeer list --json` — iapeer 0.2.14), otherwise the core's
298
+ // DOCUMENTED create default (no --path — требование Артура;
299
+ // IAPEER_ROOT-aware).
290
300
  const freshPeers = createdAny ? listPeers(flags.iapeerBin) : peers;
291
301
  for (const role of ROLE_NAMES) {
292
- const registryCwd = (freshPeers ?? []).find((p) => p.personality === role)?.cwd;
293
- const peerCwd = registryCwd || path.join(iapeerDir, "peers", role);
302
+ const personality = rolePersonality(role);
303
+ const registryCwd = (freshPeers ?? []).find((p) => p.personality === personality)?.cwd;
304
+ const peerCwd = registryCwd || path.join(iapeerDir, "peers", personality);
305
+ // COLLISION GUARD (прецедент 10.06: «index» был занят живым
306
+ // MergeMind-Индексом; бренд-имена ролей — решение Артура, защита
307
+ // целиком здесь): a pre-existing peer whose doctrine is NOT ours is
308
+ // somebody else's — rendering over it would hijack a live peer.
309
+ // FAIL loud with a recipe, never render.
310
+ if (doctrineOwnership(peerCwd) === "foreign") {
311
+ rolesOk = false;
312
+ console.log(
313
+ ` roles COLLISION: peer "${personality}" exists with a foreign doctrine ` +
314
+ `(${path.join(peerCwd, ".iapeer", "IAPEER.md")}) — not touching it. Recipe: ` +
315
+ `rename/remove that peer (iapeer stop ${personality} && iapeer remove ${personality}) ` +
316
+ `or move its cwd, then re-run init`,
317
+ );
318
+ continue;
319
+ }
294
320
  const template = roleTemplatePath(paths.templatesDir, locale, role);
295
321
  const rendered = renderDoctrine({ templatePath: template, peerCwd, version });
296
322
  if (rendered.action === "missing-template") {
@@ -308,8 +334,8 @@ export async function cmdInit(argv: string[]): Promise<number> {
308
334
  const wakePolicy = cwEntry ? patchWakePolicyEphemeral(cwEntry.peerCwd) : "missing-profile";
309
335
  step(
310
336
  "roles",
311
- `${roleEntries.map((r) => r.role).join(", ")} (doctrines v${version}, ` +
312
- `copywriter wake_policy: ${wakePolicy}, manifest ${paths.rolesManifestPath})`,
337
+ `${roleEntries.map((r) => rolePersonality(r.role as (typeof ROLE_NAMES)[number])).join(", ")} ` +
338
+ `(doctrines v${version}, copywriter wake_policy: ${wakePolicy}, manifest ${paths.rolesManifestPath})`,
313
339
  rolesOk && roleEntries.length === ROLE_NAMES.length && wakePolicy !== "missing-profile",
314
340
  );
315
341
  }
@@ -398,8 +424,12 @@ export async function cmdInit(argv: string[]): Promise<number> {
398
424
  }
399
425
 
400
426
  // 10. host-wide guide fragment (layer 5 — reaches every peer on next wakes)
401
- const guidePath = writeHostWideGuideFragment(iapeerDir, guideText(locale));
402
- step("guide", guidePath);
427
+ if (flags.skipGuide) {
428
+ step("guide", "skipped (--skip-guide) — roll out by a separate decision after the fleet plugin swap");
429
+ } else {
430
+ const guidePath = writeHostWideGuideFragment(iapeerDir, guideText(locale));
431
+ step("guide", guidePath);
432
+ }
403
433
 
404
434
  console.log(
405
435
  failures
@@ -30,6 +30,38 @@ import {
30
30
  export const ROLE_NAMES = ["index", "copywriter", "dreamweaver"] as const;
31
31
  export type RoleName = (typeof ROLE_NAMES)[number];
32
32
 
33
+ /**
34
+ * Peer PERSONALITY of a role — the BRAND names, identical to the conceptual
35
+ * role names (решение Артура 10.06: «index должен остаться с таким же
36
+ * именем — это бренд, не memory-index»; the `memory-*` namespace variant
37
+ * was built and rolled back the same day). Collision safety therefore rests
38
+ * ENTIRELY on the init roles-step GUARD: a pre-existing peer with a foreign
39
+ * doctrine fails loud with a recipe, never gets rendered over (прецедент:
40
+ * «index» был занят живым MergeMind-Индексом до cutover'а). The single
41
+ * mapping point is kept so a future namespace decision is one line.
42
+ */
43
+ export function rolePersonality(role: RoleName): string {
44
+ return role;
45
+ }
46
+
47
+ /**
48
+ * Who owns a peer's rendered doctrine — the init COLLISION GUARD's eye.
49
+ * "ours" = carries the ADR-010 marker; "foreign" = somebody else's live
50
+ * doctrine (rendering over it would hijack the peer — FAIL loud);
51
+ * "none" = bare peer, ours to render.
52
+ */
53
+ export function doctrineOwnership(peerCwd: string): "ours" | "foreign" | "none" {
54
+ try {
55
+ const current = fs.readFileSync(
56
+ path.join(peerCwd, ".iapeer", "IAPEER.md"),
57
+ "utf-8",
58
+ );
59
+ return current.includes("<!-- iapeer-memory doctrine") ? "ours" : "foreign";
60
+ } catch {
61
+ return "none";
62
+ }
63
+ }
64
+
33
65
  const ROLES: Record<LocaleId, Record<RoleName, string>> = {
34
66
  en: {
35
67
  index: INDEX_DOCTRINE_EN,