@hobin/developer 0.1.1 → 0.1.2
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 +31 -11
- package/SOURCES.md +104 -0
- package/extensions/developer.ts +67 -24
- package/extensions/references/behavior-preserving-structural-change.md +145 -0
- package/extensions/skills.ts +1 -16
- package/extensions/state.ts +9 -1
- package/package.json +2 -1
- package/skills/abstraction-review/references/field-card.md +7 -0
- package/skills/abstraction-review/references/recipe-cards.md +67 -0
- package/skills/abstraction-review/references/repair-table.md +5 -0
- package/skills/model/SKILL.md +6 -1
- package/skills/model/references/problem-modeling.md +294 -235
- package/skills/model/references/worked-models-and-specialized-techniques.md +218 -0
- package/skills/naming-judgment/SKILL.md +5 -3
- package/skills/naming-judgment/references/domain-naming.md +202 -0
- package/skills/schedule/SKILL.md +1 -1
- package/skills/schedule/references/structural-change-timing.md +80 -13
- package/skills/signal/SKILL.md +2 -2
- package/skills/signal/references/structural-movement.md +202 -0
- package/skills/sketch/SKILL.md +35 -8
- package/skills/sketch/references/abstraction-barriers-and-closure.md +160 -0
- package/skills/sketch/references/abstraction-composition-and-state.md +184 -0
- package/skills/sketch/references/composition-generative-recursion-and-accumulators.md +196 -0
- package/skills/sketch/references/data-driven-design.md +177 -0
- package/skills/sketch/references/data-shape-template-catalog.md +241 -0
- package/skills/sketch/references/generic-operations-and-languages.md +134 -0
- package/skills/sketch/references/processes-state-and-time.md +171 -0
- package/skills/sketch/references/responsibility-and-variation.md +245 -0
- package/skills/verify/references/verifier-selection-and-pass-but-wrong.md +61 -3
- package/skills/naming-judgment/references/elements-of-clojure-naming.md +0 -74
- package/skills/signal/references/flocking-and-structural-movement.md +0 -157
- package/skills/sketch/references/design-recipe-and-abstraction-barriers.md +0 -169
package/README.md
CHANGED
|
@@ -87,7 +87,8 @@ Developer adds two model-facing protocol tools:
|
|
|
87
87
|
2. The route target is `direct` or one currently available Developer skill.
|
|
88
88
|
3. A skill route returns the exact Pi-discovered `SKILL.md` instructions and canonical
|
|
89
89
|
path; a direct route keeps implementation tools available for the justified
|
|
90
|
-
action.
|
|
90
|
+
action. A behavior-preserving structural direct route also loads the focused
|
|
91
|
+
execution profile that keeps edits small and returns to stable green landings.
|
|
91
92
|
4. `developer_record_judgment` closes the route with a status, result, evidence,
|
|
92
93
|
artifacts, and any newly opened questions.
|
|
93
94
|
|
|
@@ -140,12 +141,12 @@ or the user may invoke one directly with `/skill:<name>`.
|
|
|
140
141
|
| Skill | Helps decide |
|
|
141
142
|
| --- | --- |
|
|
142
143
|
| `specify` | Product meaning, scope, invariants, risks, and blocking unknowns |
|
|
143
|
-
| `model` |
|
|
144
|
-
| `sketch` |
|
|
145
|
-
| `signal` |
|
|
146
|
-
| `naming-judgment` |
|
|
144
|
+
| `model` | Condition spaces, contracts, replacement, transitions, guarantees, and verification targets |
|
|
145
|
+
| `sketch` | Data-driven skeletons, abstraction and composition boundaries, state, responsibility, and variation |
|
|
146
|
+
| `signal` | Evidence-backed structural movement and model-code mismatch |
|
|
147
|
+
| `naming-judgment` | Domain sense, honest effects, and change-preserving names |
|
|
147
148
|
| `abstraction-review` | Whether a candidate should be kept, revised, split, rejected, or deferred |
|
|
148
|
-
| `schedule` |
|
|
149
|
+
| `schedule` | Behavior/structure timing: now, after, or never |
|
|
149
150
|
| `verify` | Verifier selection, evidence relevance, and pass-but-wrong risk |
|
|
150
151
|
| `visualize` | The smallest visual surface that lowers judgment cost |
|
|
151
152
|
| `adversarial-eval` | Finite, escalating attempts to falsify a skill or implementation claim |
|
|
@@ -154,10 +155,27 @@ Pi's loaded resource metadata is authoritative. If package configuration filters
|
|
|
154
155
|
or disables a skill, Developer cannot route to it even if its file exists in the
|
|
155
156
|
npm package.
|
|
156
157
|
|
|
157
|
-
Several skills link to detailed documents under their own
|
|
158
|
-
directory. Pi loads the `SKILL.md` instructions on demand; the
|
|
159
|
-
when
|
|
160
|
-
skill directory.
|
|
158
|
+
Several skills link to detailed capability documents under their own
|
|
159
|
+
`references/` directory. Pi loads the `SKILL.md` instructions on demand; the
|
|
160
|
+
instructions say when each reference is worth reading and resolve it relative to
|
|
161
|
+
the skill directory. A reference may synthesize several sources because the
|
|
162
|
+
leaf's question, not a book title, owns the capability. Each source-derived
|
|
163
|
+
reference includes a source trace, and [SOURCES.md](./SOURCES.md) maps the full
|
|
164
|
+
book coverage across leaves and the direct path. Small, settled judgments do not
|
|
165
|
+
need the extra context.
|
|
166
|
+
|
|
167
|
+
Primary references retain the capability's core insight and one complete case.
|
|
168
|
+
When a question has materially different derivation rules, the skill routes a
|
|
169
|
+
more focused supporting reference—for example, data-shape templates separately
|
|
170
|
+
from generative recursion, or representation barriers separately from state and
|
|
171
|
+
time. This follows Pi's documented progressive-disclosure structure: supporting
|
|
172
|
+
files stay relative to their skill and load only when the unresolved question
|
|
173
|
+
needs them. `SOURCES.md` also records the reference-quality contract used to
|
|
174
|
+
prevent source summaries from replacing executable judgment recipes.
|
|
175
|
+
|
|
176
|
+
Developer treats Pi's loaded `systemPromptOptions.skills` metadata as the skill
|
|
177
|
+
SSOT. It does not rescan the package and cannot route a skill Pi filtered,
|
|
178
|
+
disabled, or replaced through resource configuration.
|
|
161
179
|
|
|
162
180
|
## State, branches, and compaction
|
|
163
181
|
|
|
@@ -201,11 +219,13 @@ pinning, and security behavior.
|
|
|
201
219
|
```text
|
|
202
220
|
extensions/
|
|
203
221
|
├── developer.ts # command, protocol tools, events, and Pi integration
|
|
222
|
+
├── references/ # direct execution profiles loaded through tool results
|
|
204
223
|
├── state.ts # replayable developer/v2 branch state
|
|
205
|
-
├── skills.ts #
|
|
224
|
+
├── skills.ts # loaded-skill filtering and instruction rendering
|
|
206
225
|
├── tool-policy.ts # strict-mode active-tool reconciliation
|
|
207
226
|
└── tui.ts # selectors, widget, status panel, and prompt preparation
|
|
208
227
|
skills/ # ten independently loadable Pi skills
|
|
228
|
+
SOURCES.md # source-to-capability maintenance trace
|
|
209
229
|
evals/ # model-dependent scenarios and workspace assertions
|
|
210
230
|
tests/ # deterministic state, policy, extension, and TUI tests
|
|
211
231
|
```
|
package/SOURCES.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Source-to-Capability Trace
|
|
2
|
+
|
|
3
|
+
Developer is organized around questions the agent must answer, not around books.
|
|
4
|
+
This maintainer document records how source material is decomposed across leaf
|
|
5
|
+
skills and the direct execution path. It is not a Pi runtime resource or a
|
|
6
|
+
required workflow.
|
|
7
|
+
|
|
8
|
+
## Ownership Rule
|
|
9
|
+
|
|
10
|
+
A source idea belongs where its observable job is performed:
|
|
11
|
+
|
|
12
|
+
- product meaning and change pressure go to `specify`;
|
|
13
|
+
- cases, rules, contracts, and transitions go to `model`;
|
|
14
|
+
- implementable boundaries and collaborations go to `sketch`;
|
|
15
|
+
- visible structural movement goes to `signal`;
|
|
16
|
+
- candidate stability goes to `abstraction-review`;
|
|
17
|
+
- timing goes to `schedule`;
|
|
18
|
+
- names go to `naming-judgment`;
|
|
19
|
+
- claim-to-evidence judgment goes to `verify`;
|
|
20
|
+
- already-justified mutation goes to the `direct` route.
|
|
21
|
+
|
|
22
|
+
No source implies a mandatory route order. Several leaves may use different
|
|
23
|
+
parts of the same source because they answer different questions.
|
|
24
|
+
|
|
25
|
+
## Runtime Reference Quality
|
|
26
|
+
|
|
27
|
+
`SKILL.md` owns discovery, the core question, output, completion, and conditional
|
|
28
|
+
routing. A reference loaded through that route must be usable without reopening
|
|
29
|
+
the original source or reconstructing unstated steps.
|
|
30
|
+
|
|
31
|
+
Match detail to the reference's job:
|
|
32
|
+
|
|
33
|
+
- a concept or design reference needs the core insight, input, derivation rule,
|
|
34
|
+
completed example, counterexample or failure diagnosis, output, and boundary;
|
|
35
|
+
- a recipe card needs a trigger, input artifact, construction rule, output
|
|
36
|
+
artifact, observable stop, and repair direction;
|
|
37
|
+
- a diagnostic table needs a symptom, evidence path, repair artifact, and
|
|
38
|
+
re-check;
|
|
39
|
+
- a worked-example file needs enough context to show why the decision follows,
|
|
40
|
+
not only a polished final answer;
|
|
41
|
+
- a mutation protocol needs an entry contract, safe intermediate states,
|
|
42
|
+
evidence rhythm, reroute conditions, and a stable landing.
|
|
43
|
+
|
|
44
|
+
Split a reference when independent questions would otherwise make it expensive
|
|
45
|
+
to load, but keep the central insight and one complete example in the primary
|
|
46
|
+
reference. Splitting by chapter title or merely to shorten a file is not a
|
|
47
|
+
capability boundary.
|
|
48
|
+
|
|
49
|
+
## Capability Matrix
|
|
50
|
+
|
|
51
|
+
| Source | Capability extracted | Primary owner | Supporting owner or execution path |
|
|
52
|
+
| --- | --- | --- | --- |
|
|
53
|
+
| *99 Bottles of OOP*, ch. 1-2 | simple concrete baseline, Shameless Green, cost-effective and intention-revealing tests | `verify` | `signal`, `schedule` |
|
|
54
|
+
| *99 Bottles of OOP*, ch. 3-4 | real change pressure, point of attack, closest pair, smallest difference, horizontal movement | `signal` | `direct` behavior-preserving protocol |
|
|
55
|
+
| *99 Bottles of OOP*, ch. 4-5 | stable landings, responsibility-derived names, argument and data movement | `abstraction-review`, `naming-judgment` | `direct` behavior-preserving protocol |
|
|
56
|
+
| *99 Bottles of OOP*, ch. 5-8 | responsibility separation, messages, type transitions, polymorphism, dependency direction, object creation at the edge, factory tradeoffs | `sketch`, `abstraction-review` | `model`, `schedule` |
|
|
57
|
+
| *99 Bottles of OOP*, ch. 9 | unit boundaries, context independence, role verification, obsolete-test removal | `verify` | `sketch` |
|
|
58
|
+
| *How to Design Programs* | information analysis, data definitions, examples, templates, structural and generative recursion, accumulators, iterative refinement | `sketch` | `model`, `verify`, `signal`, `abstraction-review` |
|
|
59
|
+
| *Structure and Interpretation of Computer Programs*, ch. 1-2 | procedural abstraction, process shape, higher-order composition, data abstraction, closure, conventional interfaces, multiple representations | `sketch`, `abstraction-review` | `model` |
|
|
60
|
+
| *Structure and Interpretation of Computer Programs*, ch. 3 | state, identity, history, concurrency, streams, constraint propagation | `model`, `sketch` | `abstraction-review`, `verify` |
|
|
61
|
+
| *Structure and Interpretation of Computer Programs*, ch. 4-5 | language/evaluator boundaries and explicit execution machinery | `sketch`, `abstraction-review` | imported only when a real DSL, interpreter, compiler, or runtime boundary exists |
|
|
62
|
+
| *Logic for Programmers*, ch. 2-3 | predicates, sets, quantifiers, logical refactoring, runtime-semantic caveats | `model` | `verify` |
|
|
63
|
+
| *Logic for Programmers*, ch. 4-6 | partial specifications, properties, contracts, replacement, proof limits | `model`, `verify` | `abstraction-review` |
|
|
64
|
+
| *Logic for Programmers*, ch. 7-12 | relational constraints, decision tables, domain/time/system models, solvers, logic programming | `model` | `verify` |
|
|
65
|
+
| *Elements of Clojure*: Names | narrow and consistent sense, honest effects and scope crossing | `naming-judgment` | `model` |
|
|
66
|
+
| *Elements of Clojure*: Idioms | language-specific conventions and explicit operational semantics | project conventions or `direct` | generalized only when the semantic lesson survives the language |
|
|
67
|
+
| *Elements of Clojure*: Indirection | abstraction cost, module environment/model/interface/assumptions, principled versus adaptable systems | `sketch`, `abstraction-review` | `schedule` |
|
|
68
|
+
| *Elements of Clojure*: Composition | units of computation and pull-transform-push process boundaries | `sketch` | `naming-judgment`, `verify` |
|
|
69
|
+
| *Tidy First?* | separate behavior from structure, small tidyings, optionality, reversibility, first/after/later/never | `schedule` | `direct` behavior-preserving protocol, `signal` |
|
|
70
|
+
|
|
71
|
+
## Intentionally Not Imported As Universal Rules
|
|
72
|
+
|
|
73
|
+
Source coverage does not mean copying every example or language idiom into every
|
|
74
|
+
task. The following remain contextual:
|
|
75
|
+
|
|
76
|
+
- Ruby class and factory shapes from *99 Bottles* are evidence about
|
|
77
|
+
responsibility and variation, not a destination for every design.
|
|
78
|
+
- Scheme evaluators, register machines, and compiler details from SICP are used
|
|
79
|
+
only for tasks that actually expose those runtime or language boundaries.
|
|
80
|
+
- Clojure-specific arities, option maps, atoms, macros, and interop idioms stay
|
|
81
|
+
subordinate to the target project's language and conventions.
|
|
82
|
+
- Logic solvers, formal proof, TLA+, Prolog, and answer-set programming are
|
|
83
|
+
selected only when the modeled uncertainty and risk justify their cost.
|
|
84
|
+
- HtDP recipes are adapted to existing repositories; they do not require adding
|
|
85
|
+
pedagogical comments, signatures, or templates to production code.
|
|
86
|
+
|
|
87
|
+
These exclusions prevent a respected source from becoming a framework-shaped
|
|
88
|
+
answer imposed on unrelated product work.
|
|
89
|
+
|
|
90
|
+
## Reassembly Check
|
|
91
|
+
|
|
92
|
+
When adding or revising a source-derived reference, record:
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
Source location: book, edition/version, chapter or section
|
|
96
|
+
Capability: the question or action the idea improves
|
|
97
|
+
Owner: one leaf or the direct path
|
|
98
|
+
Boundary: what the owner must not absorb
|
|
99
|
+
Observable effect: the artifact, decision, or safer execution behavior expected
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Create a new leaf only when a recurring question has a distinct input, output,
|
|
103
|
+
completion condition, and boundary that no existing owner can express. A new
|
|
104
|
+
source, technique, or vocabulary is not by itself evidence for a new leaf.
|
package/extensions/developer.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
1
2
|
import { dirname, resolve } from "node:path";
|
|
2
3
|
import { fileURLToPath } from "node:url";
|
|
3
4
|
|
|
@@ -14,11 +15,7 @@ import {
|
|
|
14
15
|
import { Text } from "@earendil-works/pi-tui";
|
|
15
16
|
import { Type } from "typebox";
|
|
16
17
|
|
|
17
|
-
import {
|
|
18
|
-
availablePackageSkills,
|
|
19
|
-
loadCandidateSkills,
|
|
20
|
-
renderSkillMethod,
|
|
21
|
-
} from "./skills.ts";
|
|
18
|
+
import { availablePackageSkills, renderSkillMethod } from "./skills.ts";
|
|
22
19
|
import {
|
|
23
20
|
JUDGMENT_TOOL,
|
|
24
21
|
MODE_ENTRY,
|
|
@@ -31,6 +28,7 @@ import {
|
|
|
31
28
|
reconstructState,
|
|
32
29
|
type DeveloperMode,
|
|
33
30
|
type DeveloperState,
|
|
31
|
+
type DirectExecutionProfile,
|
|
34
32
|
type JudgmentEvent,
|
|
35
33
|
type ModeEvent,
|
|
36
34
|
type RouteEvent,
|
|
@@ -50,7 +48,13 @@ import {
|
|
|
50
48
|
} from "./tui.ts";
|
|
51
49
|
|
|
52
50
|
const PROTOCOL_TOOLS = [ROUTE_TOOL, JUDGMENT_TOOL] as const;
|
|
53
|
-
const
|
|
51
|
+
const extensionRoot = dirname(fileURLToPath(import.meta.url));
|
|
52
|
+
const skillsRoot = resolve(extensionRoot, "..", "skills");
|
|
53
|
+
const structuralChangeMethodPath = resolve(
|
|
54
|
+
extensionRoot,
|
|
55
|
+
"references",
|
|
56
|
+
"behavior-preserving-structural-change.md",
|
|
57
|
+
);
|
|
54
58
|
const MAX_PENDING_QUESTIONS = 20;
|
|
55
59
|
const MAX_QUESTION_CHARS = 2_000;
|
|
56
60
|
const MAX_EVIDENCE_CHARS = 2_000;
|
|
@@ -116,6 +120,7 @@ function protocolPrompt(state: DeveloperState, availableSkillNames: string[]): s
|
|
|
116
120
|
"- Treat the protocol as adaptive routing, never as a fixed sequence of skills.",
|
|
117
121
|
`- Call ${ROUTE_TOOL} only when one concrete development question needs focused judgment or direct action.`,
|
|
118
122
|
"- Use owner=direct when the next local action is already justified; otherwise set owner to the available skill whose scope best fits the question.",
|
|
123
|
+
"- For direct structural work intended to preserve behavior, set execution_profile=behavior-preserving-structure; omit the field for other direct actions and every skill route.",
|
|
119
124
|
`- Follow the skill instructions returned by ${ROUTE_TOOL}, then close that route with ${JUDGMENT_TOOL}.`,
|
|
120
125
|
"- Keep a direct route open through implementation and evidence collection; record its judgment afterward.",
|
|
121
126
|
"- Protocol state is routing bookkeeping. Idle never proves product completion, user acceptance, or current verification.",
|
|
@@ -156,7 +161,8 @@ function protocolPrompt(state: DeveloperState, availableSkillNames: string[]): s
|
|
|
156
161
|
function routeRenderText(event: RouteEvent | undefined): string {
|
|
157
162
|
if (!event) return "Route unavailable";
|
|
158
163
|
const target = event.targetQuestionId ? ` · revisits ${event.targetQuestionId}` : "";
|
|
159
|
-
|
|
164
|
+
const profile = event.executionProfile ? `/${event.executionProfile}` : "";
|
|
165
|
+
return `${event.owner}${profile} · ${compactLine(event.question)}${target}`;
|
|
160
166
|
}
|
|
161
167
|
|
|
162
168
|
function judgmentRenderText(event: JudgmentEvent | undefined): string {
|
|
@@ -165,7 +171,6 @@ function judgmentRenderText(event: JudgmentEvent | undefined): string {
|
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
export default async function developer(pi: ExtensionAPI) {
|
|
168
|
-
const candidates = loadCandidateSkills(skillsRoot);
|
|
169
174
|
let availableSkills = new Map<string, Skill>();
|
|
170
175
|
let state = initialState();
|
|
171
176
|
let routeOpening = false;
|
|
@@ -240,17 +245,12 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
240
245
|
refreshUI(ctx);
|
|
241
246
|
};
|
|
242
247
|
|
|
243
|
-
const
|
|
248
|
+
const SharedRouteParams = {
|
|
244
249
|
question: Type.String({
|
|
245
250
|
minLength: 1,
|
|
246
251
|
maxLength: MAX_QUESTION_CHARS,
|
|
247
252
|
description: "The single concrete judgment or action question to route",
|
|
248
253
|
}),
|
|
249
|
-
owner: Type.String({
|
|
250
|
-
minLength: 1,
|
|
251
|
-
maxLength: 64,
|
|
252
|
-
description: "direct, or an exact skill name from the current Available Developer skills list",
|
|
253
|
-
}),
|
|
254
254
|
reason: Type.String({
|
|
255
255
|
minLength: 1,
|
|
256
256
|
maxLength: MAX_QUESTION_CHARS,
|
|
@@ -265,7 +265,33 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
265
265
|
open_question_id: Type.Optional(
|
|
266
266
|
Type.String({ maxLength: 512, description: "Exact pending question ID when this route revisits one" }),
|
|
267
267
|
),
|
|
268
|
-
}
|
|
268
|
+
};
|
|
269
|
+
const RouteParams = Type.Union([
|
|
270
|
+
Type.Object(
|
|
271
|
+
{
|
|
272
|
+
...SharedRouteParams,
|
|
273
|
+
owner: Type.String({
|
|
274
|
+
minLength: 1,
|
|
275
|
+
maxLength: 64,
|
|
276
|
+
pattern: "^(?!direct$)[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
277
|
+
description: "Exact skill name from the current Available Developer skills list",
|
|
278
|
+
}),
|
|
279
|
+
},
|
|
280
|
+
{ additionalProperties: false, description: "Route one question to a Developer skill" },
|
|
281
|
+
),
|
|
282
|
+
Type.Object(
|
|
283
|
+
{
|
|
284
|
+
...SharedRouteParams,
|
|
285
|
+
owner: Type.Literal("direct", { description: "Use Pi implementation tools for an already-justified action" }),
|
|
286
|
+
execution_profile: Type.Optional(
|
|
287
|
+
Type.Literal("behavior-preserving-structure", {
|
|
288
|
+
description: "Load the focused structural-mutation protocol; omit for ordinary direct action",
|
|
289
|
+
}),
|
|
290
|
+
),
|
|
291
|
+
},
|
|
292
|
+
{ additionalProperties: false, description: "Route one already-justified direct action" },
|
|
293
|
+
),
|
|
294
|
+
]);
|
|
269
295
|
|
|
270
296
|
pi.registerTool({
|
|
271
297
|
name: ROUTE_TOOL,
|
|
@@ -302,15 +328,28 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
302
328
|
if (owner !== "direct" && !skill) {
|
|
303
329
|
fail(`Developer skill ${owner} is unavailable or disabled in the current Pi resource configuration.`);
|
|
304
330
|
}
|
|
331
|
+
const requestedExecutionProfile =
|
|
332
|
+
"execution_profile" in params ? params.execution_profile : undefined;
|
|
333
|
+
if (owner !== "direct" && requestedExecutionProfile !== undefined) {
|
|
334
|
+
fail("execution_profile is valid only when owner=direct.");
|
|
335
|
+
}
|
|
336
|
+
const executionProfile: DirectExecutionProfile | undefined =
|
|
337
|
+
owner === "direct" ? (requestedExecutionProfile ?? "ordinary") : undefined;
|
|
305
338
|
|
|
306
339
|
const method =
|
|
307
|
-
owner
|
|
308
|
-
?
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
340
|
+
owner !== "direct"
|
|
341
|
+
? await renderSkillMethod(skill!)
|
|
342
|
+
: executionProfile === "behavior-preserving-structure"
|
|
343
|
+
? [
|
|
344
|
+
'<developer-direct-profile name="behavior-preserving-structure">',
|
|
345
|
+
(await readFile(structuralChangeMethodPath, "utf8")).trim(),
|
|
346
|
+
"</developer-direct-profile>",
|
|
347
|
+
].join("\n")
|
|
348
|
+
: [
|
|
349
|
+
"# Direct action",
|
|
350
|
+
"",
|
|
351
|
+
"The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
|
|
352
|
+
].join("\n");
|
|
314
353
|
|
|
315
354
|
const event: RouteEvent = {
|
|
316
355
|
protocol: PROTOCOL,
|
|
@@ -322,12 +361,14 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
322
361
|
knownEvidence: (params.known_evidence ?? []).map((item) => item.trim()).filter(Boolean),
|
|
323
362
|
targetQuestionId,
|
|
324
363
|
methodLocation: skill?.filePath,
|
|
364
|
+
executionProfile,
|
|
325
365
|
};
|
|
326
366
|
const response = [
|
|
327
367
|
`Route ID: ${event.routeId}`,
|
|
328
368
|
`Question: ${event.question}`,
|
|
329
369
|
`Target: ${event.owner}`,
|
|
330
370
|
skill ? `Skill location: ${skill.filePath}` : "Skill location: direct action; no skill file",
|
|
371
|
+
executionProfile ? `Execution profile: ${executionProfile}` : "Execution profile: skill judgment",
|
|
331
372
|
`Reason: ${event.reason}`,
|
|
332
373
|
`Known evidence: ${event.knownEvidence.length > 0 ? event.knownEvidence.join(" | ") : "none"}`,
|
|
333
374
|
targetQuestionId ? `Revisits pending question: ${targetQuestionId}` : "Revisits pending question: none",
|
|
@@ -381,6 +422,9 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
381
422
|
}
|
|
382
423
|
text += `\n${theme.fg("dim", `revisits · ${event.targetQuestionId ?? "none"}`)}`;
|
|
383
424
|
text += `\n${theme.fg("dim", `skill · ${event.methodLocation ?? "direct action"}`)}`;
|
|
425
|
+
if (event.executionProfile) {
|
|
426
|
+
text += `\n${theme.fg("dim", `profile · ${event.executionProfile}`)}`;
|
|
427
|
+
}
|
|
384
428
|
}
|
|
385
429
|
if (!expanded && event) text += ` · ${keyHint("app.tools.expand", "details")}`;
|
|
386
430
|
return reusableText(text, context.lastComponent);
|
|
@@ -537,7 +581,6 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
537
581
|
if (typeof ctx.getSystemPromptOptions !== "function") return;
|
|
538
582
|
availableSkills = availablePackageSkills(
|
|
539
583
|
ctx.getSystemPromptOptions().skills ?? [],
|
|
540
|
-
candidates,
|
|
541
584
|
skillsRoot,
|
|
542
585
|
);
|
|
543
586
|
};
|
|
@@ -668,7 +711,7 @@ export default async function developer(pi: ExtensionAPI) {
|
|
|
668
711
|
});
|
|
669
712
|
|
|
670
713
|
pi.on("before_agent_start", (event) => {
|
|
671
|
-
availableSkills = availablePackageSkills(event.systemPromptOptions.skills ?? [],
|
|
714
|
+
availableSkills = availablePackageSkills(event.systemPromptOptions.skills ?? [], skillsRoot);
|
|
672
715
|
if (state.mode === "off") return;
|
|
673
716
|
return { systemPrompt: event.systemPrompt + protocolPrompt(state, [...availableSkills.keys()]) };
|
|
674
717
|
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Behavior-Preserving Structural Change
|
|
2
|
+
|
|
3
|
+
Use this protocol only after a `direct` route has justified a concrete structural
|
|
4
|
+
move. It governs mutation; it does not discover the move, approve an abstraction,
|
|
5
|
+
or decide its timing.
|
|
6
|
+
|
|
7
|
+
## Entry Contract
|
|
8
|
+
|
|
9
|
+
Before editing, state:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
Behavior to preserve: the narrow observable claim
|
|
13
|
+
Structural move: one concrete rearrangement
|
|
14
|
+
Pressure: why the accepted task needs or benefits from it now
|
|
15
|
+
Baseline evidence: the cheapest relevant check that is currently green
|
|
16
|
+
Affected surface: files, callers, persisted forms, or public boundaries
|
|
17
|
+
Stop: the next stable, pausable state
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If the behavior claim, timing, or candidate is still under judgment, close or
|
|
21
|
+
pause the direct route and route that question to the appropriate leaf.
|
|
22
|
+
|
|
23
|
+
## Smallest Green Transformation
|
|
24
|
+
|
|
25
|
+
Keep behavior and structure distinct in reasoning even if they eventually share
|
|
26
|
+
a commit. Prefer the smallest movement whose failure can be localized.
|
|
27
|
+
|
|
28
|
+
For replacement-shaped refactors, use this progression:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
new shape parses
|
|
32
|
+
-> new shape executes
|
|
33
|
+
-> callers use its result
|
|
34
|
+
-> replaced code becomes unreachable
|
|
35
|
+
-> replaced code is deleted
|
|
36
|
+
-> relevant evidence is green
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For movement across a boundary, update one sender or caller at a time when the
|
|
40
|
+
old and new forms can safely coexist. Confirm that each intermediate state is
|
|
41
|
+
valid. Do not rely on a final large diff to explain which step changed behavior.
|
|
42
|
+
|
|
43
|
+
## Evidence Rhythm
|
|
44
|
+
|
|
45
|
+
After each meaningful movement:
|
|
46
|
+
|
|
47
|
+
1. run the narrowest check that can catch the likely break;
|
|
48
|
+
2. inspect the diff for mixed behavior and structural changes;
|
|
49
|
+
3. reduce the step when the failure cannot be explained locally;
|
|
50
|
+
4. return to a green, deployable state before selecting another movement;
|
|
51
|
+
5. re-observe the code instead of following a predetermined final design.
|
|
52
|
+
|
|
53
|
+
A passing test is useful only when it exercises the preserved behavior. When no
|
|
54
|
+
cheap verifier exists, keep the movement smaller and record the residual risk.
|
|
55
|
+
|
|
56
|
+
## Stable Landing
|
|
57
|
+
|
|
58
|
+
A stable landing is not merely a green command. It is a state where:
|
|
59
|
+
|
|
60
|
+
- the preserved behavior has relevant evidence;
|
|
61
|
+
- names and intermediate compatibility shapes are honest enough to pause;
|
|
62
|
+
- no dead replacement path or half-moved caller remains accidentally active;
|
|
63
|
+
- the diff has one explainable structural purpose;
|
|
64
|
+
- the next decision can be made from the new evidence.
|
|
65
|
+
|
|
66
|
+
Stop there when the accepted structural purpose is complete. Further cleanup,
|
|
67
|
+
polymorphism, factory design, or public abstraction requires its own current
|
|
68
|
+
pressure and judgment.
|
|
69
|
+
|
|
70
|
+
## Reroute Conditions
|
|
71
|
+
|
|
72
|
+
Leave direct execution and open a focused judgment when the movement reveals:
|
|
73
|
+
|
|
74
|
+
- an unexpected product behavior or policy choice: `specify`;
|
|
75
|
+
- missing cases, transition rules, or replacement obligations: `model`;
|
|
76
|
+
- unresolved ownership, collaboration, or data flow: `sketch`;
|
|
77
|
+
- a new comparison or uncertain structural direction: `signal`;
|
|
78
|
+
- a responsibility, role, boundary, or abstraction candidate: `abstraction-review`;
|
|
79
|
+
- a disputed name or vocabulary boundary: `naming-judgment`;
|
|
80
|
+
- a question of now, after, or never: `schedule`;
|
|
81
|
+
- weak or irrelevant evidence: `verify`.
|
|
82
|
+
|
|
83
|
+
This is adaptive rerouting, not a required sequence.
|
|
84
|
+
|
|
85
|
+
## Worked Mutation Trace
|
|
86
|
+
|
|
87
|
+
Accepted move: replace duplicated schedule conversion in two callers with the
|
|
88
|
+
already-reviewed pure `toScheduleContent` function.
|
|
89
|
+
|
|
90
|
+
```text
|
|
91
|
+
Behavior to preserve:
|
|
92
|
+
both callers produce the same ScheduleContent or domain error for the
|
|
93
|
+
characterized table of variants
|
|
94
|
+
Structural move:
|
|
95
|
+
introduce the function, move one caller, then the second, then delete the
|
|
96
|
+
duplicate expressions
|
|
97
|
+
Baseline evidence:
|
|
98
|
+
focused table test plus both caller integration tests are green
|
|
99
|
+
Affected surface:
|
|
100
|
+
converter module, two callers, no persistence format
|
|
101
|
+
Stop:
|
|
102
|
+
both callers use the converter; old expressions are unreachable and removed
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Mutation sequence:
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
1. add the pure function with one characterized caller's exact behavior
|
|
109
|
+
2. run the conversion table
|
|
110
|
+
3. switch caller A; run its integration and inspect the diff
|
|
111
|
+
4. switch caller B; run its integration and inspect the diff
|
|
112
|
+
5. search for the replaced expression and remove dead code
|
|
113
|
+
6. run the focused table, integrations, typecheck, and packed-source check
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
If caller B reveals that `null` and missing have different product meanings, do
|
|
117
|
+
not broaden the helper until everything passes. Return that policy to `model`;
|
|
118
|
+
caller A remains at a stable landing. If both callers move cleanly, stop.
|
|
119
|
+
Creating a converter class or registry is a new judgment, not “finishing” this
|
|
120
|
+
refactor.
|
|
121
|
+
|
|
122
|
+
## Failure Checks
|
|
123
|
+
|
|
124
|
+
The protocol is being misused when:
|
|
125
|
+
|
|
126
|
+
- tests are changed merely to preserve green output after behavior drift;
|
|
127
|
+
- several differences are removed before the first can be inspected;
|
|
128
|
+
- a class, strategy, polymorphic hierarchy, or factory was chosen in advance;
|
|
129
|
+
- behavior work is hidden inside a structural label;
|
|
130
|
+
- the route continues after a new human-owned policy question appears;
|
|
131
|
+
- an intermediate state cannot safely run or be reviewed;
|
|
132
|
+
- cleanup continues past the accepted pressure because the code could be nicer.
|
|
133
|
+
|
|
134
|
+
## Source Trace
|
|
135
|
+
|
|
136
|
+
- Sandi Metz, Katrina Owen, and TJ Stankus, *99 Bottles of OOP*, Second
|
|
137
|
+
Edition, version 2.2.2, 2024: chapters 3-5 on methodical transformations,
|
|
138
|
+
flocking, gradual refactoring, stable landings, and sender-by-sender movement.
|
|
139
|
+
- Kent Beck, *Tidy First?: A Personal Exercise in Empirical Software Design*,
|
|
140
|
+
O'Reilly Media, 2023: separating behavior and structure, keeping tidyings
|
|
141
|
+
small, and managing structural change as optionality.
|
|
142
|
+
- Matthias Felleisen, Robert Bruce Findler, Matthew Flatt, and Shriram
|
|
143
|
+
Krishnamurthi, *How to Design Programs, Second Edition*, MIT Press, 2018:
|
|
144
|
+
iterative refinement and propagating a changed data design through dependent
|
|
145
|
+
artifacts.
|
package/extensions/skills.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { isAbsolute, relative, resolve } from "node:path";
|
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_MAX_BYTES,
|
|
7
7
|
DEFAULT_MAX_LINES,
|
|
8
|
-
loadSkillsFromDir,
|
|
9
8
|
stripFrontmatter,
|
|
10
9
|
type Skill,
|
|
11
10
|
} from "@earendil-works/pi-coding-agent";
|
|
@@ -33,30 +32,16 @@ export function isWithinRoot(root: string, path: string): boolean {
|
|
|
33
32
|
return relation === "" || (!relation.startsWith("..") && !isAbsolute(relation));
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
export function loadCandidateSkills(skillsRoot: string): Map<string, Skill> {
|
|
37
|
-
const loaded = loadSkillsFromDir({ dir: skillsRoot, source: "@hobin/developer" });
|
|
38
|
-
const candidates = new Map<string, Skill>();
|
|
39
|
-
|
|
40
|
-
for (const skill of loaded.skills) {
|
|
41
|
-
if (candidates.has(skill.name)) throw new Error(`Duplicate Developer skill name: ${skill.name}`);
|
|
42
|
-
candidates.set(skill.name, skill);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (candidates.size === 0) throw new Error(`No Developer skills found in ${skillsRoot}`);
|
|
46
|
-
return candidates;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
35
|
export function availablePackageSkills(
|
|
50
36
|
loadedSkills: Skill[],
|
|
51
|
-
candidates: ReadonlyMap<string, Skill>,
|
|
52
37
|
skillsRoot: string,
|
|
53
38
|
): Map<string, Skill> {
|
|
54
39
|
const available = new Map<string, Skill>();
|
|
55
40
|
|
|
56
41
|
for (const skill of loadedSkills) {
|
|
57
42
|
if (skill.disableModelInvocation) continue;
|
|
58
|
-
if (!candidates.has(skill.name)) continue;
|
|
59
43
|
if (!isWithinRoot(skillsRoot, skill.filePath)) continue;
|
|
44
|
+
if (available.has(skill.name)) throw new Error(`Duplicate Pi-loaded Developer skill name: ${skill.name}`);
|
|
60
45
|
available.set(skill.name, skill);
|
|
61
46
|
}
|
|
62
47
|
|
package/extensions/state.ts
CHANGED
|
@@ -9,6 +9,7 @@ export const LEGACY_JUDGMENT_TOOL = "record_judgment" as const;
|
|
|
9
9
|
export type DeveloperMode = "off" | "on" | "strict";
|
|
10
10
|
export type JudgmentStatus = "resolved" | "needs-evidence" | "not-applicable" | "blocked";
|
|
11
11
|
export type PendingQuestionStatus = "needs-evidence" | "blocked";
|
|
12
|
+
export type DirectExecutionProfile = "ordinary" | "behavior-preserving-structure";
|
|
12
13
|
|
|
13
14
|
export interface ModeEvent {
|
|
14
15
|
protocol: typeof PROTOCOL;
|
|
@@ -26,6 +27,7 @@ export interface RouteEvent {
|
|
|
26
27
|
knownEvidence: string[];
|
|
27
28
|
targetQuestionId?: string;
|
|
28
29
|
methodLocation?: string;
|
|
30
|
+
executionProfile?: DirectExecutionProfile;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export interface PendingQuestion {
|
|
@@ -143,6 +145,10 @@ function isJudgmentStatus(value: unknown): value is JudgmentStatus {
|
|
|
143
145
|
return value === "resolved" || value === "needs-evidence" || value === "not-applicable" || value === "blocked";
|
|
144
146
|
}
|
|
145
147
|
|
|
148
|
+
function isDirectExecutionProfile(value: unknown): value is DirectExecutionProfile {
|
|
149
|
+
return value === "ordinary" || value === "behavior-preserving-structure";
|
|
150
|
+
}
|
|
151
|
+
|
|
146
152
|
function parsePendingQuestion(value: unknown): PendingQuestion | undefined {
|
|
147
153
|
if (!isObject(value)) return undefined;
|
|
148
154
|
if (
|
|
@@ -178,7 +184,8 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
178
184
|
typeof value.reason !== "string" ||
|
|
179
185
|
!isStringArray(value.knownEvidence) ||
|
|
180
186
|
(value.targetQuestionId !== undefined && typeof value.targetQuestionId !== "string") ||
|
|
181
|
-
(value.methodLocation !== undefined && typeof value.methodLocation !== "string")
|
|
187
|
+
(value.methodLocation !== undefined && typeof value.methodLocation !== "string") ||
|
|
188
|
+
(value.executionProfile !== undefined && !isDirectExecutionProfile(value.executionProfile))
|
|
182
189
|
) {
|
|
183
190
|
return undefined;
|
|
184
191
|
}
|
|
@@ -192,6 +199,7 @@ export function normalizeDeveloperEvent(value: unknown): DeveloperEvent | undefi
|
|
|
192
199
|
knownEvidence: value.knownEvidence,
|
|
193
200
|
targetQuestionId: value.targetQuestionId,
|
|
194
201
|
methodLocation: value.methodLocation,
|
|
202
|
+
executionProfile: value.executionProfile,
|
|
195
203
|
};
|
|
196
204
|
}
|
|
197
205
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hobin/developer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Adaptive product-development reasoning and evidence for Pi.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"extensions",
|
|
31
31
|
"skills",
|
|
32
32
|
"README.md",
|
|
33
|
+
"SOURCES.md",
|
|
33
34
|
"LICENSE"
|
|
34
35
|
],
|
|
35
36
|
"engines": {
|
|
@@ -30,6 +30,12 @@ The wish opens freedom; the stop check brings the review back to evidence.
|
|
|
30
30
|
Failure is not handled by a better name. Failure exposes a broken layer,
|
|
31
31
|
contract, or missing artifact.
|
|
32
32
|
|
|
33
|
+
Use [the recipe cards](recipe-cards.md) when the derivation rule is missing,
|
|
34
|
+
[the repair table](repair-table.md) when the stop check fails, and
|
|
35
|
+
[the worked examples](worked-examples.md) when a completed calibration case is
|
|
36
|
+
needed. The field card remains the auditable summary; these files supply focused
|
|
37
|
+
construction, repair, and examples.
|
|
38
|
+
|
|
33
39
|
## Field Card
|
|
34
40
|
|
|
35
41
|
Fill this card for serious reviews.
|
|
@@ -153,6 +159,7 @@ If the sentence cannot be filled:
|
|
|
153
159
|
| --- | --- | --- |
|
|
154
160
|
| The problem vocabulary is missing or misleading | Language | Notation As Data |
|
|
155
161
|
| Similar functions, workflows, or tests move together | Language/Unit | Movement Pattern Extraction |
|
|
162
|
+
| Data, methods, messages, or change reasons suggest a misplaced owner | Unit/Boundary | Responsibility Boundary |
|
|
156
163
|
| Operation results do not remain composable | Unit | Closure Composition Unit |
|
|
157
164
|
| A loop, state transition, or contract has no preserved meaning | Law | Invariant Iteration |
|
|
158
165
|
| Callers must know raw representation or hidden policy | Boundary | Data Abstraction Boundary |
|