@pathmode/mcp-server 1.14.1 → 1.15.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
@@ -34476,6 +34476,41 @@ function formatIntentMd(spec, opts = {}) {
34476
34476
  sections.push(`- [ ] ${renderCheckLine(c)}`);
34477
34477
  }
34478
34478
  }
34479
+ // `## Confirmations` is emitted LAST so it never sits between the fields a reader is
34480
+ // comparing, and so appending one cannot shift any other section's parse. Derived fields
34481
+ // (assurance, source) are NOT written: anything read from a file is local-unverified.
34482
+ const rawConfirmations = spec.confirmations;
34483
+ const confirmations = Array.isArray(rawConfirmations)
34484
+ ? rawConfirmations
34485
+ : [];
34486
+ // Filter BEFORE emitting the heading: a spec whose only confirmation targets an
34487
+ // unwaivable dimension must not leave an empty `## Confirmations` section behind.
34488
+ const emittable = confirmations.filter((c) => {
34489
+ const dim = String(c.dimension ?? '').toLowerCase();
34490
+ const kind = String(c.kind ?? '').toLowerCase();
34491
+ const by = String(c.by ?? '').toLowerCase();
34492
+ return ['objective', 'outcomes'].includes(dim)
34493
+ && ['confirmed', 'waived'].includes(kind)
34494
+ && ['agent', 'human'].includes(by);
34495
+ });
34496
+ if (emittable.length) {
34497
+ sections.push('');
34498
+ sections.push('## Confirmations');
34499
+ for (const c of emittable) {
34500
+ const dim = String(c.dimension ?? '').toLowerCase();
34501
+ const kind = String(c.kind ?? '').toLowerCase();
34502
+ const by = String(c.by ?? '').toLowerCase();
34503
+ sections.push('');
34504
+ sections.push(`**${dim}** — ${kind} by ${by}`);
34505
+ for (const key of ['actor', 'problem', 'outcome', 'observable', 'reason', 'anchor', 'at']) {
34506
+ const v = c[key];
34507
+ if (typeof v !== 'string' || !v.trim())
34508
+ continue;
34509
+ // Values are single-line by contract; the writer flattens rather than escaping.
34510
+ sections.push(`- ${key}: ${v.replace(/\s+/g, ' ').trim()}`);
34511
+ }
34512
+ }
34513
+ }
34479
34514
  sections.push('');
34480
34515
  return sections.join('\n');
34481
34516
  }
@@ -34890,6 +34925,43 @@ exports.parseIntentMarkdown = parseIntentMarkdown;
34890
34925
  const fs_1 = __importDefault(__nccwpck_require__(9896));
34891
34926
  const path_1 = __importDefault(__nccwpck_require__(6928));
34892
34927
  const gray_matter_1 = __importDefault(__nccwpck_require__(9599));
34928
+ const CONFIRMATION_HEADER_RE = /^\*\*(objective|outcomes)\*\*\s*[—–-]\s*(confirmed|waived)\s+by\s+(agent|human)\s*$/i;
34929
+ const CONFIRMATION_FIELD_RE = /^\s*[-*]\s+(actor|problem|outcome|observable|reason|anchor|at)\s*:\s*(.+)$/i;
34930
+ /**
34931
+ * Parse `## Confirmations` permissively; the gate validates.
34932
+ *
34933
+ * A block missing its anchor still parses and is reported by the gate as `malformed` or `stale`
34934
+ * rather than vanishing here. Silent drops at parse time are what make "my confirmation did
34935
+ * nothing" undebuggable, which is the precedent this deliberately does not copy.
34936
+ *
34937
+ * A value runs to end of line, so it may contain colons, dashes and quotes without escaping.
34938
+ * A duplicate key inside one block is last-wins, matching splitSections' duplicate-heading rule.
34939
+ * A line matching neither shape is ignored, so stray prose cannot open a block.
34940
+ */
34941
+ function extractConfirmations(sections) {
34942
+ const out = [];
34943
+ let current = null;
34944
+ for (const line of sections.get('Confirmations') ?? []) {
34945
+ const header = line.trim().match(CONFIRMATION_HEADER_RE);
34946
+ if (header) {
34947
+ current = {
34948
+ dimension: header[1].toLowerCase(),
34949
+ kind: header[2].toLowerCase(),
34950
+ by: header[3].toLowerCase(),
34951
+ assurance: 'local-unverified',
34952
+ source: 'file',
34953
+ };
34954
+ out.push(current);
34955
+ continue;
34956
+ }
34957
+ if (!current)
34958
+ continue;
34959
+ const field = line.match(CONFIRMATION_FIELD_RE);
34960
+ if (field)
34961
+ current[field[1].toLowerCase()] = field[2].trim();
34962
+ }
34963
+ return out;
34964
+ }
34893
34965
  const DECISIONS_HEADING = 'Decisions & Ruled-Out Alternatives';
