@temet/cli 0.2.0 → 0.3.1

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 (38) hide show
  1. package/dist/audit.d.ts +34 -0
  2. package/dist/audit.js +531 -0
  3. package/dist/index.js +85 -40
  4. package/dist/lib/analysis-types.d.ts +7 -0
  5. package/dist/lib/analysis-types.js +1 -0
  6. package/dist/lib/audit-tracking.d.ts +55 -0
  7. package/dist/lib/audit-tracking.js +185 -0
  8. package/dist/lib/cli-args.d.ts +22 -0
  9. package/dist/lib/cli-args.js +65 -0
  10. package/dist/lib/editorial-taxonomy.d.ts +17 -0
  11. package/dist/lib/editorial-taxonomy.js +91 -0
  12. package/dist/lib/heuristics.d.ts +15 -0
  13. package/dist/lib/heuristics.js +341 -0
  14. package/dist/lib/hook-installer.d.ts +13 -0
  15. package/dist/lib/hook-installer.js +130 -0
  16. package/dist/lib/narrator-lite.d.ts +25 -0
  17. package/dist/lib/narrator-lite.js +231 -0
  18. package/dist/lib/notifier.d.ts +9 -0
  19. package/dist/lib/notifier.js +57 -0
  20. package/dist/lib/path-resolver.d.ts +39 -0
  21. package/dist/lib/path-resolver.js +152 -0
  22. package/dist/lib/profile-report.d.ts +18 -0
  23. package/dist/lib/profile-report.js +148 -0
  24. package/dist/lib/report-writer.d.ts +7 -0
  25. package/dist/lib/report-writer.js +73 -0
  26. package/dist/lib/session-audit.d.ts +24 -0
  27. package/dist/lib/session-audit.js +94 -0
  28. package/dist/lib/session-parser.d.ts +35 -0
  29. package/dist/lib/session-parser.js +130 -0
  30. package/dist/lib/skill-mapper.d.ts +3 -0
  31. package/dist/lib/skill-mapper.js +173 -0
  32. package/dist/lib/skill-naming.d.ts +1 -0
  33. package/dist/lib/skill-naming.js +50 -0
  34. package/dist/lib/types.d.ts +17 -0
  35. package/dist/lib/types.js +2 -0
  36. package/dist/lib/workflow-detector.d.ts +11 -0
  37. package/dist/lib/workflow-detector.js +125 -0
  38. package/package.json +2 -2
