@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/README.md +24 -1
- package/dist/index.js +707 -199
- package/dist/packages/intentspec-format/serializeIntentMd.d.ts +164 -0
- package/dist/packages/intentspec-format/serializeIntentMd.d.ts.map +1 -0
- package/dist/packages/mcp-server/src/adopt.d.ts +12 -0
- package/dist/packages/mcp-server/src/adopt.d.ts.map +1 -0
- package/dist/packages/mcp-server/src/api-client.d.ts +7 -0
- package/dist/packages/mcp-server/src/api-client.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/cli-info.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/intent-compiler.d.ts +19 -1
- package/dist/packages/mcp-server/src/intent-compiler.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/local-reader.d.ts +8 -0
- package/dist/packages/mcp-server/src/local-reader.d.ts.map +1 -1
- package/dist/packages/mcp-server/src/project-config.d.ts +12 -0
- package/dist/packages/mcp-server/src/project-config.d.ts.map +1 -0
- package/dist/packages/mcp-server/src/push-spec.d.ts +19 -12
- package/dist/packages/mcp-server/src/push-spec.d.ts.map +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
40038
|
-
|
|
40039
|
-
|
|
40040
|
-
|
|
40041
|
-
|
|
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
|
-
|
|
40046
|
-
.
|
|
40047
|
-
|
|
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
|
-
|
|
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.
|
|
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
|