@hobin/developer 0.1.7 → 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.
- package/README.md +212 -355
- package/REFERENCE_ROUTING.md +243 -0
- package/extensions/developer.ts +601 -23
- package/extensions/machine.ts +45 -0
- package/extensions/references/behavior-preserving-structural-change.md +87 -8
- package/extensions/skills.ts +330 -52
- package/extensions/state.ts +227 -2
- package/extensions/tool-policy.ts +87 -0
- package/package.json +9 -3
- package/skills/abstraction-review/SKILL.md +37 -23
- package/skills/abstraction-review/reference-policy.json +66 -0
- package/skills/abstraction-review/references/field-card.md +113 -168
- package/skills/abstraction-review/references/repair-table.md +66 -80
- package/skills/abstraction-review/references/worked-examples.md +79 -256
- package/skills/model/SKILL.md +31 -13
- package/skills/model/reference-policy.json +96 -0
- package/skills/model/references/contract-and-replacement-models.md +112 -0
- package/skills/model/references/logic-query-semantics.md +89 -0
- package/skills/model/references/planning-models.md +75 -0
- package/skills/model/references/problem-modeling.md +143 -276
- package/skills/model/references/proof-obligations.md +71 -0
- package/skills/model/references/relational-constraint-models.md +110 -0
- package/skills/model/references/solver-result-boundaries.md +83 -0
- package/skills/model/references/temporal-behavior-models.md +106 -0
- package/skills/naming-judgment/SKILL.md +17 -4
- package/skills/naming-judgment/reference-policy.json +29 -0
- package/skills/naming-judgment/references/domain-naming.md +29 -10
- package/skills/schedule/SKILL.md +16 -6
- package/skills/schedule/reference-policy.json +29 -0
- package/skills/schedule/references/structural-change-timing.md +81 -18
- package/skills/signal/SKILL.md +17 -6
- package/skills/signal/reference-policy.json +29 -0
- package/skills/signal/references/structural-movement.md +87 -14
- package/skills/sketch/SKILL.md +38 -42
- package/skills/sketch/reference-policy.json +212 -0
- package/skills/sketch/references/accumulator-invariants.md +125 -0
- package/skills/sketch/references/closure-and-conventional-interfaces.md +87 -0
- package/skills/sketch/references/composition-by-wishes.md +69 -0
- package/skills/sketch/references/data-driven-design.md +99 -22
- package/skills/sketch/references/data-shape-template-catalog.md +58 -19
- package/skills/sketch/references/design-levels-and-boundaries.md +133 -0
- package/skills/sketch/references/earned-abstraction.md +75 -0
- package/skills/sketch/references/generative-recursion.md +122 -0
- package/skills/sketch/references/generic-dispatch-systems.md +75 -0
- package/skills/sketch/references/language-semantics.md +82 -0
- package/skills/sketch/references/meaning-preserving-conversions.md +69 -0
- package/skills/sketch/references/process-shape-and-resources.md +157 -0
- package/skills/sketch/references/representation-barriers.md +73 -0
- package/skills/sketch/references/responsibility-and-collaboration.md +108 -0
- package/skills/sketch/references/runtime-and-compilation.md +84 -0
- package/skills/sketch/references/selection-and-creation.md +76 -0
- package/skills/sketch/references/state-history-and-order.md +126 -0
- package/skills/sketch/references/type-transitions.md +54 -0
- package/skills/sketch/references/variation-roles.md +59 -0
- package/skills/verify/SKILL.md +17 -6
- package/skills/verify/reference-policy.json +30 -0
- package/skills/verify/references/verifier-selection-and-pass-but-wrong.md +152 -192
- package/SOURCES.md +0 -104
- package/skills/abstraction-review/references/recipe-cards.md +0 -322
- package/skills/model/references/worked-models-and-specialized-techniques.md +0 -218
- package/skills/sketch/references/abstraction-barriers-and-closure.md +0 -160
- package/skills/sketch/references/abstraction-composition-and-state.md +0 -184
- package/skills/sketch/references/composition-generative-recursion-and-accumulators.md +0 -196
- package/skills/sketch/references/generic-operations-and-languages.md +0 -134
- package/skills/sketch/references/processes-state-and-time.md +0 -171
- package/skills/sketch/references/responsibility-and-variation.md +0 -245
package/extensions/developer.ts
CHANGED
|
@@ -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 {
|
|
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,13 +57,21 @@ 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";
|
|
55
68
|
import {
|
|
69
|
+
TOOL_POLICY_LIFECYCLE_ENTRY,
|
|
56
70
|
builtinControlledToolCapabilities,
|
|
57
71
|
isControlledToolAllowed,
|
|
58
72
|
reconcileProtocolTools,
|
|
73
|
+
reloadSafeToolPolicyMarker,
|
|
74
|
+
toolPolicyReloadRequiresRestart,
|
|
59
75
|
type ProtocolToolAccess,
|
|
60
76
|
type ToolPolicyMemory,
|
|
61
77
|
} from "./tool-policy.ts";
|
|
@@ -73,7 +89,7 @@ import {
|
|
|
73
89
|
type ImmediateQuestionDisposition,
|
|
74
90
|
} from "./tui.ts";
|
|
75
91
|
|
|
76
|
-
const PROTOCOL_TOOLS = [ROUTE_TOOL, JUDGMENT_TOOL] as const;
|
|
92
|
+
const PROTOCOL_TOOLS = [ROUTE_TOOL, REFERENCE_TOOL, JUDGMENT_TOOL] as const;
|
|
77
93
|
const extensionRoot = dirname(fileURLToPath(import.meta.url));
|
|
78
94
|
const skillsRoot = resolve(extensionRoot, "..", "skills");
|
|
79
95
|
const structuralChangeMethodPath = resolve(
|
|
@@ -88,6 +104,8 @@ const MAX_EVIDENCE_CHARS = 2_000;
|
|
|
88
104
|
const MAX_RESULT_CHARS = 12_000;
|
|
89
105
|
const MAX_ARTIFACT_CHARS = 4_096;
|
|
90
106
|
const DEVELOPER_COMMAND_ACTIONS = ["on", "status", "questions", "off"] as const;
|
|
107
|
+
const TOOL_POLICY_RESTART_MESSAGE =
|
|
108
|
+
"Developer detected an in-process package reload from a version without reload-safe tool handoff. Restart the Pi process before enabling Developer; /reload and /develop off/on cannot safely reconstruct the prior built-in tool selection.";
|
|
91
109
|
|
|
92
110
|
function textResult<T>(text: string, details: T) {
|
|
93
111
|
return {
|
|
@@ -137,6 +155,27 @@ function compactLine(value: string, maxChars = 160): string {
|
|
|
137
155
|
return line.length <= maxChars ? line : `${line.slice(0, maxChars - 1)}…`;
|
|
138
156
|
}
|
|
139
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
|
+
|
|
140
179
|
function normalizedQuestion(value: string): string {
|
|
141
180
|
return value
|
|
142
181
|
.toLocaleLowerCase()
|
|
@@ -408,11 +447,13 @@ function protocolPrompt(
|
|
|
408
447
|
"Developer protocol is active.",
|
|
409
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.",
|
|
410
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.",
|
|
411
451
|
`- Call ${ROUTE_TOOL} for exactly one concrete judgment or one green-to-green implementation movement.`,
|
|
412
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.",
|
|
413
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.",
|
|
414
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.",
|
|
415
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.`,
|
|
416
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.",
|
|
417
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.",
|
|
418
459
|
"- Protocol state is routing bookkeeping. Idle never proves product completion, user acceptance, or current verification.",
|
|
@@ -460,6 +501,9 @@ function protocolPrompt(
|
|
|
460
501
|
if (state.activeRoute.methodLocation) {
|
|
461
502
|
lines.push(
|
|
462
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"}.`,
|
|
463
507
|
);
|
|
464
508
|
}
|
|
465
509
|
}
|
|
@@ -529,6 +573,26 @@ function expandedJudgment(
|
|
|
529
573
|
|
|
530
574
|
const evidence = [
|
|
531
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
|
+
: []),
|
|
532
596
|
...event.artifacts.map((artifact) =>
|
|
533
597
|
detailLine(theme, "artifact", artifact),
|
|
534
598
|
),
|
|
@@ -560,6 +624,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
560
624
|
let routeOpening = false;
|
|
561
625
|
const routesWithMutation = new Set<string>();
|
|
562
626
|
let toolPolicyMemory: ToolPolicyMemory = { withheldBuiltins: new Set() };
|
|
627
|
+
let toolPolicyRestartRequired = false;
|
|
563
628
|
|
|
564
629
|
pi.registerFlag("develop", {
|
|
565
630
|
description: "Start with the Developer protocol enabled",
|
|
@@ -582,7 +647,31 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
582
647
|
pi.setActiveTools(next.activeTools);
|
|
583
648
|
};
|
|
584
649
|
|
|
650
|
+
const releaseProtocolTools = () => {
|
|
651
|
+
const current = pi.getActiveTools();
|
|
652
|
+
const next = reconcileProtocolTools({
|
|
653
|
+
activeTools: current,
|
|
654
|
+
allTools: pi.getAllTools(),
|
|
655
|
+
enabled: false,
|
|
656
|
+
access: protocolToolAccess(state),
|
|
657
|
+
protocolTools: PROTOCOL_TOOLS,
|
|
658
|
+
memory: toolPolicyMemory,
|
|
659
|
+
});
|
|
660
|
+
toolPolicyMemory = next.memory;
|
|
661
|
+
if (!sameToolSet(current, next.activeTools))
|
|
662
|
+
pi.setActiveTools(next.activeTools);
|
|
663
|
+
};
|
|
664
|
+
|
|
585
665
|
const refreshUI = (ctx: ExtensionContext) => {
|
|
666
|
+
if (toolPolicyRestartRequired) {
|
|
667
|
+
ctx.ui.setStatus("developer", "developer · restart required");
|
|
668
|
+
ctx.ui.setWidget(
|
|
669
|
+
"developer",
|
|
670
|
+
["blocked · restart Pi to reset Developer tool access"],
|
|
671
|
+
{ placement: "belowEditor" },
|
|
672
|
+
);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
586
675
|
if (!state.enabled) {
|
|
587
676
|
ctx.ui.setStatus("developer", undefined);
|
|
588
677
|
ctx.ui.setWidget("developer", undefined);
|
|
@@ -643,12 +732,19 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
643
732
|
};
|
|
644
733
|
|
|
645
734
|
const reconstruct = (ctx: ExtensionContext) => {
|
|
646
|
-
state =
|
|
735
|
+
state = toolPolicyRestartRequired
|
|
736
|
+
? initialState()
|
|
737
|
+
: reconstructState(ctx.sessionManager.getBranch());
|
|
647
738
|
syncProtocolTools();
|
|
648
739
|
refreshUI(ctx);
|
|
649
740
|
};
|
|
650
741
|
|
|
651
|
-
const setEnabled = (enabled: boolean, ctx: ExtensionContext) => {
|
|
742
|
+
const setEnabled = (enabled: boolean, ctx: ExtensionContext): boolean => {
|
|
743
|
+
if (enabled && toolPolicyRestartRequired) {
|
|
744
|
+
ctx.ui.notify(TOOL_POLICY_RESTART_MESSAGE, "error");
|
|
745
|
+
refreshUI(ctx);
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
652
748
|
const event: ActivationEvent = {
|
|
653
749
|
protocol: PROTOCOL,
|
|
654
750
|
kind: "activation",
|
|
@@ -658,6 +754,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
658
754
|
state = applyDeveloperEvent(state, event);
|
|
659
755
|
syncProtocolTools();
|
|
660
756
|
refreshUI(ctx);
|
|
757
|
+
return true;
|
|
661
758
|
};
|
|
662
759
|
|
|
663
760
|
const SharedRouteParams = {
|
|
@@ -768,11 +865,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
768
865
|
name: ROUTE_TOOL,
|
|
769
866
|
label: "Developer Route Question",
|
|
770
867
|
description:
|
|
771
|
-
"Route one concrete judgment or one green-to-green implementation movement.
|
|
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.",
|
|
772
869
|
promptSnippet: "Choose how to handle one development question",
|
|
773
870
|
promptGuidelines: [
|
|
774
871
|
`Call ${ROUTE_TOOL} only when there is no active Developer route.`,
|
|
775
|
-
`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.`,
|
|
776
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.`,
|
|
777
874
|
`After a resolved model, use sketch for first feature implementation framing or signal for existing-code structural movement before implementation mutation.`,
|
|
778
875
|
],
|
|
@@ -896,20 +993,29 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
896
993
|
? (requestedExecutionProfile ?? "ordinary")
|
|
897
994
|
: undefined;
|
|
898
995
|
|
|
899
|
-
const
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
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
|
+
}
|
|
913
1019
|
|
|
914
1020
|
const implementationStep =
|
|
915
1021
|
target === "implementation"
|
|
@@ -938,6 +1044,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
938
1044
|
reason,
|
|
939
1045
|
knownEvidence,
|
|
940
1046
|
consideredAlternatives,
|
|
1047
|
+
availableReferences,
|
|
1048
|
+
referenceRoutes: referencePolicy.routes,
|
|
1049
|
+
referenceExemptionCriteria: referencePolicy.exemption,
|
|
1050
|
+
referencePolicySha256: referencePolicy.contentSha256,
|
|
1051
|
+
loadedReferences: [],
|
|
941
1052
|
targetQuestionId,
|
|
942
1053
|
methodLocation: skill?.filePath,
|
|
943
1054
|
executionProfile,
|
|
@@ -962,6 +1073,17 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
962
1073
|
]
|
|
963
1074
|
: []),
|
|
964
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
|
+
: []),
|
|
965
1087
|
`Known evidence: ${event.knownEvidence.length > 0 ? event.knownEvidence.join(" | ") : "none"}`,
|
|
966
1088
|
`Alternatives reconsidered: ${
|
|
967
1089
|
event.consideredAlternatives.length > 0
|
|
@@ -1042,6 +1164,15 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1042
1164
|
}
|
|
1043
1165
|
text += `\n${detailLine(theme, "revisits", event.targetQuestionId ?? "none")}`;
|
|
1044
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
|
+
}
|
|
1045
1176
|
if (event.executionProfile) {
|
|
1046
1177
|
text += `\n${detailLine(theme, "profile", event.executionProfile)}`;
|
|
1047
1178
|
}
|
|
@@ -1057,6 +1188,192 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1057
1188
|
},
|
|
1058
1189
|
});
|
|
1059
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
|
+
|
|
1060
1377
|
const ChoiceResponseOptionParam = Type.Object(
|
|
1061
1378
|
{
|
|
1062
1379
|
value: Type.String({
|
|
@@ -1166,6 +1483,54 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1166
1483
|
},
|
|
1167
1484
|
{ additionalProperties: false },
|
|
1168
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
|
+
);
|
|
1169
1534
|
const JudgmentParams = Type.Object({
|
|
1170
1535
|
route_id: Type.String({
|
|
1171
1536
|
minLength: 1,
|
|
@@ -1188,6 +1553,14 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1188
1553
|
maxItems: 20,
|
|
1189
1554
|
description: "Evidence supporting the judgment or blocker",
|
|
1190
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),
|
|
1191
1564
|
open_questions: Type.Optional(
|
|
1192
1565
|
Type.Array(OpenQuestionParam, {
|
|
1193
1566
|
maxItems: 10,
|
|
@@ -1219,6 +1592,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1219
1592
|
promptGuidelines: [
|
|
1220
1593
|
`Use ${JUDGMENT_TOOL} with the exact active Developer route ID.`,
|
|
1221
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.`,
|
|
1222
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.`,
|
|
1223
1597
|
`Do not use ${JUDGMENT_TOOL} with resolved, not-applicable, or blocked status without at least one concrete basis.`,
|
|
1224
1598
|
],
|
|
@@ -1253,6 +1627,34 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1253
1627
|
if (params.route_id !== activeRoute.routeId) {
|
|
1254
1628
|
fail(`Route ID mismatch. Active route is ${activeRoute.routeId}.`);
|
|
1255
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
|
+
}
|
|
1256
1658
|
|
|
1257
1659
|
const basis = params.basis.map((item) => item.trim()).filter(Boolean);
|
|
1258
1660
|
const result = params.result.trim();
|
|
@@ -1261,6 +1663,131 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1261
1663
|
fail(`${params.status} judgments require at least one concrete basis.`);
|
|
1262
1664
|
}
|
|
1263
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
|
+
|
|
1264
1791
|
const openedQuestions = buildOpenedQuestions(
|
|
1265
1792
|
params.open_questions ?? [],
|
|
1266
1793
|
state,
|
|
@@ -1361,6 +1888,8 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1361
1888
|
status: params.status,
|
|
1362
1889
|
result,
|
|
1363
1890
|
basis,
|
|
1891
|
+
referenceBasis,
|
|
1892
|
+
referenceExemption,
|
|
1364
1893
|
openedQuestions,
|
|
1365
1894
|
questionUpdates,
|
|
1366
1895
|
artifacts: (params.artifacts ?? [])
|
|
@@ -1576,9 +2105,10 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1576
2105
|
return true;
|
|
1577
2106
|
};
|
|
1578
2107
|
|
|
1579
|
-
const setAndNotifyEnabled = (enabled: boolean) => {
|
|
1580
|
-
setEnabled(enabled, ctx);
|
|
2108
|
+
const setAndNotifyEnabled = (enabled: boolean): boolean => {
|
|
2109
|
+
if (!setEnabled(enabled, ctx)) return false;
|
|
1581
2110
|
ctx.ui.notify(`Developer: ${enabled ? "on" : "off"}`, "info");
|
|
2111
|
+
return true;
|
|
1582
2112
|
};
|
|
1583
2113
|
|
|
1584
2114
|
const turnOff = async (): Promise<boolean> => {
|
|
@@ -1603,6 +2133,11 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1603
2133
|
};
|
|
1604
2134
|
|
|
1605
2135
|
const inspectStatus = async () => {
|
|
2136
|
+
if (toolPolicyRestartRequired) {
|
|
2137
|
+
ctx.ui.notify(TOOL_POLICY_RESTART_MESSAGE, "error");
|
|
2138
|
+
refreshUI(ctx);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
1606
2141
|
refreshAvailableSkills(ctx);
|
|
1607
2142
|
if (ctx.mode === "tui") {
|
|
1608
2143
|
await showDeveloperStatus(ctx, {
|
|
@@ -1752,6 +2287,30 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1752
2287
|
});
|
|
1753
2288
|
|
|
1754
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
|
+
|
|
1755
2314
|
const capability = builtinControlledToolCapabilities(pi.getAllTools()).get(
|
|
1756
2315
|
event.toolName,
|
|
1757
2316
|
);
|
|
@@ -1796,14 +2355,33 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
1796
2355
|
};
|
|
1797
2356
|
});
|
|
1798
2357
|
|
|
1799
|
-
pi.on("session_start", (
|
|
2358
|
+
pi.on("session_start", (event, ctx) => {
|
|
2359
|
+
const branch = ctx.sessionManager.getBranch();
|
|
2360
|
+
toolPolicyRestartRequired =
|
|
2361
|
+
event.reason === "reload" &&
|
|
2362
|
+
toolPolicyReloadRequiresRestart({
|
|
2363
|
+
entries: branch,
|
|
2364
|
+
protocol: PROTOCOL,
|
|
2365
|
+
protocolTools: PROTOCOL_TOOLS,
|
|
2366
|
+
});
|
|
1800
2367
|
reconstruct(ctx);
|
|
2368
|
+
if (toolPolicyRestartRequired) {
|
|
2369
|
+
ctx.ui.notify(TOOL_POLICY_RESTART_MESSAGE, "error");
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
1801
2372
|
const startEnabled = pi.getFlag("develop");
|
|
1802
2373
|
if (startEnabled === true && !state.enabled) setEnabled(true, ctx);
|
|
1803
2374
|
});
|
|
1804
2375
|
pi.on("session_tree", (_event, ctx) => reconstruct(ctx));
|
|
1805
2376
|
pi.on("agent_settled", (_event, ctx) => refreshUI(ctx));
|
|
1806
2377
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
2378
|
+
releaseProtocolTools();
|
|
2379
|
+
if (!toolPolicyRestartRequired) {
|
|
2380
|
+
pi.appendEntry(
|
|
2381
|
+
TOOL_POLICY_LIFECYCLE_ENTRY,
|
|
2382
|
+
reloadSafeToolPolicyMarker(PROTOCOL),
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
1807
2385
|
ctx.ui.setStatus("developer", undefined);
|
|
1808
2386
|
ctx.ui.setWidget("developer", undefined);
|
|
1809
2387
|
});
|