induscode 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.
- induscode/__init__.py +56 -0
- induscode/addons/__init__.py +176 -0
- induscode/addons/contract.py +923 -0
- induscode/addons/dispatch/__init__.py +43 -0
- induscode/addons/dispatch/event_dispatcher.py +348 -0
- induscode/addons/dispatch/tool_interceptor.py +349 -0
- induscode/addons/host.py +469 -0
- induscode/addons/loader.py +314 -0
- induscode/addons/manifest.py +232 -0
- induscode/addons/surface.py +199 -0
- induscode/boot/__init__.py +108 -0
- induscode/boot/auth_vault.py +323 -0
- induscode/boot/boot.py +210 -0
- induscode/boot/contract.py +223 -0
- induscode/boot/invocation.py +117 -0
- induscode/boot/runners/__init__.py +42 -0
- induscode/boot/runners/link_runner.py +82 -0
- induscode/boot/runners/oneshot_runner.py +85 -0
- induscode/boot/runners/registry.py +46 -0
- induscode/boot/runners/repl_runner.py +340 -0
- induscode/boot/runners/session.py +549 -0
- induscode/boot/stages.py +198 -0
- induscode/boot/upgrade/__init__.py +36 -0
- induscode/boot/upgrade/apply.py +125 -0
- induscode/boot/upgrade/upgrades.py +136 -0
- induscode/briefing/__init__.py +115 -0
- induscode/briefing/compose.py +414 -0
- induscode/briefing/contract.py +528 -0
- induscode/briefing/macros.py +721 -0
- induscode/briefing/skills.py +417 -0
- induscode/capability_deck/__init__.py +233 -0
- induscode/capability_deck/bridge_ledger/__init__.py +66 -0
- induscode/capability_deck/bridge_ledger/key.py +181 -0
- induscode/capability_deck/bridge_ledger/ledger.py +276 -0
- induscode/capability_deck/bridge_ledger/network.py +336 -0
- induscode/capability_deck/builtin_bridge.py +358 -0
- induscode/capability_deck/cards/__init__.py +116 -0
- induscode/capability_deck/cards/bg_process.py +482 -0
- induscode/capability_deck/cards/memory.py +226 -0
- induscode/capability_deck/cards/saas.py +280 -0
- induscode/capability_deck/cards/task.py +256 -0
- induscode/capability_deck/cards/todo.py +312 -0
- induscode/capability_deck/contract.py +450 -0
- induscode/capability_deck/manifest.py +126 -0
- induscode/capability_deck/provision.py +217 -0
- induscode/channels/__init__.py +146 -0
- induscode/channels/contract.py +585 -0
- induscode/channels/framer.py +132 -0
- induscode/channels/link/__init__.py +50 -0
- induscode/channels/link/dialog.py +246 -0
- induscode/channels/link/driver.py +308 -0
- induscode/channels/link/server.py +217 -0
- induscode/channels/oneshot.py +178 -0
- induscode/channels/ops.py +140 -0
- induscode/channels/session_ops.py +172 -0
- induscode/conductor/__init__.py +240 -0
- induscode/conductor/catalog.py +309 -0
- induscode/conductor/conductor.py +1084 -0
- induscode/conductor/contract.py +1035 -0
- induscode/conductor/matcher.py +291 -0
- induscode/conductor/serialize.py +575 -0
- induscode/conductor/signal_hub.py +382 -0
- induscode/conductor/skill_parse.py +294 -0
- induscode/conductor/transcript_store.py +449 -0
- induscode/console/__init__.py +236 -0
- induscode/console/app.py +1677 -0
- induscode/console/components/__init__.py +62 -0
- induscode/console/components/banner.py +499 -0
- induscode/console/components/banner_sweep.py +188 -0
- induscode/console/components/emblem.py +181 -0
- induscode/console/components/status_bar.py +102 -0
- induscode/console/contract.py +836 -0
- induscode/console/input/__init__.py +107 -0
- induscode/console/input/chord.py +197 -0
- induscode/console/input/dir_reader.py +113 -0
- induscode/console/input/intents.py +258 -0
- induscode/console/input/providers.py +469 -0
- induscode/console/mount.py +137 -0
- induscode/console/overlays/__init__.py +94 -0
- induscode/console/overlays/auth.py +503 -0
- induscode/console/overlays/pickers.py +526 -0
- induscode/console/overlays/router.py +129 -0
- induscode/console/overlays/sessions.py +232 -0
- induscode/console/reducer.py +145 -0
- induscode/console/resume_picker.py +156 -0
- induscode/console/slash_commands/__init__.py +78 -0
- induscode/console/slash_commands/builtins.py +254 -0
- induscode/console/slash_commands/dynamic.py +217 -0
- induscode/console/slash_commands/integrations.py +949 -0
- induscode/console/slash_commands/transcript.py +404 -0
- induscode/console/slash_commands/workbench.py +430 -0
- induscode/console/startup.py +434 -0
- induscode/console/theme/__init__.py +44 -0
- induscode/console/theme/adapter.py +168 -0
- induscode/console/theme/palette.py +128 -0
- induscode/console/theme/resolve.py +123 -0
- induscode/console/theme/tokens.py +185 -0
- induscode/console_slash/__init__.py +111 -0
- induscode/console_slash/contract.py +185 -0
- induscode/console_slash/registry.py +140 -0
- induscode/console_slash/resolve.py +194 -0
- induscode/console_slash/shared.py +172 -0
- induscode/entry.py +108 -0
- induscode/insight/__init__.py +153 -0
- induscode/insight/collector.py +73 -0
- induscode/insight/replay.py +305 -0
- induscode/insight/wrapper.py +1115 -0
- induscode/kit/__init__.py +82 -0
- induscode/kit/clipboard_image.py +215 -0
- induscode/kit/external_editor.py +120 -0
- induscode/kit/image.py +188 -0
- induscode/kit/shell.py +89 -0
- induscode/kit/tool_fetch.py +288 -0
- induscode/launch/__init__.py +224 -0
- induscode/launch/catalog.py +310 -0
- induscode/launch/contract.py +569 -0
- induscode/launch/credentials.py +852 -0
- induscode/launch/invocation/__init__.py +39 -0
- induscode/launch/invocation/attachments.py +281 -0
- induscode/launch/invocation/flags.py +210 -0
- induscode/launch/invocation/read.py +369 -0
- induscode/launch/invocation/usage.py +110 -0
- induscode/launch/oauth.py +808 -0
- induscode/launch/packages.py +299 -0
- induscode/launch/pickers.py +291 -0
- induscode/py.typed +0 -0
- induscode/runtime_bridge/__init__.py +166 -0
- induscode/runtime_bridge/bridges/__init__.py +66 -0
- induscode/runtime_bridge/bridges/_drive.py +268 -0
- induscode/runtime_bridge/bridges/builtins.py +177 -0
- induscode/runtime_bridge/bridges/claude_cli.py +198 -0
- induscode/runtime_bridge/bridges/codex_cli.py +203 -0
- induscode/runtime_bridge/bridges/indusagi_cli.py +217 -0
- induscode/runtime_bridge/broker.py +397 -0
- induscode/runtime_bridge/contract.py +734 -0
- induscode/runtime_bridge/sink.py +351 -0
- induscode/sessions/__init__.py +25 -0
- induscode/sessions/contract.py +119 -0
- induscode/sessions/library.py +350 -0
- induscode/settings/__init__.py +47 -0
- induscode/settings/contract.py +313 -0
- induscode/settings/manager.py +268 -0
- induscode/transcript_export/__init__.py +109 -0
- induscode/transcript_export/contract.py +522 -0
- induscode/transcript_export/publish.py +455 -0
- induscode/transcript_export/sgr.py +566 -0
- induscode/transcript_export/template.py +319 -0
- induscode/transcript_export/theme_bridge.py +325 -0
- induscode/window_budget/__init__.py +76 -0
- induscode/window_budget/budget/__init__.py +26 -0
- induscode/window_budget/budget/estimate.py +273 -0
- induscode/window_budget/budget/gate.py +60 -0
- induscode/window_budget/budget/slice.py +145 -0
- induscode/window_budget/condenser.py +170 -0
- induscode/window_budget/contract.py +329 -0
- induscode/window_budget/summarize/__init__.py +33 -0
- induscode/window_budget/summarize/condense.py +212 -0
- induscode/window_budget/summarize/prompt.py +241 -0
- induscode/workspace/__init__.py +30 -0
- induscode/workspace/brand.py +96 -0
- induscode/workspace/locator.py +269 -0
- induscode-0.1.0.dist-info/METADATA +97 -0
- induscode-0.1.0.dist-info/RECORD +167 -0
- induscode-0.1.0.dist-info/WHEEL +4 -0
- induscode-0.1.0.dist-info/entry_points.txt +3 -0
- induscode-0.1.0.dist-info/licenses/CREDITS.md +22 -0
- induscode-0.1.0.dist-info/licenses/NOTICE +7 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
"""Briefing contract — the FROZEN type surface of the prompt layer.
|
|
2
|
+
|
|
3
|
+
This module is the single typed seam between the coding-agent's runtime state
|
|
4
|
+
and the LLM-facing **briefing** (the system prompt). It declares *only* shapes
|
|
5
|
+
plus a handful of tiny inert helpers — no filesystem walks, no string
|
|
6
|
+
assembly — so every later module (the section pipeline, the macro scanner, and
|
|
7
|
+
the capability-card loader) is written against the names declared here. The
|
|
8
|
+
file is intentionally small, append-mostly, and stable.
|
|
9
|
+
|
|
10
|
+
Design stance (ported from TS ``src/briefing/contract.ts``):
|
|
11
|
+
|
|
12
|
+
- The briefing is **declarative, not a string template**. A :data:`Briefing`
|
|
13
|
+
is an ordered list of :class:`BriefingSection` descriptors, each a small
|
|
14
|
+
record that decides whether it applies to a :class:`BriefingContext` and
|
|
15
|
+
renders its own fragment. :func:`~induscode.briefing.compose.compose_briefing`
|
|
16
|
+
folds the enabled sections into the final prompt; there is no mega-literal
|
|
17
|
+
with ``{{TOKEN}}`` placeholders.
|
|
18
|
+
- Macros are a **single-pass ``$arg`` model**. A :class:`Macro` carries a body
|
|
19
|
+
and a source label; expansion is one left-to-right scan over the body that
|
|
20
|
+
resolves ``$1`` / ``$@`` / ``$ARGUMENTS`` / ``${@:N:L}`` against a
|
|
21
|
+
:class:`MacroScope`. The contract names the token kinds and the scope; it
|
|
22
|
+
does not run regexes.
|
|
23
|
+
- A :class:`SkillCard` is a CapabilityCard parsed from a ``SKILL.md`` document.
|
|
24
|
+
The Agent-Skills *format* (frontmatter keys, name/description limits) is a
|
|
25
|
+
public spec and is kept; the validation prose and field policy are the
|
|
26
|
+
briefing's own.
|
|
27
|
+
|
|
28
|
+
Port note — transcript-export types relocated
|
|
29
|
+
---------------------------------------------
|
|
30
|
+
The TS ``briefing/contract.ts`` additionally hosted the shared types of the
|
|
31
|
+
HTML transcript exporter: the SGR machine (``SgrState`` / ``SgrToken`` /
|
|
32
|
+
``SgrMutation`` / ``SGR_INITIAL_STATE``), the export palette (``ExportTheme``
|
|
33
|
+
/ ``FALLBACK_EXPORT_THEME`` / ``Rgb`` / ``LuminanceLut`` / ``ThemeMode`` /
|
|
34
|
+
``ThemeBridge``), and the publish surface (``TranscriptPart`` /
|
|
35
|
+
``WidgetRender`` / ``PublishOptions`` / ``SHELL_SLOTS`` / ``ShellSlot``).
|
|
36
|
+
In the Python build those types are owned by
|
|
37
|
+
``induscode.transcript_export.contract`` (ported separately) — this module
|
|
38
|
+
holds only the briefing-owned vocabulary: sections, macros, skills,
|
|
39
|
+
:class:`ContextDoc`, and :class:`BriefingFault` (which transcript-export
|
|
40
|
+
imports from here, fault kinds ``publish`` / ``theme`` included, so the closed
|
|
41
|
+
fault set stays in one place).
|
|
42
|
+
|
|
43
|
+
Framework anchors (all from the ``indusagi`` package — the sibling rebuilt
|
|
44
|
+
framework this app targets):
|
|
45
|
+
|
|
46
|
+
- :class:`AgentState`, :class:`AgentTool` ← ``indusagi.agent``
|
|
47
|
+
- :class:`TextContent`, :class:`ImageContent` ← ``indusagi.ai``
|
|
48
|
+
|
|
49
|
+
The briefing never re-declares these; it composes them.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
from __future__ import annotations
|
|
53
|
+
|
|
54
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
55
|
+
from dataclasses import dataclass
|
|
56
|
+
from datetime import datetime
|
|
57
|
+
from typing import ClassVar, Final, Literal, TypeAlias
|
|
58
|
+
|
|
59
|
+
from indusagi.agent import AgentState, AgentTool
|
|
60
|
+
from indusagi.ai import ImageContent, TextContent
|
|
61
|
+
|
|
62
|
+
__all__ = [
|
|
63
|
+
"AgentState",
|
|
64
|
+
"AgentTool",
|
|
65
|
+
"AllToken",
|
|
66
|
+
"Briefing",
|
|
67
|
+
"BriefingContext",
|
|
68
|
+
"BriefingFault",
|
|
69
|
+
"BriefingFaultKind",
|
|
70
|
+
"BriefingInputs",
|
|
71
|
+
"BriefingSection",
|
|
72
|
+
"ContextDoc",
|
|
73
|
+
"ImageContent",
|
|
74
|
+
"LiteralToken",
|
|
75
|
+
"Macro",
|
|
76
|
+
"MacroOrigin",
|
|
77
|
+
"MacroScope",
|
|
78
|
+
"MacroToken",
|
|
79
|
+
"MacroTokenKind",
|
|
80
|
+
"PositionalToken",
|
|
81
|
+
"SKILL_DESCRIPTION_LIMIT",
|
|
82
|
+
"SKILL_NAME_LIMIT",
|
|
83
|
+
"SkillCard",
|
|
84
|
+
"SkillDiagnostic",
|
|
85
|
+
"SkillFrontmatter",
|
|
86
|
+
"SkillLoad",
|
|
87
|
+
"SkillOutcomeKind",
|
|
88
|
+
"SliceToken",
|
|
89
|
+
"SubagentBrief",
|
|
90
|
+
"TextContent",
|
|
91
|
+
"briefing_fault",
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Briefing sections (declarative system-prompt pipeline)
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
101
|
+
class SubagentBrief:
|
|
102
|
+
"""A named delegate role advertised in the subagent section of the briefing.
|
|
103
|
+
|
|
104
|
+
The minimum a section needs to list a delegate the primary agent may hand
|
|
105
|
+
work to: a stable invocation ``name``, a one-line ``purpose``, and an
|
|
106
|
+
optional ``when`` hint describing the situations the role is suited to.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
# Stable name the primary agent uses to delegate to this role.
|
|
110
|
+
name: str
|
|
111
|
+
# One-line description of what the role is for.
|
|
112
|
+
purpose: str
|
|
113
|
+
# Optional hint describing when delegating to this role is appropriate.
|
|
114
|
+
when: str | None = None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
118
|
+
class ContextDoc:
|
|
119
|
+
"""A project-context document inlined into the briefing.
|
|
120
|
+
|
|
121
|
+
Project files (AGENTS-style guidance) are surfaced under their own section
|
|
122
|
+
so the model can read repository conventions. ``path`` labels the source;
|
|
123
|
+
``body`` is the verbatim text to inline.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
# Source path of the document, used as its heading label.
|
|
127
|
+
path: str
|
|
128
|
+
# Verbatim document text to inline into the briefing.
|
|
129
|
+
body: str
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
133
|
+
class BriefingContext:
|
|
134
|
+
"""The working context a :class:`BriefingSection` reads when it decides
|
|
135
|
+
whether to apply and what to render.
|
|
136
|
+
|
|
137
|
+
This is the data-driven replacement for the old string-template's
|
|
138
|
+
placeholder bag. Every field is optional so a section that does not need a
|
|
139
|
+
given input simply ignores it, and a caller can compose a partial briefing
|
|
140
|
+
(e.g. one with no skills, or no project docs) without populating the whole
|
|
141
|
+
shape. A section is pure with respect to this context: it reads, it never
|
|
142
|
+
mutates.
|
|
143
|
+
|
|
144
|
+
- ``workspace`` — absolute working directory the session is scoped to; the
|
|
145
|
+
footer section surfaces it and skills resolve relative paths against it.
|
|
146
|
+
- ``tools`` — the capabilities advertised this turn; the tools/guidelines
|
|
147
|
+
sections gate themselves on which ids are present.
|
|
148
|
+
- ``cwd`` — alias kept distinct from ``workspace`` for sections that want
|
|
149
|
+
the *display* path (may be relativized) versus the absolute root.
|
|
150
|
+
- ``skills`` — the :class:`SkillCard` records eligible for model
|
|
151
|
+
invocation; the skills section renders these into its block.
|
|
152
|
+
- ``subagents`` — the named delegate roles to advertise in the delegate
|
|
153
|
+
block.
|
|
154
|
+
- ``context_docs`` — project context documents (AGENTS-style files) to
|
|
155
|
+
inline.
|
|
156
|
+
- ``now`` — the timestamp the footer stamps; injectable for determinism.
|
|
157
|
+
- ``extras`` — an open bag for app-novel sections to read bespoke inputs
|
|
158
|
+
without widening this contract on every addition.
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
# Absolute working directory the session's briefing is scoped to.
|
|
162
|
+
workspace: str | None = None
|
|
163
|
+
# The capabilities advertised this turn, by their wire-facing descriptors.
|
|
164
|
+
tools: Sequence[AgentTool] | None = None
|
|
165
|
+
# Display path for the working directory (may be relativized).
|
|
166
|
+
cwd: str | None = None
|
|
167
|
+
# Capability cards eligible for model invocation, for the skills block.
|
|
168
|
+
skills: Sequence[SkillCard] | None = None
|
|
169
|
+
# Named delegate roles to advertise in the subagent/delegate section.
|
|
170
|
+
subagents: Sequence[SubagentBrief] | None = None
|
|
171
|
+
# Project context documents to inline under a project-context section.
|
|
172
|
+
context_docs: Sequence[ContextDoc] | None = None
|
|
173
|
+
# Timestamp the footer stamps; injectable so renders are deterministic.
|
|
174
|
+
now: datetime | None = None
|
|
175
|
+
# Open bag for app-novel sections to read bespoke inputs.
|
|
176
|
+
extras: Mapping[str, object] | None = None
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
180
|
+
class BriefingSection:
|
|
181
|
+
"""One declarative section of the briefing.
|
|
182
|
+
|
|
183
|
+
The unit of the data-driven pipeline that replaces the old string
|
|
184
|
+
template: an ordered array of these is composed by
|
|
185
|
+
:func:`~induscode.briefing.compose.compose_briefing`, which renders each
|
|
186
|
+
applicable section and joins the fragments. A section owns its own gating
|
|
187
|
+
— via the optional :attr:`applies` predicate — and its own rendering, so
|
|
188
|
+
adding a section is adding one descriptor to the array, not editing a
|
|
189
|
+
central literal.
|
|
190
|
+
|
|
191
|
+
- ``id`` — stable identifier for ordering, dedup, and selective inclusion.
|
|
192
|
+
- ``title`` — optional human-facing heading the section may emit.
|
|
193
|
+
- ``applies`` — optional predicate deciding whether this section
|
|
194
|
+
contributes to a given context; when ``None`` the section is treated as
|
|
195
|
+
always applicable.
|
|
196
|
+
- ``render`` — produce the section's fragment for a context. Pure: reads
|
|
197
|
+
the context, returns a string, performs no I/O and mutates nothing.
|
|
198
|
+
Returning an empty string is allowed and is dropped from the composed
|
|
199
|
+
output.
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
# Stable identifier for ordering, dedup, and selective inclusion.
|
|
203
|
+
id: str
|
|
204
|
+
# Optional human-facing heading the section may emit.
|
|
205
|
+
title: str | None = None
|
|
206
|
+
# Optional predicate; when None the section always renders.
|
|
207
|
+
applies: Callable[[BriefingContext], bool] | None = None
|
|
208
|
+
# Render this section's fragment for a context (pure).
|
|
209
|
+
render: Callable[[BriefingContext], str]
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
#: An ordered briefing recipe: the sections, in render order.
|
|
213
|
+
#:
|
|
214
|
+
#: ``compose_briefing`` walks this list, skips sections whose
|
|
215
|
+
#: :attr:`BriefingSection.applies` returns ``False``, renders the rest, and
|
|
216
|
+
#: joins the non-empty fragments. The recipe is data; swapping the section set
|
|
217
|
+
#: (or its order) reconfigures the whole prompt without touching the composer.
|
|
218
|
+
Briefing: TypeAlias = Sequence[BriefingSection]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
222
|
+
class BriefingInputs:
|
|
223
|
+
"""Inputs handed to ``compose_briefing`` to assemble a full briefing string.
|
|
224
|
+
|
|
225
|
+
Pairs the :data:`Briefing` recipe with the :class:`BriefingContext` to
|
|
226
|
+
render it against, plus two override hooks that mirror the legacy
|
|
227
|
+
custom-prompt path: ``prelude`` text prepended ahead of the sections, and
|
|
228
|
+
``append`` text added after them. Both are optional; the common case
|
|
229
|
+
passes only ``sections`` and ``context``.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
# The ordered section recipe to render.
|
|
233
|
+
sections: Briefing
|
|
234
|
+
# The context the sections render against.
|
|
235
|
+
context: BriefingContext
|
|
236
|
+
# Optional text prepended ahead of the rendered sections.
|
|
237
|
+
prelude: str | None = None
|
|
238
|
+
# Optional text appended after the rendered sections.
|
|
239
|
+
append: str | None = None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# ---------------------------------------------------------------------------
|
|
243
|
+
# Macros (single-pass $arg model)
|
|
244
|
+
# ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
#: Where a :class:`Macro` was discovered, for labelling and precedence.
|
|
247
|
+
#:
|
|
248
|
+
#: - ``user`` — the per-user macro directory.
|
|
249
|
+
#: - ``project`` — the project-local macro directory.
|
|
250
|
+
#: - ``path`` — an explicitly supplied path outside the standard roots.
|
|
251
|
+
#: - ``builtin`` — a macro shipped with the app.
|
|
252
|
+
MacroOrigin: TypeAlias = Literal["user", "project", "path", "builtin"]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
256
|
+
class Macro:
|
|
257
|
+
"""A user-defined slash macro: a named prompt body with ``$arg`` placeholders.
|
|
258
|
+
|
|
259
|
+
Discovered from ``*.md`` files (frontmatter + body) under the
|
|
260
|
+
user/project/path roots. Invoking ``/<name> args…`` resolves the body
|
|
261
|
+
against a :class:`MacroScope` built from the args, substituting the
|
|
262
|
+
``$arg`` tokens in one pass. The field names are the briefing's own; the
|
|
263
|
+
placeholder *syntax* is the shared cross-tool convention and is preserved.
|
|
264
|
+
|
|
265
|
+
- ``name`` — the invocation name (without the leading slash).
|
|
266
|
+
- ``description`` — one-line summary for command listings; derived from
|
|
267
|
+
frontmatter or the body's opening, with an ``origin``-derived label.
|
|
268
|
+
- ``body`` — the prompt text containing the ``$arg`` placeholders.
|
|
269
|
+
- ``origin`` — where the macro was found, for labelling and precedence.
|
|
270
|
+
- ``source`` — absolute path of the file the macro was loaded from.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
# Invocation name, without the leading slash.
|
|
274
|
+
name: str
|
|
275
|
+
# One-line description for command listings.
|
|
276
|
+
description: str
|
|
277
|
+
# Prompt body containing `$arg` placeholders.
|
|
278
|
+
body: str
|
|
279
|
+
# Where the macro was discovered.
|
|
280
|
+
origin: MacroOrigin
|
|
281
|
+
# Absolute path of the file the macro was loaded from.
|
|
282
|
+
source: str
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
286
|
+
class MacroScope:
|
|
287
|
+
"""The argument environment a :class:`Macro` body is resolved against.
|
|
288
|
+
|
|
289
|
+
Built from a parsed invocation: ``args`` is the positional vector (1-based
|
|
290
|
+
in the ``$N`` syntax, 0-based in this tuple), ``all`` is the original
|
|
291
|
+
joined argument string the ``$@`` / ``$ARGUMENTS`` tokens expand to, and
|
|
292
|
+
``raw`` is the untouched text after the command name (kept for diagnostics
|
|
293
|
+
and for tokens that want the pre-split form). A token references this
|
|
294
|
+
scope; the scanner reads it.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
# Positional arguments, in order; `$1` maps to index 0.
|
|
298
|
+
args: tuple[str, ...]
|
|
299
|
+
# The joined argument string `$@` / `$ARGUMENTS` expand to.
|
|
300
|
+
all: str
|
|
301
|
+
# The verbatim text following the command name, before splitting.
|
|
302
|
+
raw: str
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
#: The kinds of unit the single-pass ``$arg`` scanner emits.
|
|
306
|
+
#:
|
|
307
|
+
#: - ``literal`` — a run of ordinary text copied through unchanged.
|
|
308
|
+
#: - ``positional`` — a ``$N`` reference to one positional argument.
|
|
309
|
+
#: - ``all`` — a ``$@`` or ``$ARGUMENTS`` reference to the joined arguments.
|
|
310
|
+
#: - ``slice`` — a ``${@:N}`` / ``${@:N:L}`` reference to an argument
|
|
311
|
+
#: sub-range.
|
|
312
|
+
MacroTokenKind: TypeAlias = Literal["literal", "positional", "all", "slice"]
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
@dataclass(frozen=True, slots=True)
|
|
316
|
+
class LiteralToken:
|
|
317
|
+
"""A run of ordinary text copied through unchanged."""
|
|
318
|
+
|
|
319
|
+
kind: ClassVar[Literal["literal"]] = "literal"
|
|
320
|
+
text: str
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
@dataclass(frozen=True, slots=True)
|
|
324
|
+
class PositionalToken:
|
|
325
|
+
"""A ``$N`` reference carrying the 1-based ``index`` of its argument."""
|
|
326
|
+
|
|
327
|
+
kind: ClassVar[Literal["positional"]] = "positional"
|
|
328
|
+
index: int
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@dataclass(frozen=True, slots=True)
|
|
332
|
+
class AllToken:
|
|
333
|
+
"""A ``$@`` / ``$ARGUMENTS`` reference; carries nothing beyond its kind."""
|
|
334
|
+
|
|
335
|
+
kind: ClassVar[Literal["all"]] = "all"
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
@dataclass(frozen=True, slots=True)
|
|
339
|
+
class SliceToken:
|
|
340
|
+
"""A ``${@:N}`` / ``${@:N:L}`` reference carrying ``start`` and an
|
|
341
|
+
optional ``length``."""
|
|
342
|
+
|
|
343
|
+
kind: ClassVar[Literal["slice"]] = "slice"
|
|
344
|
+
start: int
|
|
345
|
+
length: int | None = None
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
#: One unit produced by the single-pass macro scanner.
|
|
349
|
+
#:
|
|
350
|
+
#: The body is scanned left-to-right into a flat token stream; resolving the
|
|
351
|
+
#: stream against a :class:`MacroScope` and concatenating yields the expanded
|
|
352
|
+
#: text. This replaces the legacy multi-pass regex substitution with one scan
|
|
353
|
+
#: emitting these discriminated tokens.
|
|
354
|
+
MacroToken: TypeAlias = LiteralToken | PositionalToken | AllToken | SliceToken
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
# ---------------------------------------------------------------------------
|
|
358
|
+
# Skill cards (CapabilityCard loaded from a SKILL.md)
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
363
|
+
class SkillFrontmatter:
|
|
364
|
+
"""The parsed frontmatter of a ``SKILL.md`` document.
|
|
365
|
+
|
|
366
|
+
The Agent-Skills format is a public spec, so these keys are kept; the
|
|
367
|
+
policy over them (which are required, which are allowed) is the briefing's
|
|
368
|
+
own. Only ``name`` and ``description`` carry semantics for the prompt; the
|
|
369
|
+
rest are metadata the loader preserves but does not interpret.
|
|
370
|
+
|
|
371
|
+
- ``name`` — the skill's invocation name; must match its directory and use
|
|
372
|
+
lowercase hyphenated segments.
|
|
373
|
+
- ``description`` — required, single-line summary the model reads to
|
|
374
|
+
decide when the skill applies.
|
|
375
|
+
- ``license`` — optional SPDX-style license tag.
|
|
376
|
+
- ``compatibility`` — optional free-form compatibility note.
|
|
377
|
+
- ``metadata`` — optional open key/value bag.
|
|
378
|
+
- ``allowed_tools`` — optional restriction on which tools the skill may
|
|
379
|
+
use while active (the format's kebab-case ``allowed-tools`` key).
|
|
380
|
+
- ``disable_model_invocation`` — when true the skill is hidden from the
|
|
381
|
+
briefing and exposed only as an explicit ``/skill:<name>`` command (the
|
|
382
|
+
format's ``disable-model-invocation`` key).
|
|
383
|
+
"""
|
|
384
|
+
|
|
385
|
+
# Invocation name; must match the parent directory, lowercase-hyphenated.
|
|
386
|
+
name: str | None = None
|
|
387
|
+
# Required single-line summary the model reads to gate the skill.
|
|
388
|
+
description: str | None = None
|
|
389
|
+
# Optional SPDX-style license tag.
|
|
390
|
+
license: str | None = None
|
|
391
|
+
# Optional free-form compatibility note.
|
|
392
|
+
compatibility: str | None = None
|
|
393
|
+
# Optional open metadata bag.
|
|
394
|
+
metadata: Mapping[str, object] | None = None
|
|
395
|
+
# Optional restriction on tools the skill may use while active.
|
|
396
|
+
allowed_tools: tuple[str, ...] | None = None
|
|
397
|
+
# Hide from the briefing; expose only as an explicit command.
|
|
398
|
+
disable_model_invocation: bool | None = None
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
402
|
+
class SkillCard:
|
|
403
|
+
"""A capability card loaded from a ``SKILL.md`` document.
|
|
404
|
+
|
|
405
|
+
The resolved, validated form of one skill: its ``name``, the
|
|
406
|
+
``description`` the model reads, the markdown ``body`` (instructions
|
|
407
|
+
loaded on demand), and the on-disk ``location`` of the file. ``origin``
|
|
408
|
+
records where it was discovered and ``frontmatter`` retains the parsed
|
|
409
|
+
header for introspection. Unlike a deck capability, a skill card is
|
|
410
|
+
*documentation the model reads*, not a callable — the model loads
|
|
411
|
+
``location`` with the reader when a task matches ``description``.
|
|
412
|
+
"""
|
|
413
|
+
|
|
414
|
+
# Validated invocation name.
|
|
415
|
+
name: str
|
|
416
|
+
# Single-line summary surfaced in the briefing.
|
|
417
|
+
description: str
|
|
418
|
+
# Markdown instruction text below the frontmatter.
|
|
419
|
+
body: str
|
|
420
|
+
# Absolute path of the `SKILL.md` file.
|
|
421
|
+
location: str
|
|
422
|
+
# Where the card was discovered.
|
|
423
|
+
origin: MacroOrigin
|
|
424
|
+
# Parsed frontmatter, retained for introspection.
|
|
425
|
+
frontmatter: SkillFrontmatter
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
#: Outcome categories the skill loader can attach to a candidate file.
|
|
429
|
+
#:
|
|
430
|
+
#: - ``loaded`` — a valid card was produced.
|
|
431
|
+
#: - ``skipped`` — the file was ignored by policy (e.g. not a ``SKILL.md``).
|
|
432
|
+
#: - ``invalid`` — the file was a skill but failed validation.
|
|
433
|
+
#: - ``collision`` — a card with the same name already won; this one was
|
|
434
|
+
#: dropped.
|
|
435
|
+
SkillOutcomeKind: TypeAlias = Literal["loaded", "skipped", "invalid", "collision"]
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
439
|
+
class SkillDiagnostic:
|
|
440
|
+
"""A single diagnostic the skill loader emits for one candidate file.
|
|
441
|
+
|
|
442
|
+
Replaces the legacy parallel ``{skills, diagnostics}`` arrays with one
|
|
443
|
+
tagged record per candidate: ``kind`` is the outcome, ``location`` the
|
|
444
|
+
file it concerns, and ``detail`` a human-readable explanation written in
|
|
445
|
+
the briefing's own voice.
|
|
446
|
+
"""
|
|
447
|
+
|
|
448
|
+
# Outcome category for the candidate.
|
|
449
|
+
kind: SkillOutcomeKind
|
|
450
|
+
# Absolute path of the candidate file the diagnostic concerns.
|
|
451
|
+
location: str
|
|
452
|
+
# Human-readable explanation of the outcome.
|
|
453
|
+
detail: str
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@dataclass(frozen=True, slots=True, kw_only=True)
|
|
457
|
+
class SkillLoad:
|
|
458
|
+
"""The aggregate result of loading skills from a set of roots.
|
|
459
|
+
|
|
460
|
+
``cards`` are the validated, deduped cards (first writer wins per name);
|
|
461
|
+
``diagnostics`` is the flat stream of per-candidate outcomes for surfacing
|
|
462
|
+
in a load report. A consumer renders ``cards`` into the briefing and may
|
|
463
|
+
show ``diagnostics`` to the user.
|
|
464
|
+
"""
|
|
465
|
+
|
|
466
|
+
# Validated, deduped cards in discovery order.
|
|
467
|
+
cards: tuple[SkillCard, ...]
|
|
468
|
+
# Per-candidate outcomes accumulated during the load.
|
|
469
|
+
diagnostics: tuple[SkillDiagnostic, ...]
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
#: Maximum length of a validated skill name, per the Agent-Skills format.
|
|
473
|
+
SKILL_NAME_LIMIT: Final[int] = 64
|
|
474
|
+
|
|
475
|
+
#: Maximum length of a validated skill description, per the format.
|
|
476
|
+
SKILL_DESCRIPTION_LIMIT: Final[int] = 1024
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
# ---------------------------------------------------------------------------
|
|
480
|
+
# Faults
|
|
481
|
+
# ---------------------------------------------------------------------------
|
|
482
|
+
|
|
483
|
+
#: The closed set of failure categories this layer can surface.
|
|
484
|
+
#:
|
|
485
|
+
#: - ``skill_invalid`` — a ``SKILL.md`` failed format validation.
|
|
486
|
+
#: - ``macro_invalid`` — a macro file could not be parsed.
|
|
487
|
+
#: - ``publish`` — building or writing the HTML transcript failed (raised by
|
|
488
|
+
#: ``induscode.transcript_export``, which shares this fault type).
|
|
489
|
+
#: - ``theme`` — a theme color could not be parsed or derived (ditto).
|
|
490
|
+
BriefingFaultKind: TypeAlias = Literal["skill_invalid", "macro_invalid", "publish", "theme"]
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
class BriefingFault(Exception):
|
|
494
|
+
"""A typed, discriminated failure raised by the briefing/transcript layer.
|
|
495
|
+
|
|
496
|
+
``kind`` selects the category, ``message`` is a human-readable summary,
|
|
497
|
+
and the optional ``cause`` carries the underlying error for logging
|
|
498
|
+
without forcing consumers to parse the message. Construct one with
|
|
499
|
+
:func:`briefing_fault`.
|
|
500
|
+
|
|
501
|
+
Port note: the TS shape was a plain frozen record ``throw``-n as a value;
|
|
502
|
+
Python requires raisables to be exceptions, so the same three fields ride
|
|
503
|
+
on an :class:`Exception` subclass.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
def __init__(
|
|
507
|
+
self, kind: BriefingFaultKind, message: str, cause: object | None = None
|
|
508
|
+
) -> None:
|
|
509
|
+
super().__init__(message)
|
|
510
|
+
# Failure category — the discriminant consumers switch on.
|
|
511
|
+
self.kind: BriefingFaultKind = kind
|
|
512
|
+
# Human-readable, single-line summary of what went wrong.
|
|
513
|
+
self.message: str = message
|
|
514
|
+
# Underlying error or structured detail, if any.
|
|
515
|
+
self.cause: object | None = cause
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def briefing_fault(
|
|
519
|
+
kind: BriefingFaultKind, message: str, cause: object | None = None
|
|
520
|
+
) -> BriefingFault:
|
|
521
|
+
"""Construct a :class:`BriefingFault`. The single sanctioned way to mint
|
|
522
|
+
one, so the shape stays uniform across every producer.
|
|
523
|
+
|
|
524
|
+
:param kind: the failure category
|
|
525
|
+
:param message: a human-readable, single-line summary
|
|
526
|
+
:param cause: optional underlying error or structured detail
|
|
527
|
+
"""
|
|
528
|
+
return BriefingFault(kind, message, cause)
|