specguard-mcp 0.1.35 → 0.1.36

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/README.md CHANGED
@@ -628,12 +628,15 @@ body text, whatever file they sit in), clustered by similarity. Answers the refa
628
628
  the overview's per-run rankings cannot: where the same test is written twice, before you delete or
629
629
  merge anything.
630
630
 
631
- **This is the expensive read in this toolset.** The census is linear but measured in seconds —
632
- seven queries at every size, tens of seconds extrapolated at the 20,000-test design point — which
633
- is why the platform serves it only to a client that asks (`?near_duplicates=`, shipped by SPGD-703)
634
- and answers `near_duplicates: null` on the plain overview. Calling this tool **is** the ask;
635
- `get_repository_overview` never sends it, so an agent reading the overview cannot pay the census by
636
- accident.
631
+ **This call is served stored** (SPGD-1474): the platform computes the census once per write that
632
+ moves its inputs — at each ingest, after identity resolution settles the run's identities, and
633
+ again when a run is deleted from the repository (which can move the run the weight figures are
634
+ weighed on) — and keeps it — so the answer returns in
635
+ milliseconds instead of running the minutes-scale computation that used to sit behind the ask and
636
+ never fit this bridge's 30-second deadline. The opt-in ask is unchanged wire contract
637
+ (`?near_duplicates=`, shipped by SPGD-703, still opens the block; the plain overview still answers
638
+ `near_duplicates: null` without it, and `get_repository_overview` never sends the ask). Calling
639
+ this tool **is** the ask.
637
640
 
638
641
  Nothing about the **census** is choosable — the clusters are the repository's, computed over
