sumd 0.3.35__tar.gz → 0.3.37__tar.gz

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.
Files changed (36) hide show
  1. {sumd-0.3.35 → sumd-0.3.37}/CHANGELOG.md +18 -0
  2. {sumd-0.3.35 → sumd-0.3.37}/PKG-INFO +6 -6
  3. {sumd-0.3.35 → sumd-0.3.37}/README.md +5 -5
  4. {sumd-0.3.35 → sumd-0.3.37}/pyproject.toml +1 -1
  5. {sumd-0.3.35 → sumd-0.3.37}/sumd/__init__.py +1 -1
  6. {sumd-0.3.35 → sumd-0.3.37}/sumd/extractor.py +52 -0
  7. {sumd-0.3.35 → sumd-0.3.37}/sumd/pipeline.py +9 -1
  8. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/__init__.py +3 -0
  9. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/base.py +1 -0
  10. sumd-0.3.37/sumd/sections/swop.py +68 -0
  11. {sumd-0.3.35 → sumd-0.3.37}/sumd/validator.py +2 -0
  12. {sumd-0.3.35 → sumd-0.3.37}/.gitignore +0 -0
  13. {sumd-0.3.35 → sumd-0.3.37}/LICENSE +0 -0
  14. {sumd-0.3.35 → sumd-0.3.37}/sumd/cli.py +0 -0
  15. {sumd-0.3.35 → sumd-0.3.37}/sumd/generator.py +0 -0
  16. {sumd-0.3.35 → sumd-0.3.37}/sumd/mcp_server.py +0 -0
  17. {sumd-0.3.35 → sumd-0.3.37}/sumd/models.py +0 -0
  18. {sumd-0.3.35 → sumd-0.3.37}/sumd/parser.py +0 -0
  19. {sumd-0.3.35 → sumd-0.3.37}/sumd/renderer.py +0 -0
  20. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/api_stubs.py +0 -0
  21. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/architecture.py +0 -0
  22. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/call_graph.py +0 -0
  23. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/code_analysis.py +0 -0
  24. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/configuration.py +0 -0
  25. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/dependencies.py +0 -0
  26. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/deployment.py +0 -0
  27. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/environment.py +0 -0
  28. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/extras.py +0 -0
  29. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/interfaces.py +0 -0
  30. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/metadata.py +0 -0
  31. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/quality.py +0 -0
  32. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/refactor_analysis.py +0 -0
  33. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/source_snippets.py +0 -0
  34. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/test_contracts.py +0 -0
  35. {sumd-0.3.35 → sumd-0.3.37}/sumd/sections/workflows.py +0 -0
  36. {sumd-0.3.35 → sumd-0.3.37}/sumd/toon_parser.py +0 -0
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.37] - 2026-04-23
11
+
12
+ ### Docs
13
+ - Update README.md
14
+
15
+ ### Other
16
+ - Update sumd/extractor.py
17
+ - Update sumd/pipeline.py
18
+ - Update sumd/sections/__init__.py
19
+ - Update sumd/sections/base.py
20
+ - Update sumd/sections/swop.py
21
+ - Update sumd/validator.py
22
+
23
+ ## [0.3.36] - 2026-04-23
24
+
25
+ ### Docs
26
+ - Update README.md
27
+
10
28
  ## [0.3.35] - 2026-04-23
11
29
 
12
30
  ### Docs
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sumd
3
- Version: 0.3.35
3
+ Version: 0.3.37
4
4
  Summary: SUMD - Structured Unified Markdown Descriptor for AI-aware project documentation
5
5
  Project-URL: Homepage, https://github.com/tom/statement
6
6
  Project-URL: Repository, https://github.com/tom/statement
@@ -41,17 +41,17 @@ Description-Content-Type: text/markdown
41
41
 
42
42
  ## AI Cost Tracking
43
43
 
44
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.3.35-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
45
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-21.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
44
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.3.37-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
45
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-23.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
46
46
 
