harness-everything 0.42.1 → 0.43.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.
@@ -1382,6 +1382,278 @@ function gitOutput(root, args, encoding = 'buffer') {
1382
1382
  return execFileSync('git', ['-C', root, ...args], { encoding, maxBuffer: 64 * 1024 * 1024 });
1383
1383
  }
1384
1384
 
1385
+ function nulFields(bytes) {
1386
+ const fields = [];
1387
+ let start = 0;
1388
+ for (let index = 0; index < bytes.length; index++) {
1389
+ if (bytes[index] !== 0) continue;
1390
+ fields.push(bytes.subarray(start, index));
1391
+ start = index + 1;
1392
+ }
1393
+ if (start < bytes.length) fields.push(bytes.subarray(start));
1394
+ return fields;
1395
+ }
1396
+
1397
+ function gitPathValue(bytes) {
1398
+ const decoded = bytes.toString('utf8');
1399
+ if (Buffer.from(decoded, 'utf8').equals(bytes)) return { path: decoded, matchPath: decoded, encoding: 'utf-8' };
1400
+ let escaped = '';
1401
+ for (const byte of bytes) escaped += byte >= 0x20 && byte <= 0x7e && byte !== 0x5c
1402
+ ? String.fromCharCode(byte)
1403
+ : `\\x${byte.toString(16).padStart(2, '0')}`;
1404
+ return { path: escaped, matchPath: escaped, encoding: 'escaped-bytes' };
1405
+ }
1406
+
1407
+ function inventoryPathIsDocument(pathBytes, manifest) {
1408
+ return matchesAny(gitPathValue(pathBytes).matchPath, manifest.documentGlobs);
1409
+ }
1410
+
1411
+ function inventoryPathDisposition(pathBytes, manifest, mode) {
1412
+ const display = gitPathValue(pathBytes);
1413
+ if (!inventoryPathIsDocument(pathBytes, manifest)) {
1414
+ if (mode === '160000') return { role: 'gitlink', lifecycle: 'external', disposition: 'excluded', reason: 'Gitlink identity is retained; inventory does not recurse into submodules.' };
1415
+ if (mode === '120000') return { role: 'symlink', lifecycle: 'current', disposition: 'excluded', reason: 'Symlink identity is retained; inventory does not follow its target.' };
1416
+ return { role: 'non-document endpoint', lifecycle: 'unknown', disposition: 'excluded', reason: 'Non-document endpoint is retained only to preserve Git rename identity.' };
1417
+ }
1418
+ const rule = manifest.rules.find(item => matchesAny(display.matchPath, [item.match]));
1419
+ if (rule) return { role: rule.role, lifecycle: rule.lifecycle, disposition: rule.disposition, reason: rule.reason };
1420
+ return { role: 'unknown', lifecycle: 'unknown', disposition: 'unknown', reason: 'No manifest rule classifies this tracked document path.' };
1421
+ }
1422
+
1423
+ function assertNoSymlinkPathAncestors(workspace, relative, { allowLeafSymlink = false, allowMissing = false, label = 'path' } = {}) {
1424
+ let current = workspace;
1425
+ const components = relative.split(path.sep);
1426
+ for (let index = 0; index < components.length; index++) {
1427
+ current = path.join(current, components[index]);
1428
+ let entry;
1429
+ try { entry = fs.lstatSync(current); } catch (error) {
1430
+ if (allowMissing && error.code === 'ENOENT') return;
1431
+ throw error;
1432
+ }
1433
+ const leaf = index === components.length - 1;
1434
+ if (entry.isSymbolicLink()) {
1435
+ if (allowLeafSymlink && leaf) return;
1436
+ throw new Error(`${label} cannot pass through a symbolic link`);
1437
+ }
1438
+ if (!leaf && !entry.isDirectory()) throw new Error(`${label} parent path components must be directories`);
1439
+ }
1440
+ }
1441
+
1442
+ function loadInventoryManifest(root, manifestPath) {
1443
+ const workspace = fs.realpathSync(root);
1444
+ const absolute = path.resolve(workspace, manifestPath);
1445
+ const relative = path.relative(workspace, absolute);
1446
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
1447
+ throw new Error('--manifest must name a file within --workspace');
1448
+ }
1449
+ assertNoSymlinkPathAncestors(workspace, relative, { label: '--manifest path' });
1450
+ const stat = fs.lstatSync(absolute);
1451
+ if (!stat.isFile() || stat.size > 1024 * 1024) throw new Error('--manifest must be a regular file no larger than 1 MiB');
1452
+ const bytes = fs.readFileSync(absolute);
1453
+ let manifest;
1454
+ try { manifest = JSON.parse(bytes.toString('utf8')); } catch (error) { throw new Error(`cannot parse inventory manifest: ${error.message}`); }
1455
+ if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest) || manifest.schemaVersion !== 1
1456
+ || !Array.isArray(manifest.documentGlobs) || !manifest.documentGlobs.length
1457
+ || !manifest.documentGlobs.every(item => typeof item === 'string' && item.length)
1458
+ || !Array.isArray(manifest.rules)) {
1459
+ throw new Error('inventory manifest requires schemaVersion 1, documentGlobs, and rules');
1460
+ }
1461
+ for (const rule of manifest.rules) {
1462
+ if (!rule || typeof rule !== 'object' || typeof rule.match !== 'string' || !rule.match
1463
+ || typeof rule.role !== 'string' || !rule.role
1464
+ || typeof rule.lifecycle !== 'string' || !rule.lifecycle
1465
+ || !['applicable', 'inventory-only', 'excluded', 'unknown', 'unavailable'].includes(rule.disposition)
1466
+ || typeof rule.reason !== 'string' || !rule.reason) {
1467
+ throw new Error('each inventory rule requires match, role, lifecycle, disposition, and reason');
1468
+ }
1469
+ }
1470
+ return { manifest, absolute, sha256: crypto.createHash('sha256').update(bytes).digest('hex') };
1471
+ }
1472
+
1473
+ function proposedInventoryPath(workspace, value) {
1474
+ if (typeof value !== 'string' || !value || path.isAbsolute(value)) throw new Error('--proposed-untracked must name a relative path within --workspace');
1475
+ const absolute = path.resolve(workspace, value);
1476
+ const relative = path.relative(workspace, absolute);
1477
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
1478
+ throw new Error('--proposed-untracked must name a path within --workspace');
1479
+ }
1480
+ assertNoSymlinkPathAncestors(workspace, relative, { allowLeafSymlink: true, allowMissing: true, label: '--proposed-untracked path' });
1481
+ return Buffer.from(relative.split(path.sep).join('/'), 'utf8');
1482
+ }
1483
+
1484
+ function gitTreeEntries(root, commit) {
1485
+ const fields = nulFields(gitOutput(root, ['ls-tree', '-r', '-z', '--full-tree', commit]));
1486
+ const result = new Map();
1487
+ for (const field of fields) {
1488
+ const tab = field.indexOf(0x09);
1489
+ if (tab < 0) throw new Error('malformed NUL-delimited Git tree entry');
1490
+ const header = field.subarray(0, tab).toString('ascii').split(' ');
1491
+ if (header.length !== 3 || !/^\d{6}$/.test(header[0]) || !/^(blob|commit)$/.test(header[1]) || !/^[0-9a-f]{40,64}$/.test(header[2])) {
1492
+ throw new Error('malformed Git tree identity');
1493
+ }
1494
+ const relative = Buffer.from(field.subarray(tab + 1));
1495
+ result.set(relative.toString('base64'), { pathBytes: relative, mode: header[0], type: header[1], oid: header[2] });
1496
+ }
1497
+ return result;
1498
+ }
1499
+
1500
+ function gitDiffEntries(root, commit) {
1501
+ const fields = nulFields(gitOutput(root, ['diff', '--raw', '--no-abbrev', '-z', '--find-renames', commit, '--']));
1502
+ const result = new Map();
1503
+ for (let index = 0; index < fields.length;) {
1504
+ const header = fields[index++].toString('ascii');
1505
+ const match = /^:(\d{6}) (\d{6}) ([0-9a-f]{40,64}) ([0-9a-f]{40,64}) ([A-Z][0-9]*)$/.exec(header);
1506
+ if (!match || index >= fields.length) throw new Error('malformed NUL-delimited Git diff entry');
1507
+ const [, oldMode, newMode, oldOid, newOid, statusText] = match;
1508
+ const status = statusText[0];
1509
+ const first = Buffer.from(fields[index++]);
1510
+ if (status === 'R' || status === 'C') {
1511
+ if (index >= fields.length) throw new Error('truncated NUL-delimited rename entry');
1512
+ const second = Buffer.from(fields[index++]);
1513
+ const shared = { oldMode, newMode, oldOid, newOid, statusText };
1514
+ result.set(first.toString('base64'), { ...shared, pathBytes: first, status: 'renamed-from', partner: second, endpoint: 'source' });
1515
+ result.set(second.toString('base64'), { ...shared, pathBytes: second, status: 'renamed-to', partner: first, endpoint: 'target' });
1516
+ } else {
1517
+ const isDelete = status === 'D';
1518
+ result.set(first.toString('base64'), {
1519
+ oldMode, newMode, oldOid, newOid, statusText, pathBytes: first,
1520
+ status: status === 'A' ? 'added' : isDelete ? 'deleted' : status === 'T' ? 'type-changed' : status === 'U' ? 'unmerged' : 'modified',
1521
+ endpoint: isDelete ? 'source' : 'target'
1522
+ });
1523
+ }
1524
+ }
1525
+ return result;
1526
+ }
1527
+
1528
+ function gitBlobOid(root, bytes) {
1529
+ // shortcut: Hash raw worktree bytes without Git filters; use a trusted path-aware oracle if canonical filtered OIDs are required.
1530
+ return execFileSync('git', ['-C', root, 'hash-object', '--stdin'], { encoding: 'utf8', input: bytes, maxBuffer: 1024 * 1024 }).trim();
1531
+ }
1532
+
1533
+ function gitFilesystemPath(root, pathBytes) {
1534
+ const display = gitPathValue(pathBytes);
1535
+ if (display.encoding === 'utf-8') return path.join(path.resolve(root), ...display.path.split('/'));
1536
+ if (process.platform === 'win32') throw new Error('non-UTF-8 Git path bytes are not openable on Windows');
1537
+ return Buffer.concat([Buffer.from(`${path.resolve(root)}${path.sep}`), pathBytes]);
1538
+ }
1539
+
1540
+ function worktreeGitIdentity(root, pathBytes) {
1541
+ const absolute = gitFilesystemPath(root, pathBytes);
1542
+ const stat = fs.lstatSync(absolute);
1543
+ let mode;
1544
+ let bytes;
1545
+ if (stat.isSymbolicLink()) {
1546
+ mode = '120000';
1547
+ bytes = fs.readlinkSync(absolute, { encoding: 'buffer' });
1548
+ } else if (stat.isFile()) {
1549
+ mode = stat.mode & 0o111 ? '100755' : '100644';
1550
+ bytes = fs.readFileSync(absolute);
1551
+ } else {
1552
+ return { mode: null, oid: null, type: null, reason: 'candidate path is not a regular file or symlink; no target was opened' };
1553
+ }
1554
+ return { mode, oid: gitBlobOid(root, bytes), type: 'blob', oidBasis: 'raw worktree bytes; no Git filters applied' };
1555
+ }
1556
+
1557
+ function buildGitDocumentInventory(root, baseRef, manifestPath, proposedPaths = []) {
1558
+ const workspace = fs.realpathSync(root);
1559
+ const loaded = loadInventoryManifest(workspace, manifestPath);
1560
+ const commit = gitOutput(workspace, ['rev-parse', '--verify', '--quiet', '--end-of-options', `${baseRef}^{commit}`], 'utf8').trim();
1561
+ const tree = gitOutput(workspace, ['rev-parse', '--verify', '--quiet', '--end-of-options', `${commit}^{tree}`], 'utf8').trim();
1562
+ const baseEntries = gitTreeEntries(workspace, commit);
1563
+ const changes = gitDiffEntries(workspace, commit);
1564
+ const proposedByKey = new Map(proposedPaths.map(value => {
1565
+ const bytes = proposedInventoryPath(workspace, value);
1566
+ return [bytes.toString('base64'), bytes];
1567
+ }));
1568
+ const relevantKeys = new Set();
1569
+ for (const [key, item] of baseEntries) {
1570
+ if (inventoryPathIsDocument(item.pathBytes, loaded.manifest) || item.mode === '120000' || item.mode === '160000') relevantKeys.add(key);
1571
+ }
1572
+ for (const [key, change] of changes) {
1573
+ const item = baseEntries.get(key);
1574
+ if (inventoryPathIsDocument(change.pathBytes, loaded.manifest)
1575
+ || change.partner && inventoryPathIsDocument(change.partner, loaded.manifest)
1576
+ || item && inventoryPathIsDocument(item.pathBytes, loaded.manifest)
1577
+ || item && (item.mode === '120000' || item.mode === '160000')
1578
+ || change.oldMode === '120000' || change.oldMode === '160000' || change.newMode === '120000' || change.newMode === '160000') {
1579
+ relevantKeys.add(key);
1580
+ if (change.partner) relevantKeys.add(change.partner.toString('base64'));
1581
+ }
1582
+ }
1583
+ for (const key of proposedByKey.keys()) relevantKeys.add(key);
1584
+ const records = [];
1585
+ for (const key of relevantKeys) {
1586
+ const base = baseEntries.get(key) || null;
1587
+ const change = changes.get(key) || null;
1588
+ const recordPathBytes = base ? base.pathBytes : change ? change.pathBytes : proposedByKey.get(key);
1589
+ if (!recordPathBytes) continue;
1590
+ let current = null;
1591
+ let status = change ? change.status : base ? 'unchanged' : 'added';
1592
+ let currentReason = null;
1593
+ if (change && change.status !== 'deleted' && change.status !== 'renamed-from') {
1594
+ if (change.newMode === '160000') {
1595
+ current = /^[0-9a-f]{40,64}$/.test(change.newOid) && !/^0+$/.test(change.newOid)
1596
+ ? { mode: change.newMode, oid: change.newOid, type: 'commit' }
1597
+ : null;
1598
+ if (!current) currentReason = 'gitlink candidate identity unavailable without recursing into the submodule';
1599
+ } else if (change.newMode !== '000000') {
1600
+ try { current = worktreeGitIdentity(workspace, recordPathBytes); }
1601
+ catch (error) { currentReason = `working-tree identity unavailable: ${error.message}`; }
1602
+ }
1603
+ } else if (!change && base) {
1604
+ current = { mode: base.mode, oid: base.oid, type: base.type, oidBasis: 'unchanged base-tree object' };
1605
+ } else if (!base && !change && proposedByKey.has(key)) {
1606
+ try { current = worktreeGitIdentity(workspace, recordPathBytes); }
1607
+ catch (error) { currentReason = `proposed untracked identity unavailable: ${error.message}`; status = 'unavailable'; }
1608
+ }
1609
+ if (current && current.reason) { currentReason = current.reason; current = null; status = 'unavailable'; }
1610
+ const identityMode = (current && current.mode) || (base && base.mode) || (change && change.oldMode);
1611
+ const document = inventoryPathIsDocument(recordPathBytes, loaded.manifest);
1612
+ const disposition = inventoryPathDisposition(recordPathBytes, loaded.manifest, identityMode);
1613
+ const shown = gitPathValue(recordPathBytes);
1614
+ const record = {
1615
+ path: shown.path,
1616
+ pathEncoding: shown.encoding,
1617
+ pathBytesBase64: recordPathBytes.toString('base64'),
1618
+ document,
1619
+ kind: identityMode === '160000' ? 'gitlink' : identityMode === '120000' ? 'symlink' : 'file',
1620
+ status,
1621
+ base: base ? { mode: base.mode, type: base.type, oid: base.oid } : null,
1622
+ current,
1623
+ ...disposition,
1624
+ validation: { outcome: 'unchecked', reason: 'P1 inventory does not execute semantic validators.' }
1625
+ };
1626
+ if (currentReason) record.currentReason = currentReason;
1627
+ if (change && change.partner) {
1628
+ const partner = gitPathValue(change.partner);
1629
+ record.rename = { endpoint: change.endpoint, partnerPath: partner.path, partnerPathBytesBase64: change.partner.toString('base64') };
1630
+ }
1631
+ records.push(record);
1632
+ }
1633
+ records.sort((left, right) => Buffer.compare(Buffer.from(left.pathBytesBase64, 'base64'), Buffer.from(right.pathBytesBase64, 'base64')));
1634
+ const documents = records.filter(item => item.document);
1635
+ const classified = documents.filter(item => item.role && item.lifecycle && item.disposition && item.reason);
1636
+ const identity = records.map(item => ({ pathBytesBase64: item.pathBytesBase64, status: item.status, base: item.base, current: item.current, rename: item.rename || null }));
1637
+ return {
1638
+ schemaVersion: 1,
1639
+ base: { requestedRef: baseRef, commit, tree },
1640
+ candidate: {
1641
+ kind: 'working-tree',
1642
+ scope: 'tracked document paths, explicit proposed paths, and symlink/gitlink and rename endpoint identities',
1643
+ proposedUntrackedPaths: proposedByKey.size,
1644
+ sha256: crypto.createHash('sha256').update(JSON.stringify(identity)).digest('hex')
1645
+ },
1646
+ manifest: { path: path.relative(workspace, loaded.absolute).split(path.sep).join('/'), sha256: loaded.sha256 },
1647
+ coverage: {
1648
+ identifiedDocumentPaths: documents.length,
1649
+ explicitDispositionPaths: classified.length,
1650
+ complete: documents.length === classified.length,
1651
+ claim: 'disposition coverage only; semantic validation was not performed'
1652
+ },
1653
+ records
1654
+ };
1655
+ }
1656
+
1385
1657
  function parseInventoryDoc(relativePath, bytes) {
1386
1658
  const parsed = parseFrontmatter(Buffer.from(bytes).toString('utf8'));
1387
1659
  if (parsed.error || !parsed.data || typeof parsed.data.id !== 'string') return null;
@@ -2299,10 +2571,14 @@ function buildIndex(docs, config, options = {}) {
2299
2571
 
2300
2572
  function parseCli(argv) {
2301
2573
  const [command, ...rest] = argv;
2302
- const options = { command, changedProvided: false, changedPaths: [], all: false };
2574
+ const options = { command, changedProvided: false, changedPaths: [], proposedUntracked: [], all: false };
2303
2575
  for (let index = 0; index < rest.length; index++) {
2304
2576
  const arg = rest[index];
2305
2577
  if (arg === '--all') options.all = true;
2578
+ else if (arg === '--proposed-untracked') {
2579
+ if (index + 1 >= rest.length) throw new Error(`${arg} requires a value`);
2580
+ options.proposedUntracked.push(rest[++index]);
2581
+ }
2306
2582
  else if (arg === '--changed-path') {
2307
2583
  if (index + 1 >= rest.length) throw new Error(`${arg} requires a value`);
2308
2584
  const value = rest[++index];
@@ -2310,16 +2586,24 @@ function parseCli(argv) {
2310
2586
  options.changedProvided = true;
2311
2587
  options.changedPaths.push(value);
2312
2588
  }
2313
- else if (arg === '--workspace' || arg === '--config' || arg === '--changed' || arg === '--base') {
2589
+ else if (arg === '--workspace' || arg === '--config' || arg === '--changed' || arg === '--base' || arg === '--manifest') {
2314
2590
  if (index + 1 >= rest.length) throw new Error(`${arg} requires a value`);
2315
2591
  const value = rest[++index];
2316
2592
  if (arg === '--workspace') options.workspace = value;
2317
2593
  if (arg === '--config') options.configFile = value;
2318
2594
  if (arg === '--changed') { options.changedProvided = true; options.changedPaths = options.changedPaths.concat(value.split(',').map(item => item.trim()).filter(Boolean)); }
2319
2595
  if (arg === '--base') options.base = value;
2596
+ if (arg === '--manifest') options.manifestFile = value;
2320
2597
  } else throw new Error(`unknown argument: ${arg}`);
2321
2598
  }
2599
+ if (command === 'inventory') {
2600
+ if (!options.workspace || !options.manifestFile || !options.base) throw new Error('inventory requires --workspace, --manifest, and --base');
2601
+ if (options.configFile || options.changedProvided || options.all) throw new Error('inventory accepts only --workspace, --manifest, and --base');
2602
+ return options;
2603
+ }
2604
+ if (options.proposedUntracked.length) throw new Error('--proposed-untracked is only valid with inventory');
2322
2605
  if (!['lint', 'index'].includes(command)) throw new Error('usage: doc-lifecycle.js <lint|index> --workspace <dir> --config <json-file, relative to --workspace> [--changed a,b | --changed-path <path> (repeatable, cumulative) | --all] [--base <git-rev>]');
2606
+ if (options.manifestFile) throw new Error('--manifest is only valid with inventory');
2323
2607
  if (!options.workspace || !options.configFile) throw new Error('--workspace and --config are required');
2324
2608
  if (!options.changedProvided && !options.all) throw new Error('lint and index require --changed, --changed-path, or --all');
2325
2609
  if (options.all && options.changedProvided) throw new Error('choose either --changed/--changed-path or --all');
@@ -2361,6 +2645,15 @@ function main(argv = process.argv.slice(2)) {
2361
2645
  let options;
2362
2646
  try { options = parseCli(argv); } catch (error) { process.stderr.write(`${error.message}\n`); return 2; }
2363
2647
  const workspacePath = path.resolve(options.workspace);
2648
+ if (options.command === 'inventory') {
2649
+ try {
2650
+ process.stdout.write(`${JSON.stringify(buildGitDocumentInventory(workspacePath, options.base, options.manifestFile, options.proposedUntracked), null, 2)}\n`);
2651
+ return 0;
2652
+ } catch (error) {
2653
+ process.stderr.write(`inventory failed: ${error.message}\n`);
2654
+ return 2;
2655
+ }
2656
+ }
2364
2657
  if (options.changedProvided) {
2365
2658
  try { options.changedPaths = options.changedPaths.map(item => normalizeChangedPath(workspacePath, item)); } catch (error) {
2366
2659
  process.stderr.write(`${error.message}\n`);
@@ -2399,6 +2692,6 @@ function main(argv = process.argv.slice(2)) {
2399
2692
  return analysis.diagnostics.some(item => item.severity === 'error') ? 1 : 0;
2400
2693
  }
2401
2694
 
2402
- module.exports = { readGitBaseInventory, scanWorkspaceFiles, parseFrontmatter, parseRequirementBlocks, collectDocs, analyzeDocs, resolveRef, lintDocs, buildIndex, diffRevisions };
2695
+ module.exports = { readGitBaseInventory, scanWorkspaceFiles, parseFrontmatter, parseRequirementBlocks, collectDocs, analyzeDocs, resolveRef, lintDocs, buildIndex, diffRevisions, buildGitDocumentInventory };
2403
2696
 
2404
2697
  if (require.main === module) process.exitCode = main();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "harness-enforcement",
3
3
  "description": "Repository inventory for the opencode enforcement plugin and its standalone helper",
4
- "version": "0.42.1",
4
+ "version": "0.43.0",
5
5
  "runtime": "index.mjs",
6
6
  "hooks": {
7
7
  "verify": "hooks/verify.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.42.1",
3
+ "version": "0.43.0",
4
4
  "description": "Harness OS: An Orchestrated Agent Operating System for AI-driven Development",
5
5
  "main": "harness-everything/scripts/bootstrap.js",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harness-everything",
3
- "version": "0.42.1",
3
+ "version": "0.43.0",
4
4
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
5
5
  "author": {
6
6
  "name": "Miya Daniel",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "harness-everything",
4
- "version": "0.42.1",
4
+ "version": "0.43.0",
5
5
  "description": "Invariant-first engineering harness for routing, verification, and failure recovery while preserving agent autonomy.",
6
6
  "author": {
7
7
  "name": "Miya Daniel",
@@ -4,7 +4,7 @@ description: "Route software/project work through Harness: classify tier/topolog
4
4
  license: Apache-2.0
5
5
  metadata:
6
6
  author: Miya Daniel
7
- version: 0.41.0
7
+ version: 0.43.0
8
8
  ---
9
9
 
10
10
  # Harness Everything