@ijfw/memory-server 1.3.0 → 1.4.1

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.
Files changed (68) hide show
  1. package/README.md +67 -0
  2. package/fixtures/team/book.json +47 -0
  3. package/fixtures/team/business.json +47 -0
  4. package/fixtures/team/content.json +47 -0
  5. package/fixtures/team/design.json +47 -0
  6. package/fixtures/team/mixed.json +59 -0
  7. package/fixtures/team/research.json +47 -0
  8. package/fixtures/team/software.json +47 -0
  9. package/package.json +1 -9
  10. package/src/.registry-meta-key.pem +3 -0
  11. package/src/active-extension-writer.js +142 -0
  12. package/src/blackboard.js +360 -0
  13. package/src/cli-run.js +91 -0
  14. package/src/codex-agents.js +177 -0
  15. package/src/compute/extract.js +3 -0
  16. package/src/compute/fts5.js +4 -4
  17. package/src/compute/graph-lock.js +0 -2
  18. package/src/compute/migrations/003-tier-semantic.js +3 -3
  19. package/src/compute/runner.js +44 -15
  20. package/src/compute/schema.sql +1 -1
  21. package/src/cross-orchestrator-cli.js +974 -13
  22. package/src/cross-orchestrator.js +9 -1
  23. package/src/dashboard-client.html +353 -1
  24. package/src/dashboard-server.js +318 -2
  25. package/src/design-intelligence.js +721 -0
  26. package/src/dispatch/colon-syntax.js +31 -3
  27. package/src/dispatch/domain-manifest.js +251 -0
  28. package/src/dispatch/extension.js +637 -0
  29. package/src/dispatch/override.js +221 -0
  30. package/src/dispatch-planner.js +1 -0
  31. package/src/dream/runner.mjs +3 -3
  32. package/src/extension-installer.js +1269 -0
  33. package/src/extension-manifest-schema.js +301 -0
  34. package/src/extension-permission-check.mjs +79 -0
  35. package/src/extension-registry.js +619 -0
  36. package/src/extension-signer.js +905 -0
  37. package/src/gate-result-formatter.js +95 -0
  38. package/src/gate-result-schema.js +274 -0
  39. package/src/gate-result.js +195 -0
  40. package/src/intent-router.js +2 -0
  41. package/src/lib/npm-view.js +1 -0
  42. package/src/memory/fts5.js +3 -3
  43. package/src/memory/migrations/002-tier-semantic.js +2 -2
  44. package/src/memory/staleness.js +1 -1
  45. package/src/memory/tier-promotion.js +6 -6
  46. package/src/memory/tokenize.js +1 -1
  47. package/src/memory-feedback.js +372 -0
  48. package/src/override-manifest-schema.js +146 -0
  49. package/src/override-resolver.js +699 -0
  50. package/src/override-use-registry.js +307 -0
  51. package/src/overrides/presets/academic.md +101 -0
  52. package/src/overrides/presets/book.md +87 -0
  53. package/src/overrides/presets/campaign.md +95 -0
  54. package/src/overrides/presets/screenplay.md +99 -0
  55. package/src/recovery/checkpoint.js +191 -0
  56. package/src/redactor.js +2 -0
  57. package/src/runtime-mediator.js +207 -0
  58. package/src/sandbox.js +17 -3
  59. package/src/server.js +94 -2
  60. package/src/swarm/dispatch-prompt.js +154 -0
  61. package/src/swarm/planner.js +399 -0
  62. package/src/swarm/review.js +136 -0
  63. package/src/swarm/worktree.js +239 -0
  64. package/src/team/generator.js +119 -0
  65. package/src/team/schemas.js +341 -0
  66. package/src/trident/dispatch.js +47 -0
  67. package/src/update-check.js +1 -1
  68. package/src/vectors.js +7 -8
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # @ijfw/memory-server
2
+
3
+ IJFW MCP memory server — the runtime backend that powers memory, metrics,
4
+ update checks, and the extension sandbox for all supported AI coding agents.
5
+
6
+ ## Install
7
+
8
+ This package is installed automatically by `@ijfw/install`. You generally
9
+ do not need to install it manually.
10
+
11
+ ```bash
12
+ npm install -g @ijfw/memory-server
13
+ ```
14
+
15
+ ## Extension CLI
16
+
17
+ IJFW ships a full extension system for installing and sandboxing third-party skills.
18
+
19
+ ```bash
20
+ # Publisher key management
21
+ ijfw extension keygen <author> # Generate an Ed25519 publisher keypair
22
+ ijfw extension trust <keyId> <publicKey> # Add a publisher to your trusted store
23
+ ijfw extension trust-registry [<url>] # Pull + apply the hosted publisher registry
24
+ ijfw extension untrust <keyId> # Remove a publisher from your trusted store
25
+ ijfw extension trusted # List all trusted publishers
26
+
27
+ # Extension lifecycle
28
+ ijfw extension add <source> [flags] # Install an extension (npm name, local path, or https git URL)
29
+ --allow-unsigned # Accept extensions with no signature
30
+ --accept-untrusted # Accept extensions signed by an untrusted publisher (prompts on TTY)
31
+ --activate # Auto-activate after install
32
+ ijfw extension activate <name> # Activate an installed extension (enforces declared permissions)
33
+ ijfw extension deactivate # Deactivate the current extension
34
+
35
+ # Admin / registry maintainer (rare)
36
+ ijfw extension rotate-keys <oldKeyId> <newKeyId> # Produce a signed rotation token
37
+ ijfw extension keygen-meta <author> # Generate the registry meta-keypair
38
+ ijfw extension sign-registry <path> # Sign a registry JSON file in place
39
+ ijfw extension verify-registry <path> # Verify a registry JSON signature
40
+ ijfw extension registry-status # Show registry cache age + signature status
41
+ ```
42
+
43
+ The rotation flow and registry maintainer docs live in `docs/REGISTRY-MAINTAINER.md`.
44
+
45
+ ## MCP Tools
46
+
47
+ | Tool | Description |
48
+ |------|-------------|
49
+ | `ijfw_memory_store` | Store a memory entry |
50
+ | `ijfw_memory_recall` | Recall memory entries |
51
+ | `ijfw_memory_search` | Full-text search over memories |
52
+ | `ijfw_memory_prelude` | Load project context at session start |
53
+ | `ijfw_cross_project_search` | Search memories across projects |
54
+ | `ijfw_metrics` | Read cost + usage metrics |
55
+ | `ijfw_update_check` | Check for IJFW updates |
56
+ | `ijfw_update_apply` | Apply a pending IJFW update |
57
+ | `ijfw_prompt_check` | Validate a prompt against IJFW rules |
58
+ | `ijfw_run` | Run a sandboxed IJFW command |
59
+
60
+ ## Build (contributors)
61
+
62
+ ```bash
63
+ cd mcp-server
64
+ npm install
65
+ npm test
66
+ node --experimental-sqlite --test test-*.js
67
+ ```
@@ -0,0 +1,47 @@
1
+ {
2
+ "charter": {
3
+ "schema_version": "team-charter/v1",
4
+ "team_name": "book-development-team",
5
+ "project_archetypes": ["book"],
6
+ "roles": [
7
+ {
8
+ "name": "chapter-writer",
9
+ "role_type": "book",
10
+ "model": "sonnet",
11
+ "effort": "medium",
12
+ "phase_scope": ["outline", "draft", "revise"],
13
+ "owns": [{"artifact_type": "chapter", "paths": ["manuscript/chapters/**"]}],
14
+ "reviews": [{"artifact_type": "outline", "criteria": ["structure", "continuity"]}],
15
+ "handoff": {"format": "markdown", "required_sections": ["chapter_summary", "open_threads", "changed_artifacts"]},
16
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": ["manuscript/chapters/**"]}
17
+ },
18
+ {
19
+ "name": "continuity-editor",
20
+ "role_type": "review",
21
+ "model": "sonnet",
22
+ "effort": "medium",
23
+ "phase_scope": ["review", "revise"],
24
+ "owns": [{"artifact_type": "continuity_notes", "paths": ["manuscript/notes/**"]}],
25
+ "reviews": [{"artifact_type": "chapter", "criteria": ["timeline", "voice", "consistency"]}],
26
+ "handoff": {"format": "markdown", "required_sections": ["findings", "continuity_risks"]},
27
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
28
+ }
29
+ ]
30
+ },
31
+ "workflow": {
32
+ "schema_version": "team-workflow/v1",
33
+ "project_archetypes": ["book"],
34
+ "artifacts": [
35
+ {"id": "chapter-draft", "type": "chapter", "paths": ["manuscript/chapters/ch01.md"], "owner": "chapter-writer", "reviewers": ["continuity-editor"], "depends_on": [], "verification": ["continuity review"]},
36
+ {"id": "continuity-notes", "type": "continuity_notes", "paths": ["manuscript/notes/continuity.md"], "owner": "continuity-editor", "reviewers": ["chapter-writer"], "depends_on": ["chapter-draft"], "verification": ["open thread audit"]}
37
+ ],
38
+ "waves": [
39
+ {"id": "w1", "mode": "parallel", "artifact_ids": ["chapter-draft"]},
40
+ {"id": "w2", "mode": "review", "artifact_ids": ["continuity-notes"]}
41
+ ]
42
+ },
43
+ "blackboard": {
44
+ "tasks": [{"id": "task-chapter", "title": "Draft chapter one", "status": "ready", "artifact_ids": ["chapter-draft"], "owner": "chapter-writer"}],
45
+ "claims": [{"id": "claim-chapter", "artifact_id": "chapter-draft", "agent": "chapter-writer", "status": "active", "paths": ["manuscript/chapters/ch01.md"]}]
46
+ }
47
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "charter": {
3
+ "schema_version": "team-charter/v1",
4
+ "team_name": "strategy-operations-team",
5
+ "project_archetypes": ["business"],
6
+ "roles": [
7
+ {
8
+ "name": "strategy-lead",
9
+ "role_type": "business",
10
+ "model": "sonnet",
11
+ "effort": "medium",
12
+ "phase_scope": ["diagnose", "plan", "decide"],
13
+ "owns": [{"artifact_type": "strategy_doc", "paths": ["strategy/**"]}],
14
+ "reviews": [{"artifact_type": "risk_register", "criteria": ["assumptions", "decision-quality"]}],
15
+ "handoff": {"format": "markdown", "required_sections": ["recommendation", "assumptions", "tradeoffs"]},
16
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
17
+ },
18
+ {
19
+ "name": "risk-reviewer",
20
+ "role_type": "review",
21
+ "model": "sonnet",
22
+ "effort": "medium",
23
+ "phase_scope": ["review", "verify"],
24
+ "owns": [{"artifact_type": "risk_register", "paths": ["strategy/risks/**"]}],
25
+ "reviews": [{"artifact_type": "strategy_doc", "criteria": ["feasibility", "downside", "metrics"]}],
26
+ "handoff": {"format": "markdown", "required_sections": ["risks", "mitigations", "decision_points"]},
27
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
28
+ }
29
+ ]
30
+ },
31
+ "workflow": {
32
+ "schema_version": "team-workflow/v1",
33
+ "project_archetypes": ["business"],
34
+ "artifacts": [
35
+ {"id": "operating-plan", "type": "strategy_doc", "paths": ["strategy/operating-plan.md"], "owner": "strategy-lead", "reviewers": ["risk-reviewer"], "depends_on": [], "verification": ["decision review"]},
36
+ {"id": "risk-register", "type": "risk_register", "paths": ["strategy/risks/register.md"], "owner": "risk-reviewer", "reviewers": ["strategy-lead"], "depends_on": ["operating-plan"], "verification": ["assumption audit"]}
37
+ ],
38
+ "waves": [
39
+ {"id": "w1", "mode": "sequential", "artifact_ids": ["operating-plan"]},
40
+ {"id": "w2", "mode": "review", "artifact_ids": ["risk-register"]}
41
+ ]
42
+ },
43
+ "blackboard": {
44
+ "tasks": [{"id": "task-plan", "title": "Draft operating plan", "status": "ready", "artifact_ids": ["operating-plan"], "owner": "strategy-lead"}],
45
+ "claims": [{"id": "claim-plan", "artifact_id": "operating-plan", "agent": "strategy-lead", "status": "active", "paths": ["strategy/operating-plan.md"]}]
46
+ }
47
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "charter": {
3
+ "schema_version": "team-charter/v1",
4
+ "team_name": "content-production-team",
5
+ "project_archetypes": ["content"],
6
+ "roles": [
7
+ {
8
+ "name": "content-strategist",
9
+ "role_type": "content",
10
+ "model": "sonnet",
11
+ "effort": "medium",
12
+ "phase_scope": ["plan", "execute"],
13
+ "owns": [{"artifact_type": "brief", "paths": ["content/briefs/**"]}],
14
+ "reviews": [{"artifact_type": "article", "criteria": ["positioning", "brand-voice"]}],
15
+ "handoff": {"format": "markdown", "required_sections": ["message", "audience", "risks"]},
16
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
17
+ },
18
+ {
19
+ "name": "editor",
20
+ "role_type": "review",
21
+ "model": "sonnet",
22
+ "effort": "medium",
23
+ "phase_scope": ["review", "verify"],
24
+ "owns": [{"artifact_type": "article", "paths": ["content/posts/**"]}],
25
+ "reviews": [{"artifact_type": "brief", "criteria": ["clarity", "seo", "voice"]}],
26
+ "handoff": {"format": "markdown", "required_sections": ["edits", "findings", "publication_status"]},
27
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": ["content/posts/**"]}
28
+ }
29
+ ]
30
+ },
31
+ "workflow": {
32
+ "schema_version": "team-workflow/v1",
33
+ "project_archetypes": ["content"],
34
+ "artifacts": [
35
+ {"id": "campaign-brief", "type": "brief", "paths": ["content/briefs/campaign.md"], "owner": "content-strategist", "reviewers": ["editor"], "depends_on": [], "verification": ["brand voice review"]},
36
+ {"id": "article-draft", "type": "article", "paths": ["content/posts/*.mdx"], "owner": "editor", "reviewers": ["content-strategist"], "depends_on": ["campaign-brief"], "verification": ["seo checklist"]}
37
+ ],
38
+ "waves": [
39
+ {"id": "w1", "mode": "sequential", "artifact_ids": ["campaign-brief"]},
40
+ {"id": "w2", "mode": "review", "artifact_ids": ["article-draft"]}
41
+ ]
42
+ },
43
+ "blackboard": {
44
+ "tasks": [{"id": "task-brief", "title": "Prepare campaign brief", "status": "ready", "artifact_ids": ["campaign-brief"], "owner": "content-strategist"}],
45
+ "claims": [{"id": "claim-brief", "artifact_id": "campaign-brief", "agent": "content-strategist", "status": "active", "paths": ["content/briefs/campaign.md"]}]
46
+ }
47
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "charter": {
3
+ "schema_version": "team-charter/v1",
4
+ "team_name": "design-studio-team",
5
+ "project_archetypes": ["design"],
6
+ "roles": [
7
+ {
8
+ "name": "product-designer",
9
+ "role_type": "design",
10
+ "model": "sonnet",
11
+ "effort": "medium",
12
+ "phase_scope": ["discovery", "shape", "execute"],
13
+ "owns": [{"artifact_type": "screen", "paths": ["design/screens/**"]}],
14
+ "reviews": [{"artifact_type": "prototype", "criteria": ["usability", "accessibility"]}],
15
+ "handoff": {"format": "markdown", "required_sections": ["rationale", "changed_artifacts", "open_questions"]},
16
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": ["design/tokens/**"]}
17
+ },
18
+ {
19
+ "name": "visual-qa",
20
+ "role_type": "review",
21
+ "model": "sonnet",
22
+ "effort": "low",
23
+ "phase_scope": ["review", "verify"],
24
+ "owns": [{"artifact_type": "audit", "paths": ["design/reviews/**"]}],
25
+ "reviews": [{"artifact_type": "screen", "criteria": ["layout", "contrast", "responsive-fit"]}],
26
+ "handoff": {"format": "markdown", "required_sections": ["findings", "severity", "recommendations"]},
27
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
28
+ }
29
+ ]
30
+ },
31
+ "workflow": {
32
+ "schema_version": "team-workflow/v1",
33
+ "project_archetypes": ["design"],
34
+ "artifacts": [
35
+ {"id": "screen-system", "type": "screen", "paths": ["design/screens/**"], "owner": "product-designer", "reviewers": ["visual-qa"], "depends_on": [], "verification": ["visual audit"]},
36
+ {"id": "design-review", "type": "audit", "paths": ["design/reviews/**"], "owner": "visual-qa", "reviewers": ["product-designer"], "depends_on": ["screen-system"], "verification": ["accessibility checklist"]}
37
+ ],
38
+ "waves": [
39
+ {"id": "w1", "mode": "parallel", "artifact_ids": ["screen-system"]},
40
+ {"id": "w2", "mode": "review", "artifact_ids": ["design-review"]}
41
+ ]
42
+ },
43
+ "blackboard": {
44
+ "tasks": [{"id": "task-screens", "title": "Draft core screens", "status": "ready", "artifact_ids": ["screen-system"], "owner": "product-designer"}],
45
+ "claims": [{"id": "claim-screens", "artifact_id": "screen-system", "agent": "product-designer", "status": "active", "paths": ["design/screens/**"]}]
46
+ }
47
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "charter": {
3
+ "schema_version": "team-charter/v1",
4
+ "team_name": "mixed-launch-team",
5
+ "project_archetypes": ["software", "design", "content", "mixed"],
6
+ "roles": [
7
+ {
8
+ "name": "app-engineer",
9
+ "role_type": "software",
10
+ "model": "sonnet",
11
+ "effort": "medium",
12
+ "phase_scope": ["execute", "verify"],
13
+ "owns": [{"artifact_type": "app", "paths": ["app/**"]}],
14
+ "reviews": [{"artifact_type": "screen", "criteria": ["integration", "accessibility"]}],
15
+ "handoff": {"format": "markdown", "required_sections": ["changed_artifacts", "tests", "risks"]},
16
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": ["app/routes/**"]}
17
+ },
18
+ {
19
+ "name": "ux-designer",
20
+ "role_type": "design",
21
+ "model": "sonnet",
22
+ "effort": "medium",
23
+ "phase_scope": ["shape", "review"],
24
+ "owns": [{"artifact_type": "screen", "paths": ["design/**"]}],
25
+ "reviews": [{"artifact_type": "app", "criteria": ["usability", "visual-fit"]}],
26
+ "handoff": {"format": "markdown", "required_sections": ["rationale", "screens", "review_notes"]},
27
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": ["design/tokens/**"]}
28
+ },
29
+ {
30
+ "name": "launch-editor",
31
+ "role_type": "content",
32
+ "model": "sonnet",
33
+ "effort": "low",
34
+ "phase_scope": ["execute", "review"],
35
+ "owns": [{"artifact_type": "launch_copy", "paths": ["content/launch/**"]}],
36
+ "reviews": [{"artifact_type": "screen", "criteria": ["message-match", "voice"]}],
37
+ "handoff": {"format": "markdown", "required_sections": ["copy_changes", "voice_notes"]},
38
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
39
+ }
40
+ ]
41
+ },
42
+ "workflow": {
43
+ "schema_version": "team-workflow/v1",
44
+ "project_archetypes": ["software", "design", "content", "mixed"],
45
+ "artifacts": [
46
+ {"id": "app-shell", "type": "app", "paths": ["app/**"], "owner": "app-engineer", "reviewers": ["ux-designer"], "depends_on": [], "verification": ["npm test"]},
47
+ {"id": "product-screens", "type": "screen", "paths": ["design/**"], "owner": "ux-designer", "reviewers": ["app-engineer", "launch-editor"], "depends_on": [], "verification": ["visual audit"]},
48
+ {"id": "launch-copy", "type": "launch_copy", "paths": ["content/launch/**"], "owner": "launch-editor", "reviewers": ["ux-designer"], "depends_on": ["product-screens"], "verification": ["brand voice review"]}
49
+ ],
50
+ "waves": [
51
+ {"id": "w1", "mode": "parallel", "artifact_ids": ["app-shell", "product-screens"]},
52
+ {"id": "w2", "mode": "review", "artifact_ids": ["launch-copy"]}
53
+ ]
54
+ },
55
+ "blackboard": {
56
+ "tasks": [{"id": "task-launch", "title": "Coordinate launch surface", "status": "ready", "artifact_ids": ["app-shell", "product-screens"], "owner": "app-engineer"}],
57
+ "claims": [{"id": "claim-copy", "artifact_id": "launch-copy", "agent": "launch-editor", "status": "active", "paths": ["content/launch/**"]}]
58
+ }
59
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "charter": {
3
+ "schema_version": "team-charter/v1",
4
+ "team_name": "research-synthesis-team",
5
+ "project_archetypes": ["research"],
6
+ "roles": [
7
+ {
8
+ "name": "research-lead",
9
+ "role_type": "research",
10
+ "model": "sonnet",
11
+ "effort": "high",
12
+ "phase_scope": ["question", "collect", "synthesize"],
13
+ "owns": [{"artifact_type": "evidence_table", "paths": ["research/evidence/**"]}],
14
+ "reviews": [{"artifact_type": "synthesis", "criteria": ["method", "source-quality"]}],
15
+ "handoff": {"format": "markdown", "required_sections": ["sources", "findings", "confidence"]},
16
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
17
+ },
18
+ {
19
+ "name": "method-reviewer",
20
+ "role_type": "review",
21
+ "model": "sonnet",
22
+ "effort": "medium",
23
+ "phase_scope": ["review", "verify"],
24
+ "owns": [{"artifact_type": "method_audit", "paths": ["research/reviews/**"]}],
25
+ "reviews": [{"artifact_type": "evidence_table", "criteria": ["bias", "traceability", "coverage"]}],
26
+ "handoff": {"format": "markdown", "required_sections": ["limitations", "risk", "recommendations"]},
27
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
28
+ }
29
+ ]
30
+ },
31
+ "workflow": {
32
+ "schema_version": "team-workflow/v1",
33
+ "project_archetypes": ["research"],
34
+ "artifacts": [
35
+ {"id": "evidence-table", "type": "evidence_table", "paths": ["research/evidence/table.md"], "owner": "research-lead", "reviewers": ["method-reviewer"], "depends_on": [], "verification": ["source audit"]},
36
+ {"id": "method-audit", "type": "method_audit", "paths": ["research/reviews/method.md"], "owner": "method-reviewer", "reviewers": ["research-lead"], "depends_on": ["evidence-table"], "verification": ["methodology review"]}
37
+ ],
38
+ "waves": [
39
+ {"id": "w1", "mode": "parallel", "artifact_ids": ["evidence-table"]},
40
+ {"id": "w2", "mode": "review", "artifact_ids": ["method-audit"]}
41
+ ]
42
+ },
43
+ "blackboard": {
44
+ "tasks": [{"id": "task-evidence", "title": "Build evidence table", "status": "ready", "artifact_ids": ["evidence-table"], "owner": "research-lead"}],
45
+ "claims": [{"id": "claim-evidence", "artifact_id": "evidence-table", "agent": "research-lead", "status": "active", "paths": ["research/evidence/table.md"]}]
46
+ }
47
+ }
@@ -0,0 +1,47 @@
1
+ {
2
+ "charter": {
3
+ "schema_version": "team-charter/v1",
4
+ "team_name": "software-delivery-team",
5
+ "project_archetypes": ["software"],
6
+ "roles": [
7
+ {
8
+ "name": "implementation-engineer",
9
+ "role_type": "software",
10
+ "model": "sonnet",
11
+ "effort": "medium",
12
+ "phase_scope": ["plan", "execute", "verify"],
13
+ "owns": [{"artifact_type": "module", "paths": ["src/**/*.js"]}],
14
+ "reviews": [{"artifact_type": "test", "criteria": ["coverage", "regression"]}],
15
+ "handoff": {"format": "markdown", "required_sections": ["changed_artifacts", "tests", "risks"]},
16
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": ["src/shared/**"]}
17
+ },
18
+ {
19
+ "name": "test-reviewer",
20
+ "role_type": "qa",
21
+ "model": "sonnet",
22
+ "effort": "low",
23
+ "phase_scope": ["review", "verify"],
24
+ "owns": [{"artifact_type": "test", "paths": ["test/**/*.js"]}],
25
+ "reviews": [{"artifact_type": "module", "criteria": ["behavior", "edge-cases"]}],
26
+ "handoff": {"format": "markdown", "required_sections": ["findings", "verification"]},
27
+ "coordination": {"parallel_safe": true, "claim_required": true, "conflicts_with": []}
28
+ }
29
+ ]
30
+ },
31
+ "workflow": {
32
+ "schema_version": "team-workflow/v1",
33
+ "project_archetypes": ["software"],
34
+ "artifacts": [
35
+ {"id": "runtime-module", "type": "module", "paths": ["src/**/*.js"], "owner": "implementation-engineer", "reviewers": ["test-reviewer"], "depends_on": [], "verification": ["npm test"]},
36
+ {"id": "regression-tests", "type": "test", "paths": ["test/**/*.js"], "owner": "test-reviewer", "reviewers": ["implementation-engineer"], "depends_on": ["runtime-module"], "verification": ["node --test test/**/*.js"]}
37
+ ],
38
+ "waves": [
39
+ {"id": "w1", "mode": "parallel", "artifact_ids": ["runtime-module"]},
40
+ {"id": "w2", "mode": "review", "artifact_ids": ["regression-tests"]}
41
+ ]
42
+ },
43
+ "blackboard": {
44
+ "tasks": [{"id": "task-runtime", "title": "Implement runtime module", "status": "ready", "artifact_ids": ["runtime-module"], "owner": "implementation-engineer", "depends_on": []}],
45
+ "claims": [{"id": "claim-runtime", "artifact_id": "runtime-module", "agent": "implementation-engineer", "status": "active", "paths": ["src/**/*.js"], "claimed_at": "2026-05-15T00:00:00Z"}]
46
+ }
47
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ijfw/memory-server",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
4
4
  "description": "Cross-platform persistent memory server for IJFW. 10 MCP tools (memory + admin/update). Works with 13 MCP-using platforms (Claude Code, Codex, Gemini CLI, Cursor, Windsurf, Copilot, Hermes, Wayland, OpenCode, QwenCode, Cline, KimiCode, OpenClaw) plus Aider via rules-only tier.",
5
5
  "author": "Sean Donahoe",
6
6
  "license": "MIT",
@@ -26,14 +26,6 @@
26
26
  "ajv": "^8.12.0",
27
27
  "ajv-formats": "^3.0.1"
28
28
  },
