etymd 0.6.0 → 0.7.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 +36 -0
- package/README.md +5 -1
- package/dist/{approve-ZLOW4A7R.js → approve-TXF3MA3G.js} +2 -2
- package/dist/{audit-ICT2JK7A.js → audit-354X7QN6.js} +4 -4
- package/dist/{brief-PDUGGMLI.js → brief-QUYHRWH6.js} +2 -2
- package/dist/{chunk-BQGCLPHS.js → chunk-7LVCGCHB.js} +1 -1
- package/dist/{chunk-OESQNO2J.js → chunk-LXEYYITD.js} +70 -4
- package/dist/{chunk-HLWCODYX.js → chunk-OGIHOTLP.js} +1 -1
- package/dist/{chunk-ULEWJ3ZK.js → chunk-XJMN2HMH.js} +1 -1
- package/dist/cli.js +13 -13
- package/dist/{doctor-PORXFSYT.js → doctor-2BU6WCI6.js} +4 -4
- package/dist/{fleet-JHNQOLSY.js → fleet-3UQ65X4Y.js} +4 -4
- package/dist/{gates-KHC7HPIF.js → gates-MYNOZCON.js} +2 -2
- package/dist/index.d.ts +5 -0
- package/dist/index.js +70 -4
- package/dist/{init-IPUPP4GB.js → init-52PR24Q4.js} +2 -2
- package/dist/{scan-6L4L2GXT.js → scan-GSDICQCX.js} +2 -2
- package/dist/scan-M6RKDNYR.js +5 -0
- package/package.json +1 -1
- package/dist/scan-D4M7JXE3.js +0 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# etymd
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7371c8b: Decisions records can require fields of their own, and etymd holds no opinion about which.
|
|
8
|
+
|
|
9
|
+
A decisions file already opts into per-entry format checks with a marker. It can now append field
|
|
10
|
+
names to it — `<!-- decisions-format: 1 fields=Owner,Rollback -->` — and every entry after the
|
|
11
|
+
marker must carry each one. `Owner:` and `**Owner:**` both count. Decision record:
|
|
12
|
+
[`docs/decisions/007-declared-entry-fields.md`](docs/decisions/007-declared-entry-fields.md).
|
|
13
|
+
|
|
14
|
+
**The tool ships the shape, not the vocabulary.** Etymd never interprets a declared name and has no
|
|
15
|
+
list of fields it thinks a record ought to have; it verifies that a line introducing the name
|
|
16
|
+
exists, and that is all. Which fields are worth requiring is the file's position to take, not the
|
|
17
|
+
tool's — a name coined for one project's process is exactly the kind of opinion this package does
|
|
18
|
+
not ship, however cleanly it would mechanize.
|
|
19
|
+
|
|
20
|
+
**Nothing is dropped in silence.** A name that cannot be used as a field, a marker version this
|
|
21
|
+
build does not know, and a redeclared `Scope` are each disclosed in the lens report. A file that
|
|
22
|
+
believes it declared a requirement, and is quietly audited without it, would come back clean for
|
|
23
|
+
the one reason this tool exists to reject — so the failure is stated rather than absorbed.
|
|
24
|
+
Declarable names are letters, digits, spaces, `-` and `_`, which keeps regex metacharacters out of
|
|
25
|
+
the matcher by construction.
|
|
26
|
+
|
|
27
|
+
**Enforcement is every-entry, with no keyword trigger.** Requiring the fields only on entries whose
|
|
28
|
+
prose looks like it is claiming something would flag an entry that mentions a closed tab or a fixed
|
|
29
|
+
price, and a false "your file is lying" costs more trust than a missed one. Every entry after the
|
|
30
|
+
marker is held to the same rule, which is what `Scope:` already does and takes one sentence to
|
|
31
|
+
explain.
|
|
32
|
+
|
|
33
|
+
**Existing files are unaffected.** `fields=` is an extension rather than a new format version: a
|
|
34
|
+
marker without it behaves exactly as before, pre-marker entries stay untouched, and a repo that
|
|
35
|
+
declares nothing sees no change. The declaration lives on the marker rather than in
|
|
36
|
+
`.etymd/config.json` so the requirement sits beside the entries it governs, which also lets a repo
|
|
37
|
+
keep two decision records with different obligations.
|
|
38
|
+
|
|
3
39
|
## 0.6.0
|
|
4
40
|
|
|
5
41
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -147,7 +147,11 @@ _relative_ — a state doc is stale only when the repo moved past it, so a dorma
|
|
|
147
147
|
state is current; a tracked file with uncommitted edits is treated fresh-now (the refresh is
|
|
148
148
|
already on disk) and disclosed. Decisions records get format checks (`Scope:` presence, a
|
|
149
149
|
`Revisit:` date that, once past, becomes a finding) — opt in by adding the literal marker
|
|
150
|
-
`<!-- decisions-format: 1 -->` anywhere in the file; forward-only, never retroactive.
|
|
150
|
+
`<!-- decisions-format: 1 -->` anywhere in the file; forward-only, never retroactive. A file can
|
|
151
|
+
require field names of its own by appending them to the marker —
|
|
152
|
+
`<!-- decisions-format: 1 fields=Owner,Rollback -->` — and each is then checked on every entry.
|
|
153
|
+
Etymd ships no field vocabulary and reads no meaning into the names; it verifies only that what
|
|
154
|
+
the file declared is present, and discloses any name it could not use. Duplicate
|
|
151
155
|
or out-of-order `D-NNN` ids are flagged with a rename action even without the marker — an
|
|
152
156
|
append race is a defect in the file's own convention, not a format opinion.
|
|
153
157
|
|
|
@@ -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-XJMN2HMH.js';
|
|
4
|
+
import { VERSION } from './chunk-OGIHOTLP.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';
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
export { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { run } from './chunk-7LVCGCHB.js';
|
|
3
|
+
import './chunk-LXEYYITD.js';
|
|
4
4
|
import './chunk-OKU3HXIH.js';
|
|
5
5
|
import './chunk-OAFYLBVG.js';
|
|
6
6
|
import './chunk-K7QBTANO.js';
|
|
7
7
|
import './chunk-5BVKFJWM.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import './chunk-XJMN2HMH.js';
|
|
9
|
+
import './chunk-OGIHOTLP.js';
|
|
10
10
|
import './chunk-C7LBUFNU.js';
|
|
11
11
|
import './chunk-6DUDIVIC.js';
|
|
12
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-XJMN2HMH.js';
|
|
4
|
+
import './chunk-OGIHOTLP.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 { parseFailOnTier, runAudit, meetsFailOn } from './chunk-
|
|
2
|
+
import { parseFailOnTier, runAudit, meetsFailOn } from './chunk-LXEYYITD.js';
|
|
3
3
|
import { print, section, theme, renderLensCoverage, renderFindings, renderLedgerDiff } from './chunk-5BVKFJWM.js';
|
|
4
4
|
|
|
5
5
|
// src/commands/audit.ts
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { measureContext } from './chunk-OKU3HXIH.js';
|
|
3
3
|
import { readLedger, reconcileLedger, writeLedger, visibleFindings } from './chunk-OAFYLBVG.js';
|
|
4
4
|
import { DEFAULT_CONFIG, readConfig, CONFIG_FILE } from './chunk-K7QBTANO.js';
|
|
5
|
-
import { scanProject, expandFileGlobs } from './chunk-
|
|
5
|
+
import { scanProject, expandFileGlobs } from './chunk-XJMN2HMH.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';
|
|
@@ -1177,7 +1177,52 @@ var instructionTruthLens = {
|
|
|
1177
1177
|
};
|
|
1178
1178
|
var LENS_ID4 = "state-freshness";
|
|
1179
1179
|
var DECISIONS_FORMAT_MARKER = "<!-- decisions-format: 1 -->";
|
|
1180
|
+
var KNOWN_FORMAT_VERSION = 1;
|
|
1181
|
+
var MARKER_RE = /<!--\s*decisions-format:\s*(\d+)([^>]*?)-->/;
|
|
1182
|
+
var FIELD_NAME_RE = /^[A-Za-z0-9 _-]+$/;
|
|
1183
|
+
var BUILT_IN_FIELDS = /* @__PURE__ */ new Set(["scope"]);
|
|
1180
1184
|
var MS_PER_DAY = 864e5;
|
|
1185
|
+
function parseDecisionsFormat(text) {
|
|
1186
|
+
const m = MARKER_RE.exec(text);
|
|
1187
|
+
if (!m) return null;
|
|
1188
|
+
const problems = [];
|
|
1189
|
+
const fields = [];
|
|
1190
|
+
const version = Number(m[1]);
|
|
1191
|
+
if (version !== KNOWN_FORMAT_VERSION) {
|
|
1192
|
+
problems.push(
|
|
1193
|
+
`declares decisions-format version ${version}; this etymd understands version ${KNOWN_FORMAT_VERSION} \u2014 checked as version ${KNOWN_FORMAT_VERSION}.`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1196
|
+
const attrs = (m[2] ?? "").trim();
|
|
1197
|
+
if (!attrs) return { fields, problems };
|
|
1198
|
+
const declared = /^fields=(.*)$/.exec(attrs);
|
|
1199
|
+
if (!declared) {
|
|
1200
|
+
problems.push(`marker attribute \`${attrs}\` is not understood \u2014 ignored (only \`fields=\`).`);
|
|
1201
|
+
return { fields, problems };
|
|
1202
|
+
}
|
|
1203
|
+
const seen = new Set(BUILT_IN_FIELDS);
|
|
1204
|
+
for (const raw of declared[1].split(",")) {
|
|
1205
|
+
const name = raw.trim();
|
|
1206
|
+
if (!name) continue;
|
|
1207
|
+
if (!FIELD_NAME_RE.test(name)) {
|
|
1208
|
+
problems.push(
|
|
1209
|
+
`declared field \`${name}\` is not a usable field name (letters, digits, spaces, \`-\`, \`_\`) \u2014 not checked.`
|
|
1210
|
+
);
|
|
1211
|
+
continue;
|
|
1212
|
+
}
|
|
1213
|
+
const key = name.toLowerCase();
|
|
1214
|
+
if (seen.has(key)) continue;
|
|
1215
|
+
seen.add(key);
|
|
1216
|
+
fields.push(name);
|
|
1217
|
+
}
|
|
1218
|
+
if (fields.length === 0 && problems.length === 0) {
|
|
1219
|
+
problems.push("marker declares `fields=` with no field names \u2014 no extra fields checked.");
|
|
1220
|
+
}
|
|
1221
|
+
return { fields, problems };
|
|
1222
|
+
}
|
|
1223
|
+
function hasField(block, name) {
|
|
1224
|
+
return new RegExp(`${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s*]*:`).test(block);
|
|
1225
|
+
}
|
|
1181
1226
|
function parseDecisionEntries(text) {
|
|
1182
1227
|
const headings = [...text.matchAll(/^## .*$/gm)];
|
|
1183
1228
|
const entries = [];
|
|
@@ -1233,9 +1278,23 @@ function checkIdSequence(file, entries) {
|
|
|
1233
1278
|
}
|
|
1234
1279
|
return findings;
|
|
1235
1280
|
}
|
|
1236
|
-
function checkFormatFields(file, entries, today) {
|
|
1281
|
+
function checkFormatFields(file, entries, today, declaredFields) {
|
|
1237
1282
|
const findings = [];
|
|
1238
1283
|
for (const entry of entries) {
|
|
1284
|
+
for (const field of declaredFields) {
|
|
1285
|
+
if (hasField(entry.block, field)) continue;
|
|
1286
|
+
findings.push({
|
|
1287
|
+
id: `${LENS_ID4}/field-missing:${file}:${entry.id}:${field}`,
|
|
1288
|
+
lens: LENS_ID4,
|
|
1289
|
+
tier: "gap",
|
|
1290
|
+
claim: `${file} ${entry.id} has no ${field}: field`,
|
|
1291
|
+
evidence: [`${file}: ${entry.id}`, `${file} marker declares required field \`${field}\``],
|
|
1292
|
+
why: "The file declares this field required on every entry after the marker; whatever reads the record for it finds nothing here.",
|
|
1293
|
+
action: `Add a ${field}: line to ${entry.id}.`,
|
|
1294
|
+
effort: "S",
|
|
1295
|
+
confidence: "high"
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1239
1298
|
if (!/Scope[\s*]*:/.test(entry.block)) {
|
|
1240
1299
|
findings.push({
|
|
1241
1300
|
id: `${LENS_ID4}/scope-missing:${file}:${entry.id}`,
|
|
@@ -1351,14 +1410,21 @@ var stateFreshnessLens = {
|
|
|
1351
1410
|
}
|
|
1352
1411
|
const entries = parseDecisionEntries(text);
|
|
1353
1412
|
findings.push(...checkIdSequence(a.path, entries));
|
|
1354
|
-
|
|
1413
|
+
const format = parseDecisionsFormat(text);
|
|
1414
|
+
if (!format) {
|
|
1355
1415
|
disclosures.push(
|
|
1356
1416
|
`${a.path} carries no \`${DECISIONS_FORMAT_MARKER}\` marker \u2014 format checks skipped (forward-only, never retroactive); id-sequence checks still ran.`
|
|
1357
1417
|
);
|
|
1358
1418
|
outOfScope.push(a.path);
|
|
1359
1419
|
continue;
|
|
1360
1420
|
}
|
|
1361
|
-
|
|
1421
|
+
for (const problem of format.problems) disclosures.push(`${a.path}: ${problem}`);
|
|
1422
|
+
if (format.fields.length > 0) {
|
|
1423
|
+
disclosures.push(
|
|
1424
|
+
`${a.path} declares required entry fields: ${format.fields.join(", ")} \u2014 checked on every entry (etymd attaches no meaning to the names).`
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
findings.push(...checkFormatFields(a.path, entries, today, format.fields));
|
|
1362
1428
|
}
|
|
1363
1429
|
disclosures.push(
|
|
1364
1430
|
`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 { VERSION } from './chunk-
|
|
2
|
+
import { VERSION } from './chunk-OGIHOTLP.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';
|
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-OGIHOTLP.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-354X7QN6.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-52PR24Q4.js');
|
|
48
48
|
await run({ cwd: resolveCwd(cmd), yes: opts.yes });
|
|
49
49
|
})
|
|
50
50
|
);
|
|
@@ -52,19 +52,19 @@ 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-TXF3MA3G.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-GSDICQCX.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-2BU6WCI6.js');
|
|
68
68
|
await run({ cwd: resolveCwd(cmd), json: opts.json });
|
|
69
69
|
})
|
|
70
70
|
);
|
|
@@ -76,7 +76,7 @@ program.command("context").description("Measure the always-loaded context footpr
|
|
|
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-QUYHRWH6.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-3UQ65X4Y.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-3UQ65X4Y.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-3UQ65X4Y.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-3UQ65X4Y.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-3UQ65X4Y.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-MYNOZCON.js');
|
|
200
200
|
await run({ cwd: resolveCwd(cmd), ci: opts.ci, yes: opts.yes });
|
|
201
201
|
})
|
|
202
202
|
);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { run } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
import { run } from './chunk-7LVCGCHB.js';
|
|
3
|
+
import './chunk-LXEYYITD.js';
|
|
4
4
|
import './chunk-OKU3HXIH.js';
|
|
5
5
|
import './chunk-OAFYLBVG.js';
|
|
6
6
|
import './chunk-K7QBTANO.js';
|
|
7
7
|
import './chunk-5BVKFJWM.js';
|
|
8
|
-
import './chunk-
|
|
9
|
-
import './chunk-
|
|
8
|
+
import './chunk-XJMN2HMH.js';
|
|
9
|
+
import './chunk-OGIHOTLP.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-
|
|
3
|
+
import { parseFailOnTier, meetsFailOn, runAudit } from './chunk-LXEYYITD.js';
|
|
4
4
|
import './chunk-OKU3HXIH.js';
|
|
5
5
|
import { readLedger } from './chunk-OAFYLBVG.js';
|
|
6
6
|
import { DEFAULT_CONFIG } from './chunk-K7QBTANO.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-XJMN2HMH.js';
|
|
9
|
+
import './chunk-OGIHOTLP.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';
|
|
@@ -688,7 +688,7 @@ async function checkCorpEmails(manifest, findings, disclosures) {
|
|
|
688
688
|
}
|
|
689
689
|
async function checkGateDrift(manifest, findings, disclosures) {
|
|
690
690
|
const { planWorkflow } = await import('./generate-UTKQ4GE6.js');
|
|
691
|
-
const { scanProject: scanProject2 } = await import('./scan-
|
|
691
|
+
const { scanProject: scanProject2 } = await import('./scan-M6RKDNYR.js');
|
|
692
692
|
const { readConfig } = await import('./config-UJRSCDMD.js');
|
|
693
693
|
for (const entry of manifest.entries) {
|
|
694
694
|
const root = entry.resolvedRoot;
|
|
@@ -3,8 +3,8 @@ import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
|
3
3
|
import { derivedCommands, planWorkflow, runPrefix, isSafeGateCommand } from './chunk-KTIEYKFK.js';
|
|
4
4
|
import { readConfig, CONFIG_FILE, configPath } from './chunk-K7QBTANO.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-XJMN2HMH.js';
|
|
7
|
+
import './chunk-OGIHOTLP.js';
|
|
8
8
|
import './chunk-C7LBUFNU.js';
|
|
9
9
|
import './chunk-6DUDIVIC.js';
|
|
10
10
|
import { readText, git } from './chunk-IV3FYVTS.js';
|
package/dist/index.d.ts
CHANGED
|
@@ -721,6 +721,11 @@ declare const contextEconomyLens: Lens;
|
|
|
721
721
|
* The marker that opts a decisions file into per-entry format checks. Forward-only by design:
|
|
722
722
|
* mandatory-field checks apply to files that declared the format, never retroactively to
|
|
723
723
|
* pre-existing records — old decisions are history, not defects.
|
|
724
|
+
*
|
|
725
|
+
* A file may append `fields=A,B` to require field names of its own choosing on every entry.
|
|
726
|
+
* Etymd ships no vocabulary for those names and attaches no meaning to them: it checks only that
|
|
727
|
+
* a field the file itself declared is present. A marker with no `fields=` behaves as it always
|
|
728
|
+
* has, so the attribute is an extension rather than a new format version.
|
|
724
729
|
*/
|
|
725
730
|
declare const DECISIONS_FORMAT_MARKER = "<!-- decisions-format: 1 -->";
|
|
726
731
|
/**
|
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.7.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",
|
|
@@ -2617,7 +2617,52 @@ init_config();
|
|
|
2617
2617
|
init_util();
|
|
2618
2618
|
var LENS_ID4 = "state-freshness";
|
|
2619
2619
|
var DECISIONS_FORMAT_MARKER = "<!-- decisions-format: 1 -->";
|
|
2620
|
+
var KNOWN_FORMAT_VERSION = 1;
|
|
2621
|
+
var MARKER_RE = /<!--\s*decisions-format:\s*(\d+)([^>]*?)-->/;
|
|
2622
|
+
var FIELD_NAME_RE = /^[A-Za-z0-9 _-]+$/;
|
|
2623
|
+
var BUILT_IN_FIELDS = /* @__PURE__ */ new Set(["scope"]);
|
|
2620
2624
|
var MS_PER_DAY = 864e5;
|
|
2625
|
+
function parseDecisionsFormat(text) {
|
|
2626
|
+
const m = MARKER_RE.exec(text);
|
|
2627
|
+
if (!m) return null;
|
|
2628
|
+
const problems = [];
|
|
2629
|
+
const fields = [];
|
|
2630
|
+
const version = Number(m[1]);
|
|
2631
|
+
if (version !== KNOWN_FORMAT_VERSION) {
|
|
2632
|
+
problems.push(
|
|
2633
|
+
`declares decisions-format version ${version}; this etymd understands version ${KNOWN_FORMAT_VERSION} \u2014 checked as version ${KNOWN_FORMAT_VERSION}.`
|
|
2634
|
+
);
|
|
2635
|
+
}
|
|
2636
|
+
const attrs = (m[2] ?? "").trim();
|
|
2637
|
+
if (!attrs) return { fields, problems };
|
|
2638
|
+
const declared = /^fields=(.*)$/.exec(attrs);
|
|
2639
|
+
if (!declared) {
|
|
2640
|
+
problems.push(`marker attribute \`${attrs}\` is not understood \u2014 ignored (only \`fields=\`).`);
|
|
2641
|
+
return { fields, problems };
|
|
2642
|
+
}
|
|
2643
|
+
const seen = new Set(BUILT_IN_FIELDS);
|
|
2644
|
+
for (const raw of declared[1].split(",")) {
|
|
2645
|
+
const name = raw.trim();
|
|
2646
|
+
if (!name) continue;
|
|
2647
|
+
if (!FIELD_NAME_RE.test(name)) {
|
|
2648
|
+
problems.push(
|
|
2649
|
+
`declared field \`${name}\` is not a usable field name (letters, digits, spaces, \`-\`, \`_\`) \u2014 not checked.`
|
|
2650
|
+
);
|
|
2651
|
+
continue;
|
|
2652
|
+
}
|
|
2653
|
+
const key = name.toLowerCase();
|
|
2654
|
+
if (seen.has(key)) continue;
|
|
2655
|
+
seen.add(key);
|
|
2656
|
+
fields.push(name);
|
|
2657
|
+
}
|
|
2658
|
+
if (fields.length === 0 && problems.length === 0) {
|
|
2659
|
+
problems.push("marker declares `fields=` with no field names \u2014 no extra fields checked.");
|
|
2660
|
+
}
|
|
2661
|
+
return { fields, problems };
|
|
2662
|
+
}
|
|
2663
|
+
function hasField(block, name) {
|
|
2664
|
+
return new RegExp(`${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s*]*:`).test(block);
|
|
2665
|
+
}
|
|
2621
2666
|
function parseDecisionEntries(text) {
|
|
2622
2667
|
const headings = [...text.matchAll(/^## .*$/gm)];
|
|
2623
2668
|
const entries = [];
|
|
@@ -2673,9 +2718,23 @@ function checkIdSequence(file, entries) {
|
|
|
2673
2718
|
}
|
|
2674
2719
|
return findings;
|
|
2675
2720
|
}
|
|
2676
|
-
function checkFormatFields(file, entries, today) {
|
|
2721
|
+
function checkFormatFields(file, entries, today, declaredFields) {
|
|
2677
2722
|
const findings = [];
|
|
2678
2723
|
for (const entry of entries) {
|
|
2724
|
+
for (const field of declaredFields) {
|
|
2725
|
+
if (hasField(entry.block, field)) continue;
|
|
2726
|
+
findings.push({
|
|
2727
|
+
id: `${LENS_ID4}/field-missing:${file}:${entry.id}:${field}`,
|
|
2728
|
+
lens: LENS_ID4,
|
|
2729
|
+
tier: "gap",
|
|
2730
|
+
claim: `${file} ${entry.id} has no ${field}: field`,
|
|
2731
|
+
evidence: [`${file}: ${entry.id}`, `${file} marker declares required field \`${field}\``],
|
|
2732
|
+
why: "The file declares this field required on every entry after the marker; whatever reads the record for it finds nothing here.",
|
|
2733
|
+
action: `Add a ${field}: line to ${entry.id}.`,
|
|
2734
|
+
effort: "S",
|
|
2735
|
+
confidence: "high"
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2679
2738
|
if (!/Scope[\s*]*:/.test(entry.block)) {
|
|
2680
2739
|
findings.push({
|
|
2681
2740
|
id: `${LENS_ID4}/scope-missing:${file}:${entry.id}`,
|
|
@@ -2791,14 +2850,21 @@ var stateFreshnessLens = {
|
|
|
2791
2850
|
}
|
|
2792
2851
|
const entries = parseDecisionEntries(text);
|
|
2793
2852
|
findings.push(...checkIdSequence(a.path, entries));
|
|
2794
|
-
|
|
2853
|
+
const format = parseDecisionsFormat(text);
|
|
2854
|
+
if (!format) {
|
|
2795
2855
|
disclosures.push(
|
|
2796
2856
|
`${a.path} carries no \`${DECISIONS_FORMAT_MARKER}\` marker \u2014 format checks skipped (forward-only, never retroactive); id-sequence checks still ran.`
|
|
2797
2857
|
);
|
|
2798
2858
|
outOfScope.push(a.path);
|
|
2799
2859
|
continue;
|
|
2800
2860
|
}
|
|
2801
|
-
|
|
2861
|
+
for (const problem of format.problems) disclosures.push(`${a.path}: ${problem}`);
|
|
2862
|
+
if (format.fields.length > 0) {
|
|
2863
|
+
disclosures.push(
|
|
2864
|
+
`${a.path} declares required entry fields: ${format.fields.join(", ")} \u2014 checked on every entry (etymd attaches no meaning to the names).`
|
|
2865
|
+
);
|
|
2866
|
+
}
|
|
2867
|
+
findings.push(...checkFormatFields(a.path, entries, today, format.fields));
|
|
2802
2868
|
}
|
|
2803
2869
|
disclosures.push(
|
|
2804
2870
|
`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.`
|
|
@@ -3,8 +3,8 @@ import { applyFiles } from './chunk-EF6BPBG6.js';
|
|
|
3
3
|
import { planWorkflow } from './chunk-KTIEYKFK.js';
|
|
4
4
|
import './chunk-K7QBTANO.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-XJMN2HMH.js';
|
|
7
|
+
import { VERSION } from './chunk-OGIHOTLP.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-XJMN2HMH.js';
|
|
4
|
+
import './chunk-OGIHOTLP.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