@pathmode/mcp-server 1.4.5 → 1.6.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/index.js CHANGED
@@ -25565,7 +25565,7 @@ class Ajv {
25565
25565
  constructor(opts = {}) {
25566
25566
  this.schemas = {};
25567
25567
  this.refs = {};
25568
- this.formats = {};
25568
+ this.formats = Object.create(null);
25569
25569
  this._compilations = new Set();
25570
25570
  this._loading = {};
25571
25571
  this._cache = new Map();
@@ -28017,6 +28017,7 @@ exports["default"] = def;
28017
28017
 
28018
28018
  Object.defineProperty(exports, "__esModule", ({ value: true }));
28019
28019
  const code_1 = __nccwpck_require__(8484);
28020
+ const util_1 = __nccwpck_require__(4464);
28020
28021
  const codegen_1 = __nccwpck_require__(1436);
28021
28022
  const error = {
28022
28023
  message: ({ schemaCode }) => (0, codegen_1.str) `must match pattern "${schemaCode}"`,
@@ -28029,11 +28030,19 @@ const def = {
28029
28030
  $data: true,
28030
28031
  error,
28031
28032
  code(cxt) {
28032
- const { data, $data, schema, schemaCode, it } = cxt;
28033
- // TODO regexp should be wrapped in try/catchs
28033
+ const { gen, data, $data, schema, schemaCode, it } = cxt;
28034
28034
  const u = it.opts.unicodeRegExp ? "u" : "";
28035
- const regExp = $data ? (0, codegen_1._) `(new RegExp(${schemaCode}, ${u}))` : (0, code_1.usePattern)(cxt, schema);
28036
- cxt.fail$data((0, codegen_1._) `!${regExp}.test(${data})`);
28035
+ if ($data) {
28036
+ const { regExp } = it.opts.code;
28037
+ const regExpCode = regExp.code === "new RegExp" ? (0, codegen_1._) `new RegExp` : (0, util_1.useFunc)(gen, regExp);
28038
+ const valid = gen.let("valid");
28039
+ gen.try(() => gen.assign(valid, (0, codegen_1._) `${regExpCode}(${schemaCode}, ${u}).test(${data})`), () => gen.assign(valid, false));
28040
+ cxt.fail$data((0, codegen_1._) `!${valid}`);
28041
+ }
28042
+ else {
28043
+ const regExp = (0, code_1.usePattern)(cxt, schema);
28044
+ cxt.fail$data((0, codegen_1._) `!${regExp}.test(${data})`);
28045
+ }
28037
28046
  },
28038
28047
  };
28039
28048
  exports["default"] = def;
@@ -33680,6 +33689,198 @@ class PathmodeClient {
33680
33689
  exports.PathmodeClient = PathmodeClient;
33681
33690
 
33682
33691
 
33692
+ /***/ }),
33693
+
33694
+ /***/ 3783:
33695
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
33696
+
33697
+ "use strict";
33698
+
33699
+ /**
33700
+ * Pathmode MCP Install Skills Command
33701
+ *
33702
+ * Copies the bundled Claude Code skill pack into .claude/skills/
33703
+ * (project-local, default) or ~/.claude/skills/ (global, with --global).
33704
+ *
33705
+ * Usage:
33706
+ * npx @pathmode/mcp-server install-skills
33707
+ * npx @pathmode/mcp-server install-skills --global
33708
+ * npx @pathmode/mcp-server install-skills --force
33709
+ */
33710
+ var __importDefault = (this && this.__importDefault) || function (mod) {
33711
+ return (mod && mod.__esModule) ? mod : { "default": mod };
33712
+ };
33713
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
33714
+ exports.isInstallSkillsCommand = isInstallSkillsCommand;
33715
+ exports.runInstallSkills = runInstallSkills;
33716
+ const fs_1 = __importDefault(__nccwpck_require__(9896));
33717
+ const path_1 = __importDefault(__nccwpck_require__(6928));
33718
+ const os_1 = __importDefault(__nccwpck_require__(857));
33719
+ const BOLD = '\x1b[1m';
33720
+ const DIM = '\x1b[2m';
33721
+ const GREEN = '\x1b[32m';
33722
+ const RED = '\x1b[31m';
33723
+ const YELLOW = '\x1b[33m';
33724
+ const CYAN = '\x1b[36m';
33725
+ const RESET = '\x1b[0m';
33726
+ function log(msg) { console.log(msg); }
33727
+ function success(msg) { console.log(` ${GREEN}✓${RESET} ${msg}`); }
33728
+ function warn(msg) { console.log(` ${YELLOW}!${RESET} ${msg}`); }
33729
+ function fail(msg) { console.log(` ${RED}✗${RESET} ${msg}`); }
33730
+ function isInstallSkillsCommand(argv = process.argv) {
33731
+ return argv.includes('install-skills');
33732
+ }
33733
+ function getInstallSkillsArgs(argv = process.argv) {
33734
+ const idx = argv.findIndex(a => a === 'install-skills');
33735
+ return idx === -1 ? [] : argv.slice(idx + 1);
33736
+ }
33737
+ function shortenPath(p) {
33738
+ const home = os_1.default.homedir();
33739
+ return p.startsWith(home) ? '~' + p.slice(home.length) : p;
33740
+ }
33741
+ /**
33742
+ * Locate the bundled skills/ directory.
33743
+ *
33744
+ * When installed via npm, this file is at <pkg>/dist/install-skills.js
33745
+ * and skills/ sits at <pkg>/skills/. When running from source (ts-node),
33746
+ * this file is at <pkg>/src/install-skills.ts and skills/ is at <pkg>/skills/.
33747
+ *
33748
+ * Either way, going up one level from __dirname and joining "skills" finds it.
33749
+ */
33750
+ function findSkillsDir() {
33751
+ const candidates = [
33752
+ path_1.default.resolve(__dirname, '..', 'skills'), // npm install layout (dist/ -> ../skills)
33753
+ path_1.default.resolve(__dirname, '..', '..', 'skills'), // edge case if bundled deeper
33754
+ ];
33755
+ for (const c of candidates) {
33756
+ if (!fs_1.default.existsSync(c) || !fs_1.default.statSync(c).isDirectory())
33757
+ continue;
33758
+ // Sanity check: directory must contain at least one <name>/SKILL.md
33759
+ const entries = fs_1.default.readdirSync(c);
33760
+ const hasSkill = entries.some(e => fs_1.default.existsSync(path_1.default.join(c, e, 'SKILL.md')));
33761
+ if (hasSkill)
33762
+ return c;
33763
+ }
33764
+ return null;
33765
+ }
33766
+ function copyRecursive(src, dest) {
33767
+ if (!fs_1.default.existsSync(dest)) {
33768
+ fs_1.default.mkdirSync(dest, { recursive: true });
33769
+ }
33770
+ const entries = fs_1.default.readdirSync(src, { withFileTypes: true });
33771
+ for (const e of entries) {
33772
+ const srcPath = path_1.default.join(src, e.name);
33773
+ const destPath = path_1.default.join(dest, e.name);
33774
+ if (e.isDirectory()) {
33775
+ copyRecursive(srcPath, destPath);
33776
+ }
33777
+ else {
33778
+ fs_1.default.copyFileSync(srcPath, destPath);
33779
+ }
33780
+ }
33781
+ }
33782
+ async function runInstallSkills() {
33783
+ const args = getInstallSkillsArgs();
33784
+ const isGlobal = args.includes('--global');
33785
+ const isForce = args.includes('--force');
33786
+ const wantsHelp = args.includes('--help') || args.includes('-h');
33787
+ log('');
33788
+ log(`${BOLD}Pathmode Skills Install${RESET}`);
33789
+ log(`${DIM}──────────────────────${RESET}`);
33790
+ log('');
33791
+ if (wantsHelp) {
33792
+ log(`Usage: ${CYAN}npx @pathmode/mcp-server install-skills${RESET} [options]`);
33793
+ log('');
33794
+ log('Options:');
33795
+ log(` ${BOLD}--global${RESET} Install into ~/.claude/skills/ instead of ./.claude/skills/`);
33796
+ log(` ${BOLD}--force${RESET} Overwrite existing skill directories`);
33797
+ log(` ${BOLD}--help${RESET} Show this message`);
33798
+ log('');
33799
+ log('Copies the bundled Claude Code skill pack into your skills directory.');
33800
+ log('Skills auto-trigger when your natural-language request matches their description —');
33801
+ log('no slash commands needed.');
33802
+ log('');
33803
+ return;
33804
+ }
33805
+ const skillsDir = findSkillsDir();
33806
+ if (!skillsDir) {
33807
+ fail('Could not locate the bundled skills/ directory.');
33808
+ log(` Expected to find it at <package-root>/skills/ relative to ${shortenPath(__dirname)}.`);
33809
+ log(` If you cloned this repo and are running from source, ensure ${BOLD}skills/${RESET} exists at the package root.`);
33810
+ log('');
33811
+ process.exit(1);
33812
+ }
33813
+ const targetDir = isGlobal
33814
+ ? path_1.default.join(os_1.default.homedir(), '.claude', 'skills')
33815
+ : path_1.default.join(process.cwd(), '.claude', 'skills');
33816
+ log(` Source: ${DIM}${shortenPath(skillsDir)}${RESET}`);
33817
+ log(` Target: ${DIM}${shortenPath(targetDir)}${RESET}`);
33818
+ log('');
33819
+ if (!fs_1.default.existsSync(targetDir)) {
33820
+ try {
33821
+ fs_1.default.mkdirSync(targetDir, { recursive: true });
33822
+ }
33823
+ catch (err) {
33824
+ fail(`Could not create target directory: ${err.message}`);
33825
+ log('');
33826
+ process.exit(1);
33827
+ }
33828
+ }
33829
+ const entries = fs_1.default.readdirSync(skillsDir, { withFileTypes: true });
33830
+ let installed = 0;
33831
+ let skipped = 0;
33832
+ let overwritten = 0;
33833
+ for (const e of entries) {
33834
+ if (!e.isDirectory())
33835
+ continue;
33836
+ // Only copy directories that contain a SKILL.md
33837
+ const skillFile = path_1.default.join(skillsDir, e.name, 'SKILL.md');
33838
+ if (!fs_1.default.existsSync(skillFile))
33839
+ continue;
33840
+ const destPath = path_1.default.join(targetDir, e.name);
33841
+ const existsAlready = fs_1.default.existsSync(destPath);
33842
+ if (existsAlready && !isForce) {
33843
+ warn(`${e.name} ${DIM}(already installed — pass --force to overwrite)${RESET}`);
33844
+ skipped++;
33845
+ continue;
33846
+ }
33847
+ try {
33848
+ if (existsAlready && isForce) {
33849
+ fs_1.default.rmSync(destPath, { recursive: true, force: true });
33850
+ }
33851
+ copyRecursive(path_1.default.join(skillsDir, e.name), destPath);
33852
+ success(e.name);
33853
+ if (existsAlready)
33854
+ overwritten++;
33855
+ else
33856
+ installed++;
33857
+ }
33858
+ catch (err) {
33859
+ fail(`${e.name} — ${err.message}`);
33860
+ }
33861
+ }
33862
+ log('');
33863
+ const lines = [];
33864
+ if (installed > 0)
33865
+ lines.push(`${BOLD}${installed}${RESET} installed`);
33866
+ if (overwritten > 0)
33867
+ lines.push(`${BOLD}${overwritten}${RESET} updated`);
33868
+ if (skipped > 0)
33869
+ lines.push(`${BOLD}${skipped}${RESET} skipped`);
33870
+ log(` ${lines.join(' · ') || 'No skills processed.'}`);
33871
+ log('');
33872
+ if (installed + overwritten > 0) {
33873
+ log(` ${BOLD}Next:${RESET} Restart Claude Code so the new skills register at session start.`);
33874
+ log(` Then try: ${CYAN}"help me write a spec for [your problem]"${RESET}`);
33875
+ log('');
33876
+ }
33877
+ else if (skipped > 0) {
33878
+ log(` All skills already installed. Run with ${BOLD}--force${RESET} to overwrite.`);
33879
+ log('');
33880
+ }
33881
+ }
33882
+
33883
+
33683
33884
  /***/ }),
