@sentropic/track 0.96.2 → 0.97.1

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.
@@ -7,11 +7,12 @@ export interface DesyncFinding {
7
7
  hint: string;
8
8
  }
9
9
  /**
10
- * SPEC §4 round-trip / desync rule: when an Item's `body` references a markdown file (a single-line
11
- * path ending in `.md`), that file MUST exist and its H1 title MUST match the Item title. A missing
12
- * file or a title mismatch is a desync finding (MVP reports; it never auto-repairs). Inline-prose
13
- * bodies (the common case, incl. BRANCH-imported items) are not file references and are skipped.
14
- * Each finding carries a `hint` — a suggested fix the human/agent may apply (track never does).
10
+ * SPEC §4 round-trip / desync rule: when an Item's `body` is a markdown file reference (see
11
+ * {@link resolveMarkdownRef}), that file MUST exist and its H1 title MUST match the Item title. A
12
+ * missing file or a title mismatch is a desync finding (MVP reports; it never auto-repairs).
13
+ * Inline-prose bodies (the common case, incl. BRANCH-imported items and spec citations) are not file
14
+ * references and are skipped. Each finding carries a `hint` — a suggested fix the human/agent may
15
+ * apply (track never does).
15
16
  */
16
17
  export declare function desyncFindings(state: State, cwd: string): DesyncFinding[];
17
18
  //# sourceMappingURL=desync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"desync.d.ts","sourceRoot":"","sources":["../../src/cli/desync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,kGAAkG;IAClG,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,EAAE,CAkCzE"}
