@temet/cli 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/macos/README.md +8 -0
- package/assets/macos/Temet.app.zip +0 -0
- package/dist/audit.d.ts +3 -1
- package/dist/audit.js +214 -26
- package/dist/index.js +60 -12
- package/dist/lib/audit-tracking.js +13 -2
- package/dist/lib/cli-args.d.ts +1 -0
- package/dist/lib/cli-args.js +13 -3
- package/dist/lib/diagnostic-runner.d.ts +13 -0
- package/dist/lib/diagnostic-runner.js +95 -0
- package/dist/lib/editorial-taxonomy.d.ts +17 -0
- package/dist/lib/editorial-taxonomy.js +91 -0
- package/dist/lib/menubar-installer.d.ts +19 -0
- package/dist/lib/menubar-installer.js +99 -0
- package/dist/lib/menubar-state.d.ts +78 -0
- package/dist/lib/menubar-state.js +275 -0
- package/dist/lib/notifier.d.ts +2 -0
- package/dist/lib/notifier.js +11 -0
- package/dist/lib/path-resolver.d.ts +22 -1
- package/dist/lib/path-resolver.js +120 -24
- package/dist/lib/profile-report.d.ts +18 -0
- package/dist/lib/profile-report.js +148 -0
- package/dist/lib/report-writer.d.ts +9 -0
- package/dist/lib/report-writer.js +91 -0
- package/dist/lib/session-audit.d.ts +1 -0
- package/dist/lib/session-audit.js +2 -0
- package/dist/plan.d.ts +33 -0
- package/dist/plan.js +90 -0
- package/dist/traces.d.ts +41 -0
- package/dist/traces.js +119 -0
- package/package.json +3 -2
package/dist/lib/notifier.d.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { AuditChange } from "./audit-tracking.js";
|
|
2
|
+
import type { MenubarProgress } from "./menubar-state.js";
|
|
2
3
|
export type NotificationPayload = {
|
|
3
4
|
title: string;
|
|
4
5
|
body: string;
|
|
5
6
|
};
|
|
6
7
|
/** Build the notification text from audit changes. Returns null if nothing to notify. */
|
|
7
8
|
export declare function formatNotification(changes: AuditChange[], projectLabel: string): NotificationPayload | null;
|
|
9
|
+
export declare function formatProgressNotification(progress: MenubarProgress | null | undefined): NotificationPayload | null;
|
|
8
10
|
/** Send a native OS notification. Fire-and-forget. */
|
|
9
11
|
export declare function sendNotification(payload: NotificationPayload): Promise<void>;
|
package/dist/lib/notifier.js
CHANGED
|
@@ -23,6 +23,17 @@ export function formatNotification(changes, projectLabel) {
|
|
|
23
23
|
: `${first.title} and ${meaningful.length - 1} more`;
|
|
24
24
|
return { title: "Temet", body };
|
|
25
25
|
}
|
|
26
|
+
export function formatProgressNotification(progress) {
|
|
27
|
+
if (!progress)
|
|
28
|
+
return null;
|
|
29
|
+
const body = progress.notificationBody?.trim();
|
|
30
|
+
if (!body)
|
|
31
|
+
return null;
|
|
32
|
+
return {
|
|
33
|
+
title: progress.notificationTitle || "Temet update",
|
|
34
|
+
body,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
26
37
|
/** Send a native OS notification. Fire-and-forget. */
|
|
27
38
|
export async function sendNotification(payload) {
|
|
28
39
|
const { title, body } = payload;
|
|
@@ -1,5 +1,24 @@
|
|
|
1
|
+
export type SessionCandidate = {
|
|
2
|
+
sessionDir: string;
|
|
3
|
+
label: string;
|
|
4
|
+
updatedAtMs: number;
|
|
5
|
+
};
|
|
6
|
+
export type SessionResolution = {
|
|
7
|
+
path: string | null;
|
|
8
|
+
source: "explicit" | "env" | "stdin" | "cwd" | "recent" | "none";
|
|
9
|
+
candidates: SessionCandidate[];
|
|
10
|
+
};
|
|
11
|
+
type ResolveSessionPathOptions = {
|
|
12
|
+
cwd?: string;
|
|
13
|
+
claudeRoot?: string;
|
|
14
|
+
hookInput?: {
|
|
15
|
+
cwd?: string;
|
|
16
|
+
} | null;
|
|
17
|
+
};
|
|
1
18
|
/** Convert an absolute project directory to the Claude Code session directory slug. */
|
|
2
19
|
export declare function cwdToSessionDir(cwd: string): string;
|
|
20
|
+
export declare function humanizeSessionDir(sessionDir: string): string;
|
|
21
|
+
export declare function findClaudeProjectCandidates(claudeRoot?: string): SessionCandidate[];
|
|
3
22
|
/**
|
|
4
23
|
* Try to read the JSON stdin from a Claude Code hook (non-blocking).
|
|
5
24
|
* Only safe to call when we know stdin has finite piped data (hook context).
|
|
@@ -14,5 +33,7 @@ export declare function readHookStdin(): {
|
|
|
14
33
|
* 2. $CLAUDE_PROJECT_DIR env var
|
|
15
34
|
* 3. stdin JSON cwd (hook context)
|
|
16
35
|
* 4. process.cwd()
|
|
36
|
+
* 5. most recent valid Claude project
|
|
17
37
|
*/
|
|
18
|
-
export declare function resolveSessionPath(explicit: string | undefined, env: NodeJS.ProcessEnv):
|
|
38
|
+
export declare function resolveSessionPath(explicit: string | undefined, env: NodeJS.ProcessEnv, options?: ResolveSessionPathOptions): SessionResolution;
|
|
39
|
+
export {};
|
|
@@ -1,10 +1,75 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
const DEFAULT_CLAUDE_ROOT = path.join(homedir(), ".claude", "projects");
|
|
4
5
|
/** Convert an absolute project directory to the Claude Code session directory slug. */
|
|
5
6
|
export function cwdToSessionDir(cwd) {
|
|
6
7
|
const slug = path.resolve(cwd).replace(/\//g, "-");
|
|
7
|
-
return path.join(
|
|
8
|
+
return path.join(DEFAULT_CLAUDE_ROOT, slug);
|
|
9
|
+
}
|
|
10
|
+
export function humanizeSessionDir(sessionDir) {
|
|
11
|
+
const slug = path.basename(sessionDir);
|
|
12
|
+
if (!slug)
|
|
13
|
+
return sessionDir;
|
|
14
|
+
if (slug.startsWith("-")) {
|
|
15
|
+
return slug.replace(/-/g, "/");
|
|
16
|
+
}
|
|
17
|
+
return slug;
|
|
18
|
+
}
|
|
19
|
+
function hasJsonlSessions(dir) {
|
|
20
|
+
try {
|
|
21
|
+
for (const entry of readdirSync(dir)) {
|
|
22
|
+
const full = path.join(dir, entry);
|
|
23
|
+
const stat = statSync(full);
|
|
24
|
+
if (stat.isDirectory()) {
|
|
25
|
+
if (hasJsonlSessions(full))
|
|
26
|
+
return true;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (stat.isFile() && entry.endsWith(".jsonl"))
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
function candidateFromSessionDir(sessionDir) {
|
|
39
|
+
if (!existsSync(sessionDir) || !hasJsonlSessions(sessionDir))
|
|
40
|
+
return null;
|
|
41
|
+
let updatedAtMs = 0;
|
|
42
|
+
try {
|
|
43
|
+
updatedAtMs = statSync(sessionDir).mtimeMs;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
updatedAtMs = 0;
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
sessionDir,
|
|
50
|
+
label: humanizeSessionDir(sessionDir),
|
|
51
|
+
updatedAtMs,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export function findClaudeProjectCandidates(claudeRoot = DEFAULT_CLAUDE_ROOT) {
|
|
55
|
+
try {
|
|
56
|
+
return readdirSync(claudeRoot)
|
|
57
|
+
.map((entry) => path.join(claudeRoot, entry))
|
|
58
|
+
.filter((full) => {
|
|
59
|
+
try {
|
|
60
|
+
return statSync(full).isDirectory();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
.map(candidateFromSessionDir)
|
|
67
|
+
.filter((candidate) => candidate !== null)
|
|
68
|
+
.sort((a, b) => b.updatedAtMs - a.updatedAtMs);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
8
73
|
}
|
|
9
74
|
/**
|
|
10
75
|
* Try to read the JSON stdin from a Claude Code hook (non-blocking).
|
|
@@ -13,8 +78,6 @@ export function cwdToSessionDir(cwd) {
|
|
|
13
78
|
*/
|
|
14
79
|
export function readHookStdin() {
|
|
15
80
|
try {
|
|
16
|
-
// Only attempt if stdin is piped (non-TTY) AND we're in a hook context.
|
|
17
|
-
// CLAUDECODE env var is set when running inside Claude Code.
|
|
18
81
|
if (process.stdin.isTTY)
|
|
19
82
|
return null;
|
|
20
83
|
if (!process.env.CLAUDECODE)
|
|
@@ -32,25 +95,58 @@ export function readHookStdin() {
|
|
|
32
95
|
* 2. $CLAUDE_PROJECT_DIR env var
|
|
33
96
|
* 3. stdin JSON cwd (hook context)
|
|
34
97
|
* 4. process.cwd()
|
|
98
|
+
* 5. most recent valid Claude project
|
|
35
99
|
*/
|
|
36
|
-
export function resolveSessionPath(explicit, env) {
|
|
37
|
-
if (explicit)
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
100
|
+
export function resolveSessionPath(explicit, env, options = {}) {
|
|
101
|
+
if (explicit) {
|
|
102
|
+
return {
|
|
103
|
+
path: explicit,
|
|
104
|
+
source: "explicit",
|
|
105
|
+
candidates: [],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const cwd = options.cwd ?? process.cwd();
|
|
109
|
+
const claudeRoot = options.claudeRoot ?? DEFAULT_CLAUDE_ROOT;
|
|
110
|
+
const fromEnv = env.CLAUDE_PROJECT_DIR
|
|
111
|
+
? candidateFromSessionDir(path.join(claudeRoot, path.resolve(env.CLAUDE_PROJECT_DIR).replace(/\//g, "-")))
|
|
112
|
+
: null;
|
|
113
|
+
if (fromEnv) {
|
|
114
|
+
return {
|
|
115
|
+
path: fromEnv.sessionDir,
|
|
116
|
+
source: "env",
|
|
117
|
+
candidates: [fromEnv],
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const hookInput = options.hookInput ?? readHookStdin();
|
|
121
|
+
const fromStdin = hookInput?.cwd
|
|
122
|
+
? candidateFromSessionDir(path.join(claudeRoot, path.resolve(hookInput.cwd).replace(/\//g, "-")))
|
|
123
|
+
: null;
|
|
124
|
+
if (fromStdin) {
|
|
125
|
+
return {
|
|
126
|
+
path: fromStdin.sessionDir,
|
|
127
|
+
source: "stdin",
|
|
128
|
+
candidates: [fromStdin],
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
const fromCwd = candidateFromSessionDir(path.join(claudeRoot, path.resolve(cwd).replace(/\//g, "-")));
|
|
132
|
+
if (fromCwd) {
|
|
133
|
+
return {
|
|
134
|
+
path: fromCwd.sessionDir,
|
|
135
|
+
source: "cwd",
|
|
136
|
+
candidates: [fromCwd],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const candidates = findClaudeProjectCandidates(claudeRoot);
|
|
140
|
+
if (candidates.length > 0) {
|
|
141
|
+
return {
|
|
142
|
+
path: candidates[0].sessionDir,
|
|
143
|
+
source: "recent",
|
|
144
|
+
candidates,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
path: null,
|
|
149
|
+
source: "none",
|
|
150
|
+
candidates: [],
|
|
151
|
+
};
|
|
56
152
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TrackingResult } from "./audit-tracking.js";
|
|
2
|
+
import type { AuditResult } from "./session-audit.js";
|
|
3
|
+
import type { CompetencyEntry } from "./types.js";
|
|
4
|
+
export type NarrativeSection = {
|
|
5
|
+
title: string;
|
|
6
|
+
tagline?: string;
|
|
7
|
+
body: string;
|
|
8
|
+
};
|
|
9
|
+
export type SkillAuditReport = {
|
|
10
|
+
title: string;
|
|
11
|
+
subtitle: string;
|
|
12
|
+
tacitSkills: NarrativeSection[];
|
|
13
|
+
blindSpots: NarrativeSection[];
|
|
14
|
+
professionalDna: string;
|
|
15
|
+
metaSkill: NarrativeSection;
|
|
16
|
+
meaningfulChanges: string[];
|
|
17
|
+
};
|
|
18
|
+
export declare function buildSkillAuditReport(result: Pick<AuditResult, "sessionCount" | "promptCount" | "toolCallCount" | "workflowCount" | "signals">, competencies: CompetencyEntry[], tracking?: Pick<TrackingResult, "changes">): SkillAuditReport;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { extractEditorialEvidence, fallbackEditorialSummary, isCommoditySkillName, matchEditorialSkill, } from "./editorial-taxonomy.js";
|
|
2
|
+
const PROFICIENCY_SCORE = {
|
|
3
|
+
expert: 5,
|
|
4
|
+
proficient: 4,
|
|
5
|
+
competent: 3,
|
|
6
|
+
advanced_beginner: 2,
|
|
7
|
+
novice: 1,
|
|
8
|
+
};
|
|
9
|
+
function scoreCompetency(entry) {
|
|
10
|
+
const evidenceCount = entry.evidence.examples.length +
|
|
11
|
+
entry.evidence.decisionCriteria.length +
|
|
12
|
+
entry.evidence.antiPatterns.length +
|
|
13
|
+
entry.evidence.mentorAdvice.length;
|
|
14
|
+
return PROFICIENCY_SCORE[entry.proficiencyLevel] * 10 + evidenceCount;
|
|
15
|
+
}
|
|
16
|
+
function pickTacitSkills(competencies) {
|
|
17
|
+
const sorted = [...competencies].sort((a, b) => scoreCompetency(b) - scoreCompetency(a));
|
|
18
|
+
const usedTitles = new Set();
|
|
19
|
+
const sections = [];
|
|
20
|
+
const presetMatches = sorted.filter((entry) => matchEditorialSkill(entry));
|
|
21
|
+
const fallbackCandidates = sorted.filter((entry) => !matchEditorialSkill(entry) && !isCommoditySkillName(entry.name));
|
|
22
|
+
for (const entry of presetMatches) {
|
|
23
|
+
const preset = matchEditorialSkill(entry);
|
|
24
|
+
const title = preset?.title ?? entry.name;
|
|
25
|
+
if (usedTitles.has(title))
|
|
26
|
+
continue;
|
|
27
|
+
usedTitles.add(title);
|
|
28
|
+
const evidence = extractEditorialEvidence(entry);
|
|
29
|
+
sections.push({
|
|
30
|
+
title,
|
|
31
|
+
tagline: preset?.tagline,
|
|
32
|
+
body: preset
|
|
33
|
+
? preset.render(entry, evidence)
|
|
34
|
+
: fallbackEditorialSummary(entry),
|
|
35
|
+
});
|
|
36
|
+
if (sections.length >= 10)
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
if (sections.length < 6) {
|
|
40
|
+
for (const entry of fallbackCandidates) {
|
|
41
|
+
const preset = matchEditorialSkill(entry);
|
|
42
|
+
const title = preset?.title ?? entry.name;
|
|
43
|
+
if (usedTitles.has(title))
|
|
44
|
+
continue;
|
|
45
|
+
usedTitles.add(title);
|
|
46
|
+
const evidence = extractEditorialEvidence(entry);
|
|
47
|
+
sections.push({
|
|
48
|
+
title,
|
|
49
|
+
tagline: preset?.tagline,
|
|
50
|
+
body: preset
|
|
51
|
+
? preset.render(entry, evidence)
|
|
52
|
+
: fallbackEditorialSummary(entry),
|
|
53
|
+
});
|
|
54
|
+
if (sections.length >= 8)
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return sections;
|
|
59
|
+
}
|
|
60
|
+
function hasSkill(competencies, pattern) {
|
|
61
|
+
return competencies.some((entry) => pattern.test(entry.name.toLowerCase()));
|
|
62
|
+
}
|
|
63
|
+
function buildBlindSpots(result, competencies) {
|
|
64
|
+
const blindSpots = [];
|
|
65
|
+
if (result.workflowCount >= 80) {
|
|
66
|
+
blindSpots.push({
|
|
67
|
+
title: "Feature sprawl",
|
|
68
|
+
body: "You can generate momentum faster than you stabilize it. When the number of patterns and directions keeps climbing, the risk is not lack of output. It is losing the discipline to decide what deserves to become a finished path.",
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (hasSkill(competencies, /version control/) ||
|
|
72
|
+
result.signals.some((signal) => /version control/i.test(signal.skill))) {
|
|
73
|
+
blindSpots.push({
|
|
74
|
+
title: "Git hygiene under pressure",
|
|
75
|
+
body: "Your work suggests strong version-control awareness, but also the kind of environment where branch, commit, and release pressure can still create avoidable mess. This is usually a systems problem, not a knowledge problem.",
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (hasSkill(competencies, /research|architecture|code comprehension|refactoring/)) {
|
|
79
|
+
blindSpots.push({
|
|
80
|
+
title: "Technical detours",
|
|
81
|
+
body: "You are capable of going deep, which is a strength until exploration keeps running after product value has flattened. The risk is not bad engineering. It is staying fascinated one loop too long.",
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const fallback = [
|
|
85
|
+
{
|
|
86
|
+
title: "Feature sprawl",
|
|
87
|
+
body: "You move quickly once a direction looks promising. The risk is not inactivity. It is letting the number of live possibilities grow faster than the number of finished outcomes.",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
title: "Coordination drag",
|
|
91
|
+
body: "As your systems become more capable, the hidden cost shifts toward alignment, naming, and decision clarity. Work can stay technically correct while still becoming harder to coordinate.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
title: "Technical detours",
|
|
95
|
+
body: "Deep exploration is one of your strengths, but it can keep producing value long after the product question should have ended the loop. Good curiosity still needs a stopping rule.",
|
|
96
|
+
},
|
|
97
|
+
];
|
|
98
|
+
for (const item of fallback) {
|
|
99
|
+
if (blindSpots.some((existing) => existing.title === item.title))
|
|
100
|
+
continue;
|
|
101
|
+
blindSpots.push(item);
|
|
102
|
+
if (blindSpots.length >= 3)
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
return blindSpots.slice(0, 3);
|
|
106
|
+
}
|
|
107
|
+
function buildProfessionalDna(tacitSkills, competencies) {
|
|
108
|
+
const anchors = tacitSkills.slice(0, 3).map((skill) => skill.title);
|
|
109
|
+
const stacks = competencies
|
|
110
|
+
.filter((entry) => /cloudflare|react|api|type system|component|security|shell/i.test(entry.name))
|
|
111
|
+
.slice(0, 4)
|
|
112
|
+
.map((entry) => entry.name);
|
|
113
|
+
const stackLine = stacks.length > 0
|
|
114
|
+
? `The technical signal clusters around ${stacks.join(", ")}.`
|
|
115
|
+
: "The technical signal clusters around architecture, implementation judgment, and repeated execution patterns.";
|
|
116
|
+
return `This profile points to someone who turns ambiguity into execution. The strongest signals are ${anchors.join(", ")}, which together describe a person who reads systems carefully, imposes structure early, and keeps moving through short feedback loops instead of relying on one big heroic pass. ${stackLine} The distinctive advantage is not one isolated skill, but the ability to convert messy work into something legible, directional, and reusable.`;
|
|
117
|
+
}
|
|
118
|
+
function buildMetaSkill(tacitSkills) {
|
|
119
|
+
const titles = tacitSkills.map((skill) => skill.title.toLowerCase());
|
|
120
|
+
if (titles.some((title) => title.includes("plan-first")) &&
|
|
121
|
+
titles.some((title) => title.includes("codebase archaeology"))) {
|
|
122
|
+
return {
|
|
123
|
+
title: "Turning ambiguity into executable systems",
|
|
124
|
+
body: "Across the report, the deepest pattern is not just building or reviewing. It is repeatedly taking vague, shifting work and turning it into a system that can be understood, executed, and improved. That is the skill underneath the other skills.",
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
title: "Building feedback loops around real work",
|
|
129
|
+
body: "The common thread is not any one tool or framework. It is the habit of turning real work into a loop: observe, judge, change, test, and run again. That is what makes the rest of the profile coherent.",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function buildMeaningfulChanges(tracking) {
|
|
133
|
+
if (!tracking || tracking.changes.length === 0)
|
|
134
|
+
return [];
|
|
135
|
+
return tracking.changes.map((change) => `${change.title} — ${change.detail}`);
|
|
136
|
+
}
|
|
137
|
+
export function buildSkillAuditReport(result, competencies, tracking) {
|
|
138
|
+
const tacitSkills = pickTacitSkills(competencies);
|
|
139
|
+
return {
|
|
140
|
+
title: `TACIT SKILL EXTRACTION — ${result.promptCount.toLocaleString("en-US")} prompts analyzed`,
|
|
141
|
+
subtitle: `${result.sessionCount.toLocaleString("en-US")} sessions · ${result.toolCallCount.toLocaleString("en-US")} tool calls · ${result.workflowCount.toLocaleString("en-US")} workflow patterns. This is a deterministic Temet reading of the skills, habits, and judgment patterns that show up repeatedly in your real AI work.`,
|
|
142
|
+
tacitSkills,
|
|
143
|
+
blindSpots: buildBlindSpots(result, competencies),
|
|
144
|
+
professionalDna: buildProfessionalDna(tacitSkills, competencies),
|
|
145
|
+
metaSkill: buildMetaSkill(tacitSkills),
|
|
146
|
+
meaningfulChanges: buildMeaningfulChanges(tracking),
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AuditResult } from "./session-audit.js";
|
|
2
|
+
import type { TrackingResult } from "./audit-tracking.js";
|
|
3
|
+
import type { CompetencyEntry } from "./types.js";
|
|
4
|
+
export declare function buildAuditTextReport(result: Pick<AuditResult, "sessionCount" | "promptCount" | "toolCallCount" | "signals" | "workflowCount" | "workflows">, competencies: CompetencyEntry[], bilan?: string, tracking?: Pick<TrackingResult, "changes" | "current">): string;
|
|
5
|
+
export declare function buildReportPath(projectLabel: string, now?: Date): string;
|
|
6
|
+
export declare function getReportDirectory(projectLabel: string): string;
|
|
7
|
+
export declare function findLatestReportPath(projectLabel: string): Promise<string | null>;
|
|
8
|
+
export declare function saveAuditTextReport(projectLabel: string, content: string, now?: Date): Promise<string>;
|
|
9
|
+
export declare function openReportFile(filePath: string, platform?: NodeJS.Platform): Promise<void>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { mkdir, readdir, 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 function getReportDirectory(projectLabel) {
|
|
58
|
+
const safeLabel = projectLabel.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
59
|
+
return path.join(homedir(), ".temet", "reports", safeLabel);
|
|
60
|
+
}
|
|
61
|
+
export async function findLatestReportPath(projectLabel) {
|
|
62
|
+
const dir = getReportDirectory(projectLabel);
|
|
63
|
+
try {
|
|
64
|
+
const entries = await readdir(dir);
|
|
65
|
+
const latest = entries
|
|
66
|
+
.filter((entry) => entry.endsWith(".txt"))
|
|
67
|
+
.sort()
|
|
68
|
+
.at(-1);
|
|
69
|
+
return latest ? path.join(dir, latest) : null;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export async function saveAuditTextReport(projectLabel, content, now = new Date()) {
|
|
76
|
+
const filePath = buildReportPath(projectLabel, now);
|
|
77
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
78
|
+
await writeFile(filePath, content, "utf8");
|
|
79
|
+
return filePath;
|
|
80
|
+
}
|
|
81
|
+
export async function openReportFile(filePath, platform = process.platform) {
|
|
82
|
+
if (platform === "darwin") {
|
|
83
|
+
await execFileAsync("open", ["-a", "TextEdit", filePath]);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (platform === "win32") {
|
|
87
|
+
await execFileAsync("notepad", [filePath]);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
await execFileAsync("xdg-open", [filePath]);
|
|
91
|
+
}
|
|
@@ -78,9 +78,11 @@ export async function runAudit(sessionFiles, onProgress) {
|
|
|
78
78
|
elapsedMs: Date.now() - workflowStartedAt,
|
|
79
79
|
});
|
|
80
80
|
const competencies = mapSignalsToCompetencies(allSignals);
|
|
81
|
+
const promptCount = combined.messages.filter((message) => message.role === "user").length;
|
|
81
82
|
return {
|
|
82
83
|
sessionCount: sessionFiles.length,
|
|
83
84
|
messageCount: combined.messages.length,
|
|
85
|
+
promptCount,
|
|
84
86
|
toolCallCount: combined.toolCalls.length,
|
|
85
87
|
signalCount: allSignals.length,
|
|
86
88
|
workflowCount: workflows.length,
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { AuditCliOptions } from "./lib/cli-args.js";
|
|
2
|
+
type PlanItem = {
|
|
3
|
+
title: string;
|
|
4
|
+
action: string;
|
|
5
|
+
reason: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildPlanJsonOutput(input: {
|
|
8
|
+
professionalDna: string;
|
|
9
|
+
keep: Array<{
|
|
10
|
+
title: string;
|
|
11
|
+
tagline?: string;
|
|
12
|
+
body: string;
|
|
13
|
+
}>;
|
|
14
|
+
watch: Array<{
|
|
15
|
+
title: string;
|
|
16
|
+
body: string;
|
|
17
|
+
}>;
|
|
18
|
+
plan: PlanItem[];
|
|
19
|
+
}): {
|
|
20
|
+
professionalDna: string;
|
|
21
|
+
keep: Array<{
|
|
22
|
+
title: string;
|
|
23
|
+
tagline?: string;
|
|
24
|
+
body: string;
|
|
25
|
+
}>;
|
|
26
|
+
watch: Array<{
|
|
27
|
+
title: string;
|
|
28
|
+
body: string;
|
|
29
|
+
}>;
|
|
30
|
+
plan: PlanItem[];
|
|
31
|
+
};
|
|
32
|
+
export declare function runPlanCommand(opts: AuditCliOptions): Promise<void>;
|
|
33
|
+
export {};
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { resolveDiagnostics } from "./lib/diagnostic-runner.js";
|
|
2
|
+
import { buildSkillAuditReport } from "./lib/profile-report.js";
|
|
3
|
+
function actionForBlindSpot(title) {
|
|
4
|
+
switch (title) {
|
|
5
|
+
case "Feature sprawl":
|
|
6
|
+
return {
|
|
7
|
+
title,
|
|
8
|
+
action: "Finish one live path before opening another new direction this week.",
|
|
9
|
+
reason: "Your output is not the constraint. Selection and closure are.",
|
|
10
|
+
};
|
|
11
|
+
case "Git hygiene under pressure":
|
|
12
|
+
return {
|
|
13
|
+
title,
|
|
14
|
+
action: "Reduce branch churn and land one clean, reviewable slice before the next risky change.",
|
|
15
|
+
reason: "The skill is there; the failure mode is pressure, not ignorance.",
|
|
16
|
+
};
|
|
17
|
+
case "Technical detours":
|
|
18
|
+
return {
|
|
19
|
+
title,
|
|
20
|
+
action: "Timebox deep exploration and force a product decision when the learning curve flattens.",
|
|
21
|
+
reason: "Curiosity is helping you, but it still needs a stopping rule.",
|
|
22
|
+
};
|
|
23
|
+
case "Coordination drag":
|
|
24
|
+
return {
|
|
25
|
+
title,
|
|
26
|
+
action: "Write the one-line decision and owner before you expand the system further.",
|
|
27
|
+
reason: "Alignment cost is starting to compete with build cost.",
|
|
28
|
+
};
|
|
29
|
+
default:
|
|
30
|
+
return {
|
|
31
|
+
title,
|
|
32
|
+
action: "Keep the loop short and choose one concrete improvement to test next.",
|
|
33
|
+
reason: "The report points to a leverage point, not a reason to add more layers.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function buildPlanItems(blindSpots, meaningfulChanges) {
|
|
38
|
+
const items = blindSpots
|
|
39
|
+
.slice(0, 3)
|
|
40
|
+
.map((spot) => actionForBlindSpot(spot.title));
|
|
41
|
+
if (meaningfulChanges.length > 0) {
|
|
42
|
+
items.push({
|
|
43
|
+
title: "Recent movement",
|
|
44
|
+
action: "Protect the newest positive change and turn it into a repeated habit.",
|
|
45
|
+
reason: meaningfulChanges[0] ?? "A new pattern just surfaced; reinforce it.",
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
return items.slice(0, 3);
|
|
49
|
+
}
|
|
50
|
+
export function buildPlanJsonOutput(input) {
|
|
51
|
+
return input;
|
|
52
|
+
}
|
|
53
|
+
function printPlan(output) {
|
|
54
|
+
console.log("PLAN");
|
|
55
|
+
console.log("");
|
|
56
|
+
console.log(output.professionalDna);
|
|
57
|
+
console.log("");
|
|
58
|
+
console.log("Keep");
|
|
59
|
+
for (const item of output.keep) {
|
|
60
|
+
console.log(` - ${item.title}${item.tagline ? ` — ${item.tagline}` : ""}`);
|
|
61
|
+
}
|
|
62
|
+
console.log("");
|
|
63
|
+
console.log("Watch");
|
|
64
|
+
for (const item of output.watch) {
|
|
65
|
+
console.log(` - ${item.title}: ${item.body}`);
|
|
66
|
+
}
|
|
67
|
+
console.log("");
|
|
68
|
+
console.log("Plan");
|
|
69
|
+
for (const item of output.plan) {
|
|
70
|
+
console.log(` - ${item.action}`);
|
|
71
|
+
console.log(` Why: ${item.reason}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export async function runPlanCommand(opts) {
|
|
75
|
+
const diagnostics = await resolveDiagnostics(opts);
|
|
76
|
+
if (opts.quiet)
|
|
77
|
+
return;
|
|
78
|
+
const report = buildSkillAuditReport(diagnostics.result, diagnostics.competencies, diagnostics.tracking);
|
|
79
|
+
const output = buildPlanJsonOutput({
|
|
80
|
+
professionalDna: diagnostics.bilan?.trim() || report.professionalDna,
|
|
81
|
+
keep: report.tacitSkills.slice(0, 3),
|
|
82
|
+
watch: report.blindSpots.slice(0, 3),
|
|
83
|
+
plan: buildPlanItems(report.blindSpots, report.meaningfulChanges),
|
|
84
|
+
});
|
|
85
|
+
if (opts.json) {
|
|
86
|
+
console.log(JSON.stringify(output, null, 2));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
printPlan(output);
|
|
90
|
+
}
|