29
- "optionalDependencies": {
30
- "@xenova/transformers": "^2.17.0"
31
- },
32
- "peerDependenciesMeta": {
33
- "@xenova/transformers": {
34
- "optional": true
35
- }
36
- },
37
29
  "files": [
38
30
  "bin/",
39
31
  "src/",
@@ -0,0 +1,3 @@
1
+ -----BEGIN PUBLIC KEY-----
2
+ MCowBQYDK2VwAyEAL2lCdti0bYiFTGUo/hffy+NiBUBXdbDcdaDmjJS27i0=
3
+ -----END PUBLIC KEY-----
@@ -0,0 +1,142 @@
1
+ /**
2
+ * active-extension-writer.js -- IJFW v1.4.0 W7.1/B2-H-01
3
+ *
4
+ * Writes ~/.ijfw/state/active-extension.json from an extension manifest, and
5
+ * clears it on deactivate. Used by:
6
+ * - `ijfw_run extension:activate <name>` CLI command
7
+ * - installExtension when opts.activate is set
8
+ */
9
+
10
+ import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
11
+ import { join, dirname } from 'node:path';
12
+ import { homedir } from 'node:os';
13
+ import { randomBytes } from 'node:crypto';
14
+
15
+ const STATE_PATH_REL = ['.ijfw', 'state', 'active-extension.json'];
16
+
17
+ function statePath(home) {
18
+ return join(home || homedir(), ...STATE_PATH_REL);
19
+ }
20
+
21
+ /**
22
+ * Write the active-extension state file from a manifest + scope.
23
+ * Validates required fields before write. Atomic write via tmp+rename.
24
+ *
25
+ * @param {{ name: string, permissions: { reads: string[], writes: string[] } }} manifest
26
+ * @param {'project'|'org'|'user'} scope
27
+ * @param {{ homeDir?: string }} [opts]
28
+ * @returns {Promise<{ ok: boolean, path?: string, error?: string }>}
29
+ */
30
+ export async function writeActiveExtension(manifest, scope, opts = {}) {
31
+ if (!manifest || typeof manifest !== 'object') {
32
+ return { ok: false, error: 'manifest must be an object' };
33
+ }
34
+ if (typeof manifest.name !== 'string' || manifest.name.length === 0) {
35
+ return { ok: false, error: 'manifest.name required' };
36
+ }
37
+ if (!['project', 'org', 'user'].includes(scope)) {
38
+ return { ok: false, error: `invalid scope: ${scope}` };
39
+ }
40
+ if (!manifest.permissions || typeof manifest.permissions !== 'object') {
41
+ return { ok: false, error: 'manifest.permissions required' };
42
+ }
43
+ const reads = Array.isArray(manifest.permissions.reads) ? manifest.permissions.reads : [];
44
+ const writes = Array.isArray(manifest.permissions.writes) ? manifest.permissions.writes : [];
45
+ const out = {
46
+ name: manifest.name,
47
+ scope,
48
+ permissions: { reads, writes },
49
+ activated_at: new Date().toISOString(),
50
+ };
51
+ const home = opts && opts.homeDir ? opts.homeDir : (process.env.HOME || homedir());
52
+ const path = statePath(home);
53
+ await mkdir(dirname(path), { recursive: true });
54
+ const tmp = `${path}.tmp.${randomBytes(4).toString('hex')}`;
55
+ await writeFile(tmp, JSON.stringify(out, null, 2) + '\n', 'utf8');
56
+ const { rename } = await import('node:fs/promises');
57
+ await rename(tmp, path);
58
+ return { ok: true, path };
59
+ }
60
+
61
+ /**
62
+ * Clear the active-extension state file. Idempotent -- succeeds if file is absent.
63
+ *
64
+ * @param {{ homeDir?: string }} [opts]
65
+ * @returns {Promise<{ ok: boolean, removed: boolean }>}
66
+ */
67
+ export async function clearActiveExtension(opts = {}) {
68
+ const home = opts && opts.homeDir ? opts.homeDir : (process.env.HOME || homedir());
69
+ try {
70
+ await unlink(statePath(home));
71
+ return { ok: true, removed: true };
72
+ } catch (err) {
73
+ if (err && err.code === 'ENOENT') return { ok: true, removed: false };
74
+ return { ok: false, removed: false };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Read an installed extension's manifest by name. Resolves from project/org/user
80
+ * scope (project first, then org, then user). Used by `extension activate <name>`
81
+ * which doesn't carry the full manifest in args.
82
+ *
83
+ * Scope paths (matching the rest of the codebase):
84
+ * project: <projectRoot>/.ijfw/extensions/<name>/manifest.json
85
+ * org: ~/.ijfw/extensions-org/<name>/manifest.json
86
+ * user: ~/.ijfw/extensions-user/<name>/manifest.json
87
+ *
88
+ * @param {string} name
89
+ * @param {string} [projectRoot]
90
+ * @param {{ homeDir?: string, strictShadow?: boolean }} [opts]
91
+ * @returns {Promise<{ ok: boolean, manifest?: object, scope?: string, path?: string, error?: string }>}
92
+ */
93
+ export async function findInstalledManifest(name, projectRoot, opts = {}) {
94
+ // eslint-disable-next-line security/detect-unsafe-regex -- anchored, bounded npm name shape; no nested ambiguous repetition
95
+ if (typeof name !== 'string' || !/^(@[a-z0-9-]+\/)?[a-z][a-z0-9-]*$/.test(name)) {
96
+ return { ok: false, error: 'invalid extension name' };
97
+ }
98
+ const home = opts && opts.homeDir ? opts.homeDir : (process.env.HOME || homedir());
99
+ const candidates = [];
100
+ if (projectRoot) {
101
+ candidates.push({ scope: 'project', path: join(projectRoot, '.ijfw', 'extensions', name, 'manifest.json') });
102
+ }
103
+ candidates.push({ scope: 'org', path: join(home, '.ijfw', 'extensions-org', name, 'manifest.json') });
104
+ candidates.push({ scope: 'user', path: join(home, '.ijfw', 'extensions-user', name, 'manifest.json') });
105
+
106
+ // Collect all found manifests to detect project-scope shadowing.
107
+ const found = [];
108
+ for (const c of candidates) {
109
+ try {
110
+ const raw = await readFile(c.path, 'utf8');
111
+ const manifest = JSON.parse(raw);
112
+ found.push({ scope: c.scope, path: c.path, manifest });
113
+ } catch (err) {
114
+ if (err && err.code === 'ENOENT') continue;
115
+ if (err instanceof SyntaxError) continue;
116
+ }
117
+ }
118
+
119
+ if (found.length === 0) {
120
+ return { ok: false, error: `extension "${name}" not found in project/org/user scope` };
121
+ }
122
+
123
+ const winner = found[0];
124
+
125
+ // B13.1: warn when project-scope shadows a lower-priority scope entry.
126
+ if (winner.scope === 'project' && found.length > 1) {
127
+ const shadowed = found[1];
128
+ const winnerKeyId = winner.manifest.signature?.keyId ?? '(unsigned)';
129
+ const shadowedKeyId = shadowed.manifest.signature?.keyId ?? '(unsigned)';
130
+ if (opts && opts.strictShadow) {
131
+ return {
132
+ ok: false,
133
+ error: `extension activate: project-scope "${name}" shadows ${shadowed.scope}-scope "${name}" (keyId ${winnerKeyId} vs ${shadowedKeyId}) — refused by strictShadow`,
134
+ };
135
+ }
136
+ process.stderr.write(
137
+ `[ijfw] extension activate: project-scope "${name}" shadows ${shadowed.scope}-scope "${name}" (keyId ${winnerKeyId} vs ${shadowedKeyId}) — using project\n`,
138
+ );
139
+ }
140
+
141
+ return { ok: true, manifest: winner.manifest, scope: winner.scope, path: winner.path };
142
+ }