@nurix/etna 0.20.42 → 0.20.43

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.
@@ -1,5 +1,5 @@
1
1
  {
2
- "fingerprint": "bd1f2b63adef",
2
+ "fingerprint": "774d2e20c947",
3
3
  "harnesses": {
4
4
  "code": {
5
5
  "plugin": "code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "code",
3
3
  "name": "code",
4
- "version": "3.20.12",
4
+ "version": "3.20.13",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -29,5 +29,5 @@
29
29
  "origin": "https://cdn.nustack.io",
30
30
  "track": "latest"
31
31
  },
32
- "digest": "16ebb3336e2e"
32
+ "digest": "55a739ce31d2"
33
33
  }
@@ -455,7 +455,11 @@ function principleNumbers(lines) {
455
455
  // The body's sentences long enough to state a rule, as content-word sets; headings, tables and the footer drop out.
456
456
  function sentencesOf(lines) {
457
457
  const out = [];
458
+ // The opening states the document's boundary and points at its neighbours; a sentence there is a pointer, not a rule.
459
+ let opened = false;
458
460
  for (const { n, line } of lines) {
461
+ if (/^##\s+/.test(line)) opened = true;
462
+ if (!opened) continue;
459
463
  if (DOCTRINE_CLOSING.test(line)) break;
460
464
  if (/^\s*(#|\|)/.test(line) || !line.trim()) continue;
461
465
  const plain = line.replace(/^\s*([-*]|\d+\.)\s+/, '').replace(/\*\*[^*]+\*\*:?/g, '').replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/`/g, '');
@@ -520,12 +524,17 @@ function lintDoctrine(path, text, lines, disabled, findings) {
520
524
  const words = bare.replace(/\*\*/g, '').replace(/^\s*([-*]|\d+\.)\s+/, '');
521
525
  if (!disabled.has('modal-verb') && /\b(must|should|shall)\b/i.test(words)) findings.push({ path, n, severity: 'warn', id: 'modal-verb', message: 'a modal in a specification — state what happens ("the request is rejected"); being in this document makes it a requirement', excerpt: line.trim().slice(0, 90) });
522
526
  if (!disabled.has('question') && /\?(\s|$)/.test(words)) findings.push({ path, n, severity: 'warn', id: 'question', message: 'a question in prose — state the rule declaratively', excerpt: line.trim().slice(0, 90) });
527
+ // A term the set governs (any sibling's objects:, defined in its Terms table) is a term of art, never a vague synonym.
528
+ const ownTerms = [...new Set([...siblings.flatMap((d) => d.objects), ...listValue((frontmatter(text) || new Map()).get('objects')).map((o) => o.toLowerCase())])];
529
+ const unowned = ownTerms.reduce((acc, term) => acc.replace(new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}s?\\b`, 'gi'), 'X'), words);
523
530
  if (!disabled.has('vague-term')) for (const [pattern, use] of VAGUE_TERMS) {
524
- const m = pattern.exec(words);
531
+ const m = pattern.exec(unowned);
525
532
  if (m) { findings.push({ path, n, severity: 'warn', id: 'vague-term', message: `"${m[0]}" — use the standard term: ${use} (references/style-guide.md § Vocabulary)`, excerpt: line.trim().slice(0, 90) }); break; }
526
533
  }
527
534
  if (!disabled.has('long-sentence')) {
528
- for (const sentence of words.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
535
+ // A bullet's bold label names the rule; the sentence it leads is counted without it.
536
+ const unlabelled = bare.replace(/^\s*([-*]|\d+\.)\s+\*\*[^*]+\*\*:?\s*/, '').replace(/\*\*/g, '');
537
+ for (const sentence of unlabelled.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
529
538
  const list = /:\s/.test(sentence) && (sentence.split(/:\s/).slice(1).join(' ').match(/[,;]/g) || []).length >= 2;
530
539
  const count = (sentence.match(/[\w'’-]+/g) || []).length;
531
540
  if (count > 25 && !list) { findings.push({ path, n, severity: 'warn', id: 'long-sentence', message: `${count} words in one sentence — about twenty-five at most, one idea each; a colon list is exempt`, excerpt: sentence.trim().slice(0, 90) }); break; }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "delivery",
3
3
  "name": "delivery",
4
- "version": "0.2.47",
4
+ "version": "0.2.48",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -14,5 +14,5 @@
14
14
  "origin": "https://cdn.nustack.io",
15
15
  "track": "latest"
16
16
  },
17
- "digest": "0e79c085c86d"
17
+ "digest": "9f42da40515c"
18
18
  }
@@ -455,7 +455,11 @@ function principleNumbers(lines) {
455
455
  // The body's sentences long enough to state a rule, as content-word sets; headings, tables and the footer drop out.
456
456
  function sentencesOf(lines) {
457
457
  const out = [];
458
+ // The opening states the document's boundary and points at its neighbours; a sentence there is a pointer, not a rule.
459
+ let opened = false;
458
460
  for (const { n, line } of lines) {
461
+ if (/^##\s+/.test(line)) opened = true;
462
+ if (!opened) continue;
459
463
  if (DOCTRINE_CLOSING.test(line)) break;
460
464
  if (/^\s*(#|\|)/.test(line) || !line.trim()) continue;
461
465
  const plain = line.replace(/^\s*([-*]|\d+\.)\s+/, '').replace(/\*\*[^*]+\*\*:?/g, '').replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/`/g, '');
@@ -520,12 +524,17 @@ function lintDoctrine(path, text, lines, disabled, findings) {
520
524
  const words = bare.replace(/\*\*/g, '').replace(/^\s*([-*]|\d+\.)\s+/, '');
521
525
  if (!disabled.has('modal-verb') && /\b(must|should|shall)\b/i.test(words)) findings.push({ path, n, severity: 'warn', id: 'modal-verb', message: 'a modal in a specification — state what happens ("the request is rejected"); being in this document makes it a requirement', excerpt: line.trim().slice(0, 90) });
522
526
  if (!disabled.has('question') && /\?(\s|$)/.test(words)) findings.push({ path, n, severity: 'warn', id: 'question', message: 'a question in prose — state the rule declaratively', excerpt: line.trim().slice(0, 90) });
527
+ // A term the set governs (any sibling's objects:, defined in its Terms table) is a term of art, never a vague synonym.
528
+ const ownTerms = [...new Set([...siblings.flatMap((d) => d.objects), ...listValue((frontmatter(text) || new Map()).get('objects')).map((o) => o.toLowerCase())])];
529
+ const unowned = ownTerms.reduce((acc, term) => acc.replace(new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}s?\\b`, 'gi'), 'X'), words);
523
530
  if (!disabled.has('vague-term')) for (const [pattern, use] of VAGUE_TERMS) {
524
- const m = pattern.exec(words);
531
+ const m = pattern.exec(unowned);
525
532
  if (m) { findings.push({ path, n, severity: 'warn', id: 'vague-term', message: `"${m[0]}" — use the standard term: ${use} (references/style-guide.md § Vocabulary)`, excerpt: line.trim().slice(0, 90) }); break; }
526
533
  }
527
534
  if (!disabled.has('long-sentence')) {
528
- for (const sentence of words.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
535
+ // A bullet's bold label names the rule; the sentence it leads is counted without it.
536
+ const unlabelled = bare.replace(/^\s*([-*]|\d+\.)\s+\*\*[^*]+\*\*:?\s*/, '').replace(/\*\*/g, '');
537
+ for (const sentence of unlabelled.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
529
538
  const list = /:\s/.test(sentence) && (sentence.split(/:\s/).slice(1).join(' ').match(/[,;]/g) || []).length >= 2;
530
539
  const count = (sentence.match(/[\w'’-]+/g) || []).length;
531
540
  if (count > 25 && !list) { findings.push({ path, n, severity: 'warn', id: 'long-sentence', message: `${count} words in one sentence — about twenty-five at most, one idea each; a colon list is exempt`, excerpt: sentence.trim().slice(0, 90) }); break; }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "harness": "etna",
3
3
  "name": "etna",
4
- "version": "1.5.37",
4
+ "version": "1.5.38",
5
5
  "files": [
6
6
  ".prettierignore",
7
7
  ".prettierrc.json",
@@ -14,5 +14,5 @@
14
14
  "origin": "https://cdn.nustack.io",
15
15
  "track": "latest"
16
16
  },
17
- "digest": "d2ee878e5057"
17
+ "digest": "3e652499a2b3"
18
18
  }
@@ -455,7 +455,11 @@ function principleNumbers(lines) {
455
455
  // The body's sentences long enough to state a rule, as content-word sets; headings, tables and the footer drop out.
456
456
  function sentencesOf(lines) {
457
457
  const out = [];
458
+ // The opening states the document's boundary and points at its neighbours; a sentence there is a pointer, not a rule.
459
+ let opened = false;
458
460
  for (const { n, line } of lines) {
461
+ if (/^##\s+/.test(line)) opened = true;
462
+ if (!opened) continue;
459
463
  if (DOCTRINE_CLOSING.test(line)) break;
460
464
  if (/^\s*(#|\|)/.test(line) || !line.trim()) continue;
461
465
  const plain = line.replace(/^\s*([-*]|\d+\.)\s+/, '').replace(/\*\*[^*]+\*\*:?/g, '').replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/`/g, '');
@@ -520,12 +524,17 @@ function lintDoctrine(path, text, lines, disabled, findings) {
520
524
  const words = bare.replace(/\*\*/g, '').replace(/^\s*([-*]|\d+\.)\s+/, '');
521
525
  if (!disabled.has('modal-verb') && /\b(must|should|shall)\b/i.test(words)) findings.push({ path, n, severity: 'warn', id: 'modal-verb', message: 'a modal in a specification — state what happens ("the request is rejected"); being in this document makes it a requirement', excerpt: line.trim().slice(0, 90) });
522
526
  if (!disabled.has('question') && /\?(\s|$)/.test(words)) findings.push({ path, n, severity: 'warn', id: 'question', message: 'a question in prose — state the rule declaratively', excerpt: line.trim().slice(0, 90) });
527
+ // A term the set governs (any sibling's objects:, defined in its Terms table) is a term of art, never a vague synonym.
528
+ const ownTerms = [...new Set([...siblings.flatMap((d) => d.objects), ...listValue((frontmatter(text) || new Map()).get('objects')).map((o) => o.toLowerCase())])];
529
+ const unowned = ownTerms.reduce((acc, term) => acc.replace(new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}s?\\b`, 'gi'), 'X'), words);
523
530
  if (!disabled.has('vague-term')) for (const [pattern, use] of VAGUE_TERMS) {
524
- const m = pattern.exec(words);
531
+ const m = pattern.exec(unowned);
525
532
  if (m) { findings.push({ path, n, severity: 'warn', id: 'vague-term', message: `"${m[0]}" — use the standard term: ${use} (references/style-guide.md § Vocabulary)`, excerpt: line.trim().slice(0, 90) }); break; }
526
533
  }
527
534
  if (!disabled.has('long-sentence')) {
528
- for (const sentence of words.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
535
+ // A bullet's bold label names the rule; the sentence it leads is counted without it.
536
+ const unlabelled = bare.replace(/^\s*([-*]|\d+\.)\s+\*\*[^*]+\*\*:?\s*/, '').replace(/\*\*/g, '');
537
+ for (const sentence of unlabelled.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
529
538
  const list = /:\s/.test(sentence) && (sentence.split(/:\s/).slice(1).join(' ').match(/[,;]/g) || []).length >= 2;
530
539
  const count = (sentence.match(/[\w'’-]+/g) || []).length;
531
540
  if (count > 25 && !list) { findings.push({ path, n, severity: 'warn', id: 'long-sentence', message: `${count} words in one sentence — about twenty-five at most, one idea each; a colon list is exempt`, excerpt: sentence.trim().slice(0, 90) }); break; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nurix/etna",
3
- "version": "0.20.42",
3
+ "version": "0.20.43",
4
4
  "description": "Bootstrap a project with a Nurix harness — writes the committed contract (CLAUDE.md, AGENTS.md, .claude/harness.json, settings.json, the mount hook) and mounts the harness itself, gitignored, from a bundled payload; sessions keep it current from the CDN.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,5 +46,5 @@
46
46
  "agent",
47
47
  "plugin"
48
48
  ],
49
- "etnaFingerprint": "bd1f2b63adef"
49
+ "etnaFingerprint": "774d2e20c947"
50
50
  }