fraim 2.0.281 → 2.0.283

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.
@@ -14,9 +14,9 @@ exports.buildAntigravityCommandContent = buildAntigravityCommandContent;
14
14
  exports.buildGeminiCommandContent = buildGeminiCommandContent;
15
15
  exports.describeInvocationSurface = describeInvocationSurface;
16
16
  exports.FRAIM_LAUNCH_PHRASE = 'Use FRAIM for <job or task>';
17
- exports.CURSOR_MDC_FRONTMATTER = `---
18
- description: FRAIM discovery and execution contract
19
- alwaysApply: true
17
+ exports.CURSOR_MDC_FRONTMATTER = `---
18
+ description: FRAIM discovery and execution contract
19
+ alwaysApply: true
20
20
  ---`;
21
21
  exports.FRAIM_DEFERRED_TOOL_PRELOAD = [
22
22
  'fraim_connect',
@@ -38,112 +38,112 @@ function buildDeferredToolBootstrapSection(profile) {
38
38
  const preloadList = exports.FRAIM_DEFERRED_TOOL_PRELOAD.map((toolName) => `\`${toolName}\``).join(', ');
39
39
  switch (profile) {
40
40
  case 'claude-toolsearch':
41
- return `0. **Preload deferred FRAIM tools when needed**:
42
- - If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, call \`ToolSearch\` once to load ${preloadList}.
43
- - Do the preload as one batched discovery step, not one search per tool.
44
-
41
+ return `0. **Preload deferred FRAIM tools when needed**:
42
+ - If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, call \`ToolSearch\` once to load ${preloadList}.
43
+ - Do the preload as one batched discovery step, not one search per tool.
44
+
45
45
  `;
46
46
  case 'codex-tool-search':
47
- return `0. **Preload deferred FRAIM tools when needed**:
48
- - If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, call \`tool_search\` once to load ${preloadList}.
49
- - Do the preload as one batched discovery step, not one search per tool.
50
-
47
+ return `0. **Preload deferred FRAIM tools when needed**:
48
+ - If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, call \`tool_search\` once to load ${preloadList}.
49
+ - Do the preload as one batched discovery step, not one search per tool.
50
+
51
51
  `;
52
52
  case 'generic-tool-discovery':
53
- return `0. **Preload deferred FRAIM tools when needed**:
54
- - If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, use the host's tool discovery surface once to load ${preloadList}.
55
- - Do the preload as one batched discovery step, not one search per tool.
56
-
53
+ return `0. **Preload deferred FRAIM tools when needed**:
54
+ - If FRAIM MCP tools are unavailable because this host lazily loads deferred tool schemas, use the host's tool discovery surface once to load ${preloadList}.
55
+ - Do the preload as one batched discovery step, not one search per tool.
56
+
57
57
  `;
58
58
  default:
59
59
  return '';
60
60
  }
61
61
  }
62
62
  function buildFraimInvocationBody(profile = 'none') {
63
- return `Follow this process:
64
-
65
- ${buildDeferredToolBootstrapSection(profile)}1. **Confirm FRAIM activation**:
66
- Use this process only when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job. For ordinary requests, answer or work normally; do not scan FRAIM stubs first.
67
-
68
- 2. **If the user did not specify a FRAIM job or topic after activation**:
69
- If local FRAIM job stubs are present in the workspace, inspect those first and match the request locally. Also inspect \`fraim/personalized-employee/jobs/\` for local overrides or repo-specific jobs. If local files are missing or you cannot inspect workspace files, call \`list_fraim_jobs()\` to view the full catalog, including any proxy-discoverable personalized jobs.
70
-
71
- 3. **Find the match**:
72
- If the user names an exact FRAIM job, call \`get_fraim_job({ job: "<job-name>" })\` directly. Otherwise, match the user's request to a FRAIM job from the local stub catalog, \`fraim/personalized-employee/jobs/\`, or the full \`list_fraim_jobs()\` response. If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification. Do not pick the nearest catalog job.
73
-
74
- 4. **Load the full content**:
75
- - For jobs, call \`get_fraim_job({ job: "<matched-job-name>" })\`.
76
- - For skills, use the content returned by \`get_fraim_file(...)\`.
77
-
78
- 5. **Execute**:
79
- - For jobs, follow the phased instructions and use \`seekMentoring\` when the job requires phase transitions.
80
- - For skills, apply the skill steps directly to the user's current context.
81
- - ${exports.FRAIM_MCP_UNAVAILABLE_MANAGER_GUIDANCE}
63
+ return `Follow this process:
64
+
65
+ ${buildDeferredToolBootstrapSection(profile)}1. **Confirm FRAIM activation**:
66
+ Use this process only when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job. For ordinary requests, answer or work normally; do not scan FRAIM stubs first.
67
+
68
+ 2. **If the user did not specify a FRAIM job or topic after activation**:
69
+ If local FRAIM job stubs are present in the workspace, inspect those first and match the request locally. Also inspect \`fraim/personalized-employee/jobs/\` for local overrides or repo-specific jobs. If local files are missing or you cannot inspect workspace files, call \`list_fraim_jobs()\` to view the full catalog, including any proxy-discoverable personalized jobs.
70
+
71
+ 3. **Find the match**:
72
+ If the user names an exact FRAIM job, call \`get_fraim_job({ job: "<job-name>" })\` directly. Otherwise, match the user's request to a FRAIM job from the local stub catalog, \`fraim/personalized-employee/jobs/\`, or the full \`list_fraim_jobs()\` response. If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification. Do not pick the nearest catalog job.
73
+
74
+ 4. **Load the full content**:
75
+ - For jobs, call \`get_fraim_job({ job: "<matched-job-name>" })\`.
76
+ - For skills, use the content returned by \`get_fraim_file(...)\`.
77
+
78
+ 5. **Execute**:
79
+ - For jobs, follow the phased instructions and use \`seekMentoring\` when the job requires phase transitions.
80
+ - For skills, apply the skill steps directly to the user's current context.
81
+ - ${exports.FRAIM_MCP_UNAVAILABLE_MANAGER_GUIDANCE}
82
82
  `;
83
83
  }
