@dzhechkov/p-replicator 1.10.4 → 1.12.0

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.
Files changed (68) hide show
  1. package/.dz-manifest.json +119 -47
  2. package/CHANGELOG.md +85 -0
  3. package/MULTIPLATFORM_ROADMAP.md +1 -1
  4. package/README/eng/01_quickstart.md +3 -3
  5. package/README/eng/02_user_guide.md +1 -1
  6. package/README/eng/03_admin_guide.md +2 -2
  7. package/README/eng/04_api_reference.md +11 -5
  8. package/README/eng/README.md +1 -1
  9. package/README/ru/01_quickstart.md +3 -3
  10. package/README/ru/02_user_guide.md +1 -1
  11. package/README/ru/03_admin_guide.md +2 -2
  12. package/README/ru/04_api_reference.md +11 -5
  13. package/README/ru/README.md +1 -1
  14. package/README/ru/html/index.html +7 -7
  15. package/README.md +154 -38
  16. package/bin/cli.js +0 -0
  17. package/package.json +12 -10
  18. package/sbom.json +226 -46
  19. package/scripts/check-pipeline-gaps.sh +413 -0
  20. package/src/commands/doctor.js +94 -4
  21. package/src/rule-components.json +11 -0
  22. package/src/utils.js +3 -8
  23. package/templates/.claude/agents/harvest-coordinator.md +10 -1
  24. package/templates/.claude/commands/feature.md +57 -9
  25. package/templates/.claude/commands/go.md +11 -0
  26. package/templates/.claude/commands/harvest.md +41 -3
  27. package/templates/.claude/commands/myinsights.md +21 -26
  28. package/templates/.claude/commands/replicate.md +10 -1
  29. package/templates/.claude/commands/start.md +8 -0
  30. package/templates/.claude/hooks/check-ports.cjs +409 -20
  31. package/templates/.claude/hooks/session-insights.cjs +158 -25
  32. package/templates/.claude/hooks/statusline.cjs +2 -2
  33. package/templates/.claude/hooks/write-insight.cjs +253 -0
  34. package/templates/.claude/rules/cost-of-detection-ladder.md +96 -0
  35. package/templates/.claude/rules/docker-ports.md +41 -19
  36. package/templates/.claude/rules/feature-lifecycle.md +14 -3
  37. package/templates/.claude/rules/honest-configuration.md +54 -0
  38. package/templates/.claude/rules/insights-capture.md +10 -5
  39. package/templates/.claude/rules/replicate-pipeline.md +4 -2
  40. package/templates/.claude/rules/skill-interface-protocol.md +1 -0
  41. package/templates/.claude/rules/swarm-file-evidence.md +46 -0
  42. package/templates/.claude/settings.json +13 -1
  43. package/templates/.claude/skills/knowledge-extractor/modules/01-agent-review.md +16 -5
  44. package/templates/.claude/skills/sparc-prd-mini/SKILL.md +86 -16
  45. package/tests/e2e/lifecycle.test.js +55 -9
  46. package/tests/e2e/packed-insights-writer.test.js +308 -0
  47. package/tests/fixtures/prep-traceability-fixture/docs/features/order-refund/01_specification.md +29 -0
  48. package/tests/fixtures/prep-traceability-fixture/docs/features/order-refund/02_pseudocode.md +57 -0
  49. package/tests/snapshot/baseline.json +24 -20
  50. package/tests/snapshot/templates.test.js +47 -0
  51. package/tests/unit/absence-is-not-emptiness.test.js +15 -1
  52. package/tests/unit/check-pipeline-gaps.test.js +94 -0
  53. package/tests/unit/check-ports.test.js +729 -2
  54. package/tests/unit/db-port-rule.test.js +36 -5
  55. package/tests/unit/detection-ladder-contract.test.js +302 -0
  56. package/tests/unit/detection-ladder-registry.test.js +52 -0
  57. package/tests/unit/doctor-insight-flow.test.js +315 -0
  58. package/tests/unit/external-dependency-check.test.js +19 -19
  59. package/tests/unit/honest-failure-rules.test.js +492 -0
  60. package/tests/unit/hooks-project-anchored.test.js +67 -3
  61. package/tests/unit/insights-docs-tell-the-truth.test.js +52 -31
  62. package/tests/unit/insights-dz-delegation.test.js +197 -0
  63. package/tests/unit/insights-writer.test.js +285 -0
  64. package/tests/unit/shipped-suite-context.test.js +3 -1
  65. package/tests/unit/traceability-machine-ids.test.js +413 -0
  66. package/tests/unit/traceability-negative-fixture.test.js +322 -0
  67. package/tests/unit/utils.test.js +3 -2
  68. package/LICENSE +0 -21
