@am_shork/attest 0.1.5 → 0.1.7

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.
@@ -1,20 +1,23 @@
1
1
  // High-level operations composing the core layers (design §9). These are
2
2
  // tool-agnostic; the CLI is a thin shell that calls them and renders the result.
3
3
  import { createLoader } from './loader.js';
4
- import { loadRegistry, parseAllSpecFiles, parseChangeSpecs, listChangeNames, findFiles, } from './locate.js';
4
+ import { loadRegistry, parseSpecs, parseChangeSpecs, listChangeNames, scanProject, } from './locate.js';
5
5
  import { validateStructure, detectPotentialDrift } from './validator.js';
6
6
  import { runAndCollect } from './runner.js';
7
7
  import { applyDelta } from './apply.js';
8
- import { evaluateGate } from './gate.js';
8
+ import { evaluateGate, declaredNotRunIssues } from './gate.js';
9
+ import { renderMarkdown, staleIssue } from './render.js';
10
+ import { readFile } from 'node:fs/promises';
9
11
  import { join, relative } from 'node:path';
10
12
  import { configDefaults } from 'vitest/config';
11
13
  import { hasError } from './types.js';
12
14
  /** Static structural check (design §9: `attest check`). */
13
15
  export async function runCheck(root) {
16
+ const scan = await scanProject(root);
14
17
  const loader = await createLoader();
15
18
  try {
16
- const { registry, issues } = await loadRegistry(root, loader);
17
- const plan = await parseAllSpecFiles(root);
19
+ const { registry, issues } = await loadRegistry(root, loader, scan.reqsFiles);
20
+ const plan = await parseSpecs(scan.specFiles, root);
18
21
  return [
19
22
  ...issues,
20
23
  ...validateStructure(registry, plan),
@@ -27,15 +30,16 @@ export async function runCheck(root) {
27
30
  }
28
31
  /** Executable verification (design §9: `attest verify`). */
29
32
  export async function runVerify(root, options = {}) {
33
+ const scan = await scanProject(root);
30
34
  const loader = await createLoader();
31
35
  let registry;
32
36
  let plan;
33
37
  const issues = [];
34
38
  try {
35
- const loaded = await loadRegistry(root, loader);
39
+ const loaded = await loadRegistry(root, loader, scan.reqsFiles);
36
40
  registry = loaded.registry;
37
41
  issues.push(...loaded.issues);
38
- plan = await parseAllSpecFiles(root);
42
+ plan = await parseSpecs(scan.specFiles, root);
39
43
  }
40
44
  finally {
41
45
  await loader.close();
@@ -46,26 +50,16 @@ export async function runVerify(root, options = {}) {
46
50
  if (!run.passed) {
47
51
  issues.push({ level: 'ERROR', code: 'tests-red', message: 'Some tests are failing.' });
48
52
  }
49
- for (const s of plan.scenarios) {
50
- if (!run.runtimeCoverage.get(s.reqId)?.has(s.name)) {
51
- issues.push({
52
- level: 'ERROR',
53
- code: 'declared-not-run',
54
- reqId: s.reqId,
55
- file: s.file,
56
- line: s.line,
57
- message: `scenario "${s.name}" statically declares it attests ${s.reqId}, but never executed (skipped, or excluded by an .only?).`,
58
- });
59
- }
60
- }
53
+ issues.push(...declaredNotRunIssues(plan, run));
61
54
  return { issues, passed: run.passed, ok: run.passed && !hasError(issues) };
62
55
  }
63
56
  /** Coverage-only report (design §9: `attest cover`). */
64
57
  export async function runCover(root) {
58
+ const scan = await scanProject(root);
65
59
  const loader = await createLoader();
66
60
  try {
67
- const { registry } = await loadRegistry(root, loader);
68
- const plan = await parseAllSpecFiles(root);
61
+ const { registry } = await loadRegistry(root, loader, scan.reqsFiles);
62
+ const plan = await parseSpecs(scan.specFiles, root);
69
63
  const counts = new Map();
70
64
  for (const s of plan.scenarios) {
71
65
  counts.set(s.reqId, (counts.get(s.reqId) ?? 0) + 1);
@@ -82,11 +76,96 @@ export async function runCover(root) {
82
76
  await loader.close();
83
77
  }
84
78
  }
79
+ /**
80
+ * Markdown projection of the intent layer (design §9: `attest render`).
81
+ *
82
+ * Reads the registry and nothing else — no spec parse, no run — because the
83
+ * document is intent only. That is what keeps it a pure function of the
84
+ * `*.reqs.ts` files, so a committed rendering can be gated without going stale
85
+ * every time a line moves in a test file.
86
+ */
87
+ export async function runRender(root) {
88
+ const loader = await createLoader();
89
+ try {
90
+ const { registry, issues } = await loadRegistry(root, loader);
91
+ // A registry that failed to load yields a document that silently omits
92
+ // requirements. An incomplete spec doc is worse than none, so refuse.
93
+ if (hasError(issues))
94
+ return { markdown: '', issues };
95
+ return { markdown: renderMarkdown(registry), issues };
96
+ }
97
+ finally {
98
+ await loader.close();
99
+ }
100
+ }
101
+ /**
102
+ * Freshness gate for a committed rendering (`attest render --check`).
103
+ *
104
+ * A generated file checked into the repo is itself a drift surface — the exact
105
+ * failure mode Attest exists to remove — so the projection only earns its place
106
+ * alongside a gate that fails when the file no longer matches its source.
107
+ */
108
+ export async function runRenderCheck(root, outFile, target) {
109
+ const { markdown, issues } = await runRender(root);
110
+ if (hasError(issues))
111
+ return issues;
112
+ const current = await readFile(outFile, 'utf8').catch(() => undefined);
113
+ const stale = staleIssue(target, current, markdown);
114
+ return stale ? [...issues, stale] : issues;
115
+ }
116
+ /**
117
+ * A change name must name one directory inside `changes/`, and nothing else.
118
+ *
119
+ * The name is interpolated into a path whose module is then *executed*, and
120
+ * `join` normalises `..` — so an unvalidated name loads code from anywhere on
121
+ * disk. That is reachable from CI the moment the name comes from a branch or
122
+ * MR title.
123
+ *
124
+ * The test is path safety, not a character whitelist: the name never reaches a
125
+ * glob (only *sibling* names do, and changeExcludeGlobs escapes those), so
126
+ * `feat(auth)` or a name with a space is a perfectly good directory and there
127
+ * is no reason for the gate to refuse it. Rejecting only what can escape the
128
+ * directory keeps the guard exactly as wide as the danger.
129
+ */
130
+ function isSafeChangeName(name) {
131
+ return (name !== '' &&
132
+ name !== '.' &&
133
+ name !== '..' &&
134
+ !name.includes('/') &&
135
+ !name.includes('\\') &&
136
+ !name.includes('\0'));
137
+ }
138
+ /**
139
+ * The globs that keep *other* proposals out of a change's gate run.
140
+ *
141
+ * Sibling names come from `readdir`, not from the guard above, so they are
142
+ * escaped: a directory called `feat(auth)` pasted in raw is a *pattern*, it
143
+ * matches nothing, and that sibling's specs silently join the run — quietly
144
+ * widening the scope of the one check that decides "done".
145
+ */
146
+ export function changeExcludeGlobs(others) {
147
+ return others.map((n) => `**/changes/${escapeGlob(n)}/**`);
148
+ }
149
+ function escapeGlob(name) {
150
+ return name.replace(/[*?[\]{}()!+@|^$]/g, '\\$&');
151
+ }
85
152
  /** Archive gate for a change (design §8, §9: `attest archive <change>`). */
86
153
  export async function runArchive(root, changeName, options = {}) {
154
+ // Checked before any loader starts: nothing should be resolved, let alone
155
+ // evaluated, on behalf of a name we have already rejected.
156
+ if (!isSafeChangeName(changeName)) {
157
+ return [
158
+ {
159
+ level: 'ERROR',
160
+ code: 'invalid-change-name',
161
+ message: `Invalid change name ${JSON.stringify(changeName)}. A change name must be one directory inside changes/: it cannot be empty, "." or "..", or contain a path separator.`,
162
+ },
163
+ ];
164
+ }
165
+ const scan = await scanProject(root);
87
166
  const loader = await createLoader();
88
167
  try {
89
- const { registry: base, issues } = await loadRegistry(root, loader);
168
+ const { registry: base, issues } = await loadRegistry(root, loader, scan.reqsFiles);
90
169
  if (issues.some((i) => i.level === 'ERROR'))
91
170
  return issues;
92
171
  const deltaPath = join(root, 'changes', changeName, 'requirements.delta.ts');
@@ -109,7 +188,7 @@ export async function runArchive(root, changeName, options = {}) {
109
188
  if (applied.issues.length > 0)
110
189
  return applied.issues;
111
190
  // Static plan = merged base suite + this change's specs (design §8).
112
- const basePlan = await parseAllSpecFiles(root);
191
+ const basePlan = await parseSpecs(scan.specFiles, root);
113
192
  const changePlan = await parseChangeSpecs(root, changeName);
114
193
  const plan = {
115
194
  scenarios: [...basePlan.scenarios, ...changePlan.scenarios],
@@ -120,7 +199,7 @@ export async function runArchive(root, changeName, options = {}) {
120
199
  const exclude = [
121
200
  ...configDefaults.exclude,
122
201
  '**/archive/**',
123
- ...others.map((n) => `**/changes/${n}/**`),
202
+ ...changeExcludeGlobs(others),
124
203
  ];
125
204
  const run = await runAndCollect({ root, quiet: true, exclude, vitestConfig: options.vitestConfig });
126
205
  return evaluateGate({ registry: applied.registry, plan, run });
@@ -131,6 +210,6 @@ export async function runArchive(root, changeName, options = {}) {
131
210
  }
132
211
  /** True when a spec suite exists at all (used by the CLI to warn on empty roots). */
133
212
  export async function hasSpecs(root) {
134
- return (await findFiles(root, (n) => n.endsWith('.spec.ts'))).length > 0;
213
+ return (await scanProject(root)).specFiles.length > 0;
135
214
  }
136
215
  //# sourceMappingURL=pipeline.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,iFAAiF;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,SAAS,GACV,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IACzC,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAC9D,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC3C,OAAO;YACL,GAAG,MAAM;YACT,GAAG,iBAAiB,CAAC,QAAQ,EAAE,IAAI,CAAC;YACpC,GAAG,oBAAoB,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;SACxD,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAcD,4DAA4D;AAC5D,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAAY,EAAE,UAAsB,EAAE;IACpE,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;IACpC,IAAI,QAAkB,CAAC;IACvB,IAAI,IAAgB,CAAC;IACrB,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QAChD,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACvC,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,MAAM,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;IAClD,MAAM,CAAC,IAAI,CAAC,GAAG,oBAAoB,CAAC,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;IAErE,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC3F,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;QAChB,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;IACzF,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,CAAC,IAAI,CAAC;gBACV,KAAK,EAAE,OAAO;gBACd,IAAI,EAAE,kBAAkB;gBACxB,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,OAAO,EAAE,aAAa,CAAC,CAAC,IAAI,oCAAoC,CAAC,CAAC,KAAK,2DAA2D;aACnI,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,GAAG,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;AAC7E,CAAC;AAQD,wDAAwD;AACxD,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IACzC,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACtD,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;QACzC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YAC/B,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACtD,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC;aACzB,IAAI,EAAE;aACN,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YACf,KAAK;YACL,OAAO,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC;YACrC,aAAa,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;SACtC,CAAC,CAAC,CAAC;IACR,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,IAAY,EACZ,UAAkB,EAClB,UAAsB,EAAE;IAExB,MAAM,MAAM,GAAG,MAAM,YAAY,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpE,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC;YAAE,OAAO,MAAM,CAAC;QAE3D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,uBAAuB,CAAC,CAAC;QAC7E,IAAI,KAAoB,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,IAAI,CAA6B,SAAS,CAAC,CAAC;YACrE,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO;gBACL;oBACE,KAAK,EAAE,OAAO;oBACd,IAAI,EAAE,kBAAkB;oBACxB,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;oBAC/B,OAAO,EAAE,gDAAgD,UAAU,MAAM,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE;iBAC5H;aACF,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACxC,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC,MAAM,CAAC;QAErD,qEAAqE;QACrE,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;QAC5D,MAAM,IAAI,GAAe;YACvB,SAAS,EAAE,CAAC,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC;YAC3D,SAAS,EAAE,CAAC,GAAG,QAAQ,CAAC,SAAS,EAAE,GAAG,UAAU,CAAC,SAAS,CAAC;SAC5D,CAAC;QAEF,6EAA6E;QAC7E,MAAM,MAAM,GAAG,CAAC,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC;QAC7E,MAAM,OAAO,GAAG;YACd,GAAG,cAAc,CAAC,OAAO;YACzB,eAAe;YACf,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC;SAC3C,CAAC;QACF,MAAM,GAAG,GAAG,MAAM,aAAa,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAEpG,OAAO,YAAY,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC;IACjE,CAAC;YAAS,CAAC;QACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,IAAY;IACzC,OAAO,CAAC,MAAM,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC3E,CAAC"}
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/core/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { Issue, Registry } from './types.js';
2
+ /**
3
+ * Render the registry as a standalone Markdown document.
4
+ *
5
+ * Deliberately takes no formatting options (not even a title): every knob that
6
+ * changes the output is a way for `--check` to disagree with the run that
7
+ * generated the file, and a freshness gate that can be wrong about staleness is
8
+ * worse than no gate.
9
+ */
10
+ export declare function renderMarkdown(registry: Registry): string;
11
+ /**
12
+ * How to talk about the file being gated. Both strings come from the caller
13
+ * rather than being derived from the paths: a path relative to the *project
14
+ * root* is not the path the user typed, and a command rebuilt from it drops the
15
+ * `[dir]` argument — which made the fix hint name a different registry than the
16
+ * one being checked.
17
+ */
18
+ export interface RenderTarget {
19
+ /** The output path as the user wrote it. */
20
+ display: string;
21
+ /** The exact command that regenerates the file. */
22
+ command: string;
23
+ }
24
+ /**
25
+ * The freshness verdict for a committed rendering: `undefined` when the file on
26
+ * disk is exactly what the registry renders to now, an ERROR otherwise.
27
+ *
28
+ * The two failures get distinct codes — `missing-spec-doc` for a rendering that
29
+ * was never generated, `stale-spec-doc` for one whose bytes drifted — even
30
+ * though the fix is the same command. `message` is prose and not part of the
31
+ * API, so a code shared between the two would leave a consumer that cares about
32
+ * the difference (a first-time setup versus a document going out of date) with
33
+ * nothing to branch on but the wording.
34
+ *
35
+ * Kept here rather than in the pipeline, and pure, because this is the verdict
36
+ * that makes a generated file safe to commit: a byte comparison, no heuristics.
37
+ */
38
+ export declare function staleIssue(target: RenderTarget, current: string | undefined, fresh: string): Issue | undefined;
39
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAe,MAAM,YAAY,CAAC;AAQ/D;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,CAezD;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,KAAK,EAAE,MAAM,GACZ,KAAK,GAAG,SAAS,CAYnB"}
@@ -0,0 +1,168 @@
1
+ // Human-readable projection of the intent layer (design §0: Markdown is an
2
+ // *output*, never an input).
3
+ //
4
+ // The registry is the only source of truth; this module renders it for people
5
+ // who do not read TypeScript — reviewers, QA, audit. Nothing here is ever read
6
+ // back, so the direction OpenSpec's pure-Markdown model went (Markdown as
7
+ // truth, and the free-form drift that comes with it) stays closed.
8
+ //
9
+ // Three properties this file must keep:
10
+ // - **Intent only.** The document says what the system promises, never what is
11
+ // proven or green: coverage and results are verdicts, and verdicts belong to
12
+ // `cover` / `verify`, which recompute them on demand. Putting them here also
13
+ // made the document churn on code that changed no intent at all — a line
14
+ // shifting in a spec file rewrote it — and in a green repo the coverage was
15
+ // constant anyway (an uncovered requirement is a `check` ERROR), so it was
16
+ // diff noise carrying no information.
17
+ // - **A pure function of the registry.** No parse, no run, no options. The
18
+ // rendering therefore changes if and only if a `*.reqs.ts` changes, which is
19
+ // what makes `--check` a signal instead of a chore.
20
+ // - **Params interpolated into the statement.** The source says
21
+ // "{idleTimeoutMin} minutes"; a human reader wants "30 minutes". This is the
22
+ // one thing the projection gives that reading the source does not.
23
+ import { byCodeUnit } from './order.js';
24
+ const BANNER = '<!-- Generated by `attest render` — do not edit. Edit the `*.reqs.ts` registry and regenerate. -->';
25
+ /**
26
+ * Render the registry as a standalone Markdown document.
27
+ *
28
+ * Deliberately takes no formatting options (not even a title): every knob that
29
+ * changes the output is a way for `--check` to disagree with the run that
30
+ * generated the file, and a freshness gate that can be wrong about staleness is
31
+ * worse than no gate.
32
+ */
33
+ export function renderMarkdown(registry) {
34
+ const ids = Object.keys(registry).sort(compareIds);
35
+ const out = [BANNER, '', '# Requirements', ''];
36
+ if (ids.length === 0) {
37
+ out.push('_No requirements are defined yet._', '');
38
+ return out.join('\n');
39
+ }
40
+ out.push(...overviewTable(registry, ids), '');
41
+ for (const id of ids) {
42
+ out.push(...section(id, registry[id]), '');
43
+ }
44
+ return out.join('\n');
45
+ }
46
+ /**
47
+ * The freshness verdict for a committed rendering: `undefined` when the file on
48
+ * disk is exactly what the registry renders to now, an ERROR otherwise.
49
+ *
50
+ * The two failures get distinct codes — `missing-spec-doc` for a rendering that
51
+ * was never generated, `stale-spec-doc` for one whose bytes drifted — even
52
+ * though the fix is the same command. `message` is prose and not part of the
53
+ * API, so a code shared between the two would leave a consumer that cares about
54
+ * the difference (a first-time setup versus a document going out of date) with
55
+ * nothing to branch on but the wording.
56
+ *
57
+ * Kept here rather than in the pipeline, and pure, because this is the verdict
58
+ * that makes a generated file safe to commit: a byte comparison, no heuristics.
59
+ */
60
+ export function staleIssue(target, current, fresh) {
61
+ if (current === fresh)
62
+ return undefined;
63
+ const missing = current === undefined;
64
+ const why = missing
65
+ ? `No rendering at "${target.display}".`
66
+ : `"${target.display}" no longer matches the registry.`;
67
+ return {
68
+ level: 'ERROR',
69
+ code: missing ? 'missing-spec-doc' : 'stale-spec-doc',
70
+ file: target.display,
71
+ message: `${why} Regenerate it with: ${target.command}`,
72
+ };
73
+ }
74
+ /**
75
+ * Order ids the way a reader expects: prefix alphabetically, then the number
76
+ * numerically. A plain string sort puts ATX-10 between ATX-1 and ATX-2, which
77
+ * scrambles the document as soon as a registry reaches ten requirements.
78
+ */
79
+ function compareIds(a, b) {
80
+ const split = (id) => {
81
+ const at = id.lastIndexOf('-');
82
+ return [id.slice(0, at), Number(id.slice(at + 1))];
83
+ };
84
+ const [prefixA, numA] = split(a);
85
+ const [prefixB, numB] = split(b);
86
+ // byCodeUnit, not localeCompare: the ordering has to be identical on every
87
+ // machine, or `--check` would flap with the runner's locale.
88
+ if (prefixA !== prefixB)
89
+ return byCodeUnit(prefixA, prefixB);
90
+ return numA - numB;
91
+ }
92
+ /**
93
+ * An index of the whole registry: id + the promise itself. Deliberately carries
94
+ * no counts — a roll-up would conflict in every pair of parallel branches that
95
+ * each add a requirement, for a number the table below already shows.
96
+ */
97
+ function overviewTable(registry, ids) {
98
+ const rows = ids.map((id) => {
99
+ const req = registry[id];
100
+ return `| [${id}](${anchor(id)}) | ${cell(interpolate(req.statement, req.params))} |`;
101
+ });
102
+ return ['| ID | Requirement |', '| --- | --- |', ...rows];
103
+ }
104
+ function section(id, req) {
105
+ const out = [
106
+ `## ${id}`,
107
+ '',
108
+ interpolate(req.statement, req.params),
109
+ '',
110
+ `**Why:** ${req.rationale}`,
111
+ ];
112
+ const params = Object.entries(req.params);
113
+ if (params.length > 0) {
114
+ out.push('', '| Param | Value |', '| --- | --- |', ...params.map(([name, value]) => `| ${code(name)} | ${cell(formatValue(value))} |`));
115
+ }
116
+ if (req.outOfScope.length > 0) {
117
+ out.push('', '**Out of scope**', '', ...req.outOfScope.map((s) => `- ${s}`));
118
+ }
119
+ return out;
120
+ }
121
+ /**
122
+ * Substitute `{param}` placeholders with the actual value, emphasised so a
123
+ * reader can see which words in the sentence are pinned to a single source.
124
+ * An unbound placeholder is left verbatim — `check` already reports it as an
125
+ * `unbound-param` error, and silently swallowing it here would hide that.
126
+ */
127
+ function interpolate(statement, params) {
128
+ // `Object.hasOwn`, never `in`: `'toString' in {}` is true, and an `in` probe
129
+ // would splice `function toString() { [native code] }` into a document that
130
+ // reviewers and audit read as the system's promise.
131
+ return statement.replace(/\{(\w+)\}/g, (whole, name) => Object.hasOwn(params, name) ? `**${plain(params[name])}**` : whole);
132
+ }
133
+ /**
134
+ * A param value as prose, for inline use inside a statement. Escaped, unlike
135
+ * the statement and rationale around it: those are prose an author may want to
136
+ * mark up, a param value is data and must survive verbatim — an unescaped `*`
137
+ * would otherwise break out of the emphasis it is wrapped in.
138
+ */
139
+ function plain(value) {
140
+ const one = (v) => v.replace(/([*_`[\]\\])/g, '\\$1');
141
+ return Array.isArray(value) ? value.map((v) => one(String(v))).join(', ') : one(String(value));
142
+ }
143
+ /** A param value as code, for the params table. */
144
+ function formatValue(value) {
145
+ return Array.isArray(value)
146
+ ? value.map((v) => code(String(v))).join(', ')
147
+ : code(String(value));
148
+ }
149
+ /**
150
+ * Wrap text in a code span that survives backticks in the value: the fence has
151
+ * to be longer than the longest run inside it, and a value touching a backtick
152
+ * at either end needs padding spaces.
153
+ */
154
+ function code(value) {
155
+ const longest = Math.max(0, ...[...value.matchAll(/`+/g)].map((m) => m[0].length));
156
+ const fence = '`'.repeat(longest + 1);
157
+ const pad = value.startsWith('`') || value.endsWith('`') ? ' ' : '';
158
+ return `${fence}${pad}${value}${pad}${fence}`;
159
+ }
160
+ /** Make prose safe inside a table cell: no row-breaking pipes, no newlines. */
161
+ function cell(text) {
162
+ return text.replace(/\s*\n\s*/g, ' ').replace(/\|/g, '\\|');
163
+ }
164
+ /** GitHub/GitLab slug for a `## AUTH-3` heading. */
165
+ function anchor(id) {
166
+ return `#${id.toLowerCase()}`;
167
+ }
168
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/core/render.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExE;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,GAAG,KAAK,EAAE,CA+C/E;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,QAAQ,EAAE,GACf,KAAK,EAAE,CAuBT"}
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAkB,QAAQ,EAAE,MAAM,YAAY,CAAC;AAExF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,UAAU,GAAG,KAAK,EAAE,CAkD/E;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,QAAQ,EAAE,GACf,KAAK,EAAE,CA+BT"}
@@ -35,10 +35,13 @@ export function validateStructure(registry, plan) {
35
35
  }
36
36
  }
37
37
  // unbound-param: statement uses {param} but params does not define it.
38
+ // `Object.hasOwn`, never `in`: `'toString' in {}` is true, so an `in` probe
39
+ // silently accepts placeholders no author declared — and render.ts would then
40
+ // interpolate the prototype method into the document.
38
41
  for (const [id, req] of Object.entries(registry)) {
39
42
  for (const m of req.statement.matchAll(/\{(\w+)\}/g)) {
40
43
  const name = m[1];
41
- if (!(name in req.params)) {
44
+ if (!Object.hasOwn(req.params, name)) {
42
45
  issues.push({
43
46
  level: 'ERROR',
44
47
  code: 'unbound-param',
@@ -61,15 +64,24 @@ export function validateStructure(registry, plan) {
61
64
  export function detectPotentialDrift(registry, plan, refs) {
62
65
  const readsParams = new Set(refs.map((r) => r.reqId));
63
66
  const issues = [];
67
+ // Index the scenarios once. Filtering the whole plan per requirement made
68
+ // this O(requirements x scenarios), which is the product that grows fastest
69
+ // in exactly the repos where `check` needs to stay fast.
70
+ const firstCovering = new Map();
71
+ for (const s of plan.scenarios) {
72
+ if (!firstCovering.has(s.reqId))
73
+ firstCovering.set(s.reqId, s);
74
+ }
64
75
  for (const [id, req] of Object.entries(registry)) {
65
76
  if (Object.keys(req.params).length === 0)
66
77
  continue;
67
- const covering = plan.scenarios.filter((s) => s.reqId === id);
68
- if (covering.length === 0 || readsParams.has(id))
78
+ if (readsParams.has(id))
69
79
  continue;
70
80
  // Anchor the hint at the first covering scenario so it stays clickable,
71
81
  // even though the verdict is now about the requirement as a whole.
72
- const at = covering[0];
82
+ const at = firstCovering.get(id);
83
+ if (!at)
84
+ continue;
73
85
  issues.push({
74
86
  level: 'INFO',
75
87
  code: 'possible-drift',
@@ -1 +1 @@
1
- {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validator.js","sourceRoot":"","sources":["../../src/core/validator.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@am_shork/attest",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
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
6
  "engines": {
@@ -71,6 +71,8 @@
71
71
  "attest": "node bin/attest.js",
72
72
  "check:self": "node bin/attest.js check self",
73
73
  "verify:self": "node bin/attest.js verify self",
74
- "cover:self": "node bin/attest.js cover self"
74
+ "cover:self": "node bin/attest.js cover self",
75
+ "render:self": "node bin/attest.js render self --out self/requirements/SPEC.md",
76
+ "render:self:check": "node bin/attest.js render self --out self/requirements/SPEC.md --check"
75
77
  }
76
78
  }