44from __future__ import annotations
55
66import argparse
7+ import hashlib
78import json
89import os
910import re
@@ -37,12 +38,31 @@ def prepare_updates(root: Path, version: str) -> list[tuple[Path, str, int]]:
3738 raise ValueError (f"invalid strict semantic version: { version } " )
3839
3940 owned = root / "plugins" / "gestalt" / ".codex-plugin" / "plugin.json"
40- manifests = [owned ] if owned .exists () else sorted ((root / "plugins" ).glob ("*/.codex-plugin/plugin.json" ))
41+ context_root = root / "plugins" / "context-mode"
42+ context_manifest = context_root / ".codex-plugin" / "plugin.json"
43+ context_package = context_root / "package.json"
44+ context_provenance = context_root / "UPSTREAM.md"
45+ checksum_fixture = (
46+ root
47+ / "tests"
48+ / "plugins"
49+ / "context-mode"
50+ / "fixtures"
51+ / "context-mode-codex-hardening-4b1348d.sha256"
52+ )
53+ repository_layout = owned .exists ()
54+ manifests = (
55+ [owned , context_manifest , context_package ]
56+ if repository_layout
57+ else sorted ((root / "plugins" ).glob ("*/.codex-plugin/plugin.json" ))
58+ )
4159 manifests = [path for path in manifests if path .is_file () and not path .is_symlink ()]
42- if not manifests :
60+ expected_manifest_count = 3 if repository_layout else 1
61+ if len (manifests ) < expected_manifest_count :
4362 raise ValueError ("no Dyne-owned plugin manifests found" )
4463
45- updates = []
64+ updates : list [tuple [Path , str , int ]] = []
65+ context_replacements : dict [str , bytes ] = {}
4666 for path in manifests :
4767 try :
4868 manifest = json .loads (path .read_text (encoding = "utf-8" ))
@@ -53,6 +73,55 @@ def prepare_updates(root: Path, version: str) -> list[tuple[Path, str, int]]:
5373 manifest ["version" ] = version
5474 serialized = json .dumps (manifest , indent = 2 , ensure_ascii = False ) + "\n "
5575 updates .append ((path , serialized , stat .S_IMODE (path .stat ().st_mode )))
76+ if repository_layout and path .is_relative_to (context_root ):
77+ context_replacements [str (path .relative_to (context_root ))] = serialized .encode ()
78+
79+ if repository_layout :
80+ try :
81+ provenance = context_provenance .read_text (encoding = "utf-8" )
82+ except OSError as error :
83+ raise ValueError (f"cannot read { context_provenance } : { error } " ) from error
84+ provenance , replacements = re .subn (
85+ r"(?m)^- Downstream package version: `[^`]+`$" ,
86+ f"- Downstream package version: `{ version } `" ,
87+ provenance ,
88+ )
89+ if replacements != 1 :
90+ raise ValueError (f"expected one downstream package version in { context_provenance } " )
91+ updates .append (
92+ (context_provenance , provenance , stat .S_IMODE (context_provenance .stat ().st_mode ))
93+ )
94+ context_replacements ["UPSTREAM.md" ] = provenance .encode ()
95+
96+ try :
97+ fixture_lines = checksum_fixture .read_text (encoding = "utf-8" ).splitlines ()
98+ except OSError as error :
99+ raise ValueError (f"cannot read { checksum_fixture } : { error } " ) from error
100+ fixture_targets = set (context_replacements )
101+ fixture_seen : set [str ] = set ()
102+ rewritten_fixture : list [str ] = []
103+ for line in fixture_lines :
104+ try :
105+ mode , remainder = line .split (" " , 1 )
106+ _ , relative = remainder .split (" " , 1 )
107+ except ValueError as error :
108+ raise ValueError (f"malformed checksum fixture line: { line } " ) from error
109+ replacement = context_replacements .get (relative )
110+ if replacement is not None :
111+ digest = hashlib .sha256 (replacement ).hexdigest ()
112+ line = f"{ mode } { digest } { relative } "
113+ fixture_seen .add (relative )
114+ rewritten_fixture .append (line )
115+ if fixture_seen != fixture_targets :
116+ missing = sorted (fixture_targets - fixture_seen )
117+ raise ValueError (f"checksum fixture is missing versioned paths: { missing } " )
118+ updates .append (
119+ (
120+ checksum_fixture ,
121+ "\n " .join (rewritten_fixture ) + "\n " ,
122+ stat .S_IMODE (checksum_fixture .stat ().st_mode ),
123+ )
124+ )
56125 return updates
57126
58127
0 commit comments