Skill Tracer
Skills › gstack › gstack › archetypes.ts

archetypes.ts

186 lines · scripts/archetypes.ts at 3f79d81

1/**
2 * Archetypes — one-word builder identities computed from dimension clusters.
3 *
4 * Used by future /plan-tune vibe and /plan-tune narrative commands (v2).
5 * v1 ships the definitions but doesn't wire them into user-facing output
6 * yet. This file exists so the archetype model is stable by the time v2
7 * narrative generation ships.
8 *
9 * Design
10 * ------
11 * Each archetype is a point or region in the 5-dimensional psychographic
12 * space. `distance()` computes L2 distance from a profile to the archetype
13 * center, scaled by the archetype's "tightness" (how close you have to be
14 * to match). The archetype with smallest distance is the user's match.
15 *
16 * When no archetype is within threshold, return 'Polymath' — a calibrated
17 * "doesn't fit the common patterns" label that's respectful rather than
18 * generic.
19 */
21import type { Dimension } from './psychographic-signals';
23export interface Archetype {
24 /** Short vibe label — one or two words. */
25 name: string;
26 /** One-line description anchored in observable behavior. */
27 description: string;
28 /** Center point in the 5-dimensional space. */
29 center: Record<Dimension, number>;
30 /** Inverse-weighted radius. Smaller = tighter match needed. */
31 tightness: number;
34export const ARCHETYPES: readonly Archetype[] = [
35 {
36 name: 'Cathedral Builder',
37 description: 'Boil the ocean. Architecture first. Ship the complete thing.',
38 center: {
39 scope_appetite: 0.85,
40 risk_tolerance: 0.55,
41 detail_preference: 0.5,
42 autonomy: 0.5,
43 architecture_care: 0.85,
44 },
45 tightness: 1.0,
46 },
47 {
48 name: 'Ship-It Pragmatist',
49 description: 'Small scope, fast iteration. Good enough is done.',
50 center: {
51 scope_appetite: 0.25,
52 risk_tolerance: 0.75,
53 detail_preference: 0.3,
54 autonomy: 0.65,
55 architecture_care: 0.4,
56 },
57 tightness: 1.0,
58 },
59 {
60 name: 'Deep Craft',
61 description: 'Every detail matters. Verbose explanations. Slow and considered.',
62 center: {
63 scope_appetite: 0.6,
64 risk_tolerance: 0.35,
65 detail_preference: 0.85,
66 autonomy: 0.35,
67 architecture_care: 0.85,
68 },
69 tightness: 1.0,
70 },
71 {
72 name: 'Taste Maker',
73 description: 'Decisions feel intuitive. Overrides recommendations when taste dictates.',
74 center: {
75 scope_appetite: 0.6,
76 risk_tolerance: 0.6,
77 detail_preference: 0.5,
78 autonomy: 0.4,
79 architecture_care: 0.7,
80 },
81 tightness: 0.9,
82 },
83 {
84 name: 'Solo Operator',
85 description: 'High autonomy. Delegate to the agent. Trust but verify.',
86 center: {
87 scope_appetite: 0.5,
88 risk_tolerance: 0.7,
89 detail_preference: 0.3,
90 autonomy: 0.85,
91 architecture_care: 0.55,
92 },
93 tightness: 0.9,
94 },
95 {
96 name: 'Consultant',
97 description: 'Hands-on. Wants to be consulted on everything. Verifies each step.',
98 center: {
99 scope_appetite: 0.5,
100 risk_tolerance: 0.3,
101 detail_preference: 0.7,
102 autonomy: 0.2,
103 architecture_care: 0.65,
104 },
105 tightness: 0.9,
106 },
107 {
108 name: 'Wedge Hunter',
109 description: 'Narrow scope aggressively. Find the smallest thing worth building.',
110 center: {
111 scope_appetite: 0.15,
112 risk_tolerance: 0.5,
113 detail_preference: 0.4,
114 autonomy: 0.55,
115 architecture_care: 0.6,
116 },
117 tightness: 0.85,
118 },
119 {
120 name: 'Builder-Coach',
121 description: 'Balanced steering. Makes room for the agent to propose and challenge.',
122 center: {
123 scope_appetite: 0.55,
124 risk_tolerance: 0.5,
125 detail_preference: 0.55,
126 autonomy: 0.55,
127 architecture_care: 0.6,
128 },
129 tightness: 0.75,
130 },
131];
133/**
134 * Fallback used when no archetype is close enough — meaning the user's
135 * dimension cluster genuinely doesn't match any named pattern.
136 */
137export const FALLBACK_ARCHETYPE: Archetype = {
138 name: 'Polymath',
139 description: "Your steering style doesn't fit a common archetype. That's a compliment.",
140 center: { scope_appetite: 0.5, risk_tolerance: 0.5, detail_preference: 0.5, autonomy: 0.5, architecture_care: 0.5 },
141 tightness: 0,
142};
144const DIMENSIONS: readonly Dimension[] = [
145 'scope_appetite',
146 'risk_tolerance',
147 'detail_preference',
148 'autonomy',
149 'architecture_care',
150] as const;
152function euclidean(a: Record<Dimension, number>, b: Record<Dimension, number>): number {
153 let sumSq = 0;
154 for (const d of DIMENSIONS) {
155 const diff = (a[d] ?? 0.5) - (b[d] ?? 0.5);
156 sumSq += diff * diff;
157 }
158 return Math.sqrt(sumSq);
161/**
162 * Match a profile to its best archetype.
163 * Returns FALLBACK_ARCHETYPE if no defined archetype is within threshold.
164 */
165export function matchArchetype(dims: Record<Dimension, number>): Archetype {
166 let best: Archetype = FALLBACK_ARCHETYPE;
167 let bestScore = Infinity; // lower is better
168 // Threshold: if no archetype scores below this, return Polymath.
169 // Max possible distance in [0,1]^5 is sqrt(5) ≈ 2.236. 0.55 = ~half the space.
170 const THRESHOLD = 0.55;
171 for (const arch of ARCHETYPES) {
172 const dist = euclidean(dims, arch.center);
173 // Scale by tightness — tighter archetypes require smaller actual distance.
174 const scaled = dist / (arch.tightness || 1);
175 if (scaled < bestScore && scaled <= THRESHOLD) {
176 bestScore = scaled;
177 best = arch;
178 }
179 }
180 return best;
183/** All archetype names, useful for tests and /plan-tune stats. */
184export function getAllArchetypeNames(): string[] {
185 return ARCHETYPES.map((a) => a.name).concat(FALLBACK_ARCHETYPE.name);