@topy-ai/maggie 0.7.46 → 0.7.47

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-zh-TW.md CHANGED
@@ -29,9 +29,17 @@ npx @topy-ai/maggie init --scope project --agent all
29
29
  npx @topy-ai/maggie doctor --scope project --project .
30
30
  ```
31
31
 
32
- 目前 release 是 `0.7.46`。新增 browser adapter capability preflight、privacy-safe
33
- feedback evidence references/relationships、bounded batch-review evidence graph、
34
- remediation suggestions,以及 root-level unittest import 支援。`browser-audit
32
+ 目前 release 是 `0.7.47`。本版處理 2026-09-26 五條新 feedback:project-scope
33
+ `maggie init` 會建立本地 memory store;context 預設只讀取本 project scope;memory hook
34
+ 在 local Python adapter 不存在時 fallback 到已安裝 CLI;重複 init 保留既有檔案與時間戳;
35
+ `maggie feedback list` 的 `--project` 可放在子命令前或後。Generated VPS runner 現在記錄
36
+ Maggie 版本與 template fingerprint;preflight 會警示過期/無標記 runner,其他路徑可用
37
+ `maggie deployment --check-runner PATH` 檢查。
38
+
39
+ 0.7.46 feedback batch 仍包含 release profile capability gate、deployment readiness bounded
40
+ retry、Google account/property target verification、GA/GTM external-write idempotency/readback、
41
+ browser adapter capability preflight、privacy-safe feedback evidence references/relationships,
42
+ 以及 root-level unittest import 支援。`browser-audit
35
43
  --check-browser` 會在導覽前拒絕 missing/incompatible adapter,包括解析成
36
44
  `xdg-open` 的系統 `browse`;feedback graph 不保存 raw payload、logs、credentials
37
45
  或本機路徑。另包含 0.7.45 的 Codex/Claude Code 一次性 user-scope 安裝、bundled
@@ -105,7 +113,7 @@ structure drift 或重複 treatment product 時 fail closed。
105
113
  deployment、memory、feedback 和 MaggieDash。內容先 draft/review,外部寫入、
106
114
  publish 與 production deployment 需要明確確認。
107
115
 
108
- 目前 source tree 的正式 release 是 `0.7.46`。本版完成 2026-09-24 booking feedback batch:Calendar grid/free-slot entry 與 host modal focus-restoration evidence。本版新增 Starter marketplace provisioning plan;本版完成 feedback batches 22、23:加入 declarative browser interaction evidence、design markup/style/content-key gate、compiler source anchoring、entity double-escape protection、`update --help` read-only、noindex hypothetical indexability、可過期 baseline acknowledgement、DOM-only link extraction regression,以及 MaggieDash 0.2.10 的 content override、短期 browser session、CDN-safe public-session cache contracts。authentication 和 isolated dev server 仍由 host 負責。`maggie-booking` workflow、傳統 email/password、Stripe、Resend、public booking flow 及既有安全邊界維持不變;provider-bound video generation 與 transactional delivery 仍記錄在 issue #39,前一批延後功能仍在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
116
+ 目前 source tree 的正式 release 是 `0.7.47`。本版完成 2026-09-24 booking feedback batch:Calendar grid/free-slot entry 與 host modal focus-restoration evidence。本版新增 Starter marketplace provisioning plan;本版完成 feedback batches 22、23:加入 declarative browser interaction evidence、design markup/style/content-key gate、compiler source anchoring、entity double-escape protection、`update --help` read-only、noindex hypothetical indexability、可過期 baseline acknowledgement、DOM-only link extraction regression,以及 MaggieDash 0.2.10 的 content override、短期 browser session、CDN-safe public-session cache contracts。authentication 和 isolated dev server 仍由 host 負責。`maggie-booking` workflow、傳統 email/password、Stripe、Resend、public booking flow 及既有安全邊界維持不變;provider-bound video generation 與 transactional delivery 仍記錄在 issue #39,前一批延後功能仍在 issue #38,不會直接成為 active memory。0.7.33 記錄並審查一筆更正回饋;確認 0.7.32 已正確處理 batch index,沒有修改已驗證的 shared behavior。0.7.32 加入 feedback 一基索引支援、MaggieDash panel 的 source/freshness/error evidence、Consent Mode 與實際 write/no-op reconciliation、SEO baseline recapture、sitemap origin rebasing、content-diff scope、migration ledger reconciliation、deployment credential preflight、release provenance/runtime preflight、icon release gate、opt-in blog auto-publish gate,以及 least-privilege VPS deployer。也修復 top-level `maggie feedback` dispatcher parity。0.7.31 加入 update 差異摘要、atomic installer copy、subset install manifest merge,以及 inventory drift 時 doctor non-zero。0.7.30 補上 feedback tracking issue closeout gate 與 batch 14/15 audit ledger。0.7.29 修正 npm package landing page 使用英文 README;0.7.28 加入 versioned Gemini model policy、明確 fallback 與 provenance、bundled-first CLI
109
117
  dispatch、`maggie --version`、feedback batch review 聚合與重複偵測、marketplace enrichment evidence、booking
