hypomnema 1.4.2 → 1.5.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/commands/audit.md +1 -1
- package/commands/crystallize.md +8 -8
- package/commands/doctor.md +1 -1
- package/commands/feedback.md +2 -2
- package/commands/graph.md +1 -1
- package/commands/ingest.md +1 -1
- package/commands/init.md +2 -2
- package/commands/lint.md +1 -1
- package/commands/query.md +1 -1
- package/commands/rename.md +1 -1
- package/commands/resume.md +1 -1
- package/commands/stats.md +1 -1
- package/commands/uninstall.md +4 -2
- package/commands/upgrade.md +1 -1
- package/commands/verify.md +1 -1
- package/docs/ARCHITECTURE.md +2 -2
- package/docs/CONTRIBUTING.md +8 -7
- package/hooks/hypo-auto-commit.mjs +2 -2
- package/hooks/hypo-auto-minimal-crystallize.mjs +7 -7
- package/hooks/hypo-compact-guard.mjs +2 -2
- package/hooks/hypo-cwd-change.mjs +27 -37
- package/hooks/hypo-personal-check.mjs +37 -22
- package/hooks/hypo-session-end.mjs +1 -1
- package/hooks/hypo-session-record.mjs +2 -2
- package/hooks/hypo-session-start.mjs +34 -40
- package/hooks/hypo-shared.mjs +271 -71
- package/hooks/version-check.mjs +1 -1
- package/package.json +5 -1
- package/scripts/check-tracker-ids.mjs +69 -31
- package/scripts/crystallize.mjs +82 -38
- package/scripts/doctor.mjs +7 -7
- package/scripts/feedback-sync.mjs +5 -5
- package/scripts/feedback.mjs +9 -10
- package/scripts/init.mjs +7 -7
- package/scripts/lib/check-tracker-ids.mjs +90 -32
- package/scripts/lib/design-history-stale.mjs +1 -1
- package/scripts/lib/extensions.mjs +7 -7
- package/scripts/lib/failure-type.mjs +1 -1
- package/scripts/lib/feedback-scope.mjs +1 -1
- package/scripts/lib/project-create.mjs +2 -2
- package/scripts/lib/template-schema-version.mjs +1 -1
- package/scripts/lib/wd-match.mjs +181 -0
- package/scripts/lib/wikilink.mjs +1 -1
- package/scripts/lint.mjs +5 -5
- package/scripts/rename.mjs +1 -1
- package/scripts/resume.mjs +20 -22
- package/scripts/session-audit.mjs +1 -1
- package/scripts/stats.mjs +3 -3
- package/scripts/uninstall.mjs +3 -3
- package/scripts/upgrade.mjs +17 -18
- package/skills/crystallize/SKILL.md +7 -7
- package/skills/graph/SKILL.md +2 -2
- package/skills/ingest/SKILL.md +4 -4
- package/skills/lint/SKILL.md +2 -2
- package/skills/query/SKILL.md +2 -2
- package/skills/verify/SKILL.md +2 -2
- package/templates/SCHEMA.md +1 -1
- package/templates/hypo-config.md +1 -1
- package/templates/hypo-guide.md +20 -14
- package/templates/projects/_template/hot.md +1 -1
- package/scripts/fix-status-verify.mjs +0 -256
- package/scripts/lib/adr-corpus.mjs +0 -79
- package/scripts/lib/fix-manifest.mjs +0 -109
- package/scripts/lib/fix-status-verify.mjs +0 -439
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* fix-status-verify (CLI) — verify fix→test linkage + ADR-line evidence
|
|
4
|
-
* against wiki spec claims.
|
|
5
|
-
*
|
|
6
|
-
* Phase 1: test-green half (anchors × spec status × runner results).
|
|
7
|
-
* Phase 2 (A-sot): manifest validation + manifest↔anchor drift + ADR core
|
|
8
|
-
* decision grep against the production corpus. See scripts/lib/fix-manifest.mjs
|
|
9
|
-
* and scripts/lib/fix-status-verify.mjs headers.
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* node scripts/fix-status-verify.mjs [--hypo-dir <path>]
|
|
13
|
-
* [--spec <path>]
|
|
14
|
-
* [--runner <path>]
|
|
15
|
-
* [--test-command "<cmd>"]
|
|
16
|
-
* [--json]
|
|
17
|
-
*
|
|
18
|
-
* Exit 0 if no error-level findings, 1 otherwise.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { readFileSync, existsSync } from 'node:fs';
|
|
22
|
-
import { join, dirname, resolve } from 'node:path';
|
|
23
|
-
import { homedir } from 'node:os';
|
|
24
|
-
import { spawnSync } from 'node:child_process';
|
|
25
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
26
|
-
import {
|
|
27
|
-
parseAnchors,
|
|
28
|
-
parseStatus,
|
|
29
|
-
parseRunnerOutput,
|
|
30
|
-
verifyMatrix,
|
|
31
|
-
isReferenceStub,
|
|
32
|
-
validateManifest,
|
|
33
|
-
checkManifestCoverage,
|
|
34
|
-
checkAdrLines,
|
|
35
|
-
FIX_MANIFEST,
|
|
36
|
-
NO_ADR,
|
|
37
|
-
} from './lib/fix-status-verify.mjs';
|
|
38
|
-
import { buildCorpusSearch } from './lib/adr-corpus.mjs';
|
|
39
|
-
|
|
40
|
-
const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
41
|
-
|
|
42
|
-
// Production-code corpus for the ADR-line grep (spec §A amendment 2026-06-07:
|
|
43
|
-
// templates/ ships via npm `files`, so prompt-driven fixes are verifiable).
|
|
44
|
-
const CORPUS_DIRS = ['scripts', 'hooks', 'commands', 'skills', 'templates'];
|
|
45
|
-
// MUST exclude the manifest itself — it holds every adrKeyLine as a literal and
|
|
46
|
-
// would self-match, making ADR_LINE_MISSING impossible to ever fire.
|
|
47
|
-
const CORPUS_EXCLUDE = ['scripts/lib/fix-manifest.mjs'];
|
|
48
|
-
|
|
49
|
-
function parseArgs(argv) {
|
|
50
|
-
const out = {
|
|
51
|
-
hypoDir: process.env.HYPO_DIR || join(homedir(), 'hypomnema'),
|
|
52
|
-
spec: null,
|
|
53
|
-
runner: join(REPO, 'tests/runner.mjs'),
|
|
54
|
-
testCommand: 'npm test',
|
|
55
|
-
json: false,
|
|
56
|
-
manifest: null,
|
|
57
|
-
};
|
|
58
|
-
for (let i = 0; i < argv.length; i++) {
|
|
59
|
-
const a = argv[i];
|
|
60
|
-
if (a === '--hypo-dir') out.hypoDir = argv[++i];
|
|
61
|
-
else if (a === '--spec') out.spec = argv[++i];
|
|
62
|
-
else if (a === '--runner') out.runner = argv[++i];
|
|
63
|
-
else if (a === '--test-command') out.testCommand = argv[++i];
|
|
64
|
-
else if (a === '--manifest') out.manifest = argv[++i];
|
|
65
|
-
else if (a === '--json') out.json = true;
|
|
66
|
-
else if (a === '--help' || a === '-h') {
|
|
67
|
-
printHelp();
|
|
68
|
-
process.exit(0);
|
|
69
|
-
} else {
|
|
70
|
-
console.error(`unknown arg: ${a}`);
|
|
71
|
-
process.exit(2);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (!out.spec) {
|
|
75
|
-
out.spec = join(out.hypoDir, 'projects/hypomnema/spec-v1.2.md');
|
|
76
|
-
}
|
|
77
|
-
return out;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function printHelp() {
|
|
81
|
-
console.log(
|
|
82
|
-
[
|
|
83
|
-
'fix-status-verify — Phase 1 (test-green half) of learned_behavior #6',
|
|
84
|
-
'',
|
|
85
|
-
'Options:',
|
|
86
|
-
' --hypo-dir <path> Wiki root (default: $HYPO_DIR or ~/hypomnema)',
|
|
87
|
-
' --spec <path> Override spec-v1.2.md path',
|
|
88
|
-
' --runner <path> Override tests/runner.mjs path',
|
|
89
|
-
' --test-command "<cmd>" Test invocation (default: "npm test")',
|
|
90
|
-
' --json Emit machine-readable JSON report',
|
|
91
|
-
'',
|
|
92
|
-
'Exit 0 if no error findings, 1 otherwise.',
|
|
93
|
-
'',
|
|
94
|
-
'NOTE: The default --spec is a `type: reference` redirect stub (the real',
|
|
95
|
-
'spec moved to archive/). Running without --spec fails with STUB_SPEC by',
|
|
96
|
-
'design — pass --spec <real spec> to verify against actual claims.',
|
|
97
|
-
'',
|
|
98
|
-
'Phase 2 (A-sot): also greps each manifest adrKeyLine against the',
|
|
99
|
-
'production corpus (scripts/ hooks/ commands/ skills/ templates/) and',
|
|
100
|
-
'checks manifest↔anchor drift. NO_ADR rows skip the grep (test-green only).',
|
|
101
|
-
].join('\n'),
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function runTests(testCommand) {
|
|
106
|
-
// Parse simple command (no shell metacharacters supported in args; this is
|
|
107
|
-
// a maintainer tool, not a security boundary).
|
|
108
|
-
const parts = testCommand.split(/\s+/).filter(Boolean);
|
|
109
|
-
const [cmd, ...args] = parts;
|
|
110
|
-
const result = spawnSync(cmd, args, {
|
|
111
|
-
cwd: REPO,
|
|
112
|
-
encoding: 'utf-8',
|
|
113
|
-
env: { ...process.env, FORCE_COLOR: '0' },
|
|
114
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
stdout: result.stdout || '',
|
|
118
|
-
stderr: result.stderr || '',
|
|
119
|
-
exitCode: result.status,
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function formatFinding(f) {
|
|
124
|
-
const icon = f.level === 'error' ? '✗' : '⚠';
|
|
125
|
-
// Some findings are not tied to a specific fix # (STUB_SPEC,
|
|
126
|
-
// TEST_RUN_NONZERO_EXIT). Only render the `fix #N` segment when present so
|
|
127
|
-
// they don't print `fix #undefined`.
|
|
128
|
-
const ref = f.fixNum != null ? ` fix #${f.fixNum}` : '';
|
|
129
|
-
return ` ${icon} [${f.class}]${ref}` + (f.testName ? ` (${f.testName})` : '') + `: ${f.detail}`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function main() {
|
|
133
|
-
const opts = parseArgs(process.argv.slice(2));
|
|
134
|
-
|
|
135
|
-
// Manifest source: built-in code constant by default (ADR 0036). --manifest
|
|
136
|
-
// <path.mjs> overrides for tests, which inject a fixture manifest matching
|
|
137
|
-
// their synthetic fixes so the real manifest does not couple to fixtures.
|
|
138
|
-
let manifest = FIX_MANIFEST;
|
|
139
|
-
if (opts.manifest) {
|
|
140
|
-
if (!existsSync(opts.manifest)) {
|
|
141
|
-
console.error(`manifest not found: ${opts.manifest}`);
|
|
142
|
-
process.exit(2);
|
|
143
|
-
}
|
|
144
|
-
const mod = await import(pathToFileURL(resolve(opts.manifest)).href);
|
|
145
|
-
manifest = mod.FIX_MANIFEST;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (!existsSync(opts.spec)) {
|
|
149
|
-
console.error(`spec not found: ${opts.spec}`);
|
|
150
|
-
console.error('hint: pass --hypo-dir <path> or set $HYPO_DIR');
|
|
151
|
-
process.exit(2);
|
|
152
|
-
}
|
|
153
|
-
if (!existsSync(opts.runner)) {
|
|
154
|
-
console.error(`runner not found: ${opts.runner}`);
|
|
155
|
-
process.exit(2);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const specText = readFileSync(opts.spec, 'utf-8');
|
|
159
|
-
const runnerText = readFileSync(opts.runner, 'utf-8');
|
|
160
|
-
|
|
161
|
-
const anchors = parseAnchors(runnerText);
|
|
162
|
-
const status = parseStatus(specText);
|
|
163
|
-
const specIsStub = isReferenceStub(specText);
|
|
164
|
-
|
|
165
|
-
if (!opts.json) {
|
|
166
|
-
console.log(`fix-status-verify (Phase 1)`);
|
|
167
|
-
console.log(` spec: ${opts.spec}`);
|
|
168
|
-
console.log(` runner: ${opts.runner}`);
|
|
169
|
-
console.log(` ${status.size} positive status claim(s), ${anchors.size} anchor(s)`);
|
|
170
|
-
console.log(` running: ${opts.testCommand}`);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
const testRun = runTests(opts.testCommand);
|
|
174
|
-
const testResults = parseRunnerOutput(testRun.stdout + '\n' + testRun.stderr);
|
|
175
|
-
|
|
176
|
-
if (!opts.json) {
|
|
177
|
-
const passes = [...testResults.values()].filter((v) => v === 'pass').length;
|
|
178
|
-
const fails = [...testResults.values()].filter((v) => v === 'fail').length;
|
|
179
|
-
console.log(` test run: ${passes} pass, ${fails} fail (exit ${testRun.exitCode})`);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
const matrixResult = verifyMatrix({ anchors, status, testResults, specIsStub });
|
|
183
|
-
const findings = [...matrixResult.findings];
|
|
184
|
-
|
|
185
|
-
// Phase 2 (A-sot): manifest validation + ADR-line grep. validateManifest and
|
|
186
|
-
// checkAdrLines are spec-independent (manifest/code health) and run always;
|
|
187
|
-
// checkManifestCoverage keys off the spec status (a no-op under STUB_SPEC,
|
|
188
|
-
// where status is empty).
|
|
189
|
-
const needsCorpus = manifest.some((r) => r.adrKeyLine !== NO_ADR);
|
|
190
|
-
const adrSearch = needsCorpus
|
|
191
|
-
? buildCorpusSearch({ repoRoot: REPO, includeDirs: CORPUS_DIRS, excludePaths: CORPUS_EXCLUDE })
|
|
192
|
-
: () => false;
|
|
193
|
-
const adrExists = (adrPath) => existsSync(join(opts.hypoDir, 'projects/hypomnema', adrPath));
|
|
194
|
-
findings.push(...validateManifest(manifest));
|
|
195
|
-
findings.push(...checkManifestCoverage({ manifest, anchors, status }));
|
|
196
|
-
findings.push(...checkAdrLines({ manifest, searchFn: adrSearch, adrExistsFn: adrExists }));
|
|
197
|
-
|
|
198
|
-
// CLI-level error: if the test command itself exited nonzero, the test run
|
|
199
|
-
// is not green even if the anchored tests happen to all pass in the parsed
|
|
200
|
-
// output. Surface as a synthetic error finding so `ok` flips false.
|
|
201
|
-
if (testRun.exitCode !== 0) {
|
|
202
|
-
findings.push({
|
|
203
|
-
level: 'error',
|
|
204
|
-
class: 'TEST_RUN_NONZERO_EXIT',
|
|
205
|
-
detail: `test command "${opts.testCommand}" exited ${testRun.exitCode}`,
|
|
206
|
-
exitCode: testRun.exitCode,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
209
|
-
const ok = !findings.some((f) => f.level === 'error') && testRun.exitCode === 0;
|
|
210
|
-
|
|
211
|
-
const MANDATORY_NOTE =
|
|
212
|
-
'test-linkage + green + ADR-line grep (Phase 2): manifest evidence checked against production corpus';
|
|
213
|
-
|
|
214
|
-
if (opts.json) {
|
|
215
|
-
console.log(
|
|
216
|
-
JSON.stringify(
|
|
217
|
-
{
|
|
218
|
-
ok,
|
|
219
|
-
spec: opts.spec,
|
|
220
|
-
runner: opts.runner,
|
|
221
|
-
statusClaims: status.size,
|
|
222
|
-
anchorCount: anchors.size,
|
|
223
|
-
testsRan: testResults.size,
|
|
224
|
-
testExitCode: testRun.exitCode,
|
|
225
|
-
findings,
|
|
226
|
-
note: MANDATORY_NOTE,
|
|
227
|
-
},
|
|
228
|
-
null,
|
|
229
|
-
2,
|
|
230
|
-
),
|
|
231
|
-
);
|
|
232
|
-
} else {
|
|
233
|
-
const errors = findings.filter((f) => f.level === 'error');
|
|
234
|
-
const warns = findings.filter((f) => f.level === 'warn');
|
|
235
|
-
if (errors.length === 0 && warns.length === 0) {
|
|
236
|
-
console.log(` ✓ all ${status.size} claimed-merged fix(es) verified`);
|
|
237
|
-
} else {
|
|
238
|
-
if (errors.length) {
|
|
239
|
-
console.log(`\nerrors (${errors.length}):`);
|
|
240
|
-
for (const f of errors) console.log(formatFinding(f));
|
|
241
|
-
}
|
|
242
|
-
if (warns.length) {
|
|
243
|
-
console.log(`\nwarnings (${warns.length}):`);
|
|
244
|
-
for (const f of warns) console.log(formatFinding(f));
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
console.log(`\n(${MANDATORY_NOTE})`);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
process.exit(ok ? 0 : 1);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
main().catch((e) => {
|
|
254
|
-
console.error(e);
|
|
255
|
-
process.exit(2);
|
|
256
|
-
});
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* adr-corpus — fs-backed production-code corpus search for the ADR-line grep.
|
|
3
|
-
*
|
|
4
|
-
* Kept separate from the pure verifier (scripts/lib/fix-status-verify.mjs) so
|
|
5
|
-
* that layer stays IO-free and unit-testable with injected searchFns. This
|
|
6
|
-
* module is itself testable against real temp directories.
|
|
7
|
-
*
|
|
8
|
-
* CRITICAL (self-match): the manifest module (scripts/lib/fix-manifest.mjs)
|
|
9
|
-
* lives *inside* the scripts/ corpus and holds every adrKeyLine as a literal.
|
|
10
|
-
* If it were scanned, every line would self-match and ADR_LINE_MISSING could
|
|
11
|
-
* never fire — the gate would be silently vacuous. The builder therefore
|
|
12
|
-
* excludes caller-supplied paths, resolved absolute, BEFORE reading any file.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
16
|
-
import { join, resolve } from 'node:path';
|
|
17
|
-
|
|
18
|
-
const DEFAULT_EXTENSIONS = ['.mjs', '.js', '.md', '.json', '.cjs'];
|
|
19
|
-
|
|
20
|
-
function* walk(dir, excludeAbs, extensions) {
|
|
21
|
-
let entries;
|
|
22
|
-
try {
|
|
23
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
24
|
-
} catch {
|
|
25
|
-
return; // missing corpus dir is not fatal — other dirs may exist
|
|
26
|
-
}
|
|
27
|
-
for (const ent of entries) {
|
|
28
|
-
const full = join(dir, ent.name);
|
|
29
|
-
if (excludeAbs.has(resolve(full))) continue;
|
|
30
|
-
if (ent.isDirectory()) {
|
|
31
|
-
if (ent.name === 'node_modules' || ent.name === '.git') continue;
|
|
32
|
-
yield* walk(full, excludeAbs, extensions);
|
|
33
|
-
} else if (ent.isFile()) {
|
|
34
|
-
if (extensions.some((e) => ent.name.endsWith(e))) yield full;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Build a fixed-string corpus search function.
|
|
41
|
-
*
|
|
42
|
-
* buildCorpusSearch({ repoRoot, includeDirs, excludePaths, extensions })
|
|
43
|
-
* → (literal:string) => boolean
|
|
44
|
-
*
|
|
45
|
-
* - includeDirs / excludePaths are resolved relative to repoRoot.
|
|
46
|
-
* - excludePaths are matched by resolved absolute path (handles symlinks of the
|
|
47
|
-
* caller-supplied path consistently with the walk's resolve()).
|
|
48
|
-
* - search is case-sensitive String.includes (fixed string, not regex).
|
|
49
|
-
*
|
|
50
|
-
* Files are read once and cached so repeated searches (one per manifest row)
|
|
51
|
-
* do not re-walk the tree.
|
|
52
|
-
*/
|
|
53
|
-
export function buildCorpusSearch({
|
|
54
|
-
repoRoot,
|
|
55
|
-
includeDirs,
|
|
56
|
-
excludePaths = [],
|
|
57
|
-
extensions = DEFAULT_EXTENSIONS,
|
|
58
|
-
}) {
|
|
59
|
-
const excludeAbs = new Set(excludePaths.map((p) => resolve(repoRoot, p)));
|
|
60
|
-
const contents = [];
|
|
61
|
-
for (const dir of includeDirs) {
|
|
62
|
-
const abs = resolve(repoRoot, dir);
|
|
63
|
-
let isDir = false;
|
|
64
|
-
try {
|
|
65
|
-
isDir = statSync(abs).isDirectory();
|
|
66
|
-
} catch {
|
|
67
|
-
isDir = false;
|
|
68
|
-
}
|
|
69
|
-
if (!isDir) continue;
|
|
70
|
-
for (const file of walk(abs, excludeAbs, extensions)) {
|
|
71
|
-
try {
|
|
72
|
-
contents.push(readFileSync(file, 'utf-8'));
|
|
73
|
-
} catch {
|
|
74
|
-
/* unreadable file — skip */
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return (literal) => contents.some((text) => text.includes(literal));
|
|
79
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* fix-manifest — evidence mapping for claimed-merged fixes (Phase 2, A-sot).
|
|
3
|
-
*
|
|
4
|
-
* ADR 0036: this module is the *evidence* SoT (fix → test + ADR-line), NOT the
|
|
5
|
-
* *status* SoT. "Is fix N merged?" is answered solely by the wiki spec
|
|
6
|
-
* (parseStatus). A manifest row says "if the spec claims N merged, here is the
|
|
7
|
-
* test that proves the behavior and the production-code line that proves the
|
|
8
|
-
* ADR's core decision shipped."
|
|
9
|
-
*
|
|
10
|
-
* Shape (ADR 0036 decision 2 — NO `status` field):
|
|
11
|
-
* { fixId:number, testNames:string[], adrPath:string|null, adrKeyLine:string }
|
|
12
|
-
*
|
|
13
|
-
* - testNames: MUST set-equal the `// @fix #N:` anchors in tests/runner.mjs
|
|
14
|
-
* (drift is an error — MANIFEST_TEST_DRIFT). Multiple anchors → multiple
|
|
15
|
-
* names. The NO_AUTO_TEST sentinel is the ONLY allowed lone entry; it may
|
|
16
|
-
* not be mixed with real test names.
|
|
17
|
-
* - adrPath: path (relative to the wiki root) of the ADR whose core decision
|
|
18
|
-
* this fix implements. `null` iff adrKeyLine is the NO_ADR sentinel.
|
|
19
|
-
* - adrKeyLine: a maintainer-curated literal that embodies the fix's shipped
|
|
20
|
-
* decision and exists verbatim in production code (scripts/ hooks/ commands/
|
|
21
|
-
* skills/ templates/). Verified by fixed-string grep — 0 hits is
|
|
22
|
-
* ADR_LINE_MISSING. The NO_ADR sentinel exempts a fix that has no ADR (small
|
|
23
|
-
* / doctor fixes); the test-green check still applies. NO_ADR is NOT for
|
|
24
|
-
* fixes that have an ADR but whose evidence lives outside the corpus.
|
|
25
|
-
*
|
|
26
|
-
* Coverage contract: every fix that is BOTH claimed-merged in the spec AND
|
|
27
|
-
* anchored in the runner must have exactly one row here (MANIFEST_MISSING_ROW
|
|
28
|
-
* is an error). Fixes anchored but not claimed (e.g. #18) are ORPHAN_ANCHOR
|
|
29
|
-
* warnings and need no row.
|
|
30
|
-
*
|
|
31
|
-
* Corpus note (spec §A amendment, 2026-06-07): the ADR-line grep corpus is
|
|
32
|
-
* scripts/ hooks/ commands/ skills/ AND templates/. templates/ ships via npm
|
|
33
|
-
* `files`, so prompt-driven fixes whose decision is installed as template text
|
|
34
|
-
* (e.g. #20 proactive close offer) are honestly verifiable there.
|
|
35
|
-
*/
|
|
36
|
-
|
|
37
|
-
export const NO_ADR = 'NO_ADR';
|
|
38
|
-
export const NO_AUTO_TEST = 'NO_AUTO_TEST';
|
|
39
|
-
|
|
40
|
-
export const FIX_MANIFEST = [
|
|
41
|
-
{
|
|
42
|
-
fixId: 15,
|
|
43
|
-
testNames: ['all type-conditional fields present → green'],
|
|
44
|
-
adrPath: 'decisions/0030-hypoignore-enforce-all-injection-hooks.md',
|
|
45
|
-
adrKeyLine: 'isIgnored(path, HYPO_DIR, patterns)',
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
fixId: 17,
|
|
49
|
-
testNames: [
|
|
50
|
-
'5 mandatory memory files fresh → suppressOutput:true',
|
|
51
|
-
'project hot.md not updated today → block, reason names the file',
|
|
52
|
-
'open-questions.md absent/stale → still passes (conditional, not gated)',
|
|
53
|
-
],
|
|
54
|
-
adrPath: 'decisions/0022-session-close-ux-automation.md',
|
|
55
|
-
adrKeyLine: 'sessionCloseFileStatus',
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
// Behavioral / prompt-driven: no automated test, evidence is the installed
|
|
59
|
-
// template prompt (templates/hypo-guide.md). Has an ADR (0022), so NOT
|
|
60
|
-
// NO_ADR — the adrKeyLine greps the shipped template text.
|
|
61
|
-
fixId: 20,
|
|
62
|
-
testNames: [NO_AUTO_TEST],
|
|
63
|
-
adrPath: 'decisions/0022-session-close-ux-automation.md',
|
|
64
|
-
adrKeyLine: '이 작업이 마무리되었나요? 세션을 정리(crystallize)할까요?',
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
fixId: 25,
|
|
68
|
-
testNames: [
|
|
69
|
-
'replay-compact-guard-detects-slash-clear: /clear with incomplete wiki → WIKI_AUTOCLOSE',
|
|
70
|
-
],
|
|
71
|
-
adrPath: 'decisions/0022-session-close-ux-automation.md',
|
|
72
|
-
adrKeyLine: '[WIKI_AUTOCLOSE]',
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
// Removal fix (capacity bypass deleted). The shipped evidence is the
|
|
76
|
-
// deliberate removal-marker comment + the negative-control test.
|
|
77
|
-
fixId: 26,
|
|
78
|
-
testNames: [
|
|
79
|
-
'replay-personal-check-bypass-order: wiki-context-critical.json does NOT bypass (negative control)',
|
|
80
|
-
],
|
|
81
|
-
adrPath: 'decisions/0022-session-close-ux-automation.md',
|
|
82
|
-
adrKeyLine: 'Capacity bypass (≥90%) REMOVED',
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
fixId: 27,
|
|
86
|
-
testNames: [
|
|
87
|
-
'replay-auto-minimal-crystallize-on-incomplete-close: mutating + no marker + close-intent → block',
|
|
88
|
-
'replay-auto-minimal-crystallize-on-incomplete-close: valid marker → continue (even with close-intent)',
|
|
89
|
-
],
|
|
90
|
-
adrPath: 'decisions/0022-session-close-ux-automation.md',
|
|
91
|
-
adrKeyLine: 'The hook NEVER writes the marker',
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
// No dedicated ADR (schema-vocab tag validation); test-green only.
|
|
95
|
-
fixId: 36,
|
|
96
|
-
testNames: ['PascalCase tag → error', 'unknown tag (not in vocab) → error'],
|
|
97
|
-
adrPath: null,
|
|
98
|
-
adrKeyLine: NO_ADR,
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
fixId: 38,
|
|
102
|
-
testNames: [
|
|
103
|
-
'clean-wiki payload → ok:true, new entries appended (apply dedup is exact-entry, not date-based)',
|
|
104
|
-
'idempotent: re-running same payload produces no new bytes (file mtimes unchanged)',
|
|
105
|
-
],
|
|
106
|
-
adrPath: 'decisions/0029-crystallize-session-close-depth-expansion.md',
|
|
107
|
-
adrKeyLine: 'exact-entry append dedup',
|
|
108
|
-
},
|
|
109
|
-
];
|