@nurix/etna 0.20.42 → 0.20.44
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/data/harness-index.json +1 -1
- package/data/harnesses/code/harness.json +2 -2
- package/data/harnesses/code/skills/documentation/scripts/doc-lint.mjs +13 -3
- package/data/harnesses/delivery/harness.json +2 -2
- package/data/harnesses/delivery/skills/documentation/scripts/doc-lint.mjs +13 -3
- package/data/harnesses/etna/harness.json +2 -2
- package/data/harnesses/etna/skills/documentation/scripts/doc-lint.mjs +13 -3
- package/package.json +2 -2
package/data/harness-index.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"harness": "code",
|
|
3
3
|
"name": "code",
|
|
4
|
-
"version": "3.20.
|
|
4
|
+
"version": "3.20.14",
|
|
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": "
|
|
32
|
+
"digest": "a1ccbb9247d0"
|
|
33
33
|
}
|
|
@@ -455,10 +455,15 @@ 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
|
+
// A citation's words name where a rule lives; two sentences citing one heading share no rule by it.
|
|
466
|
+
const plain = line.replace(/^\s*([-*]|\d+\.)\s+/, '').replace(/\*\*[^*]+\*\*:?/g, '').replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/`/g, '').replace(/\([^()]*(?:principles?\s+\d|§)[^()]*\)/g, '');
|
|
462
467
|
for (const sentence of plain.split(/(?<=[.;])\s+(?=[A-Z(])/)) {
|
|
463
468
|
if (SPEC_SENTENCE.test(sentence)) continue;
|
|
464
469
|
const words = new Set((sentence.toLowerCase().match(/[a-z][a-z'-]+/g) || []).filter((w) => w.length > 2 && !STOPWORDS.has(w)));
|
|
@@ -520,12 +525,17 @@ function lintDoctrine(path, text, lines, disabled, findings) {
|
|
|
520
525
|
const words = bare.replace(/\*\*/g, '').replace(/^\s*([-*]|\d+\.)\s+/, '');
|
|
521
526
|
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
527
|
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) });
|
|
528
|
+
// A term the set governs (any sibling's objects:, defined in its Terms table) is a term of art, never a vague synonym.
|
|
529
|
+
const ownTerms = [...new Set([...siblings.flatMap((d) => d.objects), ...listValue((frontmatter(text) || new Map()).get('objects')).map((o) => o.toLowerCase())])];
|
|
530
|
+
const unowned = ownTerms.reduce((acc, term) => acc.replace(new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}s?\\b`, 'gi'), 'X'), words);
|
|
523
531
|
if (!disabled.has('vague-term')) for (const [pattern, use] of VAGUE_TERMS) {
|
|
524
|
-
const m = pattern.exec(
|
|
532
|
+
const m = pattern.exec(unowned);
|
|
525
533
|
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
534
|
}
|
|
527
535
|
if (!disabled.has('long-sentence')) {
|
|
528
|
-
|
|
536
|
+
// A bullet's bold label names the rule; the sentence it leads is counted without it.
|
|
537
|
+
const unlabelled = bare.replace(/^\s*([-*]|\d+\.)\s+\*\*[^*]+\*\*:?\s*/, '').replace(/\*\*/g, '');
|
|
538
|
+
for (const sentence of unlabelled.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
|
|
529
539
|
const list = /:\s/.test(sentence) && (sentence.split(/:\s/).slice(1).join(' ').match(/[,;]/g) || []).length >= 2;
|
|
530
540
|
const count = (sentence.match(/[\w'’-]+/g) || []).length;
|
|
531
541
|
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.
|
|
4
|
+
"version": "0.2.49",
|
|
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": "
|
|
17
|
+
"digest": "ab77b0fbab3a"
|
|
18
18
|
}
|
|
@@ -455,10 +455,15 @@ 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
|
+
// A citation's words name where a rule lives; two sentences citing one heading share no rule by it.
|
|
466
|
+
const plain = line.replace(/^\s*([-*]|\d+\.)\s+/, '').replace(/\*\*[^*]+\*\*:?/g, '').replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/`/g, '').replace(/\([^()]*(?:principles?\s+\d|§)[^()]*\)/g, '');
|
|
462
467
|
for (const sentence of plain.split(/(?<=[.;])\s+(?=[A-Z(])/)) {
|
|
463
468
|
if (SPEC_SENTENCE.test(sentence)) continue;
|
|
464
469
|
const words = new Set((sentence.toLowerCase().match(/[a-z][a-z'-]+/g) || []).filter((w) => w.length > 2 && !STOPWORDS.has(w)));
|
|
@@ -520,12 +525,17 @@ function lintDoctrine(path, text, lines, disabled, findings) {
|
|
|
520
525
|
const words = bare.replace(/\*\*/g, '').replace(/^\s*([-*]|\d+\.)\s+/, '');
|
|
521
526
|
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
527
|
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) });
|
|
528
|
+
// A term the set governs (any sibling's objects:, defined in its Terms table) is a term of art, never a vague synonym.
|
|
529
|
+
const ownTerms = [...new Set([...siblings.flatMap((d) => d.objects), ...listValue((frontmatter(text) || new Map()).get('objects')).map((o) => o.toLowerCase())])];
|
|
530
|
+
const unowned = ownTerms.reduce((acc, term) => acc.replace(new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}s?\\b`, 'gi'), 'X'), words);
|
|
523
531
|
if (!disabled.has('vague-term')) for (const [pattern, use] of VAGUE_TERMS) {
|
|
524
|
-
const m = pattern.exec(
|
|
532
|
+
const m = pattern.exec(unowned);
|
|
525
533
|
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
534
|
}
|
|
527
535
|
if (!disabled.has('long-sentence')) {
|
|
528
|
-
|
|
536
|
+
// A bullet's bold label names the rule; the sentence it leads is counted without it.
|
|
537
|
+
const unlabelled = bare.replace(/^\s*([-*]|\d+\.)\s+\*\*[^*]+\*\*:?\s*/, '').replace(/\*\*/g, '');
|
|
538
|
+
for (const sentence of unlabelled.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
|
|
529
539
|
const list = /:\s/.test(sentence) && (sentence.split(/:\s/).slice(1).join(' ').match(/[,;]/g) || []).length >= 2;
|
|
530
540
|
const count = (sentence.match(/[\w'’-]+/g) || []).length;
|
|
531
541
|
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.
|
|
4
|
+
"version": "1.5.39",
|
|
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": "
|
|
17
|
+
"digest": "04e9b6671929"
|
|
18
18
|
}
|
|
@@ -455,10 +455,15 @@ 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
|
+
// A citation's words name where a rule lives; two sentences citing one heading share no rule by it.
|
|
466
|
+
const plain = line.replace(/^\s*([-*]|\d+\.)\s+/, '').replace(/\*\*[^*]+\*\*:?/g, '').replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/`/g, '').replace(/\([^()]*(?:principles?\s+\d|§)[^()]*\)/g, '');
|
|
462
467
|
for (const sentence of plain.split(/(?<=[.;])\s+(?=[A-Z(])/)) {
|
|
463
468
|
if (SPEC_SENTENCE.test(sentence)) continue;
|
|
464
469
|
const words = new Set((sentence.toLowerCase().match(/[a-z][a-z'-]+/g) || []).filter((w) => w.length > 2 && !STOPWORDS.has(w)));
|
|
@@ -520,12 +525,17 @@ function lintDoctrine(path, text, lines, disabled, findings) {
|
|
|
520
525
|
const words = bare.replace(/\*\*/g, '').replace(/^\s*([-*]|\d+\.)\s+/, '');
|
|
521
526
|
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
527
|
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) });
|
|
528
|
+
// A term the set governs (any sibling's objects:, defined in its Terms table) is a term of art, never a vague synonym.
|
|
529
|
+
const ownTerms = [...new Set([...siblings.flatMap((d) => d.objects), ...listValue((frontmatter(text) || new Map()).get('objects')).map((o) => o.toLowerCase())])];
|
|
530
|
+
const unowned = ownTerms.reduce((acc, term) => acc.replace(new RegExp(`\\b${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}s?\\b`, 'gi'), 'X'), words);
|
|
523
531
|
if (!disabled.has('vague-term')) for (const [pattern, use] of VAGUE_TERMS) {
|
|
524
|
-
const m = pattern.exec(
|
|
532
|
+
const m = pattern.exec(unowned);
|
|
525
533
|
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
534
|
}
|
|
527
535
|
if (!disabled.has('long-sentence')) {
|
|
528
|
-
|
|
536
|
+
// A bullet's bold label names the rule; the sentence it leads is counted without it.
|
|
537
|
+
const unlabelled = bare.replace(/^\s*([-*]|\d+\.)\s+\*\*[^*]+\*\*:?\s*/, '').replace(/\*\*/g, '');
|
|
538
|
+
for (const sentence of unlabelled.split(/(?<=[.;!])\s+(?=[A-Z(])/)) {
|
|
529
539
|
const list = /:\s/.test(sentence) && (sentence.split(/:\s/).slice(1).join(' ').match(/[,;]/g) || []).length >= 2;
|
|
530
540
|
const count = (sentence.match(/[\w'’-]+/g) || []).length;
|
|
531
541
|
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.
|
|
3
|
+
"version": "0.20.44",
|
|
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": "
|
|
49
|
+
"etnaFingerprint": "192f46e9ad7e"
|
|
50
50
|
}
|