@njeybe/nativ 2.4.0 → 2.4.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/CHANGELOG.md +11 -0
- package/dist/core/spec-slices.js +119 -7
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.4.1
|
|
4
|
+
|
|
5
|
+
### Fixed & Improved
|
|
6
|
+
|
|
7
|
+
- **Resilient Spec Slices Resolution.** Enhanced `resolveSpecSlices` in `src/core/spec-slices.ts` to gracefully resolve non-standard and shorthand contract/spec references across `.ai/` contracts:
|
|
8
|
+
- **Shorthand Markdown Section Anchors:** Supports numeric and prefix section references (e.g. `ui_specs.md#6`, `ui_specs.md#15`, `ui_specs.md#15.1`) by matching section prefixes when exact heading slug matches are not found.
|
|
9
|
+
- **JSON Root Keys without Leading Slash:** Automatically resolves JSON references missing a leading slash (e.g. `api_contracts.json#confirmationPageContracts`, `api_contracts.json#legalDocumentsContract`) by falling back to root property pointers and dot-path navigation.
|
|
10
|
+
- **Route Names Lookup:** Resolves named route references (e.g. `api_contracts.json#coaches.payment-qr`, `company.coaches.payment-qr`) across `routingPolicy.playerRoutes`, `venueScopedRoutes`, and other route collections.
|
|
11
|
+
- **Mail Events by Trigger:** Resolves mail event references by trigger name (e.g. `api_contracts.json#Coach Lesson Booked`, `Coach Payment Proof Uploaded`).
|
|
12
|
+
- **Database Schema Tables:** Resolves table references directly by name in `relevantTables` and `tables` collections (e.g. `db_schema.json#legal_documents`, `db_schema.json#coach_bookings`, `db_schema.json#sports`).
|
|
13
|
+
|
|
3
14
|
## 2.4.0
|
|
4
15
|
|
|
5
16
|
### Added
|
package/dist/core/spec-slices.js
CHANGED
|
@@ -16,9 +16,7 @@ function cap(text, max) {
|
|
|
16
16
|
return { text, truncated: false };
|
|
17
17
|
return { text: text.slice(0, Math.max(0, max)) + TRUNCATION_MARKER, truncated: true };
|
|
18
18
|
}
|
|
19
|
-
function
|
|
20
|
-
const wanted = decodeURIComponent(anchor).toLowerCase();
|
|
21
|
-
const lines = content.split(/\r?\n/);
|
|
19
|
+
function findSection(lines, predicate) {
|
|
22
20
|
const seen = new Map();
|
|
23
21
|
let inFence = false;
|
|
24
22
|
let start = -1;
|
|
@@ -40,17 +38,41 @@ function markdownSection(content, anchor) {
|
|
|
40
38
|
return lines.slice(start, i).join('\n').trimEnd();
|
|
41
39
|
continue;
|
|
42
40
|
}
|
|
43
|
-
const
|
|
41
|
+
const headingText = m[2];
|
|
42
|
+
const base = slugifyHeading(headingText);
|
|
44
43
|
const count = seen.get(base) ?? 0;
|
|
45
44
|
seen.set(base, count + 1);
|
|
46
45
|
const slug = count === 0 ? base : `${base}-${count}`;
|
|
47
|
-
if (slug
|
|
46
|
+
if (predicate(slug, headingText)) {
|
|
48
47
|
start = i;
|
|
49
48
|
level = hLevel;
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
return start >= 0 ? lines.slice(start).join('\n').trimEnd() : null;
|
|
53
52
|
}
|
|
53
|
+
function markdownSection(content, anchor) {
|
|
54
|
+
const wanted = decodeURIComponent(anchor).trim().toLowerCase();
|
|
55
|
+
const lines = content.split(/\r?\n/);
|
|
56
|
+
// 1. Exact slug match (standard markdown anchor)
|
|
57
|
+
const exact = findSection(lines, (slug) => slug === wanted);
|
|
58
|
+
if (exact !== null)
|
|
59
|
+
return exact;
|
|
60
|
+
// 2. Prefix / section number match (e.g. "6" or "15.1" matching "## 6. Coach..." or "### 15.1 Shared Pieces")
|
|
61
|
+
const prefixMatch = findSection(lines, (_slug, headingText) => {
|
|
62
|
+
const raw = headingText.trim().toLowerCase();
|
|
63
|
+
if (raw === wanted)
|
|
64
|
+
return true;
|
|
65
|
+
if (raw.startsWith(`${wanted}.`) || raw.startsWith(`${wanted} `) || raw.startsWith(`${wanted}:`))
|
|
66
|
+
return true;
|
|
67
|
+
const base = slugifyHeading(headingText);
|
|
68
|
+
if (base.startsWith(`${wanted}-`) || base === wanted)
|
|
69
|
+
return true;
|
|
70
|
+
return false;
|
|
71
|
+
});
|
|
72
|
+
if (prefixMatch !== null)
|
|
73
|
+
return prefixMatch;
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
54
76
|
function jsonPointerValue(doc, pointer) {
|
|
55
77
|
if (pointer === '')
|
|
56
78
|
return { found: true, value: doc };
|
|
@@ -80,6 +102,73 @@ function jsonPointerValue(doc, pointer) {
|
|
|
80
102
|
}
|
|
81
103
|
return { found: true, value: current };
|
|
82
104
|
}
|
|
105
|
+
function getByDotPath(obj, pathStr) {
|
|
106
|
+
const parts = pathStr.split('.');
|
|
107
|
+
let cur = obj;
|
|
108
|
+
for (const p of parts) {
|
|
109
|
+
if (cur && typeof cur === 'object' && Object.prototype.hasOwnProperty.call(cur, p)) {
|
|
110
|
+
cur = cur[p];
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
return { found: false };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { found: true, value: cur };
|
|
117
|
+
}
|
|
118
|
+
function findEntityInDoc(doc, ref) {
|
|
119
|
+
if (!doc || typeof doc !== 'object')
|
|
120
|
+
return { found: false };
|
|
121
|
+
// 1. Direct table match in relevantTables or tables
|
|
122
|
+
const tables = Array.isArray(doc.relevantTables) ? doc.relevantTables : (Array.isArray(doc.tables) ? doc.tables : null);
|
|
123
|
+
if (tables) {
|
|
124
|
+
const table = tables.find((t) => t && (t.name === ref || t.tableName === ref || t.id === ref));
|
|
125
|
+
if (table)
|
|
126
|
+
return { found: true, value: table };
|
|
127
|
+
}
|
|
128
|
+
if (doc.tables && typeof doc.tables === 'object' && !Array.isArray(doc.tables) && Object.prototype.hasOwnProperty.call(doc.tables, ref)) {
|
|
129
|
+
return { found: true, value: doc.tables[ref] };
|
|
130
|
+
}
|
|
131
|
+
// 2. Mail events trigger match
|
|
132
|
+
if (Array.isArray(doc.mailEvents)) {
|
|
133
|
+
const event = doc.mailEvents.find((m) => m && (m.trigger === ref || m.endpoint === ref || m.id === ref));
|
|
134
|
+
if (event)
|
|
135
|
+
return { found: true, value: event };
|
|
136
|
+
}
|
|
137
|
+
// 3. Routes match in routingPolicy or routes
|
|
138
|
+
const routeLists = [];
|
|
139
|
+
if (doc.routingPolicy && typeof doc.routingPolicy === 'object') {
|
|
140
|
+
if (Array.isArray(doc.routingPolicy.playerRoutes))
|
|
141
|
+
routeLists.push(doc.routingPolicy.playerRoutes);
|
|
142
|
+
if (Array.isArray(doc.routingPolicy.venueScopedRoutes))
|
|
143
|
+
routeLists.push(doc.routingPolicy.venueScopedRoutes);
|
|
144
|
+
if (Array.isArray(doc.routingPolicy.adminRoutes))
|
|
145
|
+
routeLists.push(doc.routingPolicy.adminRoutes);
|
|
146
|
+
if (Array.isArray(doc.routingPolicy.routes))
|
|
147
|
+
routeLists.push(doc.routingPolicy.routes);
|
|
148
|
+
}
|
|
149
|
+
if (Array.isArray(doc.routes))
|
|
150
|
+
routeLists.push(doc.routes);
|
|
151
|
+
for (const list of routeLists) {
|
|
152
|
+
const route = list.find((r) => r && (r.name === ref ||
|
|
153
|
+
r.id === ref ||
|
|
154
|
+
(Array.isArray(r.routeNames) && r.routeNames.includes(ref))));
|
|
155
|
+
if (route)
|
|
156
|
+
return { found: true, value: route };
|
|
157
|
+
}
|
|
158
|
+
// 4. Contract list items (e.g. confirmationPageContracts array)
|
|
159
|
+
for (const key of Object.keys(doc)) {
|
|
160
|
+
const val = doc[key];
|
|
161
|
+
if (Array.isArray(val)) {
|
|
162
|
+
const match = val.find((item) => item && typeof item === 'object' && (item.name === ref ||
|
|
163
|
+
item.id === ref ||
|
|
164
|
+
item.trigger === ref ||
|
|
165
|
+
(Array.isArray(item.routeNames) && item.routeNames.includes(ref))));
|
|
166
|
+
if (match)
|
|
167
|
+
return { found: true, value: match };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { found: false };
|
|
171
|
+
}
|
|
83
172
|
function joinUrl(base, p) {
|
|
84
173
|
return typeof base === 'string' ? base.replace(/\/+$/, '') + p : p;
|
|
85
174
|
}
|
|
@@ -115,9 +204,32 @@ function endpointValue(doc, fragment) {
|
|
|
115
204
|
return byId ? { found: true, value: byId } : { found: false };
|
|
116
205
|
}
|
|
117
206
|
function jsonRefValue(doc, fragment) {
|
|
118
|
-
if (fragment === ''
|
|
207
|
+
if (fragment === '')
|
|
119
208
|
return jsonPointerValue(doc, fragment);
|
|
120
|
-
|
|
209
|
+
// 1. Direct JSON pointer starting with /
|
|
210
|
+
if (fragment.startsWith('/')) {
|
|
211
|
+
const ptr = jsonPointerValue(doc, fragment);
|
|
212
|
+
if (ptr.found)
|
|
213
|
+
return ptr;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// 2. JSON pointer or top-level property without leading slash (e.g. #confirmationPageContracts)
|
|
217
|
+
const ptr = jsonPointerValue(doc, `/${fragment}`);
|
|
218
|
+
if (ptr.found)
|
|
219
|
+
return ptr;
|
|
220
|
+
// 3. Dot-path property navigation (e.g. #contract.section)
|
|
221
|
+
if (fragment.includes('.')) {
|
|
222
|
+
const dot = getByDotPath(doc, fragment);
|
|
223
|
+
if (dot.found)
|
|
224
|
+
return dot;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// 4. Endpoint lookup by method + path or id
|
|
228
|
+
const endpoint = endpointValue(doc, fragment);
|
|
229
|
+
if (endpoint.found)
|
|
230
|
+
return endpoint;
|
|
231
|
+
// 5. Named entity lookup: route names, mail event triggers, DB schema tables
|
|
232
|
+
return findEntityInDoc(doc, fragment);
|
|
121
233
|
}
|
|
122
234
|
/**
|
|
123
235
|
* Resolves specRefs ("ui_specs.md#anchor", "api_contracts.json#/paths/~1x",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nativ",
|
|
3
3
|
"displayName": "nativ",
|
|
4
|
-
"version": "2.4.
|
|
4
|
+
"version": "2.4.1",
|
|
5
5
|
"description": "Role-based multi-agent workflow for Claude Code: architect, worker, verifier and explorer agents, task-scope enforcement, and the nativ MCP server.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "JB Natividad"
|