@hobin/developer 0.1.10 → 0.1.12

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.
package/README.md CHANGED
@@ -7,7 +7,7 @@ Use it when a task may hide product rules, consequential cases, ownership,
7
7
  compatibility, structural timing, or pass-but-wrong verification. Developer:
8
8
 
9
9
  - turns consequential uncertainty into explicit questions;
10
- - routes Pi to one of ten focused judgment skills;
10
+ - routes Pi to one of eleven focused judgment skills;
11
11
  - loads deeper references only when an observable trigger calls for them;
12
12
  - keeps implementation, unresolved questions, and verification evidence visible
13
13
  on the current session branch.
@@ -70,6 +70,19 @@ The tests pass after this cache rewrite.
70
70
  Check what they do not prove before calling it done.
71
71
  ```
72
72
 
73
+ For a scope-bound existing-code diagnosis and improvement plan, ask for Doctor
74
+ explicitly and name a path, flow, boundary, package, or subsystem when you can:
75
+
76
+ ```text
77
+ Run a thorough Doctor review of the checkout request-to-persistence flow.
78
+ Preserve current external behavior, consult every triggered Developer judgment,
79
+ and produce a now/next/observe/leave-alone plan without modifying files.
80
+ ```
81
+
82
+ When no useful scope is supplied, Doctor performs a cheap orientation pass and
83
+ proposes one. It distinguishes requested, inspected, and claim scope rather than
84
+ presenting a repository sample as an exhaustive review.
85
+
73
86
  ## Why Developer
74
87
 
75
88
  Ordinary coding requests often contain decisions that look like implementation
@@ -84,7 +97,10 @@ details but are actually product, model, boundary, or evidence questions.
84
97
  | Branches lose rationale | Evidence replays with its branch |
85
98
 
86
99
  Developer coordinates judgment; Pi still reads, edits, runs, and tests the
87
- product with its normal tools.
100
+ product with its normal tools. Across those judgments, an unchecked assertion,
101
+ cast, non-null claim, or typed decode is never treated as evidence that a domain
102
+ invariant holds: broader input must cross an owned parser or smart-constructor
103
+ boundary whose success returns the refined value.
88
104
 
89
105
  ## How it works
90
106
 
@@ -115,9 +131,11 @@ and the plausible judgment routes add no useful information.
115
131
 
116
132
  | Need | Developer helps Pi… |
117
133
  | --- | --- |
134
+ | Diagnose existing code | Bound the review, preserve behavior, consult triggered judgments, and order justified improvements |
118
135
  | Clarify a feature | Separate meaning, constraints, examples, and blockers |
119
136
  | Model behavior | Enumerate cases, contracts, transitions, and guarantees |
120
137
  | Shape code | Expose data, interfaces, collaboration, state, and checks |
138
+ | Refine input safely | Turn broader input into invariant-carrying domain values before dependent effects |
121
139
  | Inspect a design | Identify structural movement and model-code mismatch |
122
140
  | Review an abstraction | Keep, revise, split, reject, or defer the candidate |
123
141
  | Time a refactor | Separate behavior work and judge the cost of delay |
@@ -133,6 +151,7 @@ directly with `/skill:<name>`.
133
151
 
134
152
  | Skill | Helps decide |
135
153
  | --- | --- |
154
+ | `doctor` | What a bounded existing-code scope must preserve, treat, observe, or leave alone |
136
155
  | `specify` | Product meaning, scope, invariants, and blockers |
137
156
  | `model` | Cases, contracts, transitions, guarantees, and checks |
138
157
  | `sketch` | Data, interfaces, collaboration, flow, state, and code shape |
@@ -148,6 +167,14 @@ Each skill produces an artifact suited to its question instead of defaulting to
148
167
  undifferentiated prose. Simple judgments remain compact when prose is genuinely
149
168
  clearer.
150
169
 
170
+ Doctor is an explicit coordination workflow, not the default front door for
171
+ ordinary tasks. It first makes a broad, shallow disposition of every available
172
+ Developer judgment lens inside a declared scope. It then closes so each
173
+ triggered question can run through its owning skill and all reference-policy
174
+ routes supported by observable evidence. A final Doctor route synthesizes those
175
+ owner judgments into a treatment plan; Doctor never reads sibling references or
176
+ replaces their methods with one giant checklist.
177
+
151
178
  ## Commands
152
179
 
153
180
  | Command | Effect |
@@ -198,9 +198,10 @@ separation behavior, and pass-but-wrong cases.
198
198
 
199
199
  | Skill | Policy routes | Shape |
200
200
  | --- | --- | --- |
201
+ | `doctor` | none | scope, preservation baseline, universal lens disposition, consultation ledger, diagnosis, and treatment order; owner skills select and apply their own references |
201
202
  | `abstraction-review` | `candidate-contract-review`, `failed-candidate-check`, `candidate-calibration` | review, failure localization, or promise-based calibration; no shadow construction |
202
203
  | `model` | `condition-space`, `contract-replacement`, `relational-constraints`, `temporal-behavior`, `proof-obligation`, `solver-result-boundary`, `logic-query-semantics`, `planning-model` | one model artifact per uncertainty and stop condition |
203
- | `sketch` | `data-driven-design`, `data-shape-template`, `composition-by-wishes`, `earned-abstraction`, `generative-recursion`, `accumulator-invariant`, `design-levels`, `representation-barrier`, `closure-interface`, `process-resources`, `state-history-order`, `generic-dispatch`, `meaning-preserving-conversion`, `language-semantics`, `runtime-compilation`, `responsibility-collaboration`, `variation-role`, `type-transition`, `selection-creation` | one implementable design question per route, with route-local common kernels only where required |
204
+ | `sketch` | `data-driven-design`, `data-shape-template`, `composition-by-wishes`, `earned-abstraction`, `generative-recursion`, `accumulator-invariant`, `evidence-preserving-boundary`, `design-levels`, `representation-barrier`, `closure-interface`, `process-resources`, `state-history-order`, `generic-dispatch`, `meaning-preserving-conversion`, `language-semantics`, `runtime-compilation`, `responsibility-collaboration`, `variation-role`, `type-transition`, `selection-creation` | one implementable design question per route, with route-local common kernels only where required |
204
205
  | `signal` | `behavior-preserving-movement` | singleton |
205
206
  | `schedule` | `structural-timing-tradeoff` | singleton |
206
207
  | `naming-judgment` | `domain-sense-boundary` | singleton |
@@ -209,6 +210,14 @@ separation behavior, and pass-but-wrong cases.
209
210
  | `visualize` | none | no packaged reference |
210
211
  | `adversarial-eval` | none | no packaged reference |
211
212
 
213
+ Doctor does not aggregate sibling reference files into its own policy. In a
214
+ thorough Doctor review it dispositions every available skill, closes the Doctor
215
+ triage route, and hands each triggered question to its owner skill. The owner
216
+ route then applies this contract to every independently triggered policy route
217
+ and co-required reference set before Doctor synthesizes the recorded judgments.
218
+ This makes all references eligible without loading unrelated methods or losing
219
+ route-local provenance.
220
+
212
221
  The implementation execution profile under `extensions/references/` is selected
213
222
  by `execution_profile`, not by skill reference routing, and remains a separate
214
223
  contract.
@@ -264,7 +264,7 @@ export function reconstructCompactionLanguage(
264
264
  }
265
265
 
266
266
  export function continuityMarkerText(tag: LanguageTag): string {
267
- return `Post-compaction continuity: user-visible prose language=${tag}; explicit language requests win.`;
267
+ return `Silent control: do not acknowledge or quote this marker; continue the current task. User-visible prose language=${tag}; explicit user language requests win.`;
268
268
  }
269
269
 
270
270
  export function continuityContextMessage(
@@ -295,9 +295,11 @@ export function projectCompactionContinuity<T>(
295
295
  state: CompactionLanguageState;
296
296
  }
297
297
  | undefined {
298
- if (!state.pending) return undefined;
298
+ if (!state.pending || state.pending.injected) return undefined;
299
+ // Pi presents custom messages to the model as user messages. Prepend this
300
+ // one-shot control so the retained task remains the latest request.
299
301
  return {
300
- messages: [...messages, continuityContextMessage(state.pending, timestamp)],
302
+ messages: [continuityContextMessage(state.pending, timestamp), ...messages],
301
303
  state: markContinuityInjected(state),
302
304
  };
303
305
  }
@@ -53,6 +53,7 @@ import {
53
53
  applyDeveloperEvent,
54
54
  canApplyDeveloperEvent,
55
55
  developerSnapshot,
56
+ formatInvariantHandling,
56
57
  initialState,
57
58
  normalizeDeveloperEvent,
58
59
  parseChoiceResponseSpec,
@@ -62,6 +63,7 @@ import {
62
63
  type ChoiceResponseSpec,
63
64
  type DeveloperState,
64
65
  type ImplementationProfile,
66
+ type InvariantHandling,
65
67
  type FocusEvent,
66
68
  type JudgmentEvent,
67
69
  type PendingQuestion,
@@ -215,6 +217,59 @@ interface ChoiceResponseSpecInput {
215
217
  fields: ChoiceResponseFieldInput[];
216
218
  }
217
219
 
220
+ type InvariantHandlingInput =
221
+ | {
222
+ kind: "not-applicable";
223
+ reason: string;
224
+ }
225
+ | {
226
+ kind: "evidence-preserving-boundary";
227
+ raw_representation: string;
228
+ refined_representation: string;
229
+ producer: string;
230
+ failure: string;
231
+ first_effect: string;
232
+ }
233
+ | {
234
+ kind: "trusted-compiler-gap";
235
+ assertion: string;
236
+ established_by: string;
237
+ limitation: string;
238
+ containment: string;
239
+ verification: string;
240
+ };
241
+
242
+ function invariantHandlingFromInput(
243
+ input: InvariantHandlingInput | undefined,
244
+ ): InvariantHandling {
245
+ if (!input) {
246
+ return {
247
+ kind: "not-applicable",
248
+ reason:
249
+ "Legacy direct tool execution omitted the now-required invariant-handling declaration.",
250
+ };
251
+ }
252
+ if (input.kind === "not-applicable") return input;
253
+ if (input.kind === "evidence-preserving-boundary") {
254
+ return {
255
+ kind: input.kind,
256
+ rawRepresentation: input.raw_representation,
257
+ refinedRepresentation: input.refined_representation,
258
+ producer: input.producer,
259
+ failure: input.failure,
260
+ firstEffect: input.first_effect,
261
+ };
262
+ }
263
+ return {
264
+ kind: input.kind,
265
+ assertion: input.assertion,
266
+ establishedBy: input.established_by,
267
+ limitation: input.limitation,
268
+ containment: input.containment,
269
+ verification: input.verification,
270
+ };
271
+ }
272
+
218
273
  interface OpenQuestionInput {
219
274
  question: string;
220
275
  context?: string;
@@ -460,9 +515,14 @@ function protocolPrompt(
460
515
  "Developer protocol is active.",
461
516
  "- Use the default topology as a conditional backbone, not a rigid lifecycle: clarify meaning when needed -> model consequential cases -> sketch the first implementation surface for new behavior or signal the smallest structural movement in existing code -> execute one implementation step -> verify current claims.",
462
517
  "- Adapt away from that topology when evidence makes a stage not applicable, but never jump directly from a resolved model to mutation: use sketch for feature implementation or signal for existing-code structural movement first.",
518
+ "- Route doctor only for an explicitly requested scope-bound diagnosis and improvement plan across several judgment kinds; it is not a mandatory prelude, pull-request review, environment check, or synonym for ordinary implementation.",
519
+ "- A thorough Doctor review is broad and shallow before it is narrow and deep: establish requested, inspected, and claim scope; disposition every other available Developer skill with evidence; close Doctor triage; route every triggered distinct consultation to its owning skill; let that owner select and apply every triggered policy route and co-required reference; then return to Doctor to synthesize diagnosis and treatment order. Doctor must not load sibling references or flatten their methods into one checklist.",
520
+ "- While Doctor consultations remain, keep one agent-owned before-completion synthesis question and update its remaining consultation evidence after each owner judgment; do not open one pending question per lens. Resolve it only in the final Doctor synthesis route.",
463
521
  "- Route by the requested work product, not keywords: use sketch to create an original caller-facing interface, representation boundary, ownership map, or collaboration; use abstraction-review only when a concrete candidate surface already exists to keep/revise/split/reject/defer; use model first only when unresolved cases, rules, or policy can materially change that surface.",
464
522
  `- Call ${ROUTE_TOOL} for exactly one concrete judgment or one green-to-green implementation movement.`,
465
- "- Use target=implementation only when the next local movement, stable landing, and narrow verification are already justified; otherwise choose the focused skill whose scope fits the current question.",
523
+ "- Use target=implementation only when the next local movement, stable landing, narrow verification, and invariant handling are already justified; otherwise choose the focused skill whose scope fits the current question.",
524
+ "- Invariant gate: an unchecked assertion, cast, non-null claim, ignored conversion result, or typed deserialization target is not evidence that broader, external, persisted, legacy, or otherwise less-trusted input satisfies a domain invariant. Parse or construct a refined value whose success carries the learned information before dependent effects; validation followed by narrowing does not satisfy this gate.",
525
+ "- Every implementation route must classify invariant handling as not applicable, an evidence-preserving boundary, or a trusted compiler gap. A trusted compiler gap is valid only after a complete check or trusted contract already established the fact, the language cannot retain it, the unsafe operation is isolated to one owner, and a falsifier is named. Encountering a missing boundary requires sketch, not an implementation assertion.",
466
526
  "- An implementation step has one observable difference and one structural or behavioral purpose. Stop when its failure is locally explainable and the repository is green, pausable, and reviewable.",
467
527
  "- For implementation structural work intended to preserve behavior, set execution_profile=behavior-preserving-structure; omit the field for other implementation actions and every skill route.",
468
528
  `- Follow the routed method, then close the route with ${JUDGMENT_TOOL}. After every implementation stable landing, route again from the new evidence before selecting another movement.`,
@@ -511,6 +571,11 @@ function protocolPrompt(
511
571
  lines.push(
512
572
  `Active route: ${state.activeRoute.routeId} · ${state.activeRoute.target} · ${state.activeRoute.question}`,
513
573
  );
574
+ if (state.activeRoute.implementationStep) {
575
+ lines.push(
576
+ `Active invariant handling: ${formatInvariantHandling(state.activeRoute.implementationStep.invariantHandling)}. Stop and route sketch instead of mutating if this declaration is contradicted by the code.`,
577
+ );
578
+ }
514
579
  if (state.activeRoute.methodLocation) {
515
580
  lines.push(
516
581
  `Active skill location: ${state.activeRoute.methodLocation}. Read it again if compaction or a later turn no longer contains the full instructions.`,
@@ -825,6 +890,83 @@ export default async function developer(pi: ExtensionAPI) {
825
890
  },
826
891
  { additionalProperties: false },
827
892
  );
893
+ const InvariantHandlingParam = Type.Union([
894
+ Type.Object(
895
+ {
896
+ kind: Type.Literal("not-applicable"),
897
+ reason: Type.String({
898
+ minLength: 1,
899
+ maxLength: MAX_EVIDENCE_CHARS,
900
+ description:
901
+ "Why this movement neither creates nor narrows broader or less-trusted data into an invariant-carrying value",
902
+ }),
903
+ },
904
+ { additionalProperties: false },
905
+ ),
906
+ Type.Object(
907
+ {
908
+ kind: Type.Literal("evidence-preserving-boundary"),
909
+ raw_representation: Type.String({
910
+ minLength: 1,
911
+ maxLength: MAX_EVIDENCE_CHARS,
912
+ }),
913
+ refined_representation: Type.String({
914
+ minLength: 1,
915
+ maxLength: MAX_EVIDENCE_CHARS,
916
+ }),
917
+ producer: Type.String({
918
+ minLength: 1,
919
+ maxLength: MAX_EVIDENCE_CHARS,
920
+ description:
921
+ "Parser, refinement function, or smart constructor whose success returns the refined value",
922
+ }),
923
+ failure: Type.String({
924
+ minLength: 1,
925
+ maxLength: MAX_EVIDENCE_CHARS,
926
+ }),
927
+ first_effect: Type.String({
928
+ minLength: 1,
929
+ maxLength: MAX_EVIDENCE_CHARS,
930
+ description:
931
+ "First dependent domain effect allowed only after successful refinement",
932
+ }),
933
+ },
934
+ { additionalProperties: false },
935
+ ),
936
+ Type.Object(
937
+ {
938
+ kind: Type.Literal("trusted-compiler-gap"),
939
+ assertion: Type.String({
940
+ minLength: 1,
941
+ maxLength: MAX_EVIDENCE_CHARS,
942
+ }),
943
+ established_by: Type.String({
944
+ minLength: 1,
945
+ maxLength: MAX_EVIDENCE_CHARS,
946
+ description:
947
+ "Complete check or trusted runtime contract that already established the asserted fact",
948
+ }),
949
+ limitation: Type.String({
950
+ minLength: 1,
951
+ maxLength: MAX_EVIDENCE_CHARS,
952
+ description:
953
+ "Why ordinary language narrowing cannot retain the established fact",
954
+ }),
955
+ containment: Type.String({
956
+ minLength: 1,
957
+ maxLength: MAX_EVIDENCE_CHARS,
958
+ description: "Single parser or constructor owner containing the gap",
959
+ }),
960
+ verification: Type.String({
961
+ minLength: 1,
962
+ maxLength: MAX_EVIDENCE_CHARS,
963
+ description:
964
+ "Falsifier for the claimed prior evidence and containment",
965
+ }),
966
+ },
967
+ { additionalProperties: false },
968
+ ),
969
+ ]);
828
970
  const RouteParams = Type.Union([
829
971
  Type.Object(
830
972
  {
@@ -867,6 +1009,7 @@ export default async function developer(pi: ExtensionAPI) {
867
1009
  description:
868
1010
  "The narrowest check that can catch the likely break in this movement",
869
1011
  }),
1012
+ invariant_handling: InvariantHandlingParam,
870
1013
  alternatives_considered: Type.Optional(
871
1014
  Type.Array(RouteAlternativeParam, {
872
1015
  maxItems: 6,
@@ -892,11 +1035,11 @@ export default async function developer(pi: ExtensionAPI) {
892
1035
  name: ROUTE_TOOL,
893
1036
  label: "Developer Route Question",
894
1037
  description:
895
- "Route one concrete judgment or one green-to-green implementation movement. Route by work product: sketch creates an original design surface, abstraction-review judges an already-shaped candidate, and model settles unresolved conditions before design. Then use implementation stable landings and verify before completion.",
1038
+ "Route one concrete judgment or one green-to-green implementation movement. Route by work product: doctor coordinates an explicitly requested scope-bound existing-code diagnosis, sketch creates an original design surface, abstraction-review judges an already-shaped candidate, and model settles unresolved conditions before design. Implementation must declare how invariant-bearing values are constructed without unchecked narrowing. Then use stable landings and verify before completion.",
896
1039
  promptSnippet: "Choose how to handle one development question",
897
1040
  promptGuidelines: [
898
1041
  `Call ${ROUTE_TOOL} only when there is no active Developer route.`,
899
- `Use ${ROUTE_TOOL} with the most focused skill supported by current evidence; choose sketch for an original interface or boundary, abstraction-review only for an already-shaped candidate, and model only for unresolved condition space. target=implementation requires one movement, one stable landing, and one narrow verification.`,
1042
+ `Use ${ROUTE_TOOL} with the most focused skill supported by current evidence; choose doctor only for an explicit scope-bound existing-code diagnosis and improvement plan, choose sketch for an original interface or boundary, abstraction-review only for an already-shaped candidate, and model only for unresolved condition space. target=implementation requires one movement, one stable landing, one narrow verification, and a truthful invariant_handling declaration. Validation followed by an assertion is not an evidence-preserving boundary.`,
900
1043
  `When ${ROUTE_TOOL} follows an implementation judgment with another implementation route, cite the previous landing in known_evidence and record plausible skill routes in alternatives_considered instead of selecting implementation by momentum.`,
901
1044
  `After a resolved model, use sketch for first feature implementation framing or signal for existing-code structural movement before implementation mutation.`,
902
1045
  ],
@@ -1041,6 +1184,10 @@ export default async function developer(pi: ExtensionAPI) {
1041
1184
  "# Implementation action",
1042
1185
  "",
1043
1186
  "The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
1187
+ "",
1188
+ "## Invariant gate",
1189
+ "",
1190
+ "Follow the route's invariant-handling declaration. Broader or less-trusted input must become an invariant-carrying value through a parser, refinement function, or smart constructor before dependent effects. Validation followed by an assertion, cast, non-null claim, ignored conversion result, or typed decode is not evidence. If the declared handling is incomplete, stop without mutation and route sketch. Keep any trusted compiler gap isolated to its declared owner and verify its prior evidence and containment.",
1044
1191
  ].join("\n");
1045
1192
  }
1046
1193
 
@@ -1059,6 +1206,11 @@ export default async function developer(pi: ExtensionAPI) {
1059
1206
  ? params.verification
1060
1207
  : "Run the narrowest relevant check and inspect the resulting diff or output."
1061
1208
  ).trim(),
1209
+ invariantHandling: invariantHandlingFromInput(
1210
+ "invariant_handling" in params
1211
+ ? params.invariant_handling
1212
+ : undefined,
1213
+ ),
1062
1214
  }
1063
1215
  : undefined;
1064
1216
 
@@ -1096,6 +1248,7 @@ export default async function developer(pi: ExtensionAPI) {
1096
1248
  `Movement: ${implementationStep.movement}`,
1097
1249
  `Stable landing: ${implementationStep.stopCondition}`,
1098
1250
  `Narrow verification: ${implementationStep.verification}`,
1251
+ `Invariant handling: ${formatInvariantHandling(implementationStep.invariantHandling)}`,
1099
1252
  "Stop this implementation route at that landing, record the evidence, and route again before another movement.",
1100
1253
  ]
1101
1254
  : []),
@@ -1111,6 +1264,11 @@ export default async function developer(pi: ExtensionAPI) {
1111
1264
  `Reference contract: choose policy routes by observable trigger, use ${REFERENCE_TOOL} for every reference in each selected route's co-required set, and provide reference_basis for every loaded path; use reference_exemption only when no policy route applies.`,
