fraim-framework 2.0.95 → 2.0.97
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/bin/fraim.js +1 -1
- package/dist/src/cli/commands/add-ide.js +1 -1
- package/dist/src/cli/commands/doctor.js +6 -6
- package/dist/src/cli/commands/init-project.js +63 -52
- package/dist/src/cli/commands/list-overridable.js +33 -55
- package/dist/src/cli/commands/list.js +35 -9
- package/dist/src/cli/commands/migrate-project-fraim.js +42 -0
- package/dist/src/cli/commands/override.js +18 -39
- package/dist/src/cli/commands/setup.js +1 -1
- package/dist/src/cli/commands/sync.js +34 -27
- package/dist/src/cli/doctor/check-runner.js +3 -3
- package/dist/src/cli/doctor/checks/global-setup-checks.js +13 -13
- package/dist/src/cli/doctor/checks/project-setup-checks.js +12 -12
- package/dist/src/cli/doctor/checks/scripts-checks.js +2 -2
- package/dist/src/cli/doctor/checks/workflow-checks.js +56 -60
- package/dist/src/cli/doctor/reporters/console-reporter.js +1 -1
- package/dist/src/cli/fraim.js +3 -1
- package/dist/src/cli/mcp/mcp-server-registry.js +1 -1
- package/dist/src/cli/services/device-flow-service.js +83 -0
- package/dist/src/cli/setup/auto-mcp-setup.js +2 -2
- package/dist/src/cli/setup/first-run.js +4 -3
- package/dist/src/cli/utils/agent-adapters.js +126 -0
- package/dist/src/cli/utils/fraim-gitignore.js +15 -21
- package/dist/src/cli/utils/project-bootstrap.js +93 -0
- package/dist/src/cli/utils/remote-sync.js +20 -67
- package/dist/src/core/ai-mentor.js +31 -49
- package/dist/src/core/config-loader.js +57 -62
- package/dist/src/core/config-writer.js +75 -0
- package/dist/src/core/types.js +1 -1
- package/dist/src/core/utils/job-parser.js +176 -0
- package/dist/src/core/utils/local-registry-resolver.js +61 -71
- package/dist/src/core/utils/project-fraim-migration.js +103 -0
- package/dist/src/core/utils/project-fraim-paths.js +38 -0
- package/dist/src/core/utils/stub-generator.js +41 -75
- package/dist/src/core/utils/workflow-parser.js +5 -3
- package/dist/src/local-mcp-server/learning-context-builder.js +229 -0
- package/dist/src/local-mcp-server/stdio-server.js +124 -51
- package/dist/src/local-mcp-server/usage-collector.js +109 -27
- package/index.js +1 -1
- package/package.json +3 -4
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateWorkflowStub = generateWorkflowStub;
|
|
4
|
-
exports.parseRegistryWorkflow = parseRegistryWorkflow;
|
|
5
3
|
exports.generateJobStub = generateJobStub;
|
|
6
4
|
exports.generateSkillStub = generateSkillStub;
|
|
7
5
|
exports.generateRuleStub = generateRuleStub;
|
|
@@ -46,54 +44,22 @@ function extractLeadParagraph(content) {
|
|
|
46
44
|
return paragraphLines.join(' ').trim();
|
|
47
45
|
}
|
|
48
46
|
/**
|
|
49
|
-
*
|
|
50
|
-
* These stubs are committed to the user's repo for discoverability.
|
|
47
|
+
* Coaching stubs are discoverability artifacts and should be resolved with get_fraim_file.
|
|
51
48
|
*/
|
|
52
|
-
function
|
|
53
|
-
return `${STUB_MARKER}
|
|
54
|
-
# FRAIM
|
|
55
|
-
|
|
56
|
-
> [!IMPORTANT]
|
|
57
|
-
> This is a **FRAIM-managed workflow stub**.
|
|
58
|
-
> To load the full context (rules, templates, and execution steps), ask your AI agent to:
|
|
59
|
-
> \`@fraim get_fraim_workflow("${workflowName}")\`
|
|
60
|
-
>
|
|
61
|
-
> DO NOT EXECUTE.
|
|
49
|
+
function generateJobStub(jobName, _jobPath, intent, outcome, steps) {
|
|
50
|
+
return `${STUB_MARKER}
|
|
51
|
+
# FRAIM Job: ${jobName}
|
|
62
52
|
|
|
63
53
|
## Intent
|
|
64
54
|
${intent}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const principlesMatch = content.match(/##\s*principles\s+([\s\S]*?)(?=\n##|$)/i);
|
|
74
|
-
const intent = intentMatch ? intentMatch[1].trim() : 'No intent defined.';
|
|
75
|
-
const principles = principlesMatch
|
|
76
|
-
? principlesMatch[1].trim().split(/\r?\n/).map(l => l.replace(/^- /, '').trim()).filter(l => l)
|
|
77
|
-
: [];
|
|
78
|
-
return { intent, principles };
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Coaching stubs are discoverability artifacts and should be resolved with get_fraim_file.
|
|
82
|
-
*/
|
|
83
|
-
function generateJobStub(jobName, _jobPath, intent, outcome, steps) {
|
|
84
|
-
return `${STUB_MARKER}
|
|
85
|
-
# FRAIM Job: ${jobName}
|
|
86
|
-
|
|
87
|
-
## Intent
|
|
88
|
-
${intent}
|
|
89
|
-
|
|
90
|
-
## Outcome
|
|
91
|
-
${outcome}
|
|
92
|
-
|
|
93
|
-
## Steps
|
|
94
|
-
${steps}
|
|
95
|
-
|
|
96
|
-
---
|
|
55
|
+
|
|
56
|
+
## Outcome
|
|
57
|
+
${outcome}
|
|
58
|
+
|
|
59
|
+
## Steps
|
|
60
|
+
${steps}
|
|
61
|
+
|
|
62
|
+
---
|
|
97
63
|
|
|
98
64
|
> [!IMPORTANT]
|
|
99
65
|
> **For AI Agents:** Do NOT attempt to execute this job based on the Intent/Outcome above.
|
|
@@ -110,41 +76,41 @@ ${steps}
|
|
|
110
76
|
* Generates a lightweight markdown stub for a skill.
|
|
111
77
|
*/
|
|
112
78
|
function generateSkillStub(skillName, skillPath, skillInput, skillOutput) {
|
|
113
|
-
return `${STUB_MARKER}
|
|
114
|
-
# FRAIM Skill: ${skillName}
|
|
115
|
-
|
|
116
|
-
## Skill Input
|
|
117
|
-
${skillInput}
|
|
118
|
-
|
|
119
|
-
## Skill Output
|
|
120
|
-
${skillOutput}
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
> [!IMPORTANT]
|
|
125
|
-
> **For AI Agents:** This is a discoverability stub for the skill.
|
|
126
|
-
> All execution details must be fetched from MCP before use.
|
|
127
|
-
> To retrieve the complete skill instructions, call:
|
|
128
|
-
> \`get_fraim_file({ path: "skills/${skillPath}" })\`
|
|
79
|
+
return `${STUB_MARKER}
|
|
80
|
+
# FRAIM Skill: ${skillName}
|
|
81
|
+
|
|
82
|
+
## Skill Input
|
|
83
|
+
${skillInput}
|
|
84
|
+
|
|
85
|
+
## Skill Output
|
|
86
|
+
${skillOutput}
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
> [!IMPORTANT]
|
|
91
|
+
> **For AI Agents:** This is a discoverability stub for the skill.
|
|
92
|
+
> All execution details must be fetched from MCP before use.
|
|
93
|
+
> To retrieve the complete skill instructions, call:
|
|
94
|
+
> \`get_fraim_file({ path: "skills/${skillPath}" })\`
|
|
129
95
|
`;
|
|
130
96
|
}
|
|
131
97
|
/**
|
|
132
98
|
* Generates a lightweight markdown stub for a rule.
|
|
133
99
|
*/
|
|
134
100
|
function generateRuleStub(ruleName, rulePath, intent) {
|
|
135
|
-
return `${STUB_MARKER}
|
|
136
|
-
# FRAIM Rule: ${ruleName}
|
|
137
|
-
|
|
138
|
-
## Intent
|
|
139
|
-
${intent}
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
> [!IMPORTANT]
|
|
144
|
-
> **For AI Agents:** This is a discoverability stub for the rule.
|
|
145
|
-
> All rule details must be fetched from MCP before use.
|
|
146
|
-
> To retrieve the complete rule instructions, call:
|
|
147
|
-
> \`get_fraim_file({ path: "rules/${rulePath}" })\`
|
|
101
|
+
return `${STUB_MARKER}
|
|
102
|
+
# FRAIM Rule: ${ruleName}
|
|
103
|
+
|
|
104
|
+
## Intent
|
|
105
|
+
${intent}
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
> [!IMPORTANT]
|
|
110
|
+
> **For AI Agents:** This is a discoverability stub for the rule.
|
|
111
|
+
> All rule details must be fetched from MCP before use.
|
|
112
|
+
> To retrieve the complete rule instructions, call:
|
|
113
|
+
> \`get_fraim_file({ path: "rules/${rulePath}" })\`
|
|
148
114
|
`;
|
|
149
115
|
}
|
|
150
116
|
/**
|
|
@@ -5,13 +5,15 @@ const fs_1 = require("fs");
|
|
|
5
5
|
const path_1 = require("path");
|
|
6
6
|
class WorkflowParser {
|
|
7
7
|
static extractMetadataBlock(content) {
|
|
8
|
-
|
|
8
|
+
// Allow leading comments and whitespace before frontmatter
|
|
9
|
+
const frontmatterMatch = content.match(/^[\s\S]*?---\r?\n([\s\S]+?)\r?\n---/);
|
|
9
10
|
if (frontmatterMatch) {
|
|
10
11
|
try {
|
|
12
|
+
const startIndex = frontmatterMatch.index || 0;
|
|
11
13
|
return {
|
|
12
14
|
state: 'valid',
|
|
13
15
|
metadata: JSON.parse(frontmatterMatch[1]),
|
|
14
|
-
bodyStartIndex: frontmatterMatch[0].length
|
|
16
|
+
bodyStartIndex: startIndex + frontmatterMatch[0].length
|
|
15
17
|
};
|
|
16
18
|
}
|
|
17
19
|
catch {
|
|
@@ -107,7 +109,7 @@ class WorkflowParser {
|
|
|
107
109
|
overview = contentAfterMetadata;
|
|
108
110
|
}
|
|
109
111
|
const phases = new Map();
|
|
110
|
-
const phaseSections = restOfContent.split(/^##\s+Phase:\s
|
|
112
|
+
const phaseSections = restOfContent.split(/^##\s+Phase:\s*/im);
|
|
111
113
|
if (!metadata.phases) {
|
|
112
114
|
metadata.phases = {};
|
|
113
115
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* LearningContextBuilder builds the learning context section for a user.
|
|
4
|
+
*
|
|
5
|
+
* Lives in the local proxy so learning files are always read from the correct
|
|
6
|
+
* workspace root on the user's machine.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.buildLearningContextSection = buildLearningContextSection;
|
|
10
|
+
const fs_1 = require("fs");
|
|
11
|
+
const path_1 = require("path");
|
|
12
|
+
const project_fraim_paths_1 = require("../core/utils/project-fraim-paths");
|
|
13
|
+
const LEARNINGS_REL = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee/learnings').replace(/\/$/, '');
|
|
14
|
+
const DEFAULT_THRESHOLD = 3.0;
|
|
15
|
+
function getLearningsBase(workspaceRoot) {
|
|
16
|
+
return (0, path_1.join)(workspaceRoot, LEARNINGS_REL);
|
|
17
|
+
}
|
|
18
|
+
function getScoreThreshold(workspaceRoot) {
|
|
19
|
+
try {
|
|
20
|
+
const configPath = (0, project_fraim_paths_1.getWorkspaceConfigPath)(workspaceRoot);
|
|
21
|
+
if ((0, fs_1.existsSync)(configPath)) {
|
|
22
|
+
const config = JSON.parse((0, fs_1.readFileSync)(configPath, 'utf8'));
|
|
23
|
+
const t = config?.learning?.scoreThreshold;
|
|
24
|
+
if (typeof t === 'number' && t > 0)
|
|
25
|
+
return t;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// Fall through to default.
|
|
30
|
+
}
|
|
31
|
+
return DEFAULT_THRESHOLD;
|
|
32
|
+
}
|
|
33
|
+
function computeEffectiveScore(severity, lastSeenDate, recurrences, fileType) {
|
|
34
|
+
const baseScore = severity === 'P-HIGH' ? 8 : severity === 'P-MED' ? 5 : 3;
|
|
35
|
+
const halfLife = fileType === 'mistake-patterns' ? 90 : 180;
|
|
36
|
+
let daysSinceLastSeen = 0;
|
|
37
|
+
try {
|
|
38
|
+
const lastSeen = new Date(lastSeenDate);
|
|
39
|
+
const now = new Date();
|
|
40
|
+
daysSinceLastSeen = Math.max(0, (now.getTime() - lastSeen.getTime()) / (1000 * 60 * 60 * 24));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Treat as fresh.
|
|
44
|
+
}
|
|
45
|
+
const decay = Math.pow(0.5, daysSinceLastSeen / halfLife);
|
|
46
|
+
const recurrenceBoost = Math.log2(Math.max(1, recurrences) + 1);
|
|
47
|
+
return baseScore * decay * recurrenceBoost;
|
|
48
|
+
}
|
|
49
|
+
function countMistakePatternEntries(filePath, threshold) {
|
|
50
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
51
|
+
return { active: 0, dormant: 0 };
|
|
52
|
+
try {
|
|
53
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
54
|
+
const lines = content.split('\n');
|
|
55
|
+
let active = 0;
|
|
56
|
+
let dormant = 0;
|
|
57
|
+
let inEntry = false;
|
|
58
|
+
let currentSeverity = null;
|
|
59
|
+
let currentLastSeen = '';
|
|
60
|
+
let currentRecurrences = 1;
|
|
61
|
+
const processCurrentEntry = () => {
|
|
62
|
+
if (!currentSeverity)
|
|
63
|
+
return;
|
|
64
|
+
const score = computeEffectiveScore(currentSeverity, currentLastSeen, currentRecurrences, 'mistake-patterns');
|
|
65
|
+
if (score >= threshold)
|
|
66
|
+
active++;
|
|
67
|
+
else
|
|
68
|
+
dormant++;
|
|
69
|
+
};
|
|
70
|
+
for (const line of lines) {
|
|
71
|
+
const headerMatch = line.match(/^## \[(P-HIGH|P-MED|P-LOW)\]/);
|
|
72
|
+
if (headerMatch) {
|
|
73
|
+
processCurrentEntry();
|
|
74
|
+
inEntry = true;
|
|
75
|
+
currentSeverity = headerMatch[1];
|
|
76
|
+
currentLastSeen = '';
|
|
77
|
+
currentRecurrences = 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!inEntry)
|
|
81
|
+
continue;
|
|
82
|
+
const lastSeenMatch = line.match(/^\*\*Last seen\*\*:\s*(.+)/);
|
|
83
|
+
if (lastSeenMatch) {
|
|
84
|
+
currentLastSeen = lastSeenMatch[1].trim();
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const recurrenceMatch = line.match(/^\*\*Recurrences\*\*:\s*(\d+)/);
|
|
88
|
+
if (recurrenceMatch) {
|
|
89
|
+
currentRecurrences = parseInt(recurrenceMatch[1], 10);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
processCurrentEntry();
|
|
93
|
+
return { active, dormant };
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return { active: 0, dormant: 0 };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function readFrontmatter(content) {
|
|
100
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
101
|
+
if (!match)
|
|
102
|
+
return {};
|
|
103
|
+
const frontmatter = {};
|
|
104
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
105
|
+
const separator = line.indexOf(':');
|
|
106
|
+
if (separator === -1)
|
|
107
|
+
continue;
|
|
108
|
+
const key = line.slice(0, separator).trim();
|
|
109
|
+
const value = line.slice(separator + 1).trim();
|
|
110
|
+
if (key) {
|
|
111
|
+
frontmatter[key] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return frontmatter;
|
|
115
|
+
}
|
|
116
|
+
function isUnsynthesizedRetrospective(filePath) {
|
|
117
|
+
try {
|
|
118
|
+
const content = (0, fs_1.readFileSync)(filePath, 'utf8');
|
|
119
|
+
const frontmatter = readFrontmatter(content);
|
|
120
|
+
const synthesized = frontmatter.synthesized?.toLowerCase();
|
|
121
|
+
return synthesized !== 'true' && synthesized !== 'yes';
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function buildLearningContextSection(workspaceRoot, userId, forJob) {
|
|
128
|
+
const learningsBase = getLearningsBase(workspaceRoot);
|
|
129
|
+
const threshold = getScoreThreshold(workspaceRoot);
|
|
130
|
+
const l2MistakePath = (0, path_1.join)(learningsBase, 'org-mistake-patterns.md');
|
|
131
|
+
const l2PrefPath = (0, path_1.join)(learningsBase, 'org-preferences.md');
|
|
132
|
+
const l2CoachPath = (0, path_1.join)(learningsBase, 'org-manager-coaching.md');
|
|
133
|
+
const l2MistakePresent = (0, fs_1.existsSync)(l2MistakePath);
|
|
134
|
+
const l2PrefPresent = (0, fs_1.existsSync)(l2PrefPath);
|
|
135
|
+
const l2CoachPresent = (0, fs_1.existsSync)(l2CoachPath);
|
|
136
|
+
const l2MistakeCounts = l2MistakePresent ? countMistakePatternEntries(l2MistakePath, threshold) : null;
|
|
137
|
+
const l1MistakePath = (0, path_1.join)(learningsBase, `${userId}-mistake-patterns.md`);
|
|
138
|
+
const l1PrefPath = (0, path_1.join)(learningsBase, `${userId}-preferences.md`);
|
|
139
|
+
const l1CoachPath = (0, path_1.join)(learningsBase, `${userId}-manager-coaching.md`);
|
|
140
|
+
const l1MistakePresent = (0, fs_1.existsSync)(l1MistakePath);
|
|
141
|
+
const l1PrefPresent = (0, fs_1.existsSync)(l1PrefPath);
|
|
142
|
+
const l1CoachPresent = (0, fs_1.existsSync)(l1CoachPath);
|
|
143
|
+
const l1MistakeCounts = l1MistakePresent ? countMistakePatternEntries(l1MistakePath, threshold) : null;
|
|
144
|
+
let l0CoachingCount = 0;
|
|
145
|
+
const rawPath = (0, path_1.join)(learningsBase, 'raw');
|
|
146
|
+
if ((0, fs_1.existsSync)(rawPath)) {
|
|
147
|
+
try {
|
|
148
|
+
l0CoachingCount = (0, fs_1.readdirSync)(rawPath).filter(f => f.startsWith(`${userId}-`)).length;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Ignore read failures.
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
let l0RetroCount = 0;
|
|
155
|
+
const retrospectivesPath = (0, path_1.join)(workspaceRoot, 'docs', 'retrospectives');
|
|
156
|
+
if ((0, fs_1.existsSync)(retrospectivesPath)) {
|
|
157
|
+
try {
|
|
158
|
+
l0RetroCount = (0, fs_1.readdirSync)(retrospectivesPath)
|
|
159
|
+
.filter(f => f.startsWith(`${userId}-`) && f.endsWith('.md'))
|
|
160
|
+
.filter(f => isUnsynthesizedRetrospective((0, path_1.join)(retrospectivesPath, f))).length;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Ignore read failures.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const hasL2 = l2MistakePresent || l2PrefPresent || l2CoachPresent;
|
|
167
|
+
const hasL1 = l1MistakePresent || l1PrefPresent || l1CoachPresent;
|
|
168
|
+
const hasContent = hasL2 || hasL1 || l0CoachingCount > 0 || l0RetroCount > 0;
|
|
169
|
+
if (!hasContent)
|
|
170
|
+
return '';
|
|
171
|
+
let section = forJob
|
|
172
|
+
? '\n\n## Learning Context for This Job\n\n'
|
|
173
|
+
: '\n\n## Learning Context\n\n';
|
|
174
|
+
if (hasL2) {
|
|
175
|
+
section += '### L2 - Org patterns\n';
|
|
176
|
+
if (l2MistakePresent)
|
|
177
|
+
section += `\`${LEARNINGS_REL}/org-mistake-patterns.md\` (entries above score threshold)\n`;
|
|
178
|
+
if (l2PrefPresent)
|
|
179
|
+
section += `\`${LEARNINGS_REL}/org-preferences.md\` (all entries)\n`;
|
|
180
|
+
if (l2CoachPresent)
|
|
181
|
+
section += `\`${LEARNINGS_REL}/org-manager-coaching.md\` (all entries)\n`;
|
|
182
|
+
if (l2MistakeCounts && l2MistakeCounts.dormant > 0) {
|
|
183
|
+
section += `Dormant: ${l2MistakeCounts.dormant} org pattern${l2MistakeCounts.dormant !== 1 ? 's' : ''} below threshold\n`;
|
|
184
|
+
}
|
|
185
|
+
section += '\n';
|
|
186
|
+
}
|
|
187
|
+
if (hasL1) {
|
|
188
|
+
section += '### L1 - Your patterns\n';
|
|
189
|
+
if (l1PrefPresent)
|
|
190
|
+
section += `\`${LEARNINGS_REL}/${userId}-preferences.md\` (all entries)\n`;
|
|
191
|
+
if (l1CoachPresent)
|
|
192
|
+
section += `\`${LEARNINGS_REL}/${userId}-manager-coaching.md\` (all entries)\n`;
|
|
193
|
+
if (l1MistakePresent)
|
|
194
|
+
section += `\`${LEARNINGS_REL}/${userId}-mistake-patterns.md\` (entries above score threshold)\n`;
|
|
195
|
+
if (l1MistakeCounts && l1MistakeCounts.dormant > 0) {
|
|
196
|
+
section += `Dormant: ${l1MistakeCounts.dormant} personal pattern${l1MistakeCounts.dormant !== 1 ? 's' : ''} below threshold\n`;
|
|
197
|
+
}
|
|
198
|
+
section += '\n';
|
|
199
|
+
}
|
|
200
|
+
if (l0CoachingCount > 0 || l0RetroCount > 0) {
|
|
201
|
+
section += '### L0 - Your unprocessed signals\n';
|
|
202
|
+
if (l0CoachingCount > 0) {
|
|
203
|
+
section += `${l0CoachingCount} coaching moment${l0CoachingCount !== 1 ? 's' : ''} in \`${LEARNINGS_REL}/raw/${userId}-*\`\n`;
|
|
204
|
+
}
|
|
205
|
+
if (l0RetroCount > 0) {
|
|
206
|
+
section += `${l0RetroCount} retrospective${l0RetroCount !== 1 ? 's' : ''} in \`docs/retrospectives/${userId}-*\` with \`synthesized: false\` or missing\n`;
|
|
207
|
+
}
|
|
208
|
+
section += '\n';
|
|
209
|
+
}
|
|
210
|
+
const totalL0 = l0CoachingCount + l0RetroCount;
|
|
211
|
+
if (forJob) {
|
|
212
|
+
if (hasL2 || hasL1) {
|
|
213
|
+
section += 'Use the relevant patterns, preferences, and coaching signals in this job.\n';
|
|
214
|
+
}
|
|
215
|
+
if (totalL0 >= 5) {
|
|
216
|
+
section += '\n';
|
|
217
|
+
section += `Warning: ${totalL0} unprocessed signals pending. Consider running \`end-of-day-debrief\` before starting today's work.\n`;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
section += 'Use this synthesized learning context throughout the session.\n';
|
|
222
|
+
if (totalL0 >= 5) {
|
|
223
|
+
section += '\n';
|
|
224
|
+
section += `Warning: synthesis overdue with ${totalL0} unprocessed signals.\n`;
|
|
225
|
+
section += 'Run `end-of-day-debrief` before starting today\'s work.\n';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return section;
|
|
229
|
+
}
|