@hobin/developer 0.1.8 → 0.1.10

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.
Files changed (66) hide show
  1. package/README.md +215 -372
  2. package/REFERENCE_ROUTING.md +243 -0
  3. package/extensions/compaction-language.ts +303 -0
  4. package/extensions/developer.ts +607 -20
  5. package/extensions/machine.ts +45 -0
  6. package/extensions/references/behavior-preserving-structural-change.md +87 -8
  7. package/extensions/skills.ts +330 -52
  8. package/extensions/state.ts +227 -2
  9. package/package.json +9 -3
  10. package/skills/abstraction-review/SKILL.md +37 -23
  11. package/skills/abstraction-review/reference-policy.json +66 -0
  12. package/skills/abstraction-review/references/field-card.md +113 -168
  13. package/skills/abstraction-review/references/repair-table.md +66 -80
  14. package/skills/abstraction-review/references/worked-examples.md +79 -256
  15. package/skills/model/SKILL.md +31 -13
  16. package/skills/model/reference-policy.json +96 -0
  17. package/skills/model/references/contract-and-replacement-models.md +112 -0
  18. package/skills/model/references/logic-query-semantics.md +89 -0
  19. package/skills/model/references/planning-models.md +75 -0
  20. package/skills/model/references/problem-modeling.md +143 -276
  21. package/skills/model/references/proof-obligations.md +71 -0
  22. package/skills/model/references/relational-constraint-models.md +110 -0
  23. package/skills/model/references/solver-result-boundaries.md +83 -0
  24. package/skills/model/references/temporal-behavior-models.md +106 -0
  25. package/skills/naming-judgment/SKILL.md +17 -4
  26. package/skills/naming-judgment/reference-policy.json +29 -0
  27. package/skills/naming-judgment/references/domain-naming.md +29 -10
  28. package/skills/schedule/SKILL.md +16 -6
  29. package/skills/schedule/reference-policy.json +29 -0
  30. package/skills/schedule/references/structural-change-timing.md +81 -18
  31. package/skills/signal/SKILL.md +17 -6
  32. package/skills/signal/reference-policy.json +29 -0
  33. package/skills/signal/references/structural-movement.md +87 -14
  34. package/skills/sketch/SKILL.md +38 -42
  35. package/skills/sketch/reference-policy.json +212 -0
  36. package/skills/sketch/references/accumulator-invariants.md +125 -0
  37. package/skills/sketch/references/closure-and-conventional-interfaces.md +87 -0
  38. package/skills/sketch/references/composition-by-wishes.md +69 -0
  39. package/skills/sketch/references/data-driven-design.md +99 -22
  40. package/skills/sketch/references/data-shape-template-catalog.md +58 -19
  41. package/skills/sketch/references/design-levels-and-boundaries.md +133 -0
  42. package/skills/sketch/references/earned-abstraction.md +75 -0
  43. package/skills/sketch/references/generative-recursion.md +122 -0
  44. package/skills/sketch/references/generic-dispatch-systems.md +75 -0
  45. package/skills/sketch/references/language-semantics.md +82 -0
  46. package/skills/sketch/references/meaning-preserving-conversions.md +69 -0
  47. package/skills/sketch/references/process-shape-and-resources.md +157 -0
  48. package/skills/sketch/references/representation-barriers.md +73 -0
  49. package/skills/sketch/references/responsibility-and-collaboration.md +108 -0
  50. package/skills/sketch/references/runtime-and-compilation.md +84 -0
  51. package/skills/sketch/references/selection-and-creation.md +76 -0
  52. package/skills/sketch/references/state-history-and-order.md +126 -0
  53. package/skills/sketch/references/type-transitions.md +54 -0
  54. package/skills/sketch/references/variation-roles.md +59 -0
  55. package/skills/verify/SKILL.md +17 -6
  56. package/skills/verify/reference-policy.json +30 -0
  57. package/skills/verify/references/verifier-selection-and-pass-but-wrong.md +152 -192
  58. package/SOURCES.md +0 -104
  59. package/skills/abstraction-review/references/recipe-cards.md +0 -322
  60. package/skills/model/references/worked-models-and-specialized-techniques.md +0 -218
  61. package/skills/sketch/references/abstraction-barriers-and-closure.md +0 -160
  62. package/skills/sketch/references/abstraction-composition-and-state.md +0 -184
  63. package/skills/sketch/references/composition-generative-recursion-and-accumulators.md +0 -196
  64. package/skills/sketch/references/generic-operations-and-languages.md +0 -134
  65. package/skills/sketch/references/processes-state-and-time.md +0 -171
  66. package/skills/sketch/references/responsibility-and-variation.md +0 -245
