@holmes-lab/holmes-kit 0.20.1 → 0.20.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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  <!-- @implements A-SPEC-209 -->
8
+ ## [0.20.2] - 2026-09-16
9
+
10
+ Two findings the Windows cycle recorded and could not close, closed — without giving autonomy or the
11
+ drift check one inch of slack.
12
+
13
+ ### Fixed
14
+ - **A sealed spec can now admit a root-level file** (A-SPEC-654). `Files to Touch` tokens had to contain
15
+ a slash, so a spec listing `- jest.config.js` produced no token at all and its own anchored write was
16
+ refused as "outside its Files to Touch scope" — measured 2026-09-15 on Windows, where A-SPEC-652.4 had
17
+ to be redesigned around it. A list item whose first word is a filename with an extension is now a token;
18
+ a filename mentioned inside prose, a version-like item (`- 0.20.1`) and a bare word still yield nothing,
19
+ and the enforcer still admits exactly that one path.
20
+ - **A CRLF checkout is no longer reported as skill drift.** Under `core.autocrlf=true`, git rewrote the
21
+ skill files the installer writes with LF, so `doctor` reported three untouched skills as `drifted` and
22
+ advised a refresh that produced LF for the next checkout to convert straight back (measured 2026-09-16,
23
+ Windows). `init`'s `.gitattributes` block now declares `.claude/skills/**/SKILL.md` and
24
+ `.agents/skills/**/SKILL.md` as `text eol=lf`, so checkout stops re-creating the difference. One
25
+ `holmes-kit skills refresh` converges an existing CRLF working tree.
26
+
27
+ ### Unchanged on purpose
28
+ - **Autonomy did not widen with the new tokens.** Measured before the change: 36 A-SPECs in this
29
+ repository have prose-only Files to Touch and 8 of them would have gained tokens — one mentioning
30
+ `handlers.ts` in a sentence would have flipped from human approval to self-approval. The grader now
31
+ requires at least one directory-scoped token, so a spec whose tokens are all root-level files still asks
32
+ a human: a root-level file is project-wide configuration. A test walks every A-SPEC in the store and
33
+ asserts each verdict equals the pre-change verdict.
34
+ - **The drift comparison is still byte-exact.** Folding CRLF into it was implemented, measured against
35
+ A-SPEC-190 §9a — whitespace-touched files are `drifted` *and a refresh reclaims them* — and reverted:
36
+ it would have made the first of those three mutations invisible rather than recoverable. That refusal
37
+ is pinned in the new suite.
38
+
39
+ ### Measured
40
+ - macOS: 503 suites / 6,461 passed / 11 skipped; official `test_run` green, last-green recorded.
41
+ - Still open: the Windows residual single-assertion reds. They need the failure text from the Windows box
42
+ (`npx jest --json`); nothing in this repository names why they fail, and guessing from a macOS run would
43
+ be inventing evidence.
44
+
8
45
  ## [0.20.1] - 2026-09-16
9
46
 
10
47
  The Windows release. 0.20.0's external matrix was run natively on Windows 11 (Node 24, npm 12, pytest 9)
package/dist/.build-id CHANGED
@@ -1 +1 @@
1
- 37e99456-mu2vxydy
1
+ 5796f530-mu3b082d
@@ -61,6 +61,16 @@ exports.ATTRIBUTE_LINES = [
61
61
  '# 남는 체인 이음새는 `holmes-kit ledger rechain`의 일이다.',
62
62
  '.ax/ledger/*.jsonl merge=union',
63
63
  '.ax/approvals/queue.jsonl merge=union',
64
+ // @implements A-SPEC-654
65
+ // The installer writes these with LF and doctor compares them byte for byte (A-SPEC-190 §9a: a
66
+ // whitespace-touched file is `drifted` so a refresh can reclaim it). Under `core.autocrlf=true`
67
+ // checkout rewrote them to CRLF, so three untouched skills read `drifted` and the advised refresh
68
+ // produced LF for the next checkout to convert straight back — measured 2026-09-16 on Windows.
69
+ // Declaring the files settles it where the disagreement is, instead of teaching the drift check to
70
+ // ignore a difference it exists to catch.
71
+ '# @implements A-SPEC-654 — 설치기가 LF 로 쓰는 파일은 체크아웃에서 CRLF 로 바뀌지 않는다.',
72
+ '.claude/skills/**/SKILL.md text eol=lf',
73
+ '.agents/skills/**/SKILL.md text eol=lf',
64
74
  ];
65
75
  /** As `mergeGitignore`, over `.gitattributes` — the same marker block machinery, so idempotence
66
76
  * and user-content preservation are inherited rather than re-implemented. */
@@ -178,6 +178,12 @@ function specApprovalAutonomy(spec, resolveParent, descendants) {
178
178
  return 'hitl';
179
179
  if (paths.some(isHighRiskPath))
180
180
  return 'hitl'; // gate/governance/taint file
181
+ // @implements A-SPEC-654 — a ROOT-level file is project-wide configuration (the test runner, the
182
+ // manifest, the ignore policy): admitting it at the gate was the point of that slice, but grading
183
+ // it auto would be a widening nobody asked for. A spec earns self-approval only when it also names
184
+ // a directory-scoped surface — the same doubt `paths.length === 0` already encodes.
185
+ if (!paths.some((p) => p.includes('/')))
186
+ return 'hitl';
181
187
  return 'auto';
182
188
  }
