dunstan 0.1.12 → 0.1.13

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
@@ -29,7 +29,7 @@ report a pull request's handback before it says done ([`plugins/dunstan/`](plugi
29
29
  /plugin install dunstan@barglabs
30
30
  ```
31
31
 
32
- The plugin runs the MCP server with `npx -y dunstan@0.1.12 mcp`, the `dunstan` npm package at that
32
+ The plugin runs the MCP server with `npx -y dunstan@0.1.13 mcp`, the `dunstan` npm package at that
33
33
  exact version: one self-contained file, no dependencies.
34
34
 
35
35
  **The token.** When you enable the plugin, Claude Code asks for an optional GitHub token and keeps it
package/dist/dunstan.mjs CHANGED
@@ -9852,7 +9852,7 @@ async function reportFromComment(client, repository, number4, login) {
9852
9852
  // src/record/checker.ts
9853
9853
  import { readFileSync } from "node:fs";
9854
9854
  var CHECKER_NAME = "dunstan";
9855
- var CHECKER_VERSION = "0.1.12";
9855
+ var CHECKER_VERSION = "0.1.13";
9856
9856
  function checkerIdentity(artifact) {
9857
9857
  return {
9858
9858
  name: CHECKER_NAME,
@@ -10515,7 +10515,7 @@ var FILES = { "handback-block-0.1.schema.json": `{
10515
10515
  },
10516
10516
  "readerReason": {
10517
10517
  "type": "string",
10518
- "pattern": "^(?:no_matching_record_item|declared_item_not_among_candidates|candidate_not_in_evidence|ambiguous_candidates|workflow_not_in_record|workflow_name_ambiguous|no_comparable_record_field|file_list_truncated|checks_incomplete|all_succeeded_mismatch|test_not_executed|test_outcome_mismatch|not_closing|not_found|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request_files|check_runs|workflow_runs|pull_request_commits|closing_references|repository|issue|timeline|artifact))$"
10518
+ "pattern": "^(?:no_matching_record_item|declared_item_not_among_candidates|candidate_not_in_evidence|duplicate_record_item|ambiguous_candidates|workflow_not_in_record|workflow_name_ambiguous|no_comparable_record_field|file_list_truncated|checks_incomplete|all_succeeded_mismatch|test_not_executed|test_outcome_mismatch|not_closing|not_found|closing_link_unsettled|closing_base_not_default|closing_event_unsettled|evidence_field_unpopulated:[A-Za-z0-9_.]+|source_unreadable:(?:pull_request_files|check_runs|workflow_runs|pull_request_commits|closing_references|repository|issue|timeline|artifact))$"
10519
10519
  }
10520
10520
  }
10521
10521
  }
@@ -12626,11 +12626,14 @@ function identifierMatches(claim2, items, repository) {
12626
12626
  const shas = shaTokens(texts);
12627
12627
  const issues = issueRefs(texts, repository);
12628
12628
  const named = (value, inside) => texts.some((t) => t === value || occursBounded(t, value, inside));
12629
- const qualified = (item) => item.type === "test" && item.id !== item.fields[0]?.[1] && named(item.id, WORD);
12629
+ const classnameOf = (item) => item.fields.find(([f]) => f === "classname")?.[1];
12630
+ const qualified = (item) => {
12631
+ const classname = classnameOf(item);
12632
+ return item.type === "test" && classname !== void 0 && named(`${classname}.${item.fields[0]?.[1]}`, WORD);
12633
+ };
12630
12634
  const qualifiedNames = new Set(
12631
12635
  items.filter(qualified).map((item) => item.fields[0]?.[1])
12632
12636
  );
12633
- const classnameOf = (item) => item.fields.find(([f]) => f === "classname")?.[1];
12634
12637
  const classnames = [
12635
12638
  ...new Set(items.filter((item) => item.type === "test").map(classnameOf))
12636
12639
  ].filter((c) => c !== void 0);
@@ -12691,7 +12694,7 @@ function identifierMatches(claim2, items, repository) {
12691
12694
 
12692
12695
  // src/retrieval/items.ts
12693
12696
  function testId(test) {
12694
- return test.classname === void 0 ? test.name : `${test.classname}.${test.name}`;
12697
+ return JSON.stringify([test.classname ?? null, test.name]);
12695
12698
  }
12696
12699
  function recordItems(evidence) {
12697
12700
  const items = [];
@@ -12939,7 +12942,11 @@ function sectionReason(type, evidence) {
12939
12942
  }
12940
12943
  function lookup(type, evidence) {
12941
12944
  const entries = /* @__PURE__ */ new Map();
12942
- const put = (id, value) => entries.set(id, value);
12945
+ const duplicates = /* @__PURE__ */ new Set();
12946
+ const put = (id, value) => {
12947
+ if (entries.has(id)) duplicates.add(id);
12948
+ else entries.set(id, value);
12949
+ };
12943
12950
  const items = evidence.items;
12944
12951
  if (type === "file" && evidence.files?.status === "ok") {
12945
12952
  for (const f of evidence.files.entries) put(f.path, f);
@@ -12953,7 +12960,7 @@ function lookup(type, evidence) {
12953
12960
  } else if (type === "test" && items?.tests.status === "ok") {
12954
12961
  for (const t of items.tests.entries) put(testId(t), t);
12955
12962
  }
12956
- return entries;
12963
+ return { entries, duplicates };
12957
12964
  }
12958
12965
  var ISSUE_REF2 = /^(?:[A-Za-z0-9][A-Za-z0-9-]{0,38}\/[A-Za-z0-9._-]{1,100})?#[1-9][0-9]{0,9}$/;
12959
12966
  function combinedCandidates(typed, floor) {
@@ -13037,7 +13044,7 @@ function checkReaderClaim(claim2, evidence, repository) {
13037
13044
  }
13038
13045
  const missing = sectionReason(type, evidence);
13039
13046
  if (missing !== void 0) return unverifiable2(missing);
13040
- const entries = lookup(type, evidence);
13047
+ const { entries, duplicates } = lookup(type, evidence);
13041
13048
  const typed = claim2.candidates.filter((c) => c.type === type);
13042
13049
  if (typed.some((c) => entries.get(c.id) === void 0)) {
13043
13050
  return unverifiable2("candidate_not_in_evidence");
@@ -13047,6 +13054,7 @@ function checkReaderClaim(claim2, evidence, repository) {
13047
13054
  if (relevant.length === 0) {
13048
13055
  return unverifiable2(truncated ? "file_list_truncated" : "no_matching_record_item");
13049
13056
  }
13057
+ if (relevant.some((c) => duplicates.has(c.id))) return unverifiable2("duplicate_record_item");
13050
13058
  if (type === "check_run") {
13051
13059
  const scoped = namedWorkflowRuns(claim2.text, relevant, evidence);
13052
13060
  if (!Array.isArray(scoped)) return unverifiable2(scoped.reason);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dunstan",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "mcpName": "io.github.BargLabs/dunstan",
5
5
  "description": "Holds a coding agent's completion report to the record: deterministic checks of a declared handback block against the repository's own metadata.",
6
6
  "license": "AGPL-3.0-only",