fraim 2.0.343 → 2.0.344

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 (28) hide show
  1. package/dist/registry/scripts/artifact-paths.js +108 -0
  2. package/dist/registry/scripts/learning/completion-state.js +205 -5
  3. package/dist/registry/scripts/learning-usage/local-runtime.js +217 -0
  4. package/dist/src/cli/commands/init-project.js +2 -12
  5. package/dist/src/cli/commands/sync.js +36 -162
  6. package/dist/src/cli/setup/ide-invocation-surfaces.js +3 -3
  7. package/dist/src/cli/setup/user-level-sync.js +3 -3
  8. package/dist/src/cli/utils/agent-adapters.js +12 -12
  9. package/dist/src/cli/utils/fraim-gitignore.js +28 -3
  10. package/dist/src/cli/utils/pack-home.js +10 -0
  11. package/dist/src/cli/utils/project-catalog-cleanup.js +62 -0
  12. package/dist/src/cli/utils/remote-sync.js +26 -2
  13. package/dist/src/core/ai-mentor.js +29 -12
  14. package/dist/src/core/config-loader.js +4 -0
  15. package/dist/src/core/fraim-config-schema.generated.js +22 -0
  16. package/dist/src/core/utils/local-registry-resolver.js +33 -57
  17. package/dist/src/fraim/db-service.js +127 -118
  18. package/dist/src/local-mcp-server/artifact-retention-cleanup.js +79 -6
  19. package/dist/src/local-mcp-server/learning-context-builder.js +101 -105
  20. package/dist/src/local-mcp-server/learning-source-record.js +122 -0
  21. package/dist/src/local-mcp-server/learning-usage-attestation.js +66 -56
  22. package/dist/src/local-mcp-server/learning-usage-command.js +5 -4
  23. package/dist/src/local-mcp-server/stdio-server.js +9 -6
  24. package/dist/src/local-mcp-server/usage-collector.js +2 -0
  25. package/dist/src/mcp/tool-schemas.js +4 -0
  26. package/dist/src/services/mcp-service.js +5 -3
  27. package/dist/src/services/usage-analytics-service.js +1 -0
  28. package/package.json +3 -1
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_ARTIFACT_PATHS = void 0;
7
+ exports.resolveArtifactPaths = resolveArtifactPaths;
8
+ exports.resolveLearningPaths = resolveLearningPaths;
9
+ exports.resolveRunJournalDirectory = resolveRunJournalDirectory;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_path_1 = __importDefault(require("node:path"));
12
+ const local_runtime_1 = require("./learning-usage/local-runtime");
13
+ /** Existing layout suffixes, also used to validate saved receipts; these are not config keys. */
14
+ exports.DEFAULT_ARTIFACT_PATHS = { evidence: 'docs/evidence', raw: 'learnings/raw', retrospectives: 'learnings/retrospectives', archive: 'learnings/archive' };
15
+ const key = (value) => process.platform === 'win32' ? value.toLowerCase() : value;
16
+ const within = (root, target) => {
17
+ const rel = node_path_1.default.relative(key(root), key(target));
18
+ return rel === '' || (rel !== '..' && !rel.startsWith('..' + node_path_1.default.sep) && !node_path_1.default.isAbsolute(rel));
19
+ };
20
+ function existingAncestor(target) {
21
+ let current = target;
22
+ while (!node_fs_1.default.existsSync(current)) {
23
+ const parent = node_path_1.default.dirname(current);
24
+ if (parent === current)
25
+ break;
26
+ current = parent;
27
+ }
28
+ return current;
29
+ }
30
+ function relativeDirectory(value, field) {
31
+ if (typeof value !== 'string' || !value || value.includes('\\') || /[:*?"<>|\x00-\x1f]/.test(value)
32
+ || node_path_1.default.isAbsolute(value) || value.split('/').some(part => !part || part === '.' || part === '..' || /[. ]$/.test(part))) {
33
+ throw new Error(`Invalid artifactPaths.${field}: expected a relative directory within its owning home`);
34
+ }
35
+ }
36
+ function directory(root, value, field) {
37
+ relativeDirectory(value, field);
38
+ const target = node_path_1.default.resolve(root, value);
39
+ if (!within(root, target))
40
+ throw new Error(`Invalid artifactPaths.${field}: path escapes owning home`);
41
+ // Reads never provision homes. Existing links must not redirect a writer outside its owner.
42
+ if (node_fs_1.default.existsSync(root) && !within(node_fs_1.default.realpathSync(root), node_fs_1.default.realpathSync(existingAncestor(target)))) {
43
+ throw new Error(`Invalid artifactPaths.${field}: linked path escapes owning home`);
44
+ }
45
+ if (node_fs_1.default.existsSync(target) && !node_fs_1.default.statSync(target).isDirectory())
46
+ throw new Error(`Invalid artifactPaths.${field}: target is not a directory`);
47
+ return target.replace(/\\/g, '/');
48
+ }
49
+ function resolveArtifactPaths(homeRoot, contentRoot) {
50
+ if (!node_path_1.default.isAbsolute(homeRoot) || !node_path_1.default.isAbsolute(contentRoot))
51
+ throw new Error('Absolute owning homeRoot and contentRoot are required');
52
+ homeRoot = node_path_1.default.resolve(homeRoot);
53
+ contentRoot = node_path_1.default.resolve(contentRoot);
54
+ if (!within(homeRoot, contentRoot))
55
+ throw new Error('Personalized contentRoot escapes owning homeRoot');
56
+ const configPath = node_path_1.default.join(node_path_1.default.dirname(contentRoot), 'config.json');
57
+ let config = {};
58
+ try {
59
+ config = (0, local_runtime_1.readJsonIfObject)(configPath, { strict: true });
60
+ }
61
+ catch {
62
+ throw new Error(`Cannot read artifact configuration: ${configPath}`);
63
+ }
64
+ const configured = config.artifactPaths === undefined ? {} : config.artifactPaths;
65
+ // Defaults follow the configured home layout, but every value has the same root-relative basis.
66
+ // Resolve only in memory: config.json stores user overrides, never materialized defaults.
67
+ const contentDirectory = node_path_1.default.relative(homeRoot, contentRoot).replace(/\\/g, '/');
68
+ const defaults = {
69
+ evidence: exports.DEFAULT_ARTIFACT_PATHS.evidence,
70
+ rawLearnings: node_path_1.default.posix.join(contentDirectory, exports.DEFAULT_ARTIFACT_PATHS.raw),
71
+ learningRetrospectives: node_path_1.default.posix.join(contentDirectory, exports.DEFAULT_ARTIFACT_PATHS.retrospectives),
72
+ learningArchive: node_path_1.default.posix.join(contentDirectory, exports.DEFAULT_ARTIFACT_PATHS.archive),
73
+ };
74
+ if (!configured || typeof configured !== 'object' || Array.isArray(configured)
75
+ || Object.keys(configured).some(field => !Object.prototype.hasOwnProperty.call(defaults, field)))
76
+ throw new Error('Invalid artifactPaths configuration: use evidence, rawLearnings, learningRetrospectives or learningArchive');
77
+ const values = { ...defaults, ...configured };
78
+ for (const [field, value] of Object.entries(values))
79
+ relativeDirectory(value, field);
80
+ const outputs = {
81
+ evidence: directory(homeRoot, values.evidence, 'evidence'),
82
+ raw: directory(homeRoot, values.rawLearnings, 'rawLearnings'),
83
+ retrospectives: directory(homeRoot, values.learningRetrospectives, 'learningRetrospectives'),
84
+ archive: directory(homeRoot, values.learningArchive, 'learningArchive'),
85
+ };
86
+ const entries = Object.entries(outputs);
87
+ for (let index = 0; index < entries.length; index++)
88
+ for (const [otherField, other] of entries.slice(index + 1)) {
89
+ const [field, target] = entries[index];
90
+ if (within(target, other) || within(other, target))
91
+ throw new Error(`Artifact directories overlap: ${field} and ${otherField}`);
92
+ }
93
+ return { configPath: configPath.replace(/\\/g, '/'), evidence: outputs.evidence, raw: outputs.raw,
94
+ retrospectives: outputs.retrospectives,
95
+ archiveRaw: directory(homeRoot, values.learningArchive + '/raw', 'learningArchive'),
96
+ archiveRetrospectives: directory(homeRoot, values.learningArchive + '/retrospectives', 'learningArchive') };
97
+ }
98
+ /** Learning readers use the same owner and mapping as inventory and completion. */
99
+ function resolveLearningPaths(homeRoot, contentRoot) {
100
+ const { evidence, ...learningPaths } = resolveArtifactPaths(homeRoot, contentRoot);
101
+ return learningPaths;
102
+ }
103
+ /** Recovery journals stay discoverable when configurable evidence outputs move. */
104
+ function resolveRunJournalDirectory(homeRoot) {
105
+ if (!node_path_1.default.isAbsolute(homeRoot))
106
+ throw new Error('Absolute owning homeRoot is required');
107
+ return directory(node_path_1.default.resolve(homeRoot), exports.DEFAULT_ARTIFACT_PATHS.evidence + '/sleep', 'runJournal');
108
+ }
@@ -3,14 +3,186 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.COMPLETION_END = exports.COMPLETION_START = void 0;
6
+ exports.COMPLETION_END = exports.COMPLETION_START = exports.UnsupportedRunHistoryError = exports.sourcePathKey = void 0;
7
+ exports.cycleRetrospectivePath = cycleRetrospectivePath;
7
8
  exports.validateProcessedSources = validateProcessedSources;
9
+ exports.canonicalJournal = canonicalJournal;
10
+ exports.readRunJournal = readRunJournal;
11
+ exports.readSourceReservations = readSourceReservations;
12
+ exports.normalizeSourceParticipants = normalizeSourceParticipants;
13
+ exports.sourceAuthorAllowed = sourceAuthorAllowed;
8
14
  exports.decodeCompletion = decodeCompletion;
9
15
  const node_path_1 = __importDefault(require("node:path"));
16
+ const node_fs_1 = __importDefault(require("node:fs"));
17
+ const node_crypto_1 = require("node:crypto");
18
+ const artifact_paths_1 = require("../artifact-paths");
19
+ const sourcePathKey = (file) => {
20
+ const value = node_path_1.default.resolve(file).replace(/\\/g, '/');
21
+ return process.platform === 'win32' ? value.toLowerCase() : value;
22
+ };
23
+ exports.sourcePathKey = sourcePathKey;
24
+ function cycleRetrospectivePath(runId, author, level, directory = artifact_paths_1.DEFAULT_ARTIFACT_PATHS.retrospectives) {
25
+ return `${directory}/${author}-${runId}-${level === 'Org' ? 'organizational-learning-synthesis' : 'sleep-on-learnings'}-retrospective.md`;
26
+ }
10
27
  function validateProcessedSources(sources) {
11
- if (!Array.isArray(sources) || new Set(sources.map(item => item.path)).size !== sources.length || sources.some(item => !item || !node_path_1.default.isAbsolute(item.path) || !node_path_1.default.isAbsolute(item.destination) || !['raw', 'retrospective'].includes(item.kind)
12
- || !/^[a-f0-9]{64}$/.test(item.sha256) || !/^[a-f0-9]{64}$/.test(item.resultHash) || !/^\d{4}-\d{2}-\d{2}$/.test(item.synthesizedAt)))
28
+ if (!Array.isArray(sources))
13
29
  throw new Error('invalid-processed-sources');
30
+ const paths = new Set();
31
+ for (const item of sources) {
32
+ if (!item || !node_path_1.default.isAbsolute(item.path) || !node_path_1.default.isAbsolute(item.destination) || !['raw', 'retrospective'].includes(item.kind)
33
+ || !/^[a-f0-9]{64}$/.test(item.sha256) || !/^[a-f0-9]{64}$/.test(item.resultHash)
34
+ || !/^\d{4}-\d{2}-\d{2}$/.test(item.synthesizedAt) || !Number.isFinite(Date.parse(item.synthesizedAt))
35
+ || new Date(item.synthesizedAt).toISOString().slice(0, 10) !== item.synthesizedAt)
36
+ throw new Error('invalid-processed-sources');
37
+ const source = (0, exports.sourcePathKey)(item.path), destination = (0, exports.sourcePathKey)(item.destination);
38
+ if (/[\x00-\x1f]/.test(item.path + item.destination) || source === destination
39
+ || (0, exports.sourcePathKey)(node_path_1.default.basename(item.path)) !== (0, exports.sourcePathKey)(node_path_1.default.basename(item.destination))
40
+ || paths.has(source) || paths.has(destination))
41
+ throw new Error('invalid-processed-sources');
42
+ const folder = item.kind === 'raw' ? 'raw' : 'retrospectives';
43
+ if (item.layout !== undefined) {
44
+ const layout = item.layout;
45
+ if (!layout || Object.keys(layout).some(key => !['homeRoot', 'contentRoot', 'sourceDirectory', 'archiveDirectory'].includes(key)) || ['contentRoot', 'sourceDirectory', 'archiveDirectory', ...(layout.homeRoot !== undefined ? ['homeRoot'] : [])].some(key => typeof layout[key] !== 'string' || !node_path_1.default.isAbsolute(layout[key]) || /[\x00-\x1f]/.test(layout[key])))
46
+ throw new Error('invalid-processed-sources');
47
+ const contentRoot = (0, exports.sourcePathKey)(layout.homeRoot ?? layout.contentRoot);
48
+ for (const directory of [layout.sourceDirectory, layout.archiveDirectory, ...(layout.homeRoot ? [layout.contentRoot] : [])]) {
49
+ const relative = node_path_1.default.relative(contentRoot, (0, exports.sourcePathKey)(directory));
50
+ if (!relative || relative === '..' || relative.startsWith('..' + node_path_1.default.sep) || node_path_1.default.isAbsolute(relative))
51
+ throw new Error('invalid-processed-sources');
52
+ }
53
+ if ((0, exports.sourcePathKey)(node_path_1.default.dirname(item.path)) !== (0, exports.sourcePathKey)(layout.sourceDirectory)
54
+ || (0, exports.sourcePathKey)(node_path_1.default.dirname(item.destination)) !== (0, exports.sourcePathKey)(layout.archiveDirectory)
55
+ || node_path_1.default.basename(layout.archiveDirectory) !== folder)
56
+ throw new Error('invalid-processed-sources');
57
+ }
58
+ else {
59
+ // Only the already supported default-layout receipts omit a frozen layout.
60
+ const sourceDir = item.kind === 'raw' ? artifact_paths_1.DEFAULT_ARTIFACT_PATHS.raw : artifact_paths_1.DEFAULT_ARTIFACT_PATHS.retrospectives;
61
+ const suffix = `/${sourceDir}/${node_path_1.default.basename(item.path).toLowerCase()}`;
62
+ const contentRoot = node_path_1.default.resolve(item.path, ...sourceDir.split('/').map(() => '..'), '..');
63
+ const expected = (0, exports.sourcePathKey)(node_path_1.default.join(contentRoot, artifact_paths_1.DEFAULT_ARTIFACT_PATHS.archive, folder, node_path_1.default.basename(item.path)));
64
+ if (!source.toLowerCase().endsWith(suffix) || destination !== expected)
65
+ throw new Error('invalid-processed-sources');
66
+ }
67
+ paths.add(source);
68
+ paths.add(destination);
69
+ }
70
+ }
71
+ function canonicalJournal(value) {
72
+ return Array.isArray(value) ? '[' + value.map(canonicalJournal).join(',') + ']'
73
+ : value && typeof value === 'object' ? '{' + Object.keys(value).filter(key => value[key] !== undefined).sort().map(key => JSON.stringify(key) + ':' + canonicalJournal(value[key])).join(',') + '}' : JSON.stringify(value);
74
+ }
75
+ const journalHash = (value) => (0, node_crypto_1.createHash)('sha256').update(canonicalJournal(value)).digest('hex');
76
+ class UnsupportedRunHistoryError extends Error {
77
+ constructor() { super('unsupported-run-history'); }
78
+ }
79
+ exports.UnsupportedRunHistoryError = UnsupportedRunHistoryError;
80
+ /** Recognize the receipts written before typed sources and archive subdirectories existed. */
81
+ function historicalProcessedSources(sources) {
82
+ if (!Array.isArray(sources))
83
+ return false;
84
+ const paths = new Set();
85
+ return sources.every(item => {
86
+ if (!keys(item, ['path', 'destination', 'kind', 'sha256', 'resultHash', 'synthesizedAt'])
87
+ || !text(item.path) || !text(item.destination) || !node_path_1.default.isAbsolute(item.path) || !node_path_1.default.isAbsolute(item.destination)
88
+ || /[\x00-\x1f]/.test(item.path + item.destination) || !hash(item.sha256) || !hash(item.resultHash)
89
+ || typeof item.synthesizedAt !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(item.synthesizedAt)
90
+ || !Number.isFinite(Date.parse(item.synthesizedAt)) || new Date(item.synthesizedAt).toISOString().slice(0, 10) !== item.synthesizedAt)
91
+ return false;
92
+ const source = (0, exports.sourcePathKey)(item.path), destination = (0, exports.sourcePathKey)(item.destination);
93
+ if (paths.has(source))
94
+ return false;
95
+ paths.add(source);
96
+ if (item.kind === 'retrospective') {
97
+ // A current learnings/retrospectives receipt with a broken destination is not historical.
98
+ return source === destination && /\/retrospectives\/.+/.test(source)
99
+ && !/\/learnings\/retrospectives\//.test(source);
100
+ }
101
+ return item.kind === 'raw' && /\/learnings\/raw\/.+/.test(source)
102
+ && destination === source.replace(/\/learnings\/raw\//, '/learnings/archive/') && item.sha256 === item.resultHash;
103
+ });
104
+ }
105
+ /** Shared, read-only boundary: validate the full chain before excluding a recognized prior format. */
106
+ function readRunJournal(file) {
107
+ const raw = node_fs_1.default.readFileSync(file, 'utf8');
108
+ if (!raw.endsWith('\n'))
109
+ throw new Error('journal-truncated: explicit recovery required');
110
+ const events = [], operations = new Set();
111
+ let lastHash = '';
112
+ for (const line of raw.trimEnd().split('\n')) {
113
+ let event;
114
+ try {
115
+ event = JSON.parse(line);
116
+ }
117
+ catch {
118
+ throw new Error('journal-corrupt');
119
+ }
120
+ if (!event || !event.payload || event.sequence !== events.length + 1 || event.previousHash !== lastHash
121
+ || event.payloadHash !== journalHash(event.payload) || typeof event.operationId !== 'string'
122
+ || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,159}$/.test(event.operationId))
123
+ throw new Error('journal-corrupt');
124
+ if (operations.has(event.operationId))
125
+ throw new Error('journal-duplicate-operation');
126
+ operations.add(event.operationId);
127
+ events.push(event);
128
+ lastHash = journalHash(event);
129
+ }
130
+ const created = events[0]?.payload;
131
+ if (created?.type !== 'created' || created.data?.schemaVersion !== 1 || !ownerValid(created.data.owner)
132
+ || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,159}$/.test(created.data.runId) || !text(created.data.reviewer))
133
+ throw new Error('invalid-run');
134
+ const plans = events.filter(event => event.payload.type === 'source-completion').map(event => event.payload.data);
135
+ try {
136
+ for (const sources of plans)
137
+ validateProcessedSources(sources);
138
+ }
139
+ catch (error) {
140
+ if (keys(created.data, ['schemaVersion', 'runId', 'owner', 'reviewer'])
141
+ && plans.length > 0 && plans.every(historicalProcessedSources))
142
+ return { events, lastHash, format: 'historical' };
143
+ throw error;
144
+ }
145
+ return { events, lastHash, format: 'current' };
146
+ }
147
+ /** Only exact, hash-chain-verified current archive plans reserve files. Older plans are outside this lifecycle. */
148
+ function readSourceReservations(runDirectories, excludingRunFile) {
149
+ const reserved = new Set();
150
+ for (const directory of new Set(runDirectories.map(dir => node_path_1.default.resolve(dir)))) {
151
+ let names;
152
+ try {
153
+ names = node_fs_1.default.readdirSync(directory);
154
+ }
155
+ catch (error) {
156
+ if (error.code === 'ENOENT')
157
+ continue;
158
+ throw error;
159
+ }
160
+ for (const name of names.filter(name => name.endsWith('.queue.jsonl'))) {
161
+ const file = node_path_1.default.join(directory, name);
162
+ if (excludingRunFile && (0, exports.sourcePathKey)(file) === (0, exports.sourcePathKey)(excludingRunFile))
163
+ continue;
164
+ try {
165
+ if (node_fs_1.default.lstatSync(file).isSymbolicLink())
166
+ throw new Error('run-path-link-conflict');
167
+ const journal = readRunJournal(file);
168
+ if (journal.format === 'historical')
169
+ continue;
170
+ for (const event of journal.events)
171
+ if (event.payload.type === 'source-completion') {
172
+ for (const source of event.payload.data) {
173
+ reserved.add((0, exports.sourcePathKey)(source.path));
174
+ reserved.add((0, exports.sourcePathKey)(source.destination));
175
+ }
176
+ }
177
+ }
178
+ catch (error) {
179
+ if (error.code === 'ENOENT')
180
+ continue;
181
+ throw Object.assign(new Error('source-reservation-journal-conflict'), { cause: error });
182
+ }
183
+ }
184
+ }
185
+ return [...reserved].sort();
14
186
  }
