@hobin/developer 0.1.8 → 0.1.9

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 (65) hide show
  1. package/README.md +215 -372
  2. package/REFERENCE_ROUTING.md +243 -0
  3. package/extensions/developer.ts +533 -18
  4. package/extensions/machine.ts +45 -0
  5. package/extensions/references/behavior-preserving-structural-change.md +87 -8
  6. package/extensions/skills.ts +330 -52
  7. package/extensions/state.ts +227 -2
  8. package/package.json +9 -3
  9. package/skills/abstraction-review/SKILL.md +37 -23
  10. package/skills/abstraction-review/reference-policy.json +66 -0
  11. package/skills/abstraction-review/references/field-card.md +113 -168
  12. package/skills/abstraction-review/references/repair-table.md +66 -80
  13. package/skills/abstraction-review/references/worked-examples.md +79 -256
  14. package/skills/model/SKILL.md +31 -13
  15. package/skills/model/reference-policy.json +96 -0
  16. package/skills/model/references/contract-and-replacement-models.md +112 -0
  17. package/skills/model/references/logic-query-semantics.md +89 -0
  18. package/skills/model/references/planning-models.md +75 -0
  19. package/skills/model/references/problem-modeling.md +143 -276
  20. package/skills/model/references/proof-obligations.md +71 -0
  21. package/skills/model/references/relational-constraint-models.md +110 -0
  22. package/skills/model/references/solver-result-boundaries.md +83 -0
  23. package/skills/model/references/temporal-behavior-models.md +106 -0
  24. package/skills/naming-judgment/SKILL.md +17 -4
  25. package/skills/naming-judgment/reference-policy.json +29 -0
  26. package/skills/naming-judgment/references/domain-naming.md +29 -10
  27. package/skills/schedule/SKILL.md +16 -6
  28. package/skills/schedule/reference-policy.json +29 -0
  29. package/skills/schedule/references/structural-change-timing.md +81 -18
  30. package/skills/signal/SKILL.md +17 -6
  31. package/skills/signal/reference-policy.json +29 -0
  32. package/skills/signal/references/structural-movement.md +87 -14
  33. package/skills/sketch/SKILL.md +38 -42
  34. package/skills/sketch/reference-policy.json +212 -0
  35. package/skills/sketch/references/accumulator-invariants.md +125 -0
  36. package/skills/sketch/references/closure-and-conventional-interfaces.md +87 -0
  37. package/skills/sketch/references/composition-by-wishes.md +69 -0
  38. package/skills/sketch/references/data-driven-design.md +99 -22
  39. package/skills/sketch/references/data-shape-template-catalog.md +58 -19
  40. package/skills/sketch/references/design-levels-and-boundaries.md +133 -0
  41. package/skills/sketch/references/earned-abstraction.md +75 -0
  42. package/skills/sketch/references/generative-recursion.md +122 -0
  43. package/skills/sketch/references/generic-dispatch-systems.md +75 -0
  44. package/skills/sketch/references/language-semantics.md +82 -0
  45. package/skills/sketch/references/meaning-preserving-conversions.md +69 -0
  46. package/skills/sketch/references/process-shape-and-resources.md +157 -0
  47. package/skills/sketch/references/representation-barriers.md +73 -0
  48. package/skills/sketch/references/responsibility-and-collaboration.md +108 -0
  49. package/skills/sketch/references/runtime-and-compilation.md +84 -0
  50. package/skills/sketch/references/selection-and-creation.md +76 -0
  51. package/skills/sketch/references/state-history-and-order.md +126 -0
  52. package/skills/sketch/references/type-transitions.md +54 -0
  53. package/skills/sketch/references/variation-roles.md +59 -0
  54. package/skills/verify/SKILL.md +17 -6
  55. package/skills/verify/reference-policy.json +30 -0
  56. package/skills/verify/references/verifier-selection-and-pass-but-wrong.md +152 -192
  57. package/SOURCES.md +0 -104
  58. package/skills/abstraction-review/references/recipe-cards.md +0 -322
  59. package/skills/model/references/worked-models-and-specialized-techniques.md +0 -218
  60. package/skills/sketch/references/abstraction-barriers-and-closure.md +0 -160
  61. package/skills/sketch/references/abstraction-composition-and-state.md +0 -184
  62. package/skills/sketch/references/composition-generative-recursion-and-accumulators.md +0 -196
  63. package/skills/sketch/references/generic-operations-and-languages.md +0 -134
  64. package/skills/sketch/references/processes-state-and-time.md +0 -171
  65. package/skills/sketch/references/responsibility-and-variation.md +0 -245
