@harness-engineering/cli 1.11.0 → 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.
- package/dist/{agents-md-ZFV6RR5J.js → agents-md-KIS2RSMG.js} +1 -1
- package/dist/{architecture-EXNUMH5R.js → architecture-AJAUDRQQ.js} +2 -2
- package/dist/bin/harness-mcp.js +10 -10
- package/dist/bin/harness.js +12 -12
- package/dist/{check-phase-gate-VZFOY2PO.js → check-phase-gate-K7QCSYRJ.js} +2 -2
- package/dist/{chunk-GSIVNYVJ.js → chunk-2SWJ4VO7.js} +4 -4
- package/dist/{chunk-I6JZYEGT.js → chunk-747VBPA4.js} +41 -41
- package/dist/{chunk-2YSQOUHO.js → chunk-AE2OWWDH.js} +72 -30
- package/dist/{chunk-ZWC3MN5E.js → chunk-B5SBNH4S.js} +705 -203
- package/dist/{chunk-NC6PXVWT.js → chunk-CTTFXXKJ.js} +3 -3
- package/dist/{chunk-X3MN5UQJ.js → chunk-EAURF4LH.js} +1 -1
- package/dist/{chunk-PA2XHK75.js → chunk-FLOEMHDF.js} +3 -3
- package/dist/{chunk-Z75JC6I2.js → chunk-JLXOEO5C.js} +2 -2
- package/dist/{chunk-TI4TGEX6.js → chunk-OIGVQF5V.js} +1 -1
- package/dist/{chunk-WUJTCNOU.js → chunk-TJVVU3HB.js} +1 -1
- package/dist/{chunk-WJZDO6OY.js → chunk-YXOG2277.js} +2 -2
- package/dist/{chunk-2NCIKJES.js → chunk-ZU2UBYBY.js} +1 -1
- package/dist/{ci-workflow-K5RCRNYR.js → ci-workflow-NBL4OT4A.js} +1 -1
- package/dist/{dist-JVZ2MKBC.js → dist-IJ4J4C5G.js} +3 -1
- package/dist/{docs-PWCUVYWU.js → docs-CPTMH3VY.js} +2 -2
- package/dist/{engine-6XUP6GAK.js → engine-BUWPAAGD.js} +1 -1
- package/dist/{entropy-4I6JEYAC.js → entropy-Z4FYVQ7L.js} +2 -2
- package/dist/{feedback-TNIW534S.js → feedback-TT6WF5YX.js} +1 -1
- package/dist/{generate-agent-definitions-MWKEA5NU.js → generate-agent-definitions-J5HANRNR.js} +1 -1
- package/dist/index.d.ts +39 -2
- package/dist/index.js +17 -13
- package/dist/{loader-4FIPIFII.js → loader-PCU5YWRH.js} +1 -1
- package/dist/{mcp-MOKLYNZL.js → mcp-YM6QLHLZ.js} +10 -10
- package/dist/{performance-BTOJCPXU.js → performance-YJVXOKIB.js} +2 -2
- package/dist/{review-pipeline-3YTW3463.js → review-pipeline-KGMIMLIE.js} +1 -1
- package/dist/{runtime-GO7K2PJE.js → runtime-F6R27LD6.js} +1 -1
- package/dist/{security-4P2GGFF6.js → security-MX5VVXBC.js} +1 -1
- package/dist/{validate-JN44D2Q7.js → validate-EFNMSFKD.js} +2 -2
- package/dist/{validate-cross-check-DB7RIFFF.js → validate-cross-check-LJX65SBS.js} +1 -1
- package/package.json +3 -3
|
@@ -5,9 +5,10 @@ import {
|
|
|
5
5
|
OutputFormatter,
|
|
6
6
|
OutputMode,
|
|
7
7
|
createCheckPhaseGateCommand,
|
|
8
|
+
findConfigFile,
|
|
8
9
|
findFiles,
|
|
9
10
|
resolveConfig
|
|
10
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-ZU2UBYBY.js";
|
|
11
12
|
import {
|
|
12
13
|
createGenerateAgentDefinitionsCommand,
|
|
13
14
|
generateAgentDefinitions
|
|
@@ -49,7 +50,7 @@ import {
|
|
|
49
50
|
generateSlashCommands,
|
|
50
51
|
handleGetImpact,
|
|
51
52
|
handleOrphanDeletion
|
|
52
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-747VBPA4.js";
|
|
53
54
|
import {
|
|
54
55
|
VALID_PLATFORMS
|
|
55
56
|
} from "./chunk-ZOAWBDWU.js";
|
|
@@ -79,11 +80,13 @@ import {
|
|
|
79
80
|
ArchConfigSchema,
|
|
80
81
|
BaselineManager,
|
|
81
82
|
BlueprintGenerator,
|
|
83
|
+
BundleSchema,
|
|
82
84
|
CriticalPathResolver,
|
|
83
85
|
EntropyAnalyzer,
|
|
84
86
|
ProjectScanner,
|
|
85
87
|
SecurityScanner,
|
|
86
88
|
TypeScriptParser,
|
|
89
|
+
addProvenance,
|
|
87
90
|
appendLearning,
|
|
88
91
|
applyFixes,
|
|
89
92
|
archiveStream,
|
|
@@ -91,6 +94,7 @@ import {
|
|
|
91
94
|
checkDocCoverage,
|
|
92
95
|
createFixes,
|
|
93
96
|
createStream,
|
|
97
|
+
deepMergeConstraints,
|
|
94
98
|
defineLayer,
|
|
95
99
|
detectCircularDepsInFiles,
|
|
96
100
|
detectDeadCode,
|
|
@@ -104,6 +108,9 @@ import {
|
|
|
104
108
|
parseDiff,
|
|
105
109
|
parseManifest,
|
|
106
110
|
parseSecurityConfig,
|
|
111
|
+
readLockfile,
|
|
112
|
+
removeContributions,
|
|
113
|
+
removeProvenance,
|
|
107
114
|
requestPeerReview,
|
|
108
115
|
resolveStreamPath,
|
|
109
116
|
runAll,
|
|
@@ -113,15 +120,16 @@ import {
|
|
|
113
120
|
validateAgentsMap,
|
|
114
121
|
validateDependencies,
|
|
115
122
|
validateKnowledgeMap,
|
|
116
|
-
writeConfig
|
|
117
|
-
|
|
123
|
+
writeConfig,
|
|
124
|
+
writeLockfile
|
|
125
|
+
} from "./chunk-AE2OWWDH.js";
|
|
118
126
|
import {
|
|
119
127
|
Err,
|
|
120
128
|
Ok
|
|
121
129
|
} from "./chunk-MHBMTPW7.js";
|
|
122
130
|
|
|
123
131
|
// src/index.ts
|
|
124
|
-
import { Command as
|
|
132
|
+
import { Command as Command53 } from "commander";
|
|
125
133
|
|
|
126
134
|
// src/commands/validate.ts
|
|
127
135
|
import { Command } from "commander";
|
|
@@ -200,7 +208,7 @@ function createValidateCommand() {
|
|
|
200
208
|
process.exit(result.error.exitCode);
|
|
201
209
|
}
|
|
202
210
|
if (opts.crossCheck) {
|
|
203
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
211
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-LJX65SBS.js");
|
|
204
212
|
const cwd = process.cwd();
|
|
205
213
|
const specsDir = path.join(cwd, "docs", "specs");
|
|
206
214
|
const plansDir = path.join(cwd, "docs", "plans");
|
|
@@ -468,10 +476,10 @@ async function runCheckSecurity(cwd, options) {
|
|
|
468
476
|
const projectRoot = path4.resolve(cwd);
|
|
469
477
|
let configData = {};
|
|
470
478
|
try {
|
|
471
|
-
const
|
|
479
|
+
const fs23 = await import("fs");
|
|
472
480
|
const configPath = path4.join(projectRoot, "harness.config.json");
|
|
473
|
-
if (
|
|
474
|
-
const raw =
|
|
481
|
+
if (fs23.existsSync(configPath)) {
|
|
482
|
+
const raw = fs23.readFileSync(configPath, "utf-8");
|
|
475
483
|
const parsed = JSON.parse(raw);
|
|
476
484
|
configData = parsed.security ?? {};
|
|
477
485
|
}
|
|
@@ -558,7 +566,7 @@ function createPerfCommand() {
|
|
|
558
566
|
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob, _opts, cmd) => {
|
|
559
567
|
const globalOpts = cmd.optsWithGlobals();
|
|
560
568
|
const cwd = process.cwd();
|
|
561
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
569
|
+
const { BenchmarkRunner } = await import("./dist-IJ4J4C5G.js");
|
|
562
570
|
const runner = new BenchmarkRunner();
|
|
563
571
|
const benchFiles = runner.discover(cwd, glob);
|
|
564
572
|
if (benchFiles.length === 0) {
|
|
@@ -627,7 +635,7 @@ Results (${result.results.length} benchmarks):`);
|
|
|
627
635
|
baselines.command("update").description("Update baselines from latest benchmark run").action(async (_opts, cmd) => {
|
|
628
636
|
const globalOpts = cmd.optsWithGlobals();
|
|
629
637
|
const cwd = process.cwd();
|
|
630
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
638
|
+
const { BenchmarkRunner } = await import("./dist-IJ4J4C5G.js");
|
|
631
639
|
const runner = new BenchmarkRunner();
|
|
632
640
|
const manager = new BaselineManager(cwd);
|
|
633
641
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -655,7 +663,7 @@ Results (${result.results.length} benchmarks):`);
|
|
|
655
663
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
656
664
|
const globalOpts = cmd.optsWithGlobals();
|
|
657
665
|
const cwd = process.cwd();
|
|
658
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
666
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-IJ4J4C5G.js");
|
|
659
667
|
const analyzer = new EntropyAnalyzer2({
|
|
660
668
|
rootDir: path5.resolve(cwd),
|
|
661
669
|
analyze: { complexity: true, coupling: true }
|
|
@@ -1905,7 +1913,7 @@ function sortedStringify(obj) {
|
|
|
1905
1913
|
2
|
|
1906
1914
|
);
|
|
1907
1915
|
}
|
|
1908
|
-
function
|
|
1916
|
+
function readLockfile2(filePath) {
|
|
1909
1917
|
if (!fs5.existsSync(filePath)) {
|
|
1910
1918
|
return createEmptyLockfile();
|
|
1911
1919
|
}
|
|
@@ -1925,7 +1933,7 @@ function readLockfile(filePath) {
|
|
|
1925
1933
|
}
|
|
1926
1934
|
return parsed;
|
|
1927
1935
|
}
|
|
1928
|
-
function
|
|
1936
|
+
function writeLockfile2(filePath, lockfile) {
|
|
1929
1937
|
const dir = path14.dirname(filePath);
|
|
1930
1938
|
fs5.mkdirSync(dir, { recursive: true });
|
|
1931
1939
|
fs5.writeFileSync(filePath, sortedStringify(lockfile) + "\n", "utf-8");
|
|
@@ -1996,8 +2004,18 @@ function collectSkills(opts) {
|
|
|
1996
2004
|
const globalDir = resolveGlobalSkillsDir();
|
|
1997
2005
|
const skillsDir = path15.dirname(globalDir);
|
|
1998
2006
|
const communityBase = path15.join(skillsDir, "community");
|
|
2007
|
+
const communityPlatformDir = path15.join(communityBase, "claude-code");
|
|
1999
2008
|
const lockfilePath = path15.join(communityBase, "skills-lock.json");
|
|
2000
|
-
const lockfile =
|
|
2009
|
+
const lockfile = readLockfile2(lockfilePath);
|
|
2010
|
+
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
2011
|
+
for (const skill of communitySkills) {
|
|
2012
|
+
const pkgName = `@harness-skills/${skill.name}`;
|
|
2013
|
+
const lockEntry = lockfile.skills[pkgName];
|
|
2014
|
+
if (lockEntry) {
|
|
2015
|
+
skill.version = lockEntry.version;
|
|
2016
|
+
}
|
|
2017
|
+
}
|
|
2018
|
+
addUnique(communitySkills);
|
|
2001
2019
|
for (const [pkgName, entry] of Object.entries(lockfile.skills)) {
|
|
2002
2020
|
const shortName = pkgName.replace("@harness-skills/", "");
|
|
2003
2021
|
if (!seen.has(shortName)) {
|
|
@@ -2393,6 +2411,9 @@ function createInfoCommand() {
|
|
|
2393
2411
|
import { Command as Command25 } from "commander";
|
|
2394
2412
|
|
|
2395
2413
|
// src/registry/npm-client.ts
|
|
2414
|
+
import * as fs10 from "fs";
|
|
2415
|
+
import * as path19 from "path";
|
|
2416
|
+
import * as os2 from "os";
|
|
2396
2417
|
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
2397
2418
|
var FETCH_TIMEOUT_MS = 3e4;
|
|
2398
2419
|
var HARNESS_SKILLS_SCOPE = "@harness-skills/";
|
|
@@ -2411,12 +2432,37 @@ function extractSkillName(packageName) {
|
|
|
2411
2432
|
}
|
|
2412
2433
|
return packageName;
|
|
2413
2434
|
}
|
|
2414
|
-
|
|
2435
|
+
function readNpmrcToken(registryUrl) {
|
|
2436
|
+
const { hostname, pathname } = new URL(registryUrl);
|
|
2437
|
+
const registryPath = `//${hostname}${pathname.replace(/\/$/, "")}/:_authToken=`;
|
|
2438
|
+
const candidates = [path19.join(process.cwd(), ".npmrc"), path19.join(os2.homedir(), ".npmrc")];
|
|
2439
|
+
for (const npmrcPath of candidates) {
|
|
2440
|
+
try {
|
|
2441
|
+
const content = fs10.readFileSync(npmrcPath, "utf-8");
|
|
2442
|
+
for (const line of content.split("\n")) {
|
|
2443
|
+
const trimmed = line.trim();
|
|
2444
|
+
if (trimmed.startsWith(registryPath)) {
|
|
2445
|
+
return trimmed.slice(registryPath.length).trim();
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
} catch {
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
return null;
|
|
2452
|
+
}
|
|
2453
|
+
async function fetchPackageMetadata(packageName, registryUrl) {
|
|
2454
|
+
const registry = registryUrl ?? NPM_REGISTRY;
|
|
2455
|
+
const headers = {};
|
|
2456
|
+
if (registryUrl) {
|
|
2457
|
+
const token = readNpmrcToken(registryUrl);
|
|
2458
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
2459
|
+
}
|
|
2415
2460
|
const encodedName = encodeURIComponent(packageName);
|
|
2416
|
-
const url = `${
|
|
2461
|
+
const url = `${registry}/${encodedName}`;
|
|
2417
2462
|
let response;
|
|
2418
2463
|
try {
|
|
2419
2464
|
response = await fetch(url, {
|
|
2465
|
+
headers,
|
|
2420
2466
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
2421
2467
|
});
|
|
2422
2468
|
} catch {
|
|
@@ -2432,11 +2478,16 @@ async function fetchPackageMetadata(packageName) {
|
|
|
2432
2478
|
}
|
|
2433
2479
|
return await response.json();
|
|
2434
2480
|
}
|
|
2435
|
-
async function downloadTarball(tarballUrl) {
|
|
2481
|
+
async function downloadTarball(tarballUrl, authToken) {
|
|
2436
2482
|
let lastError;
|
|
2483
|
+
const headers = {};
|
|
2484
|
+
if (authToken) {
|
|
2485
|
+
headers["Authorization"] = `Bearer ${authToken}`;
|
|
2486
|
+
}
|
|
2437
2487
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
2438
2488
|
try {
|
|
2439
2489
|
const response = await fetch(tarballUrl, {
|
|
2490
|
+
headers,
|
|
2440
2491
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
2441
2492
|
});
|
|
2442
2493
|
if (!response.ok) {
|
|
@@ -2450,12 +2501,19 @@ async function downloadTarball(tarballUrl) {
|
|
|
2450
2501
|
}
|
|
2451
2502
|
throw new Error(`Download failed for ${tarballUrl}. Try again. (${lastError?.message})`);
|
|
2452
2503
|
}
|
|
2453
|
-
async function searchNpmRegistry(query) {
|
|
2504
|
+
async function searchNpmRegistry(query, registryUrl) {
|
|
2505
|
+
const registry = registryUrl ?? NPM_REGISTRY;
|
|
2506
|
+
const headers = {};
|
|
2507
|
+
if (registryUrl) {
|
|
2508
|
+
const token = readNpmrcToken(registryUrl);
|
|
2509
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
2510
|
+
}
|
|
2454
2511
|
const searchText = encodeURIComponent(`scope:harness-skills ${query}`);
|
|
2455
|
-
const url = `${
|
|
2512
|
+
const url = `${registry}/-/v1/search?text=${searchText}&size=20`;
|
|
2456
2513
|
let response;
|
|
2457
2514
|
try {
|
|
2458
2515
|
response = await fetch(url, {
|
|
2516
|
+
headers,
|
|
2459
2517
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
|
|
2460
2518
|
});
|
|
2461
2519
|
} catch {
|
|
@@ -2476,7 +2534,7 @@ async function searchNpmRegistry(query) {
|
|
|
2476
2534
|
|
|
2477
2535
|
// src/commands/skill/search.ts
|
|
2478
2536
|
async function runSearch(query, opts) {
|
|
2479
|
-
const results = await searchNpmRegistry(query);
|
|
2537
|
+
const results = await searchNpmRegistry(query, opts.registry);
|
|
2480
2538
|
return results.filter((r) => {
|
|
2481
2539
|
if (opts.platform && !r.keywords.includes(opts.platform)) {
|
|
2482
2540
|
return false;
|
|
@@ -2488,7 +2546,7 @@ async function runSearch(query, opts) {
|
|
|
2488
2546
|
});
|
|
2489
2547
|
}
|
|
2490
2548
|
function createSearchCommand() {
|
|
2491
|
-
return new Command25("search").description("Search for community skills on the @harness-skills registry").argument("<query>", "Search query").option("--platform <platform>", "Filter by platform (e.g., claude-code)").option("--trigger <trigger>", "Filter by trigger type (e.g., manual, automatic)").action(async (query, opts, cmd) => {
|
|
2549
|
+
return new Command25("search").description("Search for community skills on the @harness-skills registry").argument("<query>", "Search query").option("--platform <platform>", "Filter by platform (e.g., claude-code)").option("--trigger <trigger>", "Filter by trigger type (e.g., manual, automatic)").option("--registry <url>", "Use a custom npm registry URL").action(async (query, opts, cmd) => {
|
|
2492
2550
|
const globalOpts = cmd.optsWithGlobals();
|
|
2493
2551
|
try {
|
|
2494
2552
|
const results = await runSearch(query, opts);
|
|
@@ -2525,8 +2583,8 @@ Found ${results.length} skill(s):
|
|
|
2525
2583
|
|
|
2526
2584
|
// src/commands/skill/create.ts
|
|
2527
2585
|
import { Command as Command26 } from "commander";
|
|
2528
|
-
import * as
|
|
2529
|
-
import * as
|
|
2586
|
+
import * as path20 from "path";
|
|
2587
|
+
import * as fs11 from "fs";
|
|
2530
2588
|
import YAML from "yaml";
|
|
2531
2589
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
2532
2590
|
function buildReadme(name, description) {
|
|
@@ -2610,22 +2668,22 @@ function runCreate(name, opts) {
|
|
|
2610
2668
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
2611
2669
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
2612
2670
|
}
|
|
2613
|
-
const baseDir = opts.outputDir ??
|
|
2614
|
-
const skillDir =
|
|
2615
|
-
if (
|
|
2671
|
+
const baseDir = opts.outputDir ?? path20.join(process.cwd(), "agents", "skills", "claude-code");
|
|
2672
|
+
const skillDir = path20.join(baseDir, name);
|
|
2673
|
+
if (fs11.existsSync(skillDir)) {
|
|
2616
2674
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
2617
2675
|
}
|
|
2618
|
-
|
|
2676
|
+
fs11.mkdirSync(skillDir, { recursive: true });
|
|
2619
2677
|
const description = opts.description || `A community skill: ${name}`;
|
|
2620
2678
|
const skillYaml = buildSkillYaml(name, opts);
|
|
2621
|
-
const skillYamlPath =
|
|
2622
|
-
|
|
2679
|
+
const skillYamlPath = path20.join(skillDir, "skill.yaml");
|
|
2680
|
+
fs11.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
2623
2681
|
const skillMd = buildSkillMd(name, description);
|
|
2624
|
-
const skillMdPath =
|
|
2625
|
-
|
|
2682
|
+
const skillMdPath = path20.join(skillDir, "SKILL.md");
|
|
2683
|
+
fs11.writeFileSync(skillMdPath, skillMd);
|
|
2626
2684
|
const readme = buildReadme(name, description);
|
|
2627
|
-
const readmePath =
|
|
2628
|
-
|
|
2685
|
+
const readmePath = path20.join(skillDir, "README.md");
|
|
2686
|
+
fs11.writeFileSync(readmePath, readme);
|
|
2629
2687
|
return {
|
|
2630
2688
|
name,
|
|
2631
2689
|
directory: skillDir,
|
|
@@ -2653,7 +2711,7 @@ function createCreateCommand() {
|
|
|
2653
2711
|
logger.info(`
|
|
2654
2712
|
Next steps:`);
|
|
2655
2713
|
logger.info(
|
|
2656
|
-
` 1. Edit ${
|
|
2714
|
+
` 1. Edit ${path20.join(result.directory, "SKILL.md")} with your skill content`
|
|
2657
2715
|
);
|
|
2658
2716
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
2659
2717
|
logger.info(` 3. Run: harness skills publish`);
|
|
@@ -2667,27 +2725,28 @@ Next steps:`);
|
|
|
2667
2725
|
|
|
2668
2726
|
// src/commands/skill/publish.ts
|
|
2669
2727
|
import { Command as Command27 } from "commander";
|
|
2670
|
-
import * as
|
|
2671
|
-
import * as
|
|
2728
|
+
import * as fs14 from "fs";
|
|
2729
|
+
import * as path23 from "path";
|
|
2672
2730
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
2673
2731
|
|
|
2674
2732
|
// src/registry/validator.ts
|
|
2675
|
-
import * as
|
|
2676
|
-
import * as
|
|
2733
|
+
import * as fs13 from "fs";
|
|
2734
|
+
import * as path22 from "path";
|
|
2677
2735
|
import { parse as parse5 } from "yaml";
|
|
2678
2736
|
import semver from "semver";
|
|
2679
2737
|
|
|
2680
2738
|
// src/registry/bundled-skills.ts
|
|
2681
|
-
import * as
|
|
2739
|
+
import * as fs12 from "fs";
|
|
2740
|
+
import * as path21 from "path";
|
|
2682
2741
|
function getBundledSkillNames(bundledSkillsDir) {
|
|
2683
|
-
if (!
|
|
2742
|
+
if (!fs12.existsSync(bundledSkillsDir)) {
|
|
2684
2743
|
return /* @__PURE__ */ new Set();
|
|
2685
2744
|
}
|
|
2686
|
-
const entries =
|
|
2745
|
+
const entries = fs12.readdirSync(bundledSkillsDir);
|
|
2687
2746
|
const names = /* @__PURE__ */ new Set();
|
|
2688
2747
|
for (const entry of entries) {
|
|
2689
2748
|
try {
|
|
2690
|
-
const stat =
|
|
2749
|
+
const stat = fs12.statSync(path21.join(bundledSkillsDir, String(entry)));
|
|
2691
2750
|
if (stat.isDirectory()) {
|
|
2692
2751
|
names.add(String(entry));
|
|
2693
2752
|
}
|
|
@@ -2698,16 +2757,16 @@ function getBundledSkillNames(bundledSkillsDir) {
|
|
|
2698
2757
|
}
|
|
2699
2758
|
|
|
2700
2759
|
// src/registry/validator.ts
|
|
2701
|
-
async function validateForPublish(skillDir) {
|
|
2760
|
+
async function validateForPublish(skillDir, registryUrl) {
|
|
2702
2761
|
const errors = [];
|
|
2703
|
-
const skillYamlPath =
|
|
2704
|
-
if (!
|
|
2762
|
+
const skillYamlPath = path22.join(skillDir, "skill.yaml");
|
|
2763
|
+
if (!fs13.existsSync(skillYamlPath)) {
|
|
2705
2764
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
2706
2765
|
return { valid: false, errors };
|
|
2707
2766
|
}
|
|
2708
2767
|
let skillMeta;
|
|
2709
2768
|
try {
|
|
2710
|
-
const raw =
|
|
2769
|
+
const raw = fs13.readFileSync(skillYamlPath, "utf-8");
|
|
2711
2770
|
const parsed = parse5(raw);
|
|
2712
2771
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
2713
2772
|
if (!result.success) {
|
|
@@ -2729,11 +2788,11 @@ async function validateForPublish(skillDir) {
|
|
|
2729
2788
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
2730
2789
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
2731
2790
|
}
|
|
2732
|
-
const skillMdPath =
|
|
2733
|
-
if (!
|
|
2791
|
+
const skillMdPath = path22.join(skillDir, "SKILL.md");
|
|
2792
|
+
if (!fs13.existsSync(skillMdPath)) {
|
|
2734
2793
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
2735
2794
|
} else {
|
|
2736
|
-
const content =
|
|
2795
|
+
const content = fs13.readFileSync(skillMdPath, "utf-8");
|
|
2737
2796
|
if (!content.includes("## When to Use")) {
|
|
2738
2797
|
errors.push('SKILL.md must contain a "## When to Use" section.');
|
|
2739
2798
|
}
|
|
@@ -2750,21 +2809,25 @@ async function validateForPublish(skillDir) {
|
|
|
2750
2809
|
}
|
|
2751
2810
|
try {
|
|
2752
2811
|
const packageName = resolvePackageName(skillMeta.name);
|
|
2753
|
-
const metadata = await fetchPackageMetadata(packageName);
|
|
2812
|
+
const metadata = await fetchPackageMetadata(packageName, registryUrl);
|
|
2754
2813
|
const publishedVersion = metadata["dist-tags"]?.latest;
|
|
2755
2814
|
if (publishedVersion && !semver.gt(skillMeta.version, publishedVersion)) {
|
|
2756
2815
|
errors.push(
|
|
2757
2816
|
`Version ${skillMeta.version} must be greater than published version ${publishedVersion}. Bump the version in skill.yaml.`
|
|
2758
2817
|
);
|
|
2759
2818
|
}
|
|
2760
|
-
} catch {
|
|
2819
|
+
} catch (err) {
|
|
2820
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2821
|
+
if (!msg.includes("not found")) {
|
|
2822
|
+
errors.push(`Cannot verify version against npm registry: ${msg}`);
|
|
2823
|
+
}
|
|
2761
2824
|
}
|
|
2762
2825
|
if (skillMeta.depends_on && skillMeta.depends_on.length > 0) {
|
|
2763
2826
|
for (const dep of skillMeta.depends_on) {
|
|
2764
2827
|
if (bundledNames.has(dep)) continue;
|
|
2765
2828
|
try {
|
|
2766
2829
|
const depPkg = resolvePackageName(dep);
|
|
2767
|
-
await fetchPackageMetadata(depPkg);
|
|
2830
|
+
await fetchPackageMetadata(depPkg, registryUrl);
|
|
2768
2831
|
} catch {
|
|
2769
2832
|
errors.push(
|
|
2770
2833
|
`Dependency "${dep}" not found on npm or as a bundled skill. Publish it first or remove from depends_on.`
|
|
@@ -2801,7 +2864,7 @@ function derivePackageJson(skill) {
|
|
|
2801
2864
|
|
|
2802
2865
|
// src/commands/skill/publish.ts
|
|
2803
2866
|
async function runPublish(skillDir, opts) {
|
|
2804
|
-
const validation = await validateForPublish(skillDir);
|
|
2867
|
+
const validation = await validateForPublish(skillDir, opts.registry);
|
|
2805
2868
|
if (!validation.valid) {
|
|
2806
2869
|
const errorList = validation.errors.map((e) => ` - ${e}`).join("\n");
|
|
2807
2870
|
throw new Error(`Pre-publish validation failed:
|
|
@@ -2809,11 +2872,11 @@ ${errorList}`);
|
|
|
2809
2872
|
}
|
|
2810
2873
|
const meta = validation.skillMeta;
|
|
2811
2874
|
const pkg = derivePackageJson(meta);
|
|
2812
|
-
const pkgPath =
|
|
2813
|
-
|
|
2814
|
-
const readmePath =
|
|
2815
|
-
if (!
|
|
2816
|
-
const skillMdContent =
|
|
2875
|
+
const pkgPath = path23.join(skillDir, "package.json");
|
|
2876
|
+
fs14.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
2877
|
+
const readmePath = path23.join(skillDir, "README.md");
|
|
2878
|
+
if (!fs14.existsSync(readmePath)) {
|
|
2879
|
+
const skillMdContent = fs14.readFileSync(path23.join(skillDir, "SKILL.md"), "utf-8");
|
|
2817
2880
|
const readme = `# ${pkg.name}
|
|
2818
2881
|
|
|
2819
2882
|
${meta.description}
|
|
@@ -2827,7 +2890,7 @@ harness install ${meta.name}
|
|
|
2827
2890
|
---
|
|
2828
2891
|
|
|
2829
2892
|
${skillMdContent}`;
|
|
2830
|
-
|
|
2893
|
+
fs14.writeFileSync(readmePath, readme);
|
|
2831
2894
|
}
|
|
2832
2895
|
if (opts.dryRun) {
|
|
2833
2896
|
return {
|
|
@@ -2837,7 +2900,11 @@ ${skillMdContent}`;
|
|
|
2837
2900
|
dryRun: true
|
|
2838
2901
|
};
|
|
2839
2902
|
}
|
|
2840
|
-
|
|
2903
|
+
const publishArgs = ["publish", "--access", "public"];
|
|
2904
|
+
if (opts.registry) {
|
|
2905
|
+
publishArgs.push("--registry", opts.registry);
|
|
2906
|
+
}
|
|
2907
|
+
execFileSync3("npm", publishArgs, {
|
|
2841
2908
|
cwd: skillDir,
|
|
2842
2909
|
stdio: "pipe",
|
|
2843
2910
|
timeout: 6e4
|
|
@@ -2849,12 +2916,13 @@ ${skillMdContent}`;
|
|
|
2849
2916
|
};
|
|
2850
2917
|
}
|
|
2851
2918
|
function createPublishCommand() {
|
|
2852
|
-
return new Command27("publish").description("Validate and publish a skill to @harness-skills on npm").option("--dry-run", "Run validation and generate package.json without publishing").option("--dir <dir>", "Skill directory (default: current directory)").action(async (opts, cmd) => {
|
|
2919
|
+
return new Command27("publish").description("Validate and publish a skill to @harness-skills on npm").option("--dry-run", "Run validation and generate package.json without publishing").option("--dir <dir>", "Skill directory (default: current directory)").option("--registry <url>", "Use a custom npm registry URL").action(async (opts, cmd) => {
|
|
2853
2920
|
const globalOpts = cmd.optsWithGlobals();
|
|
2854
2921
|
const skillDir = opts.dir || process.cwd();
|
|
2855
2922
|
try {
|
|
2856
2923
|
const result = await runPublish(skillDir, {
|
|
2857
|
-
dryRun: opts.dryRun
|
|
2924
|
+
dryRun: opts.dryRun,
|
|
2925
|
+
registry: opts.registry
|
|
2858
2926
|
});
|
|
2859
2927
|
if (globalOpts.json) {
|
|
2860
2928
|
logger.raw(result);
|
|
@@ -2889,11 +2957,11 @@ import { Command as Command33 } from "commander";
|
|
|
2889
2957
|
|
|
2890
2958
|
// src/commands/state/show.ts
|
|
2891
2959
|
import { Command as Command29 } from "commander";
|
|
2892
|
-
import * as
|
|
2960
|
+
import * as path24 from "path";
|
|
2893
2961
|
function createShowCommand() {
|
|
2894
2962
|
return new Command29("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
2895
2963
|
const globalOpts = cmd.optsWithGlobals();
|
|
2896
|
-
const projectPath =
|
|
2964
|
+
const projectPath = path24.resolve(opts.path);
|
|
2897
2965
|
const result = await loadState(projectPath, opts.stream);
|
|
2898
2966
|
if (!result.ok) {
|
|
2899
2967
|
logger.error(result.error.message);
|
|
@@ -2934,12 +3002,12 @@ Decisions: ${state.decisions.length}`);
|
|
|
2934
3002
|
|
|
2935
3003
|
// src/commands/state/reset.ts
|
|
2936
3004
|
import { Command as Command30 } from "commander";
|
|
2937
|
-
import * as
|
|
2938
|
-
import * as
|
|
3005
|
+
import * as fs15 from "fs";
|
|
3006
|
+
import * as path25 from "path";
|
|
2939
3007
|
import * as readline from "readline";
|
|
2940
3008
|
function createResetCommand() {
|
|
2941
3009
|
return new Command30("reset").description("Reset project state (deletes .harness/state.json)").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").option("--yes", "Skip confirmation prompt").action(async (opts, _cmd) => {
|
|
2942
|
-
const projectPath =
|
|
3010
|
+
const projectPath = path25.resolve(opts.path);
|
|
2943
3011
|
let statePath;
|
|
2944
3012
|
if (opts.stream) {
|
|
2945
3013
|
const streamResult = await resolveStreamPath(projectPath, { stream: opts.stream });
|
|
@@ -2948,19 +3016,19 @@ function createResetCommand() {
|
|
|
2948
3016
|
process.exit(ExitCode.ERROR);
|
|
2949
3017
|
return;
|
|
2950
3018
|
}
|
|
2951
|
-
statePath =
|
|
3019
|
+
statePath = path25.join(streamResult.value, "state.json");
|
|
2952
3020
|
} else {
|
|
2953
|
-
statePath =
|
|
3021
|
+
statePath = path25.join(projectPath, ".harness", "state.json");
|
|
2954
3022
|
}
|
|
2955
|
-
if (!
|
|
3023
|
+
if (!fs15.existsSync(statePath)) {
|
|
2956
3024
|
logger.info("No state file found. Nothing to reset.");
|
|
2957
3025
|
process.exit(ExitCode.SUCCESS);
|
|
2958
3026
|
return;
|
|
2959
3027
|
}
|
|
2960
3028
|
if (!opts.yes) {
|
|
2961
3029
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2962
|
-
const answer = await new Promise((
|
|
2963
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
3030
|
+
const answer = await new Promise((resolve27) => {
|
|
3031
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve27);
|
|
2964
3032
|
});
|
|
2965
3033
|
rl.close();
|
|
2966
3034
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -2970,7 +3038,7 @@ function createResetCommand() {
|
|
|
2970
3038
|
}
|
|
2971
3039
|
}
|
|
2972
3040
|
try {
|
|
2973
|
-
|
|
3041
|
+
fs15.unlinkSync(statePath);
|
|
2974
3042
|
logger.success("Project state reset.");
|
|
2975
3043
|
} catch (e) {
|
|
2976
3044
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -2983,10 +3051,10 @@ function createResetCommand() {
|
|
|
2983
3051
|
|
|
2984
3052
|
// src/commands/state/learn.ts
|
|
2985
3053
|
import { Command as Command31 } from "commander";
|
|
2986
|
-
import * as
|
|
3054
|
+
import * as path26 from "path";
|
|
2987
3055
|
function createLearnCommand() {
|
|
2988
3056
|
return new Command31("learn").description("Append a learning to .harness/learnings.md").argument("<message>", "The learning to record").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (message, opts, _cmd) => {
|
|
2989
|
-
const projectPath =
|
|
3057
|
+
const projectPath = path26.resolve(opts.path);
|
|
2990
3058
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
2991
3059
|
if (!result.ok) {
|
|
2992
3060
|
logger.error(result.error.message);
|
|
@@ -3000,12 +3068,12 @@ function createLearnCommand() {
|
|
|
3000
3068
|
|
|
3001
3069
|
// src/commands/state/streams.ts
|
|
3002
3070
|
import { Command as Command32 } from "commander";
|
|
3003
|
-
import * as
|
|
3071
|
+
import * as path27 from "path";
|
|
3004
3072
|
function createStreamsCommand() {
|
|
3005
3073
|
const command = new Command32("streams").description("Manage state streams");
|
|
3006
3074
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => {
|
|
3007
3075
|
const globalOpts = cmd.optsWithGlobals();
|
|
3008
|
-
const projectPath =
|
|
3076
|
+
const projectPath = path27.resolve(opts.path);
|
|
3009
3077
|
const indexResult = await loadStreamIndex(projectPath);
|
|
3010
3078
|
const result = await listStreams(projectPath);
|
|
3011
3079
|
if (!result.ok) {
|
|
@@ -3029,7 +3097,7 @@ function createStreamsCommand() {
|
|
|
3029
3097
|
process.exit(ExitCode.SUCCESS);
|
|
3030
3098
|
});
|
|
3031
3099
|
command.command("create <name>").description("Create a new stream").option("--path <path>", "Project root path", ".").option("--branch <branch>", "Associate with a git branch").action(async (name, opts) => {
|
|
3032
|
-
const projectPath =
|
|
3100
|
+
const projectPath = path27.resolve(opts.path);
|
|
3033
3101
|
const result = await createStream(projectPath, name, opts.branch);
|
|
3034
3102
|
if (!result.ok) {
|
|
3035
3103
|
logger.error(result.error.message);
|
|
@@ -3040,7 +3108,7 @@ function createStreamsCommand() {
|
|
|
3040
3108
|
process.exit(ExitCode.SUCCESS);
|
|
3041
3109
|
});
|
|
3042
3110
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3043
|
-
const projectPath =
|
|
3111
|
+
const projectPath = path27.resolve(opts.path);
|
|
3044
3112
|
const result = await archiveStream(projectPath, name);
|
|
3045
3113
|
if (!result.ok) {
|
|
3046
3114
|
logger.error(result.error.message);
|
|
@@ -3051,7 +3119,7 @@ function createStreamsCommand() {
|
|
|
3051
3119
|
process.exit(ExitCode.SUCCESS);
|
|
3052
3120
|
});
|
|
3053
3121
|
command.command("activate <name>").description("Set the active stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => {
|
|
3054
|
-
const projectPath =
|
|
3122
|
+
const projectPath = path27.resolve(opts.path);
|
|
3055
3123
|
const result = await setActiveStream(projectPath, name);
|
|
3056
3124
|
if (!result.ok) {
|
|
3057
3125
|
logger.error(result.error.message);
|
|
@@ -3163,8 +3231,8 @@ function createCheckCommand() {
|
|
|
3163
3231
|
|
|
3164
3232
|
// src/commands/ci/init.ts
|
|
3165
3233
|
import { Command as Command35 } from "commander";
|
|
3166
|
-
import * as
|
|
3167
|
-
import * as
|
|
3234
|
+
import * as fs16 from "fs";
|
|
3235
|
+
import * as path28 from "path";
|
|
3168
3236
|
var ALL_CHECKS = [
|
|
3169
3237
|
"validate",
|
|
3170
3238
|
"deps",
|
|
@@ -3265,8 +3333,8 @@ function generateCIConfig(options) {
|
|
|
3265
3333
|
});
|
|
3266
3334
|
}
|
|
3267
3335
|
function detectPlatform() {
|
|
3268
|
-
if (
|
|
3269
|
-
if (
|
|
3336
|
+
if (fs16.existsSync(".github")) return "github";
|
|
3337
|
+
if (fs16.existsSync(".gitlab-ci.yml")) return "gitlab";
|
|
3270
3338
|
return null;
|
|
3271
3339
|
}
|
|
3272
3340
|
function createInitCommand2() {
|
|
@@ -3282,12 +3350,12 @@ function createInitCommand2() {
|
|
|
3282
3350
|
process.exit(result.error.exitCode);
|
|
3283
3351
|
}
|
|
3284
3352
|
const { filename, content } = result.value;
|
|
3285
|
-
const targetPath =
|
|
3286
|
-
const dir =
|
|
3287
|
-
|
|
3288
|
-
|
|
3353
|
+
const targetPath = path28.resolve(filename);
|
|
3354
|
+
const dir = path28.dirname(targetPath);
|
|
3355
|
+
fs16.mkdirSync(dir, { recursive: true });
|
|
3356
|
+
fs16.writeFileSync(targetPath, content);
|
|
3289
3357
|
if (platform === "generic" && process.platform !== "win32") {
|
|
3290
|
-
|
|
3358
|
+
fs16.chmodSync(targetPath, "755");
|
|
3291
3359
|
}
|
|
3292
3360
|
if (globalOpts.json) {
|
|
3293
3361
|
console.log(JSON.stringify({ file: filename, platform }));
|
|
@@ -3367,10 +3435,10 @@ function prompt(question) {
|
|
|
3367
3435
|
input: process.stdin,
|
|
3368
3436
|
output: process.stdout
|
|
3369
3437
|
});
|
|
3370
|
-
return new Promise((
|
|
3438
|
+
return new Promise((resolve27) => {
|
|
3371
3439
|
rl.question(question, (answer) => {
|
|
3372
3440
|
rl.close();
|
|
3373
|
-
|
|
3441
|
+
resolve27(answer.trim().toLowerCase());
|
|
3374
3442
|
});
|
|
3375
3443
|
});
|
|
3376
3444
|
}
|
|
@@ -3508,7 +3576,7 @@ function createGenerateCommand3() {
|
|
|
3508
3576
|
|
|
3509
3577
|
// src/commands/graph/scan.ts
|
|
3510
3578
|
import { Command as Command39 } from "commander";
|
|
3511
|
-
import * as
|
|
3579
|
+
import * as path29 from "path";
|
|
3512
3580
|
async function runScan(projectPath) {
|
|
3513
3581
|
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-M6BQODWC.js");
|
|
3514
3582
|
const store = new GraphStore();
|
|
@@ -3521,13 +3589,13 @@ async function runScan(projectPath) {
|
|
|
3521
3589
|
await new GitIngestor(store).ingest(projectPath);
|
|
3522
3590
|
} catch {
|
|
3523
3591
|
}
|
|
3524
|
-
const graphDir =
|
|
3592
|
+
const graphDir = path29.join(projectPath, ".harness", "graph");
|
|
3525
3593
|
await store.save(graphDir);
|
|
3526
3594
|
return { nodeCount: store.nodeCount, edgeCount: store.edgeCount, durationMs: Date.now() - start };
|
|
3527
3595
|
}
|
|
3528
3596
|
function createScanCommand() {
|
|
3529
3597
|
return new Command39("scan").description("Scan project and build knowledge graph").argument("[path]", "Project root path", ".").action(async (inputPath, _opts, cmd) => {
|
|
3530
|
-
const projectPath =
|
|
3598
|
+
const projectPath = path29.resolve(inputPath);
|
|
3531
3599
|
const globalOpts = cmd.optsWithGlobals();
|
|
3532
3600
|
try {
|
|
3533
3601
|
const result = await runScan(projectPath);
|
|
@@ -3547,12 +3615,12 @@ function createScanCommand() {
|
|
|
3547
3615
|
|
|
3548
3616
|
// src/commands/graph/ingest.ts
|
|
3549
3617
|
import { Command as Command40 } from "commander";
|
|
3550
|
-
import * as
|
|
3618
|
+
import * as path30 from "path";
|
|
3551
3619
|
async function loadConnectorConfig(projectPath, source) {
|
|
3552
3620
|
try {
|
|
3553
|
-
const
|
|
3554
|
-
const configPath =
|
|
3555
|
-
const config = JSON.parse(await
|
|
3621
|
+
const fs23 = await import("fs/promises");
|
|
3622
|
+
const configPath = path30.join(projectPath, "harness.config.json");
|
|
3623
|
+
const config = JSON.parse(await fs23.readFile(configPath, "utf-8"));
|
|
3556
3624
|
const connector = config.graph?.connectors?.find(
|
|
3557
3625
|
(c) => c.source === source
|
|
3558
3626
|
);
|
|
@@ -3592,7 +3660,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
3592
3660
|
JiraConnector,
|
|
3593
3661
|
SlackConnector
|
|
3594
3662
|
} = await import("./dist-M6BQODWC.js");
|
|
3595
|
-
const graphDir =
|
|
3663
|
+
const graphDir = path30.join(projectPath, ".harness", "graph");
|
|
3596
3664
|
const store = new GraphStore();
|
|
3597
3665
|
await store.load(graphDir);
|
|
3598
3666
|
if (opts?.all) {
|
|
@@ -3659,7 +3727,7 @@ function createIngestCommand() {
|
|
|
3659
3727
|
process.exit(1);
|
|
3660
3728
|
}
|
|
3661
3729
|
const globalOpts = cmd.optsWithGlobals();
|
|
3662
|
-
const projectPath =
|
|
3730
|
+
const projectPath = path30.resolve(globalOpts.config ? path30.dirname(globalOpts.config) : ".");
|
|
3663
3731
|
try {
|
|
3664
3732
|
const result = await runIngest(projectPath, opts.source ?? "", {
|
|
3665
3733
|
full: opts.full,
|
|
@@ -3682,11 +3750,11 @@ function createIngestCommand() {
|
|
|
3682
3750
|
|
|
3683
3751
|
// src/commands/graph/query.ts
|
|
3684
3752
|
import { Command as Command41 } from "commander";
|
|
3685
|
-
import * as
|
|
3753
|
+
import * as path31 from "path";
|
|
3686
3754
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
3687
3755
|
const { GraphStore, ContextQL } = await import("./dist-M6BQODWC.js");
|
|
3688
3756
|
const store = new GraphStore();
|
|
3689
|
-
const graphDir =
|
|
3757
|
+
const graphDir = path31.join(projectPath, ".harness", "graph");
|
|
3690
3758
|
const loaded = await store.load(graphDir);
|
|
3691
3759
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
3692
3760
|
const params = {
|
|
@@ -3702,7 +3770,7 @@ async function runQuery(projectPath, rootNodeId, opts) {
|
|
|
3702
3770
|
function createQueryCommand() {
|
|
3703
3771
|
return new Command41("query").description("Query the knowledge graph").argument("<rootNodeId>", "Starting node ID").option("--depth <n>", "Max traversal depth", "3").option("--types <types>", "Comma-separated node types to include").option("--edges <edges>", "Comma-separated edge types to include").option("--bidirectional", "Traverse both directions").action(async (rootNodeId, opts, cmd) => {
|
|
3704
3772
|
const globalOpts = cmd.optsWithGlobals();
|
|
3705
|
-
const projectPath =
|
|
3773
|
+
const projectPath = path31.resolve(globalOpts.config ? path31.dirname(globalOpts.config) : ".");
|
|
3706
3774
|
try {
|
|
3707
3775
|
const result = await runQuery(projectPath, rootNodeId, {
|
|
3708
3776
|
depth: parseInt(opts.depth),
|
|
@@ -3731,18 +3799,18 @@ function createQueryCommand() {
|
|
|
3731
3799
|
import { Command as Command42 } from "commander";
|
|
3732
3800
|
|
|
3733
3801
|
// src/commands/graph/status.ts
|
|
3734
|
-
import * as
|
|
3802
|
+
import * as path32 from "path";
|
|
3735
3803
|
async function runGraphStatus(projectPath) {
|
|
3736
3804
|
const { GraphStore } = await import("./dist-M6BQODWC.js");
|
|
3737
|
-
const graphDir =
|
|
3805
|
+
const graphDir = path32.join(projectPath, ".harness", "graph");
|
|
3738
3806
|
const store = new GraphStore();
|
|
3739
3807
|
const loaded = await store.load(graphDir);
|
|
3740
3808
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
3741
|
-
const
|
|
3742
|
-
const metaPath =
|
|
3809
|
+
const fs23 = await import("fs/promises");
|
|
3810
|
+
const metaPath = path32.join(graphDir, "metadata.json");
|
|
3743
3811
|
let lastScan = "unknown";
|
|
3744
3812
|
try {
|
|
3745
|
-
const meta = JSON.parse(await
|
|
3813
|
+
const meta = JSON.parse(await fs23.readFile(metaPath, "utf-8"));
|
|
3746
3814
|
lastScan = meta.lastScanTimestamp;
|
|
3747
3815
|
} catch {
|
|
3748
3816
|
}
|
|
@@ -3753,8 +3821,8 @@ async function runGraphStatus(projectPath) {
|
|
|
3753
3821
|
}
|
|
3754
3822
|
let connectorSyncStatus = {};
|
|
3755
3823
|
try {
|
|
3756
|
-
const syncMetaPath =
|
|
3757
|
-
const syncMeta = JSON.parse(await
|
|
3824
|
+
const syncMetaPath = path32.join(graphDir, "sync-metadata.json");
|
|
3825
|
+
const syncMeta = JSON.parse(await fs23.readFile(syncMetaPath, "utf-8"));
|
|
3758
3826
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
3759
3827
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
3760
3828
|
}
|
|
@@ -3771,10 +3839,10 @@ async function runGraphStatus(projectPath) {
|
|
|
3771
3839
|
}
|
|
3772
3840
|
|
|
3773
3841
|
// src/commands/graph/export.ts
|
|
3774
|
-
import * as
|
|
3842
|
+
import * as path33 from "path";
|
|
3775
3843
|
async function runGraphExport(projectPath, format) {
|
|
3776
3844
|
const { GraphStore } = await import("./dist-M6BQODWC.js");
|
|
3777
|
-
const graphDir =
|
|
3845
|
+
const graphDir = path33.join(projectPath, ".harness", "graph");
|
|
3778
3846
|
const store = new GraphStore();
|
|
3779
3847
|
const loaded = await store.load(graphDir);
|
|
3780
3848
|
if (!loaded) throw new Error("No graph found. Run `harness scan` first.");
|
|
@@ -3803,13 +3871,13 @@ async function runGraphExport(projectPath, format) {
|
|
|
3803
3871
|
}
|
|
3804
3872
|
|
|
3805
3873
|
// src/commands/graph/index.ts
|
|
3806
|
-
import * as
|
|
3874
|
+
import * as path34 from "path";
|
|
3807
3875
|
function createGraphCommand() {
|
|
3808
3876
|
const graph = new Command42("graph").description("Knowledge graph management");
|
|
3809
3877
|
graph.command("status").description("Show graph statistics").action(async (_opts, cmd) => {
|
|
3810
3878
|
try {
|
|
3811
3879
|
const globalOpts = cmd.optsWithGlobals();
|
|
3812
|
-
const projectPath =
|
|
3880
|
+
const projectPath = path34.resolve(globalOpts.config ? path34.dirname(globalOpts.config) : ".");
|
|
3813
3881
|
const result = await runGraphStatus(projectPath);
|
|
3814
3882
|
if (globalOpts.json) {
|
|
3815
3883
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -3836,7 +3904,7 @@ function createGraphCommand() {
|
|
|
3836
3904
|
});
|
|
3837
3905
|
graph.command("export").description("Export graph").requiredOption("--format <format>", "Output format (json, mermaid)").action(async (opts, cmd) => {
|
|
3838
3906
|
const globalOpts = cmd.optsWithGlobals();
|
|
3839
|
-
const projectPath =
|
|
3907
|
+
const projectPath = path34.resolve(globalOpts.config ? path34.dirname(globalOpts.config) : ".");
|
|
3840
3908
|
try {
|
|
3841
3909
|
const output = await runGraphExport(projectPath, opts.format);
|
|
3842
3910
|
console.log(output);
|
|
@@ -3852,7 +3920,7 @@ function createGraphCommand() {
|
|
|
3852
3920
|
import { Command as Command43 } from "commander";
|
|
3853
3921
|
function createMcpCommand() {
|
|
3854
3922
|
return new Command43("mcp").description("Start the MCP (Model Context Protocol) server on stdio").action(async () => {
|
|
3855
|
-
const { startServer: startServer2 } = await import("./mcp-
|
|
3923
|
+
const { startServer: startServer2 } = await import("./mcp-YM6QLHLZ.js");
|
|
3856
3924
|
await startServer2();
|
|
3857
3925
|
});
|
|
3858
3926
|
}
|
|
@@ -3860,8 +3928,8 @@ function createMcpCommand() {
|
|
|
3860
3928
|
// src/commands/impact-preview.ts
|
|
3861
3929
|
import { Command as Command44 } from "commander";
|
|
3862
3930
|
import { execSync as execSync3 } from "child_process";
|
|
3863
|
-
import * as
|
|
3864
|
-
import * as
|
|
3931
|
+
import * as path35 from "path";
|
|
3932
|
+
import * as fs17 from "fs";
|
|
3865
3933
|
function getStagedFiles(cwd) {
|
|
3866
3934
|
try {
|
|
3867
3935
|
const output = execSync3("git diff --cached --name-only", {
|
|
@@ -3875,7 +3943,7 @@ function getStagedFiles(cwd) {
|
|
|
3875
3943
|
}
|
|
3876
3944
|
function graphExists(projectPath) {
|
|
3877
3945
|
try {
|
|
3878
|
-
return
|
|
3946
|
+
return fs17.existsSync(path35.join(projectPath, ".harness", "graph", "graph.json"));
|
|
3879
3947
|
} catch {
|
|
3880
3948
|
return false;
|
|
3881
3949
|
}
|
|
@@ -3884,7 +3952,7 @@ function extractNodeName(id) {
|
|
|
3884
3952
|
const parts = id.split(":");
|
|
3885
3953
|
if (parts.length > 1) {
|
|
3886
3954
|
const fullPath = parts.slice(1).join(":");
|
|
3887
|
-
return
|
|
3955
|
+
return path35.basename(fullPath);
|
|
3888
3956
|
}
|
|
3889
3957
|
return id;
|
|
3890
3958
|
}
|
|
@@ -4007,7 +4075,7 @@ function formatPerFile(perFileResults) {
|
|
|
4007
4075
|
return lines.join("\n");
|
|
4008
4076
|
}
|
|
4009
4077
|
async function runImpactPreview(options) {
|
|
4010
|
-
const projectPath =
|
|
4078
|
+
const projectPath = path35.resolve(options.path ?? process.cwd());
|
|
4011
4079
|
const stagedFiles = getStagedFiles(projectPath);
|
|
4012
4080
|
if (stagedFiles.length === 0) {
|
|
4013
4081
|
return "Impact Preview: no staged changes";
|
|
@@ -4229,19 +4297,19 @@ function createCheckArchCommand() {
|
|
|
4229
4297
|
|
|
4230
4298
|
// src/commands/blueprint.ts
|
|
4231
4299
|
import { Command as Command46 } from "commander";
|
|
4232
|
-
import * as
|
|
4300
|
+
import * as path36 from "path";
|
|
4233
4301
|
function createBlueprintCommand() {
|
|
4234
4302
|
return new Command46("blueprint").description("Generate a self-contained, interactive blueprint of the codebase").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory", "docs/blueprint").action(async (projectPath, options) => {
|
|
4235
4303
|
try {
|
|
4236
|
-
const rootDir =
|
|
4237
|
-
const outputDir =
|
|
4304
|
+
const rootDir = path36.resolve(projectPath);
|
|
4305
|
+
const outputDir = path36.resolve(options.output);
|
|
4238
4306
|
logger.info(`Scanning project at ${rootDir}...`);
|
|
4239
4307
|
const scanner = new ProjectScanner(rootDir);
|
|
4240
4308
|
const data = await scanner.scan();
|
|
4241
4309
|
logger.info(`Generating blueprint to ${outputDir}...`);
|
|
4242
4310
|
const generator = new BlueprintGenerator();
|
|
4243
4311
|
await generator.generate(data, { outputDir });
|
|
4244
|
-
logger.success(`Blueprint generated successfully at ${
|
|
4312
|
+
logger.success(`Blueprint generated successfully at ${path36.join(outputDir, "index.html")}`);
|
|
4245
4313
|
} catch (error) {
|
|
4246
4314
|
logger.error(
|
|
4247
4315
|
`Failed to generate blueprint: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -4253,15 +4321,15 @@ function createBlueprintCommand() {
|
|
|
4253
4321
|
|
|
4254
4322
|
// src/commands/share.ts
|
|
4255
4323
|
import { Command as Command47 } from "commander";
|
|
4256
|
-
import * as
|
|
4257
|
-
import * as
|
|
4324
|
+
import * as fs18 from "fs";
|
|
4325
|
+
import * as path37 from "path";
|
|
4258
4326
|
import { parse as parseYaml } from "yaml";
|
|
4259
4327
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
4260
4328
|
function createShareCommand() {
|
|
4261
4329
|
return new Command47("share").description("Extract and publish a constraints bundle from constraints.yaml").argument("[path]", "Path to the project root", ".").option("-o, --output <dir>", "Output directory for the bundle", ".").action(async (projectPath, options) => {
|
|
4262
|
-
const rootDir =
|
|
4263
|
-
const manifestPath =
|
|
4264
|
-
if (!
|
|
4330
|
+
const rootDir = path37.resolve(projectPath);
|
|
4331
|
+
const manifestPath = path37.join(rootDir, MANIFEST_FILENAME);
|
|
4332
|
+
if (!fs18.existsSync(manifestPath)) {
|
|
4265
4333
|
logger.error(
|
|
4266
4334
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
4267
4335
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -4270,7 +4338,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4270
4338
|
}
|
|
4271
4339
|
let parsed;
|
|
4272
4340
|
try {
|
|
4273
|
-
const raw =
|
|
4341
|
+
const raw = fs18.readFileSync(manifestPath, "utf-8");
|
|
4274
4342
|
parsed = parseYaml(raw);
|
|
4275
4343
|
} catch (err) {
|
|
4276
4344
|
logger.error(
|
|
@@ -4284,7 +4352,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4284
4352
|
process.exit(1);
|
|
4285
4353
|
}
|
|
4286
4354
|
const manifest = manifestResult.value;
|
|
4287
|
-
const configResult = resolveConfig(
|
|
4355
|
+
const configResult = resolveConfig(path37.join(rootDir, "harness.config.json"));
|
|
4288
4356
|
if (!configResult.ok) {
|
|
4289
4357
|
logger.error(configResult.error.message);
|
|
4290
4358
|
process.exit(1);
|
|
@@ -4302,12 +4370,11 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4302
4370
|
);
|
|
4303
4371
|
process.exit(1);
|
|
4304
4372
|
}
|
|
4305
|
-
const outputDir =
|
|
4306
|
-
const outputPath =
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
logger.error(`Failed to write bundle: ${err instanceof Error ? err.message : String(err)}`);
|
|
4373
|
+
const outputDir = path37.resolve(options.output);
|
|
4374
|
+
const outputPath = path37.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
4375
|
+
const writeResult = await writeConfig(outputPath, bundle);
|
|
4376
|
+
if (!writeResult.ok) {
|
|
4377
|
+
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
4311
4378
|
process.exit(1);
|
|
4312
4379
|
}
|
|
4313
4380
|
logger.success(`Bundle written to ${outputPath}`);
|
|
@@ -4315,25 +4382,25 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
4315
4382
|
}
|
|
4316
4383
|
|
|
4317
4384
|
// src/commands/install.ts
|
|
4318
|
-
import * as
|
|
4319
|
-
import * as
|
|
4385
|
+
import * as fs20 from "fs";
|
|
4386
|
+
import * as path39 from "path";
|
|
4320
4387
|
import { Command as Command48 } from "commander";
|
|
4321
4388
|
import { parse as yamlParse } from "yaml";
|
|
4322
4389
|
|
|
4323
4390
|
// src/registry/tarball.ts
|
|
4324
|
-
import * as
|
|
4325
|
-
import * as
|
|
4326
|
-
import * as
|
|
4391
|
+
import * as fs19 from "fs";
|
|
4392
|
+
import * as path38 from "path";
|
|
4393
|
+
import * as os3 from "os";
|
|
4327
4394
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
4328
4395
|
function extractTarball(tarballBuffer) {
|
|
4329
|
-
const tmpDir =
|
|
4330
|
-
const tarballPath =
|
|
4396
|
+
const tmpDir = fs19.mkdtempSync(path38.join(os3.tmpdir(), "harness-skill-install-"));
|
|
4397
|
+
const tarballPath = path38.join(tmpDir, "package.tgz");
|
|
4331
4398
|
try {
|
|
4332
|
-
|
|
4399
|
+
fs19.writeFileSync(tarballPath, tarballBuffer);
|
|
4333
4400
|
execFileSync5("tar", ["-xzf", tarballPath, "-C", tmpDir], {
|
|
4334
4401
|
timeout: 3e4
|
|
4335
4402
|
});
|
|
4336
|
-
|
|
4403
|
+
fs19.unlinkSync(tarballPath);
|
|
4337
4404
|
} catch (err) {
|
|
4338
4405
|
cleanupTempDir(tmpDir);
|
|
4339
4406
|
throw new Error(
|
|
@@ -4344,37 +4411,37 @@ function extractTarball(tarballBuffer) {
|
|
|
4344
4411
|
return tmpDir;
|
|
4345
4412
|
}
|
|
4346
4413
|
function placeSkillContent(extractedPkgDir, communityBaseDir, skillName, platforms) {
|
|
4347
|
-
const files =
|
|
4414
|
+
const files = fs19.readdirSync(extractedPkgDir);
|
|
4348
4415
|
for (const platform of platforms) {
|
|
4349
|
-
const targetDir =
|
|
4350
|
-
if (
|
|
4351
|
-
|
|
4416
|
+
const targetDir = path38.join(communityBaseDir, platform, skillName);
|
|
4417
|
+
if (fs19.existsSync(targetDir)) {
|
|
4418
|
+
fs19.rmSync(targetDir, { recursive: true, force: true });
|
|
4352
4419
|
}
|
|
4353
|
-
|
|
4420
|
+
fs19.mkdirSync(targetDir, { recursive: true });
|
|
4354
4421
|
for (const file of files) {
|
|
4355
4422
|
if (file === "package.json" || file === "node_modules") continue;
|
|
4356
|
-
const srcPath =
|
|
4357
|
-
const destPath =
|
|
4358
|
-
const stat =
|
|
4423
|
+
const srcPath = path38.join(extractedPkgDir, file);
|
|
4424
|
+
const destPath = path38.join(targetDir, file);
|
|
4425
|
+
const stat = fs19.statSync(srcPath);
|
|
4359
4426
|
if (stat.isDirectory()) {
|
|
4360
|
-
|
|
4427
|
+
fs19.cpSync(srcPath, destPath, { recursive: true });
|
|
4361
4428
|
} else {
|
|
4362
|
-
|
|
4429
|
+
fs19.copyFileSync(srcPath, destPath);
|
|
4363
4430
|
}
|
|
4364
4431
|
}
|
|
4365
4432
|
}
|
|
4366
4433
|
}
|
|
4367
4434
|
function removeSkillContent(communityBaseDir, skillName, platforms) {
|
|
4368
4435
|
for (const platform of platforms) {
|
|
4369
|
-
const targetDir =
|
|
4370
|
-
if (
|
|
4371
|
-
|
|
4436
|
+
const targetDir = path38.join(communityBaseDir, platform, skillName);
|
|
4437
|
+
if (fs19.existsSync(targetDir)) {
|
|
4438
|
+
fs19.rmSync(targetDir, { recursive: true, force: true });
|
|
4372
4439
|
}
|
|
4373
4440
|
}
|
|
4374
4441
|
}
|
|
4375
4442
|
function cleanupTempDir(dirPath) {
|
|
4376
4443
|
try {
|
|
4377
|
-
|
|
4444
|
+
fs19.rmSync(dirPath, { recursive: true, force: true });
|
|
4378
4445
|
} catch {
|
|
4379
4446
|
}
|
|
4380
4447
|
}
|
|
@@ -4407,44 +4474,111 @@ function resolveVersion(metadata, versionRange) {
|
|
|
4407
4474
|
return metadata.versions[matched];
|
|
4408
4475
|
}
|
|
4409
4476
|
function findDependentsOf(lockfile, targetPackageName) {
|
|
4410
|
-
const
|
|
4411
|
-
|
|
4412
|
-
|
|
4477
|
+
const dependents = [];
|
|
4478
|
+
const targetEntry = lockfile.skills[targetPackageName];
|
|
4479
|
+
if (targetEntry?.dependencyOf) {
|
|
4480
|
+
dependents.push(targetEntry.dependencyOf);
|
|
4481
|
+
}
|
|
4482
|
+
return dependents;
|
|
4413
4483
|
}
|
|
4414
4484
|
|
|
4415
4485
|
// src/commands/install.ts
|
|
4416
4486
|
function validateSkillYaml(parsed) {
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
if (typeof obj["name"] !== "string" || typeof obj["version"] !== "string" || !Array.isArray(obj["platforms"])) {
|
|
4422
|
-
throw new Error("contains invalid skill.yaml");
|
|
4487
|
+
const result = SkillMetadataSchema.safeParse(parsed);
|
|
4488
|
+
if (!result.success) {
|
|
4489
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
|
|
4490
|
+
throw new Error(`contains invalid skill.yaml: ${issues}`);
|
|
4423
4491
|
}
|
|
4424
4492
|
return {
|
|
4425
|
-
name:
|
|
4426
|
-
version:
|
|
4427
|
-
platforms:
|
|
4428
|
-
depends_on:
|
|
4493
|
+
name: result.data.name,
|
|
4494
|
+
version: result.data.version,
|
|
4495
|
+
platforms: result.data.platforms,
|
|
4496
|
+
depends_on: result.data.depends_on ?? []
|
|
4429
4497
|
};
|
|
4430
4498
|
}
|
|
4499
|
+
async function runLocalInstall(fromPath, options) {
|
|
4500
|
+
const resolvedPath = path39.resolve(fromPath);
|
|
4501
|
+
if (!fs20.existsSync(resolvedPath)) {
|
|
4502
|
+
throw new Error(`--from path does not exist: ${resolvedPath}`);
|
|
4503
|
+
}
|
|
4504
|
+
const stat = fs20.statSync(resolvedPath);
|
|
4505
|
+
let extractDir = null;
|
|
4506
|
+
let pkgDir;
|
|
4507
|
+
if (stat.isDirectory()) {
|
|
4508
|
+
pkgDir = resolvedPath;
|
|
4509
|
+
} else if (resolvedPath.endsWith(".tgz") || resolvedPath.endsWith(".tar.gz")) {
|
|
4510
|
+
const tarballBuffer = fs20.readFileSync(resolvedPath);
|
|
4511
|
+
extractDir = extractTarball(tarballBuffer);
|
|
4512
|
+
pkgDir = path39.join(extractDir, "package");
|
|
4513
|
+
} else {
|
|
4514
|
+
throw new Error(`--from path must be a directory or .tgz file. Got: ${resolvedPath}`);
|
|
4515
|
+
}
|
|
4516
|
+
try {
|
|
4517
|
+
const skillYamlPath = path39.join(pkgDir, "skill.yaml");
|
|
4518
|
+
if (!fs20.existsSync(skillYamlPath)) {
|
|
4519
|
+
throw new Error(`No skill.yaml found at ${skillYamlPath}`);
|
|
4520
|
+
}
|
|
4521
|
+
const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
|
|
4522
|
+
const parsed = yamlParse(rawYaml);
|
|
4523
|
+
const skillYaml = validateSkillYaml(parsed);
|
|
4524
|
+
const shortName = skillYaml.name;
|
|
4525
|
+
const globalDir = resolveGlobalSkillsDir();
|
|
4526
|
+
const skillsDir = path39.dirname(globalDir);
|
|
4527
|
+
const communityBase = path39.join(skillsDir, "community");
|
|
4528
|
+
const lockfilePath = path39.join(communityBase, "skills-lock.json");
|
|
4529
|
+
const bundledNames = getBundledSkillNames(globalDir);
|
|
4530
|
+
if (bundledNames.has(shortName)) {
|
|
4531
|
+
throw new Error(
|
|
4532
|
+
`'${shortName}' is a bundled skill and cannot be overridden by community installs.`
|
|
4533
|
+
);
|
|
4534
|
+
}
|
|
4535
|
+
placeSkillContent(pkgDir, communityBase, shortName, skillYaml.platforms);
|
|
4536
|
+
const packageName = `@harness-skills/${shortName}`;
|
|
4537
|
+
const lockfile = readLockfile2(lockfilePath);
|
|
4538
|
+
const entry = {
|
|
4539
|
+
version: skillYaml.version,
|
|
4540
|
+
resolved: `local:${resolvedPath}`,
|
|
4541
|
+
integrity: "",
|
|
4542
|
+
platforms: skillYaml.platforms,
|
|
4543
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4544
|
+
dependencyOf: options._dependencyOf ?? null
|
|
4545
|
+
};
|
|
4546
|
+
const updatedLockfile = updateLockfileEntry(lockfile, packageName, entry);
|
|
4547
|
+
writeLockfile2(lockfilePath, updatedLockfile);
|
|
4548
|
+
return {
|
|
4549
|
+
installed: true,
|
|
4550
|
+
name: packageName,
|
|
4551
|
+
version: skillYaml.version
|
|
4552
|
+
};
|
|
4553
|
+
} finally {
|
|
4554
|
+
if (extractDir) {
|
|
4555
|
+
cleanupTempDir(extractDir);
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
}
|
|
4431
4559
|
async function runInstall(skillName, options) {
|
|
4560
|
+
if (options.from && options.registry) {
|
|
4561
|
+
throw new Error("--from and --registry cannot be used together");
|
|
4562
|
+
}
|
|
4563
|
+
if (options.from) {
|
|
4564
|
+
return runLocalInstall(options.from, options);
|
|
4565
|
+
}
|
|
4432
4566
|
const packageName = resolvePackageName(skillName);
|
|
4433
4567
|
const shortName = extractSkillName(packageName);
|
|
4434
4568
|
const globalDir = resolveGlobalSkillsDir();
|
|
4435
|
-
const skillsDir =
|
|
4436
|
-
const communityBase =
|
|
4437
|
-
const lockfilePath =
|
|
4569
|
+
const skillsDir = path39.dirname(globalDir);
|
|
4570
|
+
const communityBase = path39.join(skillsDir, "community");
|
|
4571
|
+
const lockfilePath = path39.join(communityBase, "skills-lock.json");
|
|
4438
4572
|
const bundledNames = getBundledSkillNames(globalDir);
|
|
4439
4573
|
if (bundledNames.has(shortName)) {
|
|
4440
4574
|
throw new Error(
|
|
4441
4575
|
`'${shortName}' is a bundled skill and cannot be overridden by community installs.`
|
|
4442
4576
|
);
|
|
4443
4577
|
}
|
|
4444
|
-
const metadata = await fetchPackageMetadata(packageName);
|
|
4578
|
+
const metadata = await fetchPackageMetadata(packageName, options.registry);
|
|
4445
4579
|
const versionInfo = resolveVersion(metadata, options.version);
|
|
4446
4580
|
const resolvedVersion = versionInfo.version;
|
|
4447
|
-
const lockfile =
|
|
4581
|
+
const lockfile = readLockfile2(lockfilePath);
|
|
4448
4582
|
const existingEntry = lockfile.skills[packageName];
|
|
4449
4583
|
const previousVersion = existingEntry?.version;
|
|
4450
4584
|
if (existingEntry && existingEntry.version === resolvedVersion && !options.force) {
|
|
@@ -4455,16 +4589,17 @@ async function runInstall(skillName, options) {
|
|
|
4455
4589
|
version: resolvedVersion
|
|
4456
4590
|
};
|
|
4457
4591
|
}
|
|
4458
|
-
const
|
|
4592
|
+
const authToken = options.registry ? readNpmrcToken(options.registry) ?? void 0 : void 0;
|
|
4593
|
+
const tarballBuffer = await downloadTarball(versionInfo.dist.tarball, authToken);
|
|
4459
4594
|
const extractDir = extractTarball(tarballBuffer);
|
|
4460
4595
|
let skillYaml;
|
|
4461
4596
|
try {
|
|
4462
|
-
const extractedPkgDir =
|
|
4463
|
-
const skillYamlPath =
|
|
4464
|
-
if (!
|
|
4597
|
+
const extractedPkgDir = path39.join(extractDir, "package");
|
|
4598
|
+
const skillYamlPath = path39.join(extractedPkgDir, "skill.yaml");
|
|
4599
|
+
if (!fs20.existsSync(skillYamlPath)) {
|
|
4465
4600
|
throw new Error(`contains invalid skill.yaml: file not found in package`);
|
|
4466
4601
|
}
|
|
4467
|
-
const rawYaml =
|
|
4602
|
+
const rawYaml = fs20.readFileSync(skillYamlPath, "utf-8");
|
|
4468
4603
|
const parsed = yamlParse(rawYaml);
|
|
4469
4604
|
skillYaml = validateSkillYaml(parsed);
|
|
4470
4605
|
placeSkillContent(extractedPkgDir, communityBase, shortName, skillYaml.platforms);
|
|
@@ -4479,10 +4614,10 @@ async function runInstall(skillName, options) {
|
|
|
4479
4614
|
integrity: versionInfo.dist.integrity,
|
|
4480
4615
|
platforms: skillYaml.platforms,
|
|
4481
4616
|
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4482
|
-
dependencyOf: null
|
|
4617
|
+
dependencyOf: options._dependencyOf ?? null
|
|
4483
4618
|
};
|
|
4484
4619
|
let updatedLockfile = updateLockfileEntry(lockfile, packageName, entry);
|
|
4485
|
-
|
|
4620
|
+
writeLockfile2(lockfilePath, updatedLockfile);
|
|
4486
4621
|
const result = {
|
|
4487
4622
|
installed: true,
|
|
4488
4623
|
name: packageName,
|
|
@@ -4494,14 +4629,17 @@ async function runInstall(skillName, options) {
|
|
|
4494
4629
|
}
|
|
4495
4630
|
const deps = skillYaml.depends_on ?? [];
|
|
4496
4631
|
for (const dep of deps) {
|
|
4497
|
-
logger.info(`Installing dependency: ${dep}`);
|
|
4498
|
-
await runInstall(dep, {
|
|
4632
|
+
logger.info(`Installing dependency: ${dep} (required by ${shortName})`);
|
|
4633
|
+
await runInstall(dep, {
|
|
4634
|
+
_dependencyOf: packageName,
|
|
4635
|
+
...options.registry !== void 0 ? { registry: options.registry } : {}
|
|
4636
|
+
});
|
|
4499
4637
|
}
|
|
4500
4638
|
return result;
|
|
4501
4639
|
}
|
|
4502
4640
|
function createInstallCommand() {
|
|
4503
4641
|
const cmd = new Command48("install");
|
|
4504
|
-
cmd.description("Install a community skill from the @harness-skills registry").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--version <range>", "Semver range or exact version to install").option("--force", "Force reinstall even if same version is already installed").action(async (skill, opts) => {
|
|
4642
|
+
cmd.description("Install a community skill from the @harness-skills registry").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--version <range>", "Semver range or exact version to install").option("--force", "Force reinstall even if same version is already installed").option("--from <path>", "Install from a local directory or .tgz file").option("--registry <url>", "Use a custom npm registry URL").action(async (skill, opts) => {
|
|
4505
4643
|
try {
|
|
4506
4644
|
const result = await runInstall(skill, opts);
|
|
4507
4645
|
if (result.skipped) {
|
|
@@ -4523,17 +4661,377 @@ function createInstallCommand() {
|
|
|
4523
4661
|
return cmd;
|
|
4524
4662
|
}
|
|
4525
4663
|
|
|
4526
|
-
// src/commands/
|
|
4527
|
-
import * as
|
|
4664
|
+
// src/commands/install-constraints.ts
|
|
4665
|
+
import * as fs21 from "fs/promises";
|
|
4666
|
+
import * as path40 from "path";
|
|
4528
4667
|
import { Command as Command49 } from "commander";
|
|
4668
|
+
import semver3 from "semver";
|
|
4669
|
+
async function runInstallConstraints(options) {
|
|
4670
|
+
const { source, configPath, lockfilePath } = options;
|
|
4671
|
+
let rawBundle;
|
|
4672
|
+
try {
|
|
4673
|
+
rawBundle = await fs21.readFile(source, "utf-8");
|
|
4674
|
+
} catch (err) {
|
|
4675
|
+
if (isNodeError(err) && err.code === "ENOENT") {
|
|
4676
|
+
return { ok: false, error: `Bundle file not found: ${source}` };
|
|
4677
|
+
}
|
|
4678
|
+
return {
|
|
4679
|
+
ok: false,
|
|
4680
|
+
error: `Failed to read bundle: ${err instanceof Error ? err.message : String(err)}`
|
|
4681
|
+
};
|
|
4682
|
+
}
|
|
4683
|
+
let parsedJson;
|
|
4684
|
+
try {
|
|
4685
|
+
parsedJson = JSON.parse(rawBundle);
|
|
4686
|
+
} catch {
|
|
4687
|
+
return { ok: false, error: `Bundle file contains invalid JSON: ${source}` };
|
|
4688
|
+
}
|
|
4689
|
+
const bundleResult = BundleSchema.safeParse(parsedJson);
|
|
4690
|
+
if (!bundleResult.success) {
|
|
4691
|
+
const issues = bundleResult.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
4692
|
+
return { ok: false, error: `Bundle schema validation failed: ${issues}` };
|
|
4693
|
+
}
|
|
4694
|
+
const bundle = bundleResult.data;
|
|
4695
|
+
if (bundle.minHarnessVersion) {
|
|
4696
|
+
const installed = semver3.valid(semver3.coerce(CLI_VERSION));
|
|
4697
|
+
const required = semver3.valid(semver3.coerce(bundle.minHarnessVersion));
|
|
4698
|
+
if (installed && required && semver3.lt(installed, required)) {
|
|
4699
|
+
return {
|
|
4700
|
+
ok: false,
|
|
4701
|
+
error: `Bundle requires harness version >= ${bundle.minHarnessVersion}, but installed version is ${CLI_VERSION}. Please upgrade.`
|
|
4702
|
+
};
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
const constraintKeys = Object.keys(bundle.constraints).filter(
|
|
4706
|
+
(k) => bundle.constraints[k] !== void 0
|
|
4707
|
+
);
|
|
4708
|
+
if (constraintKeys.length === 0) {
|
|
4709
|
+
return {
|
|
4710
|
+
ok: false,
|
|
4711
|
+
error: "Bundle contains no constraints. Nothing to install."
|
|
4712
|
+
};
|
|
4713
|
+
}
|
|
4714
|
+
let localConfig;
|
|
4715
|
+
try {
|
|
4716
|
+
const raw = await fs21.readFile(configPath, "utf-8");
|
|
4717
|
+
localConfig = JSON.parse(raw);
|
|
4718
|
+
} catch (err) {
|
|
4719
|
+
return {
|
|
4720
|
+
ok: false,
|
|
4721
|
+
error: `Failed to read local config at ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
4722
|
+
};
|
|
4723
|
+
}
|
|
4724
|
+
const lockfileResult = await readLockfile(lockfilePath);
|
|
4725
|
+
if (!lockfileResult.ok) {
|
|
4726
|
+
return { ok: false, error: lockfileResult.error };
|
|
4727
|
+
}
|
|
4728
|
+
const existingLockfile = lockfileResult.value ?? {
|
|
4729
|
+
version: 1,
|
|
4730
|
+
packages: {}
|
|
4731
|
+
};
|
|
4732
|
+
const existingEntry = existingLockfile.packages[bundle.name];
|
|
4733
|
+
if (existingEntry && existingEntry.version === bundle.version) {
|
|
4734
|
+
return {
|
|
4735
|
+
ok: true,
|
|
4736
|
+
value: {
|
|
4737
|
+
installed: false,
|
|
4738
|
+
packageName: bundle.name,
|
|
4739
|
+
version: bundle.version,
|
|
4740
|
+
contributionsCount: 0,
|
|
4741
|
+
conflicts: [],
|
|
4742
|
+
alreadyInstalled: true
|
|
4743
|
+
}
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
if (existingEntry) {
|
|
4747
|
+
const oldContributions = existingEntry.contributions ?? {};
|
|
4748
|
+
localConfig = removeContributions(localConfig, oldContributions);
|
|
4749
|
+
}
|
|
4750
|
+
const mergeResult = deepMergeConstraints(localConfig, bundle.constraints);
|
|
4751
|
+
if (mergeResult.conflicts.length > 0) {
|
|
4752
|
+
if (options.forceLocal) {
|
|
4753
|
+
} else if (options.forcePackage) {
|
|
4754
|
+
for (const conflict of mergeResult.conflicts) {
|
|
4755
|
+
applyPackageValue(mergeResult.config, conflict);
|
|
4756
|
+
addConflictContribution(mergeResult.contributions, conflict);
|
|
4757
|
+
}
|
|
4758
|
+
} else if (!options.dryRun) {
|
|
4759
|
+
return {
|
|
4760
|
+
ok: false,
|
|
4761
|
+
error: formatConflictsError(mergeResult.conflicts)
|
|
4762
|
+
};
|
|
4763
|
+
}
|
|
4764
|
+
}
|
|
4765
|
+
if (options.dryRun) {
|
|
4766
|
+
return {
|
|
4767
|
+
ok: true,
|
|
4768
|
+
value: {
|
|
4769
|
+
installed: false,
|
|
4770
|
+
packageName: bundle.name,
|
|
4771
|
+
version: bundle.version,
|
|
4772
|
+
contributionsCount: Object.keys(mergeResult.contributions).length,
|
|
4773
|
+
conflicts: mergeResult.conflicts,
|
|
4774
|
+
dryRun: true
|
|
4775
|
+
}
|
|
4776
|
+
};
|
|
4777
|
+
}
|
|
4778
|
+
const writeResult = await writeConfig(configPath, mergeResult.config);
|
|
4779
|
+
if (!writeResult.ok) {
|
|
4780
|
+
return {
|
|
4781
|
+
ok: false,
|
|
4782
|
+
error: `Failed to write config: ${writeResult.error instanceof Error ? writeResult.error.message : String(writeResult.error)}`
|
|
4783
|
+
};
|
|
4784
|
+
}
|
|
4785
|
+
const lockfileEntry = {
|
|
4786
|
+
version: bundle.version,
|
|
4787
|
+
source,
|
|
4788
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4789
|
+
contributions: mergeResult.contributions
|
|
4790
|
+
};
|
|
4791
|
+
const updatedLockfile = addProvenance(existingLockfile, bundle.name, lockfileEntry);
|
|
4792
|
+
const lockfileWriteResult = await writeLockfile(lockfilePath, updatedLockfile);
|
|
4793
|
+
if (!lockfileWriteResult.ok) {
|
|
4794
|
+
return {
|
|
4795
|
+
ok: false,
|
|
4796
|
+
error: `Config was written but lockfile write failed: ${lockfileWriteResult.error.message}. Lockfile may be out of sync.`
|
|
4797
|
+
};
|
|
4798
|
+
}
|
|
4799
|
+
return {
|
|
4800
|
+
ok: true,
|
|
4801
|
+
value: {
|
|
4802
|
+
installed: true,
|
|
4803
|
+
packageName: bundle.name,
|
|
4804
|
+
version: bundle.version,
|
|
4805
|
+
contributionsCount: Object.keys(mergeResult.contributions).length,
|
|
4806
|
+
conflicts: mergeResult.conflicts
|
|
4807
|
+
}
|
|
4808
|
+
};
|
|
4809
|
+
}
|
|
4810
|
+
var sectionAppliers = {
|
|
4811
|
+
layers(config, key, value) {
|
|
4812
|
+
const layers = config.layers;
|
|
4813
|
+
const idx = layers.findIndex((l) => l.name === key);
|
|
4814
|
+
if (idx >= 0) layers[idx] = value;
|
|
4815
|
+
},
|
|
4816
|
+
forbiddenImports(config, key, value) {
|
|
4817
|
+
const rules = config.forbiddenImports;
|
|
4818
|
+
const idx = rules.findIndex((r) => r.from === key);
|
|
4819
|
+
if (idx >= 0) rules[idx] = value;
|
|
4820
|
+
},
|
|
4821
|
+
"architecture.thresholds"(config, key, value) {
|
|
4822
|
+
const arch = config.architecture;
|
|
4823
|
+
if (arch?.thresholds) arch.thresholds[key] = value;
|
|
4824
|
+
},
|
|
4825
|
+
"architecture.modules"(config, key, value) {
|
|
4826
|
+
const arch = config.architecture;
|
|
4827
|
+
const [modulePath, category] = key.split(":");
|
|
4828
|
+
if (arch?.modules && modulePath && category && arch.modules[modulePath]) {
|
|
4829
|
+
arch.modules[modulePath][category] = value;
|
|
4830
|
+
}
|
|
4831
|
+
},
|
|
4832
|
+
"security.rules"(config, key, value) {
|
|
4833
|
+
const security = config.security;
|
|
4834
|
+
if (security?.rules) security.rules[key] = value;
|
|
4835
|
+
}
|
|
4836
|
+
};
|
|
4837
|
+
function applyPackageValue(config, conflict) {
|
|
4838
|
+
const applier = sectionAppliers[conflict.section];
|
|
4839
|
+
if (applier) applier(config, conflict.key, conflict.packageValue);
|
|
4840
|
+
}
|
|
4841
|
+
function addConflictContribution(contributions, conflict) {
|
|
4842
|
+
const section = conflict.section;
|
|
4843
|
+
const existing = contributions[section] ?? [];
|
|
4844
|
+
existing.push(conflict.key);
|
|
4845
|
+
contributions[section] = existing;
|
|
4846
|
+
}
|
|
4847
|
+
function formatConflictsError(conflicts) {
|
|
4848
|
+
const lines = [
|
|
4849
|
+
`${conflicts.length} conflict(s) detected. Resolve with --force-local or --force-package:`,
|
|
4850
|
+
""
|
|
4851
|
+
];
|
|
4852
|
+
for (const c of conflicts) {
|
|
4853
|
+
lines.push(` [${c.section}] ${c.key}: ${c.description}`);
|
|
4854
|
+
lines.push(` Local: ${JSON.stringify(c.localValue)}`);
|
|
4855
|
+
lines.push(` Package: ${JSON.stringify(c.packageValue)}`);
|
|
4856
|
+
lines.push("");
|
|
4857
|
+
}
|
|
4858
|
+
return lines.join("\n");
|
|
4859
|
+
}
|
|
4860
|
+
function isNodeError(err) {
|
|
4861
|
+
return err instanceof Error && "code" in err;
|
|
4862
|
+
}
|
|
4863
|
+
function resolveConfigPath(opts) {
|
|
4864
|
+
if (opts.config) return path40.resolve(opts.config);
|
|
4865
|
+
const found = findConfigFile();
|
|
4866
|
+
if (!found.ok) {
|
|
4867
|
+
logger.error(found.error.message);
|
|
4868
|
+
process.exit(1);
|
|
4869
|
+
}
|
|
4870
|
+
return found.value;
|
|
4871
|
+
}
|
|
4872
|
+
function logInstallResult(val, opts) {
|
|
4873
|
+
if (val.dryRun) {
|
|
4874
|
+
logger.info(`[dry-run] Would install ${val.packageName}@${val.version}`);
|
|
4875
|
+
logger.info(`[dry-run] ${val.contributionsCount} section(s) would be added`);
|
|
4876
|
+
if (val.conflicts.length > 0) {
|
|
4877
|
+
logger.warn(`[dry-run] ${val.conflicts.length} conflict(s) detected`);
|
|
4878
|
+
for (const c of val.conflicts) {
|
|
4879
|
+
logger.warn(` [${c.section}] ${c.key}: ${c.description}`);
|
|
4880
|
+
}
|
|
4881
|
+
}
|
|
4882
|
+
return;
|
|
4883
|
+
}
|
|
4884
|
+
if (val.alreadyInstalled) {
|
|
4885
|
+
logger.info(`${val.packageName}@${val.version} is already installed. No changes made.`);
|
|
4886
|
+
return;
|
|
4887
|
+
}
|
|
4888
|
+
logger.success(
|
|
4889
|
+
`Installed ${val.packageName}@${val.version} (${val.contributionsCount} section(s) merged)`
|
|
4890
|
+
);
|
|
4891
|
+
if (val.conflicts.length > 0) {
|
|
4892
|
+
logger.warn(
|
|
4893
|
+
`${val.conflicts.length} conflict(s) resolved with ${opts.forceLocal ? "--force-local" : "--force-package"}`
|
|
4894
|
+
);
|
|
4895
|
+
}
|
|
4896
|
+
}
|
|
4897
|
+
async function handleInstallConstraints(source, opts) {
|
|
4898
|
+
const configPath = resolveConfigPath(opts);
|
|
4899
|
+
const projectRoot = path40.dirname(configPath);
|
|
4900
|
+
const lockfilePath = path40.join(projectRoot, ".harness", "constraints.lock.json");
|
|
4901
|
+
const resolvedSource = path40.resolve(source);
|
|
4902
|
+
if (opts.forceLocal && opts.forcePackage) {
|
|
4903
|
+
logger.error("Cannot use both --force-local and --force-package.");
|
|
4904
|
+
process.exit(1);
|
|
4905
|
+
}
|
|
4906
|
+
const result = await runInstallConstraints({
|
|
4907
|
+
source: resolvedSource,
|
|
4908
|
+
configPath,
|
|
4909
|
+
lockfilePath,
|
|
4910
|
+
...opts.forceLocal && { forceLocal: true },
|
|
4911
|
+
...opts.forcePackage && { forcePackage: true },
|
|
4912
|
+
...opts.dryRun && { dryRun: true }
|
|
4913
|
+
});
|
|
4914
|
+
if (!result.ok) {
|
|
4915
|
+
logger.error(result.error);
|
|
4916
|
+
process.exit(1);
|
|
4917
|
+
}
|
|
4918
|
+
logInstallResult(result.value, opts);
|
|
4919
|
+
}
|
|
4920
|
+
function createInstallConstraintsCommand() {
|
|
4921
|
+
const cmd = new Command49("install-constraints");
|
|
4922
|
+
cmd.description("Install a constraints bundle into the local harness config").argument("<source>", "Path to a .harness-constraints.json bundle file").option("--force-local", "Resolve all conflicts by keeping local values").option("--force-package", "Resolve all conflicts by using package values").option("--dry-run", "Show what would change without writing files").option("-c, --config <path>", "Path to harness.config.json").action(handleInstallConstraints);
|
|
4923
|
+
return cmd;
|
|
4924
|
+
}
|
|
4925
|
+
|
|
4926
|
+
// src/commands/uninstall-constraints.ts
|
|
4927
|
+
import * as fs22 from "fs/promises";
|
|
4928
|
+
import * as path41 from "path";
|
|
4929
|
+
import { Command as Command50 } from "commander";
|
|
4930
|
+
async function runUninstallConstraints(options) {
|
|
4931
|
+
const { packageName, configPath, lockfilePath } = options;
|
|
4932
|
+
const lockfileResult = await readLockfile(lockfilePath);
|
|
4933
|
+
if (!lockfileResult.ok) {
|
|
4934
|
+
return { ok: false, error: lockfileResult.error };
|
|
4935
|
+
}
|
|
4936
|
+
if (lockfileResult.value === null) {
|
|
4937
|
+
return { ok: false, error: "No lockfile found. No constraint packages are installed." };
|
|
4938
|
+
}
|
|
4939
|
+
const lockfile = lockfileResult.value;
|
|
4940
|
+
const entry = lockfile.packages[packageName];
|
|
4941
|
+
if (!entry) {
|
|
4942
|
+
return {
|
|
4943
|
+
ok: false,
|
|
4944
|
+
error: `Package '${packageName}' is not installed.`
|
|
4945
|
+
};
|
|
4946
|
+
}
|
|
4947
|
+
let localConfig;
|
|
4948
|
+
try {
|
|
4949
|
+
const raw = await fs22.readFile(configPath, "utf-8");
|
|
4950
|
+
localConfig = JSON.parse(raw);
|
|
4951
|
+
} catch (err) {
|
|
4952
|
+
return {
|
|
4953
|
+
ok: false,
|
|
4954
|
+
error: `Failed to read local config at ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
4955
|
+
};
|
|
4956
|
+
}
|
|
4957
|
+
const contributions = entry.contributions ?? {};
|
|
4958
|
+
const sectionsRemoved = Object.keys(contributions);
|
|
4959
|
+
const updatedConfig = removeContributions(localConfig, contributions);
|
|
4960
|
+
const { lockfile: updatedLockfile } = removeProvenance(lockfile, packageName);
|
|
4961
|
+
const writeResult = await writeConfig(configPath, updatedConfig);
|
|
4962
|
+
if (!writeResult.ok) {
|
|
4963
|
+
return {
|
|
4964
|
+
ok: false,
|
|
4965
|
+
error: `Failed to write config: ${writeResult.error instanceof Error ? writeResult.error.message : String(writeResult.error)}`
|
|
4966
|
+
};
|
|
4967
|
+
}
|
|
4968
|
+
const lockfileWriteResult = await writeLockfile(lockfilePath, updatedLockfile);
|
|
4969
|
+
if (!lockfileWriteResult.ok) {
|
|
4970
|
+
return {
|
|
4971
|
+
ok: false,
|
|
4972
|
+
error: `Config was written but lockfile write failed: ${lockfileWriteResult.error.message}. Lockfile may be out of sync.`
|
|
4973
|
+
};
|
|
4974
|
+
}
|
|
4975
|
+
return {
|
|
4976
|
+
ok: true,
|
|
4977
|
+
value: {
|
|
4978
|
+
removed: true,
|
|
4979
|
+
packageName,
|
|
4980
|
+
version: entry.version,
|
|
4981
|
+
sectionsRemoved
|
|
4982
|
+
}
|
|
4983
|
+
};
|
|
4984
|
+
}
|
|
4985
|
+
function createUninstallConstraintsCommand() {
|
|
4986
|
+
const cmd = new Command50("uninstall-constraints");
|
|
4987
|
+
cmd.description("Remove a previously installed constraints package").argument("<name>", "Name of the constraint package to uninstall").option("-c, --config <path>", "Path to harness.config.json").action(async (name, opts) => {
|
|
4988
|
+
let configPath;
|
|
4989
|
+
if (opts.config) {
|
|
4990
|
+
configPath = path41.resolve(opts.config);
|
|
4991
|
+
} else {
|
|
4992
|
+
const found = findConfigFile();
|
|
4993
|
+
if (!found.ok) {
|
|
4994
|
+
logger.error(found.error.message);
|
|
4995
|
+
process.exit(1);
|
|
4996
|
+
}
|
|
4997
|
+
configPath = found.value;
|
|
4998
|
+
}
|
|
4999
|
+
const projectRoot = path41.dirname(configPath);
|
|
5000
|
+
const lockfilePath = path41.join(projectRoot, ".harness", "constraints.lock.json");
|
|
5001
|
+
const result = await runUninstallConstraints({
|
|
5002
|
+
packageName: name,
|
|
5003
|
+
configPath,
|
|
5004
|
+
lockfilePath
|
|
5005
|
+
});
|
|
5006
|
+
if (!result.ok) {
|
|
5007
|
+
logger.error(result.error);
|
|
5008
|
+
process.exit(1);
|
|
5009
|
+
}
|
|
5010
|
+
const val = result.value;
|
|
5011
|
+
if (val.sectionsRemoved.length === 0) {
|
|
5012
|
+
logger.success(
|
|
5013
|
+
`Removed ${val.packageName}@${val.version} (no contributed rules to remove)`
|
|
5014
|
+
);
|
|
5015
|
+
} else {
|
|
5016
|
+
logger.success(
|
|
5017
|
+
`Removed ${val.packageName}@${val.version} (${val.sectionsRemoved.length} section(s): ${val.sectionsRemoved.join(", ")})`
|
|
5018
|
+
);
|
|
5019
|
+
}
|
|
5020
|
+
});
|
|
5021
|
+
return cmd;
|
|
5022
|
+
}
|
|
5023
|
+
|
|
5024
|
+
// src/commands/uninstall.ts
|
|
5025
|
+
import * as path42 from "path";
|
|
5026
|
+
import { Command as Command51 } from "commander";
|
|
4529
5027
|
async function runUninstall(skillName, options) {
|
|
4530
5028
|
const packageName = resolvePackageName(skillName);
|
|
4531
5029
|
const shortName = extractSkillName(packageName);
|
|
4532
5030
|
const globalDir = resolveGlobalSkillsDir();
|
|
4533
|
-
const skillsDir =
|
|
4534
|
-
const communityBase =
|
|
4535
|
-
const lockfilePath =
|
|
4536
|
-
const lockfile =
|
|
5031
|
+
const skillsDir = path42.dirname(globalDir);
|
|
5032
|
+
const communityBase = path42.join(skillsDir, "community");
|
|
5033
|
+
const lockfilePath = path42.join(communityBase, "skills-lock.json");
|
|
5034
|
+
const lockfile = readLockfile2(lockfilePath);
|
|
4537
5035
|
const entry = lockfile.skills[packageName];
|
|
4538
5036
|
if (!entry) {
|
|
4539
5037
|
throw new Error(`Skill '${shortName}' is not installed.`);
|
|
@@ -4550,7 +5048,7 @@ async function runUninstall(skillName, options) {
|
|
|
4550
5048
|
}
|
|
4551
5049
|
removeSkillContent(communityBase, shortName, entry.platforms);
|
|
4552
5050
|
const updatedLockfile = removeLockfileEntry(lockfile, packageName);
|
|
4553
|
-
|
|
5051
|
+
writeLockfile2(lockfilePath, updatedLockfile);
|
|
4554
5052
|
const result = {
|
|
4555
5053
|
removed: true,
|
|
4556
5054
|
name: packageName,
|
|
@@ -4562,7 +5060,7 @@ async function runUninstall(skillName, options) {
|
|
|
4562
5060
|
return result;
|
|
4563
5061
|
}
|
|
4564
5062
|
function createUninstallCommand() {
|
|
4565
|
-
const cmd = new
|
|
5063
|
+
const cmd = new Command51("uninstall");
|
|
4566
5064
|
cmd.description("Uninstall a community skill").argument("<skill>", "Skill name or @harness-skills/scoped package name").option("--force", "Remove even if other skills depend on this one").action(async (skill, opts) => {
|
|
4567
5065
|
try {
|
|
4568
5066
|
const result = await runUninstall(skill, opts);
|
|
@@ -4581,13 +5079,13 @@ function createUninstallCommand() {
|
|
|
4581
5079
|
}
|
|
4582
5080
|
|
|
4583
5081
|
// src/commands/orchestrator.ts
|
|
4584
|
-
import { Command as
|
|
4585
|
-
import * as
|
|
5082
|
+
import { Command as Command52 } from "commander";
|
|
5083
|
+
import * as path43 from "path";
|
|
4586
5084
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
4587
5085
|
function createOrchestratorCommand() {
|
|
4588
|
-
const orchestrator = new
|
|
5086
|
+
const orchestrator = new Command52("orchestrator");
|
|
4589
5087
|
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to WORKFLOW.md", "WORKFLOW.md").action(async (opts) => {
|
|
4590
|
-
const workflowPath =
|
|
5088
|
+
const workflowPath = path43.resolve(process.cwd(), opts.workflow);
|
|
4591
5089
|
const loader = new WorkflowLoader();
|
|
4592
5090
|
const result = await loader.loadWorkflow(workflowPath);
|
|
4593
5091
|
if (!result.ok) {
|
|
@@ -4612,7 +5110,7 @@ function createOrchestratorCommand() {
|
|
|
4612
5110
|
|
|
4613
5111
|
// src/index.ts
|
|
4614
5112
|
function createProgram() {
|
|
4615
|
-
const program = new
|
|
5113
|
+
const program = new Command53();
|
|
4616
5114
|
program.name("harness").description("CLI for Harness Engineering toolkit").version(CLI_VERSION).option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--verbose", "Verbose output").option("--quiet", "Minimal output");
|
|
4617
5115
|
program.addCommand(createValidateCommand());
|
|
4618
5116
|
program.addCommand(createCheckDepsCommand());
|
|
@@ -4647,6 +5145,8 @@ function createProgram() {
|
|
|
4647
5145
|
program.addCommand(createBlueprintCommand());
|
|
4648
5146
|
program.addCommand(createShareCommand());
|
|
4649
5147
|
program.addCommand(createInstallCommand());
|
|
5148
|
+
program.addCommand(createInstallConstraintsCommand());
|
|
5149
|
+
program.addCommand(createUninstallConstraintsCommand());
|
|
4650
5150
|
program.addCommand(createUninstallCommand());
|
|
4651
5151
|
program.addCommand(createOrchestratorCommand());
|
|
4652
5152
|
return program;
|
|
@@ -4662,6 +5162,8 @@ export {
|
|
|
4662
5162
|
runImpactPreview,
|
|
4663
5163
|
runCheckArch,
|
|
4664
5164
|
runInstall,
|
|
5165
|
+
runInstallConstraints,
|
|
5166
|
+
runUninstallConstraints,
|
|
4665
5167
|
runUninstall,
|
|
4666
5168
|
createProgram
|
|
4667
5169
|
};
|