diffusers-workflow 0.4.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.
- diffusers_workflow-0.4.0.dist-info/METADATA +318 -0
- diffusers_workflow-0.4.0.dist-info/RECORD +260 -0
- diffusers_workflow-0.4.0.dist-info/WHEEL +5 -0
- diffusers_workflow-0.4.0.dist-info/entry_points.txt +7 -0
- diffusers_workflow-0.4.0.dist-info/licenses/LICENSE +201 -0
- diffusers_workflow-0.4.0.dist-info/top_level.txt +2 -0
- dw/__init__.py +440 -0
- dw/adapter_compatibility.py +226 -0
- dw/arguments.py +1231 -0
- dw/assessment_rules.py +159 -0
- dw/assets.py +130 -0
- dw/cache_blocks.json +16 -0
- dw/cache_blocks.py +146 -0
- dw/community_pipelines/pipeline_flux_rf_inversion.py +1184 -0
- dw/content_types.py +150 -0
- dw/dissolve_frame_errors.py +121 -0
- dw/docs/ACCELERATION.md +352 -0
- dw/docs/AGENT_LOOP.md +95 -0
- dw/docs/DEPENDENCIES.md +91 -0
- dw/docs/IP_ADAPTER.md +109 -0
- dw/docs/LORAS.md +131 -0
- dw/docs/MCP.md +517 -0
- dw/docs/PROMPT_WEIGHTING.md +78 -0
- dw/docs/QUANTIZATION.md +230 -0
- dw/docs/RECIPES_24GB.md +201 -0
- dw/docs/RELEASING.md +195 -0
- dw/docs/REMOTE.md +140 -0
- dw/docs/REPL_COMMANDS.md +121 -0
- dw/docs/REPL_WORKER_GUIDE.md +51 -0
- dw/docs/SECURITY.md +272 -0
- dw/docs/SECURITY_QUICKREF.md +112 -0
- dw/docs/SERVER.md +679 -0
- dw/docs/TASKS.md +1741 -0
- dw/docs/TESTING.md +71 -0
- dw/docs/WORKFLOW_GUIDE.md +2038 -0
- dw/docs/WORKSPACES.md +316 -0
- dw/download_watch.py +335 -0
- dw/elision.py +306 -0
- dw/events.py +275 -0
- dw/for_each.py +409 -0
- dw/host_memory.py +258 -0
- dw/host_memory_projection.py +230 -0
- dw/hub_cache.py +432 -0
- dw/introspection.py +1228 -0
- dw/kernel_availability.py +208 -0
- dw/locations.py +599 -0
- dw/log_setup.py +45 -0
- dw/loudness.py +82 -0
- dw/media_audio.py +217 -0
- dw/media_frames.py +367 -0
- dw/media_info.py +297 -0
- dw/pipeline_processors/chain.py +821 -0
- dw/pipeline_processors/config_objects.py +237 -0
- dw/pipeline_processors/pipeline.py +2297 -0
- dw/pipeline_processors/remote.py +46 -0
- dw/plan.py +920 -0
- dw/previous_results.py +411 -0
- dw/probe_paths.py +59 -0
- dw/prompt_schema.json +48 -0
- dw/prompt_weighting.py +378 -0
- dw/prompts.py +159 -0
- dw/realize.py +250 -0
- dw/reference_limits.py +215 -0
- dw/reference_names.py +125 -0
- dw/repl.py +338 -0
- dw/repl_commands.py +836 -0
- dw/repl_worker.py +159 -0
- dw/result.py +1720 -0
- dw/result_fps.py +82 -0
- dw/run.py +162 -0
- dw/runs.py +768 -0
- dw/scalar_result_validation.py +97 -0
- dw/schema.py +283 -0
- dw/security.py +1038 -0
- dw/select_validation.py +115 -0
- dw/serve.py +277 -0
- dw/server/__init__.py +2 -0
- dw/server/app.py +4586 -0
- dw/server/assess.py +132 -0
- dw/server/catalog_shape.py +487 -0
- dw/server/enhancers.py +129 -0
- dw/server/exports.py +480 -0
- dw/server/guides.py +257 -0
- dw/server/jobs.py +1561 -0
- dw/server/mcp_mount.py +95 -0
- dw/server/netinfo.py +124 -0
- dw/server/observed_cost.py +379 -0
- dw/server/sysinfo.py +71 -0
- dw/server/ui/assets/abap-08VXUWAP.js +1 -0
- dw/server/ui/assets/apex-BWPQTe0t.js +1 -0
- dw/server/ui/assets/azcli-Bc_sGQ0U.js +1 -0
- dw/server/ui/assets/bat-i0X4ZdIN.js +1 -0
- dw/server/ui/assets/bicep-B5-_aFwp.js +2 -0
- dw/server/ui/assets/cameligo-DMUM7wLl.js +1 -0
- dw/server/ui/assets/clojure-Cm7r79vr.js +1 -0
- dw/server/ui/assets/codicon-Brq4_Ui5.ttf +0 -0
- dw/server/ui/assets/coffee-Ba7i2nA0.js +1 -0
- dw/server/ui/assets/cpp-C7h46wYY.js +1 -0
- dw/server/ui/assets/csharp-BKxtCVv1.js +1 -0
- dw/server/ui/assets/csp-bTuwJoIa.js +1 -0
- dw/server/ui/assets/css-DIMkf-bt.js +3 -0
- dw/server/ui/assets/css.worker-B3ciXF_0.js +93 -0
- dw/server/ui/assets/cssMode-CPznxfY8.js +1 -0
- dw/server/ui/assets/cypher-CVaqCwHa.js +1 -0
- dw/server/ui/assets/dart-onAF5SnQ.js +1 -0
- dw/server/ui/assets/dockerfile-DZFCIeNp.js +1 -0
- dw/server/ui/assets/ecl-D05T4iGw.js +1 -0
- dw/server/ui/assets/editor-jjEx9u7D.css +1 -0
- dw/server/ui/assets/editor.api-CpWcotrd.js +847 -0
- dw/server/ui/assets/editor.worker-q-txB4vs.js +30 -0
- dw/server/ui/assets/elixir-6RTg0lbw.js +1 -0
- dw/server/ui/assets/flow9-C5_-GSwl.js +1 -0
- dw/server/ui/assets/freemarker2-CXtRM8N4.js +3 -0
- dw/server/ui/assets/fsharp-C8Ef5oNN.js +1 -0
- dw/server/ui/assets/go-C-y9NEjX.js +1 -0
- dw/server/ui/assets/graphql-fmXr3nnJ.js +1 -0
- dw/server/ui/assets/handlebars-N7x-6NMY.js +1 -0
- dw/server/ui/assets/hcl-CpzslTdj.js +1 -0
- dw/server/ui/assets/html-PhsdjHSr.js +1 -0
- dw/server/ui/assets/html.worker-C93Ht9o9.js +506 -0
- dw/server/ui/assets/htmlMode-Dgj0SEok.js +1 -0
- dw/server/ui/assets/index-3Vw6WAPW.css +1 -0
- dw/server/ui/assets/index-DgrYhQd9.js +43 -0
- dw/server/ui/assets/ini-sBoK_t0W.js +1 -0
- dw/server/ui/assets/java-BEtHBSE6.js +1 -0
- dw/server/ui/assets/javascript-BJqN9Qhv.js +1 -0
- dw/server/ui/assets/json.worker-B2V3pomh.js +62 -0
- dw/server/ui/assets/jsonMode-DbM4SWSv.js +7 -0
- dw/server/ui/assets/julia-Bri6UV-V.js +1 -0
- dw/server/ui/assets/kotlin-BOotOW0E.js +1 -0
- dw/server/ui/assets/less-B9JPFI3C.js +2 -0
- dw/server/ui/assets/lexon-CfSJPG6W.js +1 -0
- dw/server/ui/assets/liquid-BWr8lEc4.js +1 -0
- dw/server/ui/assets/lspLanguageFeatures-C1iGuDyZ.js +4 -0
- dw/server/ui/assets/lua-CsQS60Ue.js +1 -0
- dw/server/ui/assets/m3-D-oSqn_W.js +1 -0
- dw/server/ui/assets/markdown-Cimd5fb3.js +1 -0
- dw/server/ui/assets/mdx-DAdMi_0p.js +1 -0
- dw/server/ui/assets/mips-CIPQ_RoX.js +1 -0
- dw/server/ui/assets/monaco--ixms01u.css +1 -0
- dw/server/ui/assets/monaco-BGCeEqaw.js +56 -0
- dw/server/ui/assets/msdax-DauUninz.js +1 -0
- dw/server/ui/assets/mysql-SOo6toE5.js +1 -0
- dw/server/ui/assets/objective-c-FvmIjYaQ.js +1 -0
- dw/server/ui/assets/pascal-DrH0SRf2.js +1 -0
- dw/server/ui/assets/pascaligo-D-ptJ9y-.js +1 -0
- dw/server/ui/assets/perl-oz_6vUea.js +1 -0
- dw/server/ui/assets/pgsql-DTj74zXo.js +1 -0
- dw/server/ui/assets/php-nr791fC2.js +1 -0
- dw/server/ui/assets/pla-CopQ2nXW.js +1 -0
- dw/server/ui/assets/postiats-43DmfD33.js +1 -0
- dw/server/ui/assets/powerquery-D3hlyOfw.js +1 -0
- dw/server/ui/assets/powershell-DmHpPYUd.js +1 -0
- dw/server/ui/assets/protobuf-C531GsRP.js +2 -0
- dw/server/ui/assets/pug-Z5eAx3Zn.js +1 -0
- dw/server/ui/assets/python-Bcn70HdC.js +1 -0
- dw/server/ui/assets/qsharp-DkqhCAOL.js +1 -0
- dw/server/ui/assets/r-BwWrilGY.js +1 -0
- dw/server/ui/assets/razor-D1HmNnby.js +1 -0
- dw/server/ui/assets/redis-ClamHrr6.js +1 -0
- dw/server/ui/assets/redshift-DT7zqm-g.js +1 -0
- dw/server/ui/assets/restructuredtext-BYgofb2h.js +1 -0
- dw/server/ui/assets/ruby-DezsRK8O.js +1 -0
- dw/server/ui/assets/rust-DdL9SqIa.js +1 -0
- dw/server/ui/assets/sb-CcwsVR0C.js +1 -0
- dw/server/ui/assets/scala-DHpiXF5c.js +1 -0
- dw/server/ui/assets/scheme-BeGwcela.js +1 -0
- dw/server/ui/assets/scss-gp-XZpBa.js +3 -0
- dw/server/ui/assets/shell-CC2rA5mh.js +1 -0
- dw/server/ui/assets/solidity-BEEn4gHE.js +1 -0
- dw/server/ui/assets/sophia-CRfGWb83.js +1 -0
- dw/server/ui/assets/sparql-D_Lu-MrJ.js +1 -0
- dw/server/ui/assets/sql-NEE52Syq.js +1 -0
- dw/server/ui/assets/st-DbInun42.js +1 -0
- dw/server/ui/assets/swift-Bxkupp3x.js +1 -0
- dw/server/ui/assets/systemverilog-Bz4Y3fRF.js +1 -0
- dw/server/ui/assets/tcl-DISqw1ZD.js +1 -0
- dw/server/ui/assets/ts.worker-D7T1-Ig5.js +67738 -0
- dw/server/ui/assets/tsMode-D6u0XmOW.js +11 -0
- dw/server/ui/assets/twig-De2hgUGE.js +1 -0
- dw/server/ui/assets/typescript-BU6v-LMV.js +1 -0
- dw/server/ui/assets/typespec-B8J7ngcE.js +1 -0
- dw/server/ui/assets/vb-DV3o63ZY.js +1 -0
- dw/server/ui/assets/wgsl-DpFanUEy.js +298 -0
- dw/server/ui/assets/workers-Cn7cTUKr.js +1 -0
- dw/server/ui/assets/xml--0LP2Lwk.js +1 -0
- dw/server/ui/assets/yaml-mpBg9jnt.js +1 -0
- dw/server/ui/index.html +17 -0
- dw/server/updater.py +192 -0
- dw/settings.py +98 -0
- dw/shot_span_preflight.py +116 -0
- dw/shots.py +359 -0
- dw/slice_preflight.py +148 -0
- dw/step.py +187 -0
- dw/step_cache.py +442 -0
- dw/subfolders.py +107 -0
- dw/task_domains.py +307 -0
- dw/tasks/assess.py +826 -0
- dw/tasks/audio_transcription.py +88 -0
- dw/tasks/audio_utils.py +1862 -0
- dw/tasks/background_remover.py +43 -0
- dw/tasks/borders.py +113 -0
- dw/tasks/compose_text.py +74 -0
- dw/tasks/concat_videos.py +300 -0
- dw/tasks/depth_estimator.py +54 -0
- dw/tasks/diffusion_upscale.py +109 -0
- dw/tasks/dissolve_videos.py +342 -0
- dw/tasks/format_messages.py +24 -0
- dw/tasks/gather.py +173 -0
- dw/tasks/grade.py +97 -0
- dw/tasks/image_to_text.py +43 -0
- dw/tasks/image_utils.py +764 -0
- dw/tasks/interpolate_frames.py +252 -0
- dw/tasks/judge.py +68 -0
- dw/tasks/model_cache.py +55 -0
- dw/tasks/pair_audio.py +268 -0
- dw/tasks/qr_code.py +19 -0
- dw/tasks/restore_faces.py +175 -0
- dw/tasks/rife_model.py +192 -0
- dw/tasks/segment.py +121 -0
- dw/tasks/select.py +111 -0
- dw/tasks/speech_generation.py +228 -0
- dw/tasks/stabilize.py +129 -0
- dw/tasks/task.py +920 -0
- dw/tasks/tensor_image.py +57 -0
- dw/tasks/text_generation.py +169 -0
- dw/tasks/text_sections.py +80 -0
- dw/tasks/upscale.py +203 -0
- dw/tasks/video_utils.py +624 -0
- dw/tasks/zoe_depth.py +71 -0
- dw/teacache.py +381 -0
- dw/teacache_models.json +99 -0
- dw/test.py +29 -0
- dw/type_helpers.py +231 -0
- dw/validate.py +68 -0
- dw/variable_constraints.py +444 -0
- dw/variables.py +443 -0
- dw/video_extensions.py +141 -0
- dw/vram_estimate.py +116 -0
- dw/worker.py +764 -0
- dw/workflow.py +2007 -0
- dw/workflow_schema.json +1346 -0
- dw/workflow_sources.py +383 -0
- dw/workflows/h3_context_ir.json +57 -0
- dw/workflows/test.json +31 -0
- dw/workspace.py +730 -0
- dw_mcp/__init__.py +6 -0
- dw_mcp/__main__.py +133 -0
- dw_mcp/assets.py +336 -0
- dw_mcp/authoring.py +114 -0
- dw_mcp/catalog.py +360 -0
- dw_mcp/client.py +486 -0
- dw_mcp/diagnose.py +371 -0
- dw_mcp/exports.py +84 -0
- dw_mcp/guides.py +35 -0
- dw_mcp/media.py +638 -0
- dw_mcp/models.py +97 -0
- dw_mcp/prompts.py +104 -0
- dw_mcp/server.py +1343 -0
- dw_mcp/workspaces.py +212 -0
dw/server/guides.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"""The prose guides, served by the engine they describe.
|
|
2
|
+
|
|
3
|
+
The gap they close is that a catalog entry describes what a workflow *is*,
|
|
4
|
+
and an open-ended request ("a lego movie trailer set in the marvel
|
|
5
|
+
universe") names nothing that appears in any of them. What has to be
|
|
6
|
+
matched is shape - multi-shot video, cuts, a consistent cast, narration -
|
|
7
|
+
and that is what the guides are written in. An agent that reads one before
|
|
8
|
+
choosing composes from what exists instead of authoring a fresh workflow
|
|
9
|
+
badly.
|
|
10
|
+
|
|
11
|
+
They are served from here, not read from the MCP client's install, because
|
|
12
|
+
the guides an agent reads have to be the guides for the engine it is about
|
|
13
|
+
to drive: an MCP at one version against a server at another would
|
|
14
|
+
otherwise index sections the server does not have.
|
|
15
|
+
|
|
16
|
+
Two things keep this cheap. The listing carries each guide's section
|
|
17
|
+
headings, so the index is itself the routing table and is small enough to
|
|
18
|
+
read every time. And a guide can be fetched one section at a time, because
|
|
19
|
+
handing over three thousand lines of TASKS.md is how an agent ends up
|
|
20
|
+
reading none of it carefully.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
import re
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
# The docs that bear on choosing a capability. Deliberately not all of them -
|
|
27
|
+
# testing, releasing, security and dependency notes are for people working on
|
|
28
|
+
# dw, and listing them would dilute an index whose whole value is that it is
|
|
29
|
+
# short enough to read in full.
|
|
30
|
+
GUIDES = {
|
|
31
|
+
"workflows": (
|
|
32
|
+
"WORKFLOW_GUIDE.md",
|
|
33
|
+
"How the catalog is organised - templates/ teaching a pattern, models/ "
|
|
34
|
+
"recording what makes a checkpoint fit - and how a workflow is put "
|
|
35
|
+
"together: steps, variables, the reference conventions that carry data "
|
|
36
|
+
"between steps, sub-workflows, and releasing models mid-run. Read "
|
|
37
|
+
"before authoring one.",
|
|
38
|
+
),
|
|
39
|
+
"tasks": (
|
|
40
|
+
"TASKS.md",
|
|
41
|
+
"The utility task commands - image and video processing, audio, "
|
|
42
|
+
"captioning, text and speech generation, gathering. Compose these "
|
|
43
|
+
"before writing anything new.",
|
|
44
|
+
),
|
|
45
|
+
"recipes": (
|
|
46
|
+
"RECIPES_24GB.md",
|
|
47
|
+
"What actually fits and runs on a 24 GB accelerator, model by model. "
|
|
48
|
+
"Read before proposing something large.",
|
|
49
|
+
),
|
|
50
|
+
"acceleration": (
|
|
51
|
+
"ACCELERATION.md",
|
|
52
|
+
"Offloading, attention slicing, caching and compilation - how to make "
|
|
53
|
+
"a pipeline fit or run faster, and what each costs.",
|
|
54
|
+
),
|
|
55
|
+
"quantization": (
|
|
56
|
+
"QUANTIZATION.md",
|
|
57
|
+
"Running a model in fewer bits: the supported frameworks and how a "
|
|
58
|
+
"quantization config is written per component.",
|
|
59
|
+
),
|
|
60
|
+
"loras": (
|
|
61
|
+
"LORAS.md",
|
|
62
|
+
"Loading, weighting and combining LoRA adapters.",
|
|
63
|
+
),
|
|
64
|
+
"prompt-weighting": (
|
|
65
|
+
"PROMPT_WEIGHTING.md",
|
|
66
|
+
"Emphasis and de-emphasis syntax in prompts, and which pipelines honour it.",
|
|
67
|
+
),
|
|
68
|
+
"ip-adapter": (
|
|
69
|
+
"IP_ADAPTER.md",
|
|
70
|
+
"Conditioning generation on a reference image with IP-Adapter.",
|
|
71
|
+
),
|
|
72
|
+
"workspaces": (
|
|
73
|
+
"WORKSPACES.md",
|
|
74
|
+
"How a run's workflows, prompts, assets and outputs are rooted, and "
|
|
75
|
+
"what changes when a workspace is named.",
|
|
76
|
+
),
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Only the top level. Sub-headings would triple the index for detail that is
|
|
80
|
+
# better reached by reading the section they sit in
|
|
81
|
+
SECTION_PATTERN = re.compile(r"^## (.+)$", re.M)
|
|
82
|
+
|
|
83
|
+
# A doc's link to an example is a repo-relative path, which resolves for a
|
|
84
|
+
# reader with a checkout and dead-ends for the one these guides are served
|
|
85
|
+
# to: an agent holding the MCP and nothing else. Said once, on a payload
|
|
86
|
+
# that contains such a path, rather than rewritten into 114 links in prose
|
|
87
|
+
# that people read too.
|
|
88
|
+
CATALOG_PATH_NOTE = (
|
|
89
|
+
"Paths like `workflows/templates/minimax/image-to-video.json` in this "
|
|
90
|
+
"text are repo-relative and are not served by this machine. The catalog "
|
|
91
|
+
"name is the part after `workflows/` with `.json` dropped - "
|
|
92
|
+
"`templates/minimax/image-to-video` - which is what `get_workflow`, "
|
|
93
|
+
"`validate_workflow`, `run_workflow` and a sub-workflow step's `path` "
|
|
94
|
+
"all take. Paths under `dw/workflows/` are the packaged built-ins a "
|
|
95
|
+
"sub-workflow names as `builtin:<file>.json`."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
# The same shape tests/test_docs_links.py checks the targets of: the
|
|
99
|
+
# lookbehind keeps `workflows/` as the start of the path, so `dw/workflows/`
|
|
100
|
+
# and `tests/test_data/workflows/` do not match.
|
|
101
|
+
_EXAMPLE_PATH = re.compile(r"(?<![\w/-])(?:\.\./)?workflows/[A-Za-z0-9_.\-/]+\.json")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class GuideError(LookupError):
|
|
105
|
+
"""A guide or section that does not exist. The message names what does,
|
|
106
|
+
so the route can hand it straight back as a 404 detail."""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _guide_file(file_name):
|
|
110
|
+
"""Where a guide lives: the repo's docs/ in a checkout, else the copy
|
|
111
|
+
build_dist.sh puts under dw/docs/ for an install.
|
|
112
|
+
|
|
113
|
+
The checkout wins because the build leaves dw/docs/ behind; if that copy
|
|
114
|
+
took precedence, editing docs/ would change nothing an agent reads. The
|
|
115
|
+
same rule default_ui_dir applies to the SPA.
|
|
116
|
+
"""
|
|
117
|
+
root = Path(__file__).resolve().parent.parent.parent
|
|
118
|
+
checkout = root / "docs" / file_name
|
|
119
|
+
if checkout.is_file():
|
|
120
|
+
return checkout
|
|
121
|
+
return root / "dw" / "docs" / file_name
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def read_guide(name):
|
|
125
|
+
"""One guide's full markdown text.
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
GuideError: If the name is not a guide.
|
|
129
|
+
FileNotFoundError: If the guide's file is missing from this install.
|
|
130
|
+
"""
|
|
131
|
+
if name not in GUIDES:
|
|
132
|
+
raise GuideError(
|
|
133
|
+
f"No guide named '{name}'. The guides are: {', '.join(sorted(GUIDES))}."
|
|
134
|
+
)
|
|
135
|
+
path = _guide_file(GUIDES[name][0])
|
|
136
|
+
if not path.is_file():
|
|
137
|
+
raise FileNotFoundError(
|
|
138
|
+
f"The '{name}' guide is missing from this install "
|
|
139
|
+
f"({GUIDES[name][0]} was not found)."
|
|
140
|
+
)
|
|
141
|
+
return path.read_text(encoding="utf-8")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _sections(text):
|
|
145
|
+
"""The top-level section headings of a guide, in the order they appear."""
|
|
146
|
+
return SECTION_PATTERN.findall(text)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _normalized(heading):
|
|
150
|
+
"""A heading reduced to what a loose match compares.
|
|
151
|
+
|
|
152
|
+
An agent reproduces a heading from the listing approximately -
|
|
153
|
+
"speech-generation" for "Speech Generation" - and refusing that costs a
|
|
154
|
+
round trip to say something it already knew.
|
|
155
|
+
"""
|
|
156
|
+
return re.sub(r"[^a-z0-9]+", "", heading.lower())
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _extract_section(text, section):
|
|
160
|
+
"""One section's text, heading included, or None if there is no such one."""
|
|
161
|
+
wanted = _normalized(section)
|
|
162
|
+
matches = list(SECTION_PATTERN.finditer(text))
|
|
163
|
+
for index, match in enumerate(matches):
|
|
164
|
+
if _normalized(match.group(1)) != wanted:
|
|
165
|
+
continue
|
|
166
|
+
end = matches[index + 1].start() if index + 1 < len(matches) else len(text)
|
|
167
|
+
return match.group(1), text[match.start() : end].rstrip() + "\n"
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _noted(body):
|
|
172
|
+
"""The payload, with the repo-path rule attached when it holds one.
|
|
173
|
+
|
|
174
|
+
Attached to the body rather than woven into the guides because the same
|
|
175
|
+
markdown is read on GitHub and in an editor, where the links resolve.
|
|
176
|
+
"""
|
|
177
|
+
if _EXAMPLE_PATH.search(body.get("content") or ""):
|
|
178
|
+
body["catalog_paths"] = CATALOG_PATH_NOTE
|
|
179
|
+
return body
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def list_guides():
|
|
183
|
+
"""Every guide, with what it covers and the sections it holds."""
|
|
184
|
+
listed = []
|
|
185
|
+
for name, (file_name, summary) in GUIDES.items():
|
|
186
|
+
listed.append(
|
|
187
|
+
{
|
|
188
|
+
"name": name,
|
|
189
|
+
"file": file_name,
|
|
190
|
+
"summary": summary,
|
|
191
|
+
"sections": _sections(read_guide(name)),
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
return {"guides": listed}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def get_guide(name, section=None):
|
|
198
|
+
"""One guide, or one section of it.
|
|
199
|
+
|
|
200
|
+
Without a `section` the answer is the *index*: the guide's preamble,
|
|
201
|
+
its first section, and the headings of the rest - not the whole file.
|
|
202
|
+
A full WORKFLOW_GUIDE.md is ~19.6k tokens and TASKS.md ~16k, which is
|
|
203
|
+
more in one call than the entire 59-tool MCP surface costs to connect,
|
|
204
|
+
and an agent can make that call twice before noticing (#101). Every
|
|
205
|
+
withheld section is named in `sections` and fetched by name, so
|
|
206
|
+
nothing is unreachable - only unspent by accident.
|
|
207
|
+
"""
|
|
208
|
+
text = read_guide(name)
|
|
209
|
+
if section is None:
|
|
210
|
+
return _noted(_index(name, text))
|
|
211
|
+
|
|
212
|
+
found = _extract_section(text, section)
|
|
213
|
+
if found is None:
|
|
214
|
+
raise GuideError(
|
|
215
|
+
f"The '{name}' guide has no section '{section}'. Its sections are: "
|
|
216
|
+
f"{', '.join(_sections(text))}."
|
|
217
|
+
)
|
|
218
|
+
heading, content = found
|
|
219
|
+
return _noted({"name": name, "section": heading, "content": content})
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _index(name, text):
|
|
223
|
+
"""The guide's opening plus the headings of what was not sent.
|
|
224
|
+
|
|
225
|
+
`content` is everything before the first heading followed by the first
|
|
226
|
+
section - the part that says what the guide is for - so an agent that
|
|
227
|
+
reads only this still knows which section it wants.
|
|
228
|
+
"""
|
|
229
|
+
matches = list(SECTION_PATTERN.finditer(text))
|
|
230
|
+
headings = [match.group(1) for match in matches]
|
|
231
|
+
if not matches:
|
|
232
|
+
return {
|
|
233
|
+
"name": name,
|
|
234
|
+
"section": None,
|
|
235
|
+
"content": text,
|
|
236
|
+
"sections": headings,
|
|
237
|
+
"withheld": [],
|
|
238
|
+
}
|
|
239
|
+
end = matches[1].start() if len(matches) > 1 else len(text)
|
|
240
|
+
content = text[:end].rstrip() + "\n"
|
|
241
|
+
withheld = headings[1:]
|
|
242
|
+
answer = {
|
|
243
|
+
"name": name,
|
|
244
|
+
"section": None,
|
|
245
|
+
"content": content,
|
|
246
|
+
"sections": headings,
|
|
247
|
+
"withheld": withheld,
|
|
248
|
+
}
|
|
249
|
+
if withheld:
|
|
250
|
+
answer["note"] = (
|
|
251
|
+
f"This is the '{name}' guide's opening and its first section. "
|
|
252
|
+
f"{len(withheld)} further section(s) - {len(text) - len(content)} "
|
|
253
|
+
f"characters - were not sent: read one with "
|
|
254
|
+
f"get_guide('{name}', section='<heading>'). `sections` lists "
|
|
255
|
+
f"every heading in order."
|
|
256
|
+
)
|
|
257
|
+
return answer
|