@@ -18,7 +18,27 @@ import {
18
18
  import { Container, Markdown, Text } from "@earendil-works/pi-tui";
19
19
  import { Type, type Static } from "typebox";
20
20
 
21
- import { availablePackageSkills, renderSkillMethod } from "./skills.ts";
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";
34
+ import {
35
+ availablePackageSkills,
36
+ isWithinRoot,
37
+ loadSkillReference,
38
+ loadSkillReferencePolicy,
39
+ renderSkillMethod,
40
+ skillReferencePaths,
41
+ } from "./skills.ts";
22
42
  import {
23
43
  ACTIVATION_ENTRY,
24
44
  FOCUS_ENTRY,
@@ -28,6 +48,7 @@ import {
28
48
  MAX_RESPONSE_OPTIONS,
29
49
  MAX_RESPONSE_TEXT_CHARS,
30
50
  PROTOCOL,
51
+ REFERENCE_TOOL,
31
52
  ROUTE_TOOL,
32
53
  applyDeveloperEvent,
33
54
  canApplyDeveloperEvent,
@@ -49,6 +70,11 @@ import {
49
70
  type QuestionResolutionOwner,
50
71
  type QuestionUpdate,
51
72
  type QuestionUpdateStatus,
73
+ type ReferenceBasis,
74
+ type ReferenceExemption,
75
+ type ReferenceLoadEvent,
76
+ type ReferencePolicyExemption,
77
+ type ReferencePolicyRoute,
52
78
  type RouteAlternative,
53
79
  type RouteEvent,
54
80
  } from "./state.ts";
@@ -76,7 +102,7 @@ import {
76
102
  type ImmediateQuestionDisposition,
77
103
  } from "./tui.ts";
78
104
 
79
- const PROTOCOL_TOOLS = [ROUTE_TOOL, JUDGMENT_TOOL] as const;
105
+ const PROTOCOL_TOOLS = [ROUTE_TOOL, REFERENCE_TOOL, JUDGMENT_TOOL] as const;
80
106
  const extensionRoot = dirname(fileURLToPath(import.meta.url));
81
107
  const skillsRoot = resolve(extensionRoot, "..", "skills");
82
108
  const structuralChangeMethodPath = resolve(
@@ -142,6 +168,27 @@ function compactLine(value: string, maxChars = 160): string {
142
168
  return line.length <= maxChars ? line : `${line.slice(0, maxChars - 1)}…`;
143
169
  }
144
170
 
171
+ function referenceRouteSummary(route: ReferencePolicyRoute): string {
172
+ return `${route.id}: question=${route.question} | trigger=${route.trigger} | refines=${route.methodStep} | references=${route.references.join(" + ")} (read order: ${route.readOrder}) | produces=${route.artifacts.join("; ")} | stop=${route.stop} | separate when=${route.separateWhen}`;
173
+ }
174
+
175
+ function referencePolicyLines(
176
+ routes: ReferencePolicyRoute[],
177
+ exemption: ReferencePolicyExemption | undefined,
178
+ ): string[] {
179
+ if (routes.length === 0) return [];
180
+ return [
181
+ "Reference routes (each route extends one named judgment step and answers one narrower question; its references are one co-required set, while read order is declared separately):",
182
+ ...routes.map((route) => `- ${referenceRouteSummary(route)}`),
183
+ ...(exemption
184
+ ? [
185
+ `Reference exemption applies only when: ${exemption.when}`,
186
+ `Exemption evidence: ${exemption.evidence.join(" | ")}`,
187
+ ]
188
+ : []),
189
+ ];
190
+ }
191
+
145
192
  function normalizedQuestion(value: string): string {
146
193
  return value
147
194
  .toLocaleLowerCase()
@@ -413,11 +460,13 @@ function protocolPrompt(
413
460
  "Developer protocol is active.",
414
461
  "- 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.",
415
462
  "- 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.",
463
+ "- 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.",
416
464
  `- Call ${ROUTE_TOOL} for exactly one concrete judgment or one green-to-green implementation movement.`,
417
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.",
418
466
  "- 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.",
419
467
  "- 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.",
420
468
  `- 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.`,
469
+ `- During a reference-bearing skill route, select every policy route whose observable trigger addresses a distinct unresolved question. Each selected route must refine its declared skill-method step, produce its declared artifact, satisfy its stop condition, and hand off instead of absorbing work covered by separate_when. Load every reference in each route's co-required set with ${REFERENCE_TOOL} in its explicit order. Do not select a broad fallback route when a narrower selected route already includes its work. A resolved judgment must apply every loaded reference in reference_basis, or use reference_exemption only when no route trigger applies.`,
421
470
  "- Consecutive implementation routes are valid when the new evidence still justifies implementation action. On a reroute after implementation, explicitly reconsider the most plausible skill routes and record why they are not needed; do not choose implementation by momentum.",
422
471
  "- Do not carry a predetermined implementation queue through multiple implementation steps. Re-observe after each stable landing and reroute to a skill whenever meaning, cases, design, structural direction, timing, naming, or evidence becomes uncertain.",
423
472
  "- Protocol state is routing bookkeeping. Idle never proves product completion, user acceptance, or current verification.",
@@ -465,6 +514,9 @@ function protocolPrompt(
465
514
  if (state.activeRoute.methodLocation) {
466
515
  lines.push(
467
516
  `Active skill location: ${state.activeRoute.methodLocation}. Read it again if compaction or a later turn no longer contains the full instructions.`,
517
+ `Available routed references: ${state.activeRoute.availableReferences.join(", ") || "none"}.`,
518
+ `Reference routes: ${state.activeRoute.referenceRoutes.map((route) => `${route.id}[${route.references.join("+")}; order=${route.readOrder}]`).join(" | ") || "none"}. Re-read the skill's reference-policy.json if trigger or artifact details were compacted.`,
519
+ `Loaded routed references: ${state.activeRoute.loadedReferences.map((reference) => `${reference.path}@${reference.contentSha256.slice(0, 12)}[${reference.referenceRouteIds.join(",") || "legacy"}]`).join(", ") || "none"}.`,
468
520
  );
469
521
  }
470
522
  }
@@ -534,6 +586,26 @@ function expandedJudgment(
534
586
 
535
587
  const evidence = [
536
588
  ...event.basis.map((basis) => detailLine(theme, "basis", basis)),
589
+ ...(event.referenceBasis ?? []).flatMap((reference) => [
590
+ detailLine(
591
+ theme,
592
+ "reference",
593
+ `${reference.path}@${reference.contentSha256.slice(0, 12)} [${reference.referenceRouteIds.join(", ") || "legacy"}] — trigger: ${reference.trigger}`,
594
+ "accent",
595
+ ),
596
+ detailLine(theme, "applied rule", reference.appliedRule, "text"),
597
+ detailLine(theme, "resulting artifact", reference.artifact, "text"),
598
+ ]),
599
+ ...(event.referenceExemption
600
+ ? [
601
+ detailLine(
602
+ theme,
603
+ "reference exemption",
604
+ `${event.referenceExemption.reason} — ${event.referenceExemption.evidence.join(" | ")}`,
605
+ "warning",
606
+ ),
607
+ ]
608
+ : []),
537
609
  ...event.artifacts.map((artifact) =>
538
610
  detailLine(theme, "artifact", artifact),
539
611
  ),
@@ -562,6 +634,8 @@ function expandedJudgment(
562
634
  export default async function developer(pi: ExtensionAPI) {
563
635
  let availableSkills = new Map<string, Skill>();
564
636
  let state = initialState();
637
+ let compactionLanguage: CompactionLanguageState =
638
+ initialCompactionLanguageState();
565
639
  let routeOpening = false;
566
640
  const routesWithMutation = new Set<string>();
567
641
  let toolPolicyMemory: ToolPolicyMemory = { withheldBuiltins: new Set() };
@@ -673,9 +747,11 @@ export default async function developer(pi: ExtensionAPI) {
673
747
  };
674
748
 
675
749
  const reconstruct = (ctx: ExtensionContext) => {
750
+ const branch = ctx.sessionManager.getBranch();
676
751
  state = toolPolicyRestartRequired
677
752
  ? initialState()
678
- : reconstructState(ctx.sessionManager.getBranch());
753
+ : reconstructState(branch);
754
+ compactionLanguage = reconstructCompactionLanguage(branch);
679
755
  syncProtocolTools();
680
756
  refreshUI(ctx);
681
757
  };
@@ -693,6 +769,16 @@ export default async function developer(pi: ExtensionAPI) {
693
769
  };
694
770
  pi.appendEntry(ACTIVATION_ENTRY, event);
695
771
  state = applyDeveloperEvent(state, event);
772
+ if (!enabled && compactionLanguage.pending) {
773
+ const consumed = continuityConsumed(
774
+ compactionLanguage.pending.compactionId,
775
+ );
776
+ pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, consumed);
777
+ compactionLanguage = applyCompactionLanguageEvent(
778
+ compactionLanguage,
779
+ consumed,
780
+ );
781
+ }
696
782
  syncProtocolTools();
697
783
  refreshUI(ctx);
698
784
  return true;
@@ -806,11 +892,11 @@ export default async function developer(pi: ExtensionAPI) {
806
892
  name: ROUTE_TOOL,
807
893
  label: "Developer Route Question",
808
894
  description:
809
- "Route one concrete judgment or one green-to-green implementation movement. Uses an adaptive default topology: model, then sketch for feature shape or signal for structural movement, implementation stable landings, and verify before completion.",
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.",
810
896
  promptSnippet: "Choose how to handle one development question",
811
897
  promptGuidelines: [
812
898
  `Call ${ROUTE_TOOL} only when there is no active Developer route.`,
813
- `Use ${ROUTE_TOOL} with the most focused skill supported by current evidence; target=implementation requires one movement, one stable landing, and one narrow verification.`,
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.`,
814
900
  `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.`,
815
901
  `After a resolved model, use sketch for first feature implementation framing or signal for existing-code structural movement before implementation mutation.`,
816
902
  ],
@@ -934,20 +1020,29 @@ export default async function developer(pi: ExtensionAPI) {
934
1020
  ? (requestedExecutionProfile ?? "ordinary")
935
1021
  : undefined;
936
1022
 
937
- const method =
938
- target !== "implementation"
939
- ? await renderSkillMethod(skill!)
940
- : executionProfile === "behavior-preserving-structure"
941
- ? [
942
- '<developer-implementation-profile name="behavior-preserving-structure">',
943
- (await readFile(structuralChangeMethodPath, "utf8")).trim(),
944
- "</developer-implementation-profile>",
945
- ].join("\n")
946
- : [
947
- "# Implementation action",
948
- "",
949
- "The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
950
- ].join("\n");
1023
+ const availableReferences = skill
1024
+ ? await skillReferencePaths(skill)
1025
+ : [];
1026
+ const referencePolicy = skill
1027
+ ? await loadSkillReferencePolicy(skill, availableReferences)
1028
+ : { routes: [] };
1029
+ let method: string;
1030
+ if (skill) {
1031
+ method = await renderSkillMethod(skill);
1032
+ } else if (executionProfile === "behavior-preserving-structure") {
1033
+ const profile = await readFile(structuralChangeMethodPath, "utf8");
1034
+ method = [
1035
+ '<developer-implementation-profile name="behavior-preserving-structure">',
1036
+ profile.trim(),
1037
+ "</developer-implementation-profile>",
1038
+ ].join("\n");
1039
+ } else {
1040
+ method = [
1041
+ "# Implementation action",
1042
+ "",
1043
+ "The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
1044
+ ].join("\n");
1045
+ }
951
1046
 
952
1047
  const implementationStep =
953
1048
  target === "implementation"
@@ -976,6 +1071,11 @@ export default async function developer(pi: ExtensionAPI) {
976
1071
  reason,
977
1072
  knownEvidence,
978
1073
  consideredAlternatives,
1074
+ availableReferences,
1075
+ referenceRoutes: referencePolicy.routes,
1076
+ referenceExemptionCriteria: referencePolicy.exemption,
1077
+ referencePolicySha256: referencePolicy.contentSha256,
1078
+ loadedReferences: [],
979
1079
  targetQuestionId,
980
1080
  methodLocation: skill?.filePath,
981
1081
  executionProfile,
@@ -1000,6 +1100,17 @@ export default async function developer(pi: ExtensionAPI) {
1000
1100
  ]
1001
1101
  : []),
1002
1102
  `Reason: ${event.reason}`,
1103
+ `Available references: ${event.availableReferences.join(" | ") || "none"}`,
1104
+ ...referencePolicyLines(
1105
+ event.referenceRoutes,
1106
+ event.referenceExemptionCriteria,
1107
+ ),
1108
+ `Reference policy SHA-256: ${event.referencePolicySha256 ?? "none"}`,
1109
+ ...(skill && event.availableReferences.length > 0
1110
+ ? [
1111
+ `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
+ ]
1113
+ : []),
1003
1114
  `Known evidence: ${event.knownEvidence.length > 0 ? event.knownEvidence.join(" | ") : "none"}`,
1004
1115
  `Alternatives reconsidered: ${
1005
1116
  event.consideredAlternatives.length > 0
@@ -1080,6 +1191,15 @@ export default async function developer(pi: ExtensionAPI) {
1080
1191
  }
1081
1192
  text += `\n${detailLine(theme, "revisits", event.targetQuestionId ?? "none")}`;
1082
1193
  text += `\n${detailLine(theme, "skill", event.methodLocation ?? "implementation action")}`;
1194
+ if ((event.availableReferences ?? []).length > 0) {
1195
+ text += `\n${detailLine(theme, "references", event.availableReferences.join(", "))}`;
1196
+ }
1197
+ for (const route of event.referenceRoutes ?? []) {
1198
+ text += `\n${detailLine(theme, `reference route ${route.id}`, `${route.question} · ${route.trigger} → ${route.references.join(" + ")} · step=${route.methodStep} · stop=${route.stop} · separate=${route.separateWhen}`)}`;
1199
+ }
1200
+ if (event.referencePolicySha256) {
1201
+ text += `\n${detailLine(theme, "reference policy", event.referencePolicySha256)}`;
1202
+ }
1083
1203
  if (event.executionProfile) {
1084
1204
  text += `\n${detailLine(theme, "profile", event.executionProfile)}`;
1085
1205
  }
@@ -1095,6 +1215,192 @@ export default async function developer(pi: ExtensionAPI) {
1095
1215
  },
1096
1216
  });
1097
1217
 
1218
+ const ReferenceParams = Type.Object(
1219
+ {
1220
+ reference_route: Type.String({
1221
+ minLength: 1,
1222
+ maxLength: 128,
1223
+ description:
1224
+ "Exact reference route ID listed by the active Developer skill policy",
1225
+ }),
1226
+ path: Type.String({
1227
+ minLength: 1,
1228
+ maxLength: MAX_ARTIFACT_CHARS,
1229
+ description:
1230
+ "Exact direct skill-relative reference path listed by the active route, such as references/data-driven-design.md",
1231
+ }),
1232
+ reason: Type.String({
1233
+ minLength: 1,
1234
+ maxLength: MAX_EVIDENCE_CHARS,
1235
+ description:
1236
+ "Observable task evidence that makes this reference relevant now",
1237
+ }),
1238
+ },
1239
+ { additionalProperties: false },
1240
+ );
1241
+
1242
+ pi.registerTool({
1243
+ name: REFERENCE_TOOL,
1244
+ label: "Developer Load Reference",
1245
+ description:
1246
+ "Select one active policy route while loading one reference from its co-required set, recording route ID, exact path, content SHA-256, Source Trace, and trigger evidence.",
1247
+ promptSnippet: "Load an active Developer skill reference with provenance",
1248
+ promptGuidelines: [
1249
+ `Use ${REFERENCE_TOOL}, not an ordinary read, for any reference that supports an active Developer skill judgment.`,
1250
+ `Call ${REFERENCE_TOOL} with a policy reference_route and one not-yet-loaded path from that route's co-required set; state the observable trigger and apply it only at the declared method step. One successful load satisfies an overlapping path for every selected route, so never reload a shared path and provide only one reference_basis entry per distinct path.`,
1251
+ ],
1252
+ parameters: ReferenceParams,
1253
+ executionMode: "sequential",
1254
+ renderShell: "self",
1255
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1256
+ if (!state.enabled) fail("Developer protocol is off.");
1257
+ const activeRoute = state.activeRoute;
1258
+ if (!activeRoute) fail("There is no active Developer skill route.");
1259
+ if (activeRoute.target === "implementation") {
1260
+ fail("Implementation routes do not load skill references.");
1261
+ }
1262
+ const referenceRouteId = params.reference_route.trim();
1263
+ const path = params.path.trim().replaceAll("\\", "/");
1264
+ const reason = params.reason.trim();
1265
+ if (!referenceRouteId || !path || !reason) {
1266
+ fail(
1267
+ "Reference route, path, and reason must contain non-whitespace text.",
1268
+ );
1269
+ }
1270
+ const skill = availableSkills.get(activeRoute.target);
1271
+ if (!skill || skill.filePath !== activeRoute.methodLocation) {
1272
+ fail(
1273
+ "The active skill source is no longer available at the route's canonical location. Route again against the current Pi resources.",
1274
+ );
1275
+ }
1276
+ const availableReferences =
1277
+ activeRoute.availableReferences.length > 0
1278
+ ? activeRoute.availableReferences
1279
+ : await skillReferencePaths(skill);
1280
+ let referenceRoutes = activeRoute.referenceRoutes;
1281
+ if (referenceRoutes.length === 0) {
1282
+ const policy = await loadSkillReferencePolicy(
1283
+ skill,
1284
+ availableReferences,
1285
+ );
1286
+ referenceRoutes = policy.routes;
1287
+ }
1288
+ const policyRoute = referenceRoutes.find(
1289
+ (candidate) => candidate.id === referenceRouteId,
1290
+ );
1291
+ if (!policyRoute) {
1292
+ fail(
1293
+ `Unknown reference route ${referenceRouteId}. Available routes: ${referenceRoutes.map((route) => route.id).join(", ") || "none"}.`,
1294
+ );
1295
+ }
1296
+ if (!policyRoute.references.includes(path)) {
1297
+ fail(
1298
+ `Reference ${path} is not part of route ${policyRoute.id}. Co-required route references: ${policyRoute.references.join(", ")}.`,
1299
+ );
1300
+ }
1301
+ const loadedPaths = new Set(
1302
+ activeRoute.loadedReferences.map((reference) => reference.path),
1303
+ );
1304
+ if (loadedPaths.has(path)) {
1305
+ fail(
1306
+ `Reference ${path} is already loaded. One successful load satisfies overlapping route membership; select ${policyRoute.id} by loading one of its not-yet-loaded paths and keep one reference_basis entry per distinct path.`,
1307
+ );
1308
+ }
1309
+ if (policyRoute.readOrder === "listed") {
1310
+ const pathIndex = policyRoute.references.indexOf(path);
1311
+ const missingEarlierReferences = policyRoute.references
1312
+ .slice(0, pathIndex)
1313
+ .filter((reference) => !loadedPaths.has(reference));
1314
+ if (missingEarlierReferences.length > 0) {
1315
+ fail(
1316
+ `Reference route ${policyRoute.id} declares listed read order. Load these earlier route references first: ${missingEarlierReferences.join(", ")}.`,
1317
+ );
1318
+ }
1319
+ }
1320
+ const reference = await loadSkillReference(skill, path);
1321
+ const event: ReferenceLoadEvent = {
1322
+ protocol: PROTOCOL,
1323
+ kind: "reference-load",
1324
+ routeId: activeRoute.routeId,
1325
+ target: activeRoute.target,
1326
+ path: reference.path,
1327
+ reason,
1328
+ contentSha256: reference.contentSha256,
1329
+ sourceTrace: reference.sourceTrace,
1330
+ referenceRouteIds: [policyRoute.id],
1331
+ };
1332
+ if (!canApplyDeveloperEvent(state, event)) {
1333
+ fail(
1334
+ "Developer machine guard rejected the reference load for the current active route.",
1335
+ );
1336
+ }
1337
+ const response = [
1338
+ `Route ID: ${event.routeId}`,
1339
+ `Target: ${event.target}`,
1340
+ `Reference policy route: ${policyRoute.id}`,
1341
+ `Route question: ${policyRoute.question}`,
1342
+ `Route trigger: ${policyRoute.trigger}`,
1343
+ `Skill method step refined: ${policyRoute.methodStep}`,
1344
+ `Co-required references: ${policyRoute.references.join(" + ")}`,
1345
+ `Read order: ${policyRoute.readOrder}`,
1346
+ `Expected application artifacts: ${policyRoute.artifacts.join(" | ")}`,
1347
+ `Route stop: ${policyRoute.stop}`,
1348
+ `Separate or hand off when: ${policyRoute.separateWhen}`,
1349
+ `Reference: ${event.path}`,
1350
+ `Reference SHA-256: ${event.contentSha256}`,
1351
+ `Source Trace: ${event.sourceTrace ? "present in loaded content and recorded in tool details" : "not declared by this reference"}`,
1352
+ `Trigger evidence: ${event.reason}`,
1353
+ "",
1354
+ "<developer-reference>",
1355
+ reference.content,
1356
+ "</developer-reference>",
1357
+ ].join("\n");
1358
+ ensureSafeToolText(response, "Developer reference result");
1359
+ state = applyDeveloperEvent(state, event);
1360
+ syncProtocolTools();
1361
+ refreshUI(ctx);
1362
+ return textResult(response, event);
1363
+ },
1364
+ renderCall(args, theme, context) {
1365
+ const path =
1366
+ typeof args.path === "string" && args.path.length > 0 ? args.path : "…";
1367
+ return reusableText(
1368
+ `${theme.fg("toolTitle", theme.bold(REFERENCE_TOOL))} ${theme.fg("accent", path)}`,
1369
+ context.lastComponent,
1370
+ );
1371
+ },
1372
+ renderResult(result, { expanded, isPartial }, theme, context) {
1373
+ if (isPartial) {
1374
+ return reusableText(
1375
+ theme.fg("warning", "loading Developer reference…"),
1376
+ context.lastComponent,
1377
+ );
1378
+ }
1379
+ if (context.isError) {
1380
+ return reusableText(
1381
+ theme.fg("error", resultText(result) || "Developer reference failed"),
1382
+ context.lastComponent,
1383
+ );
1384
+ }
1385
+ const event = result.details as ReferenceLoadEvent | undefined;
1386
+ let text = theme.fg(
1387
+ "success",
1388
+ `loaded ${event?.path ?? "Developer reference"}`,
1389
+ );
1390
+ if (expanded && event) {
1391
+ text += `\n${detailLine(theme, "route", event.routeId, "text")}`;
1392
+ text += `\n${detailLine(theme, "target", event.target, "accent")}`;
1393
+ text += `\n${detailLine(theme, "reference route", event.referenceRouteIds.join(", ") || "legacy", "accent")}`;
1394
+ text += `\n${detailLine(theme, "sha256", event.contentSha256)}`;
1395
+ text += `\n${detailLine(theme, "trigger", event.reason, "text")}`;
1396
+ text += `\n${detailLine(theme, "source trace", event.sourceTrace || "not declared", event.sourceTrace ? "muted" : "warning")}`;
1397
+ }
1398
+ if (!expanded && event)
1399
+ text += ` · ${keyHint("app.tools.expand", "provenance")}`;
1400
+ return reusableText(text, context.lastComponent);
1401
+ },
1402
+ });
1403
+
1098
1404
  const ChoiceResponseOptionParam = Type.Object(
1099
1405
  {
1100
1406
  value: Type.String({
@@ -1204,6 +1510,54 @@ export default async function developer(pi: ExtensionAPI) {
1204
1510
  },
1205
1511
  { additionalProperties: false },
1206
1512
  );
1513
+ const ReferenceBasisParam = Type.Object(
1514
+ {
1515
+ path: Type.String({
1516
+ minLength: 1,
1517
+ maxLength: MAX_ARTIFACT_CHARS,
1518
+ description: `A reference path successfully loaded through ${REFERENCE_TOOL} during this route`,
1519
+ }),
1520
+ trigger: Type.String({
1521
+ minLength: 1,
1522
+ maxLength: MAX_EVIDENCE_CHARS,
1523
+ description:
1524
+ "Observable input, code, state, or requirement condition that made the reference applicable",
1525
+ }),
1526
+ applied_rule: Type.String({
1527
+ minLength: 1,
1528
+ maxLength: MAX_EVIDENCE_CHARS,
1529
+ description:
1530
+ "The concrete derivation, diagnostic, or decision rule taken from the loaded reference",
1531
+ }),
1532
+ artifact: Type.String({
1533
+ minLength: 1,
1534
+ maxLength: MAX_ARTIFACT_CHARS,
1535
+ description:
1536
+ "The result table, model, interface, trace, decision, or check produced by applying that rule",
1537
+ }),
1538
+ },
1539
+ { additionalProperties: false },
1540
+ );
1541
+ const ReferenceExemptionParam = Type.Object(
1542
+ {
1543
+ reason: Type.String({
1544
+ minLength: 1,
1545
+ maxLength: MAX_EVIDENCE_CHARS,
1546
+ description:
1547
+ "Why none of the active skill's available references can add useful judgment for this resolved local question",
1548
+ }),
1549
+ evidence: Type.Array(
1550
+ Type.String({ minLength: 1, maxLength: MAX_EVIDENCE_CHARS }),
1551
+ {
1552
+ minItems: 1,
1553
+ maxItems: 8,
1554
+ description:
1555
+ "Concrete facts supporting the exemption rather than a convenience claim",
1556
+ },
1557
+ ),
1558
+ },
1559
+ { additionalProperties: false },
1560
+ );
1207
1561
  const JudgmentParams = Type.Object({
1208
1562
  route_id: Type.String({
1209
1563
  minLength: 1,
@@ -1226,6 +1580,14 @@ export default async function developer(pi: ExtensionAPI) {
1226
1580
  maxItems: 20,
1227
1581
  description: "Evidence supporting the judgment or blocker",
1228
1582
  }),
1583
+ reference_basis: Type.Optional(
1584
+ Type.Array(ReferenceBasisParam, {
1585
+ maxItems: 12,
1586
+ description:
1587
+ "Loaded references linked to their trigger, applied rule, and resulting judgment artifact",
1588
+ }),
1589
+ ),
1590
+ reference_exemption: Type.Optional(ReferenceExemptionParam),
1229
1591
  open_questions: Type.Optional(
1230
1592
  Type.Array(OpenQuestionParam, {
1231
1593
  maxItems: 10,
@@ -1257,6 +1619,7 @@ export default async function developer(pi: ExtensionAPI) {
1257
1619
  promptGuidelines: [
1258
1620
  `Use ${JUDGMENT_TOOL} with the exact active Developer route ID.`,
1259
1621
  `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.`,
1260
1623
  `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.`,
1261
1624
  `Do not use ${JUDGMENT_TOOL} with resolved, not-applicable, or blocked status without at least one concrete basis.`,
1262
1625
  ],
@@ -1291,6 +1654,34 @@ export default async function developer(pi: ExtensionAPI) {
1291
1654
  if (params.route_id !== activeRoute.routeId) {
1292
1655
  fail(`Route ID mismatch. Active route is ${activeRoute.routeId}.`);
1293
1656
  }
1657
+ const activeSkill =
1658
+ activeRoute.target === "implementation"
1659
+ ? undefined
1660
+ : availableSkills.get(activeRoute.target);
1661
+ let availableReferences: string[] = [];
1662
+ if (activeRoute.target !== "implementation") {
1663
+ if (activeRoute.availableReferences.length > 0) {
1664
+ availableReferences = activeRoute.availableReferences;
1665
+ } else if (
1666
+ activeSkill &&
1667
+ activeSkill.filePath === activeRoute.methodLocation
1668
+ ) {
1669
+ availableReferences = await skillReferencePaths(activeSkill);
1670
+ }
1671
+ }
1672
+ let referenceRoutes = activeRoute.referenceRoutes;
1673
+ if (
1674
+ activeRoute.target !== "implementation" &&
1675
+ referenceRoutes.length === 0 &&
1676
+ activeSkill &&
1677
+ activeSkill.filePath === activeRoute.methodLocation
1678
+ ) {
1679
+ const policy = await loadSkillReferencePolicy(
1680
+ activeSkill,
1681
+ availableReferences,
1682
+ );
1683
+ referenceRoutes = policy.routes;
1684
+ }
1294
1685
 
1295
1686
  const basis = params.basis.map((item) => item.trim()).filter(Boolean);
1296
1687
  const result = params.result.trim();
@@ -1299,6 +1690,131 @@ export default async function developer(pi: ExtensionAPI) {
1299
1690
  fail(`${params.status} judgments require at least one concrete basis.`);
1300
1691
  }
1301
1692
 
1693
+ const referenceBasisInput = (params.reference_basis ?? []).map(
1694
+ (item) => ({
1695
+ path: item.path.trim().replaceAll("\\", "/"),
1696
+ trigger: item.trigger.trim(),
1697
+ appliedRule: item.applied_rule.trim(),
1698
+ artifact: item.artifact.trim(),
1699
+ }),
1700
+ );
1701
+ const referenceExemptionInput = params.reference_exemption
1702
+ ? {
1703
+ reason: params.reference_exemption.reason.trim(),
1704
+ evidence: params.reference_exemption.evidence
1705
+ .map((item) => item.trim())
1706
+ .filter(Boolean),
1707
+ }
1708
+ : undefined;
1709
+ if (referenceBasisInput.length > 0 && referenceExemptionInput) {
1710
+ fail("Use reference_basis or reference_exemption, never both.");
1711
+ }
1712
+ if (
1713
+ new Set(referenceBasisInput.map((item) => item.path)).size !==
1714
+ referenceBasisInput.length
1715
+ ) {
1716
+ fail("Each reference_basis path must be unique within a judgment.");
1717
+ }
1718
+ if (
1719
+ activeRoute.target === "implementation" &&
1720
+ (referenceBasisInput.length > 0 || referenceExemptionInput)
1721
+ ) {
1722
+ fail(
1723
+ "Implementation judgments do not accept skill reference evidence.",
1724
+ );
1725
+ }
1726
+ if (referenceExemptionInput && params.status !== "resolved") {
1727
+ fail(
1728
+ "reference_exemption is valid only for a resolved skill judgment.",
1729
+ );
1730
+ }
1731
+ if (referenceExemptionInput && availableReferences.length === 0) {
1732
+ fail(
1733
+ "reference_exemption is unnecessary because the active skill has no available references.",
1734
+ );
1735
+ }
1736
+ if (referenceExemptionInput && activeRoute.loadedReferences.length > 0) {
1737
+ fail(
1738
+ "reference_exemption is valid only when no routed reference was loaded during this route.",
1739
+ );
1740
+ }
1741
+ if (
1742
+ params.status === "resolved" &&
1743
+ availableReferences.length > 0 &&
1744
+ referenceBasisInput.length === 0 &&
1745
+ !referenceExemptionInput
1746
+ ) {
1747
+ fail(
1748
+ `A resolved ${activeRoute.target} judgment must provide reference_basis for references loaded through ${REFERENCE_TOOL}, or a concrete reference_exemption.`,
1749
+ );
1750
+ }
1751
+ const referenceBasisPaths = new Set(
1752
+ referenceBasisInput.map((item) => item.path),
1753
+ );
1754
+ const loadedReferences = new Map(
1755
+ activeRoute.loadedReferences.map((reference) => [
1756
+ reference.path,
1757
+ reference,
1758
+ ]),
1759
+ );
1760
+ const loadedPaths = new Set(loadedReferences.keys());
1761
+ const basisMissingForLoaded = [...loadedPaths].filter(
1762
+ (path) => !referenceBasisPaths.has(path),
1763
+ );
1764
+ if (basisMissingForLoaded.length > 0) {
1765
+ fail(
1766
+ `Every loaded reference must have a reference_basis entry: ${basisMissingForLoaded.join(", ")}.`,
1767
+ );
1768
+ }
1769
+ const selectedReferenceRouteIds = new Set(
1770
+ activeRoute.loadedReferences.flatMap(
1771
+ (reference) => reference.referenceRouteIds,
1772
+ ),
1773
+ );
1774
+ for (const referenceRouteId of selectedReferenceRouteIds) {
1775
+ const route = referenceRoutes.find(
1776
+ (candidate) => candidate.id === referenceRouteId,
1777
+ );
1778
+ if (!route) {
1779
+ fail(
1780
+ `Loaded reference selected unknown policy route ${referenceRouteId}; route again against the current skill policy.`,
1781
+ );
1782
+ }
1783
+ const missingRouteReferences = route.references.filter(
1784
+ (path) => !loadedPaths.has(path),
1785
+ );
1786
+ if (missingRouteReferences.length > 0) {
1787
+ fail(
1788
+ `Reference route ${route.id} is incomplete. Load its co-required references before judgment: ${missingRouteReferences.join(", ")}.`,
1789
+ );
1790
+ }
1791
+ }
1792
+ for (const loaded of activeRoute.loadedReferences) {
1793
+ for (const referenceRouteId of loaded.referenceRouteIds) {
1794
+ const route = referenceRoutes.find(
1795
+ (candidate) => candidate.id === referenceRouteId,
1796
+ );
1797
+ if (route && !route.references.includes(loaded.path)) {
1798
+ fail(
1799
+ `Reference ${loaded.path} is not part of its recorded policy route ${route.id}.`,
1800
+ );
1801
+ }
1802
+ }
1803
+ }
1804
+ const referenceBasis: ReferenceBasis[] = referenceBasisInput.map(
1805
+ (item) => {
1806
+ const loaded = loadedReferences.get(item.path);
1807
+ if (!loaded) {
1808
+ fail(
1809
+ `reference_basis path ${item.path} was not successfully loaded through ${REFERENCE_TOOL} during this route.`,
1810
+ );
1811
+ }
1812
+ return { ...loaded, ...item };
1813
+ },
1814
+ );
1815
+ const referenceExemption: ReferenceExemption | undefined =
1816
+ referenceExemptionInput;
1817
+
1302
1818
  const openedQuestions = buildOpenedQuestions(
1303
1819
  params.open_questions ?? [],
1304
1820
  state,
@@ -1399,6 +1915,8 @@ export default async function developer(pi: ExtensionAPI) {
1399
1915
  status: params.status,
1400
1916
  result,
1401
1917
  basis,
1918
+ referenceBasis,
1919
+ referenceExemption,
1402
1920
  openedQuestions,
1403
1921
  questionUpdates,
1404
1922
  artifacts: (params.artifacts ?? [])
@@ -1783,6 +2301,41 @@ export default async function developer(pi: ExtensionAPI) {
1783
2301
  },
1784
2302
  );
1785
2303
 
2304
+ pi.on("input", (event) => {
2305
+ if (!state.enabled) return;
2306
+ const tag = detectStrongUserLanguage(event.text, event.source);
2307
+ if (!tag || tag === compactionLanguage.language) return;
2308
+ const observed = languageObserved(tag);
2309
+ pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, observed);
2310
+ compactionLanguage = applyCompactionLanguageEvent(
2311
+ compactionLanguage,
2312
+ observed,
2313
+ );
2314
+ });
2315
+
2316
+ pi.on("session_compact", (event) => {
2317
+ if (!state.enabled || !compactionLanguage.language) return;
2318
+ const pending = continuityPending(
2319
+ event.compactionEntry.id,
2320
+ compactionLanguage.language,
2321
+ );
2322
+ const next = applyCompactionLanguageEvent(compactionLanguage, pending);
2323
+ if (next === compactionLanguage) return;
2324
+ pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, pending);
2325
+ compactionLanguage = next;
2326
+ });
2327
+
2328
+ pi.on("context", (event) => {
2329
+ if (!state.enabled) return;
2330
+ const projection = projectCompactionContinuity(
2331
+ event.messages,
2332
+ compactionLanguage,
2333
+ );
2334
+ if (!projection) return;
2335
+ compactionLanguage = projection.state;
2336
+ return { messages: projection.messages as typeof event.messages };
2337
+ });
2338
+
1786
2339
  pi.on("before_agent_start", (event) => {
1787
2340
  availableSkills = availablePackageSkills(
1788
2341
  event.systemPromptOptions.skills ?? [],
@@ -1796,6 +2349,30 @@ export default async function developer(pi: ExtensionAPI) {
1796
2349
  });
1797
2350
 
1798
2351
  pi.on("tool_call", (event) => {
2352
+ if (
2353
+ state.enabled &&
2354
+ event.toolName === "read" &&
2355
+ state.activeRoute?.target !== "implementation"
2356
+ ) {
2357
+ const activeSkill = state.activeRoute
2358
+ ? availableSkills.get(state.activeRoute.target)
2359
+ : undefined;
2360
+ const requestedPath = event.input.path;
2361
+ if (
2362
+ activeSkill &&
2363
+ typeof requestedPath === "string" &&
2364
+ isWithinRoot(
2365
+ resolve(activeSkill.baseDir, "references"),
2366
+ resolve(requestedPath),
2367
+ )
2368
+ ) {
2369
+ return {
2370
+ block: true,
2371
+ reason: `Developer skill references must be loaded through ${REFERENCE_TOOL} with a policy route so provenance and application remain auditable.`,
2372
+ };
2373
+ }
2374
+ }
2375
+
1799
2376
  const capability = builtinControlledToolCapabilities(pi.getAllTools()).get(
1800
2377
  event.toolName,
1801
2378
  );
@@ -1858,7 +2435,17 @@ export default async function developer(pi: ExtensionAPI) {
1858
2435
  if (startEnabled === true && !state.enabled) setEnabled(true, ctx);
1859
2436
  });
1860
2437
  pi.on("session_tree", (_event, ctx) => reconstruct(ctx));
1861
- pi.on("agent_settled", (_event, ctx) => refreshUI(ctx));
2438
+ pi.on("agent_settled", (_event, ctx) => {
2439
+ const consumed = settlementContinuityEvent(compactionLanguage);
2440
+ if (consumed) {
2441
+ pi.appendEntry(COMPACTION_LANGUAGE_ENTRY, consumed);
2442
+ compactionLanguage = applyCompactionLanguageEvent(
2443
+ compactionLanguage,
2444
+ consumed,
2445
+ );
2446
+ }
2447
+ refreshUI(ctx);
2448
+ });
1862
2449
  pi.on("session_shutdown", (_event, ctx) => {
1863
2450
  releaseProtocolTools();
1864
2451
  if (!toolPolicyRestartRequired) {