@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.
Files changed (32) hide show
  1. package/README.md +31 -11
  2. package/SOURCES.md +104 -0
  3. package/extensions/developer.ts +67 -24
  4. package/extensions/references/behavior-preserving-structural-change.md +145 -0
  5. package/extensions/skills.ts +1 -16
  6. package/extensions/state.ts +9 -1
  7. package/package.json +2 -1
  8. package/skills/abstraction-review/references/field-card.md +7 -0
  9. package/skills/abstraction-review/references/recipe-cards.md +67 -0
  10. package/skills/abstraction-review/references/repair-table.md +5 -0
  11. package/skills/model/SKILL.md +6 -1
  12. package/skills/model/references/problem-modeling.md +294 -235
  13. package/skills/model/references/worked-models-and-specialized-techniques.md +218 -0
  14. package/skills/naming-judgment/SKILL.md +5 -3
  15. package/skills/naming-judgment/references/domain-naming.md +202 -0
  16. package/skills/schedule/SKILL.md +1 -1
  17. package/skills/schedule/references/structural-change-timing.md +80 -13
  18. package/skills/signal/SKILL.md +2 -2
  19. package/skills/signal/references/structural-movement.md +202 -0
  20. package/skills/sketch/SKILL.md +35 -8
  21. package/skills/sketch/references/abstraction-barriers-and-closure.md +160 -0
  22. package/skills/sketch/references/abstraction-composition-and-state.md +184 -0
  23. package/skills/sketch/references/composition-generative-recursion-and-accumulators.md +196 -0
  24. package/skills/sketch/references/data-driven-design.md +177 -0
  25. package/skills/sketch/references/data-shape-template-catalog.md +241 -0
  26. package/skills/sketch/references/generic-operations-and-languages.md +134 -0
  27. package/skills/sketch/references/processes-state-and-time.md +171 -0
  28. package/skills/sketch/references/responsibility-and-variation.md +245 -0
  29. package/skills/verify/references/verifier-selection-and-pass-but-wrong.md +61 -3
  30. package/skills/naming-judgment/references/elements-of-clojure-naming.md +0 -74
  31. package/skills/signal/references/flocking-and-structural-movement.md +0 -157
  32. 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` | Logic for Programmers-style condition spaces, guarantees, and verification targets |
144
- | `sketch` | SICP-style abstraction barriers and HtDP-style data-directed design recipes |
145
- | `signal` | Flocking-style small structural movement and model-code mismatch |
146
- | `naming-judgment` | Elements of Clojure-style domain sense and change-preserving names |
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` | Tidy First-style behavior/structure timing: now, after, or never |
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 `references/`
158
- directory. Pi loads the `SKILL.md` instructions on demand; the instructions say
159
- when the additional reference is worth reading and resolve it relative to the
160
- skill directory. Small, already-settled judgments do not need the extra context.
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 # Pi-native skill discovery and instruction loading
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.
@@ -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 skillsRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..", "skills");
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
- return `${event.owner} · ${compactLine(event.question)}${target}`;
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 RouteParams = Type.Object({
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 === "direct"
308
- ? [
309
- "# Direct action",
310
- "",
311
- "The next local action is already justified. Keep this route open while using Pi implementation tools and collecting evidence.",
312
- ].join("\n")
313
- : await renderSkillMethod(skill!);
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 ?? [], candidates, skillsRoot);
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.
@@ -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
 
@@ -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.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 |