@sdd-method/author-cli 0.16.1 → 0.16.2

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.
@@ -6,7 +6,11 @@
6
6
  // - a member observed after the set date;
7
7
  // - a prior member absent from the export and not declared carried (with a
8
8
  // reason) or removed (with a reason) — never left to be inferred;
9
- // - a member re-exported at the same revision whose bytes differ;
9
+ // - a member re-exported at the same revision whose bytes differ, unless the only
10
+ // difference is files under a path declared --not-source (with a reason) that the
11
+ // prior export holds and the new one does not: analysis a tool wrote into the old
12
+ // export, not the repository. The prior baseline is never edited, the excused files
13
+ // and the reason are recorded, and a declaration that excuses nothing is refused;
10
14
  // - no recorded credential scan, or one with findings but no disposition —
11
15
  // a snapshot owner often cannot remediate upstream code, so findings land
12
16
  // with the disposition on the record, never as a false `clean`;
@@ -68,6 +72,17 @@ export async function land(inputs, write) {
68
72
  const s = splitFolder(m.exported_folder_name);
69
73
  return s ? [[s.stem, m.canonical_repo_key]] : [];
70
74
  }));
75
+ const notSource = new Map();
76
+ for (const [raw, reason] of inputs.notSource ?? []) {
77
+ const p = raw.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
78
+ if (p === "" || p.startsWith("/") || p.split("/").includes(".."))
79
+ throw new SnapshotError(`--not-source '${raw}' must be a relative path inside a member folder`);
80
+ if (!reason.trim())
81
+ throw new SnapshotError(`--not-source ${raw} needs a reason: --not-source <path>=<why it is not source>`);
82
+ notSource.set(p, reason.trim());
83
+ }
84
+ const declaredPath = (f) => [...notSource.keys()].find((p) => f === p || f.startsWith(`${p}/`));
85
+ const excusedBy = new Map();
71
86
  const known = inputs.sddPath ? await knownRepositoryIds(inputs.sddPath) : new Set();
72
87
  const keyProblems = [];
73
88
  const members = [];
@@ -105,18 +120,35 @@ export async function land(inputs, write) {
105
120
  const before = await hashTree(memberBytesDir(areaRoot, prior, p));
106
121
  const after = await hashTree(join(dir, folder));
107
122
  const d = diffTrees(before, after);
108
- const changed = d.added.length + d.removed.length + d.modified.length;
123
+ // Only a file the prior export holds and the new one lacks, under a declared path, is excused.
124
+ const excused = d.removed.filter((f) => declaredPath(f) !== undefined);
125
+ const changed = d.added.length + d.modified.length + d.removed.length - excused.length;
109
126
  if (changed > 0) {
110
- throw new SnapshotError(`${key} was re-exported at the same revision ${revision} as ${prior.snapshot_id}, but ${changed} file(s) differ — one of the two exports is not what its name says`);
127
+ throw new SnapshotError(`${key} was re-exported at the same revision ${revision} as ${prior.snapshot_id}, but ${changed} file(s) differ — one of the two exports is not what its name says` +
128
+ (notSource.size > 0 ? ` (--not-source excuses only files the prior export holds and this one does not, under a declared path)` : ""));
111
129
  }
112
130
  entry.reverified_at = md ?? id;
113
131
  entry.reverification = `re-exported at the same revision as ${prior.snapshot_id}; ${after.size} file(s) byte-identical`;
132
+ if (excused.length > 0) {
133
+ const byPath = new Map();
134
+ for (const f of excused) {
135
+ const p = declaredPath(f);
136
+ byPath.set(p, (byPath.get(p) ?? 0) + 1);
137
+ excusedBy.set(p, (excusedBy.get(p) ?? 0) + 1);
138
+ }
139
+ entry.reverification += `; ${[...byPath].map(([p, n]) => `${n} file(s) under ${p}/ in ${prior.snapshot_id} only, not source: ${notSource.get(p)}`).join("; ")}`;
140
+ write(`note: ${key}: ${excused.length} file(s) under a --not-source path excused (prior export only)\n`);
141
+ }
114
142
  }
115
143
  members.push(entry);
116
144
  }
117
145
  if (keyProblems.length > 0) {
118
146
  throw new SnapshotError(`${keyProblems.length} derived key(s) are not repository ids:\n - ${keyProblems.join("\n - ")}`);
119
147
  }
148
+ const unused = [...notSource.keys()].filter((p) => !excusedBy.has(p));
149
+ if (unused.length > 0) {
150
+ throw new SnapshotError(`--not-source ${unused.join(", ")} excused nothing: no same-revision member's prior export holds files there that this one lacks; drop the declaration`);
151
+ }
120
152
  const removed = [];
121
153
  const unaccounted = [];
122
154
  for (const p of prior?.members ?? []) {
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.17.1",
3
- "built_against_sdd_cli": "5.3.1"
2
+ "version": "0.17.2",
3
+ "built_against_sdd_cli": "5.3.2"
4
4
  }
@@ -72,6 +72,7 @@ export function registerSnapshot(program) {
72
72
  .option("--role-hint <key=role>", "role_hint for a member; continuing members otherwise keep the prior baseline's (repeatable)", collectPairs, new Map())
73
73
  .option("--credential-scan <record>", "tool=…,version=…,date=YYYY-MM-DD,result=clean — or result=<findings>,disposition=<what, and why it stands>")
74
74
  .option("--notes <text>", "coherence caveats for the manifest")
75
+ .option("--not-source <path=reason>", "a path inside member folders that is not source (a folder a tool wrote into the prior export), and why: a same-revision re-export may lack it, recorded on the member; nothing else is excused (repeatable)", collectPairs, new Map())
75
76
  .action(async (area, o) => {
76
77
  const scan = record(o.credentialScan);
77
78
  exit(await runSnapshotLand({
@@ -85,6 +86,7 @@ export function registerSnapshot(program) {
85
86
  removed: o.removed,
86
87
  keys: o.key,
87
88
  roleHints: o.roleHint,
89
+ notSource: o.notSource,
88
90
  ...(scan ? { credentialScan: scan } : {}),
89
91
  ...(o.notes !== undefined ? { notes: o.notes } : {}),
90
92
  }, out));
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "5.3.1",
3
- "built_against_sdd_cli": "5.3.1"
2
+ "version": "5.3.2",
3
+ "built_against_sdd_cli": "5.3.2"
4
4
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sdd-method/author-cli",
3
- "version": "0.16.1",
4
- "sddCliVersion": "5.3.1",
3
+ "version": "0.16.2",
4
+ "sddCliVersion": "5.3.2",
5
5
  "description": "Re-author one SDD's content — scaffold, plan, contracts, conformance, api-surface, snapshots, briefings, catalogue check — plus every read-only verify verb. Ships only the code those verbs reach; it cannot sync the baseline or rebuild the catalogues. Maintenance is in @sdd-method/maintain-cli, administration in @sdd-method/sdd-cli.",
6
6
  "license": "UNLICENSED",
7
7
  "type": "module",