@davidbalzan/groundwork 0.4.2 → 0.4.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidbalzan/groundwork",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Groundwork — an installable AI development workflow (skills + doc methodology) you bolt onto any repo.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -29,7 +29,7 @@
29
29
  "author": "David Balzan",
30
30
  "license": "UNLICENSED",
31
31
  "dependencies": {
32
- "@davidbalzan/groundwork-seam": "0.1.6"
32
+ "@davidbalzan/groundwork-seam": "0.1.7"
33
33
  },
34
34
  "scripts": {
35
35
  "groundwork": "node src/cli.mjs",
@@ -7,7 +7,7 @@ import { countCheckboxes, progressBar } from "../lib/progress.mjs";
7
7
  import { ARTIFACTS, workDocPaths } from "../lib/artifacts.mjs";
8
8
  import { log, bold, green, yellow, dim, cyan } from "../lib/log.mjs";
9
9
  import { adrTripwire } from "../lib/adr-tripwire.mjs";
10
- import { parseFactsDoc, parseWorkDoc, workDocIssues, workDocLegacyWriteIssues, workDocIssuesDetailed, queueItemsOf, doneEntriesOf, workstreamsV1RowsOf } from "@davidbalzan/groundwork-seam";
10
+ import { parseFactsDoc, parseWorkDoc, workDocIssues, workDocLegacyWriteIssues, workDocIssuesDetailed, queueItemsOf, doneEntriesOf, workstreamsV1RowsOf, sweepTagOf, sweepTagCensus } from "@davidbalzan/groundwork-seam";
11
11
 
12
12
  /**
13
13
  * `groundwork doctor` — flag drift between the docs and reality. Offline + deterministic.
@@ -871,6 +871,86 @@ guard("version-truth", () => {
871
871
  }
872
872
  });
873
873
 
874
+ // A sweep census nobody has to grep for.
875
+ //
876
+ // MATCH A TAG BY ITS POSITION, NOT BY ITS OCCURRENCE. A corpus that documents its
877
+ // own tags guarantees the tag string appears in items that do not carry it — items
878
+ // cite other sweeps by name, and every `<!-- queue-prune: -->` receipt quotes the
879
+ // lines it removed. **The trail that makes a move checkable is the trail that makes
880
+ // a naive scan wrong**, and it worsens as the trail grows.
881
+ //
882
+ // Measured on the live corpus: grep 14 · parser-by-occurrence 14 · parser-by-
883
+ // position 13. Using the parser was NECESSARY AND NOT SUFFICIENT. This check exists
884
+ // so the correct number is available without anyone reaching for `grep -c`, because
885
+ // the reaching is the defect and it happened three times in twenty minutes.
886
+ //
887
+ // It reports rather than judges: a tag count is not a problem, and the only failure
888
+ // it can name is a MENTION that outnumbers the CARRIERS, which is the shape that
889
+ // misleads.
890
+ guard("sweep-census", () => {
891
+ if (!queueDocs.length) return;
892
+ const items = queueDocs.flatMap((rel) => queueItemsOf(parseWorkDoc(readText(path.join(docs, rel)))));
893
+ if (!items.length) return;
894
+ const census = sweepTagCensus(items);
895
+ const tags = Object.keys(census).sort();
896
+ if (!tags.length) {
897
+ push("sweep-census", "ok", [`${items.length} item(s), none carrying a sweep tag`]);
898
+ return;
899
+ }
900
+ const lines = tags.map((t) => {
901
+ const carried = census[t];
902
+ // Mentions across ALL item text, the number a grep would return.
903
+ const mentioned = items.filter((i) => new RegExp(`\\[SWEEP:${t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\]`, "i").test(i.text)).length;
904
+ const gap = mentioned - carried;
905
+ return `${t}: ${carried} carried` + (gap > 0 ? ` · ${mentioned} mentioned — a naive scan over-counts by ${gap}` : "");
906
+ });
907
+ push("sweep-census", "ok", [
908
+ `${items.length} item(s) across ${queueDocs.length} work-doc(s), tags matched at POSITION not occurrence`,
909
+ ...lines,
910
+ ]);
911
+ });
912
+
913
+ // A pin that states a count and cannot reproduce it.
914
+ //
915
+ // A STATED METHOD THAT CONTRADICTS ITS OWN PUBLISHED RESULT IS WORSE THAN NO
916
+ // METHOD: it looks auditable, so it invites exactly the trust it cannot bear. The
917
+ // board's `[SWEEP:canon.2]` pin says "13 members" and carries a predicate that
918
+ // returns 9 — the four delivered members were closed and the prose count was not.
919
+ // A reader who runs the predicate finds the discrepancy; a reader who trusts the
920
+ // number does not, and the number is the part that looks like a fact.
921
+ //
922
+ // So a pin asserting a subset must be RE-DERIVABLE, and this re-derives it. The
923
+ // predicate is the pin's own: position-matched sweep tags over parsed queue items,
924
+ // which is the only method of the three that was ever right.
925
+ guard("pin-reproducibility", () => {
926
+ const board = path.join(docs, "WORKSTREAMS.md");
927
+ if (!exists(board) || !queueDocs.length) return;
928
+ const text = readText(board);
929
+ // `### \`[SWEEP:<tag>]\` — pinned at <sha>, <n> members`
930
+ const pins = [...text.matchAll(/^###\s+`\[SWEEP:([a-z0-9._-]+)\]`[^\n]*?(\d+)\s+members/gim)];
931
+ if (!pins.length) {
932
+ push("pin-reproducibility", "info", ["no pinned sweeps on the board — nothing to re-derive"]);
933
+ return;
934
+ }
935
+ const items = queueDocs.flatMap((rel) => queueItemsOf(parseWorkDoc(readText(path.join(docs, rel)))));
936
+ const problems = [];
937
+ const okLines = [];
938
+ for (const [, tag, claimed] of pins) {
939
+ const actual = items.filter((i) => sweepTagOf(i) === tag.toLowerCase()).length;
940
+ if (Number(claimed) !== actual) {
941
+ problems.push(
942
+ `[SWEEP:${tag}] pin claims ${claimed} members; its own predicate returns ${actual}. ` +
943
+ `A stated count that the stated method contradicts is worse than no count — it looks auditable. ` +
944
+ `Re-derive the pin, or record why the difference is expected.`,
945
+ );
946
+ } else {
947
+ okLines.push(`[SWEEP:${tag}]: ${actual} members, predicate reproduces the count`);
948
+ }
949
+ }
950
+ if (problems.length) push("pin-reproducibility", "warn", [...problems, ...okLines]);
951
+ else push("pin-reproducibility", "ok", okLines);
952
+ });
953
+
874
954
  const factsFile = path.join(docs, "FACTS.md");
875
955
  guard("facts-freshness", () => {
876
956
  if (exists(factsFile)) {