33684
33885
 
33685
33886
  /***/ 6488:
@@ -33702,6 +33903,7 @@ exports.getCompileIntentPrompt = getCompileIntentPrompt;
33702
33903
  exports.formatIntentMd = formatIntentMd;
33703
33904
  exports.formatCursorRules = formatCursorRules;
33704
33905
  exports.formatClaudeMdSection = formatClaudeMdSection;
33906
+ exports.formatOutcomeRubric = formatOutcomeRubric;
33705
33907
  /** Extract text from a string or structured outcome. */
33706
33908
  function getOutcomeText(o) {
33707
33909
  return typeof o === 'string' ? o : o.text;
@@ -34057,6 +34259,224 @@ function formatClaudeMdSection(spec) {
34057
34259
  sections.push('<!-- PATHMODE:END -->');
34058
34260
  return sections.join('\n\n');
34059
34261
  }
34262
+ function getOutcomePriority(o) {
34263
+ return typeof o === 'string' ? undefined : o.priority;
34264
+ }
34265
+ /** Map an outcome priority to grader-facing strength language. */
34266
+ function priorityToBar(priority) {
34267
+ switch (priority) {
34268
+ case 'should': return 'Expected';
34269
+ case 'could': return 'Optional — note if present, do not fail if absent';
34270
+ case 'must':
34271
+ default: return 'Required';
34272
+ }
34273
+ }
34274
+ /**
34275
+ * Dedup constraint strings by a normalized key — strips trailing parenthetical
34276
+ * refs (e.g. "(CON-2)"), trailing punctuation, and case, so an intent constraint
34277
+ * and its constitution twin collapse to one. Keeps the first (more specific) form.
34278
+ */
34279
+ function dedupeByNormalized(items) {
34280
+ const seen = new Set();
34281
+ const out = [];
34282
+ for (const raw of items) {
34283
+ const item = raw?.trim();
34284
+ if (!item)
34285
+ continue;
34286
+ const key = item
34287
+ .toLowerCase()
34288
+ .replace(/\s*\([^)]*\)\s*/g, ' ')
34289
+ .replace(/[.\s]+$/g, '')
34290
+ .replace(/\s+/g, ' ')
34291
+ .trim();
34292
+ if (seen.has(key))
34293
+ continue;
34294
+ seen.add(key);
34295
+ out.push(item);
34296
+ }
34297
+ return out;
34298
+ }
34299
+ /**
34300
+ * Build the writer-facing task description — what the agent reads. Maps to the
34301
+ * `description` field of a `user.define_outcome` event.
34302
+ */
34303
+ function buildWriterDescription(spec) {
34304
+ const sections = [];
34305
+ sections.push(`Deliver: ${spec.title || 'Untitled intent'}.`);
34306
+ if (spec.objective) {
34307
+ sections.push('');
34308
+ sections.push(`Why this matters: ${spec.objective}`);
34309
+ }
34310
+ if (spec.scope?.inScope?.length) {
34311
+ sections.push('');
34312
+ sections.push('In scope:');
34313
+ for (const item of spec.scope.inScope)
34314
+ sections.push(`- ${item}`);
34315
+ }
34316
+ if (spec.scope?.outOfScope?.length) {
34317
+ sections.push('');
34318
+ sections.push('Out of scope — do not do these:');
34319
+ for (const item of spec.scope.outOfScope)
34320
+ sections.push(`- ${item}`);
34321
+ }
34322
+ const ic = spec.implementationContext;
34323
+ if (ic?.relevantAreas?.length) {
34324
+ sections.push('');
34325
+ sections.push('Relevant areas of the codebase:');
34326
+ for (const a of ic.relevantAreas) {
34327
+ if (a?.path?.trim())
34328
+ sections.push(`- ${a.path}${a.reason?.trim() ? ` — ${a.reason}` : ''}`);
34329
+ }
34330
+ }
34331
+ if (ic?.currentBehavior?.trim()) {
34332
+ sections.push('');
34333
+ sections.push(`Current behavior: ${ic.currentBehavior.trim()}`);
34334
+ }
34335
+ sections.push('');
34336
+ sections.push('Write your deliverables to /mnt/session/outputs/. Iterate until the grader is satisfied.');
34337
+ return sections.join('\n');
34338
+ }
34339
+ /**
34340
+ * Build the grader-facing rubric — what the independent grader reads. Maps to the
34341
+ * `rubric` field of a `user.define_outcome` event. Every criterion is written to
34342
+ * force the grader to find concrete evidence rather than trust the writer.
34343
+ */
34344
+ function buildGraderRubric(spec, opts = {}) {
34345
+ const sections = [];
34346
+ sections.push(`You are grading the artifact(s) in /mnt/session/outputs/ for: "${spec.title || 'Untitled intent'}".`);
34347
+ sections.push('Score each criterion below independently. PASS a criterion only when you can point to concrete evidence in the artifact — a measured value, a visible state, a passing test, or a quoted line. The writer asserting it is done is NOT evidence.');
34348
+ // Outcomes → coverage criteria
34349
+ const outcomes = (spec.outcomes ?? []).filter((o) => getOutcomeText(o)?.trim());
34350
+ if (outcomes.length) {
34351
+ sections.push('');
34352
+ sections.push('## Outcomes (coverage)');
34353
+ let i = 1;
34354
+ for (const o of outcomes) {
34355
+ const bar = priorityToBar(getOutcomePriority(o));
34356
+ sections.push(`${i}. [${bar}] ${getOutcomeText(o)}`);
34357
+ sections.push(' - Evidence: point to the specific value, state, or test result in the artifact that proves this. If you cannot, mark FAIL.');
34358
+ i++;
34359
+ }
34360
+ }
34361
+ // Edge cases → must-handle criteria (structured edge cases + implementation-context risks)
34362
+ const edgeCases = (spec.edgeCases ?? []).filter((ec) => ec.scenario?.trim() || ec.expectedBehavior?.trim());
34363
+ const risks = (spec.implementationContext?.risks ?? []).filter((r) => r?.trim());
34364
+ if (edgeCases.length || risks.length) {
34365
+ sections.push('');
34366
+ sections.push('## Edge cases (must handle)');
34367
+ for (const ec of edgeCases) {
34368
+ sections.push(`- ${ec.scenario} → expected: ${ec.expectedBehavior}`);
34369
+ sections.push(` - Evidence: locate or construct the "${ec.scenario}" condition and confirm the expected behavior occurs. Absent handling = FAIL.`);
34370
+ }
34371
+ for (const r of risks) {
34372
+ sections.push(`- Guard against: ${r}`);
34373
+ sections.push(' - Evidence: show the artifact handles this failure mode — a test, a guard clause, or a visible safe state. Unaddressed = FAIL.');
34374
+ }
34375
+ }
34376
+ // Constraints + constitution + out-of-scope → out-of-bounds / no-fire list
34377
+ const outOfBounds = [];
34378
+ if (spec.constraints?.length)
34379
+ outOfBounds.push(...spec.constraints);
34380
+ if (opts.constitutionRules?.length)
34381
+ outOfBounds.push(...opts.constitutionRules);
34382
+ if (spec.scope?.outOfScope?.length) {
34383
+ for (const item of spec.scope.outOfScope)
34384
+ outOfBounds.push(`Stays out of scope: ${item}`);
34385
+ }
34386
+ const cleanedBounds = dedupeByNormalized(outOfBounds);
34387
+ if (cleanedBounds.length) {
34388
+ sections.push('');
34389
+ sections.push('## Constraints (out of bounds — FAIL the artifact if any are violated)');
34390
+ for (const c of cleanedBounds)
34391
+ sections.push(`- ${c}`);
34392
+ }
34393
+ // Verification → procedures the grader must run to produce evidence
34394
+ const v = spec.verification;
34395
+ const checks = [];
34396
+ for (const t of v?.e2eTests ?? [])
34397
+ if (t?.trim())
34398
+ checks.push(`[e2e] ${t}`);
34399
+ for (const t of v?.unitTests ?? [])
34400
+ if (t?.trim())
34401
+ checks.push(`[unit] ${t}`);
34402
+ for (const t of v?.manualChecks ?? [])
34403
+ if (t?.trim())
34404
+ checks.push(`[manual] ${t}`);
34405
+ for (const t of spec.implementationContext?.verificationSuggestions ?? [])
34406
+ if (t?.trim())
34407
+ checks.push(`[suggested] ${t}`);
34408
+ if (checks.length) {
34409
+ sections.push('');
34410
+ sections.push('## Checks to run (produce the evidence yourself)');
34411
+ for (const c of checks)
34412
+ sections.push(`- ${c}`);
34413
+ }
34414
+ // Health metrics → observable-signal criteria
34415
+ const metrics = (spec.healthMetrics ?? []).filter((m) => m?.trim());
34416
+ if (metrics.length) {
34417
+ sections.push('');
34418
+ sections.push('## Observable signals');
34419
+ for (const m of metrics) {
34420
+ sections.push(`- The artifact leaves a way to measure: ${m}`);
34421
+ }
34422
+ }
34423
+ // Grader output format — proven shape from the Outcomes cookbook
34424
+ sections.push('');
34425
+ sections.push('## Output format');
34426
+ sections.push('Line 1: a scoreboard — "Outcomes X/Y met. Constraints OK|VIOLATED. Edge cases X/Y."');
34427
+ sections.push('Then one bullet per FAILED item only: "<section> <item> — FAIL. <what is missing and what to change>." One sentence per bullet.');
34428
+ sections.push('Do not fail the artifact for style preferences, pre-existing issues outside this intent, or anything not listed above.');
34429
+ return sections.join('\n');
34430
+ }
34431
+ /**
34432
+ * Generate a Claude Managed Agents "Outcomes" rubric document for an intent.
34433
+ *
34434
+ * Outcomes (`user.define_outcome`) takes two separate fields: a `description`
34435
+ * the writer agent reads, and a `rubric` the independent grader reads. This
34436
+ * exporter renders both — clearly separated so they paste straight into the API —
34437
+ * turning the intent's outcomes, edge cases, constraints, constitution rules and
34438
+ * verification into checkable, evidence-forcing grader criteria.
34439
+ *
34440
+ * Docs: https://platform.claude.com/docs/en/managed-agents/define-outcomes
34441
+ */
34442
+ function formatOutcomeRubric(spec, opts = {}) {
34443
+ const maxIterations = opts.maxIterations ?? 5;
34444
+ const description = buildWriterDescription(spec);
34445
+ const rubric = buildGraderRubric(spec, opts);
34446
+ const hasVerification = [
34447
+ ...(spec.verification?.e2eTests ?? []),
34448
+ ...(spec.verification?.unitTests ?? []),
34449
+ ...(spec.verification?.manualChecks ?? []),
34450
+ ].some((t) => t?.trim());
34451
+ const doc = [];
34452
+ doc.push('<!-- Pathmode → Claude Managed Agents: Outcomes rubric -->');
34453
+ doc.push(`<!-- Generated ${new Date().toISOString()} | pathmode.io -->`);
34454
+ doc.push('');
34455
+ doc.push('# How to use this');
34456
+ doc.push('');
34457
+ doc.push('Send a `user.define_outcome` event to a Managed Agents session (beta header `managed-agents-2026-04-01`):');
34458
+ doc.push('- Put **Writer Description** into the `description` field (the agent reads this).');
34459
+ doc.push('- Put **Grader Rubric** into the `rubric` field (the independent grader reads this).');
34460
+ doc.push(`- Suggested \`max_iterations\`: ${maxIterations} (Outcomes default 3, max 20).`);
34461
+ if (!hasVerification) {
34462
+ doc.push('');
34463
+ doc.push('> ⚠ This intent has no verification defined, so grader criteria fall back to generic evidence requirements. Run `verify-intent` to sharpen the checks.');
34464
+ }
34465
+ doc.push('');
34466
+ doc.push('---');
34467
+ doc.push('');
34468
+ doc.push('# Writer Description');
34469
+ doc.push('');
34470
+ doc.push(description);
34471
+ doc.push('');
34472
+ doc.push('---');
34473
+ doc.push('');
34474
+ doc.push('# Grader Rubric');
34475
+ doc.push('');
34476
+ doc.push(rubric);
34477
+ doc.push('');
34478
+ return doc.join('\n');
34479
+ }
34060
34480
 
