@am_shork/attest 0.7.0 → 0.7.2
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 +562 -112
- package/README.md +4 -4
- package/bin/attest.js +0 -0
- package/dist/cli/action.d.ts +48 -0
- package/dist/cli/action.js +100 -0
- package/dist/cli/index.js +11 -32
- package/dist/cli/report.js +9 -1
- package/dist/core/apply.js +7 -10
- package/dist/core/docs.d.ts +1 -1
- package/dist/core/docs.js +2 -0
- package/dist/core/locate.d.ts +9 -10
- package/dist/core/locate.js +58 -15
- package/dist/core/merge.js +27 -2
- package/dist/core/order.d.ts +17 -0
- package/dist/core/order.js +25 -0
- package/dist/core/pipeline.js +34 -14
- package/dist/core/render.js +90 -50
- package/dist/core/runner.js +4 -6
- package/dist/core/schema.d.ts +13 -6
- package/dist/core/schema.js +54 -18
- package/dist/core/splice.d.ts +13 -12
- package/dist/core/splice.js +59 -18
- package/dist/core/static-registry.js +6 -0
- package/dist/core/status.js +4 -9
- package/dist/core/terminal.d.ts +9 -23
- package/dist/core/terminal.js +9 -23
- package/dist/core/types.d.ts +1 -1
- package/dist/core/validator.d.ts +1 -0
- package/dist/core/validator.js +37 -0
- package/package.json +29 -17
package/dist/core/status.js
CHANGED
|
@@ -31,16 +31,11 @@ import { hasRecordedRed, recordedOutcome } from './red-record.js';
|
|
|
31
31
|
export function statusRows(addedIds, plan, firstRun) {
|
|
32
32
|
// Index the scenarios once, the way `validator.ts`'s `detectPotentialDrift`
|
|
33
33
|
// does and for the reason recorded there: filtering the whole plan per
|
|
34
|
-
// requirement is O(requirements x scenarios)
|
|
35
|
-
// one, so the inner term is the whole repository's scenario count
|
|
36
|
-
// outer is only what this change adds.
|
|
34
|
+
// requirement is O(requirements x scenarios), and the plan here is the
|
|
35
|
+
// *merged* one, so the inner term is the whole repository's scenario count.
|
|
37
36
|
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
// inconsistency it is rather than as a slow path. Two functions over one
|
|
41
|
-
// shape held two beliefs about whether it is worth indexing; they hold one
|
|
42
|
-
// now. Insertion order is plan order, which is file then line, so the
|
|
43
|
-
// scenarios a row carries are still in the order their author reads them.
|
|
37
|
+
// Insertion order is plan order, which is file then line, so the scenarios a
|
|
38
|
+
// row carries stay in the order their author reads them.
|
|
44
39
|
const byReqId = new Map();
|
|
45
40
|
for (const s of plan.scenarios) {
|
|
46
41
|
const group = byReqId.get(s.reqId);
|
package/dist/core/terminal.d.ts
CHANGED
|
@@ -16,32 +16,18 @@ export declare function inline(text: string): string;
|
|
|
16
16
|
/**
|
|
17
17
|
* Every C0 control except the newline, plus DEL and the C1 range, as a space.
|
|
18
18
|
*
|
|
19
|
-
* A space rather than deletion
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* A space rather than deletion, and the newline exempt by not being in the
|
|
20
|
+
* class: both are design §9.1, stated there because they hold for every artifact
|
|
21
|
+
* Attest writes rather than only for this one.
|
|
22
22
|
*
|
|
23
|
-
* The
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* none when nothing matches, which is the case every real registry is. Measured
|
|
27
|
-
* over 120,000 characters: **27x** faster on plain ASCII, **54x** with
|
|
28
|
-
* newlines, **84x** on CJK. A payload that is *entirely* control bytes is a
|
|
29
|
-
* wash (0.9x), because then there is nothing to fast-path and both spellings
|
|
30
|
-
* build a new string; both are linear either way, so this is a constant factor
|
|
31
|
-
* rather than a second ATX-59.
|
|
23
|
+
* The one thing §9.1 does not reach: surrogates `D800`–`DFFF` fall outside every
|
|
24
|
+
* range below, so a pair is never touched and never split — the case that makes
|
|
25
|
+
* a per-code-point rewrite look necessary when it is not.
|
|
32
26
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
27
|
+
* Written out rather than computed per character so the scan allocates nothing
|
|
28
|
+
* when nothing matches, which is what every real registry is; `render` calls
|
|
35
29
|
* this once per statement, rationale, param key, param value and out-of-scope
|
|
36
|
-
* entry, on the
|
|
37
|
-
*
|
|
38
|
-
* **Byte-identical to the spelling it replaces**, which is the only thing that
|
|
39
|
-
* mattered: checked exhaustively over every code unit in the BMP, and over
|
|
40
|
-
* 200,000 randomised strings mixing control bytes, CJK, astral characters and
|
|
41
|
-
* lone surrogates. Surrogates are the case the spread existed to get right —
|
|
42
|
-
* `D800`–`DFFF` fall outside every range below, so neither spelling touches a
|
|
43
|
-
* pair or splits one — and newlines survive here as they always did, by not
|
|
44
|
-
* being in the class.
|
|
30
|
+
* entry, on the `render --check` path ATX-59 came off. Measured in `[0.7.0]`.
|
|
45
31
|
*/
|
|
46
32
|
export declare function control(text: string): string;
|
|
47
33
|
//# sourceMappingURL=terminal.d.ts.map
|
package/dist/core/terminal.js
CHANGED
|
@@ -41,32 +41,18 @@ export function inline(text) {
|
|
|
41
41
|
/**
|
|
42
42
|
* Every C0 control except the newline, plus DEL and the C1 range, as a space.
|
|
43
43
|
*
|
|
44
|
-
* A space rather than deletion
|
|
45
|
-
*
|
|
46
|
-
*
|
|
44
|
+
* A space rather than deletion, and the newline exempt by not being in the
|
|
45
|
+
* class: both are design §9.1, stated there because they hold for every artifact
|
|
46
|
+
* Attest writes rather than only for this one.
|
|
47
47
|
*
|
|
48
|
-
* The
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* none when nothing matches, which is the case every real registry is. Measured
|
|
52
|
-
* over 120,000 characters: **27x** faster on plain ASCII, **54x** with
|
|
53
|
-
* newlines, **84x** on CJK. A payload that is *entirely* control bytes is a
|
|
54
|
-
* wash (0.9x), because then there is nothing to fast-path and both spellings
|
|
55
|
-
* build a new string; both are linear either way, so this is a constant factor
|
|
56
|
-
* rather than a second ATX-59.
|
|
48
|
+
* The one thing §9.1 does not reach: surrogates `D800`–`DFFF` fall outside every
|
|
49
|
+
* range below, so a pair is never touched and never split — the case that makes
|
|
50
|
+
* a per-code-point rewrite look necessary when it is not.
|
|
57
51
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
52
|
+
* Written out rather than computed per character so the scan allocates nothing
|
|
53
|
+
* when nothing matches, which is what every real registry is; `render` calls
|
|
60
54
|
* this once per statement, rationale, param key, param value and out-of-scope
|
|
61
|
-
* entry, on the
|
|
62
|
-
*
|
|
63
|
-
* **Byte-identical to the spelling it replaces**, which is the only thing that
|
|
64
|
-
* mattered: checked exhaustively over every code unit in the BMP, and over
|
|
65
|
-
* 200,000 randomised strings mixing control bytes, CJK, astral characters and
|
|
66
|
-
* lone surrogates. Surrogates are the case the spread existed to get right —
|
|
67
|
-
* `D800`–`DFFF` fall outside every range below, so neither spelling touches a
|
|
68
|
-
* pair or splits one — and newlines survive here as they always did, by not
|
|
69
|
-
* being in the class.
|
|
55
|
+
* entry, on the `render --check` path ATX-59 came off. Measured in `[0.7.0]`.
|
|
70
56
|
*/
|
|
71
57
|
export function control(text) {
|
|
72
58
|
// eslint-disable-next-line no-control-regex -- matching control characters is the whole function.
|
package/dist/core/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type { Requirement, Registry } from './schema.js';
|
|
1
|
+
export type { Requirement, Registry, ParamValue } from './schema.js';
|
|
2
2
|
export type { IssueCode } from './docs.js';
|
|
3
3
|
import type { IssueCode } from './docs.js';
|
|
4
4
|
/** A scenario extracted statically from a spec file (design §5.2). */
|
package/dist/core/validator.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare function uncoveredIssues(registry: Registry, plan: AttestPlan): I
|
|
|
15
15
|
* - orphan-test: a scenario covers an unknown requirement id
|
|
16
16
|
* - uncovered-requirement: a requirement has no scenario
|
|
17
17
|
* - unbound-param: a statement placeholder has no matching param
|
|
18
|
+
* - non-scalar-interpolation: a statement placeholder names a structured param
|
|
18
19
|
*
|
|
19
20
|
* and one WARNING:
|
|
20
21
|
* - rationale-placeholder: a `{name}` in a rationale, which is never interpolated
|
package/dist/core/validator.js
CHANGED
|
@@ -30,6 +30,7 @@ export function uncoveredIssues(registry, plan) {
|
|
|
30
30
|
* - orphan-test: a scenario covers an unknown requirement id
|
|
31
31
|
* - uncovered-requirement: a requirement has no scenario
|
|
32
32
|
* - unbound-param: a statement placeholder has no matching param
|
|
33
|
+
* - non-scalar-interpolation: a statement placeholder names a structured param
|
|
33
34
|
*
|
|
34
35
|
* and one WARNING:
|
|
35
36
|
* - rationale-placeholder: a `{name}` in a rationale, which is never interpolated
|
|
@@ -78,6 +79,14 @@ export function validateStructure(registry, plan, registryIncomplete = false) {
|
|
|
78
79
|
// `Object.hasOwn`, never `in`: `'toString' in {}` is true, so an `in` probe
|
|
79
80
|
// silently accepts placeholders no author declared — and render.ts would then
|
|
80
81
|
// interpolate the prototype method into the document.
|
|
82
|
+
//
|
|
83
|
+
// non-scalar-interpolation: the placeholder resolves, but to a value that has
|
|
84
|
+
// no reading as a sentence. This is what the schema's old scalar-or-list union
|
|
85
|
+
// was actually defending — `[object Object]` in the rendered requirement
|
|
86
|
+
// document — and it is stated here because that is where the damage is. A
|
|
87
|
+
// param the statement never names can be any shape it likes: a kind -> payload
|
|
88
|
+
// table the scenario reads is not a rendering problem, and refusing it in the
|
|
89
|
+
// schema refused the drift-prone value along with the display bug.
|
|
81
90
|
for (const [id, req] of Object.entries(registry)) {
|
|
82
91
|
for (const m of req.statement.matchAll(/\{(\w+)\}/g)) {
|
|
83
92
|
const name = m[1];
|
|
@@ -88,6 +97,16 @@ export function validateStructure(registry, plan, registryIncomplete = false) {
|
|
|
88
97
|
reqId: id,
|
|
89
98
|
message: `Requirement "${id}" uses {${name}} in its statement, but params does not define it.`,
|
|
90
99
|
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (!interpolatable(req.params[name])) {
|
|
103
|
+
issues.push({
|
|
104
|
+
level: 'ERROR',
|
|
105
|
+
code: 'non-scalar-interpolation',
|
|
106
|
+
reqId: id,
|
|
107
|
+
message: `Requirement "${id}" uses {${name}} in its statement, but that param is ${shapeOf(req.params[name])}. ` +
|
|
108
|
+
`A statement placeholder must be a scalar or a list of scalars.`,
|
|
109
|
+
});
|
|
91
110
|
}
|
|
92
111
|
}
|
|
93
112
|
}
|
|
@@ -156,4 +175,22 @@ export function detectPotentialDrift(registry, plan, refs) {
|
|
|
156
175
|
}
|
|
157
176
|
return issues;
|
|
158
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Whether a param has a reading as a run of words inside a sentence.
|
|
180
|
+
*
|
|
181
|
+
* A scalar does; a list of scalars does, as a comma-separated series. Anything
|
|
182
|
+
* deeper does not — `render` would have to choose a punctuation for structure,
|
|
183
|
+
* and every choice is a way for the document to say something the registry did
|
|
184
|
+
* not. `null` is a scalar here: it renders as `null`, which is the value.
|
|
185
|
+
*/
|
|
186
|
+
function interpolatable(value) {
|
|
187
|
+
const isScalar = (v) => v === null || typeof v !== 'object';
|
|
188
|
+
return Array.isArray(value) ? value.every(isScalar) : isScalar(value);
|
|
189
|
+
}
|
|
190
|
+
/** How to name the offending shape in the diagnostic, in the author's terms. */
|
|
191
|
+
function shapeOf(value) {
|
|
192
|
+
if (Array.isArray(value))
|
|
193
|
+
return 'a list with a structured element';
|
|
194
|
+
return 'an object';
|
|
195
|
+
}
|
|
159
196
|
//# sourceMappingURL=validator.js.map
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@am_shork/attest",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.2",
|
|
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",
|
|
6
7
|
"engines": {
|
|
7
8
|
"node": ">=20.19"
|
|
8
9
|
},
|
|
@@ -36,6 +37,24 @@
|
|
|
36
37
|
"publishConfig": {
|
|
37
38
|
"access": "public"
|
|
38
39
|
},
|
|
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
|
+
},
|
|
39
58
|
"keywords": [
|
|
40
59
|
"tdd",
|
|
41
60
|
"spec",
|
|
@@ -62,20 +81,13 @@
|
|
|
62
81
|
"vite": "^8.1.5",
|
|
63
82
|
"vitest": "^4.1.10"
|
|
64
83
|
},
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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"
|
|
84
|
+
"pnpm": {
|
|
85
|
+
"onlyBuiltDependencies": [
|
|
86
|
+
"esbuild"
|
|
87
|
+
],
|
|
88
|
+
"overrides": {
|
|
89
|
+
"brace-expansion@<5.0.9": ">=5.0.9",
|
|
90
|
+
"js-yaml@<4.3.1": ">=4.3.1"
|
|
91
|
+
}
|
|
80
92
|
}
|
|
81
|
-
}
|
|
93
|
+
}
|