183
189
  default:
@@ -29,7 +29,18 @@ export declare function isTestPath(relPath: string): boolean;
29
29
  * one bounded fragment, so no ReDoS surface.
30
30
  */
31
31
  export declare function matchesFtt(token: string, relPath: string): boolean;
32
- /** Concrete path-like tokens of a Files-to-Touch section (globs included) — prose yields none. */
32
+ export declare function isRootFileToken(token: string): boolean;
33
+ /**
34
+ * Concrete path-like tokens of a Files-to-Touch section (globs included) — prose yields none.
35
+ *
36
+ * @implements A-SPEC-654
37
+ * The slashed match alone could not see a ROOT-level file, so a sealed spec listing `- jest.config.js`
38
+ * admitted nothing at all and its own anchored write was refused as out of scope (measured 2026-09-15 on
39
+ * Windows; A-SPEC-652.4 was redesigned around it). A bare filename counts only as a LIST ITEM's first
40
+ * word: `- \`tsconfig.test.json\` (신규)` is a token, while `\`cfg.ts\`, \`ddg.ts\` 를 고친다` — prose, and a
41
+ * word carrying a comma — stays what it always was, nothing. The enforcer is unchanged: a root token
42
+ * admits that exact path (`matchesFtt`), and the grader requires a directory-scoped token of its own.
43
+ */
33
44
  export declare function fttPathTokens(fttText: string): string[];
34
45
  export interface ScopeInput {
35
46
  relPath: string;
@@ -25,6 +25,7 @@
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.isTestPath = isTestPath;
27
27
  exports.matchesFtt = matchesFtt;
28
+ exports.isRootFileToken = isRootFileToken;
28
29
  exports.fttPathTokens = fttPathTokens;
29
30
  exports.judgeScope = judgeScope;
30
31
  /** Repo test conventions — the single census every dispensation consumer shares. */
@@ -63,9 +64,37 @@ function matchesFtt(token, relPath) {
63
64
  return relPath === tok; // file literal: that file only
64
65
  return relPath === tok || relPath.startsWith(tok + '/'); // dir literal: its subtree
65
66
  }
66
- /** Concrete path-like tokens of a Files-to-Touch section (globs included) — prose yields none. */
67
+ /**
68
+ * @implements A-SPEC-654
69
+ * A repository-ROOT file named as a Files-to-Touch item: a bare filename with an extension that starts
70
+ * with a letter, so a version (`0.20.1`) is not a file and a bare word (`README`) is not either.
71
+ */
72
+ const ROOT_FILE = /^[\w@.-]+\.[A-Za-z][A-Za-z0-9]*$/;
73
+ function isRootFileToken(token) {
74
+ return !token.includes('/') && ROOT_FILE.test(token);
75
+ }
76
+ /**
77
+ * Concrete path-like tokens of a Files-to-Touch section (globs included) — prose yields none.
78
+ *
79
+ * @implements A-SPEC-654
80
+ * The slashed match alone could not see a ROOT-level file, so a sealed spec listing `- jest.config.js`
81
+ * admitted nothing at all and its own anchored write was refused as out of scope (measured 2026-09-15 on
82
+ * Windows; A-SPEC-652.4 was redesigned around it). A bare filename counts only as a LIST ITEM's first
83
+ * word: `- \`tsconfig.test.json\` (신규)` is a token, while `\`cfg.ts\`, \`ddg.ts\` 를 고친다` — prose, and a
84
+ * word carrying a comma — stays what it always was, nothing. The enforcer is unchanged: a root token
85
+ * admits that exact path (`matchesFtt`), and the grader requires a directory-scoped token of its own.
86
+ */
67
87
  function fttPathTokens(fttText) {
68
- return fttText.match(/[\w@.*-]+(?:\/[\w@.*-]+)+/g) ?? [];
88
+ const tokens = [...(fttText.match(/[\w@.*-]+(?:\/[\w@.*-]+)+/g) ?? [])];
89
+ for (const line of fttText.split('\n')) {
90
+ const item = /^\s*[-*]\s+(.*)$/.exec(line);
91
+ if (!item)
92
+ continue;
93
+ const word = (item[1].trim().split(/\s+/)[0] ?? '').replace(/^[`'"]+|[`'"]+$/g, '');
94
+ if (isRootFileToken(word) && !tokens.includes(word))
95
+ tokens.push(word);
96
+ }
97
+ return tokens;
69
98
  }
70
99
  // @implements A-SPEC-508.1
71
100
  function judgeScope(input) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "//": "@implements A-SPEC-209",
3
3
  "name": "@holmes-lab/holmes-kit",
4
- "version": "0.20.1",
4
+ "version": "0.20.2",
5
5
  "description": "Holmes-Kit — deterministic Agentic Software Engineering (ASE) harness with causal traceability (spec chain + D-CPG + RTM + phase guardrail)",
6
6
  "main": "dist/holmes/mcp/server.js",
7
7
  "types": "dist/holmes/mcp/server.d.ts",