15
187
  exports.COMPLETION_START = '<!-- fraim-sleep-decisions:v1 -->';
16
188
  exports.COMPLETION_END = '<!-- /fraim-sleep-decisions -->';
@@ -22,6 +194,27 @@ const ownerValid = (value) => object(value) && ['Project', 'Manager', 'Org'].inc
22
194
  && keys(value, ['level', 'path']) && text(value.path) && node_path_1.default.isAbsolute(value.path)
23
195
  && !/[\x00-\x1f]/.test(value.path) && value.path === (process.platform === 'win32' ? node_path_1.default.resolve(value.path).replace(/\\/g, '/').toLowerCase() : node_path_1.default.resolve(value.path));
24
196
  const sameOwner = (a, b) => a.level === b.level && a.path === b.path;
197
+ /** Explicit session grants are bound to an Org run, never inferred from source metadata. */
198
+ function normalizeSourceParticipants(value, owner) {
199
+ if (owner.level !== 'Org' || !Array.isArray(value))
200
+ throw new Error('invalid-source-participants');
201
+ const identities = new Set();
202
+ const result = value.map(item => {
203
+ if (!keys(item, ['author', 'owner']) || !text(item.author) || item.author.trim() !== item.author
204
+ || /[\\/:*?"<>|\x00-\x1f]/.test(item.author) || /[. ]$/.test(item.author) || !ownerValid(item.owner))
205
+ throw new Error('invalid-source-participants');
206
+ const identity = JSON.stringify([item.author, item.owner.level, item.owner.path]);
207
+ if (identities.has(identity))
208
+ throw new Error('invalid-source-participants');
209
+ identities.add(identity);
210
+ return { author: item.author, owner: { ...item.owner } };
211
+ });
212
+ return result.sort((a, b) => JSON.stringify(a).localeCompare(JSON.stringify(b)));
213
+ }
214
+ function sourceAuthorAllowed(context, author, owner) {
215
+ return author === context.reviewer || (context.owner.level === 'Org'
216
+ && !!context.sourceParticipants?.some(grant => grant.author === author && sameOwner(grant.owner, owner)));
217
+ }
25
218
  function decodeCompletion(markdown, expected = {}) {
26
219
  const fail = () => { throw new Error('invalid-completion-decisions'); };
27
220
  if ((markdown.match(/<!--\s*fraim-sleep-decisions:/g) || []).length !== 1 || markdown.split(exports.COMPLETION_START).length !== 2 || markdown.split(exports.COMPLETION_END).length !== 2)
@@ -40,16 +233,23 @@ function decodeCompletion(markdown, expected = {}) {
40
233
  catch {
41
234
  fail();
42
235
  }
43
- if (!keys(state, ['schemaVersion', 'runId', 'owner', 'finalizationState', 'decisions', 'sourceDispositions', 'cleanup', 'reconciliations', 'retrospective', 'processedSources']) || state.schemaVersion !== 1 || !text(state.runId) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(state.runId) || !ownerValid(state.owner)
236
+ if (!keys(state, ['schemaVersion', 'runId', 'owner', 'finalizationState', 'decisions', 'sourceDispositions', 'cleanup', 'reconciliations', 'retrospective', 'processedSources', 'sourceParticipants']) || state.schemaVersion !== 1 || !text(state.runId) || !/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(state.runId) || !ownerValid(state.owner)
44
237
  || !['pending', 'complete'].includes(state.finalizationState) || !Array.isArray(state.decisions) || !Array.isArray(state.sourceDispositions) || !Array.isArray(state.cleanup))
45
238
  fail();
46
239
  if ((expected.runId && expected.runId !== state.runId) || (expected.owner && !sameOwner(expected.owner, state.owner)))
47
240
  fail();
48
241
  if (state.retrospective !== undefined && (!keys(state.retrospective, ['relativePath', 'sha256'])
49
- || state.retrospective.relativePath !== `retrospectives/${state.runId}-sleep-postmortem.md` || !hash(state.retrospective.sha256)))
242
+ || typeof state.retrospective.relativePath !== 'string' || node_path_1.default.isAbsolute(state.retrospective.relativePath)
243
+ || state.retrospective.relativePath.includes('\\') || state.retrospective.relativePath.includes(':')
244
+ || /[\x00-\x1f]/.test(state.retrospective.relativePath)
245
+ || state.retrospective.relativePath.split('/').some((part) => !part || part === '.' || part === '..')
246
+ || !node_path_1.default.basename(state.retrospective.relativePath).endsWith(`-${state.runId}-${state.owner.level === 'Org' ? 'organizational-learning-synthesis' : 'sleep-on-learnings'}-retrospective.md`)
247
+ || !hash(state.retrospective.sha256)))
50
248
  fail();
51
249
  if (state.processedSources !== undefined)
52
250
  validateProcessedSources(state.processedSources);
251
+ if (state.sourceParticipants !== undefined)
252
+ normalizeSourceParticipants(state.sourceParticipants, state.owner);
53
253
  const ids = new Set();
54
254
  for (const item of state.decisions) {
55
255
  if (!keys(item, ['actionId', 'revision', 'digest', 'owner', 'claimKey', 'evidenceFingerprint', 'sourceRefs', 'decision', 'publicationRef', 'target', 'dependencies']) || !text(item.actionId) || ids.has(item.actionId) || !Number.isInteger(item.revision) || item.revision < 1 || !hash(item.digest) || !ownerValid(item.owner)
@@ -0,0 +1,217 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.WORKSPACE_FRAIM_DIRNAME = exports.LEARNING_DOMAINS = void 0;
7
+ exports.getWorkspaceFraimDir = getWorkspaceFraimDir;
8
+ exports.getWorkspaceConfigPath = getWorkspaceConfigPath;
9
+ exports.getWorkspaceFraimPath = getWorkspaceFraimPath;
10
+ exports.getWorkspaceFraimDisplayPath = getWorkspaceFraimDisplayPath;
11
+ exports.getUserFraimDirPath = getUserFraimDirPath;
12
+ exports.getUserFraimDisplayPath = getUserFraimDisplayPath;
13
+ exports.getUserFraimLearningsDir = getUserFraimLearningsDir;
14
+ exports.getWorkspaceLearningsDir = getWorkspaceLearningsDir;
15
+ exports.getConfiguredPortableLearningsDir = getConfiguredPortableLearningsDir;
16
+ exports.getConfiguredPortableLearningsDisplayPath = getConfiguredPortableLearningsDisplayPath;
17
+ exports.readBrandFromDir = readBrandFromDir;
18
+ exports.resolvePackHome = resolvePackHome;
19
+ exports.readJsonIfObject = readJsonIfObject;
20
+ exports.isPathInsideOrEqual = isPathInsideOrEqual;
21
+ const fs_1 = require("fs");
22
+ const os_1 = __importDefault(require("os"));
23
+ const path_1 = require("path");
24
+ /** Share domain data with the MCP runtime and proposal authoring helpers. */
25
+ exports.LEARNING_DOMAINS = Object.freeze(JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '../learning/learning-domains.json'), 'utf8')).domains);
26
+ exports.WORKSPACE_FRAIM_DIRNAME = 'fraim';
27
+ function getWorkspaceFraimDir(projectRoot = process.cwd()) {
28
+ return (0, path_1.join)(projectRoot, exports.WORKSPACE_FRAIM_DIRNAME);
29
+ }
30
+ function getWorkspaceConfigPath(projectRoot = process.cwd()) {
31
+ return (0, path_1.join)(getWorkspaceFraimDir(projectRoot), 'config.json');
32
+ }
33
+ function getWorkspaceFraimPath(projectRoot = process.cwd(), ...parts) {
34
+ return (0, path_1.join)(getWorkspaceFraimDir(projectRoot), ...parts);
35
+ }
36
+ function getWorkspaceFraimDisplayPath(relativePath = '') {
37
+ const normalized = normalizeRelativePath(relativePath);
38
+ return normalized.length > 0 ? `${exports.WORKSPACE_FRAIM_DIRNAME}/${normalized}` : `${exports.WORKSPACE_FRAIM_DIRNAME}/`;
39
+ }
40
+ function getUserFraimDirPath() {
41
+ const override = process.env.FRAIM_USER_DIR;
42
+ if (override)
43
+ return override;
44
+ if (process.env.FRAIM_TEST_SANDBOX === '1') {
45
+ throw new Error('FRAIM_USER_DIR is unset inside a sandboxed test run (FRAIM_TEST_SANDBOX=1). ' +
46
+ 'Refusing to fall back to the real ~/.fraim.');
47
+ }
48
+ return (0, path_1.join)(os_1.default.homedir(), '.fraim');
49
+ }
50
+ function getUserFraimDisplayPath(relativePath = '') {
51
+ const normalized = normalizeRelativePath(relativePath);
52
+ return normalized.length > 0 ? `~/.fraim/${normalized}` : '~/.fraim/';
53
+ }
54
+ function getUserFraimLearningsDir() {
55
+ return (0, path_1.join)(getUserFraimDirPath(), 'personalized-employee', 'learnings');
56
+ }
57
+ function getWorkspaceLearningsDir(projectRoot = process.cwd()) {
58
+ return getWorkspaceFraimPath(projectRoot, 'personalized-employee', 'learnings');
59
+ }
60
+ function getConfiguredPortableLearningsDir(projectRoot = process.cwd()) {
61
+ const config = readWorkspaceConfig(projectRoot);
62
+ const configured = resolveConfiguredLearningGlobalPath(config?.learning?.globalPath);
63
+ return configured || getUserFraimLearningsDir();
64
+ }
65
+ function getConfiguredPortableLearningsDisplayPath(projectRoot = process.cwd()) {
66
+ const config = readWorkspaceConfig(projectRoot);
67
+ const configured = resolveConfiguredLearningGlobalPath(config?.learning?.globalPath);
68
+ if (configured)
69
+ return normalizeDisplayPath(configured);
70
+ return getUserFraimDisplayPath('personalized-employee/learnings').replace(/\/$/, '');
71
+ }
72
+ function readBrandFromDir(dir) {
73
+ const brandPath = (0, path_1.join)(dir, 'org_brand.json');
74
+ try {
75
+ if (!(0, fs_1.existsSync)(brandPath))
76
+ return null;
77
+ const parsed = JSON.parse((0, fs_1.readFileSync)(brandPath, 'utf8'));
78
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ function resolvePackHome(layer) {
85
+ const config = getLayerBackend(layer);
86
+ const standard = standardHome(layer);
87
+ const make = (root, extra) => ({
88
+ layer,
89
+ root,
90
+ contentRoot: (0, path_1.join)(root, 'personalized-employee'),
91
+ ...extra,
92
+ });
93
+ if (!config || config.backend === 'single-machine') {
94
+ return make(standard, {
95
+ backend: config?.backend ?? 'single-machine',
96
+ synced: false,
97
+ requiresPublish: false,
98
+ });
99
+ }
100
+ if (config.backend === 'local-folder' && config.localPath) {
101
+ return make(absolutize(config.localPath), {
102
+ backend: 'local-folder',
103
+ synced: true,
104
+ requiresPublish: false,
105
+ legacyRoot: standard,
106
+ });
107
+ }
108
+ if (config.backend === 'git' && config.gitUrl) {
109
+ return make(config.localPath ? absolutize(config.localPath) : defaultCloneDir(layer), {
110
+ backend: 'git',
111
+ synced: true,
112
+ requiresPublish: true,
113
+ gitUrl: config.gitUrl,
114
+ legacyRoot: standard,
115
+ });
116
+ }
117
+ if (config.backend === 'fraim-cloud') {
118
+ return make(cloudHome(layer), {
119
+ backend: 'fraim-cloud',
120
+ synced: true,
121
+ requiresPublish: true,
122
+ legacyRoot: standard,
123
+ });
124
+ }
125
+ return make(standard, {
126
+ backend: config.backend,
127
+ synced: false,
128
+ requiresPublish: false,
129
+ });
130
+ }
131
+ function normalizeRelativePath(relativePath) {
132
+ return relativePath.replace(/^[\\/]+/, '').replace(/[\\/]+/g, '/');
133
+ }
134
+ function normalizeDisplayPath(fullPath) {
135
+ return fullPath.replace(/\\/g, '/');
136
+ }
137
+ /** Existing config reader; strict consumers reject damaged configuration instead of guessing defaults. */
138
+ function readJsonIfObject(filePath, options = {}) {
139
+ try {
140
+ if (!options.strict && !(0, fs_1.existsSync)(filePath))
141
+ return {};
142
+ const content = (0, fs_1.readFileSync)(filePath, 'utf8');
143
+ const parsed = JSON.parse(options.strict ? content.replace(/^\uFEFF/, '') : content);
144
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
145
+ return parsed;
146
+ if (options.strict)
147
+ throw new Error(`Invalid configuration object: ${filePath}`);
148
+ return {};
149
+ }
150
+ catch (error) {
151
+ if (options.strict && error.code !== 'ENOENT')
152
+ throw error;
153
+ return {};
154
+ }
155
+ }
156
+ function readWorkspaceConfig(projectRoot) {
157
+ return readJsonIfObject(getWorkspaceConfigPath(projectRoot));
158
+ }
159
+ function readUserFraimConfig() {
160
+ return readJsonIfObject((0, path_1.join)(getUserFraimDirPath(), 'config.json'));
161
+ }
162
+ function getLayerBackend(layer) {
163
+ const raw = (layer === 'manager'
164
+ ? readUserFraimConfig().managerStorage
165
+ : readUserFraimConfig().organization);
166
+ if (!raw || typeof raw !== 'object')
167
+ return null;
168
+ const backend = raw.backend;
169
+ if (backend !== 'single-machine' && backend !== 'local-folder' && backend !== 'git' && backend !== 'fraim-cloud') {
170
+ return null;
171
+ }
172
+ const localPath = typeof raw.localPath === 'string' ? raw.localPath.trim() : '';
173
+ const gitUrl = typeof raw.gitUrl === 'string' ? raw.gitUrl.trim() : '';
174
+ const id = typeof raw.id === 'string' ? raw.id : undefined;
175
+ if (backend === 'local-folder')
176
+ return localPath ? { backend, localPath, id } : null;
177
+ if (backend === 'git')
178
+ return gitUrl ? { backend, gitUrl, id, ...(localPath ? { localPath } : {}) } : null;
179
+ return { backend, id };
180
+ }
181
+ function resolveConfiguredLearningGlobalPath(rawPath) {
182
+ if (typeof rawPath !== 'string')
183
+ return null;
184
+ const trimmed = rawPath.trim();
185
+ if (!trimmed)
186
+ return null;
187
+ if (/^[a-z]+:\/\//i.test(trimmed))
188
+ return null;
189
+ if (trimmed.startsWith('~')) {
190
+ const homeRelative = trimmed.slice(1).replace(/^[\\/]+/, '');
191
+ return (0, path_1.normalize)((0, path_1.join)(os_1.default.homedir(), homeRelative));
192
+ }
193
+ if (!(0, path_1.isAbsolute)(trimmed))
194
+ return null;
195
+ return (0, path_1.normalize)(trimmed);
196
+ }
197
+ function absolutize(p) {
198
+ return (0, path_1.isAbsolute)(p) ? p : (0, path_1.resolve)(getUserFraimDirPath(), p);
199
+ }
200
+ function packsDir() {
201
+ return (0, path_1.join)(getUserFraimDirPath(), 'packs');
202
+ }
203
+ function standardHome(layer) {
204
+ if (layer === 'manager')
205
+ return getUserFraimDirPath();
206
+ return (0, path_1.join)(getUserFraimDirPath(), 'org');
207
+ }
208
+ function cloudHome(layer) {
209
+ return (0, path_1.join)(getUserFraimDirPath(), layer === 'org' ? 'org' : 'manager');
210
+ }
211
+ function defaultCloneDir(layer) {
212
+ return (0, path_1.join)(packsDir(), `${layer}-repo`);
213
+ }
214
+ function isPathInsideOrEqual(parentPath, childPath) {
215
+ const rel = (0, path_1.relative)(parentPath, childPath);
216
+ return rel === '' || (rel.length > 0 && !rel.startsWith('..') && !(0, path_1.isAbsolute)(rel));
217
+ }
@@ -14,7 +14,6 @@ const platform_detection_1 = require("../utils/platform-detection");
14
14
  const ide_detector_1 = require("../setup/ide-detector");
15
15
  const claude_code_telemetry_1 = require("../setup/claude-code-telemetry");
16
16
  const fraim_gitignore_1 = require("../utils/fraim-gitignore");
17
- const agent_adapters_1 = require("../utils/agent-adapters");
18
17
  const project_fraim_paths_1 = require("../../core/utils/project-fraim-paths");
19
18
  const project_bootstrap_1 = require("../utils/project-bootstrap");
20
19
  const user_config_1 = require("../utils/user-config");
@@ -148,9 +147,6 @@ const runInitProject = async (options = {}) => {
148
147
  }
149
148
  [
150
149
  'jobs',
151
- 'ai-employee/jobs',
152
- 'ai-employee/skills',
153
- 'ai-manager/jobs',
154
150
  'personalized-employee',
155
151
  ].forEach((dir) => {
156
152
  const dirPath = path_1.default.join(fraimDir, dir);
@@ -172,7 +168,7 @@ const runInitProject = async (options = {}) => {
172
168
  console.log(chalk_1.default.green('Removed legacy FRAIM sync block from .gitignore'));
173
169
  }
174
170
  if (!process.env.FRAIM_SKIP_SYNC) {
175
- await (0, sync_1.runSync)({ projectRoot, failHard, projectAdapters: options.projectAdapters });
171
+ await (0, sync_1.runSync)({ projectRoot, failHard });
176
172
  result.syncPerformed = true;
177
173
  }
178
174
  else {
@@ -191,16 +187,10 @@ const runInitProject = async (options = {}) => {
191
187
  if (claudeCodeAvailable) {
192
188
  (0, claude_code_telemetry_1.ensureClaudeCodeTelemetryEnv)();
193
189
  }
194
- if (options.projectAdapters) {
195
- const adapterUpdates = (0, agent_adapters_1.ensureAgentAdapterFiles)(projectRoot, detectedConfigTypes.length > 0 ? detectedConfigTypes : null);
196
- if (adapterUpdates.length > 0) {
197
- console.log(chalk_1.default.green(`Updated FRAIM agent adapter files: ${adapterUpdates.join(', ')}`));
198
- }
199
- }
200
190
  (0, project_bootstrap_1.printInitProjectSummary)(result);
201
191
  };
202
192
  exports.runInitProject = runInitProject;
203
193
  exports.initProjectCommand = new commander_1.Command('init-project')
204
194
  .description('Initialize FRAIM in the current project (requires global setup)')
205
- .option('--project-adapters', 'Write legacy project-local FRAIM agent adapter files')
195
+ .option('--project-adapters', 'Deprecated compatibility option (no effect; use machine-level FRAIM setup)')
206
196
  .action((options) => (0, exports.runInitProject)({ projectAdapters: Boolean(options.projectAdapters) }));