1
+ {"version":3,"file":"desync.d.ts","sourceRoot":"","sources":["../../src/cli/desync.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAE7C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,QAAQ,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;IACd,kGAAkG;IAClG,IAAI,EAAE,MAAM,CAAA;CACb;AA6CD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,aAAa,EAAE,CAmCzE"}
@@ -1,20 +1,70 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { isAbsolute, join } from 'node:path';
3
3
  /**
4
- * SPEC §4 round-trip / desync rule: when an Item's `body` references a markdown file (a single-line
5
- * path ending in `.md`), that file MUST exist and its H1 title MUST match the Item title. A missing
6
- * file or a title mismatch is a desync finding (MVP reports; it never auto-repairs). Inline-prose
7
- * bodies (the common case, incl. BRANCH-imported items) are not file references and are skipped.
8
- * Each finding carries a `hint` a suggested fix the human/agent may apply (track never does).
4
+ * Decide whether an item `body` is a markdown *file reference* (SPEC §4 round-trip). Returns the
5
+ * resolved path plus whether it exists on disk, or `null` for everything that is NOT a reference
6
+ * prose, a `.md`-suffixed domain/email, or any body that is not unambiguously a single path.
7
+ *
8
+ * The round-trip rule must also fire on a *missing* file, so existence alone cannot be the gate: for
9
+ * an absent file we still need a syntactic decision "did the author mean a path?". That decision is
10
+ * deliberately CONSERVATIVE — precision over recall — because the reported defect was prose being
11
+ * mistaken for a path, and a `.md`-suffix is a false signal (`.md` is also Moldova's ccTLD, and any
12
+ * sentence can end in a "…foo.md" citation). The gate is:
13
+ *
14
+ * 1. If the resolved path EXISTS on disk, it is a reference (ground truth — bare or nested, with
15
+ * spaces or not). This is what validates a real spec file's H1 against the item title.
16
+ * 2. Otherwise it is a reference only when the body is a single, unambiguous path *token*:
17
+ * - it contains a directory separator `/` (a nested path such as `docs/specs/foo.md`); a
18
+ * bare word / domain / email like `service.md` or `contact@service.md` is NOT a reference
19
+ * unless it actually exists (rule 1), so `.md` TLDs never desync;
20
+ * - it contains NO whitespace — prose that merely cites a spec ("… Spec: docs/x.md") has
21
+ * interior whitespace and is not a path token;
22
+ * - it contains no `:` — a glued prose label ("Ref:docs/x.md", "See:docs/x.md") is prose,
23
+ * not a clean path (`:` is not used in this repo's paths).
24
+ *
25
+ * POLICY — ESCALATED to the conductor (two intentional, reversible defaults):
26
+ * (a) A MISSING path that contains whitespace (e.g. `docs/Getting Started.md`) is skipped as
27
+ * prose. This is IRREDUCIBLE: a spaced path is character-for-character indistinguishable from
28
+ * a spaced prose body that ends in a "…/foo.md" citation (both have whitespace + `/` + `.md`),
29
+ * so flagging the former necessarily re-flags the latter — i.e. re-opens the reported bug. The
30
+ * repo's spec-file convention uses hyphenated, space-free names, so precision-first is safe
31
+ * here today. If spaced spec filenames are ever adopted, relax the `\s` rule below (one line)
32
+ * and accept that prose citations will desync again.
33
+ * (b) A MISSING bare name (no `/`) is not validated, so `.md` domains/emails never desync. A bare
34
+ * spec is validated only once it exists (rule 1).
35
+ */
36
+ function resolveMarkdownRef(body, cwd) {
37
+ if (!body.endsWith('.md'))
38
+ return null;
39
+ const path = isAbsolute(body) ? body : join(cwd, body);
40
+ if (existsSync(path))
41
+ return { path, exists: true };
42
+ if (/\s/.test(body))
43
+ return null; // prose / spaced citation — not a single path token
44
+ if (!body.includes('/'))
45
+ return null; // bare word / domain / email that does not exist
46
+ if (body.includes(':'))
47
+ return null; // glued prose label (Ref:/See:/Spec:) — not a clean path
48
+ return { path, exists: false }; // a clean, nested, missing path: a genuine desync
49
+ }
50
+ /**
51
+ * SPEC §4 round-trip / desync rule: when an Item's `body` is a markdown file reference (see
52
+ * {@link resolveMarkdownRef}), that file MUST exist and its H1 title MUST match the Item title. A
53
+ * missing file or a title mismatch is a desync finding (MVP reports; it never auto-repairs).
54
+ * Inline-prose bodies (the common case, incl. BRANCH-imported items and spec citations) are not file
55
+ * references and are skipped. Each finding carries a `hint` — a suggested fix the human/agent may
56
+ * apply (track never does).
9
57
  */
10
58
  export function desyncFindings(state, cwd) {
11
59
  const findings = [];
12
60
  for (const item of state.items.values()) {
13
61
  const ref = item.body?.trim();
14
- if (ref === undefined || !/^[^\n]+\.md$/.test(ref))
62
+ if (ref === undefined)
63
+ continue;
64
+ const resolved = resolveMarkdownRef(ref, cwd);
65
+ if (resolved === null)
15
66
  continue;
16
- const path = isAbsolute(ref) ? ref : join(cwd, ref);
17
- if (!existsSync(path)) {
67
+ if (!resolved.exists) {
18
68
  findings.push({
19
69
  kind: 'desync',
20
70
  itemId: item.id,
@@ -23,7 +73,7 @@ export function desyncFindings(state, cwd) {
23
73
  });
24
74
  continue;
25
75
  }
26
- const h1 = /^#\s+(.+?)\s*$/m.exec(readFileSync(path, 'utf8'))?.[1];
76
+ const h1 = /^#\s+(.+?)\s*$/m.exec(readFileSync(resolved.path, 'utf8'))?.[1];
27
77
  if (h1 === undefined) {
28
78
  // SPEC §4 requires the H1 to MATCH the title; a file with no H1 cannot match.
29
79
  findings.push({
@@ -1 +1 @@
1
- {"version":3,"file":"desync.js","sourceRoot":"","sources":["../../src/cli/desync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAY5C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,KAAY,EAAE,GAAW;IACtD,MAAM,QAAQ,GAAoB,EAAE,CAAA;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAA;QAC7B,IAAI,GAAG,KAAK,SAAS,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,SAAQ;QAC5D,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QACnD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,MAAM,EAAE,gCAAgC,GAAG,EAAE;gBAC7C,IAAI,EAAE,WAAW,GAAG,mBAAmB,IAAI,CAAC,KAAK,qCAAqC;aACvF,CAAC,CAAA;YACF,SAAQ;QACV,CAAC;QACD,MAAM,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAClE,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,8EAA8E;YAC9E,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,MAAM,EAAE,kCAAkC,GAAG,EAAE;gBAC/C,IAAI,EAAE,0BAA0B,IAAI,CAAC,KAAK,SAAS,GAAG,GAAG;aAC1D,CAAC,CAAA;QACJ,CAAC;aAAM,IAAI,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,MAAM,EAAE,OAAO,EAAE,oBAAoB,IAAI,CAAC,KAAK,MAAM,GAAG,GAAG;gBAC3D,IAAI,EAAE,sCAAsC,EAAE,oBAAoB,GAAG,WAAW,IAAI,CAAC,KAAK,GAAG;aAC9F,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
1
+ {"version":3,"file":"desync.js","sourceRoot":"","sources":["../../src/cli/desync.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAY5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,SAAS,kBAAkB,CAAC,IAAY,EAAE,GAAW;IACnD,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACtC,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACtD,IAAI,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;IACnD,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA,CAAC,oDAAoD;IACrF,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA,CAAC,iDAAiD;IACtF,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA,CAAC,yDAAyD;IAC7F,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA,CAAC,kDAAkD;AACnF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,cAAc,CAAC,KAAY,EAAE,GAAW;IACtD,MAAM,QAAQ,GAAoB,EAAE,CAAA;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAA;QAC7B,IAAI,GAAG,KAAK,SAAS;YAAE,SAAQ;QAC/B,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QAC7C,IAAI,QAAQ,KAAK,IAAI;YAAE,SAAQ;QAC/B,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrB,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,MAAM,EAAE,gCAAgC,GAAG,EAAE;gBAC7C,IAAI,EAAE,WAAW,GAAG,mBAAmB,IAAI,CAAC,KAAK,qCAAqC;aACvF,CAAC,CAAA;YACF,SAAQ;QACV,CAAC;QACD,MAAM,EAAE,GAAG,iBAAiB,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;QAC3E,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,8EAA8E;YAC9E,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,MAAM,EAAE,kCAAkC,GAAG,EAAE;gBAC/C,IAAI,EAAE,0BAA0B,IAAI,CAAC,KAAK,SAAS,GAAG,GAAG;aAC1D,CAAC,CAAA;QACJ,CAAC;aAAM,IAAI,EAAE,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;YAC7B,QAAQ,CAAC,IAAI,CAAC;gBACZ,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,MAAM,EAAE,OAAO,EAAE,oBAAoB,IAAI,CAAC,KAAK,MAAM,GAAG,GAAG;gBAC3D,IAAI,EAAE,sCAAsC,EAAE,oBAAoB,GAAG,WAAW,IAAI,CAAC,KAAK,GAAG;aAC9F,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentropic/track",
3
- "version": "0.96.2",
3
+ "version": "0.97.1",
4
4
  "description": "Typed product-backlog and spec/plan/UAT system of record for the sentropic ecosystem (record-only MVP).",
5
5
  "license": "MIT",
6
6
  "author": "Fabien Antoine",