@@ -0,0 +1,322 @@
1
+ 'use strict';
2
+
3
+ // Deterministic filesystem/string checks plus one real local subprocess: no model or network call.
4
+ // CLAIM BOUND: P1-P3 are fixture-side evidence; P4/P5 are pipeline-side evidence.
5
+ // P1-P3 prove that this change authors a faithful gap-carrier. P4/P5 read the shipped skill.
6
+ // No assertion here proves that a live model would miss the gap.
7
+
8
+ const { test, describe } = require('node:test');
9
+ const assert = require('node:assert/strict');
10
+ const fs = require('node:fs');
11
+ const os = require('node:os');
12
+ const path = require('node:path');
13
+ const { spawnSync } = require('node:child_process');
14
+
15
+ const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
16
+ const FIXTURE_FEATURE = path.join(
17
+ PACKAGE_ROOT, 'tests', 'fixtures', 'prep-traceability-fixture',
18
+ 'docs', 'features', 'order-refund',
19
+ );
20
+ const SPECIFICATION = path.join(FIXTURE_FEATURE, '01_specification.md');
21
+ const PSEUDOCODE = path.join(FIXTURE_FEATURE, '02_pseudocode.md');
22
+ const SKILL = path.join(
23
+ PACKAGE_ROOT, 'templates', '.claude', 'skills', 'sparc-prd-mini', 'SKILL.md',
24
+ );
25
+ const FEATURE_COMMAND = path.join(
26
+ PACKAGE_ROOT, 'templates', '.claude', 'commands', 'feature.md',
27
+ );
28
+ const PACKAGE_JSON = path.join(PACKAGE_ROOT, 'package.json');
29
+ const CHECKER = path.join(PACKAGE_ROOT, 'scripts', 'check-pipeline-gaps.sh');
30
+
31
+ const read = (file) => fs.readFileSync(file, 'utf8');
32
+
33
+ function h2Section(src, heading) {
34
+ const marker = `## ${heading}`;
35
+ const start = src.indexOf(marker);
36
+ assert.ok(start >= 0, `02_pseudocode.md must contain the ${heading} block`);
37
+
38
+ const contentStart = start + marker.length;
39
+ const tail = src.slice(contentStart);
40
+ const nextHeading = tail.search(/^## /m);
41
+ const endMarker = tail.indexOf(`<!-- END ${heading} -->`);
42
+ const boundaries = [nextHeading, endMarker].filter((offset) => offset >= 0);
43
+ const end = boundaries.length > 0
44
+ ? contentStart + Math.min(...boundaries)
45
+ : src.length;
46
+
47
+ return src.slice(start, end);
48
+ }
49
+
50
+ function algorithmEvidence(src) {
51
+ return h2Section(src, 'Core Algorithms');
52
+ }
53
+
54
+ function scenarioCoverage(src) {
55
+ return h2Section(src, 'Scenario Coverage');
56
+ }
57
+
58
+ function markdownSection(src, heading) {
59
+ const level = heading.match(/^#+/)[0].length;
60
+ const start = src.indexOf(heading);
61
+ assert.ok(start >= 0, `${heading} must remain addressable`);
62
+
63
+ const contentStart = start + heading.length;
64
+ const tail = src.slice(contentStart);
65
+ const nextHeading = tail.search(new RegExp(`^#{1,${level}}\\s`, 'm'));
66
+ const end = nextHeading >= 0 ? contentStart + nextHeading : src.length;
67
+ return src.slice(start, end);
68
+ }
69
+
70
+ function textBetween(src, startMarker, endMarker) {
71
+ const start = src.indexOf(startMarker);
72
+ const end = src.indexOf(endMarker, start + startMarker.length);
73
+ assert.ok(start >= 0 && end > start, `${startMarker} must precede ${endMarker}`);
74
+ return src.slice(start, end);
75
+ }
76
+
77
+ function roleMap(section) {
78
+ const match = section.match(/```yaml[\s\S]*?DOCUMENT_ROLE_MAP:\r?\n([\s\S]*?)```/);
79
+ assert.ok(match, 'the scoped section must contain a DOCUMENT_ROLE_MAP YAML block');
80
+ return Object.fromEntries(
81
+ [...match[1].matchAll(/^ (specification|pseudocode|architecture|refinement|completion): (\S+)$/gm)]
82
+ .map((entry) => [entry[1], entry[2]]),
83
+ );
84
+ }
85
+
86
+ function stepInstruction(skill, startMarker, endMarker) {
87
+ return textBetween(skill, startMarker, endMarker);
88
+ }
89
+
90
+ function registeredUnitTests(script) {
91
+ return new Set(script.split(/\s+/).filter((part) => /^tests\/unit\/.*\.test\.js$/.test(part)));
92
+ }
93
+
94
+ function unregisteredUnitTests(fileNames, script) {
95
+ const registered = registeredUnitTests(script);
96
+ return fileNames
97
+ .filter((name) => name.endsWith('.test.js'))
98
+ .filter((name) => !registered.has(`tests/unit/${name}`))
99
+ .sort();
100
+ }
101
+
102
+ describe('the per-feature traceability gap is a named negative fixture (PR-021)', () => {
103
+ test('P1 — the fixture carries exactly one requirement with no algorithm', () => {
104
+ const specification = read(SPECIFICATION);
105
+ const pseudocode = read(PSEUDOCODE);
106
+ const requirementIds = [...specification.matchAll(/^### (FR-order-refund-\d+)\b/gm)]
107
+ .map((match) => match[1]);
108
+ const algorithmIds = [...pseudocode.matchAll(/^REQUIREMENT: `(FR-order-refund-\d+)`$/gm)]
109
+ .map((match) => match[1]);
110
+
111
+ assert.deepEqual(requirementIds, [
112
+ 'FR-order-refund-1', 'FR-order-refund-2', 'FR-order-refund-3',
113
+ ]);
114
+ assert.deepEqual(algorithmIds, ['FR-order-refund-1', 'FR-order-refund-3']);
115
+ assert.deepEqual(
116
+ requirementIds.filter((id) => !algorithmIds.includes(id)),
117
+ ['FR-order-refund-2'],
118
+ );
119
+ assert.doesNotMatch(algorithmEvidence(pseudocode), /FR-order-refund-2/,
120
+ 'the deliberately un-algorithmed requirement must be absent from algorithm evidence');
121
+ });
122
+
123
+ test('P2 — the Scenario Coverage block is structurally present with both tables', () => {
124
+ const block = scenarioCoverage(read(PSEUDOCODE));
125
+ assert.match(block, /^## Scenario Coverage$/m);
126
+ assert.match(block, /Not claimed by any algorithm:\s*\n\s*\| Scenario \| Reason \|/);
127
+ assert.match(block,
128
+ /Claimed by an algorithm but absent from Specification\.md:\s*\n\s*\| Algorithm \| Claimed ID \|/);
129
+ });
130
+
131
+ test('P3 — Scenario Coverage names the missing requirement in the unclaimed table', () => {
132
+ const block = scenarioCoverage(read(PSEUDOCODE));
133
+ const unclaimed = textBetween(
134
+ block,
135
+ 'Not claimed by any algorithm:',
136
+ 'Claimed by an algorithm but absent from Specification.md:',
137
+ );
138
+ const dangling = block.slice(block.indexOf('Claimed by an algorithm but absent from Specification.md:'));
139
+
140
+ assert.match(unclaimed, /^\| FR-order-refund-2 \| out-of-mvp-scope \|$/m,
141
+ 'the unclaimed-scenario table must name the deliberately un-algorithmed requirement');
142
+ assert.doesNotMatch(dangling, /FR-order-refund-2/,
143
+ 'the dangling-algorithm table must not make the opposite claim about FR-order-refund-2');
144
+ });
145
+
146
+ test('P4 — the skill defines the complete project-default document role map', () => {
147
+ const contract = markdownSection(read(SKILL), '## Document Role Map Contract');
148
+ const defaults = roleMap(markdownSection(contract, '### Project-level default'));
149
+
150
+ assert.deepEqual(defaults, {
151
+ specification: 'Specification.md',
152
+ pseudocode: 'Pseudocode.md',
153
+ architecture: 'Architecture.md',
154
+ refinement: 'Refinement.md',
155
+ completion: 'Completion.md',
156
+ }, 'the project contour must retain its complete five-role filename map');
157
+ assert.match(contract, /Resolve the active map once before Phase 3/,
158
+ 'the skill must resolve the role map before any mapped document or gate is written');
159
+ });
160
+
161
+ test('P5 — Scenario Coverage resolves its inputs and output through document roles', () => {
162
+ const skill = read(SKILL);
163
+ const instruction = stepInstruction(
164
+ skill,
165
+ '**Шаг 4.9 — ПОКРЫТИЕ СЦЕНАРИЕВ',
166
+ '**[MANUAL] CP4:**',
167
+ );
168
+
169
+ assert.match(instruction,
170
+ /Resolve role `specification` through `DOCUMENT_ROLE_MAP` as `SPECIFICATION_FILE`/,
171
+ 'Scenario Coverage must resolve the specification input through its role');
172
+ assert.match(instruction,
173
+ /Resolve role `pseudocode` through `DOCUMENT_ROLE_MAP` as `PSEUDOCODE_FILE`/,
174
+ 'Scenario Coverage must resolve the pseudocode input and output through its role');
175
+ assert.match(instruction, /Re-read `SPECIFICATION_FILE`/,
176
+ 'Scenario Coverage must read the resolved specification target');
177
+ assert.match(instruction, /Write a `## Scenario Coverage` block into `PSEUDOCODE_FILE`/,
178
+ 'Scenario Coverage must write to the resolved pseudocode target');
179
+ });
180
+
181
+ test('the skill resolves gate inputs through the role map in both the project and per-feature contours', () => {
182
+ const skill = read(SKILL);
183
+ const featureCommand = read(FEATURE_COMMAND);
184
+ const contract = markdownSection(skill, '## Document Role Map Contract');
185
+ const projectMap = roleMap(markdownSection(contract, '### Project-level default'));
186
+ const featureMap = roleMap(markdownSection(featureCommand, '### Phase 1 document role map'));
187
+ const scenarioGate = stepInstruction(
188
+ skill,
189
+ '**Шаг 4.9 — ПОКРЫТИЕ СЦЕНАРИЕВ',
190
+ '**[MANUAL] CP4:**',
191
+ );
192
+ const reconciliationGate = stepInstruction(
193
+ skill,
194
+ '**Шаг 5.9 — СВЕРКА С ПСЕВДОКОДОМ',
195
+ '**[MANUAL] CP5:**',
196
+ );
197
+
198
+ assert.deepEqual(projectMap, {
199
+ specification: 'Specification.md',
200
+ pseudocode: 'Pseudocode.md',
201
+ architecture: 'Architecture.md',
202
+ refinement: 'Refinement.md',
203
+ completion: 'Completion.md',
204
+ }, 'the project contour must resolve roles to the existing project-level filenames');
205
+ assert.deepEqual(featureMap, {
206
+ specification: '01_specification.md',
207
+ pseudocode: '02_pseudocode.md',
208
+ architecture: '03_architecture.md',
209
+ refinement: '04_refinement.md',
210
+ completion: '05_completion.md',
211
+ }, '/feature must pass its complete per-feature role map to the skill');
212
+ assert.doesNotMatch(skill,
213
+ /01_specification\.md|02_pseudocode\.md|03_architecture\.md|04_refinement\.md|05_completion\.md/,
214
+ 'per-feature filenames must remain owned by the feature command, not duplicated into the skill');
215
+ assert.match(scenarioGate,
216
+ /Resolve role `specification` through `DOCUMENT_ROLE_MAP` as `SPECIFICATION_FILE`/,
217
+ 'Scenario Coverage must resolve its specification input by role in either contour');
218
+ assert.match(scenarioGate,
219
+ /Resolve role `pseudocode` through `DOCUMENT_ROLE_MAP` as `PSEUDOCODE_FILE`/,
220
+ 'Scenario Coverage must resolve its pseudocode input and output by role in either contour');
221
+ assert.match(reconciliationGate,
222
+ /Resolve role `pseudocode` through `DOCUMENT_ROLE_MAP` as `PSEUDOCODE_FILE`/,
223
+ 'Reconciliation must resolve its pseudocode input by role in either contour');
224
+ assert.match(reconciliationGate,
225
+ /Resolve role `architecture` through `DOCUMENT_ROLE_MAP` as `ARCHITECTURE_FILE`/,
226
+ 'Reconciliation must resolve its architecture input and output by role in either contour');
227
+ });
228
+
229
+ test('P6 — every tests/unit/*.test.js is registered in package.json', () => {
230
+ const pkg = JSON.parse(read(PACKAGE_JSON));
231
+ const unitFiles = fs.readdirSync(path.join(PACKAGE_ROOT, 'tests', 'unit'));
232
+
233
+ assert.deepEqual(unregisteredUnitTests(unitFiles, pkg.scripts['test:unit']), [],
234
+ 'test:unit has a file on disk that it never executes');
235
+ assert.deepEqual(unregisteredUnitTests(unitFiles, pkg.scripts.test), [],
236
+ 'test has a unit file on disk that it never executes');
237
+
238
+ const probe = '__unregistered-probe.test.js';
239
+ assert.deepEqual(
240
+ unregisteredUnitTests([...unitFiles, probe], pkg.scripts['test:unit']),
241
+ [probe],
242
+ 'the registration guard must fire on a real injected unregistered filename',
243
+ );
244
+ });
245
+
246
+ test('P7 — the file states the bound of its own claim', () => {
247
+ const source = read(__filename);
248
+ assert.match(source,
249
+ /CLAIM BOUND: P1-P3 are fixture-side evidence; P4\/P5 are pipeline-side evidence\./);
250
+ assert.match(source, /No assertion here proves that a live model would miss the gap\./);
251
+ });
252
+
253
+ test('P8 — packaged checker rejects the planted FR-order-refund-2 gap and a healed twin passes', (t) => {
254
+ const fixtureRoot = path.join(
255
+ PACKAGE_ROOT, 'tests', 'fixtures', 'prep-traceability-fixture',
256
+ );
257
+ const args = [
258
+ CHECKER, fixtureRoot, '--traceability', '--role-map-source', FEATURE_COMMAND,
259
+ '--project-role-map-source', SKILL,
260
+ ];
261
+ const broken = spawnSync('bash', args, { encoding: 'utf8' });
262
+ const brokenOutput = `${broken.stdout || ''}${broken.stderr || ''}`;
263
+ assert.equal(broken.status, 1, brokenOutput);
264
+ assert.match(brokenOutput,
265
+ /GAP order-refund specification->pseudocode FR-order-refund-2/);
266
+ assert.match(brokenOutput, /missing-algorithm=1 orphan-algorithm=0/);
267
+ assert.doesNotMatch(brokenOutput,
268
+ /GAP order-refund pseudocode->specification FR-order-refund-2/);
269
+
270
+ const healedRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'traceability-healed-'));
271
+ t.after(() => fs.rmSync(healedRoot, { recursive: true, force: true }));
272
+ fs.cpSync(fixtureRoot, healedRoot, { recursive: true });
273
+ const healedPseudo = path.join(
274
+ healedRoot, 'docs', 'features', 'order-refund', '02_pseudocode.md',
275
+ );
276
+ fs.appendFileSync(healedPseudo, [
277
+ '',
278
+ '### Algorithm: Notify the buyer',
279
+ '',
280
+ 'REQUIREMENT: `FR-order-refund-2`',
281
+ '',
282
+ ].join('\n'));
283
+ const healed = spawnSync('bash', [
284
+ CHECKER, healedRoot, '--traceability', '--role-map-source', FEATURE_COMMAND,
285
+ '--project-role-map-source', SKILL,
286
+ ], { encoding: 'utf8' });
287
+ assert.equal(healed.status, 0, `${healed.stdout || ''}${healed.stderr || ''}`);
288
+ });
289
+
290
+ // AM-1's Confirmation (plan 06, line 291): the flip must be COMPLETE, not cosmetic. P3/P4/P5 were
291
+ // written as pins asserting the DEFECT, carrying instructions to invert them once cd0b52f6 landed.
292
+ // Any such instruction surviving under a pin that now asserts the FIXED state is a reader trap:
293
+ // the next maintainer either re-inverts it (breaking the guard) or trusts a stale note.
294
+ // SCOPE NOTE, learned the hard way while writing this: the first version scanned the whole file
295
+ // and failed on ITS OWN explanatory comment, which necessarily quotes the forbidden phrases. A
296
+ // self-reading guard must exclude itself, so this one slices out exactly the three pin bodies.
297
+ test('P9 — P3/P4/P5 carry only corrected positive labels and messages', () => {
298
+ const source = read(__filename);
299
+ const pinBody = (name) => {
300
+ const start = source.indexOf(`test('${name} —`);
301
+ assert.ok(start >= 0, `${name} must remain present and addressable`);
302
+ const next = source.indexOf("\n test('", start + 1);
303
+ return source.slice(start, next >= 0 ? next : source.length);
304
+ };
305
+ const forbidden = [/INVERTED\s+PIN/i, /\bflip\b/i, /pins?\s+the\s+(?:current\s+)?defect/i,
306
+ /will\s+be\s+(?:flipped|inverted)/i, /cd0b52f6/];
307
+ const positive = {
308
+ P3: /names the missing requirement in the unclaimed table/,
309
+ P4: /defines the complete project-default document role map/,
310
+ P5: /resolves its inputs and output through document roles/,
311
+ };
312
+ for (const name of ['P3', 'P4', 'P5']) {
313
+ const body = pinBody(name);
314
+ for (const pattern of forbidden) {
315
+ assert.ok(!pattern.test(body),
316
+ `${name} still carries pre-flip language (${pattern}) — the flip was cosmetic`);
317
+ }
318
+ assert.match(body, positive[name],
319
+ `${name} must state its corrected positive property, not the absence of the old defect`);
320
+ }
321
+ });
322
+ });
@@ -341,11 +341,12 @@ describe('COMPONENTS.items SSOT', () => {
341
341
  }
342
342
  });
343
343
 
344
- test('item counts: 10 skills, 11 commands, 4 agents, 6 rules, 1 settings', () => {
344
+ test('item counts: 10 skills, 11 commands, 4 agents, 9 rules, 1 settings', () => {
345
345
  assert.equal(Object.keys(utils.COMPONENTS.skills.items).length, 10, 'skills count');
346
346
  assert.equal(Object.keys(utils.COMPONENTS.commands.items).length, 11, 'commands count (post v1.4: + 9 generic commands)');
347
347
  assert.equal(Object.keys(utils.COMPONENTS.agents.items).length, 4, 'agents count');
348
- assert.equal(Object.keys(utils.COMPONENTS.rules.items).length, 6, 'rules count (post v1.4: + 3 generic rules; + docker-ports)');
348
+ assert.equal(Object.keys(utils.COMPONENTS.rules.items).length, 9,
349
+ 'rules count (+ honest failure rules)');
349
350
  assert.ok(utils.COMPONENTS.settings, 'settings component group added in v1.4');
350
351
  assert.equal(Object.keys(utils.COMPONENTS.settings.items).length, 1, 'settings.json count');
351
352
  });
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 dzhechko
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.