1112
1265
  ]
1113
1266
  : []),
1267
+ ...(skill && event.availableReferences.length === 0
1268
+ ? [
1269
+ "Reference contract: this skill has no packaged references; omit both reference_basis and reference_exemption from its judgment.",
1270
+ ]
1271
+ : []),
1114
1272
  `Known evidence: ${event.knownEvidence.length > 0 ? event.knownEvidence.join(" | ") : "none"}`,
1115
1273
  `Alternatives reconsidered: ${
1116
1274
  event.consideredAlternatives.length > 0
@@ -1173,7 +1331,8 @@ export default async function developer(pi: ExtensionAPI) {
1173
1331
  context.lastComponent,
1174
1332
  );
1175
1333
  }
1176
- const event = result.details as RouteEvent | undefined;
1334
+ const normalized = normalizeDeveloperEvent(result.details);
1335
+ const event = normalized?.kind === "route" ? normalized : undefined;
1177
1336
  let text = theme.fg("success", `routed ${routeRenderText(event)}`);
1178
1337
  if (expanded && event) {
1179
1338
  text += `\n${detailLine(theme, "route", event.routeId, "text")}`;
@@ -1207,6 +1366,7 @@ export default async function developer(pi: ExtensionAPI) {
1207
1366
  text += `\n${detailLine(theme, "movement", event.implementationStep.movement, "text")}`;
1208
1367
  text += `\n${detailLine(theme, "landing", event.implementationStep.stopCondition)}`;
1209
1368
  text += `\n${detailLine(theme, "verify", event.implementationStep.verification)}`;
1369
+ text += `\n${detailLine(theme, "invariant", formatInvariantHandling(event.implementationStep.invariantHandling))}`;
1210
1370
  }
1211
1371
  }
1212
1372
  if (!expanded && event)
@@ -1382,7 +1542,9 @@ export default async function developer(pi: ExtensionAPI) {
1382
1542
  context.lastComponent,
1383
1543
  );
1384
1544
  }
1385
- const event = result.details as ReferenceLoadEvent | undefined;
1545
+ const normalized = normalizeDeveloperEvent(result.details);
1546
+ const event =
1547
+ normalized?.kind === "reference-load" ? normalized : undefined;
1386
1548
  let text = theme.fg(
1387
1549
  "success",
1388
1550
  `loaded ${event?.path ?? "Developer reference"}`,
@@ -1619,7 +1781,7 @@ export default async function developer(pi: ExtensionAPI) {
1619
1781
  promptGuidelines: [
1620
1782
  `Use ${JUDGMENT_TOOL} with the exact active Developer route ID.`,
1621
1783
  `Use ${JUDGMENT_TOOL} result as Markdown and preserve the routed skill's inspectable tables, diagrams, matrices, timelines, and code blocks instead of reducing them to prose.`,
