@hobin/developer 0.1.9 → 0.1.11
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 +5 -1
- package/REFERENCE_ROUTING.md +1 -1
- package/extensions/compaction-language.ts +303 -0
- package/extensions/developer.ts +235 -8
- package/extensions/references/behavior-preserving-structural-change.md +10 -2
- package/extensions/skills.ts +6 -2
- package/extensions/state.ts +136 -34
- package/extensions/tui.ts +5 -0
- package/package.json +1 -1
- package/skills/model/SKILL.md +6 -2
- package/skills/model/references/problem-modeling.md +13 -3
- package/skills/sketch/SKILL.md +11 -3
- package/skills/sketch/reference-policy.json +13 -3
- package/skills/sketch/references/evidence-preserving-boundaries.md +200 -0
- package/skills/verify/SKILL.md +5 -2
- package/skills/verify/references/verifier-selection-and-pass-but-wrong.md +12 -1
package/extensions/developer.ts
CHANGED
|
@@ -18,6 +18,19 @@ import {
|
|
|
18
18
|
import { Container, Markdown, Text } from "@earendil-works/pi-tui";
|
|
19
19
|
import { Type, type Static } from "typebox";
|
|
20
20
|
|
|
21
|
+
import {
|
|
22
|
+
COMPACTION_LANGUAGE_ENTRY,
|
|
23
|
+
applyCompactionLanguageEvent,
|
|
24
|
+
continuityConsumed,
|
|
25
|
+
continuityPending,
|
|
26
|
+
detectStrongUserLanguage,
|
|
27
|
+
initialCompactionLanguageState,
|
|
28
|
+
languageObserved,
|
|
29
|
+
projectCompactionContinuity,
|
|
30
|
+
reconstructCompactionLanguage,
|
|
31
|
+
settlementContinuityEvent,
|
|
32
|
+
type CompactionLanguageState,
|
|
33
|
+
} from "./compaction-language.ts";
|
|
21
34
|
import {
|
|
22
35
|
availablePackageSkills,
|
|
23
36
|
isWithinRoot,
|
|
@@ -40,6 +53,7 @@ import {
|
|
|
40
53
|
applyDeveloperEvent,
|
|
41
54
|
canApplyDeveloperEvent,
|
|
42
55
|
developerSnapshot,
|
|
56
|
+
formatInvariantHandling,
|
|
43
57
|
initialState,
|
|
44
58
|
normalizeDeveloperEvent,
|
|
45
59
|
parseChoiceResponseSpec,
|
|
@@ -49,6 +63,7 @@ import {
|
|
|
49
63
|
type ChoiceResponseSpec,
|
|
50
64
|
type DeveloperState,
|
|
51
65
|
type ImplementationProfile,
|
|
66
|
+
type InvariantHandling,
|
|
52
67
|
type FocusEvent,
|
|
53
68
|
type JudgmentEvent,
|
|
54
69
|
type PendingQuestion,
|
|
@@ -202,6 +217,59 @@ interface ChoiceResponseSpecInput {
|
|
|
202
217
|
fields: ChoiceResponseFieldInput[];
|
|
203
218
|
}
|
|
204
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
|
+
|
|
205
273
|
interface OpenQuestionInput {
|
|
206
274
|
question: string;
|
|
207
275
|
context?: string;
|
|
@@ -449,7 +517,9 @@ function protocolPrompt(
|
|
|
449
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.",
|
|
450
518
|
"- 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.",
|
|
451
519
|
`- Call ${ROUTE_TOOL} for exactly one concrete judgment or one green-to-green implementation movement.`,
|
|
452
|
-
"- Use target=implementation only when the next local movement, stable landing,
|
|
520
|
+
"- 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.",
|
|
521
|
+
"- 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.",
|
|
522
|
+
"- 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.",
|
|
453
523
|
"- 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.",
|
|
454
524
|
"- 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.",
|
|
455
525
|
`- 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.`,
|
|
@@ -498,6 +568,11 @@ function protocolPrompt(
|
|
|
498
568
|
lines.push(
|
|
499
569
|
`Active route: ${state.activeRoute.routeId} · ${state.activeRoute.target} · ${state.activeRoute.question}`,
|
|
500
570
|
);
|
|
571
|
+
if (state.activeRoute.implementationStep) {
|
|
572
|
+
lines.push(
|
|
573
|
+
`Active invariant handling: ${formatInvariantHandling(state.activeRoute.implementationStep.invariantHandling)}. Stop and route sketch instead of mutating if this declaration is contradicted by the code.`,
|
|
574
|
+
);
|
|
575
|
+
}
|
|
501
576
|
if (state.activeRoute.methodLocation) {
|
|
502
577
|
lines.push(
|
|
503
578
|
`Active skill location: ${state.activeRoute.methodLocation}. Read it again if compaction or a later turn no longer contains the full instructions.`,
|
|
@@ -621,6 +696,8 @@ function expandedJudgment(
|
|
|
621
696
|
export default async function developer(pi: ExtensionAPI) {
|
|
622
697
|
let availableSkills = new Map<string, Skill>();
|
|
623
698
|
let state = initialState();
|
|
699
|
+
let compactionLanguage: CompactionLanguageState =
|
|
700
|
+
initialCompactionLanguageState();
|
|
624
701
|
let routeOpening = false;
|
|
625
702
|
const routesWithMutation = new Set<string>();
|
|
626
703
|
let toolPolicyMemory: ToolPolicyMemory = { withheldBuiltins: new Set() };
|
|
@@ -732,9 +809,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
732
809
|
};
|
|
733
810
|
|
|
734
811
|
const reconstruct = (ctx: ExtensionContext) => {
|
|
812
|
+
const branch = ctx.sessionManager.getBranch();
|
|
735
813
|
state = toolPolicyRestartRequired
|
|
736
814
|
? initialState()
|
|
737
|
-
: reconstructState(
|
|
815
|
+
: reconstructState(branch);
|
|
816
|
+
compactionLanguage = reconstructCompactionLanguage(branch);
|
|
738
817
|
syncProtocolTools();
|
|
739
818
|
refreshUI(ctx);
|
|
740
819
|
};
|
|
@@ -752,6 +831,16 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
752
831
|
};
|
|
753
832
|
pi.appendEntry(ACTIVATION_ENTRY, event);
|
|
754
833
|
state = applyDeveloperEvent(state, event);
|
|
834
|
+
if (!enabled && compactionLanguage.pending) {
|
|
835
|
+
const consumed = continuityConsumed(
|
|
836
|
+
compactionLanguage.pending.compactionId,
|
|
837
|
+
);
|
|
838
|
+
pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, consumed);
|
|
839
|
+
compactionLanguage = applyCompactionLanguageEvent(
|
|
840
|
+
compactionLanguage,
|
|
841
|
+
consumed,
|
|
842
|
+
);
|
|
843
|
+
}
|
|
755
844
|
syncProtocolTools();
|
|
756
845
|
refreshUI(ctx);
|
|
757
846
|
return true;
|
|
@@ -798,6 +887,83 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
798
887
|
},
|
|
799
888
|
{ additionalProperties: false },
|
|
800
889
|
);
|
|
890
|
+
const InvariantHandlingParam = Type.Union([
|
|
891
|
+
Type.Object(
|
|
892
|
+
{
|
|
893
|
+
kind: Type.Literal("not-applicable"),
|
|
894
|
+
reason: Type.String({
|
|
895
|
+
minLength: 1,
|
|
896
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
897
|
+
description:
|
|
898
|
+
"Why this movement neither creates nor narrows broader or less-trusted data into an invariant-carrying value",
|
|
899
|
+
}),
|
|
900
|
+
},
|
|
901
|
+
{ additionalProperties: false },
|
|
902
|
+
),
|
|
903
|
+
Type.Object(
|
|
904
|
+
{
|
|
905
|
+
kind: Type.Literal("evidence-preserving-boundary"),
|
|
906
|
+
raw_representation: Type.String({
|
|
907
|
+
minLength: 1,
|
|
908
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
909
|
+
}),
|
|
910
|
+
refined_representation: Type.String({
|
|
911
|
+
minLength: 1,
|
|
912
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
913
|
+
}),
|
|
914
|
+
producer: Type.String({
|
|
915
|
+
minLength: 1,
|
|
916
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
917
|
+
description:
|
|
918
|
+
"Parser, refinement function, or smart constructor whose success returns the refined value",
|
|
919
|
+
}),
|
|
920
|
+
failure: Type.String({
|
|
921
|
+
minLength: 1,
|
|
922
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
923
|
+
}),
|
|
924
|
+
first_effect: Type.String({
|
|
925
|
+
minLength: 1,
|
|
926
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
927
|
+
description:
|
|
928
|
+
"First dependent domain effect allowed only after successful refinement",
|
|
929
|
+
}),
|
|
930
|
+
},
|
|
931
|
+
{ additionalProperties: false },
|
|
932
|
+
),
|
|
933
|
+
Type.Object(
|
|
934
|
+
{
|
|
935
|
+
kind: Type.Literal("trusted-compiler-gap"),
|
|
936
|
+
assertion: Type.String({
|
|
937
|
+
minLength: 1,
|
|
938
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
939
|
+
}),
|
|
940
|
+
established_by: Type.String({
|
|
941
|
+
minLength: 1,
|
|
942
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
943
|
+
description:
|
|
944
|
+
"Complete check or trusted runtime contract that already established the asserted fact",
|
|
945
|
+
}),
|
|
946
|
+
limitation: Type.String({
|
|
947
|
+
minLength: 1,
|
|
948
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
949
|
+
description:
|
|
950
|
+
"Why ordinary language narrowing cannot retain the established fact",
|
|
951
|
+
}),
|
|
952
|
+
containment: Type.String({
|
|
953
|
+
minLength: 1,
|
|
954
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
955
|
+
description: "Single parser or constructor owner containing the gap",
|
|
956
|
+
}),
|
|
957
|
+
verification: Type.String({
|
|
958
|
+
minLength: 1,
|
|
959
|
+
maxLength: MAX_EVIDENCE_CHARS,
|
|
960
|
+
description:
|
|
961
|
+
"Falsifier for the claimed prior evidence and containment",
|
|
962
|
+
}),
|
|
963
|
+
},
|
|
964
|
+
{ additionalProperties: false },
|
|
965
|
+
),
|
|
966
|
+
]);
|
|
801
967
|
const RouteParams = Type.Union([
|
|
802
968
|
Type.Object(
|
|
803
969
|
{
|
|
@@ -840,6 +1006,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
840
1006
|
description:
|
|
841
1007
|
"The narrowest check that can catch the likely break in this movement",
|
|
842
1008
|
}),
|
|
1009
|
+
invariant_handling: InvariantHandlingParam,
|
|
843
1010
|
alternatives_considered: Type.Optional(
|
|
844
1011
|
Type.Array(RouteAlternativeParam, {
|
|
845
1012
|
maxItems: 6,
|
|
@@ -865,11 +1032,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
865
1032
|
name: ROUTE_TOOL,
|
|
866
1033
|
label: "Developer Route Question",
|
|
867
1034
|
description:
|
|
868
|
-
"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
|
|
1035
|
+
"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. Implementation must declare how invariant-bearing values are constructed without unchecked narrowing. Then use stable landings and verify before completion.",
|
|
869
1036
|
promptSnippet: "Choose how to handle one development question",
|
|
870
1037
|
promptGuidelines: [
|
|
871
1038
|
`Call ${ROUTE_TOOL} only when there is no active Developer route.`,
|
|
872
|
-
`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,
|
|
1039
|
+
`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, one narrow verification, and a truthful invariant_handling declaration. Validation followed by an assertion is not an evidence-preserving boundary.`,
|
|
873
1040
|
`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.`,
|
|
874
1041
|
`After a resolved model, use sketch for first feature implementation framing or signal for existing-code structural movement before implementation mutation.`,
|
|
875
1042
|
],
|
|
@@ -1014,6 +1181,10 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1014
1181
|
"# Implementation action",
|
|
1015
1182
|
"",
|
|
1016
1183
|
"The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
|
|
1184
|
+
"",
|
|
1185
|
+
"## Invariant gate",
|
|
1186
|
+
"",
|
|
1187
|
+
"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.",
|
|
1017
1188
|
].join("\n");
|
|
1018
1189
|
}
|
|
1019
1190
|
|
|
@@ -1032,6 +1203,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1032
1203
|
? params.verification
|
|
1033
1204
|
: "Run the narrowest relevant check and inspect the resulting diff or output."
|
|
1034
1205
|
).trim(),
|
|
1206
|
+
invariantHandling: invariantHandlingFromInput(
|
|
1207
|
+
"invariant_handling" in params
|
|
1208
|
+
? params.invariant_handling
|
|
1209
|
+
: undefined,
|
|
1210
|
+
),
|
|
1035
1211
|
}
|
|
1036
1212
|
: undefined;
|
|
1037
1213
|
|
|
@@ -1069,6 +1245,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1069
1245
|
`Movement: ${implementationStep.movement}`,
|
|
1070
1246
|
`Stable landing: ${implementationStep.stopCondition}`,
|
|
1071
1247
|
`Narrow verification: ${implementationStep.verification}`,
|
|
1248
|
+
`Invariant handling: ${formatInvariantHandling(implementationStep.invariantHandling)}`,
|
|
1072
1249
|
"Stop this implementation route at that landing, record the evidence, and route again before another movement.",
|
|
1073
1250
|
]
|
|
1074
1251
|
: []),
|
|
@@ -1146,7 +1323,8 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1146
1323
|
context.lastComponent,
|
|
1147
1324
|
);
|
|
1148
1325
|
}
|
|
1149
|
-
const
|
|
1326
|
+
const normalized = normalizeDeveloperEvent(result.details);
|
|
1327
|
+
const event = normalized?.kind === "route" ? normalized : undefined;
|
|
1150
1328
|
let text = theme.fg("success", `routed ${routeRenderText(event)}`);
|
|
1151
1329
|
if (expanded && event) {
|
|
1152
1330
|
text += `\n${detailLine(theme, "route", event.routeId, "text")}`;
|
|
@@ -1180,6 +1358,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1180
1358
|
text += `\n${detailLine(theme, "movement", event.implementationStep.movement, "text")}`;
|
|
1181
1359
|
text += `\n${detailLine(theme, "landing", event.implementationStep.stopCondition)}`;
|
|
1182
1360
|
text += `\n${detailLine(theme, "verify", event.implementationStep.verification)}`;
|
|
1361
|
+
text += `\n${detailLine(theme, "invariant", formatInvariantHandling(event.implementationStep.invariantHandling))}`;
|
|
1183
1362
|
}
|
|
1184
1363
|
}
|
|
1185
1364
|
if (!expanded && event)
|
|
@@ -1355,7 +1534,9 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1355
1534
|
context.lastComponent,
|
|
1356
1535
|
);
|
|
1357
1536
|
}
|
|
1358
|
-
const
|
|
1537
|
+
const normalized = normalizeDeveloperEvent(result.details);
|
|
1538
|
+
const event =
|
|
1539
|
+
normalized?.kind === "reference-load" ? normalized : undefined;
|
|
1359
1540
|
let text = theme.fg(
|
|
1360
1541
|
"success",
|
|
1361
1542
|
`loaded ${event?.path ?? "Developer reference"}`,
|
|
@@ -1984,7 +2165,8 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1984
2165
|
context.lastComponent,
|
|
1985
2166
|
);
|
|
1986
2167
|
}
|
|
1987
|
-
const
|
|
2168
|
+
const normalized = normalizeDeveloperEvent(result.details);
|
|
2169
|
+
const event = normalized?.kind === "judgment" ? normalized : undefined;
|
|
1988
2170
|
const summary = judgmentRenderText(event);
|
|
1989
2171
|
let text =
|
|
1990
2172
|
event?.status === "resolved"
|
|
@@ -2274,6 +2456,41 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
2274
2456
|
},
|
|
2275
2457
|
);
|
|
2276
2458
|
|
|
2459
|
+
pi.on("input", (event) => {
|
|
2460
|
+
if (!state.enabled) return;
|
|
2461
|
+
const tag = detectStrongUserLanguage(event.text, event.source);
|
|
2462
|
+
if (!tag || tag === compactionLanguage.language) return;
|
|
2463
|
+
const observed = languageObserved(tag);
|
|
2464
|
+
pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, observed);
|
|
2465
|
+
compactionLanguage = applyCompactionLanguageEvent(
|
|
2466
|
+
compactionLanguage,
|
|
2467
|
+
observed,
|
|
2468
|
+
);
|
|
2469
|
+
});
|
|
2470
|
+
|
|
2471
|
+
pi.on("session_compact", (event) => {
|
|
2472
|
+
if (!state.enabled || !compactionLanguage.language) return;
|
|
2473
|
+
const pending = continuityPending(
|
|
2474
|
+
event.compactionEntry.id,
|
|
2475
|
+
compactionLanguage.language,
|
|
2476
|
+
);
|
|
2477
|
+
const next = applyCompactionLanguageEvent(compactionLanguage, pending);
|
|
2478
|
+
if (next === compactionLanguage) return;
|
|
2479
|
+
pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, pending);
|
|
2480
|
+
compactionLanguage = next;
|
|
2481
|
+
});
|
|
2482
|
+
|
|
2483
|
+
pi.on("context", (event) => {
|
|
2484
|
+
if (!state.enabled) return;
|
|
2485
|
+
const projection = projectCompactionContinuity(
|
|
2486
|
+
event.messages,
|
|
2487
|
+
compactionLanguage,
|
|
2488
|
+
);
|
|
2489
|
+
if (!projection) return;
|
|
2490
|
+
compactionLanguage = projection.state;
|
|
2491
|
+
return { messages: projection.messages as typeof event.messages };
|
|
2492
|
+
});
|
|
2493
|
+
|
|
2277
2494
|
pi.on("before_agent_start", (event) => {
|
|
2278
2495
|
availableSkills = availablePackageSkills(
|
|
2279
2496
|
event.systemPromptOptions.skills ?? [],
|
|
@@ -2373,7 +2590,17 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
2373
2590
|
if (startEnabled === true && !state.enabled) setEnabled(true, ctx);
|
|
2374
2591
|
});
|
|
2375
2592
|
pi.on("session_tree", (_event, ctx) => reconstruct(ctx));
|
|
2376
|
-
pi.on("agent_settled", (_event, ctx) =>
|
|
2593
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
2594
|
+
const consumed = settlementContinuityEvent(compactionLanguage);
|
|
2595
|
+
if (consumed) {
|
|
2596
|
+
pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, consumed);
|
|
2597
|
+
compactionLanguage = applyCompactionLanguageEvent(
|
|
2598
|
+
compactionLanguage,
|
|
2599
|
+
consumed,
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
refreshUI(ctx);
|
|
2603
|
+
});
|
|
2377
2604
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
2378
2605
|
releaseProtocolTools();
|
|
2379
2606
|
if (!toolPolicyRestartRequired) {
|
|
@@ -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,
|
package/extensions/skills.ts
CHANGED
|
@@ -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
|
|
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
|
|
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.`,
|
package/extensions/state.ts
CHANGED
|
@@ -46,6 +46,36 @@ export type QuestionUpdateStatus =
|
|
|
46
46
|
export type ImplementationProfile =
|
|
47
47
|
| "ordinary"
|
|
48
48
|
| "behavior-preserving-structure";
|
|
49
|
+
export type InvariantHandling =
|
|
50
|
+
| {
|
|
51
|
+
kind: "not-applicable";
|
|
52
|
+
reason: string;
|
|
53
|
+
}
|
|
54
|
+
| {
|
|
55
|
+
kind: "evidence-preserving-boundary";
|
|
56
|
+
rawRepresentation: string;
|
|
57
|
+
refinedRepresentation: string;
|
|
58
|
+
producer: string;
|
|
59
|
+
failure: string;
|
|
60
|
+
firstEffect: string;
|
|
61
|
+
}
|
|
62
|
+
| {
|
|
63
|
+
kind: "trusted-compiler-gap";
|
|
64
|
+
assertion: string;
|
|
65
|
+
establishedBy: string;
|
|
66
|
+
limitation: string;
|
|
67
|
+
containment: string;
|
|
68
|
+
verification: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function formatInvariantHandling(value: InvariantHandling): string {
|
|
72
|
+
if (value.kind === "not-applicable")
|
|
73
|
+
return `not applicable — ${value.reason}`;
|
|
74
|
+
if (value.kind === "evidence-preserving-boundary") {
|
|
75
|
+
return `${value.rawRepresentation} -> ${value.refinedRepresentation} via ${value.producer}; failure=${value.failure}; first effect=${value.firstEffect}`;
|
|
76
|
+
}
|
|
77
|
+
return `trusted compiler gap — ${value.assertion}; established by ${value.establishedBy}; limitation=${value.limitation}; contained at ${value.containment}; check=${value.verification}`;
|
|
78
|
+
}
|
|
49
79
|
|
|
50
80
|
export interface ActivationEvent {
|
|
51
81
|
protocol: typeof PROTOCOL;
|
|
@@ -63,6 +93,7 @@ export interface ImplementationStepContract {
|
|
|
63
93
|
movement: string;
|
|
64
94
|
stopCondition: string;
|
|
65
95
|
verification: string;
|
|
96
|
+
invariantHandling: InvariantHandling;
|
|
66
97
|
}
|
|
67
98
|
|
|
68
99
|
export interface RouteAlternative {
|
|
@@ -248,6 +279,20 @@ function isStringArray(value: unknown): value is string[] {
|
|
|
248
279
|
);
|
|
249
280
|
}
|
|
250
281
|
|
|
282
|
+
function parseArray<T>(
|
|
283
|
+
value: unknown,
|
|
284
|
+
parse: (item: unknown) => T | undefined,
|
|
285
|
+
): T[] | undefined {
|
|
286
|
+
if (!Array.isArray(value)) return undefined;
|
|
287
|
+
const parsed: T[] = [];
|
|
288
|
+
for (const item of value) {
|
|
289
|
+
const result = parse(item);
|
|
290
|
+
if (result === undefined) return undefined;
|
|
291
|
+
parsed.push(result);
|
|
292
|
+
}
|
|
293
|
+
return parsed;
|
|
294
|
+
}
|
|
295
|
+
|
|
251
296
|
function isJudgmentStatus(value: unknown): value is JudgmentStatus {
|
|
252
297
|
return (
|
|
253
298
|
value === "resolved" ||
|
|
@@ -263,6 +308,51 @@ function isImplementationProfile(
|
|
|
263
308
|
return value === "ordinary" || value === "behavior-preserving-structure";
|
|
264
309
|
}
|
|
265
310
|
|
|
311
|
+
function parseInvariantHandling(value: unknown): InvariantHandling | undefined {
|
|
312
|
+
if (!isObject(value) || typeof value.kind !== "string") return undefined;
|
|
313
|
+
if (value.kind === "not-applicable") {
|
|
314
|
+
if (typeof value.reason !== "string") return undefined;
|
|
315
|
+
return { kind: value.kind, reason: value.reason };
|
|
316
|
+
}
|
|
317
|
+
if (value.kind === "evidence-preserving-boundary") {
|
|
318
|
+
if (
|
|
319
|
+
typeof value.rawRepresentation !== "string" ||
|
|
320
|
+
typeof value.refinedRepresentation !== "string" ||
|
|
321
|
+
typeof value.producer !== "string" ||
|
|
322
|
+
typeof value.failure !== "string" ||
|
|
323
|
+
typeof value.firstEffect !== "string"
|
|
324
|
+
) {
|
|
325
|
+
return undefined;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
kind: value.kind,
|
|
329
|
+
rawRepresentation: value.rawRepresentation,
|
|
330
|
+
refinedRepresentation: value.refinedRepresentation,
|
|
331
|
+
producer: value.producer,
|
|
332
|
+
failure: value.failure,
|
|
333
|
+
firstEffect: value.firstEffect,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
if (value.kind !== "trusted-compiler-gap") return undefined;
|
|
337
|
+
if (
|
|
338
|
+
typeof value.assertion !== "string" ||
|
|
339
|
+
typeof value.establishedBy !== "string" ||
|
|
340
|
+
typeof value.limitation !== "string" ||
|
|
341
|
+
typeof value.containment !== "string" ||
|
|
342
|
+
typeof value.verification !== "string"
|
|
343
|
+
) {
|
|
344
|
+
return undefined;
|
|
345
|
+
}
|
|
346
|
+
return {
|
|
347
|
+
kind: value.kind,
|
|
348
|
+
assertion: value.assertion,
|
|
349
|
+
establishedBy: value.establishedBy,
|
|
350
|
+
limitation: value.limitation,
|
|
351
|
+
containment: value.containment,
|
|
352
|
+
verification: value.verification,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
|
|
266
356
|
function parseImplementationStep(
|
|
267
357
|
value: unknown,
|
|
268
358
|
): ImplementationStepContract | undefined {
|
|
@@ -274,10 +364,20 @@ function parseImplementationStep(
|
|
|
274
364
|
) {
|
|
275
365
|
return undefined;
|
|
276
366
|
}
|
|
367
|
+
const invariantHandling =
|
|
368
|
+
value.invariantHandling === undefined
|
|
369
|
+
? {
|
|
370
|
+
kind: "not-applicable" as const,
|
|
371
|
+
reason:
|
|
372
|
+
"Legacy implementation route predates the invariant-handling declaration.",
|
|
373
|
+
}
|
|
374
|
+
: parseInvariantHandling(value.invariantHandling);
|
|
375
|
+
if (!invariantHandling) return undefined;
|
|
277
376
|
return {
|
|
278
377
|
movement: value.movement,
|
|
279
378
|
stopCondition: value.stopCondition,
|
|
280
379
|
verification: value.verification,
|
|
380
|
+
invariantHandling,
|
|
281
381
|
};
|
|
282
382
|
}
|
|
283
383
|
|
|
@@ -622,19 +722,21 @@ export function normalizeDeveloperEvent(
|
|
|
622
722
|
) {
|
|
623
723
|
return undefined;
|
|
624
724
|
}
|
|
625
|
-
const consideredAlternatives =
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
const referenceRoutes =
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
725
|
+
const consideredAlternatives =
|
|
726
|
+
value.consideredAlternatives === undefined
|
|
727
|
+
? []
|
|
728
|
+
: parseArray(value.consideredAlternatives, parseRouteAlternative);
|
|
729
|
+
if (!consideredAlternatives) return undefined;
|
|
730
|
+
const referenceRoutes =
|
|
731
|
+
value.referenceRoutes === undefined
|
|
732
|
+
? []
|
|
733
|
+
: parseArray(value.referenceRoutes, parseReferencePolicyRoute);
|
|
734
|
+
if (!referenceRoutes) return undefined;
|
|
735
|
+
const loadedReferences =
|
|
736
|
+
value.loadedReferences === undefined
|
|
737
|
+
? []
|
|
738
|
+
: parseArray(value.loadedReferences, parseReferenceLoad);
|
|
739
|
+
if (!loadedReferences) return undefined;
|
|
638
740
|
return {
|
|
639
741
|
protocol: PROTOCOL,
|
|
640
742
|
kind: "route",
|
|
@@ -643,15 +745,15 @@ export function normalizeDeveloperEvent(
|
|
|
643
745
|
target: value.target,
|
|
644
746
|
reason: value.reason,
|
|
645
747
|
knownEvidence: value.knownEvidence,
|
|
646
|
-
consideredAlternatives
|
|
748
|
+
consideredAlternatives,
|
|
647
749
|
availableReferences: value.availableReferences ?? [],
|
|
648
|
-
referenceRoutes
|
|
750
|
+
referenceRoutes,
|
|
649
751
|
referenceExemptionCriteria:
|
|
650
752
|
value.referenceExemptionCriteria === undefined
|
|
651
753
|
? undefined
|
|
652
754
|
: parseReferencePolicyExemption(value.referenceExemptionCriteria),
|
|
653
755
|
referencePolicySha256: value.referencePolicySha256,
|
|
654
|
-
loadedReferences
|
|
756
|
+
loadedReferences,
|
|
655
757
|
targetQuestionId: value.targetQuestionId,
|
|
656
758
|
methodLocation: value.methodLocation,
|
|
657
759
|
executionProfile: value.executionProfile,
|
|
@@ -698,21 +800,21 @@ export function normalizeDeveloperEvent(
|
|
|
698
800
|
return undefined;
|
|
699
801
|
}
|
|
700
802
|
|
|
701
|
-
const openedQuestions =
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
)
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (questionUpdates
|
|
803
|
+
const openedQuestions = parseArray(
|
|
804
|
+
value.openedQuestions,
|
|
805
|
+
parsePendingQuestion,
|
|
806
|
+
);
|
|
807
|
+
if (!openedQuestions) return undefined;
|
|
808
|
+
const referenceBasis =
|
|
809
|
+
value.referenceBasis === undefined
|
|
810
|
+
? []
|
|
811
|
+
: parseArray(value.referenceBasis, parseReferenceBasis);
|
|
812
|
+
if (!referenceBasis) return undefined;
|
|
813
|
+
const questionUpdates =
|
|
814
|
+
value.questionUpdates === undefined
|
|
815
|
+
? []
|
|
816
|
+
: parseArray(value.questionUpdates, parseQuestionUpdate);
|
|
817
|
+
if (!questionUpdates) return undefined;
|
|
716
818
|
return {
|
|
717
819
|
protocol: PROTOCOL,
|
|
718
820
|
kind: "judgment",
|
|
@@ -722,13 +824,13 @@ export function normalizeDeveloperEvent(
|
|
|
722
824
|
status: value.status,
|
|
723
825
|
result: value.result,
|
|
724
826
|
basis: value.basis,
|
|
725
|
-
referenceBasis
|
|
827
|
+
referenceBasis,
|
|
726
828
|
referenceExemption:
|
|
727
829
|
value.referenceExemption === undefined
|
|
728
830
|
? undefined
|
|
729
831
|
: parseReferenceExemption(value.referenceExemption),
|
|
730
|
-
openedQuestions
|
|
731
|
-
questionUpdates
|
|
832
|
+
openedQuestions,
|
|
833
|
+
questionUpdates,
|
|
732
834
|
artifacts: value.artifacts,
|
|
733
835
|
changedArtifacts:
|
|
734
836
|
typeof value.changedArtifacts === "boolean"
|