@dzhechkov/harness-cli 0.3.195 → 0.3.198
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/README.md +37 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +145 -9
- package/dist/cli.js.map +1 -1
- package/package.json +12 -12
- package/src/cli.ts +152 -8
- package/LICENSE +0 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/harness-cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.198",
|
|
4
4
|
"description": "The dz CLI — install AI skills for Claude Code, Codex, OpenCode, Hermes, OpenClaude, GitHub Copilot. 35 commands, 13 presets, 6 platform targets.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,7 +37,15 @@
|
|
|
37
37
|
"src",
|
|
38
38
|
"README.md"
|
|
39
39
|
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc -p tsconfig.json",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest",
|
|
44
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
45
|
+
"lint": "tsc -p tsconfig.json --noEmit"
|
|
46
|
+
},
|
|
40
47
|
"dependencies": {
|
|
48
|
+
"@dzhechkov/harness-core": "workspace:*",
|
|
41
49
|
"@dzhechkov/harness-presets": "^0.5.0",
|
|
42
50
|
"@dzhechkov/scout": "^0.8.0",
|
|
43
51
|
"@dzhechkov/skills-devops": "^0.3.0",
|
|
@@ -53,8 +61,7 @@
|
|
|
53
61
|
"@dzhechkov/skills-idea2prd": "^0.1.0",
|
|
54
62
|
"@dzhechkov/skills-reverse-engineering": "^0.1.0",
|
|
55
63
|
"@dzhechkov/skills-presentation-storyteller": "^0.1.0",
|
|
56
|
-
"@dzhechkov/skills-website-cloner": "^0.1.0"
|
|
57
|
-
"@dzhechkov/harness-core": "0.3.92"
|
|
64
|
+
"@dzhechkov/skills-website-cloner": "^0.1.0"
|
|
58
65
|
},
|
|
59
66
|
"devDependencies": {
|
|
60
67
|
"@types/node": "^25.6.0",
|
|
@@ -72,12 +79,5 @@
|
|
|
72
79
|
"url": "https://github.com/djd1m/dz-harness-hub.git",
|
|
73
80
|
"directory": "packages/@dzhechkov/harness-cli"
|
|
74
81
|
},
|
|
75
|
-
"homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-cli#readme"
|
|
76
|
-
|
|
77
|
-
"build": "tsc -p tsconfig.json",
|
|
78
|
-
"test": "vitest run",
|
|
79
|
-
"test:watch": "vitest",
|
|
80
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
81
|
-
"lint": "tsc -p tsconfig.json --noEmit"
|
|
82
|
-
}
|
|
83
|
-
}
|
|
82
|
+
"homepage": "https://github.com/djd1m/dz-harness-hub/tree/main/packages/@dzhechkov/harness-cli#readme"
|
|
83
|
+
}
|
package/src/cli.ts
CHANGED
|
@@ -51,9 +51,12 @@ import {
|
|
|
51
51
|
WORKFLOW_NAMES,
|
|
52
52
|
importEcc,
|
|
53
53
|
recordPattern,
|
|
54
|
+
resolveLearningBackend,
|
|
55
|
+
storeStats,
|
|
54
56
|
consolidateSessions,
|
|
55
57
|
pruneNoisePatterns,
|
|
56
58
|
recallHybrid,
|
|
59
|
+
teachGuard,
|
|
57
60
|
mirrorPatternsToVector,
|
|
58
61
|
vectorMirrorEnabled,
|
|
59
62
|
vectorTierStatus,
|
|
@@ -63,6 +66,8 @@ import {
|
|
|
63
66
|
importRvfCheckpoint,
|
|
64
67
|
statuslineData,
|
|
65
68
|
writeFeatureAdrState,
|
|
69
|
+
computeUsage,
|
|
70
|
+
readUsageLimits,
|
|
66
71
|
queryBookKnowledge,
|
|
67
72
|
loadStorePatternsSync,
|
|
68
73
|
bundleSkills,
|
|
@@ -122,6 +127,7 @@ Usage:
|
|
|
122
127
|
dz brain init [--project <dir>] [--k <N>] (wire the grounding hook into .claude/settings.json — opt-in)
|
|
123
128
|
dz statusline [--json] [--install] [--project <dir>] (live self-learning panel for Claude Code's status bar; reads the CC JSON payload from STDIN)
|
|
124
129
|
dz statusline --fa-record --slug <s> --step "<label>" [--recalled <n>] [--stored <n>] [--mode <m>] (feature-adr: record live per-run learning state → 📐 panel segment)
|
|
130
|
+
dz usage [--json] [--project <dir>] (ESTIMATE Claude session (5h-block) + weekly (7d) token usage from local transcripts; exit 0 ALWAYS; pct=null when memory.usage.{sessionTokenLimit,weeklyTokenLimit} unconfigured in .dz/config.json)
|
|
125
131
|
dz pretrain [--project <dir>]
|
|
126
132
|
dz recommend "<task description>"
|
|
127
133
|
dz compose <preset1+preset2+...> [--target <name>]
|
|
@@ -1037,11 +1043,14 @@ function cmdStatuslineFaRecord(options: Map<string, string>, cwd: string, write:
|
|
|
1037
1043
|
if (recalled === undefined) return 1;
|
|
1038
1044
|
const stored = parseCount('stored');
|
|
1039
1045
|
if (stored === undefined) return 1;
|
|
1046
|
+
const reinforced = parseCount('reinforced');
|
|
1047
|
+
if (reinforced === undefined) return 1;
|
|
1040
1048
|
|
|
1041
1049
|
const mode = options.get('mode');
|
|
1042
1050
|
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
1043
1051
|
const state = writeFeatureAdrState(projectRoot, {
|
|
1044
1052
|
slug, step, recalled, stored,
|
|
1053
|
+
...(reinforced > 0 ? { reinforced } : {}),
|
|
1045
1054
|
...(mode !== undefined && mode.trim() !== '' ? { mode: mode.trim() } : {}),
|
|
1046
1055
|
});
|
|
1047
1056
|
|
|
@@ -1049,7 +1058,7 @@ function cmdStatuslineFaRecord(options: Map<string, string>, cwd: string, write:
|
|
|
1049
1058
|
write(`dz statusline --fa-record: could not write learning state under ${projectRoot}/.dz/feature-adr/`);
|
|
1050
1059
|
return 1;
|
|
1051
1060
|
}
|
|
1052
|
-
write(`dz statusline: recorded /feature-adr learning state for "${slug}" (${step}) — 🎓 ${state.pool} pool · ↑${state.recalled} used · +${state.stored} new`);
|
|
1061
|
+
write(`dz statusline: recorded /feature-adr learning state for "${slug}" (${step}) — 🎓 ${state.pool} pool · ↑${state.recalled} used · +${state.stored} new · ↻${state.reinforced ?? 0} reinforced`);
|
|
1053
1062
|
return 0;
|
|
1054
1063
|
}
|
|
1055
1064
|
|
|
@@ -1091,7 +1100,7 @@ function cmdStatusline(
|
|
|
1091
1100
|
// Live /feature-adr run in flight → PREPEND the pipeline learning segment to the base dz line.
|
|
1092
1101
|
const fa = data.featureAdr;
|
|
1093
1102
|
if (fa !== undefined) {
|
|
1094
|
-
line = `📐 feature-adr ${fa.step} · 🎓 ${fa.pool} pool · ↑${fa.recalled} used · +${fa.stored} new · ${line}`;
|
|
1103
|
+
line = `📐 feature-adr ${fa.step} · 🎓 ${fa.pool} pool · ↑${fa.recalled} used · +${fa.stored} new · ↻${fa.reinforced ?? 0} reinforced · ${line}`;
|
|
1095
1104
|
}
|
|
1096
1105
|
|
|
1097
1106
|
write(line);
|
|
@@ -1103,6 +1112,73 @@ function cmdStatusline(
|
|
|
1103
1112
|
}
|
|
1104
1113
|
}
|
|
1105
1114
|
|
|
1115
|
+
/**
|
|
1116
|
+
* `dz usage` — print an ESTIMATE of Claude session (active 5h-block) + weekly (rolling 7d) token
|
|
1117
|
+
* usage, aggregated READONLY from the local transcript store (see {@link computeUsage}). `--json`
|
|
1118
|
+
* emits the single-line contract the feature-adr usage-probe agent parses; the human path prints a
|
|
1119
|
+
* compact `session ~74% (resets 19:00) · week ~52% (resets Sat)` or an unconfigured hint.
|
|
1120
|
+
*
|
|
1121
|
+
* **Exit code is 0 ALWAYS** — including on internal error the whole body is guarded and prints the
|
|
1122
|
+
* all-null JSON, so a probe can NEVER distinguish "usage unknown" from "command failed" via a
|
|
1123
|
+
* non-zero exit (unknown is a first-class value, INV-3). `--project <dir>` scopes ONLY the
|
|
1124
|
+
* `.dz/config.json` limits read; the measurement is account-wide (all projects, FR-1.6).
|
|
1125
|
+
*/
|
|
1126
|
+
function cmdUsage(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): number {
|
|
1127
|
+
const projectRoot = resolve(cwd, options.get('project') ?? '.');
|
|
1128
|
+
const nullContract = () =>
|
|
1129
|
+
JSON.stringify({
|
|
1130
|
+
sessionPct: null,
|
|
1131
|
+
weeklyPct: null,
|
|
1132
|
+
sessionTokens: 0,
|
|
1133
|
+
weeklyTokens: 0,
|
|
1134
|
+
resetsAt: { session: null, weekly: null },
|
|
1135
|
+
limits: { session: null, weekly: null },
|
|
1136
|
+
estimated: true,
|
|
1137
|
+
});
|
|
1138
|
+
try {
|
|
1139
|
+
const u = computeUsage(projectRoot);
|
|
1140
|
+
const lim = readUsageLimits(projectRoot);
|
|
1141
|
+
if (flags.has('json')) {
|
|
1142
|
+
write(
|
|
1143
|
+
JSON.stringify({
|
|
1144
|
+
sessionPct: u.sessionPct,
|
|
1145
|
+
weeklyPct: u.weeklyPct,
|
|
1146
|
+
sessionTokens: u.sessionTokens,
|
|
1147
|
+
weeklyTokens: u.weeklyTokens,
|
|
1148
|
+
resetsAt: { session: u.sessionResetsAt, weekly: u.weeklyResetsAt },
|
|
1149
|
+
limits: { session: lim.sessionTokenLimit ?? null, weekly: lim.weeklyTokenLimit ?? null },
|
|
1150
|
+
estimated: true,
|
|
1151
|
+
}),
|
|
1152
|
+
);
|
|
1153
|
+
return 0;
|
|
1154
|
+
}
|
|
1155
|
+
if (u.sessionPct === null && u.weeklyPct === null) {
|
|
1156
|
+
write(
|
|
1157
|
+
'usage: unconfigured — set memory.usage.sessionTokenLimit / weeklyTokenLimit in .dz/config.json (percentages are ESTIMATES calibrated from observed exhaustion)',
|
|
1158
|
+
);
|
|
1159
|
+
return 0;
|
|
1160
|
+
}
|
|
1161
|
+
// Compact human line — a short HH:MM / weekday hint on the resets, best-effort.
|
|
1162
|
+
const clock = (iso: string | null): string => {
|
|
1163
|
+
if (!iso) return '?';
|
|
1164
|
+
try {
|
|
1165
|
+
return new Date(iso).toISOString().slice(11, 16);
|
|
1166
|
+
} catch {
|
|
1167
|
+
return '?';
|
|
1168
|
+
}
|
|
1169
|
+
};
|
|
1170
|
+
const s = u.sessionPct === null ? 'n/a' : '~' + u.sessionPct + '%';
|
|
1171
|
+
const w = u.weeklyPct === null ? 'n/a' : '~' + u.weeklyPct + '%';
|
|
1172
|
+
write('usage: session ' + s + ' (resets ' + clock(u.sessionResetsAt) + ') · week ' + w + ' (resets ' + clock(u.weeklyResetsAt) + ') · estimated');
|
|
1173
|
+
return 0;
|
|
1174
|
+
} catch {
|
|
1175
|
+
// never let a probe see a non-zero exit — print the all-null contract and exit 0.
|
|
1176
|
+
if (flags.has('json')) write(nullContract());
|
|
1177
|
+
else write('usage: unconfigured — set memory.usage.sessionTokenLimit / weeklyTokenLimit in .dz/config.json');
|
|
1178
|
+
return 0;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1106
1182
|
async function cmdTeach(options: Map<string, string>, flags: Set<string>, cwd: string, write: Write): Promise<number> {
|
|
1107
1183
|
const projectRoot = options.get('project') ?? cwd;
|
|
1108
1184
|
|
|
@@ -1176,9 +1252,37 @@ async function cmdTeach(options: Map<string, string>, flags: Set<string>, cwd: s
|
|
|
1176
1252
|
write(` Backend: memory (@dzhechkov/memory) Total now: ${loadStorePatternsSync(projectRoot).length}`);
|
|
1177
1253
|
// ONE batched mirror call through the same seam as single-teach (QR-6 — no bespoke path).
|
|
1178
1254
|
await emitMirror(projectRoot, importedRecs, 'dz-teach-import');
|
|
1255
|
+
if (imported > 0) {
|
|
1256
|
+
const report = await harmonizeVectorStore(projectRoot, {});
|
|
1257
|
+
write(` ℹ ${imported} imported — ${report.clusters.length} near-duplicate cluster(s): review with dz vector harmonize (dry-run); merge with dz vector harmonize --apply after backup`);
|
|
1258
|
+
}
|
|
1179
1259
|
return 0;
|
|
1180
1260
|
}
|
|
1181
1261
|
|
|
1262
|
+
const reinforce = options.get('reinforce');
|
|
1263
|
+
if (reinforce !== undefined && reinforce.trim() !== '') {
|
|
1264
|
+
const backend = resolveLearningBackend(projectRoot);
|
|
1265
|
+
const sampleReward = options.has('reward') ? parseFloat(options.get('reward') ?? '0.8') : undefined;
|
|
1266
|
+
backend.addSample({
|
|
1267
|
+
dzId: reinforce,
|
|
1268
|
+
kind: 'reinforce',
|
|
1269
|
+
ts: new Date().toISOString(),
|
|
1270
|
+
...(sampleReward !== undefined ? { reward: sampleReward } : {}),
|
|
1271
|
+
});
|
|
1272
|
+
const trained = await backend.train();
|
|
1273
|
+
if (trained.flushed > 0) {
|
|
1274
|
+
write(`↳ reinforced ${reinforce}`);
|
|
1275
|
+
return 0;
|
|
1276
|
+
}
|
|
1277
|
+
// HIGH-fix: a no-match must NOT auto-teach the raw argument — callers pass dzIds or truncated
|
|
1278
|
+
// text, so auto-teach minted garbage lessons (observed live). Fail with an honest advisory:
|
|
1279
|
+
// if the lesson is genuinely new, the caller teaches it EXPLICITLY with the full text.
|
|
1280
|
+
write(`dz teach --reinforce: no existing pattern matched ${JSON.stringify(reinforce)} — nothing reinforced`);
|
|
1281
|
+
write(' If this is a genuinely NEW lesson, teach it explicitly: dz teach "<full lesson text>" --reward <0-1> --domain <area>');
|
|
1282
|
+
write(' To find the exact pattern to reinforce: dz recall "<terms>" (match by its full text)');
|
|
1283
|
+
return 1;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1182
1286
|
const pattern = options.get('_positional_0');
|
|
1183
1287
|
if (!pattern) {
|
|
1184
1288
|
write('dz teach: pattern description required');
|
|
@@ -1191,6 +1295,23 @@ async function cmdTeach(options: Map<string, string>, flags: Set<string>, cwd: s
|
|
|
1191
1295
|
const reward = parseFloat(options.get('reward') ?? '0.8');
|
|
1192
1296
|
const domain = options.get('domain') ?? 'general';
|
|
1193
1297
|
|
|
1298
|
+
if (flags.has('guard')) {
|
|
1299
|
+
const verdict = await teachGuard(projectRoot, pattern, { reward: Math.max(0, Math.min(1, reward)) });
|
|
1300
|
+
if (verdict.action === 'reinforce') {
|
|
1301
|
+
const backend = resolveLearningBackend(projectRoot);
|
|
1302
|
+
backend.addSample({ dzId: verdict.dzId, kind: 'reinforce', reward: Math.max(0, Math.min(1, reward)), ts: new Date().toISOString() });
|
|
1303
|
+
const trained = await backend.train();
|
|
1304
|
+
// HIGH-fix: only claim success when the reinforce actually FLUSHED. With backend 'off'
|
|
1305
|
+
// (NoopLearningBackend) or a flush failure, flushed === 0 — falling through to the plain
|
|
1306
|
+
// teach below so the lesson is NEVER silently discarded (the exact silent-drop the ADR forbids).
|
|
1307
|
+
if (trained.flushed > 0) {
|
|
1308
|
+
write(`↳ reinforced existing pattern ${verdict.dzId} (cos=${verdict.cosine.toFixed(2)}) — not re-added`);
|
|
1309
|
+
return 0;
|
|
1310
|
+
}
|
|
1311
|
+
write(`dz teach --guard: reinforce of ${verdict.dzId} did not flush (backend off or write failure) — teaching the lesson normally instead`);
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1194
1315
|
// Distill pattern into actionable rule (claude-smart inspired)
|
|
1195
1316
|
// Convert "what happened" into "what to do next time"
|
|
1196
1317
|
const isRule = pattern.toLowerCase().startsWith('always') || pattern.toLowerCase().startsWith('never') ||
|
|
@@ -1293,6 +1414,23 @@ async function cmdRecall(options: Map<string, string>, flags: Set<string>, cwd:
|
|
|
1293
1414
|
// With --json this is the portable export the agentdb-memory MCP bridge consumes.
|
|
1294
1415
|
if (all) {
|
|
1295
1416
|
const patterns = loadStorePatternsSync(projectRoot);
|
|
1417
|
+
if (flags.has('stats')) {
|
|
1418
|
+
const stats = storeStats(projectRoot);
|
|
1419
|
+
const backendStats = resolveLearningBackend(projectRoot).getStats();
|
|
1420
|
+
if (asJson) {
|
|
1421
|
+
write(JSON.stringify({ patterns, stats, learning: backendStats }));
|
|
1422
|
+
return 0;
|
|
1423
|
+
}
|
|
1424
|
+
write(`dz recall --all --stats — ${patterns.length} learned pattern(s)`);
|
|
1425
|
+
write(` backend: ${backendStats.backend}${backendStats.advisory !== undefined ? ` (${backendStats.advisory})` : ''}`);
|
|
1426
|
+
write(` domains: ${Object.entries(stats.perDomain).map(([k, v]) => `${k}=${v}`).join(', ') || 'none'}`);
|
|
1427
|
+
write(` exact-dup groups: ${stats.exactDupGroups}`);
|
|
1428
|
+
write(` re-teach trend: ${stats.teachEvents} teach event(s), ${stats.reinforceEvents} reinforce event(s)`);
|
|
1429
|
+
write(' top uses:');
|
|
1430
|
+
for (const row of stats.topUses) write(` ${row.uses}× [${row.reward.toFixed(2)}] (${row.domain}) ${row.pattern.slice(0, 80)}`);
|
|
1431
|
+
write(' near-dup density: run dz vector harmonize (dry-run)');
|
|
1432
|
+
return 0;
|
|
1433
|
+
}
|
|
1296
1434
|
if (asJson) {
|
|
1297
1435
|
write(JSON.stringify(patterns));
|
|
1298
1436
|
} else {
|
|
@@ -1767,12 +1905,11 @@ async function cmdBrain(
|
|
|
1767
1905
|
// Default recall = pure FTS order (nothing regresses). `--rerank` opts into the deterministic
|
|
1768
1906
|
// lexical reranker (over-fetch → field-weighted reorder → trim) for a more on-point top-K.
|
|
1769
1907
|
const rerank = flags.has('rerank');
|
|
1770
|
-
// Default MATCH = all (AND) — precise.
|
|
1771
|
-
//
|
|
1772
|
-
//
|
|
1773
|
-
// with --rerank: over-fetch broadly (OR), then reorder on-point first.
|
|
1908
|
+
// Default MATCH = all (AND) — precise when it hits. If it returns zero hits, queryBrain retries
|
|
1909
|
+
// once with OR and labels the result as broadened. Explicit `--any` still starts broad and is not
|
|
1910
|
+
// labeled as a fallback. Pairs well with --rerank: over-fetch broadly, then reorder on-point first.
|
|
1774
1911
|
const anyMatch = flags.has('any');
|
|
1775
|
-
const
|
|
1912
|
+
const queryResult = await queryBrain({
|
|
1776
1913
|
query,
|
|
1777
1914
|
limit,
|
|
1778
1915
|
depsRoot: resolveDepsRoot(cwd),
|
|
@@ -1780,8 +1917,13 @@ async function cmdBrain(
|
|
|
1780
1917
|
...(rerank ? { rerank: true } : {}),
|
|
1781
1918
|
...(anyMatch ? { match: 'any' as const } : {}),
|
|
1782
1919
|
});
|
|
1783
|
-
|
|
1920
|
+
const { hits, error, broadened } = queryResult;
|
|
1921
|
+
if (asJson) {
|
|
1922
|
+
write(JSON.stringify(broadened === true ? { hits, broadened: true } : hits));
|
|
1923
|
+
return 0;
|
|
1924
|
+
}
|
|
1784
1925
|
write(`dz brain query "${query}"${source !== undefined ? ` --source ${source}` : ''}${anyMatch ? ' --any' : ''}${rerank ? ' --rerank' : ''} — ${hits.length} KU hit(s)`);
|
|
1926
|
+
if (broadened === true) write(` broadened to any-term match: ${hits.length} hit(s)`);
|
|
1785
1927
|
if (error !== undefined) write(` (${error})`);
|
|
1786
1928
|
for (const h of hits) {
|
|
1787
1929
|
const loc = h.chapter !== undefined
|
|
@@ -3365,6 +3507,8 @@ export async function runCli(argv: string[], io: CliIo = {}): Promise<number> {
|
|
|
3365
3507
|
return await cmdBrain(options, flags, cwd, write, readStdin);
|
|
3366
3508
|
case 'statusline':
|
|
3367
3509
|
return cmdStatusline(options, flags, cwd, write, readStdin);
|
|
3510
|
+
case 'usage':
|
|
3511
|
+
return cmdUsage(options, flags, cwd, write);
|
|
3368
3512
|
case 'setup':
|
|
3369
3513
|
return await cmdSetup(options, flags, cwd, write);
|
|
3370
3514
|
case 'pretrain':
|
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.
|