packwright 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,311 @@
1
+ import json
2
+
3
+ import yaml
4
+
5
+ from packwright.core.emotion_engine_contract import (
6
+ EMOTION_ENGINE_AVAILABLE_RUNTIME,
7
+ EMOTION_ENGINE_MODES,
8
+ emotion_engine_feature,
9
+ )
10
+ from packwright.core.errors import PackwrightValidationError
11
+ from packwright.core.knowledge_contract import knowledge_feature, knowledge_files
12
+ from packwright.core.memory_projection import project_memory_file
13
+ from packwright.core.naming import (
14
+ character_mission,
15
+ character_name,
16
+ character_slug,
17
+ character_user_name,
18
+ character_voice_summary,
19
+ durable_memory_source,
20
+ reference_prefix,
21
+ save_context_skill_path,
22
+ )
23
+ from packwright.core.path_safety import resolve_mechanism_file
24
+ from packwright.core.validation import validate_mechanism
25
+ from packwright.core.workspace_contract import workspace_feature, workspace_files
26
+
27
+
28
+ ADAPTER_NAME = "codex"
29
+
30
+
31
+ def compile_to_codex_pack(mechanism, references=None):
32
+ """Compile a resolved character mechanism into a Codex adapter pack."""
33
+ validate_mechanism(mechanism)
34
+ if ADAPTER_NAME not in mechanism["targets"].get("supported", []):
35
+ raise PackwrightValidationError(["Codex adapter is not listed in targets.supported"])
36
+
37
+ references = references or {}
38
+ skill_path = save_context_skill_path(mechanism, ADAPTER_NAME)
39
+ pack = {
40
+ "AGENTS.md": _render_agents_md(mechanism),
41
+ skill_path: _render_save_context_skill(mechanism, references),
42
+ }
43
+ pack.update(_reference_files(mechanism))
44
+ pack.update(_memory_skeleton_files(mechanism))
45
+ pack.update(_knowledge_files())
46
+ pack.update(_workspace_files(mechanism))
47
+ pack["manifest.json"] = _render_manifest(mechanism, references, sorted(pack.keys()) + ["manifest.json"])
48
+ return pack
49
+
50
+
51
+ def _render_agents_md(mechanism):
52
+ identity = mechanism["identity"]
53
+ name = character_name(mechanism)
54
+ user_name = character_user_name(mechanism)
55
+ skill_path = save_context_skill_path(mechanism, ADAPTER_NAME)
56
+ voice_summary = _sentence(character_voice_summary(mechanism))
57
+ lines = [
58
+ f"# {name}",
59
+ "",
60
+ f"You are {name}.",
61
+ "",
62
+ f"{name} is {identity['role']}",
63
+ "",
64
+ character_mission(mechanism),
65
+ "",
66
+ "## Work Focus",
67
+ ]
68
+ lines.extend(f"- {item}" for item in identity.get("work_focus", []))
69
+ lines.extend(["", "## Personality"])
70
+ lines.extend(f"- {trait}" for trait in identity.get("personality", []))
71
+ lines.extend(
72
+ [
73
+ "",
74
+ "## Voice",
75
+ f"- {voice_summary}",
76
+ "- Say what matters first.",
77
+ "- Use warmth through attentiveness, not decoration.",
78
+ "- Treat file reads as internal work unless the user asks for evidence; do not turn normal replies into audit logs.",
79
+ "- When uncertain, name the uncertainty and check the source.",
80
+ "- When corrected, restate the corrected model and adjust without defensiveness.",
81
+ "",
82
+ "## Working Rules",
83
+ "- Preserve the user's stated intent and scope.",
84
+ "- Read relevant files before making factual claims.",
85
+ "- Keep durable memory in files, not in long prompt text.",
86
+ "- Use session index notes to make prior work discoverable.",
87
+ "- Use `workspace/<domain>/` for generated drafts, artifacts, and archives; keep memory files focused on state, decisions, and indexes.",
88
+ "- Use `knowledge/` only for reviewed reusable models and patterns; keep current project state in `memory/`.",
89
+ "- Ask before consequential changes.",
90
+ "- Do not invent emotional or relationship state.",
91
+ f"- When memory is empty, say there is no pickup yet and help {user_name} establish the first useful context; do not quote template placeholders.",
92
+ "",
93
+ "## Use When Needed",
94
+ f"- Read `{skill_path}` for milestone handoff or explicit context-save requests.",
95
+ "- Read `memory/index.md` first when prior context may matter; it is the memory router, not a project state source.",
96
+ "- Read `memory/profile.md` when stable user, subject, learner, creator, or relationship facts could affect the task.",
97
+ "- Read `memory/workstreams.md` when the request belongs to a long-running domain, needs domain routing, or may later be promoted to a separate agent.",
98
+ "- Read `memory/projects/<slug>.md` when a specific project is named or clearly implied; project files are the source of project state and decisions.",
99
+ "- Read `memory/session-index.md` when the user refers to earlier sessions, previous work, or an unnamed prior plan.",
100
+ "- Read `memory/source-map.md` when facts need source lookup, verification paths, or source-of-truth files.",
101
+ "- Read `knowledge/index.md` only when reusable domain knowledge may help; load the smallest useful note set.",
102
+ "- Read `sources/*/manifest.json` only when provenance or original source lookup matters.",
103
+ "- Read `memory/todos.md` for current action queues and commitments.",
104
+ "- Read `memory/collaboration.md` when collaboration calibration, repair history, or user-specific working preferences affect the response.",
105
+ "- Treat `memory/pinned.md`, `memory/recent-activity.md`, `memory/knowledge_map.md`, and `memory/relationship-state.md` only as compatibility files unless the memory index points to them.",
106
+ "",
107
+ ]
108
+ )
109
+ return "\n".join(lines)
110
+
111
+
112
+ def _render_save_context_skill(mechanism, references):
113
+ memory_policy = _load_yaml_ref(mechanism, mechanism["mechanism"]["memory_policy_path"])
114
+ name = character_name(mechanism)
115
+ user_name = character_user_name(mechanism)
116
+
117
+ lines = [
118
+ f"# {name} Save Context",
119
+ "",
120
+ f"Use this skill at milestone handoff, session close, or when {user_name} explicitly asks {name} to preserve context.",
121
+ "",
122
+ "## Procedure",
123
+ "1. Identify the current objective, scope, decisions, changed files, verification, and open questions.",
124
+ "2. Update the canonical owner file instead of copying the same fact across layers.",
125
+ "3. Update `memory/profile.md` only for stable cross-workstream profile facts the user intentionally provides or confirms.",
126
+ "4. Update `memory/workstreams.md` for long-running domain routing, and `memory/workstreams/<slug>.md` for dense domain state.",
127
+ "5. Update `memory/projects/<slug>.md` for project state, decisions, open loops, and project-specific sources.",
128
+ "6. Update `memory/session-index.md` for session/thread lookup entries, not project state summaries.",
129
+ "7. Update `memory/source-map.md` for source-of-truth paths, verification routes, workspace artifacts, and lookup pointers.",
130
+ "8. Update `memory/todos.md` for action queues and commitments.",
131
+ "9. Update `memory/collaboration.md` only for stable collaboration calibrations; do not write ordinary praise, transient mood, or live Emotion Engine state.",
132
+ "10. Put generated drafts, artifacts, and archives under `workspace/<domain>/`; do not copy full deliverables into memory files.",
133
+ "11. Update `memory/index.md` only when active projects, memory owners, or routing rules change.",
134
+ "12. Report what was saved, what remains unsaved, and where the next session should resume.",
135
+ "",
136
+ "## Memory Tracks",
137
+ ]
138
+ for track_name, track in memory_policy.get("tracks", {}).items():
139
+ if track.get("id") == "emotion-state":
140
+ continue
141
+ lines.append(f"- {track_name}: {track.get('purpose', '')}")
142
+
143
+ lines.extend(
144
+ [
145
+ "",
146
+ "## Boundary Notes",
147
+ f"- {name} local memory files remain the durable memory source of truth.",
148
+ "- `memory/profile.md` stores explicit stable profile facts, not inferred mood or secrets.",
149
+ "- `memory/workstreams.md` is a domain router for long-running areas; project files still own project-specific state.",
150
+ "- `memory/session-index.md` is a lookup index, not a project state source.",
151
+ "- `AGENTS.md` is stable identity/default behavior, not learned collaboration calibration.",
152
+ "- `knowledge/index.md` is a recall router for reviewed reusable knowledge, not current project status.",
153
+ "- `sources/*/manifest.json` stores provenance for knowledge notes and external sources; it is not the knowledge body.",
154
+ "- `workspace/<domain>/` stores drafts, deliverables, and archives; important outputs should be indexed in `memory/source-map.md`.",
155
+ "- `.emotion-engine/codex-state.json` stores dynamic emotion state; do not mirror it into memory files.",
156
+ "- Fact assertion gates are session guards, not a skill.",
157
+ "",
158
+ ]
159
+ )
160
+ return "\n".join(lines)
161
+
162
+
163
+ def _reference_files(mechanism):
164
+ prefix = reference_prefix(mechanism, ADAPTER_NAME)
165
+ refs = {
166
+ f"{prefix}/identity/persona.md": _read_text_ref(mechanism, mechanism["identity"]["persona_path"]),
167
+ f"{prefix}/identity/voice.md": _read_text_ref(mechanism, mechanism["identity"]["voice_path"]),
168
+ f"{prefix}/identity/relationship.md": _read_text_ref(mechanism, mechanism["identity"]["relationship_path"]),
169
+ f"{prefix}/operating/principles.md": _read_text_ref(mechanism, mechanism["operating"]["principles_path"]),
170
+ f"{prefix}/operating/boundaries.md": _read_text_ref(mechanism, mechanism["operating"]["boundaries_path"]),
171
+ f"{prefix}/mechanism/context-loading.yaml": _read_text_ref(mechanism, mechanism["mechanism"]["context_loading_path"]),
172
+ f"{prefix}/mechanism/session-guards.yaml": _read_text_ref(mechanism, mechanism["mechanism"]["session_guards_path"]),
173
+ f"{prefix}/mechanism/memory-policy.yaml": _read_text_ref(mechanism, mechanism["mechanism"]["memory_policy_path"]),
174
+ f"{prefix}/projection/platform-capabilities.yaml": _read_text_ref(
175
+ mechanism, mechanism["projection"]["platform_capabilities_path"]
176
+ ),
177
+ f"{prefix}/projection/ownership-contract.yaml": _read_text_ref(
178
+ mechanism, mechanism["projection"]["ownership_contract_path"]
179
+ ),
180
+ f"{prefix}/emotion/model.yaml": _read_text_ref(mechanism, mechanism["emotion"]["model_path"]),
181
+ f"{prefix}/emotion/state-schema.yaml": _read_text_ref(mechanism, mechanism["emotion"]["state_schema_path"]),
182
+ f"{prefix}/emotion/update-policy.yaml": _read_text_ref(mechanism, mechanism["emotion"]["update_policy_path"]),
183
+ f"{prefix}/emotion/voice-modulation.yaml": _read_text_ref(
184
+ mechanism, mechanism["emotion"]["voice_modulation_path"]
185
+ ),
186
+ f"{prefix}/emotion/memory-events.yaml": _read_text_ref(mechanism, mechanism["emotion"]["memory_events_path"]),
187
+ }
188
+ for skill in mechanism["skills"]:
189
+ refs[f"{prefix}/source-skills/{skill['id']}/SKILL.md"] = _read_text_ref(mechanism, skill["path"])
190
+ return refs
191
+
192
+
193
+ def _memory_skeleton_files(mechanism):
194
+ files = {}
195
+ for item in mechanism["memory"]["local_files"]:
196
+ path = item["path"]
197
+ files[path] = project_memory_file(mechanism, ADAPTER_NAME, path, _read_text_ref(mechanism, path))
198
+ return files
199
+
200
+
201
+ def _workspace_files(mechanism):
202
+ return workspace_files(_read_text_ref(mechanism, "workspace/README.md"))
203
+
204
+
205
+ def _knowledge_files():
206
+ return knowledge_files()
207
+
208
+
209
+ def _render_manifest(mechanism, references, artifacts):
210
+ slug = character_slug(mechanism)
211
+ emotion_mode = _recommended_emotion_mode(mechanism)
212
+ manifest = {
213
+ "kind": "CodexAdapterPack",
214
+ "adapter": ADAPTER_NAME,
215
+ "source_mechanism": references.get("source_mechanism", mechanism.get("source", {}).get("path")),
216
+ "character": {
217
+ "name": character_name(mechanism),
218
+ "slug": slug,
219
+ "user_name": character_user_name(mechanism),
220
+ "direct_emotional_interaction": mechanism.get("emotion", {}).get(
221
+ "direct_interaction", "decide_later"
222
+ ),
223
+ "relationship_continuity": mechanism.get("emotion", {}).get(
224
+ "relationship_continuity", "warm_selective"
225
+ ),
226
+ "emotion_style": character_voice_summary(mechanism),
227
+ },
228
+ "features": {
229
+ "emotion_engine": emotion_engine_feature("codex", installed=False, mode=emotion_mode),
230
+ "memory": _memory_feature(),
231
+ "knowledge": knowledge_feature(),
232
+ "workspace": workspace_feature(),
233
+ },
234
+ "resolved_parameters": mechanism.get("resolved_parameters", {}),
235
+ "run": mechanism.get("run", {}),
236
+ "artifacts": artifacts,
237
+ "implementation_scope": mechanism.get("implementation_scope", {}),
238
+ "ownership": {
239
+ "model_loop": "codex",
240
+ "thread_state": "codex",
241
+ "tools": "codex",
242
+ "durable_memory_source_of_truth": durable_memory_source(mechanism),
243
+ "hooks": "project_config_or_plugin_dependent",
244
+ },
245
+ "boundaries": {
246
+ "is_runtime_executor": False,
247
+ "implements_cloud": False,
248
+ "implemented_runtime": ADAPTER_NAME,
249
+ "emotion_engine_runtime": EMOTION_ENGINE_AVAILABLE_RUNTIME,
250
+ "emotion_engine_mode": emotion_mode,
251
+ "emotion_engine_status": mechanism.get("emotion", {}).get("status"),
252
+ "reserved_runtimes": sorted(mechanism.get("targets", {}).get("reserved", {}).keys()),
253
+ "reserved_specs": sorted(mechanism.get("reserved_specs", {}).keys()),
254
+ },
255
+ }
256
+ return json.dumps(manifest, indent=2, sort_keys=True) + "\n"
257
+
258
+
259
+ def _recommended_emotion_mode(mechanism):
260
+ mode = mechanism.get("emotion", {}).get("recommended_mode", "light")
261
+ return mode if mode in EMOTION_ENGINE_MODES else "light"
262
+
263
+
264
+ def _memory_feature():
265
+ return {
266
+ "core_files": [
267
+ "memory/index.md",
268
+ "memory/profile.md",
269
+ "memory/workstreams.md",
270
+ "memory/workstreams/_template.md",
271
+ "memory/projects/*.md",
272
+ "memory/session-index.md",
273
+ "memory/source-map.md",
274
+ "memory/todos.md",
275
+ "memory/collaboration.md",
276
+ ],
277
+ "pinned_items": 20,
278
+ "recent_activity_hot_entries": 20,
279
+ "session_index_entries": 20,
280
+ "compatibility_files": [
281
+ "memory/pinned.md",
282
+ "memory/recent-activity.md",
283
+ "memory/knowledge_map.md",
284
+ "memory/relationship-state.md",
285
+ ],
286
+ "workstream_load_policy": "load_router_then_relevant_detail",
287
+ "workstream_promotion": "workstream_to_independent_agent_when_persona_toolchain_or_memory_contract_diverges",
288
+ "project_summary_lines": 12,
289
+ }
290
+
291
+
292
+ def _sentence(text):
293
+ text = str(text or "").strip()
294
+ if not text:
295
+ return ""
296
+ if text[-1] in ".!?":
297
+ return text
298
+ return text + "."
299
+
300
+
301
+ def _read_text_ref(mechanism, rel_path):
302
+ path = _resolve_ref(mechanism, rel_path)
303
+ return path.read_text(encoding="utf-8")
304
+
305
+
306
+ def _load_yaml_ref(mechanism, rel_path):
307
+ return yaml.safe_load(_read_text_ref(mechanism, rel_path)) or {}
308
+
309
+
310
+ def _resolve_ref(mechanism, rel_path):
311
+ return resolve_mechanism_file(mechanism, rel_path)