fraim 2.0.346 → 2.0.349

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 (34) hide show
  1. package/dist/registry/scripts/learning/completion-state.js +80 -4
  2. package/dist/registry/scripts/learning/corpus-audit.js +7 -7
  3. package/dist/registry/scripts/learning/entry-authoring.js +181 -0
  4. package/dist/registry/scripts/learning/entry-format.js +10 -1
  5. package/dist/registry/scripts/learning/outcome-assessment.js +162 -0
  6. package/dist/registry/scripts/learning/outcome-types.js +92 -0
  7. package/dist/registry/scripts/learning/owner-identity.js +11 -0
  8. package/dist/registry/scripts/learning/proposal-queue.js +81 -0
  9. package/dist/registry/scripts/learning/run-application.js +1003 -0
  10. package/dist/registry/scripts/learning/run-publication.js +364 -0
  11. package/dist/registry/scripts/learning/run-record.js +895 -0
  12. package/dist/registry/scripts/learning-usage/learning-source-record.js +131 -0
  13. package/dist/src/cli/commands/learning.js +64 -0
  14. package/dist/src/cli/doctor/checks/scripts-checks.js +53 -1
  15. package/dist/src/cli/fraim.js +2 -0
  16. package/dist/src/cli/learning/inventory.js +285 -0
  17. package/dist/src/cli/learning/queue-operations.js +199 -0
  18. package/dist/src/cli/learning/runtime.js +82 -0
  19. package/dist/src/cli/learning/usage.js +277 -0
  20. package/dist/src/core/fraim-config-schema.generated.js +0 -4
  21. package/dist/src/core/utils/local-registry-resolver.js +13 -3
  22. package/dist/src/local-mcp-server/learning-context-builder.js +109 -379
  23. package/dist/src/local-mcp-server/learning-entry-format.js +25 -0
  24. package/dist/src/local-mcp-server/learning-usage-analysis.js +12 -410
  25. package/dist/src/local-mcp-server/learning-usage-eligibility.js +8 -14
  26. package/dist/src/local-mcp-server/learning-usage-projection.js +5 -22
  27. package/dist/src/local-mcp-server/learning-usage-store.js +93 -393
  28. package/dist/src/local-mcp-server/learning-usage-verify.js +9 -40
  29. package/dist/src/local-mcp-server/stdio-server.js +50 -114
  30. package/dist/src/mcp/tool-schemas.js +2 -1
  31. package/package.json +9 -1
  32. package/dist/src/local-mcp-server/learning-firing-parser.js +0 -247
  33. package/dist/src/local-mcp-server/learning-usage-attestation.js +0 -238
  34. package/dist/src/local-mcp-server/learning-usage-command.js +0 -518