34894
34966
  /**
34895
34967
  * Read all intent.md files from the current directory and subdirectories (1 level deep).
@@ -34957,7 +35029,8 @@ function readIntentFile(filePath) {
34957
35029
  * (lib/intentMdParse.ts) to this implementation's behavior on gate-relevant fields.
34958
35030
  */
34959
35031
  function parseIntentMarkdown(content, fallbackId = 'intent') {
34960
- const { data, content: body } = (0, gray_matter_1.default)(content);
35032
+ const { data, content: rawBody } = (0, gray_matter_1.default)(content);
35033
+ const body = stripFencedBlocks(rawBody);
34961
35034
  const sections = splitSections(body);
34962
35035
  const parsedVerification = extractVerification(sections);
34963
35036
  const frontmatterVerification = data.verification && typeof data.verification === 'object'
@@ -34984,6 +35057,7 @@ function parseIntentMarkdown(content, fallbackId = 'intent') {
34984
35057
  healthMetrics: extractListSection(sections, 'Health Metrics'),
34985
35058
  scope: extractScope(sections) || undefined,
34986
35059
  verification,
35060
+ confirmations: extractConfirmations(sections),
34987
35061
  source: 'local',
34988
35062
  };
34989
35063
  }
@@ -34995,6 +35069,46 @@ function parseIntentMarkdown(content, fallbackId = 'intent') {
34995
35069
  * wins. `###` is not a section boundary (`^##\s+` cannot match `### `), so sub-headings stay
34996
35070
  * inside their parent section.
34997
35071
  */
35072
+ /**
35073
+ * Remove fenced code blocks before any structural parsing.
35074
+ *
35075
+ * A spec's fenced blocks are examples and payloads, never spec structure, but the
35076
+ * section walker is line-based: a `## ` inside a fence opens a bogus section, and
35077
+ * `isListItem` matches `^\s*[-*]\s`, so a C comment line (` * @brief ...`) or a YAML
35078
+ * list inside a fence becomes an outcome. Measured in the wild: one OpenSpec change
35079
+ * contributed 21 Doxygen `@brief` lines as "outcomes" and passed a gate a reviewer
35080
+ * fails it on (docs/research/openspec-corpus-audit.md).
35081
+ *
35082
+ * Fenced content is DROPPED, not kept-and-ignored. Nothing downstream reads a fence as
35083
+ * a check (verification takes list items only), so keeping it would only feed code
35084
+ * noise to the lexical gates that read the prose fields.
35085
+ *
35086
+ * Indented (4-space) code blocks are deliberately NOT handled: they are
35087
+ * indistinguishable from indented list continuations, and dropping those would lose
35088
+ * real content. An unclosed fence swallows the rest of the document, which is what a
35089
+ * markdown renderer does, so the parser reads what the author sees.
35090
+ */
35091
+ function stripFencedBlocks(body) {
35092
+ const out = [];
35093
+ let fence = null;
35094
+ for (const line of body.split('\n')) {
35095
+ const marker = line.match(/^\s*(`{3,}|~{3,})/);
35096
+ if (marker) {
35097
+ const char = marker[1][0];
35098
+ const len = marker[1].length;
35099
+ if (!fence) {
35100
+ fence = { char, len };
35101
+ continue;
35102
+ }
35103
+ if (char === fence.char && len >= fence.len)
35104
+ fence = null;
35105
+ continue;
35106
+ }
35107
+ if (!fence)
35108
+ out.push(line);
35109
+ }
35110
+ return out.join('\n');
35111
+ }
34998
35112
  function splitSections(body) {
34999
35113
  const sections = new Map();
35000
35114
  let current = null;
@@ -35506,6 +35620,8 @@ exports.isConstraintConcrete = isConstraintConcrete;
35506
35620
  exports.isVerificationCheckSubstantive = isVerificationCheckSubstantive;
35507
35621
  exports.isObjectiveSpecific = isObjectiveSpecific;
35508
35622
  exports.isOutcomeMeasurable = isOutcomeMeasurable;
35623
+ exports.normalizeAnchor = normalizeAnchor;
35624
+ exports.fieldDigest = fieldDigest;
35509
35625
  exports.computeReadinessVerdict = computeReadinessVerdict;
35510
35626
  exports.formatReadinessFrontmatter = formatReadinessFrontmatter;
35511
35627
  exports.formatReadinessVerdict = formatReadinessVerdict;
@@ -35624,6 +35740,114 @@ exports.READINESS_BLOCKER_DESCRIPTIONS = {
35624
35740
  verification: 'No concrete verification — describe at least one check specific enough to run.',
35625
35741
  };
35626
35742
  /** Display order for the gate strip (title first, matching the Preflight page). */
35743
+ // ── Confirmation anchoring ──────────────────────────────────────────────────
35744
+ /**
35745
+ * Fold away everything that is a rewrite of the same claim, and nothing that is a change to it.
35746
+ * NFC because one glyph has two encodings; soft hyphens because editors inject them invisibly;
35747
+ * quote/dash folding and markdown emphasis because adding `**` around a word is formatting, not
35748
+ * a reword. Lowercased and whitespace-collapsed last.
35749
+ */
35750
+ function normalizeAnchor(value) {
35751
+ return coerceText(value)
35752
+ .normalize('NFC')
35753
+ .replace(/­/g, '')
35754
+ .replace(/[‘’ʼ]/g, "'")
35755
+ .replace(/[“”]/g, '"')
35756
+ .replace(/[–—]/g, '-')
35757
+ .replace(/\*/g, '')
35758
+ .replace(/\s+/g, ' ')
35759
+ .trim()
35760
+ .toLowerCase();
35761
+ }
35762
+ /**
35763
+ * 64-bit FNV-1a over the normalized field text, as 16 lowercase hex chars.
35764
+ *
35765
+ * NOT sha256, and deliberately not node:crypto. lib/intentReadiness.ts is imported by
35766
+ * 'use client' components (PreflightVerdictDemo, SpecPanel, DraftWorkspace), so the app-side
35767
+ * half of this parity pair runs in the browser, where node crypto does not exist and
35768
+ * crypto.subtle is async while this gate is synchronous and pure. Do not "upgrade" this to
35769
+ * sha256 without checking that constraint first.
35770
+ *
35771
+ * A weak hash is adequate because this is change detection, not a security boundary: anyone
35772
+ * who can forge a confirmation can edit the digest beside it. Accidental collision over 64 bits
35773
+ * is negligible, and a collision only means a confirmation outlives an edit it should not have.
35774
+ */
35775
+ function fieldDigest(text) {
35776
+ const s = normalizeAnchor(text);
35777
+ let h = 0xcbf29ce484222325n;
35778
+ const prime = 0x100000001b3n;
35779
+ const mask = 0xffffffffffffffffn;
35780
+ for (let i = 0; i < s.length; i++) {
35781
+ h = (h ^ BigInt(s.charCodeAt(i))) * prime & mask;
35782
+ }
35783
+ return h.toString(16).padStart(16, '0');
35784
+ }
35785
+ /** Only these two are waivable or confirmable. goal and verification are unrepresentable here. */
35786
+ const CONFIRMABLE = ['objective', 'outcomes'];
35787
+ /**
35788
+ * Apply confirmations to the dimensions the gate could not confirm lexically.
35789
+ *
35790
+ * Structural checks ONLY: the gate never reads the prose of `actor`, `problem` or `reason`, it
35791
+ * only checks that they are present and that the anchor still matches the field it was made
35792
+ * against. Interpreting them is the consumer's job, which is the whole point of the split.
35793
+ *
35794
+ * The uniform rule is `anchor === fieldDigest(current field text)`. It covers both kinds and
35795
+ * both directions: editing a confirmed objective kills the confirmation, and filling in an
35796
+ * objective that was waived as absent kills the waiver, because '' and the new text digest
35797
+ * differently.
35798
+ */
35799
+ function applyConfirmations(detail, digests, raw) {
35800
+ const rejected = [];
35801
+ for (const entry of asArray(raw)) {
35802
+ const c = (entry ?? {});
35803
+ const dim = coerceText(c.dimension).trim();
35804
+ const kind = coerceText(c.kind).trim();
35805
+ const by = coerceText(c.by).trim();
35806
+ if (!CONFIRMABLE.includes(dim)) {
35807
+ rejected.push({ code: 'unknown-dimension', dimension: dim || '(none)' });
35808
+ continue;
35809
+ }
35810
+ const d = detail[dim];
35811
+ if (kind === 'waived') {
35812
+ // Amendment A: a waiver is a human decision. An agent may propose one; only a human
35813
+ // may make it. Without this an agent's cheapest route to a green gate is one call.
35814
+ if (by !== 'human') {
35815
+ rejected.push({ code: 'waiver-requires-human', dimension: dim });
35816
+ continue;
35817
+ }
35818
+ if (!coerceText(c.reason).trim()) {
35819
+ rejected.push({ code: 'malformed', dimension: dim });
35820
+ continue;
35821
+ }
35822
+ }
35823
+ else if (kind === 'confirmed') {
35824
+ const filled = dim === 'objective'
35825
+ ? coerceText(c.actor).trim() && coerceText(c.problem).trim()
35826
+ : coerceText(c.outcome).trim() && coerceText(c.observable).trim();
35827
+ if (!filled) {
35828
+ rejected.push({ code: 'malformed', dimension: dim });
35829
+ continue;
35830
+ }
35831
+ // M3: there is no path from a blank spec to a pass. Absent is unconfirmable;
35832
+ // waiving it is the honest move and stays available above.
35833
+ if (d?.state === 'absent') {
35834
+ rejected.push({ code: 'nothing-to-confirm', dimension: dim });
35835
+ continue;
35836
+ }
35837
+ }
35838
+ else {
35839
+ rejected.push({ code: 'malformed', dimension: dim });
35840
+ continue;
35841
+ }
35842
+ if (coerceText(c.anchor).trim() !== digests[dim]) {
35843
+ rejected.push({ code: 'stale', dimension: dim });
35844
+ continue;
35845
+ }
35846
+ if (d && d.state !== 'pass')
35847
+ d.state = kind === 'waived' ? 'not_applicable' : 'pass';
35848
+ }
35849
+ return rejected;
35850
+ }
35627
35851
  exports.READINESS_GATE_ORDER = ['goal', 'objective', 'outcomes', 'constraints', 'edgeCases', 'verification'];
35628
35852
  /** Blocker emission order — byte-identical to lib/intentReadiness computeReviewSignals. */
35629
35853
  const BLOCKER_ORDER = ['objective', 'goal', 'outcomes', 'constraints', 'edgeCases', 'verification'];
@@ -35669,7 +35893,41 @@ function computeReadinessVerdict(spec) {
35669
35893
  const failingBlockers = BLOCKER_ORDER
35670
35894
  .filter(k => !signals[k])
35671
35895
  .map(k => exports.READINESS_BLOCKER_DESCRIPTIONS[k]);
35672
- return { ready: failingBlockers.length === 0, signals, failingBlockers };
35896
+ // What the reader read, per dimension, independent of whether the heuristic accepted it.
35897
+ // Placeholder text is deliberately not extracted: no path from a blank spec to confirmable.
35898
+ const objectiveText = coerceText(spec.objective).trim();
35899
+ const titleText = coerceText(spec.title).trim();
35900
+ const extractedBy = {
35901
+ goal: isLikelyPlaceholderText(titleText) ? '' : titleText,
35902
+ objective: isLikelyPlaceholderText(objectiveText) ? '' : objectiveText,
35903
+ outcomes: nonEmptyOutcomes.join('; '),
35904
+ constraints: asArray(spec.constraints).map(c => coerceText(c).trim()).filter(Boolean).join('; '),
35905
+ edgeCases: asArray(spec.edgeCases)
35906
+ .map((ec) => coerceText(ec?.scenario).trim())
35907
+ .filter(Boolean).join('; '),
35908
+ verification: verificationDescriptions(spec.verification).join('; '),
35909
+ };
35910
+ const detail = {};
35911
+ for (const key of exports.READINESS_GATE_ORDER) {
35912
+ const text = extractedBy[key] ?? '';
35913
+ detail[key] = signals[key]
35914
+ ? { state: 'pass', ...(text ? { extracted: text } : {}) }
35915
+ : text
35916
+ ? { state: 'unconfirmed', extracted: text }
35917
+ : { state: 'absent' };
35918
+ }
35919
+ // Confirmations only ever move a dimension the heuristics already failed; `signals` is never
35920
+ // mutated, so the lexical verdict stays visible underneath.
35921
+ const anchorDigests = {
35922
+ objective: fieldDigest(extractedBy.objective),
35923
+ outcomes: fieldDigest(extractedBy.outcomes),
35924
+ };
35925
+ const rejectedConfirmations = applyConfirmations(detail, anchorDigests, spec.confirmations);
35926
+ const confirmedReady = exports.READINESS_GATE_ORDER.every(k => signals[k] || detail[k]?.state === 'pass' || detail[k]?.state === 'not_applicable');
35927
+ return {
35928
+ ready: failingBlockers.length === 0,
35929
+ signals, failingBlockers, detail, rejectedConfirmations, confirmedReady,
35930
+ };
35673
35931
  }
35674
35932
  const GATE_LABELS = {
35675
35933
  goal: 'Title',
@@ -35702,6 +35960,24 @@ function formatReadinessFrontmatter(verdict) {
35702
35960
  const blocking = exports.READINESS_GATE_ORDER.filter((k) => !verdict.signals[k]).map((k) => FRONTMATTER_GATE_LABELS[k]);
35703
35961
  return `failed ${passed}/${total} — blocking: ${blocking.join(', ')}`;
35704
35962
  }
35963
+ /**
35964
+ * The extra line an `unconfirmed` dimension earns: what was read, and an honest statement of
35965
+ * what the check could not find in it.
35966
+ *
35967
+ * Only objective and outcomes appear here, and that scoping is the finding, not caution. The
35968
+ * audit measured that most failures on those two are false negatives from a fixed English
35969
+ * vocabulary. Verification's measured misses were all extraction failures, so quoting text
35970
+ * back there would imply a confidence the evidence does not support.
35971
+ */
35972
+ const UNCONFIRMED_HINT = {
35973
+ objective: 'Read this, but could not confirm an affected actor and a concrete problem in it. If it already names them, this check reads a fixed vocabulary and may be missing valid phrasing:',
35974
+ outcomes: 'Read these, but could not confirm an observable threshold in enough of them. If they are already observable, this check reads a fixed vocabulary and may be missing valid phrasing:',
35975
+ };
35976
+ /** Keep a quoted extraction to one readable line; the full text is in verdict.detail. */
35977
+ function truncateForQuote(text, max = 160) {
35978
+ const flat = text.replace(/\s+/g, ' ').trim();
35979
+ return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`;
35980
+ }
35705
35981
  /** Human/agent-readable verdict block, stable enough to parse by line prefix. */
35706
35982
  function formatReadinessVerdict(verdict, specTitle) {
35707
35983
  const lines = [];
@@ -35711,8 +35987,21 @@ function formatReadinessVerdict(verdict, specTitle) {
35711
35987
  }
35712
35988
  else {
35713
35989
  lines.push(`✗ Preflight failed${name ? ` for "${name}"` : ''}. ${verdict.failingBlockers.length}/6 checks blocking.`);
35714
- for (const b of verdict.failingBlockers)
35715
- lines.push(` ✗ ${b}`);
35990
+ // Blocker lines stay byte-identical and in order; an unconfirmed dimension gets an
35991
+ // extra indented line quoting what was actually read. Without it the verdict says
35992
+ // "name the actor" to an author who named one in words this vocabulary misses, which
35993
+ // an audit of 80 real specs measured as the majority of these failures
35994
+ // (docs/research/openspec-corpus-audit.md).
35995
+ for (const key of BLOCKER_ORDER) {
35996
+ if (verdict.signals[key])
35997
+ continue;
35998
+ lines.push(` ✗ ${exports.READINESS_BLOCKER_DESCRIPTIONS[key]}`);
35999
+ const d = verdict.detail?.[key];
36000
+ if (d?.state === 'unconfirmed' && d.extracted && UNCONFIRMED_HINT[key]) {
36001
+ lines.push(` ↳ ${UNCONFIRMED_HINT[key]}`);
36002
+ lines.push(` "${truncateForQuote(d.extracted)}"`);
36003
+ }
36004
+ }
35716
36005
  }
35717
36006
  lines.push('');
35718
36007
  lines.push(exports.READINESS_GATE_ORDER.map(k => `${verdict.signals[k] ? '✓' : '·'} ${GATE_LABELS[k]}`).join(' '));
@@ -65652,7 +65941,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"$schema":"http://json-schema.org/dra
65652
65941
  /***/ ((module) => {
65653
65942
 
65654
65943
  "use strict";
65655
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.14.1","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"}}');
65944
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.15.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"}}');
65656
65945
 
65657
65946
  /***/ })
65658
65947
 
@@ -67152,6 +67441,92 @@ function startMcpServer() {
67152
67441
  }],
67153
67442
  };
67154
67443
  });
67444
+ /**
67445
+ * Confirm or waive a readiness dimension the deterministic gate could not confirm on its own.
67446
+ *
67447
+ * M1 (a confirmation must not be minted in the write that authored the field) is enforced by the
67448
+ * TOOL BOUNDARY, not by tracking state: `intent_save` has no `confirmations` parameter, so the
67449
+ * only way a confirmation exists is a separate call against a file a previous write produced.
67450
+ * Keep it that way. Adding confirmations to intent_save would let an agent author a field and
67451
+ * vouch for it in one breath, which is the rubber stamp this whole mechanism exists to price.
67452
+ *
67453
+ * The tool stamps `by: agent` for a confirmation and never accepts a `by` parameter. A waiver is
67454
+ * a human decision (contract amendment A) and requires `humanApproved`, which the agent may only
67455
+ * set after the user has actually said so. That is forgeable, and so is every other byte of a
67456
+ * local file; what it buys is that the shortcut is not an official one-call path to a green gate.
67457
+ */
67458
+ server.registerTool('confirm_intent_dimension', {
67459
+ title: 'Confirm or waive a readiness dimension',
67460
+ description: 'Resolve an "unconfirmed" readiness dimension in a local intent.md. Use this when check_intent_readiness reports that it READ your objective or outcomes but could not confirm them: the gate matches a fixed English vocabulary and misses valid phrasing, including other languages. ' +
67461
+ 'confirm: state who is affected and what goes wrong for them, in your own words, and the dimension passes. ' +
67462
+ 'waive: mark the dimension as not applicable to this change (a pure refactor has no product outcome). A waiver is a HUMAN decision, so set humanApproved only after the user has explicitly agreed. ' +
67463
+ 'The confirmation is bound to the exact text it was made against and dies automatically if that text is edited. You cannot confirm a dimension the gate read nothing for; write the field first, save, then confirm.',
67464
+ inputSchema: {
67465
+ dimension: zod_1.z.enum(['objective', 'outcomes']).describe('Only these two are confirmable. Title and verification are never waivable.'),
67466
+ action: zod_1.z.enum(['confirm', 'waive']).describe('confirm: vouch that the text names what the gate could not find. waive: this dimension does not apply to this change.'),
67467
+ actor: zod_1.z.string().optional().describe('confirm + objective: who is affected, in your words.'),
67468
+ problem: zod_1.z.string().optional().describe('confirm + objective: what goes wrong for them today.'),
67469
+ outcome: zod_1.z.string().optional().describe('confirm + outcomes: the observable change.'),
67470
+ observable: zod_1.z.string().optional().describe('confirm + outcomes: how you would see it.'),
67471
+ reason: zod_1.z.string().optional().describe('waive: why this dimension does not apply.'),
67472
+ humanApproved: zod_1.z.boolean().optional().describe('waive only. Set ONLY after the user has explicitly approved the waiver. Recorded as a human decision that this environment cannot verify.'),
67473
+ path: zod_1.z.string().optional().describe('Path to the intent file. Defaults to intent.md.'),
67474
+ },
67475
+ }, async ({ dimension, action, actor, problem, outcome, observable, reason, humanApproved, path }) => {
67476
+ const filePath = resolveWithinProject(path || 'intent.md');
67477
+ const existing = (0, local_reader_1.readIntentFile)(filePath);
67478
+ if (!existing) {
67479
+ return { content: [{ type: 'text', text: `✗ No intent file at ${filePath}. Save one with intent_save first, then confirm.` }] };
67480
+ }
67481
+ const verdict = (0, readiness_1.computeReadinessVerdict)({ ...existing, confirmations: existing.confirmations });
67482
+ const state = verdict.detail[dimension]?.state;
67483
+ if (state === 'pass') {
67484
+ return { content: [{ type: 'text', text: `· ${dimension} already passes on its own. Nothing to confirm.` }] };
67485
+ }
67486
+ if (action === 'confirm' && state === 'absent') {
67487
+ return { content: [{ type: 'text', text: `✗ Cannot confirm ${dimension}: the reader found nothing there. Write the field, save with intent_save, then confirm. If it genuinely does not apply to this change, waive it instead.` }] };
67488
+ }
67489
+ if (action === 'waive' && !humanApproved) {
67490
+ return { content: [{ type: 'text', text: `✗ A waiver is a human decision. Ask the user whether ${dimension} genuinely does not apply to this change, and call again with humanApproved once they have said so.` }] };
67491
+ }
67492
+ if (action === 'confirm') {
67493
+ const missing = dimension === 'objective'
67494
+ ? (!actor?.trim() || !problem?.trim()) && 'actor and problem'
67495
+ : (!outcome?.trim() || !observable?.trim()) && 'outcome and observable';
67496
+ if (missing)
67497
+ return { content: [{ type: 'text', text: `✗ A confirmation must name the ${missing}. A bare yes is not a confirmation.` }] };
67498
+ }
67499
+ if (action === 'waive' && !reason?.trim()) {
67500
+ return { content: [{ type: 'text', text: '✗ A waiver must carry a reason.' }] };
67501
+ }
67502
+ // Anchor to exactly what the gate read for this dimension, so an edit invalidates it.
67503
+ const anchor = (0, readiness_1.fieldDigest)(verdict.detail[dimension]?.extracted ?? '');
67504
+ const record = action === 'waive'
67505
+ ? { dimension, kind: 'waived', by: 'human', reason: reason.trim(), anchor }
67506
+ : {
67507
+ dimension, kind: 'confirmed', by: 'agent', anchor,
67508
+ ...(dimension === 'objective'
67509
+ ? { actor: actor.trim(), problem: problem.trim() }
67510
+ : { outcome: outcome.trim(), observable: observable.trim() }),
67511
+ };
67512
+ // One live record per dimension: a re-confirmation replaces rather than accumulates.
67513
+ const kept = existing.confirmations.filter(c => c.dimension !== dimension);
67514
+ const content = (0, intent_compiler_1.formatIntentMd)({ ...existing, confirmations: [...kept, record] });
67515
+ (0, fs_1.writeFileSync)(filePath, content, 'utf-8');
67516
+ const after = (0, readiness_1.computeReadinessVerdict)({ ...existing, confirmations: [...kept, record] });
67517
+ return {
67518
+ content: [{
67519
+ type: 'text',
67520
+ text: [
67521
+ `✓ ${dimension} ${action === 'waive' ? 'waived' : 'confirmed'} in ${filePath}`,
67522
+ ` ${after.detail[dimension]?.state === 'not_applicable' ? 'not applicable to this change' : 'confirmed against the current text'} · anchor ${anchor}`,
67523
+ action === 'waive' ? ' Recorded as a human decision this environment cannot verify.' : '',
67524
+ '',
67525
+ (0, readiness_1.formatReadinessVerdict)(after),
67526
+ ].filter(Boolean).join('\n'),
67527
+ }],
67528
+ };
67529
+ });
67155
67530
  server.tool('intent_export', 'Export an intent spec as .cursorrules, a CLAUDE.md or AGENTS.md section, or a Claude Managed Agents Outcomes rubric for AI agent consumption. Use agents-md for Codex, Cursor, and other AGENTS.md-aware agents.', {
67156
67531
  format: zod_1.z.enum(['cursorrules', 'claude-md', 'agents-md', 'outcome-rubric']).describe('Export format'),
67157
67532
  spec: zod_1.z.object(intentSpecSchema),
@@ -1 +1 @@
1
- {"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/intent-compiler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/intent-compiler.ts"],"names":[],"mappings":"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"}
@@ -28,6 +28,28 @@ export interface LocalVerification {
28
28
  unitTests?: string[];
29
29
  e2eTests?: string[];
30
30
  }
31
+ /**
32
+ * A confirmation or waiver as it sits in `## Confirmations`.
33
+ *
34
+ * `assurance` and `source` are DERIVED, never written in the file: anything read out of a local
35
+ * file is `local-unverified` from `file`, whatever the `by` line claims. A hand-edited block
36
+ * asserting `by: human` is still a human claim we cannot check, and every surface must render it
37
+ * as such (contract amendments C and M4).
38
+ */
39
+ export interface LocalConfirmation {
40
+ dimension: string;
41
+ kind: string;
42
+ by: string;
43
+ assurance: 'local-unverified';
44
+ source: 'file';
45
+ actor?: string;
46
+ problem?: string;
47
+ outcome?: string;
48
+ observable?: string;
49
+ reason?: string;
50
+ anchor?: string;
51
+ at?: string;
52
+ }
31
53
  export interface LocalIntent {
32
54
  id: string;
33
55
  status: string;
@@ -55,6 +77,8 @@ export interface LocalIntent {
55
77
  outOfScope?: string[];
56
78
  };
57
79
  verification: LocalVerification;
80
+ /** From `## Confirmations`; validated by the readiness gate, not here. */
81
+ confirmations: LocalConfirmation[];
58
82
  source: 'local';
59
83
  }
60
84
  /** Frontmatter identity of an existing intent.md, for non-destructive saves. */
@@ -1 +1 @@
1
- {"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/local-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,MAAM,MAAM,qBAAqB,GAAG,SAAS,GAAG,QAAQ,GAAG,gBAAgB,GAAG,kBAAkB,GAAG,MAAM,CAAC;AAE1G,MAAM,WAAW,sBAAsB;IACnC,IAAI,EAAE,qBAAqB,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wGAAwG;AACxG,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAC9B,MAAM,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAClC,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;iDAE6C;IAC7C,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,aAAa,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACtD,YAAY,EAAE,iBAAiB,CAAC;IAChC,MAAM,EAAE,OAAO,CAAC;CACnB;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;6EACyE;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAID;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,EAAE,CAmBhD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAevE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAUnE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAW,GAAG,WAAW,CAiCvF"}
1
+ {"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/local-reader.ts"],"names":[],"mappings":"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"}
@@ -21,6 +21,44 @@ export declare function isObjectiveSpecific(objective: string | null | undefined
21
21
  export declare function isOutcomeMeasurable(text: string): boolean;
22
22
  export declare const READINESS_BLOCKER_DESCRIPTIONS: Record<string, string>;
23
23
  /** Display order for the gate strip (title first, matching the Preflight page). */
24
+ /**
25
+ * Fold away everything that is a rewrite of the same claim, and nothing that is a change to it.
26
+ * NFC because one glyph has two encodings; soft hyphens because editors inject them invisibly;
27
+ * quote/dash folding and markdown emphasis because adding `**` around a word is formatting, not
28
+ * a reword. Lowercased and whitespace-collapsed last.
29
+ */
30
+ export declare function normalizeAnchor(value: unknown): string;
31
+ /**
32
+ * 64-bit FNV-1a over the normalized field text, as 16 lowercase hex chars.
33
+ *
34
+ * NOT sha256, and deliberately not node:crypto. lib/intentReadiness.ts is imported by
35
+ * 'use client' components (PreflightVerdictDemo, SpecPanel, DraftWorkspace), so the app-side
36
+ * half of this parity pair runs in the browser, where node crypto does not exist and
37
+ * crypto.subtle is async while this gate is synchronous and pure. Do not "upgrade" this to
38
+ * sha256 without checking that constraint first.
39
+ *
40
+ * A weak hash is adequate because this is change detection, not a security boundary: anyone
41
+ * who can forge a confirmation can edit the digest beside it. Accidental collision over 64 bits
42
+ * is negligible, and a collision only means a confirmation outlives an edit it should not have.
43
+ */
44
+ export declare function fieldDigest(text: unknown): string;
45
+ /** A confirmation or waiver as it arrives, before any structural validation. */
46
+ export interface ConfirmationRecord {
47
+ dimension?: unknown;
48
+ kind?: unknown;
49
+ by?: unknown;
50
+ anchor?: unknown;
51
+ actor?: unknown;
52
+ problem?: unknown;
53
+ outcome?: unknown;
54
+ observable?: unknown;
55
+ reason?: unknown;
56
+ }
57
+ export interface RejectedConfirmation {
58
+ /** Stable machine code; nothing is ever dropped silently. */
59
+ code: 'unknown-dimension' | 'malformed' | 'stale' | 'waiver-requires-human' | 'nothing-to-confirm';
60
+ dimension: string;
61
+ }
24
62
  export declare const READINESS_GATE_ORDER: readonly ["goal", "objective", "outcomes", "constraints", "edgeCases", "verification"];
25
63
  /** Loose spec shape: covers LocalIntent, cloud intent JSON, and hand-built specs. */
26
64
  export interface ReadinessSpecInput {
@@ -30,11 +68,38 @@ export interface ReadinessSpecInput {
30
68
  constraints?: unknown;
31
69
  edgeCases?: unknown;
32
70
  verification?: unknown;
71
+ /** Confirmations and waivers; see docs/READINESS_CONFIRMATION_CONTRACT.md. */
72
+ confirmations?: unknown;
73
+ }
74
+ /**
75
+ * Why a dimension is not passing, which is not the same question as whether it passes.
76
+ * MIRROR of DimensionState in lib/intentReadiness.ts; see that file for the full rationale.
77
+ *
78
+ * `absent` means nothing substantive was extracted and the blocker copy is fair.
79
+ * `unconfirmed` means text WAS extracted and the lexical test rejected it, which on real
80
+ * in-the-wild specs is usually OUR error: an audit of 80 changes found ~80% of objective
81
+ * failures and ~94% of outcome failures are false negatives, most of them extracted
82
+ * correctly and then discarded by the word lists (docs/research/openspec-corpus-audit.md).
83
+ */
84
+ export type DimensionState = 'pass' | 'unconfirmed' | 'absent' | 'not_applicable';
85
+ export interface DimensionDetail {
86
+ state: DimensionState;
87
+ /** What the reader actually read for this dimension, for quoting back. */
88
+ extracted?: string;
33
89
  }
34
90
  export interface ReadinessVerdict {
35
91
  ready: boolean;
36
92
  signals: Record<string, boolean>;
37
93
  failingBlockers: string[];
94
+ /** Additive: failingBlockers stays byte-identical, richer messaging is opt-in. */
95
+ detail: Record<string, DimensionDetail>;
96
+ /** Confirmations that did not apply, with a reason. Nothing is ever dropped silently. */
97
+ rejectedConfirmations: RejectedConfirmation[];
98
+ /**
99
+ * Ready once confirmations and waivers are counted. `ready` stays the pure lexical verdict
100
+ * so a surface can distinguish what was earned from what was vouched for.
101
+ */
102
+ confirmedReady: boolean;
38
103
  }
39
104
  /**
40
105
  * The preflight: six deterministic gates over an intent spec. Mirrors
@@ -1 +1 @@
1
- {"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/readiness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQjD;AAED,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,CAAC,EAAE,CAIjE;AAoCD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAQ3E;AAID,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAI5D;AAED,wBAAgB,8BAA8B,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAI9F;AAQD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAOjF;AAQD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAKzD;AAID,eAAO,MAAM,8BAA8B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAOjE,CAAC;AAEF,mFAAmF;AACnF,eAAO,MAAM,oBAAoB,wFAAyF,CAAC;AAK3H,qFAAqF;AACrF,MAAM,WAAW,kBAAkB;IAC/B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,gBAAgB;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,eAAe,EAAE,MAAM,EAAE,CAAC;CAC7B;AAoBD;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,kBAAkB,GAAG,gBAAgB,CAkBlF;AAqBD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAM5E;AAED,iFAAiF;AACjF,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAgB5F"}
1
+ {"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/readiness.ts"],"names":[],"mappings":"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"}
package/manifest.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.3",
3
3
  "name": "pathmode",
4
4
  "display_name": "Pathmode",
5
- "version": "1.14.1",
5
+ "version": "1.15.0",
6
6
  "description": "Deterministic preflight for the intent you hand to a coding agent: six calibrated gates, the exact blockers named, no model call, no key needed.",
7
7
  "long_description": "Pathmode MCP Server runs a deterministic preflight before your coding agent builds: check_intent_readiness scores an intent spec against six calibrated gates (title, objective, outcomes, constraints, edge cases, verification) and names the exact blockers, with no model call and no account. Keyless local mode works out of the box; specs live in intent.md in your repo, plain markdown you own, and skills for drafting, pressure-testing, and handing off intent ride along. Connect a Pathmode workspace with an API key to sync intent and evidence across a team, analyze dependency graphs, and verify pull requests against the outcomes you agreed to.",
8
8
  "author": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pathmode/mcp-server",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },