@am_shork/attest 0.1.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 +50 -0
- package/LICENSE +21 -0
- package/README.md +101 -0
- package/bin/attest.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +69 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/report.d.ts +7 -0
- package/dist/cli/report.d.ts.map +1 -0
- package/dist/cli/report.js +45 -0
- package/dist/cli/report.js.map +1 -0
- package/dist/core/apply.d.ts +13 -0
- package/dist/core/apply.d.ts.map +1 -0
- package/dist/core/apply.js +94 -0
- package/dist/core/apply.js.map +1 -0
- package/dist/core/gate.d.ts +22 -0
- package/dist/core/gate.d.ts.map +1 -0
- package/dist/core/gate.js +44 -0
- package/dist/core/gate.js.map +1 -0
- package/dist/core/loader.d.ts +6 -0
- package/dist/core/loader.d.ts.map +1 -0
- package/dist/core/loader.js +39 -0
- package/dist/core/loader.js.map +1 -0
- package/dist/core/locate.d.ts +22 -0
- package/dist/core/locate.d.ts.map +1 -0
- package/dist/core/locate.js +117 -0
- package/dist/core/locate.js.map +1 -0
- package/dist/core/parser.d.ts +3 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +0 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/pipeline.d.ts +22 -0
- package/dist/core/pipeline.d.ts.map +1 -0
- package/dist/core/pipeline.js +135 -0
- package/dist/core/pipeline.js.map +1 -0
- package/dist/core/registry.d.ts +35 -0
- package/dist/core/registry.d.ts.map +1 -0
- package/dist/core/registry.js +41 -0
- package/dist/core/registry.js.map +1 -0
- package/dist/core/runner.d.ts +15 -0
- package/dist/core/runner.d.ts.map +1 -0
- package/dist/core/runner.js +59 -0
- package/dist/core/runner.js.map +1 -0
- package/dist/core/schema.d.ts +42 -0
- package/dist/core/schema.d.ts.map +1 -0
- package/dist/core/schema.js +20 -0
- package/dist/core/schema.js.map +1 -0
- package/dist/core/types.d.ts +39 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/validator.d.ts +15 -0
- package/dist/core/validator.d.ts.map +1 -0
- package/dist/core/validator.js +73 -0
- package/dist/core/validator.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/runtime.d.ts +11 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +32 -0
- package/dist/runtime.js.map +1 -0
- package/package.json +76 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Structural validation + anti-drift heuristic (design §5.3, §6).
|
|
2
|
+
// Graded reporting: ERROR / WARNING / INFO, each with file + line + fix hint.
|
|
3
|
+
/**
|
|
4
|
+
* Static structural validation (design §5.3). Emits ERROR-level issues for:
|
|
5
|
+
* - orphan-test: a scenario covers an unknown requirement id
|
|
6
|
+
* - uncovered-requirement: a requirement has no scenario
|
|
7
|
+
* - unbound-param: a statement placeholder has no matching param
|
|
8
|
+
*/
|
|
9
|
+
export function validateStructure(registry, plan) {
|
|
10
|
+
const issues = [];
|
|
11
|
+
const knownIds = new Set(Object.keys(registry));
|
|
12
|
+
const coveredIds = new Set(plan.scenarios.map((s) => s.reqId));
|
|
13
|
+
// orphan-test: covers a requirement that does not exist.
|
|
14
|
+
for (const s of plan.scenarios) {
|
|
15
|
+
if (!knownIds.has(s.reqId)) {
|
|
16
|
+
issues.push({
|
|
17
|
+
level: 'ERROR',
|
|
18
|
+
code: 'orphan-test',
|
|
19
|
+
file: s.file,
|
|
20
|
+
line: s.line,
|
|
21
|
+
message: `scenario "${s.name}" 覆蓋未知需求 "${s.reqId}"。請在 registry 新增,或修正 ID。`,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// uncovered-requirement: intent exists but no scenario attests it.
|
|
26
|
+
for (const id of knownIds) {
|
|
27
|
+
if (!coveredIds.has(id)) {
|
|
28
|
+
issues.push({
|
|
29
|
+
level: 'ERROR',
|
|
30
|
+
code: 'uncovered-requirement',
|
|
31
|
+
message: `需求 "${id}" 沒有任何 scenario 佐證。加一個 scenario 或標記為 deferred。`,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// unbound-param: statement uses {param} but params does not define it.
|
|
36
|
+
for (const [id, req] of Object.entries(registry)) {
|
|
37
|
+
for (const m of req.statement.matchAll(/\{(\w+)\}/g)) {
|
|
38
|
+
const name = m[1];
|
|
39
|
+
if (!(name in req.params)) {
|
|
40
|
+
issues.push({
|
|
41
|
+
level: 'ERROR',
|
|
42
|
+
code: 'unbound-param',
|
|
43
|
+
message: `需求 "${id}" 的 statement 用了 {${name}},但 params 未定義它。`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return issues;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Weak anti-drift heuristic (design §6, mechanism 3). Emits an INFO when a
|
|
52
|
+
* scenario covers a requirement that has params but the scenario does not
|
|
53
|
+
* reference them — a place a drifting hardcoded literal could hide.
|
|
54
|
+
*/
|
|
55
|
+
export function detectPotentialDrift(registry, plan, refs) {
|
|
56
|
+
return plan.scenarios.flatMap((s) => {
|
|
57
|
+
const req = registry[s.reqId];
|
|
58
|
+
const hasParams = req !== undefined && Object.keys(req.params).length > 0;
|
|
59
|
+
const referencesParams = refs.some((r) => r.reqId === s.reqId && r.scenario === s.name);
|
|
60
|
+
return hasParams && !referencesParams
|
|
61
|
+
? [
|
|
62
|
+
{
|
|
63
|
+
level: 'INFO',
|
|
64
|
+
code: 'possible-drift',
|
|
65
|
+
file: s.file,
|
|
66
|
+
line: s.line,
|
|
67
|
+
message: `scenario "${s.name}" 覆蓋 ${s.reqId}(有 params)卻未引用它,可能藏有會漂移的硬寫字面值。`,
|
|
68
|
+
},
|
|
69
|
+
]
|
|
70
|
+
: [];
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { requirement, scenario } from './runtime.js';
|
|
2
|
+
export { defineRequirements, delta } from './core/registry.js';
|
|
3
|
+
export type { RegistryDelta } from './core/registry.js';
|
|
4
|
+
export type { Requirement, Registry, ParsedScenario, AttestPlan, Level, Issue, } from './core/types.js';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC/D,YAAY,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,YAAY,EACV,WAAW,EACX,QAAQ,EACR,cAAc,EACd,UAAU,EACV,KAAK,EACL,KAAK,GACN,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// Sole public entry point for the `attest` package (design §10).
|
|
2
|
+
// Users import only from here; core/ internals are intentionally not exported.
|
|
3
|
+
export { requirement, scenario } from './runtime.js';
|
|
4
|
+
export { defineRequirements, delta } from './core/registry.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,+EAA+E;AAE/E,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Group scenarios that verify one requirement. The describe block is named
|
|
3
|
+
* `[id]` so runtime coverage can be reconstructed from the task tree (§5.4).
|
|
4
|
+
*/
|
|
5
|
+
export declare function requirement(id: string, body: () => void): void;
|
|
6
|
+
/**
|
|
7
|
+
* Declare a single verifiable scenario. Must be nested inside requirement().
|
|
8
|
+
* The assertion itself is delegated to Vitest `it` (design §3).
|
|
9
|
+
*/
|
|
10
|
+
export declare function scenario(name: string, fn: () => void | Promise<void>): void;
|
|
11
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,wBAAgB,WAAW,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,IAAI,GAAG,IAAI,CAU9D;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAK3E"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Authoring runtime API: requirement() / scenario() (design §3).
|
|
2
|
+
// Pure delegation to Vitest; no in-process coverage singleton — coverage is
|
|
3
|
+
// reconstructed from the Vitest task tree instead (design §5.4).
|
|
4
|
+
import { describe, it } from 'vitest';
|
|
5
|
+
let currentReqId = null;
|
|
6
|
+
/**
|
|
7
|
+
* Group scenarios that verify one requirement. The describe block is named
|
|
8
|
+
* `[id]` so runtime coverage can be reconstructed from the task tree (§5.4).
|
|
9
|
+
*/
|
|
10
|
+
export function requirement(id, body) {
|
|
11
|
+
describe(`[${id}]`, () => {
|
|
12
|
+
const prev = currentReqId;
|
|
13
|
+
currentReqId = id;
|
|
14
|
+
try {
|
|
15
|
+
body();
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
currentReqId = prev;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Declare a single verifiable scenario. Must be nested inside requirement().
|
|
24
|
+
* The assertion itself is delegated to Vitest `it` (design §3).
|
|
25
|
+
*/
|
|
26
|
+
export function scenario(name, fn) {
|
|
27
|
+
if (!currentReqId) {
|
|
28
|
+
throw new Error(`scenario("${name}") 必須巢狀在 requirement(...) 內`);
|
|
29
|
+
}
|
|
30
|
+
it(name, fn);
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,4EAA4E;AAC5E,iEAAiE;AAEjE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAEtC,IAAI,YAAY,GAAkB,IAAI,CAAC;AAEvC;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,EAAU,EAAE,IAAgB;IACtD,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE;QACvB,MAAM,IAAI,GAAG,YAAY,CAAC;QAC1B,YAAY,GAAG,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,IAAI,EAAE,CAAC;QACT,CAAC;gBAAS,CAAC;YACT,YAAY,GAAG,IAAI,CAAC;QACtB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,EAA8B;IACnE,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,6BAA6B,CAAC,CAAC;IAClE,CAAC;IACD,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;AACf,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@am_shork/attest",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TDD-native spec framework: tests are the source of truth for verification, ID-bound requirements the source of truth for intent.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@10.28.0",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.19"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://gitlab.com/Pseudorca/attest.git"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://gitlab.com/Pseudorca/attest#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://gitlab.com/Pseudorca/attest/-/issues"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"attest": "bin/attest.js"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"dist",
|
|
29
|
+
"bin",
|
|
30
|
+
"CHANGELOG.md"
|
|
31
|
+
],
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json",
|
|
37
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:watch": "vitest",
|
|
40
|
+
"lint": "eslint .",
|
|
41
|
+
"prepack": "pnpm run build",
|
|
42
|
+
"attest": "node bin/attest.js",
|
|
43
|
+
"check:self": "node bin/attest.js check self",
|
|
44
|
+
"verify:self": "node bin/attest.js verify self",
|
|
45
|
+
"cover:self": "node bin/attest.js cover self"
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"tdd",
|
|
49
|
+
"spec",
|
|
50
|
+
"requirements",
|
|
51
|
+
"drift",
|
|
52
|
+
"vitest"
|
|
53
|
+
],
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"chalk": "^5.3.0",
|
|
57
|
+
"commander": "^12.1.0",
|
|
58
|
+
"typescript": "^5.5.0",
|
|
59
|
+
"zod": "^3.23.0"
|
|
60
|
+
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"vite": "^5.0.0 || ^6.0.0",
|
|
63
|
+
"vitest": "^2.0.0 || ^3.0.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@eslint/js": "^9.9.0",
|
|
67
|
+
"@types/node": "^20.19.0",
|
|
68
|
+
"eslint": "^9.9.0",
|
|
69
|
+
"typescript-eslint": "^8.2.0",
|
|
70
|
+
"vite": "^5.4.0",
|
|
71
|
+
"vitest": "^2.0.0"
|
|
72
|
+
},
|
|
73
|
+
"pnpm": {
|
|
74
|
+
"onlyBuiltDependencies": ["esbuild"]
|
|
75
|
+
}
|
|
76
|
+
}
|