@lark-apaas/coding-steering 0.1.31 → 0.1.32-beta.0

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,75 @@
1
+ #!/usr/bin/env python3
2
+ """Export a markdown-only consumer attachment for generation agents.
3
+
4
+ The full style package can still be zipped for audit/storage. This exporter is
5
+ for the runtime generation path: one plain markdown file with design.md and
6
+ layouts.md inlined, so attachment preprocessing does not enter archive handling.
7
+ """
8
+ import argparse
9
+ import os
10
+ import sys
11
+
12
+
13
+ def fail(msg):
14
+ print('export_consumer_md.py: %s' % msg, file=sys.stderr)
15
+ sys.exit(1)
16
+
17
+
18
+ def read_text(path, required=True):
19
+ if not os.path.exists(path):
20
+ if required:
21
+ fail('missing %s' % path)
22
+ return ''
23
+ with open(path, 'r', encoding='utf-8') as f:
24
+ return f.read()
25
+
26
+
27
+ def main():
28
+ ap = argparse.ArgumentParser()
29
+ ap.add_argument('pack_dir')
30
+ ap.add_argument('out_md')
31
+ args = ap.parse_args()
32
+
33
+ pack = os.path.abspath(args.pack_dir)
34
+ out = os.path.abspath(args.out_md)
35
+ if not os.path.isdir(pack):
36
+ fail('pack_dir not found: %s' % pack)
37
+
38
+ design = read_text(os.path.join(pack, 'design.md'))
39
+ layouts = read_text(os.path.join(pack, 'layouts.md'), required=False)
40
+ manifest = read_text(os.path.join(pack, 'manifest.json'), required=False)
41
+
42
+ content = [
43
+ '# Miaoda PPT Style Consumer Attachment',
44
+ '',
45
+ '> Runtime rule: this is a plain markdown style attachment. Use the text',
46
+ '> below directly. Do not invoke archive/zip preprocessing, do not search',
47
+ '> `.agent/*/attachments`, do not repair files, and do not read any other',
48
+ '> style package files. Start building after this document is read once.',
49
+ '',
50
+ '## design.md',
51
+ '',
52
+ design.rstrip(),
53
+ ]
54
+ if layouts:
55
+ content += ['', '## layouts.md', '', layouts.rstrip()]
56
+ if manifest:
57
+ content += [
58
+ '',
59
+ '## manifest.json',
60
+ '',
61
+ '```json',
62
+ manifest.rstrip(),
63
+ '```',
64
+ ]
65
+ content.append('')
66
+
67
+ os.makedirs(os.path.dirname(out), exist_ok=True)
68
+ with open(out, 'w', encoding='utf-8') as f:
69
+ f.write('\n'.join(content))
70
+
71
+ print('markdown -> %s (%d bytes)' % (out, os.path.getsize(out)))
72
+
73
+
74
+ if __name__ == '__main__':
75
+ main()
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env python3
2
+ """Export a consumer-only style package zip.
3
+
4
+ The zip keeps only files needed by the generation agent. Every entry is stored
5
+ without compression and the first entry is a plain-text guide containing the
6
+ fast path plus the core markdown, so attachment text extraction can surface the
7
+ rules without asking the agent to repair or parse binary zip bytes.
8
+ """
9
+ import argparse
10
+ import hashlib
11
+ import json
12
+ import os
13
+ import shutil
14
+ import sys
15
+ import zipfile
16
+
17
+
18
+ KEEP_ROOT = {'design.md', 'layouts.md', 'manifest.json'}
19
+
20
+
21
+ def fail(msg):
22
+ print('export_consumer_zip.py: %s' % msg, file=sys.stderr)
23
+ sys.exit(1)
24
+
25
+
26
+ def sha256_file(path):
27
+ h = hashlib.sha256()
28
+ with open(path, 'rb') as f:
29
+ for chunk in iter(lambda: f.read(1024 * 1024), b''):
30
+ h.update(chunk)
31
+ return h.hexdigest()
32
+
33
+
34
+ def copy_tree(src, dst, include_assets=True):
35
+ if os.path.exists(dst):
36
+ shutil.rmtree(dst)
37
+ os.makedirs(dst)
38
+ for name in KEEP_ROOT:
39
+ src_path = os.path.join(src, name)
40
+ if os.path.exists(src_path):
41
+ shutil.copy2(src_path, os.path.join(dst, name))
42
+ assets_src = os.path.join(src, 'assets')
43
+ if include_assets and os.path.isdir(assets_src):
44
+ shutil.copytree(assets_src, os.path.join(dst, 'assets'))
45
+ if not os.path.exists(os.path.join(dst, 'design.md')):
46
+ fail('missing design.md in %s' % src)
47
+
48
+
49
+ def rel_files(root):
50
+ out = []
51
+ for base, dirs, files in os.walk(root):
52
+ dirs[:] = sorted(d for d in dirs if d != 'ref')
53
+ for name in sorted(files):
54
+ path = os.path.join(base, name)
55
+ rel = os.path.relpath(path, root)
56
+ out.append(rel)
57
+ return out
58
+
59
+
60
+ def rewrite_manifest(root):
61
+ manifest_path = os.path.join(root, 'manifest.json')
62
+ if not os.path.exists(manifest_path):
63
+ return
64
+ with open(manifest_path, 'r', encoding='utf-8') as f:
65
+ manifest = json.load(f)
66
+ files = []
67
+ for rel in rel_files(root):
68
+ if rel == 'manifest.json':
69
+ continue
70
+ path = os.path.join(root, rel)
71
+ files.append({
72
+ 'path': rel,
73
+ 'bytes': os.path.getsize(path),
74
+ 'sha256': sha256_file(path),
75
+ })
76
+ digest = hashlib.sha256()
77
+ for item in files:
78
+ digest.update(item['path'].encode('utf-8'))
79
+ digest.update(item['sha256'].encode('ascii'))
80
+ manifest['files'] = files
81
+ manifest['totals'] = {
82
+ 'file_count': len(files),
83
+ 'bytes': sum(x['bytes'] for x in files),
84
+ 'sha256': digest.hexdigest(),
85
+ 'asset_count': len(manifest.get('assets') or []),
86
+ }
87
+ manifest['consumerOnly'] = True
88
+ manifest['ref'] = None
89
+ with open(manifest_path, 'w', encoding='utf-8') as f:
90
+ json.dump(manifest, f, ensure_ascii=False, indent=2)
91
+ f.write('\n')
92
+
93
+
94
+ def extract_fast_path(design):
95
+ lines = design.splitlines()
96
+ start = None
97
+ end = None
98
+ for i, line in enumerate(lines):
99
+ if line.strip() == '## Agent Fast Path':
100
+ start = i
101
+ continue
102
+ if start is not None and i > start and line.startswith('## '):
103
+ end = i
104
+ break
105
+ if start is None:
106
+ return ''
107
+ return '\n'.join(lines[start:end]) + '\n'
108
+
109
+
110
+ def build_prefix(root):
111
+ with open(os.path.join(root, 'design.md'), 'r', encoding='utf-8') as f:
112
+ design = f.read()
113
+ layouts_path = os.path.join(root, 'layouts.md')
114
+ layouts = ''
115
+ if os.path.exists(layouts_path):
116
+ with open(layouts_path, 'r', encoding='utf-8') as f:
117
+ layouts = f.read()
118
+ fast = extract_fast_path(design)
119
+ layout_excerpt = layouts[:12000]
120
+ if len(layouts) > len(layout_excerpt):
121
+ layout_excerpt += '\n<!-- layouts.md truncated in text prefix; full file is in zip. -->\n'
122
+ return '''PPTX_STYLE_PACKAGE_TEXT_ENTRY_V1
123
+
124
+ This attachment is a Miaoda PPT style package. Use the markdown below directly.
125
+ Do not repair ZIP bytes, parse binary payloads, search attachment folders, or
126
+ rebuild the archive. The ZIP payload after this text exists only to carry image
127
+ assets. If you can read this text, you already have the style rules.
128
+
129
+ === 00_AGENT_FAST_PATH.md ===
130
+ %s
131
+ === design.md ===
132
+ %s
133
+ === layouts.md ===
134
+ %s
135
+ === ZIP_PAYLOAD_BELOW ===
136
+ ''' % (fast, design, layout_excerpt)
137
+
138
+
139
+ def write_zip(root, zip_payload_path):
140
+ prefix = os.path.basename(root)
141
+ compression = zipfile.ZIP_STORED
142
+ with zipfile.ZipFile(zip_payload_path, 'w', compression=compression) as zf:
143
+ guide = build_prefix(root)
144
+ zf.writestr(prefix + '/00_AGENT_FAST_PATH.md', guide)
145
+ for rel in rel_files(root):
146
+ zf.write(os.path.join(root, rel), prefix + '/' + rel)
147
+
148
+
149
+ def main():
150
+ ap = argparse.ArgumentParser()
151
+ ap.add_argument('pack_dir')
152
+ ap.add_argument('out_zip')
153
+ ap.add_argument('--work-dir',
154
+ help='consumer-only directory to materialize; default: <out_zip without .zip>')
155
+ ap.add_argument('--no-assets', action='store_true',
156
+ help='export text-only zip: keep asset metadata but omit binary assets')
157
+ args = ap.parse_args()
158
+
159
+ pack = os.path.abspath(args.pack_dir)
160
+ out_zip = os.path.abspath(args.out_zip)
161
+ if not os.path.isdir(pack):
162
+ fail('pack_dir not found: %s' % pack)
163
+ work_dir = os.path.abspath(args.work_dir or os.path.splitext(out_zip)[0])
164
+ copy_tree(pack, work_dir, include_assets=not args.no_assets)
165
+ rewrite_manifest(work_dir)
166
+
167
+ write_zip(work_dir, out_zip)
168
+
169
+ print('consumer dir -> %s' % work_dir)
170
+ print('zip -> %s (%d bytes)' % (out_zip, os.path.getsize(out_zip)))
171
+ print('files -> %d' % len(rel_files(work_dir)))
172
+
173
+
174
+ if __name__ == '__main__':
175
+ main()