1622
- `For a resolved skill route with available references, use ${JUDGMENT_TOOL} reference_basis to connect each relied-on ${REFERENCE_TOOL} load to its trigger, applied rule, and resulting artifact, or use reference_exemption when no reference is relevant. Never provide both.`,
1784
+ `For a resolved skill route with available references, use ${JUDGMENT_TOOL} reference_basis to connect each relied-on ${REFERENCE_TOOL} load to its trigger, applied rule, and resulting artifact, or use reference_exemption when no reference is relevant. Never provide both. For a skill with no packaged references, omit both fields.`,
1623
1785
  `Use ${JUDGMENT_TOOL} open_questions with resolution_owner, gate, and resolution_criteria. User-owned gated questions require explanatory context before controls; for finite required decisions, add a choice-form response_spec with one field per decision. Use question_updates whenever current evidence settles or changes any existing pending question.`,
1624
1786
  `Do not use ${JUDGMENT_TOOL} with resolved, not-applicable, or blocked status without at least one concrete basis.`,
1625
1787
  ],
@@ -2011,7 +2173,8 @@ export default async function developer(pi: ExtensionAPI) {
2011
2173
  context.lastComponent,
2012
2174
  );
2013
2175
  }
2014
- const event = result.details as JudgmentEvent | undefined;
2176
+ const normalized = normalizeDeveloperEvent(result.details);
2177
+ const event = normalized?.kind === "judgment" ? normalized : undefined;
2015
2178
  const summary = judgmentRenderText(event);