110
118
  worker/resolver evidence,以及 read-only migration preflight。它也包含 host-owned mobile app surface contract、signed-in
111
119
  camera-state QA、直接 Astro route resolution、correlated feedback batch,以及
package/README.md CHANGED
@@ -89,10 +89,20 @@ Maggie keeps the existing project foundation and asks for decisions before
89
89
  shared routes, analytics, or publishing boundaries change. The current
90
90
  package ships 20 installable skills and a local-first MaggieDash foundation.
91
91
 
92
- The current release is `0.7.46`. It adds browser-adapter capability preflight,
93
- privacy-safe feedback evidence references/relationships, bounded batch-review
94
- evidence graphs, remediation suggestions, and root-level unittest import
95
- support. `browser-audit --check-browser` rejects missing or incompatible
92
+ The current release is `0.7.47`. Project initialization creates a local Maggie
93
+ Memory store, memory contexts default to project scope, and the shared hook
94
+ falls back to the installed CLI if project Python tools are absent. Repeated
95
+ memory initialization preserves existing files and timestamps. Feedback list
96
+ accepts `--project` before or after its subcommand. Generated VPS runners record
97
+ their Maggie version and template fingerprint; deployment preflight warns on
98
+ stale or unmarked runners, with `maggie deployment --check-runner PATH` for
99
+ other locations.
100
+
101
+ The 0.7.46 feedback batch remains included: capability-aware release profiles,
102
+ bounded deployment-readiness retries, verified Google account/target evidence,
103
+ idempotent external-write readback, browser-adapter capability preflight,
104
+ privacy-safe feedback evidence and relationships, and root-level unittest
105
+ import support. `browser-audit --check-browser` rejects missing or incompatible
96
106
  adapters before navigation, including a system `browse`/`xdg-open` opener.
97
107
  It also includes the 0.7.45 one-time user-scope installation for
98
108
  Codex and Claude Code, a bundled workflow runner, and scoped maintenance
@@ -657,8 +667,8 @@ artifact schemas.
657
667
  Recommended upgrade sequence for the current release:
658
668
 
659
669
  ```bash
660
- npx @topy-ai/maggie@0.7.46 update --scope project --project . --force
661
- npx @topy-ai/maggie@0.7.46 cleanup --scope project --project .
670
+ npx @topy-ai/maggie@0.7.47 update --scope project --project . --force
671
+ npx @topy-ai/maggie@0.7.47 cleanup --scope project --project .
662
672
  ```
663
673
 
664
674
  Maintainers should pass npm credentials through the repository helper, never
package/bin/maggie.js CHANGED
@@ -19,6 +19,23 @@ const TEMPLATES_ROOT = join(PACKAGE_ROOT, "bundled-templates");
19
19
  const STATE_DIR = ".maggie";
20
20
  const USER_SCOPE = "user";
21
21
  const PROJECT_SCOPE = "project";