84
84
  exports.FRAIM_INVOCATION_BODY = buildFraimInvocationBody();
85
85
  function buildClaudeSkillContent() {
86
- return `# FRAIM
87
-
86
+ return `# FRAIM
87
+
88
88
  ${buildFraimInvocationBody('claude-toolsearch')}`;
89
89
  }
90
90
  function buildClaudeCommandShimContent() {
91
- return `# FRAIM Compatibility Command
92
-
93
- Use the FRAIM skill when Claude exposes skills directly. This compatibility command keeps \`/fraim\` working on surfaces that still discover legacy command files.
94
-
91
+ return `# FRAIM Compatibility Command
92
+
93
+ Use the FRAIM skill when Claude exposes skills directly. This compatibility command keeps \`/fraim\` working on surfaces that still discover legacy command files.
94
+
95
95
  ${buildFraimInvocationBody('claude-toolsearch')}`;
96
96
  }
97
97
  function buildClaudeSlashCommandContent() {
98
98
  return buildClaudeCommandShimContent();
99
99
  }
100
100
  function buildCursorMentionRuleContent() {
101
- return `${exports.CURSOR_MDC_FRONTMATTER}
102
-
103
- # FRAIM
104
-
105
- ${buildFraimInvocationBody('generic-tool-discovery')}
101
+ return `${exports.CURSOR_MDC_FRONTMATTER}
102
+
103
+ # FRAIM
104
+
105
+ ${buildFraimInvocationBody('generic-tool-discovery')}
106
106
  `;
107
107
  }
108
- exports.CODEX_SKILL_FRONTMATTER = `---
109
- name: fraim
110
- description: Discover and execute FRAIM jobs and skills from Codex.
108
+ exports.CODEX_SKILL_FRONTMATTER = `---
109
+ name: fraim
110
+ description: Discover and execute FRAIM jobs and skills from Codex.
111
111
  ---`;
112
112
  function buildCodexSkillContent() {
113
- return `${exports.CODEX_SKILL_FRONTMATTER}
114
- # FRAIM
115
-
113
+ return `${exports.CODEX_SKILL_FRONTMATTER}
114
+ # FRAIM
115
+
116
116
  ${buildFraimInvocationBody('codex-tool-search')}`;
117
117
  }
118
118
  function buildGrokSkillContent() {
119
- return `# FRAIM
120
-
119
+ return `# FRAIM
120
+
121
121
  ${buildFraimInvocationBody('generic-tool-discovery')}`;
122
122
  }
123
123
  function buildWindsurfCommandContent() {
124
- return `# FRAIM
125
-
124
+ return `# FRAIM
125
+
126
126
  ${buildFraimInvocationBody('generic-tool-discovery')}`;
127
127
  }
128
128
  function buildKiroCommandContent() {
129
- return `# FRAIM
130
-
129
+ return `# FRAIM
130
+
131
131
  ${buildFraimInvocationBody('generic-tool-discovery')}`;
132
132
  }
133
133
  function escapeTomlMultiline(value) {
134
134
  return value.replace(/"""/g, '\\"""');
135
135
  }
136
136
  function buildAntigravityCommandContent() {
137
- return `# FRAIM
138
-
137
+ return `# FRAIM
138
+
139
139
  ${buildFraimInvocationBody('generic-tool-discovery')}`;
140
140
  }
141
141
  function buildGeminiCommandContent() {
142
- return `description = "Discover and execute FRAIM jobs and skills"
143
- prompt = """
144
- # FRAIM
145
-
146
- ${escapeTomlMultiline(buildFraimInvocationBody('generic-tool-discovery'))}
142
+ return `description = "Discover and execute FRAIM jobs and skills"
143
+ prompt = """
144
+ # FRAIM
145
+
146
+ ${escapeTomlMultiline(buildFraimInvocationBody('generic-tool-discovery'))}
147
147
  """`;
148
148
  }
149
149
  function describeInvocationSurface(ideName, invocationProfile) {
@@ -25,9 +25,9 @@ function adapterConfigTypes(file) {
25
25
  return Array.isArray(file.configType) ? file.configType : [file.configType];
26
26
  }
27
27
  function buildManagedSection(body) {
28
- return `${START_MARKER}
29
- ${body.trim()}
30
- ${END_MARKER}
28
+ return `${START_MARKER}
29
+ ${body.trim()}
30
+ ${END_MARKER}
31
31
  `;
32
32
  }
33
33
  function mergeManagedSection(existingContent, managedSection) {
@@ -59,69 +59,69 @@ function getAdapterFiles(allowedConfigTypes = null) {
59
59
  const employeeRulesPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('ai-employee/rules');
60
60
  const personalizedRootPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee');
61
61
  const projectRulesPath = (0, project_fraim_paths_1.getWorkspaceFraimDisplayPath)('personalized-employee/rules/project_rules.md');
62
- const markdownBody = buildManagedSection(`
63
- ## FRAIM
64
-
65
- This repository uses FRAIM.
66
-
67
- - The FRAIM discovery catalog lives under \`${fraimRoot}/\`.
68
- - Jobs under \`${employeeJobsPath}/\` and \`${managerJobsPath}/\` are FRAIM's primary execution units. Treat them like first-class workflows when deciding how to execute work.
69
- - Skills under \`${employeeSkillsPath}/\` are reusable capabilities that jobs compose.
70
- - Rules under \`${employeeRulesPath}/\` are always-on constraints and conventions.
71
- - Repo-specific overrides and learning artifacts live under \`${personalizedRootPath}/\` and take precedence over synced baseline content.
72
- - Use FRAIM when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job.
73
- - For ordinary requests, answer or work normally. Do not scan FRAIM stubs first.
74
- - If the user names an exact FRAIM job, call \`get_fraim_job({ job: "<job-name>" })\` directly.
75
- - When FRAIM routing is active but the job is not exact, scan the job stubs under \`${employeeJobsPath}/\` and \`${managerJobsPath}/\` to identify the most appropriate job. Read stub filenames and their Intent/Outcome sections before using catalog tools.
76
- - Once you identify the relevant job, call \`get_fraim_job({ job: "<job-name>" })\` to get the full phased instructions.
77
- - If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification.
78
- - For deeper capability detail, call \`get_fraim_file({ path: "skills/<category>/<skill-name>.md" })\` or \`get_fraim_file({ path: "rules/<category>/<rule-name>.md" })\`.
79
- - Read \`${projectRulesPath}\` if it exists before doing work.
80
- - When users ask for next step recommendations, use recommend-next-job skill under \`${employeeSkillsPath}/\` to gather context before suggesting jobs.
81
- - ${ide_invocation_surfaces_1.FRAIM_MCP_UNAVAILABLE_MANAGER_GUIDANCE}
82
-
83
- > [!IMPORTANT]
84
- > **Job stubs are for discovery only.** When a user mentions or references any file under \`${employeeJobsPath}/\` or \`${managerJobsPath}/\`, do NOT attempt to execute the job from the stub content. The stub only shows intent and phase names. Always call \`get_fraim_job({ job: "<job-name>" })\` first to get the full phased instructions before doing any work.
62
+ const markdownBody = buildManagedSection(`
63
+ ## FRAIM
64
+
65
+ This repository uses FRAIM.
66
+
67
+ - The FRAIM discovery catalog lives under \`${fraimRoot}/\`.
68
+ - Jobs under \`${employeeJobsPath}/\` and \`${managerJobsPath}/\` are FRAIM's primary execution units. Treat them like first-class workflows when deciding how to execute work.
69
+ - Skills under \`${employeeSkillsPath}/\` are reusable capabilities that jobs compose.
70
+ - Rules under \`${employeeRulesPath}/\` are always-on constraints and conventions.
71
+ - Repo-specific overrides and learning artifacts live under \`${personalizedRootPath}/\` and take precedence over synced baseline content.
72
+ - Use FRAIM when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job.
73
+ - For ordinary requests, answer or work normally. Do not scan FRAIM stubs first.
74
+ - If the user names an exact FRAIM job, call \`get_fraim_job({ job: "<job-name>" })\` directly.
75
+ - When FRAIM routing is active but the job is not exact, scan the job stubs under \`${employeeJobsPath}/\` and \`${managerJobsPath}/\` to identify the most appropriate job. Read stub filenames and their Intent/Outcome sections before using catalog tools.
76
+ - Once you identify the relevant job, call \`get_fraim_job({ job: "<job-name>" })\` to get the full phased instructions.
77
+ - If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification.
78
+ - For deeper capability detail, call \`get_fraim_file({ path: "skills/<category>/<skill-name>.md" })\` or \`get_fraim_file({ path: "rules/<category>/<rule-name>.md" })\`.
79
+ - Read \`${projectRulesPath}\` if it exists before doing work.
80
+ - When users ask for next step recommendations, use recommend-next-job skill under \`${employeeSkillsPath}/\` to gather context before suggesting jobs.
81
+ - ${ide_invocation_surfaces_1.FRAIM_MCP_UNAVAILABLE_MANAGER_GUIDANCE}
82
+
83
+ > [!IMPORTANT]
84
+ > **Job stubs are for discovery only.** When a user mentions or references any file under \`${employeeJobsPath}/\` or \`${managerJobsPath}/\`, do NOT attempt to execute the job from the stub content. The stub only shows intent and phase names. Always call \`get_fraim_job({ job: "<job-name>" })\` first to get the full phased instructions before doing any work.
85
85
  `);
86
- const cursorManagedBody = buildManagedSection(`
87
- # FRAIM
88
-
89
- ${(0, ide_invocation_surfaces_1.buildFraimInvocationBody)('generic-tool-discovery')}
86
+ const cursorManagedBody = buildManagedSection(`
87
+ # FRAIM
88
+
89
+ ${(0, ide_invocation_surfaces_1.buildFraimInvocationBody)('generic-tool-discovery')}
90
90
  `);
91
- const copilotBody = buildManagedSection(`
92
- ## FRAIM
93
-
94
- - Use \`${fraimRoot}/\` as the repository's FRAIM catalog.
95
- - FRAIM jobs are the primary execution units and should be treated like first-class workflows.
96
- - FRAIM skills are reusable capabilities jobs compose.
97
- - FRAIM rules are always-on constraints and conventions.
98
- - Repo-specific overrides and learnings live under \`${personalizedRootPath}/\`.
99
- - Use FRAIM when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job.
100
- - For ordinary requests, answer or work normally. Do not scan FRAIM stubs first.
101
- - If the user names an exact FRAIM job, fetch that full job directly with FRAIM MCP tools.
102
- - When FRAIM routing is active but the job is not exact, use local stubs to identify which job to invoke before fetching full content with FRAIM MCP tools.
103
- - If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification.
104
- - **Job stubs are for discovery only.** Never execute a job from stub content - always call \`get_fraim_job({ job: "<job-name>" })\` first.
105
- - ${ide_invocation_surfaces_1.FRAIM_MCP_UNAVAILABLE_MANAGER_GUIDANCE}
91
+ const copilotBody = buildManagedSection(`
92
+ ## FRAIM
93
+
94
+ - Use \`${fraimRoot}/\` as the repository's FRAIM catalog.
95
+ - FRAIM jobs are the primary execution units and should be treated like first-class workflows.
96
+ - FRAIM skills are reusable capabilities jobs compose.
97
+ - FRAIM rules are always-on constraints and conventions.
98
+ - Repo-specific overrides and learnings live under \`${personalizedRootPath}/\`.
99
+ - Use FRAIM when the user explicitly invokes FRAIM, names a FRAIM job, asks what FRAIM job to run, or the active surface has already selected a FRAIM job.
100
+ - For ordinary requests, answer or work normally. Do not scan FRAIM stubs first.
101
+ - If the user names an exact FRAIM job, fetch that full job directly with FRAIM MCP tools.
102
+ - When FRAIM routing is active but the job is not exact, use local stubs to identify which job to invoke before fetching full content with FRAIM MCP tools.
103
+ - If no exact or high-confidence job match exists, say that no FRAIM job matches and continue with normal tools or ask one concise clarification.
104
+ - **Job stubs are for discovery only.** Never execute a job from stub content - always call \`get_fraim_job({ job: "<job-name>" })\` first.
105
+ - ${ide_invocation_surfaces_1.FRAIM_MCP_UNAVAILABLE_MANAGER_GUIDANCE}
106
106
  `);
107
- const fraimReadme = `# FRAIM Catalog
108
-
109
- This directory is the repository-visible FRAIM surface.
110
-
111
- - \`ai-employee/jobs/\`: employee job stubs
112
- - \`ai-manager/jobs/\`: manager job stubs
113
- - \`ai-employee/skills/\`: skill stubs
114
- - \`ai-employee/rules/\`: rule stubs
115
- - \`personalized-employee/\`: repo-specific overrides and learnings
116
-
117
- When FRAIM routing is active and no exact FRAIM job is named, use the stubs here to discover which FRAIM job, skill, or rule is relevant, then load the full content through FRAIM MCP tools. If an exact FRAIM job is named, load it directly. For ordinary requests, do not scan this catalog first.
107
+ const fraimReadme = `# FRAIM Catalog
108
+
109
+ This directory is the repository-visible FRAIM surface.
110
+
111
+ - \`ai-employee/jobs/\`: employee job stubs
112
+ - \`ai-manager/jobs/\`: manager job stubs
113
+ - \`ai-employee/skills/\`: skill stubs
114
+ - \`ai-employee/rules/\`: rule stubs
115
+ - \`personalized-employee/\`: repo-specific overrides and learnings
116
+
117
+ When FRAIM routing is active and no exact FRAIM job is named, use the stubs here to discover which FRAIM job, skill, or rule is relevant, then load the full content through FRAIM MCP tools. If an exact FRAIM job is named, load it directly. For ordinary requests, do not scan this catalog first.
118
118
  `;
119
- const vscodePrompt = `# FRAIM
120
-
119
+ const vscodePrompt = `# FRAIM
120
+
121
121
  ${(0, ide_invocation_surfaces_1.buildFraimInvocationBody)('generic-tool-discovery')}`;
122
- const geminiProjectInstructions = `# Gemini Project Instructions
123
-
124
- @../AGENTS.md
122
+ const geminiProjectInstructions = `# Gemini Project Instructions
123
+
124
+ @../AGENTS.md
125
125
  `;
126
126
  const all = [
127
127
  { path: 'AGENTS.md', content: markdownBody, configType: 'standard' },
@@ -273,48 +273,48 @@ function validateHandoffContracts(args) {
273
273
  // ---------------------------------------------------------------------------
274
274
  // Rejection message builder
275
275
  // ---------------------------------------------------------------------------
276
- const ADDRESS_FEEDBACK_APPROVAL_SCHEMA = `\`\`\`javascript
277
- evidence: {
278
- approved: true // Set only after the manager explicitly approves via the Hub review action
279
- }
276
+ const ADDRESS_FEEDBACK_APPROVAL_SCHEMA = `\`\`\`javascript
277
+ evidence: {
278
+ approved: true // Set only after the manager explicitly approves via the Hub review action
279
+ }
280
280
  \`\`\``;
281
- const REVIEW_HANDOFF_SCHEMA = `\`\`\`javascript
282
- evidence: {
283
- reviewHandoff: {
284
- reviewRequired: true,
285
- reviewTarget: { kind: "pull_request", url: "<PR URL>" } | { kind: "artifact_set", files: ["<path>"] } | null,
286
- artifacts: [{ label: "<label>", path: "<path>", kind: "<kind>" }],
287
- summary: "<optional summary>",
288
- reviewActions: [
289
- { kind: "approve", label: "Approve" },
290
- { kind: "request_changes", label: "Request Changes" }
291
- ]
292
- }
293
- }
281
+ const REVIEW_HANDOFF_SCHEMA = `\`\`\`javascript
282
+ evidence: {
283
+ reviewHandoff: {
284
+ reviewRequired: true,
285
+ reviewTarget: { kind: "pull_request", url: "<PR URL>" } | { kind: "artifact_set", files: ["<path>"] } | null,
286
+ artifacts: [{ label: "<label>", path: "<path>", kind: "<kind>" }],
287
+ summary: "<optional summary>",
288
+ reviewActions: [
289
+ { kind: "approve", label: "Approve" },
290
+ { kind: "request_changes", label: "Request Changes" }
291
+ ]
292
+ }
293
+ }
294
294
  \`\`\``;
295
- const NEXT_JOB_RECOMMENDATIONS_SCHEMA = `\`\`\`javascript
296
- evidence: {
297
- nextJobRecommendations: [
298
- {
299
- jobId: "<job-slug>",
300
- label: "<human-readable label>",
301
- reason: "<optional: why this job is recommended>",
302
- contextSummary: "<optional: one sentence context for the next agent>"
303
- }
304
- // 0–3 entries; an empty array [] is valid
305
- ]
306
- }
295
+ const NEXT_JOB_RECOMMENDATIONS_SCHEMA = `\`\`\`javascript
296
+ evidence: {
297
+ nextJobRecommendations: [
298
+ {
299
+ jobId: "<job-slug>",
300
+ label: "<human-readable label>",
301
+ reason: "<optional: why this job is recommended>",
302
+ contextSummary: "<optional: one sentence context for the next agent>"
303
+ }
304
+ // 0–3 entries; an empty array [] is valid
305
+ ]
306
+ }
307
307
  \`\`\``;
308
- const DELEGATION_LEDGER_SCHEMA = `\`\`\`javascript
309
- evidence: {
310
- delegationLedger: {
311
- delegationRequired: true,
312
- objective: "<optional objective string>",
313
- tasks: [
314
- { jobId: "<job-slug>", personaKey: "<persona key or null>", briefing: "<optional briefing>" }
315
- ]
316
- }
317
- }
308
+ const DELEGATION_LEDGER_SCHEMA = `\`\`\`javascript
309
+ evidence: {
310
+ delegationLedger: {
311
+ delegationRequired: true,
312
+ objective: "<optional objective string>",
313
+ tasks: [
314
+ { jobId: "<job-slug>", personaKey: "<persona key or null>", briefing: "<optional briefing>" }
315
+ ]
316
+ }
317
+ }
318
318
  \`\`\``;
319
319
  const FIELD_SCHEMAS = {
320
320
  approved: ADDRESS_FEEDBACK_APPROVAL_SCHEMA,
@@ -50,71 +50,71 @@ function generateJobStub(jobName, _jobPath, intent, outcome, steps, displayName)
50
50
  const metadata = displayName
51
51
  ? `---\n${JSON.stringify({ name: jobName, displayName }, null, 2)}\n---\n`
52
52
  : '';
53
- return `${STUB_MARKER}
54
- ${metadata}
55
- # FRAIM Job: ${jobName}
56
-
57
- ## Intent
58
- ${intent}
59
-
60
- ## Outcome
61
- ${outcome}
62
-
63
- ## Steps
64
- ${steps}
65
-
66
- ---
67
-
68
- > [!IMPORTANT]
69
- > **For AI Agents:** Do NOT attempt to execute this job based on the Intent/Outcome above.
70
- > This stub is for discoverability only. The actual job has multiple phases with
71
- > detailed steps, validation criteria, and required skills.
72
- >
73
- > To get full phase-by-phase instructions, call:
74
- > \`get_fraim_job({ job: "${jobName}" })\`
75
- >
76
- > Then follow all phases using \`seekMentoring\` at each phase transition.
53
+ return `${STUB_MARKER}
54
+ ${metadata}
55
+ # FRAIM Job: ${jobName}
56
+
57
+ ## Intent
58
+ ${intent}
59
+
60
+ ## Outcome
61
+ ${outcome}
62
+
63
+ ## Steps
64
+ ${steps}
65
+
66
+ ---
67
+
68
+ > [!IMPORTANT]
69
+ > **For AI Agents:** Do NOT attempt to execute this job based on the Intent/Outcome above.
70
+ > This stub is for discoverability only. The actual job has multiple phases with
71
+ > detailed steps, validation criteria, and required skills.
72
+ >
73
+ > To get full phase-by-phase instructions, call:
74
+ > \`get_fraim_job({ job: "${jobName}" })\`
75
+ >
76
+ > Then follow all phases using \`seekMentoring\` at each phase transition.
77
77
  `;
78
78
  }
79
79
  /**
80
80
  * Generates a lightweight markdown stub for a skill.
81
81
  */
82
82
  function generateSkillStub(skillName, skillPath, skillInput, skillOutput) {
83
- return `${STUB_MARKER}
84
- # FRAIM Skill: ${skillName}
85
-
86
- ## Skill Input
87
- ${skillInput}
88
-
89
- ## Skill Output
90
- ${skillOutput}
91
-
92
- ---
93
-
94
- > [!IMPORTANT]
95
- > **For AI Agents:** This is a discoverability stub for the skill.
96
- > All execution details must be fetched from MCP before use.
97
- > To retrieve the complete skill instructions, call:
98
- > \`get_fraim_file({ path: "skills/${skillPath}" })\`
83
+ return `${STUB_MARKER}
84
+ # FRAIM Skill: ${skillName}
85
+
86
+ ## Skill Input
87
+ ${skillInput}
88
+
89
+ ## Skill Output
90
+ ${skillOutput}
91
+
92
+ ---
93
+
94
+ > [!IMPORTANT]
95
+ > **For AI Agents:** This is a discoverability stub for the skill.
96
+ > All execution details must be fetched from MCP before use.
97
+ > To retrieve the complete skill instructions, call:
98
+ > \`get_fraim_file({ path: "skills/${skillPath}" })\`
99
99
  `;
100
100
  }
101
101
  /**
102
102
  * Generates a lightweight markdown stub for a rule.
103
103
  */
104
104
  function generateRuleStub(ruleName, rulePath, intent) {
105
- return `${STUB_MARKER}
106
- # FRAIM Rule: ${ruleName}
107
-
108
- ## Intent
109
- ${intent}
110
-
111
- ---
112
-
113
- > [!IMPORTANT]
114
- > **For AI Agents:** This is a discoverability stub for the rule.
115
- > All rule details must be fetched from MCP before use.
116
- > To retrieve the complete rule instructions, call:
117
- > \`get_fraim_file({ path: "rules/${rulePath}" })\`
105
+ return `${STUB_MARKER}
106
+ # FRAIM Rule: ${ruleName}
107
+
108
+ ## Intent
109
+ ${intent}
110
+
111
+ ---
112
+
113
+ > [!IMPORTANT]
114
+ > **For AI Agents:** This is a discoverability stub for the rule.
115
+ > All rule details must be fetched from MCP before use.
116
+ > To retrieve the complete rule instructions, call:
117
+ > \`get_fraim_file({ path: "rules/${rulePath}" })\`
118
118
  `;
119
119
  }
120
120
  /**
@@ -121,6 +121,7 @@ class FirstRunServer {
121
121
  constructor(options) {
122
122
  this.app = (0, express_1.default)();
123
123
  this.sessionService = options.sessionService;
124
+ this.onOpenHub = options.onOpenHub;
124
125
  this.finishPromise = new Promise((resolve) => {
125
126
  this.finishResolver = resolve;
126
127
  });
@@ -299,13 +300,16 @@ class FirstRunServer {
299
300
  // a durable launcher binary that survives independently.
300
301
  this.app.post('/api/first-run/open-hub', async (_req, res) => {
301
302
  try {
302
- const result = await this.sessionService.openHub();
303
+ let result = await this.sessionService.openHub();
303
304
  // Write the next-prompt artifact as a side effect of opening the
304
305
  // Hub so the client doesn't need a separate /finish call. We
305
306
  // intentionally do NOT resolve the finishPromise - see /finish
306
307
  // handler comment above.
307
308
  if (result.ok) {
308
309
  this.sessionService.finish();
310
+ if (this.onOpenHub) {
311
+ result = { ...result, ...(await this.onOpenHub()) };
312
+ }
309
313
  }
310
314
  return res.json(result);
311
315
  }