@pathmode/mcp-server 1.14.1 → 1.15.1
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 +574 -25
- package/dist/intent-compiler.d.ts +15 -0
- package/dist/intent-compiler.d.ts.map +1 -1
- package/dist/local-reader.d.ts +24 -5
- package/dist/local-reader.d.ts.map +1 -1
- package/dist/readiness.d.ts +74 -1
- package/dist/readiness.d.ts.map +1 -1
- package/manifest.json +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34140,6 +34140,8 @@ exports.formatIntentMd = formatIntentMd;
|
|
|
34140
34140
|
exports.formatCursorRules = formatCursorRules;
|
|
34141
34141
|
exports.formatClaudeMdSection = formatClaudeMdSection;
|
|
34142
34142
|
exports.formatOutcomeRubric = formatOutcomeRubric;
|
|
34143
|
+
exports.spliceConfirmationsSection = spliceConfirmationsSection;
|
|
34144
|
+
exports.renderConfirmationsSection = renderConfirmationsSection;
|
|
34143
34145
|
// Verification check collection. Mirrors lib/verification in the main app — this package is
|
|
34144
34146
|
// standalone (own ncc build) and can't import it, so the type + adapter live here. `kind` is the
|
|
34145
34147
|
const crypto_1 = __nccwpck_require__(6982);
|
|
@@ -34476,6 +34478,41 @@ function formatIntentMd(spec, opts = {}) {
|
|
|
34476
34478
|
sections.push(`- [ ] ${renderCheckLine(c)}`);
|
|
34477
34479
|
}
|
|
34478
34480
|
}
|
|
34481
|
+
// `## Confirmations` is emitted LAST so it never sits between the fields a reader is
|
|
34482
|
+
// comparing, and so appending one cannot shift any other section's parse. Derived fields
|
|
34483
|
+
// (assurance, source) are NOT written: anything read from a file is local-unverified.
|
|
34484
|
+
const rawConfirmations = spec.confirmations;
|
|
34485
|
+
const confirmations = Array.isArray(rawConfirmations)
|
|
34486
|
+
? rawConfirmations
|
|
34487
|
+
: [];
|
|
34488
|
+
// Filter BEFORE emitting the heading: a spec whose only confirmation targets an
|
|
34489
|
+
// unwaivable dimension must not leave an empty `## Confirmations` section behind.
|
|
34490
|
+
const emittable = confirmations.filter((c) => {
|
|
34491
|
+
const dim = String(c.dimension ?? '').toLowerCase();
|
|
34492
|
+
const kind = String(c.kind ?? '').toLowerCase();
|
|
34493
|
+
const by = String(c.by ?? '').toLowerCase();
|
|
34494
|
+
return ['objective', 'outcomes'].includes(dim)
|
|
34495
|
+
&& ['confirmed', 'waived'].includes(kind)
|
|
34496
|
+
&& ['agent', 'human'].includes(by);
|
|
34497
|
+
});
|
|
34498
|
+
if (emittable.length) {
|
|
34499
|
+
sections.push('');
|
|
34500
|
+
sections.push('## Confirmations');
|
|
34501
|
+
for (const c of emittable) {
|
|
34502
|
+
const dim = String(c.dimension ?? '').toLowerCase();
|
|
34503
|
+
const kind = String(c.kind ?? '').toLowerCase();
|
|
34504
|
+
const by = String(c.by ?? '').toLowerCase();
|
|
34505
|
+
sections.push('');
|
|
34506
|
+
sections.push(`**${dim}** — ${kind} by ${by}`);
|
|
34507
|
+
for (const key of ['actor', 'problem', 'outcome', 'observable', 'reason', 'anchor', 'at']) {
|
|
34508
|
+
const v = c[key];
|
|
34509
|
+
if (typeof v !== 'string' || !v.trim())
|
|
34510
|
+
continue;
|
|
34511
|
+
// Values are single-line by contract; the writer flattens rather than escaping.
|
|
34512
|
+
sections.push(`- ${key}: ${v.replace(/\s+/g, ' ').trim()}`);
|
|
34513
|
+
}
|
|
34514
|
+
}
|
|
34515
|
+
}
|
|
34479
34516
|
sections.push('');
|
|
34480
34517
|
return sections.join('\n');
|
|
34481
34518
|
}
|
|
@@ -34860,6 +34897,62 @@ function formatOutcomeRubric(spec, opts = {}) {
|
|
|
34860
34897
|
doc.push('');
|
|
34861
34898
|
return doc.join('\n');
|
|
34862
34899
|
}
|
|
34900
|
+
/**
|
|
34901
|
+
* Splice a `## Confirmations` section into an existing intent.md WITHOUT regenerating the file.
|
|
34902
|
+
*
|
|
34903
|
+
* Regenerating through formatIntentMd is lossy by construction: LocalIntent is a projection, so
|
|
34904
|
+
* a round-trip drops unknown frontmatter keys, resets `version` and `status` to their defaults,
|
|
34905
|
+
* restamps `created`, and deletes any section the reader has no field for. Measured on a real
|
|
34906
|
+
* spec: status approved -> draft, version 7 -> 1, and `source`, `specVersion` and a `## Evidence`
|
|
34907
|
+
* section gone. A tool that records a judgment must not destroy the document it judges.
|
|
34908
|
+
*
|
|
34909
|
+
* So this is a text operation. It replaces an existing `## Confirmations` section in place, or
|
|
34910
|
+
* appends one at the end, and touches nothing else in the file.
|
|
34911
|
+
*/
|
|
34912
|
+
function spliceConfirmationsSection(original, section) {
|
|
34913
|
+
const lines = original.split('\n');
|
|
34914
|
+
const start = lines.findIndex(l => /^##\s+Confirmations\s*$/.test(l));
|
|
34915
|
+
if (start === -1) {
|
|
34916
|
+
const trimmed = original.replace(/\s*$/, '');
|
|
34917
|
+
return `${trimmed}\n\n${section.trim()}\n`;
|
|
34918
|
+
}
|
|
34919
|
+
// Run to the next `##`/`#` heading, so a later section survives untouched.
|
|
34920
|
+
let end = lines.length;
|
|
34921
|
+
for (let i = start + 1; i < lines.length; i++) {
|
|
34922
|
+
if (/^#{1,2}\s+/.test(lines[i])) {
|
|
34923
|
+
end = i;
|
|
34924
|
+
break;
|
|
34925
|
+
}
|
|
34926
|
+
}
|
|
34927
|
+
const before = lines.slice(0, start).join('\n').replace(/\s*$/, '');
|
|
34928
|
+
const after = lines.slice(end).join('\n').replace(/^\s*/, '');
|
|
34929
|
+
return `${before}\n\n${section.trim()}\n${after ? `\n${after}` : ''}`;
|
|
34930
|
+
}
|
|
34931
|
+
/** Render confirmation records as the `## Confirmations` section body. */
|
|
34932
|
+
function renderConfirmationsSection(records) {
|
|
34933
|
+
const emittable = records.filter((c) => {
|
|
34934
|
+
const dim = String(c.dimension ?? '').toLowerCase();
|
|
34935
|
+
const kind = String(c.kind ?? '').toLowerCase();
|
|
34936
|
+
const by = String(c.by ?? '').toLowerCase();
|
|
34937
|
+
return ['objective', 'outcomes'].includes(dim)
|
|
34938
|
+
&& ['confirmed', 'waived'].includes(kind)
|
|
34939
|
+
&& ['agent', 'human'].includes(by);
|
|
34940
|
+
});
|
|
34941
|
+
if (!emittable.length)
|
|
34942
|
+
return '';
|
|
34943
|
+
const out = ['## Confirmations'];
|
|
34944
|
+
for (const c of emittable) {
|
|
34945
|
+
out.push('');
|
|
34946
|
+
out.push(`**${String(c.dimension).toLowerCase()}** — ${String(c.kind).toLowerCase()} by ${String(c.by).toLowerCase()}`);
|
|
34947
|
+
for (const key of ['actor', 'problem', 'outcome', 'observable', 'reason', 'anchor', 'at']) {
|
|
34948
|
+
const v = c[key];
|
|
34949
|
+
if (typeof v !== 'string' || !v.trim())
|
|
34950
|
+
continue;
|
|
34951
|
+
out.push(`- ${key}: ${v.replace(/\s+/g, ' ').trim()}`);
|
|
34952
|
+
}
|
|
34953
|
+
}
|
|
34954
|
+
return out.join('\n');
|
|
34955
|
+
}
|
|
34863
34956
|
|
|
34864
34957
|
|
|
34865
34958
|
/***/ }),
|
|
@@ -34890,6 +34983,43 @@ exports.parseIntentMarkdown = parseIntentMarkdown;
|
|
|
34890
34983
|
const fs_1 = __importDefault(__nccwpck_require__(9896));
|
|
34891
34984
|
const path_1 = __importDefault(__nccwpck_require__(6928));
|
|
34892
34985
|
const gray_matter_1 = __importDefault(__nccwpck_require__(9599));
|
|
34986
|
+
const CONFIRMATION_HEADER_RE = /^\*\*(objective|outcomes)\*\*\s*[—–-]\s*(confirmed|waived)\s+by\s+(agent|human)\s*$/i;
|
|
34987
|
+
const CONFIRMATION_FIELD_RE = /^\s*[-*]\s+(actor|problem|outcome|observable|reason|anchor|at)\s*:\s*(.+)$/i;
|
|
34988
|
+
/**
|
|
34989
|
+
* Parse `## Confirmations` permissively; the gate validates.
|
|
34990
|
+
*
|
|
34991
|
+
* A block missing its anchor still parses and is reported by the gate as `malformed` or `stale`
|
|
34992
|
+
* rather than vanishing here. Silent drops at parse time are what make "my confirmation did
|
|
34993
|
+
* nothing" undebuggable, which is the precedent this deliberately does not copy.
|
|
34994
|
+
*
|
|
34995
|
+
* A value runs to end of line, so it may contain colons, dashes and quotes without escaping.
|
|
34996
|
+
* A duplicate key inside one block is last-wins, matching splitSections' duplicate-heading rule.
|
|
34997
|
+
* A line matching neither shape is ignored, so stray prose cannot open a block.
|
|
34998
|
+
*/
|
|
34999
|
+
function extractConfirmations(sections) {
|
|
35000
|
+
const out = [];
|
|
35001
|
+
let current = null;
|
|
35002
|
+
for (const line of sections.get('Confirmations') ?? []) {
|
|
35003
|
+
const header = line.trim().match(CONFIRMATION_HEADER_RE);
|
|
35004
|
+
if (header) {
|
|
35005
|
+
current = {
|
|
35006
|
+
dimension: header[1].toLowerCase(),
|
|
35007
|
+
kind: header[2].toLowerCase(),
|
|
35008
|
+
by: header[3].toLowerCase(),
|
|
35009
|
+
assurance: 'local-unverified',
|
|
35010
|
+
source: 'file',
|
|
35011
|
+
};
|
|
35012
|
+
out.push(current);
|
|
35013
|
+
continue;
|
|
35014
|
+
}
|
|
35015
|
+
if (!current)
|
|
35016
|
+
continue;
|
|
35017
|
+
const field = line.match(CONFIRMATION_FIELD_RE);
|
|
35018
|
+
if (field)
|
|
35019
|
+
current[field[1].toLowerCase()] = field[2].trim();
|
|
35020
|
+
}
|
|
35021
|
+
return out;
|
|
35022
|
+
}
|
|
34893
35023
|
const DECISIONS_HEADING = 'Decisions & Ruled-Out Alternatives';
|
|
34894
35024
|
/**
|
|
34895
35025
|
* Read all intent.md files from the current directory and subdirectories (1 level deep).
|
|
@@ -34956,13 +35086,65 @@ function readIntentFile(filePath) {
|
|
|
34956
35086
|
* Exported so lib/intentMdParseParity.test.ts can hold the web paste-box parser
|
|
34957
35087
|
* (lib/intentMdParse.ts) to this implementation's behavior on gate-relevant fields.
|
|
34958
35088
|
*/
|
|
35089
|
+
/**
|
|
35090
|
+
* Sections win; frontmatter fills an absence. Mirrors lib/intentMdParse.preferSection.
|
|
35091
|
+
*
|
|
35092
|
+
* Local mode originally read only files that formatIntentMd wrote, which always carry body
|
|
35093
|
+
* sections, so the fallback never fired and was omitted. The preflight CLI broke that
|
|
35094
|
+
* assumption: it points this parser at hand-written files, including the frontmatter-only
|
|
35095
|
+
* shape the /intentspec starter and `pathmode validate` both bless. Without the fallback the
|
|
35096
|
+
* same CLI validated a spec's outcomes as schema-correct and then reported "nothing found"
|
|
35097
|
+
* for them, which is the false "you wrote nothing" this codebase has spent a research cycle
|
|
35098
|
+
* stamping out.
|
|
35099
|
+
*/
|
|
35100
|
+
function preferSection(fromSection, fromFrontmatter) {
|
|
35101
|
+
if (fromSection.length > 0)
|
|
35102
|
+
return fromSection;
|
|
35103
|
+
if (!Array.isArray(fromFrontmatter))
|
|
35104
|
+
return [];
|
|
35105
|
+
return fromFrontmatter
|
|
35106
|
+
.map((v) => (typeof v === 'string' ? v : coerceItemText(v)))
|
|
35107
|
+
.map((v) => v.trim())
|
|
35108
|
+
.filter(Boolean);
|
|
35109
|
+
}
|
|
35110
|
+
/** Coerce a structured frontmatter entry ({text}, {description}) to its text, else ''. */
|
|
35111
|
+
function coerceItemText(v) {
|
|
35112
|
+
if (!v || typeof v !== 'object')
|
|
35113
|
+
return '';
|
|
35114
|
+
const o = v;
|
|
35115
|
+
for (const key of ['text', 'description', 'title']) {
|
|
35116
|
+
if (typeof o[key] === 'string')
|
|
35117
|
+
return o[key];
|
|
35118
|
+
}
|
|
35119
|
+
return '';
|
|
35120
|
+
}
|
|
35121
|
+
/** Frontmatter edge cases in the schema's {scenario, expectedBehavior} shape. */
|
|
35122
|
+
function frontmatterEdgeCases(v) {
|
|
35123
|
+
if (!Array.isArray(v))
|
|
35124
|
+
return [];
|
|
35125
|
+
return v
|
|
35126
|
+
.map((e) => {
|
|
35127
|
+
const o = (e ?? {});
|
|
35128
|
+
return {
|
|
35129
|
+
scenario: typeof o.scenario === 'string' ? o.scenario.trim() : '',
|
|
35130
|
+
expectedBehavior: typeof o.expectedBehavior === 'string' ? o.expectedBehavior.trim()
|
|
35131
|
+
: typeof o.expected_behavior === 'string' ? o.expected_behavior.trim() : '',
|
|
35132
|
+
};
|
|
35133
|
+
})
|
|
35134
|
+
.filter((e) => e.scenario || e.expectedBehavior);
|
|
35135
|
+
}
|
|
34959
35136
|
function parseIntentMarkdown(content, fallbackId = 'intent') {
|
|
34960
|
-
const { data, content:
|
|
35137
|
+
const { data, content: rawBody } = (0, gray_matter_1.default)(content);
|
|
35138
|
+
const body = stripFencedBlocks(rawBody);
|
|
34961
35139
|
const sections = splitSections(body);
|
|
34962
35140
|
const parsedVerification = extractVerification(sections);
|
|
34963
|
-
|
|
34964
|
-
|
|
34965
|
-
|
|
35141
|
+
// The public schema allows verification as a flat string array; read it as manual checks
|
|
35142
|
+
// rather than silently producing {} (the same adapter normalizeSpecForReadiness applies).
|
|
35143
|
+
const frontmatterVerification = Array.isArray(data.verification)
|
|
35144
|
+
? { manualChecks: data.verification.map(v => typeof v === 'string' ? v : coerceItemText(v)).filter(Boolean) }
|
|
35145
|
+
: data.verification && typeof data.verification === 'object'
|
|
35146
|
+
? data.verification
|
|
35147
|
+
: null;
|
|
34966
35148
|
const verification = hasVerificationContent(parsedVerification)
|
|
34967
35149
|
? parsedVerification
|
|
34968
35150
|
: (frontmatterVerification || {});
|
|
@@ -34977,13 +35159,14 @@ function parseIntentMarkdown(content, fallbackId = 'intent') {
|
|
|
34977
35159
|
title: data.title || data.userGoal || extractTitle(body) || 'Untitled Intent',
|
|
34978
35160
|
stageName: data.stage || undefined,
|
|
34979
35161
|
severity: data.severity || undefined,
|
|
34980
|
-
outcomes: extractListSection(sections, 'Outcomes'),
|
|
35162
|
+
outcomes: preferSection(extractListSection(sections, 'Outcomes'), data.outcomes),
|
|
34981
35163
|
decisions: extractDecisions(sections),
|
|
34982
|
-
constraints: extractListSection(sections, 'Constraints'),
|
|
34983
|
-
edgeCases: extractEdgeCases(sections),
|
|
34984
|
-
healthMetrics: extractListSection(sections, 'Health Metrics'),
|
|
35164
|
+
constraints: preferSection(extractListSection(sections, 'Constraints'), data.constraints),
|
|
35165
|
+
edgeCases: (() => { const fromBody = extractEdgeCases(sections); return fromBody.length ? fromBody : frontmatterEdgeCases(data.edgeCases); })(),
|
|
35166
|
+
healthMetrics: preferSection(extractListSection(sections, 'Health Metrics'), data.healthMetrics),
|
|
34985
35167
|
scope: extractScope(sections) || undefined,
|
|
34986
35168
|
verification,
|
|
35169
|
+
confirmations: extractConfirmations(sections),
|
|
34987
35170
|
source: 'local',
|
|
34988
35171
|
};
|
|
34989
35172
|
}
|
|
@@ -34995,6 +35178,46 @@ function parseIntentMarkdown(content, fallbackId = 'intent') {
|
|
|
34995
35178
|
* wins. `###` is not a section boundary (`^##\s+` cannot match `### `), so sub-headings stay
|
|
34996
35179
|
* inside their parent section.
|
|
34997
35180
|
*/
|
|
35181
|
+
/**
|
|
35182
|
+
* Remove fenced code blocks before any structural parsing.
|
|
35183
|
+
*
|
|
35184
|
+
* A spec's fenced blocks are examples and payloads, never spec structure, but the
|
|
35185
|
+
* section walker is line-based: a `## ` inside a fence opens a bogus section, and
|
|
35186
|
+
* `isListItem` matches `^\s*[-*]\s`, so a C comment line (` * @brief ...`) or a YAML
|
|
35187
|
+
* list inside a fence becomes an outcome. Measured in the wild: one OpenSpec change
|
|
35188
|
+
* contributed 21 Doxygen `@brief` lines as "outcomes" and passed a gate a reviewer
|
|
35189
|
+
* fails it on (docs/research/openspec-corpus-audit.md).
|
|
35190
|
+
*
|
|
35191
|
+
* Fenced content is DROPPED, not kept-and-ignored. Nothing downstream reads a fence as
|
|
35192
|
+
* a check (verification takes list items only), so keeping it would only feed code
|
|
35193
|
+
* noise to the lexical gates that read the prose fields.
|
|
35194
|
+
*
|
|
35195
|
+
* Indented (4-space) code blocks are deliberately NOT handled: they are
|
|
35196
|
+
* indistinguishable from indented list continuations, and dropping those would lose
|
|
35197
|
+
* real content. An unclosed fence swallows the rest of the document, which is what a
|
|
35198
|
+
* markdown renderer does, so the parser reads what the author sees.
|
|
35199
|
+
*/
|
|
35200
|
+
function stripFencedBlocks(body) {
|
|
35201
|
+
const out = [];
|
|
35202
|
+
let fence = null;
|
|
35203
|
+
for (const line of body.split('\n')) {
|
|
35204
|
+
const marker = line.match(/^\s*(`{3,}|~{3,})/);
|
|
35205
|
+
if (marker) {
|
|
35206
|
+
const char = marker[1][0];
|
|
35207
|
+
const len = marker[1].length;
|
|
35208
|
+
if (!fence) {
|
|
35209
|
+
fence = { char, len };
|
|
35210
|
+
continue;
|
|
35211
|
+
}
|
|
35212
|
+
if (char === fence.char && len >= fence.len)
|
|
35213
|
+
fence = null;
|
|
35214
|
+
continue;
|
|
35215
|
+
}
|
|
35216
|
+
if (!fence)
|
|
35217
|
+
out.push(line);
|
|
35218
|
+
}
|
|
35219
|
+
return out.join('\n');
|
|
35220
|
+
}
|
|
34998
35221
|
function splitSections(body) {
|
|
34999
35222
|
const sections = new Map();
|
|
35000
35223
|
let current = null;
|
|
@@ -35506,6 +35729,8 @@ exports.isConstraintConcrete = isConstraintConcrete;
|
|
|
35506
35729
|
exports.isVerificationCheckSubstantive = isVerificationCheckSubstantive;
|
|
35507
35730
|
exports.isObjectiveSpecific = isObjectiveSpecific;
|
|
35508
35731
|
exports.isOutcomeMeasurable = isOutcomeMeasurable;
|
|
35732
|
+
exports.normalizeAnchor = normalizeAnchor;
|
|
35733
|
+
exports.fieldDigest = fieldDigest;
|
|
35509
35734
|
exports.computeReadinessVerdict = computeReadinessVerdict;
|
|
35510
35735
|
exports.formatReadinessFrontmatter = formatReadinessFrontmatter;
|
|
35511
35736
|
exports.formatReadinessVerdict = formatReadinessVerdict;
|
|
@@ -35624,6 +35849,122 @@ exports.READINESS_BLOCKER_DESCRIPTIONS = {
|
|
|
35624
35849
|
verification: 'No concrete verification — describe at least one check specific enough to run.',
|
|
35625
35850
|
};
|
|
35626
35851
|
/** Display order for the gate strip (title first, matching the Preflight page). */
|
|
35852
|
+
// ── Confirmation anchoring ──────────────────────────────────────────────────
|
|
35853
|
+
/**
|
|
35854
|
+
* Fold away everything that is a rewrite of the same claim, and nothing that is a change to it.
|
|
35855
|
+
* NFC because one glyph has two encodings; soft hyphens because editors inject them invisibly;
|
|
35856
|
+
* quote/dash folding and markdown emphasis because adding `**` around a word is formatting, not
|
|
35857
|
+
* a reword. Lowercased and whitespace-collapsed last.
|
|
35858
|
+
*/
|
|
35859
|
+
function normalizeAnchor(value) {
|
|
35860
|
+
return coerceText(value)
|
|
35861
|
+
.normalize('NFC')
|
|
35862
|
+
.replace(//g, '')
|
|
35863
|
+
.replace(/[‘’ʼ]/g, "'")
|
|
35864
|
+
.replace(/[“”]/g, '"')
|
|
35865
|
+
.replace(/[–—]/g, '-')
|
|
35866
|
+
.replace(/\*/g, '')
|
|
35867
|
+
.replace(/\s+/g, ' ')
|
|
35868
|
+
.trim()
|
|
35869
|
+
.toLowerCase();
|
|
35870
|
+
}
|
|
35871
|
+
/**
|
|
35872
|
+
* 64-bit FNV-1a over the normalized field text, as 16 lowercase hex chars.
|
|
35873
|
+
*
|
|
35874
|
+
* NOT sha256, and deliberately not node:crypto. lib/intentReadiness.ts is imported by
|
|
35875
|
+
* 'use client' components (PreflightVerdictDemo, SpecPanel, DraftWorkspace), so the app-side
|
|
35876
|
+
* half of this parity pair runs in the browser, where node crypto does not exist and
|
|
35877
|
+
* crypto.subtle is async while this gate is synchronous and pure. Do not "upgrade" this to
|
|
35878
|
+
* sha256 without checking that constraint first.
|
|
35879
|
+
*
|
|
35880
|
+
* A weak hash is adequate because this is change detection, not a security boundary: anyone
|
|
35881
|
+
* who can forge a confirmation can edit the digest beside it. Accidental collision over 64 bits
|
|
35882
|
+
* is negligible, and a collision only means a confirmation outlives an edit it should not have.
|
|
35883
|
+
*/
|
|
35884
|
+
function fieldDigest(text) {
|
|
35885
|
+
const s = normalizeAnchor(text);
|
|
35886
|
+
let h = 0xcbf29ce484222325n;
|
|
35887
|
+
const prime = 0x100000001b3n;
|
|
35888
|
+
const mask = 0xffffffffffffffffn;
|
|
35889
|
+
for (let i = 0; i < s.length; i++) {
|
|
35890
|
+
h = (h ^ BigInt(s.charCodeAt(i))) * prime & mask;
|
|
35891
|
+
}
|
|
35892
|
+
return h.toString(16).padStart(16, '0');
|
|
35893
|
+
}
|
|
35894
|
+
/** Only these two are waivable or confirmable. goal and verification are unrepresentable here. */
|
|
35895
|
+
const CONFIRMABLE = ['objective', 'outcomes'];
|
|
35896
|
+
/**
|
|
35897
|
+
* Apply confirmations to the dimensions the gate could not confirm lexically.
|
|
35898
|
+
*
|
|
35899
|
+
* Structural checks ONLY: the gate never reads the prose of `actor`, `problem` or `reason`, it
|
|
35900
|
+
* only checks that they are present and that the anchor still matches the field it was made
|
|
35901
|
+
* against. Interpreting them is the consumer's job, which is the whole point of the split.
|
|
35902
|
+
*
|
|
35903
|
+
* The uniform rule is `anchor === fieldDigest(current field text)`. It covers both kinds and
|
|
35904
|
+
* both directions: editing a confirmed objective kills the confirmation, and filling in an
|
|
35905
|
+
* objective that was waived as absent kills the waiver, because '' and the new text digest
|
|
35906
|
+
* differently.
|
|
35907
|
+
*/
|
|
35908
|
+
function applyConfirmations(detail, digests, raw) {
|
|
35909
|
+
const rejected = [];
|
|
35910
|
+
for (const entry of asArray(raw)) {
|
|
35911
|
+
const c = (entry ?? {});
|
|
35912
|
+
const dim = coerceText(c.dimension).trim();
|
|
35913
|
+
const kind = coerceText(c.kind).trim();
|
|
35914
|
+
const by = coerceText(c.by).trim();
|
|
35915
|
+
if (!CONFIRMABLE.includes(dim)) {
|
|
35916
|
+
rejected.push({ code: 'unknown-dimension', dimension: dim || '(none)' });
|
|
35917
|
+
continue;
|
|
35918
|
+
}
|
|
35919
|
+
const d = detail[dim];
|
|
35920
|
+
if (kind === 'waived') {
|
|
35921
|
+
// Amendment A: a waiver is a human decision. An agent may propose one; only a human
|
|
35922
|
+
// may make it. Without this an agent's cheapest route to a green gate is one call.
|
|
35923
|
+
if (by !== 'human') {
|
|
35924
|
+
rejected.push({ code: 'waiver-requires-human', dimension: dim });
|
|
35925
|
+
continue;
|
|
35926
|
+
}
|
|
35927
|
+
if (!coerceText(c.reason).trim()) {
|
|
35928
|
+
rejected.push({ code: 'malformed', dimension: dim });
|
|
35929
|
+
continue;
|
|
35930
|
+
}
|
|
35931
|
+
}
|
|
35932
|
+
else if (kind === 'confirmed') {
|
|
35933
|
+
const filled = dim === 'objective'
|
|
35934
|
+
? coerceText(c.actor).trim() && coerceText(c.problem).trim()
|
|
35935
|
+
: coerceText(c.outcome).trim() && coerceText(c.observable).trim();
|
|
35936
|
+
if (!filled) {
|
|
35937
|
+
rejected.push({ code: 'malformed', dimension: dim });
|
|
35938
|
+
continue;
|
|
35939
|
+
}
|
|
35940
|
+
// M3: there is no path from a blank spec to a pass. Absent is unconfirmable;
|
|
35941
|
+
// waiving it is the honest move and stays available above.
|
|
35942
|
+
if (d?.state === 'absent') {
|
|
35943
|
+
rejected.push({ code: 'nothing-to-confirm', dimension: dim });
|
|
35944
|
+
continue;
|
|
35945
|
+
}
|
|
35946
|
+
}
|
|
35947
|
+
else {
|
|
35948
|
+
rejected.push({ code: 'malformed', dimension: dim });
|
|
35949
|
+
continue;
|
|
35950
|
+
}
|
|
35951
|
+
if (coerceText(c.anchor).trim() !== digests[dim]) {
|
|
35952
|
+
rejected.push({ code: 'stale', dimension: dim });
|
|
35953
|
+
continue;
|
|
35954
|
+
}
|
|
35955
|
+
if (d && d.state !== 'pass') {
|
|
35956
|
+
d.state = kind === 'waived' ? 'not_applicable' : 'pass';
|
|
35957
|
+
d.confirmedBy = by === 'human' ? 'human' : 'agent';
|
|
35958
|
+
// Assurance is never taken from the record: a file cannot vouch for itself. Anything
|
|
35959
|
+
// arriving without an authenticated boundary is local-unverified, and renders that way.
|
|
35960
|
+
d.assurance = coerceText(c.assurance).trim() === 'authenticated'
|
|
35961
|
+
? 'authenticated' : 'local-unverified';
|
|
35962
|
+
if (kind === 'waived')
|
|
35963
|
+
d.reason = coerceText(c.reason).trim();
|
|
35964
|
+
}
|
|
35965
|
+
}
|
|
35966
|
+
return rejected;
|
|
35967
|
+
}
|
|
35627
35968
|
exports.READINESS_GATE_ORDER = ['goal', 'objective', 'outcomes', 'constraints', 'edgeCases', 'verification'];
|
|
35628
35969
|
/** Blocker emission order — byte-identical to lib/intentReadiness computeReviewSignals. */
|
|
35629
35970
|
const BLOCKER_ORDER = ['objective', 'goal', 'outcomes', 'constraints', 'edgeCases', 'verification'];
|
|
@@ -35669,7 +36010,41 @@ function computeReadinessVerdict(spec) {
|
|
|
35669
36010
|
const failingBlockers = BLOCKER_ORDER
|
|
35670
36011
|
.filter(k => !signals[k])
|
|
35671
36012
|
.map(k => exports.READINESS_BLOCKER_DESCRIPTIONS[k]);
|
|
35672
|
-
|
|
36013
|
+
// What the reader read, per dimension, independent of whether the heuristic accepted it.
|
|
36014
|
+
// Placeholder text is deliberately not extracted: no path from a blank spec to confirmable.
|
|
36015
|
+
const objectiveText = coerceText(spec.objective).trim();
|
|
36016
|
+
const titleText = coerceText(spec.title).trim();
|
|
36017
|
+
const extractedBy = {
|
|
36018
|
+
goal: isLikelyPlaceholderText(titleText) ? '' : titleText,
|
|
36019
|
+
objective: isLikelyPlaceholderText(objectiveText) ? '' : objectiveText,
|
|
36020
|
+
outcomes: nonEmptyOutcomes.join('; '),
|
|
36021
|
+
constraints: asArray(spec.constraints).map(c => coerceText(c).trim()).filter(Boolean).join('; '),
|
|
36022
|
+
edgeCases: asArray(spec.edgeCases)
|
|
36023
|
+
.map((ec) => coerceText(ec?.scenario).trim())
|
|
36024
|
+
.filter(Boolean).join('; '),
|
|
36025
|
+
verification: verificationDescriptions(spec.verification).join('; '),
|
|
36026
|
+
};
|
|
36027
|
+
const detail = {};
|
|
36028
|
+
for (const key of exports.READINESS_GATE_ORDER) {
|
|
36029
|
+
const text = extractedBy[key] ?? '';
|
|
36030
|
+
detail[key] = signals[key]
|
|
36031
|
+
? { state: 'pass', ...(text ? { extracted: text } : {}) }
|
|
36032
|
+
: text
|
|
36033
|
+
? { state: 'unconfirmed', extracted: text }
|
|
36034
|
+
: { state: 'absent' };
|
|
36035
|
+
}
|
|
36036
|
+
// Confirmations only ever move a dimension the heuristics already failed; `signals` is never
|
|
36037
|
+
// mutated, so the lexical verdict stays visible underneath.
|
|
36038
|
+
const anchorDigests = {
|
|
36039
|
+
objective: fieldDigest(extractedBy.objective),
|
|
36040
|
+
outcomes: fieldDigest(extractedBy.outcomes),
|
|
36041
|
+
};
|
|
36042
|
+
const rejectedConfirmations = applyConfirmations(detail, anchorDigests, spec.confirmations);
|
|
36043
|
+
const confirmedReady = exports.READINESS_GATE_ORDER.every(k => signals[k] || detail[k]?.state === 'pass' || detail[k]?.state === 'not_applicable');
|
|
36044
|
+
return {
|
|
36045
|
+
ready: failingBlockers.length === 0,
|
|
36046
|
+
signals, failingBlockers, detail, rejectedConfirmations, confirmedReady,
|
|
36047
|
+
};
|
|
35673
36048
|
}
|
|
35674
36049
|
const GATE_LABELS = {
|
|
35675
36050
|
goal: 'Title',
|
|
@@ -35696,29 +36071,100 @@ const FRONTMATTER_GATE_LABELS = {
|
|
|
35696
36071
|
*/
|
|
35697
36072
|
function formatReadinessFrontmatter(verdict) {
|
|
35698
36073
|
const total = exports.READINESS_GATE_ORDER.length;
|
|
35699
|
-
|
|
35700
|
-
|
|
35701
|
-
|
|
35702
|
-
const
|
|
35703
|
-
|
|
36074
|
+
// A confirmed dimension counts as passed; a WAIVED one never does (contract amendment D:
|
|
36075
|
+
// "passed 6/6 (1 waived)" launders an exception into the score). Waivers are counted
|
|
36076
|
+
// separately and named, and a spec with both product dimensions waived is not scored at all.
|
|
36077
|
+
const passed = exports.READINESS_GATE_ORDER.filter((k) => verdict.detail?.[k]?.state === 'pass' || verdict.signals[k]).length;
|
|
36078
|
+
const waived = exports.READINESS_GATE_ORDER.filter((k) => verdict.detail?.[k]?.state === 'not_applicable');
|
|
36079
|
+
const blocking = exports.READINESS_GATE_ORDER.filter((k) => !verdict.signals[k] && verdict.detail?.[k]?.state !== 'pass' && verdict.detail?.[k]?.state !== 'not_applicable').map((k) => FRONTMATTER_GATE_LABELS[k]);
|
|
36080
|
+
if (blocking.length)
|
|
36081
|
+
return `failed ${passed}/${total} — blocking: ${blocking.join(', ')}`;
|
|
36082
|
+
if (waived.length === 2)
|
|
36083
|
+
return 'product intent preflight not applicable to this change';
|
|
36084
|
+
if (waived.length)
|
|
36085
|
+
return `ready with exceptions: ${passed} passed, ${waived.length} waived (${waived.map(k => FRONTMATTER_GATE_LABELS[k]).join(', ')})`;
|
|
36086
|
+
return `passed ${passed}/${total}`;
|
|
36087
|
+
}
|
|
36088
|
+
/**
|
|
36089
|
+
* The extra line an `unconfirmed` dimension earns: what was read, and an honest statement of
|
|
36090
|
+
* what the check could not find in it.
|
|
36091
|
+
*
|
|
36092
|
+
* Only objective and outcomes appear here, and that scoping is the finding, not caution. The
|
|
36093
|
+
* audit measured that most failures on those two are false negatives from a fixed English
|
|
36094
|
+
* vocabulary. Verification's measured misses were all extraction failures, so quoting text
|
|
36095
|
+
* back there would imply a confidence the evidence does not support.
|
|
36096
|
+
*/
|
|
36097
|
+
const UNCONFIRMED_HINT = {
|
|
36098
|
+
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:',
|
|
36099
|
+
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:',
|
|
36100
|
+
};
|
|
36101
|
+
/** Keep a quoted extraction to one readable line; the full text is in verdict.detail. */
|
|
36102
|
+
function truncateForQuote(text, max = 160) {
|
|
36103
|
+
const flat = text.replace(/\s+/g, ' ').trim();
|
|
36104
|
+
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}…`;
|
|
35704
36105
|
}
|
|
35705
36106
|
/** Human/agent-readable verdict block, stable enough to parse by line prefix. */
|
|
35706
|
-
function formatReadinessVerdict(verdict, specTitle
|
|
36107
|
+
function formatReadinessVerdict(verdict, specTitle,
|
|
36108
|
+
/** Override the closing repair line. The default names MCP tools, which are wrong in a CLI
|
|
36109
|
+
* reading a repo that has never installed Pathmode. */
|
|
36110
|
+
repairHint) {
|
|
35707
36111
|
const lines = [];
|
|
35708
36112
|
const name = (specTitle || '').trim();
|
|
35709
|
-
|
|
35710
|
-
|
|
36113
|
+
// The RESOLVED state is what a reader acts on: `ready`/`signals` stay the pure lexical
|
|
36114
|
+
// verdict underneath, so what a spec earned stays distinguishable from what was vouched for,
|
|
36115
|
+
// but a confirmed dimension must stop being reported as a blocker or the whole confirmation
|
|
36116
|
+
// loop is invisible.
|
|
36117
|
+
const resolved = (k) => verdict.detail?.[k]?.state ?? (verdict.signals[k] ? 'pass' : 'absent');
|
|
36118
|
+
const stillBlocking = BLOCKER_ORDER.filter(k => !verdict.signals[k] && resolved(k) !== 'pass' && resolved(k) !== 'not_applicable');
|
|
36119
|
+
const waivedKeys = BLOCKER_ORDER.filter(k => resolved(k) === 'not_applicable');
|
|
36120
|
+
const vouched = BLOCKER_ORDER.filter(k => !verdict.signals[k] && resolved(k) === 'pass');
|
|
36121
|
+
if (!stillBlocking.length) {
|
|
36122
|
+
if (waivedKeys.length === 2) {
|
|
36123
|
+
lines.push(`– Product intent preflight not applicable to this change${name ? ` ("${name}")` : ''}.`);
|
|
36124
|
+
}
|
|
36125
|
+
else if (waivedKeys.length) {
|
|
36126
|
+
lines.push(`✓ Ready with exceptions${name ? ` for "${name}"` : ''}. ${6 - waivedKeys.length} passed, ${waivedKeys.length} waived.`);
|
|
36127
|
+
}
|
|
36128
|
+
else {
|
|
36129
|
+
lines.push(`✓ Preflight passed${name ? ` for "${name}"` : ''}. 6/6 checks. Ready to hand to an agent.`);
|
|
36130
|
+
}
|
|
36131
|
+
for (const k of vouched) {
|
|
36132
|
+
const d = verdict.detail?.[k];
|
|
36133
|
+
const who = d?.confirmedBy ?? 'agent';
|
|
36134
|
+
lines.push(` ✓ ${GATE_LABELS[k]} confirmed by ${who}${who === 'human' && d?.assurance !== 'authenticated' ? ' (unverified)' : ''}, not by the check`);
|
|
36135
|
+
}
|
|
36136
|
+
for (const k of waivedKeys) {
|
|
36137
|
+
const d = verdict.detail?.[k];
|
|
36138
|
+
const who = d?.confirmedBy ?? 'human';
|
|
36139
|
+
lines.push(` – ${GATE_LABELS[k]} waived by ${who}${who === 'human' && d?.assurance !== 'authenticated' ? ' (unverified)' : ''}: ${d?.reason || 'no reason recorded'}`);
|
|
36140
|
+
}
|
|
35711
36141
|
}
|
|
35712
36142
|
else {
|
|
35713
|
-
lines.push(`✗ Preflight failed${name ? ` for "${name}"` : ''}. ${
|
|
35714
|
-
|
|
35715
|
-
|
|
36143
|
+
lines.push(`✗ Preflight failed${name ? ` for "${name}"` : ''}. ${stillBlocking.length}/6 checks blocking.`);
|
|
36144
|
+
// Blocker lines stay byte-identical and in order; an unconfirmed dimension gets an
|
|
36145
|
+
// extra indented line quoting what was actually read. Without it the verdict says
|
|
36146
|
+
// "name the actor" to an author who named one in words this vocabulary misses, which
|
|
36147
|
+
// an audit of 80 real specs measured as the majority of these failures
|
|
36148
|
+
// (docs/research/openspec-corpus-audit.md).
|
|
36149
|
+
for (const key of BLOCKER_ORDER) {
|
|
36150
|
+
if (verdict.signals[key] || resolved(key) === 'pass' || resolved(key) === 'not_applicable')
|
|
36151
|
+
continue;
|
|
36152
|
+
lines.push(` ✗ ${exports.READINESS_BLOCKER_DESCRIPTIONS[key]}`);
|
|
36153
|
+
const d = verdict.detail?.[key];
|
|
36154
|
+
if (d?.state === 'unconfirmed' && d.extracted && UNCONFIRMED_HINT[key]) {
|
|
36155
|
+
lines.push(` ↳ ${UNCONFIRMED_HINT[key]}`);
|
|
36156
|
+
lines.push(` "${truncateForQuote(d.extracted)}"`);
|
|
36157
|
+
}
|
|
36158
|
+
}
|
|
35716
36159
|
}
|
|
35717
36160
|
lines.push('');
|
|
35718
|
-
lines.push(exports.READINESS_GATE_ORDER.map(k =>
|
|
35719
|
-
|
|
36161
|
+
lines.push(exports.READINESS_GATE_ORDER.map(k => {
|
|
36162
|
+
const st = resolved(k);
|
|
36163
|
+
return `${st === 'pass' ? '✓' : st === 'not_applicable' ? '–' : st === 'unconfirmed' ? '?' : '·'} ${GATE_LABELS[k]}`;
|
|
36164
|
+
}).join(' '));
|
|
36165
|
+
if (stillBlocking.length) {
|
|
35720
36166
|
lines.push('');
|
|
35721
|
-
lines.push('Repair the failing fields (ask the user one targeted question per blocker), save with intent_save, and re-run check_intent_readiness. The verdict is deterministic: the same spec always gets the same result.');
|
|
36167
|
+
lines.push(repairHint ?? 'Repair the failing fields (ask the user one targeted question per blocker), save with intent_save, and re-run check_intent_readiness. The verdict is deterministic: the same spec always gets the same result.');
|
|
35722
36168
|
}
|
|
35723
36169
|
return lines.join('\n');
|
|
35724
36170
|
}
|
|
@@ -65652,7 +66098,7 @@ module.exports = /*#__PURE__*/JSON.parse('{"$schema":"http://json-schema.org/dra
|
|
|
65652
66098
|
/***/ ((module) => {
|
|
65653
66099
|
|
|
65654
66100
|
"use strict";
|
|
65655
|
-
module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.
|
|
66101
|
+
module.exports = /*#__PURE__*/JSON.parse('{"name":"@pathmode/mcp-server","version":"1.15.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"}}');
|
|
65656
66102
|
|
|
65657
66103
|
/***/ })
|
|
65658
66104
|
|
|
@@ -67070,12 +67516,23 @@ function startMcpServer() {
|
|
|
67070
67516
|
let sourceUrl;
|
|
67071
67517
|
let didNotTravel = [];
|
|
67072
67518
|
const writeSpecFile = (opts) => {
|
|
67073
|
-
|
|
67519
|
+
let content = (0, intent_compiler_1.formatIntentMd)({ ...fields, id: opts.canonicalId }, {
|
|
67074
67520
|
version, status, created,
|
|
67075
67521
|
readiness: (0, readiness_1.formatReadinessFrontmatter)(verdict),
|
|
67076
67522
|
specVersion: opts.specVersion,
|
|
67077
67523
|
source: opts.sourceUrl,
|
|
67078
67524
|
});
|
|
67525
|
+
// Carry existing confirmations across the rewrite. intent_save builds its content from
|
|
67526
|
+
// the incoming spec, which by design has no `confirmations` field (that absence is what
|
|
67527
|
+
// enforces M1: a confirmation cannot be minted in the write that authored the field).
|
|
67528
|
+
// Without this the very next save silently deletes every confirmation the file holds,
|
|
67529
|
+
// and the anchors would have caught nothing because the records would be gone.
|
|
67530
|
+
// Confirmations are re-validated on read, so a save that changed the field text simply
|
|
67531
|
+
// leaves them stale rather than wrongly live.
|
|
67532
|
+
const prior = (0, fs_1.existsSync)(filePath) ? (0, local_reader_1.readIntentFile)(filePath)?.confirmations ?? [] : [];
|
|
67533
|
+
if (prior.length) {
|
|
67534
|
+
content = (0, intent_compiler_1.spliceConfirmationsSection)(content, (0, intent_compiler_1.renderConfirmationsSection)(prior));
|
|
67535
|
+
}
|
|
67079
67536
|
(0, fs_1.writeFileSync)(filePath, content, 'utf-8');
|
|
67080
67537
|
};
|
|
67081
67538
|
if (isLocalMode) {
|
|
@@ -67152,6 +67609,98 @@ function startMcpServer() {
|
|
|
67152
67609
|
}],
|
|
67153
67610
|
};
|
|
67154
67611
|
});
|
|
67612
|
+
/**
|
|
67613
|
+
* Confirm or waive a readiness dimension the deterministic gate could not confirm on its own.
|
|
67614
|
+
*
|
|
67615
|
+
* M1 (a confirmation must not be minted in the write that authored the field) is enforced by the
|
|
67616
|
+
* TOOL BOUNDARY, not by tracking state: `intent_save` has no `confirmations` parameter, so the
|
|
67617
|
+
* only way a confirmation exists is a separate call against a file a previous write produced.
|
|
67618
|
+
* Keep it that way. Adding confirmations to intent_save would let an agent author a field and
|
|
67619
|
+
* vouch for it in one breath, which is the rubber stamp this whole mechanism exists to price.
|
|
67620
|
+
*
|
|
67621
|
+
* The tool stamps `by: agent` for a confirmation and never accepts a `by` parameter. A waiver is
|
|
67622
|
+
* a human decision (contract amendment A) and requires `humanApproved`, which the agent may only
|
|
67623
|
+
* set after the user has actually said so. That is forgeable, and so is every other byte of a
|
|
67624
|
+
* local file; what it buys is that the shortcut is not an official one-call path to a green gate.
|
|
67625
|
+
*/
|
|
67626
|
+
server.registerTool('confirm_intent_dimension', {
|
|
67627
|
+
title: 'Confirm or waive a readiness dimension',
|
|
67628
|
+
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. ' +
|
|
67629
|
+
'confirm: state who is affected and what goes wrong for them, in your own words, and the dimension passes. ' +
|
|
67630
|
+
'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. ' +
|
|
67631
|
+
'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.',
|
|
67632
|
+
inputSchema: {
|
|
67633
|
+
dimension: zod_1.z.enum(['objective', 'outcomes']).describe('Only these two are confirmable. Title and verification are never waivable.'),
|
|
67634
|
+
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.'),
|
|
67635
|
+
actor: zod_1.z.string().optional().describe('confirm + objective: who is affected, in your words.'),
|
|
67636
|
+
problem: zod_1.z.string().optional().describe('confirm + objective: what goes wrong for them today.'),
|
|
67637
|
+
outcome: zod_1.z.string().optional().describe('confirm + outcomes: the observable change.'),
|
|
67638
|
+
observable: zod_1.z.string().optional().describe('confirm + outcomes: how you would see it.'),
|
|
67639
|
+
reason: zod_1.z.string().optional().describe('waive: why this dimension does not apply.'),
|
|
67640
|
+
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.'),
|
|
67641
|
+
path: zod_1.z.string().optional().describe('Path to the intent file. Defaults to intent.md.'),
|
|
67642
|
+
},
|
|
67643
|
+
}, async ({ dimension, action, actor, problem, outcome, observable, reason, humanApproved, path }) => {
|
|
67644
|
+
const filePath = resolveWithinProject(path || 'intent.md');
|
|
67645
|
+
const existing = (0, local_reader_1.readIntentFile)(filePath);
|
|
67646
|
+
if (!existing) {
|
|
67647
|
+
return { content: [{ type: 'text', text: `✗ No intent file at ${filePath}. Save one with intent_save first, then confirm.` }] };
|
|
67648
|
+
}
|
|
67649
|
+
const verdict = (0, readiness_1.computeReadinessVerdict)({ ...existing, confirmations: existing.confirmations });
|
|
67650
|
+
const state = verdict.detail[dimension]?.state;
|
|
67651
|
+
if (state === 'pass') {
|
|
67652
|
+
return { content: [{ type: 'text', text: `· ${dimension} already passes on its own. Nothing to confirm.` }] };
|
|
67653
|
+
}
|
|
67654
|
+
if (action === 'confirm' && state === 'absent') {
|
|
67655
|
+
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.` }] };
|
|
67656
|
+
}
|
|
67657
|
+
if (action === 'waive' && !humanApproved) {
|
|
67658
|
+
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.` }] };
|
|
67659
|
+
}
|
|
67660
|
+
if (action === 'confirm') {
|
|
67661
|
+
const missing = dimension === 'objective'
|
|
67662
|
+
? (!actor?.trim() || !problem?.trim()) && 'actor and problem'
|
|
67663
|
+
: (!outcome?.trim() || !observable?.trim()) && 'outcome and observable';
|
|
67664
|
+
if (missing)
|
|
67665
|
+
return { content: [{ type: 'text', text: `✗ A confirmation must name the ${missing}. A bare yes is not a confirmation.` }] };
|
|
67666
|
+
}
|
|
67667
|
+
if (action === 'waive' && !reason?.trim()) {
|
|
67668
|
+
return { content: [{ type: 'text', text: '✗ A waiver must carry a reason.' }] };
|
|
67669
|
+
}
|
|
67670
|
+
// Anchor to exactly what the gate read for this dimension, so an edit invalidates it.
|
|
67671
|
+
const anchor = (0, readiness_1.fieldDigest)(verdict.detail[dimension]?.extracted ?? '');
|
|
67672
|
+
const record = action === 'waive'
|
|
67673
|
+
? { dimension, kind: 'waived', by: 'human', reason: reason.trim(), anchor }
|
|
67674
|
+
: {
|
|
67675
|
+
dimension, kind: 'confirmed', by: 'agent', anchor,
|
|
67676
|
+
...(dimension === 'objective'
|
|
67677
|
+
? { actor: actor.trim(), problem: problem.trim() }
|
|
67678
|
+
: { outcome: outcome.trim(), observable: observable.trim() }),
|
|
67679
|
+
};
|
|
67680
|
+
// One live record per dimension: a re-confirmation replaces rather than accumulates.
|
|
67681
|
+
const kept = existing.confirmations.filter(c => c.dimension !== dimension);
|
|
67682
|
+
const next = [...kept, record];
|
|
67683
|
+
// SPLICE, never regenerate. formatIntentMd round-trips through a lossy projection: it
|
|
67684
|
+
// would reset version and status to defaults, restamp created, and delete unknown
|
|
67685
|
+
// frontmatter keys and any section the reader has no field for. Recording a judgment
|
|
67686
|
+
// must not damage the document being judged.
|
|
67687
|
+
const original = (0, fs_1.readFileSync)(filePath, 'utf-8');
|
|
67688
|
+
const content = (0, intent_compiler_1.spliceConfirmationsSection)(original, (0, intent_compiler_1.renderConfirmationsSection)(next));
|
|
67689
|
+
(0, fs_1.writeFileSync)(filePath, content, 'utf-8');
|
|
67690
|
+
const after = (0, readiness_1.computeReadinessVerdict)({ ...existing, confirmations: [...kept, record] });
|
|
67691
|
+
return {
|
|
67692
|
+
content: [{
|
|
67693
|
+
type: 'text',
|
|
67694
|
+
text: [
|
|
67695
|
+
`✓ ${dimension} ${action === 'waive' ? 'waived' : 'confirmed'} in ${filePath}`,
|
|
67696
|
+
` ${after.detail[dimension]?.state === 'not_applicable' ? 'not applicable to this change' : 'confirmed against the current text'} · anchor ${anchor}`,
|
|
67697
|
+
action === 'waive' ? ' Recorded as a human decision this environment cannot verify.' : '',
|
|
67698
|
+
'',
|
|
67699
|
+
(0, readiness_1.formatReadinessVerdict)(after),
|
|
67700
|
+
].filter(Boolean).join('\n'),
|
|
67701
|
+
}],
|
|
67702
|
+
};
|
|
67703
|
+
});
|
|
67155
67704
|
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
67705
|
format: zod_1.z.enum(['cursorrules', 'claude-md', 'agents-md', 'outcome-rubric']).describe('Export format'),
|
|
67157
67706
|
spec: zod_1.z.object(intentSpecSchema),
|
|
@@ -137,3 +137,18 @@ export interface OutcomeRubricOptions {
|
|
|
137
137
|
* Docs: https://platform.claude.com/docs/en/managed-agents/define-outcomes
|
|
138
138
|
*/
|
|
139
139
|
export declare function formatOutcomeRubric(spec: IntentFields, opts?: OutcomeRubricOptions): string;
|
|
140
|
+
/**
|
|
141
|
+
* Splice a `## Confirmations` section into an existing intent.md WITHOUT regenerating the file.
|
|
142
|
+
*
|
|
143
|
+
* Regenerating through formatIntentMd is lossy by construction: LocalIntent is a projection, so
|
|
144
|
+
* a round-trip drops unknown frontmatter keys, resets `version` and `status` to their defaults,
|
|
145
|
+
* restamps `created`, and deletes any section the reader has no field for. Measured on a real
|
|
146
|
+
* spec: status approved -> draft, version 7 -> 1, and `source`, `specVersion` and a `## Evidence`
|
|
147
|
+
* section gone. A tool that records a judgment must not destroy the document it judges.
|
|
148
|
+
*
|
|
149
|
+
* So this is a text operation. It replaces an existing `## Confirmations` section in place, or
|
|
150
|
+
* appends one at the end, and touches nothing else in the file.
|
|
151
|
+
*/
|
|
152
|
+
export declare function spliceConfirmationsSection(original: string, section: string): string;
|
|
153
|
+
/** Render confirmation records as the `## Confirmations` section body. */
|
|
154
|
+
export declare function renderConfirmationsSection(records: Record<string, unknown>[]): string;
|
|
@@ -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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"}
|
package/dist/local-reader.d.ts
CHANGED
|
@@ -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. */
|
|
@@ -81,9 +105,4 @@ export declare function readIntentMeta(filePath: string): LocalIntentMeta | null
|
|
|
81
105
|
* Parse a single intent.md file with YAML frontmatter.
|
|
82
106
|
*/
|
|
83
107
|
export declare function readIntentFile(filePath: string): LocalIntent | null;
|
|
84
|
-
/**
|
|
85
|
-
* Parse intent.md content (frontmatter + body) into a LocalIntent. Pure: no fs, no cwd.
|
|
86
|
-
* Exported so lib/intentMdParseParity.test.ts can hold the web paste-box parser
|
|
87
|
-
* (lib/intentMdParse.ts) to this implementation's behavior on gate-relevant fields.
|
|
88
|
-
*/
|
|
89
108
|
export declare function parseIntentMarkdown(content: string, fallbackId?: string): LocalIntent;
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"","sourceRoot":"","sources":["file:///Users/jannelammi/code/Pathmode/packages/mcp-server/src/local-reader.ts"],"names":[],"mappings":"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"}
|
package/dist/readiness.d.ts
CHANGED
|
@@ -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,43 @@ 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;
|
|
89
|
+
/** Set when a confirmation or waiver moved this dimension: who vouched, and how well we know it. */
|
|
90
|
+
confirmedBy?: 'agent' | 'human';
|
|
91
|
+
assurance?: 'authenticated' | 'local-unverified';
|
|
92
|
+
/** The waiver's stated reason, for rendering the exception rather than hiding it. */
|
|
93
|
+
reason?: string;
|
|
33
94
|
}
|
|
34
95
|
export interface ReadinessVerdict {
|
|
35
96
|
ready: boolean;
|
|
36
97
|
signals: Record<string, boolean>;
|
|
37
98
|
failingBlockers: string[];
|
|
99
|
+
/** Additive: failingBlockers stays byte-identical, richer messaging is opt-in. */
|
|
100
|
+
detail: Record<string, DimensionDetail>;
|
|
101
|
+
/** Confirmations that did not apply, with a reason. Nothing is ever dropped silently. */
|
|
102
|
+
rejectedConfirmations: RejectedConfirmation[];
|
|
103
|
+
/**
|
|
104
|
+
* Ready once confirmations and waivers are counted. `ready` stays the pure lexical verdict
|
|
105
|
+
* so a surface can distinguish what was earned from what was vouched for.
|
|
106
|
+
*/
|
|
107
|
+
confirmedReady: boolean;
|
|
38
108
|
}
|
|
39
109
|
/**
|
|
40
110
|
* The preflight: six deterministic gates over an intent spec. Mirrors
|
|
@@ -50,4 +120,7 @@ export declare function computeReadinessVerdict(spec: ReadinessSpecInput): Readi
|
|
|
50
120
|
*/
|
|
51
121
|
export declare function formatReadinessFrontmatter(verdict: ReadinessVerdict): string;
|
|
52
122
|
/** Human/agent-readable verdict block, stable enough to parse by line prefix. */
|
|
53
|
-
export declare function formatReadinessVerdict(verdict: ReadinessVerdict, specTitle?: string
|
|
123
|
+
export declare function formatReadinessVerdict(verdict: ReadinessVerdict, specTitle?: string,
|
|
124
|
+
/** Override the closing repair line. The default names MCP tools, which are wrong in a CLI
|
|
125
|
+
* reading a repo that has never installed Pathmode. */
|
|
126
|
+
repairHint?: string): string;
|
package/dist/readiness.d.ts.map
CHANGED
|
@@ -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;
|
|
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.
|
|
5
|
+
"version": "1.15.1",
|
|
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": {
|