@@ -18,7 +18,14 @@ 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
+ availablePackageSkills,
23
+ isWithinRoot,
24
+ loadSkillReference,
25
+ loadSkillReferencePolicy,
26
+ renderSkillMethod,
27
+ skillReferencePaths,
28
+ } from "./skills.ts";
22
29
  import {
23
30
  ACTIVATION_ENTRY,
24
31
  FOCUS_ENTRY,
@@ -28,6 +35,7 @@ import {
28
35
  MAX_RESPONSE_OPTIONS,
29
36
  MAX_RESPONSE_TEXT_CHARS,
30
37
  PROTOCOL,
38
+ REFERENCE_TOOL,
31
39
  ROUTE_TOOL,
32
40
  applyDeveloperEvent,
33
41
  canApplyDeveloperEvent,
@@ -49,6 +57,11 @@ import {
49
57
  type QuestionResolutionOwner,
50
58
  type QuestionUpdate,
51
59
  type QuestionUpdateStatus,
60
+ type ReferenceBasis,
61
+ type ReferenceExemption,
62
+ type ReferenceLoadEvent,
63
+ type ReferencePolicyExemption,
64
+ type ReferencePolicyRoute,
52
65
  type RouteAlternative,
53
66
  type RouteEvent,
54
67
  } from "./state.ts";
@@ -76,7 +89,7 @@ import {
76
89
  type ImmediateQuestionDisposition,
77
90
  } from "./tui.ts";
78
91
 
79
- const PROTOCOL_TOOLS = [ROUTE_TOOL, JUDGMENT_TOOL] as const;
92
+ const PROTOCOL_TOOLS = [ROUTE_TOOL, REFERENCE_TOOL, JUDGMENT_TOOL] as const;
80
93
  const extensionRoot = dirname(fileURLToPath(import.meta.url));
81
94
  const skillsRoot = resolve(extensionRoot, "..", "skills");
82
95
  const structuralChangeMethodPath = resolve(
@@ -142,6 +155,27 @@ function compactLine(value: string, maxChars = 160): string {
142
155
  return line.length <= maxChars ? line : `${line.slice(0, maxChars - 1)}…`;
143
156
  }
144
157
 
158
+ function referenceRouteSummary(route: ReferencePolicyRoute): string {
159
+ 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}`;
160
+ }
161
+
162
+ function referencePolicyLines(
163
+ routes: ReferencePolicyRoute[],
164
+ exemption: ReferencePolicyExemption | undefined,
165
+ ): string[] {
166
+ if (routes.length === 0) return [];
167
+ return [
168
+ "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):",
169
+ ...routes.map((route) => `- ${referenceRouteSummary(route)}`),
170
+ ...(exemption
171
+ ? [
172
+ `Reference exemption applies only when: ${exemption.when}`,
173
+ `Exemption evidence: ${exemption.evidence.join(" | ")}`,
174
+ ]
175
+ : []),
176
+ ];
177
+ }
178
+
145
179
  function normalizedQuestion(value: string): string {
146
180
  return value
147
181
  .toLocaleLowerCase()
@@ -413,11 +447,13 @@ function protocolPrompt(
413
447
  "Developer protocol is active.",
414
448
  "- 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
449
  "- 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
+ "- 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
451
  `- Call ${ROUTE_TOOL} for exactly one concrete judgment or one green-to-green implementation movement.`,
417
452
  "- 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
453
  "- 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
454
  "- 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
455
  `- 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.`,
456
+ `- 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
457
  "- 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
458
  "- 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
459
  "- Protocol state is routing bookkeeping. Idle never proves product completion, user acceptance, or current verification.",
@@ -465,6 +501,9 @@ function protocolPrompt(
465
501
  if (state.activeRoute.methodLocation) {
466
502
  lines.push(
467
503
  `Active skill location: ${state.activeRoute.methodLocation}. Read it again if compaction or a later turn no longer contains the full instructions.`,
504
+ `Available routed references: ${state.activeRoute.availableReferences.join(", ") || "none"}.`,
505
+ `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.`,
506
+ `Loaded routed references: ${state.activeRoute.loadedReferences.map((reference) => `${reference.path}@${reference.contentSha256.slice(0, 12)}[${reference.referenceRouteIds.join(",") || "legacy"}]`).join(", ") || "none"}.`,
468
507
  );
469
508
  }
470
509
  }
@@ -534,6 +573,26 @@ function expandedJudgment(
534
573
 
535
574
  const evidence = [
536
575
  ...event.basis.map((basis) => detailLine(theme, "basis", basis)),
576
+ ...(event.referenceBasis ?? []).flatMap((reference) => [
577
+ detailLine(
578
+ theme,
579
+ "reference",
580
+ `${reference.path}@${reference.contentSha256.slice(0, 12)} [${reference.referenceRouteIds.join(", ") || "legacy"}] — trigger: ${reference.trigger}`,
581
+ "accent",
582
+ ),
583
+ detailLine(theme, "applied rule", reference.appliedRule, "text"),
584
+ detailLine(theme, "resulting artifact", reference.artifact, "text"),
585
+ ]),
586
+ ...(event.referenceExemption
587
+ ? [
588
+ detailLine(
589
+ theme,
590
+ "reference exemption",
591
+ `${event.referenceExemption.reason} — ${event.referenceExemption.evidence.join(" | ")}`,
592
+ "warning",
593
+ ),
594
+ ]
595
+ : []),
537
596
  ...event.artifacts.map((artifact) =>
538
597
  detailLine(theme, "artifact", artifact),
539
598
  ),
@@ -806,11 +865,11 @@ export default async function developer(pi: ExtensionAPI) {
806
865
  name: ROUTE_TOOL,
807
866
  label: "Developer Route Question",
808
867
  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.",
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 implementation stable landings and verify before completion.",
810
869
  promptSnippet: "Choose how to handle one development question",
811
870
  promptGuidelines: [
812
871
  `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.`,
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, and one narrow verification.`,
814
873
  `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
874
  `After a resolved model, use sketch for first feature implementation framing or signal for existing-code structural movement before implementation mutation.`,
816
875
  ],
@@ -934,20 +993,29 @@ export default async function developer(pi: ExtensionAPI) {
934
993
  ? (requestedExecutionProfile ?? "ordinary")
935
994
  : undefined;
936
995
 
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");
996
+ const availableReferences = skill
997
+ ? await skillReferencePaths(skill)
998
+ : [];
999
+ const referencePolicy = skill
1000
+ ? await loadSkillReferencePolicy(skill, availableReferences)
1001
+ : { routes: [] };
1002
+ let method: string;
1003
+ if (skill) {
1004
+ method = await renderSkillMethod(skill);
1005
+ } else if (executionProfile === "behavior-preserving-structure") {
1006
+ const profile = await readFile(structuralChangeMethodPath, "utf8");
1007
+ method = [
1008
+ '<developer-implementation-profile name="behavior-preserving-structure">',
1009
+ profile.trim(),
1010
+ "</developer-implementation-profile>",
1011
+ ].join("\n");
1012
+ } else {
1013
+ method = [
1014
+ "# Implementation action",
1015
+ "",
1016
+ "The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
1017
+ ].join("\n");
1018
+ }
951
1019
 
952
1020
  const implementationStep =
953
1021
  target === "implementation"
@@ -976,6 +1044,11 @@ export default async function developer(pi: ExtensionAPI) {
976
1044
  reason,
977
1045
  knownEvidence,
978
1046
  consideredAlternatives,
1047
+ availableReferences,
1048
+ referenceRoutes: referencePolicy.routes,
1049
+ referenceExemptionCriteria: referencePolicy.exemption,
1050
+ referencePolicySha256: referencePolicy.contentSha256,
1051
+ loadedReferences: [],
979
1052
  targetQuestionId,
980
1053
  methodLocation: skill?.filePath,
981
1054
  executionProfile,
@@ -1000,6 +1073,17 @@ export default async function developer(pi: ExtensionAPI) {
1000
1073
  ]
1001
1074
  : []),
1002
1075
  `Reason: ${event.reason}`,
1076
+ `Available references: ${event.availableReferences.join(" | ") || "none"}`,
1077
+ ...referencePolicyLines(
1078
+ event.referenceRoutes,
1079
+ event.referenceExemptionCriteria,
1080
+ ),
1081
+ `Reference policy SHA-256: ${event.referencePolicySha256 ?? "none"}`,
1082
+ ...(skill && event.availableReferences.length > 0
1083
+ ? [
1084
+ `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.`,
1085
+ ]
1086
+ : []),
1003
1087
  `Known evidence: ${event.knownEvidence.length > 0 ? event.knownEvidence.join(" | ") : "none"}`,
1004
1088
  `Alternatives reconsidered: ${
1005
1089
  event.consideredAlternatives.length > 0
@@ -1080,6 +1164,15 @@ export default async function developer(pi: ExtensionAPI) {
1080
1164
  }
1081
1165
  text += `\n${detailLine(theme, "revisits", event.targetQuestionId ?? "none")}`;
1082
1166
  text += `\n${detailLine(theme, "skill", event.methodLocation ?? "implementation action")}`;
1167
+ if ((event.availableReferences ?? []).length > 0) {
1168
+ text += `\n${detailLine(theme, "references", event.availableReferences.join(", "))}`;
1169
+ }
1170
+ for (const route of event.referenceRoutes ?? []) {
1171
+ text += `\n${detailLine(theme, `reference route ${route.id}`, `${route.question} · ${route.trigger} → ${route.references.join(" + ")} · step=${route.methodStep} · stop=${route.stop} · separate=${route.separateWhen}`)}`;
1172
+ }
1173
+ if (event.referencePolicySha256) {
1174
+ text += `\n${detailLine(theme, "reference policy", event.referencePolicySha256)}`;
1175
+ }
1083
1176
  if (event.executionProfile) {
1084
1177
  text += `\n${detailLine(theme, "profile", event.executionProfile)}`;
1085
1178
  }
@@ -1095,6 +1188,192 @@ export default async function developer(pi: ExtensionAPI) {
1095
1188
  },
1096
1189
  });
1097
1190
 
1191
+ const ReferenceParams = Type.Object(
1192
+ {
1193
+ reference_route: Type.String({
1194
+ minLength: 1,
1195
+ maxLength: 128,
1196
+ description:
1197
+ "Exact reference route ID listed by the active Developer skill policy",
1198
+ }),
1199
+ path: Type.String({
1200
+ minLength: 1,
1201
+ maxLength: MAX_ARTIFACT_CHARS,
1202
+ description:
1203
+ "Exact direct skill-relative reference path listed by the active route, such as references/data-driven-design.md",
1204
+ }),
1205
+ reason: Type.String({
1206
+ minLength: 1,
1207
+ maxLength: MAX_EVIDENCE_CHARS,
1208
+ description:
1209
+ "Observable task evidence that makes this reference relevant now",
1210
+ }),
1211
+ },
1212
+ { additionalProperties: false },
1213
+ );
1214
+
1215
+ pi.registerTool({
1216
+ name: REFERENCE_TOOL,
1217
+ label: "Developer Load Reference",
1218
+ description:
1219
+ "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.",
1220
+ promptSnippet: "Load an active Developer skill reference with provenance",
1221
+ promptGuidelines: [
1222
+ `Use ${REFERENCE_TOOL}, not an ordinary read, for any reference that supports an active Developer skill judgment.`,
1223
+ `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.`,
1224
+ ],
1225
+ parameters: ReferenceParams,
1226
+ executionMode: "sequential",
1227
+ renderShell: "self",
1228
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
1229
+ if (!state.enabled) fail("Developer protocol is off.");
1230
+ const activeRoute = state.activeRoute;
1231
+ if (!activeRoute) fail("There is no active Developer skill route.");
1232
+ if (activeRoute.target === "implementation") {
1233
+ fail("Implementation routes do not load skill references.");
1234
+ }
1235
+ const referenceRouteId = params.reference_route.trim();
1236
+ const path = params.path.trim().replaceAll("\\", "/");
1237
+ const reason = params.reason.trim();
1238
+ if (!referenceRouteId || !path || !reason) {
1239
+ fail(
1240
+ "Reference route, path, and reason must contain non-whitespace text.",
1241
+ );
1242
+ }
1243
+ const skill = availableSkills.get(activeRoute.target);
1244
+ if (!skill || skill.filePath !== activeRoute.methodLocation) {
1245
+ fail(
1246
+ "The active skill source is no longer available at the route's canonical location. Route again against the current Pi resources.",
1247
+ );
1248
+ }
1249
+ const availableReferences =
1250
+ activeRoute.availableReferences.length > 0
1251
+ ? activeRoute.availableReferences
1252
+ : await skillReferencePaths(skill);
1253
+ let referenceRoutes = activeRoute.referenceRoutes;
1254
+ if (referenceRoutes.length === 0) {
1255
+ const policy = await loadSkillReferencePolicy(
1256
+ skill,
1257
+ availableReferences,
1258
+ );
1259
+ referenceRoutes = policy.routes;
1260
+ }
1261
+ const policyRoute = referenceRoutes.find(
1262
+ (candidate) => candidate.id === referenceRouteId,
1263
+ );
1264
+ if (!policyRoute) {
1265
+ fail(
1266
+ `Unknown reference route ${referenceRouteId}. Available routes: ${referenceRoutes.map((route) => route.id).join(", ") || "none"}.`,
1267
+ );
1268
+ }
1269
+ if (!policyRoute.references.includes(path)) {
1270
+ fail(
1271
+ `Reference ${path} is not part of route ${policyRoute.id}. Co-required route references: ${policyRoute.references.join(", ")}.`,
1272
+ );
1273
+ }
1274
+ const loadedPaths = new Set(
1275
+ activeRoute.loadedReferences.map((reference) => reference.path),
1276
+ );
1277
+ if (loadedPaths.has(path)) {
1278
+ fail(
1279
+ `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.`,
1280
+ );
1281
+ }
1282
+ if (policyRoute.readOrder === "listed") {
1283
+ const pathIndex = policyRoute.references.indexOf(path);
1284
+ const missingEarlierReferences = policyRoute.references
1285
+ .slice(0, pathIndex)
1286
+ .filter((reference) => !loadedPaths.has(reference));
1287
+ if (missingEarlierReferences.length > 0) {
1288
+ fail(
1289
+ `Reference route ${policyRoute.id} declares listed read order. Load these earlier route references first: ${missingEarlierReferences.join(", ")}.`,
1290
+ );
1291
+ }
1292
+ }
1293
+ const reference = await loadSkillReference(skill, path);
1294
+ const event: ReferenceLoadEvent = {
1295
+ protocol: PROTOCOL,
1296
+ kind: "reference-load",
1297
+ routeId: activeRoute.routeId,
1298
+ target: activeRoute.target,
1299
+ path: reference.path,
1300
+ reason,
1301
+ contentSha256: reference.contentSha256,
1302
+ sourceTrace: reference.sourceTrace,
1303
+ referenceRouteIds: [policyRoute.id],
1304
+ };
1305
+ if (!canApplyDeveloperEvent(state, event)) {
1306
+ fail(
1307
+ "Developer machine guard rejected the reference load for the current active route.",
1308
+ );
1309
+ }
1310
+ const response = [
1311
+ `Route ID: ${event.routeId}`,
1312
+ `Target: ${event.target}`,
1313
+ `Reference policy route: ${policyRoute.id}`,
1314
+ `Route question: ${policyRoute.question}`,
1315
+ `Route trigger: ${policyRoute.trigger}`,
1316
+ `Skill method step refined: ${policyRoute.methodStep}`,
1317
+ `Co-required references: ${policyRoute.references.join(" + ")}`,
1318
+ `Read order: ${policyRoute.readOrder}`,
1319
+ `Expected application artifacts: ${policyRoute.artifacts.join(" | ")}`,
1320
+ `Route stop: ${policyRoute.stop}`,
1321
+ `Separate or hand off when: ${policyRoute.separateWhen}`,
1322
+ `Reference: ${event.path}`,
1323
+ `Reference SHA-256: ${event.contentSha256}`,
1324
+ `Source Trace: ${event.sourceTrace ? "present in loaded content and recorded in tool details" : "not declared by this reference"}`,
1325
+ `Trigger evidence: ${event.reason}`,
1326
+ "",
1327
+ "<developer-reference>",
1328
+ reference.content,
1329
+ "</developer-reference>",
1330
+ ].join("\n");
1331
+ ensureSafeToolText(response, "Developer reference result");
1332
+ state = applyDeveloperEvent(state, event);
1333
+ syncProtocolTools();
1334
+ refreshUI(ctx);
1335
+ return textResult(response, event);
1336
+ },
1337
+ renderCall(args, theme, context) {
1338
+ const path =
1339
+ typeof args.path === "string" && args.path.length > 0 ? args.path : "…";
1340
+ return reusableText(
1341
+ `${theme.fg("toolTitle", theme.bold(REFERENCE_TOOL))} ${theme.fg("accent", path)}`,
1342
+ context.lastComponent,
1343
+ );
1344
+ },
1345
+ renderResult(result, { expanded, isPartial }, theme, context) {
1346
+ if (isPartial) {
1347
+ return reusableText(
1348
+ theme.fg("warning", "loading Developer reference…"),
1349
+ context.lastComponent,
1350
+ );
1351
+ }
1352
+ if (context.isError) {
1353
+ return reusableText(
1354
+ theme.fg("error", resultText(result) || "Developer reference failed"),
1355
+ context.lastComponent,
1356
+ );
1357
+ }
1358
+ const event = result.details as ReferenceLoadEvent | undefined;
1359
+ let text = theme.fg(
1360
+ "success",
1361
+ `loaded ${event?.path ?? "Developer reference"}`,
1362
+ );
1363
+ if (expanded && event) {
1364
+ text += `\n${detailLine(theme, "route", event.routeId, "text")}`;
1365
+ text += `\n${detailLine(theme, "target", event.target, "accent")}`;
1366
+ text += `\n${detailLine(theme, "reference route", event.referenceRouteIds.join(", ") || "legacy", "accent")}`;
1367
+ text += `\n${detailLine(theme, "sha256", event.contentSha256)}`;
1368
+ text += `\n${detailLine(theme, "trigger", event.reason, "text")}`;
1369
+ text += `\n${detailLine(theme, "source trace", event.sourceTrace || "not declared", event.sourceTrace ? "muted" : "warning")}`;
1370
+ }
1371
+ if (!expanded && event)
1372
+ text += ` · ${keyHint("app.tools.expand", "provenance")}`;
1373
+ return reusableText(text, context.lastComponent);
1374
+ },
1375
+ });
1376
+
1098
1377
  const ChoiceResponseOptionParam = Type.Object(
1099
1378
  {
1100
1379
  value: Type.String({
@@ -1204,6 +1483,54 @@ export default async function developer(pi: ExtensionAPI) {
1204
1483
  },
1205
1484
  { additionalProperties: false },
1206
1485
  );
1486
+ const ReferenceBasisParam = Type.Object(
1487
+ {
1488
+ path: Type.String({
1489
+ minLength: 1,
1490
+ maxLength: MAX_ARTIFACT_CHARS,
1491
+ description: `A reference path successfully loaded through ${REFERENCE_TOOL} during this route`,
1492
+ }),
1493
+ trigger: Type.String({
1494
+ minLength: 1,
1495
+ maxLength: MAX_EVIDENCE_CHARS,
1496
+ description:
1497
+ "Observable input, code, state, or requirement condition that made the reference applicable",
1498
+ }),
1499
+ applied_rule: Type.String({
1500
+ minLength: 1,
1501
+ maxLength: MAX_EVIDENCE_CHARS,
1502
+ description:
1503
+ "The concrete derivation, diagnostic, or decision rule taken from the loaded reference",
1504
+ }),
1505
+ artifact: Type.String({
1506
+ minLength: 1,
1507
+ maxLength: MAX_ARTIFACT_CHARS,
1508
+ description:
1509
+ "The result table, model, interface, trace, decision, or check produced by applying that rule",
1510
+ }),
1511
+ },
1512
+ { additionalProperties: false },
1513
+ );
1514
+ const ReferenceExemptionParam = Type.Object(
1515
+ {
1516
+ reason: Type.String({
1517
+ minLength: 1,
1518
+ maxLength: MAX_EVIDENCE_CHARS,
1519
+ description:
1520
+ "Why none of the active skill's available references can add useful judgment for this resolved local question",
1521
+ }),
1522
+ evidence: Type.Array(
1523
+ Type.String({ minLength: 1, maxLength: MAX_EVIDENCE_CHARS }),
1524
+ {
1525
+ minItems: 1,
1526
+ maxItems: 8,
1527
+ description:
1528
+ "Concrete facts supporting the exemption rather than a convenience claim",
1529
+ },
1530
+ ),
1531
+ },
1532
+ { additionalProperties: false },
1533
+ );
1207
1534
  const JudgmentParams = Type.Object({
1208
1535
  route_id: Type.String({
1209
1536
  minLength: 1,
@@ -1226,6 +1553,14 @@ export default async function developer(pi: ExtensionAPI) {
1226
1553
  maxItems: 20,
1227
1554
  description: "Evidence supporting the judgment or blocker",
1228
1555
  }),
1556
+ reference_basis: Type.Optional(
1557
+ Type.Array(ReferenceBasisParam, {
1558
+ maxItems: 12,
1559
+ description:
1560
+ "Loaded references linked to their trigger, applied rule, and resulting judgment artifact",
1561
+ }),
1562
+ ),
1563
+ reference_exemption: Type.Optional(ReferenceExemptionParam),
1229
1564
  open_questions: Type.Optional(
1230
1565
  Type.Array(OpenQuestionParam, {
1231
1566
  maxItems: 10,
@@ -1257,6 +1592,7 @@ export default async function developer(pi: ExtensionAPI) {
1257
1592
  promptGuidelines: [
1258
1593
  `Use ${JUDGMENT_TOOL} with the exact active Developer route ID.`,
1259
1594
  `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.`,
1595
+ `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
1596
  `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
1597
  `Do not use ${JUDGMENT_TOOL} with resolved, not-applicable, or blocked status without at least one concrete basis.`,
1262
1598
  ],
@@ -1291,6 +1627,34 @@ export default async function developer(pi: ExtensionAPI) {
1291
1627
  if (params.route_id !== activeRoute.routeId) {
1292
1628
  fail(`Route ID mismatch. Active route is ${activeRoute.routeId}.`);
1293
1629
  }
1630
+ const activeSkill =
1631
+ activeRoute.target === "implementation"
1632
+ ? undefined
1633
+ : availableSkills.get(activeRoute.target);
1634
+ let availableReferences: string[] = [];
1635
+ if (activeRoute.target !== "implementation") {
1636
+ if (activeRoute.availableReferences.length > 0) {
1637
+ availableReferences = activeRoute.availableReferences;
1638
+ } else if (
1639
+ activeSkill &&
1640
+ activeSkill.filePath === activeRoute.methodLocation
1641
+ ) {
1642
+ availableReferences = await skillReferencePaths(activeSkill);
1643
+ }
1644
+ }
1645
+ let referenceRoutes = activeRoute.referenceRoutes;
1646
+ if (
1647
+ activeRoute.target !== "implementation" &&
1648
+ referenceRoutes.length === 0 &&
1649
+ activeSkill &&
1650
+ activeSkill.filePath === activeRoute.methodLocation
1651
+ ) {
1652
+ const policy = await loadSkillReferencePolicy(
1653
+ activeSkill,
1654
+ availableReferences,
1655
+ );
1656
+ referenceRoutes = policy.routes;
1657
+ }
1294
1658
 
1295
1659
  const basis = params.basis.map((item) => item.trim()).filter(Boolean);
1296
1660
  const result = params.result.trim();
@@ -1299,6 +1663,131 @@ export default async function developer(pi: ExtensionAPI) {
1299
1663
  fail(`${params.status} judgments require at least one concrete basis.`);
1300
1664
  }
1301
1665
 
1666
+ const referenceBasisInput = (params.reference_basis ?? []).map(
1667
+ (item) => ({
1668
+ path: item.path.trim().replaceAll("\\", "/"),
1669
+ trigger: item.trigger.trim(),
1670
+ appliedRule: item.applied_rule.trim(),
1671
+ artifact: item.artifact.trim(),
1672
+ }),
1673
+ );
1674
+ const referenceExemptionInput = params.reference_exemption
1675
+ ? {
1676
+ reason: params.reference_exemption.reason.trim(),
1677
+ evidence: params.reference_exemption.evidence
1678
+ .map((item) => item.trim())
1679
+ .filter(Boolean),
1680
+ }
1681
+ : undefined;
1682
+ if (referenceBasisInput.length > 0 && referenceExemptionInput) {
1683
+ fail("Use reference_basis or reference_exemption, never both.");
1684
+ }
1685
+ if (
1686
+ new Set(referenceBasisInput.map((item) => item.path)).size !==
1687
+ referenceBasisInput.length
1688
+ ) {
1689
+ fail("Each reference_basis path must be unique within a judgment.");
1690
+ }
1691
+ if (
1692
+ activeRoute.target === "implementation" &&
1693
+ (referenceBasisInput.length > 0 || referenceExemptionInput)
1694
+ ) {
1695
+ fail(
1696
+ "Implementation judgments do not accept skill reference evidence.",
1697
+ );
1698
+ }
1699
+ if (referenceExemptionInput && params.status !== "resolved") {
1700
+ fail(
1701
+ "reference_exemption is valid only for a resolved skill judgment.",
1702
+ );
1703
+ }
1704
+ if (referenceExemptionInput && availableReferences.length === 0) {
1705
+ fail(
1706
+ "reference_exemption is unnecessary because the active skill has no available references.",
1707
+ );
1708
+ }
1709
+ if (referenceExemptionInput && activeRoute.loadedReferences.length > 0) {
1710
+ fail(
1711
+ "reference_exemption is valid only when no routed reference was loaded during this route.",
1712
+ );
1713
+ }
1714
+ if (
1715
+ params.status === "resolved" &&
1716
+ availableReferences.length > 0 &&
1717
+ referenceBasisInput.length === 0 &&
1718
+ !referenceExemptionInput
1719
+ ) {
1720
+ fail(
1721
+ `A resolved ${activeRoute.target} judgment must provide reference_basis for references loaded through ${REFERENCE_TOOL}, or a concrete reference_exemption.`,
1722
+ );
1723
+ }
1724
+ const referenceBasisPaths = new Set(
1725
+ referenceBasisInput.map((item) => item.path),
1726
+ );
1727
+ const loadedReferences = new Map(
1728
+ activeRoute.loadedReferences.map((reference) => [
1729
+ reference.path,
1730
+ reference,
1731
+ ]),
1732
+ );
1733
+ const loadedPaths = new Set(loadedReferences.keys());
1734
+ const basisMissingForLoaded = [...loadedPaths].filter(
1735
+ (path) => !referenceBasisPaths.has(path),
1736
+ );
1737
+ if (basisMissingForLoaded.length > 0) {
1738
+ fail(
1739
+ `Every loaded reference must have a reference_basis entry: ${basisMissingForLoaded.join(", ")}.`,
1740
+ );
1741
+ }
1742
+ const selectedReferenceRouteIds = new Set(
1743
+ activeRoute.loadedReferences.flatMap(
1744
+ (reference) => reference.referenceRouteIds,
1745
+ ),
1746
+ );
1747
+ for (const referenceRouteId of selectedReferenceRouteIds) {
1748
+ const route = referenceRoutes.find(
1749
+ (candidate) => candidate.id === referenceRouteId,
1750
+ );
1751
+ if (!route) {
1752
+ fail(
1753
+ `Loaded reference selected unknown policy route ${referenceRouteId}; route again against the current skill policy.`,
1754
+ );
1755
+ }
1756
+ const missingRouteReferences = route.references.filter(
1757
+ (path) => !loadedPaths.has(path),
1758
+ );
1759
+ if (missingRouteReferences.length > 0) {
1760
+ fail(
1761
+ `Reference route ${route.id} is incomplete. Load its co-required references before judgment: ${missingRouteReferences.join(", ")}.`,
1762
+ );
1763
+ }
1764
+ }
1765
+ for (const loaded of activeRoute.loadedReferences) {
1766
+ for (const referenceRouteId of loaded.referenceRouteIds) {
1767
+ const route = referenceRoutes.find(
1768
+ (candidate) => candidate.id === referenceRouteId,
1769
+ );
1770
+ if (route && !route.references.includes(loaded.path)) {
1771
+ fail(
1772
+ `Reference ${loaded.path} is not part of its recorded policy route ${route.id}.`,
1773
+ );
1774
+ }
1775
+ }
1776
+ }
1777
+ const referenceBasis: ReferenceBasis[] = referenceBasisInput.map(
1778
+ (item) => {
1779
+ const loaded = loadedReferences.get(item.path);
1780
+ if (!loaded) {
1781
+ fail(
1782
+ `reference_basis path ${item.path} was not successfully loaded through ${REFERENCE_TOOL} during this route.`,
1783
+ );
1784
+ }
1785
+ return { ...loaded, ...item };
1786
+ },
1787
+ );
1788
+ const referenceExemption: ReferenceExemption | undefined =
1789
+ referenceExemptionInput;
1790
+
1302
1791
  const openedQuestions = buildOpenedQuestions(
1303
1792
  params.open_questions ?? [],
1304
1793
  state,
@@ -1399,6 +1888,8 @@ export default async function developer(pi: ExtensionAPI) {
1399
1888
  status: params.status,
1400
1889
  result,
1401
1890
  basis,
1891
+ referenceBasis,
1892
+ referenceExemption,
1402
1893
  openedQuestions,
1403
1894
  questionUpdates,
1404
1895
  artifacts: (params.artifacts ?? [])
@@ -1796,6 +2287,30 @@ export default async function developer(pi: ExtensionAPI) {
1796
2287
  });
1797
2288
 
1798
2289
  pi.on("tool_call", (event) => {
2290
+ if (
2291
+ state.enabled &&
2292
+ event.toolName === "read" &&
2293
+ state.activeRoute?.target !== "implementation"
2294
+ ) {
2295
+ const activeSkill = state.activeRoute
2296
+ ? availableSkills.get(state.activeRoute.target)
2297
+ : undefined;
2298
+ const requestedPath = event.input.path;
2299
+ if (
2300
+ activeSkill &&
2301
+ typeof requestedPath === "string" &&
2302
+ isWithinRoot(
2303
+ resolve(activeSkill.baseDir, "references"),
2304
+ resolve(requestedPath),
2305
+ )
2306
+ ) {
2307
+ return {
2308
+ block: true,
2309
+ reason: `Developer skill references must be loaded through ${REFERENCE_TOOL} with a policy route so provenance and application remain auditable.`,
2310
+ };
2311
+ }
2312
+ }
2313
+
1799
2314
  const capability = builtinControlledToolCapabilities(pi.getAllTools()).get(
1800
2315
  event.toolName,
1801
2316
  );