@pathmode/mcp-server 1.18.0 → 1.20.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
@@ -39170,6 +39170,562 @@ module.exports = function(str) {
39170
39170
  };
39171
39171
 
39172
39172
 
39173
+ /***/ }),
39174
+
39175
+ /***/ 229:
39176
+ /***/ ((__unused_webpack_module, exports) => {
39177
+
39178
+ "use strict";
39179
+
39180
+ /**
39181
+ * serializeIntentMd — the one implementation of the intent.md file format.
39182
+ *
39183
+ * Two writers used to exist: `lib/agentPromptGenerator.ts` (cloud exports, browser downloads) and
39184
+ * `packages/mcp-server/src/intent-compiler.ts` (every local, keyless write). They diverged in BOTH
39185
+ * directions, and the divergence cost more than tidiness:
39186
+ *
39187
+ * - the cloud writer emitted no `## Confirmations`, so a human's authenticated confirmation died
39188
+ * at the boundary and never reached the repo;
39189
+ * - the local writer emitted no authorization gate, so an agent proposal still pending human
39190
+ * judgment lost its DO-NOT-IMPLEMENT banner on the next local save.
39191
+ *
39192
+ * Both are the same failure: judgment recorded in one place not travelling to the other. This
39193
+ * module emits the union, and `serializer-parity.test.ts` holds the two callers to it.
39194
+ *
39195
+ * ZERO IMPORTS, deliberately. The local writer is bundled by ncc into a published npm package that
39196
+ * must run keyless and offline, and the cloud writer is reached from `'use client'` components — so
39197
+ * a single node builtin here (`crypto`, say) would break the browser bundle. Callers compute their
39198
+ * own environment-specific values (id minting, readiness verdicts, evidence resolution) and hand
39199
+ * the results in as plain data.
39200
+ */
39201
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
39202
+ exports.toSerializableChecks = toSerializableChecks;
39203
+ exports.renderAuthorizationGateText = renderAuthorizationGateText;
39204
+ exports.renderConfirmationsBody = renderConfirmationsBody;
39205
+ exports.serializeIntentMd = serializeIntentMd;
39206
+ // ── Small helpers ───────────────────────────────────────────────────────────
39207
+ const VERIFICATION_KIND_LABELS = {
39208
+ fastest: 'Fastest check',
39209
+ 'shipped-signal': 'Shipped signal',
39210
+ 'regression-guard': 'Regression guard',
39211
+ manual: 'Manual check',
39212
+ test: 'Automated test',
39213
+ };
39214
+ const VERIFICATION_KIND_ORDER = ['fastest', 'shipped-signal', 'regression-guard', 'manual', 'test'];
39215
+ const VERIFICATION_KIND_SET = new Set(VERIFICATION_KIND_ORDER);
39216
+ function nonEmpty(v) {
39217
+ return typeof v === 'string' && v.trim().length > 0;
39218
+ }
39219
+ /** YAML double-quoted scalars. Both writers used to interpolate raw, so a product named `The "Real"
39220
+ * One` produced a file gray-matter could not parse. */
39221
+ function yamlScalar(value) {
39222
+ if (typeof value === 'number')
39223
+ return String(value);
39224
+ return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
39225
+ }
39226
+ function outcomeOf(o) {
39227
+ return typeof o === 'string' ? { text: o } : o;
39228
+ }
39229
+ function constraintOf(c) {
39230
+ return typeof c === 'string' ? { text: c } : c;
39231
+ }
39232
+ /** `backed by:` suffix on its own indented bullet. Empty when nothing resolved, so callers append
39233
+ * unconditionally. */
39234
+ function backedBySuffix(refs, indent = ' ') {
39235
+ const clean = (refs || []).filter(nonEmpty);
39236
+ if (!clean.length)
39237
+ return '';
39238
+ return `\n${indent}- backed by: ${clean.join('; ')}`;
39239
+ }
39240
+ /** Read verification uniformly as a check collection: canonical `checks[]` plus the legacy
39241
+ * {manual,unit,e2e} buckets, which are adapted rather than dropped. */
39242
+ function toSerializableChecks(v) {
39243
+ if (!v || typeof v !== 'object')
39244
+ return [];
39245
+ const out = [];
39246
+ for (const c of Array.isArray(v.checks) ? v.checks : []) {
39247
+ if (!nonEmpty(c?.description))
39248
+ continue;
39249
+ out.push({
39250
+ kind: VERIFICATION_KIND_SET.has(String(c.kind)) ? String(c.kind) : 'test',
39251
+ description: c.description.trim(),
39252
+ status: c.status,
39253
+ verifies: c.verifies,
39254
+ });
39255
+ }
39256
+ for (const d of Array.isArray(v.manualChecks) ? v.manualChecks : []) {
39257
+ if (nonEmpty(d))
39258
+ out.push({ kind: 'manual', description: d.trim() });
39259
+ }
39260
+ for (const d of Array.isArray(v.unitTests) ? v.unitTests : []) {
39261
+ if (nonEmpty(d))
39262
+ out.push({ kind: 'test', description: d.trim() });
39263
+ }
39264
+ for (const d of Array.isArray(v.e2eTests) ? v.e2eTests : []) {
39265
+ if (nonEmpty(d))
39266
+ out.push({ kind: 'test', description: d.trim() });
39267
+ }
39268
+ return out;
39269
+ }
39270
+ function groupChecks(v) {
39271
+ const checks = toSerializableChecks(v);
39272
+ return VERIFICATION_KIND_ORDER
39273
+ .map(kind => ({ kind, label: VERIFICATION_KIND_LABELS[kind], checks: checks.filter(c => c.kind === kind) }))
39274
+ .filter(g => g.checks.length > 0);
39275
+ }
39276
+ function checkLine(c) {
39277
+ const verifies = nonEmpty(c.verifies) ? ` (verifies: ${c.verifies})` : '';
39278
+ const status = nonEmpty(c.status) && c.status !== 'unknown' ? ` [${c.status}]` : '';
39279
+ return `${c.description}${verifies}${status}`;
39280
+ }
39281
+ // ── The authorization gate ──────────────────────────────────────────────────
39282
+ /**
39283
+ * The DO-NOT-IMPLEMENT banner for an agent proposal a human has not authorized.
39284
+ *
39285
+ * Lives here, not in either caller, because the local writer used to omit it entirely: a pending
39286
+ * proposal pulled into a repo and re-saved came back without its gate, which is the one thing in
39287
+ * the file an agent must not be able to lose.
39288
+ *
39289
+ * Trailing blank line included, so callers can prepend unconditionally.
39290
+ */
39291
+ function renderAuthorizationGateText(opts) {
39292
+ if (opts.origin !== 'agent' || opts.authorization === 'authorized')
39293
+ return '';
39294
+ if (opts.authorization === 'rejected') {
39295
+ const note = nonEmpty(opts.authorizationNote) ? ` The human's note: "${opts.authorizationNote}".` : '';
39296
+ return `> **REJECTED BY HUMAN REVIEW — DO NOT IMPLEMENT.** This spec was proposed by an agent and a human rejected it.${note} Revise the proposal per the note, or ask your operator before proceeding.\n\n`;
39297
+ }
39298
+ return `> **PENDING HUMAN AUTHORIZATION — DO NOT IMPLEMENT YET.** This spec was proposed by an agent and no human has authorized it. Ask your operator to review it in Pathmode before building against it.\n\n`;
39299
+ }
39300
+ // ── Confirmations ───────────────────────────────────────────────────────────
39301
+ /**
39302
+ * Render confirmation records as the `## Confirmations` section body.
39303
+ *
39304
+ * Derived fields (`assurance`, `source`) are NOT written: anything read back out of a file is
39305
+ * local-unverified by definition, so persisting a trust level would let a record vouch for itself.
39306
+ * Records outside the dimensions the readiness gate can resolve are dropped rather than emitted,
39307
+ * so an unwaivable dimension never leaves an empty section behind.
39308
+ */
39309
+ function renderConfirmationsBody(records) {
39310
+ const emittable = (records || []).filter(c => {
39311
+ const dim = String(c?.dimension ?? '').toLowerCase();
39312
+ const kind = String(c?.kind ?? '').toLowerCase();
39313
+ const by = String(c?.by ?? '').toLowerCase();
39314
+ return ['objective', 'outcomes'].includes(dim)
39315
+ && ['confirmed', 'waived'].includes(kind)
39316
+ && ['agent', 'human'].includes(by);
39317
+ });
39318
+ if (!emittable.length)
39319
+ return '';
39320
+ const out = ['## Confirmations'];
39321
+ for (const c of emittable) {
39322
+ out.push('');
39323
+ out.push(`**${String(c.dimension).toLowerCase()}** — ${String(c.kind).toLowerCase()} by ${String(c.by).toLowerCase()}`);
39324
+ for (const key of ['actor', 'problem', 'outcome', 'observable', 'reason', 'anchor', 'at']) {
39325
+ const v = c[key];
39326
+ if (!nonEmpty(v))
39327
+ continue;
39328
+ out.push(`- ${key}: ${v.replace(/\s+/g, ' ').trim()}`);
39329
+ }
39330
+ }
39331
+ return out.join('\n');
39332
+ }
39333
+ // ── The serializer ──────────────────────────────────────────────────────────
39334
+ /**
39335
+ * Frontmatter key order. Fixed so a file rewritten by the other implementation produces a clean
39336
+ * diff instead of a reordering churn that hides the real change.
39337
+ */
39338
+ const FRONTMATTER_ORDER = [
39339
+ 'id', 'version', 'status', 'readiness', 'source', 'specVersion',
39340
+ 'origin', 'authorization', 'authorizationNote', 'evidence', 'space', 'severity', 'created', 'updated',
39341
+ ];
39342
+ function serializeIntentMd(spec, opts = {}) {
39343
+ const values = {
39344
+ id: spec.id,
39345
+ version: opts.version && opts.version >= 1 ? opts.version : 1,
39346
+ status: opts.status || 'draft',
39347
+ readiness: opts.readiness,
39348
+ source: opts.source,
39349
+ specVersion: opts.specVersion,
39350
+ origin: opts.origin,
39351
+ // Only meaningful for agent-originated specs; human-authored ones are authorized by
39352
+ // authorship and carrying a key here would imply a gate that does not exist.
39353
+ authorization: opts.origin === 'agent' ? (opts.authorization ?? 'pending') : undefined,
39354
+ // The banner renders the note as prose, but prose is not recoverable: a reader cannot tell
39355
+ // the reviewer's words from the sentence around them. Persisting it here is what stops a
39356
+ // local rewrite from keeping the REJECTED verdict while erasing the correction it asked
39357
+ // for. Single-line by contract, like a confirmation value; flattened rather than escaped.
39358
+ authorizationNote: opts.origin === 'agent' && nonEmpty(opts.authorizationNote)
39359
+ ? opts.authorizationNote.replace(/\s+/g, ' ').trim()
39360
+ : undefined,
39361
+ evidence: opts.evidence?.length || undefined,
39362
+ space: opts.space?.name,
39363
+ severity: opts.severity,
39364
+ created: opts.created,
39365
+ updated: opts.updated,
39366
+ };
39367
+ // null, not just undefined: the cloud mapper reads absent columns as null (`problemSeverity`,
39368
+ // `currentState`, a product with no name), and an unguarded null reached the quoter as a
39369
+ // crash rather than an omitted key.
39370
+ const yamlLines = FRONTMATTER_ORDER
39371
+ .filter(key => values[key] !== undefined && values[key] !== null && values[key] !== '')
39372
+ .map(key => `${key}: ${yamlScalar(values[key])}`)
39373
+ .join('\n');
39374
+ const sections = ['---', yamlLines, '---', ''];
39375
+ // Directly under the frontmatter: the `authorization` key is machine-readable, but the body
39376
+ // must also say DO NOT IMPLEMENT before any instruction an agent might act on.
39377
+ const gate = renderAuthorizationGateText(opts).trim();
39378
+ if (gate) {
39379
+ sections.push(gate);
39380
+ sections.push('');
39381
+ }
39382
+ sections.push(`# ${spec.title || 'Untitled Intent'}`);
39383
+ const space = opts.space;
39384
+ if (space && (nonEmpty(space.productVision) || nonEmpty(space.northStar) || nonEmpty(space.targetAudience)
39385
+ || space.constraints?.length || space.principles?.length)) {
39386
+ sections.push('');
39387
+ sections.push('## Space Context');
39388
+ if (nonEmpty(space.productVision))
39389
+ sections.push(`**Product Vision**: ${space.productVision}`);
39390
+ if (nonEmpty(space.northStar))
39391
+ sections.push(`**North Star**: ${space.northStar}`);
39392
+ if (nonEmpty(space.targetAudience))
39393
+ sections.push(`**Target Audience**: ${space.targetAudience}`);
39394
+ if (space.constraints?.length)
39395
+ sections.push('**Constraints**: ' + space.constraints.join(', '));
39396
+ if (space.principles?.length)
39397
+ sections.push('**Principles**: ' + space.principles.join(', '));
39398
+ }
39399
+ if (nonEmpty(spec.objective)) {
39400
+ sections.push('');
39401
+ sections.push('## Objective');
39402
+ sections.push(spec.objective);
39403
+ }
39404
+ // Heading text is the round-trip key (the reader keys sections by exact heading) — keep it
39405
+ // plain, exactly like Objective above.
39406
+ if (nonEmpty(spec.currentState)) {
39407
+ sections.push('');
39408
+ sections.push('## Current State');
39409
+ sections.push(spec.currentState.trim());
39410
+ }
39411
+ // Sits next to Current State on purpose: one is what the author says is true today, the other
39412
+ // is what the repo says. Same heading in both modes, so a handoff reads identically either way.
39413
+ const icBody = implementationContextBody(spec);
39414
+ if (icBody.length) {
39415
+ sections.push('');
39416
+ sections.push('## Implementation Context');
39417
+ sections.push(...icBody);
39418
+ }
39419
+ const decisions = (spec.decisions || []).filter(d => d && nonEmpty(d.choice) && typeof d.reason === 'string');
39420
+ if (decisions.length) {
39421
+ sections.push('');
39422
+ sections.push('## Decisions & Ruled-Out Alternatives');
39423
+ for (const d of decisions) {
39424
+ sections.push(`- **${d.choice}**${nonEmpty(d.ruledOut) ? ` (instead of: ${d.ruledOut})` : ''} — ${d.reason}`);
39425
+ if (nonEmpty(d.reopenTrigger))
39426
+ sections.push(` - reopen if: ${d.reopenTrigger.trim()}`);
39427
+ }
39428
+ }
39429
+ const outcomes = (spec.outcomes || []).map(outcomeOf).filter(o => nonEmpty(o.text));
39430
+ if (outcomes.length) {
39431
+ sections.push('');
39432
+ sections.push('## Outcomes');
39433
+ // No priority prefix. The writer used to emit `[MUST] `, which is in neither SPEC.md nor the
39434
+ // normalization corpus, and neither parser strips it — so it read back as part of the
39435
+ // outcome text. Emitting a label the format cannot read back is worse than omitting it.
39436
+ for (const o of outcomes) {
39437
+ sections.push(`- [ ] ${o.text}${backedBySuffix(o.backedBy)}`);
39438
+ }
39439
+ }
39440
+ if (spec.scope?.inScope?.length || spec.scope?.outOfScope?.length) {
39441
+ sections.push('');
39442
+ sections.push('## Scope');
39443
+ if (spec.scope.inScope?.length) {
39444
+ sections.push('**In scope:**');
39445
+ for (const s of spec.scope.inScope)
39446
+ sections.push(`- ${s}`);
39447
+ }
39448
+ if (spec.scope.outOfScope?.length) {
39449
+ sections.push('**Out of scope:**');
39450
+ for (const s of spec.scope.outOfScope)
39451
+ sections.push(`- ${s}`);
39452
+ }
39453
+ }
39454
+ const constraints = (spec.constraints || []).map(constraintOf).filter(c => nonEmpty(c.text));
39455
+ if (constraints.length) {
39456
+ sections.push('');
39457
+ sections.push('## Constraints');
39458
+ for (const c of constraints)
39459
+ sections.push(`- ${c.text}${backedBySuffix(c.backedBy)}`);
39460
+ }
39461
+ const edgeCases = (spec.edgeCases || []).filter(ec => ec && nonEmpty(ec.scenario));
39462
+ if (edgeCases.length) {
39463
+ sections.push('');
39464
+ sections.push('## Edge Cases');
39465
+ for (const ec of edgeCases) {
39466
+ sections.push(`- **${ec.scenario}**: ${ec.expectedBehavior}${backedBySuffix(ec.backedBy)}`);
39467
+ }
39468
+ }
39469
+ const evidenceBody = renderCompactEvidence(opts.evidence);
39470
+ if (evidenceBody.length) {
39471
+ sections.push('');
39472
+ sections.push('## Supporting Evidence');
39473
+ sections.push(...evidenceBody);
39474
+ }
39475
+ if (spec.healthMetrics?.length) {
39476
+ sections.push('');
39477
+ sections.push('## Health Metrics');
39478
+ for (const metric of spec.healthMetrics)
39479
+ sections.push(`- ${metric}`);
39480
+ }
39481
+ const checkGroups = groupChecks(spec.verification);
39482
+ if (checkGroups.length) {
39483
+ sections.push('');
39484
+ sections.push('## Verification');
39485
+ sections.push('_A feedback loop, not just a test list._');
39486
+ for (const g of checkGroups) {
39487
+ sections.push(`**${g.label}**:`);
39488
+ for (const c of g.checks)
39489
+ sections.push(`- [ ] ${checkLine(c)}`);
39490
+ }
39491
+ }
39492
+ // `## Confirmations` is emitted LAST so it never sits between the fields a reader is comparing,
39493
+ // and so appending one cannot shift any other section's parse.
39494
+ const confirmationsBody = renderConfirmationsBody(spec.confirmations);
39495
+ if (confirmationsBody) {
39496
+ sections.push('');
39497
+ sections.push(confirmationsBody);
39498
+ }
39499
+ return sections.join('\n');
39500
+ }
39501
+ /**
39502
+ * The body of `## Implementation Context`.
39503
+ *
39504
+ * Two sources, one section. Local mode carries prose the agent gathered; cloud intents carry the
39505
+ * analyzer's structured object. Rendering both under the same heading is what lets a spec move
39506
+ * between modes without the section disappearing on the way through.
39507
+ */
39508
+ function implementationContextBody(spec) {
39509
+ if (nonEmpty(spec.implementationContextText))
39510
+ return [spec.implementationContextText.trim()];
39511
+ const ic = spec.implementationContext;
39512
+ if (!ic)
39513
+ return [];
39514
+ const lines = [];
39515
+ if (ic.relevantAreas?.length) {
39516
+ lines.push('### Relevant areas');
39517
+ for (const a of ic.relevantAreas) {
39518
+ if (nonEmpty(a?.path))
39519
+ lines.push(`- \`${a.path}\`${nonEmpty(a.reason) ? ` — ${a.reason}` : ''}`);
39520
+ }
39521
+ }
39522
+ if (nonEmpty(ic.currentBehavior)) {
39523
+ if (lines.length)
39524
+ lines.push('');
39525
+ lines.push('### Current behavior');
39526
+ lines.push(ic.currentBehavior.trim());
39527
+ }
39528
+ if (ic.risks?.length) {
39529
+ if (lines.length)
39530
+ lines.push('');
39531
+ lines.push('### Risks');
39532
+ for (const r of ic.risks)
39533
+ if (nonEmpty(r))
39534
+ lines.push(`- ${r.trim()}`);
39535
+ }
39536
+ if (ic.verificationSuggestions?.length) {
39537
+ if (lines.length)
39538
+ lines.push('');
39539
+ lines.push('### Verification suggestions');
39540
+ for (const v of ic.verificationSuggestions)
39541
+ if (nonEmpty(v))
39542
+ lines.push(`- ${v.trim()}`);
39543
+ }
39544
+ return lines;
39545
+ }
39546
+ /** Compact evidence list — one truncated line per item, quotes as blockquotes. Lighter than the
39547
+ * execution prompt's full rendering so a committed intent.md stays readable. */
39548
+ function renderCompactEvidence(evidence) {
39549
+ const items = (evidence || []).filter(e => nonEmpty(e?.content));
39550
+ if (!items.length)
39551
+ return [];
39552
+ const lines = [];
39553
+ const truncate = (s) => (s.length > 160 ? `${s.slice(0, 160)}…` : s);
39554
+ for (const q of items.filter(e => e.type === 'quote')) {
39555
+ lines.push(`> "${truncate(q.content)}"${nonEmpty(q.source) ? ` — ${q.source}` : ''}`);
39556
+ }
39557
+ for (const e of items.filter(e => e.type !== 'quote')) {
39558
+ const severity = nonEmpty(e.severity) ? ` (${e.severity})` : '';
39559
+ lines.push(`- [${e.type}]${severity} ${truncate(e.content)}${nonEmpty(e.source) ? ` — ${e.source}` : ''}`);
39560
+ }
39561
+ return lines;
39562
+ }
39563
+
39564
+
39565
+ /***/ }),
39566
+
39567
+ /***/ 7797:
39568
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
39569
+
39570
+ "use strict";
39571
+
39572
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39573
+ return (mod && mod.__esModule) ? mod : { "default": mod };
39574
+ };
39575
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
39576
+ exports.isAdoptCommand = isAdoptCommand;
39577
+ exports.patchAdoptedIntentFrontmatter = patchAdoptedIntentFrontmatter;
39578
+ exports.runAdopt = runAdopt;
39579
+ const fs_1 = __importDefault(__nccwpck_require__(9896));
39580
+ const path_1 = __importDefault(__nccwpck_require__(6928));
39581
+ const api_client_1 = __nccwpck_require__(7475);
39582
+ const local_reader_1 = __nccwpck_require__(3518);
39583
+ const push_spec_1 = __nccwpck_require__(4239);
39584
+ const project_config_1 = __nccwpck_require__(581);
39585
+ const CODE_RE = /^pm_adopt_[A-Za-z0-9_-]{32}$/;
39586
+ function isAdoptCommand(argv = process.argv) {
39587
+ return argv.includes('adopt');
39588
+ }
39589
+ function getAdoptCode(argv = process.argv) {
39590
+ const index = argv.findIndex(arg => arg === 'adopt');
39591
+ return index < 0 ? undefined : argv.slice(index + 1).find(arg => CODE_RE.test(arg));
39592
+ }
39593
+ function apiOrigin(argv = process.argv) {
39594
+ const apiArg = argv.find(arg => arg.startsWith('--api-url='));
39595
+ if (apiArg)
39596
+ return apiArg.slice('--api-url='.length).replace(/\/$/, '');
39597
+ return argv.includes('--staging') ? 'https://staging.pathmode.io' : 'https://pathmode.io';
39598
+ }
39599
+ async function exchangeCode(code, origin, existing) {
39600
+ const response = await fetch(`${origin}/api/v1/onboarding/repo/exchange`, {
39601
+ method: 'POST',
39602
+ headers: {
39603
+ 'Content-Type': 'application/json',
39604
+ ...(existing?.apiKey ? { Authorization: `Bearer ${existing.apiKey}` } : {}),
39605
+ },
39606
+ body: JSON.stringify({ code }),
39607
+ });
39608
+ const body = await response.json().catch(() => ({}));
39609
+ if (!response.ok || !body.sessionId || !body.workspaceId || !body.productId) {
39610
+ throw new Error(body.error || `Adoption code exchange failed (${response.status})`);
39611
+ }
39612
+ if (!body.apiKey && !body.reusedCredential)
39613
+ throw new Error('The adoption exchange returned no credential.');
39614
+ return body;
39615
+ }
39616
+ /** Add cloud identity to the existing frontmatter without reformatting the user's spec body. */
39617
+ function patchAdoptedIntentFrontmatter(filePath, values) {
39618
+ const raw = fs_1.default.readFileSync(filePath, 'utf8');
39619
+ const newline = raw.includes('\r\n') ? '\r\n' : '\n';
39620
+ if (!raw.startsWith(`---${newline}`)) {
39621
+ throw new Error('intent.md must start with YAML frontmatter before it can be adopted.');
39622
+ }
39623
+ const close = raw.indexOf(`${newline}---`, 4);
39624
+ if (close < 0)
39625
+ throw new Error('intent.md has an unterminated YAML frontmatter block.');
39626
+ const headerEnd = 3 + newline.length;
39627
+ const managed = new Set(['id', 'specVersion', 'source', 'origin', 'authorization', 'authorizationNote']);
39628
+ const lines = raw.slice(headerEnd, close).split(/\r?\n/).filter(line => {
39629
+ const match = line.match(/^([A-Za-z][A-Za-z0-9_-]*):/);
39630
+ return !match || !managed.has(match[1]);
39631
+ });
39632
+ const add = (key, value) => {
39633
+ if (value !== undefined && value !== null && value !== '')
39634
+ lines.push(`${key}: ${JSON.stringify(value)}`);
39635
+ };
39636
+ add('id', values.id);
39637
+ add('specVersion', values.specVersion);
39638
+ add('source', values.sourceUrl);
39639
+ add('origin', values.origin);
39640
+ add('authorization', values.authorization);
39641
+ add('authorizationNote', values.authorizationNote);
39642
+ fs_1.default.writeFileSync(filePath, `---${newline}${lines.join(newline)}${raw.slice(close)}`, 'utf8');
39643
+ }
39644
+ async function runAdopt(argv = process.argv) {
39645
+ const code = getAdoptCode(argv);
39646
+ if (!code)
39647
+ throw new Error('Usage: npx @pathmode/mcp-server@latest adopt pm_adopt_…');
39648
+ const filePath = path_1.default.join(process.cwd(), 'intent.md');
39649
+ const intent = (0, local_reader_1.readIntentFile)(filePath);
39650
+ const meta = (0, local_reader_1.readIntentMeta)(filePath);
39651
+ if (!intent || !meta?.id) {
39652
+ throw new Error('No valid intent.md with an id was found in this directory.');
39653
+ }
39654
+ console.log('');
39655
+ console.log('Pathmode repo adoption');
39656
+ console.log('──────────────────────');
39657
+ console.log(` Found: ${intent.title}`);
39658
+ process.stdout.write(' Connecting…');
39659
+ const origin = apiOrigin(argv);
39660
+ const current = (0, api_client_1.loadConfig)();
39661
+ let exchange;
39662
+ try {
39663
+ exchange = await exchangeCode(code, origin, current?.apiUrl.replace(/\/$/, '') === origin ? current : null);
39664
+ }
39665
+ catch (error) {
39666
+ // A valid key for a different workspace is not useful for this adoption, but it
39667
+ // must not block minting the correctly scoped project credential.
39668
+ if (current
39669
+ && error instanceof Error
39670
+ && (error.message.includes('different workspace') || error.message.includes('Invalid API key'))) {
39671
+ exchange = await exchangeCode(code, origin, null);
39672
+ }
39673
+ else {
39674
+ throw error;
39675
+ }
39676
+ }
39677
+ const apiKey = exchange.apiKey || current?.apiKey;
39678
+ if (!apiKey)
39679
+ throw new Error('No repository credential is available after adoption exchange.');
39680
+ const config = {
39681
+ apiKey,
39682
+ apiUrl: exchange.apiUrl,
39683
+ workspaceId: exchange.workspaceId,
39684
+ };
39685
+ // Persist immediately after the one-time exchange. If a later network call fails, the
39686
+ // credential remains recoverable from this repo without minting a duplicate key.
39687
+ (0, project_config_1.saveProjectConnection)(config);
39688
+ (0, project_config_1.ensureProjectMcpConfig)();
39689
+ const client = new api_client_1.PathmodeClient(config);
39690
+ const pushed = await (0, push_spec_1.pushSpec)({
39691
+ client,
39692
+ id: meta.id,
39693
+ ...(meta.specVersion ? { existingSpecVersion: meta.specVersion } : {}),
39694
+ payload: {
39695
+ title: intent.title,
39696
+ objective: intent.objective,
39697
+ ...(intent.currentState ? { currentState: intent.currentState } : {}),
39698
+ outcomes: intent.outcomes,
39699
+ constraints: intent.constraints,
39700
+ healthMetrics: intent.healthMetrics,
39701
+ edgeCases: intent.edgeCases,
39702
+ verification: intent.verification,
39703
+ ...(intent.scope ? { scope: intent.scope } : {}),
39704
+ productId: exchange.productId,
39705
+ },
39706
+ decisions: intent.decisions,
39707
+ implementationContext: intent.implementationContext,
39708
+ onIdentitySettled: settled => patchAdoptedIntentFrontmatter(filePath, {
39709
+ id: settled.canonicalId,
39710
+ specVersion: settled.specVersion,
39711
+ sourceUrl: settled.sourceUrl,
39712
+ origin: settled.origin,
39713
+ authorization: settled.authorization,
39714
+ authorizationNote: settled.authorizationNote,
39715
+ }),
39716
+ });
39717
+ if (!pushed.ok)
39718
+ throw new Error(pushed.error);
39719
+ await client.completeRepoOnboarding(exchange.sessionId, pushed.canonicalId);
39720
+ console.log(' ✓');
39721
+ console.log(` Adopted into ${exchange.workspaceName} / ${exchange.productName}`);
39722
+ console.log(` ${pushed.sourceUrl}`);
39723
+ console.log('');
39724
+ console.log('Restart your coding tool once so it reloads the connected Pathmode server.');
39725
+ console.log('');
39726
+ }
39727
+
39728
+
39173
39729
  /***/ }),
