claude-code-session-manager 0.75.3 → 0.77.0
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/dist/assets/{AgentLibrary-CzQqcObq.js → AgentLibrary-B2ie8bbw.js} +2 -2
- package/dist/assets/{DataModel-Bj_WlLz8.js → DataModel-BIJPYw32.js} +1 -1
- package/dist/assets/{History-DnSi_OHm.js → History-CeY6dk9S.js} +2 -2
- package/dist/assets/{Hooks-0BB0dp3S.js → Hooks-BFH2ocKg.js} +2 -2
- package/dist/assets/{HostBilko-DHpwwsLQ.js → HostBilko-36gj9wLz.js} +1 -1
- package/dist/assets/{Library-CaJVqVvi.js → Library-C-hBct39.js} +1 -1
- package/dist/assets/{ListDetail-C1W2HmC2.js → ListDetail-CNq64VWV.js} +1 -1
- package/dist/assets/{MarkdownEditor-5Ob9FW3z.js → MarkdownEditor-Bh3qt5-1.js} +1 -1
- package/dist/assets/{McpServers-JxCSfm1S.js → McpServers-DpGN0oyz.js} +1 -1
- package/dist/assets/{Memory-BDeqlqwH.js → Memory-D59hUjC4.js} +6 -6
- package/dist/assets/{Panel-Dh9ZHuEj.js → Panel-DCgbaoci.js} +1 -1
- package/dist/assets/{Permissions-DXy-CbEY.js → Permissions-DAmQ0DYV.js} +2 -2
- package/dist/assets/{Plugins-_n1Iuc8T.js → Plugins-Dyfgn6Is.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BP_evfxE.js → ProvenanceBadge-BiYhPO1U.js} +1 -1
- package/dist/assets/SaveBar-RV7B6sOh.js +1 -0
- package/dist/assets/Scheduler-BPaNqx1b.js +14 -0
- package/dist/assets/{ScopeSwitcher-CAWzM6RI.js → ScopeSwitcher-P4mdLGNU.js} +1 -1
- package/dist/assets/{Settings-DRRozLyT.js → Settings-BL4vf5aX.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-DGHDWlz4.js → SkillReferenceGraph-BRBDyi1_.js} +1 -1
- package/dist/assets/{Skills-D8L66eiX.js → Skills-BV08gDUH.js} +2 -2
- package/dist/assets/{SystemPrompt-CYtUsonD.js → SystemPrompt-CLftSsDw.js} +1 -1
- package/dist/assets/TagLibrary-Bp8jGsd5.js +1 -0
- package/dist/assets/{TiptapBody-B2hRgbPE.js → TiptapBody-jCpuB6E5.js} +1 -1
- package/dist/assets/{Toggle-BTwsbxam.js → Toggle-D2paA1xf.js} +1 -1
- package/dist/assets/{index-DijufvkJ.js → index-BDRSqBl3.js} +704 -704
- package/dist/assets/{index-CMLnzdZC.css → index-CYhdtisq.css} +1 -1
- package/dist/assets/{settingsSchema-D6wzxAi6.js → settingsSchema-6IOLjZZN.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +8 -2
- package/plugins/session-manager-dev/skills/develop/standards.md +1 -1
- package/scripts/lib/activeSessions.cjs +116 -6
- package/scripts/project-pages-logic/dist/logic.cjs +4709 -0
- package/scripts/render-project-pages/dist/renderer.cjs +18900 -0
- package/scripts/render-project-pages.cjs +70 -0
- package/scripts/scheduler-mcp-server.cjs +269 -96
- package/scripts/validate-project-pages-summary.cjs +62 -0
- package/src/main/__tests__/agentModelResolve.test.cjs +66 -0
- package/src/main/__tests__/epicStatusMirror.test.cjs +110 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +106 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +295 -0
- package/src/main/__tests__/prdAgentType.test.cjs +103 -0
- package/src/main/__tests__/prdCreate.test.cjs +247 -0
- package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +117 -0
- package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +108 -0
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +485 -0
- package/src/main/__tests__/projectPages.test.cjs +73 -1
- package/src/main/__tests__/rcaReport.test.cjs +54 -0
- package/src/main/__tests__/runVerify.test.cjs +94 -0
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +58 -3
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +103 -0
- package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +41 -0
- package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +10 -0
- package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +78 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +242 -0
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +31 -0
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +201 -0
- package/src/main/__tests__/scheduler-leftover-fields.test.cjs +52 -0
- package/src/main/__tests__/scheduler-looks-done.test.cjs +241 -0
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +135 -0
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +222 -0
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +207 -1
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +212 -0
- package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +185 -0
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +194 -0
- package/src/main/__tests__/seedAgentPersonas.test.cjs +75 -14
- package/src/main/__tests__/seedSchedulerMcp.test.cjs +66 -0
- package/src/main/__tests__/uniquePrdNumbers.test.cjs +14 -5
- package/src/main/bilkoHost.cjs +4 -3
- package/src/main/chatRunner.cjs +6 -1
- package/src/main/config.cjs +25 -33
- package/src/main/health.cjs +153 -2
- package/src/main/index.cjs +64 -5
- package/src/main/ipcSchemas.cjs +69 -1
- package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +179 -0
- package/src/main/lib/__tests__/childWithLog.test.cjs +141 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +391 -42
- package/src/main/lib/__tests__/ephemeralCwd.test.cjs +91 -0
- package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +5 -3
- package/src/main/lib/__tests__/fixChainDepth.test.cjs +40 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +290 -5
- package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +107 -0
- package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +153 -0
- package/src/main/lib/__tests__/jobWorktree.test.cjs +6 -4
- package/src/main/lib/__tests__/landedSinceRun.test.cjs +73 -0
- package/src/main/lib/__tests__/launchFailure.test.cjs +220 -0
- package/src/main/lib/__tests__/loadGate.test.cjs +159 -0
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +102 -0
- package/src/main/lib/__tests__/opsOwnership.test.cjs +7 -0
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +151 -0
- package/src/main/lib/__tests__/opsRootResolve.test.cjs +149 -0
- package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +82 -0
- package/src/main/lib/__tests__/projectRootResolve.test.cjs +148 -0
- package/src/main/lib/__tests__/queueHealth.test.cjs +58 -0
- package/src/main/lib/__tests__/quietMachineLease.test.cjs +39 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +133 -0
- package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +19 -9
- package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +213 -0
- package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +125 -0
- package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +127 -0
- package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +109 -0
- package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +71 -0
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +217 -0
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +350 -0
- package/src/main/lib/activeIndexMerge.cjs +15 -0
- package/src/main/lib/activeIndexRebuild.cjs +133 -0
- package/src/main/lib/agentModelResolve.cjs +58 -0
- package/src/main/lib/buildTarget.cjs +3 -2
- package/src/main/lib/childWithLog.cjs +69 -2
- package/src/main/lib/claudeBin.cjs +54 -1
- package/src/main/lib/crossProjectFeedback.cjs +8 -1
- package/src/main/lib/definitionOfDone.cjs +3 -2
- package/src/main/lib/delegationReadiness.cjs +514 -26
- package/src/main/lib/ephemeralCwd.cjs +78 -0
- package/src/main/lib/epicDelegationStats.cjs +2 -1
- package/src/main/lib/epicMint.cjs +17 -1
- package/src/main/lib/epicStatusMirror.cjs +95 -0
- package/src/main/lib/epicValidationHook.cjs +2 -1
- package/src/main/lib/epicWorktreeMint.cjs +5 -2
- package/src/main/lib/fixChainDepth.cjs +45 -0
- package/src/main/lib/gitWorktree.cjs +520 -21
- package/src/main/lib/jobWorktree.cjs +2 -0
- package/src/main/lib/landedSinceRun.cjs +55 -0
- package/src/main/lib/launchFailure.cjs +357 -0
- package/src/main/lib/loadGate.cjs +134 -0
- package/src/main/lib/mcpToolCatalog.cjs +370 -0
- package/src/main/lib/opsErrorLog.cjs +12 -1
- package/src/main/lib/opsOwnership.cjs +106 -0
- package/src/main/lib/prdAdminRoutes.cjs +43 -3
- package/src/main/lib/prdAgentType.cjs +84 -0
- package/src/main/lib/prdCreate.cjs +103 -15
- package/src/main/lib/prdDeclaredPaths.cjs +70 -0
- package/src/main/lib/prdFrontmatter.cjs +17 -3
- package/src/main/lib/prdLocations.cjs +13 -6
- package/src/main/lib/projectHomeAdminRoutes.cjs +402 -0
- package/src/main/lib/projectPageSummarySchema.cjs +181 -0
- package/src/main/lib/projectRootResolve.cjs +134 -0
- package/src/main/lib/promptSessionSchema.cjs +7 -0
- package/src/main/lib/queueHealth.cjs +38 -0
- package/src/main/lib/queueStore.cjs +40 -7
- package/src/main/lib/quietMachineLease.cjs +48 -0
- package/src/main/lib/rcaReport.cjs +54 -4
- package/src/main/lib/reaperHelpers.cjs +64 -1
- package/src/main/lib/scheduleJobSchema.cjs +31 -0
- package/src/main/lib/scheduleJobTransitions.cjs +6 -2
- package/src/main/lib/schedulerBatch.cjs +301 -55
- package/src/main/lib/schedulerConfig.cjs +99 -0
- package/src/main/projectBrief.cjs +3 -2
- package/src/main/projectPages.cjs +162 -3
- package/src/main/promptSessionTranscript.cjs +0 -0
- package/src/main/pty.cjs +5 -0
- package/src/main/queueOps.cjs +15 -8
- package/src/main/runVerify.cjs +50 -9
- package/src/main/scheduler/prdParser.cjs +18 -1
- package/src/main/scheduler.cjs +1701 -130
- package/src/main/seedAgentPersonas.cjs +62 -21
- package/src/main/seedSchedulerMcp.cjs +58 -4
- package/src/main/templates/project-pages-catalog.json +741 -0
- package/src/main/templates/project-pages-pipeline.md +417 -0
- package/src/preload/api.d.ts +187 -3
- package/src/preload/index.cjs +9 -0
- package/src/seed/agents/project-home-builder.md +59 -0
- package/dist/assets/SaveBar-D-gCUx4n.js +0 -1
- package/dist/assets/Scheduler-Bpd4OGju.js +0 -14
- package/dist/assets/TagLibrary-E5CLeuVk.js +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Bash-invocable CLI wrapping the Stage 0 renderer (render.tsx →
|
|
3
|
+
// scripts/render-project-pages/dist/renderer.cjs, built by
|
|
4
|
+
// `npm run build:project-pages`). Requires that build to have already run —
|
|
5
|
+
// this script does NOT bundle on the fly, since PRD authoring standards
|
|
6
|
+
// forbid unbounded/slow steps inside a headless job.
|
|
7
|
+
//
|
|
8
|
+
// Usage:
|
|
9
|
+
// node scripts/render-project-pages.cjs <summary.json> <picks.json> <output dir> <generatedAt>
|
|
10
|
+
//
|
|
11
|
+
// generatedAt (4th arg, required) is an ISO 8601 timestamp string the CALLER
|
|
12
|
+
// stamps — Date.now() is unavailable in some execution contexts (e.g.
|
|
13
|
+
// workflow scripts) that may drive this CLI, so this script never generates
|
|
14
|
+
// its own timestamp. This is the "accept generatedAt as a 4th CLI arg"
|
|
15
|
+
// option from the PRD's two documented choices (the alternative — omitting
|
|
16
|
+
// generatedAt from manifest.json entirely — was not taken).
|
|
17
|
+
//
|
|
18
|
+
// Writes <output dir>/home.html, marketing.html, feature.html,
|
|
19
|
+
// architecture.html, brief.html, and manifest.json ({ generatedAt }).
|
|
20
|
+
//
|
|
21
|
+
// Path arguments are used as-is (no allowedRoots validation like
|
|
22
|
+
// config.cjs's validatePath) — this is a standalone local dev/ops script run
|
|
23
|
+
// by a trusted human or agent shell, not an IPC-reachable surface, so it's
|
|
24
|
+
// out of scope for that guard by design.
|
|
25
|
+
'use strict';
|
|
26
|
+
|
|
27
|
+
const fs = require('node:fs');
|
|
28
|
+
const path = require('node:path');
|
|
29
|
+
|
|
30
|
+
function fail(message) {
|
|
31
|
+
console.error(`render-project-pages: ${message}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function main() {
|
|
36
|
+
const [summaryPath, picksPath, outDir, generatedAt] = process.argv.slice(2);
|
|
37
|
+
if (!summaryPath || !picksPath || !outDir || !generatedAt) {
|
|
38
|
+
fail('usage: node scripts/render-project-pages.cjs <summary.json> <picks.json> <output dir> <generatedAt>');
|
|
39
|
+
}
|
|
40
|
+
if (Number.isNaN(Date.parse(generatedAt))) {
|
|
41
|
+
fail(`generatedAt must be an ISO 8601 timestamp, got: ${generatedAt}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const bundlePath = path.join(__dirname, 'render-project-pages', 'dist', 'renderer.cjs');
|
|
45
|
+
if (!fs.existsSync(bundlePath)) {
|
|
46
|
+
fail(`build bundle not found at ${bundlePath} — run "npm run build:project-pages" first`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let renderProjectPages;
|
|
50
|
+
try {
|
|
51
|
+
({ renderProjectPages } = require(bundlePath));
|
|
52
|
+
} catch (err) {
|
|
53
|
+
fail(`failed to load ${bundlePath}: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const summary = JSON.parse(fs.readFileSync(summaryPath, 'utf8'));
|
|
57
|
+
const picks = JSON.parse(fs.readFileSync(picksPath, 'utf8'));
|
|
58
|
+
|
|
59
|
+
const pages = renderProjectPages(summary, picks);
|
|
60
|
+
|
|
61
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
62
|
+
for (const lens of ['home', 'marketing', 'feature', 'architecture', 'brief']) {
|
|
63
|
+
fs.writeFileSync(path.join(outDir, `${lens}.html`), pages[lens]);
|
|
64
|
+
}
|
|
65
|
+
fs.writeFileSync(path.join(outDir, 'manifest.json'), JSON.stringify({ generatedAt }, null, 2));
|
|
66
|
+
|
|
67
|
+
console.log(`Wrote home.html, marketing.html, feature.html, architecture.html, brief.html, manifest.json to ${outDir}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
main();
|
|
@@ -11,6 +11,11 @@
|
|
|
11
11
|
* feedback_list_projects() -> GET /admin/feedback/targets
|
|
12
12
|
* feedback_open_session({ ... }) -> POST /admin/feedback/open-session
|
|
13
13
|
*
|
|
14
|
+
* project_home_get_contract({ cwd? }) -> GET /admin/project-home/contract
|
|
15
|
+
* project_home_validate_summary({ cwd?, summary }) -> POST /admin/project-home/validate-summary
|
|
16
|
+
* project_home_render({ cwd?, summary, picks }) -> POST /admin/project-home/render
|
|
17
|
+
* project_home_status({ cwd? }) -> GET /admin/project-home/status
|
|
18
|
+
*
|
|
14
19
|
* This is a separate process from the Electron app — it only ever reaches
|
|
15
20
|
* it over the token-authed loopback HTTP API in admin-api.json, never by
|
|
16
21
|
* requiring scheduler.cjs/localAdminHttp.cjs directly. The admin server IS the
|
|
@@ -29,11 +34,41 @@ const {
|
|
|
29
34
|
CallToolRequestSchema,
|
|
30
35
|
} = require('@modelcontextprotocol/sdk/types.js');
|
|
31
36
|
const { PRD_WORK_TYPES } = require('../src/main/lib/workTypeLibrary.cjs');
|
|
37
|
+
const { MCP_TOOL_CATALOG, MCP_RECIPES, composeDescription } = require('../src/main/lib/mcpToolCatalog.cjs');
|
|
38
|
+
|
|
39
|
+
const CATALOG_BY_NAME = new Map(MCP_TOOL_CATALOG.map((entry) => [entry.name, entry]));
|
|
40
|
+
|
|
41
|
+
function descriptionFor(toolName) {
|
|
42
|
+
const entry = CATALOG_BY_NAME.get(toolName);
|
|
43
|
+
if (!entry) throw new Error(`mcpToolCatalog.cjs has no entry for tool "${toolName}"`);
|
|
44
|
+
return composeDescription(entry);
|
|
45
|
+
}
|
|
32
46
|
|
|
33
47
|
const TOKEN_PATH = path.join(os.homedir(), '.claude', 'session-manager', 'admin-api.json');
|
|
34
48
|
|
|
49
|
+
// Defined once so every failure path points to the same next call, verbatim
|
|
50
|
+
// — never paste this sentence at each return site (PRD: session_manager_help).
|
|
51
|
+
const HELP_POINTER = ' — call session_manager_help for the correct usage';
|
|
52
|
+
|
|
53
|
+
function withPointer(text) {
|
|
54
|
+
const str = String(text);
|
|
55
|
+
return str.endsWith(HELP_POINTER) ? str : str + HELP_POINTER;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function errorResult(text) {
|
|
59
|
+
return { content: [{ type: 'text', text: withPointer(text) }], isError: true };
|
|
60
|
+
}
|
|
61
|
+
|
|
35
62
|
const NOT_RUNNING_ERROR =
|
|
36
|
-
|
|
63
|
+
`session-manager app is not running (admin API unreachable) — start it first${HELP_POINTER}`;
|
|
64
|
+
|
|
65
|
+
// Same resolution fetchReadiness() below uses: a project-home-builder session
|
|
66
|
+
// running inside an Epic worktree still targets the real project cwd, not
|
|
67
|
+
// the worktree's own pwd.
|
|
68
|
+
function resolveCwdArg(args) {
|
|
69
|
+
if (args && typeof args.cwd === 'string' && args.cwd) return args.cwd;
|
|
70
|
+
return process.env.SM_PROJECT_ROOT || process.cwd();
|
|
71
|
+
}
|
|
37
72
|
|
|
38
73
|
async function readAdminConfig() {
|
|
39
74
|
const raw = await fsp.readFile(TOKEN_PATH, 'utf8');
|
|
@@ -68,56 +103,56 @@ async function adminRequest(method, urlPath, body) {
|
|
|
68
103
|
return json;
|
|
69
104
|
}
|
|
70
105
|
|
|
106
|
+
// session_manager_help's live half. The catalog/recipes below are static and
|
|
107
|
+
// answerable purely in-process (no admin API needed), but "is this MCP
|
|
108
|
+
// server actually wired up for THIS project" needs the admin API's
|
|
109
|
+
// checkDelegationReadiness result — that's genuinely unavailable when the
|
|
110
|
+
// app is down, so this must degrade to a reported-unavailable state rather
|
|
111
|
+
// than throwing NOT_RUNNING_ERROR and failing the whole help call.
|
|
112
|
+
async function fetchReadiness() {
|
|
113
|
+
// SM_PROJECT_ROOT (when present) is a trusted hint for the real project
|
|
114
|
+
// root — forwarded so a worktree/ops-internal process.cwd() still reports
|
|
115
|
+
// the real project's readiness. See projectRootResolve.cjs.
|
|
116
|
+
const cwd = process.env.SM_PROJECT_ROOT || process.cwd();
|
|
117
|
+
try {
|
|
118
|
+
const qs = new URLSearchParams({ cwd });
|
|
119
|
+
const result = await adminRequest('GET', `/admin/mcp/readiness?${qs.toString()}`);
|
|
120
|
+
if (result?.ok === false) {
|
|
121
|
+
return { available: false, cwd, reason: result.error ?? 'readiness check failed' };
|
|
122
|
+
}
|
|
123
|
+
return { available: true, cwd, ok: result.ready, checks: result.checks };
|
|
124
|
+
} catch (e) {
|
|
125
|
+
return { available: false, cwd, reason: e?.message ?? String(e) };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
71
129
|
const TOOLS = [
|
|
72
130
|
{
|
|
73
131
|
name: 'scheduler_reset_job',
|
|
74
|
-
description:
|
|
75
|
-
+ 'Refuses a job whose status is already "completed" unless force:true is passed — resetting '
|
|
76
|
-
+ 'a completed job re-executes already-shipped work.',
|
|
132
|
+
description: descriptionFor('scheduler_reset_job'),
|
|
77
133
|
inputSchema: {
|
|
78
134
|
type: 'object',
|
|
79
135
|
properties: {
|
|
80
136
|
slug: { type: 'string', description: 'PRD slug of the job to reset' },
|
|
81
137
|
force: { type: 'boolean', description: 'Required to reset a job whose status is already "completed"' },
|
|
138
|
+
cwd: { type: 'string', description: 'Optional: the PRD project cwd, narrows/speeds the search' },
|
|
82
139
|
},
|
|
83
140
|
required: ['slug'],
|
|
84
141
|
},
|
|
85
142
|
},
|
|
86
143
|
{
|
|
87
144
|
name: 'scheduler_list_jobs',
|
|
88
|
-
description:
|
|
145
|
+
description: descriptionFor('scheduler_list_jobs'),
|
|
89
146
|
inputSchema: { type: 'object', properties: {} },
|
|
90
147
|
},
|
|
91
148
|
{
|
|
92
149
|
name: 'scheduler_create_prd',
|
|
93
|
-
description:
|
|
94
|
-
"THE ONLY SANCTIONED WAY to author a PRD. Write a new PRD file via the session-manager "
|
|
95
|
-
+ "app's admin API. Server-side validates the frontmatter, atomically allocates the NN "
|
|
96
|
-
+ 'parallel-group number, appends the engineering standards, and writes the PRD file to '
|
|
97
|
-
+ "disk. This tool ONLY writes the file — it does not create a scheduler queue row. The "
|
|
98
|
-
+ 'queue row is derived automatically by the scheduler\'s next reconcile pass (typically '
|
|
99
|
-
+ 'within ~1 minute); the response has `enqueued: false` for exactly this reason. Every '
|
|
100
|
-
+ 'PRD must join an EXISTING, already-human-approved Epic (pass sourcePromptId) — this '
|
|
101
|
-
+ 'tool never mints a new one, and refuses the write if no Epic can be resolved. '
|
|
102
|
-
+ 'TWO DISTINCT FAILURE MODES if this tool is not usable — do not conflate them: '
|
|
103
|
-
+ '(a) this tool call is PRESENT in your tool list but ERRORS as app-not-running / admin '
|
|
104
|
-
+ 'API unreachable — that is the ONLY case where hand-authoring the PRD file directly on '
|
|
105
|
-
+ 'disk is an acceptable DEGRADED, LAST-RESORT fallback; the caller MUST say so explicitly '
|
|
106
|
-
+ 'and visibly in its report (which file, why the tool was unreachable, that it needs '
|
|
107
|
-
+ 'verification) since the server-side validation, atomic NN allocation, and '
|
|
108
|
-
+ 'Epic-existence check this tool performs did not run for that file. '
|
|
109
|
-
+ '(b) this tool is ABSENT from your tool list entirely — you were never offered it, so '
|
|
110
|
-
+ 'there is no error to catch. That means the session-manager-scheduler MCP server is not '
|
|
111
|
-
+ 'registered for this project: a MISCONFIGURATION, not an offline app. In that case DO '
|
|
112
|
-
+ 'NOT hand-write any PRD file — stop and tell the human the MCP server is not registered '
|
|
113
|
-
+ '(fix: `claude mcp add session-manager-scheduler --scope user -- node '
|
|
114
|
-
+ '<session-manager-repo>/scripts/scheduler-mcp-server.cjs`, once at user scope covers '
|
|
115
|
-
+ 'every project). See /develop.',
|
|
150
|
+
description: descriptionFor('scheduler_create_prd'),
|
|
116
151
|
inputSchema: {
|
|
117
152
|
type: 'object',
|
|
118
153
|
properties: {
|
|
119
154
|
title: { type: 'string', description: 'One-line human-readable title' },
|
|
120
|
-
cwd: { type: 'string', description: 'Absolute path to the target project (where claude -p will run)' },
|
|
155
|
+
cwd: { type: 'string', description: 'Absolute path to the target project (where claude -p will run). Optional inside an Epic session — the server resolves the real project from the calling session (originClaudeSessionId/sourcePromptId) when omitted.' },
|
|
121
156
|
estimateMinutes: { type: 'number', description: 'Integer wall-clock estimate in minutes' },
|
|
122
157
|
goal: { type: 'string', description: '2-4 sentences: what the executor will build and why' },
|
|
123
158
|
acceptanceCriteria: {
|
|
@@ -134,20 +169,23 @@ const TOOLS = [
|
|
|
134
169
|
tag: {
|
|
135
170
|
type: 'string',
|
|
136
171
|
enum: PRD_WORK_TYPES,
|
|
137
|
-
description: 'Optional: the
|
|
172
|
+
description: 'Optional: the WORK TYPE of THIS PRD — independent of the parent Epic\'s own tag. An Epic is the plan; a PRD is one unit of work inside it, and a single plan may legitimately contain several different work types. Never derived or inherited from the Epic.',
|
|
173
|
+
},
|
|
174
|
+
agentType: {
|
|
175
|
+
type: 'string',
|
|
176
|
+
description: 'Optional: WHO executes this PRD — the persona name (a file at ~/.claude/agents/<name>.md, e.g. "dev-lead") the executor should adopt. Distinct from `tag` (what kind of work); this is who does it. Defaults to "dev-lead" when omitted. Rejected at write time if the name does not resolve to a readable persona file.',
|
|
177
|
+
},
|
|
178
|
+
quietMachine: {
|
|
179
|
+
type: 'boolean',
|
|
180
|
+
description: 'Optional: set true only when this PRD\'s acceptance criteria are wall-clock/timing measurements (frame time, performance fences) that CPU contention from concurrent jobs would invalidate. The scheduler dispatches it only once zero other jobs are running machine-wide, and holds every other job off the whole slot pool for its run — a whole-machine exclusive lease, not a per-project one. If the machine never goes quiet within the configured wait window (default 30 minutes), it dispatches anyway and is marked degraded. Opt-in only; omit for ordinary PRDs.',
|
|
138
181
|
},
|
|
139
182
|
},
|
|
140
|
-
required: ['title', '
|
|
183
|
+
required: ['title', 'estimateMinutes', 'goal', 'acceptanceCriteria', 'implementationNotes'],
|
|
141
184
|
},
|
|
142
185
|
},
|
|
143
186
|
{
|
|
144
187
|
name: 'scheduler_list_prds',
|
|
145
|
-
description:
|
|
146
|
-
+ 'Each entry includes its real job status (pending/running/completed/failed/needs_review, or null if not yet '
|
|
147
|
-
+ 'queued/reconciled). Optionally filter by project cwd, Epic id, and/or status. Results are paginated (default '
|
|
148
|
-
+ 'limit 100, max 500) sorted by slug ascending — check `hasMore`/`total` in the response before assuming you '
|
|
149
|
-
+ "received every PRD; page further with `offset`. Default fields are compact (no parallelGroup/estimateMinutes/"
|
|
150
|
-
+ 'sourcePromptId/epicId/archivedStatus) — pass fields:"full" to restore them.',
|
|
188
|
+
description: descriptionFor('scheduler_list_prds'),
|
|
151
189
|
inputSchema: {
|
|
152
190
|
type: 'object',
|
|
153
191
|
properties: {
|
|
@@ -162,8 +200,7 @@ const TOOLS = [
|
|
|
162
200
|
},
|
|
163
201
|
{
|
|
164
202
|
name: 'scheduler_get_prd',
|
|
165
|
-
description:
|
|
166
|
-
+ "session-manager app's admin API.",
|
|
203
|
+
description: descriptionFor('scheduler_get_prd'),
|
|
167
204
|
inputSchema: {
|
|
168
205
|
type: 'object',
|
|
169
206
|
properties: {
|
|
@@ -175,11 +212,7 @@ const TOOLS = [
|
|
|
175
212
|
},
|
|
176
213
|
{
|
|
177
214
|
name: 'scheduler_update_prd',
|
|
178
|
-
description:
|
|
179
|
-
+ 'app\'s admin API. Refuses once a queue row exists for the slug and its status is anything but "pending" '
|
|
180
|
-
+ '(running/completed/failed/needs_review) — editing the spec under a live or already-finished executor is refused, '
|
|
181
|
-
+ 'not silently applied. Only recognized frontmatter keys (title, cwd, estimateMinutes, parallelGroup, '
|
|
182
|
-
+ 'sourcePromptId, sourceTabId, tag) may be patched; unrecognized keys (e.g. dependsOn) round-trip unchanged.',
|
|
215
|
+
description: descriptionFor('scheduler_update_prd'),
|
|
183
216
|
inputSchema: {
|
|
184
217
|
type: 'object',
|
|
185
218
|
properties: {
|
|
@@ -196,6 +229,8 @@ const TOOLS = [
|
|
|
196
229
|
sourcePromptId: { type: 'string' },
|
|
197
230
|
sourceTabId: { type: 'string' },
|
|
198
231
|
tag: { type: 'string', enum: PRD_WORK_TYPES },
|
|
232
|
+
agentType: { type: 'string', description: 'WHO executes this PRD (persona name) — patchable after creation too, see scheduler_create_prd' },
|
|
233
|
+
quietMachine: { type: 'boolean', description: 'Optional: the exclusive machine-wide quiet lease flag (see scheduler_create_prd) — patchable after creation too' },
|
|
199
234
|
},
|
|
200
235
|
},
|
|
201
236
|
body: { type: 'string', description: 'Optional: full replacement body (everything after the frontmatter)' },
|
|
@@ -205,34 +240,31 @@ const TOOLS = [
|
|
|
205
240
|
},
|
|
206
241
|
{
|
|
207
242
|
name: 'scheduler_archive_prd',
|
|
208
|
-
description:
|
|
209
|
-
+ "app's admin API.",
|
|
243
|
+
description: descriptionFor('scheduler_archive_prd'),
|
|
210
244
|
inputSchema: {
|
|
211
245
|
type: 'object',
|
|
212
246
|
properties: {
|
|
213
247
|
slugs: { type: 'array', items: { type: 'string' }, description: 'Slugs to archive' },
|
|
248
|
+
cwd: { type: 'string', description: 'Optional: the PRDs\' project cwd, narrows/speeds the search for every slug in this batch' },
|
|
214
249
|
},
|
|
215
250
|
required: ['slugs'],
|
|
216
251
|
},
|
|
217
252
|
},
|
|
218
253
|
{
|
|
219
254
|
name: 'scheduler_cancel_job',
|
|
220
|
-
description:
|
|
221
|
-
+ 'API. A running job is SIGTERM\'d; a pending job is simply retired. There is no "cancelled" job status, so a '
|
|
222
|
-
+ 'cancelled job lands as "failed" with an error naming the cause. Refuses a slug whose job is already terminal '
|
|
223
|
-
+ '(completed/failed/needs_review) — nothing to cancel.',
|
|
255
|
+
description: descriptionFor('scheduler_cancel_job'),
|
|
224
256
|
inputSchema: {
|
|
225
257
|
type: 'object',
|
|
226
258
|
properties: {
|
|
227
259
|
slug: { type: 'string', description: 'PRD slug of the job to cancel' },
|
|
260
|
+
cwd: { type: 'string', description: 'Optional: the PRD project cwd, narrows/speeds the search' },
|
|
228
261
|
},
|
|
229
262
|
required: ['slug'],
|
|
230
263
|
},
|
|
231
264
|
},
|
|
232
265
|
{
|
|
233
266
|
name: 'scheduler_retag_prd',
|
|
234
|
-
description:
|
|
235
|
-
+ "parallelGroup changes, its NN- filename prefix) via the session-manager app's admin API.",
|
|
267
|
+
description: descriptionFor('scheduler_retag_prd'),
|
|
236
268
|
inputSchema: {
|
|
237
269
|
type: 'object',
|
|
238
270
|
properties: {
|
|
@@ -255,10 +287,7 @@ const TOOLS = [
|
|
|
255
287
|
},
|
|
256
288
|
{
|
|
257
289
|
name: 'chat_send_prompt',
|
|
258
|
-
description:
|
|
259
|
-
"Push a prompt into an already-open tab's chat queue via the session-manager app's admin "
|
|
260
|
-
+ 'API. The renderer resolves the tab (must currently be open) and runs the prompt through '
|
|
261
|
-
+ 'the same queued-vs-immediate path as a manual send. No-ops if the tab is unknown/closed.',
|
|
290
|
+
description: descriptionFor('chat_send_prompt'),
|
|
262
291
|
inputSchema: {
|
|
263
292
|
type: 'object',
|
|
264
293
|
properties: {
|
|
@@ -270,31 +299,12 @@ const TOOLS = [
|
|
|
270
299
|
},
|
|
271
300
|
{
|
|
272
301
|
name: 'feedback_list_projects',
|
|
273
|
-
description:
|
|
274
|
-
'List the OTHER projects on this machine that can receive feedback (i.e. that Session Manager '
|
|
275
|
-
+ 'already manages — they have a session-manager-operations/ directory). Call this FIRST when you '
|
|
276
|
-
+ 'need the exact `toCwd` for feedback_open_session and are not certain of it — never guess a path. '
|
|
277
|
-
+ 'A project missing from this list has simply never been opened in Session Manager; ask the human '
|
|
278
|
-
+ 'to open it once rather than inventing a path.',
|
|
302
|
+
description: descriptionFor('feedback_list_projects'),
|
|
279
303
|
inputSchema: { type: 'object', properties: {} },
|
|
280
304
|
},
|
|
281
305
|
{
|
|
282
306
|
name: 'feedback_open_session',
|
|
283
|
-
description:
|
|
284
|
-
'THE ONLY SANCTIONED WAY to hand a finding from THIS project to a DIFFERENT project. Opens a new '
|
|
285
|
-
+ "PROPOSED session in the receiving project's own Sessions queue, carrying your report as its "
|
|
286
|
-
+ 'opening prompt and stamped with where it came from. Session Manager performs the cross-folder '
|
|
287
|
-
+ 'write; you never write another project\'s files yourself. '
|
|
288
|
-
+ 'WHAT THIS DOES NOT DO: it does not start anything, queue a PRD, or spend a token. The session '
|
|
289
|
-
+ 'lands as `proposed` and runs only if a human in the RECEIVING project presses "Approve & start". '
|
|
290
|
-
+ 'There is no callback and no reply channel — do not wait for an answer, and do not tell the user '
|
|
291
|
-
+ 'the other project has "been fixed" or "is working on it". Report only that the proposal was '
|
|
292
|
-
+ 'delivered. '
|
|
293
|
-
+ 'WHEN NOT TO USE IT: for work in the project you are ALREADY in, run /develop inside the Epic you '
|
|
294
|
-
+ 'are already in — this tool refuses toCwd === fromCwd outright. '
|
|
295
|
-
+ 'Write the report for a reader who has never seen your project: state the symptom, where you '
|
|
296
|
-
+ 'observed it, what you expected, and (if you know) the file in THEIR repo that looks responsible. '
|
|
297
|
-
+ 'Never assume they can see your code.',
|
|
307
|
+
description: descriptionFor('feedback_open_session'),
|
|
298
308
|
inputSchema: {
|
|
299
309
|
type: 'object',
|
|
300
310
|
properties: {
|
|
@@ -329,6 +339,62 @@ const TOOLS = [
|
|
|
329
339
|
required: ['toCwd', 'fromCwd', 'title', 'body'],
|
|
330
340
|
},
|
|
331
341
|
},
|
|
342
|
+
{
|
|
343
|
+
name: 'project_home_get_contract',
|
|
344
|
+
description: descriptionFor('project_home_get_contract'),
|
|
345
|
+
inputSchema: {
|
|
346
|
+
type: 'object',
|
|
347
|
+
properties: {
|
|
348
|
+
cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root (SM_PROJECT_ROOT or process.cwd()) when omitted." },
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'project_home_validate_summary',
|
|
354
|
+
description: descriptionFor('project_home_validate_summary'),
|
|
355
|
+
inputSchema: {
|
|
356
|
+
type: 'object',
|
|
357
|
+
properties: {
|
|
358
|
+
cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
|
|
359
|
+
summary: { type: 'object', description: 'ProjectPageSummary object matching the summarySchema from project_home_get_contract' },
|
|
360
|
+
},
|
|
361
|
+
required: ['summary'],
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: 'project_home_render',
|
|
366
|
+
description: descriptionFor('project_home_render'),
|
|
367
|
+
inputSchema: {
|
|
368
|
+
type: 'object',
|
|
369
|
+
properties: {
|
|
370
|
+
cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
|
|
371
|
+
summary: { type: 'object', description: 'ProjectPageSummary object matching the summarySchema from project_home_get_contract' },
|
|
372
|
+
picks: { type: 'object', description: 'ProjectPagePicks object (lensId -> slotId -> variantId) matching the picksSchema from project_home_get_contract' },
|
|
373
|
+
},
|
|
374
|
+
required: ['summary', 'picks'],
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: 'project_home_status',
|
|
379
|
+
description: descriptionFor('project_home_status'),
|
|
380
|
+
inputSchema: {
|
|
381
|
+
type: 'object',
|
|
382
|
+
properties: {
|
|
383
|
+
cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root when omitted." },
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'session_manager_help',
|
|
389
|
+
description: descriptionFor('session_manager_help'),
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: 'object',
|
|
392
|
+
properties: {
|
|
393
|
+
tool: { type: 'string', description: 'Optional: a tool name — returns that one catalog entry, including exampleArgs' },
|
|
394
|
+
topic: { type: 'string', description: 'Optional: a recipe id — returns that recipe\'s step-by-step instructions' },
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
},
|
|
332
398
|
];
|
|
333
399
|
|
|
334
400
|
const server = new Server(
|
|
@@ -338,16 +404,21 @@ const server = new Server(
|
|
|
338
404
|
|
|
339
405
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
340
406
|
|
|
341
|
-
|
|
407
|
+
// Named + exported (see module.exports below) so mcpToolCatalog.test.cjs's
|
|
408
|
+
// sibling can exercise session_manager_help's argument handling directly —
|
|
409
|
+
// mocking node:fs/promises + global.fetch to drive adminRequest — without
|
|
410
|
+
// booting a stdio transport.
|
|
411
|
+
async function handleCallTool(request) {
|
|
342
412
|
const { name, arguments: args } = request.params;
|
|
343
413
|
try {
|
|
344
414
|
if (name === 'scheduler_reset_job') {
|
|
345
415
|
const slug = args && typeof args.slug === 'string' ? args.slug : null;
|
|
346
416
|
if (!slug) {
|
|
347
|
-
return
|
|
417
|
+
return errorResult('missing required argument: slug');
|
|
348
418
|
}
|
|
349
419
|
const force = args && args.force === true;
|
|
350
|
-
const
|
|
420
|
+
const cwd = args && typeof args.cwd === 'string' ? args.cwd : undefined;
|
|
421
|
+
const result = await adminRequest('POST', '/admin/scheduler/reset-job', { slug, force, cwd });
|
|
351
422
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
352
423
|
}
|
|
353
424
|
if (name === 'scheduler_list_jobs') {
|
|
@@ -375,7 +446,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
375
446
|
if (name === 'scheduler_get_prd') {
|
|
376
447
|
const slug = args && typeof args.slug === 'string' ? args.slug : null;
|
|
377
448
|
if (!slug) {
|
|
378
|
-
return
|
|
449
|
+
return errorResult('missing required argument: slug');
|
|
379
450
|
}
|
|
380
451
|
const qs = new URLSearchParams({ slug });
|
|
381
452
|
if (args?.cwd) qs.set('cwd', args.cwd);
|
|
@@ -385,7 +456,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
385
456
|
if (name === 'scheduler_update_prd') {
|
|
386
457
|
const slug = args && typeof args.slug === 'string' ? args.slug : null;
|
|
387
458
|
if (!slug) {
|
|
388
|
-
return
|
|
459
|
+
return errorResult('missing required argument: slug');
|
|
389
460
|
}
|
|
390
461
|
const result = await adminRequest('POST', '/admin/scheduler/update-prd', args);
|
|
391
462
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
@@ -393,28 +464,45 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
393
464
|
if (name === 'scheduler_archive_prd') {
|
|
394
465
|
const slugs = Array.isArray(args?.slugs) ? args.slugs : null;
|
|
395
466
|
if (!slugs || slugs.length === 0) {
|
|
396
|
-
return
|
|
467
|
+
return errorResult('missing required argument: slugs');
|
|
397
468
|
}
|
|
398
|
-
const
|
|
469
|
+
const cwd = args && typeof args.cwd === 'string' ? args.cwd : undefined;
|
|
470
|
+
const result = await adminRequest('POST', '/admin/scheduler/archive-prd', { slugs, cwd });
|
|
399
471
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
400
472
|
}
|
|
401
473
|
if (name === 'scheduler_cancel_job') {
|
|
402
474
|
const slug = args && typeof args.slug === 'string' ? args.slug : null;
|
|
403
475
|
if (!slug) {
|
|
404
|
-
return
|
|
476
|
+
return errorResult('missing required argument: slug');
|
|
405
477
|
}
|
|
406
|
-
const
|
|
478
|
+
const cwd = args && typeof args.cwd === 'string' ? args.cwd : undefined;
|
|
479
|
+
const result = await adminRequest('POST', '/admin/scheduler/cancel-job', { slug, cwd });
|
|
407
480
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
408
481
|
}
|
|
409
482
|
if (name === 'scheduler_retag_prd') {
|
|
410
483
|
const items = Array.isArray(args?.items) ? args.items : null;
|
|
411
484
|
if (!items || items.length === 0) {
|
|
412
|
-
return
|
|
485
|
+
return errorResult('missing required argument: items');
|
|
413
486
|
}
|
|
414
487
|
const result = await adminRequest('POST', '/admin/scheduler/retag-prd', { items });
|
|
415
488
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
416
489
|
}
|
|
417
490
|
if (name === 'scheduler_create_prd') {
|
|
491
|
+
// Hard refusal at the authoring boundary (issue #11, list C1): a
|
|
492
|
+
// headless scheduled executor performs its own acceptance criteria and
|
|
493
|
+
// never queues follow-on work — PRD 460 called /develop from inside its
|
|
494
|
+
// run, spawned a duplicate PRD, and exited 0 having done nothing. The
|
|
495
|
+
// standards prose already says so; this makes the harness enforce it.
|
|
496
|
+
// scheduler.cjs stamps SM_SCHEDULER_JOB_SLUG on every job spawn and
|
|
497
|
+
// SM_SCHEDULER_JOB_MAY_QUEUE=1 only for personas whose whole job is
|
|
498
|
+
// decomposition (architect), so a planning PRD still can.
|
|
499
|
+
if (process.env.SM_SCHEDULER_JOB_SLUG && process.env.SM_SCHEDULER_JOB_MAY_QUEUE !== '1') {
|
|
500
|
+
return errorResult(
|
|
501
|
+
`refused: scheduler_create_prd was called from inside headless scheduled job '${process.env.SM_SCHEDULER_JOB_SLUG}'. `
|
|
502
|
+
+ 'A PRD executor performs its own acceptance criteria directly and never queues follow-on work (self-queue failure class). '
|
|
503
|
+
+ 'If more work is genuinely needed, say so in your final result text — the authoring Epic decides whether to queue it.',
|
|
504
|
+
);
|
|
505
|
+
}
|
|
418
506
|
// If the caller (the model) didn't pass sourcePromptId, forward this
|
|
419
507
|
// process's own SM_CHAT_SESSION_ID (set by chatRunner.cjs on the
|
|
420
508
|
// parent claude -p process this MCP server inherited its env from) so
|
|
@@ -425,6 +513,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
425
513
|
if (!payload.sourcePromptId && process.env.SM_CHAT_SESSION_ID) {
|
|
426
514
|
payload.originClaudeSessionId = process.env.SM_CHAT_SESSION_ID;
|
|
427
515
|
}
|
|
516
|
+
// SM_PROJECT_ROOT (chatRunner.cjs/pty.cjs/scheduler.cjs's job spawn) —
|
|
517
|
+
// a trusted hint for the real project cwd, forwarded so the admin route
|
|
518
|
+
// can resolve it even when the caller omitted cwd or passed a worktree
|
|
519
|
+
// pwd. See projectRootResolve.cjs's resolveProjectContext.
|
|
520
|
+
if (process.env.SM_PROJECT_ROOT) {
|
|
521
|
+
payload.originProjectRoot = process.env.SM_PROJECT_ROOT;
|
|
522
|
+
}
|
|
428
523
|
const result = await adminRequest('POST', '/admin/scheduler/create-prd', payload);
|
|
429
524
|
// Never say "queued" — this tool only writes the PRD file; the queue
|
|
430
525
|
// row is derived by the scheduler's next reconcile pass, not by this
|
|
@@ -441,7 +536,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
441
536
|
if (name === 'feedback_open_session') {
|
|
442
537
|
for (const key of ['toCwd', 'fromCwd', 'title', 'body']) {
|
|
443
538
|
if (!args || typeof args[key] !== 'string' || !args[key].trim()) {
|
|
444
|
-
return
|
|
539
|
+
return errorResult(`missing required argument: ${key}`);
|
|
445
540
|
}
|
|
446
541
|
}
|
|
447
542
|
// Same fallback shape as scheduler_create_prd: forward this process's
|
|
@@ -452,6 +547,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
452
547
|
if (!payload.fromEpicId && process.env.SM_CHAT_SESSION_ID) {
|
|
453
548
|
payload.originClaudeSessionId = process.env.SM_CHAT_SESSION_ID;
|
|
454
549
|
}
|
|
550
|
+
// See scheduler_create_prd's own SM_PROJECT_ROOT forwarding above.
|
|
551
|
+
if (process.env.SM_PROJECT_ROOT) {
|
|
552
|
+
payload.originProjectRoot = process.env.SM_PROJECT_ROOT;
|
|
553
|
+
}
|
|
455
554
|
const result = await adminRequest('POST', '/admin/feedback/open-session', payload);
|
|
456
555
|
// Never say "sent", "filed" or "fixed" — this call delivers a PROPOSAL
|
|
457
556
|
// that a human in the other project must still approve.
|
|
@@ -464,23 +563,97 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
464
563
|
const tabId = args && typeof args.tabId === 'string' ? args.tabId : null;
|
|
465
564
|
const prompt = args && typeof args.prompt === 'string' ? args.prompt : null;
|
|
466
565
|
if (!tabId || !prompt) {
|
|
467
|
-
return
|
|
566
|
+
return errorResult('missing required arguments: tabId, prompt');
|
|
468
567
|
}
|
|
469
568
|
const result = await adminRequest('POST', '/admin/chat/send-prompt', { tabId, prompt });
|
|
470
569
|
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
471
570
|
}
|
|
472
|
-
|
|
571
|
+
if (name === 'project_home_get_contract') {
|
|
572
|
+
const cwd = resolveCwdArg(args);
|
|
573
|
+
const qs = new URLSearchParams({ cwd });
|
|
574
|
+
const result = await adminRequest('GET', `/admin/project-home/contract?${qs.toString()}`);
|
|
575
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
576
|
+
}
|
|
577
|
+
if (name === 'project_home_validate_summary') {
|
|
578
|
+
if (!args || typeof args.summary !== 'object' || args.summary === null) {
|
|
579
|
+
return errorResult('missing required argument: summary');
|
|
580
|
+
}
|
|
581
|
+
const cwd = resolveCwdArg(args);
|
|
582
|
+
const result = await adminRequest('POST', '/admin/project-home/validate-summary', { cwd, summary: args.summary });
|
|
583
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
584
|
+
}
|
|
585
|
+
if (name === 'project_home_render') {
|
|
586
|
+
if (!args || typeof args.summary !== 'object' || args.summary === null) {
|
|
587
|
+
return errorResult('missing required argument: summary');
|
|
588
|
+
}
|
|
589
|
+
if (typeof args.picks !== 'object' || args.picks === null) {
|
|
590
|
+
return errorResult('missing required argument: picks');
|
|
591
|
+
}
|
|
592
|
+
const cwd = resolveCwdArg(args);
|
|
593
|
+
const result = await adminRequest('POST', '/admin/project-home/render', { cwd, summary: args.summary, picks: args.picks });
|
|
594
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
595
|
+
}
|
|
596
|
+
if (name === 'project_home_status') {
|
|
597
|
+
const cwd = resolveCwdArg(args);
|
|
598
|
+
const qs = new URLSearchParams({ cwd });
|
|
599
|
+
const result = await adminRequest('GET', `/admin/project-home/status?${qs.toString()}`);
|
|
600
|
+
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
|
|
601
|
+
}
|
|
602
|
+
if (name === 'session_manager_help') {
|
|
603
|
+
const toolName = args && typeof args.tool === 'string' ? args.tool : null;
|
|
604
|
+
const topic = args && typeof args.topic === 'string' ? args.topic : null;
|
|
605
|
+
|
|
606
|
+
const response = {};
|
|
607
|
+
|
|
608
|
+
if (toolName) {
|
|
609
|
+
const entry = CATALOG_BY_NAME.get(toolName);
|
|
610
|
+
if (!entry) {
|
|
611
|
+
const valid = MCP_TOOL_CATALOG.map((e) => e.name);
|
|
612
|
+
return errorResult(`unknown tool "${toolName}" for session_manager_help — valid tool names: ${valid.join(', ')}`);
|
|
613
|
+
}
|
|
614
|
+
response.tool = entry;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (topic) {
|
|
618
|
+
const recipe = MCP_RECIPES.find((r) => r.id === topic);
|
|
619
|
+
if (!recipe) {
|
|
620
|
+
const valid = MCP_RECIPES.map((r) => r.id);
|
|
621
|
+
return errorResult(`unknown topic "${topic}" for session_manager_help — valid topic ids: ${valid.join(', ')}`);
|
|
622
|
+
}
|
|
623
|
+
response.recipe = recipe;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (!toolName && !topic) {
|
|
627
|
+
response.tools = MCP_TOOL_CATALOG.map((e) => ({ name: e.name, group: e.group, purpose: e.purpose }));
|
|
628
|
+
response.recipes = MCP_RECIPES.map((r) => ({ id: r.id, title: r.title }));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
response.readiness = await fetchReadiness();
|
|
632
|
+
|
|
633
|
+
return { content: [{ type: 'text', text: JSON.stringify(response) }] };
|
|
634
|
+
}
|
|
635
|
+
return errorResult(`unknown tool: ${name}`);
|
|
473
636
|
} catch (e) {
|
|
474
|
-
return
|
|
637
|
+
return errorResult(e?.message ?? String(e));
|
|
475
638
|
}
|
|
476
|
-
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
server.setRequestHandler(CallToolRequestSchema, handleCallTool);
|
|
477
642
|
|
|
478
643
|
async function main() {
|
|
479
644
|
const transport = new StdioServerTransport();
|
|
480
645
|
await server.connect(transport);
|
|
481
646
|
}
|
|
482
647
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
648
|
+
// Guarded so mcpToolCatalog.test.cjs can `require()` this file to read TOOLS
|
|
649
|
+
// (for both-directions name-parity) without opening a stdio transport as a
|
|
650
|
+
// require-time side effect — only the real `node scripts/scheduler-mcp-server.cjs`
|
|
651
|
+
// invocation satisfies require.main === module.
|
|
652
|
+
if (require.main === module) {
|
|
653
|
+
main().catch((e) => {
|
|
654
|
+
process.stderr.write(`scheduler-mcp-server fatal: ${e?.stack ?? e}\n`);
|
|
655
|
+
process.exit(1);
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
module.exports = { TOOLS, handleCallTool, HELP_POINTER, NOT_RUNNING_ERROR };
|