hunter-harness 0.1.0 → 0.1.1
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/dist/bin.js +127 -106
- package/package.json +2 -2
- package/resources/harness/general/.harness-build.json +1 -1
- package/resources/harness/general/harness-archive/SKILL.md +215 -215
- package/resources/harness/general/harness-codebase-map/SKILL.md +112 -112
- package/resources/harness/general/harness-codebase-map/templates/map-summary.md +3 -3
- package/resources/harness/general/harness-knowledge-ingest/SKILL.md +246 -246
- package/resources/harness/general/harness-knowledge-query/SKILL.md +164 -164
- package/resources/harness/general/harness-plan/SKILL.md +127 -127
- package/resources/harness/general/harness-review/SKILL.md +156 -156
- package/resources/harness/general/harness-run/SKILL.md +132 -132
- package/resources/harness/general/harness-submit/SKILL.md +159 -159
- package/resources/harness/general/harness-sync/SKILL.md +82 -82
- package/resources/harness/general/harness-test/SKILL.md +180 -180
- package/resources/harness/general/scripts/harness_deploy.py +580 -580
- package/resources/harness/java/.harness-build.json +1 -1
- package/resources/harness/java/harness-apidoc/SKILL.md +86 -86
- package/resources/harness/java/harness-archive/SKILL.md +215 -215
- package/resources/harness/java/harness-codebase-map/SKILL.md +112 -112
- package/resources/harness/java/harness-codebase-map/templates/map-summary.md +3 -3
- package/resources/harness/java/harness-knowledge-ingest/SKILL.md +246 -246
- package/resources/harness/java/harness-knowledge-query/SKILL.md +164 -164
- package/resources/harness/java/harness-package/SKILL.md +87 -87
- package/resources/harness/java/harness-plan/SKILL.md +127 -127
- package/resources/harness/java/harness-review/SKILL.md +156 -156
- package/resources/harness/java/harness-run/SKILL.md +148 -148
- package/resources/harness/java/harness-submit/SKILL.md +166 -166
- package/resources/harness/java/harness-sync/SKILL.md +82 -82
- package/resources/harness/java/harness-test/SKILL.md +192 -192
- package/resources/harness/java/scripts/harness_deploy.py +580 -580
- package/resources/harness/manifests/general.json +13 -33
- package/resources/harness/manifests/java.json +15 -35
- package/resources/bootstrap-ir/manifest.json +0 -19
- package/resources/bootstrap-ir/skills/harness-apidoc.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-archive.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-codebase-map.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-knowledge-ingest.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-package.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-plan.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-review.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-run.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-skill-optimizer.yaml +0 -28
- package/resources/bootstrap-ir/skills/harness-submit.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-sync.yaml +0 -24
- package/resources/bootstrap-ir/skills/harness-test.yaml +0 -24
- package/resources/bootstrap-ir/templates/claude-code-skill.md +0 -12
- package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-01-10-ledger-reconciliation/reports/final/summary-data.json +0 -43
- package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-02-14-ledger-snapshot-followup/reports/final/summary-data.json +0 -38
- package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-03-05-webhook-contract/reports/final/summary-data.json +0 -36
- package/resources/harness/general/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/project.yaml +0 -1
- package/resources/harness/general/harness-knowledge-ingest/tests/test_harness_knowledge.py +0 -1792
- package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-01-10-ledger-reconciliation/reports/final/summary-data.json +0 -43
- package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-02-14-ledger-snapshot-followup/reports/final/summary-data.json +0 -38
- package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/archive/2026-03-05-webhook-contract/reports/final/summary-data.json +0 -36
- package/resources/harness/java/harness-knowledge-ingest/tests/fixtures/mcp-eval-project/.harness/project.yaml +0 -1
- package/resources/harness/java/harness-knowledge-ingest/tests/test_harness_knowledge.py +0 -1792
- package/resources/manifest.json +0 -19
- package/resources/skills/harness-apidoc/SKILL.md +0 -50
- package/resources/skills/harness-archive/SKILL.md +0 -48
- package/resources/skills/harness-codebase-map/SKILL.md +0 -53
- package/resources/skills/harness-knowledge-ingest/SKILL.md +0 -48
- package/resources/skills/harness-package/SKILL.md +0 -48
- package/resources/skills/harness-plan/SKILL.md +0 -51
- package/resources/skills/harness-review/SKILL.md +0 -50
- package/resources/skills/harness-run/SKILL.md +0 -48
- package/resources/skills/harness-skill-optimizer/SKILL.md +0 -54
- package/resources/skills/harness-submit/SKILL.md +0 -47
- package/resources/skills/harness-sync/SKILL.md +0 -48
- package/resources/skills/harness-test/SKILL.md +0 -50
package/dist/bin.js
CHANGED
|
@@ -1244,9 +1244,8 @@ function decideUpdate(policy, dirty) {
|
|
|
1244
1244
|
}
|
|
1245
1245
|
|
|
1246
1246
|
// ../core/dist/project/initialize.js
|
|
1247
|
-
import {
|
|
1248
|
-
import {
|
|
1249
|
-
import { basename, join as join4, resolve as resolve3 } from "node:path";
|
|
1247
|
+
import { readFile as readFile4, stat as stat2 } from "node:fs/promises";
|
|
1248
|
+
import { basename, join as join5, resolve as resolve3 } from "node:path";
|
|
1250
1249
|
import { parse as parseYaml2, stringify as stringifyYaml } from "yaml";
|
|
1251
1250
|
|
|
1252
1251
|
// ../core/dist/transaction/transaction.js
|
|
@@ -1473,6 +1472,61 @@ async function runTransaction(projectRoot, rawOperations, options = {}) {
|
|
|
1473
1472
|
}
|
|
1474
1473
|
}
|
|
1475
1474
|
|
|
1475
|
+
// ../core/dist/project/profile-bundle.js
|
|
1476
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
1477
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
1478
|
+
import { join as join4 } from "node:path";
|
|
1479
|
+
function isProfile(value) {
|
|
1480
|
+
return value === "general" || value === "java";
|
|
1481
|
+
}
|
|
1482
|
+
function validateRelativeBundlePath(path) {
|
|
1483
|
+
if (typeof path !== "string" || path.length === 0 || path.includes("\0") || path.startsWith("/") || path.startsWith("\\") || /^[A-Za-z]:/.test(path) || path.split("/").some((part) => part === "" || part === "." || part === "..")) {
|
|
1484
|
+
throw new Error("invalid Harness Bundle path");
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
async function loadProfileBundle(resourcesRoot, profile) {
|
|
1488
|
+
const raw = JSON.parse(await readFile3(join4(resourcesRoot, "harness", "manifests", `${profile}.json`), "utf8"));
|
|
1489
|
+
if (raw.schema_version !== 1 || raw.profile !== profile || typeof raw.bundle_version !== "string" || raw.generator !== "harness_deploy.py" || !Array.isArray(raw.files)) {
|
|
1490
|
+
throw new Error(`invalid ${profile} Harness Bundle manifest`);
|
|
1491
|
+
}
|
|
1492
|
+
const files = /* @__PURE__ */ new Map();
|
|
1493
|
+
for (const item of raw.files) {
|
|
1494
|
+
validateRelativeBundlePath(item.path);
|
|
1495
|
+
if (typeof item.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(item.sha256) || files.has(item.path)) {
|
|
1496
|
+
throw new Error(`invalid ${profile} Harness Bundle manifest entry`);
|
|
1497
|
+
}
|
|
1498
|
+
const bytes = await readFile3(join4(resourcesRoot, "harness", profile, item.path));
|
|
1499
|
+
if (createHash2("sha256").update(bytes).digest("hex") !== item.sha256) {
|
|
1500
|
+
throw new Error(`Harness Bundle hash mismatch: ${item.path}`);
|
|
1501
|
+
}
|
|
1502
|
+
files.set(item.path, bytes);
|
|
1503
|
+
}
|
|
1504
|
+
return { manifest: raw, files };
|
|
1505
|
+
}
|
|
1506
|
+
async function managedBundleTargets(resourcesRoot, profile) {
|
|
1507
|
+
const bundle = await loadProfileBundle(resourcesRoot, profile);
|
|
1508
|
+
const targets = new Set(bundleTargetContents(bundle).keys());
|
|
1509
|
+
if (profile === "java")
|
|
1510
|
+
targets.add(".claude/rules/harness-profile-java.md");
|
|
1511
|
+
return targets;
|
|
1512
|
+
}
|
|
1513
|
+
function bundleTargetContents(bundle) {
|
|
1514
|
+
const contents = /* @__PURE__ */ new Map();
|
|
1515
|
+
for (const [path, bytes] of bundle.files) {
|
|
1516
|
+
const skillTarget = `.claude/skills/${path}`;
|
|
1517
|
+
contents.set(skillTarget, bytes);
|
|
1518
|
+
const agent = /^agents\/([^/]+\.md)$/.exec(path);
|
|
1519
|
+
if (agent?.[1] !== void 0) {
|
|
1520
|
+
const agentTarget = `.claude/agents/${agent[1]}`;
|
|
1521
|
+
contents.set(agentTarget, bytes);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
return contents;
|
|
1525
|
+
}
|
|
1526
|
+
function parseHarnessProfile(value) {
|
|
1527
|
+
return isProfile(value) ? value : null;
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1476
1530
|
// ../core/dist/project/uuid-v7.js
|
|
1477
1531
|
import { randomBytes } from "node:crypto";
|
|
1478
1532
|
function uuidV7(now = Date.now()) {
|
|
@@ -1498,7 +1552,7 @@ function uuidV7(now = Date.now()) {
|
|
|
1498
1552
|
// ../core/dist/project/initialize.js
|
|
1499
1553
|
async function readOptional(path) {
|
|
1500
1554
|
try {
|
|
1501
|
-
return await
|
|
1555
|
+
return await readFile4(path, "utf8");
|
|
1502
1556
|
} catch (error) {
|
|
1503
1557
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
1504
1558
|
return "";
|
|
@@ -1518,53 +1572,32 @@ async function exists2(path) {
|
|
|
1518
1572
|
}
|
|
1519
1573
|
}
|
|
1520
1574
|
async function existingProjectConfig(root) {
|
|
1521
|
-
const path =
|
|
1575
|
+
const path = join5(root, ".harness", "project.yaml");
|
|
1522
1576
|
const content = await readOptional(path);
|
|
1523
1577
|
return content === "" ? null : projectConfigSchema.parse(parseYaml2(content));
|
|
1524
1578
|
}
|
|
1525
1579
|
async function operationFor(root, path, content) {
|
|
1526
|
-
return await exists2(
|
|
1527
|
-
}
|
|
1528
|
-
async function readProfileBundle(resourcesRoot, profile) {
|
|
1529
|
-
const manifest = JSON.parse(await readFile3(join4(resourcesRoot, "harness", "manifests", `${profile}.json`), "utf8"));
|
|
1530
|
-
if (manifest.schema_version !== 1 || manifest.profile !== profile) {
|
|
1531
|
-
throw new Error(`invalid ${profile} Harness Bundle manifest`);
|
|
1532
|
-
}
|
|
1533
|
-
const files = /* @__PURE__ */ new Map();
|
|
1534
|
-
for (const item of manifest.files) {
|
|
1535
|
-
const bytes = await readFile3(join4(resourcesRoot, "harness", profile, item.path));
|
|
1536
|
-
if (createHash2("sha256").update(bytes).digest("hex") !== item.sha256) {
|
|
1537
|
-
throw new Error(`Harness Bundle hash mismatch: ${item.path}`);
|
|
1538
|
-
}
|
|
1539
|
-
files.set(item.path, bytes);
|
|
1540
|
-
}
|
|
1541
|
-
return { manifest, files };
|
|
1542
|
-
}
|
|
1543
|
-
function bundleTargets(bundleFiles) {
|
|
1544
|
-
const targets = /* @__PURE__ */ new Map();
|
|
1545
|
-
for (const [path, bytes] of bundleFiles) {
|
|
1546
|
-
targets.set(`.claude/skills/${path}`, bytes);
|
|
1547
|
-
const agent = /^agents\/([^/]+\.md)$/.exec(path);
|
|
1548
|
-
if (agent?.[1] !== void 0)
|
|
1549
|
-
targets.set(`.claude/agents/${agent[1]}`, bytes);
|
|
1550
|
-
}
|
|
1551
|
-
return targets;
|
|
1580
|
+
return await exists2(join5(root, path)) ? { operation: "modify", path, content } : { operation: "add", path, content };
|
|
1552
1581
|
}
|
|
1553
1582
|
var INSTALLED_BUNDLE_PATH = ".harness/state/local/installed-harness-bundle.json";
|
|
1554
|
-
async function
|
|
1555
|
-
const content = await readOptional(
|
|
1583
|
+
async function previousInstalledProfile(root) {
|
|
1584
|
+
const content = await readOptional(join5(root, INSTALLED_BUNDLE_PATH));
|
|
1556
1585
|
if (content === "")
|
|
1557
|
-
return
|
|
1558
|
-
|
|
1559
|
-
|
|
1586
|
+
return null;
|
|
1587
|
+
try {
|
|
1588
|
+
const parsed = JSON.parse(content);
|
|
1589
|
+
return parsed.schema_version === 1 ? parseHarnessProfile(parsed.profile) : null;
|
|
1590
|
+
} catch {
|
|
1591
|
+
return null;
|
|
1592
|
+
}
|
|
1560
1593
|
}
|
|
1561
1594
|
async function initializeProject(options) {
|
|
1562
1595
|
const root = resolve3(options.projectRoot);
|
|
1563
1596
|
const config = initConfigSchema.parse(options.config);
|
|
1564
1597
|
const existing = await existingProjectConfig(root);
|
|
1565
1598
|
const profile = config.profile;
|
|
1566
|
-
const bundle = await
|
|
1567
|
-
const bundleFiles =
|
|
1599
|
+
const bundle = await loadProfileBundle(options.resourcesRoot, profile);
|
|
1600
|
+
const bundleFiles = bundleTargetContents(bundle);
|
|
1568
1601
|
const bundleHash = sha256Bytes(canonicalJson(bundle.manifest.files));
|
|
1569
1602
|
const projectConfig = projectConfigSchema.parse({
|
|
1570
1603
|
harness: { name: "hunter-harness", schema_version: 1 },
|
|
@@ -1588,14 +1621,14 @@ async function initializeProject(options) {
|
|
|
1588
1621
|
artifact_manifest_hash: null,
|
|
1589
1622
|
files: {}
|
|
1590
1623
|
});
|
|
1591
|
-
const agentsContent = upsertManagedBlock(await readOptional(
|
|
1624
|
+
const agentsContent = upsertManagedBlock(await readOptional(join5(root, "AGENTS.md")), [
|
|
1592
1625
|
"# Hunter Harness",
|
|
1593
1626
|
"",
|
|
1594
1627
|
"Use .harness/context-index.json to route rules, Knowledge, and codebase maps.",
|
|
1595
1628
|
"Treat .claude/skills/harness-* as editable adapter working copies.",
|
|
1596
1629
|
"Do not modify .harness/state or .harness/cache directly."
|
|
1597
1630
|
].join("\n"));
|
|
1598
|
-
const claudeContent = upsertManagedBlock(await readOptional(
|
|
1631
|
+
const claudeContent = upsertManagedBlock(await readOptional(join5(root, "CLAUDE.md")), [
|
|
1599
1632
|
"@AGENTS.md",
|
|
1600
1633
|
"",
|
|
1601
1634
|
"# Hunter Harness",
|
|
@@ -1664,8 +1697,10 @@ async function initializeProject(options) {
|
|
|
1664
1697
|
files: installedFileList.sort()
|
|
1665
1698
|
};
|
|
1666
1699
|
files.set(INSTALLED_BUNDLE_PATH, JSON.stringify(installedManifest, null, 2) + "\n");
|
|
1667
|
-
const newManaged =
|
|
1668
|
-
const
|
|
1700
|
+
const newManaged = await managedBundleTargets(options.resourcesRoot, profile);
|
|
1701
|
+
const oldProfile = await previousInstalledProfile(root);
|
|
1702
|
+
const oldManaged = oldProfile === null ? /* @__PURE__ */ new Set() : await managedBundleTargets(options.resourcesRoot, oldProfile);
|
|
1703
|
+
const deleteOperations = [...oldManaged].filter((path) => !newManaged.has(path)).map((path) => ({ operation: "delete", path }));
|
|
1669
1704
|
const paths = [...files.keys()].sort((left, right) => left.localeCompare(right));
|
|
1670
1705
|
if (!options.dryRun) {
|
|
1671
1706
|
const writeOperations = await Promise.all([...files.entries()].map(async ([path, content]) => operationFor(root, path, content)));
|
|
@@ -1977,28 +2012,28 @@ function generateProposalPreview(input, scanOptions = {}) {
|
|
|
1977
2012
|
}
|
|
1978
2013
|
|
|
1979
2014
|
// ../core/dist/push/push.js
|
|
1980
|
-
import { lstat as lstat2, readFile as
|
|
1981
|
-
import { join as
|
|
2015
|
+
import { lstat as lstat2, readFile as readFile7, readdir, rm as rm4 } from "node:fs/promises";
|
|
2016
|
+
import { join as join8, relative, resolve as resolve4 } from "node:path";
|
|
1982
2017
|
import { parse as parseYaml3, stringify as stringifyYaml2 } from "yaml";
|
|
1983
2018
|
|
|
1984
2019
|
// ../core/dist/state/baseline.js
|
|
1985
|
-
import { readFile as
|
|
1986
|
-
import { join as
|
|
2020
|
+
import { readFile as readFile5 } from "node:fs/promises";
|
|
2021
|
+
import { join as join6 } from "node:path";
|
|
1987
2022
|
async function readBaseline(projectRoot) {
|
|
1988
|
-
const content = await
|
|
2023
|
+
const content = await readFile5(join6(stateLayout(projectRoot).baseline, "manifest.json"), "utf8");
|
|
1989
2024
|
return baselineManifestSchema.parse(JSON.parse(content));
|
|
1990
2025
|
}
|
|
1991
2026
|
|
|
1992
2027
|
// ../core/dist/state/locks.js
|
|
1993
2028
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
1994
|
-
import { readFile as
|
|
1995
|
-
import { join as
|
|
2029
|
+
import { readFile as readFile6, rename as rename3, rm as rm3, writeFile as writeFile3 } from "node:fs/promises";
|
|
2030
|
+
import { join as join7 } from "node:path";
|
|
1996
2031
|
async function readLock(path) {
|
|
1997
|
-
return JSON.parse(await
|
|
2032
|
+
return JSON.parse(await readFile6(path, "utf8"));
|
|
1998
2033
|
}
|
|
1999
2034
|
async function acquireProtocolLock(projectRoot, operation, options = {}) {
|
|
2000
2035
|
const layout = await ensureStateLayout(projectRoot);
|
|
2001
|
-
const lockPath =
|
|
2036
|
+
const lockPath = join7(layout.locks, "protocol.lock");
|
|
2002
2037
|
const now = options.now ?? Date.now();
|
|
2003
2038
|
const staleAfterMs = options.staleAfterMs ?? 15 * 60 * 1e3;
|
|
2004
2039
|
const record = {
|
|
@@ -2061,22 +2096,6 @@ var MANAGED_FILES = [
|
|
|
2061
2096
|
".harness/project.yaml",
|
|
2062
2097
|
".harness/context-index.json"
|
|
2063
2098
|
];
|
|
2064
|
-
var INSTALLED_BUNDLE_PATH2 = ".harness/state/local/installed-harness-bundle.json";
|
|
2065
|
-
async function installedBundlePaths(root) {
|
|
2066
|
-
try {
|
|
2067
|
-
const content = await readFile6(join7(root, INSTALLED_BUNDLE_PATH2), "utf8");
|
|
2068
|
-
const parsed = JSON.parse(content);
|
|
2069
|
-
if (parsed.schema_version === 1 && Array.isArray(parsed.files)) {
|
|
2070
|
-
return new Set(parsed.files);
|
|
2071
|
-
}
|
|
2072
|
-
} catch (error) {
|
|
2073
|
-
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2074
|
-
return /* @__PURE__ */ new Set();
|
|
2075
|
-
}
|
|
2076
|
-
throw error;
|
|
2077
|
-
}
|
|
2078
|
-
return /* @__PURE__ */ new Set();
|
|
2079
|
-
}
|
|
2080
2099
|
async function exists3(path) {
|
|
2081
2100
|
try {
|
|
2082
2101
|
await lstat2(path);
|
|
@@ -2093,7 +2112,7 @@ async function walkFiles(root, current, output) {
|
|
|
2093
2112
|
return;
|
|
2094
2113
|
}
|
|
2095
2114
|
for (const item of await readdir(current, { withFileTypes: true })) {
|
|
2096
|
-
const path =
|
|
2115
|
+
const path = join8(current, item.name);
|
|
2097
2116
|
if (item.isSymbolicLink()) {
|
|
2098
2117
|
throw new PushWorkflowError("symlink is not pushable", 6, "UNSAFE_SYMLINK");
|
|
2099
2118
|
}
|
|
@@ -2108,24 +2127,24 @@ async function managedFiles(projectRoot) {
|
|
|
2108
2127
|
const root = resolve4(projectRoot);
|
|
2109
2128
|
const paths = [];
|
|
2110
2129
|
for (const path of MANAGED_FILES) {
|
|
2111
|
-
if (await exists3(
|
|
2130
|
+
if (await exists3(join8(root, path))) {
|
|
2112
2131
|
paths.push(path);
|
|
2113
2132
|
}
|
|
2114
2133
|
}
|
|
2115
2134
|
for (const path of MANAGED_ROOTS) {
|
|
2116
|
-
await walkFiles(root,
|
|
2135
|
+
await walkFiles(root, join8(root, path), paths);
|
|
2117
2136
|
}
|
|
2118
|
-
const skillsRoot =
|
|
2137
|
+
const skillsRoot = join8(root, ".claude", "skills");
|
|
2119
2138
|
if (await exists3(skillsRoot)) {
|
|
2120
2139
|
for (const item of await readdir(skillsRoot, { withFileTypes: true })) {
|
|
2121
2140
|
if (item.isDirectory() && item.name.startsWith("harness-")) {
|
|
2122
|
-
await walkFiles(root,
|
|
2141
|
+
await walkFiles(root, join8(skillsRoot, item.name), paths);
|
|
2123
2142
|
}
|
|
2124
2143
|
}
|
|
2125
2144
|
}
|
|
2126
2145
|
const result = {};
|
|
2127
2146
|
for (const path of [...new Set(paths)].sort()) {
|
|
2128
|
-
result[path] = await
|
|
2147
|
+
result[path] = await readFile7(join8(root, path), "utf8");
|
|
2129
2148
|
}
|
|
2130
2149
|
return result;
|
|
2131
2150
|
}
|
|
@@ -2134,14 +2153,14 @@ function proposalBaseline(baseline) {
|
|
|
2134
2153
|
}
|
|
2135
2154
|
async function readProject(root) {
|
|
2136
2155
|
try {
|
|
2137
|
-
return projectConfigSchema.parse(parseYaml3(await
|
|
2156
|
+
return projectConfigSchema.parse(parseYaml3(await readFile7(join8(root, ".harness", "project.yaml"), "utf8")));
|
|
2138
2157
|
} catch {
|
|
2139
2158
|
throw new PushWorkflowError("project configuration is missing or invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
2140
2159
|
}
|
|
2141
2160
|
}
|
|
2142
2161
|
async function readOptionalJson(path) {
|
|
2143
2162
|
try {
|
|
2144
|
-
return JSON.parse(await
|
|
2163
|
+
return JSON.parse(await readFile7(path, "utf8"));
|
|
2145
2164
|
} catch (error) {
|
|
2146
2165
|
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
2147
2166
|
return null;
|
|
@@ -2153,7 +2172,7 @@ async function clientIdFor(root, explicit) {
|
|
|
2153
2172
|
if (explicit !== void 0) {
|
|
2154
2173
|
return explicit;
|
|
2155
2174
|
}
|
|
2156
|
-
const path =
|
|
2175
|
+
const path = join8(root, ".harness", "state", "local", "client.json");
|
|
2157
2176
|
const existing = await readOptionalJson(path);
|
|
2158
2177
|
if (typeof existing?.client_id === "string" && /^cli_[A-Za-z0-9_-]+$/.test(existing.client_id)) {
|
|
2159
2178
|
return existing.client_id;
|
|
@@ -2239,7 +2258,8 @@ async function pushProject(options) {
|
|
|
2239
2258
|
const root = resolve4(options.projectRoot);
|
|
2240
2259
|
let project = await readProject(root);
|
|
2241
2260
|
let baseline = await readBaseline(root);
|
|
2242
|
-
const
|
|
2261
|
+
const profile = parseHarnessProfile(project.project.profiles[0]);
|
|
2262
|
+
const installedPaths = profile === null ? /* @__PURE__ */ new Set() : await managedBundleTargets(options.resourcesRoot, profile);
|
|
2243
2263
|
let preview = makePreview(baseline, await managedFiles(root), options.confirmedProjectLocal ?? [], installedPaths);
|
|
2244
2264
|
if (preview.blocked) {
|
|
2245
2265
|
throw new PushWorkflowError("sensitive information scan blocked the proposal", 6, "SENSITIVE_CONTENT_BLOCKED");
|
|
@@ -2271,7 +2291,7 @@ async function pushProject(options) {
|
|
|
2271
2291
|
if (parsedServerUrl.protocol !== "https:") {
|
|
2272
2292
|
throw new PushWorkflowError("server_url must use HTTPS", 3, "SERVER_URL_INVALID");
|
|
2273
2293
|
}
|
|
2274
|
-
const workflowPath =
|
|
2294
|
+
const workflowPath = join8(root, ".harness", "state", "local", "push-workflow.json");
|
|
2275
2295
|
const priorWorkflow = await readOptionalJson(workflowPath);
|
|
2276
2296
|
const provisionalRequestId = priorWorkflow?.local_project_key === project.project.local_project_key ? priorWorkflow.request_id : uuidV7();
|
|
2277
2297
|
const lock = await acquireProtocolLock(root, "push", { requestId: provisionalRequestId });
|
|
@@ -2362,7 +2382,7 @@ async function pushProject(options) {
|
|
|
2362
2382
|
}
|
|
2363
2383
|
}
|
|
2364
2384
|
const finalized = await client.finalizeProposal(session.session_id, { schema_version: 1, manifest_sha256: proposalManifestHash }, requestId, workflow.finalize_idempotency_key);
|
|
2365
|
-
await atomicWriteJson(
|
|
2385
|
+
await atomicWriteJson(join8(root, ".harness", "state", "local", "push-results", finalized.proposal_id + ".json"), {
|
|
2366
2386
|
schema_version: 1,
|
|
2367
2387
|
request_id: requestId,
|
|
2368
2388
|
project_id: projectId,
|
|
@@ -2431,10 +2451,10 @@ var AGENT_DESCRIPTORS = {
|
|
|
2431
2451
|
var INSTALLABLE_AGENTS = Object.keys(AGENT_DESCRIPTORS).filter((agent) => AGENT_DESCRIPTORS[agent]?.installable === true);
|
|
2432
2452
|
|
|
2433
2453
|
// ../core/dist/transaction/recovery.js
|
|
2434
|
-
import { readFile as
|
|
2435
|
-
import { join as
|
|
2454
|
+
import { readFile as readFile8, readdir as readdir2, rm as rm5, stat as stat3 } from "node:fs/promises";
|
|
2455
|
+
import { join as join9 } from "node:path";
|
|
2436
2456
|
async function recoverTransaction(projectRoot, transactionId) {
|
|
2437
|
-
const journal = JSON.parse(await
|
|
2457
|
+
const journal = JSON.parse(await readFile8(join9(stateLayout(projectRoot).transactions, transactionId, "journal.json"), "utf8"));
|
|
2438
2458
|
if (journal.state === "committed") {
|
|
2439
2459
|
return { transactionId, status: "committed" };
|
|
2440
2460
|
}
|
|
@@ -2461,7 +2481,7 @@ async function listTransactions(projectRoot) {
|
|
|
2461
2481
|
const transactions = [];
|
|
2462
2482
|
for (const transactionId of names) {
|
|
2463
2483
|
try {
|
|
2464
|
-
const journal = JSON.parse(await
|
|
2484
|
+
const journal = JSON.parse(await readFile8(join9(root, transactionId, "journal.json"), "utf8"));
|
|
2465
2485
|
transactions.push({
|
|
2466
2486
|
transactionId,
|
|
2467
2487
|
kind: journal.kind,
|
|
@@ -2492,11 +2512,11 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
2492
2512
|
if (latest === void 0) {
|
|
2493
2513
|
throw new Error("no committed update transaction is available for rollback");
|
|
2494
2514
|
}
|
|
2495
|
-
const transactionRoot =
|
|
2496
|
-
const journal = JSON.parse(await
|
|
2497
|
-
const after = JSON.parse(await
|
|
2515
|
+
const transactionRoot = join9(stateLayout(projectRoot).transactions, latest.transactionId);
|
|
2516
|
+
const journal = JSON.parse(await readFile8(join9(transactionRoot, "journal.json"), "utf8"));
|
|
2517
|
+
const after = JSON.parse(await readFile8(join9(transactionRoot, "after", "manifest.json"), "utf8"));
|
|
2498
2518
|
for (const entry of after) {
|
|
2499
|
-
const target =
|
|
2519
|
+
const target = join9(projectRoot, entry.path);
|
|
2500
2520
|
const exists5 = await pathExists(target);
|
|
2501
2521
|
if (exists5 !== entry.exists || exists5 && await sha256File(target) !== entry.hash) {
|
|
2502
2522
|
throw new Error("cannot rollback dirty path: " + entry.path);
|
|
@@ -2509,10 +2529,10 @@ async function rollbackLatestCommittedUpdate(projectRoot) {
|
|
|
2509
2529
|
continue;
|
|
2510
2530
|
}
|
|
2511
2531
|
seen.add(snapshot.path);
|
|
2512
|
-
const target =
|
|
2532
|
+
const target = join9(projectRoot, snapshot.path);
|
|
2513
2533
|
const exists5 = await pathExists(target);
|
|
2514
2534
|
if (snapshot.existed && snapshot.snapshot_name !== null) {
|
|
2515
|
-
const content = await
|
|
2535
|
+
const content = await readFile8(join9(transactionRoot, "before", snapshot.snapshot_name));
|
|
2516
2536
|
operations.push({
|
|
2517
2537
|
operation: exists5 ? "modify" : "add",
|
|
2518
2538
|
path: snapshot.path,
|
|
@@ -2541,7 +2561,7 @@ async function cleanupOldTransactions(projectRoot, now = /* @__PURE__ */ new Dat
|
|
|
2541
2561
|
if (!removable) {
|
|
2542
2562
|
continue;
|
|
2543
2563
|
}
|
|
2544
|
-
await rm5(
|
|
2564
|
+
await rm5(join9(stateLayout(projectRoot).transactions, item.transactionId), {
|
|
2545
2565
|
recursive: true,
|
|
2546
2566
|
force: true
|
|
2547
2567
|
});
|
|
@@ -2573,8 +2593,8 @@ function managedBlockDirty(currentContent, managedBlockHash) {
|
|
|
2573
2593
|
}
|
|
2574
2594
|
|
|
2575
2595
|
// ../core/dist/update/update.js
|
|
2576
|
-
import { lstat as lstat3, readFile as
|
|
2577
|
-
import { join as
|
|
2596
|
+
import { lstat as lstat3, readFile as readFile9, rm as rm6 } from "node:fs/promises";
|
|
2597
|
+
import { join as join10, resolve as resolve5 } from "node:path";
|
|
2578
2598
|
import { parse as parseYaml6 } from "yaml";
|
|
2579
2599
|
var UpdateWorkflowError = class extends Error {
|
|
2580
2600
|
exitCode;
|
|
@@ -2598,7 +2618,7 @@ async function pathExists2(path) {
|
|
|
2598
2618
|
}
|
|
2599
2619
|
}
|
|
2600
2620
|
async function optionalContent(path) {
|
|
2601
|
-
return await pathExists2(path) ?
|
|
2621
|
+
return await pathExists2(path) ? readFile9(path, "utf8") : null;
|
|
2602
2622
|
}
|
|
2603
2623
|
function manifestPayloadHash(manifest) {
|
|
2604
2624
|
const payload = { ...manifest };
|
|
@@ -2616,10 +2636,10 @@ async function loadBlob(root, client, artifactId, operation, requestId, dryRun)
|
|
|
2616
2636
|
return null;
|
|
2617
2637
|
}
|
|
2618
2638
|
const hash = operation.content_sha256;
|
|
2619
|
-
const cacheRoot =
|
|
2620
|
-
const cachePath =
|
|
2639
|
+
const cacheRoot = join10(root, ".harness", "cache", "server-artifacts", artifactId);
|
|
2640
|
+
const cachePath = join10(cacheRoot, "blobs", hash.replace(":", "_"));
|
|
2621
2641
|
if (await pathExists2(cachePath) && await sha256File(cachePath) === hash) {
|
|
2622
|
-
return
|
|
2642
|
+
return readFile9(cachePath, "utf8");
|
|
2623
2643
|
}
|
|
2624
2644
|
const bytes = await client.downloadArtifactBlob(artifactId, hash, requestId);
|
|
2625
2645
|
if (bytes.byteLength !== operation.size_bytes || sha256Bytes(bytes) !== hash) {
|
|
@@ -2669,7 +2689,7 @@ async function updateProject(options) {
|
|
|
2669
2689
|
const root = resolve5(options.projectRoot);
|
|
2670
2690
|
let project;
|
|
2671
2691
|
try {
|
|
2672
|
-
project = projectConfigSchema.parse(parseYaml6(await
|
|
2692
|
+
project = projectConfigSchema.parse(parseYaml6(await readFile9(join10(root, ".harness", "project.yaml"), "utf8")));
|
|
2673
2693
|
} catch {
|
|
2674
2694
|
throw new UpdateWorkflowError("project configuration is invalid", 3, "PROJECT_CONFIG_INVALID");
|
|
2675
2695
|
}
|
|
@@ -2754,8 +2774,8 @@ async function updateProject(options) {
|
|
|
2754
2774
|
skipped.push({ path: target, operation: operation.operation, reason: "baseline-diverged" });
|
|
2755
2775
|
continue;
|
|
2756
2776
|
}
|
|
2757
|
-
const sourceContent = await optionalContent(
|
|
2758
|
-
const targetContent = target === source ? sourceContent : await optionalContent(
|
|
2777
|
+
const sourceContent = await optionalContent(join10(root, source));
|
|
2778
|
+
const targetContent = target === source ? sourceContent : await optionalContent(join10(root, target));
|
|
2759
2779
|
const incoming = blobs.get(operation) ?? null;
|
|
2760
2780
|
const incomingHash = contentHash(operation);
|
|
2761
2781
|
let equivalent = incomingHash !== null && targetContent !== null && sha256Bytes(targetContent) === incomingHash;
|
|
@@ -2814,7 +2834,7 @@ async function updateProject(options) {
|
|
|
2814
2834
|
dryRun: true
|
|
2815
2835
|
};
|
|
2816
2836
|
}
|
|
2817
|
-
await atomicWriteJson(
|
|
2837
|
+
await atomicWriteJson(join10(root, ".harness", "cache", "server-artifacts", manifest.artifact_id, "manifest.json"), manifest);
|
|
2818
2838
|
const lock = await acquireProtocolLock(root, "update", { requestId });
|
|
2819
2839
|
try {
|
|
2820
2840
|
const nextBaseline = baselineManifestSchema.parse(structuredClone(baseline));
|
|
@@ -2895,8 +2915,8 @@ async function updateProject(options) {
|
|
|
2895
2915
|
}
|
|
2896
2916
|
|
|
2897
2917
|
// src/config/init-config.ts
|
|
2898
|
-
import { isAbsolute as isAbsolute2, join as
|
|
2899
|
-
import { readFile as
|
|
2918
|
+
import { isAbsolute as isAbsolute2, join as join11 } from "node:path";
|
|
2919
|
+
import { readFile as readFile10 } from "node:fs/promises";
|
|
2900
2920
|
var InitConfigurationError = class extends Error {
|
|
2901
2921
|
exitCode;
|
|
2902
2922
|
constructor(message, exitCode = 3, options) {
|
|
@@ -2914,9 +2934,9 @@ function normalizeProfile(value) {
|
|
|
2914
2934
|
async function resolveInitConfig(cwd, flags, promptMissing) {
|
|
2915
2935
|
let fileConfig = {};
|
|
2916
2936
|
if (flags.config !== void 0) {
|
|
2917
|
-
const path = isAbsolute2(flags.config) ? flags.config :
|
|
2937
|
+
const path = isAbsolute2(flags.config) ? flags.config : join11(cwd, flags.config);
|
|
2918
2938
|
try {
|
|
2919
|
-
fileConfig = JSON.parse(await
|
|
2939
|
+
fileConfig = JSON.parse(await readFile10(path, "utf8"));
|
|
2920
2940
|
} catch (error) {
|
|
2921
2941
|
throw new InitConfigurationError(
|
|
2922
2942
|
"unable to read init config: " + (error instanceof Error ? error.message : String(error)),
|
|
@@ -3027,6 +3047,7 @@ async function runPush(options, dependencies) {
|
|
|
3027
3047
|
try {
|
|
3028
3048
|
const result = await pushProject({
|
|
3029
3049
|
projectRoot: dependencies.cwd,
|
|
3050
|
+
resourcesRoot: dependencies.resourcesRoot,
|
|
3030
3051
|
...options.serverUrl === void 0 ? {} : { serverUrl: options.serverUrl },
|
|
3031
3052
|
...options.tokenEnv === void 0 ? {} : { tokenEnv: options.tokenEnv },
|
|
3032
3053
|
env: dependencies.env,
|
|
@@ -3188,7 +3209,7 @@ async function runUpdate(options, dependencies) {
|
|
|
3188
3209
|
|
|
3189
3210
|
// src/commands/recovery.ts
|
|
3190
3211
|
import { stat as stat4 } from "node:fs/promises";
|
|
3191
|
-
import { join as
|
|
3212
|
+
import { join as join12 } from "node:path";
|
|
3192
3213
|
async function exists4(path) {
|
|
3193
3214
|
try {
|
|
3194
3215
|
await stat4(path);
|
|
@@ -3248,7 +3269,7 @@ async function runRecoveryMenuIfApplicable(options, dependencies) {
|
|
|
3248
3269
|
return 5;
|
|
3249
3270
|
}
|
|
3250
3271
|
}
|
|
3251
|
-
const initialized = await exists4(
|
|
3272
|
+
const initialized = await exists4(join12(dependencies.cwd, ".harness", "project.yaml"));
|
|
3252
3273
|
if (!initialized || options.nonInteractive === true || explicitConfigure(options)) {
|
|
3253
3274
|
return null;
|
|
3254
3275
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hunter-harness",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Local-first, server-governed agent harness",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"node": ">=24.0.0"
|
|
9
9
|
},
|
|
10
10
|
"bin": {
|
|
11
|
-
"hunter-harness": "
|
|
11
|
+
"hunter-harness": "dist/bin.js"
|
|
12
12
|
},
|
|
13
13
|
"files": [
|
|
14
14
|
"dist/bin.js",
|