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.
- packwright/__init__.py +3 -0
- packwright/__main__.py +5 -0
- packwright/adapters/__init__.py +10 -0
- packwright/adapters/claude_code.py +377 -0
- packwright/adapters/codex.py +311 -0
- packwright/adapters/cursor.py +375 -0
- packwright/checker/__init__.py +3 -0
- packwright/checker/scoring.py +924 -0
- packwright/cli.py +971 -0
- packwright/core/__init__.py +57 -0
- packwright/core/adapter_layout.py +35 -0
- packwright/core/adopt.py +199 -0
- packwright/core/character_intake.py +1649 -0
- packwright/core/emotion_engine_contract.py +147 -0
- packwright/core/errors.py +13 -0
- packwright/core/handoff.py +531 -0
- packwright/core/install.py +2114 -0
- packwright/core/intake_contract.py +105 -0
- packwright/core/knowledge_contract.py +212 -0
- packwright/core/loader.py +35 -0
- packwright/core/memory_projection.py +126 -0
- packwright/core/naming.py +98 -0
- packwright/core/pack_metadata.py +100 -0
- packwright/core/path_safety.py +70 -0
- packwright/core/resolver.py +66 -0
- packwright/core/validation.py +645 -0
- packwright/core/workspace_contract.py +102 -0
- packwright-0.1.0.dist-info/METADATA +213 -0
- packwright-0.1.0.dist-info/RECORD +33 -0
- packwright-0.1.0.dist-info/WHEEL +5 -0
- packwright-0.1.0.dist-info/entry_points.txt +2 -0
- packwright-0.1.0.dist-info/licenses/LICENSE +21 -0
- packwright-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from packwright.core.emotion_engine_contract import (
|
|
4
|
+
EMOTION_ENGINE_AVAILABLE_RUNTIME,
|
|
5
|
+
EMOTION_ENGINE_CLAUDE_RUNTIME,
|
|
6
|
+
EMOTION_ENGINE_CODEX_HELPER_PATH,
|
|
7
|
+
EMOTION_ENGINE_CODEX_MCP_PATH,
|
|
8
|
+
EMOTION_ENGINE_CODEX_MCP_REGISTRATION_PATH,
|
|
9
|
+
EMOTION_ENGINE_CODEX_SCRIPT_PATH,
|
|
10
|
+
EMOTION_ENGINE_CODEX_SKILL_DIR,
|
|
11
|
+
EMOTION_ENGINE_CODEX_SKILL_PATH,
|
|
12
|
+
EMOTION_ENGINE_CODEX_STATE_PATH,
|
|
13
|
+
EMOTION_ENGINE_CODEX_WRAPPER_PATH,
|
|
14
|
+
EMOTION_ENGINE_MODES,
|
|
15
|
+
EMOTION_ENGINE_RUNTIME,
|
|
16
|
+
emotion_engine_codex_expected,
|
|
17
|
+
emotion_engine_codex_manifest_diagnostics,
|
|
18
|
+
)
|
|
19
|
+
from packwright.core.errors import PackwrightValidationError
|
|
20
|
+
from packwright.core.handoff import (
|
|
21
|
+
DEFAULT_HANDOFF_DIR,
|
|
22
|
+
DEFAULT_SESSION_BRIEF_DIR,
|
|
23
|
+
HANDOFF_HELPER_PATH,
|
|
24
|
+
HANDOFF_SCHEMA,
|
|
25
|
+
HANDOFF_WRAPPER_PATH,
|
|
26
|
+
)
|
|
27
|
+
from packwright.core.knowledge_contract import (
|
|
28
|
+
KNOWLEDGE_INDEX,
|
|
29
|
+
KNOWLEDGE_MANIFEST,
|
|
30
|
+
SOURCE_MANIFESTS,
|
|
31
|
+
knowledge_artifacts,
|
|
32
|
+
)
|
|
33
|
+
from packwright.core.naming import (
|
|
34
|
+
character_name,
|
|
35
|
+
character_slug,
|
|
36
|
+
durable_memory_source,
|
|
37
|
+
reference_prefix,
|
|
38
|
+
save_context_skill_path,
|
|
39
|
+
)
|
|
40
|
+
from packwright.core.validation import file_exists, path_exists, validate_mechanism
|
|
41
|
+
from packwright.core.workspace_contract import (
|
|
42
|
+
WORKSPACE_INDEX_OWNER,
|
|
43
|
+
WORKSPACE_LAYOUT,
|
|
44
|
+
WORKSPACE_ROOT,
|
|
45
|
+
workspace_artifacts,
|
|
46
|
+
workspace_readme_required_markers,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def score_mechanism(mechanism, adapter_pack, adapter="codex", threshold=None):
|
|
51
|
+
"""Score a resolved mechanism and adapter pack."""
|
|
52
|
+
checks = []
|
|
53
|
+
try:
|
|
54
|
+
validate_mechanism(mechanism)
|
|
55
|
+
_add(checks, "mechanism_valid", True, 15, "mechanism spec passes validation")
|
|
56
|
+
except PackwrightValidationError as exc:
|
|
57
|
+
_add(checks, "mechanism_valid", False, 15, "; ".join(exc.issues))
|
|
58
|
+
return _result(checks, 85 if threshold is None else threshold)
|
|
59
|
+
|
|
60
|
+
configured_threshold = mechanism.get("checker", {}).get("threshold", 85)
|
|
61
|
+
threshold = configured_threshold if threshold is None else threshold
|
|
62
|
+
adapter_pack = adapter_pack or {}
|
|
63
|
+
adapter = _adapter_from_manifest(adapter_pack, adapter)
|
|
64
|
+
entry_path = _entry_path(mechanism, adapter)
|
|
65
|
+
skill_path = save_context_skill_path(mechanism, adapter)
|
|
66
|
+
entry = adapter_pack.get(entry_path, "")
|
|
67
|
+
skill = adapter_pack.get(skill_path, "")
|
|
68
|
+
settings = adapter_pack.get(".claude/settings.local.json.example", "")
|
|
69
|
+
manifest = _parse_json(adapter_pack.get("manifest.json", "{}"))
|
|
70
|
+
|
|
71
|
+
implemented_by = mechanism["coverage"]["implemented_by"]
|
|
72
|
+
identity = mechanism["identity"]
|
|
73
|
+
name = character_name(mechanism)
|
|
74
|
+
|
|
75
|
+
_add(
|
|
76
|
+
checks,
|
|
77
|
+
"source_files_exist",
|
|
78
|
+
_source_files_exist(mechanism),
|
|
79
|
+
10,
|
|
80
|
+
"all referenced identity, operating, mechanism, emotion, memory, and skill source files exist",
|
|
81
|
+
)
|
|
82
|
+
_add(
|
|
83
|
+
checks,
|
|
84
|
+
"coverage_paths",
|
|
85
|
+
all(path_exists(mechanism, path) for paths in implemented_by.values() for path in paths),
|
|
86
|
+
10,
|
|
87
|
+
"mechanism coverage paths all resolve",
|
|
88
|
+
)
|
|
89
|
+
_add(
|
|
90
|
+
checks,
|
|
91
|
+
"adapter_pack_manifest",
|
|
92
|
+
_manifest_matches_pack(manifest, adapter_pack, adapter),
|
|
93
|
+
10,
|
|
94
|
+
"adapter pack manifest describes emitted artifacts",
|
|
95
|
+
)
|
|
96
|
+
_add(
|
|
97
|
+
checks,
|
|
98
|
+
"projection_contracts_present",
|
|
99
|
+
_projection_contracts_present(mechanism, adapter_pack, adapter),
|
|
100
|
+
10,
|
|
101
|
+
"adapter pack includes platform capabilities and ownership contract",
|
|
102
|
+
)
|
|
103
|
+
_add(
|
|
104
|
+
checks,
|
|
105
|
+
"ownership_contract_valid",
|
|
106
|
+
_ownership_contract_valid(mechanism, manifest, adapter),
|
|
107
|
+
10,
|
|
108
|
+
"adapter manifest declares runtime ownership without taking durable memory ownership",
|
|
109
|
+
)
|
|
110
|
+
_add(
|
|
111
|
+
checks,
|
|
112
|
+
"entry_has_identity",
|
|
113
|
+
identity["name"] in entry and identity["role"] in entry and f"You are {name}." in entry,
|
|
114
|
+
10,
|
|
115
|
+
"entry file keeps character person-like stable identity hot",
|
|
116
|
+
)
|
|
117
|
+
_add(
|
|
118
|
+
checks,
|
|
119
|
+
"entry_has_voice",
|
|
120
|
+
_entry_has_voice(entry),
|
|
121
|
+
10,
|
|
122
|
+
"entry file carries stable voice guidance",
|
|
123
|
+
)
|
|
124
|
+
_add(
|
|
125
|
+
checks,
|
|
126
|
+
"entry_excludes_implementation_scope",
|
|
127
|
+
_entry_excludes_implementation_scope(mechanism, entry),
|
|
128
|
+
10,
|
|
129
|
+
"entry file does not embed Packwright implementation-scope details or run state",
|
|
130
|
+
)
|
|
131
|
+
_add(
|
|
132
|
+
checks,
|
|
133
|
+
"entry_points_to_save_context_skill",
|
|
134
|
+
skill_path in entry,
|
|
135
|
+
10,
|
|
136
|
+
"entry file points to the character save-context skill without turning foundation mechanisms into skills",
|
|
137
|
+
)
|
|
138
|
+
_add(
|
|
139
|
+
checks,
|
|
140
|
+
"entry_uses_runtime_appropriate_links",
|
|
141
|
+
_entry_uses_runtime_appropriate_links(entry, adapter, skill_path),
|
|
142
|
+
10,
|
|
143
|
+
"entry file uses Codex plain paths or Claude Code @path syntax as appropriate",
|
|
144
|
+
)
|
|
145
|
+
_add(
|
|
146
|
+
checks,
|
|
147
|
+
"on_demand_references_have_purpose",
|
|
148
|
+
_on_demand_references_have_purpose(entry, adapter),
|
|
149
|
+
10,
|
|
150
|
+
"on-demand entry references describe when or why to read each file",
|
|
151
|
+
)
|
|
152
|
+
_add(
|
|
153
|
+
checks,
|
|
154
|
+
"foundation_mechanisms_not_projected_as_skills",
|
|
155
|
+
_foundation_mechanisms_not_projected_as_skills(adapter_pack),
|
|
156
|
+
10,
|
|
157
|
+
"recent activity and fact check remain foundation mechanisms, not projected skills",
|
|
158
|
+
)
|
|
159
|
+
_add(
|
|
160
|
+
checks,
|
|
161
|
+
"save_context_skill_valid",
|
|
162
|
+
"## Procedure" in skill
|
|
163
|
+
and "memory/session-index.md" in skill
|
|
164
|
+
and "canonical owner file" in skill
|
|
165
|
+
and "## Memory Tracks" in skill,
|
|
166
|
+
10,
|
|
167
|
+
"save-context skill carries the heavy memory handoff procedure",
|
|
168
|
+
)
|
|
169
|
+
_add(
|
|
170
|
+
checks,
|
|
171
|
+
"save_context_skill_projection_neutral",
|
|
172
|
+
_save_context_skill_projection_neutral(skill),
|
|
173
|
+
10,
|
|
174
|
+
"save-context skill avoids Codex, Claude Code, and adapter-specific projection wording",
|
|
175
|
+
)
|
|
176
|
+
if adapter == "claude-code":
|
|
177
|
+
_add(
|
|
178
|
+
checks,
|
|
179
|
+
"hook_injects_facts_only",
|
|
180
|
+
_hook_injects_facts_only(settings),
|
|
181
|
+
10,
|
|
182
|
+
"SessionStart example injects date, memory, relationship, and emotion facts, not long instructions",
|
|
183
|
+
)
|
|
184
|
+
else:
|
|
185
|
+
_add(
|
|
186
|
+
checks,
|
|
187
|
+
"no_fake_claude_hook",
|
|
188
|
+
".claude/settings.local.json.example" not in adapter_pack and "SessionStart" not in entry,
|
|
189
|
+
10,
|
|
190
|
+
"non-Claude Code projection does not fake Claude Code SessionStart hook semantics",
|
|
191
|
+
)
|
|
192
|
+
_add(
|
|
193
|
+
checks,
|
|
194
|
+
"memory_skeleton_present",
|
|
195
|
+
all(item["path"] in adapter_pack for item in mechanism["memory"]["local_files"]),
|
|
196
|
+
10,
|
|
197
|
+
"adapter pack includes local memory skeleton files",
|
|
198
|
+
)
|
|
199
|
+
_add(
|
|
200
|
+
checks,
|
|
201
|
+
"memory_capacity_policy_present",
|
|
202
|
+
_memory_capacity_policy_present(mechanism, adapter_pack),
|
|
203
|
+
10,
|
|
204
|
+
"memory skeleton carries owner-based routing, profile, workstream router, project, and compatibility limits",
|
|
205
|
+
)
|
|
206
|
+
_add(
|
|
207
|
+
checks,
|
|
208
|
+
"workspace_structure_present",
|
|
209
|
+
_workspace_structure_present(mechanism, adapter_pack),
|
|
210
|
+
10,
|
|
211
|
+
"adapter pack includes workspace directories and keeps output indexing in source-map",
|
|
212
|
+
)
|
|
213
|
+
_add(
|
|
214
|
+
checks,
|
|
215
|
+
"knowledge_skeleton_present",
|
|
216
|
+
_knowledge_skeleton_present(adapter_pack, manifest, entry, adapter),
|
|
217
|
+
10,
|
|
218
|
+
"adapter pack includes a reviewed-knowledge scaffold, source manifests, and explicit loading guidance",
|
|
219
|
+
)
|
|
220
|
+
if adapter == "cursor":
|
|
221
|
+
_add(
|
|
222
|
+
checks,
|
|
223
|
+
"cursor_handoff_tool_present",
|
|
224
|
+
_cursor_handoff_tool_present(adapter_pack, manifest, entry),
|
|
225
|
+
10,
|
|
226
|
+
"Cursor pack includes target-local handoff export helper and handoff/session-brief path guidance",
|
|
227
|
+
)
|
|
228
|
+
_add(
|
|
229
|
+
checks,
|
|
230
|
+
"empty_memory_skeleton_is_user_ready",
|
|
231
|
+
_empty_memory_skeleton_is_user_ready(adapter_pack),
|
|
232
|
+
10,
|
|
233
|
+
"empty memory skeleton files avoid template placeholders and read as usable empty state",
|
|
234
|
+
)
|
|
235
|
+
if adapter == "codex":
|
|
236
|
+
_add(
|
|
237
|
+
checks,
|
|
238
|
+
"reserved_emotion_not_in_daily_codex_entry",
|
|
239
|
+
"memory/emotion-state.json.example" not in entry and "live Emotion Engine state" not in entry,
|
|
240
|
+
10,
|
|
241
|
+
"Codex daily entry keeps reserved Emotion Engine state out of normal operating prompts",
|
|
242
|
+
)
|
|
243
|
+
_add(
|
|
244
|
+
checks,
|
|
245
|
+
"emotion_specs_present",
|
|
246
|
+
_emotion_specs_present(mechanism, adapter_pack, adapter),
|
|
247
|
+
10,
|
|
248
|
+
"adapter pack includes structured Emotion Engine spec references",
|
|
249
|
+
)
|
|
250
|
+
_add(
|
|
251
|
+
checks,
|
|
252
|
+
"emotion_engine_default_light",
|
|
253
|
+
_emotion_engine_default_light(mechanism, manifest, adapter),
|
|
254
|
+
10,
|
|
255
|
+
"Emotion Engine defaults to light mode with user-visible token overhead estimates",
|
|
256
|
+
)
|
|
257
|
+
_add(
|
|
258
|
+
checks,
|
|
259
|
+
"emotion_reserved_not_runtime",
|
|
260
|
+
_emotion_reserved_not_runtime(mechanism, manifest),
|
|
261
|
+
10,
|
|
262
|
+
"Emotion Engine is placed as structured reserved state modulation, not runtime execution",
|
|
263
|
+
)
|
|
264
|
+
_add(
|
|
265
|
+
checks,
|
|
266
|
+
"reserved_runtimes_not_implemented",
|
|
267
|
+
_reserved_runtimes_not_implemented(mechanism, manifest),
|
|
268
|
+
10,
|
|
269
|
+
"reserved runtimes and specs remain explicitly non-implemented",
|
|
270
|
+
)
|
|
271
|
+
if adapter == "codex" and _emotion_engine_codex_enabled(adapter_pack, entry, manifest):
|
|
272
|
+
_add(
|
|
273
|
+
checks,
|
|
274
|
+
"emotion_engine_codex_skill_present",
|
|
275
|
+
EMOTION_ENGINE_CODEX_SKILL_PATH in adapter_pack,
|
|
276
|
+
10,
|
|
277
|
+
"Emotion Engine Codex sidecar skill is installed when enabled",
|
|
278
|
+
)
|
|
279
|
+
_add(
|
|
280
|
+
checks,
|
|
281
|
+
"emotion_engine_codex_state_present",
|
|
282
|
+
_emotion_engine_codex_state_valid(adapter_pack.get(EMOTION_ENGINE_CODEX_STATE_PATH, "")),
|
|
283
|
+
10,
|
|
284
|
+
"Emotion Engine Codex state exists as project-local runtime state when enabled",
|
|
285
|
+
)
|
|
286
|
+
_add(
|
|
287
|
+
checks,
|
|
288
|
+
"emotion_engine_codex_settle_trust_present",
|
|
289
|
+
_emotion_engine_codex_settle_trust_present(adapter_pack, entry),
|
|
290
|
+
10,
|
|
291
|
+
"Emotion Engine Codex sidecar supports conservative trust settlement",
|
|
292
|
+
)
|
|
293
|
+
_add(
|
|
294
|
+
checks,
|
|
295
|
+
"emotion_engine_codex_record_policy_present",
|
|
296
|
+
_emotion_engine_codex_record_policy_present(adapter_pack, entry),
|
|
297
|
+
10,
|
|
298
|
+
"Emotion Engine Codex sidecar supports deterministic light/always record policy",
|
|
299
|
+
)
|
|
300
|
+
_add(
|
|
301
|
+
checks,
|
|
302
|
+
"emotion_engine_codex_mcp_present",
|
|
303
|
+
_emotion_engine_codex_mcp_present(adapter_pack, manifest),
|
|
304
|
+
10,
|
|
305
|
+
"Emotion Engine Codex sidecar exposes MCP state tools without owning Packwright repair",
|
|
306
|
+
)
|
|
307
|
+
_add(
|
|
308
|
+
checks,
|
|
309
|
+
"emotion_engine_codex_project_wrapper_present",
|
|
310
|
+
_emotion_engine_codex_project_wrapper_present(adapter_pack, manifest),
|
|
311
|
+
10,
|
|
312
|
+
"project-root Emotion Engine wrapper forwards to the installed sidecar",
|
|
313
|
+
)
|
|
314
|
+
_add(
|
|
315
|
+
checks,
|
|
316
|
+
"emotion_engine_codex_entry_internal",
|
|
317
|
+
_emotion_engine_codex_entry_internal(entry),
|
|
318
|
+
10,
|
|
319
|
+
"AGENTS.md keeps Emotion Engine internals hidden from normal replies",
|
|
320
|
+
)
|
|
321
|
+
_add(
|
|
322
|
+
checks,
|
|
323
|
+
"relationship_state_not_runtime_state",
|
|
324
|
+
_relationship_state_not_runtime_state(
|
|
325
|
+
adapter_pack.get("memory/relationship-state.md", "")
|
|
326
|
+
+ "\n"
|
|
327
|
+
+ adapter_pack.get("memory/collaboration.md", "")
|
|
328
|
+
),
|
|
329
|
+
10,
|
|
330
|
+
"collaboration memory stays human-readable and does not store PAD/trust runtime JSON",
|
|
331
|
+
)
|
|
332
|
+
manifest_diagnostics = emotion_engine_codex_manifest_diagnostics(manifest)
|
|
333
|
+
_add(
|
|
334
|
+
checks,
|
|
335
|
+
"emotion_engine_codex_manifest_consistent",
|
|
336
|
+
not manifest_diagnostics,
|
|
337
|
+
10,
|
|
338
|
+
_emotion_engine_codex_diagnostic_message(
|
|
339
|
+
manifest_diagnostics,
|
|
340
|
+
"installed Emotion Engine Codex sidecar is reflected in manifest features and sidecars",
|
|
341
|
+
),
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return _result(checks, threshold)
|
|
345
|
+
def _source_files_exist(mechanism):
|
|
346
|
+
refs = [
|
|
347
|
+
mechanism["identity"]["persona_path"],
|
|
348
|
+
mechanism["identity"]["voice_path"],
|
|
349
|
+
mechanism["identity"]["relationship_path"],
|
|
350
|
+
mechanism["operating"]["principles_path"],
|
|
351
|
+
mechanism["operating"]["boundaries_path"],
|
|
352
|
+
mechanism["mechanism"]["context_loading_path"],
|
|
353
|
+
mechanism["mechanism"]["session_guards_path"],
|
|
354
|
+
mechanism["mechanism"]["memory_policy_path"],
|
|
355
|
+
mechanism["projection"]["platform_capabilities_path"],
|
|
356
|
+
mechanism["projection"]["ownership_contract_path"],
|
|
357
|
+
mechanism["emotion"]["model_path"],
|
|
358
|
+
mechanism["emotion"]["state_schema_path"],
|
|
359
|
+
mechanism["emotion"]["update_policy_path"],
|
|
360
|
+
mechanism["emotion"]["voice_modulation_path"],
|
|
361
|
+
mechanism["emotion"]["memory_events_path"],
|
|
362
|
+
]
|
|
363
|
+
refs.extend(item["path"] for item in mechanism["memory"]["local_files"])
|
|
364
|
+
refs.extend(skill["path"] for skill in mechanism["skills"])
|
|
365
|
+
return _source_files_exist_for_refs(mechanism, refs)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _source_files_exist_for_refs(mechanism, refs):
|
|
369
|
+
return all(file_exists(mechanism, ref) for ref in refs)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _adapter_from_manifest(adapter_pack, fallback):
|
|
373
|
+
manifest = _parse_json(adapter_pack.get("manifest.json", "{}"))
|
|
374
|
+
adapter = manifest.get("adapter")
|
|
375
|
+
return adapter if adapter in {"codex", "claude-code", "cursor"} else fallback
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def _entry_path(mechanism, adapter):
|
|
379
|
+
if adapter == "codex":
|
|
380
|
+
return "AGENTS.md"
|
|
381
|
+
if adapter == "claude-code":
|
|
382
|
+
return "CLAUDE.md"
|
|
383
|
+
if adapter == "cursor":
|
|
384
|
+
return f".cursor/rules/{character_slug(mechanism)}.mdc"
|
|
385
|
+
return "AGENTS.md"
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _manifest_matches_pack(manifest, adapter_pack, adapter):
|
|
389
|
+
if not isinstance(manifest, dict):
|
|
390
|
+
return False
|
|
391
|
+
all_paths = set(manifest.get("artifacts", [])) | set(adapter_pack.keys())
|
|
392
|
+
external = _manifest_external_artifacts(all_paths)
|
|
393
|
+
artifacts = set(manifest.get("artifacts", [])) - external
|
|
394
|
+
emitted = set(adapter_pack.keys()) - external
|
|
395
|
+
expected_kinds = {
|
|
396
|
+
"codex": "CodexAdapterPack",
|
|
397
|
+
"claude-code": "ClaudeCodeAdapterPack",
|
|
398
|
+
"cursor": "CursorAdapterPack",
|
|
399
|
+
}
|
|
400
|
+
expected_kind = expected_kinds.get(adapter)
|
|
401
|
+
return manifest.get("kind") == expected_kind and manifest.get("adapter") == adapter and artifacts == emitted
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def _manifest_external_artifacts(paths):
|
|
405
|
+
allowed_prefixes = (
|
|
406
|
+
f"{EMOTION_ENGINE_CODEX_SKILL_DIR}/",
|
|
407
|
+
f"{EMOTION_ENGINE_CODEX_STATE_PATH.rsplit('/', 1)[0]}/",
|
|
408
|
+
)
|
|
409
|
+
return {path for path in paths if path.startswith(allowed_prefixes)}
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def _projection_contracts_present(mechanism, adapter_pack, adapter):
|
|
413
|
+
prefix = reference_prefix(mechanism, adapter)
|
|
414
|
+
expected = [
|
|
415
|
+
f"{prefix}/projection/platform-capabilities.yaml",
|
|
416
|
+
f"{prefix}/projection/ownership-contract.yaml",
|
|
417
|
+
]
|
|
418
|
+
return all(path in adapter_pack for path in expected) and _source_files_exist_for_refs(
|
|
419
|
+
mechanism,
|
|
420
|
+
[
|
|
421
|
+
mechanism["projection"]["platform_capabilities_path"],
|
|
422
|
+
mechanism["projection"]["ownership_contract_path"],
|
|
423
|
+
],
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _ownership_contract_valid(mechanism, manifest, adapter):
|
|
428
|
+
ownership = manifest.get("ownership", {}) if isinstance(manifest, dict) else {}
|
|
429
|
+
return (
|
|
430
|
+
ownership.get("model_loop") == adapter
|
|
431
|
+
and ownership.get("thread_state") == adapter
|
|
432
|
+
and ownership.get("tools") == adapter
|
|
433
|
+
and ownership.get("durable_memory_source_of_truth") == durable_memory_source(mechanism)
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _entry_has_voice(entry):
|
|
438
|
+
return (
|
|
439
|
+
"## Voice" in entry
|
|
440
|
+
and "Say what matters first." in entry
|
|
441
|
+
and "When uncertain, name the uncertainty" in entry
|
|
442
|
+
and "When corrected, restate the corrected model" in entry
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _entry_excludes_implementation_scope(mechanism, entry):
|
|
447
|
+
forbidden = ["Packwright", "MVP", "adapter pack", "cloud service", "Do not build UI"]
|
|
448
|
+
run = mechanism.get("run", {})
|
|
449
|
+
run_values = [run.get("objective"), run.get("scope")]
|
|
450
|
+
return not any(item and item in entry for item in forbidden + run_values)
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _hook_injects_facts_only(settings_text):
|
|
454
|
+
settings = _parse_json(settings_text)
|
|
455
|
+
if not isinstance(settings, dict):
|
|
456
|
+
return False
|
|
457
|
+
hooks = settings.get("hooks", {}).get("SessionStart", [])
|
|
458
|
+
command = json.dumps(hooks)
|
|
459
|
+
required = (
|
|
460
|
+
"date",
|
|
461
|
+
"memory/index.md",
|
|
462
|
+
"memory/profile.md",
|
|
463
|
+
"memory/workstreams.md",
|
|
464
|
+
"memory/session-index.md",
|
|
465
|
+
"memory/source-map.md",
|
|
466
|
+
"memory/todos.md",
|
|
467
|
+
"memory/collaboration.md",
|
|
468
|
+
"memory/emotion-state.json.example",
|
|
469
|
+
)
|
|
470
|
+
forbidden = ("You are", "Always", "Never", "Workflow", "implement")
|
|
471
|
+
return all(item in command for item in required) and not any(item in command for item in forbidden)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _emotion_specs_present(mechanism, adapter_pack, adapter):
|
|
475
|
+
prefix = reference_prefix(mechanism, adapter)
|
|
476
|
+
expected = [
|
|
477
|
+
f"{prefix}/emotion/model.yaml",
|
|
478
|
+
f"{prefix}/emotion/state-schema.yaml",
|
|
479
|
+
f"{prefix}/emotion/update-policy.yaml",
|
|
480
|
+
f"{prefix}/emotion/voice-modulation.yaml",
|
|
481
|
+
f"{prefix}/emotion/memory-events.yaml",
|
|
482
|
+
"memory/collaboration.md",
|
|
483
|
+
"memory/emotion-state.json.example",
|
|
484
|
+
]
|
|
485
|
+
return all(path in adapter_pack for path in expected) and mechanism["emotion"]["status"] == "structured_reserved"
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _foundation_mechanisms_not_projected_as_skills(adapter_pack):
|
|
489
|
+
forbidden = (
|
|
490
|
+
".agents/skills/atlas-recent-activity/SKILL.md",
|
|
491
|
+
".agents/skills/atlas-fact-check/SKILL.md",
|
|
492
|
+
".agents/skills/atlas-work/SKILL.md",
|
|
493
|
+
".agents/skills/atlas-work/references/source-skills/recent-activity/SKILL.md",
|
|
494
|
+
".agents/skills/atlas-work/references/source-skills/fact-check/SKILL.md",
|
|
495
|
+
".claude/skills/atlas-recent-activity/SKILL.md",
|
|
496
|
+
".claude/skills/atlas-fact-check/SKILL.md",
|
|
497
|
+
".claude/skills/atlas-work/SKILL.md",
|
|
498
|
+
".claude/skills/atlas-work/references/source-skills/recent-activity/SKILL.md",
|
|
499
|
+
".claude/skills/atlas-work/references/source-skills/fact-check/SKILL.md",
|
|
500
|
+
)
|
|
501
|
+
if any(path in adapter_pack for path in forbidden):
|
|
502
|
+
return False
|
|
503
|
+
projected_foundation_suffixes = (
|
|
504
|
+
"recent-activity/SKILL.md",
|
|
505
|
+
"fact-check/SKILL.md",
|
|
506
|
+
)
|
|
507
|
+
return not any(path.endswith(projected_foundation_suffixes) for path in adapter_pack)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def _entry_uses_runtime_appropriate_links(entry, adapter, skill_path):
|
|
511
|
+
daily_memory_paths = (
|
|
512
|
+
"memory/index.md",
|
|
513
|
+
"memory/profile.md",
|
|
514
|
+
"memory/workstreams.md",
|
|
515
|
+
"memory/session-index.md",
|
|
516
|
+
"memory/source-map.md",
|
|
517
|
+
"memory/todos.md",
|
|
518
|
+
"memory/collaboration.md",
|
|
519
|
+
)
|
|
520
|
+
if adapter == "codex":
|
|
521
|
+
return (
|
|
522
|
+
"## Use When Needed" in entry
|
|
523
|
+
and f"`{skill_path}`" in entry
|
|
524
|
+
and "@" not in entry
|
|
525
|
+
and all(f"`{path}`" in entry for path in daily_memory_paths)
|
|
526
|
+
and "`memory/emotion-state.json.example`" not in entry
|
|
527
|
+
)
|
|
528
|
+
if adapter == "cursor":
|
|
529
|
+
return (
|
|
530
|
+
"## Use When Needed" in entry
|
|
531
|
+
and f"`{skill_path}`" in entry
|
|
532
|
+
and all(f"`{path}`" in entry for path in daily_memory_paths)
|
|
533
|
+
and "`memory/emotion-state.json.example`" not in entry
|
|
534
|
+
)
|
|
535
|
+
memory_paths = (*daily_memory_paths, "memory/emotion-state.json.example")
|
|
536
|
+
return (
|
|
537
|
+
"## Load When Needed" in entry
|
|
538
|
+
and f"@{skill_path}" in entry
|
|
539
|
+
and all(f"@{path}" in entry for path in memory_paths)
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def _on_demand_references_have_purpose(entry, adapter):
|
|
544
|
+
heading = "## Load When Needed" if adapter == "claude-code" else "## Use When Needed"
|
|
545
|
+
lines = _section_bullets(entry, heading)
|
|
546
|
+
minimum = 11 if adapter == "claude-code" else 10
|
|
547
|
+
if len(lines) < minimum:
|
|
548
|
+
return False
|
|
549
|
+
if adapter in {"codex", "cursor"}:
|
|
550
|
+
return all((" for " in line or " when " in line or " only as " in line) for line in lines)
|
|
551
|
+
return all(": " in line and line.split(": ", 1)[1].strip() for line in lines)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
def _section_bullets(text, heading):
|
|
555
|
+
in_section = False
|
|
556
|
+
bullets = []
|
|
557
|
+
for line in text.splitlines():
|
|
558
|
+
if line.strip() == heading:
|
|
559
|
+
in_section = True
|
|
560
|
+
continue
|
|
561
|
+
if in_section and line.startswith("## "):
|
|
562
|
+
break
|
|
563
|
+
if in_section and line.startswith("- "):
|
|
564
|
+
bullets.append(line)
|
|
565
|
+
return bullets
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def _save_context_skill_projection_neutral(skill):
|
|
569
|
+
forbidden = ("Codex", "Claude", "Cursor", "Projection Notes", "adapter pack", ".codex", ".claude", ".cursor")
|
|
570
|
+
return not any(item in skill for item in forbidden)
|
|
571
|
+
|
|
572
|
+
|
|
573
|
+
def _empty_memory_skeleton_is_user_ready(adapter_pack):
|
|
574
|
+
index = adapter_pack.get("memory/index.md", "")
|
|
575
|
+
profile = adapter_pack.get("memory/profile.md", "")
|
|
576
|
+
session_index = adapter_pack.get("memory/session-index.md", "")
|
|
577
|
+
source_map = adapter_pack.get("memory/source-map.md", "")
|
|
578
|
+
collaboration = adapter_pack.get("memory/collaboration.md", "")
|
|
579
|
+
pinned = adapter_pack.get("memory/pinned.md", "")
|
|
580
|
+
workstreams = adapter_pack.get("memory/workstreams.md", "")
|
|
581
|
+
workstream_template = adapter_pack.get("memory/workstreams/_template.md", "")
|
|
582
|
+
recent = adapter_pack.get("memory/recent-activity.md", "")
|
|
583
|
+
todos = adapter_pack.get("memory/todos.md", "")
|
|
584
|
+
knowledge_map = adapter_pack.get("memory/knowledge_map.md", "")
|
|
585
|
+
project_template = adapter_pack.get("memory/projects/_template.md", "")
|
|
586
|
+
relationship_state = adapter_pack.get("memory/relationship-state.md", "")
|
|
587
|
+
workspace_readme = adapter_pack.get("workspace/README.md", "")
|
|
588
|
+
memory_text = "\n".join(
|
|
589
|
+
(
|
|
590
|
+
index,
|
|
591
|
+
profile,
|
|
592
|
+
session_index,
|
|
593
|
+
source_map,
|
|
594
|
+
collaboration,
|
|
595
|
+
pinned,
|
|
596
|
+
workstreams,
|
|
597
|
+
workstream_template,
|
|
598
|
+
recent,
|
|
599
|
+
todos,
|
|
600
|
+
knowledge_map,
|
|
601
|
+
project_template,
|
|
602
|
+
relationship_state,
|
|
603
|
+
workspace_readme,
|
|
604
|
+
)
|
|
605
|
+
)
|
|
606
|
+
forbidden = ("template skeleton", "Add newest pickup entries below this line")
|
|
607
|
+
required = (
|
|
608
|
+
"This is the default memory router.",
|
|
609
|
+
"This file stores stable profile facts",
|
|
610
|
+
"No pinned memory has been recorded yet.",
|
|
611
|
+
"domain router",
|
|
612
|
+
"Agent Promotion",
|
|
613
|
+
"No pickup entries have been recorded yet.",
|
|
614
|
+
"newest 20",
|
|
615
|
+
"No current todos have been recorded yet.",
|
|
616
|
+
"not a knowledge base by itself",
|
|
617
|
+
"No project state has been recorded yet.",
|
|
618
|
+
"No relationship continuity notes have been recorded yet.",
|
|
619
|
+
"Use this directory for generated work products",
|
|
620
|
+
)
|
|
621
|
+
usable_state = (
|
|
622
|
+
("No active projects have been recorded yet." in index or "## Active Projects" in index)
|
|
623
|
+
and ("No session index entries have been recorded yet." in session_index or "<!-- entries -->" in session_index)
|
|
624
|
+
and (
|
|
625
|
+
"No source mappings have been recorded yet." in source_map
|
|
626
|
+
or "## Sources" in source_map
|
|
627
|
+
or "## Packwright Sources" in source_map
|
|
628
|
+
or "## Project Memory" in source_map
|
|
629
|
+
)
|
|
630
|
+
and ("No collaboration calibrations have been recorded yet." in collaboration or "## Current Calibrations" in collaboration)
|
|
631
|
+
)
|
|
632
|
+
return (
|
|
633
|
+
all(item in memory_text for item in required)
|
|
634
|
+
and usable_state
|
|
635
|
+
and not any(item in memory_text for item in forbidden)
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def _memory_capacity_policy_present(mechanism, adapter_pack):
|
|
640
|
+
limits = mechanism.get("memory", {}).get("limits", {})
|
|
641
|
+
expected = {
|
|
642
|
+
"pinned_items": 20,
|
|
643
|
+
"recent_activity_hot_entries": 20,
|
|
644
|
+
"session_index_entries": 20,
|
|
645
|
+
"workstream_summary_bullets": 7,
|
|
646
|
+
"project_summary_lines": 12,
|
|
647
|
+
"workspace_artifact_index_entries": 50,
|
|
648
|
+
}
|
|
649
|
+
if any(limits.get(key) != value for key, value in expected.items()):
|
|
650
|
+
return False
|
|
651
|
+
combined = "\n".join(
|
|
652
|
+
adapter_pack.get(path, "")
|
|
653
|
+
for path in (
|
|
654
|
+
"memory/index.md",
|
|
655
|
+
"memory/profile.md",
|
|
656
|
+
"memory/session-index.md",
|
|
657
|
+
"memory/source-map.md",
|
|
658
|
+
"memory/collaboration.md",
|
|
659
|
+
"memory/pinned.md",
|
|
660
|
+
"memory/workstreams.md",
|
|
661
|
+
"memory/workstreams/_template.md",
|
|
662
|
+
"memory/recent-activity.md",
|
|
663
|
+
"memory/projects/_template.md",
|
|
664
|
+
"workspace/README.md",
|
|
665
|
+
)
|
|
666
|
+
)
|
|
667
|
+
required = (
|
|
668
|
+
"default memory router",
|
|
669
|
+
"stable profile facts",
|
|
670
|
+
"domain router",
|
|
671
|
+
"newest 20",
|
|
672
|
+
"source registry",
|
|
673
|
+
"stable collaboration calibrations",
|
|
674
|
+
"compatibility",
|
|
675
|
+
"12 lines or fewer",
|
|
676
|
+
"generated work products",
|
|
677
|
+
)
|
|
678
|
+
return all(item in combined for item in required)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _workspace_structure_present(mechanism, adapter_pack):
|
|
682
|
+
workspace = mechanism.get("workspace", {})
|
|
683
|
+
required = set(workspace_artifacts())
|
|
684
|
+
readme = adapter_pack.get("workspace/README.md", "")
|
|
685
|
+
return (
|
|
686
|
+
workspace.get("root") == WORKSPACE_ROOT
|
|
687
|
+
and workspace.get("layout") == WORKSPACE_LAYOUT
|
|
688
|
+
and workspace.get("index_owner") == WORKSPACE_INDEX_OWNER
|
|
689
|
+
and required.issubset(adapter_pack.keys())
|
|
690
|
+
and all(marker in readme for marker in workspace_readme_required_markers())
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
|
|
694
|
+
def _knowledge_skeleton_present(adapter_pack, manifest, entry, adapter):
|
|
695
|
+
artifacts = set(manifest.get("artifacts", [])) if isinstance(manifest, dict) else set()
|
|
696
|
+
feature = manifest.get("features", {}).get("knowledge", {}) if isinstance(manifest, dict) else {}
|
|
697
|
+
required = set(knowledge_artifacts())
|
|
698
|
+
source_json_ok = all(isinstance(_parse_json(adapter_pack.get(path, "")), dict) for path in SOURCE_MANIFESTS)
|
|
699
|
+
manifest_json = _parse_json(adapter_pack.get(KNOWLEDGE_MANIFEST, ""))
|
|
700
|
+
if adapter == "claude-code":
|
|
701
|
+
entry_mentions = f"@{KNOWLEDGE_INDEX}" in entry and "@sources/local/manifest.json" in entry
|
|
702
|
+
else:
|
|
703
|
+
entry_mentions = f"`{KNOWLEDGE_INDEX}`" in entry and "`sources/*/manifest.json`" in entry
|
|
704
|
+
return (
|
|
705
|
+
required.issubset(adapter_pack.keys())
|
|
706
|
+
and required.issubset(artifacts)
|
|
707
|
+
and feature.get("root") == "knowledge"
|
|
708
|
+
and feature.get("recall_index") == KNOWLEDGE_INDEX
|
|
709
|
+
and feature.get("manifest") == KNOWLEDGE_MANIFEST
|
|
710
|
+
and feature.get("sources_root") == "sources"
|
|
711
|
+
and adapter_pack.get(KNOWLEDGE_INDEX, "").startswith("# Knowledge Recall Index")
|
|
712
|
+
and isinstance(manifest_json, dict)
|
|
713
|
+
and manifest_json.get("schema") == "packwright-knowledge-manifest/v1"
|
|
714
|
+
and source_json_ok
|
|
715
|
+
and entry_mentions
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
def _cursor_handoff_tool_present(adapter_pack, manifest, entry):
|
|
720
|
+
helper = adapter_pack.get(HANDOFF_HELPER_PATH, "")
|
|
721
|
+
wrapper = adapter_pack.get(HANDOFF_WRAPPER_PATH, "")
|
|
722
|
+
artifacts = set(manifest.get("artifacts", [])) if isinstance(manifest, dict) else set()
|
|
723
|
+
feature = manifest.get("features", {}).get("handoff", {}) if isinstance(manifest, dict) else {}
|
|
724
|
+
local_tool = manifest.get("local_tools", {}).get("handoff_export", {}) if isinstance(manifest, dict) else {}
|
|
725
|
+
return (
|
|
726
|
+
HANDOFF_HELPER_PATH in adapter_pack
|
|
727
|
+
and HANDOFF_WRAPPER_PATH in adapter_pack
|
|
728
|
+
and HANDOFF_HELPER_PATH in artifacts
|
|
729
|
+
and HANDOFF_WRAPPER_PATH in artifacts
|
|
730
|
+
and HANDOFF_SCHEMA in helper
|
|
731
|
+
and DEFAULT_HANDOFF_DIR in helper
|
|
732
|
+
and DEFAULT_SESSION_BRIEF_DIR in helper
|
|
733
|
+
and HANDOFF_HELPER_PATH.rsplit("/", 1)[-1] in wrapper
|
|
734
|
+
and feature.get("schema") == HANDOFF_SCHEMA
|
|
735
|
+
and feature.get("command") == HANDOFF_WRAPPER_PATH
|
|
736
|
+
and feature.get("default_handoff_dir") == DEFAULT_HANDOFF_DIR
|
|
737
|
+
and feature.get("session_brief_dir") == DEFAULT_SESSION_BRIEF_DIR
|
|
738
|
+
and local_tool.get("schema") == HANDOFF_SCHEMA
|
|
739
|
+
and local_tool.get("command") == HANDOFF_WRAPPER_PATH
|
|
740
|
+
and DEFAULT_HANDOFF_DIR in entry
|
|
741
|
+
and DEFAULT_SESSION_BRIEF_DIR in entry
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
def _emotion_reserved_not_runtime(mechanism, manifest):
|
|
746
|
+
boundaries = manifest.get("boundaries", {}) if isinstance(manifest, dict) else {}
|
|
747
|
+
runtime = boundaries.get("emotion_engine_runtime")
|
|
748
|
+
return (
|
|
749
|
+
mechanism["emotion"].get("status") == "structured_reserved"
|
|
750
|
+
and mechanism["emotion"].get("runtime") == "not_implemented"
|
|
751
|
+
and runtime in {False, EMOTION_ENGINE_AVAILABLE_RUNTIME, EMOTION_ENGINE_RUNTIME, EMOTION_ENGINE_CLAUDE_RUNTIME}
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def _emotion_engine_default_light(mechanism, manifest, adapter):
|
|
756
|
+
emotion = mechanism.get("emotion", {})
|
|
757
|
+
feature = manifest.get("features", {}).get("emotion_engine", {}) if isinstance(manifest, dict) else {}
|
|
758
|
+
overhead = feature.get("estimated_overhead", {})
|
|
759
|
+
return (
|
|
760
|
+
emotion.get("default_mode") == "light"
|
|
761
|
+
and feature.get("default_mode") == "light"
|
|
762
|
+
and feature.get("mode") in EMOTION_ENGINE_MODES
|
|
763
|
+
and feature.get("adapter") == adapter
|
|
764
|
+
and "light" in overhead
|
|
765
|
+
and "always" in overhead
|
|
766
|
+
and "<1%" in overhead.get("light", "")
|
|
767
|
+
and "<=5%" in overhead.get("always", "")
|
|
768
|
+
)
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def _reserved_runtimes_not_implemented(mechanism, manifest):
|
|
772
|
+
reserved_targets = mechanism.get("targets", {}).get("reserved", {})
|
|
773
|
+
reserved_specs = mechanism.get("reserved_specs", {})
|
|
774
|
+
manifest_boundaries = manifest.get("boundaries", {}) if isinstance(manifest, dict) else {}
|
|
775
|
+
specs_ok = all(
|
|
776
|
+
spec.get("status") in {"reserved", "structured_reserved"} and spec.get("runtime") == "not_implemented"
|
|
777
|
+
for spec in reserved_specs.values()
|
|
778
|
+
)
|
|
779
|
+
targets_ok = all(spec.get("status") == "reserved" for spec in reserved_targets.values())
|
|
780
|
+
manifest_ok = manifest_boundaries.get("is_runtime_executor") is False and manifest_boundaries.get("implements_cloud") is False
|
|
781
|
+
return specs_ok and targets_ok and manifest_ok
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def _emotion_engine_codex_enabled(adapter_pack, entry, manifest):
|
|
785
|
+
return (
|
|
786
|
+
emotion_engine_codex_expected(manifest, adapter_pack)
|
|
787
|
+
or "## Emotion Engine" in entry
|
|
788
|
+
or "## Optional Emotion Engine" in entry
|
|
789
|
+
)
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
def _emotion_engine_codex_state_valid(text):
|
|
793
|
+
state = _parse_json(text)
|
|
794
|
+
return (
|
|
795
|
+
isinstance(state, dict)
|
|
796
|
+
and state.get("_schema") == "emotion-engine-state/v2"
|
|
797
|
+
and isinstance(state.get("character_profile"), dict)
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
def _emotion_engine_codex_entry_internal(entry):
|
|
802
|
+
forbidden = (
|
|
803
|
+
'"pleasure"',
|
|
804
|
+
'"arousal"',
|
|
805
|
+
'"dominance"',
|
|
806
|
+
'"trust"',
|
|
807
|
+
"pleasure:",
|
|
808
|
+
"arousal:",
|
|
809
|
+
"dominance:",
|
|
810
|
+
"trust_history",
|
|
811
|
+
"emotion_trajectory",
|
|
812
|
+
)
|
|
813
|
+
return (
|
|
814
|
+
("## Emotion Engine" in entry or "## Optional Emotion Engine" in entry)
|
|
815
|
+
and EMOTION_ENGINE_CODEX_SKILL_PATH in entry
|
|
816
|
+
and EMOTION_ENGINE_CODEX_STATE_PATH in entry
|
|
817
|
+
and "settle_trust" in entry
|
|
818
|
+
and "record_policy" in entry
|
|
819
|
+
and not any(item in entry for item in forbidden)
|
|
820
|
+
)
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def _emotion_engine_codex_settle_trust_present(adapter_pack, entry):
|
|
824
|
+
skill = adapter_pack.get(EMOTION_ENGINE_CODEX_SKILL_PATH, "")
|
|
825
|
+
helper = adapter_pack.get(EMOTION_ENGINE_CODEX_HELPER_PATH, "")
|
|
826
|
+
wrapper = adapter_pack.get(EMOTION_ENGINE_CODEX_SCRIPT_PATH, "")
|
|
827
|
+
return (
|
|
828
|
+
"settle_trust" in entry
|
|
829
|
+
and "settle_trust" in skill
|
|
830
|
+
and "settle_trust" in helper
|
|
831
|
+
and 'exec "$PYTHON" "$ENGINE" "$COMMAND" "$STATE_FILE"' in wrapper
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def _emotion_engine_codex_record_policy_present(adapter_pack, entry):
|
|
836
|
+
skill = adapter_pack.get(EMOTION_ENGINE_CODEX_SKILL_PATH, "")
|
|
837
|
+
helper = adapter_pack.get(EMOTION_ENGINE_CODEX_HELPER_PATH, "")
|
|
838
|
+
return (
|
|
839
|
+
"record_policy" in entry
|
|
840
|
+
and "record_policy" in skill
|
|
841
|
+
and "Runtime Modes And Record Policy" in skill
|
|
842
|
+
and "record_policy" in helper
|
|
843
|
+
and "parse_record_policy_args" in helper
|
|
844
|
+
and "reply_bias" in helper
|
|
845
|
+
and '"decision"' in helper
|
|
846
|
+
and "generic_praise_habituated" in helper
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
def _emotion_engine_codex_mcp_present(adapter_pack, manifest):
|
|
851
|
+
mcp = adapter_pack.get(EMOTION_ENGINE_CODEX_MCP_PATH, "")
|
|
852
|
+
registration = adapter_pack.get(EMOTION_ENGINE_CODEX_MCP_REGISTRATION_PATH, "")
|
|
853
|
+
artifacts = set(manifest.get("artifacts", [])) if isinstance(manifest, dict) else set()
|
|
854
|
+
return (
|
|
855
|
+
EMOTION_ENGINE_CODEX_MCP_PATH in adapter_pack
|
|
856
|
+
and EMOTION_ENGINE_CODEX_MCP_REGISTRATION_PATH in adapter_pack
|
|
857
|
+
and EMOTION_ENGINE_CODEX_MCP_PATH in artifacts
|
|
858
|
+
and EMOTION_ENGINE_CODEX_MCP_REGISTRATION_PATH in artifacts
|
|
859
|
+
and "tools/list" in mcp
|
|
860
|
+
and "emotion_engine_record_policy" in mcp
|
|
861
|
+
and "codex" in registration
|
|
862
|
+
and "state" in registration
|
|
863
|
+
and "emotion_engine_repair" not in mcp
|
|
864
|
+
and "doctor_target" not in mcp
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _emotion_engine_codex_project_wrapper_present(adapter_pack, manifest):
|
|
869
|
+
wrapper = adapter_pack.get(EMOTION_ENGINE_CODEX_WRAPPER_PATH, "")
|
|
870
|
+
artifacts = set(manifest.get("artifacts", [])) if isinstance(manifest, dict) else set()
|
|
871
|
+
return (
|
|
872
|
+
EMOTION_ENGINE_CODEX_WRAPPER_PATH in adapter_pack
|
|
873
|
+
and EMOTION_ENGINE_CODEX_WRAPPER_PATH in artifacts
|
|
874
|
+
and EMOTION_ENGINE_CODEX_SCRIPT_PATH in wrapper
|
|
875
|
+
)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def _relationship_state_not_runtime_state(text):
|
|
879
|
+
forbidden = (
|
|
880
|
+
'"pleasure"',
|
|
881
|
+
'"arousal"',
|
|
882
|
+
'"dominance"',
|
|
883
|
+
'"trust"',
|
|
884
|
+
"trust_history",
|
|
885
|
+
"emotion_trajectory",
|
|
886
|
+
"emotion-engine-state/v2",
|
|
887
|
+
)
|
|
888
|
+
return not any(item in text for item in forbidden)
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
def _emotion_engine_codex_diagnostic_message(diagnostics, fallback):
|
|
892
|
+
if not diagnostics:
|
|
893
|
+
return fallback
|
|
894
|
+
return "; ".join(issue["message"] for issue in diagnostics)
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
def _parse_json(text):
|
|
898
|
+
try:
|
|
899
|
+
return json.loads(text)
|
|
900
|
+
except (TypeError, json.JSONDecodeError):
|
|
901
|
+
return {}
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
def _add(checks, check_id, passed, weight, message):
|
|
905
|
+
checks.append(
|
|
906
|
+
{
|
|
907
|
+
"id": check_id,
|
|
908
|
+
"passed": bool(passed),
|
|
909
|
+
"weight": weight,
|
|
910
|
+
"message": message,
|
|
911
|
+
}
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def _result(checks, threshold):
|
|
916
|
+
total = sum(check["weight"] for check in checks)
|
|
917
|
+
earned = sum(check["weight"] for check in checks if check["passed"])
|
|
918
|
+
score = round((earned / total) * 100, 2) if total else 0.0
|
|
919
|
+
return {
|
|
920
|
+
"score": score,
|
|
921
|
+
"threshold": threshold,
|
|
922
|
+
"passed": score >= threshold and all(check["passed"] for check in checks),
|
|
923
|
+
"checks": checks,
|
|
924
|
+
}
|