22
+ const PROJECT_MEMORY_FILES = {
23
+ preferences: "preferences.json",
24
+ conventions: "project-conventions.json",
25
+ lessons: "lessons.json",
26
+ errors: "error-patterns.json",
27
+ };
28
+
29
+ function initializeProjectMemory(root) {
30
+ const memoryRoot = join(root, STATE_DIR, "memory");
31
+ mkdirSync(memoryRoot, { recursive: true });
32
+ for (const [kind, filename] of Object.entries(PROJECT_MEMORY_FILES)) {
33
+ const path = join(memoryRoot, filename);
34
+ if (!existsSync(path)) {
35
+ writeFileSync(path, `${JSON.stringify({ schemaVersion: 1, kind, updatedAt: new Date().toISOString(), items: [] }, null, 2)}\n`, { flag: "wx" });
36
+ }
37
+ }
38
+ }
22
39
  function loadSkillNames() {
23
40
  try {
24
41
  return JSON.parse(readFileSync(join(SKILLS_ROOT, "catalog.json"), "utf8")).skills.map((skill) => skill.name);
@@ -488,6 +505,7 @@ function install(args) {
488
505
  if (existsSync(MARKETPLACE_ROOT)) copyIfMissing(MARKETPLACE_ROOT, join(root, "marketplace"));
489
506
  if (existsSync(CONTRACTS_ROOT)) copyIfMissing(CONTRACTS_ROOT, join(root, "contracts"));
490
507
  if (existsSync(join(TEMPLATES_ROOT, "maggiedash"))) copyIfMissing(join(TEMPLATES_ROOT, "maggiedash"), join(root, "templates", "maggiedash"));
508
+ initializeProjectMemory(root);
491
509
  }
492
510
  const manifestRoots = scope === USER_SCOPE ? allUserAgentRoots() : roots;
493
511
  const installedUserSkills = scope === USER_SCOPE ? mergedUserSkillNames(manifestRoots) : [];
@@ -496,7 +514,7 @@ function install(args) {
496
514
  : { scope, version: PACKAGE_VERSION, agents: roots.map((item) => item.slice(root.length + 1)), skills: mergedSkillNames(root, skills), managedFiles: managedFileDigests(root, roots, skills), installed_at: new Date().toISOString() };
497
515
  writeInstallManifest(root, manifest, scope);
498
516
  console.log(scope === USER_SCOPE ? `Maggie installed for this user in ${userStateRoot()}` : `Maggie installed in ${root}`);
499
- console.log(scope === USER_SCOPE ? "Project state remains local; run `maggie doctor --scope user` to verify the user install." : "Run `maggie doctor --project .` before using mutating workflows.");
517
+ console.log(scope === USER_SCOPE ? "Project state remains local; run `maggie doctor --scope user` to verify the user install." : "Project-scoped Maggie Memory is initialized locally; run `maggie doctor --project .` before using mutating workflows.");
500
518
  }
501
519
 
502
520
  function update(args) {
@@ -591,7 +609,7 @@ function workflowCli(name, args) {
591
609
  const root = projectRoot(args);
592
610
  const script = toolScript(name, root);
593
611
  if (!existsSync(script)) throw new Error(`${name} is missing: ${script}`);
594
- const env = name === "maggie_feedback.py" ? { ...process.env, MAGGIE_VERSION: PACKAGE_VERSION } : process.env;
612
+ const env = { ...process.env, MAGGIE_VERSION: PACKAGE_VERSION };
595
613
  const result = spawnSync("python3", [script, ...args], { stdio: "inherit", cwd: root, env });
596
614
  if (result.error) throw result.error;
597
615
  process.exitCode = result.status ?? 1;
@@ -1,23 +1,36 @@
1
1
  # Maggie Memory Hook
2
2
 
3
- Every Maggie skill invocation has an automatic memory lifecycle when the host
4
- project contains `tools/clis/maggie_memory.py`.
3
+ Every Maggie skill invocation uses the current project's Maggie Memory by
4
+ default. Project initialization creates `.maggie/memory/` for that repository.
5
5
 
6
6
  ## Before work
7
7
 
8
- Run:
8
+ Use the project-local adapter when it is present:
9
9
 
10
10
  ```bash
11
11
  python3 tools/clis/maggie_memory.py context --project . --skill <current-skill>
12
12
  ```
13
13
 
14
+ If that adapter is absent, use the installed CLI:
15
+
16
+ ```bash
17
+ maggie memory context --project . --skill <current-skill>
18
+ ```
19
+
14
20
  Load the returned active preferences, conventions, lessons, and matching error
15
21
  history into the current decision context. Surface material rules and verify
16
22
  them against current project evidence. Memory never replaces inspection,
17
23
  approval, bootstrap, or production gates.
18
24
 
19
- If the command is unavailable, report that memory is unavailable and continue
20
- with the normal workflow. Do not invent or silently reconstruct memory.
25
+ The context response reports `status: "uninitialized"` when Maggie Memory is
26
+ available but the project has not initialized its store. In that case, explain
27
+ that `maggie init --scope project` initializes it; do not call memory
28
+ unavailable. Report unavailable only when neither the project adapter nor the
29
+ installed `maggie` CLI can run. Do not invent or silently reconstruct memory.
30
+
31
+ Context includes only active project-scope items for the current repository.
32
+ Include user/workspace entries only after a separate explicit opt-in with
33
+ `--include-shared`.
21
34
 
22
35
  ## After work
23
36
 
@@ -219,6 +219,12 @@ files and the application remain separated (`maggie-deploy` versus
219
219
  Install and review that narrow sudoers policy on the host before executing a
220
220
  runner.
221
221
 
222
+ Generated release runners include their Maggie version and a template
223
+ fingerprint. VPS preflight checks runners under `.maggie/deployment/` and warns
224
+ when they are unmarked or stale; use `--check-runner PATH` for a runner stored
225
+ elsewhere. Regenerate from the reviewed VPS plan, inspect the diff, and replace
226
+ the checked-in runner only after review.
227
+
222
228
  VPS plans include a bounded retention policy: keep two immutable releases,
223
229
  preserve the `current` target and rollback target, and review prune candidates
224
230
  before any operator executes cleanup. Generate a read-only candidate report:
@@ -30,7 +30,9 @@ maggie feedback --project . collect \
30
30
  maggie feedback preview .maggie/feedback/<feedback-id>.json --format markdown
31
31
  ```
32
32
 
33
- The draft is written to `.maggie/feedback/`. It contains the Maggie version,
33
+ The draft is written to `.maggie/feedback/`. List local drafts with
34
+ `maggie feedback list --project .`; `--project` works before or after the
35
+ subcommand. A draft contains the Maggie version,
34
36
  skill, run ID, phase, error fingerprint, expected/actual result, reproduction
35
37
  steps, resolution, validation, and metadata-only screenshot references.
36
38
 
@@ -29,7 +29,7 @@ truth, content state, or audit log.
29
29
 
30
30
  ## Workflow
31
31
 
32
- 1. At the start of a relevant workflow, run `maggie memory context --project . --skill <skill>` and load only matching active items.
32
+ 1. At the start of a relevant workflow, run `maggie memory context --project . --skill <skill>` and load only matching active project-scope items. Use `--include-shared` only after explicit opt-in.
33
33
  2. Show material preferences, conventions, and prevention lessons before making a decision.
34
34
  3. When the user gives a durable preference or confirms a repaired pitfall, save it explicitly as `candidate` or `active` with scope and evidence.
35
35
  4. Record every meaningful failure with `record-error`; include a stable fingerprint, resolution, and validation result when fixed.
@@ -45,6 +45,15 @@ truth, content state, or audit log.
45
45
  preserving project scope. Expired active items remain inspectable through
46
46
  explicit status listing but are excluded from normal context.
47
47
 
48
+ Project-scope `maggie init` creates the repository's `.maggie/memory/` store.
49
+ Repeated initialization preserves existing memory files without rewriting
50
+ their contents or timestamps. A context response distinguishes a ready store
51
+ from an uninitialized project; the shared memory hook falls back to the
52
+ installed CLI when local Python tools are absent.
53
+
54
+ Project `context`, `search`, and `list` read only project-scope items by
55
+ default. `--include-shared` explicitly opts into user/workspace-scoped entries.
56
+
48
57
  ```bash
49
58
  maggie memory init --project .
50
59
  maggie memory context --project . --skill maggie-clone
@@ -3,7 +3,10 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
+ import hashlib
7
+ import inspect
6
8
  import json
9
+ import os
7
10
  import re
8
11
  import shlex
9
12
  import subprocess
@@ -352,6 +355,9 @@ def preflight(
352
355
  result["delegation"] = delegation
353
356
  result["vps"] = {"runtime":"Node.js standalone","reverseProxy":"Nginx","processManager":"systemd","releaseLayout":"/var/www/<site>/releases/<release> + current symlink","dns":"Cloudflare DNS or registrar DNS; verify apex and www separately","remoteCommands":["npm ci","npm run build","systemctl restart <service>","systemctl is-active <service>","curl -fsS https://<domain>/robots.txt","curl -fsS https://<domain>/sitemap.xml"],"handoverFields":["host","sshUser","domain","service","release","previousRelease","nginxConfig","migrationVersion","verification","rollback"]}
354
357
  result["next_action"] = "review VPS host, SSH user, domain/DNS, systemd service, Nginx config, release path, migrations, and rollback owner before execute"
358
+ runners = sorted(deployment_dir.rglob("release-runner.sh")) if deployment_dir.exists() else []
359
+ result["release_runners"] = [release_runner_status(path) for path in runners[:25]]
360
+ result["warnings"] = [f"generated release runner is stale or unmarked: {item['path']}" for item in result["release_runners"] if item["status"] == "stale"]
355
361
  result["blocking_checks"] = ["package_manifest", "build_command", "env_example", "node_runtime_declared", "start_command_or_runtime", "stateless_or_migration_plan", "release_layout_declared", "rollback_command_declared", "release_retention_policy", "data_release_checkpoint", "release_plan_artifact", "systemd_artifact", "nginx_artifact", "deployment_artifacts_secret_free", "rollback_evidence"]
356
362
  if require_deployer:
357
363
  result["blocking_checks"].append("deployer_delegation")
@@ -549,15 +555,51 @@ WantedBy=multi-user.target
549
555
  (output_dir / f"{domain}.conf").write_text(nginx, encoding="utf-8")
550
556
 
551
557
 
558
+ def release_runner_generator() -> dict[str, str]:
559
+ version = os.environ.get("MAGGIE_VERSION", "").strip()
560
+ if not version:
561
+ version_file = Path(__file__).resolve().parents[2] / "VERSION"
562
+ try:
563
+ version = version_file.read_text(encoding="utf-8").strip()
564
+ except OSError:
565
+ version = "unknown"
566
+ source = inspect.getsource(release_runner).encode("utf-8")
567
+ return {"version": version, "fingerprint": hashlib.sha256(source).hexdigest()[:16]}
568
+
569
+
570
+ def release_runner_status(path: Path) -> dict:
571
+ try:
572
+ content = path.read_text(encoding="utf-8", errors="replace")
573
+ except OSError as error:
574
+ return {"path": str(path), "status": "stale", "reason": f"runner cannot be read: {error}", "refresh": "Regenerate from the reviewed VPS plan and inspect the diff."}
575
+ match = re.search(r"^# Maggie release runner: generator=([^ ]+) template=([a-f0-9]{16})$", content, re.MULTILINE)
576
+ current = release_runner_generator()
577
+ if not match:
578
+ return {"path": str(path), "status": "stale", "reason": "runner has no generator version/fingerprint", "currentGenerator": current, "refresh": "Regenerate from the reviewed VPS plan and inspect the diff before replacing this file."}
579
+ generated = {"version": match.group(1), "fingerprint": match.group(2)}
580
+ stale = generated != current
581
+ return {
582
+ "path": str(path),
583
+ "status": "stale" if stale else "current",
584
+ "generatedBy": generated,
585
+ "currentGenerator": current,
586
+ "reason": "runner generator version or template fingerprint has changed" if stale else "runner matches the current generator",
587
+ "refresh": "Regenerate from the reviewed VPS plan and inspect the diff before replacing this file." if stale else "",
588
+ }
589
+
590
+
552
591
  def release_runner(plan: dict) -> str:
553
592
  """Render the ordered, reviewable VPS runner for one release."""
554
593
  root = plan["release_root"]
555
594
  service = plan["service"]
556
595
  domain = plan["domain"]
557
596
  current = plan["current_release"]
597
+ generator = release_runner_generator()
558
598
  return f'''#!/usr/bin/env bash
559
599
  set -euo pipefail
560
600
 
601
+ # Maggie release runner: generator={generator["version"]} template={generator["fingerprint"]}
602
+
561
603
  # Migration precedes the symlink flip; later releases carry agent state.
562
604
  : "${{RELEASE_ID:?set RELEASE_ID to an immutable release name}}"
563
605
  RELEASE_KIND="${{RELEASE_KIND:-upgrade}}"
@@ -653,6 +695,7 @@ def main() -> int:
653
695
  parser.add_argument("--deployer-evidence", help="delegation evidence JSON; defaults to .maggie/deployment/deployer-delegation.json")
654
696
  parser.add_argument("--plan-dir", help="directory for generated VPS artifacts")
655
697
  parser.add_argument("--runner-output", help="write a reviewable ordered VPS release runner")
698
+ parser.add_argument("--check-runner", help="read-only check of a generated runner's Maggie version and template fingerprint")
656
699
  parser.add_argument("--retention-plan", action="store_true", help="create a read-only release prune candidate plan")
657
700
  parser.add_argument("--current-link", help="current symlink for --retention-plan")
658
701
  parser.add_argument("--keep-releases", type=int, default=2)
@@ -665,6 +708,10 @@ def main() -> int:
665
708
  parser.add_argument("--has-auth", action="store_true")
666
709
  args = parser.parse_args()
667
710
  try:
711
+ if args.check_runner:
712
+ report = release_runner_status(Path(args.check_runner).expanduser().resolve())
713
+ print(json.dumps(report, indent=2, ensure_ascii=False))
714
+ return 0
668
715
  if args.verify_deployer:
669
716
  if not args.deployer_host:
670
717
  parser.error("--deployer-host is required with --verify-deployer")
@@ -575,6 +575,7 @@ def main() -> int:
575
575
  submit_parser.add_argument("--endpoint", default=DEFAULT_ENDPOINT)
576
576
  submit_parser.add_argument("--confirm", action="store_true")
577
577
  list_parser = sub.add_parser("list")
578
+ list_parser.add_argument("--project", default=argparse.SUPPRESS)
578
579
  args = parser.parse_args()
579
580
  try:
580
581
  if args.command == "collect": return collect(args)
@@ -35,12 +35,15 @@ def main() -> int:
35
35
  listing.add_argument("--status", choices=sorted(memory.STATUSES), default=None)
36
36
  listing.add_argument("--skill", default="")
37
37
  listing.add_argument("--query", default="")
38
+ listing.add_argument("--include-shared", action="store_true", help="explicitly include user/workspace memory entries")
38
39
  search = add_project(sub.add_parser("search"))
39
40
  search.add_argument("query")
40
41
  search.add_argument("--skill", default="")
42
+ search.add_argument("--include-shared", action="store_true", help="explicitly include user/workspace memory entries")
41
43
  context = add_project(sub.add_parser("context"))
42
44
  context.add_argument("--skill", default="")
43
45
  context.add_argument("--query", default="")
46
+ context.add_argument("--include-shared", action="store_true", help="explicitly include user/workspace memory entries")
44
47
  add = add_project(sub.add_parser("add"))
45
48
  add.add_argument("kind", choices=("preferences", "conventions", "lessons"))
46
49
  add.add_argument("--scope", choices=sorted(memory.SCOPES), required=True)
@@ -85,17 +88,21 @@ def main() -> int:
85
88
  output(memory.transition(args.project, args.kind, args.item_id, args.status))
86
89
  elif args.command in {"context", "search", "list"}:
87
90
  query = args.query if args.command == "search" else getattr(args, "query", "")
88
- result = memory.relevant(args.project, skill=getattr(args, "skill", ""), query=query, status=getattr(args, "status", None))
91
+ result = memory.relevant(args.project, skill=getattr(args, "skill", ""), query=query, status=getattr(args, "status", None), include_shared=getattr(args, "include_shared", False))
89
92
  if args.command == "list" and args.kind:
90
93
  result = {args.kind: result[args.kind]}
91
94
  if args.command == "list" and args.status:
92
95
  result = {kind: [item for item in items if item.get("status") == args.status] for kind, items in result.items()}
93
- output(result)
96
+ if args.command == "context":
97
+ ready = memory.initialized(args.project)
98
+ output({"status": "ready" if ready else "uninitialized", "initialized": ready, **result})
99
+ else:
100
+ output(result)
94
101
  elif args.command == "export":
95
102
  payload = {kind: memory.read(args.project, kind) for kind in memory.KINDS if kind != "errors"}
96
103
  if args.public_safe:
97
104
  for value in payload.values():
98
- value["items"] = [item for item in value["items"] if item.get("scope") != "user" and item.get("status") == "active"]
105
+ value["items"] = [item for item in value["items"] if item.get("scope") == "project" and item.get("status") == "active"]
99
106
  destination = Path(args.output)
100
107
  destination.parent.mkdir(parents=True, exist_ok=True)
101
108
  destination.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
@@ -73,7 +73,16 @@ def write(project: str | Path, kind: str, payload: dict[str, Any]) -> Path:
73
73
 
74
74
 
75
75
  def init(project: str | Path) -> dict[str, str]:
76
- return {kind: str(write(project, kind, read(project, kind))) for kind in KINDS}
76
+ paths: dict[str, str] = {}
77
+ for kind in KINDS:
78
+ path = memory_path(project, kind)
79
+ if path.exists():
80
+ # Validate existing state, but keep its bytes and mtime unchanged.
81
+ read(project, kind)
82
+ else:
83
+ write(project, kind, empty(kind))
84
+ paths[kind] = str(path)
85
+ return paths
77
86
 
78
87
 
79
88
  def _text(value: Any, field: str, required: bool = False) -> str:
@@ -176,11 +185,13 @@ def _active(item: dict[str, Any], project_id: str = "") -> bool:
176
185
  return not expires or expires > now()
177
186
 
178
187
 
179
- def relevant(project: str | Path, *, skill: str = "", query: str = "", project_id: str = "", status: str | None = None) -> dict[str, list[dict[str, Any]]]:
188
+ def relevant(project: str | Path, *, skill: str = "", query: str = "", project_id: str = "", status: str | None = None, include_shared: bool = False) -> dict[str, list[dict[str, Any]]]:
180
189
  terms = set(re.findall(r"[a-z0-9_-]+", f"{skill} {query}".lower()))
181
190
  result: dict[str, list[dict[str, Any]]] = {kind: [] for kind in KINDS}
182
191
  for kind in KINDS:
183
192
  for item in read(project, kind)["items"]:
193
+ if not include_shared and item.get("scope") != "project":
194
+ continue
184
195
  if status is not None:
185
196
  if item.get("status") != status:
186
197
  continue
@@ -194,6 +205,10 @@ def relevant(project: str | Path, *, skill: str = "", query: str = "", project_i
194
205
  return result
195
206
 
196
207
 
208
+ def initialized(project: str | Path) -> bool:
209
+ return all(memory_path(project, kind).is_file() for kind in KINDS)
210
+
211
+
197
212
  def transition(project: str | Path, kind: str, item_id: str, status: str) -> dict[str, Any]:
198
213
  if status not in STATUSES:
199
214
  raise ValueError(f"invalid status: {status}")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topy-ai/maggie",
3
- "version": "0.7.46",
3
+ "version": "0.7.47",
4
4
  "description": "Install and manage Maggie Skills for AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,23 +1,36 @@
1
1
  # Maggie Memory Hook
2
2
 
3
- Every Maggie skill invocation has an automatic memory lifecycle when the host
4
- project contains `tools/clis/maggie_memory.py`.
3
+ Every Maggie skill invocation uses the current project's Maggie Memory by
4
+ default. Project initialization creates `.maggie/memory/` for that repository.
5
5
 
6
6
  ## Before work
7
7
 
8
- Run:
8
+ Use the project-local adapter when it is present:
9
9
 
10
10
  ```bash
11
11
  python3 tools/clis/maggie_memory.py context --project . --skill <current-skill>
12
12
  ```
13
13
 
14
+ If that adapter is absent, use the installed CLI:
15
+
16
+ ```bash
17
+ maggie memory context --project . --skill <current-skill>
18
+ ```
19
+
14
20
  Load the returned active preferences, conventions, lessons, and matching error
15
21
  history into the current decision context. Surface material rules and verify
16
22
  them against current project evidence. Memory never replaces inspection,
17
23
  approval, bootstrap, or production gates.
18
24
 
19
- If the command is unavailable, report that memory is unavailable and continue
20
- with the normal workflow. Do not invent or silently reconstruct memory.
25
+ The context response reports `status: "uninitialized"` when Maggie Memory is
26
+ available but the project has not initialized its store. In that case, explain
27
+ that `maggie init --scope project` initializes it; do not call memory
28
+ unavailable. Report unavailable only when neither the project adapter nor the
29
+ installed `maggie` CLI can run. Do not invent or silently reconstruct memory.
30
+
31
+ Context includes only active project-scope items for the current repository.
32
+ Include user/workspace entries only after a separate explicit opt-in with
33
+ `--include-shared`.
21
34
 
22
35
  ## After work
23
36