dos-kernel 0.22.0__py3-none-win_amd64.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.
- dos/__init__.py +261 -0
- dos/_bin/dos-hook.exe +0 -0
- dos/_filelock.py +255 -0
- dos/_job_policy.py +97 -0
- dos/_tree.py +145 -0
- dos/admission.py +433 -0
- dos/answer_shape.py +299 -0
- dos/arbiter.py +859 -0
- dos/archive_lock.py +266 -0
- dos/arg_provenance.py +814 -0
- dos/attest.py +472 -0
- dos/breaker.py +311 -0
- dos/churn.py +226 -0
- dos/claim_extract.py +229 -0
- dos/claim_ttl.py +150 -0
- dos/cli.py +8721 -0
- dos/commit_audit.py +666 -0
- dos/completion.py +466 -0
- dos/concurrency_class.py +154 -0
- dos/config.py +1380 -0
- dos/config_lint.py +464 -0
- dos/cooldown.py +390 -0
- dos/coverage.py +387 -0
- dos/dangling_intent.py +287 -0
- dos/data_class.py +397 -0
- dos/decisions.py +1274 -0
- dos/decisions_tui.py +251 -0
- dos/dispatch_top.py +740 -0
- dos/dispatch_top_tui.py +116 -0
- dos/drivers/__init__.py +40 -0
- dos/drivers/ci_status.py +630 -0
- dos/drivers/citation_resolve.py +703 -0
- dos/drivers/decision_stop.py +98 -0
- dos/drivers/export_file.py +173 -0
- dos/drivers/export_otlp.py +275 -0
- dos/drivers/export_statsd.py +242 -0
- dos/drivers/hook_dialects.py +391 -0
- dos/drivers/job.py +47 -0
- dos/drivers/llm_judge.py +360 -0
- dos/drivers/memory_recall.py +1231 -0
- dos/drivers/notify_slack.py +373 -0
- dos/drivers/notify_webhook.py +251 -0
- dos/drivers/operator_judge.py +114 -0
- dos/drivers/os_acceptance.py +228 -0
- dos/drivers/paste_log.py +132 -0
- dos/drivers/plan_scope.py +133 -0
- dos/drivers/self_improve.py +375 -0
- dos/drivers/similarity_judge.py +249 -0
- dos/drivers/state_diff.py +274 -0
- dos/drivers/supervisor.py +347 -0
- dos/drivers/watchdog.py +363 -0
- dos/drivers/workshop.py +160 -0
- dos/durable_schema.py +344 -0
- dos/effect_witness.py +393 -0
- dos/efficiency.py +318 -0
- dos/enforce.py +414 -0
- dos/enumerate.py +776 -0
- dos/env_print.py +378 -0
- dos/event_severity.py +258 -0
- dos/evidence.py +692 -0
- dos/exec_capability.py +256 -0
- dos/export_cursor.py +143 -0
- dos/exporter.py +320 -0
- dos/firing_label.py +353 -0
- dos/fleet_roll.py +226 -0
- dos/gate_classify.py +827 -0
- dos/gh4_coverage.py +179 -0
- dos/git_delta.py +122 -0
- dos/guard.py +215 -0
- dos/health.py +552 -0
- dos/help_summary.py +519 -0
- dos/home.py +934 -0
- dos/hook_binary.py +194 -0
- dos/hook_dialect.py +271 -0
- dos/hook_exit.py +191 -0
- dos/hook_install.py +437 -0
- dos/id_alloc.py +304 -0
- dos/improve.py +499 -0
- dos/intent_ledger.py +635 -0
- dos/interpret.py +176 -0
- dos/intervention.py +769 -0
- dos/intervention_eval.py +371 -0
- dos/journal_delta.py +308 -0
- dos/judge_eval.py +328 -0
- dos/judges.py +366 -0
- dos/lane_infer.py +127 -0
- dos/lane_journal.py +1001 -0
- dos/lane_lease.py +952 -0
- dos/lane_overlap.py +228 -0
- dos/lease_health.py +282 -0
- dos/lifecycle.py +211 -0
- dos/liveness.py +352 -0
- dos/lock_modes.py +185 -0
- dos/log_source.py +395 -0
- dos/loop_decide.py +1746 -0
- dos/marker_gate.py +254 -0
- dos/marker_sensor.py +396 -0
- dos/noop_streak.py +280 -0
- dos/notify.py +479 -0
- dos/observe.py +175 -0
- dos/oracle.py +1661 -0
- dos/overlap_eval.py +214 -0
- dos/overlap_policy.py +342 -0
- dos/packet_sidecar.py +267 -0
- dos/phase_shipped.py +1985 -0
- dos/pick_priority.py +225 -0
- dos/pickable.py +369 -0
- dos/picker_oracle.py +1037 -0
- dos/plan_board.py +513 -0
- dos/plan_board_tui.py +113 -0
- dos/plan_source.py +455 -0
- dos/posttool_sensor.py +528 -0
- dos/precursor_gate.py +499 -0
- dos/precursor_gate_eval.py +239 -0
- dos/preflight.py +825 -0
- dos/pretool_sensor.py +490 -0
- dos/proc_delta.py +181 -0
- dos/productivity.py +296 -0
- dos/provider_limit.py +242 -0
- dos/py.typed +4 -0
- dos/reason_morphology.py +299 -0
- dos/reasons.py +449 -0
- dos/reconcile.py +173 -0
- dos/recurring_wedge.py +206 -0
- dos/render.py +393 -0
- dos/result_state.py +468 -0
- dos/resume.py +578 -0
- dos/resume_evidence.py +293 -0
- dos/retention.py +344 -0
- dos/reward.py +372 -0
- dos/rewind.py +587 -0
- dos/rewind_evidence.py +168 -0
- dos/rewind_tokens.py +252 -0
- dos/run_id.py +342 -0
- dos/scope.py +520 -0
- dos/scope_source.py +382 -0
- dos/scout.py +982 -0
- dos/self_modify.py +209 -0
- dos/sibling_scan.py +569 -0
- dos/skills/EXAMPLES.md +584 -0
- dos/skills/dos-class-cycle/SKILL.md +107 -0
- dos/skills/dos-dispatch/SKILL.md +177 -0
- dos/skills/dos-dispatch-loop/SKILL.md +254 -0
- dos/skills/dos-goal-gate/SKILL.md +269 -0
- dos/skills/dos-next-up/SKILL.md +231 -0
- dos/skills/dos-promote/SKILL.md +114 -0
- dos/skills/dos-replan/SKILL.md +159 -0
- dos/skills/dos-replan-loop/SKILL.md +114 -0
- dos/skills/dos-self-improve/SKILL.md +213 -0
- dos/skills/dos-supervise-loop/SKILL.md +180 -0
- dos/skills/dos-unstick/SKILL.md +108 -0
- dos/skills/dos-witness-claim/SKILL.md +251 -0
- dos/stamp.py +1002 -0
- dos/state_health.py +387 -0
- dos/status.py +114 -0
- dos/stop_policy.py +334 -0
- dos/supervise.py +1014 -0
- dos/testwitness.py +392 -0
- dos/timeline.py +1027 -0
- dos/tokens.py +485 -0
- dos/tool_stream.py +393 -0
- dos/tool_stream_eval.py +226 -0
- dos/trace.py +524 -0
- dos/verdict.py +140 -0
- dos/verdict_cli.py +189 -0
- dos/verdict_journal.py +497 -0
- dos/verdict_rollup.py +217 -0
- dos/verdicts.py +181 -0
- dos/wedge_reason.py +282 -0
- dos_kernel-0.22.0.dist-info/METADATA +859 -0
- dos_kernel-0.22.0.dist-info/RECORD +178 -0
- dos_kernel-0.22.0.dist-info/WHEEL +5 -0
- dos_kernel-0.22.0.dist-info/entry_points.txt +39 -0
- dos_kernel-0.22.0.dist-info/licenses/LICENSE +21 -0
- dos_kernel-0.22.0.dist-info/top_level.txt +2 -0
- dos_mcp/__init__.py +52 -0
- dos_mcp/py.typed +2 -0
- dos_mcp/server.py +779 -0
dos/scope_source.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
"""The scope-source seam — docs/117 Phase 4: distrust the declared EXTENT.
|
|
2
|
+
|
|
3
|
+
Why this exists
|
|
4
|
+
===============
|
|
5
|
+
|
|
6
|
+
`completion.classify` answers "is the WHOLE declared job verifiably done?" by
|
|
7
|
+
asking `resume` whether the residual (`declared − verified`) is empty. But that
|
|
8
|
+
question trusts ONE thing it should not: the **denominator**. `declared_steps`
|
|
9
|
+
comes from the run's own `INTENT` record — a self-report of *how big the job is*.
|
|
10
|
+
An agent that declares three steps, ships three, and stops has an empty residual
|
|
11
|
+
and looks `COMPLETE` — even if the real job had five. This is the completion
|
|
12
|
+
analogue of the disease the whole kernel is built against (`docs/103`): a verdict
|
|
13
|
+
computed from the judged agent's own narration of its scope.
|
|
14
|
+
|
|
15
|
+
A `ScopeSource` is the rung that distrusts the extent. It cross-checks the
|
|
16
|
+
declared steps against an **external** account of scope — the plan registry's
|
|
17
|
+
phase list, a PR's changed-files, an issue's acceptance criteria — and rules:
|
|
18
|
+
did the run declare the *whole* job, or under-declare it? Its verdict can only
|
|
19
|
+
make completion HARDER (withhold `COMPLETE`, surface `UNDERDECLARED`); it can
|
|
20
|
+
never grant completion. So "done means done against the *real* scope, not the
|
|
21
|
+
scope the agent chose to admit" becomes a kernel-checkable property — without
|
|
22
|
+
baking any host's notion of scope into the kernel.
|
|
23
|
+
|
|
24
|
+
The same seam shape, re-aimed at extent
|
|
25
|
+
=======================================
|
|
26
|
+
|
|
27
|
+
This module is the `overlap_policy` / `judges` apparatus pointed at scope:
|
|
28
|
+
|
|
29
|
+
* a `ScopeSource` **Protocol** (the contract a driver implements),
|
|
30
|
+
* a built-in **null** source `AllDeclaredScope` (always `extent_honest=True` —
|
|
31
|
+
"trust the declared extent," i.e. *today's* behavior; the unshadowable
|
|
32
|
+
baseline, the `PrefixOverlapPolicy` / `AbstainJudge` analogue),
|
|
33
|
+
* `run_scope` (one source, fail-to-strict) and `honest_under_floor` (the
|
|
34
|
+
conjunction over many — `extent_honest ⟺ every source agrees`),
|
|
35
|
+
* a by-name **resolver** (built-ins first, unshadowable, fail-loud on unknown),
|
|
36
|
+
* call-boundary entry-point **discovery** over the `dos.scope_sources` group.
|
|
37
|
+
|
|
38
|
+
The safe direction, by construction
|
|
39
|
+
====================================
|
|
40
|
+
|
|
41
|
+
The structural guarantee is the inverse of `overlap_policy`'s and *simpler* for
|
|
42
|
+
it. There the plugin returns a verdict that includes ADMIT (the dangerous,
|
|
43
|
+
false-*admit* direction), so a deterministic floor must be ANDed in to stop a
|
|
44
|
+
lying plugin from admitting a collision. Here the dangerous direction is a
|
|
45
|
+
false-*COMPLETE*, and a `ScopeSource` can only ever push toward the SAFE side —
|
|
46
|
+
it withholds completion. So no competing deterministic floor is needed: the
|
|
47
|
+
conjunction alone is the guarantee.
|
|
48
|
+
|
|
49
|
+
> A wired `ScopeSource` may turn a `COMPLETE` into `UNDERDECLARED`. It can
|
|
50
|
+
> never turn an `UNDERDECLARED` (or an `INCOMPLETE`) into `COMPLETE`.
|
|
51
|
+
|
|
52
|
+
`completion.classify` grants `COMPLETE` only as
|
|
53
|
+
``residual_empty AND honest_under_floor(scope_verdicts).extent_honest``. With no
|
|
54
|
+
source wired, `honest_under_floor(())` is honest, so completion is **byte-for-byte
|
|
55
|
+
today's "all declared verified" floor**. Each wired source can only flip an
|
|
56
|
+
`extent_honest` from True to False — strictly stricter. And `run_scope` converts
|
|
57
|
+
any raise / malformed return to `extent_honest = False` (the judge fail-to-ABSTAIN
|
|
58
|
+
analogue, biased toward *refusing completion*): a buggy or hostile source surfaces
|
|
59
|
+
a decision rather than silently certifying done. The `AllDeclaredScope` baseline is
|
|
60
|
+
the one source a plugin can never displace (the resolver returns built-ins first),
|
|
61
|
+
so the floor — "trust the declared extent" — is always reachable and never forgeable
|
|
62
|
+
away.
|
|
63
|
+
|
|
64
|
+
Purity & layering
|
|
65
|
+
=================
|
|
66
|
+
|
|
67
|
+
Pure stdlib — a Protocol, a built-in null source, the conjunction helpers, a
|
|
68
|
+
resolver. No host names, no I/O inside a verdict. It sits in the kernel beside
|
|
69
|
+
`overlap_policy` / `judges` (which likewise hold a pure protocol + resolver while
|
|
70
|
+
real *implementations* live outside). A source MAY do I/O *inside* `scope_verdict`
|
|
71
|
+
(read the plan registry, shell `git`, call an API) IFF it lives in a **driver** —
|
|
72
|
+
the JUDGE-rung allowance — but the kernel's own `AllDeclaredScope` does not.
|
|
73
|
+
Entry-point discovery (the one bit of I/O) happens at the call boundary in
|
|
74
|
+
`active_scope_sources`, exactly as `active_overlap_policy` / `active_judges` /
|
|
75
|
+
`active_predicates` do. The `dos.drivers` litmus (no `src/dos/*` except `drivers/`
|
|
76
|
+
imports `dos.drivers`) covers the real sources.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
from __future__ import annotations
|
|
80
|
+
|
|
81
|
+
import sys
|
|
82
|
+
from dataclasses import dataclass
|
|
83
|
+
from typing import Protocol, runtime_checkable
|
|
84
|
+
|
|
85
|
+
from dos.intent_ledger import LedgerState
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ───────────────────────────── the scope verdict ──────────────────────────────
|
|
89
|
+
@dataclass(frozen=True)
|
|
90
|
+
class ScopeVerdict:
|
|
91
|
+
"""One `ScopeSource`'s ruling on a run's DECLARED extent.
|
|
92
|
+
|
|
93
|
+
``extent_honest`` is the load-bearing boolean: True iff the source believes the
|
|
94
|
+
run declared the *whole* job (the residual's denominator was not understated).
|
|
95
|
+
False means the run under-declared — there is real scope it never put on the
|
|
96
|
+
books, so an empty residual does NOT mean done. ``reason`` is the operator-facing
|
|
97
|
+
one-liner. ``missing`` is the optional, legibility-only list of scope the source
|
|
98
|
+
found beyond the declared steps (e.g. plan phases not in `declared_steps`) — it
|
|
99
|
+
is carried into the `UNDERDECLARED` reason so a human sees *what* was omitted,
|
|
100
|
+
but the verdict turns on ``extent_honest`` alone. ``source`` names the ruling
|
|
101
|
+
source (for the surfaced reason / the decisions queue).
|
|
102
|
+
|
|
103
|
+
The asymmetry is the point and mirrors the seam: an honest verdict permits
|
|
104
|
+
`COMPLETE` (subject to every other source also agreeing); a dishonest one
|
|
105
|
+
withholds it. A source can move the verdict only toward `UNDERDECLARED`.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
extent_honest: bool
|
|
109
|
+
reason: str
|
|
110
|
+
source: str = ""
|
|
111
|
+
missing: tuple[str, ...] = ()
|
|
112
|
+
|
|
113
|
+
def to_dict(self) -> dict:
|
|
114
|
+
return {
|
|
115
|
+
"extent_honest": self.extent_honest,
|
|
116
|
+
"reason": self.reason,
|
|
117
|
+
"source": self.source,
|
|
118
|
+
"missing": list(self.missing),
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@runtime_checkable
|
|
123
|
+
class ScopeSource(Protocol):
|
|
124
|
+
"""The contract a driver implements to distrust a run's declared extent.
|
|
125
|
+
|
|
126
|
+
``name`` is the token `dos doctor` lists and a `--scope-source <name>` selects.
|
|
127
|
+
``scope_verdict`` is handed the run's `LedgerState` (it reads `declared_steps`
|
|
128
|
+
and `goal`/`plan`/`phase` — the *declared* extent) + the active ``config``
|
|
129
|
+
(read-only — it reads policy / locates the external account of scope, but the
|
|
130
|
+
type gives it nothing to mutate) and returns a `ScopeVerdict`.
|
|
131
|
+
|
|
132
|
+
A source MAY do I/O *inside* ``scope_verdict`` (read the plan registry, shell
|
|
133
|
+
out to `git`, call an issue tracker) — unlike a predicate or a renderer, which
|
|
134
|
+
are pure — IFF it lives in a driver, the same reason a ruling judge does. The
|
|
135
|
+
discipline that keeps it honest is NOT purity; it is the conjunction
|
|
136
|
+
(`honest_under_floor`) + fail-to-strict (`run_scope`): whatever a source
|
|
137
|
+
returns, `COMPLETE` requires EVERY source to vote honest, and a raise / bad
|
|
138
|
+
return is read as *dishonest*, so a source is structurally unable to grant
|
|
139
|
+
completion — only to withhold it.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
name: str
|
|
143
|
+
|
|
144
|
+
def scope_verdict(self, state: LedgerState, config: object) -> ScopeVerdict:
|
|
145
|
+
...
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class AllDeclaredScope:
|
|
149
|
+
"""The built-in null source: trust the declared extent (today's behavior).
|
|
150
|
+
|
|
151
|
+
Always returns ``extent_honest=True`` — it asserts that whatever the run
|
|
152
|
+
declared IS the whole job. With only this source (or none) wired, completion is
|
|
153
|
+
**byte-for-byte identical to before the seam**: `honest_under_floor` is honest,
|
|
154
|
+
so `classify` grants `COMPLETE` purely on the empty residual, exactly as it did
|
|
155
|
+
in Phase 1. It is the unshadowable baseline a plugin can never displace
|
|
156
|
+
(`resolve_scope_source` resolves built-ins first) — the scope analogue of the
|
|
157
|
+
unshadowable `prefix` policy / `abstain` judge / `text` renderer.
|
|
158
|
+
|
|
159
|
+
It does no I/O and reads nothing external — it is the *absence* of a scope
|
|
160
|
+
check, made explicit as an object so "no source wired" and "the null source"
|
|
161
|
+
are the same code path.
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
name = "all-declared"
|
|
165
|
+
|
|
166
|
+
def scope_verdict(self, state: LedgerState, config: object) -> ScopeVerdict:
|
|
167
|
+
return ScopeVerdict(
|
|
168
|
+
extent_honest=True,
|
|
169
|
+
reason="declared extent trusted (no external scope check wired)",
|
|
170
|
+
source=self.name,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# The unshadowable baseline instance — pure, stateless, reused.
|
|
175
|
+
_NULL_SOURCE = AllDeclaredScope()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def run_scope(source: ScopeSource, state: LedgerState, config: object) -> ScopeVerdict:
|
|
179
|
+
"""Run ONE `ScopeSource`, converting any misbehavior to a DISHONEST verdict.
|
|
180
|
+
|
|
181
|
+
The fail-to-strict boundary (the judge `run_judge` analogue, biased toward
|
|
182
|
+
*refusing completion*). A source that raises, or returns something that is not a
|
|
183
|
+
`ScopeVerdict`, is mapped to ``extent_honest=False`` — we withhold `COMPLETE`
|
|
184
|
+
and surface `UNDERDECLARED` rather than risk certifying done on a broken scope
|
|
185
|
+
check. This is the conservative direction for "are we done": a source failing
|
|
186
|
+
open (→ honest → COMPLETE) would let a crashing scope check silently grant
|
|
187
|
+
completion, exactly the unannounced trust the kernel refuses.
|
|
188
|
+
|
|
189
|
+
A source that returns a well-formed honest/dishonest verdict is passed through
|
|
190
|
+
verbatim (its ``reason``/``missing`` carried for the operator).
|
|
191
|
+
"""
|
|
192
|
+
name = getattr(source, "name", type(source).__name__)
|
|
193
|
+
try:
|
|
194
|
+
verdict = source.scope_verdict(state, config)
|
|
195
|
+
except Exception as e: # fail-to-strict: a raising source withholds COMPLETE
|
|
196
|
+
return ScopeVerdict(
|
|
197
|
+
extent_honest=False,
|
|
198
|
+
reason=(f"scope source {name!r} raised ({e!r}) — withholding COMPLETE "
|
|
199
|
+
f"(failing to UNDERDECLARED, the conservative direction)"),
|
|
200
|
+
source=name,
|
|
201
|
+
)
|
|
202
|
+
if not isinstance(verdict, ScopeVerdict):
|
|
203
|
+
# Never read a foreign object's `.extent_honest` — a wrong return type cannot
|
|
204
|
+
# be trusted to grant completion. Treat as dishonest (withhold COMPLETE).
|
|
205
|
+
return ScopeVerdict(
|
|
206
|
+
extent_honest=False,
|
|
207
|
+
reason=(f"scope source {name!r} returned a {type(verdict).__name__}, not "
|
|
208
|
+
f"a ScopeVerdict — withholding COMPLETE (conservative)"),
|
|
209
|
+
source=name,
|
|
210
|
+
)
|
|
211
|
+
return verdict
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass(frozen=True)
|
|
215
|
+
class ScopeConjunction:
|
|
216
|
+
"""The combined extent ruling over many sources — `extent_honest` + the why.
|
|
217
|
+
|
|
218
|
+
``extent_honest`` is the AND over every source's vote. ``verdicts`` are the
|
|
219
|
+
individual rulings (so a reader sees who voted what); ``dishonest`` is the
|
|
220
|
+
subset that withheld completion (empty iff honest). ``missing`` is the union of
|
|
221
|
+
all flagged-missing scope (deduped, order-preserving) — what `classify` folds
|
|
222
|
+
into the `UNDERDECLARED` reason."""
|
|
223
|
+
|
|
224
|
+
extent_honest: bool
|
|
225
|
+
verdicts: tuple[ScopeVerdict, ...] = ()
|
|
226
|
+
dishonest: tuple[ScopeVerdict, ...] = ()
|
|
227
|
+
missing: tuple[str, ...] = ()
|
|
228
|
+
|
|
229
|
+
@property
|
|
230
|
+
def reason(self) -> str:
|
|
231
|
+
if self.extent_honest:
|
|
232
|
+
n = len(self.verdicts)
|
|
233
|
+
return (f"declared extent confirmed honest by {n} scope source(s)"
|
|
234
|
+
if n else "declared extent trusted (no scope source wired)")
|
|
235
|
+
names = ", ".join(sorted({v.source for v in self.dishonest if v.source}))
|
|
236
|
+
miss = f" (missing: {', '.join(self.missing)})" if self.missing else ""
|
|
237
|
+
return (f"declared extent under-declared per scope source(s) "
|
|
238
|
+
f"[{names or 'unnamed'}]{miss}")
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def honest_under_floor(verdicts: tuple[ScopeVerdict, ...]) -> ScopeConjunction:
|
|
242
|
+
"""Combine scope verdicts: ``extent_honest`` iff EVERY source votes honest.
|
|
243
|
+
|
|
244
|
+
The structural soundness guarantee in one function — the inverse of
|
|
245
|
+
`overlap_policy.admissible_under_floor` and simpler, because here the dangerous
|
|
246
|
+
direction (false-COMPLETE) is the one a source *cannot reach*:
|
|
247
|
+
|
|
248
|
+
* no verdicts → honest (the floor: with nothing wired, the declared extent is
|
|
249
|
+
trusted — today's behavior, the `AllDeclaredScope` baseline).
|
|
250
|
+
* all honest → honest (every source agrees the extent was the whole job).
|
|
251
|
+
* ANY dishonest → DISHONEST (one source flagging under-declaration withholds
|
|
252
|
+
`COMPLETE`; the others cannot out-vote it — a source can only push toward
|
|
253
|
+
`UNDERDECLARED`, never away from it).
|
|
254
|
+
|
|
255
|
+
So a wired source can only ever move completion toward `UNDERDECLARED`. There is
|
|
256
|
+
no AND-with-a-floor as in `overlap_policy` because withholding is already the
|
|
257
|
+
safe side: the conjunction itself IS the guarantee. (`run_scope` should be
|
|
258
|
+
applied to each source BEFORE this, so a raising source is already a dishonest
|
|
259
|
+
verdict here — fail-to-strict composes with the conjunction.)"""
|
|
260
|
+
vs = tuple(verdicts)
|
|
261
|
+
dishonest = tuple(v for v in vs if not v.extent_honest)
|
|
262
|
+
# Union of flagged-missing scope across dishonest sources, order-preserving + deduped.
|
|
263
|
+
seen: set[str] = set()
|
|
264
|
+
missing: list[str] = []
|
|
265
|
+
for v in dishonest:
|
|
266
|
+
for m in v.missing:
|
|
267
|
+
if m not in seen:
|
|
268
|
+
seen.add(m)
|
|
269
|
+
missing.append(m)
|
|
270
|
+
return ScopeConjunction(
|
|
271
|
+
extent_honest=not dishonest,
|
|
272
|
+
verdicts=vs,
|
|
273
|
+
dishonest=dishonest,
|
|
274
|
+
missing=tuple(missing),
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# ---------------------------------------------------------------------------
|
|
279
|
+
# Resolution — built-in first, then the `dos.scope_sources` entry-point group.
|
|
280
|
+
# ---------------------------------------------------------------------------
|
|
281
|
+
|
|
282
|
+
# The entry-point group a workspace/researcher registers a scope source under.
|
|
283
|
+
SCOPE_SOURCE_ENTRY_POINT_GROUP = "dos.scope_sources"
|
|
284
|
+
|
|
285
|
+
# The built-in sources, resolvable by name and UNSHADOWABLE by a plugin (a plugin
|
|
286
|
+
# registering `all-declared` cannot displace this one — built-ins resolve first).
|
|
287
|
+
# Only the null baseline ships in the kernel; every real source (plan-registry,
|
|
288
|
+
# changed-files, acceptance-criteria) lives in a driver/plugin.
|
|
289
|
+
_BUILT_IN_SOURCES: dict[str, type] = {
|
|
290
|
+
AllDeclaredScope.name: AllDeclaredScope,
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _discover_entry_point_sources(*, _stderr=None) -> list[tuple[str, ScopeSource]]:
|
|
295
|
+
"""Find scope sources registered under the `dos.scope_sources` group.
|
|
296
|
+
|
|
297
|
+
A source plugin registers ``name = "pkg.module:SourceClass"`` in its
|
|
298
|
+
``[project.entry-points."dos.scope_sources"]``. We load each, instantiate it if
|
|
299
|
+
it is a class, and return ``(entry_point_name, source)`` pairs sorted by name
|
|
300
|
+
(stable, so `dos doctor` order is deterministic). A plugin that fails to load is
|
|
301
|
+
skipped with a one-line stderr note rather than crashing completion — the same
|
|
302
|
+
posture overlap-policy / judge / predicate discovery take."""
|
|
303
|
+
stderr = _stderr if _stderr is not None else sys.stderr
|
|
304
|
+
out: list[tuple[str, ScopeSource]] = []
|
|
305
|
+
try:
|
|
306
|
+
from importlib.metadata import entry_points
|
|
307
|
+
except Exception: # pragma: no cover - importlib.metadata always present py3.11+
|
|
308
|
+
return out
|
|
309
|
+
try:
|
|
310
|
+
eps = entry_points(group=SCOPE_SOURCE_ENTRY_POINT_GROUP)
|
|
311
|
+
except TypeError: # pragma: no cover - py<3.10 selectable-API fallback
|
|
312
|
+
eps = entry_points().get(SCOPE_SOURCE_ENTRY_POINT_GROUP, []) # type: ignore[attr-defined]
|
|
313
|
+
except Exception: # pragma: no cover - defensive: never let discovery crash a call
|
|
314
|
+
return out
|
|
315
|
+
for ep in sorted(eps, key=lambda e: e.name):
|
|
316
|
+
try:
|
|
317
|
+
obj = ep.load()
|
|
318
|
+
source = obj() if isinstance(obj, type) else obj
|
|
319
|
+
except Exception as e: # pragma: no cover - depends on third-party plugin
|
|
320
|
+
print(
|
|
321
|
+
f"warning: scope source plugin {ep.name!r} failed to load ({e}); "
|
|
322
|
+
f"skipping",
|
|
323
|
+
file=stderr,
|
|
324
|
+
)
|
|
325
|
+
continue
|
|
326
|
+
out.append((ep.name, source))
|
|
327
|
+
return out
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def resolve_scope_source(name: str, *, _stderr=None) -> ScopeSource:
|
|
331
|
+
"""Resolve a scope source by name: built-ins first, then plugins.
|
|
332
|
+
|
|
333
|
+
Built-ins (`all-declared`) resolve FIRST and cannot be shadowed by a plugin of
|
|
334
|
+
the same name — the trusted-baseline guarantee, identical to
|
|
335
|
+
`resolve_overlap_policy` / `resolve_judge`. An unknown name fails LOUD with the
|
|
336
|
+
known list (it never silently degrades to `all-declared`, which would hide a
|
|
337
|
+
typo'd source name): the caller asked for a specific scope check and getting a
|
|
338
|
+
different one silently is the unannounced substitution the kernel refuses."""
|
|
339
|
+
if name in _BUILT_IN_SOURCES:
|
|
340
|
+
return _BUILT_IN_SOURCES[name]()
|
|
341
|
+
discovered = dict(_discover_entry_point_sources(_stderr=_stderr))
|
|
342
|
+
if name in discovered:
|
|
343
|
+
return discovered[name]
|
|
344
|
+
known = sorted(set(_BUILT_IN_SOURCES) | set(discovered))
|
|
345
|
+
raise ValueError(
|
|
346
|
+
f"unknown scope source {name!r}; known: {', '.join(known)}"
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def active_scope_sources(*, config: object = None, _stderr=None) -> list[ScopeSource]:
|
|
351
|
+
"""The scope sources a CALLER threads into `completion.classify`.
|
|
352
|
+
|
|
353
|
+
Resolution: a workspace may name its sources in ``config.scope_source_names``
|
|
354
|
+
(a list — the `dos.toml [completion] scope_sources` data field); absent that, an
|
|
355
|
+
EMPTY list (NOT the null source) so the default path runs no source and is
|
|
356
|
+
byte-identical to today (`honest_under_floor(())` is honest). Does ENTRY-POINT
|
|
357
|
+
DISCOVERY (I/O) when names are configured, so it is a CALL-BOUNDARY helper (the
|
|
358
|
+
CLI's `cmd_complete`, `dos doctor`), never called inside the pure `classify`. The
|
|
359
|
+
pure default — no config — returns `[]` with no discovery, so completion's hot
|
|
360
|
+
path stays I/O-free, exactly as `built_in_predicates` / `active_overlap_policy`.
|
|
361
|
+
|
|
362
|
+
Returning `[]` (not `[AllDeclaredScope()]`) by default is deliberate: an empty
|
|
363
|
+
list and the null source produce the SAME verdict (honest), and `[]` keeps the
|
|
364
|
+
default truly side-effect-free (no discovery, no per-source call). The null
|
|
365
|
+
source exists for an operator who wants to name it explicitly / for the resolver
|
|
366
|
+
floor, not as the implicit default population."""
|
|
367
|
+
names = getattr(config, "scope_source_names", None) if config is not None else None
|
|
368
|
+
if not names:
|
|
369
|
+
return []
|
|
370
|
+
out: list[ScopeSource] = []
|
|
371
|
+
for nm in names:
|
|
372
|
+
out.append(resolve_scope_source(str(nm), _stderr=_stderr))
|
|
373
|
+
return out
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def active_scope_source_names(*, _stderr=None) -> list[str]:
|
|
377
|
+
"""The names of every resolvable scope source (built-in + discovered) — what
|
|
378
|
+
`dos doctor` lists so an operator can see which extent checks completion could
|
|
379
|
+
use (the scope analogue of "see the active predicates / judges / policies")."""
|
|
380
|
+
built = list(_BUILT_IN_SOURCES)
|
|
381
|
+
discovered = [n for n, _s in _discover_entry_point_sources(_stderr=_stderr)]
|
|
382
|
+
return built + discovered
|