@@ -0,0 +1,73 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdir, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { buildSkillAuditReport } from "./profile-report.js";
7
+ const execFileAsync = promisify(execFile);
8
+ const TEMET_WORDMARK = [
9
+ "████████╗███████╗███╗ ███╗███████╗████████╗",
10
+ "╚══██╔══╝██╔════╝████╗ ████║██╔════╝╚══██╔══╝",
11
+ " ██║ █████╗ ██╔████╔██║█████╗ ██║ ",
12
+ " ██║ ██╔══╝ ██║╚██╔╝██║██╔══╝ ██║ ",
13
+ " ██║ ███████╗██║ ╚═╝ ██║███████╗ ██║ ",
14
+ " ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ",
15
+ ];
16
+ function divider(char = "─", width = 66) {
17
+ return char.repeat(width);
18
+ }
19
+ function section(title, body) {
20
+ return [title, divider(), "", ...body, ""];
21
+ }
22
+ export function buildAuditTextReport(result, competencies, bilan, tracking) {
23
+ const lines = [];
24
+ const profile = buildSkillAuditReport(result, competencies, tracking);
25
+ lines.push(...TEMET_WORDMARK, "", "By Temet · Encode human intelligence", "temet-skills-audit", "", profile.title, "", profile.subtitle, "");
26
+ lines.push(...section(`YOUR ${profile.tacitSkills.length} TACIT SKILLS`, profile.tacitSkills.flatMap((entry, index) => {
27
+ const header = entry.tagline
28
+ ? `${index + 1}. ${entry.title} — ${entry.tagline}`
29
+ : `${index + 1}. ${entry.title}`;
30
+ return [header, "", entry.body, ""];
31
+ })));
32
+ if (profile.meaningfulChanges.length > 0) {
33
+ lines.push(...section("WHAT CHANGED SINCE THE LAST AUDIT", profile.meaningfulChanges));
34
+ }
35
+ lines.push(...section("YOUR 3 BLIND SPOTS", profile.blindSpots.flatMap((entry, index) => [
36
+ `${index + 1}. ${entry.title}`,
37
+ "",
38
+ entry.body,
39
+ "",
40
+ ])));
41
+ lines.push(...section("YOUR PROFESSIONAL DNA", [
42
+ bilan?.trim() || profile.professionalDna,
43
+ ]));
44
+ lines.push(...section("THE META-SKILL THAT TIES IT ALL TOGETHER", [
45
+ profile.metaSkill.title,
46
+ "",
47
+ profile.metaSkill.body,
48
+ ]));
49
+ lines.push(divider(), "", "Thanks for reading.", "Arnaud");
50
+ return `${lines.join("\n").trim()}\n`;
51
+ }
52
+ export function buildReportPath(projectLabel, now = new Date()) {
53
+ const date = now.toISOString().replace(/:/g, "-");
54
+ const safeLabel = projectLabel.replace(/[^a-zA-Z0-9._-]+/g, "-");
55
+ return path.join(homedir(), ".temet", "reports", safeLabel, `temet-skills-audit-${date}.txt`);
56
+ }
57
+ export async function saveAuditTextReport(projectLabel, content, now = new Date()) {
58
+ const filePath = buildReportPath(projectLabel, now);
59
+ await mkdir(path.dirname(filePath), { recursive: true });
60
+ await writeFile(filePath, content, "utf8");
61
+ return filePath;
62
+ }
63
+ export async function openReportFile(filePath, platform = process.platform) {
64
+ if (platform === "darwin") {
65
+ await execFileAsync("open", ["-a", "TextEdit", filePath]);
66
+ return;
67
+ }
68
+ if (platform === "win32") {
69
+ await execFileAsync("notepad", [filePath]);
70
+ return;
71
+ }
72
+ await execFileAsync("xdg-open", [filePath]);
73
+ }
@@ -0,0 +1,24 @@
1
+ import type { CombinedStats } from "./analysis-types.js";
2
+ import { type Signal } from "./heuristics.js";
3
+ import { type DetectedWorkflow } from "./workflow-detector.js";
4
+ import type { CompetencyEntry } from "./types.js";
5
+ export type AuditResult = {
6
+ sessionCount: number;
7
+ messageCount: number;
8
+ promptCount: number;
9
+ toolCallCount: number;
10
+ signalCount: number;
11
+ workflowCount: number;
12
+ competencies: CompetencyEntry[];
13
+ workflows: DetectedWorkflow[];
14
+ signals: Signal[];
15
+ combined: CombinedStats;
16
+ };
17
+ export declare function findSessionFiles(dirPath: string): string[];
18
+ export declare function runAudit(sessionFiles: string[], onProgress?: (event: {
19
+ phase: "scan" | "signals" | "patterns";
20
+ file?: string;
21
+ current?: number;
22
+ total?: number;
23
+ elapsedMs?: number;
24
+ }) => void): Promise<AuditResult>;
@@ -0,0 +1,94 @@
1
+ // Adapted from scripts/lib/session-audit.ts — keep in sync
2
+ import { readdirSync, statSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { extractAllSignals } from "./heuristics.js";
5
+ import { collectSession } from "./session-parser.js";
6
+ import { mapSignalsToCompetencies } from "./skill-mapper.js";
7
+ import { detectWorkflows } from "./workflow-detector.js";
8
+ // ---------- Session Discovery ----------
9
+ export function findSessionFiles(dirPath) {
10
+ const resolved = path.resolve(dirPath);
11
+ const files = [];
12
+ function walk(dir) {
13
+ let entries;
14
+ try {
15
+ entries = readdirSync(dir);
16
+ }
17
+ catch {
18
+ return;
19
+ }
20
+ for (const entry of entries) {
21
+ const full = path.join(dir, entry);
22
+ try {
23
+ const st = statSync(full);
24
+ if (st.isDirectory()) {
25
+ walk(full);
26
+ }
27
+ else if (st.isFile() && entry.endsWith(".jsonl")) {
28
+ files.push(full);
29
+ }
30
+ }
31
+ catch {
32
+ // skip inaccessible entries
33
+ }
34
+ }
35
+ }
36
+ walk(resolved);
37
+ return files.sort();
38
+ }
39
+ // ---------- Pipeline ----------
40
+ export async function runAudit(sessionFiles, onProgress) {
41
+ const allSignals = [];
42
+ const combined = {
43
+ messages: [],
44
+ toolCalls: [],
45
+ filesTouched: [],
46
+ allToolCallsBySession: [],
47
+ };
48
+ const fileSet = new Set();
49
+ let signalMs = 0;
50
+ for (const file of sessionFiles) {
51
+ onProgress?.({
52
+ phase: "scan",
53
+ file: path.basename(file),
54
+ });
55
+ const stats = await collectSession(file);
56
+ combined.messages.push(...stats.messages);
57
+ combined.toolCalls.push(...stats.toolCalls);
58
+ combined.allToolCallsBySession.push(stats.toolCalls);
59
+ for (const fp of stats.filesTouched)
60
+ fileSet.add(fp);
61
+ const signalStartedAt = Date.now();
62
+ allSignals.push(...extractAllSignals(stats));
63
+ signalMs += Date.now() - signalStartedAt;
64
+ }
65
+ combined.filesTouched = [...fileSet];
66
+ onProgress?.({
67
+ phase: "signals",
68
+ current: allSignals.length,
69
+ total: allSignals.length,
70
+ elapsedMs: signalMs,
71
+ });
72
+ const workflowStartedAt = Date.now();
73
+ const workflows = detectWorkflows(combined.allToolCallsBySession);
74
+ onProgress?.({
75
+ phase: "patterns",
76
+ current: workflows.length,
77
+ total: workflows.length,
78
+ elapsedMs: Date.now() - workflowStartedAt,
79
+ });
80
+ const competencies = mapSignalsToCompetencies(allSignals);
81
+ const promptCount = combined.messages.filter((message) => message.role === "user").length;
82
+ return {
83
+ sessionCount: sessionFiles.length,
84
+ messageCount: combined.messages.length,
85
+ promptCount,
86
+ toolCallCount: combined.toolCalls.length,
87
+ signalCount: allSignals.length,
88
+ workflowCount: workflows.length,
89
+ competencies,
90
+ workflows,
91
+ signals: allSignals,
92
+ combined,
93
+ };
94
+ }
@@ -0,0 +1,35 @@
1
+ export type ContentBlock = {
2
+ type: "text";
3
+ text: string;
4
+ } | {
5
+ type: "tool_use";
6
+ id: string;
7
+ name: string;
8
+ input: unknown;
9
+ } | {
10
+ type: "thinking";
11
+ thinking: string;
12
+ } | {
13
+ type: "tool_result";
14
+ tool_use_id: string;
15
+ content: unknown;
16
+ };
17
+ export type SessionMessage = {
18
+ uuid: string;
19
+ timestamp: string;
20
+ role: "user" | "assistant";
21
+ content: ContentBlock[];
22
+ model?: string;
23
+ };
24
+ export type ToolCall = {
25
+ id: string;
26
+ name: string;
27
+ input: unknown;
28
+ };
29
+ export type SessionStats = {
30
+ messages: SessionMessage[];
31
+ toolCalls: ToolCall[];
32
+ filesTouched: string[];
33
+ };
34
+ export declare function parseSession(filePath: string): AsyncGenerator<SessionMessage>;
35
+ export declare function collectSession(filePath: string): Promise<SessionStats>;
@@ -0,0 +1,130 @@
1
+ // Adapted from scripts/lib/session-parser.ts — keep in sync
2
+ import { createReadStream } from "node:fs";
3
+ import { createInterface } from "node:readline";
4
+ // ---------- Helpers ----------
5
+ function normalizeContent(raw) {
6
+ if (typeof raw === "string") {
7
+ return [{ type: "text", text: raw }];
8
+ }
9
+ if (!Array.isArray(raw))
10
+ return [];
11
+ return raw
12
+ .map((block) => {
13
+ if (typeof block === "string") {
14
+ return { type: "text", text: block };
15
+ }
16
+ if (!block || typeof block !== "object")
17
+ return null;
18
+ const b = block;
19
+ if (b.type === "text" && typeof b.text === "string") {
20
+ return { type: "text", text: b.text };
21
+ }
22
+ if (b.type === "tool_use" && typeof b.name === "string") {
23
+ return {
24
+ type: "tool_use",
25
+ id: b.id ?? "",
26
+ name: b.name,
27
+ input: b.input,
28
+ };
29
+ }
30
+ if (b.type === "thinking" && typeof b.thinking === "string") {
31
+ return { type: "thinking", thinking: b.thinking };
32
+ }
33
+ if (b.type === "tool_result") {
34
+ return {
35
+ type: "tool_result",
36
+ tool_use_id: b.tool_use_id ?? "",
37
+ content: b.content,
38
+ };
39
+ }
40
+ return null;
41
+ })
42
+ .filter((b) => b !== null);
43
+ }
44
+ function extractFilePathsFromToolInput(input) {
45
+ if (!input || typeof input !== "object")
46
+ return [];
47
+ const paths = [];
48
+ const inp = input;
49
+ for (const key of ["file_path", "path", "filePath", "pattern"]) {
50
+ if (typeof inp[key] === "string" && inp[key]) {
51
+ const val = inp[key];
52
+ if (val.startsWith("/") || val.includes("/")) {
53
+ paths.push(val);
54
+ }
55
+ }
56
+ }
57
+ if (typeof inp.command === "string") {
58
+ const cmd = inp.command;
59
+ const pathMatches = cmd.match(/(?:^|\s)(\/[^\s;|&>]+)/g);
60
+ if (pathMatches) {
61
+ for (const m of pathMatches) {
62
+ paths.push(m.trim());
63
+ }
64
+ }
65
+ }
66
+ return paths;
67
+ }
68
+ // ---------- Parser ----------
69
+ export async function* parseSession(filePath) {
70
+ const stream = createReadStream(filePath, { encoding: "utf-8" });
71
+ const rl = createInterface({
72
+ input: stream,
73
+ crlfDelay: Number.POSITIVE_INFINITY,
74
+ });
75
+ for await (const line of rl) {
76
+ if (!line.trim())
77
+ continue;
78
+ let entry;
79
+ try {
80
+ entry = JSON.parse(line);
81
+ }
82
+ catch {
83
+ continue;
84
+ }
85
+ const entryType = entry.type;
86
+ if (entryType !== "user" && entryType !== "assistant")
87
+ continue;
88
+ const message = entry.message;
89
+ if (!message || typeof message !== "object")
90
+ continue;
91
+ const role = message.role;
92
+ if (role !== "user" && role !== "assistant")
93
+ continue;
94
+ const content = normalizeContent(message.content);
95
+ if (content.length === 0)
96
+ continue;
97
+ yield {
98
+ uuid: entry.uuid ?? "",
99
+ timestamp: entry.timestamp ?? "",
100
+ role,
101
+ content,
102
+ model: role === "assistant" ? message.model : undefined,
103
+ };
104
+ }
105
+ }
106
+ export async function collectSession(filePath) {
107
+ const messages = [];
108
+ const toolCalls = [];
109
+ const fileSet = new Set();
110
+ for await (const msg of parseSession(filePath)) {
111
+ messages.push(msg);
112
+ for (const block of msg.content) {
113
+ if (block.type === "tool_use") {
114
+ toolCalls.push({
115
+ id: block.id,
116
+ name: block.name,
117
+ input: block.input,
118
+ });
119
+ for (const fp of extractFilePathsFromToolInput(block.input)) {
120
+ fileSet.add(fp);
121
+ }
122
+ }
123
+ }
124
+ }
125
+ return {
126
+ messages,
127
+ toolCalls,
128
+ filesTouched: [...fileSet],
129
+ };
130
+ }
@@ -0,0 +1,3 @@
1
+ import type { CompetencyEntry } from "./types.js";
2
+ import type { Signal } from "./heuristics.js";
3
+ export declare function mapSignalsToCompetencies(signals: Signal[]): CompetencyEntry[];
@@ -0,0 +1,173 @@
1
+ // Adapted from scripts/lib/skill-mapper.ts — keep in sync
2
+ import { createHash } from "node:crypto";
3
+ import { toDisplaySkillName } from "./skill-naming.js";
4
+ // ---------- Normalization ----------
5
+ function normalizeSkillName(name) {
6
+ return name.toLowerCase().replace(/\s+/g, " ").trim();
7
+ }
8
+ function makeId(name) {
9
+ const hash = createHash("sha256").update(name).digest("hex").slice(0, 8);
10
+ const slug = normalizeSkillName(name)
11
+ .replace(/[^a-z0-9]+/g, "-")
12
+ .replace(/-+$/, "");
13
+ return `session-${slug}-${hash}`;
14
+ }
15
+ // ---------- Proficiency Mapping ----------
16
+ function computeProficiency(signals) {
17
+ if (signals.length === 0)
18
+ return "novice";
19
+ // Average confidence weighted by signal count
20
+ const avgConfidence = signals.reduce((sum, s) => sum + s.confidence, 0) / signals.length;
21
+ // More signals from different extractors = more evidence
22
+ const uniqueTypes = new Set(signals.map((s) => s.type)).size;
23
+ const diversityBonus = Math.min(uniqueTypes * 0.1, 0.3);
24
+ const score = avgConfidence + diversityBonus;
25
+ if (score >= 0.9)
26
+ return "expert";
27
+ if (score >= 0.75)
28
+ return "proficient";
29
+ if (score >= 0.55)
30
+ return "competent";
31
+ if (score >= 0.35)
32
+ return "advanced_beginner";
33
+ return "novice";
34
+ }
35
+ function resolveCategory(signals) {
36
+ // Take the most common category from signals
37
+ const counts = new Map();
38
+ for (const s of signals) {
39
+ if (s.category) {
40
+ counts.set(s.category, (counts.get(s.category) ?? 0) + 1);
41
+ }
42
+ }
43
+ let best = "hard_skill";
44
+ let bestCount = 0;
45
+ for (const [cat, count] of counts) {
46
+ if (count > bestCount) {
47
+ best = cat;
48
+ bestCount = count;
49
+ }
50
+ }
51
+ return best;
52
+ }
53
+ // ---------- Evidence Builder ----------
54
+ function buildEvidence(signals) {
55
+ const examples = [];
56
+ const decisionCriteria = [];
57
+ const antiPatterns = [];
58
+ const mentorAdvice = [];
59
+ for (const s of signals) {
60
+ switch (s.type) {
61
+ case "correction":
62
+ antiPatterns.push(s.evidence);
63
+ break;
64
+ case "decision_language":
65
+ if (s.evidence.startsWith("Anti-pattern")) {
66
+ antiPatterns.push(s.evidence);
67
+ }
68
+ else {
69
+ decisionCriteria.push(s.evidence);
70
+ }
71
+ break;
72
+ case "tool_frequency":
73
+ case "domain_cluster":
74
+ examples.push(s.evidence);
75
+ break;
76
+ case "workflow":
77
+ examples.push(s.evidence);
78
+ break;
79
+ case "prompt_structure":
80
+ mentorAdvice.push(s.evidence);
81
+ break;
82
+ default:
83
+ examples.push(s.evidence);
84
+ }
85
+ }
86
+ return { examples, decisionCriteria, antiPatterns, mentorAdvice };
87
+ }
88
+ function buildDescription(name, signals) {
89
+ const types = [...new Set(signals.map((s) => s.type))];
90
+ const sources = types
91
+ .map((t) => {
92
+ switch (t) {
93
+ case "correction":
94
+ return "user corrections";
95
+ case "tool_frequency":
96
+ return "tool usage patterns";
97
+ case "decision_language":
98
+ return "stated preferences";
99
+ case "domain_cluster":
100
+ return "domain focus";
101
+ case "prompt_structure":
102
+ return "prompt patterns";
103
+ default:
104
+ return t;
105
+ }
106
+ })
107
+ .join(", ");
108
+ return `Detected from ${signals.length} signal(s) via ${sources}. Skill: ${name}.`;
109
+ }
110
+ // ---------- Synergy Detection ----------
111
+ function detectSynergies(groups, current) {
112
+ const synergies = [];
113
+ const currentFiles = new Set(current.signals
114
+ .filter((s) => s.type === "domain_cluster")
115
+ .map((s) => s.skill));
116
+ const currentTools = new Set(current.signals
117
+ .filter((s) => s.type === "tool_frequency")
118
+ .map((s) => s.skill));
119
+ for (const other of groups) {
120
+ if (other.normalizedName === current.normalizedName)
121
+ continue;
122
+ // Skills in the same domain or using the same tools likely synergize
123
+ const otherDomains = other.signals
124
+ .filter((s) => s.type === "domain_cluster")
125
+ .map((s) => s.skill);
126
+ const otherTools = other.signals
127
+ .filter((s) => s.type === "tool_frequency")
128
+ .map((s) => s.skill);
129
+ const domainOverlap = otherDomains.some((d) => currentFiles.has(d));
130
+ const toolOverlap = otherTools.some((t) => currentTools.has(t));
131
+ if (domainOverlap || toolOverlap) {
132
+ synergies.push(other.normalizedName);
133
+ }
134
+ }
135
+ return synergies.slice(0, 5);
136
+ }
137
+ // ---------- Main Mapper ----------
138
+ export function mapSignalsToCompetencies(signals) {
139
+ // Group by normalized skill name
140
+ const groupMap = new Map();
141
+ for (const s of signals) {
142
+ const displayName = toDisplaySkillName(s.skill);
143
+ const key = normalizeSkillName(displayName);
144
+ const group = groupMap.get(key);
145
+ if (group) {
146
+ group.push(s);
147
+ }
148
+ else {
149
+ groupMap.set(key, [s]);
150
+ }
151
+ }
152
+ const groups = [...groupMap.entries()].map(([normalizedName, sigs]) => ({
153
+ normalizedName,
154
+ displayName: toDisplaySkillName(sigs[0]?.skill ?? normalizedName),
155
+ signals: sigs,
156
+ }));
157
+ return groups.map((group) => {
158
+ const proficiencyLevel = computeProficiency(group.signals);
159
+ const category = resolveCategory(group.signals);
160
+ const evidence = buildEvidence(group.signals);
161
+ const synergies = detectSynergies(groups, group);
162
+ return {
163
+ id: makeId(group.displayName),
164
+ name: group.displayName,
165
+ category,
166
+ proficiencyLevel,
167
+ description: buildDescription(group.displayName, group.signals),
168
+ evidence,
169
+ prerequisites: [],
170
+ synergies,
171
+ };
172
+ });
173
+ }
@@ -0,0 +1 @@
1
+ export declare function toDisplaySkillName(name: string): string;
@@ -0,0 +1,50 @@
1
+ const DISPLAY_NAME_MAP = {
2
+ "agent-to-agent protocol": "Agent-to-agent protocol design",
3
+ "ai/llm integration": "AI product and model integration",
4
+ "api design": "API boundary and contract design",
5
+ "api integration": "External API exploration and integration",
6
+ "build tooling and automation": "Build tooling and automation design",
7
+ "chat application architecture": "Chat product architecture",
8
+ "ci/cd pipeline design": "CI/CD pipeline design",
9
+ "cloudflare workers development": "Cloudflare Workers delivery",
10
+ "code archaeology": "Codebase archaeology across large repositories",
11
+ "code comprehension": "Deep code comprehension before change",
12
+ "code editing precision": "Precise refactoring across complex dependencies",
13
+ "code generation": "Structured code generation from specs",
14
+ "code review judgment": "Code review judgment under ambiguity",
15
+ "code style standards": "Code style and consistency standards",
16
+ "competency modeling": "Competency modeling and skill structuring",
17
+ "component architecture": "Component architecture and UI structure",
18
+ "content strategy": "Content strategy and editorial structuring",
19
+ "database design": "Database schema and query design",
20
+ "engineering judgment": "Engineering judgment under ambiguity",
21
+ "error handling strategy": "Error handling and failure-path strategy",
22
+ "generative ui systems": "Generative UI system design",
23
+ "performance optimization": "Performance optimization and responsiveness",
24
+ "rapid iteration workflow": "Rapid iteration under tight feedback loops",
25
+ "react component development": "System-aware React component development",
26
+ "research methodology": "Focused research and source triage",
27
+ "security awareness": "Security-aware implementation review",
28
+ "security practices": "Security-conscious engineering decisions",
29
+ "shell scripting": "Command-line execution and shell troubleshooting",
30
+ "software architecture": "Software architecture and system boundaries",
31
+ "specification methodology": "Spec-driven implementation planning",
32
+ "task delegation": "Delegating subtasks to AI agents",
33
+ "testing methodology": "Test-driven delivery discipline",
34
+ "type system design": "Type system design and schema rigor",
35
+ "ui/ux design sense": "UI judgment and interaction clarity",
36
+ "version control judgment": "Version control judgment in risky changes",
37
+ "version control mastery": "Version control execution in active worktrees",
38
+ "version control workflow": "Version control workflow hygiene",
39
+ };
40
+ function titleCase(raw) {
41
+ return raw
42
+ .split(/\s+/)
43
+ .filter(Boolean)
44
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
45
+ .join(" ");
46
+ }
47
+ export function toDisplaySkillName(name) {
48
+ const normalized = name.toLowerCase().replace(/\s+/g, " ").trim();
49
+ return DISPLAY_NAME_MAP[normalized] ?? titleCase(normalized);
50
+ }
@@ -0,0 +1,17 @@
1
+ export type ProficiencyLevel = "novice" | "advanced_beginner" | "competent" | "proficient" | "expert";
2
+ export type CompetencyCategory = "hard_skill" | "soft_skill" | "domain_knowledge" | "tool_proficiency" | "methodology" | "judgment";
3
+ export type CompetencyEntry = {
4
+ id: string;
5
+ name: string;
6
+ category: CompetencyCategory;
7
+ proficiencyLevel: ProficiencyLevel;
8
+ description: string;
9
+ evidence: {
10
+ examples: string[];
11
+ decisionCriteria: string[];
12
+ antiPatterns: string[];
13
+ mentorAdvice: string[];
14
+ };
15
+ prerequisites: string[];
16
+ synergies: string[];
17
+ };
@@ -0,0 +1,2 @@
1
+ // Inlined from lib/competency/taxonomy.ts — keep in sync
2
+ export {};
@@ -0,0 +1,11 @@
1
+ import type { Signal } from "./heuristics.js";
2
+ import type { ToolCall } from "./session-parser.js";
3
+ export type DetectedWorkflow = {
4
+ sequence: string[];
5
+ occurrences: number;
6
+ sessions: number;
7
+ confidence: number;
8
+ description: string;
9
+ };
10
+ export declare function detectWorkflows(allToolCalls: ToolCall[][]): DetectedWorkflow[];
11
+ export declare function workflowsToSignals(workflows: DetectedWorkflow[]): Signal[];