@whamp/pi-pstack 0.7.0
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/LICENSE +21 -0
- package/README.md +68 -0
- package/agents/comment-sicko.md +34 -0
- package/agents/poteto-agent.md +13 -0
- package/extensions/pstack/ask-user-question.ts +137 -0
- package/extensions/pstack/config.ts +55 -0
- package/extensions/pstack/index.ts +330 -0
- package/extensions/pstack/pstack-config-status.ts +72 -0
- package/extensions/pstack/pstack-role-config-store.ts +287 -0
- package/extensions/pstack/pstack-role-config.ts +496 -0
- package/extensions/pstack/pstack-role-prompt.ts +33 -0
- package/extensions/pstack/pstack-roles.ts +252 -0
- package/extensions/pstack/pstack-setup-plan.ts +223 -0
- package/extensions/pstack/skill-strip.ts +62 -0
- package/package.json +60 -0
- package/skills/architect/SKILL.md +83 -0
- package/skills/architect/references/design-red-flags.md +33 -0
- package/skills/architect/references/rationale-template.md +35 -0
- package/skills/architect/references/runner-prompt.md +20 -0
- package/skills/arena/SKILL.md +71 -0
- package/skills/automate-me/SKILL.md +104 -0
- package/skills/blast-radius/SKILL.md +50 -0
- package/skills/bro/SKILL.md +7 -0
- package/skills/create-verification-skill/SKILL.md +44 -0
- package/skills/create-verification-skill/references/feature-map-example/README.md +47 -0
- package/skills/create-verification-skill/references/feature-map-example/create-note.md +39 -0
- package/skills/create-verification-skill/references/feature-map-example/search.md +45 -0
- package/skills/deslop/SKILL.md +23 -0
- package/skills/figure-it-out/SKILL.md +53 -0
- package/skills/how/SKILL.md +52 -0
- package/skills/how/references/explainer-prompt.md +55 -0
- package/skills/how/references/explorer-prompt.md +52 -0
- package/skills/interrogate/SKILL.md +111 -0
- package/skills/interrogate/references/code-quality-review.md +47 -0
- package/skills/interrogate/references/lead-judgment.md +58 -0
- package/skills/interrogate/references/reviewer-prompt.md +72 -0
- package/skills/interrogate/references/rubric.md +77 -0
- package/skills/maintain-verification-skill/SKILL.md +39 -0
- package/skills/no-comments/SKILL.md +24 -0
- package/skills/poteto-mode/SKILL.md +147 -0
- package/skills/poteto-mode/playbooks/authoring-a-skill.md +12 -0
- package/skills/poteto-mode/playbooks/autonomous-run.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-full.md +13 -0
- package/skills/poteto-mode/playbooks/autopilot-stack.md +16 -0
- package/skills/poteto-mode/playbooks/babysit.md +27 -0
- package/skills/poteto-mode/playbooks/bug-fix.md +17 -0
- package/skills/poteto-mode/playbooks/eval.md +25 -0
- package/skills/poteto-mode/playbooks/feature.md +21 -0
- package/skills/poteto-mode/playbooks/hillclimb.md +21 -0
- package/skills/poteto-mode/playbooks/investigation.md +14 -0
- package/skills/poteto-mode/playbooks/multi-phase-plan.md +156 -0
- package/skills/poteto-mode/playbooks/opening-a-pr.md +33 -0
- package/skills/poteto-mode/playbooks/orchestrate.md +199 -0
- package/skills/poteto-mode/playbooks/pause-safely.md +10 -0
- package/skills/poteto-mode/playbooks/perf-issue.md +24 -0
- package/skills/poteto-mode/playbooks/prototype.md +14 -0
- package/skills/poteto-mode/playbooks/refactoring.md +16 -0
- package/skills/poteto-mode/playbooks/runtime-forensics.md +11 -0
- package/skills/poteto-mode/playbooks/session-pickup.md +11 -0
- package/skills/poteto-mode/playbooks/shipping.md +17 -0
- package/skills/poteto-mode/playbooks/trace-forensics.md +14 -0
- package/skills/poteto-mode/playbooks/visual-parity.md +11 -0
- package/skills/poteto-mode/playbooks/worktree-cleanup.md +14 -0
- package/skills/poteto-mode/references/bugbot-triage.md +142 -0
- package/skills/poteto-mode/scripts/bootstrap.ts +62 -0
- package/skills/poteto-mode/scripts/bun.lock +67 -0
- package/skills/poteto-mode/scripts/check-plan.mjs +186 -0
- package/skills/poteto-mode/scripts/check-plan.test.mjs +25 -0
- package/skills/poteto-mode/scripts/orch/orch.test.ts +634 -0
- package/skills/poteto-mode/scripts/orch/orch.ts +578 -0
- package/skills/poteto-mode/scripts/orch/store.ts +1607 -0
- package/skills/poteto-mode/scripts/package.json +16 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.test.ts +224 -0
- package/skills/poteto-mode/scripts/watch-pr/cli.ts +223 -0
- package/skills/poteto-mode/scripts/watch-pr/fakes.test-helper.ts +118 -0
- package/skills/poteto-mode/scripts/watch-pr/github.test.ts +306 -0
- package/skills/poteto-mode/scripts/watch-pr/github.ts +699 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.test.ts +420 -0
- package/skills/poteto-mode/scripts/watch-pr/policy.ts +832 -0
- package/skills/poteto-mode/scripts/watch-pr/render.ts +169 -0
- package/skills/poteto-mode/scripts/watch-pr/tsconfig.json +13 -0
- package/skills/poteto-mode/scripts/watch-pr/types.compile.ts +93 -0
- package/skills/poteto-mode/scripts/watch-pr/types.ts +401 -0
- package/skills/poteto-mode/scripts/watch-pr/watch-pr +6 -0
- package/skills/poteto-mode/scripts/worktree-audit.sh +85 -0
- package/skills/principle-attack-the-premise/SKILL.md +23 -0
- package/skills/principle-boundary-discipline/SKILL.md +34 -0
- package/skills/principle-build-the-lever/SKILL.md +23 -0
- package/skills/principle-encode-lessons-in-structure/SKILL.md +31 -0
- package/skills/principle-exhaust-the-design-space/SKILL.md +21 -0
- package/skills/principle-experience-first/SKILL.md +19 -0
- package/skills/principle-fix-root-causes/SKILL.md +23 -0
- package/skills/principle-foundational-thinking/SKILL.md +21 -0
- package/skills/principle-guard-the-context-window/SKILL.md +17 -0
- package/skills/principle-laziness-protocol/SKILL.md +18 -0
- package/skills/principle-make-operations-idempotent/SKILL.md +24 -0
- package/skills/principle-migrate-callers-then-delete-legacy-apis/SKILL.md +22 -0
- package/skills/principle-minimize-reader-load/SKILL.md +23 -0
- package/skills/principle-model-the-domain/SKILL.md +26 -0
- package/skills/principle-never-block-on-the-human/SKILL.md +22 -0
- package/skills/principle-outcome-oriented-execution/SKILL.md +22 -0
- package/skills/principle-prove-it-works/SKILL.md +33 -0
- package/skills/principle-redesign-from-first-principles/SKILL.md +16 -0
- package/skills/principle-separate-before-serializing-shared-state/SKILL.md +16 -0
- package/skills/principle-sequence-verifiable-units/SKILL.md +22 -0
- package/skills/principle-subtract-before-you-add/SKILL.md +21 -0
- package/skills/principle-test-behavior-not-implementation/SKILL.md +25 -0
- package/skills/principle-type-system-discipline/SKILL.md +31 -0
- package/skills/recall/SKILL.md +35 -0
- package/skills/reflect/SKILL.md +73 -0
- package/skills/reflect/references/divergent-reviewer.md +43 -0
- package/skills/reflect/references/judgment-reviewer.md +42 -0
- package/skills/reflect/references/synthesizer.md +56 -0
- package/skills/reflect/references/tooling-reviewer.md +55 -0
- package/skills/setup-pstack/SKILL.md +38 -0
- package/skills/setup-pstack/references/MODEL-ROLES.md +35 -0
- package/skills/show-me-your-work/SKILL.md +82 -0
- package/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
- package/skills/show-me-your-work/scripts/log.sh +40 -0
- package/skills/swarm/SKILL.md +46 -0
- package/skills/tdd/SKILL.md +44 -0
- package/skills/teach/SKILL.md +21 -0
- package/skills/technical-writing/SKILL.md +127 -0
- package/skills/typescript-best-practices/SKILL.md +29 -0
- package/skills/typescript-best-practices/references/patterns.md +313 -0
- package/skills/unslop/SKILL.md +67 -0
- package/skills/why/SKILL.md +154 -0
- package/skills/why/references/epistemics.md +144 -0
- package/skills/why/references/investigator-prompt.md +103 -0
- package/skills/why/references/source-playbook.md +17 -0
- package/skills/why/references/sources/code-archaeology.md +88 -0
- package/skills/why/references/sources/databricks.md +70 -0
- package/skills/why/references/sources/datadog.md +99 -0
- package/skills/why/references/sources/incident-postmortem.md +15 -0
- package/skills/why/references/sources/linear.md +48 -0
- package/skills/why/references/sources/notion.md +55 -0
- package/skills/why/references/sources/sentry.md +100 -0
- package/skills/why/references/sources/slack.md +54 -0
- package/skills/why/references/synthesizer-prompt.md +135 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/** Cardinality of a pstack model role. single, repeat, fanout, or pick-one. */
|
|
2
|
+
export const ROLE_CARDINALITIES = {
|
|
3
|
+
SINGLE: "single",
|
|
4
|
+
REPEAT: "repeat",
|
|
5
|
+
FANOUT: "fanout",
|
|
6
|
+
PICK_ONE: "pick-one",
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
/** Cardinality controlling whether a role stores one selector or a model list. */
|
|
10
|
+
export type PstackRoleCardinality = (typeof ROLE_CARDINALITIES)[keyof typeof ROLE_CARDINALITIES];
|
|
11
|
+
|
|
12
|
+
/** Setup section containing a related group of pstack roles. */
|
|
13
|
+
export type PstackRoleGroup =
|
|
14
|
+
| "Implementation"
|
|
15
|
+
| "Judgment and prose"
|
|
16
|
+
| "How"
|
|
17
|
+
| "Why"
|
|
18
|
+
| "Reflect"
|
|
19
|
+
| "Arena"
|
|
20
|
+
| "Swarm"
|
|
21
|
+
| "Architect"
|
|
22
|
+
| "Interrogate";
|
|
23
|
+
|
|
24
|
+
/** One pstack role's cardinality, setup group, purpose, and v1 legacy name. */
|
|
25
|
+
export interface PstackRoleDefinition {
|
|
26
|
+
readonly cardinality: PstackRoleCardinality;
|
|
27
|
+
readonly group: PstackRoleGroup;
|
|
28
|
+
readonly purpose: string;
|
|
29
|
+
readonly legacyName: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Registry of pstack model roles. Single source for role names and cardinality. */
|
|
33
|
+
export const PSTACK_ROLES = {
|
|
34
|
+
"feature implementation": {
|
|
35
|
+
cardinality: "single",
|
|
36
|
+
group: "Implementation",
|
|
37
|
+
purpose: "Implement the scoped feature after design.",
|
|
38
|
+
legacyName: "feature, refactoring",
|
|
39
|
+
},
|
|
40
|
+
"refactoring implementation": {
|
|
41
|
+
cardinality: "single",
|
|
42
|
+
group: "Implementation",
|
|
43
|
+
purpose: "Make behavior-preserving structural edits.",
|
|
44
|
+
legacyName: "feature, refactoring",
|
|
45
|
+
},
|
|
46
|
+
"bug-fix": {
|
|
47
|
+
cardinality: "single",
|
|
48
|
+
group: "Implementation",
|
|
49
|
+
purpose: "Implement the diagnosed fix.",
|
|
50
|
+
legacyName: "bug-fix",
|
|
51
|
+
},
|
|
52
|
+
"perf-issue": {
|
|
53
|
+
cardinality: "single",
|
|
54
|
+
group: "Implementation",
|
|
55
|
+
purpose: "Implement a measured performance change.",
|
|
56
|
+
legacyName: "perf-issue",
|
|
57
|
+
},
|
|
58
|
+
hillclimb: {
|
|
59
|
+
cardinality: "repeat",
|
|
60
|
+
group: "Implementation",
|
|
61
|
+
purpose: "Implement each bounded metric experiment.",
|
|
62
|
+
legacyName: "hillclimb",
|
|
63
|
+
},
|
|
64
|
+
judgment: {
|
|
65
|
+
cardinality: "single",
|
|
66
|
+
group: "Judgment and prose",
|
|
67
|
+
purpose: "Give a general decision or second opinion.",
|
|
68
|
+
legacyName: "judgment and prose",
|
|
69
|
+
},
|
|
70
|
+
prose: {
|
|
71
|
+
cardinality: "single",
|
|
72
|
+
group: "Judgment and prose",
|
|
73
|
+
purpose: "Draft or revise user-facing writing.",
|
|
74
|
+
legacyName: "judgment and prose",
|
|
75
|
+
},
|
|
76
|
+
"hardest tasks": {
|
|
77
|
+
cardinality: "single",
|
|
78
|
+
group: "Judgment and prose",
|
|
79
|
+
purpose: "Handle an explicitly escalated difficult task.",
|
|
80
|
+
legacyName: "hardest tasks",
|
|
81
|
+
},
|
|
82
|
+
"how explorers": {
|
|
83
|
+
cardinality: "repeat",
|
|
84
|
+
group: "How",
|
|
85
|
+
purpose: "Investigate each architecture slice.",
|
|
86
|
+
legacyName: "how explorer",
|
|
87
|
+
},
|
|
88
|
+
"how explainer": {
|
|
89
|
+
cardinality: "single",
|
|
90
|
+
group: "How",
|
|
91
|
+
purpose: "Investigate and explain a simple question.",
|
|
92
|
+
legacyName: "how explainer",
|
|
93
|
+
},
|
|
94
|
+
"how synthesizer": {
|
|
95
|
+
cardinality: "single",
|
|
96
|
+
group: "How",
|
|
97
|
+
purpose: "Combine completed architecture findings.",
|
|
98
|
+
legacyName: "how explainer",
|
|
99
|
+
},
|
|
100
|
+
"why investigators": {
|
|
101
|
+
cardinality: "repeat",
|
|
102
|
+
group: "Why",
|
|
103
|
+
purpose: "Investigate each evidence source.",
|
|
104
|
+
legacyName: "why investigators",
|
|
105
|
+
},
|
|
106
|
+
"why synthesizer": {
|
|
107
|
+
cardinality: "single",
|
|
108
|
+
group: "Why",
|
|
109
|
+
purpose: "Reconcile historical evidence and uncertainty.",
|
|
110
|
+
legacyName: "why synthesizer",
|
|
111
|
+
},
|
|
112
|
+
"reflect judgment reviewer": {
|
|
113
|
+
cardinality: "single",
|
|
114
|
+
group: "Reflect",
|
|
115
|
+
purpose: "Review decisions made in the session.",
|
|
116
|
+
legacyName: "reflect judgment, divergent, synthesizer",
|
|
117
|
+
},
|
|
118
|
+
"reflect tooling reviewer": {
|
|
119
|
+
cardinality: "single",
|
|
120
|
+
group: "Reflect",
|
|
121
|
+
purpose: "Review tool choice and verification.",
|
|
122
|
+
legacyName: "reflect tooling",
|
|
123
|
+
},
|
|
124
|
+
"reflect divergent reviewer": {
|
|
125
|
+
cardinality: "single",
|
|
126
|
+
group: "Reflect",
|
|
127
|
+
purpose: "Challenge the session's shared assumptions.",
|
|
128
|
+
legacyName: "reflect judgment, divergent, synthesizer",
|
|
129
|
+
},
|
|
130
|
+
"reflect synthesizer": {
|
|
131
|
+
cardinality: "single",
|
|
132
|
+
group: "Reflect",
|
|
133
|
+
purpose: "Reconcile the three reflection reports.",
|
|
134
|
+
legacyName: "reflect judgment, divergent, synthesizer",
|
|
135
|
+
},
|
|
136
|
+
"arena runners": {
|
|
137
|
+
cardinality: "fanout",
|
|
138
|
+
group: "Arena",
|
|
139
|
+
purpose: "Produce one complete candidate per list entry.",
|
|
140
|
+
legacyName: "arena runners",
|
|
141
|
+
},
|
|
142
|
+
"arena judge pool": {
|
|
143
|
+
cardinality: "pick-one",
|
|
144
|
+
group: "Arena",
|
|
145
|
+
purpose: "Choose one model to judge all completed candidates.",
|
|
146
|
+
legacyName: "arena cross-judge pool",
|
|
147
|
+
},
|
|
148
|
+
"swarm workers": {
|
|
149
|
+
cardinality: "repeat",
|
|
150
|
+
group: "Swarm",
|
|
151
|
+
purpose: "Use one model across N bounded worker tasks.",
|
|
152
|
+
legacyName: "swarm workers",
|
|
153
|
+
},
|
|
154
|
+
"architect runners": {
|
|
155
|
+
cardinality: "fanout",
|
|
156
|
+
group: "Architect",
|
|
157
|
+
purpose: "Produce at least two distinct design candidates.",
|
|
158
|
+
legacyName: "architect runners",
|
|
159
|
+
},
|
|
160
|
+
"interrogate reviewers": {
|
|
161
|
+
cardinality: "fanout",
|
|
162
|
+
group: "Interrogate",
|
|
163
|
+
purpose: "Review the same artifact once per list entry.",
|
|
164
|
+
legacyName: "interrogate reviewers",
|
|
165
|
+
},
|
|
166
|
+
} as const satisfies Record<string, PstackRoleDefinition>;
|
|
167
|
+
|
|
168
|
+
/** Exact key for one atomic model-selection job. */
|
|
169
|
+
export type PstackRoleName = keyof typeof PSTACK_ROLES;
|
|
170
|
+
|
|
171
|
+
declare const PSTACK_MODEL_SELECTOR_BRAND: unique symbol;
|
|
172
|
+
|
|
173
|
+
/** Branded model selector. Constructed only after selector safety checks. */
|
|
174
|
+
export type PstackModelSelector = string & {
|
|
175
|
+
readonly [PSTACK_MODEL_SELECTOR_BRAND]: true;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** Canonical explicit inheritance value. auto normalizes to this. */
|
|
179
|
+
export const PSTACK_INHERIT_PARENT = "inherit-parent" as const;
|
|
180
|
+
|
|
181
|
+
type ModelList = readonly [PstackModelSelector, ...PstackModelSelector[]];
|
|
182
|
+
|
|
183
|
+
type ExplicitRoleSelection<R extends PstackRoleName> =
|
|
184
|
+
(typeof PSTACK_ROLES)[R]["cardinality"] extends "single" | "repeat"
|
|
185
|
+
? PstackModelSelector
|
|
186
|
+
: ModelList;
|
|
187
|
+
|
|
188
|
+
/** Valid normalized selection for a role's declared cardinality. */
|
|
189
|
+
export type PstackRoleSelection<R extends PstackRoleName> =
|
|
190
|
+
| typeof PSTACK_INHERIT_PARENT
|
|
191
|
+
| ExplicitRoleSelection<R>;
|
|
192
|
+
|
|
193
|
+
/** Normalized v2 role map. Omitted roles inherit the parent model. */
|
|
194
|
+
export type PstackRoleSelections = {
|
|
195
|
+
readonly [R in PstackRoleName]?: PstackRoleSelection<R>;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/** Normalized v2 pstack role configuration. */
|
|
199
|
+
export interface PstackRoleConfig {
|
|
200
|
+
readonly version: 2;
|
|
201
|
+
readonly roles: PstackRoleSelections;
|
|
202
|
+
readonly skillsEnabled: boolean;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Retired v1 role with no replacement. */
|
|
206
|
+
export const RETIRED_PSTACK_ROLE_NAME = "how critics";
|
|
207
|
+
|
|
208
|
+
/** True when value is a v2 pstack role name. */
|
|
209
|
+
export function isPstackRoleName(value: string): value is PstackRoleName {
|
|
210
|
+
return Object.hasOwn(PSTACK_ROLES, value);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** v2 pstack role names in registry order. */
|
|
214
|
+
export const PSTACK_ROLE_NAMES: readonly PstackRoleName[] =
|
|
215
|
+
Object.keys(PSTACK_ROLES).filter(isPstackRoleName);
|
|
216
|
+
|
|
217
|
+
/** Markdown role reference generated from the registry. */
|
|
218
|
+
export function formatPstackRoleReferenceMarkdown(): string {
|
|
219
|
+
const lines = [
|
|
220
|
+
"# Pstack model roles",
|
|
221
|
+
"",
|
|
222
|
+
"Generated from `extensions/pstack/pstack-roles.ts`. Edit the registry, not this file.",
|
|
223
|
+
"",
|
|
224
|
+
"Cardinality:",
|
|
225
|
+
"",
|
|
226
|
+
"- `single`: one job, one selector.",
|
|
227
|
+
"- `repeat`: one selector, reused for N children the workflow chooses.",
|
|
228
|
+
"- `fanout`: one child per list entry.",
|
|
229
|
+
"- `pick-one`: a candidate list, then one child.",
|
|
230
|
+
"",
|
|
231
|
+
"| Role | Cardinality | Purpose |",
|
|
232
|
+
"| --- | --- | --- |",
|
|
233
|
+
];
|
|
234
|
+
for (const name of PSTACK_ROLE_NAMES) {
|
|
235
|
+
const definition = PSTACK_ROLES[name];
|
|
236
|
+
lines.push(`| \`${name}\` | \`${definition.cardinality}\` | ${definition.purpose} |`);
|
|
237
|
+
}
|
|
238
|
+
lines.push("");
|
|
239
|
+
return lines.join("\n");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** v1 role name to v2 targets, derived from registry legacyName fields. */
|
|
243
|
+
export const PSTACK_LEGACY_ROLE_TARGETS: ReadonlyMap<string, readonly PstackRoleName[]> = (() => {
|
|
244
|
+
const map = new Map<string, PstackRoleName[]>();
|
|
245
|
+
for (const name of PSTACK_ROLE_NAMES) {
|
|
246
|
+
const legacyName = PSTACK_ROLES[name].legacyName;
|
|
247
|
+
const group = map.get(legacyName);
|
|
248
|
+
if (group) group.push(name);
|
|
249
|
+
else map.set(legacyName, [name]);
|
|
250
|
+
}
|
|
251
|
+
return map;
|
|
252
|
+
})();
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { isSafeModelSelector } from "./config.ts";
|
|
2
|
+
import {
|
|
3
|
+
PSTACK_INHERIT_PARENT,
|
|
4
|
+
PSTACK_ROLE_NAMES,
|
|
5
|
+
PSTACK_ROLES,
|
|
6
|
+
type PstackRoleCardinality,
|
|
7
|
+
type PstackModelSelector,
|
|
8
|
+
type PstackRoleConfig,
|
|
9
|
+
type PstackRoleGroup,
|
|
10
|
+
type PstackRoleName,
|
|
11
|
+
type PstackRoleSelections,
|
|
12
|
+
} from "./pstack-roles.ts";
|
|
13
|
+
|
|
14
|
+
/** Session model used to build setup selectors. thinkingLevel becomes a trailing suffix. */
|
|
15
|
+
export interface PstackScopedModelEntry {
|
|
16
|
+
readonly model: {
|
|
17
|
+
readonly provider: string;
|
|
18
|
+
readonly id: string;
|
|
19
|
+
};
|
|
20
|
+
readonly thinkingLevel?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** One registry-driven setup step. kind is scalar for single/repeat and list for fanout/pick-one. */
|
|
24
|
+
export interface PstackSetupRoleStep {
|
|
25
|
+
readonly role: PstackRoleName;
|
|
26
|
+
readonly cardinality: PstackRoleCardinality;
|
|
27
|
+
readonly purpose: string;
|
|
28
|
+
readonly group: PstackRoleGroup;
|
|
29
|
+
readonly kind: "scalar" | "list";
|
|
30
|
+
readonly current?: PstackModelSelector | readonly PstackModelSelector[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type PstackSetupChoice = typeof PSTACK_INHERIT_PARENT | PstackModelSelector;
|
|
34
|
+
type MutableSetupRoles = Record<string, PstackModelSelector | readonly PstackModelSelector[]>;
|
|
35
|
+
|
|
36
|
+
/** Ordered setup plan for every pstack role. */
|
|
37
|
+
export interface PstackSetupPlan {
|
|
38
|
+
readonly steps: readonly PstackSetupRoleStep[];
|
|
39
|
+
readonly choices: readonly PstackSetupChoice[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function checkedModelSelector(value: string): PstackModelSelector | undefined {
|
|
43
|
+
if (value === PSTACK_INHERIT_PARENT || value === "auto" || !isSafeModelSelector(value)) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
// SAFETY: PstackModelSelector is branded only after the shared selector validator accepts it.
|
|
47
|
+
return value as PstackModelSelector;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function uniqueSafeSelectors(selectors: readonly string[]): PstackModelSelector[] {
|
|
51
|
+
const out: PstackModelSelector[] = [];
|
|
52
|
+
const seen = new Set<string>();
|
|
53
|
+
for (const value of selectors) {
|
|
54
|
+
const selector = checkedModelSelector(value);
|
|
55
|
+
if (selector === undefined || seen.has(selector)) continue;
|
|
56
|
+
seen.add(selector);
|
|
57
|
+
out.push(selector);
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function scopedSelector(entry: PstackScopedModelEntry): string {
|
|
63
|
+
const base = `${entry.model.provider}/${entry.model.id}`;
|
|
64
|
+
return entry.thinkingLevel === undefined || entry.thinkingLevel === ""
|
|
65
|
+
? base
|
|
66
|
+
: `${base}:${entry.thinkingLevel}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Build setup selectors from scoped models, preserving thinkingLevel suffixes. */
|
|
70
|
+
export function pstackModelSelectorsFromSession(input: {
|
|
71
|
+
scopedModels?: readonly PstackScopedModelEntry[];
|
|
72
|
+
availableModels?: readonly { readonly provider: string; readonly id: string }[];
|
|
73
|
+
}): PstackModelSelector[] {
|
|
74
|
+
if (input.scopedModels !== undefined && input.scopedModels.length > 0) {
|
|
75
|
+
return uniqueSafeSelectors(input.scopedModels.map(scopedSelector));
|
|
76
|
+
}
|
|
77
|
+
return uniqueSafeSelectors(
|
|
78
|
+
(input.availableModels ?? []).map((model) => `${model.provider}/${model.id}`),
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function pstackSetupPickKind(cardinality: PstackRoleCardinality): "scalar" | "list" {
|
|
83
|
+
return cardinality === "fanout" || cardinality === "pick-one" ? "list" : "scalar";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function configuredSelectors(config: PstackRoleConfig): PstackModelSelector[] {
|
|
87
|
+
const values: string[] = [];
|
|
88
|
+
for (const name of PSTACK_ROLE_NAMES) {
|
|
89
|
+
const value = config.roles[name];
|
|
90
|
+
if (typeof value === "string") values.push(value);
|
|
91
|
+
else if (Array.isArray(value)) values.push(...value);
|
|
92
|
+
}
|
|
93
|
+
return uniqueSafeSelectors(values);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Build a registry-driven setup plan. Includes configured selectors even when unavailable. */
|
|
97
|
+
export function buildPstackSetupPlan(input: {
|
|
98
|
+
config: PstackRoleConfig;
|
|
99
|
+
sessionSelectors: readonly PstackModelSelector[];
|
|
100
|
+
}): PstackSetupPlan {
|
|
101
|
+
const extras = configuredSelectors(input.config).filter(
|
|
102
|
+
(selector) => !input.sessionSelectors.includes(selector),
|
|
103
|
+
);
|
|
104
|
+
const choices = [
|
|
105
|
+
PSTACK_INHERIT_PARENT,
|
|
106
|
+
...uniqueSafeSelectors(input.sessionSelectors),
|
|
107
|
+
...extras,
|
|
108
|
+
];
|
|
109
|
+
const steps: PstackSetupRoleStep[] = PSTACK_ROLE_NAMES.map((role) => {
|
|
110
|
+
const definition = PSTACK_ROLES[role];
|
|
111
|
+
const current = input.config.roles[role];
|
|
112
|
+
return {
|
|
113
|
+
role,
|
|
114
|
+
cardinality: definition.cardinality,
|
|
115
|
+
purpose: definition.purpose,
|
|
116
|
+
group: definition.group,
|
|
117
|
+
kind: pstackSetupPickKind(definition.cardinality),
|
|
118
|
+
...(current === undefined || current === PSTACK_INHERIT_PARENT ? {} : { current }),
|
|
119
|
+
};
|
|
120
|
+
});
|
|
121
|
+
return { steps, choices };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function stripCurrentMark(choice: string): string {
|
|
125
|
+
return choice.endsWith(" (current)") ? choice.slice(0, -" (current)".length) : choice;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function labeledChoices(
|
|
129
|
+
choices: readonly PstackSetupChoice[],
|
|
130
|
+
current: PstackModelSelector | readonly PstackModelSelector[] | undefined,
|
|
131
|
+
): string[] {
|
|
132
|
+
const currents = new Set(Array.isArray(current) ? current : current ? [current] : []);
|
|
133
|
+
return choices.map((choice) => (currents.has(choice) ? `${choice} (current)` : choice));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function rolePromptTitle(step: PstackSetupRoleStep): string {
|
|
137
|
+
return `${step.group}. ${step.role} [${step.cardinality}]. ${step.purpose}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function omitRole(roles: MutableSetupRoles, role: PstackRoleName): MutableSetupRoles {
|
|
141
|
+
if (!(role in roles)) return roles;
|
|
142
|
+
const next = { ...roles };
|
|
143
|
+
delete next[role];
|
|
144
|
+
return next;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function assignRole(
|
|
148
|
+
roles: MutableSetupRoles,
|
|
149
|
+
role: PstackRoleName,
|
|
150
|
+
value: PstackModelSelector | readonly PstackModelSelector[],
|
|
151
|
+
): MutableSetupRoles {
|
|
152
|
+
return { ...roles, [role]: value };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function asRoleSelections(roles: MutableSetupRoles): PstackRoleSelections {
|
|
156
|
+
// SAFETY: keys are registry role names and values passed selector safety checks.
|
|
157
|
+
return roles as PstackRoleSelections;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function mutableSetupRoles(config: PstackRoleConfig): MutableSetupRoles {
|
|
161
|
+
let roles: MutableSetupRoles = {};
|
|
162
|
+
for (const role of PSTACK_ROLE_NAMES) {
|
|
163
|
+
const value = config.roles[role];
|
|
164
|
+
if (value === undefined || value === PSTACK_INHERIT_PARENT) continue;
|
|
165
|
+
roles = assignRole(roles, role, value);
|
|
166
|
+
}
|
|
167
|
+
return roles;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** Collect setup choices into a new config. Cancel returns undefined and does not reuse the loaded object. */
|
|
171
|
+
export async function collectPstackSetupSelections(input: {
|
|
172
|
+
config: PstackRoleConfig;
|
|
173
|
+
plan: PstackSetupPlan;
|
|
174
|
+
select: (title: string, options: string[]) => Promise<string | undefined>;
|
|
175
|
+
}): Promise<PstackRoleConfig | undefined> {
|
|
176
|
+
let roles = mutableSetupRoles(input.config);
|
|
177
|
+
for (const step of input.plan.steps) {
|
|
178
|
+
if (step.kind === "scalar") {
|
|
179
|
+
const choice = await input.select(
|
|
180
|
+
rolePromptTitle(step),
|
|
181
|
+
labeledChoices(input.plan.choices, step.current),
|
|
182
|
+
);
|
|
183
|
+
if (choice === undefined) return undefined;
|
|
184
|
+
const value = checkedModelSelector(stripCurrentMark(choice));
|
|
185
|
+
roles =
|
|
186
|
+
value === undefined ? omitRole(roles, step.role) : assignRole(roles, step.role, value);
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const first = await input.select(
|
|
191
|
+
rolePromptTitle(step),
|
|
192
|
+
labeledChoices(input.plan.choices, step.current),
|
|
193
|
+
);
|
|
194
|
+
if (first === undefined) return undefined;
|
|
195
|
+
const selected = checkedModelSelector(stripCurrentMark(first));
|
|
196
|
+
if (selected === undefined) {
|
|
197
|
+
roles = omitRole(roles, step.role);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const picked: PstackModelSelector[] = [selected];
|
|
202
|
+
const addChoices = [
|
|
203
|
+
"done",
|
|
204
|
+
...input.plan.choices.filter((choice) => choice !== PSTACK_INHERIT_PARENT),
|
|
205
|
+
];
|
|
206
|
+
while (true) {
|
|
207
|
+
const next = await input.select(
|
|
208
|
+
`Add another model for ${step.role}? [${step.cardinality}]`,
|
|
209
|
+
addChoices,
|
|
210
|
+
);
|
|
211
|
+
if (next === undefined) return undefined;
|
|
212
|
+
if (next === "done") break;
|
|
213
|
+
const value = checkedModelSelector(stripCurrentMark(next));
|
|
214
|
+
if (value !== undefined) picked.push(value);
|
|
215
|
+
}
|
|
216
|
+
roles = assignRole(roles, step.role, picked);
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
version: 2,
|
|
220
|
+
roles: asRoleSelections(roles),
|
|
221
|
+
skillsEnabled: input.config.skillsEnabled,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface StripResult {
|
|
2
|
+
prompt: string;
|
|
3
|
+
removed: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const OPEN_TAG = "\n<available_skills>";
|
|
7
|
+
const CLOSE_TAG = "</available_skills>";
|
|
8
|
+
const ENTRY_DELIMITER = " <skill>";
|
|
9
|
+
const ENTRY_SPLIT = /(?= <skill>)/;
|
|
10
|
+
const LOCATION_PATTERN = /<location>([\s\S]*?)<\/location>/;
|
|
11
|
+
const HEADER_MARKER = "\n\nThe following skills provide specialized instructions";
|
|
12
|
+
|
|
13
|
+
function decodeXmlEntities(value: string): string {
|
|
14
|
+
return value
|
|
15
|
+
.replaceAll("<", "<")
|
|
16
|
+
.replaceAll(">", ">")
|
|
17
|
+
.replaceAll(""", '"')
|
|
18
|
+
.replaceAll("'", "'")
|
|
19
|
+
.replaceAll("&", "&");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function stripSkillsByLocationPrefix(systemPrompt: string, locationPrefix: string): StripResult {
|
|
23
|
+
const prefix = locationPrefix.trim().replace(/\/$/, "");
|
|
24
|
+
if (!prefix) return { prompt: systemPrompt, removed: 0 };
|
|
25
|
+
|
|
26
|
+
const openStart = systemPrompt.indexOf(OPEN_TAG);
|
|
27
|
+
if (openStart === -1) return { prompt: systemPrompt, removed: 0 };
|
|
28
|
+
const contentStart = openStart + OPEN_TAG.length;
|
|
29
|
+
const closeStart = systemPrompt.indexOf(CLOSE_TAG, contentStart);
|
|
30
|
+
if (closeStart === -1) return { prompt: systemPrompt, removed: 0 };
|
|
31
|
+
|
|
32
|
+
const chunks = systemPrompt.slice(contentStart, closeStart).split(ENTRY_SPLIT);
|
|
33
|
+
const kept: string[] = [];
|
|
34
|
+
let removed = 0;
|
|
35
|
+
for (const chunk of chunks) {
|
|
36
|
+
if (!chunk.startsWith(ENTRY_DELIMITER)) {
|
|
37
|
+
kept.push(chunk);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const rawLocation = chunk.match(LOCATION_PATTERN)?.[1] ?? "";
|
|
41
|
+
if (decodeXmlEntities(rawLocation).startsWith(`${prefix}/`)) {
|
|
42
|
+
removed++;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
kept.push(chunk);
|
|
46
|
+
}
|
|
47
|
+
if (removed === 0) return { prompt: systemPrompt, removed: 0 };
|
|
48
|
+
|
|
49
|
+
const anyKeptEntry = kept.some((chunk) => chunk.startsWith(ENTRY_DELIMITER));
|
|
50
|
+
if (!anyKeptEntry) {
|
|
51
|
+
const headerStart = systemPrompt.slice(0, openStart).indexOf(HEADER_MARKER);
|
|
52
|
+
const cutStart = headerStart !== -1 ? headerStart : openStart;
|
|
53
|
+
return {
|
|
54
|
+
prompt: systemPrompt.slice(0, cutStart) + systemPrompt.slice(closeStart + CLOSE_TAG.length),
|
|
55
|
+
removed,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
prompt: systemPrompt.slice(0, contentStart) + kept.join("") + systemPrompt.slice(closeStart),
|
|
60
|
+
removed,
|
|
61
|
+
};
|
|
62
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@whamp/pi-pstack",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "pstack for Pi: rigorous agent workflows you can parallelize with confidence - poteto-mode playbooks, engineering principles, multi-model review panels, and subagents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Will Hampson <whamp@ggl.slmail.me> (https://github.com/Whamp)",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Whamp/pi-extensions.git",
|
|
11
|
+
"directory": "packages/pi-pstack"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Whamp/pi-extensions/issues"
|
|
15
|
+
},
|
|
16
|
+
"homepage": "https://github.com/Whamp/pi-extensions/tree/master/packages/pi-pstack#readme",
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"pi-package",
|
|
22
|
+
"pi-extension",
|
|
23
|
+
"skills",
|
|
24
|
+
"workflow",
|
|
25
|
+
"principles",
|
|
26
|
+
"poteto-mode",
|
|
27
|
+
"subagents",
|
|
28
|
+
"unslop"
|
|
29
|
+
],
|
|
30
|
+
"files": [
|
|
31
|
+
"extensions/",
|
|
32
|
+
"!extensions/**/*.test.ts",
|
|
33
|
+
"skills/",
|
|
34
|
+
"agents/",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"typebox": "1.3.7"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
43
|
+
},
|
|
44
|
+
"pi": {
|
|
45
|
+
"extensions": [
|
|
46
|
+
"./extensions/pstack/index.ts"
|
|
47
|
+
],
|
|
48
|
+
"skills": [
|
|
49
|
+
"./skills"
|
|
50
|
+
],
|
|
51
|
+
"subagents": {
|
|
52
|
+
"agents": [
|
|
53
|
+
"./agents"
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"test": "node --experimental-strip-types --test extensions/pstack/*.test.ts skills/poteto-mode/scripts/check-plan.test.mjs scripts/reground-from-cursor.test.mjs"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architect
|
|
3
|
+
description: "Sketch types, signatures, and module structure before code, then stay in the loop while implementation fills in. Use for /skill:architect, 'architect this', 'design this', or non-trivial work where jumping to code would lock in the wrong shape."
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Architect
|
|
8
|
+
|
|
9
|
+
Design before implementing. Sketch types, function signatures, class shapes, and module boundaries with `not implemented` bodies and pseudocode. Synthesize across multiple model perspectives, then fill in code against the chosen sketch. If implementation proves the sketch wrong, throw it out and redesign.
|
|
10
|
+
|
|
11
|
+
## Start
|
|
12
|
+
|
|
13
|
+
Open a todolist with one entry per phase before starting.
|
|
14
|
+
|
|
15
|
+
1. Ground
|
|
16
|
+
2. Sketch
|
|
17
|
+
3. Agree
|
|
18
|
+
4. Implement
|
|
19
|
+
5. Scrap
|
|
20
|
+
|
|
21
|
+
## Phase A: Ground the problem
|
|
22
|
+
|
|
23
|
+
Build a real mental model of every system the new code touches. Run the **how** skill over the relevant subsystems.
|
|
24
|
+
|
|
25
|
+
Naming a file isn't grounding. Produce the traced model `how` prescribes. If the design redefines ownership or layering, also run the **why** skill on the existing shape so the rationale becomes a constraint, not a guess.
|
|
26
|
+
|
|
27
|
+
Skip Phase A only when the work is genuinely greenfield with no surrounding system to integrate.
|
|
28
|
+
|
|
29
|
+
## Phase B: Sketch
|
|
30
|
+
|
|
31
|
+
Run the **arena** skill with the design-sketch task and the Phase A grounding artifacts. Pass `references/runner-prompt.md` as each runner's prompt. Each candidate produces a design package shaped per `references/rationale-template.md`.
|
|
32
|
+
|
|
33
|
+
Override Arena's candidate selector with `architect runners` (defaults inherit-parent). Require at least two candidates. The Arena judge still uses `arena judge pool`.
|
|
34
|
+
|
|
35
|
+
Design it twice. Require at least two structurally distinct candidates before synthesis, even when the first looks sufficient. This is the **exhaust-the-design-space** principle skill made concrete. Whole-shape alternatives, not point fixes inside one shape.
|
|
36
|
+
|
|
37
|
+
Screen every candidate against [`references/design-red-flags.md`](references/design-red-flags.md) before synthesis. Reject or revise shallow modules, information leakage, temporal decomposition, and pass-through methods.
|
|
38
|
+
|
|
39
|
+
Compare viable candidates on interface depth. Prefer the design that hides more complexity behind a smaller, simpler public surface. A rich interface can keep call chains short by concentrating capability instead of scattering it across layers.
|
|
40
|
+
|
|
41
|
+
Arena returns one synthesized design package. The synthesis decision populates the rationale's "Synthesis decision" section.
|
|
42
|
+
|
|
43
|
+
## Phase C: Agree (opt-in)
|
|
44
|
+
|
|
45
|
+
Default: proceed directly to implementation with the synthesized design. No human checkpoint.
|
|
46
|
+
|
|
47
|
+
Opt in to a checkpoint when the invoker explicitly asks: "/skill:architect with checkpoint," "stop and show me before implementing," or similar. Then surface the synthesized design and pause for sign-off.
|
|
48
|
+
|
|
49
|
+
The synthesis can ship as its own commit either way, as the "scaffold first" mode of the **foundational-thinking** principle skill. Planned and scoped breakage during fill-in is fine, per the **outcome-oriented-execution** principle skill. For adversarial pressure on the design before implementing, run the **interrogate** skill on the synthesized sketch.
|
|
50
|
+
|
|
51
|
+
If the human pushes back on the shape (in a checkpoint or after the fact), treat that as Phase A evidence. Re-ground and re-run Phase B before writing more code.
|
|
52
|
+
|
|
53
|
+
## Phase D: Implement against the sketch
|
|
54
|
+
|
|
55
|
+
Replace `not implemented` bodies with code, pseudocode with logic. The synthesized sketch is the contract.
|
|
56
|
+
|
|
57
|
+
Deviations from the sketch are signal worth surfacing, not friction to absorb silently. If a function needs a parameter the sketch didn't anticipate, ask whether the sketch was wrong, the requirement was missed, or the implementation is overreaching.
|
|
58
|
+
|
|
59
|
+
## Phase E: Scrap when the architecture is wrong
|
|
60
|
+
|
|
61
|
+
If implementation keeps producing friction the sketch can't absorb, throw the sketch out. Don't bolt fixes onto a wrong design, per the **redesign-from-first-principles** and **fix-root-causes** principle skills.
|
|
62
|
+
|
|
63
|
+
The signal is a *pattern*, not single instances. Tells:
|
|
64
|
+
|
|
65
|
+
- The same shape of workaround appearing repeatedly across unrelated code.
|
|
66
|
+
- Multiple unrelated edge cases that all need special-case branches.
|
|
67
|
+
- Types that need escape hatches (`any`, casts, optional fields always set in practice) to compile.
|
|
68
|
+
- The "we need a lock" reflex when the sketch said the state wasn't shared.
|
|
69
|
+
- Callers having to know the abstraction's internal rules to use it.
|
|
70
|
+
- Two or more independent Phase D deviations of the same shape across the implementation.
|
|
71
|
+
|
|
72
|
+
Use judgment. A few edge cases don't condemn an architecture. Some problems are legitimately complex. Complexity in the data is not complexity in the design.
|
|
73
|
+
|
|
74
|
+
When you scrap:
|
|
75
|
+
|
|
76
|
+
1. Re-run the **how** skill over what's been built.
|
|
77
|
+
2. Redesign as if the new constraints had been day-one assumptions, per redesign-from-first-principles.
|
|
78
|
+
3. Subtract before adding, per the **subtract-before-you-add** principle skill. The new sketch should be smaller than the old one before it grows.
|
|
79
|
+
4. Return to Phase B and re-run arena.
|
|
80
|
+
|
|
81
|
+
## Outputs
|
|
82
|
+
|
|
83
|
+
The caller's usage is written first and the type sketch derived from it. One file with new types and signatures for small changes. Module map plus type definitions for larger work. The rationale ships alongside, shaped per `references/rationale-template.md`, including the usage sketch and the synthesis decision.
|