639
642
  every run; one call returns them all. (The server reads only that the `near_duplicates` key is
@@ -657,6 +660,12 @@ Read the response with its own rules in mind:
657
660
 
658
661
  - `similarity_floor` and `similarity_basis` sit **first** in the block and qualify every figure
659
662
  below them — a cluster count without what "similar" meant is a count you cannot act on.
663
+ - `computed_at` is when the stored census was taken and `weighed_run_id` is the run its weight
664
+ figures are from — read the stamp before acting on the figures. A census read shortly after an
665
+ ingest **or a run deletion** may be the **previous** artifact, carrying its own stamp, while the
666
+ recompute for the new state is still running; a `near_duplicates` block of `null` on this tool
667
+ means no census has
668
+ been computed for the repository yet (never a live computation, never zeros).
660
669
  - `member_count` (texts in the repository, across every run) and `example_count` (examples in the
661
670
  one run `weighed_run_id` names) are **different grains**: a three-example table-driven loop is
662
671
  one member and three examples. Never fold them.
@@ -667,6 +676,21 @@ Read the response with its own rules in mind:
667
676
  run's examples.
668
677
  - `similarity_range` is `[strongest, weakest]`: membership is transitive, similarity is not.
669
678
  - `total_seconds` is `null` where nothing was timed — never a zero that would read as free.
679
+ - `layer_source` at the head of the block states where the layer dimension comes from: **declared
680
+ via the intent protocol** when at least one clustered member's examples declared a layer, `null`
681
+ on a suite that declared nothing — absence stated, never an empty fiction. The layers are
682
+ DECLARED, never inferred: no path or directory convention is consulted anywhere in the cut (a
683
+ `request`-declared test under `spec/models/` reports `request`).
684
+ - `layer_redundancy` classifies each cluster: `cross_layer` when its members declared two or more
685
+ DISTINCT layers (the same behaviour covered on several levels — the test-pyramid question),
686
+ `same_layer` when confined to one (a plain duplicate), `null` when its members declared nothing
687
+ — neither state, never folded into `same_layer`.
688
+ - `layer_groups` holds the members grouped by declared layer (alphabetically, the undeclared LAST
689
+ as the `layer: null` group), each group's members in the same five-field shape the flat list
690
+ serves. A member whose own examples declared two layers appears in EACH group it declared, so
691
+ the groups' sizes can exceed `member_count` by exactly those members — that is the finding, not
692
+ an arithmetic bug — and a member that declared nothing stays in its cluster's null group, never
693
+ dropped.
670
694
  - `clusters: []` with a real `identity_count` is the **success** state (nothing reads alike); the
671
695
  three silences — nothing ingested, nothing embedded, nothing alike — are kept distinguishable by
672
696
  `recorded_count` / `identity_count` / the list itself.
@@ -4,7 +4,9 @@ import type { ToolDefinition } from "./types.js";
4
4
  * platform by SPGD-703 (`specguard` `c43dc19`, 2026-08-28), which added the
5
5
  * `near_duplicates` block to `RepositoryOverview` behind an opt-in ask.
6
6
  *
7
- * == What the block is, and why it is behind an ask at all
7
+ * == What the block is, and why it is behind an ask at all (this section is
8
+ * the pre-SPGD-1474 story — the cost was live on the request; the next section
9
+ * is what changed)
8
10
  *
9
11
  * It is the suite-wide near-duplicate census: which tests READ alike — same
10
12
  * body text, not same file — clustered by the engine SPGD-369 shipped
@@ -21,6 +23,27 @@ import type { ToolDefinition } from "./types.js";
21
23
  * else. Splitting the tools splits the cost along exactly the line the server
22
24
  * drew.
23
25
  *
26
+ * == SPGD-1474: the census is computed at ingest, and this read is stored
27
+ *
28
+ * The minutes-scale compute no longer sits behind the request (and could never
29
+ * fit this bridge's 30-second default deadline). The server computes the census
30
+ * once per write that moves its inputs — at each ingest, after identity
31
+ * resolution settles the run's identities, and again when a run is deleted from
32
+ * the repository (which can move the run the weight figures are weighed on) —
33
+ * persists it, and serves the stored artifact, so a call here costs one stored
34
+ * read and returns in milliseconds. The opt-in ask is unchanged wire contract
35
+ * (`?near_duplicates=` still opens the block; the plain overview still answers
36
+ * `null` without it), and the tool split still stands: one call, one census,
37
+ * no accidental cost. What changed is the latency story and the honesty
38
+ * machinery around it: the block carries `computed_at` (when the stored
39
+ * artifact was taken) and `weighed_run_id` (which run its weight figures are
40
+ * from), so a consumer can always tell how fresh the census it is reading is.
41
+ * A request arriving between a completed write and the finished recompute
42
+ * serves the PREVIOUS stored census with its own stamp — never a live
43
+ * computation, never an unstamped answer. Between those writes (an ingest, or a
44
+ * run deletion) the stored census is exactly what a live computation would
45
+ * return: the inputs change only there.
46
+ *
24
47
  * == The ask is always sent, and always spelled `"true"`
25
48
  *
26
49
  * The server reads only whether the parameter is PRESENT
@@ -5,7 +5,9 @@ import { optionalString } from "./args.js";
5
5
  * platform by SPGD-703 (`specguard` `c43dc19`, 2026-08-28), which added the
6
6
  * `near_duplicates` block to `RepositoryOverview` behind an opt-in ask.
7
7
  *
8
- * == What the block is, and why it is behind an ask at all
8
+ * == What the block is, and why it is behind an ask at all (this section is
9
+ * the pre-SPGD-1474 story — the cost was live on the request; the next section
10
+ * is what changed)
9
11
  *
10
12
  * It is the suite-wide near-duplicate census: which tests READ alike — same
11
13
  * body text, not same file — clustered by the engine SPGD-369 shipped
@@ -22,6 +24,27 @@ import { optionalString } from "./args.js";
22
24
  * else. Splitting the tools splits the cost along exactly the line the server
23
25
  * drew.
24
26
  *
27
+ * == SPGD-1474: the census is computed at ingest, and this read is stored
28
+ *
29
+ * The minutes-scale compute no longer sits behind the request (and could never
30
+ * fit this bridge's 30-second default deadline). The server computes the census
31
+ * once per write that moves its inputs — at each ingest, after identity
32
+ * resolution settles the run's identities, and again when a run is deleted from
33
+ * the repository (which can move the run the weight figures are weighed on) —
34
+ * persists it, and serves the stored artifact, so a call here costs one stored
35
+ * read and returns in milliseconds. The opt-in ask is unchanged wire contract
36
+ * (`?near_duplicates=` still opens the block; the plain overview still answers
37
+ * `null` without it), and the tool split still stands: one call, one census,
38
+ * no accidental cost. What changed is the latency story and the honesty
39
+ * machinery around it: the block carries `computed_at` (when the stored
40
+ * artifact was taken) and `weighed_run_id` (which run its weight figures are
41
+ * from), so a consumer can always tell how fresh the census it is reading is.
42
+ * A request arriving between a completed write and the finished recompute
43
+ * serves the PREVIOUS stored census with its own stamp — never a live
44
+ * computation, never an unstamped answer. Between those writes (an ingest, or a
45
+ * run deletion) the stored census is exactly what a live computation would
46
+ * return: the inputs change only there.
47
+ *
25
48
  * == The ask is always sent, and always spelled `"true"`
26
49
  *
27
50
  * The server reads only whether the parameter is PRESENT
@@ -78,10 +101,20 @@ const nearDuplicateClusters = {
78
101
  "(same body text, whatever file they sit in), clustered by similarity. Answers the refactoring " +
79
102
  "question the overview's per-run rankings cannot: where is the same test written twice, before " +
80
103
  "you delete or merge anything. " +
81
- "THIS IS THE EXPENSIVE READ ON THIS BRIDGE: the census is linear but measured in seconds — seven " +
82
- "queries at every size, tens of seconds extrapolated at the 20,000-test design point — which is " +
83
- "exactly why the server serves it only to a client that asks (`?near_duplicates=`) and answers " +
84
- "`near_duplicates: null` on the plain overview. Calling this tool IS the ask; nothing about the " +
104
+ "This call is SERVED STORED: the server computes the census at each ingest and on each run " +
105
+ "deletion and keeps it, so the " +
106
+ "answer returns in milliseconds instead of running the minutes-scale computation that used to " +
107
+ "sit behind the ask (and never fit this bridge's 30-second deadline). READ THE STAMP: " +
108
+ "`computed_at` is when the stored artifact was taken and `weighed_run_id` is the run its weight " +
109
+ "figures are from — a census read shortly after an ingest or a run deletion may be the PREVIOUS " +
110
+ "artifact, stamped, " +
111
+ "while the recompute runs; between such writes the stored census is exactly what a live computation " +
112
+ "would return. The opt-in ask is unchanged wire contract: the server answers `near_duplicates: " +
113
+ "null` on the plain overview (no ask, not one query), and `null` ON THIS TOOL means no census has " +
114
+ "been computed for the repository yet — a repository that has never ingested, read in the window " +
115
+ "before its first computation lands. It is never a live computation and never zeros: a repository " +
116
+ "whose every test reads differently serves a stored block with `clusters: []` and real counts. " +
117
+ "Calling this tool IS the ask; nothing about the " +
85
118
  "census is choosable — the clusters are the repository's, computed over every run, and one call " +
86
119
  "returns them all. WHICH repository is censused is the one choice there is: pass `repository` " +
87
120
  "(a numeric id from `list_repositories`) to census that named repository under either member " +
@@ -102,6 +135,21 @@ const nearDuplicateClusters = {
102
135
  "[strongest, weakest]: membership is transitive, similarity is not, and the gap between the edges " +
103
136
  "is the merge risk. `total_seconds` is raw and `null` where nothing was timed — never a zero " +
104
137
  "that would read as free. " +
138
+ "READ THE LAYER CUT AS DECLARATIONS, NEVER AS A GUESS: `layer_source` at the head of the block " +
139
+ "states where the layer dimension comes from — it is 'declared via the intent protocol' when at " +
140
+ "least one clustered member's examples declared a layer, and `null` on a suite that declared " +
141
+ "nothing, which is absence STATED, not an empty grouping to read as a finding. Each cluster " +
142
+ "carries `layer_redundancy` — `cross_layer` when its members declared two or more DISTINCT " +
143
+ "layers (the same behaviour covered on several levels: the test-pyramid question), `same_layer` " +
144
+ "when confined to one (a plain duplicate), `null` when its members declared nothing, which is " +
145
+ "neither and must not be folded into `same_layer` — and `layer_groups`, the members grouped by " +
146
+ "the layer their examples declared, alphabetically, with the undeclared LAST as the " +
147
+ "`layer: null` group. No path or directory convention is consulted anywhere in the cut: a " +
148
+ "`request`-declared test living under spec/models is reported `request`, because declarations " +
149
+ "do not lie and paths do. A member whose own examples declared two layers appears in EACH group " +
150
+ "it declared — so the groups' sizes can exceed `member_count` by exactly those members, which " +
151
+ "is the finding, not an arithmetic bug — and a member that declared nothing stays in its " +
152
+ "cluster's null group, never dropped. " +
105
153
  "A quiet answer is a FINDING, not a gap: `clusters: []` with a real `identity_count` is the " +
106
154
  "success state (nothing reads alike), and the three silences — nothing ingested " +
107
155
  "(`recorded_count: 0`), nothing embedded (`identity_count: 0`), nothing alike — are kept " +
@@ -1 +1 @@
1
- {"version":3,"file":"near-duplicate-clusters.js","sourceRoot":"","sources":["../../../src/tools/near-duplicate-clusters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsEG;AACH,MAAM,qBAAqB,GAAmB;IAC5C,IAAI,EAAE,yBAAyB;IAE/B,KAAK,EAAE,yBAAyB;IAEhC,WAAW,EACT,2FAA2F;QAC3F,gGAAgG;QAChG,gGAAgG;QAChG,gCAAgC;QAChC,kGAAkG;QAClG,iGAAiG;QACjG,gGAAgG;QAChG,iGAAiG;QACjG,iGAAiG;QACjG,+FAA+F;QAC/F,8FAA8F;QAC9F,gGAAgG;QAChG,6FAA6F;QAC7F,WAAW;QACX,iGAAiG;QACjG,6FAA6F;QAC7F,+FAA+F;QAC/F,gGAAgG;QAChG,8DAA8D;QAC9D,iGAAiG;QACjG,8FAA8F;QAC9F,iGAAiG;QACjG,iGAAiG;QACjG,oGAAoG;QACpG,2FAA2F;QAC3F,mGAAmG;QACnG,8FAA8F;QAC9F,2BAA2B;QAC3B,6FAA6F;QAC7F,iFAAiF;QACjF,0FAA0F;QAC1F,6EAA6E;QAC7E,wFAAwF;QACxF,sFAAsF;QACtF,4FAA4F;QAC5F,4FAA4F;QAC5F,wFAAwF;QACxF,0FAA0F;QAC1F,wFAAwF;QACxF,uCAAuC;QACvC,2FAA2F;QAC3F,2FAA2F;QAC3F,2FAA2F;QAC3F,4FAA4F;QAC5F,8FAA8F;QAC9F,kBAAkB;IAEpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,UAAU,EAAE;gBACV,IAAI,EAAE,QAAQ;gBACd,WAAW,EACT,sFAAsF;oBACtF,iFAAiF;oBACjF,6CAA6C;oBAC7C,wFAAwF;oBACxF,6EAA6E;oBAC7E,+EAA+E;oBAC/E,qFAAqF;oBACrF,kFAAkF;oBAClF,4EAA4E;oBAC5E,iFAAiF;oBACjF,wEAAwE;oBACxE,4DAA4D;oBAC5D,mFAAmF;oBACnF,6DAA6D;aAChE;SACF;QACD,oBAAoB,EAAE,KAAK;KAC5B;IAED,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO;QACrB,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC,CAAC;QACpE,uEAAuE;QACvE,oDAAoD;QACpD,wEAAwE;QACxE,yEAAyE;QACzE,sEAAsE;QACtE,wEAAwE;QACxE,0EAA0E;QAC1E,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,gBAAgB,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QAEnE,MAAM,QAAQ,GAAG,MAAM,aAAa,CAClC,GAAG,EACH,IAAI,EACJ;YACE,uEAAuE;YACvE,mEAAmE;YACnE,kDAAkD;YAClD,eAAe,EAAE,MAAM;SACxB,EACD,OAAO,CAAC,KAAK,CACd,CAAC;QAEF,OAAO;YACL,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACvC,UAAU,EAAE,QAAQ;SACrB,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"near-duplicate-clusters.js","sourceRoot":"","sources":["../../../src/tools/near-duplicate-clusters.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specguard-mcp",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "MCP server exposing SpecGuard suite intelligence to AI coding agents",
5
5
  "license": "ISC",
6
6
  "type": "module",