2016
2179
  let text =
2017
2180
  event?.status === "resolved"
@@ -46,7 +46,10 @@ For movement across a boundary, update one sender or caller at a time when the
46
46
  old and new forms can safely coexist. Confirm that each intermediate state is
47
47
  valid. Do not rely on a final large diff to explain which step changed behavior.
48
48
  A forwarding interface over the old implementation can be a temporary seam, but
49
- it is not evidence that the interface is a stable public abstraction.
49
+ it is not evidence that the interface is a stable public abstraction. An
50
+ assertion, cast, non-null claim, or unchecked typed decode is not a compatibility
51
+ seam: if old or external data is broader than the new invariant, an adapter must
52
+ parse and return the new representation or explicit failure.
50
53
 
51
54
  For suspected dead code, combine static references, dynamic reachability, and a
52
55
  scoped observation window appropriate to rare jobs, reflection, configuration,
@@ -192,10 +195,15 @@ The protocol is being misused when:
192
195
  - behavior work is hidden inside a structural label;
193
196
  - the route continues after a new human-owned policy question appears;
194
197
  - an intermediate state cannot safely run or be reviewed;
195
- - cleanup continues past the accepted pressure because the code could be nicer.
198
+ - cleanup continues past the accepted pressure because the code could be nicer;
199
+ - validation is preserved while its result is discarded and an assertion claims
200
+ the new representation.
196
201
 
