etymd 0.7.0 → 0.8.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 +15 -0
- package/dist/{approve-TXF3MA3G.js → approve-MYOIDCAM.js} +2 -2
- package/dist/audit-HO56CZNV.js +12 -0
- package/dist/{brief-QUYHRWH6.js → brief-XI4RCNYS.js} +2 -2
- package/dist/{chunk-OGIHOTLP.js → chunk-72Y2V7VF.js} +1 -1
- package/dist/{chunk-XJMN2HMH.js → chunk-CIRNG736.js} +1 -1
- package/dist/{chunk-K7QBTANO.js → chunk-D2RI3CCT.js} +4 -3
- package/dist/{chunk-OKU3HXIH.js → chunk-JSUYV26K.js} +1 -1
- package/dist/{chunk-7LVCGCHB.js → chunk-KBYEWHYX.js} +1 -1
- package/dist/{chunk-LXEYYITD.js → chunk-PRIH2NZF.js} +22 -13
- package/dist/{chunk-KTIEYKFK.js → chunk-WENIJMVV.js} +20 -2
- package/dist/cli.js +14 -14
- package/dist/{config-UJRSCDMD.js → config-7WCVZCM6.js} +1 -1
- package/dist/{context-ZEUI5ESZ.js → context-5ANOP2IJ.js} +2 -2
- package/dist/{doctor-2BU6WCI6.js → doctor-ZAAEVVB4.js} +6 -6
- package/dist/{fleet-3UQ65X4Y.js → fleet-6U6XW72I.js} +8 -8
- package/dist/{gates-MYNOZCON.js → gates-N25LGIR2.js} +38 -9
- package/dist/generate-IPY5QIR5.js +6 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.js +45 -15
- package/dist/{init-52PR24Q4.js → init-SSAJVX52.js} +4 -4
- package/dist/{scan-GSDICQCX.js → scan-H2IPH4C3.js} +2 -2
- package/dist/scan-LVJOAOUJ.js +5 -0
- package/package.json +1 -1
- package/dist/audit-354X7QN6.js +0 -12
- package/dist/generate-UTKQ4GE6.js +0 -6
- package/dist/scan-M6RKDNYR.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f2c1bfd: `etymd gates` no longer writes a gate that cannot fail. It derives whether any risk-tier finding is
|
|
8
|
+
reachable in the repo (a package manifest to contradict, a state document to fall behind); where
|
|
9
|
+
none is, the generated hook drops to `--fail-on gap` and the output says so and why. A `failOn`
|
|
10
|
+
recorded in `.etymd/config.json` is never adjusted, and `gates` now states which tier it wrote and
|
|
11
|
+
where that tier came from — the config key was previously unmentioned anywhere in its output.
|
|
12
|
+
- 743ad44: Declared-field checks are forward-only from the marker's position, not from the file. A decisions
|
|
13
|
+
file that declares required entry fields mid-life no longer demands them from the entries appended
|
|
14
|
+
above the marker — an append-only record cannot backfill them. The built-in `Scope:` check shares
|
|
15
|
+
the same gate; `Revisit:` keeps whole-file reach, since it fires only where the entry already wrote
|
|
16
|
+
a date down. Entries exempt by position are counted and named in the lens disclosures.
|
|
17
|
+
|
|
3
18
|
## 0.7.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { section, theme, print, renderBaselineDrift, glyph } from './chunk-5BVKFJWM.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import { VERSION } from './chunk-
|
|
3
|
+
import { scanProject } from './chunk-CIRNG736.js';
|
|
4
|
+
import { VERSION } from './chunk-72Y2V7VF.js';
|
|
5
5
|
import { readBaseline, summarizeBaselineDrift, isDriftEmpty, writeBaseline, deriveProfile } from './chunk-C7LBUFNU.js';
|
|
6
6
|
import { PACK_VERSION } from './chunk-6DUDIVIC.js';
|
|
7
7
|
import './chunk-IV3FYVTS.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { run } from './chunk-KBYEWHYX.js';
|
|
3
|
+
import './chunk-PRIH2NZF.js';
|
|
4
|
+
import './chunk-JSUYV26K.js';
|
|
5
|
+
import './chunk-OAFYLBVG.js';
|
|
6
|
+
import './chunk-D2RI3CCT.js';
|
|
7
|
+
import './chunk-5BVKFJWM.js';
|
|
8
|
+
import './chunk-CIRNG736.js';
|
|
9
|
+
import './chunk-72Y2V7VF.js';
|
|
10
|
+
import './chunk-C7LBUFNU.js';
|
|
11
|
+
import './chunk-6DUDIVIC.js';
|
|
12
|
+
import './chunk-IV3FYVTS.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, theme } from './chunk-5BVKFJWM.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-CIRNG736.js';
|
|
4
|
+
import './chunk-72Y2V7VF.js';
|
|
5
5
|
import { ETYMD_DIR } from './chunk-C7LBUFNU.js';
|
|
6
6
|
import './chunk-6DUDIVIC.js';
|
|
7
7
|
import './chunk-IV3FYVTS.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-72Y2V7VF.js';
|
|
3
3
|
import { PACK_VERSION } from './chunk-6DUDIVIC.js';
|
|
4
4
|
import { normalizeRelPath, isDirectory, readJson, git, matchesAnyGlob, pathExists, readText } from './chunk-IV3FYVTS.js';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -33,7 +33,7 @@ async function readConfig(root) {
|
|
|
33
33
|
const target = configPath(root);
|
|
34
34
|
const problems = [];
|
|
35
35
|
if (!await pathExists(target)) {
|
|
36
|
-
return { config: DEFAULT_CONFIG, present: false, problems };
|
|
36
|
+
return { config: DEFAULT_CONFIG, present: false, problems, explicit: { gatesFailOn: false } };
|
|
37
37
|
}
|
|
38
38
|
const raw = await readText(target);
|
|
39
39
|
let parsed;
|
|
@@ -43,11 +43,11 @@ async function readConfig(root) {
|
|
|
43
43
|
problems.push(
|
|
44
44
|
`${CONFIG_FILE} exists but is not valid JSON (${err instanceof Error ? err.message : String(err)}) \u2014 defaults used.`
|
|
45
45
|
);
|
|
46
|
-
return { config: DEFAULT_CONFIG, present: true, problems };
|
|
46
|
+
return { config: DEFAULT_CONFIG, present: true, problems, explicit: { gatesFailOn: false } };
|
|
47
47
|
}
|
|
48
48
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
49
49
|
problems.push(`${CONFIG_FILE} must contain a JSON object \u2014 defaults used.`);
|
|
50
|
-
return { config: DEFAULT_CONFIG, present: true, problems };
|
|
50
|
+
return { config: DEFAULT_CONFIG, present: true, problems, explicit: { gatesFailOn: false } };
|
|
51
51
|
}
|
|
52
52
|
const obj = parsed;
|
|
53
53
|
const instructions = obj.instructions ?? {};
|
|
@@ -73,6 +73,7 @@ async function readConfig(root) {
|
|
|
73
73
|
return {
|
|
74
74
|
present: true,
|
|
75
75
|
problems,
|
|
76
|
+
explicit: { gatesFailOn: failOn !== void 0 },
|
|
76
77
|
config: {
|
|
77
78
|
instructions: {
|
|
78
79
|
include: readGlobList(instructions.include, "instructions.include", problems) ?? DEFAULT_CONFIG.instructions.include,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
2
|
+
import { DEFAULT_CONFIG } from './chunk-D2RI3CCT.js';
|
|
3
3
|
import { readText, wordCount, approxTokens, pathExists } from './chunk-IV3FYVTS.js';
|
|
4
4
|
import { promises } from 'node:fs';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-
|
|
2
|
+
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-PRIH2NZF.js';
|
|
3
3
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-5BVKFJWM.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/audit.ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { measureContext } from './chunk-
|
|
2
|
+
import { measureContext } from './chunk-JSUYV26K.js';
|
|
3
3
|
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-OAFYLBVG.js';
|
|
4
|
-
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-
|
|
5
|
-
import { scanProject, expandFileGlobs } from './chunk-
|
|
4
|
+
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-D2RI3CCT.js';
|
|
5
|
+
import { scanProject, expandFileGlobs } from './chunk-CIRNG736.js';
|
|
6
6
|
import { ETYMD_DIR, writeCachedFacts, readBaseline, deriveProfile, baselineCarriesMachinePath, BASELINE_FILE } from './chunk-C7LBUFNU.js';
|
|
7
7
|
import { PACK_VERSION } from './chunk-6DUDIVIC.js';
|
|
8
8
|
import { pathExists, readJson, git, readText, isDirectory, matchesAnyGlob, isCiEnvironment, normalizeRelPath, isExecutable } from './chunk-IV3FYVTS.js';
|
|
@@ -1187,6 +1187,7 @@ function parseDecisionsFormat(text) {
|
|
|
1187
1187
|
if (!m) return null;
|
|
1188
1188
|
const problems = [];
|
|
1189
1189
|
const fields = [];
|
|
1190
|
+
const offset = m.index ?? 0;
|
|
1190
1191
|
const version = Number(m[1]);
|
|
1191
1192
|
if (version !== KNOWN_FORMAT_VERSION) {
|
|
1192
1193
|
problems.push(
|
|
@@ -1194,11 +1195,11 @@ function parseDecisionsFormat(text) {
|
|
|
1194
1195
|
);
|
|
1195
1196
|
}
|
|
1196
1197
|
const attrs = (m[2] ?? "").trim();
|
|
1197
|
-
if (!attrs) return { fields, problems };
|
|
1198
|
+
if (!attrs) return { fields, offset, problems };
|
|
1198
1199
|
const declared = /^fields=(.*)$/.exec(attrs);
|
|
1199
1200
|
if (!declared) {
|
|
1200
1201
|
problems.push(`marker attribute \`${attrs}\` is not understood \u2014 ignored (only \`fields=\`).`);
|
|
1201
|
-
return { fields, problems };
|
|
1202
|
+
return { fields, offset, problems };
|
|
1202
1203
|
}
|
|
1203
1204
|
const seen = new Set(BUILT_IN_FIELDS);
|
|
1204
1205
|
for (const raw of declared[1].split(",")) {
|
|
@@ -1218,7 +1219,7 @@ function parseDecisionsFormat(text) {
|
|
|
1218
1219
|
if (fields.length === 0 && problems.length === 0) {
|
|
1219
1220
|
problems.push("marker declares `fields=` with no field names \u2014 no extra fields checked.");
|
|
1220
1221
|
}
|
|
1221
|
-
return { fields, problems };
|
|
1222
|
+
return { fields, offset, problems };
|
|
1222
1223
|
}
|
|
1223
1224
|
function hasField(block, name) {
|
|
1224
1225
|
return new RegExp(`${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s*]*:`).test(block);
|
|
@@ -1230,9 +1231,10 @@ function parseDecisionEntries(text) {
|
|
|
1230
1231
|
const h = headings[i];
|
|
1231
1232
|
const m = /^## (D-(\d+))\b/.exec(h[0]);
|
|
1232
1233
|
if (!m) continue;
|
|
1233
|
-
const
|
|
1234
|
+
const offset = h.index ?? 0;
|
|
1235
|
+
const start = offset + h[0].length;
|
|
1234
1236
|
const end = i + 1 < headings.length ? headings[i + 1].index : void 0;
|
|
1235
|
-
entries.push({ id: m[1], num: Number(m[2]), block: text.slice(start, end) });
|
|
1237
|
+
entries.push({ id: m[1], num: Number(m[2]), block: text.slice(start, end), offset });
|
|
1236
1238
|
}
|
|
1237
1239
|
return entries;
|
|
1238
1240
|
}
|
|
@@ -1278,10 +1280,11 @@ function checkIdSequence(file, entries) {
|
|
|
1278
1280
|
}
|
|
1279
1281
|
return findings;
|
|
1280
1282
|
}
|
|
1281
|
-
function checkFormatFields(file, entries, today, declaredFields) {
|
|
1283
|
+
function checkFormatFields(file, entries, today, declaredFields, markerOffset) {
|
|
1282
1284
|
const findings = [];
|
|
1283
1285
|
for (const entry of entries) {
|
|
1284
|
-
|
|
1286
|
+
const bound = entry.offset >= markerOffset;
|
|
1287
|
+
for (const field of bound ? declaredFields : []) {
|
|
1285
1288
|
if (hasField(entry.block, field)) continue;
|
|
1286
1289
|
findings.push({
|
|
1287
1290
|
id: `${LENS_ID4}/field-missing:${file}:${entry.id}:${field}`,
|
|
@@ -1295,7 +1298,7 @@ function checkFormatFields(file, entries, today, declaredFields) {
|
|
|
1295
1298
|
confidence: "high"
|
|
1296
1299
|
});
|
|
1297
1300
|
}
|
|
1298
|
-
if (!/Scope[\s*]*:/.test(entry.block)) {
|
|
1301
|
+
if (bound && !/Scope[\s*]*:/.test(entry.block)) {
|
|
1299
1302
|
findings.push({
|
|
1300
1303
|
id: `${LENS_ID4}/scope-missing:${file}:${entry.id}`,
|
|
1301
1304
|
lens: LENS_ID4,
|
|
@@ -1419,12 +1422,18 @@ var stateFreshnessLens = {
|
|
|
1419
1422
|
continue;
|
|
1420
1423
|
}
|
|
1421
1424
|
for (const problem of format.problems) disclosures.push(`${a.path}: ${problem}`);
|
|
1425
|
+
const exempt = entries.filter((e) => e.offset < format.offset);
|
|
1422
1426
|
if (format.fields.length > 0) {
|
|
1423
1427
|
disclosures.push(
|
|
1424
|
-
`${a.path} declares required entry fields: ${format.fields.join(", ")} \u2014 checked on every entry (etymd attaches no meaning to the names).`
|
|
1428
|
+
`${a.path} declares required entry fields: ${format.fields.join(", ")} \u2014 checked on every entry at or after the marker (etymd attaches no meaning to the names).`
|
|
1425
1429
|
);
|
|
1426
1430
|
}
|
|
1427
|
-
|
|
1431
|
+
if (exempt.length > 0) {
|
|
1432
|
+
disclosures.push(
|
|
1433
|
+
`${a.path}: ${exempt.length} entr${exempt.length === 1 ? "y" : "ies"} precede the format marker (${exempt[0]?.id}\u2026${exempt[exempt.length - 1]?.id}) \u2014 field presence not checked there (forward-only from the marker's position).`
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1436
|
+
findings.push(...checkFormatFields(a.path, entries, today, format.fields, format.offset));
|
|
1428
1437
|
}
|
|
1429
1438
|
disclosures.push(
|
|
1430
1439
|
`Thresholds: staleAfterDays ${budgets.staleAfterDays} (3x escalates to risk), state budget ${budgets.maxChars} chars (${budgets.staleAfterDays === DEFAULT_CONFIG.state.staleAfterDays && budgets.maxChars === DEFAULT_CONFIG.state.maxChars ? `defaults \u2014 override under \`state\` in ${CONFIG_FILE}` : `set in ${CONFIG_FILE}`}). Decisions artifacts are exempt from age \u2014 old decisions are history, not defects.`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
2
|
+
import { DEFAULT_CONFIG } from './chunk-D2RI3CCT.js';
|
|
3
3
|
import { PACK_VERSION } from './chunk-6DUDIVIC.js';
|
|
4
4
|
import { readText, pathExists } from './chunk-IV3FYVTS.js';
|
|
5
5
|
import path from 'node:path';
|
|
@@ -292,6 +292,24 @@ function derivedCommands(facts, existingHook) {
|
|
|
292
292
|
}
|
|
293
293
|
return base;
|
|
294
294
|
}
|
|
295
|
+
function riskReachability(facts) {
|
|
296
|
+
const reasons = [];
|
|
297
|
+
if (facts.publishRoute !== "none") {
|
|
298
|
+
reasons.push("an instruction file can name a package script that no longer exists");
|
|
299
|
+
}
|
|
300
|
+
if (facts.artifacts.some((a) => a.kind === "state" && a.exists)) {
|
|
301
|
+
reasons.push("a state doc can fall far enough behind the repo to escalate");
|
|
302
|
+
}
|
|
303
|
+
return reasons;
|
|
304
|
+
}
|
|
305
|
+
function deriveFailOn(facts, recorded) {
|
|
306
|
+
const reachable = riskReachability(facts);
|
|
307
|
+
if (recorded.explicit) return { failOn: recorded.failOn, source: "config", reachable };
|
|
308
|
+
if (recorded.failOn === "risk" && reachable.length === 0) {
|
|
309
|
+
return { failOn: "gap", source: "derived", reachable };
|
|
310
|
+
}
|
|
311
|
+
return { failOn: recorded.failOn, source: "default", reachable };
|
|
312
|
+
}
|
|
295
313
|
async function planWorkflow(root, facts, opts) {
|
|
296
314
|
const out = [];
|
|
297
315
|
const add = async (rel, contents, label, executable = false) => {
|
|
@@ -346,4 +364,4 @@ async function planWorkflow(root, facts, opts) {
|
|
|
346
364
|
return out;
|
|
347
365
|
}
|
|
348
366
|
|
|
349
|
-
export { derivedCommands, isSafeGateCommand, planWorkflow, runPrefix };
|
|
367
|
+
export { deriveFailOn, derivedCommands, isSafeGateCommand, planWorkflow, riskReachability, runPrefix };
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-72Y2V7VF.js';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import pc from 'picocolors';
|
|
@@ -31,7 +31,7 @@ program.command("audit").description("Verify every instruction claim against the
|
|
|
31
31
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
32
32
|
).action(
|
|
33
33
|
(opts, cmd) => action(async () => {
|
|
34
|
-
const { run } = await import('./audit-
|
|
34
|
+
const { run } = await import('./audit-HO56CZNV.js');
|
|
35
35
|
await run({
|
|
36
36
|
cwd: resolveCwd(cmd),
|
|
37
37
|
json: opts.json,
|
|
@@ -44,7 +44,7 @@ program.command("audit").description("Verify every instruction claim against the
|
|
|
44
44
|
);
|
|
45
45
|
program.command("init").description("Onboard the truth guard: approve the baseline; scaffold AGENTS.md only if missing").option("-y, --yes", "accept defaults without prompting (never overwrites)").action(
|
|
46
46
|
(opts, cmd) => action(async () => {
|
|
47
|
-
const { run } = await import('./init-
|
|
47
|
+
const { run } = await import('./init-SSAJVX52.js');
|
|
48
48
|
await run({ cwd: resolveCwd(cmd), yes: opts.yes });
|
|
49
49
|
})
|
|
50
50
|
);
|
|
@@ -52,31 +52,31 @@ program.command("approve").description(
|
|
|
52
52
|
"Re-approve the committed baseline after intentional structural changes (non-interactive)"
|
|
53
53
|
).action(
|
|
54
54
|
(_opts, cmd) => action(async () => {
|
|
55
|
-
const { run } = await import('./approve-
|
|
55
|
+
const { run } = await import('./approve-MYOIDCAM.js');
|
|
56
56
|
await run({ cwd: resolveCwd(cmd) });
|
|
57
57
|
})
|
|
58
58
|
);
|
|
59
59
|
program.command("scan").description("Deterministically reckon the project into a facts index").option("--json", "print the raw facts as JSON").option("--no-save", "do not write the .etymd cache").action(
|
|
60
60
|
(opts, cmd) => action(async () => {
|
|
61
|
-
const { run } = await import('./scan-
|
|
61
|
+
const { run } = await import('./scan-H2IPH4C3.js');
|
|
62
62
|
await run({ cwd: resolveCwd(cmd), json: opts.json, save: opts.save });
|
|
63
63
|
})
|
|
64
64
|
);
|
|
65
65
|
program.command("doctor").description('Alias for `audit --truth` \u2014 "are the recorded instructions still true?"').option("--json", "print findings as JSON").action(
|
|
66
66
|
(opts, cmd) => action(async () => {
|
|
67
|
-
const { run } = await import('./doctor-
|
|
67
|
+
const { run } = await import('./doctor-ZAAEVVB4.js');
|
|
68
68
|
await run({ cwd: resolveCwd(cmd), json: opts.json });
|
|
69
69
|
})
|
|
70
70
|
);
|
|
71
71
|
program.command("context").description("Measure the always-loaded context footprint and flag extraction candidates").option("--json", "print the budget as JSON").action(
|
|
72
72
|
(opts, cmd) => action(async () => {
|
|
73
|
-
const { run } = await import('./context-
|
|
73
|
+
const { run } = await import('./context-5ANOP2IJ.js');
|
|
74
74
|
await run({ cwd: resolveCwd(cmd), json: opts.json });
|
|
75
75
|
})
|
|
76
76
|
);
|
|
77
77
|
program.command("brief").description("Emit a grounded briefing for the in-repo agent to complete the semantic layer").option("--human", "write a human onboarding brief instead of the agent briefing").action(
|
|
78
78
|
(opts, cmd) => action(async () => {
|
|
79
|
-
const { run } = await import('./brief-
|
|
79
|
+
const { run } = await import('./brief-XI4RCNYS.js');
|
|
80
80
|
await run({ cwd: resolveCwd(cmd), human: opts.human });
|
|
81
81
|
})
|
|
82
82
|
);
|
|
@@ -111,7 +111,7 @@ var fleet = program.command("fleet").description(
|
|
|
111
111
|
"exit non-zero when findings at/above this tier exist (risk|gap|polish)"
|
|
112
112
|
).action(
|
|
113
113
|
(opts, cmd) => action(async () => {
|
|
114
|
-
const { sweep } = await import('./fleet-
|
|
114
|
+
const { sweep } = await import('./fleet-6U6XW72I.js');
|
|
115
115
|
await sweep({
|
|
116
116
|
cwd: resolveCwd(cmd),
|
|
117
117
|
manifest: opts.manifest,
|
|
@@ -126,7 +126,7 @@ var fleet = program.command("fleet").description(
|
|
|
126
126
|
);
|
|
127
127
|
fleet.command("check").description("Validate the manifest pair only \u2014 no lenses: dangling mappings, duplicates, leaks").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").option("--json", "print the findings as JSON (EXPERIMENTAL through 0.2.x)").action(
|
|
128
128
|
(_opts, cmd) => action(async () => {
|
|
129
|
-
const { check } = await import('./fleet-
|
|
129
|
+
const { check } = await import('./fleet-6U6XW72I.js');
|
|
130
130
|
const opts = cmd.optsWithGlobals();
|
|
131
131
|
await check({ cwd: resolveCwd(cmd), manifest: opts.manifest, json: opts.json });
|
|
132
132
|
})
|
|
@@ -136,7 +136,7 @@ fleet.command("add").argument("<dir>", "directory of the project to register").d
|
|
|
136
136
|
"public-repo | public-bound | private \u2014 mandatory for personal entries"
|
|
137
137
|
).option("-y, --yes", "skip prompts; every mandatory value must be passed as a flag").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
138
138
|
(dir, opts, cmd) => action(async () => {
|
|
139
|
-
const { add } = await import('./fleet-
|
|
139
|
+
const { add } = await import('./fleet-6U6XW72I.js');
|
|
140
140
|
const shared = cmd.optsWithGlobals();
|
|
141
141
|
await add({
|
|
142
142
|
cwd: resolveCwd(cmd),
|
|
@@ -159,7 +159,7 @@ fleet.command("dismiss").argument("<name>", "the registered project name").argum
|
|
|
159
159
|
"Dismiss a project's finding from any cwd \u2014 corp ledgers persist beside the manifest"
|
|
160
160
|
).requiredOption("--reason <text>", "why it is dismissed \u2014 recorded so the decision survives").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
161
161
|
(name, id, _opts, cmd) => action(async () => {
|
|
162
|
-
const { dismiss } = await import('./fleet-
|
|
162
|
+
const { dismiss } = await import('./fleet-6U6XW72I.js');
|
|
163
163
|
const opts = cmd.optsWithGlobals();
|
|
164
164
|
await dismiss({
|
|
165
165
|
cwd: resolveCwd(cmd),
|
|
@@ -174,7 +174,7 @@ fleet.command("accept").argument("<name>", "the registered project name").argume
|
|
|
174
174
|
"Accept a project's finding as a known trade-off \u2014 corp ledgers persist beside the manifest"
|
|
175
175
|
).option("--reason <text>", "optional note on why the trade-off is accepted").option("--manifest <file>", "the fleet manifest \u2014 required unless the cwd holds registry.json").action(
|
|
176
176
|
(name, id, _opts, cmd) => action(async () => {
|
|
177
|
-
const { accept } = await import('./fleet-
|
|
177
|
+
const { accept } = await import('./fleet-6U6XW72I.js');
|
|
178
178
|
const opts = cmd.optsWithGlobals();
|
|
179
179
|
await accept({ cwd: resolveCwd(cmd), manifest: opts.manifest, name, id, reason: opts.reason });
|
|
180
180
|
})
|
|
@@ -196,7 +196,7 @@ program.command("screen").description(
|
|
|
196
196
|
);
|
|
197
197
|
program.command("gates").description("Install the local git-hook gates (process \u2192 pre-commit, correctness \u2192 pre-push)").option("--ci", "note about the CI review gate (ships later; local gates install now)").option("-y, --yes", "skip prompts; never overwrites a hand-edited hook").action(
|
|
198
198
|
(opts, cmd) => action(async () => {
|
|
199
|
-
const { run } = await import('./gates-
|
|
199
|
+
const { run } = await import('./gates-N25LGIR2.js');
|
|
200
200
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
201
201
|
})
|
|
202
202
|
);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { measureContext } from './chunk-
|
|
3
|
-
import { readConfig } from './chunk-
|
|
2
|
+
import { measureContext } from './chunk-JSUYV26K.js';
|
|
3
|
+
import { readConfig } from './chunk-D2RI3CCT.js';
|
|
4
4
|
import { print, renderContext } from './chunk-5BVKFJWM.js';
|
|
5
5
|
import './chunk-C7LBUFNU.js';
|
|
6
6
|
import './chunk-IV3FYVTS.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import './chunk-
|
|
2
|
+
import { run } from './chunk-KBYEWHYX.js';
|
|
3
|
+
import './chunk-PRIH2NZF.js';
|
|
4
|
+
import './chunk-JSUYV26K.js';
|
|
5
5
|
import './chunk-OAFYLBVG.js';
|
|
6
|
-
import './chunk-
|
|
6
|
+
import './chunk-D2RI3CCT.js';
|
|
7
7
|
import './chunk-5BVKFJWM.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import './chunk-CIRNG736.js';
|
|
9
|
+
import './chunk-72Y2V7VF.js';
|
|
10
10
|
import './chunk-C7LBUFNU.js';
|
|
11
11
|
import './chunk-6DUDIVIC.js';
|
|
12
12
|
import './chunk-IV3FYVTS.js';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { dismiss, accept } from './chunk-V2SOHPXF.js';
|
|
3
|
-
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-PRIH2NZF.js';
|
|
4
|
+
import './chunk-JSUYV26K.js';
|
|
5
5
|
import { readLedger } from './chunk-OAFYLBVG.js';
|
|
6
|
-
import { DEFAULT_CONFIG } from './chunk-
|
|
6
|
+
import { DEFAULT_CONFIG } from './chunk-D2RI3CCT.js';
|
|
7
7
|
import { print, theme, section, renderFleetRows, renderFleetNotes, renderFindings, TIER_BADGE, glyph } from './chunk-5BVKFJWM.js';
|
|
8
|
-
import { scanProject } from './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import { scanProject } from './chunk-CIRNG736.js';
|
|
9
|
+
import './chunk-72Y2V7VF.js';
|
|
10
10
|
import { ETYMD_DIR } from './chunk-C7LBUFNU.js';
|
|
11
11
|
import './chunk-6DUDIVIC.js';
|
|
12
12
|
import { readText, pathExists, git, isDirectory } from './chunk-IV3FYVTS.js';
|
|
@@ -687,9 +687,9 @@ async function checkCorpEmails(manifest, findings, disclosures) {
|
|
|
687
687
|
}
|
|
688
688
|
}
|
|
689
689
|
async function checkGateDrift(manifest, findings, disclosures) {
|
|
690
|
-
const { planWorkflow } = await import('./generate-
|
|
691
|
-
const { scanProject: scanProject2 } = await import('./scan-
|
|
692
|
-
const { readConfig } = await import('./config-
|
|
690
|
+
const { planWorkflow } = await import('./generate-IPY5QIR5.js');
|
|
691
|
+
const { scanProject: scanProject2 } = await import('./scan-LVJOAOUJ.js');
|
|
692
|
+
const { readConfig } = await import('./config-7WCVZCM6.js');
|
|
693
693
|
for (const entry of manifest.entries) {
|
|
694
694
|
const root = entry.resolvedRoot;
|
|
695
695
|
if (!root || !await isDirectory(root)) continue;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
3
|
-
import { derivedCommands, planWorkflow, runPrefix, isSafeGateCommand } from './chunk-
|
|
4
|
-
import { readConfig, CONFIG_FILE, configPath } from './chunk-
|
|
3
|
+
import { deriveFailOn, derivedCommands, planWorkflow, runPrefix, isSafeGateCommand } from './chunk-WENIJMVV.js';
|
|
4
|
+
import { readConfig, CONFIG_FILE, configPath } from './chunk-D2RI3CCT.js';
|
|
5
5
|
import { section, print, glyph, theme, renderPlan } from './chunk-5BVKFJWM.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import './chunk-
|
|
6
|
+
import { scanProject } from './chunk-CIRNG736.js';
|
|
7
|
+
import './chunk-72Y2V7VF.js';
|
|
8
8
|
import './chunk-C7LBUFNU.js';
|
|
9
9
|
import './chunk-6DUDIVIC.js';
|
|
10
10
|
import { readText, git } from './chunk-IV3FYVTS.js';
|
|
@@ -55,13 +55,30 @@ var PUBLISH_GATE_VALUE = `./${PUBLISH_GATE_SCRIPT}`;
|
|
|
55
55
|
function publishGateKey(route) {
|
|
56
56
|
return route === "vscode" ? "vscode:prepublish" : "prepublishOnly";
|
|
57
57
|
}
|
|
58
|
-
function printGateSummary(gates, facts, source) {
|
|
58
|
+
function printGateSummary(gates, facts, source, tier) {
|
|
59
59
|
const run2 = runPrefix(facts.packageManager);
|
|
60
60
|
const shellStep = facts.shell?.scripts ? `shellcheck (${facts.shell.scripts} script${facts.shell.scripts === 1 ? "" : "s"})` : "";
|
|
61
61
|
const listed = [...gates.commands.map((c) => `${run2} ${c}`), ...shellStep ? [shellStep] : []];
|
|
62
62
|
const cmds = listed.length ? listed.join(", ") : "none detected";
|
|
63
63
|
print(` ${glyph.bullet} ${theme.dim("pre-push runs")} ${theme.info(cmds)}`);
|
|
64
|
-
|
|
64
|
+
const origin = tier.source === "config" ? `set in ${CONFIG_FILE}` : tier.source === "derived" ? "derived \u2014 see below" : "default";
|
|
65
|
+
print(
|
|
66
|
+
` ${glyph.bullet} ${theme.dim("audit fails on")} ${theme.info(gates.failOn)} ${theme.dim(`(${origin})`)}`
|
|
67
|
+
);
|
|
68
|
+
if (tier.reachable.length === 0) {
|
|
69
|
+
print(
|
|
70
|
+
` ${theme.warn("no risk-tier finding can fire in this repo")} ${theme.dim("(no package manifest to contradict, no state doc to fall behind)")}`
|
|
71
|
+
);
|
|
72
|
+
print(
|
|
73
|
+
` ${theme.dim(
|
|
74
|
+
gates.failOn === "risk" ? `\`--fail-on risk\` would be a gate that always passes \u2014 change it in ${CONFIG_FILE} under \`gates.failOn\`` : `so the tier was lowered to \`${gates.failOn}\`, which this repo can reach \u2014 pin or override it in ${CONFIG_FILE} under \`gates.failOn\``
|
|
75
|
+
)}`
|
|
76
|
+
);
|
|
77
|
+
} else if (gates.failOn === "risk") {
|
|
78
|
+
print(
|
|
79
|
+
` ${theme.dim(`reachable at risk here: ${tier.reachable.join("; ")} \u2014 lower the tier in ${CONFIG_FILE} under \`gates.failOn\` to catch more`)}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
65
82
|
print(
|
|
66
83
|
` ${glyph.bullet} ${theme.dim("publish screen")} ${theme.info(gates.publishGate ? "yes" : "no")}${gates.publishGate && !facts.publishable ? theme.dim(" (overridden)") : ""}`
|
|
67
84
|
);
|
|
@@ -169,10 +186,16 @@ async function run(opts) {
|
|
|
169
186
|
}
|
|
170
187
|
}
|
|
171
188
|
const existingPrePush = await readText(path.join(opts.cwd, ".githooks", "pre-push"));
|
|
172
|
-
const { config, present: hasConfig } = await readConfig(opts.cwd);
|
|
189
|
+
const { config, present: hasConfig, explicit } = await readConfig(opts.cwd);
|
|
173
190
|
const configSource = { recorded: hasConfig, willRecord: !opts.yes };
|
|
191
|
+
const derivedTier = deriveFailOn(facts, {
|
|
192
|
+
failOn: config.gates.failOn,
|
|
193
|
+
explicit: explicit.gatesFailOn
|
|
194
|
+
});
|
|
195
|
+
let tier = { source: derivedTier.source, reachable: derivedTier.reachable };
|
|
174
196
|
let gateConfig = {
|
|
175
197
|
...config.gates,
|
|
198
|
+
failOn: derivedTier.failOn,
|
|
176
199
|
commands: config.gates.commands.length ? config.gates.commands : derivedCommands(facts, existingPrePush ?? void 0),
|
|
177
200
|
publishGate: config.gates.publishGate ?? facts.publishable
|
|
178
201
|
};
|
|
@@ -186,7 +209,7 @@ async function run(opts) {
|
|
|
186
209
|
};
|
|
187
210
|
let gateFiles = await plan();
|
|
188
211
|
renderPlan(gateFiles, { regeneratesStale: true });
|
|
189
|
-
printGateSummary(gateConfig, facts, configSource);
|
|
212
|
+
printGateSummary(gateConfig, facts, configSource, tier);
|
|
190
213
|
if (!opts.yes) {
|
|
191
214
|
const choice = await select({
|
|
192
215
|
message: "Install these gates?",
|
|
@@ -208,9 +231,10 @@ async function run(opts) {
|
|
|
208
231
|
return;
|
|
209
232
|
}
|
|
210
233
|
gateConfig = customized;
|
|
234
|
+
tier = { source: "config", reachable: derivedTier.reachable };
|
|
211
235
|
gateFiles = await plan();
|
|
212
236
|
renderPlan(gateFiles);
|
|
213
|
-
printGateSummary(gateConfig, facts, configSource);
|
|
237
|
+
printGateSummary(gateConfig, facts, configSource, tier);
|
|
214
238
|
}
|
|
215
239
|
await writeGateConfig(opts.cwd, gateConfig);
|
|
216
240
|
}
|
|
@@ -247,6 +271,11 @@ async function run(opts) {
|
|
|
247
271
|
}
|
|
248
272
|
}
|
|
249
273
|
print(` ${glyph.ok} ${theme.dim("set")} ${theme.code("core.hooksPath = .githooks")}`);
|
|
274
|
+
print(
|
|
275
|
+
` ${glyph.ok} ${theme.dim("pre-push audit fails on")} ${theme.info(gateConfig.failOn)} ${theme.dim(
|
|
276
|
+
tier.source === "config" ? `(from ${CONFIG_FILE})` : tier.source === "derived" ? `(derived \u2014 no risk-tier finding can fire here; set \`gates.failOn\` in ${CONFIG_FILE} to pin a tier)` : `(default \u2014 set \`gates.failOn\` in ${CONFIG_FILE} to change it)`
|
|
277
|
+
)}`
|
|
278
|
+
);
|
|
250
279
|
const wrotePublishGate = result.written.includes(PUBLISH_GATE_SCRIPT);
|
|
251
280
|
if (wrotePublishGate) {
|
|
252
281
|
const pkgPath = path.join(opts.cwd, "package.json");
|
package/dist/index.d.ts
CHANGED
|
@@ -241,6 +241,15 @@ interface LoadedConfig {
|
|
|
241
241
|
present: boolean;
|
|
242
242
|
/** Malformed or ignored input — surfaced as disclosures, never silently dropped. */
|
|
243
243
|
problems: string[];
|
|
244
|
+
/**
|
|
245
|
+
* Keys the file actually set, as opposed to keys that collapsed to a default. `gates.failOn`
|
|
246
|
+
* defaults to the same word a user might write deliberately, so the resolved value cannot say
|
|
247
|
+
* which happened — and a derivation may adjust a default it chose while never touching a tier
|
|
248
|
+
* someone decided on.
|
|
249
|
+
*/
|
|
250
|
+
explicit: {
|
|
251
|
+
gatesFailOn: boolean;
|
|
252
|
+
};
|
|
244
253
|
}
|
|
245
254
|
declare const DEFAULT_CONFIG: EtymdConfig;
|
|
246
255
|
declare function configPath(root: string): string;
|
|
@@ -722,7 +731,9 @@ declare const contextEconomyLens: Lens;
|
|
|
722
731
|
* mandatory-field checks apply to files that declared the format, never retroactively to
|
|
723
732
|
* pre-existing records — old decisions are history, not defects.
|
|
724
733
|
*
|
|
725
|
-
* A file may append `fields=A,B` to require field names of its own choosing on every entry
|
|
734
|
+
* A file may append `fields=A,B` to require field names of its own choosing on every entry at or
|
|
735
|
+
* after the marker — position is the gate, so a marker moved down the file exempts the history
|
|
736
|
+
* above it and one left at the top governs the whole file.
|
|
726
737
|
* Etymd ships no vocabulary for those names and attaches no meaning to them: it checks only that
|
|
727
738
|
* a field the file itself declared is present. A marker with no `fields=` behaves as it always
|
|
728
739
|
* has, so the attribute is an extension rather than a new format version.
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ var init_package = __esm({
|
|
|
30
30
|
"package.json"() {
|
|
31
31
|
package_default = {
|
|
32
32
|
name: "etymd",
|
|
33
|
-
version: "0.
|
|
33
|
+
version: "0.8.0",
|
|
34
34
|
description: "Keep your agent instructions true \u2014 verify AGENTS.md, CLAUDE.md, rules & skills against the actual repo, with drift caught over time and a regression ledger.",
|
|
35
35
|
keywords: [
|
|
36
36
|
"cli",
|
|
@@ -924,7 +924,7 @@ async function readConfig(root) {
|
|
|
924
924
|
const target = configPath(root);
|
|
925
925
|
const problems = [];
|
|
926
926
|
if (!await pathExists(target)) {
|
|
927
|
-
return { config: DEFAULT_CONFIG, present: false, problems };
|
|
927
|
+
return { config: DEFAULT_CONFIG, present: false, problems, explicit: { gatesFailOn: false } };
|
|
928
928
|
}
|
|
929
929
|
const raw = await readText(target);
|
|
930
930
|
let parsed;
|
|
@@ -934,11 +934,11 @@ async function readConfig(root) {
|
|
|
934
934
|
problems.push(
|
|
935
935
|
`${CONFIG_FILE} exists but is not valid JSON (${err instanceof Error ? err.message : String(err)}) \u2014 defaults used.`
|
|
936
936
|
);
|
|
937
|
-
return { config: DEFAULT_CONFIG, present: true, problems };
|
|
937
|
+
return { config: DEFAULT_CONFIG, present: true, problems, explicit: { gatesFailOn: false } };
|
|
938
938
|
}
|
|
939
939
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
940
940
|
problems.push(`${CONFIG_FILE} must contain a JSON object \u2014 defaults used.`);
|
|
941
|
-
return { config: DEFAULT_CONFIG, present: true, problems };
|
|
941
|
+
return { config: DEFAULT_CONFIG, present: true, problems, explicit: { gatesFailOn: false } };
|
|
942
942
|
}
|
|
943
943
|
const obj = parsed;
|
|
944
944
|
const instructions = obj.instructions ?? {};
|
|
@@ -964,6 +964,7 @@ async function readConfig(root) {
|
|
|
964
964
|
return {
|
|
965
965
|
present: true,
|
|
966
966
|
problems,
|
|
967
|
+
explicit: { gatesFailOn: failOn !== void 0 },
|
|
967
968
|
config: {
|
|
968
969
|
instructions: {
|
|
969
970
|
include: readGlobList(instructions.include, "instructions.include", problems) ?? DEFAULT_CONFIG.instructions.include,
|
|
@@ -1285,8 +1286,10 @@ var init_templates = __esm({
|
|
|
1285
1286
|
// src/core/generate.ts
|
|
1286
1287
|
var generate_exports = {};
|
|
1287
1288
|
__export(generate_exports, {
|
|
1289
|
+
deriveFailOn: () => deriveFailOn,
|
|
1288
1290
|
derivedCommands: () => derivedCommands,
|
|
1289
|
-
planWorkflow: () => planWorkflow
|
|
1291
|
+
planWorkflow: () => planWorkflow,
|
|
1292
|
+
riskReachability: () => riskReachability
|
|
1290
1293
|
});
|
|
1291
1294
|
function derivedCommands(facts, existingHook) {
|
|
1292
1295
|
const c = facts.commands;
|
|
@@ -1298,6 +1301,24 @@ function derivedCommands(facts, existingHook) {
|
|
|
1298
1301
|
}
|
|
1299
1302
|
return base;
|
|
1300
1303
|
}
|
|
1304
|
+
function riskReachability(facts) {
|
|
1305
|
+
const reasons = [];
|
|
1306
|
+
if (facts.publishRoute !== "none") {
|
|
1307
|
+
reasons.push("an instruction file can name a package script that no longer exists");
|
|
1308
|
+
}
|
|
1309
|
+
if (facts.artifacts.some((a) => a.kind === "state" && a.exists)) {
|
|
1310
|
+
reasons.push("a state doc can fall far enough behind the repo to escalate");
|
|
1311
|
+
}
|
|
1312
|
+
return reasons;
|
|
1313
|
+
}
|
|
1314
|
+
function deriveFailOn(facts, recorded) {
|
|
1315
|
+
const reachable = riskReachability(facts);
|
|
1316
|
+
if (recorded.explicit) return { failOn: recorded.failOn, source: "config", reachable };
|
|
1317
|
+
if (recorded.failOn === "risk" && reachable.length === 0) {
|
|
1318
|
+
return { failOn: "gap", source: "derived", reachable };
|
|
1319
|
+
}
|
|
1320
|
+
return { failOn: recorded.failOn, source: "default", reachable };
|
|
1321
|
+
}
|
|
1301
1322
|
async function planWorkflow(root, facts, opts) {
|
|
1302
1323
|
const out = [];
|
|
1303
1324
|
const add = async (rel, contents, label, executable = false) => {
|
|
@@ -2627,6 +2648,7 @@ function parseDecisionsFormat(text) {
|
|
|
2627
2648
|
if (!m) return null;
|
|
2628
2649
|
const problems = [];
|
|
2629
2650
|
const fields = [];
|
|
2651
|
+
const offset = m.index ?? 0;
|
|
2630
2652
|
const version = Number(m[1]);
|
|
2631
2653
|
if (version !== KNOWN_FORMAT_VERSION) {
|
|
2632
2654
|
problems.push(
|
|
@@ -2634,11 +2656,11 @@ function parseDecisionsFormat(text) {
|
|
|
2634
2656
|
);
|
|
2635
2657
|
}
|
|
2636
2658
|
const attrs = (m[2] ?? "").trim();
|
|
2637
|
-
if (!attrs) return { fields, problems };
|
|
2659
|
+
if (!attrs) return { fields, offset, problems };
|
|
2638
2660
|
const declared = /^fields=(.*)$/.exec(attrs);
|
|
2639
2661
|
if (!declared) {
|
|
2640
2662
|
problems.push(`marker attribute \`${attrs}\` is not understood \u2014 ignored (only \`fields=\`).`);
|
|
2641
|
-
return { fields, problems };
|
|
2663
|
+
return { fields, offset, problems };
|
|
2642
2664
|
}
|
|
2643
2665
|
const seen = new Set(BUILT_IN_FIELDS);
|
|
2644
2666
|
for (const raw of declared[1].split(",")) {
|
|
@@ -2658,7 +2680,7 @@ function parseDecisionsFormat(text) {
|
|
|
2658
2680
|
if (fields.length === 0 && problems.length === 0) {
|
|
2659
2681
|
problems.push("marker declares `fields=` with no field names \u2014 no extra fields checked.");
|
|
2660
2682
|
}
|
|
2661
|
-
return { fields, problems };
|
|
2683
|
+
return { fields, offset, problems };
|
|
2662
2684
|
}
|
|
2663
2685
|
function hasField(block, name) {
|
|
2664
2686
|
return new RegExp(`${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s*]*:`).test(block);
|
|
@@ -2670,9 +2692,10 @@ function parseDecisionEntries(text) {
|
|
|
2670
2692
|
const h = headings[i];
|
|
2671
2693
|
const m = /^## (D-(\d+))\b/.exec(h[0]);
|
|
2672
2694
|
if (!m) continue;
|
|
2673
|
-
const
|
|
2695
|
+
const offset = h.index ?? 0;
|
|
2696
|
+
const start = offset + h[0].length;
|
|
2674
2697
|
const end = i + 1 < headings.length ? headings[i + 1].index : void 0;
|
|
2675
|
-
entries.push({ id: m[1], num: Number(m[2]), block: text.slice(start, end) });
|
|
2698
|
+
entries.push({ id: m[1], num: Number(m[2]), block: text.slice(start, end), offset });
|
|
2676
2699
|
}
|
|
2677
2700
|
return entries;
|
|
2678
2701
|
}
|
|
@@ -2718,10 +2741,11 @@ function checkIdSequence(file, entries) {
|
|
|
2718
2741
|
}
|
|
2719
2742
|
return findings;
|
|
2720
2743
|
}
|
|
2721
|
-
function checkFormatFields(file, entries, today, declaredFields) {
|
|
2744
|
+
function checkFormatFields(file, entries, today, declaredFields, markerOffset) {
|
|
2722
2745
|
const findings = [];
|
|
2723
2746
|
for (const entry of entries) {
|
|
2724
|
-
|
|
2747
|
+
const bound = entry.offset >= markerOffset;
|
|
2748
|
+
for (const field of bound ? declaredFields : []) {
|
|
2725
2749
|
if (hasField(entry.block, field)) continue;
|
|
2726
2750
|
findings.push({
|
|
2727
2751
|
id: `${LENS_ID4}/field-missing:${file}:${entry.id}:${field}`,
|
|
@@ -2735,7 +2759,7 @@ function checkFormatFields(file, entries, today, declaredFields) {
|
|
|
2735
2759
|
confidence: "high"
|
|
2736
2760
|
});
|
|
2737
2761
|
}
|
|
2738
|
-
if (!/Scope[\s*]*:/.test(entry.block)) {
|
|
2762
|
+
if (bound && !/Scope[\s*]*:/.test(entry.block)) {
|
|
2739
2763
|
findings.push({
|
|
2740
2764
|
id: `${LENS_ID4}/scope-missing:${file}:${entry.id}`,
|
|
2741
2765
|
lens: LENS_ID4,
|
|
@@ -2859,12 +2883,18 @@ var stateFreshnessLens = {
|
|
|
2859
2883
|
continue;
|
|
2860
2884
|
}
|
|
2861
2885
|
for (const problem of format.problems) disclosures.push(`${a.path}: ${problem}`);
|
|
2886
|
+
const exempt = entries.filter((e) => e.offset < format.offset);
|
|
2862
2887
|
if (format.fields.length > 0) {
|
|
2863
2888
|
disclosures.push(
|
|
2864
|
-
`${a.path} declares required entry fields: ${format.fields.join(", ")} \u2014 checked on every entry (etymd attaches no meaning to the names).`
|
|
2889
|
+
`${a.path} declares required entry fields: ${format.fields.join(", ")} \u2014 checked on every entry at or after the marker (etymd attaches no meaning to the names).`
|
|
2890
|
+
);
|
|
2891
|
+
}
|
|
2892
|
+
if (exempt.length > 0) {
|
|
2893
|
+
disclosures.push(
|
|
2894
|
+
`${a.path}: ${exempt.length} entr${exempt.length === 1 ? "y" : "ies"} precede the format marker (${exempt[0]?.id}\u2026${exempt[exempt.length - 1]?.id}) \u2014 field presence not checked there (forward-only from the marker's position).`
|
|
2865
2895
|
);
|
|
2866
2896
|
}
|
|
2867
|
-
findings.push(...checkFormatFields(a.path, entries, today, format.fields));
|
|
2897
|
+
findings.push(...checkFormatFields(a.path, entries, today, format.fields, format.offset));
|
|
2868
2898
|
}
|
|
2869
2899
|
disclosures.push(
|
|
2870
2900
|
`Thresholds: staleAfterDays ${budgets.staleAfterDays} (3x escalates to risk), state budget ${budgets.maxChars} chars (${budgets.staleAfterDays === DEFAULT_CONFIG.state.staleAfterDays && budgets.maxChars === DEFAULT_CONFIG.state.maxChars ? `defaults \u2014 override under \`state\` in ${CONFIG_FILE}` : `set in ${CONFIG_FILE}`}). Decisions artifacts are exempt from age \u2014 old decisions are history, not defects.`
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
3
|
-
import { planWorkflow } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { planWorkflow } from './chunk-WENIJMVV.js';
|
|
4
|
+
import './chunk-D2RI3CCT.js';
|
|
5
5
|
import { theme, renderFacts, print, renderPlan, section, glyph } from './chunk-5BVKFJWM.js';
|
|
6
|
-
import { scanProject } from './chunk-
|
|
7
|
-
import { VERSION } from './chunk-
|
|
6
|
+
import { scanProject } from './chunk-CIRNG736.js';
|
|
7
|
+
import { VERSION } from './chunk-72Y2V7VF.js';
|
|
8
8
|
import { writeCachedFacts, deriveProfile, writeBaseline, CACHE_DIR } from './chunk-C7LBUFNU.js';
|
|
9
9
|
import { PACK_VERSION } from './chunk-6DUDIVIC.js';
|
|
10
10
|
import { git, readText } from './chunk-IV3FYVTS.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { print, renderFacts, theme } from './chunk-5BVKFJWM.js';
|
|
3
|
-
import { scanProject } from './chunk-
|
|
4
|
-
import './chunk-
|
|
3
|
+
import { scanProject } from './chunk-CIRNG736.js';
|
|
4
|
+
import './chunk-72Y2V7VF.js';
|
|
5
5
|
import { writeCachedFacts } from './chunk-C7LBUFNU.js';
|
|
6
6
|
import './chunk-6DUDIVIC.js';
|
|
7
7
|
import './chunk-IV3FYVTS.js';
|
package/package.json
CHANGED
package/dist/audit-354X7QN6.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
export { run } from './chunk-7LVCGCHB.js';
|
|
3
|
-
import './chunk-LXEYYITD.js';
|
|
4
|
-
import './chunk-OKU3HXIH.js';
|
|
5
|
-
import './chunk-OAFYLBVG.js';
|
|
6
|
-
import './chunk-K7QBTANO.js';
|
|
7
|
-
import './chunk-5BVKFJWM.js';
|
|
8
|
-
import './chunk-XJMN2HMH.js';
|
|
9
|
-
import './chunk-OGIHOTLP.js';
|
|
10
|
-
import './chunk-C7LBUFNU.js';
|
|
11
|
-
import './chunk-6DUDIVIC.js';
|
|
12
|
-
import './chunk-IV3FYVTS.js';
|