34061
34481
 
34062
34482
  /***/ }),
@@ -64125,9 +64545,11 @@ var exports = __webpack_exports__;
64125
64545
  * Connects Claude Code, Cursor, and other AI agents to your Intent Layer.
64126
64546
  *
64127
64547
  * Usage:
64128
- * npx @pathmode/mcp-server # Cloud mode (uses ~/.pathmode/config.json)
64129
- * npx @pathmode/mcp-server --local # Local mode (reads intent.md from cwd)
64130
- * npx @pathmode/mcp-server setup pm_live_xxx # Auto-configure your tools
64548
+ * npx @pathmode/mcp-server # Cloud mode (uses ~/.pathmode/config.json)
64549
+ * npx @pathmode/mcp-server --local # Local mode (reads intent.md from cwd)
64550
+ * npx @pathmode/mcp-server setup pm_live_xxx # Auto-configure your tools
64551
+ * npx @pathmode/mcp-server install-skills # Copy the skill pack into .claude/skills/
64552
+ * npx @pathmode/mcp-server install-skills --global # Install into ~/.claude/skills/ instead
64131
64553
  *
64132
64554
  * The Intent Compiler (compile-intent prompt, intent_save, intent_export tools)
64133
64555
  * works without an API key — zero-config intent spec building in Claude Code.