39174
39730
 
39175
39731
  /***/ 7475:
@@ -39191,6 +39747,7 @@ exports.loadConfig = loadConfig;
39191
39747
  const fs_1 = __importDefault(__nccwpck_require__(9896));
39192
39748
  const path_1 = __importDefault(__nccwpck_require__(6928));
39193
39749
  const os_1 = __importDefault(__nccwpck_require__(857));
39750
+ const project_config_1 = __nccwpck_require__(581);
39194
39751
  const isDebug = process.env.PATHMODE_MCP_DEBUG === '1';
39195
39752
  /** An API failure that keeps the response body, so callers can act on `code` and its payload. */
39196
39753
  class ApiError extends Error {
@@ -39218,6 +39775,12 @@ function normalizeApiKey(raw) {
39218
39775
  return key;
39219
39776
  }
39220
39777
  function loadConfig() {
39778
+ // A complete repository binding is the most specific configuration. It wins
39779
+ // over process/global defaults on this machine; CI still falls through to env
39780
+ // because the external home-secret file is intentionally not in the repository.
39781
+ const projectConfig = (0, project_config_1.loadProjectConnection)();
39782
+ if (projectConfig)
39783
+ return projectConfig;
39221
39784
  const envKey = normalizeApiKey(process.env.PATHMODE_API_KEY);
39222
39785
  if (envKey) {
39223
39786
  return {
@@ -39370,6 +39933,13 @@ class PathmodeClient {
39370
39933
  const res = await this.fetch(`/intents/${id}`);
39371
39934
  return res.json();
39372
39935
  }
39936
+ async completeRepoOnboarding(sessionId, intentId) {
39937
+ const res = await this.fetch('/onboarding/repo/complete', {
39938
+ method: 'POST',
39939
+ body: JSON.stringify({ sessionId, intentId }),
39940
+ });
39941
+ await res.json();
39942
+ }
39373
39943
  async getIntentPrompt(id, agentType = 'claude-code', mode = 'execute') {
39374
39944
  const res = await this.fetch(`/intents/${id}/prompt?agent_type=${agentType}&mode=${mode}`);
39375
39945
  return res.json();
@@ -39548,6 +40118,7 @@ function formatHelp(version) {
39548
40118
  ' npx @pathmode/mcp-server Start the server (stdio). This is what an',
39549
40119
  ' MCP client runs; it waits on stdin by design.',
39550
40120
  ' npx @pathmode/mcp-server setup [key] Configure Claude Code/Desktop, Cursor, Windsurf.',
40121
+ ' npx @pathmode/mcp-server adopt <code> Adopt this repo\'s existing intent.md.',
39551
40122
  ' npx @pathmode/mcp-server install-skills Install the skill pack into this project.',
39552
40123
  ' npx @pathmode/mcp-server --version Print the version.',
39553
40124
  ' npx @pathmode/mcp-server --help Print this help.',
@@ -39792,6 +40363,10 @@ exports.writeConfirmationRecord = writeConfirmationRecord;
39792
40363
  // Verification check collection. Mirrors lib/verification in the main app — this package is
39793
40364
  // standalone (own ncc build) and can't import it, so the type + adapter live here. `kind` is the
39794
40365
  const crypto_1 = __nccwpck_require__(6982);
40366
+ // The intent.md file format itself. Shared verbatim with the cloud writer
40367
+ // (lib/agentPromptGenerator.ts) so a spec crossing between local and connected mode keeps every
40368
+ // section — notably `## Confirmations` and the authorization gate, which each side used to drop.
40369
+ const serializeIntentMd_1 = __nccwpck_require__(229);
39795
40370
  const VERIFICATION_KIND_LABELS = {
39796
40371
  fastest: 'Fastest check',
39797
40372
  'shipped-signal': 'Shipped signal',
@@ -39984,185 +40559,27 @@ function decisionLines(decisions, heading) {
39984
40559
  * is saved twice must keep its identity and its lifecycle state. Only a genuinely new intent
39985
40560
  * (no id) gets a minted id, version 1, and status 'draft'.
39986
40561
  */
39987
- /**
39988
- * The body of `## Implementation Context` in intent.md.
39989
- *
39990
- * Two sources, one section. Local mode carries prose the agent gathered; cloud intents carry the
39991
- * analyzer's structured object. Rendering both under the same heading is what lets a spec move
39992
- * between modes without the section disappearing on the way through.
39993
- */
39994
- function implementationContextBody(spec) {
39995
- const text = spec.implementationContextText?.trim();
39996
- if (text)
39997
- return [text];
39998
- const ic = spec.implementationContext;
39999
- if (!ic)
40000
- return [];
40001
- const lines = [];
40002
- if (ic.relevantAreas?.length) {
40003
- lines.push('### Relevant areas');
40004
- for (const a of ic.relevantAreas) {
40005
- if (a?.path?.trim())
40006
- lines.push(`- \`${a.path}\`${a.reason?.trim() ? ` — ${a.reason}` : ''}`);
40007
- }
40008
- }
40009
- if (ic.currentBehavior?.trim()) {
40010
- if (lines.length)
40011
- lines.push('');
40012
- lines.push('### Current behavior');
40013
- lines.push(ic.currentBehavior.trim());
40014
- }
40015
- if (ic.risks?.length) {
40016
- if (lines.length)
40017
- lines.push('');
40018
- lines.push('### Risks');
40019
- for (const r of ic.risks)
40020
- if (r?.trim())
40021
- lines.push(`- ${r.trim()}`);
40022
- }
40023
- if (ic.verificationSuggestions?.length) {
40024
- if (lines.length)
40025
- lines.push('');
40026
- lines.push('### Verification suggestions');
40027
- for (const v of ic.verificationSuggestions)
40028
- if (v?.trim())
40029
- lines.push(`- ${v.trim()}`);
40030
- }
40031
- return lines;
40032
- }
40033
40562
  function formatIntentMd(spec, opts = {}) {
40034
40563
  const now = new Date().toISOString();
40035
- const frontmatter = {
40564
+ // The only thing this wrapper still owns is id minting, which needs `crypto` and therefore
40565
+ // cannot live in the shared serializer (it is imported from browser bundles too).
40566
+ return (0, serializeIntentMd_1.serializeIntentMd)({
40567
+ ...spec,
40568
+ // Only a genuinely new intent gets a minted id; a re-save keeps its identity.
40036
40569
  id: spec.id || (0, crypto_1.randomUUID)(),
40037
- version: opts.version && opts.version >= 1 ? opts.version : 1,
40038
- status: opts.status || 'draft',
40039
- ...(opts.readiness ? { readiness: opts.readiness } : {}),
40040
- ...(opts.source ? { source: opts.source } : {}),
40041
- ...(opts.specVersion ? { specVersion: opts.specVersion } : {}),
40570
+ confirmations: spec.confirmations,
40571
+ }, {
40572
+ version: opts.version,
40573
+ status: opts.status,
40574
+ readiness: opts.readiness,
40575
+ source: opts.source,
40576
+ specVersion: opts.specVersion,
40042
40577
  created: opts.created || now,
40043
40578
  updated: now,
40044
- };
40045
- const yamlLines = Object.entries(frontmatter)
40046
- .map(([key, value]) => `${key}: ${typeof value === 'string' ? `"${value}"` : value}`)
40047
- .join('\n');
40048
- const sections = [];
40049
- sections.push('---');
40050
- sections.push(yamlLines);
40051
- sections.push('---');
40052
- sections.push('');
40053
- sections.push(`# ${spec.title || 'Untitled Intent'}`);
40054
- if (spec.objective) {
40055
- sections.push('');
40056
- sections.push('## Objective');
40057
- sections.push(spec.objective);
40058
- }
40059
- // Heading text is the round-trip key (readIntentFile keys sections by exact
40060
- // heading) — keep it plain, exactly like Objective above.
40061
- if (spec.currentState?.trim()) {
40062
- sections.push('');
40063
- sections.push('## Current State');
40064
- sections.push(spec.currentState.trim());
40065
- }
40066
- // Sits next to Current State on purpose: one is what the author says is true today, the other is
40067
- // what the repo says. Same heading the cloud agent prompt emits, so a handoff reads identically
40068
- // in either mode.
40069
- const icBody = implementationContextBody(spec);
40070
- if (icBody.length) {
40071
- sections.push('');
40072
- sections.push('## Implementation Context');
40073
- sections.push(...icBody);
40074
- }
40075
- sections.push(...decisionLines(spec.decisions, '## Decisions & Ruled-Out Alternatives'));
40076
- if (spec.outcomes?.length) {
40077
- sections.push('');
40078
- sections.push('## Outcomes');
40079
- for (const outcome of spec.outcomes) {
40080
- sections.push(`- [ ] ${getPriorityLabel(outcome)}${getOutcomeText(outcome)}`);
40081
- }
40082
- }
40083
- if (spec.scope?.inScope?.length || spec.scope?.outOfScope?.length) {
40084
- sections.push('');
40085
- sections.push('## Scope');
40086
- if (spec.scope.inScope?.length) {
40087
- sections.push('**In scope:**');
40088
- for (const s of spec.scope.inScope)
40089
- sections.push(`- ${s}`);
40090
- }
40091
- if (spec.scope.outOfScope?.length) {
40092
- sections.push('**Out of scope:**');
40093
- for (const s of spec.scope.outOfScope)
40094
- sections.push(`- ${s}`);
40095
- }
40096
- }
40097
- if (spec.constraints?.length) {
40098
- sections.push('');
40099
- sections.push('## Constraints');
40100
- for (const constraint of spec.constraints) {
40101
- sections.push(`- ${constraint}`);
40102
- }
40103
- }
40104
- if (spec.edgeCases?.length) {
40105
- sections.push('');
40106
- sections.push('## Edge Cases');
40107
- for (const ec of spec.edgeCases) {
40108
- sections.push(`- **${ec.scenario}**: ${ec.expectedBehavior}`);
40109
- }
40110
- }
40111
- if (spec.healthMetrics?.length) {
40112
- sections.push('');
40113
- sections.push('## Health Metrics');
40114
- for (const metric of spec.healthMetrics) {
40115
- sections.push(`- ${metric}`);
40116
- }
40117
- }
40118
- const intentMdChecks = groupVerificationChecks(spec.verification);
40119
- if (intentMdChecks.length) {
40120
- sections.push('');
40121
- sections.push('## Verification');
40122
- sections.push('_A feedback loop, not just a test list._');
40123
- for (const g of intentMdChecks) {
40124
- sections.push(`**${g.label}**:`);
40125
- for (const c of g.checks)
40126
- sections.push(`- [ ] ${renderCheckLine(c)}`);
40127
- }
40128
- }
40129
- // `## Confirmations` is emitted LAST so it never sits between the fields a reader is
40130
- // comparing, and so appending one cannot shift any other section's parse. Derived fields
40131
- // (assurance, source) are NOT written: anything read from a file is local-unverified.
40132
- const rawConfirmations = spec.confirmations;
40133
- const confirmations = Array.isArray(rawConfirmations)
40134
- ? rawConfirmations
40135
- : [];
40136
- // Filter BEFORE emitting the heading: a spec whose only confirmation targets an
40137
- // unwaivable dimension must not leave an empty `## Confirmations` section behind.
40138
- const emittable = confirmations.filter((c) => {
40139
- const dim = String(c.dimension ?? '').toLowerCase();
40140
- const kind = String(c.kind ?? '').toLowerCase();
40141
- const by = String(c.by ?? '').toLowerCase();
40142
- return ['objective', 'outcomes'].includes(dim)
40143
- && ['confirmed', 'waived'].includes(kind)
40144
- && ['agent', 'human'].includes(by);
40145
- });
40146
- if (emittable.length) {
40147
- sections.push('');
40148
- sections.push('## Confirmations');
40149
- for (const c of emittable) {
40150
- const dim = String(c.dimension ?? '').toLowerCase();
40151
- const kind = String(c.kind ?? '').toLowerCase();
40152
- const by = String(c.by ?? '').toLowerCase();
40153
- sections.push('');
40154
- sections.push(`**${dim}** — ${kind} by ${by}`);
40155
- for (const key of ['actor', 'problem', 'outcome', 'observable', 'reason', 'anchor', 'at']) {
40156
- const v = c[key];
40157
- if (typeof v !== 'string' || !v.trim())
40158
- continue;
40159
- // Values are single-line by contract; the writer flattens rather than escaping.
40160
- sections.push(`- ${key}: ${v.replace(/\s+/g, ' ').trim()}`);
40161
- }
40162
- }
40163
- }
40164
- sections.push('');
40165
- return sections.join('\n');
40579
+ origin: opts.origin,
40580
+ authorization: opts.authorization,
40581
+ authorizationNote: opts.authorizationNote,
40582
+ }) + '\n';
40166
40583
  }
40167
40584
  // ============================================================
40168
40585
  // Format: .cursorrules
@@ -40576,30 +40993,10 @@ function spliceConfirmationsSection(original, section) {
40576
40993
  const after = lines.slice(end).join('\n').replace(/^\s*/, '');
40577
40994
  return `${before}\n\n${section.trim()}\n${after ? `\n${after}` : ''}`;
40578
40995
  }
40579
- /** Render confirmation records as the `## Confirmations` section body. */
40996
+ /** Render confirmation records as the `## Confirmations` section body. Thin wrapper over the
40997
+ * shared serializer so the splice path and the full-file write cannot format records differently. */
40580
40998
  function renderConfirmationsSection(records) {
40581
- const emittable = records.filter((c) => {
40582
- const dim = String(c.dimension ?? '').toLowerCase();
40583
- const kind = String(c.kind ?? '').toLowerCase();
40584
- const by = String(c.by ?? '').toLowerCase();
40585
- return ['objective', 'outcomes'].includes(dim)
40586
- && ['confirmed', 'waived'].includes(kind)
40587
- && ['agent', 'human'].includes(by);
40588
- });
40589
- if (!emittable.length)
40590
- return '';
40591
- const out = ['## Confirmations'];
40592
- for (const c of emittable) {
40593
- out.push('');
40594
- out.push(`**${String(c.dimension).toLowerCase()}** — ${String(c.kind).toLowerCase()} by ${String(c.by).toLowerCase()}`);
40595
- for (const key of ['actor', 'problem', 'outcome', 'observable', 'reason', 'anchor', 'at']) {
40596
- const v = c[key];
40597
- if (typeof v !== 'string' || !v.trim())
40598
- continue;
40599
- out.push(`- ${key}: ${v.replace(/\s+/g, ' ').trim()}`);
40600
- }
40601
- }
40602
- return out.join('\n');
40999
+ return (0, serializeIntentMd_1.renderConfirmationsBody)(records);
40603
41000
  }
40604
41001
  function writeConfirmationRecord(opts) {
40605
41002
  let raw = opts.read();
@@ -40734,6 +41131,9 @@ function readIntentMeta(filePath) {
40734
41131
  status: typeof data.status === 'string' && data.status.trim() ? data.status.trim() : 'draft',
40735
41132
  created: typeof data.created === 'string' ? data.created : undefined,
40736
41133
  specVersion: typeof data.specVersion === 'string' && data.specVersion.trim() ? data.specVersion.trim() : undefined,
41134
+ origin: typeof data.origin === 'string' && data.origin.trim() ? data.origin.trim() : undefined,
41135
+ authorization: typeof data.authorization === 'string' && data.authorization.trim() ? data.authorization.trim() : undefined,
41136
+ authorizationNote: typeof data.authorizationNote === 'string' && data.authorizationNote.trim() ? data.authorizationNote.trim() : undefined,
40737
41137
  };
40738
41138
  }
40739
41139
  catch {
@@ -41798,6 +42198,83 @@ function mergePathmodeSection(existing, section) {
41798
42198
  }
41799
42199
 
41800
42200
 
42201
+ /***/ }),
42202
+
42203
+ /***/ 581:
42204
+ /***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
42205
+
42206
+ "use strict";
42207
+
42208
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42209
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42210
+ };
42211
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
42212
+ exports.projectBindingFile = projectBindingFile;
42213
+ exports.loadProjectConnection = loadProjectConnection;
42214
+ exports.saveProjectConnection = saveProjectConnection;
42215
+ exports.ensureProjectMcpConfig = ensureProjectMcpConfig;
42216
+ const fs_1 = __importDefault(__nccwpck_require__(9896));
42217
+ const os_1 = __importDefault(__nccwpck_require__(857));
42218
+ const path_1 = __importDefault(__nccwpck_require__(6928));
42219
+ const homeWorkspaceFile = (workspaceId, homeDir = os_1.default.homedir()) => path_1.default.join(homeDir, '.pathmode', 'workspaces', `${workspaceId}.json`);
42220
+ function projectBindingFile(cwd = process.cwd()) {
42221
+ return path_1.default.join(cwd, '.pathmode', 'config.json');
42222
+ }
42223
+ function readJson(filePath) {
42224
+ try {
42225
+ return JSON.parse(fs_1.default.readFileSync(filePath, 'utf8'));
42226
+ }
42227
+ catch {
42228
+ return null;
42229
+ }
42230
+ }
42231
+ function loadProjectConnection(cwd = process.cwd(), homeDir = os_1.default.homedir()) {
42232
+ const binding = readJson(projectBindingFile(cwd));
42233
+ const workspaceId = typeof binding?.workspaceId === 'string' ? binding.workspaceId.trim() : '';
42234
+ if (!workspaceId)
42235
+ return null;
42236
+ const secret = readJson(homeWorkspaceFile(workspaceId, homeDir));
42237
+ const apiKey = typeof secret?.apiKey === 'string' ? secret.apiKey.trim() : '';
42238
+ if (!apiKey)
42239
+ return null;
42240
+ const apiUrl = typeof binding?.apiUrl === 'string' && binding.apiUrl.trim()
42241
+ ? binding.apiUrl.trim()
42242
+ : (typeof secret?.apiUrl === 'string' && secret.apiUrl.trim() ? secret.apiUrl.trim() : 'https://pathmode.io');
42243
+ return { apiKey, apiUrl, workspaceId };
42244
+ }
42245
+ function saveProjectConnection(config, cwd = process.cwd(), homeDir = os_1.default.homedir()) {
42246
+ const bindingPath = projectBindingFile(cwd);
42247
+ fs_1.default.mkdirSync(path_1.default.dirname(bindingPath), { recursive: true });
42248
+ fs_1.default.writeFileSync(bindingPath, JSON.stringify({
42249
+ workspaceId: config.workspaceId,
42250
+ apiUrl: config.apiUrl,
42251
+ }, null, 2) + '\n', 'utf8');
42252
+ const secretPath = homeWorkspaceFile(config.workspaceId, homeDir);
42253
+ fs_1.default.mkdirSync(path_1.default.dirname(secretPath), { recursive: true });
42254
+ fs_1.default.writeFileSync(secretPath, JSON.stringify({
42255
+ apiKey: config.apiKey,
42256
+ apiUrl: config.apiUrl,
42257
+ }, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
42258
+ try {
42259
+ fs_1.default.chmodSync(secretPath, 0o600);
42260
+ }
42261
+ catch { /* best effort on non-POSIX filesystems */ }
42262
+ }
42263
+ /** Ensure the repository has a keyless MCP declaration. The credential is resolved
42264
+ * from the project binding at runtime and is never written into a committable file. */
42265
+ function ensureProjectMcpConfig(cwd = process.cwd()) {
42266
+ const configPath = path_1.default.join(cwd, '.mcp.json');
42267
+ const existing = fs_1.default.existsSync(configPath) ? readJson(configPath) : {};
42268
+ if (!existing)
42269
+ throw new Error(`${configPath} is not valid JSON; fix it before adopting.`);
42270
+ const servers = existing.mcpServers && typeof existing.mcpServers === 'object'
42271
+ ? existing.mcpServers
42272
+ : {};
42273
+ servers.pathmode = { command: 'npx', args: ['@pathmode/mcp-server'] };
42274
+ fs_1.default.writeFileSync(configPath, JSON.stringify({ ...existing, mcpServers: servers }, null, 2) + '\n', 'utf8');
42275
+ }
42276
+
42277
+
41801
42278
  /***/ }),
41802
42279
 
41803
42280
  /***/ 4239:
@@ -41836,6 +42313,9 @@ async function pushSpec(input) {
41836
42313
  let canonicalId;
41837
42314
  let specVersion;
41838
42315
  let sourceUrl;
42316
+ let origin;
42317
+ let authorization;
42318
+ let authorizationNote;
41839
42319
  try {
41840
42320
  // On create the local id travels with the spec. Promotion must not change identity: a
41841
42321
  // keyless author may already have stamped `intent/<uuid>` on a branch, and a new id would
@@ -41863,6 +42343,9 @@ async function pushSpec(input) {
41863
42343
  }
41864
42344
  canonicalId = saved.id || id;
41865
42345
  specVersion = saved.specVersion;
42346
+ origin = saved.origin;
42347
+ authorization = saved.authorization;
42348
+ authorizationNote = saved.authorizationNote;
41866
42349
  sourceUrl = `https://www.pathmode.io/intent/${canonicalId}`;
41867
42350
  }
41868
42351
  catch (e) {
@@ -41876,7 +42359,7 @@ async function pushSpec(input) {
41876
42359
  ...(products ? { products } : {}),
41877
42360
  };
41878
42361
  }
41879
- input.onIdentitySettled({ canonicalId, specVersion, sourceUrl });
42362
+ input.onIdentitySettled({ canonicalId, specVersion, sourceUrl, origin, authorization, authorizationNote });
41880
42363
  const didNotTravel = [];
41881
42364
  for (const d of input.decisions ?? []) {
41882
42365
  try {
@@ -71463,7 +71946,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"$schema":"http://json-schema.org/dra
71463
71946
  /***/ ((module) => {
71464
71947
 
71465
71948
  "use strict";
71466
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.18.0","publishConfig":{"access":"public"},"mcpName":"io.github.pathmodeio/mcp-server","description":"Deterministic intent preflight before your agent builds: six calibrated gates, keyless, no model call. Draft and sharpen specs in conversation, or connect a Pathmode workspace to sync intent and evidence across a team.","main":"dist/index.js","bin":{"pathmode-mcp":"dist/index.js"},"files":["dist/","manifest.json","icon.svg","README.md","skills/"],"scripts":{"build":"rm -rf dist && ncc build src/index.ts -o dist","dev":"ts-node src/index.ts","prepublishOnly":"npm run build"},"keywords":["pathmode","mcp","model-context-protocol","claude-code","claude-code-skills","agent-skills","cursor","windsurf","intent-engineering","intent-compiler","ai-agents","product-development","dependency-graph","strategic-planning"],"author":"Pathmode","license":"MIT","type":"commonjs","engines":{"node":">=18.0.0"},"homepage":"https://pathmode.io","dependencies":{"@modelcontextprotocol/sdk":"^1.12.1","gray-matter":"^4.0.3","zod":"^3.24.0"},"devDependencies":{"@types/node":"^25.1.0","@vercel/ncc":"^0.38.4","ts-node":"^10.9.2","typescript":"^5.9.3"}}');
71949
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.20.0","publishConfig":{"access":"public"},"mcpName":"io.github.pathmodeio/mcp-server","description":"Deterministic intent preflight before your agent builds: six calibrated gates, keyless, no model call. Draft and sharpen specs in conversation, or connect a Pathmode workspace to sync intent and evidence across a team.","main":"dist/index.js","bin":{"pathmode-mcp":"dist/index.js"},"files":["dist/","manifest.json","icon.svg","README.md","skills/"],"scripts":{"build":"rm -rf dist && ncc build src/index.ts -o dist","dev":"ts-node src/index.ts","prepublishOnly":"npm run build"},"keywords":["pathmode","mcp","model-context-protocol","claude-code","claude-code-skills","agent-skills","cursor","windsurf","intent-engineering","intent-compiler","ai-agents","product-development","dependency-graph","strategic-planning"],"author":"Pathmode","license":"MIT","type":"commonjs","engines":{"node":">=18.0.0"},"homepage":"https://pathmode.io","dependencies":{"@modelcontextprotocol/sdk":"^1.12.1","gray-matter":"^4.0.3","zod":"^3.24.0"},"devDependencies":{"@types/node":"^25.1.0","@vercel/ncc":"^0.38.4","ts-node":"^10.9.2","typescript":"^5.9.3"}}');
71467
71950
 
71468
71951
  /***/ })
71469
71952
 
@@ -71573,6 +72056,7 @@ const setup_1 = __nccwpck_require__(8294);
71573
72056
  const measurement_schema_1 = __nccwpck_require__(1635);
71574
72057
  const install_skills_1 = __nccwpck_require__(3783);
71575
72058
  const cli_info_1 = __nccwpck_require__(7198);
72059
+ const adopt_1 = __nccwpck_require__(7797);
71576
72060
  // Server version is sourced from package.json so the version reported to MCP
71577
72061
  // clients always matches the published package. ncc statically resolves this
71578
72062
  // require() and inlines the JSON at build time (no runtime fs read).
@@ -71595,6 +72079,12 @@ else if ((0, setup_1.isSetupCommand)()) {
71595
72079
  process.exit(1);
71596
72080
  });
71597
72081
  }
72082
+ else if ((0, adopt_1.isAdoptCommand)()) {
72083
+ (0, adopt_1.runAdopt)().then(() => process.exit(0)).catch((err) => {
72084
+ console.error(`\n ✗ ${err instanceof Error ? err.message : String(err)}\n`);
72085
+ process.exit(1);
72086
+ });
72087
+ }
71598
72088
  else if ((0, install_skills_1.isInstallSkillsCommand)()) {
71599
72089
  (0, install_skills_1.runInstallSkills)().then(() => process.exit(0)).catch((err) => {
71600
72090
  console.error(err);
@@ -72932,6 +73422,24 @@ function startMcpServer() {
72932
73422
  readiness: (0, readiness_1.formatReadinessFrontmatter)(verdict),
72933
73423
  specVersion: opts.specVersion,
72934
73424
  source: opts.sourceUrl,
73425
+ // The gate is carried, never inferred. Two sources, and the order matters:
73426
+ //
73427
+ // - CLOUD: whatever identity just settled to. A v1 create is stamped
73428
+ // origin='agent' + pending by the API, so a create that took the file's
73429
+ // value (there is none) wrote an UNGATED file for a spec the workspace
73430
+ // considers unauthorized. The server is the authority in this mode; it
73431
+ // also means a human authorizing in the workspace clears the local banner
73432
+ // on the next save instead of leaving a stale one.
73433
+ // - LOCAL: the file being replaced, and only on an update. Without it, an
73434
+ // agent editing a pending proposal cleared its own gate.
73435
+ //
73436
+ // The note follows the same rule, and the distinction that makes it work is
73437
+ // `undefined` (the server did not speak) versus `null` (the server says there
73438
+ // is none now). `??` would collapse the two and keep showing a rejection note
73439
+ // after the reviewer authorized the spec, or after they rewrote the note.
73440
+ origin: opts.origin !== undefined ? opts.origin : (decision.action === 'update' ? existing?.origin : undefined),
73441
+ authorization: opts.authorization !== undefined ? opts.authorization : (decision.action === 'update' ? existing?.authorization : undefined),
73442
+ authorizationNote: opts.authorizationNote !== undefined ? opts.authorizationNote : existing?.authorizationNote,
72935
73443
  });
72936
73444
  // Carry existing confirmations across the rewrite. intent_save builds its content from
72937
73445
  // the incoming spec, which by design has no `confirmations` field (that absence is what