@@ -1,238 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractLegacyTitle = exports.parseFiringSection = void 0;
4
- exports.recordAttestationsFromRetrospective = recordAttestationsFromRetrospective;
5
- exports.readRetrospectiveForAttestation = readRetrospectiveForAttestation;
6
- exports.runBackfill = runBackfill;
7
- const artifact_paths_1 = require("../../registry/scripts/artifact-paths");
8
- /**
9
- * Issue #1103 — turning an attestation into a usage record.
10
- *
11
- * The retrospective is the durable record of a firing; the usage store is derived
12
- * from it (R17). So there is exactly one parser (`learning-firing-parser.ts`) and
13
- * one recorder here, used by two entry points: the live attestation at the end of
14
- * a job, and the backfill over the retrospectives already on disk (R15).
15
- *
16
- * Matching is an id or an exact title, and nothing else. R5 forbids resolving a
17
- * prose reference to the closest title, and an unmatched item is reported rather
18
- * than guessed at: a wrong match writes a firing onto the wrong entry, which is
19
- * worse than no firing at all.
20
- */
21
- const fs_1 = require("fs");
22
- const path_1 = require("path");
23
- const learning_source_record_1 = require("./learning-source-record");
24
- const learning_usage_store_1 = require("./learning-usage-store");
25
- const learning_firing_parser_1 = require("./learning-firing-parser");
26
- const learning_context_builder_1 = require("./learning-context-builder");
27
- // Re-exported so callers that only need one of the two halves keep one import.
28
- var learning_firing_parser_2 = require("./learning-firing-parser");
29
- Object.defineProperty(exports, "parseFiringSection", { enumerable: true, get: function () { return learning_firing_parser_2.parseFiringSection; } });
30
- Object.defineProperty(exports, "extractLegacyTitle", { enumerable: true, get: function () { return learning_firing_parser_2.extractLegacyTitle; } });
31
- const ENTRY_INDEX_READS = [
32
- { scope: 'manager', level: 'machine' },
33
- { scope: 'manager', level: 'project' },
34
- { scope: 'org', level: 'machine' },
35
- { scope: 'reverse', level: 'machine' },
36
- { scope: 'reverse', level: 'project' },
37
- ];
38
- function buildEntryIndex(workspaceRoot, userId) {
39
- const byId = new Map();
40
- const byTitle = new Map();
41
- // Keep this list in parity with the preserved-entry levels that the offer
42
- // path can surface. Storage resolution stays inside readPreservedLearnings.
43
- for (const { scope, level } of ENTRY_INDEX_READS) {
44
- let entries = [];
45
- try {
46
- entries = (0, learning_context_builder_1.readPreservedLearnings)(workspaceRoot, userId, scope, level);
47
- }
48
- catch {
49
- continue;
50
- }
51
- for (const entry of entries) {
52
- const fileType = fileTypeOf(entry);
53
- const attestable = {
54
- // #1752 A1: one definition of what an entry is, shared with the offer
55
- // path. These two disagreeing is defect 4.
56
- key: (0, learning_usage_store_1.resolveEntryIdentity)({ id: entry.id, fileType, title: entry.title }).key,
57
- family: 'learning',
58
- title: entry.title,
59
- fileType,
60
- level: entry.level,
61
- };
62
- if (entry.id && !byId.has(entry.id))
63
- byId.set(entry.id, attestable);
64
- const titleKey = (0, learning_usage_store_1.normalizeEntryTitle)(entry.title);
65
- if (!byTitle.has(titleKey))
66
- byTitle.set(titleKey, attestable);
67
- }
68
- }
69
- for (const rule of (0, learning_context_builder_1.collectOfferedRuleFiles)(workspaceRoot, true)) {
70
- const attestable = {
71
- key: rule.key,
72
- family: 'rule',
73
- title: rule.title,
74
- fileType: rule.fileType,
75
- level: rule.level,
76
- };
77
- if (!byId.has(rule.key))
78
- byId.set(rule.key, attestable);
79
- const titleKey = (0, learning_usage_store_1.normalizeEntryTitle)(rule.title);
80
- if (!byTitle.has(titleKey))
81
- byTitle.set(titleKey, attestable);
82
- if (rule.id && !byId.has(rule.id))
83
- byId.set(rule.id, attestable);
84
- }
85
- return { byId, byTitle };
86
- }
87
- function fileTypeOf(entry) {
88
- return learning_context_builder_1.CATEGORY_TO_FILETYPE[entry.category] ?? 'mistake-patterns';
89
- }
90
- function retrospectiveDate(content, fallback) {
91
- const match = content.match(/^date:\s*(\d{4}-\d{2}-\d{2})\s*$/m);
92
- return match ? match[1] : fallback;
93
- }
94
- /**
95
- * Record the firings a retrospective attests. Idempotent per (entry, source), so a
96
- * re-run, or a backfill that overlaps a live attestation, cannot inflate a count.
97
- */
98
- function recordAttestationsFromRetrospective(workspaceRoot, userId, options) {
99
- const limits = options.limits ?? (0, learning_usage_store_1.resolveUsageLimits)(workspaceRoot);
100
- const parsed = (0, learning_firing_parser_1.parseFiringSection)(options.content, { source: options.source, legacy: options.legacy });
101
- const date = options.date ?? retrospectiveDate(options.content, new Date().toISOString().slice(0, 10));
102
- const index = buildEntryIndex(workspaceRoot, userId);
103
- const unmatched = [];
104
- const inputs = [];
105
- for (const item of parsed.items) {
106
- let entry;
107
- if (item.id) {
108
- entry = index.byId.get(item.id);
109
- if (!entry) {
110
- unmatched.push({
111
- id: item.id,
112
- title: item.title,
113
- reason: `No entry carries the id ${item.id}. Reported unmatched rather than assigned to the closest title.`,
114
- raw: item.raw,
115
- });
116
- continue;
117
- }
118
- }
119
- else {
120
- entry = index.byTitle.get((0, learning_usage_store_1.normalizeEntryTitle)(item.title));
121
- if (!entry) {
122
- unmatched.push({
123
- id: null,
124
- title: item.title,
125
- reason: 'No entry has exactly this title. Exact titles match; a paraphrase is reported rather than resolved by similarity.',
126
- raw: item.raw,
127
- });
128
- continue;
129
- }
130
- }
131
- inputs.push({
132
- key: entry.key,
133
- family: entry.family,
134
- title: entry.title,
135
- fileType: entry.fileType,
136
- level: entry.level,
137
- outcome: item.outcome,
138
- note: item.note,
139
- job: options.job,
140
- date,
141
- agent: options.agent,
142
- model: options.model,
143
- source: item.format === 'legacy' ? 'backfill-legacy' : 'attested',
144
- sourceRef: options.source,
145
- });
146
- }
147
- let applied = 0;
148
- let alreadyRecorded = 0;
149
- if (inputs.length > 0) {
150
- const result = (0, learning_usage_store_1.recordFirings)(inputs, {
151
- limits, now: options.now, storePath: options.storePath, integrityMode: options.integrityMode,
152
- });
153
- applied = result.recorded;
154
- alreadyRecorded = result.alreadyRecorded;
155
- }
156
- return {
157
- source: options.source,
158
- sectionState: parsed.sectionState,
159
- itemsRead: parsed.items.length + parsed.rejected.length,
160
- applied,
161
- alreadyRecorded,
162
- unmatched,
163
- rejected: parsed.rejected,
164
- };
165
- }
166
- // ── R15: backfill ────────────────────────────────────────────────────────────
167
- /** Resolve live attestation against its recorded home and immutable accounting identity. */
168
- function readRetrospectiveForAttestation(workspaceRoot, userId, filePath) {
169
- let reason = 'Retrospective is outside configured learning homes';
170
- for (const home of (0, learning_context_builder_1.getLearningInputHomes)(workspaceRoot)) {
171
- const result = (0, learning_context_builder_1.readLearningSourceFile)((0, path_1.resolve)(filePath), home.root, home.contentRoot);
172
- if (result.status !== 'valid') {
173
- reason = result.reason;
174
- continue;
175
- }
176
- if (result.record.kind !== 'retrospective' || result.record.author !== userId) {
177
- throw new Error('Retrospective kind or author does not match this attestation');
178
- }
179
- return { content: result.content, record: result.record };
180
- }
181
- throw new Error(`Invalid learning retrospective: ${reason}`);
182
- }
183
- /** Read only typed future reflections, including archives, with stable accounting identity. */
184
- function runBackfill(workspaceRoot, userId, options) {
185
- const limits = options.limits ?? (0, learning_usage_store_1.resolveUsageLimits)(workspaceRoot);
186
- const report = {
187
- filesScanned: 0, filesWithSection: 0, itemsRead: 0, applied: 0,
188
- alreadyRecorded: 0, unmatched: [], rejected: [], synthesizedFilesRead: 0, invalidRecords: [],
189
- };
190
- const seen = new Set();
191
- for (const home of (0, learning_context_builder_1.getLearningInputHomes)(workspaceRoot)) {
192
- const paths = (0, artifact_paths_1.resolveLearningPaths)(home.root, home.contentRoot);
193
- for (const dir of [paths.retrospectives, paths.archiveRetrospectives]) {
194
- if (options.retrospectivesDir && (0, learning_source_record_1.normalizeLearningSourcePath)(dir) !== (0, learning_source_record_1.normalizeLearningSourcePath)(options.retrospectivesDir))
195
- continue;
196
- let files;
197
- try {
198
- if ((0, fs_1.lstatSync)(dir).isSymbolicLink())
199
- continue;
200
- files = (0, fs_1.readdirSync)(dir).filter(f => f.endsWith('.md'));
201
- }
202
- catch {
203
- continue;
204
- }
205
- for (const file of files) {
206
- const filePath = (0, path_1.join)(dir, file);
207
- const parsed = (0, learning_context_builder_1.readLearningSourceFile)(filePath, home.root, home.contentRoot);
208
- if (parsed.status !== 'valid') {
209
- if (parsed.status === 'invalid')
210
- report.invalidRecords.push({ path: filePath, reason: parsed.reason });
211
- continue;
212
- }
213
- const { record } = parsed;
214
- if (record.kind !== 'retrospective' || record.author !== userId)
215
- continue;
216
- if (seen.has(record.usageIdentity))
217
- continue;
218
- seen.add(record.usageIdentity);
219
- report.filesScanned++;
220
- if (record.synthesized)
221
- report.synthesizedFilesRead++;
222
- const result = recordAttestationsFromRetrospective(workspaceRoot, userId, {
223
- content: parsed.content, source: record.usageIdentity, job: record.job, date: record.date,
224
- agent: options.agent, model: options.model, legacy: false,
225
- limits, now: options.now, storePath: options.storePath, integrityMode: 'flag',
226
- });
227
- if (result.sectionState !== 'absent')
228
- report.filesWithSection++;
229
- report.itemsRead += result.itemsRead;
230
- report.applied += result.applied;
231
- report.alreadyRecorded += result.alreadyRecorded;
232
- report.unmatched.push(...result.unmatched);
233
- report.rejected.push(...result.rejected);
234
- }
235
- }
236
- }
237
- return report;
238
- }