@@ -64166,16 +64588,23 @@ const api_client_1 = __nccwpck_require__(7475);
64166
64588
  const local_reader_1 = __nccwpck_require__(3518);
64167
64589
  const intent_compiler_1 = __nccwpck_require__(6488);
64168
64590
  const setup_1 = __nccwpck_require__(8294);
64591
+ const install_skills_1 = __nccwpck_require__(3783);
64169
64592
  // ─── Subcommand routing ───────────────────────────────────────
64170
- // `setup` uses stdout for human-readable output and must run
64171
- // before StdioServerTransport claims stdout for JSON-RPC.
64172
- // We call startMcpServer() only when NOT in setup mode.
64593
+ // Human-readable subcommands (`setup`, `install-skills`) use stdout
64594
+ // and must run before StdioServerTransport claims stdout for JSON-RPC.
64595
+ // We call startMcpServer() only when NOT in a subcommand.
64173
64596
  if ((0, setup_1.isSetupCommand)()) {
64174
64597
  (0, setup_1.runSetup)().then(() => process.exit(0)).catch((err) => {
64175
64598
  console.error(err);
64176
64599
  process.exit(1);
64177
64600
  });
64178
64601
  }
64602
+ else if ((0, install_skills_1.isInstallSkillsCommand)()) {
64603
+ (0, install_skills_1.runInstallSkills)().then(() => process.exit(0)).catch((err) => {
64604
+ console.error(err);
64605
+ process.exit(1);
64606
+ });
64607
+ }
64179
64608
  else {
64180
64609
  startMcpServer();
64181
64610
  }
