@am_shork/attest 0.7.2 → 0.7.4
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 +608 -166
- package/README.md +10 -1
- package/bin/attest.js +0 -0
- package/dist/cli/json.d.ts +3 -2
- package/dist/cli/json.js +3 -2
- package/dist/core/compiler.d.ts +32 -0
- package/dist/core/compiler.js +78 -0
- package/dist/core/docs.d.ts +1 -1
- package/dist/core/docs.js +2 -0
- package/dist/core/loader.js +63 -33
- package/dist/core/locate.d.ts +22 -2
- package/dist/core/locate.js +29 -4
- package/dist/core/parser.d.ts +12 -0
- package/dist/core/parser.js +18 -2
- package/dist/core/paths.d.ts +16 -0
- package/dist/core/paths.js +20 -1
- package/dist/core/pipeline.js +72 -9
- package/dist/core/render.js +190 -21
- package/dist/core/skill.js +19 -0
- package/dist/core/static-registry.d.ts +34 -2
- package/dist/core/static-registry.js +133 -8
- package/dist/core/validator.d.ts +9 -4
- package/dist/core/validator.js +74 -21
- package/package.json +17 -29
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// value is fixed by the source text — a second walker would be a second answer
|
|
21
21
|
// to "is this a literal", and those two answers must not be able to disagree.
|
|
22
22
|
import ts from 'typescript';
|
|
23
|
+
import { parseSource } from './compiler.js';
|
|
23
24
|
import { RegistryValidationError, withProposedRequirements } from './registry.js';
|
|
24
25
|
import { RegistrySchema } from './schema.js';
|
|
25
26
|
/** The authoring function a registry file must default-export the result of. */
|
|
@@ -45,7 +46,20 @@ class NotStatic extends Error {
|
|
|
45
46
|
* has to be run — and only the prose around it differs.
|
|
46
47
|
*/
|
|
47
48
|
function extractLiteralExport(file, source, fn, noun) {
|
|
48
|
-
|
|
49
|
+
// Strict: a source that does not compile is refused here rather than read out
|
|
50
|
+
// of whatever the parser recovered from it. Before this, a registry truncated
|
|
51
|
+
// before its closing `});` came back as a complete registry.
|
|
52
|
+
const parsed = parseSource(file, source);
|
|
53
|
+
if ('error' in parsed) {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
reason: 'syntax',
|
|
57
|
+
message: `This ${noun} does not compile (${parsed.error.message}), so nothing in it was checked. ` +
|
|
58
|
+
`Fix the syntax error and run again.`,
|
|
59
|
+
...(parsed.error.line === undefined ? {} : { line: parsed.error.line }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const sf = parsed.sf;
|
|
49
63
|
const lineOf = (node) => sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
50
64
|
const exported = defaultExportExpression(sf);
|
|
51
65
|
if (!exported) {
|
|
@@ -91,10 +105,25 @@ function failure(extracted, code) {
|
|
|
91
105
|
...(extracted.line === undefined ? {} : { line: extracted.line }),
|
|
92
106
|
};
|
|
93
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* What this reader calls the three ways extraction fails; `DELTA_CODE` below is
|
|
110
|
+
* the other half.
|
|
111
|
+
*
|
|
112
|
+
* A table per reader rather than a ternary in each, because only one row
|
|
113
|
+
* differs in kind: `syntax` is the same answer for both — a file that does not
|
|
114
|
+
* compile is not a registry problem or a delta problem, it is not a file — and
|
|
115
|
+
* two readers spelling that differently is exactly the disagreement this module
|
|
116
|
+
* opens by refusing to allow.
|
|
117
|
+
*/
|
|
118
|
+
const REGISTRY_CODE = {
|
|
119
|
+
syntax: 'unreadable-file',
|
|
120
|
+
'no-default': 'registry-no-default',
|
|
121
|
+
'not-static': 'registry-not-static',
|
|
122
|
+
};
|
|
94
123
|
export function readRegistrySource(file, source) {
|
|
95
124
|
const extracted = extractLiteralExport(file, source, DEFINE, 'registry');
|
|
96
125
|
if (!extracted.ok) {
|
|
97
|
-
return failure(extracted, extracted.reason
|
|
126
|
+
return failure(extracted, REGISTRY_CODE[extracted.reason]);
|
|
98
127
|
}
|
|
99
128
|
const result = RegistrySchema.safeParse(extracted.value);
|
|
100
129
|
if (!result.success) {
|
|
@@ -109,6 +138,12 @@ export function readRegistrySource(file, source) {
|
|
|
109
138
|
}
|
|
110
139
|
return { ok: true, registry: result.data };
|
|
111
140
|
}
|
|
141
|
+
/** The delta reader's half of the table above `readRegistrySource`. */
|
|
142
|
+
const DELTA_CODE = {
|
|
143
|
+
syntax: 'unreadable-file',
|
|
144
|
+
'no-default': 'change-not-found',
|
|
145
|
+
'not-static': 'registry-not-static',
|
|
146
|
+
};
|
|
112
147
|
/**
|
|
113
148
|
* Read a change's `delta({ … })` from its source (design §7).
|
|
114
149
|
*
|
|
@@ -127,7 +162,7 @@ export function readRegistrySource(file, source) {
|
|
|
127
162
|
export function readDeltaSource(file, source) {
|
|
128
163
|
const extracted = extractLiteralExport(file, source, DELTA, 'delta');
|
|
129
164
|
if (!extracted.ok) {
|
|
130
|
-
return failure(extracted, extracted.reason
|
|
165
|
+
return failure(extracted, DELTA_CODE[extracted.reason]);
|
|
131
166
|
}
|
|
132
167
|
const value = extracted.value;
|
|
133
168
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
@@ -148,6 +183,78 @@ export function readDeltaSource(file, source) {
|
|
|
148
183
|
// thing that would notice, and it should have nothing to notice.
|
|
149
184
|
return { ok: true, delta: withProposedRequirements(value) };
|
|
150
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* The requirement ids a source *declares*, read from a file that could not be
|
|
188
|
+
* read as a registry (design §5.3).
|
|
189
|
+
*
|
|
190
|
+
* This is not a second registry reader and cannot become one: it returns ids and
|
|
191
|
+
* nothing else, it validates none of them, and no command builds a `Registry`
|
|
192
|
+
* from what it finds. What it exists for is the question `orphan-test` cannot
|
|
193
|
+
* otherwise answer — is this scenario attesting an id that a *broken* file
|
|
194
|
+
* declares, or one that genuinely does not exist — and the answer decides
|
|
195
|
+
* whether a finding is a fact or fallout.
|
|
196
|
+
*
|
|
197
|
+
* It reads the ids rather than the file's id *prefix*, which is what this was
|
|
198
|
+
* first framed as needing. The prefix cannot be recovered from a path: this
|
|
199
|
+
* repository's own registry is `attest.reqs.ts` holding `ATX-*`, which is the
|
|
200
|
+
* evidence that killed the prefix-matches-filename rule (see CHANGELOG,
|
|
201
|
+
* `Considered and rejected`). The ids are in the source, so nothing has to be
|
|
202
|
+
* inferred from a naming convention that nothing enforces.
|
|
203
|
+
*
|
|
204
|
+
* Recall is partial by construction, and the direction of the miss is the point:
|
|
205
|
+
* an id it does not find keeps its `orphan-test`, so the report stays noisy —
|
|
206
|
+
* never wrong. Measured over the failure modes that produce an unreadable
|
|
207
|
+
* registry: a truncated file, a missing default export, a non-literal *value*, a
|
|
208
|
+
* schema-invalid entry, a module that throws at import, and a spread of ids from
|
|
209
|
+
* another module all yield the ids written in this file; only a registry built
|
|
210
|
+
* by a call — `export default buildReqs()` — yields none, and that file contains
|
|
211
|
+
* no id to find.
|
|
212
|
+
*
|
|
213
|
+
* `ts.createSourceFile` is deliberately error-tolerant, which is what lets the
|
|
214
|
+
* first of those cases work at all: the parser recovers an object literal from a
|
|
215
|
+
* file that does not compile.
|
|
216
|
+
*/
|
|
217
|
+
export function declaredIdsFromSource(file, source) {
|
|
218
|
+
const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
|
219
|
+
const names = localNames(sf, DEFINE);
|
|
220
|
+
const ids = new Set();
|
|
221
|
+
// Any `defineRequirements({ … })` in the file, not only the exported one: the
|
|
222
|
+
// failure being diagnosed is often that the call is not where it should be —
|
|
223
|
+
// `export const registry = …` is a whole failure mode — and the ids are no
|
|
224
|
+
// less declared for it. Scoped to that call's argument all the same, so an
|
|
225
|
+
// id-shaped key in unrelated data is not mistaken for a requirement.
|
|
226
|
+
const visit = (node) => {
|
|
227
|
+
// `arguments.length > 0` rather than `=== 1`, which is what the reader
|
|
228
|
+
// demands: this runs on files the reader has already refused, so being
|
|
229
|
+
// stricter than it here could only lose ids it is not deciding anything by.
|
|
230
|
+
if (ts.isCallExpression(node) && node.arguments.length > 0 && callsAuthoringFn(node, names, DEFINE)) {
|
|
231
|
+
const arg = unwrap(node.arguments[0]);
|
|
232
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
233
|
+
for (const prop of arg.properties) {
|
|
234
|
+
const name = prop.name;
|
|
235
|
+
if (!name)
|
|
236
|
+
continue;
|
|
237
|
+
if (!ts.isStringLiteral(name) && !ts.isIdentifier(name))
|
|
238
|
+
continue;
|
|
239
|
+
if (REQUIREMENT_ID.test(name.text))
|
|
240
|
+
ids.add(name.text);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
ts.forEachChild(node, visit);
|
|
245
|
+
};
|
|
246
|
+
visit(sf);
|
|
247
|
+
return [...ids];
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* The id shape, as a bare regex rather than through `RequirementIdSchema`.
|
|
251
|
+
*
|
|
252
|
+
* Nothing is being validated here — a key that does not look like an id is
|
|
253
|
+
* simply not one, and there is no author to tell. Kept identical to the schema's
|
|
254
|
+
* pattern by `tests/static-registry.spec.ts`, which is the only place the two
|
|
255
|
+
* spellings can be held together.
|
|
256
|
+
*/
|
|
257
|
+
const REQUIREMENT_ID = /^[A-Z]+-\d+$/;
|
|
151
258
|
/**
|
|
152
259
|
* The expression a file default-exports, following one level of local binding.
|
|
153
260
|
*
|
|
@@ -191,10 +298,20 @@ function constInitializer(sf, name) {
|
|
|
191
298
|
function authoringCall(expr, sf, fn) {
|
|
192
299
|
if (!ts.isCallExpression(expr) || expr.arguments.length !== 1)
|
|
193
300
|
return undefined;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
301
|
+
return callsAuthoringFn(expr, localNames(sf, fn), fn) ? expr.arguments[0] : undefined;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Whether a call is a call of `fn`, by the local names the imports bound to it.
|
|
305
|
+
*
|
|
306
|
+
* One predicate for both readers of this question — the extraction above and the
|
|
307
|
+
* id recovery below — because they must not be able to disagree about what
|
|
308
|
+
* counts as the authoring call. An alias rule taught to one and not the other
|
|
309
|
+
* would make recovery silently miss exactly the files the reader refuses.
|
|
310
|
+
*/
|
|
311
|
+
function callsAuthoringFn(call, names, fn) {
|
|
312
|
+
const callee = call.expression;
|
|
313
|
+
return ((ts.isIdentifier(callee) && names.has(callee.text)) ||
|
|
314
|
+
(ts.isPropertyAccessExpression(callee) && callee.name.text === fn));
|
|
198
315
|
}
|
|
199
316
|
/** Local names bound to the imported `fn`, including aliases. */
|
|
200
317
|
function localNames(sf, fn) {
|
|
@@ -329,7 +446,15 @@ function numericValue(node) {
|
|
|
329
446
|
return value;
|
|
330
447
|
}
|
|
331
448
|
export function registryInsertionPoint(file, source) {
|
|
332
|
-
|
|
449
|
+
// The write site, and the one where a recovered AST does damage rather than
|
|
450
|
+
// merely misreports: an offset taken from a file that does not compile would
|
|
451
|
+
// splice a new requirement into it. `undefined` is this function's documented
|
|
452
|
+
// refusal to guess, and a source that will not parse is the clearest case of
|
|
453
|
+
// one there is.
|
|
454
|
+
const parsed = parseSource(file, source);
|
|
455
|
+
if ('error' in parsed)
|
|
456
|
+
return undefined;
|
|
457
|
+
const sf = parsed.sf;
|
|
333
458
|
const exported = defaultExportExpression(sf);
|
|
334
459
|
if (!exported)
|
|
335
460
|
return undefined;
|
package/dist/core/validator.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { UnreadableRegistry } from './locate.js';
|
|
1
2
|
import type { AttestPlan, Issue, ParamRef, Registry } from './types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Uncovered requirements: intent exists but no scenario attests it (design §5.3).
|
|
@@ -20,11 +21,15 @@ export declare function uncoveredIssues(registry: Registry, plan: AttestPlan): I
|
|
|
20
21
|
* and one WARNING:
|
|
21
22
|
* - rationale-placeholder: a `{name}` in a rationale, which is never interpolated
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* and one more WARNING, which exists only when a registry file failed to load:
|
|
25
|
+
* - orphan-from-failed-registry: the scenarios attesting ids that file declares
|
|
26
|
+
*
|
|
27
|
+
* `unreadable` is the `*.reqs.ts` files that failed to load, each with the ids
|
|
28
|
+
* its source still names (see `loadRegistry`). It changes no verdict — only
|
|
29
|
+
* which findings are stated per scenario and which are stated once, for the
|
|
30
|
+
* reason below.
|
|
26
31
|
*/
|
|
27
|
-
export declare function validateStructure(registry: Registry, plan: AttestPlan,
|
|
32
|
+
export declare function validateStructure(registry: Registry, plan: AttestPlan, unreadable?: readonly UnreadableRegistry[]): Issue[];
|
|
28
33
|
/**
|
|
29
34
|
* Weak anti-drift heuristic (design §6, mechanism 3). Evaluated per requirement,
|
|
30
35
|
* not per scenario: a requirement that owns params is quiet as soon as *any* one
|
package/dist/core/validator.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Structural validation + anti-drift heuristic (design §5.3, §6).
|
|
2
2
|
// Graded reporting: ERROR / WARNING / INFO, each with file + line + fix hint.
|
|
3
|
+
import { byCodeUnit } from './order.js';
|
|
3
4
|
/**
|
|
4
5
|
* Uncovered requirements: intent exists but no scenario attests it (design §5.3).
|
|
5
6
|
*
|
|
@@ -35,11 +36,15 @@ export function uncoveredIssues(registry, plan) {
|
|
|
35
36
|
* and one WARNING:
|
|
36
37
|
* - rationale-placeholder: a `{name}` in a rationale, which is never interpolated
|
|
37
38
|
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* and one more WARNING, which exists only when a registry file failed to load:
|
|
40
|
+
* - orphan-from-failed-registry: the scenarios attesting ids that file declares
|
|
41
|
+
*
|
|
42
|
+
* `unreadable` is the `*.reqs.ts` files that failed to load, each with the ids
|
|
43
|
+
* its source still names (see `loadRegistry`). It changes no verdict — only
|
|
44
|
+
* which findings are stated per scenario and which are stated once, for the
|
|
45
|
+
* reason below.
|
|
41
46
|
*/
|
|
42
|
-
export function validateStructure(registry, plan,
|
|
47
|
+
export function validateStructure(registry, plan, unreadable = []) {
|
|
43
48
|
const issues = [];
|
|
44
49
|
const knownIds = new Set(Object.keys(registry));
|
|
45
50
|
// orphan-test: covers a requirement that does not exist.
|
|
@@ -50,27 +55,75 @@ export function validateStructure(registry, plan, registryIncomplete = false) {
|
|
|
50
55
|
// following the hint would add a duplicate. That case is not rare when it
|
|
51
56
|
// happens — one unreadable `*.reqs.ts` orphans every scenario of every
|
|
52
57
|
// requirement it declared, so the wrong advice is also the loudest thing in
|
|
53
|
-
// the report.
|
|
58
|
+
// the report. Measured on one broken registry of three requirements: six
|
|
59
|
+
// ERRORs, of which one was the cause and five were its shadow.
|
|
60
|
+
//
|
|
61
|
+
// Which of the two a given orphan is *can* be decided here, because
|
|
62
|
+
// `loadRegistry` brings back the ids the broken file's source still names. So
|
|
63
|
+
// the scenarios attesting those ids are not reported one by one: they are
|
|
64
|
+
// fallout from a finding already in the report, and the fallout is stated once
|
|
65
|
+
// per file that caused it. That is the same move `duplicate-prefix` makes —
|
|
66
|
+
// the fact is about the file, not about each requirement that reveals it — and
|
|
67
|
+
// the same order ATX-62 records for `spec-load-failed`: the replacement comes
|
|
68
|
+
// first, the withdrawal follows it, because suppressing with nothing in its
|
|
69
|
+
// place trades a wrong message for silence.
|
|
54
70
|
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
const
|
|
61
|
-
|
|
71
|
+
// Nothing is lost by collapsing them. The replacement names the file, the
|
|
72
|
+
// count and the ids, so a reader can still see which scenarios are affected;
|
|
73
|
+
// the load failure is already an ERROR, so the verdict cannot move; and the
|
|
74
|
+
// scenarios themselves are not defective — they will be verified normally the
|
|
75
|
+
// moment the registry loads.
|
|
76
|
+
const swallowed = new Map(unreadable.map((u) => [u.file, []]));
|
|
77
|
+
const ownerOf = new Map();
|
|
78
|
+
for (const u of unreadable)
|
|
79
|
+
for (const id of u.ids)
|
|
80
|
+
ownerOf.set(id, u.file);
|
|
81
|
+
// An unreadable file whose ids could not be recovered leaves the question open
|
|
82
|
+
// for every orphan that is not claimed by another one, and the hedged advice
|
|
83
|
+
// is what an open question sounds like. When every unreadable file gave up its
|
|
84
|
+
// ids, an orphan none of them claims is genuinely unknown — and gets the plain
|
|
85
|
+
// advice it deserves, which the hedge had been withdrawing from correct
|
|
86
|
+
// findings too.
|
|
87
|
+
const someFileOpaque = unreadable.some((u) => u.ids.length === 0);
|
|
88
|
+
const orphanFix = (id) => someFileOpaque
|
|
89
|
+
? `A registry file failed to load and its ids could not be read, so "${id}" may be one of them — fix that first, and add it only if it is still unknown afterwards.`
|
|
62
90
|
: `Add it to the registry, or fix the id.`;
|
|
63
91
|
for (const s of plan.scenarios) {
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
line: s.line,
|
|
71
|
-
message: `scenario "${s.name}" attests unknown requirement "${s.reqId}". ${orphanFix(s.reqId)}`,
|
|
72
|
-
});
|
|
92
|
+
if (knownIds.has(s.reqId))
|
|
93
|
+
continue;
|
|
94
|
+
const owner = ownerOf.get(s.reqId);
|
|
95
|
+
if (owner !== undefined) {
|
|
96
|
+
swallowed.get(owner).push(s.reqId);
|
|
97
|
+
continue;
|
|
73
98
|
}
|
|
99
|
+
issues.push({
|
|
100
|
+
level: 'ERROR',
|
|
101
|
+
code: 'orphan-test',
|
|
102
|
+
reqId: s.reqId,
|
|
103
|
+
file: s.file,
|
|
104
|
+
line: s.line,
|
|
105
|
+
message: `scenario "${s.name}" attests unknown requirement "${s.reqId}". ${orphanFix(s.reqId)}`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// WARNING, not ERROR: the defect is the load failure, which is an ERROR of its
|
|
109
|
+
// own and always present when this is. Reporting it a second time at the same
|
|
110
|
+
// level would say a broken registry is two problems.
|
|
111
|
+
//
|
|
112
|
+
// No `reqId`: the finding is about a file and the set of ids it took down with
|
|
113
|
+
// it, and no single requirement is implicated — the same reason
|
|
114
|
+
// `duplicate-prefix` and `spec-load-failed` carry none.
|
|
115
|
+
for (const u of unreadable) {
|
|
116
|
+
const ids = swallowed.get(u.file);
|
|
117
|
+
if (ids.length === 0)
|
|
118
|
+
continue;
|
|
119
|
+
const unique = [...new Set(ids)].sort(byCodeUnit);
|
|
120
|
+
issues.push({
|
|
121
|
+
level: 'WARNING',
|
|
122
|
+
code: 'orphan-from-failed-registry',
|
|
123
|
+
file: u.file,
|
|
124
|
+
message: `${ids.length} ${ids.length === 1 ? 'scenario attests' : 'scenarios attest'} ids ${u.file} declares (${unique.join(', ')}), and it failed to load — so those ids are missing from this run. ` +
|
|
125
|
+
`They are not orphans: fix the load failure reported above and they will be verified as usual.`,
|
|
126
|
+
});
|
|
74
127
|
}
|
|
75
128
|
// uncovered-requirement: intent exists but no scenario attests it. Shared
|
|
76
129
|
// with `cover`, so the two commands cannot drift apart.
|
package/package.json
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@am_shork/attest",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.4",
|
|
4
4
|
"description": "TDD-native spec framework: tests are the source of truth for verification, ID-bound requirements the source of truth for intent.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"packageManager": "pnpm@10.28.0",
|
|
7
6
|
"engines": {
|
|
8
7
|
"node": ">=20.19"
|
|
9
8
|
},
|
|
@@ -37,24 +36,6 @@
|
|
|
37
36
|
"publishConfig": {
|
|
38
37
|
"access": "public"
|
|
39
38
|
},
|
|
40
|
-
"scripts": {
|
|
41
|
-
"clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
|
|
42
|
-
"build": "pnpm run clean && tsc -p tsconfig.json",
|
|
43
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
44
|
-
"typecheck:all": "tsc -p tsconfig.typecheck.json",
|
|
45
|
-
"test": "vitest run",
|
|
46
|
-
"test:watch": "vitest",
|
|
47
|
-
"test:consumer": "pnpm run build && vitest run --config vitest.consumer.config.ts",
|
|
48
|
-
"lint": "eslint .",
|
|
49
|
-
"prepack": "pnpm run build",
|
|
50
|
-
"prepublishOnly": "pnpm run test:consumer",
|
|
51
|
-
"attest": "node bin/attest.js",
|
|
52
|
-
"check:self": "node bin/attest.js check self",
|
|
53
|
-
"verify:self": "node bin/attest.js verify self",
|
|
54
|
-
"cover:self": "node bin/attest.js cover self",
|
|
55
|
-
"render:self": "node bin/attest.js render self --out self/requirements/SPEC.md",
|
|
56
|
-
"render:self:check": "node bin/attest.js render self --out self/requirements/SPEC.md --check"
|
|
57
|
-
},
|
|
58
39
|
"keywords": [
|
|
59
40
|
"tdd",
|
|
60
41
|
"spec",
|
|
@@ -81,13 +62,20 @@
|
|
|
81
62
|
"vite": "^8.1.5",
|
|
82
63
|
"vitest": "^4.1.10"
|
|
83
64
|
},
|
|
84
|
-
"
|
|
85
|
-
"
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
"
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
65
|
+
"scripts": {
|
|
66
|
+
"clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
|
|
67
|
+
"build": "pnpm run clean && tsc -p tsconfig.json",
|
|
68
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
69
|
+
"typecheck:all": "tsc -p tsconfig.typecheck.json",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:watch": "vitest",
|
|
72
|
+
"test:consumer": "pnpm run build && vitest run --config vitest.consumer.config.ts",
|
|
73
|
+
"lint": "eslint .",
|
|
74
|
+
"attest": "node bin/attest.js",
|
|
75
|
+
"check:self": "node bin/attest.js check self",
|
|
76
|
+
"verify:self": "node bin/attest.js verify self",
|
|
77
|
+
"cover:self": "node bin/attest.js cover self",
|
|
78
|
+
"render:self": "node bin/attest.js render self --out self/requirements/SPEC.md",
|
|
79
|
+
"render:self:check": "node bin/attest.js render self --out self/requirements/SPEC.md --check"
|
|
92
80
|
}
|
|
93
|
-
}
|
|
81
|
+
}
|