197
202
  ## Source Trace
198
203
 
204
+ - Alexis King, “Parse, don’t validate,” published November 5, 2019, for
205
+ evidence-preserving refinement before execution. The compatibility-seam and
206
+ migration wording is a Developer adaptation.
199
207
  - Sandi Metz, Katrina Owen, and TJ Stankus, *99 Bottles of OOP*, Second
200
208
  Edition, version 2.2.2, 2024: Chapters 3-5, pp. 51-137, especially
201
209
  pp. 58-71, 84-101, and 113-125, on methodical transformations, flocking,
@@ -86,13 +86,17 @@ export function availablePackageSkills(
86
86
  return available;
87
87
  }
88
88
 
89
+ function hasErrorCode(error: unknown, code: string): boolean {
90
+ return isRecord(error) && error.code === code;
91
+ }
92
+
89
93
  export async function skillReferencePaths(skill: Skill): Promise<string[]> {
90
94
  const referencesRoot = resolve(skill.baseDir, "references");
91
95
  let entries: Dirent[];
92
96
  try {
93
97
  entries = await readdir(referencesRoot, { withFileTypes: true });
94
98
  } catch (error) {
95
- if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
99
+ if (hasErrorCode(error, "ENOENT")) return [];
96
100
  throw error;
97
101
  }
98
102
 
@@ -248,7 +252,7 @@ export async function loadSkillReferencePolicy(
248
252
  try {
249
253
  source = await readFile(policyPath, "utf8");
250
254
  } catch (error) {
251
- if ((error as NodeJS.ErrnoException).code === "ENOENT") {
255
+ if (hasErrorCode(error, "ENOENT")) {
252
256
  if (catalog.length > 0) {
253
257
  throw new Error(
254
258
  `Developer skill ${skill.name} has references but no reference-policy.json.`,