@@ -64199,7 +64628,7 @@ function startMcpServer() {
64199
64628
  // ============================================================
64200
64629
  const server = new mcp_js_1.McpServer({
64201
64630
  name: 'pathmode',
64202
- version: '1.4.4',
64631
+ version: '1.4.5',
64203
64632
  });
64204
64633
  // Annotation presets
64205
64634
  const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
@@ -64214,6 +64643,28 @@ function startMcpServer() {
64214
64643
  class CloudClientError extends Error {
64215
64644
  constructor() { super(CLOUD_REQUIRED_MSG); this.name = 'CloudClientError'; }
64216
64645
  }
64646
+ function normalizeText(value) {
64647
+ return (value || '').trim();
64648
+ }
64649
+ // Keep this selection heuristic aligned with the canonical readiness rules in
64650
+ // /Users/jannelammi/code/Pathmode/lib/intentReadiness.ts. The MCP package cannot
64651
+ // import app/lib code directly, so this mirrors only the minimum logic needed
64652
+ // for get_current_intent fallback behavior.
64653
+ function isPlaceholderIntent(intent) {
64654
+ const title = normalizeText(intent.title).toLowerCase();
64655
+ const objective = normalizeText(intent.objective);
64656
+ const outcomeCount = (intent.outcomes || []).filter((outcome) => normalizeText(typeof outcome === 'string' ? outcome : outcome?.text).length > 0).length;
64657
+ return title === 'new intent' || title === 'untitled intent' || objective.length < 15 || outcomeCount === 0;
64658
+ }
64659
+ function pickCurrentIntent(intents) {
64660
+ const approvedReady = intents.find((intent) => intent.status === 'approved' && !isPlaceholderIntent(intent));
64661
+ if (approvedReady)
64662
+ return approvedReady;
64663
+ const anyReady = intents.find((intent) => !isPlaceholderIntent(intent));
64664
+ if (anyReady)
64665
+ return anyReady;
64666
+ return intents[0];
64667
+ }
64217
64668
  // Note: The MCP SDK catches errors thrown in tool handlers and returns them as
64218
64669
  // error text results. CloudClientError thrown by requireCloudClient() will
64219
64670
  // surface its message to the client without crashing the server.
@@ -64222,14 +64673,14 @@ function startMcpServer() {
64222
64673
  // ============================================================
64223
64674
  server.registerTool('get_current_intent', {
64224
64675
  title: 'Get Current Intent',
64225
- description: 'Get the currently active intent (first approved, or most recently updated). Returns the full IntentSpec with objective, outcomes, constraints, and edge cases.',
64676
+ description: 'Get the currently active intent, preferring approved intents with real objective/outcome content over empty stubs. Returns the full IntentSpec with objective, outcomes, constraints, and edge cases.',
64226
64677
  inputSchema: { status: zod_1.z.string().optional().describe('Filter by status: draft, validated, approved, shipped, verified') },
64227
64678
  annotations: READ_ONLY,
64228
64679
  }, async ({ status }) => {
64229
64680
  if (isLocalMode) {
64230
64681
  const intents = (0, local_reader_1.readLocalIntents)();
64231
64682
  const filtered = status ? intents.filter(i => i.status === status) : intents;
64232
- const current = filtered[0];
64683
+ const current = pickCurrentIntent(filtered);
64233
64684
  if (!current) {
64234
64685
  return { content: [{ type: 'text', text: 'No intents found locally.' }] };
64235
64686
  }
@@ -64242,9 +64693,9 @@ function startMcpServer() {
64242
64693
  if (allIntents.length === 0) {
64243
64694
  return { content: [{ type: 'text', text: 'No intents found in workspace.' }] };
64244
64695
  }
64245
- return { content: [{ type: 'text', text: JSON.stringify(allIntents[0], null, 2) }] };
64696
+ return { content: [{ type: 'text', text: JSON.stringify(pickCurrentIntent(allIntents), null, 2) }] };
64246
64697
  }
64247
- return { content: [{ type: 'text', text: JSON.stringify(intents[0], null, 2) }] };
64698
+ return { content: [{ type: 'text', text: JSON.stringify(pickCurrentIntent(intents), null, 2) }] };
64248
64699
  });
64249
64700
  server.registerTool('list_intents', {
64250
64701
  title: 'List Intents',
@@ -64540,11 +64991,27 @@ function startMcpServer() {
64540
64991
  return { content: [{ type: 'text', text: `Graph analysis failed: ${e.message}` }] };
64541
64992
  }
64542
64993
  });
64994
+ /** Map a cloud ApiIntent into the IntentFields shape the formatters consume. */
64995
+ function apiIntentToFields(intent) {
64996
+ const v = (intent.verification || {});
64997
+ return {
64998
+ id: intent.id,
64999
+ title: intent.title,
65000
+ objective: intent.objective,
65001
+ outcomes: intent.outcomes ?? [],
65002
+ constraints: intent.constraints,
65003
+ edgeCases: (intent.edgeCases ?? []).map((e) => ({ scenario: e.scenario, expectedBehavior: e.expectedBehavior })),
65004
+ healthMetrics: intent.healthMetrics,
65005
+ scope: intent.scope,
65006
+ verification: { manualChecks: v.manualChecks, unitTests: v.unitTests, e2eTests: v.e2eTests },
65007
+ implementationContext: intent.implementationContext ?? undefined,
65008
+ };
65009
+ }
64543
65010
  server.registerTool('export_context', {
64544
65011
  title: 'Export Context',
64545
- description: 'Export workspace context as a formatted file. Use "claude-md" for CLAUDE.md (full workspace context), "cursorrules" for Cursor AI rules, or "intent-md" for a single intent specification file. For cursorrules/intent-md, product context is always derived from the resolved intent. For claude-md, pass productId to select a specific product, otherwise the first active product is used.',
65012
+ description: 'Export workspace context as a formatted file. Use "claude-md" for CLAUDE.md (full workspace context), "cursorrules" for Cursor AI rules, "intent-md" for a single intent specification file, or "outcome-rubric" for a Claude Managed Agents Outcomes rubric (writer description + evidence-forcing grader rubric) derived from the resolved intent, its implementation context, and the workspace constitution. For cursorrules/intent-md/outcome-rubric, product context is always derived from the resolved intent. For claude-md, pass productId to select a specific product, otherwise the first active product is used.',
64546
65013
  inputSchema: {
64547
- format: zod_1.z.enum(['claude-md', 'cursorrules', 'intent-md']).describe('Export format'),
65014
+ format: zod_1.z.enum(['claude-md', 'cursorrules', 'intent-md', 'outcome-rubric']).describe('Export format'),
64548
65015
  intentId: zod_1.z.string().optional().describe('Intent ID (optional, for cursorrules and intent-md)'),
64549
65016
  productId: zod_1.z.string().optional().describe('Product ID (optional, only used for claude-md format to select a specific product)'),
64550
65017
  },
@@ -64554,6 +65021,25 @@ function startMcpServer() {
64554
65021
  return { content: [{ type: 'text', text: 'Export requires cloud mode. Use PATHMODE_API_KEY to connect.' }] };
64555
65022
  }
64556
65023
  try {
65024
+ if (format === 'outcome-rubric') {
65025
+ const cloud = requireCloudClient();
65026
+ const intent = intentId
65027
+ ? await cloud.getIntent(intentId)
65028
+ : (pickCurrentIntent(await cloud.listIntents('approved')) || pickCurrentIntent(await cloud.listIntents()));
65029
+ if (!intent) {
65030
+ return { content: [{ type: 'text', text: 'No intent found to export. Pass an intentId, or create an approved intent first.' }] };
65031
+ }
65032
+ let constitutionRules = [];
65033
+ try {
65034
+ const constitution = await cloud.getConstitution();
65035
+ constitutionRules = (constitution?.rules ?? [])
65036
+ .filter((r) => r?.isActive !== false && r?.text?.trim())
65037
+ .map((r) => r.text.trim());
65038
+ }
65039
+ catch { /* constitution is optional context */ }
65040
+ const rubric = (0, intent_compiler_1.formatOutcomeRubric)(apiIntentToFields(intent), { constitutionRules });
65041
+ return { content: [{ type: 'text', text: rubric }] };
65042
+ }
64557
65043
  const content = await requireCloudClient().exportContext(format, intentId, productId);
64558
65044
  return { content: [{ type: 'text', text: content }] };
64559
65045
  }
@@ -64937,8 +65423,8 @@ function startMcpServer() {
64937
65423
  }],
64938
65424
  };
64939
65425
  });
64940
- server.tool('intent_export', 'Export an intent spec as .cursorrules or CLAUDE.md section for AI agent consumption.', {
64941
- format: zod_1.z.enum(['cursorrules', 'claude-md']).describe('Export format'),
65426
+ server.tool('intent_export', 'Export an intent spec as .cursorrules, a CLAUDE.md section, or a Claude Managed Agents Outcomes rubric for AI agent consumption.', {
65427
+ format: zod_1.z.enum(['cursorrules', 'claude-md', 'outcome-rubric']).describe('Export format'),
64942
65428
  spec: zod_1.z.object(intentSpecSchema),
64943
65429
  path: zod_1.z.string().optional().describe('Output file path. Defaults to .cursorrules or CLAUDE.md'),
64944
65430
  }, async ({ format, spec, path }) => {
@@ -64953,6 +65439,17 @@ function startMcpServer() {
64953
65439
  }],
64954
65440
  };
64955
65441
  }
65442
+ else if (format === 'outcome-rubric') {
65443
+ const content = (0, intent_compiler_1.formatOutcomeRubric)(spec);
65444
+ const filePath = (0, path_1.resolve)(process.cwd(), path || 'outcome-rubric.md');
65445
+ (0, fs_1.writeFileSync)(filePath, content, 'utf-8');
65446
+ return {
65447
+ content: [{
65448
+ type: 'text',
65449
+ text: `✓ Exported Outcomes rubric to ${filePath}\n\nPaste the Writer Description into the \`description\` field and the Grader Rubric into the \`rubric\` field of a Managed Agents \`user.define_outcome\` event. Docs: https://platform.claude.com/docs/en/managed-agents/define-outcomes`,
65450
+ }],
65451
+ };
65452
+ }
64956
65453
  else {
64957
65454
  const section = (0, intent_compiler_1.formatClaudeMdSection)(spec);
64958
65455
  const filePath = (0, path_1.resolve)(process.cwd(), path || 'CLAUDE.md');
@@ -64962,7 +65459,10 @@ function startMcpServer() {
64962
65459
  existing = (0, fs_1.readFileSync)(filePath, 'utf-8');
64963
65460
  }
64964
65461
  catch { /* file doesn't exist yet */ }
64965
- const marker = /<!-- PATHMODE:START -->[\s\S]*?<!-- PATHMODE:END -->/;
65462
+ // Tolerant of the suffixed start marker emitted by formatClaudeMdSection
65463
+ // (`<!-- PATHMODE:START - Do not edit... -->`), so re-exports replace
65464
+ // the block instead of appending a duplicate.
65465
+ const marker = /<!-- PATHMODE:START[\s\S]*?-->[\s\S]*?<!-- PATHMODE:END -->/;
64966
65466
  const updated = marker.test(existing)
64967
65467
  ? existing.replace(marker, section)
64968
65468
  : existing ? existing + '\n\n' + section : section;