47
- - 🤖 **LLM usage:** $7.5000 (65 commits)
48
- - 👤 **Human dev:** ~$2193 (21.9h @ $100/h, 30min dedup)
47
+ - 🤖 **LLM usage:** $7.5000 (67 commits)
48
+ - 👤 **Human dev:** ~$2387 (23.9h @ $100/h, 30min dedup)
49
49
 
50
50
  Generated on 2026-04-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
51
51
 
52
52
  ---
53
53
 
54
- ![Version](https://img.shields.io/badge/version-0.3.35-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
54
+ ![Version](https://img.shields.io/badge/version-0.3.37-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
55
55
 
56
56
  **SUMD** (Structured Unified Markdown Descriptor) is a semantic project descriptor format in Markdown.
57
57
  It defines intent, structure, execution entry points, and the mental model of a system for both humans and LLMs.
@@ -3,17 +3,17 @@
3
3
 
4
4
  ## AI Cost Tracking
5
5
 
6
- ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.3.35-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
- ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-21.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
6
+ ![PyPI](https://img.shields.io/badge/pypi-costs-blue) ![Version](https://img.shields.io/badge/version-0.3.37-blue) ![Python](https://img.shields.io/badge/python-3.9+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
7
+ ![AI Cost](https://img.shields.io/badge/AI%20Cost-$7.50-orange) ![Human Time](https://img.shields.io/badge/Human%20Time-23.9h-blue) ![Model](https://img.shields.io/badge/Model-openrouter%2Fqwen%2Fqwen3--coder--next-lightgrey)
8
8
 
9
- - 🤖 **LLM usage:** $7.5000 (65 commits)
10
- - 👤 **Human dev:** ~$2193 (21.9h @ $100/h, 30min dedup)
9
+ - 🤖 **LLM usage:** $7.5000 (67 commits)
10
+ - 👤 **Human dev:** ~$2387 (23.9h @ $100/h, 30min dedup)
11
11
 
12
12
  Generated on 2026-04-23 using [openrouter/qwen/qwen3-coder-next](https://openrouter.ai/qwen/qwen3-coder-next)
13
13
 
14
14
  ---
15
15
 
16
- ![Version](https://img.shields.io/badge/version-0.3.35-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
16
+ ![Version](https://img.shields.io/badge/version-0.3.37-blue) ![Python](https://img.shields.io/badge/python-3.10+-blue) ![License](https://img.shields.io/badge/license-Apache--2.0-green)
17
17
 
18
18
  **SUMD** (Structured Unified Markdown Descriptor) is a semantic project descriptor format in Markdown.
19
19
  It defines intent, structure, execution entry points, and the mental model of a system for both humans and LLMs.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "sumd"
7
- version = "0.3.35"
7
+ version = "0.3.37"
8
8
  description = "SUMD - Structured Unified Markdown Descriptor for AI-aware project documentation"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -4,7 +4,7 @@ SUMD is a semantic project descriptor format in Markdown that defines intent,
4
4
  structure, execution entry points, and mental model of a system for both humans and LLMs.
5
5
  """
6
6
 
7
- __version__ = "0.3.35"
7
+ __version__ = "0.3.37"
8
8
 
9
9
  from sumd.models import SUMDDocument
10
10
  from sumd.parser import (
@@ -877,6 +877,58 @@ def extract_source_snippets(proj_dir: Path, pkg_name: str) -> list[dict]:
877
877
  return results
878
878
 
879
879
 
880
+ def extract_swop(proj_dir: Path) -> dict[str, Any]:
881
+ """Extract SWOP manifest files from .swop/manifests/<context>/ directory.
882
+
883
+ Returns dict with structure:
884
+ {
885
+ "contexts": {
886
+ "<context_name>": {
887
+ "commands": {file, content},
888
+ "queries": {file, content},
889
+ "events": {file, content}
890
+ }
891
+ },
892
+ "sources": [list of file paths]
893
+ }
894
+ """
895
+ swop_dir = proj_dir / ".swop" / "manifests"
896
+ if not swop_dir.is_dir():
897
+ return {}
898
+
899
+ contexts: dict[str, dict[str, Any]] = {}
900
+ sources: list[str] = []
901
+
902
+ for context_dir in sorted(swop_dir.iterdir()):
903
+ if not context_dir.is_dir():
904
+ continue
905
+
906
+ context_name = context_dir.name
907
+ context_data: dict[str, dict[str, str]] = {}
908
+
909
+ for manifest_type in ("commands.yml", "queries.yml", "events.yml"):
910
+ manifest_path = context_dir / manifest_type
911
+ if manifest_path.exists():
912
+ rel_path = f".swop/manifests/{context_name}/{manifest_type}"
913
+ try:
914
+ content = manifest_path.read_text(encoding="utf-8").rstrip()
915
+ context_data[manifest_type.replace(".yml", "")] = {
916
+ "file": rel_path,
917
+ "content": content,
918
+ }
919
+ sources.append(rel_path)
920
+ except Exception:
921
+ pass
922
+
923
+ if context_data:
924
+ contexts[context_name] = context_data
925
+
926
+ if not contexts:
927
+ return {}
928
+
929
+ return {"contexts": contexts, "sources": sources}
930
+
931
+
880
932
  def extract_project_analysis(
881
933
  proj_dir: Path, refactor: bool = False
882
934
  ) -> list[dict[str, str]]:
@@ -42,6 +42,7 @@ from sumd.extractor import (
42
42
  extract_readme_title,
43
43
  extract_requirements,
44
44
  extract_source_snippets,
45
+ extract_swop,
45
46
  extract_taskfile,
46
47
  generate_map_toon,
47
48
  required_tools_for_profile,
@@ -228,11 +229,15 @@ def _collect_sources(
228
229
  pkg_json: dict,
229
230
  modules: list,
230
231
  project_analysis: list,
232
+ swop: dict,
231
233
  ) -> list[str]:
232
234
  """Build the list of source labels that contributed data to this SUMD."""
233
- return _collect_pkg_sources(
235
+ sources = _collect_pkg_sources(
234
236
  pyproj, reqs, tasks, makefile, scenarios, openapi, doql, pyqual, goal, env_vars
235
237
  ) + _collect_infra_sources(dockerfile, compose, pkg_json, modules, project_analysis)
238
+ if swop.get("sources"):
239
+ sources.extend(swop["sources"])
240
+ return sources
236
241
 
237
242
 
238
243
  def _inject_toc(content: str) -> str:
@@ -289,6 +294,7 @@ class RenderPipeline:
289
294
  dockerfile = extract_dockerfile(proj_dir)
290
295
  compose = extract_docker_compose(proj_dir)
291
296
  pkg_json = extract_package_json(proj_dir)
297
+ swop = extract_swop(proj_dir)
292
298
 
293
299
  # Auto-regenerate map.toon.yaml — pure-Python, always fast.
294
300
  _refresh_map_toon(proj_dir)
@@ -320,6 +326,7 @@ class RenderPipeline:
320
326
  pkg_json,
321
327
  modules,
322
328
  project_analysis,
329
+ swop,
323
330
  )
324
331
 
325
332
  return RenderContext(
@@ -348,6 +355,7 @@ class RenderPipeline:
348
355
  pkg_json=pkg_json,
349
356
  project_analysis=project_analysis,
350
357
  source_snippets=source_snippets,
358
+ swop=swop,
351
359
  raw_sources=self.raw_sources,
352
360
  sources_used=sources_used,
353
361
  title=title or name,
@@ -28,6 +28,7 @@ from sumd.sections.metadata import MetadataSection
28
28
  from sumd.sections.quality import QualitySection
29
29
  from sumd.sections.refactor_analysis import RefactorAnalysisSection
30
30
  from sumd.sections.source_snippets import SourceSnippetsSection
31
+ from sumd.sections.swop import SwopSection
31
32
  from sumd.sections.test_contracts import TestContractsSection
32
33
  from sumd.sections.workflows import WorkflowsSection
33
34
 
@@ -36,6 +37,7 @@ from sumd.sections.workflows import WorkflowsSection
36
37
  SECTION_REGISTRY: list[type] = [
37
38
  MetadataSection,
38
39
  ArchitectureSection,
40
+ SwopSection,
39
41
  InterfacesSection,
40
42
  WorkflowsSection,
41
43
  QualitySection,
@@ -71,6 +73,7 @@ PROFILES: dict[str, set[str]] = {
71
73
  "rich": {
72
74
  "metadata",
73
75
  "architecture",
76
+ "swop",
74
77
  "interfaces",
75
78
  "workflows",
76
79
  "quality",
@@ -53,6 +53,7 @@ class RenderContext:
53
53
  pkg_json: dict = field(default_factory=dict)
54
54
  project_analysis: list = field(default_factory=list)
55
55
  source_snippets: list = field(default_factory=list)
56
+ swop: dict = field(default_factory=dict)
56
57
 
57
58
  # Rendering options
58
59
  raw_sources: bool = True
@@ -0,0 +1,68 @@
1
+ """sumd.sections.swop — SwopSection for SWOP manifest files."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from sumd.sections.base import RenderContext, Section
6
+
7
+
8
+ def _render_swop_section(swop: dict, raw_sources: bool) -> list[str]:
9
+ """Render SWOP manifest section."""
10
+ L: list[str] = []
11
+ a = L.append
12
+
13
+ if not swop.get("contexts"):
14
+ return L
15
+
16
+ contexts = swop["contexts"]
17
+ sources = swop.get("sources", [])
18
+
19
+ a("## SWOP")
20
+ a("")
21
+ a("SWOP - Bi-directional runtime reconciler and drift-aware state graph for full-stack systems.")
22
+ a("")
23
+
24
+ if raw_sources:
25
+ # Render raw YAML files
26
+ for context_name, context_data in sorted(contexts.items()):
27
+ a(f"### Context: `{context_name}`")
28
+ a("")
29
+
30
+ for manifest_type in ("commands", "queries", "events"):
31
+ if manifest_type in context_data:
32
+ manifest = context_data[manifest_type]
33
+ file_path = manifest["file"]
34
+ content = manifest["content"]
35
+ a(f"#### {manifest_type.capitalize()} (`{file_path}`)")
36
+ a("")
37
+ a(f"```yaml markpact:swop path={file_path}")
38
+ a(content)
39
+ a("```")
40
+ a("")
41
+ else:
42
+ # Render parsed/structured view
43
+ for context_name, context_data in sorted(contexts.items()):
44
+ a(f"### Context: `{context_name}`")
45
+ a("")
46
+
47
+ for manifest_type in ("commands", "queries", "events"):
48
+ if manifest_type in context_data:
49
+ file_path = context_data[manifest_type]["file"]
50
+ a(f"- **{manifest_type.capitalize()}**: `{file_path}`")
51
+ a("")
52
+
53
+ return L
54
+
55
+
56
+ class SwopSection:
57
+ name = "swop"
58
+ level = 2
59
+ profiles = frozenset({"rich"})
60
+
61
+ def should_render(self, ctx: RenderContext) -> bool:
62
+ return bool(ctx.swop.get("contexts"))
63
+
64
+ def render(self, ctx: RenderContext) -> list[str]:
65
+ return _render_swop_section(ctx.swop, ctx.raw_sources)
66
+
67
+
68
+ assert isinstance(SwopSection(), Section)
@@ -122,6 +122,7 @@ _VALID_MARKPACT_KINDS = {
122
122
  "taskfile",
123
123
  "pyqual",
124
124
  "analysis",
125
+ "swop",
125
126
  }
126
127
  _MARKPACT_REQUIRED_ATTRS = {
127
128
  "file": "path",
@@ -131,6 +132,7 @@ _MARKPACT_REQUIRED_ATTRS = {
131
132
  "taskfile": "path",
132
133
  "pyqual": "path",
133
134
  "analysis": "path",
135
+ "swop": "path",
134
136
  }
135
137
 
136
138
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes