@suss/intent-ir 0.0.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/LICENSE +205 -0
- package/README.md +37 -0
- package/dist/index.cjs +353 -0
- package/dist/index.d.cts +326 -0
- package/dist/index.d.ts +326 -0
- package/dist/index.js +320 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// src/findings.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var IntentFindingKindSchema = z.enum([
|
|
4
|
+
// System intent (kind: boundary) vs code:
|
|
5
|
+
"uncoveredOutcome",
|
|
6
|
+
// intent declares an outcome the code never produces
|
|
7
|
+
"unimplementedBoundary",
|
|
8
|
+
// intent boundary has no implementing code at all
|
|
9
|
+
"outcomeShapeMismatch",
|
|
10
|
+
// a matched outcome whose body shapes disagree
|
|
11
|
+
"undeclaredOutcome",
|
|
12
|
+
// code produces a REST status the intent doesn't declare
|
|
13
|
+
"unkeyableBoundary",
|
|
14
|
+
// intent boundary can't be keyed, so it can't be checked
|
|
15
|
+
// Outcome intent (kind: prd) — scenario link coverage against system
|
|
16
|
+
// intent. These concretise the proposal's "scenario not linked /
|
|
17
|
+
// dangling / ambiguous" set (docs/internal/proposals/intent-specs.md);
|
|
18
|
+
// the proposal deferred concrete names to implementation time.
|
|
19
|
+
"unlinkedScenario",
|
|
20
|
+
// scenario carries no structured link (info — a valid pending state)
|
|
21
|
+
"danglingScenarioLink",
|
|
22
|
+
// link names an intent / outcome no boundary intent declares
|
|
23
|
+
"ambiguousScenarioLink"
|
|
24
|
+
// link resolves to two or more boundary intents sharing the name
|
|
25
|
+
]);
|
|
26
|
+
var IntentFindingSeveritySchema = z.enum(["error", "warning", "info"]);
|
|
27
|
+
var IntentRefSchema = z.object({
|
|
28
|
+
/** The intent doc's `name` (boundary) or `title` (prd). */
|
|
29
|
+
name: z.string(),
|
|
30
|
+
/** The declared outcome id, when the finding is outcome-specific. */
|
|
31
|
+
outcomeId: z.string().optional()
|
|
32
|
+
});
|
|
33
|
+
var IntentFindingSuppressionSchema = z.object({
|
|
34
|
+
/** The rule's human-written justification. */
|
|
35
|
+
reason: z.string(),
|
|
36
|
+
effect: z.enum(["mark", "downgrade", "hide"]),
|
|
37
|
+
/** Original severity, present only when effect is "downgrade". */
|
|
38
|
+
originalSeverity: IntentFindingSeveritySchema.optional()
|
|
39
|
+
});
|
|
40
|
+
var IntentFindingSchema = z.object({
|
|
41
|
+
kind: IntentFindingKindSchema,
|
|
42
|
+
severity: IntentFindingSeveritySchema,
|
|
43
|
+
/**
|
|
44
|
+
* Human-readable boundary label — e.g. `GET /users/:id` or
|
|
45
|
+
* `fn:@suss/cli::contract`. The key the intent and code were paired on.
|
|
46
|
+
*/
|
|
47
|
+
boundary: z.string(),
|
|
48
|
+
/** The intent side of the finding. */
|
|
49
|
+
intent: IntentRefSchema,
|
|
50
|
+
/**
|
|
51
|
+
* The matched code summary as `${file}::${name}`, when the finding
|
|
52
|
+
* concerns a specific implementation. Absent for unimplementedBoundary.
|
|
53
|
+
*/
|
|
54
|
+
code: z.string().optional(),
|
|
55
|
+
/**
|
|
56
|
+
* PRD-scenario extension — present only on outcome-intent findings
|
|
57
|
+
* (unlinkedScenario / danglingScenarioLink / ambiguousScenarioLink).
|
|
58
|
+
* Identifies the scenario (by its optional title) and the qualified
|
|
59
|
+
* outcome ref (`<intent-name>.<outcome-id>`) that failed to resolve.
|
|
60
|
+
* The peer / boundary findings leave it unset — this is the "intent
|
|
61
|
+
* finding extension" decision 2 of the proposal anticipated.
|
|
62
|
+
*/
|
|
63
|
+
scenario: z.object({
|
|
64
|
+
title: z.string().optional(),
|
|
65
|
+
link: z.string().optional()
|
|
66
|
+
}).optional(),
|
|
67
|
+
message: z.string(),
|
|
68
|
+
/** Present when a .sussignore rule matched this finding. */
|
|
69
|
+
suppressed: IntentFindingSuppressionSchema.optional()
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/schema.ts
|
|
73
|
+
import { z as z2 } from "zod";
|
|
74
|
+
var IntentSourceSchema = z2.enum(["author", "inferred", "inferred, curated"]).default("author");
|
|
75
|
+
var PrimitiveTypeName = z2.enum([
|
|
76
|
+
"string",
|
|
77
|
+
"integer",
|
|
78
|
+
"number",
|
|
79
|
+
"boolean",
|
|
80
|
+
"null",
|
|
81
|
+
"unknown"
|
|
82
|
+
]);
|
|
83
|
+
var ShapeSchema = z2.lazy(
|
|
84
|
+
() => z2.union([
|
|
85
|
+
z2.object({ type: PrimitiveTypeName }),
|
|
86
|
+
z2.object({ type: z2.literal("array"), items: ShapeSchema.optional() }),
|
|
87
|
+
z2.object({
|
|
88
|
+
type: z2.literal("object"),
|
|
89
|
+
properties: z2.record(z2.string(), ShapeSchema).optional(),
|
|
90
|
+
required: z2.array(z2.string()).optional()
|
|
91
|
+
})
|
|
92
|
+
])
|
|
93
|
+
);
|
|
94
|
+
var BodyShapeSchema = z2.union([
|
|
95
|
+
ShapeSchema,
|
|
96
|
+
z2.object({
|
|
97
|
+
properties: z2.record(z2.string(), ShapeSchema).optional(),
|
|
98
|
+
required: z2.array(z2.string()).optional()
|
|
99
|
+
})
|
|
100
|
+
]);
|
|
101
|
+
var RestBoundarySchema = z2.object({
|
|
102
|
+
transport: z2.literal("http").default("http"),
|
|
103
|
+
semantics: z2.literal("rest"),
|
|
104
|
+
method: z2.string().min(1),
|
|
105
|
+
path: z2.string().min(1)
|
|
106
|
+
});
|
|
107
|
+
var FunctionCallBoundarySchema = z2.object({
|
|
108
|
+
transport: z2.string().default("in-process"),
|
|
109
|
+
semantics: z2.literal("function-call"),
|
|
110
|
+
/** Repo-relative module path, when the boundary is an intra-repo unit. */
|
|
111
|
+
module: z2.string().optional(),
|
|
112
|
+
/** Named export within the module / package. */
|
|
113
|
+
exportName: z2.string().optional(),
|
|
114
|
+
/** Package name when the boundary is a public package export. */
|
|
115
|
+
package: z2.string().optional(),
|
|
116
|
+
/** Path to the export within the package (sub-path + nested names). */
|
|
117
|
+
exportPath: z2.array(z2.string()).optional()
|
|
118
|
+
});
|
|
119
|
+
var BoundarySchema = z2.discriminatedUnion("semantics", [
|
|
120
|
+
RestBoundarySchema,
|
|
121
|
+
FunctionCallBoundarySchema
|
|
122
|
+
]);
|
|
123
|
+
var ResponseOutcomeSchema = z2.object({
|
|
124
|
+
status: z2.number().int().min(100).max(599),
|
|
125
|
+
body: BodyShapeSchema.optional()
|
|
126
|
+
});
|
|
127
|
+
var ReturnsOutcomeSchema = z2.object({
|
|
128
|
+
body: BodyShapeSchema.optional()
|
|
129
|
+
});
|
|
130
|
+
var ThrowsOutcomeSchema = z2.object({
|
|
131
|
+
errorType: z2.string().optional()
|
|
132
|
+
});
|
|
133
|
+
function emptyIfNull(schema) {
|
|
134
|
+
return z2.preprocess((v) => v === null ? {} : v, schema);
|
|
135
|
+
}
|
|
136
|
+
var BoundaryTransitionSchema = z2.object({
|
|
137
|
+
id: z2.string().min(1),
|
|
138
|
+
when: z2.string().min(1),
|
|
139
|
+
response: emptyIfNull(ResponseOutcomeSchema).optional(),
|
|
140
|
+
returns: emptyIfNull(ReturnsOutcomeSchema).optional(),
|
|
141
|
+
throws: emptyIfNull(ThrowsOutcomeSchema).optional()
|
|
142
|
+
}).refine(
|
|
143
|
+
(t) => [t.response, t.returns, t.throws].filter((o) => o !== void 0).length === 1,
|
|
144
|
+
{
|
|
145
|
+
message: "each transition must declare exactly one outcome: response, returns, or throws"
|
|
146
|
+
}
|
|
147
|
+
);
|
|
148
|
+
var BoundaryIntentSchema = z2.object({
|
|
149
|
+
kind: z2.literal("boundary"),
|
|
150
|
+
/** Name PRDs reference outcomes through: `<name>.<transition-id>`. */
|
|
151
|
+
name: z2.string().min(1),
|
|
152
|
+
purpose: z2.string().min(1),
|
|
153
|
+
audience: z2.string().min(1),
|
|
154
|
+
source: IntentSourceSchema,
|
|
155
|
+
boundary: BoundarySchema,
|
|
156
|
+
transitions: z2.array(BoundaryTransitionSchema).min(1)
|
|
157
|
+
});
|
|
158
|
+
var PrdScenarioSchema = z2.object({
|
|
159
|
+
title: z2.string().min(1).optional(),
|
|
160
|
+
/** The condition, in the author's terms. */
|
|
161
|
+
when: z2.string().min(1),
|
|
162
|
+
/** The expected outcome, in the author's terms. Always present. */
|
|
163
|
+
expect: z2.string().min(1),
|
|
164
|
+
/**
|
|
165
|
+
* Optional structured link(s) to system-intent outcomes
|
|
166
|
+
* (`<intent-name>.<outcome-id>`). A scenario without `link` is a
|
|
167
|
+
* valid pending-link state: fully human-readable, not yet machine-
|
|
168
|
+
* linked. The link is filled in later by a facilitator (a person, a
|
|
169
|
+
* platform, or an LLM at authoring time) — never required to author.
|
|
170
|
+
*
|
|
171
|
+
* (The human-readable parts are `when` / `expect`; the field is named
|
|
172
|
+
* `link`, not `then`, because a data object with a `then` property is
|
|
173
|
+
* treated as a thenable by Promise resolution — a latent footgun.)
|
|
174
|
+
*/
|
|
175
|
+
link: z2.union([z2.string().min(1), z2.array(z2.string().min(1)).min(1)]).optional()
|
|
176
|
+
});
|
|
177
|
+
var PrdSchema = z2.object({
|
|
178
|
+
kind: z2.literal("prd"),
|
|
179
|
+
title: z2.string().min(1),
|
|
180
|
+
purpose: z2.string().min(1),
|
|
181
|
+
audience: z2.string().min(1),
|
|
182
|
+
source: IntentSourceSchema,
|
|
183
|
+
scenarios: z2.array(PrdScenarioSchema).min(1)
|
|
184
|
+
});
|
|
185
|
+
var IntentDocSchema = z2.discriminatedUnion("kind", [
|
|
186
|
+
BoundaryIntentSchema,
|
|
187
|
+
PrdSchema
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
// src/summary.ts
|
|
191
|
+
import {
|
|
192
|
+
functionCallBinding,
|
|
193
|
+
restBinding
|
|
194
|
+
} from "@suss/ir-core";
|
|
195
|
+
function intentDocToSummary(doc) {
|
|
196
|
+
if (doc.kind === "prd") {
|
|
197
|
+
return prdToSummary(doc);
|
|
198
|
+
}
|
|
199
|
+
return boundaryIntentToSummary(doc);
|
|
200
|
+
}
|
|
201
|
+
function boundaryIntentToSummary(doc) {
|
|
202
|
+
return {
|
|
203
|
+
kind: "boundary",
|
|
204
|
+
name: doc.name,
|
|
205
|
+
purpose: doc.purpose,
|
|
206
|
+
audience: doc.audience,
|
|
207
|
+
source: doc.source,
|
|
208
|
+
boundary: toBoundaryBinding(doc.boundary),
|
|
209
|
+
outcomes: doc.transitions.map(toOutcome)
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function prdToSummary(doc) {
|
|
213
|
+
return {
|
|
214
|
+
kind: "prd",
|
|
215
|
+
title: doc.title,
|
|
216
|
+
purpose: doc.purpose,
|
|
217
|
+
audience: doc.audience,
|
|
218
|
+
source: doc.source,
|
|
219
|
+
scenarios: doc.scenarios.map((s) => ({
|
|
220
|
+
title: s.title ?? null,
|
|
221
|
+
when: s.when,
|
|
222
|
+
expect: s.expect,
|
|
223
|
+
link: s.link === void 0 ? [] : Array.isArray(s.link) ? s.link : [s.link]
|
|
224
|
+
}))
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function toBoundaryBinding(boundary) {
|
|
228
|
+
if (boundary.semantics === "rest") {
|
|
229
|
+
return restBinding({
|
|
230
|
+
transport: boundary.transport,
|
|
231
|
+
method: boundary.method,
|
|
232
|
+
path: boundary.path,
|
|
233
|
+
recognition: "intent"
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
return functionCallBinding({
|
|
237
|
+
transport: boundary.transport,
|
|
238
|
+
recognition: "intent",
|
|
239
|
+
...boundary.module !== void 0 ? { module: boundary.module } : {},
|
|
240
|
+
...boundary.exportName !== void 0 ? { exportName: boundary.exportName } : {},
|
|
241
|
+
...boundary.package !== void 0 ? { package: boundary.package } : {},
|
|
242
|
+
...boundary.exportPath !== void 0 ? { exportPath: boundary.exportPath } : {}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function toOutcome(t) {
|
|
246
|
+
const base = { id: t.id, when: t.when };
|
|
247
|
+
if (t.response !== void 0) {
|
|
248
|
+
return {
|
|
249
|
+
...base,
|
|
250
|
+
kind: "response",
|
|
251
|
+
status: t.response.status,
|
|
252
|
+
body: t.response.body ? bodyToTypeShape(t.response.body) : null,
|
|
253
|
+
errorType: null
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (t.returns !== void 0) {
|
|
257
|
+
return {
|
|
258
|
+
...base,
|
|
259
|
+
kind: "return",
|
|
260
|
+
status: null,
|
|
261
|
+
body: t.returns.body ? bodyToTypeShape(t.returns.body) : null,
|
|
262
|
+
errorType: null
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
...base,
|
|
267
|
+
kind: "throw",
|
|
268
|
+
status: null,
|
|
269
|
+
body: null,
|
|
270
|
+
errorType: t.throws?.errorType ?? null
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function bodyToTypeShape(body) {
|
|
274
|
+
if (!("type" in body)) {
|
|
275
|
+
if (body.properties === void 0) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
return recordShape(body.properties);
|
|
279
|
+
}
|
|
280
|
+
return shapeToTypeShape(body);
|
|
281
|
+
}
|
|
282
|
+
function shapeToTypeShape(shape) {
|
|
283
|
+
if (shape.type === "array") {
|
|
284
|
+
return {
|
|
285
|
+
type: "array",
|
|
286
|
+
items: shape.items !== void 0 ? shapeToTypeShape(shape.items) : { type: "unknown" }
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (shape.type === "object") {
|
|
290
|
+
return recordShape(shape.properties ?? {});
|
|
291
|
+
}
|
|
292
|
+
return PRIMITIVE_TYPE_SHAPES[shape.type];
|
|
293
|
+
}
|
|
294
|
+
function recordShape(authored) {
|
|
295
|
+
const properties = {};
|
|
296
|
+
for (const [name, prop] of Object.entries(authored)) {
|
|
297
|
+
properties[name] = shapeToTypeShape(prop);
|
|
298
|
+
}
|
|
299
|
+
return { type: "record", properties };
|
|
300
|
+
}
|
|
301
|
+
var PRIMITIVE_TYPE_SHAPES = {
|
|
302
|
+
string: { type: "text" },
|
|
303
|
+
integer: { type: "integer" },
|
|
304
|
+
number: { type: "number" },
|
|
305
|
+
boolean: { type: "boolean" },
|
|
306
|
+
null: { type: "null" },
|
|
307
|
+
unknown: { type: "unknown" }
|
|
308
|
+
};
|
|
309
|
+
export {
|
|
310
|
+
BodyShapeSchema,
|
|
311
|
+
BoundarySchema,
|
|
312
|
+
IntentDocSchema,
|
|
313
|
+
IntentFindingKindSchema,
|
|
314
|
+
IntentFindingSchema,
|
|
315
|
+
IntentFindingSeveritySchema,
|
|
316
|
+
IntentFindingSuppressionSchema,
|
|
317
|
+
IntentRefSchema,
|
|
318
|
+
IntentSourceSchema,
|
|
319
|
+
intentDocToSummary
|
|
320
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@suss/intent-ir",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Intent IR for suss — the team-authored side of the loop. PRD (outcome) intent and system intent, in shapes that pair against derived behavioral summaries.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"suss",
|
|
8
|
+
"intent",
|
|
9
|
+
"prd",
|
|
10
|
+
"ir"
|
|
11
|
+
],
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"author": "Nimbus Cloud AI LLC",
|
|
14
|
+
"homepage": "https://github.com/nimbuscloud-ai/suss/tree/main/packages/intent-ir#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/nimbuscloud-ai/suss.git",
|
|
18
|
+
"directory": "packages/intent-ir"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/nimbuscloud-ai/suss/issues"
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"require": "./dist/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsup",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"test:coverage": "vitest run --coverage",
|
|
42
|
+
"lint": "biome check .",
|
|
43
|
+
"lint:fix": "biome check --write .",
|
|
44
|
+
"typecheck": "tsc --noEmit"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"zod": "^4.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@suss/ir-core": "0.0.1"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"tsup": "*",
|
|
54
|
+
"typescript": "*",
|
|
55
|
+
"vitest": "*",
|
|
56
|
+
"zod": "^4.3.6"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|