network-ai 4.4.3 → 4.5.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.
@@ -115,6 +115,7 @@ Match your problem to the Network-AI primitive:
115
115
  | Race conditions in parallel agent writes | `LockedBlackboard` with `priority-wins` | Higher-priority agents preempt lower-priority writes on conflict |
116
116
  | Need to expose all tools to an AI via MCP | `McpSseServer` + `network-ai-server` | HTTP/SSE server at `GET /sse`, `POST /mcp`, `GET /tools` |
117
117
  | Runtime AI control of the orchestrator | `ControlMcpTools` | AI can read/set config, spawn/stop agents, drive FSM transitions |
118
+ | Agents lose project context between sessions | `ProjectContextManager` (`context_manager.py`) | Persistent Layer-3 JSON file injected into every agent's system prompt |
118
119
 
119
120
  ---
120
121
 
package/QUICKSTART.md CHANGED
@@ -445,6 +445,67 @@ python scripts/revoke_token.py --cleanup
445
445
  python scripts/validate_token.py --token "grant_85364b44..."
446
446
  ```
447
447
 
448
+ ### Project Context (Layer 3 — Persistent Memory)
449
+
450
+ Long-lived project state that every agent inherits, regardless of session:
451
+
452
+ ```bash
453
+ # Initialise once
454
+ python scripts/context_manager.py init \
455
+ --name "MyProject" \
456
+ --description "Multi-agent workflow" \
457
+ --version "1.0.0"
458
+
459
+ # Print formatted block to inject into agent system prompts
460
+ python scripts/context_manager.py inject
461
+
462
+ # Record an architecture decision
463
+ python scripts/context_manager.py update \
464
+ --section decisions \
465
+ --add '{"decision": "Use atomic blackboard commits", "rationale": "Prevent race conditions"}'
466
+
467
+ # Update milestones
468
+ python scripts/context_manager.py update --section milestones --complete "Ship v1.0"
469
+ python scripts/context_manager.py update --section milestones --add '{"planned": "Vector memory integration"}'
470
+
471
+ # Set tech stack
472
+ python scripts/context_manager.py update --section stack --set '{"language": "TypeScript", "runtime": "Node.js 18"}'
473
+
474
+ # Add a goal or ban an approach
475
+ python scripts/context_manager.py update --section goals --add "Ship v2.0 before Q3"
476
+ python scripts/context_manager.py update --section banned --add "Direct DB writes from agents"
477
+
478
+ # Print full context as JSON
479
+ python scripts/context_manager.py show
480
+ ```
481
+
482
+ Context is stored in `data/project-context.json`. The `inject` command outputs a markdown block ready to prepend to any agent's system prompt.
483
+
484
+ ---
485
+
486
+ ## Use with Claude, ChatGPT & Codex
487
+
488
+ Three integration files are included in the repo root:
489
+
490
+ | File | Use |
491
+ |---|---|
492
+ | [`claude-tools.json`](claude-tools.json) | Claude API tool use & OpenAI Codex — drop into the `tools` array |
493
+ | [`openapi.yaml`](openapi.yaml) | Custom GPT Actions — import directly in the GPT editor |
494
+ | [`claude-project-prompt.md`](claude-project-prompt.md) | Claude Projects — paste into Custom Instructions |
495
+
496
+ **Claude API / Codex:**
497
+ ```js
498
+ import tools from './claude-tools.json' assert { type: 'json' };
499
+ // Pass tools array to anthropic.messages.create({ tools }) or OpenAI chat completions
500
+ ```
501
+
502
+ **Custom GPT Actions:**
503
+ In the GPT editor → Actions → Import from URL, or paste `openapi.yaml` directly.
504
+ Set the server URL to your running `npx network-ai-server --port 3001` instance.
505
+
506
+ **Claude Projects:**
507
+ Copy the contents of `claude-project-prompt.md` into a Claude Project's Custom Instructions field. No server required for instruction-only mode.
508
+
448
509
  ---
449
510
 
450
511
  ## Fan-Out / Fan-In Pattern
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![CI](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/ci.yml/badge.svg)](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/ci.yml)
6
6
  [![CodeQL](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/codeql.yml/badge.svg)](https://github.com/jovanSAPFIONEER/Network-AI/actions/workflows/codeql.yml)
7
- [![Release](https://img.shields.io/badge/release-v4.3.7-blue.svg)](https://github.com/jovanSAPFIONEER/Network-AI/releases)
7
+ [![Release](https://img.shields.io/badge/release-v4.5.1-blue.svg)](https://github.com/jovanSAPFIONEER/Network-AI/releases)
8
8
  [![npm](https://img.shields.io/npm/dw/network-ai.svg?label=npm%20downloads)](https://www.npmjs.com/package/network-ai)
9
9
  [![Tests](https://img.shields.io/badge/tests-1399%20passing-brightgreen.svg)](#testing)
10
10
  [![Adapters](https://img.shields.io/badge/frameworks-14%20supported-blueviolet.svg)](#adapter-system)
@@ -21,6 +21,7 @@ Network-AI is a TypeScript/Node.js multi-agent orchestrator that adds coordinati
21
21
  - **Shared blackboard with locking** — atomic `propose → validate → commit` prevents race conditions and split-brain failures across parallel agents
22
22
  - **Guardrails and budgets** — FSM governance, per-agent token ceilings, HMAC audit trails, and permission gating
23
23
  - **14 adapters** — LangChain (+ streaming), AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, Custom (+ streaming), OpenClaw, A2A, and Codex — no glue code, no lock-in
24
+ - **Persistent project memory (Layer 3)** — `context_manager.py` injects decisions, goals, stack, milestones, and banned patterns into every system prompt so agents always have full project context
24
25
 
25
26
  > **The silent failure mode in multi-agent systems:** parallel agents writing to the same key
26
27
  > use last-write-wins by default — one agent's result silently overwrites another's mid-flight.
@@ -56,6 +57,7 @@ Network-AI is a TypeScript/Node.js multi-agent orchestrator that adds coordinati
56
57
  | No visibility into what agents did | HMAC-signed audit log on every write, permission grant, and FSM transition |
57
58
  | Locked into one AI framework | 14 adapters — mix LangChain + AutoGen + CrewAI + Codex + custom in one swarm |
58
59
  | Agents escalating beyond their scope | `AuthGuardian` — scoped permission tokens required before sensitive operations |
60
+ | Agents lack project context between runs | `ProjectContextManager` (Layer 3) — inject decisions, goals, stack, and milestones into every system prompt |
59
61
 
60
62
  ---
61
63
 
@@ -71,10 +73,14 @@ flowchart TD
71
73
  classDef blackboard fill:#0c4a6e,stroke:#0284c7,color:#bae6fd
72
74
  classDef adapters fill:#064e3b,stroke:#059669,color:#a7f3d0
73
75
  classDef audit fill:#1e293b,stroke:#475569,color:#94a3b8
76
+ classDef context fill:#3b1f00,stroke:#b45309,color:#fef3c7
74
77
 
75
78
  App["Your Application"]:::app
76
79
  App -->|"createSwarmOrchestrator()"| SO
77
80
 
81
+ PC["ProjectContextManager\n(Layer 3 — persistent memory)\ngoals · stack · decisions\nmilestones · banned"]:::context
82
+ PC -->|"injected into system prompt"| SO
83
+
78
84
  subgraph SO["SwarmOrchestrator"]
79
85
  AG["AuthGuardian\n(permission gating)"]:::security
80
86
  AR["AdapterRegistry\n(route tasks to frameworks)"]:::routing
package/SKILL.md CHANGED
@@ -165,6 +165,85 @@ python {baseDir}/scripts/blackboard.py write "task:001:final" \
165
165
 
166
166
  ---
167
167
 
168
+ ## The 3-Layer Memory Model
169
+
170
+ Every agent in the swarm operates with three memory layers, each with a different scope and lifetime:
171
+
172
+ | Layer | Name | Lifetime | Managed by |
173
+ |-------|------|----------|------------|
174
+ | **1** | Agent context | Ephemeral — current task only | Platform (per-session) |
175
+ | **2** | Blackboard | TTL-scoped — shared across agents | `scripts/blackboard.py` |
176
+ | **3** | Project context | Persistent — survives all sessions | `scripts/context_manager.py` |
177
+
178
+ ### Layer 1 — Agent Context
179
+ Each agent's own context window: the current task instructions, conversation history, and immediate working memory. Managed automatically by the OpenClaw/LLM platform. Nothing to configure.
180
+
181
+ ### Layer 2 — Blackboard (Shared Coordination State)
182
+ A shared markdown file (`swarm-blackboard.md`) for real-time cross-agent coordination: task results, grant tokens, status flags, and TTL-scoped cache entries. Agents read and write via `scripts/blackboard.py`. Entries expire automatically.
183
+
184
+ ### Layer 3 — Project Context (Persistent Long-Term Memory)
185
+ A JSON file (`data/project-context.json`) that holds information every agent should know, regardless of what session or task is running:
186
+ - **Goals** — long-term objectives of the project
187
+ - **Tech stack** — languages, frameworks, infrastructure
188
+ - **Milestones** — completed, in-progress, and planned work
189
+ - **Architecture decisions** — design choices and their rationales
190
+ - **Banned approaches** — approaches that have been ruled out
191
+
192
+ #### Initialising Project Context
193
+
194
+ ```bash
195
+ python {baseDir}/scripts/context_manager.py init \
196
+ --name "MyProject" \
197
+ --description "Multi-agent workflow automation" \
198
+ --version "1.0.0"
199
+ ```
200
+
201
+ #### Injecting Context into an Agent System Prompt
202
+
203
+ ```bash
204
+ python {baseDir}/scripts/context_manager.py inject
205
+ ```
206
+
207
+ Copy the output block to the top of your agent's system prompt. Every agent that receives this block shares the same long-term project awareness.
208
+
209
+ #### Recording a Decision
210
+
211
+ ```bash
212
+ python {baseDir}/scripts/context_manager.py update \
213
+ --section decisions \
214
+ --add '{"decision": "Use atomic blackboard commits", "rationale": "Prevent race conditions in parallel agents"}'
215
+ ```
216
+
217
+ #### Updating Milestones
218
+
219
+ ```bash
220
+ # Mark a milestone complete
221
+ python {baseDir}/scripts/context_manager.py update \
222
+ --section milestones --complete "Ship v2.0"
223
+
224
+ # Add a planned milestone
225
+ python {baseDir}/scripts/context_manager.py update \
226
+ --section milestones --add '{"planned": "Integrate vector memory"}'
227
+ ```
228
+
229
+ #### Setting the Tech Stack
230
+
231
+ ```bash
232
+ python {baseDir}/scripts/context_manager.py update \
233
+ --section stack \
234
+ --set '{"language": "TypeScript", "runtime": "Node.js 18", "framework": "Network-AI v4.5"}'
235
+ ```
236
+
237
+ #### Banning an Approach
238
+
239
+ ```bash
240
+ python {baseDir}/scripts/context_manager.py update \
241
+ --section banned \
242
+ --add "Direct database writes from agent scripts (use permission gating)"
243
+ ```
244
+
245
+ ---
246
+
168
247
  ## When to Use This Skill
169
248
 
170
249
  - **Task Delegation**: Route work to specialized agents (data_analyst, strategy_advisor, risk_assessor)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "network-ai",
3
- "version": "4.4.3",
3
+ "version": "4.5.1",
4
4
  "description": "AI agent orchestration framework for TypeScript/Node.js - 14 adapters (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, A2A, Codex + streaming variants). Built-in CLI, security, swarm intelligence, real-time streaming, and agentic workflow patterns.",
5
5
  "homepage": "https://github.com/jovanSAPFIONEER/Network-AI#readme",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,334 @@
1
+ #!/usr/bin/env python3
2
+ # SECURITY: This script makes NO network calls and spawns NO subprocesses.
3
+ # All I/O is local file operations only:
4
+ # READS: data/project-context.json, data/audit_log.jsonl
5
+ # WRITES: data/project-context.json, data/audit_log.jsonl
6
+ # Imports used: argparse, json, sys, datetime, pathlib, typing
7
+ # No imports of: requests, socket, subprocess, urllib, http, ssl, ftplib, smtplib
8
+ """
9
+ Project Context Manager - Persistent Layer-3 Memory for Agent Swarms
10
+
11
+ Maintains a JSON file that stores long-lived project context: goals, architecture
12
+ decisions, tech stack, milestones, and banned approaches. This context is injected
13
+ into every agent session so all agents share the same project-level awareness,
14
+ regardless of what's currently on the short-term blackboard.
15
+
16
+ THE 3-LAYER MEMORY MODEL
17
+ Layer 1 — Agent context : current task, immediate instructions (ephemeral, per-agent)
18
+ Layer 2 — Blackboard : task results, grants, coordination state (shared, TTL-scoped)
19
+ Layer 3 — Project context : architecture decisions, goals, stack, milestones (THIS FILE)
20
+
21
+ Usage:
22
+ python context_manager.py init --name "MyProject" [--description "..."] [--version "1.0.0"]
23
+ python context_manager.py show
24
+ python context_manager.py inject
25
+ python context_manager.py update --section decisions --add '{"decision": "...", "rationale": "..."}'
26
+ python context_manager.py update --section milestones --complete "task name"
27
+ python context_manager.py update --section milestones --add '{"planned": "task name"}'
28
+ python context_manager.py update --section stack --set '{"language": "TypeScript"}'
29
+ python context_manager.py update --section goals --add "Ship v2.0 before Q3"
30
+ python context_manager.py update --section banned --add "Direct DB writes from agents"
31
+
32
+ Examples:
33
+ python context_manager.py init --name "Network-AI" --description "Multi-agent swarm framework" --version "4.5.0"
34
+ python context_manager.py update --section decisions --add '{"decision": "Use atomic blackboard commits", "rationale": "Prevent race conditions"}'
35
+ python context_manager.py update --section milestones --complete "v4.4.3 ClawHub clean-scan"
36
+ python context_manager.py inject
37
+ """
38
+
39
+ import argparse
40
+ import json
41
+ import sys
42
+ from datetime import datetime, timezone
43
+ from pathlib import Path
44
+ from typing import Any, cast
45
+
46
+ CONTEXT_PATH = Path(__file__).parent.parent / "data" / "project-context.json"
47
+ AUDIT_LOG_PATH = Path(__file__).parent.parent / "data" / "audit_log.jsonl"
48
+
49
+ EMPTY_CONTEXT: dict[str, Any] = {
50
+ "project": {
51
+ "name": "",
52
+ "description": "",
53
+ "version": ""
54
+ },
55
+ "goals": [],
56
+ "stack": {},
57
+ "milestones": {
58
+ "completed": [],
59
+ "in_progress": [],
60
+ "planned": []
61
+ },
62
+ "decisions": [],
63
+ "banned_approaches": [],
64
+ "agents": {},
65
+ "updated_at": ""
66
+ }
67
+
68
+
69
+ # ---------------------------------------------------------------------------
70
+ # Helpers
71
+ # ---------------------------------------------------------------------------
72
+
73
+ def _now_iso() -> str:
74
+ return datetime.now(timezone.utc).isoformat()
75
+
76
+
77
+ def _load() -> dict[str, Any]:
78
+ if not CONTEXT_PATH.exists():
79
+ print(
80
+ f"[context_manager] No project context found at {CONTEXT_PATH}.\n"
81
+ "Run: python context_manager.py init --name \"YourProject\"",
82
+ file=sys.stderr
83
+ )
84
+ sys.exit(1)
85
+ with CONTEXT_PATH.open("r", encoding="utf-8") as fh:
86
+ return json.load(fh)
87
+
88
+
89
+ def _save(ctx: dict[str, Any]) -> None:
90
+ ctx["updated_at"] = _now_iso()
91
+ CONTEXT_PATH.parent.mkdir(parents=True, exist_ok=True)
92
+ with CONTEXT_PATH.open("w", encoding="utf-8") as fh:
93
+ json.dump(ctx, fh, indent=2)
94
+ fh.write("\n")
95
+
96
+
97
+ def _audit(action: str, detail: dict[str, Any]) -> None:
98
+ entry: dict[str, Any] = {
99
+ "timestamp": _now_iso(),
100
+ "action": action,
101
+ "details": {"source": "context_manager", **detail}
102
+ }
103
+ AUDIT_LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
104
+ with AUDIT_LOG_PATH.open("a", encoding="utf-8") as fh:
105
+ fh.write(json.dumps(entry) + "\n")
106
+
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # Commands
110
+ # ---------------------------------------------------------------------------
111
+
112
+ def cmd_init(args: argparse.Namespace) -> int:
113
+ if CONTEXT_PATH.exists():
114
+ print(f"[context_manager] Context file already exists at {CONTEXT_PATH}.")
115
+ print("Use 'update' to change individual sections, or delete the file to reinitialise.")
116
+ return 1
117
+
118
+ ctx = json.loads(json.dumps(EMPTY_CONTEXT)) # deep copy
119
+ ctx["project"]["name"] = args.name
120
+ ctx["project"]["description"] = args.description or ""
121
+ ctx["project"]["version"] = args.version or ""
122
+ _save(ctx)
123
+ _audit("init", {"name": args.name, "version": args.version})
124
+ print(f"[context_manager] Project context initialised: {CONTEXT_PATH}")
125
+ return 0
126
+
127
+
128
+ def cmd_show(args: argparse.Namespace) -> int: # noqa: ARG001
129
+ ctx = _load()
130
+ print(json.dumps(ctx, indent=2))
131
+ return 0
132
+
133
+
134
+ def cmd_inject(args: argparse.Namespace) -> int: # noqa: ARG001
135
+ """Print a formatted block suitable for injection into an agent system prompt."""
136
+ ctx = _load()
137
+ p = ctx.get("project", {})
138
+
139
+ lines: list[str] = []
140
+ lines.append("## Project Context (Layer 3 — Persistent Memory)")
141
+ lines.append("")
142
+
143
+ if p.get("name"):
144
+ name_str = p["name"]
145
+ if p.get("version"):
146
+ name_str += f" v{p['version']}"
147
+ lines.append(f"**Project:** {name_str}")
148
+ if p.get("description"):
149
+ lines.append(f"**Description:** {p['description']}")
150
+ lines.append("")
151
+
152
+ goals = ctx.get("goals", [])
153
+ if goals:
154
+ lines.append("### Goals")
155
+ for g in goals:
156
+ lines.append(f"- {g}")
157
+ lines.append("")
158
+
159
+ stack = ctx.get("stack", {})
160
+ if stack:
161
+ lines.append("### Tech Stack")
162
+ for k, v in stack.items():
163
+ lines.append(f"- **{k}**: {v}")
164
+ lines.append("")
165
+
166
+ milestones = ctx.get("milestones", {})
167
+ in_progress = milestones.get("in_progress", [])
168
+ planned = milestones.get("planned", [])
169
+ completed = milestones.get("completed", [])
170
+ if in_progress or planned or completed:
171
+ lines.append("### Milestones")
172
+ for item in in_progress:
173
+ lines.append(f"- 🔄 {item} *(in progress)*")
174
+ for item in planned:
175
+ lines.append(f"- ⏳ {item}")
176
+ for item in completed:
177
+ lines.append(f"- ✅ {item}")
178
+ lines.append("")
179
+
180
+ decisions = ctx.get("decisions", [])
181
+ if decisions:
182
+ lines.append("### Architecture Decisions")
183
+ for d in decisions:
184
+ if isinstance(d, dict):
185
+ d_typed: dict[str, Any] = cast(dict[str, Any], d)
186
+ dec: str = str(d_typed.get("decision", d))
187
+ rat: str = str(d_typed.get("rationale", ""))
188
+ lines.append(f"- **{dec}**" + (f" — {rat}" if rat else ""))
189
+ else:
190
+ lines.append(f"- {d}")
191
+ lines.append("")
192
+
193
+ banned = ctx.get("banned_approaches", [])
194
+ if banned:
195
+ lines.append("### Banned Approaches")
196
+ for b in banned:
197
+ lines.append(f"- ❌ {b}")
198
+ lines.append("")
199
+
200
+ lines.append(f"*Context last updated: {ctx.get('updated_at', 'unknown')}*")
201
+
202
+ print("\n".join(lines))
203
+ return 0
204
+
205
+
206
+ def cmd_update(args: argparse.Namespace) -> int:
207
+ ctx = _load()
208
+ section = args.section
209
+
210
+ if section == "decisions":
211
+ if not args.add:
212
+ print("[context_manager] --add is required for section 'decisions'", file=sys.stderr)
213
+ return 1
214
+ entry: Any = json.loads(args.add)
215
+ ctx.setdefault("decisions", []).append(entry)
216
+ _audit("update_decisions", {"added": entry})
217
+
218
+ elif section == "milestones":
219
+ milestones = ctx.setdefault("milestones", {"completed": [], "in_progress": [], "planned": []})
220
+ if args.complete:
221
+ name = args.complete
222
+ # Move from in_progress or planned → completed
223
+ for bucket in ("in_progress", "planned"):
224
+ lst: list[Any] = milestones.setdefault(bucket, [])
225
+ if name in lst:
226
+ lst.remove(name)
227
+ milestones.setdefault("completed", []).append(name)
228
+ _audit("milestone_complete", {"name": name})
229
+ elif args.add:
230
+ entry: Any = json.loads(args.add)
231
+ if isinstance(entry, dict):
232
+ for bucket in ("planned", "in_progress", "completed"):
233
+ if bucket in entry:
234
+ milestones.setdefault(bucket, []).append(entry[bucket])
235
+ _audit("milestone_add", {"bucket": bucket, "name": entry[bucket]})
236
+ else:
237
+ milestones.setdefault("planned", []).append(str(entry))
238
+ _audit("milestone_add", {"bucket": "planned", "name": str(entry)})
239
+ else:
240
+ print("[context_manager] Provide --add or --complete for section 'milestones'", file=sys.stderr)
241
+ return 1
242
+
243
+ elif section == "stack":
244
+ if not args.set:
245
+ print("[context_manager] --set is required for section 'stack'", file=sys.stderr)
246
+ return 1
247
+ updates = json.loads(args.set)
248
+ ctx.setdefault("stack", {}).update(updates)
249
+ _audit("update_stack", {"updates": updates})
250
+
251
+ elif section == "goals":
252
+ if not args.add:
253
+ print("[context_manager] --add is required for section 'goals'", file=sys.stderr)
254
+ return 1
255
+ ctx.setdefault("goals", []).append(args.add)
256
+ _audit("update_goals", {"added": args.add})
257
+
258
+ elif section == "banned":
259
+ if not args.add:
260
+ print("[context_manager] --add is required for section 'banned'", file=sys.stderr)
261
+ return 1
262
+ ctx.setdefault("banned_approaches", []).append(args.add)
263
+ _audit("update_banned", {"added": args.add})
264
+
265
+ elif section == "project":
266
+ if not args.set:
267
+ print("[context_manager] --set is required for section 'project'", file=sys.stderr)
268
+ return 1
269
+ updates = json.loads(args.set)
270
+ ctx.setdefault("project", {}).update(updates)
271
+ _audit("update_project", {"updates": updates})
272
+
273
+ else:
274
+ print(f"[context_manager] Unknown section '{section}'. "
275
+ "Valid: decisions, milestones, stack, goals, banned, project", file=sys.stderr)
276
+ return 1
277
+
278
+ _save(ctx)
279
+ print(f"[context_manager] Section '{section}' updated.")
280
+ return 0
281
+
282
+
283
+ # ---------------------------------------------------------------------------
284
+ # CLI
285
+ # ---------------------------------------------------------------------------
286
+
287
+ def build_parser() -> argparse.ArgumentParser:
288
+ parser = argparse.ArgumentParser(
289
+ prog="context_manager.py",
290
+ description="Project Context Manager — Layer-3 persistent memory for agent swarms"
291
+ )
292
+ sub = parser.add_subparsers(dest="command", required=True)
293
+
294
+ # init
295
+ p_init = sub.add_parser("init", help="Initialise a new project context file")
296
+ p_init.add_argument("--name", required=True, help="Project name")
297
+ p_init.add_argument("--description", default="", help="Short project description")
298
+ p_init.add_argument("--version", default="", help="Current project version")
299
+
300
+ # show
301
+ sub.add_parser("show", help="Print the full context as JSON")
302
+
303
+ # inject
304
+ sub.add_parser("inject", help="Print formatted context for agent system-prompt injection")
305
+
306
+ # update
307
+ p_update = sub.add_parser("update", help="Update a specific context section")
308
+ p_update.add_argument(
309
+ "--section", required=True,
310
+ choices=["decisions", "milestones", "stack", "goals", "banned", "project"],
311
+ help="Section to update"
312
+ )
313
+ p_update.add_argument("--add", help="JSON string or plain string to append")
314
+ p_update.add_argument("--set", help="JSON object to merge/set (used by stack and project)")
315
+ p_update.add_argument("--complete", help="Mark a milestone as completed (milestones section)")
316
+
317
+ return parser
318
+
319
+
320
+ def main() -> int:
321
+ parser = build_parser()
322
+ args = parser.parse_args()
323
+
324
+ dispatch = {
325
+ "init": cmd_init,
326
+ "show": cmd_show,
327
+ "inject": cmd_inject,
328
+ "update": cmd_update,
329
+ }
330
+ return dispatch[args.command](args)
331
+
332
+
333
+ if __name__ == "__main__":
334
+ sys.exit(main())