@am_shork/attest 0.7.4 → 0.9.0
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 +1449 -157
- package/README.md +3 -2
- package/dist/cli/index.js +11 -8
- package/dist/cli/json.d.ts +26 -1
- package/dist/cli/json.js +28 -2
- package/dist/cli/report.js +9 -1
- package/dist/core/apply.d.ts +18 -1
- package/dist/core/apply.js +19 -2
- package/dist/core/gate.js +3 -3
- package/dist/core/locate.d.ts +19 -4
- package/dist/core/locate.js +90 -40
- package/dist/core/merge.js +218 -72
- package/dist/core/pipeline.d.ts +17 -1
- package/dist/core/pipeline.js +149 -35
- package/dist/core/red-record.d.ts +14 -5
- package/dist/core/red-record.js +82 -24
- package/dist/core/registry-issues.d.ts +30 -0
- package/dist/core/registry-issues.js +26 -0
- package/dist/core/registry.d.ts +12 -5
- package/dist/core/registry.js +10 -8
- package/dist/core/runner.js +21 -9
- package/dist/core/schema.d.ts +32 -27
- package/dist/core/schema.js +33 -5
- package/dist/core/skill.js +101 -27
- package/dist/core/splice.d.ts +62 -3
- package/dist/core/splice.js +297 -27
- package/dist/core/static-registry.d.ts +42 -0
- package/dist/core/static-registry.js +136 -21
- package/dist/core/status.js +2 -2
- package/dist/core/terminal.js +5 -2
- package/dist/core/types.d.ts +39 -10
- package/dist/core/validator.d.ts +1 -0
- package/dist/core/validator.js +22 -0
- package/package.json +2 -2
package/dist/core/skill.js
CHANGED
|
@@ -34,9 +34,13 @@
|
|
|
34
34
|
// workflow to an agent that trusts it. The backstop is that every mistake it
|
|
35
35
|
// could cause is a diagnostic with a fix hint — a registry written the old way
|
|
36
36
|
// is `registry-not-static`, and the agent corrects itself from the report.
|
|
37
|
-
// It has happened
|
|
38
|
-
//
|
|
39
|
-
//
|
|
37
|
+
// It has happened twice, the second time on a diagnostic that was not added but
|
|
38
|
+
// *redefined* — the code table and the prose below it disagreed, and an agent
|
|
39
|
+
// branches on the table. So: read this file when a release adds a diagnostic
|
|
40
|
+
// **or changes what one means**, and grep this string for the code rather than
|
|
41
|
+
// trusting that the section you edited was its only mention. Nothing gates it —
|
|
42
|
+
// `ATX-57` catches a code the engine cannot emit, never one it can, and its
|
|
43
|
+
// rationale records that one-directionality as deliberate.
|
|
40
44
|
/**
|
|
41
45
|
* The one sentence that decides whether the workflow is ever loaded.
|
|
42
46
|
*
|
|
@@ -102,6 +106,19 @@ promises is a two-stage workflow, and the stages are separate on purpose.
|
|
|
102
106
|
rule forbids, and the distinction is the whole of it — that rule is about the
|
|
103
107
|
**expectation** the system is measured against; this pin asserts what the
|
|
104
108
|
**intent** is.
|
|
109
|
+
- **Prefer a quantifier a scenario can iterate.** A statement that says *every*,
|
|
110
|
+
*all* or *any* has a finite set of scenarios under it and nothing holding the
|
|
111
|
+
two together — the id is covered, \`check\` is green, and whether those
|
|
112
|
+
scenarios span what the sentence claims is the part no gate looks at. What
|
|
113
|
+
decides the risk is *what* the quantifier ranges over. Over the inputs one
|
|
114
|
+
place processes, representative inputs settle it. Over a list the code itself
|
|
115
|
+
enumerates, a scenario looping that same list covers a new member by
|
|
116
|
+
construction — **this is the shape to write**, and when that list lives in
|
|
117
|
+
\`params\` rather than in the code, the pin above is what keeps it honest, since
|
|
118
|
+
an author can shorten it. Over *places in the implementation*, each needing its
|
|
119
|
+
own hand-written scenario, a new place is a new obligation and nothing
|
|
120
|
+
enumerates them: that is the one that silently stops being true. When you cannot avoid it, name the places in the statement instead
|
|
121
|
+
of quantifying over them, so a reader can count what is missing.
|
|
105
122
|
|
|
106
123
|
## Rules the engine enforces
|
|
107
124
|
|
|
@@ -115,11 +132,16 @@ promises is a two-stage workflow, and the stages are separate on purpose.
|
|
|
115
132
|
or it is an \`unbound-param\` ERROR. A \`{placeholder}\` written into a
|
|
116
133
|
*rationale* is a \`rationale-placeholder\` WARNING — rationales are not
|
|
117
134
|
interpolated, so it would reach the rendered document with its braces intact.
|
|
118
|
-
- **A registry is a literal.** Every value is written in the
|
|
119
|
-
constant (\`params: { maxMb: MAX_MB }\`), no computed value,
|
|
120
|
-
\`check\`, \`cover\` and \`render\` read \`*.reqs.ts\` with the
|
|
121
|
-
never execute it, so anything they cannot read is a
|
|
122
|
-
ERROR.
|
|
135
|
+
- **A registry is a literal, and so is a delta.** Every value is written in the
|
|
136
|
+
file: no imported constant (\`params: { maxMb: MAX_MB }\`), no computed value,
|
|
137
|
+
no \`Date.now()\`. \`check\`, \`cover\` and \`render\` read \`*.reqs.ts\` with the
|
|
138
|
+
compiler API and never execute it, so anything they cannot read is a
|
|
139
|
+
\`registry-not-static\` ERROR. **The same rule covers
|
|
140
|
+
\`requirements.delta.ts\`**, which \`check\` and \`status\` read the same way — a
|
|
141
|
+
\`const\` lifted out of a delta is that same error, and a delta is the more
|
|
142
|
+
exposed of the two, living in \`changes/\` — the part of the tree that is by
|
|
143
|
+
definition still under review.
|
|
144
|
+
An imported constant also breaks the single-source rule outright — the
|
|
123
145
|
value's real owner is then somewhere else. Inline it, and have the application
|
|
124
146
|
code read it from the registry.
|
|
125
147
|
- **Write prose as one long single-line string.** A template literal with no
|
|
@@ -163,13 +185,31 @@ lib/game/specs/
|
|
|
163
185
|
└── fog.proposed.spec.ts # this change's, and ../fog already resolves
|
|
164
186
|
\`\`\`
|
|
165
187
|
|
|
166
|
-
That name is the whole mechanism. It keeps the file out of \`attest verify\`
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
188
|
+
That name is the whole mechanism. It keeps the file out of \`attest verify\`
|
|
189
|
+
while the change is in flight — the scenarios are red by construction — and
|
|
190
|
+
\`attest archive <name>\` pulls it in by the requirement ids it covers: a change
|
|
191
|
+
claims the proposed specs that declare a scenario for an id it ADDs, renames to,
|
|
192
|
+
or MODIFIEs. Nothing lists paths anywhere, so nothing can fall out of step. A
|
|
193
|
+
proposed spec no change claims is a \`proposed-spec-unclaimed\` ERROR from
|
|
194
|
+
\`attest check\`, because it would otherwise run nowhere at all.
|
|
195
|
+
|
|
196
|
+
**It does not hide the file from your own test command.** \`*.proposed.spec.ts\`
|
|
197
|
+
still ends in \`.spec.ts\`, so an ordinary \`**/*.spec.ts\` include — Vitest's own
|
|
198
|
+
default among them — picks it up, and during stage 1 those scenarios are
|
|
199
|
+
*supposed* to fail. Only Attest's scope excludes them. If a red \`npm test\` while
|
|
200
|
+
a change is in flight is a problem for you, exclude the pattern in your own
|
|
201
|
+
config; that is a decision about your suite, and Attest does not make it for you:
|
|
202
|
+
|
|
203
|
+
\`\`\`ts
|
|
204
|
+
// vitest.config.ts — optional, and only if you want your own suite green
|
|
205
|
+
import { configDefaults, defineConfig } from 'vitest/config';
|
|
206
|
+
|
|
207
|
+
export default defineConfig({
|
|
208
|
+
// Spread the defaults: a bare \`exclude\` REPLACES them, and Vitest's include
|
|
209
|
+
// reaches node_modules without them.
|
|
210
|
+
test: { exclude: [...configDefaults.exclude, '**/*.proposed.spec.ts'] },
|
|
211
|
+
});
|
|
212
|
+
\`\`\`
|
|
173
213
|
|
|
174
214
|
Writing it at its merged location is what makes merging it a rename. Its relative
|
|
175
215
|
imports resolve now exactly as they will afterwards, so \`../fog\` never becomes
|
|
@@ -266,11 +306,17 @@ To see where the change stands at any point, without paying for a run:
|
|
|
266
306
|
attest status <name> --json # per added requirement: no-scenario / unproven / proven
|
|
267
307
|
\`\`\`
|
|
268
308
|
|
|
269
|
-
It reads the delta, the specs and \`first-run.json\`, and reports
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
309
|
+
It reads the delta, the specs and \`first-run.json\`, and reports **two** of the
|
|
310
|
+
gate's obligations, for the ids this change ADDs only: does each have a scenario,
|
|
311
|
+
and has each scenario been recorded failing. A change in flight is \`ok: true\`
|
|
312
|
+
with rows to work through — this command never fails on unmet obligations, so do
|
|
313
|
+
not read a clean table as "done".
|
|
314
|
+
|
|
315
|
+
**Everything else the gate checks is invisible here**, and that is more than
|
|
316
|
+
whether the tests pass. A scenario left \`skip\`ped is \`declared-not-run\` and
|
|
317
|
+
blocks the gate with the suite fully green; so does a spec file that will not
|
|
318
|
+
import, or a proposed spec no delta claims. A clean table plus green tests is
|
|
319
|
+
still not a verdict — \`attest archive\` is the only thing that decides.
|
|
274
320
|
|
|
275
321
|
Then present the proposal, the ids, and the red output, and **stop**. Wait for
|
|
276
322
|
agreement before implementing.
|
|
@@ -304,9 +350,32 @@ once. Branch on \`issues[].code\`, never on \`message\`:
|
|
|
304
350
|
| \`add-conflict\` | the delta adds an id that already exists with different content |
|
|
305
351
|
| \`change-not-found\` | no \`requirements.delta.ts\` for that name |
|
|
306
352
|
| \`proposed-spec-name-taken\` | a proposed spec's merged name is already held by another spec |
|
|
307
|
-
| \`apply-unsupported-delta\` | \`--apply\` writes back ADDED
|
|
353
|
+
| \`apply-unsupported-delta\` | \`--apply\` writes back ADDED and MODIFIED; this delta carries RENAMED or REMOVED, or a MODIFIED value whose source span holds a comment |
|
|
308
354
|
| \`apply-no-prefix-owner\` | no registry file owns the prefix of an id this change adds |
|
|
309
355
|
|
|
356
|
+
### Iterating: run the spec directly, decide with the gate
|
|
357
|
+
|
|
358
|
+
The gate does everything above after every edit, which makes it the slowest
|
|
359
|
+
possible inner loop. While you are still making a scenario pass, run that one
|
|
360
|
+
file instead, and keep \`attest archive\` for when you think you are finished:
|
|
361
|
+
|
|
362
|
+
\`\`\`
|
|
363
|
+
vitest run path/to/fog.proposed.spec.ts
|
|
364
|
+
\`\`\`
|
|
365
|
+
|
|
366
|
+
Two things make that safe, and one keeps it from being a verdict:
|
|
367
|
+
|
|
368
|
+
- **It writes nothing.** \`first-run.json\` is only ever written by the gate, so
|
|
369
|
+
no number of direct runs can touch the record or the never-red obligation.
|
|
370
|
+
- **\`attest status <name>\` still answers the readable half** without a run,
|
|
371
|
+
exactly as in stage 1.
|
|
372
|
+
- **A green file here does not predict the gate.** Your config supplies aliases,
|
|
373
|
+
transforms and a DOM; the gate's child run is isolated (\`config: false\`) and
|
|
374
|
+
supplies none of them unless told to. So a scenario can pass directly and fail
|
|
375
|
+
under the gate — that gap is the first thing to suspect when it does. See
|
|
376
|
+
\`tests-red\` in the troubleshooting document, and \`--vitest-config\` if your
|
|
377
|
+
specs need that environment.
|
|
378
|
+
|
|
310
379
|
### Four things you must not do
|
|
311
380
|
|
|
312
381
|
Each turns the gate green without changing the system, which is the exact
|
|
@@ -352,7 +421,8 @@ attest archive <name> --apply
|
|
|
352
421
|
\`\`\`
|
|
353
422
|
|
|
354
423
|
That finishes the merge the verdict just approved: it splices the change's ADDED
|
|
355
|
-
requirements into the registry file owning their id prefix,
|
|
424
|
+
requirements into the registry file owning their id prefix, writes each MODIFIED
|
|
425
|
+
requirement's changed values over the values they replace, repoints each proposed
|
|
356
426
|
spec's import of \`requirements.delta.ts\` at that registry, renames the specs in
|
|
357
427
|
place, and moves \`changes/<name>/\` to \`archive/<date>-<name>/\`. It re-runs the
|
|
358
428
|
gate first and writes nothing if that fails, and it prints every path it touched.
|
|
@@ -366,10 +436,14 @@ Two things it does not do, and both are still yours:
|
|
|
366
436
|
registry is a \`stale-spec-doc\` ERROR.
|
|
367
437
|
2. **Run the merged suite.** \`attest verify\`, on the result, reported.
|
|
368
438
|
|
|
369
|
-
**It writes back ADDED
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
439
|
+
**It writes back ADDED and MODIFIED.** A delta also carrying RENAMED or REMOVED is
|
|
440
|
+
refused whole as \`apply-unsupported-delta\`, with nothing written — the gate still
|
|
441
|
+
checked all four, so nothing about the change is unverified, but the rest is merged
|
|
442
|
+
by hand. The same code refuses one MODIFIED *value* when a comment sits inside the
|
|
443
|
+
span it would write over: the edit replaces a value and never the entry holding it,
|
|
444
|
+
so \`statement\` and \`rationale\` can never hit this, while a \`params\` value or an
|
|
445
|
+
\`outOfScope\` list can. Move the comment beside the value it belongs to, and run
|
|
446
|
+
the command again.
|
|
373
447
|
|
|
374
448
|
Merging by hand, when it refuses: splice the delta's entries into the registry
|
|
375
449
|
file that owns their prefix; then for each \`*.proposed.spec.ts\`, **repoint its
|
|
@@ -387,7 +461,7 @@ move, so nothing else about it does either. Then move the change folder.
|
|
|
387
461
|
| \`attest cover\` | which requirements lack a scenario. |
|
|
388
462
|
| \`attest render\` | the registry as Markdown for human readers; \`--check\` gates a committed copy. |
|
|
389
463
|
| \`attest archive <change>\` | the completion gate for a proposed change; \`--apply\` also performs the merge it approves. |
|
|
390
|
-
| \`attest status <change>\` |
|
|
464
|
+
| \`attest status <change>\` | per id the change ADDs, two of the gate's obligations — scenario written, seen red — read without running the suite. A strict subset of the gate, never a second one. |
|
|
391
465
|
|
|
392
466
|
\`verify\` starts the child run **isolated** — it does not read \`vitest.config.ts\`,
|
|
393
467
|
so a verdict never depends on ambient configuration. Specs needing transforms, a
|
package/dist/core/splice.d.ts
CHANGED
|
@@ -36,6 +36,64 @@ export declare function requirementSource(id: string, req: Requirement, indent:
|
|
|
36
36
|
* the same delta must produce the same file twice.
|
|
37
37
|
*/
|
|
38
38
|
export declare function spliceRequirements(file: string, source: string, additions: Registry): string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* One requirement a change modifies: what the registry holds, and what the gate
|
|
41
|
+
* proved the end state to be.
|
|
42
|
+
*
|
|
43
|
+
* Both halves, rather than the delta's patch, for the reason `MergeInputs.applied`
|
|
44
|
+
* exists — the patch is the authoring shape and the end state is what was proved
|
|
45
|
+
* green. Which fields actually move is then a *derived* fact, computed here by
|
|
46
|
+
* comparing the two, so a delta that restates a value it does not change writes
|
|
47
|
+
* nothing.
|
|
48
|
+
*/
|
|
49
|
+
export interface Modification {
|
|
50
|
+
id: string;
|
|
51
|
+
/** The entry as the file holds it — the gate's `base`. */
|
|
52
|
+
before: Requirement;
|
|
53
|
+
/** The entry as the gate proved it — `applied`. */
|
|
54
|
+
after: Requirement;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Why one value could not be written over, if it could not.
|
|
58
|
+
*
|
|
59
|
+
* `field` is `statement`, `outOfScope`, `params.totpWindowSec` — the path of the
|
|
60
|
+
* value, spelled the way the author would say it — and absent only when the
|
|
61
|
+
* refusal is about the entry rather than a value in it. The caller turns these
|
|
62
|
+
* into diagnostics; nothing here writes prose, because a refusal from this
|
|
63
|
+
* module refuses a whole merge and only the caller knows what to say about that.
|
|
64
|
+
*/
|
|
65
|
+
export interface ModifyRefusal {
|
|
66
|
+
reqId: string;
|
|
67
|
+
field?: string;
|
|
68
|
+
reason: 'comment' | 'entry-not-found' | 'not-a-literal' | 'param-dropped';
|
|
69
|
+
}
|
|
70
|
+
export type ModifyResult = {
|
|
71
|
+
ok: true;
|
|
72
|
+
source: string;
|
|
73
|
+
} | {
|
|
74
|
+
ok: false;
|
|
75
|
+
refusals: ModifyRefusal[];
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* `source` with each modified requirement's **changed values** written over.
|
|
79
|
+
*
|
|
80
|
+
* The other half of `--apply`'s write-back, and the one the design refused for
|
|
81
|
+
* five releases. What makes it allowable is stated at the top of this file and
|
|
82
|
+
* is a property of *granularity*: this never replaces an entry, only the span of
|
|
83
|
+
* a value inside one. A `statement` is a single string literal, so a comment
|
|
84
|
+
* cannot be inside what it overwrites; a `params` key or an `outOfScope` list
|
|
85
|
+
* can be, and that span is the only place this has to ask. Everything else in
|
|
86
|
+
* the file — the keys, the commas, the layout, every comment attached to a field
|
|
87
|
+
* that did not move — is bytes this does not address.
|
|
88
|
+
*
|
|
89
|
+
* A refusal is per value and the caller's response is whole-merge: the list is
|
|
90
|
+
* returned complete rather than at the first one, so an author fixing them by
|
|
91
|
+
* hand sees the whole of what stopped it.
|
|
92
|
+
*
|
|
93
|
+
* `undefined` under the same condition `spliceRequirements` returns it, through
|
|
94
|
+
* the same reader.
|
|
95
|
+
*/
|
|
96
|
+
export declare function spliceModifications(file: string, source: string, changes: readonly Modification[]): ModifyResult | undefined;
|
|
39
97
|
/**
|
|
40
98
|
* `source` with every import of `from` repointed at `to`.
|
|
41
99
|
*
|
|
@@ -45,9 +103,10 @@ export declare function spliceRequirements(file: string, source: string, additio
|
|
|
45
103
|
* discipline as the splice, for the same reason.
|
|
46
104
|
*
|
|
47
105
|
* The extension is taken from the specifier being replaced rather than chosen
|
|
48
|
-
* here. Whether a project writes `./x.reqs.js` or `./x.reqs
|
|
49
|
-
* its module resolution, uniform across the project, and already
|
|
50
|
-
* specifier sitting in front of us
|
|
106
|
+
* here. Whether a project writes `./x.reqs.js`, `./x.reqs.ts` or `./x.reqs` is a
|
|
107
|
+
* property of its module resolution, uniform across the project, and already
|
|
108
|
+
* answered by the specifier sitting in front of us — the third spelling
|
|
109
|
+
* included, where the answer is "none".
|
|
51
110
|
*/
|
|
52
111
|
export declare function repointImport(file: string, source: string, from: string, to: string): string;
|
|
53
112
|
//# sourceMappingURL=splice.d.ts.map
|