@harness-engineering/cli 2.2.0 → 2.3.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/skills/claude-code/harness-roadmap-pilot/SKILL.md +13 -7
- package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +13 -7
- package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +13 -7
- package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +13 -7
- package/dist/{agents-md-L4X4MOWT.js → agents-md-N7YBQPE3.js} +2 -2
- package/dist/{architecture-WJRIYQ45.js → architecture-PP7WERSV.js} +3 -3
- package/dist/{assess-project-PHC2HO3V.js → assess-project-WXFCJQTX.js} +1 -1
- package/dist/bin/harness-mcp.js +13 -13
- package/dist/bin/harness.js +22 -22
- package/dist/{check-phase-gate-646KY67H.js → check-phase-gate-R3KQFBAH.js} +3 -3
- package/dist/{chunk-GOOTYTTV.js → chunk-35FI23SO.js} +855 -428
- package/dist/{chunk-L2IDE7WS.js → chunk-3LPOTTPX.js} +255 -89
- package/dist/{chunk-K4WRQTEF.js → chunk-4DWOMGUM.js} +1661 -434
- package/dist/{chunk-W4AFGFFB.js → chunk-4NFV5UZO.js} +2 -2
- package/dist/{chunk-224XKVLP.js → chunk-5KTTKXQN.js} +1 -1
- package/dist/{chunk-KV7A5OZT.js → chunk-5PPV4WYY.js} +3 -3
- package/dist/{chunk-CPYL6LJR.js → chunk-6CHNY3NJ.js} +1 -1
- package/dist/{chunk-VKDBM3RX.js → chunk-7PGYJO3L.js} +3 -3
- package/dist/{chunk-E7W3YOXL.js → chunk-D2LZUJLP.js} +2 -2
- package/dist/{chunk-4HKVZO7R.js → chunk-DLBMYLYR.js} +1 -1
- package/dist/{chunk-YWD6WXXM.js → chunk-EJPR5VCT.js} +6 -6
- package/dist/{chunk-2ZURBWEV.js → chunk-GTXUUGFG.js} +3 -3
- package/dist/{chunk-OJMO4JBQ.js → chunk-GUIX2TMZ.js} +1 -1
- package/dist/{chunk-TNZYQTEE.js → chunk-KUVPSUG4.js} +1 -1
- package/dist/{chunk-YLTRFZAT.js → chunk-KWD6FF7M.js} +6 -6
- package/dist/{chunk-VJQLUS5I.js → chunk-L3CEN42B.js} +1 -1
- package/dist/{chunk-YLOAQZLA.js → chunk-LM4F5KBK.js} +2 -2
- package/dist/{chunk-JPWICLXQ.js → chunk-MKPRB4PL.js} +1 -1
- package/dist/{chunk-2QPHCR22.js → chunk-MT2MMBUA.js} +5 -5
- package/dist/{chunk-QTUPHTTK.js → chunk-NP2ZI6E3.js} +1 -1
- package/dist/{chunk-LO7ZLV7S.js → chunk-PW3U4YJV.js} +4 -2
- package/dist/{chunk-36X7TRUI.js → chunk-YEBUFRTT.js} +2 -2
- package/dist/{ci-workflow-TOS5X527.js → ci-workflow-GFCXFJ4G.js} +2 -2
- package/dist/{dist-6RT2AVYU.js → dist-I4LWG3IN.js} +23 -1
- package/dist/{docs-HOJMQYDZ.js → docs-UXKDKPJF.js} +3 -3
- package/dist/{engine-D6UYH6DB.js → engine-A63FRDLF.js} +2 -2
- package/dist/{entropy-LZ4IDGZR.js → entropy-IFIP4ZP4.js} +2 -2
- package/dist/{feedback-WV7EQVK7.js → feedback-W36DSJQA.js} +1 -1
- package/dist/{generate-agent-definitions-GFNXJCBY.js → generate-agent-definitions-F4YMKQLP.js} +3 -3
- package/dist/hooks/block-no-verify.js +19 -1
- package/dist/index.d.ts +15 -9
- package/dist/index.js +22 -22
- package/dist/{loader-Z23JTXIZ.js → loader-4UAOGUSS.js} +2 -2
- package/dist/{mcp-JDJGELGD.js → mcp-YW7PLXVQ.js} +13 -13
- package/dist/{performance-7ATLHTQU.js → performance-AZ5UYPOQ.js} +3 -3
- package/dist/{review-pipeline-COBGGXHJ.js → review-pipeline-OLQVGBWJ.js} +2 -2
- package/dist/{runtime-KCBNQUXK.js → runtime-GG3U5HH5.js} +2 -2
- package/dist/{security-MAPRIFQT.js → security-MYTRFSR7.js} +1 -1
- package/dist/{validate-74XTQR7Q.js → validate-QI4V4JY6.js} +3 -3
- package/dist/{validate-cross-check-MI5MZ7MB.js → validate-cross-check-XRSSAXJP.js} +2 -2
- package/package.json +4 -4
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generateRuntime
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-KUVPSUG4.js";
|
|
4
4
|
import {
|
|
5
5
|
generateAgentsMd
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DLBMYLYR.js";
|
|
7
7
|
import {
|
|
8
8
|
generateCIWorkflow
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-MKPRB4PL.js";
|
|
10
10
|
import {
|
|
11
11
|
TemplateEngine
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-5KTTKXQN.js";
|
|
13
13
|
import {
|
|
14
14
|
generate,
|
|
15
15
|
validate
|
|
@@ -28,18 +28,18 @@ import {
|
|
|
28
28
|
OutputMode,
|
|
29
29
|
createCheckPhaseGateCommand,
|
|
30
30
|
findFiles
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-D2LZUJLP.js";
|
|
32
32
|
import {
|
|
33
33
|
createGenerateAgentDefinitionsCommand,
|
|
34
34
|
generateAgentDefinitions
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-GUIX2TMZ.js";
|
|
36
36
|
import {
|
|
37
37
|
toKebabCase
|
|
38
38
|
} from "./chunk-KET4QQZB.js";
|
|
39
39
|
import {
|
|
40
40
|
listPersonas,
|
|
41
41
|
loadPersona
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-L3CEN42B.js";
|
|
43
43
|
import {
|
|
44
44
|
createScanCommand,
|
|
45
45
|
loadIngestOptions
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
loadCachedSnapshot,
|
|
58
58
|
persistToolingConfig,
|
|
59
59
|
recommend
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-3LPOTTPX.js";
|
|
61
61
|
import {
|
|
62
62
|
loadGraphStore
|
|
63
63
|
} from "./chunk-KTWKW6KJ.js";
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
import {
|
|
68
68
|
findConfigFile,
|
|
69
69
|
resolveConfig
|
|
70
|
-
} from "./chunk-
|
|
70
|
+
} from "./chunk-PW3U4YJV.js";
|
|
71
71
|
import {
|
|
72
72
|
VALID_PLATFORMS
|
|
73
73
|
} from "./chunk-3ISINLYT.js";
|
|
@@ -115,6 +115,7 @@ import {
|
|
|
115
115
|
computeWindow,
|
|
116
116
|
createFixes,
|
|
117
117
|
createStream,
|
|
118
|
+
createTrackerClient,
|
|
118
119
|
crossReferenceUndocumentedFixes,
|
|
119
120
|
deepMergeConstraints,
|
|
120
121
|
defineLayer,
|
|
@@ -133,14 +134,18 @@ import {
|
|
|
133
134
|
isoWeek,
|
|
134
135
|
listStreams,
|
|
135
136
|
listTaintedSessions,
|
|
137
|
+
loadProjectRoadmapMode,
|
|
136
138
|
loadState,
|
|
137
139
|
loadStreamIndex,
|
|
140
|
+
loadTrackerClientConfigFromProject,
|
|
138
141
|
loadTrackerSyncConfig,
|
|
139
142
|
mapInjectionFindings,
|
|
140
143
|
mapSecurityFindings,
|
|
144
|
+
migrate_exports,
|
|
141
145
|
parseDiff,
|
|
142
146
|
parseFileRegions,
|
|
143
147
|
parseManifest,
|
|
148
|
+
parseRoadmap,
|
|
144
149
|
parseSecurityConfig,
|
|
145
150
|
pruneLearnings,
|
|
146
151
|
readIdentity,
|
|
@@ -161,10 +166,11 @@ import {
|
|
|
161
166
|
validateDependencies,
|
|
162
167
|
validateKnowledgeMap,
|
|
163
168
|
validatePulseConfig,
|
|
169
|
+
validateRoadmapMode,
|
|
164
170
|
validateSolutionsDir,
|
|
165
171
|
writeConfig,
|
|
166
172
|
writeLockfile
|
|
167
|
-
} from "./chunk-
|
|
173
|
+
} from "./chunk-4DWOMGUM.js";
|
|
168
174
|
import {
|
|
169
175
|
Err,
|
|
170
176
|
Ok
|
|
@@ -189,7 +195,7 @@ import {
|
|
|
189
195
|
} from "./chunk-KFQGP6VL.js";
|
|
190
196
|
|
|
191
197
|
// src/index.ts
|
|
192
|
-
import { Command as
|
|
198
|
+
import { Command as Command93 } from "commander";
|
|
193
199
|
|
|
194
200
|
// src/commands/add.ts
|
|
195
201
|
init_dist();
|
|
@@ -378,7 +384,7 @@ function registerSkillsCommand(adoption) {
|
|
|
378
384
|
const globalOpts = cmd.optsWithGlobals();
|
|
379
385
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
380
386
|
const cwd = process.cwd();
|
|
381
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
387
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-I4LWG3IN.js");
|
|
382
388
|
const records = readAdoptionRecords(cwd);
|
|
383
389
|
if (records.length === 0) {
|
|
384
390
|
if (globalOpts.json) {
|
|
@@ -414,7 +420,7 @@ function registerRecentCommand(adoption) {
|
|
|
414
420
|
const globalOpts = cmd.optsWithGlobals();
|
|
415
421
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
416
422
|
const cwd = process.cwd();
|
|
417
|
-
const { readAdoptionRecords } = await import("./dist-
|
|
423
|
+
const { readAdoptionRecords } = await import("./dist-I4LWG3IN.js");
|
|
418
424
|
const records = readAdoptionRecords(cwd);
|
|
419
425
|
if (records.length === 0) {
|
|
420
426
|
if (globalOpts.json) {
|
|
@@ -492,7 +498,7 @@ function registerSkillCommand(adoption) {
|
|
|
492
498
|
adoption.command("skill <name>").description("Show detail for a specific skill").action(async (name, _opts, cmd) => {
|
|
493
499
|
const globalOpts = cmd.optsWithGlobals();
|
|
494
500
|
const cwd = process.cwd();
|
|
495
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
501
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-I4LWG3IN.js");
|
|
496
502
|
const records = readAdoptionRecords(cwd);
|
|
497
503
|
const skillRecords = records.filter((r) => r.skill === name);
|
|
498
504
|
if (skillRecords.length === 0) {
|
|
@@ -1436,10 +1442,10 @@ async function runCheckSecurity(cwd, options) {
|
|
|
1436
1442
|
const projectRoot = path8.resolve(cwd);
|
|
1437
1443
|
let configData = {};
|
|
1438
1444
|
try {
|
|
1439
|
-
const
|
|
1445
|
+
const fs44 = await import("fs");
|
|
1440
1446
|
const configPath = path8.join(projectRoot, "harness.config.json");
|
|
1441
|
-
if (
|
|
1442
|
-
const raw =
|
|
1447
|
+
if (fs44.existsSync(configPath)) {
|
|
1448
|
+
const raw = fs44.readFileSync(configPath, "utf-8");
|
|
1443
1449
|
const parsed = JSON.parse(raw);
|
|
1444
1450
|
configData = parsed.security ?? {};
|
|
1445
1451
|
}
|
|
@@ -2888,11 +2894,11 @@ import { Command as Command27 } from "commander";
|
|
|
2888
2894
|
// src/commands/graph/status.ts
|
|
2889
2895
|
import * as path15 from "path";
|
|
2890
2896
|
async function readConnectorSyncStatus(graphDir) {
|
|
2891
|
-
const
|
|
2897
|
+
const fs44 = await import("fs/promises");
|
|
2892
2898
|
const result = {};
|
|
2893
2899
|
try {
|
|
2894
2900
|
const syncMetaPath = path15.join(graphDir, "sync-metadata.json");
|
|
2895
|
-
const syncMeta = JSON.parse(await
|
|
2901
|
+
const syncMeta = JSON.parse(await fs44.readFile(syncMetaPath, "utf-8"));
|
|
2896
2902
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
2897
2903
|
result[name] = data.lastSyncTimestamp;
|
|
2898
2904
|
}
|
|
@@ -3042,9 +3048,9 @@ import { Command as Command26 } from "commander";
|
|
|
3042
3048
|
import * as path18 from "path";
|
|
3043
3049
|
async function loadConnectorConfig(projectPath, source) {
|
|
3044
3050
|
try {
|
|
3045
|
-
const
|
|
3051
|
+
const fs44 = await import("fs/promises");
|
|
3046
3052
|
const configPath = path18.join(projectPath, "harness.config.json");
|
|
3047
|
-
const config = JSON.parse(await
|
|
3053
|
+
const config = JSON.parse(await fs44.readFile(configPath, "utf-8"));
|
|
3048
3054
|
const connector = config.graph?.connectors?.[source];
|
|
3049
3055
|
return connector ?? {};
|
|
3050
3056
|
} catch {
|
|
@@ -4372,8 +4378,8 @@ function extractSkillName(packageName) {
|
|
|
4372
4378
|
return packageName;
|
|
4373
4379
|
}
|
|
4374
4380
|
function readNpmrcToken(registryUrl) {
|
|
4375
|
-
const { hostname, pathname } = new URL(registryUrl);
|
|
4376
|
-
const registryPath = `//${
|
|
4381
|
+
const { hostname: hostname2, pathname } = new URL(registryUrl);
|
|
4382
|
+
const registryPath = `//${hostname2}${pathname.replace(/\/$/, "")}/:_authToken=`;
|
|
4377
4383
|
const candidates = [path28.join(process.cwd(), ".npmrc"), path28.join(os3.homedir(), ".npmrc")];
|
|
4378
4384
|
for (const npmrcPath of candidates) {
|
|
4379
4385
|
try {
|
|
@@ -5461,8 +5467,8 @@ function createKnowledgePipelineCommand() {
|
|
|
5461
5467
|
const projectDir = process.cwd();
|
|
5462
5468
|
try {
|
|
5463
5469
|
const graphDir = path38.join(projectDir, ".harness", "graph");
|
|
5464
|
-
const
|
|
5465
|
-
await
|
|
5470
|
+
const fs44 = await import("fs/promises");
|
|
5471
|
+
await fs44.mkdir(graphDir, { recursive: true });
|
|
5466
5472
|
const { GraphStore, KnowledgePipelineRunner } = await import("./dist-D27LJR2T.js");
|
|
5467
5473
|
const store = new GraphStore();
|
|
5468
5474
|
try {
|
|
@@ -5805,7 +5811,7 @@ function createMcpCommand() {
|
|
|
5805
5811
|
parseBudget
|
|
5806
5812
|
).action(async (opts) => {
|
|
5807
5813
|
const [{ startServer: startServer2, getToolDefinitions: getToolDefinitions2 }, { selectTier }] = await Promise.all([
|
|
5808
|
-
import("./mcp-
|
|
5814
|
+
import("./mcp-YW7PLXVQ.js"),
|
|
5809
5815
|
import("./tool-tiers-7QGZ3FKY.js")
|
|
5810
5816
|
]);
|
|
5811
5817
|
if (opts.tools && opts.tools.length > 0) {
|
|
@@ -5867,18 +5873,20 @@ function walk(dir, predicate) {
|
|
|
5867
5873
|
}
|
|
5868
5874
|
return out;
|
|
5869
5875
|
}
|
|
5876
|
+
function extractAutopilotTopic(state) {
|
|
5877
|
+
const match = (state.specPath ?? "").match(/^docs\/changes\/([^/]+)\/proposal\.md$/);
|
|
5878
|
+
return match?.[1] ?? null;
|
|
5879
|
+
}
|
|
5870
5880
|
function buildAutopilotMap(cwd) {
|
|
5871
5881
|
const map = /* @__PURE__ */ new Map();
|
|
5872
5882
|
const sessionsDir = path40.join(cwd, ".harness", "sessions");
|
|
5873
5883
|
if (!fs24.existsSync(sessionsDir)) return map;
|
|
5874
5884
|
for (const session of listDirs(sessionsDir)) {
|
|
5875
5885
|
if (!session.startsWith("changes--")) continue;
|
|
5876
|
-
const
|
|
5877
|
-
const state = readJson2(statePath);
|
|
5886
|
+
const state = readJson2(path40.join(sessionsDir, session, "autopilot-state.json"));
|
|
5878
5887
|
if (!state) continue;
|
|
5879
|
-
const
|
|
5880
|
-
if (!
|
|
5881
|
-
const topic = specMatch[1];
|
|
5888
|
+
const topic = extractAutopilotTopic(state);
|
|
5889
|
+
if (!topic) continue;
|
|
5882
5890
|
for (const phase of state.phases ?? []) {
|
|
5883
5891
|
if (phase.planPath) map.set(phase.planPath, topic);
|
|
5884
5892
|
}
|
|
@@ -5912,8 +5920,7 @@ function extractTopicFromFilename(filename, proposalTopics) {
|
|
|
5912
5920
|
}
|
|
5913
5921
|
return best;
|
|
5914
5922
|
}
|
|
5915
|
-
|
|
5916
|
-
const cwd = opts.cwd ?? process.cwd();
|
|
5923
|
+
function resolveDocsDir(cwd) {
|
|
5917
5924
|
const configPath = path40.join(cwd, "harness.config.json");
|
|
5918
5925
|
const configResult = fs24.existsSync(configPath) ? resolveConfig(configPath) : null;
|
|
5919
5926
|
let docsDir;
|
|
@@ -5929,71 +5936,72 @@ async function buildPlan(opts) {
|
|
|
5929
5936
|
}
|
|
5930
5937
|
const docsAbs = path40.resolve(cwd, docsDir);
|
|
5931
5938
|
const relRaw = path40.relative(cwd, docsAbs).replaceAll("\\", "/");
|
|
5932
|
-
|
|
5939
|
+
return relRaw === "" ? "docs" : relRaw;
|
|
5940
|
+
}
|
|
5941
|
+
function collectAdrMoves(cwd, docsRel) {
|
|
5942
|
+
const moves = [];
|
|
5943
|
+
const legacyAdrDir = path40.join(cwd, ".harness", "architecture");
|
|
5944
|
+
if (!fs24.existsSync(legacyAdrDir)) return moves;
|
|
5945
|
+
for (const topic of listDirs(legacyAdrDir)) {
|
|
5946
|
+
const topicSrc = path40.join(legacyAdrDir, topic);
|
|
5947
|
+
for (const file of listFiles(topicSrc, (n) => n.endsWith(".md"))) {
|
|
5948
|
+
moves.push({
|
|
5949
|
+
src: `.harness/architecture/${topic}/${file}`,
|
|
5950
|
+
dest: `${docsRel}/architecture/${topic}/${file}`
|
|
5951
|
+
});
|
|
5952
|
+
}
|
|
5953
|
+
}
|
|
5954
|
+
return moves;
|
|
5955
|
+
}
|
|
5956
|
+
function collectProposalTopics(cwd) {
|
|
5957
|
+
const topics = /* @__PURE__ */ new Set();
|
|
5958
|
+
const changesDir = path40.join(cwd, "docs", "changes");
|
|
5959
|
+
for (const topic of listDirs(changesDir)) {
|
|
5960
|
+
if (fs24.existsSync(path40.join(changesDir, topic, "proposal.md"))) {
|
|
5961
|
+
topics.add(topic);
|
|
5962
|
+
}
|
|
5963
|
+
}
|
|
5964
|
+
return topics;
|
|
5965
|
+
}
|
|
5966
|
+
function classifyPlan(planRel, planAbs, filename, autopilotMap, proposalTopics) {
|
|
5967
|
+
const fromAutopilot = autopilotMap.get(planRel);
|
|
5968
|
+
if (fromAutopilot) return { topic: fromAutopilot, via: "autopilot" };
|
|
5969
|
+
const fromHeader = extractTopicFromHeader(planAbs, proposalTopics);
|
|
5970
|
+
if (fromHeader) return { topic: fromHeader, via: "header" };
|
|
5971
|
+
const fromFilename = extractTopicFromFilename(filename, proposalTopics);
|
|
5972
|
+
if (fromFilename) return { topic: fromFilename, via: "filename" };
|
|
5973
|
+
return null;
|
|
5974
|
+
}
|
|
5975
|
+
async function buildPlan(opts) {
|
|
5976
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
5977
|
+
const docsRel = resolveDocsDir(cwd);
|
|
5933
5978
|
const plan = {
|
|
5934
5979
|
cwd,
|
|
5935
5980
|
isGitRepo: isGitRepo(cwd),
|
|
5936
5981
|
docsDir: docsRel,
|
|
5937
|
-
adrMoves:
|
|
5982
|
+
adrMoves: collectAdrMoves(cwd, docsRel),
|
|
5938
5983
|
planMoves: [],
|
|
5939
5984
|
orphanPlans: [],
|
|
5940
|
-
proposalTopics:
|
|
5985
|
+
proposalTopics: collectProposalTopics(cwd)
|
|
5941
5986
|
};
|
|
5942
|
-
const legacyAdrDir = path40.join(cwd, ".harness", "architecture");
|
|
5943
|
-
if (fs24.existsSync(legacyAdrDir)) {
|
|
5944
|
-
for (const topic of listDirs(legacyAdrDir)) {
|
|
5945
|
-
const topicSrc = path40.join(legacyAdrDir, topic);
|
|
5946
|
-
for (const file of listFiles(topicSrc, (n) => n.endsWith(".md"))) {
|
|
5947
|
-
const src = `.harness/architecture/${topic}/${file}`;
|
|
5948
|
-
const dest = `${docsRel}/architecture/${topic}/${file}`;
|
|
5949
|
-
plan.adrMoves.push({ src, dest });
|
|
5950
|
-
}
|
|
5951
|
-
}
|
|
5952
|
-
}
|
|
5953
5987
|
const legacyPlanDir = path40.join(cwd, "docs", "plans");
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
topic = fromAutopilot;
|
|
5972
|
-
via = "autopilot";
|
|
5973
|
-
} else {
|
|
5974
|
-
const fromHeader = extractTopicFromHeader(planAbs, plan.proposalTopics);
|
|
5975
|
-
if (fromHeader) {
|
|
5976
|
-
topic = fromHeader;
|
|
5977
|
-
via = "header";
|
|
5978
|
-
} else {
|
|
5979
|
-
const fromFilename = extractTopicFromFilename(file, plan.proposalTopics);
|
|
5980
|
-
if (fromFilename) {
|
|
5981
|
-
topic = fromFilename;
|
|
5982
|
-
via = "filename";
|
|
5983
|
-
}
|
|
5984
|
-
}
|
|
5985
|
-
}
|
|
5986
|
-
if (topic && via) {
|
|
5987
|
-
plan.planMoves.push({
|
|
5988
|
-
src: planRel,
|
|
5989
|
-
dest: `docs/changes/${topic}/${subdir}/${file}`,
|
|
5990
|
-
topic,
|
|
5991
|
-
via,
|
|
5992
|
-
subdir
|
|
5993
|
-
});
|
|
5994
|
-
} else {
|
|
5995
|
-
plan.orphanPlans.push(planRel);
|
|
5996
|
-
}
|
|
5988
|
+
if (!fs24.existsSync(legacyPlanDir)) return plan;
|
|
5989
|
+
const autopilotMap = buildAutopilotMap(cwd);
|
|
5990
|
+
for (const file of listFiles(legacyPlanDir, (n) => n.endsWith(".md") && n !== "index.md")) {
|
|
5991
|
+
const planRel = `docs/plans/${file}`;
|
|
5992
|
+
const planAbs = path40.join(legacyPlanDir, file);
|
|
5993
|
+
const subdir = file.includes("VERIFICATION") ? "verifications" : "plans";
|
|
5994
|
+
const classified = classifyPlan(planRel, planAbs, file, autopilotMap, plan.proposalTopics);
|
|
5995
|
+
if (classified) {
|
|
5996
|
+
plan.planMoves.push({
|
|
5997
|
+
src: planRel,
|
|
5998
|
+
dest: `docs/changes/${classified.topic}/${subdir}/${file}`,
|
|
5999
|
+
topic: classified.topic,
|
|
6000
|
+
via: classified.via,
|
|
6001
|
+
subdir
|
|
6002
|
+
});
|
|
6003
|
+
} else {
|
|
6004
|
+
plan.orphanPlans.push(planRel);
|
|
5997
6005
|
}
|
|
5998
6006
|
}
|
|
5999
6007
|
return plan;
|
|
@@ -6068,7 +6076,7 @@ function updateReferences(plan) {
|
|
|
6068
6076
|
}
|
|
6069
6077
|
async function prompt(question, def = "n") {
|
|
6070
6078
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6071
|
-
const answer = await new Promise((
|
|
6079
|
+
const answer = await new Promise((resolve40) => rl.question(question, resolve40));
|
|
6072
6080
|
rl.close();
|
|
6073
6081
|
return (answer.trim() || def).toLowerCase();
|
|
6074
6082
|
}
|
|
@@ -6166,6 +6174,29 @@ async function detectLegacyArtifacts(cwd) {
|
|
|
6166
6174
|
const planLegacy = fs24.existsSync(planLegacyDir) && listFiles(planLegacyDir, (n) => n.endsWith(".md") && n !== "index.md").length > 0;
|
|
6167
6175
|
return { adrLegacy, planLegacy };
|
|
6168
6176
|
}
|
|
6177
|
+
async function confirmApply(opts) {
|
|
6178
|
+
if (opts.yes || !process.stdin.isTTY) return true;
|
|
6179
|
+
const answer = await prompt("Apply this migration? [y/N] ", "n");
|
|
6180
|
+
return answer === "y" || answer === "yes";
|
|
6181
|
+
}
|
|
6182
|
+
function applyMigration(plan, opts) {
|
|
6183
|
+
const { moved, failed } = executeMigration(plan);
|
|
6184
|
+
console.log("");
|
|
6185
|
+
if (failed > 0) logger.error(` Moved ${moved} file(s); ${failed} failed.`);
|
|
6186
|
+
else logger.success(` Moved ${moved} file(s).`);
|
|
6187
|
+
let references = { files: 0, replacements: 0 };
|
|
6188
|
+
if (!opts.skipReferences) {
|
|
6189
|
+
references = updateReferences(plan);
|
|
6190
|
+
if (references.replacements > 0) {
|
|
6191
|
+
logger.success(
|
|
6192
|
+
` Updated ${references.replacements} reference(s) across ${references.files} file(s).`
|
|
6193
|
+
);
|
|
6194
|
+
} else {
|
|
6195
|
+
logger.info(" No reference updates needed.");
|
|
6196
|
+
}
|
|
6197
|
+
}
|
|
6198
|
+
return { moved, failed, references };
|
|
6199
|
+
}
|
|
6169
6200
|
async function runMigrate(opts) {
|
|
6170
6201
|
const cwd = opts.cwd ?? process.cwd();
|
|
6171
6202
|
const plan = await buildPlan({ ...opts, cwd });
|
|
@@ -6188,34 +6219,13 @@ async function runMigrate(opts) {
|
|
|
6188
6219
|
const movesPlanned = plan.adrMoves.length + plan.planMoves.length;
|
|
6189
6220
|
if (opts.dryRun) {
|
|
6190
6221
|
logger.info(" Dry run \u2014 no files moved.");
|
|
6191
|
-
return Ok({
|
|
6192
|
-
...empty,
|
|
6193
|
-
movesPlanned,
|
|
6194
|
-
orphansRemaining: plan.orphanPlans.length
|
|
6195
|
-
});
|
|
6222
|
+
return Ok({ ...empty, movesPlanned, orphansRemaining: plan.orphanPlans.length });
|
|
6196
6223
|
}
|
|
6197
|
-
if (!opts
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
logger.info(" Cancelled.");
|
|
6201
|
-
return Ok({ ...empty, movesPlanned, cancelled: true });
|
|
6202
|
-
}
|
|
6203
|
-
}
|
|
6204
|
-
const { moved, failed } = executeMigration(plan);
|
|
6205
|
-
console.log("");
|
|
6206
|
-
if (failed > 0) logger.error(` Moved ${moved} file(s); ${failed} failed.`);
|
|
6207
|
-
else logger.success(` Moved ${moved} file(s).`);
|
|
6208
|
-
let references = { files: 0, replacements: 0 };
|
|
6209
|
-
if (!opts.skipReferences) {
|
|
6210
|
-
references = updateReferences(plan);
|
|
6211
|
-
if (references.replacements > 0) {
|
|
6212
|
-
logger.success(
|
|
6213
|
-
` Updated ${references.replacements} reference(s) across ${references.files} file(s).`
|
|
6214
|
-
);
|
|
6215
|
-
} else {
|
|
6216
|
-
logger.info(" No reference updates needed.");
|
|
6217
|
-
}
|
|
6224
|
+
if (!await confirmApply(opts)) {
|
|
6225
|
+
logger.info(" Cancelled.");
|
|
6226
|
+
return Ok({ ...empty, movesPlanned, cancelled: true });
|
|
6218
6227
|
}
|
|
6228
|
+
const { moved, failed, references } = applyMigration(plan, opts);
|
|
6219
6229
|
console.log("");
|
|
6220
6230
|
logger.info(" Next: review changes with `git status` / `git diff`, then commit.");
|
|
6221
6231
|
return Ok({
|
|
@@ -6296,7 +6306,7 @@ function registerBenchCommand(perf) {
|
|
|
6296
6306
|
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob2, _opts, cmd) => {
|
|
6297
6307
|
const globalOpts = cmd.optsWithGlobals();
|
|
6298
6308
|
const cwd = process.cwd();
|
|
6299
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
6309
|
+
const { BenchmarkRunner } = await import("./dist-I4LWG3IN.js");
|
|
6300
6310
|
const runner = new BenchmarkRunner();
|
|
6301
6311
|
const benchFiles = runner.discover(cwd, glob2);
|
|
6302
6312
|
if (benchFiles.length === 0) {
|
|
@@ -6360,7 +6370,7 @@ async function runBaselinesShow(globalOpts) {
|
|
|
6360
6370
|
}
|
|
6361
6371
|
async function runBaselinesUpdate(globalOpts) {
|
|
6362
6372
|
const cwd = process.cwd();
|
|
6363
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
6373
|
+
const { BenchmarkRunner } = await import("./dist-I4LWG3IN.js");
|
|
6364
6374
|
const runner = new BenchmarkRunner();
|
|
6365
6375
|
const manager = new BaselineManager(cwd);
|
|
6366
6376
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -6401,7 +6411,7 @@ function registerReportCommand(perf) {
|
|
|
6401
6411
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
6402
6412
|
const globalOpts = cmd.optsWithGlobals();
|
|
6403
6413
|
const cwd = process.cwd();
|
|
6404
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
6414
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-I4LWG3IN.js");
|
|
6405
6415
|
const analyzer = new EntropyAnalyzer2({
|
|
6406
6416
|
rootDir: path42.resolve(cwd),
|
|
6407
6417
|
analyze: { complexity: true, coupling: true }
|
|
@@ -6735,14 +6745,39 @@ function bootstrapTrackerCommand(opts, verb) {
|
|
|
6735
6745
|
}
|
|
6736
6746
|
return { token, projectPath, trackerConfig };
|
|
6737
6747
|
}
|
|
6738
|
-
function buildNameToExternalIdMap(projectPath) {
|
|
6748
|
+
async function buildNameToExternalIdMap(projectPath) {
|
|
6749
|
+
const mode = loadProjectRoadmapMode(projectPath);
|
|
6750
|
+
if (mode === "file-less") {
|
|
6751
|
+
const trackerCfg = loadTrackerClientConfigFromProject(projectPath);
|
|
6752
|
+
if (!trackerCfg.ok) {
|
|
6753
|
+
logger.error(`File-less mode: ${trackerCfg.error.message}`);
|
|
6754
|
+
return null;
|
|
6755
|
+
}
|
|
6756
|
+
const clientR = createTrackerClient(trackerCfg.value);
|
|
6757
|
+
if (!clientR.ok) {
|
|
6758
|
+
logger.error(`File-less mode: ${clientR.error.message}`);
|
|
6759
|
+
return null;
|
|
6760
|
+
}
|
|
6761
|
+
const r = await clientR.value.fetchAll();
|
|
6762
|
+
if (!r.ok) {
|
|
6763
|
+
logger.error(`File-less mode: failed to fetch features from tracker: ${r.error.message}`);
|
|
6764
|
+
return null;
|
|
6765
|
+
}
|
|
6766
|
+
const fileLessMap = /* @__PURE__ */ new Map();
|
|
6767
|
+
for (const f of r.value.features) {
|
|
6768
|
+
if (f.externalId) {
|
|
6769
|
+
fileLessMap.set(f.name.toLowerCase(), f.externalId);
|
|
6770
|
+
}
|
|
6771
|
+
}
|
|
6772
|
+
return fileLessMap;
|
|
6773
|
+
}
|
|
6739
6774
|
const roadmapFile = path44.join(projectPath, "docs", "roadmap.md");
|
|
6740
6775
|
if (!fs26.existsSync(roadmapFile)) {
|
|
6741
6776
|
logger.error("No docs/roadmap.md found. Cannot map issue hashes to external IDs.");
|
|
6742
6777
|
return null;
|
|
6743
6778
|
}
|
|
6744
|
-
const { parseRoadmap } = (init_dist(), __toCommonJS(dist_exports));
|
|
6745
|
-
const roadmapParsed =
|
|
6779
|
+
const { parseRoadmap: parseRoadmap2 } = (init_dist(), __toCommonJS(dist_exports));
|
|
6780
|
+
const roadmapParsed = parseRoadmap2(fs26.readFileSync(roadmapFile, "utf-8"));
|
|
6746
6781
|
if (!roadmapParsed.ok) {
|
|
6747
6782
|
logger.error("Failed to parse docs/roadmap.md");
|
|
6748
6783
|
return null;
|
|
@@ -6789,10 +6824,10 @@ async function runPublishAnalyses(opts) {
|
|
|
6789
6824
|
const bootstrap = bootstrapTrackerCommand(opts, "publish");
|
|
6790
6825
|
if (!bootstrap) return process.exit(1);
|
|
6791
6826
|
const { token, projectPath, trackerConfig } = bootstrap;
|
|
6792
|
-
const nameToExternalId = buildNameToExternalIdMap(projectPath);
|
|
6827
|
+
const nameToExternalId = await buildNameToExternalIdMap(projectPath);
|
|
6793
6828
|
if (!nameToExternalId) return process.exit(1);
|
|
6794
6829
|
const { AnalysisArchive } = await import("@harness-engineering/orchestrator");
|
|
6795
|
-
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-
|
|
6830
|
+
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-I4LWG3IN.js");
|
|
6796
6831
|
const archive = new AnalysisArchive(path44.join(projectPath, ".harness", "analyses"));
|
|
6797
6832
|
const analyses = await archive.list();
|
|
6798
6833
|
if (analyses.length === 0) {
|
|
@@ -7031,11 +7066,326 @@ function createRecommendCommand() {
|
|
|
7031
7066
|
return command;
|
|
7032
7067
|
}
|
|
7033
7068
|
|
|
7034
|
-
// src/commands/
|
|
7069
|
+
// src/commands/roadmap/index.ts
|
|
7070
|
+
import { Command as Command62 } from "commander";
|
|
7071
|
+
|
|
7072
|
+
// src/commands/roadmap/migrate.ts
|
|
7035
7073
|
init_dist();
|
|
7036
7074
|
import { Command as Command61 } from "commander";
|
|
7037
|
-
import
|
|
7038
|
-
import
|
|
7075
|
+
import * as fs28 from "fs";
|
|
7076
|
+
import * as path46 from "path";
|
|
7077
|
+
import chalk10 from "chalk";
|
|
7078
|
+
|
|
7079
|
+
// src/commands/roadmap/migrate-lock.ts
|
|
7080
|
+
import * as fs27 from "fs";
|
|
7081
|
+
import * as path45 from "path";
|
|
7082
|
+
import * as os6 from "os";
|
|
7083
|
+
var LOCK_FILE_NAME = "migrate.lock";
|
|
7084
|
+
var STALE_LOCK_MS = 30 * 60 * 1e3;
|
|
7085
|
+
function isPidAlive(pid) {
|
|
7086
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
7087
|
+
try {
|
|
7088
|
+
process.kill(pid, 0);
|
|
7089
|
+
return true;
|
|
7090
|
+
} catch {
|
|
7091
|
+
return false;
|
|
7092
|
+
}
|
|
7093
|
+
}
|
|
7094
|
+
function acquireMigrateLock(projectRoot) {
|
|
7095
|
+
const harnessDir = path45.join(projectRoot, ".harness");
|
|
7096
|
+
const lockPath = path45.join(harnessDir, LOCK_FILE_NAME);
|
|
7097
|
+
if (!fs27.existsSync(harnessDir)) fs27.mkdirSync(harnessDir, { recursive: true });
|
|
7098
|
+
if (fs27.existsSync(lockPath)) {
|
|
7099
|
+
const existingRaw = (() => {
|
|
7100
|
+
try {
|
|
7101
|
+
return fs27.readFileSync(lockPath, "utf-8");
|
|
7102
|
+
} catch {
|
|
7103
|
+
return null;
|
|
7104
|
+
}
|
|
7105
|
+
})();
|
|
7106
|
+
const existing = (() => {
|
|
7107
|
+
if (!existingRaw) return null;
|
|
7108
|
+
try {
|
|
7109
|
+
return JSON.parse(existingRaw);
|
|
7110
|
+
} catch {
|
|
7111
|
+
return null;
|
|
7112
|
+
}
|
|
7113
|
+
})();
|
|
7114
|
+
if (existing) {
|
|
7115
|
+
const ageMs = Date.now() - new Date(existing.startedAt).getTime();
|
|
7116
|
+
const stale = !isPidAlive(existing.pid) || ageMs > STALE_LOCK_MS;
|
|
7117
|
+
if (!stale) {
|
|
7118
|
+
return {
|
|
7119
|
+
reason: "in-progress",
|
|
7120
|
+
existing,
|
|
7121
|
+
message: `another migration is in progress (pid ${existing.pid} on ${existing.hostname}, started ${existing.startedAt})`
|
|
7122
|
+
};
|
|
7123
|
+
}
|
|
7124
|
+
}
|
|
7125
|
+
}
|
|
7126
|
+
const payload = {
|
|
7127
|
+
pid: process.pid,
|
|
7128
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7129
|
+
hostname: os6.hostname()
|
|
7130
|
+
};
|
|
7131
|
+
fs27.writeFileSync(lockPath, JSON.stringify(payload, null, 2) + "\n");
|
|
7132
|
+
return {
|
|
7133
|
+
release: () => {
|
|
7134
|
+
try {
|
|
7135
|
+
fs27.unlinkSync(lockPath);
|
|
7136
|
+
} catch {
|
|
7137
|
+
}
|
|
7138
|
+
}
|
|
7139
|
+
};
|
|
7140
|
+
}
|
|
7141
|
+
function isRefusal(r) {
|
|
7142
|
+
return r.reason === "in-progress";
|
|
7143
|
+
}
|
|
7144
|
+
|
|
7145
|
+
// src/commands/roadmap/migrate.ts
|
|
7146
|
+
var MigrateExitCode = {
|
|
7147
|
+
SUCCESS: 0,
|
|
7148
|
+
GENERIC_FAILURE: 1,
|
|
7149
|
+
AMBIGUOUS: 2,
|
|
7150
|
+
ARCHIVE_COLLISION: 3,
|
|
7151
|
+
CONFIG_ERROR: 4,
|
|
7152
|
+
PARTIAL_CREATE: 5
|
|
7153
|
+
};
|
|
7154
|
+
function reportToExitCode(report) {
|
|
7155
|
+
if (report.mode !== "aborted") return MigrateExitCode.SUCCESS;
|
|
7156
|
+
const reason = report.abortReason ?? "";
|
|
7157
|
+
if (reason.startsWith("create failed") && report.createdSoFar && report.createdSoFar.length > 0) {
|
|
7158
|
+
return MigrateExitCode.PARTIAL_CREATE;
|
|
7159
|
+
}
|
|
7160
|
+
if (reason.startsWith("ambiguous features")) return MigrateExitCode.AMBIGUOUS;
|
|
7161
|
+
if (reason.startsWith("archive-collision")) return MigrateExitCode.ARCHIVE_COLLISION;
|
|
7162
|
+
if (reason.startsWith("config rewrite failed")) return MigrateExitCode.CONFIG_ERROR;
|
|
7163
|
+
return MigrateExitCode.GENERIC_FAILURE;
|
|
7164
|
+
}
|
|
7165
|
+
function printPlanSummary(plan, dryRun) {
|
|
7166
|
+
const banner = dryRun ? chalk10.cyan("DRY RUN ") : "";
|
|
7167
|
+
console.log(`${banner}Migration plan:`);
|
|
7168
|
+
console.log(` Would create: ${plan.toCreate.length}`);
|
|
7169
|
+
for (const e of plan.toCreate) console.log(` - ${e.name}`);
|
|
7170
|
+
console.log(` Would update: ${plan.toUpdate.length}`);
|
|
7171
|
+
for (const e of plan.toUpdate) console.log(` - ${e.name} (${e.externalId}): ${e.diff}`);
|
|
7172
|
+
console.log(` Unchanged: ${plan.unchanged.length}`);
|
|
7173
|
+
console.log(` Would append history: ${plan.historyToAppend.length}`);
|
|
7174
|
+
console.log(` Ambiguous: ${plan.ambiguous.length}`);
|
|
7175
|
+
for (const a of plan.ambiguous) {
|
|
7176
|
+
console.log(` - ${a.name} (existing: ${a.existingIssueRef})`);
|
|
7177
|
+
}
|
|
7178
|
+
}
|
|
7179
|
+
function printReport(report) {
|
|
7180
|
+
if (report.mode === "aborted") {
|
|
7181
|
+
logger.error(`Migration aborted: ${report.abortReason ?? "unknown reason"}`);
|
|
7182
|
+
if (report.createdSoFar && report.createdSoFar.length > 0) {
|
|
7183
|
+
logger.warn(
|
|
7184
|
+
`Features created before abort (record manually): ${report.createdSoFar.map((c) => `${c.name} \u2192 ${c.externalId}`).join(", ")}`
|
|
7185
|
+
);
|
|
7186
|
+
}
|
|
7187
|
+
return;
|
|
7188
|
+
}
|
|
7189
|
+
if (report.mode === "dry-run") {
|
|
7190
|
+
logger.info(`DRY RUN complete: ${report.created + report.updated} writes would be performed.`);
|
|
7191
|
+
return;
|
|
7192
|
+
}
|
|
7193
|
+
if (report.mode === "already-migrated") {
|
|
7194
|
+
logger.success("Already migrated; nothing to do.");
|
|
7195
|
+
return;
|
|
7196
|
+
}
|
|
7197
|
+
logger.success(
|
|
7198
|
+
`Migration applied: ${report.created} created, ${report.updated} updated, ${report.unchanged} unchanged, ${report.historyAppended} history events appended.`
|
|
7199
|
+
);
|
|
7200
|
+
if (report.archivedTo) logger.info(`Archived: ${report.archivedFrom} -> ${report.archivedTo}`);
|
|
7201
|
+
if (report.configBackup) logger.info(`Config backup: ${report.configBackup}`);
|
|
7202
|
+
}
|
|
7203
|
+
async function collectHistoryHashes(client, externalId) {
|
|
7204
|
+
const result = await client.fetchHistory(externalId);
|
|
7205
|
+
const set = /* @__PURE__ */ new Set();
|
|
7206
|
+
if (!result.ok) return set;
|
|
7207
|
+
for (const e of result.value) set.add(migrate_exports.hashHistoryEvent(e));
|
|
7208
|
+
return set;
|
|
7209
|
+
}
|
|
7210
|
+
function makeRawBodyResolver(_client, _features) {
|
|
7211
|
+
return async () => null;
|
|
7212
|
+
}
|
|
7213
|
+
function buildJsonOutput(plan, report, error, exitCode) {
|
|
7214
|
+
const out = {
|
|
7215
|
+
ok: exitCode === MigrateExitCode.SUCCESS,
|
|
7216
|
+
mode: report ? report.mode : "error",
|
|
7217
|
+
exitCode
|
|
7218
|
+
};
|
|
7219
|
+
if (plan) {
|
|
7220
|
+
out.plan = {
|
|
7221
|
+
toCreate: plan.toCreate.map((e) => ({ name: e.name })),
|
|
7222
|
+
toUpdate: plan.toUpdate.map((e) => ({
|
|
7223
|
+
name: e.name,
|
|
7224
|
+
externalId: e.externalId,
|
|
7225
|
+
diff: e.diff
|
|
7226
|
+
})),
|
|
7227
|
+
unchanged: plan.unchanged,
|
|
7228
|
+
historyToAppend: plan.historyToAppend.map((e) => ({
|
|
7229
|
+
externalId: e.externalId,
|
|
7230
|
+
type: e.event.type
|
|
7231
|
+
})),
|
|
7232
|
+
ambiguous: plan.ambiguous
|
|
7233
|
+
};
|
|
7234
|
+
}
|
|
7235
|
+
if (report) out.report = report;
|
|
7236
|
+
if (error) out.error = error;
|
|
7237
|
+
return out;
|
|
7238
|
+
}
|
|
7239
|
+
async function runRoadmapMigrate(opts) {
|
|
7240
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
7241
|
+
const format = opts.format ?? "human";
|
|
7242
|
+
const isJson = format === "json";
|
|
7243
|
+
if (!opts.to) {
|
|
7244
|
+
return Err(new CLIError("missing required argument: --to <target>", ExitCode.ERROR));
|
|
7245
|
+
}
|
|
7246
|
+
if (opts.to === "file-backed") {
|
|
7247
|
+
return Err(
|
|
7248
|
+
new CLIError(
|
|
7249
|
+
"--to=file-backed reverse migration is not yet implemented; track in a future spec"
|
|
7250
|
+
)
|
|
7251
|
+
);
|
|
7252
|
+
}
|
|
7253
|
+
if (opts.to !== "file-less") {
|
|
7254
|
+
return Err(
|
|
7255
|
+
new CLIError(`unsupported migration target: ${opts.to} (only "file-less" supported today)`)
|
|
7256
|
+
);
|
|
7257
|
+
}
|
|
7258
|
+
if (loadProjectRoadmapMode(cwd) === "file-less") {
|
|
7259
|
+
const alreadyMigratedReport = {
|
|
7260
|
+
created: 0,
|
|
7261
|
+
updated: 0,
|
|
7262
|
+
unchanged: 0,
|
|
7263
|
+
historyAppended: 0,
|
|
7264
|
+
archivedFrom: null,
|
|
7265
|
+
archivedTo: null,
|
|
7266
|
+
configBackup: null,
|
|
7267
|
+
mode: "already-migrated"
|
|
7268
|
+
};
|
|
7269
|
+
if (isJson) {
|
|
7270
|
+
console.log(
|
|
7271
|
+
JSON.stringify(
|
|
7272
|
+
buildJsonOutput(void 0, alreadyMigratedReport, void 0, MigrateExitCode.SUCCESS)
|
|
7273
|
+
)
|
|
7274
|
+
);
|
|
7275
|
+
} else {
|
|
7276
|
+
logger.success("Already migrated; nothing to do.");
|
|
7277
|
+
}
|
|
7278
|
+
return Ok(alreadyMigratedReport);
|
|
7279
|
+
}
|
|
7280
|
+
const lockResult = acquireMigrateLock(cwd);
|
|
7281
|
+
if (isRefusal(lockResult)) {
|
|
7282
|
+
return Err(new CLIError(lockResult.message));
|
|
7283
|
+
}
|
|
7284
|
+
try {
|
|
7285
|
+
let client;
|
|
7286
|
+
if (opts.client) {
|
|
7287
|
+
client = opts.client;
|
|
7288
|
+
} else {
|
|
7289
|
+
const cfgR = loadTrackerClientConfigFromProject(cwd);
|
|
7290
|
+
if (!cfgR.ok) return Err(new CLIError(cfgR.error.message));
|
|
7291
|
+
const clientR = createTrackerClient(cfgR.value);
|
|
7292
|
+
if (!clientR.ok) return Err(new CLIError(clientR.error.message));
|
|
7293
|
+
client = clientR.value;
|
|
7294
|
+
}
|
|
7295
|
+
const roadmapPath = path46.join(cwd, "docs", "roadmap.md");
|
|
7296
|
+
if (!fs28.existsSync(roadmapPath)) {
|
|
7297
|
+
return Err(new CLIError(`docs/roadmap.md not found in ${cwd}`));
|
|
7298
|
+
}
|
|
7299
|
+
const roadmapR = parseRoadmap(fs28.readFileSync(roadmapPath, "utf-8"));
|
|
7300
|
+
if (!roadmapR.ok) {
|
|
7301
|
+
return Err(new CLIError(`failed to parse docs/roadmap.md: ${roadmapR.error.message}`));
|
|
7302
|
+
}
|
|
7303
|
+
const roadmap = roadmapR.value;
|
|
7304
|
+
const fetchR = await client.fetchAll();
|
|
7305
|
+
if (!fetchR.ok) {
|
|
7306
|
+
return Err(new CLIError(`failed to fetch tracker features: ${fetchR.error.message}`));
|
|
7307
|
+
}
|
|
7308
|
+
const existingFeatures = fetchR.value.features;
|
|
7309
|
+
const getRawBody = makeRawBodyResolver(client, existingFeatures);
|
|
7310
|
+
const fetchHashes = (id) => collectHistoryHashes(client, id);
|
|
7311
|
+
const plan = await migrate_exports.buildMigrationPlan(
|
|
7312
|
+
roadmap,
|
|
7313
|
+
existingFeatures,
|
|
7314
|
+
fetchHashes,
|
|
7315
|
+
getRawBody
|
|
7316
|
+
);
|
|
7317
|
+
if (!isJson) printPlanSummary(plan, opts.dryRun);
|
|
7318
|
+
const deps = {
|
|
7319
|
+
client,
|
|
7320
|
+
readFile: (p) => fs28.readFileSync(p, "utf-8"),
|
|
7321
|
+
writeFile: (p, b) => fs28.writeFileSync(p, b),
|
|
7322
|
+
renameFile: (from, to) => fs28.renameSync(from, to),
|
|
7323
|
+
existsFile: (p) => fs28.existsSync(p)
|
|
7324
|
+
};
|
|
7325
|
+
const reportR = await migrate_exports.runMigrationPlan(plan, deps, {
|
|
7326
|
+
projectRoot: cwd,
|
|
7327
|
+
dryRun: opts.dryRun
|
|
7328
|
+
});
|
|
7329
|
+
if (!reportR.ok) {
|
|
7330
|
+
return Err(new CLIError(`migration failed: ${reportR.error.message}`));
|
|
7331
|
+
}
|
|
7332
|
+
if (isJson) {
|
|
7333
|
+
const exitCode = reportToExitCode(reportR.value);
|
|
7334
|
+
console.log(JSON.stringify(buildJsonOutput(plan, reportR.value, void 0, exitCode)));
|
|
7335
|
+
} else {
|
|
7336
|
+
printReport(reportR.value);
|
|
7337
|
+
}
|
|
7338
|
+
return Ok(reportR.value);
|
|
7339
|
+
} finally {
|
|
7340
|
+
lockResult.release();
|
|
7341
|
+
}
|
|
7342
|
+
}
|
|
7343
|
+
function createRoadmapMigrateCommand() {
|
|
7344
|
+
return new Command61("migrate").description("Migrate the project roadmap to a different storage mode").requiredOption("--to <target>", 'Migration target (only "file-less" supported today)').option("--dry-run", "Print the migration plan without making any changes", false).option(
|
|
7345
|
+
"--format <fmt>",
|
|
7346
|
+
'Output format: "human" (default) or "json" (single JSON object for CI consumers)',
|
|
7347
|
+
"human"
|
|
7348
|
+
).action(async (options) => {
|
|
7349
|
+
const format = options.format === "json" ? "json" : "human";
|
|
7350
|
+
const result = await runRoadmapMigrate({
|
|
7351
|
+
to: options.to,
|
|
7352
|
+
dryRun: Boolean(options.dryRun),
|
|
7353
|
+
format
|
|
7354
|
+
});
|
|
7355
|
+
if (!result.ok) {
|
|
7356
|
+
if (format === "json") {
|
|
7357
|
+
console.log(
|
|
7358
|
+
JSON.stringify(
|
|
7359
|
+
buildJsonOutput(
|
|
7360
|
+
void 0,
|
|
7361
|
+
void 0,
|
|
7362
|
+
result.error.message,
|
|
7363
|
+
MigrateExitCode.CONFIG_ERROR
|
|
7364
|
+
)
|
|
7365
|
+
)
|
|
7366
|
+
);
|
|
7367
|
+
} else {
|
|
7368
|
+
logger.error(result.error.message);
|
|
7369
|
+
}
|
|
7370
|
+
process.exit(format === "json" ? MigrateExitCode.CONFIG_ERROR : result.error.exitCode);
|
|
7371
|
+
}
|
|
7372
|
+
const exitCode = reportToExitCode(result.value);
|
|
7373
|
+
process.exit(exitCode);
|
|
7374
|
+
});
|
|
7375
|
+
}
|
|
7376
|
+
|
|
7377
|
+
// src/commands/roadmap/index.ts
|
|
7378
|
+
function createRoadmapCommand() {
|
|
7379
|
+
const roadmap = new Command62("roadmap").description("Roadmap management");
|
|
7380
|
+
roadmap.addCommand(createRoadmapMigrateCommand());
|
|
7381
|
+
return roadmap;
|
|
7382
|
+
}
|
|
7383
|
+
|
|
7384
|
+
// src/commands/scan-config.ts
|
|
7385
|
+
init_dist();
|
|
7386
|
+
import { Command as Command63 } from "commander";
|
|
7387
|
+
import { existsSync as existsSync25, readFileSync as readFileSync18, writeFileSync as writeFileSync17 } from "fs";
|
|
7388
|
+
import { join as join36, relative as relative3 } from "path";
|
|
7039
7389
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
7040
7390
|
function stripHighSeverityPatterns(content, injectionFindings) {
|
|
7041
7391
|
const highLines = /* @__PURE__ */ new Set();
|
|
@@ -7061,17 +7411,17 @@ function applyFix(filePath, targetDir, content, injectionFindings) {
|
|
|
7061
7411
|
if (!hasHighSeverity) return;
|
|
7062
7412
|
const { cleaned, linesStripped } = stripHighSeverityPatterns(content, injectionFindings);
|
|
7063
7413
|
if (linesStripped > 0) {
|
|
7064
|
-
|
|
7414
|
+
writeFileSync17(filePath, cleaned);
|
|
7065
7415
|
logger.info(
|
|
7066
7416
|
`scan-config --fix: stripped ${linesStripped} high-severity line(s) from ${relative3(targetDir, filePath).replaceAll("\\", "/")}`
|
|
7067
7417
|
);
|
|
7068
7418
|
}
|
|
7069
7419
|
}
|
|
7070
7420
|
function scanSingleFile(filePath, targetDir, scanner, options) {
|
|
7071
|
-
if (!
|
|
7421
|
+
if (!existsSync25(filePath)) return null;
|
|
7072
7422
|
let content;
|
|
7073
7423
|
try {
|
|
7074
|
-
content =
|
|
7424
|
+
content = readFileSync18(filePath, "utf8");
|
|
7075
7425
|
} catch {
|
|
7076
7426
|
return null;
|
|
7077
7427
|
}
|
|
@@ -7092,7 +7442,7 @@ async function runScanConfig(targetDir, options) {
|
|
|
7092
7442
|
const scanner = new SecurityScanner(parseSecurityConfig({}));
|
|
7093
7443
|
const results = [];
|
|
7094
7444
|
for (const configFile of CONFIG_FILES) {
|
|
7095
|
-
const result = scanSingleFile(
|
|
7445
|
+
const result = scanSingleFile(join36(targetDir, configFile), targetDir, scanner, options);
|
|
7096
7446
|
if (result) results.push(result);
|
|
7097
7447
|
}
|
|
7098
7448
|
return { exitCode: computeScanExitCode(results), results };
|
|
@@ -7122,7 +7472,7 @@ function formatTextOutput(result) {
|
|
|
7122
7472
|
}
|
|
7123
7473
|
}
|
|
7124
7474
|
function createScanConfigCommand() {
|
|
7125
|
-
const command = new
|
|
7475
|
+
const command = new Command63("scan-config").description(
|
|
7126
7476
|
"Scan CLAUDE.md, AGENTS.md, .gemini/settings.json, and skill.yaml for prompt injection patterns"
|
|
7127
7477
|
).option("--path <dir>", "Target directory to scan (default: cwd)").option("--fix", "Strip high-severity patterns from files in-place").action(async (opts, cmd) => {
|
|
7128
7478
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -7139,24 +7489,24 @@ function createScanConfigCommand() {
|
|
|
7139
7489
|
}
|
|
7140
7490
|
|
|
7141
7491
|
// src/commands/setup.ts
|
|
7142
|
-
import { Command as
|
|
7492
|
+
import { Command as Command64 } from "commander";
|
|
7493
|
+
import * as fs31 from "fs";
|
|
7494
|
+
import * as os8 from "os";
|
|
7495
|
+
import * as path49 from "path";
|
|
7496
|
+
import chalk12 from "chalk";
|
|
7497
|
+
|
|
7498
|
+
// src/utils/first-run.ts
|
|
7143
7499
|
import * as fs29 from "fs";
|
|
7144
7500
|
import * as os7 from "os";
|
|
7145
7501
|
import * as path47 from "path";
|
|
7146
|
-
|
|
7147
|
-
|
|
7148
|
-
// src/utils/first-run.ts
|
|
7149
|
-
import * as fs27 from "fs";
|
|
7150
|
-
import * as os6 from "os";
|
|
7151
|
-
import * as path45 from "path";
|
|
7152
|
-
var HARNESS_DIR = path45.join(os6.homedir(), ".harness");
|
|
7153
|
-
var MARKER_FILE = path45.join(HARNESS_DIR, ".setup-complete");
|
|
7502
|
+
var HARNESS_DIR = path47.join(os7.homedir(), ".harness");
|
|
7503
|
+
var MARKER_FILE = path47.join(HARNESS_DIR, ".setup-complete");
|
|
7154
7504
|
function isFirstRun() {
|
|
7155
|
-
return !
|
|
7505
|
+
return !fs29.existsSync(MARKER_FILE);
|
|
7156
7506
|
}
|
|
7157
7507
|
function markSetupComplete() {
|
|
7158
|
-
|
|
7159
|
-
|
|
7508
|
+
fs29.mkdirSync(HARNESS_DIR, { recursive: true });
|
|
7509
|
+
fs29.writeFileSync(MARKER_FILE, "", "utf-8");
|
|
7160
7510
|
}
|
|
7161
7511
|
function printFirstRunWelcome() {
|
|
7162
7512
|
try {
|
|
@@ -7169,19 +7519,19 @@ function printFirstRunWelcome() {
|
|
|
7169
7519
|
}
|
|
7170
7520
|
|
|
7171
7521
|
// src/commands/telemetry-wizard.ts
|
|
7172
|
-
import * as
|
|
7173
|
-
import * as
|
|
7522
|
+
import * as fs30 from "fs";
|
|
7523
|
+
import * as path48 from "path";
|
|
7174
7524
|
import readline2 from "readline";
|
|
7175
|
-
import
|
|
7525
|
+
import chalk11 from "chalk";
|
|
7176
7526
|
function prompt2(question) {
|
|
7177
7527
|
const rl = readline2.createInterface({
|
|
7178
7528
|
input: process.stdin,
|
|
7179
7529
|
output: process.stdout
|
|
7180
7530
|
});
|
|
7181
|
-
return new Promise((
|
|
7531
|
+
return new Promise((resolve40) => {
|
|
7182
7532
|
rl.question(question, (answer) => {
|
|
7183
7533
|
rl.close();
|
|
7184
|
-
|
|
7534
|
+
resolve40(answer.trim().toLowerCase());
|
|
7185
7535
|
});
|
|
7186
7536
|
});
|
|
7187
7537
|
}
|
|
@@ -7190,10 +7540,10 @@ function promptRaw(question) {
|
|
|
7190
7540
|
input: process.stdin,
|
|
7191
7541
|
output: process.stdout
|
|
7192
7542
|
});
|
|
7193
|
-
return new Promise((
|
|
7543
|
+
return new Promise((resolve40) => {
|
|
7194
7544
|
rl.question(question, (answer) => {
|
|
7195
7545
|
rl.close();
|
|
7196
|
-
|
|
7546
|
+
resolve40(answer.trim());
|
|
7197
7547
|
});
|
|
7198
7548
|
});
|
|
7199
7549
|
}
|
|
@@ -7201,21 +7551,21 @@ function isNo(answer) {
|
|
|
7201
7551
|
return answer === "n" || answer === "no";
|
|
7202
7552
|
}
|
|
7203
7553
|
function isTelemetryConfigured(cwd) {
|
|
7204
|
-
const configPath =
|
|
7554
|
+
const configPath = path48.join(cwd, "harness.config.json");
|
|
7205
7555
|
try {
|
|
7206
|
-
const raw = JSON.parse(
|
|
7556
|
+
const raw = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
|
|
7207
7557
|
return raw.telemetry !== void 0 && raw.adoption !== void 0;
|
|
7208
7558
|
} catch {
|
|
7209
7559
|
return false;
|
|
7210
7560
|
}
|
|
7211
7561
|
}
|
|
7212
7562
|
function writeTelemetryConfig(cwd, result) {
|
|
7213
|
-
const configPath =
|
|
7563
|
+
const configPath = path48.join(cwd, "harness.config.json");
|
|
7214
7564
|
try {
|
|
7215
|
-
const raw = JSON.parse(
|
|
7565
|
+
const raw = JSON.parse(fs30.readFileSync(configPath, "utf-8"));
|
|
7216
7566
|
raw.telemetry = { enabled: result.telemetryEnabled };
|
|
7217
7567
|
raw.adoption = { enabled: result.adoptionEnabled };
|
|
7218
|
-
|
|
7568
|
+
fs30.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
7219
7569
|
} catch (err) {
|
|
7220
7570
|
console.warn(
|
|
7221
7571
|
`\u26A0 Could not update ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -7223,15 +7573,15 @@ function writeTelemetryConfig(cwd, result) {
|
|
|
7223
7573
|
}
|
|
7224
7574
|
const hasIdentity = result.identity.project || result.identity.team || result.identity.alias;
|
|
7225
7575
|
if (hasIdentity) {
|
|
7226
|
-
const harnessDir =
|
|
7227
|
-
|
|
7228
|
-
const telemetryFile =
|
|
7229
|
-
|
|
7576
|
+
const harnessDir = path48.join(cwd, ".harness");
|
|
7577
|
+
fs30.mkdirSync(harnessDir, { recursive: true });
|
|
7578
|
+
const telemetryFile = path48.join(harnessDir, "telemetry.json");
|
|
7579
|
+
fs30.writeFileSync(telemetryFile, JSON.stringify({ identity: result.identity }, null, 2) + "\n");
|
|
7230
7580
|
}
|
|
7231
7581
|
}
|
|
7232
7582
|
async function promptIdentity() {
|
|
7233
7583
|
console.log(
|
|
7234
|
-
` ${
|
|
7584
|
+
` ${chalk11.cyan("Identity")} (optional) \u2014 tag telemetry with project/team/alias for filtering.`
|
|
7235
7585
|
);
|
|
7236
7586
|
console.log(" Leave blank to keep anonymous.");
|
|
7237
7587
|
const answer = await prompt2(" Set identity fields? (y/N) ");
|
|
@@ -7249,20 +7599,20 @@ async function promptIdentity() {
|
|
|
7249
7599
|
async function runTelemetryWizard() {
|
|
7250
7600
|
if (!process.stdin.isTTY) return null;
|
|
7251
7601
|
console.log("");
|
|
7252
|
-
console.log(` ${
|
|
7602
|
+
console.log(` ${chalk11.bold("Telemetry Configuration")}`);
|
|
7253
7603
|
console.log("");
|
|
7254
7604
|
console.log(" Harness can collect anonymous usage data to help improve the tool.");
|
|
7255
7605
|
console.log(" You can change these settings anytime in harness.config.json.");
|
|
7256
7606
|
console.log("");
|
|
7257
7607
|
console.log(
|
|
7258
|
-
` ${
|
|
7608
|
+
` ${chalk11.cyan("Anonymous telemetry")} \u2014 error rates, command usage, performance metrics.`
|
|
7259
7609
|
);
|
|
7260
7610
|
console.log(" No code, file contents, or personally identifiable info is sent.");
|
|
7261
7611
|
const telemetryAnswer = await prompt2(" Enable anonymous telemetry? (Y/n) ");
|
|
7262
7612
|
const telemetryEnabled = !isNo(telemetryAnswer);
|
|
7263
7613
|
console.log("");
|
|
7264
7614
|
console.log(
|
|
7265
|
-
` ${
|
|
7615
|
+
` ${chalk11.cyan("Adoption tracking")} \u2014 which skills are used, how often, and success rates.`
|
|
7266
7616
|
);
|
|
7267
7617
|
console.log(" Stored locally in .harness/adoption/. Never sent externally.");
|
|
7268
7618
|
const adoptionAnswer = await prompt2(" Enable local adoption tracking? (Y/n) ");
|
|
@@ -7272,8 +7622,8 @@ async function runTelemetryWizard() {
|
|
|
7272
7622
|
return { telemetryEnabled, adoptionEnabled, identity };
|
|
7273
7623
|
}
|
|
7274
7624
|
async function ensureTelemetryConfigured(cwd) {
|
|
7275
|
-
const configPath =
|
|
7276
|
-
if (!
|
|
7625
|
+
const configPath = path48.join(cwd, "harness.config.json");
|
|
7626
|
+
if (!fs30.existsSync(configPath)) {
|
|
7277
7627
|
return { status: "warn", message: "Not a harness project \u2014 skipped telemetry configuration" };
|
|
7278
7628
|
}
|
|
7279
7629
|
if (isTelemetryConfigured(cwd)) {
|
|
@@ -7319,7 +7669,7 @@ function runSlashCommandGeneration() {
|
|
|
7319
7669
|
}
|
|
7320
7670
|
}
|
|
7321
7671
|
function detectClient(dirName) {
|
|
7322
|
-
return
|
|
7672
|
+
return fs31.existsSync(path49.join(os8.homedir(), dirName));
|
|
7323
7673
|
}
|
|
7324
7674
|
async function runMcpSetup(cwd) {
|
|
7325
7675
|
const results = [];
|
|
@@ -7335,7 +7685,7 @@ async function runMcpSetup(cwd) {
|
|
|
7335
7685
|
{ name: "Cursor", dir: ".cursor", client: "cursor", configTarget: ".cursor/mcp.json" },
|
|
7336
7686
|
{
|
|
7337
7687
|
name: "OpenCode",
|
|
7338
|
-
dir:
|
|
7688
|
+
dir: path49.join(".config", "opencode"),
|
|
7339
7689
|
client: "opencode",
|
|
7340
7690
|
configTarget: "opencode.json"
|
|
7341
7691
|
}
|
|
@@ -7362,12 +7712,12 @@ async function runMcpSetup(cwd) {
|
|
|
7362
7712
|
return results;
|
|
7363
7713
|
}
|
|
7364
7714
|
function formatStep(result) {
|
|
7365
|
-
const icon = result.status === "pass" ?
|
|
7715
|
+
const icon = result.status === "pass" ? chalk12.green("\u2713") : result.status === "warn" ? chalk12.yellow("\u26A0") : chalk12.red("\u2717");
|
|
7366
7716
|
return ` ${icon} ${result.message}`;
|
|
7367
7717
|
}
|
|
7368
7718
|
function configureTier0Integrations(cwd) {
|
|
7369
7719
|
try {
|
|
7370
|
-
const mcpPath =
|
|
7720
|
+
const mcpPath = path49.join(cwd, ".mcp.json");
|
|
7371
7721
|
const config = readMcpConfig(mcpPath);
|
|
7372
7722
|
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
7373
7723
|
const added = [];
|
|
@@ -7376,9 +7726,9 @@ function configureTier0Integrations(cwd) {
|
|
|
7376
7726
|
writeMcpEntry(mcpPath, integration.name, integration.mcpConfig);
|
|
7377
7727
|
added.push(integration.displayName);
|
|
7378
7728
|
}
|
|
7379
|
-
const geminiDir =
|
|
7380
|
-
if (
|
|
7381
|
-
const geminiPath =
|
|
7729
|
+
const geminiDir = path49.join(cwd, ".gemini");
|
|
7730
|
+
if (fs31.existsSync(geminiDir)) {
|
|
7731
|
+
const geminiPath = path49.join(geminiDir, "settings.json");
|
|
7382
7732
|
const geminiConfig = readMcpConfig(geminiPath);
|
|
7383
7733
|
for (const integration of tier0) {
|
|
7384
7734
|
if (!geminiConfig.mcpServers[integration.name]) {
|
|
@@ -7386,9 +7736,9 @@ function configureTier0Integrations(cwd) {
|
|
|
7386
7736
|
}
|
|
7387
7737
|
}
|
|
7388
7738
|
}
|
|
7389
|
-
const opencodePath =
|
|
7390
|
-
const opencodeGlobalDir =
|
|
7391
|
-
if (
|
|
7739
|
+
const opencodePath = path49.join(cwd, "opencode.json");
|
|
7740
|
+
const opencodeGlobalDir = path49.join(os8.homedir(), ".config", "opencode");
|
|
7741
|
+
if (fs31.existsSync(opencodePath) || fs31.existsSync(opencodeGlobalDir)) {
|
|
7392
7742
|
for (const integration of tier0) {
|
|
7393
7743
|
writeOpencodeMcpEntry(opencodePath, integration.name, integration.mcpConfig);
|
|
7394
7744
|
}
|
|
@@ -7406,14 +7756,14 @@ function configureTier0Integrations(cwd) {
|
|
|
7406
7756
|
}
|
|
7407
7757
|
}
|
|
7408
7758
|
function ensureHooks(cwd) {
|
|
7409
|
-
const configPath =
|
|
7410
|
-
if (!
|
|
7759
|
+
const configPath = path49.join(cwd, "harness.config.json");
|
|
7760
|
+
if (!fs31.existsSync(configPath)) {
|
|
7411
7761
|
return { status: "warn", message: "Not a harness project \u2014 skipped hook installation" };
|
|
7412
7762
|
}
|
|
7413
7763
|
let profile = "standard";
|
|
7414
|
-
const profilePath =
|
|
7764
|
+
const profilePath = path49.join(cwd, ".harness", "hooks", "profile.json");
|
|
7415
7765
|
try {
|
|
7416
|
-
const data = JSON.parse(
|
|
7766
|
+
const data = JSON.parse(fs31.readFileSync(profilePath, "utf-8"));
|
|
7417
7767
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
7418
7768
|
profile = data.profile;
|
|
7419
7769
|
}
|
|
@@ -7484,10 +7834,10 @@ async function runSetup(cwd) {
|
|
|
7484
7834
|
return { steps, success };
|
|
7485
7835
|
}
|
|
7486
7836
|
function createSetupCommand() {
|
|
7487
|
-
return new
|
|
7837
|
+
return new Command64("setup").description("Configure harness environment: slash commands, MCP, and more").action(async () => {
|
|
7488
7838
|
const cwd = process.cwd();
|
|
7489
7839
|
console.log("");
|
|
7490
|
-
console.log(` ${
|
|
7840
|
+
console.log(` ${chalk12.bold("harness setup")}`);
|
|
7491
7841
|
console.log("");
|
|
7492
7842
|
const { steps, success } = await runSetup(cwd);
|
|
7493
7843
|
for (const step of steps) {
|
|
@@ -7507,15 +7857,15 @@ function createSetupCommand() {
|
|
|
7507
7857
|
|
|
7508
7858
|
// src/commands/share.ts
|
|
7509
7859
|
init_dist();
|
|
7510
|
-
import { Command as
|
|
7511
|
-
import * as
|
|
7512
|
-
import * as
|
|
7860
|
+
import { Command as Command65 } from "commander";
|
|
7861
|
+
import * as fs32 from "fs";
|
|
7862
|
+
import * as path50 from "path";
|
|
7513
7863
|
import { parse as parseYaml } from "yaml";
|
|
7514
7864
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
7515
7865
|
async function runShareAction(projectPath, options) {
|
|
7516
|
-
const rootDir =
|
|
7517
|
-
const manifestPath =
|
|
7518
|
-
if (!
|
|
7866
|
+
const rootDir = path50.resolve(projectPath);
|
|
7867
|
+
const manifestPath = path50.join(rootDir, MANIFEST_FILENAME);
|
|
7868
|
+
if (!fs32.existsSync(manifestPath)) {
|
|
7519
7869
|
logger.error(
|
|
7520
7870
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
7521
7871
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -7524,7 +7874,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
7524
7874
|
}
|
|
7525
7875
|
let parsed;
|
|
7526
7876
|
try {
|
|
7527
|
-
const raw =
|
|
7877
|
+
const raw = fs32.readFileSync(manifestPath, "utf-8");
|
|
7528
7878
|
parsed = parseYaml(raw);
|
|
7529
7879
|
} catch (err) {
|
|
7530
7880
|
logger.error(
|
|
@@ -7538,7 +7888,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
7538
7888
|
process.exit(1);
|
|
7539
7889
|
}
|
|
7540
7890
|
const manifest = manifestResult.value;
|
|
7541
|
-
const configResult = resolveConfig(
|
|
7891
|
+
const configResult = resolveConfig(path50.join(rootDir, "harness.config.json"));
|
|
7542
7892
|
if (!configResult.ok) {
|
|
7543
7893
|
logger.error(configResult.error.message);
|
|
7544
7894
|
process.exit(1);
|
|
@@ -7556,8 +7906,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
7556
7906
|
);
|
|
7557
7907
|
process.exit(1);
|
|
7558
7908
|
}
|
|
7559
|
-
const outputDir =
|
|
7560
|
-
const outputPath =
|
|
7909
|
+
const outputDir = path50.resolve(options.output);
|
|
7910
|
+
const outputPath = path50.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
7561
7911
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
7562
7912
|
if (!writeResult.ok) {
|
|
7563
7913
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -7566,28 +7916,28 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
7566
7916
|
logger.success(`Bundle written to ${outputPath}`);
|
|
7567
7917
|
}
|
|
7568
7918
|
function createShareCommand() {
|
|
7569
|
-
return new
|
|
7919
|
+
return new Command65("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) => {
|
|
7570
7920
|
await runShareAction(projectPath, options);
|
|
7571
7921
|
});
|
|
7572
7922
|
}
|
|
7573
7923
|
|
|
7574
7924
|
// src/commands/skill/index.ts
|
|
7575
|
-
import { Command as
|
|
7925
|
+
import { Command as Command73 } from "commander";
|
|
7576
7926
|
|
|
7577
7927
|
// src/commands/skill/list.ts
|
|
7578
|
-
import { Command as
|
|
7579
|
-
import * as
|
|
7580
|
-
import * as
|
|
7928
|
+
import { Command as Command66 } from "commander";
|
|
7929
|
+
import * as fs33 from "fs";
|
|
7930
|
+
import * as path51 from "path";
|
|
7581
7931
|
import { parse } from "yaml";
|
|
7582
7932
|
function scanDirectory(dirPath, source) {
|
|
7583
|
-
if (!
|
|
7584
|
-
const entries =
|
|
7933
|
+
if (!fs33.existsSync(dirPath)) return [];
|
|
7934
|
+
const entries = fs33.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
7585
7935
|
const skills = [];
|
|
7586
7936
|
for (const name of entries) {
|
|
7587
|
-
const yamlPath =
|
|
7588
|
-
if (!
|
|
7937
|
+
const yamlPath = path51.join(dirPath, name, "skill.yaml");
|
|
7938
|
+
if (!fs33.existsSync(yamlPath)) continue;
|
|
7589
7939
|
try {
|
|
7590
|
-
const raw =
|
|
7940
|
+
const raw = fs33.readFileSync(yamlPath, "utf-8");
|
|
7591
7941
|
const parsed = parse(raw);
|
|
7592
7942
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
7593
7943
|
if (result.success) {
|
|
@@ -7605,10 +7955,10 @@ function scanDirectory(dirPath, source) {
|
|
|
7605
7955
|
}
|
|
7606
7956
|
function collectCommunitySkills(seen, allSkills) {
|
|
7607
7957
|
const globalDir = resolveGlobalSkillsDir();
|
|
7608
|
-
const skillsDir =
|
|
7609
|
-
const communityBase =
|
|
7610
|
-
const communityPlatformDir =
|
|
7611
|
-
const lockfilePath =
|
|
7958
|
+
const skillsDir = path51.dirname(globalDir);
|
|
7959
|
+
const communityBase = path51.join(skillsDir, "community");
|
|
7960
|
+
const communityPlatformDir = path51.join(communityBase, "claude-code");
|
|
7961
|
+
const lockfilePath = path51.join(communityBase, "skills-lock.json");
|
|
7612
7962
|
const lockfile = readLockfile2(lockfilePath);
|
|
7613
7963
|
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
7614
7964
|
for (const skill of communitySkills) {
|
|
@@ -7678,7 +8028,7 @@ function printSkillsVerbose(skills) {
|
|
|
7678
8028
|
for (const s of skills) printSkillEntry(s);
|
|
7679
8029
|
}
|
|
7680
8030
|
function createListCommand3() {
|
|
7681
|
-
return new
|
|
8031
|
+
return new Command66("list").description("List available skills").option("--installed", "Show only community-installed skills").option("--local", "Show only project-local skills").option("--all", "Show all skills (default)").action(async (opts, cmd) => {
|
|
7682
8032
|
const globalOpts = cmd.optsWithGlobals();
|
|
7683
8033
|
const skills = collectSkills({ filter: resolveFilter(opts) });
|
|
7684
8034
|
if (globalOpts.json) {
|
|
@@ -7693,9 +8043,9 @@ function createListCommand3() {
|
|
|
7693
8043
|
}
|
|
7694
8044
|
|
|
7695
8045
|
// src/commands/skill/run.ts
|
|
7696
|
-
import { Command as
|
|
7697
|
-
import * as
|
|
7698
|
-
import * as
|
|
8046
|
+
import { Command as Command67 } from "commander";
|
|
8047
|
+
import * as fs34 from "fs";
|
|
8048
|
+
import * as path52 from "path";
|
|
7699
8049
|
import { parse as parse2 } from "yaml";
|
|
7700
8050
|
|
|
7701
8051
|
// src/skill/complexity.ts
|
|
@@ -7791,10 +8141,10 @@ ${options.principles}`);
|
|
|
7791
8141
|
|
|
7792
8142
|
// src/commands/skill/run.ts
|
|
7793
8143
|
function loadSkillMetadata(skillDir) {
|
|
7794
|
-
const yamlPath =
|
|
7795
|
-
if (!
|
|
8144
|
+
const yamlPath = path52.join(skillDir, "skill.yaml");
|
|
8145
|
+
if (!fs34.existsSync(yamlPath)) return null;
|
|
7796
8146
|
try {
|
|
7797
|
-
const result = SkillMetadataSchema.safeParse(parse2(
|
|
8147
|
+
const result = SkillMetadataSchema.safeParse(parse2(fs34.readFileSync(yamlPath, "utf-8")));
|
|
7798
8148
|
return result.success ? result.data : null;
|
|
7799
8149
|
} catch {
|
|
7800
8150
|
return null;
|
|
@@ -7806,22 +8156,22 @@ function resolveComplexity(metadata, requested, projectPath) {
|
|
|
7806
8156
|
return requested;
|
|
7807
8157
|
}
|
|
7808
8158
|
function loadPrinciples(projectPath) {
|
|
7809
|
-
const principlesPath =
|
|
7810
|
-
return
|
|
8159
|
+
const principlesPath = path52.join(projectPath, "docs", "principles.md");
|
|
8160
|
+
return fs34.existsSync(principlesPath) ? fs34.readFileSync(principlesPath, "utf-8") : void 0;
|
|
7811
8161
|
}
|
|
7812
8162
|
function readMostRecentFileInDir(dirPath) {
|
|
7813
|
-
const files =
|
|
7814
|
-
if (files.length > 0) return
|
|
8163
|
+
const files = fs34.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs34.statSync(path52.join(dirPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
8164
|
+
if (files.length > 0) return fs34.readFileSync(path52.join(dirPath, files[0].name), "utf-8");
|
|
7815
8165
|
return void 0;
|
|
7816
8166
|
}
|
|
7817
8167
|
function loadPriorState(metadata, projectPath) {
|
|
7818
8168
|
if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
|
|
7819
8169
|
for (const stateFilePath of metadata.state.files) {
|
|
7820
|
-
const fullPath =
|
|
7821
|
-
if (!
|
|
7822
|
-
const stat =
|
|
8170
|
+
const fullPath = path52.join(projectPath, stateFilePath);
|
|
8171
|
+
if (!fs34.existsSync(fullPath)) continue;
|
|
8172
|
+
const stat = fs34.statSync(fullPath);
|
|
7823
8173
|
if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
|
|
7824
|
-
return
|
|
8174
|
+
return fs34.readFileSync(fullPath, "utf-8");
|
|
7825
8175
|
}
|
|
7826
8176
|
return void 0;
|
|
7827
8177
|
}
|
|
@@ -7841,9 +8191,9 @@ function resolvePhaseState(metadata, projectPath, phase) {
|
|
|
7841
8191
|
}
|
|
7842
8192
|
function appendProjectState(content, metadata, projectPath, hasPathOpt) {
|
|
7843
8193
|
if (!metadata?.state.persistent || !hasPathOpt) return content;
|
|
7844
|
-
const stateFile =
|
|
7845
|
-
if (!
|
|
7846
|
-
const stateContent =
|
|
8194
|
+
const stateFile = path52.join(projectPath, ".harness", "state.json");
|
|
8195
|
+
if (!fs34.existsSync(stateFile)) return content;
|
|
8196
|
+
const stateContent = fs34.readFileSync(stateFile, "utf-8");
|
|
7847
8197
|
return content + `
|
|
7848
8198
|
|
|
7849
8199
|
---
|
|
@@ -7855,14 +8205,14 @@ ${stateContent}
|
|
|
7855
8205
|
}
|
|
7856
8206
|
async function runSkill(name, opts) {
|
|
7857
8207
|
const skillsDir = resolveSkillsDir();
|
|
7858
|
-
const skillDir =
|
|
7859
|
-
if (!
|
|
8208
|
+
const skillDir = path52.join(skillsDir, name);
|
|
8209
|
+
if (!fs34.existsSync(skillDir)) {
|
|
7860
8210
|
logger.error(`Skill not found: ${name}`);
|
|
7861
8211
|
process.exit(ExitCode.ERROR);
|
|
7862
8212
|
return;
|
|
7863
8213
|
}
|
|
7864
8214
|
const metadata = loadSkillMetadata(skillDir);
|
|
7865
|
-
const projectPath = opts.path ?
|
|
8215
|
+
const projectPath = opts.path ? path52.resolve(opts.path) : process.cwd();
|
|
7866
8216
|
const complexity = resolveComplexity(
|
|
7867
8217
|
metadata,
|
|
7868
8218
|
opts.complexity ?? "standard",
|
|
@@ -7889,14 +8239,14 @@ async function runSkill(name, opts) {
|
|
|
7889
8239
|
...stateWarning !== void 0 && { stateWarning },
|
|
7890
8240
|
...opts.party !== void 0 && { party: opts.party }
|
|
7891
8241
|
});
|
|
7892
|
-
const skillMdPath =
|
|
7893
|
-
if (!
|
|
8242
|
+
const skillMdPath = path52.join(skillDir, "SKILL.md");
|
|
8243
|
+
if (!fs34.existsSync(skillMdPath)) {
|
|
7894
8244
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
7895
8245
|
process.exit(ExitCode.ERROR);
|
|
7896
8246
|
return;
|
|
7897
8247
|
}
|
|
7898
8248
|
const content = appendProjectState(
|
|
7899
|
-
|
|
8249
|
+
fs34.readFileSync(skillMdPath, "utf-8"),
|
|
7900
8250
|
metadata,
|
|
7901
8251
|
projectPath,
|
|
7902
8252
|
!!opts.path
|
|
@@ -7905,13 +8255,13 @@ async function runSkill(name, opts) {
|
|
|
7905
8255
|
process.exit(ExitCode.SUCCESS);
|
|
7906
8256
|
}
|
|
7907
8257
|
function createRunCommand3() {
|
|
7908
|
-
return new
|
|
8258
|
+
return new Command67("run").description("Run a skill (outputs SKILL.md content with context preamble)").argument("<name>", "Skill name (e.g., harness-tdd)").option("--path <path>", "Project root path for context injection").option("--complexity <level>", "Rigor level: fast, standard, thorough", "standard").option("--phase <name>", "Start at a specific phase (for re-entry)").option("--party", "Enable multi-perspective evaluation").action(async (name, opts) => runSkill(name, opts));
|
|
7909
8259
|
}
|
|
7910
8260
|
|
|
7911
8261
|
// src/commands/skill/validate.ts
|
|
7912
|
-
import { Command as
|
|
7913
|
-
import * as
|
|
7914
|
-
import * as
|
|
8262
|
+
import { Command as Command68 } from "commander";
|
|
8263
|
+
import * as fs35 from "fs";
|
|
8264
|
+
import * as path53 from "path";
|
|
7915
8265
|
import { parse as parse3 } from "yaml";
|
|
7916
8266
|
var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
7917
8267
|
"## When to Use",
|
|
@@ -7923,11 +8273,11 @@ var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
|
7923
8273
|
];
|
|
7924
8274
|
var KNOWLEDGE_REQUIRED_SECTIONS = ["## Instructions"];
|
|
7925
8275
|
function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
7926
|
-
if (!
|
|
8276
|
+
if (!fs35.existsSync(skillMdPath)) {
|
|
7927
8277
|
errors.push(`${name}: missing SKILL.md`);
|
|
7928
8278
|
return;
|
|
7929
8279
|
}
|
|
7930
|
-
const mdContent =
|
|
8280
|
+
const mdContent = fs35.readFileSync(skillMdPath, "utf-8");
|
|
7931
8281
|
if (!mdContent.trim().startsWith("# ")) {
|
|
7932
8282
|
errors.push(`${name}/SKILL.md: must start with an h1 heading`);
|
|
7933
8283
|
}
|
|
@@ -7952,20 +8302,20 @@ function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
|
7952
8302
|
}
|
|
7953
8303
|
}
|
|
7954
8304
|
function validateSkillEntry(name, skillsDir, errors) {
|
|
7955
|
-
const skillDir =
|
|
7956
|
-
const yamlPath =
|
|
7957
|
-
if (!
|
|
8305
|
+
const skillDir = path53.join(skillsDir, name);
|
|
8306
|
+
const yamlPath = path53.join(skillDir, "skill.yaml");
|
|
8307
|
+
if (!fs35.existsSync(yamlPath)) {
|
|
7958
8308
|
errors.push(`${name}: missing skill.yaml`);
|
|
7959
8309
|
return false;
|
|
7960
8310
|
}
|
|
7961
8311
|
try {
|
|
7962
|
-
const raw =
|
|
8312
|
+
const raw = fs35.readFileSync(yamlPath, "utf-8");
|
|
7963
8313
|
const result = SkillMetadataSchema.safeParse(parse3(raw));
|
|
7964
8314
|
if (!result.success) {
|
|
7965
8315
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
7966
8316
|
return false;
|
|
7967
8317
|
}
|
|
7968
|
-
validateSkillMd(name,
|
|
8318
|
+
validateSkillMd(name, path53.join(skillDir, "SKILL.md"), result.data.type, errors);
|
|
7969
8319
|
return true;
|
|
7970
8320
|
} catch (e) {
|
|
7971
8321
|
errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7973,15 +8323,15 @@ function validateSkillEntry(name, skillsDir, errors) {
|
|
|
7973
8323
|
}
|
|
7974
8324
|
}
|
|
7975
8325
|
function createValidateCommand2() {
|
|
7976
|
-
return new
|
|
8326
|
+
return new Command68("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
7977
8327
|
const globalOpts = cmd.optsWithGlobals();
|
|
7978
8328
|
const skillsDir = resolveSkillsDir();
|
|
7979
|
-
if (!
|
|
8329
|
+
if (!fs35.existsSync(skillsDir)) {
|
|
7980
8330
|
logger.info("No skills directory found.");
|
|
7981
8331
|
process.exit(ExitCode.SUCCESS);
|
|
7982
8332
|
return;
|
|
7983
8333
|
}
|
|
7984
|
-
const entries =
|
|
8334
|
+
const entries = fs35.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
7985
8335
|
const errors = [];
|
|
7986
8336
|
let validated = 0;
|
|
7987
8337
|
for (const name of entries) {
|
|
@@ -8003,24 +8353,24 @@ function createValidateCommand2() {
|
|
|
8003
8353
|
}
|
|
8004
8354
|
|
|
8005
8355
|
// src/commands/skill/info.ts
|
|
8006
|
-
import { Command as
|
|
8007
|
-
import * as
|
|
8008
|
-
import * as
|
|
8356
|
+
import { Command as Command69 } from "commander";
|
|
8357
|
+
import * as fs36 from "fs";
|
|
8358
|
+
import * as path54 from "path";
|
|
8009
8359
|
import { parse as parse4 } from "yaml";
|
|
8010
8360
|
function loadSkillMetadata2(name) {
|
|
8011
8361
|
const skillsDir = resolveSkillsDir();
|
|
8012
|
-
const skillDir =
|
|
8013
|
-
if (!
|
|
8362
|
+
const skillDir = path54.join(skillsDir, name);
|
|
8363
|
+
if (!fs36.existsSync(skillDir)) {
|
|
8014
8364
|
logger.error(`Skill not found: ${name}`);
|
|
8015
8365
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
8016
8366
|
}
|
|
8017
|
-
const yamlPath =
|
|
8018
|
-
if (!
|
|
8367
|
+
const yamlPath = path54.join(skillDir, "skill.yaml");
|
|
8368
|
+
if (!fs36.existsSync(yamlPath)) {
|
|
8019
8369
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
8020
8370
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
8021
8371
|
}
|
|
8022
8372
|
try {
|
|
8023
|
-
const raw =
|
|
8373
|
+
const raw = fs36.readFileSync(yamlPath, "utf-8");
|
|
8024
8374
|
const parsed = parse4(raw);
|
|
8025
8375
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
8026
8376
|
if (!result.success) {
|
|
@@ -8049,7 +8399,7 @@ function printSkillInfo(skill) {
|
|
|
8049
8399
|
console.log(`Persistent: ${skill.state.persistent}`);
|
|
8050
8400
|
}
|
|
8051
8401
|
function createInfoCommand() {
|
|
8052
|
-
return new
|
|
8402
|
+
return new Command69("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
8053
8403
|
const globalOpts = cmd.optsWithGlobals();
|
|
8054
8404
|
const loaded = loadSkillMetadata2(name);
|
|
8055
8405
|
if (!loaded.ok) {
|
|
@@ -8066,7 +8416,7 @@ function createInfoCommand() {
|
|
|
8066
8416
|
}
|
|
8067
8417
|
|
|
8068
8418
|
// src/commands/skill/search.ts
|
|
8069
|
-
import { Command as
|
|
8419
|
+
import { Command as Command70 } from "commander";
|
|
8070
8420
|
async function runSearch(query, opts) {
|
|
8071
8421
|
const results = await searchNpmRegistry(query, opts.registry);
|
|
8072
8422
|
return results.filter((r) => {
|
|
@@ -8111,15 +8461,15 @@ async function runSearchAction(query, opts, globalOpts) {
|
|
|
8111
8461
|
}
|
|
8112
8462
|
}
|
|
8113
8463
|
function createSearchCommand() {
|
|
8114
|
-
return new
|
|
8464
|
+
return new Command70("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) => {
|
|
8115
8465
|
await runSearchAction(query, opts, cmd.optsWithGlobals());
|
|
8116
8466
|
});
|
|
8117
8467
|
}
|
|
8118
8468
|
|
|
8119
8469
|
// src/commands/skill/create.ts
|
|
8120
|
-
import { Command as
|
|
8121
|
-
import * as
|
|
8122
|
-
import * as
|
|
8470
|
+
import { Command as Command71 } from "commander";
|
|
8471
|
+
import * as path55 from "path";
|
|
8472
|
+
import * as fs37 from "fs";
|
|
8123
8473
|
import YAML from "yaml";
|
|
8124
8474
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
8125
8475
|
function buildReadme(name, description) {
|
|
@@ -8203,22 +8553,22 @@ function runCreate(name, opts) {
|
|
|
8203
8553
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
8204
8554
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
8205
8555
|
}
|
|
8206
|
-
const baseDir = opts.outputDir ??
|
|
8207
|
-
const skillDir =
|
|
8208
|
-
if (
|
|
8556
|
+
const baseDir = opts.outputDir ?? path55.join(process.cwd(), "agents", "skills", "claude-code");
|
|
8557
|
+
const skillDir = path55.join(baseDir, name);
|
|
8558
|
+
if (fs37.existsSync(skillDir)) {
|
|
8209
8559
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
8210
8560
|
}
|
|
8211
|
-
|
|
8561
|
+
fs37.mkdirSync(skillDir, { recursive: true });
|
|
8212
8562
|
const description = opts.description || `A community skill: ${name}`;
|
|
8213
8563
|
const skillYaml = buildSkillYaml(name, opts);
|
|
8214
|
-
const skillYamlPath =
|
|
8215
|
-
|
|
8564
|
+
const skillYamlPath = path55.join(skillDir, "skill.yaml");
|
|
8565
|
+
fs37.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
8216
8566
|
const skillMd = buildSkillMd(name, description);
|
|
8217
|
-
const skillMdPath =
|
|
8218
|
-
|
|
8567
|
+
const skillMdPath = path55.join(skillDir, "SKILL.md");
|
|
8568
|
+
fs37.writeFileSync(skillMdPath, skillMd);
|
|
8219
8569
|
const readme = buildReadme(name, description);
|
|
8220
|
-
const readmePath =
|
|
8221
|
-
|
|
8570
|
+
const readmePath = path55.join(skillDir, "README.md");
|
|
8571
|
+
fs37.writeFileSync(readmePath, readme);
|
|
8222
8572
|
return {
|
|
8223
8573
|
name,
|
|
8224
8574
|
directory: skillDir,
|
|
@@ -8232,12 +8582,12 @@ function printCreateResult(name, result) {
|
|
|
8232
8582
|
}
|
|
8233
8583
|
logger.info(`
|
|
8234
8584
|
Next steps:`);
|
|
8235
|
-
logger.info(` 1. Edit ${
|
|
8585
|
+
logger.info(` 1. Edit ${path55.join(result.directory, "SKILL.md")} with your skill content`);
|
|
8236
8586
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
8237
8587
|
logger.info(` 3. Run: harness skills publish`);
|
|
8238
8588
|
}
|
|
8239
8589
|
function createCreateCommand() {
|
|
8240
|
-
return new
|
|
8590
|
+
return new Command71("create").description("Scaffold a new community skill").argument("<name>", "Skill name (kebab-case)").option("--description <desc>", "Skill description").option("--type <type>", "Skill type: rigid or flexible", "flexible").option("--platforms <platforms>", "Comma-separated platforms (default: claude-code)").option("--triggers <triggers>", "Comma-separated triggers (default: manual)").option("--output-dir <dir>", "Output directory (default: agents/skills/claude-code/)").action(async (name, opts, cmd) => {
|
|
8241
8591
|
const globalOpts = cmd.optsWithGlobals();
|
|
8242
8592
|
try {
|
|
8243
8593
|
const result = runCreate(name, {
|
|
@@ -8260,26 +8610,26 @@ function createCreateCommand() {
|
|
|
8260
8610
|
}
|
|
8261
8611
|
|
|
8262
8612
|
// src/commands/skill/publish.ts
|
|
8263
|
-
import { Command as
|
|
8264
|
-
import * as
|
|
8265
|
-
import * as
|
|
8613
|
+
import { Command as Command72 } from "commander";
|
|
8614
|
+
import * as fs39 from "fs";
|
|
8615
|
+
import * as path57 from "path";
|
|
8266
8616
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
8267
8617
|
|
|
8268
8618
|
// src/registry/validator.ts
|
|
8269
|
-
import * as
|
|
8270
|
-
import * as
|
|
8619
|
+
import * as fs38 from "fs";
|
|
8620
|
+
import * as path56 from "path";
|
|
8271
8621
|
import { parse as parse5 } from "yaml";
|
|
8272
8622
|
import semver4 from "semver";
|
|
8273
8623
|
async function validateForPublish(skillDir, registryUrl) {
|
|
8274
8624
|
const errors = [];
|
|
8275
|
-
const skillYamlPath =
|
|
8276
|
-
if (!
|
|
8625
|
+
const skillYamlPath = path56.join(skillDir, "skill.yaml");
|
|
8626
|
+
if (!fs38.existsSync(skillYamlPath)) {
|
|
8277
8627
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
8278
8628
|
return { valid: false, errors };
|
|
8279
8629
|
}
|
|
8280
8630
|
let skillMeta;
|
|
8281
8631
|
try {
|
|
8282
|
-
const raw =
|
|
8632
|
+
const raw = fs38.readFileSync(skillYamlPath, "utf-8");
|
|
8283
8633
|
const parsed = parse5(raw);
|
|
8284
8634
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
8285
8635
|
if (!result.success) {
|
|
@@ -8301,11 +8651,11 @@ async function validateForPublish(skillDir, registryUrl) {
|
|
|
8301
8651
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
8302
8652
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
8303
8653
|
}
|
|
8304
|
-
const skillMdPath =
|
|
8305
|
-
if (!
|
|
8654
|
+
const skillMdPath = path56.join(skillDir, "SKILL.md");
|
|
8655
|
+
if (!fs38.existsSync(skillMdPath)) {
|
|
8306
8656
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
8307
8657
|
} else {
|
|
8308
|
-
const content =
|
|
8658
|
+
const content = fs38.readFileSync(skillMdPath, "utf-8");
|
|
8309
8659
|
if (!content.includes("## When to Use")) {
|
|
8310
8660
|
errors.push('SKILL.md must contain a "## When to Use" section.');
|
|
8311
8661
|
}
|
|
@@ -8385,11 +8735,11 @@ ${errorList}`);
|
|
|
8385
8735
|
}
|
|
8386
8736
|
const meta = validation.skillMeta;
|
|
8387
8737
|
const pkg = derivePackageJson(meta);
|
|
8388
|
-
const pkgPath =
|
|
8389
|
-
|
|
8390
|
-
const readmePath =
|
|
8391
|
-
if (!
|
|
8392
|
-
const skillMdContent =
|
|
8738
|
+
const pkgPath = path57.join(skillDir, "package.json");
|
|
8739
|
+
fs39.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
8740
|
+
const readmePath = path57.join(skillDir, "README.md");
|
|
8741
|
+
if (!fs39.existsSync(readmePath)) {
|
|
8742
|
+
const skillMdContent = fs39.readFileSync(path57.join(skillDir, "SKILL.md"), "utf-8");
|
|
8393
8743
|
const readme = `# ${pkg.name}
|
|
8394
8744
|
|
|
8395
8745
|
${meta.description}
|
|
@@ -8403,7 +8753,7 @@ harness install ${meta.name}
|
|
|
8403
8753
|
---
|
|
8404
8754
|
|
|
8405
8755
|
${skillMdContent}`;
|
|
8406
|
-
|
|
8756
|
+
fs39.writeFileSync(readmePath, readme);
|
|
8407
8757
|
}
|
|
8408
8758
|
if (opts.dryRun) {
|
|
8409
8759
|
return {
|
|
@@ -8429,7 +8779,7 @@ ${skillMdContent}`;
|
|
|
8429
8779
|
};
|
|
8430
8780
|
}
|
|
8431
8781
|
function createPublishCommand() {
|
|
8432
|
-
return new
|
|
8782
|
+
return new Command72("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) => {
|
|
8433
8783
|
const globalOpts = cmd.optsWithGlobals();
|
|
8434
8784
|
const skillDir = opts.dir || process.cwd();
|
|
8435
8785
|
try {
|
|
@@ -8454,7 +8804,7 @@ function createPublishCommand() {
|
|
|
8454
8804
|
|
|
8455
8805
|
// src/commands/skill/index.ts
|
|
8456
8806
|
function createSkillCommand() {
|
|
8457
|
-
const command = new
|
|
8807
|
+
const command = new Command73("skill").description("Skill management commands");
|
|
8458
8808
|
command.addCommand(createListCommand3());
|
|
8459
8809
|
command.addCommand(createRunCommand3());
|
|
8460
8810
|
command.addCommand(createValidateCommand2());
|
|
@@ -8467,9 +8817,9 @@ function createSkillCommand() {
|
|
|
8467
8817
|
|
|
8468
8818
|
// src/commands/snapshot.ts
|
|
8469
8819
|
init_dist();
|
|
8470
|
-
import { Command as
|
|
8820
|
+
import { Command as Command74 } from "commander";
|
|
8471
8821
|
import { execSync as execSync5 } from "child_process";
|
|
8472
|
-
import
|
|
8822
|
+
import chalk13 from "chalk";
|
|
8473
8823
|
function getCommitHash3(cwd) {
|
|
8474
8824
|
try {
|
|
8475
8825
|
return execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).toString().trim();
|
|
@@ -8494,9 +8844,9 @@ function formatDelta(delta) {
|
|
|
8494
8844
|
function directionSymbol(direction) {
|
|
8495
8845
|
switch (direction) {
|
|
8496
8846
|
case "improving":
|
|
8497
|
-
return
|
|
8847
|
+
return chalk13.green("improving");
|
|
8498
8848
|
case "declining":
|
|
8499
|
-
return
|
|
8849
|
+
return chalk13.red("declining");
|
|
8500
8850
|
case "stable":
|
|
8501
8851
|
return "=";
|
|
8502
8852
|
}
|
|
@@ -8649,7 +8999,7 @@ function registerListCommand(parent) {
|
|
|
8649
8999
|
});
|
|
8650
9000
|
}
|
|
8651
9001
|
function createSnapshotCommand() {
|
|
8652
|
-
const command = new
|
|
9002
|
+
const command = new Command74("snapshot").description("Architecture timeline snapshot commands");
|
|
8653
9003
|
registerCaptureCommand(command);
|
|
8654
9004
|
registerTrendsCommand(command);
|
|
8655
9005
|
registerListCommand(command);
|
|
@@ -8657,12 +9007,12 @@ function createSnapshotCommand() {
|
|
|
8657
9007
|
}
|
|
8658
9008
|
|
|
8659
9009
|
// src/commands/state/index.ts
|
|
8660
|
-
import { Command as
|
|
9010
|
+
import { Command as Command79 } from "commander";
|
|
8661
9011
|
|
|
8662
9012
|
// src/commands/state/show.ts
|
|
8663
9013
|
init_dist();
|
|
8664
|
-
import { Command as
|
|
8665
|
-
import * as
|
|
9014
|
+
import { Command as Command75 } from "commander";
|
|
9015
|
+
import * as path58 from "path";
|
|
8666
9016
|
function printStateText(state, stream) {
|
|
8667
9017
|
if (stream) console.log(`Stream: ${stream}`);
|
|
8668
9018
|
console.log(`Schema Version: ${state.schemaVersion}`);
|
|
@@ -8686,9 +9036,9 @@ function printStateProgress(progress) {
|
|
|
8686
9036
|
}
|
|
8687
9037
|
}
|
|
8688
9038
|
function createShowCommand() {
|
|
8689
|
-
return new
|
|
9039
|
+
return new Command75("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
8690
9040
|
const globalOpts = cmd.optsWithGlobals();
|
|
8691
|
-
const result = await loadState(
|
|
9041
|
+
const result = await loadState(path58.resolve(opts.path), opts.stream);
|
|
8692
9042
|
if (!result.ok) {
|
|
8693
9043
|
logger.error(result.error.message);
|
|
8694
9044
|
process.exit(ExitCode.ERROR);
|
|
@@ -8704,9 +9054,9 @@ function createShowCommand() {
|
|
|
8704
9054
|
|
|
8705
9055
|
// src/commands/state/reset.ts
|
|
8706
9056
|
init_dist();
|
|
8707
|
-
import { Command as
|
|
8708
|
-
import * as
|
|
8709
|
-
import * as
|
|
9057
|
+
import { Command as Command76 } from "commander";
|
|
9058
|
+
import * as fs40 from "fs";
|
|
9059
|
+
import * as path59 from "path";
|
|
8710
9060
|
import * as readline3 from "readline";
|
|
8711
9061
|
async function resolveStatePath(projectPath, stream) {
|
|
8712
9062
|
if (stream) {
|
|
@@ -8715,22 +9065,22 @@ async function resolveStatePath(projectPath, stream) {
|
|
|
8715
9065
|
logger.error(streamResult.error.message);
|
|
8716
9066
|
process.exit(ExitCode.ERROR);
|
|
8717
9067
|
}
|
|
8718
|
-
return
|
|
9068
|
+
return path59.join(streamResult.value, "state.json");
|
|
8719
9069
|
}
|
|
8720
|
-
return
|
|
9070
|
+
return path59.join(projectPath, ".harness", "state.json");
|
|
8721
9071
|
}
|
|
8722
9072
|
async function confirmReset() {
|
|
8723
9073
|
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
8724
|
-
const answer = await new Promise((
|
|
8725
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
9074
|
+
const answer = await new Promise((resolve40) => {
|
|
9075
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve40);
|
|
8726
9076
|
});
|
|
8727
9077
|
rl.close();
|
|
8728
9078
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
8729
9079
|
}
|
|
8730
9080
|
async function runReset(opts) {
|
|
8731
|
-
const projectPath =
|
|
9081
|
+
const projectPath = path59.resolve(opts.path);
|
|
8732
9082
|
const statePath = await resolveStatePath(projectPath, opts.stream);
|
|
8733
|
-
if (!
|
|
9083
|
+
if (!fs40.existsSync(statePath)) {
|
|
8734
9084
|
logger.info("No state file found. Nothing to reset.");
|
|
8735
9085
|
process.exit(ExitCode.SUCCESS);
|
|
8736
9086
|
}
|
|
@@ -8739,7 +9089,7 @@ async function runReset(opts) {
|
|
|
8739
9089
|
process.exit(ExitCode.SUCCESS);
|
|
8740
9090
|
}
|
|
8741
9091
|
try {
|
|
8742
|
-
|
|
9092
|
+
fs40.unlinkSync(statePath);
|
|
8743
9093
|
logger.success("Project state reset.");
|
|
8744
9094
|
} catch (e) {
|
|
8745
9095
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -8748,16 +9098,16 @@ async function runReset(opts) {
|
|
|
8748
9098
|
process.exit(ExitCode.SUCCESS);
|
|
8749
9099
|
}
|
|
8750
9100
|
function createResetCommand() {
|
|
8751
|
-
return new
|
|
9101
|
+
return new Command76("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) => runReset(opts));
|
|
8752
9102
|
}
|
|
8753
9103
|
|
|
8754
9104
|
// src/commands/state/learn.ts
|
|
8755
9105
|
init_dist();
|
|
8756
|
-
import { Command as
|
|
8757
|
-
import * as
|
|
9106
|
+
import { Command as Command77 } from "commander";
|
|
9107
|
+
import * as path60 from "path";
|
|
8758
9108
|
function createLearnCommand() {
|
|
8759
|
-
return new
|
|
8760
|
-
const projectPath =
|
|
9109
|
+
return new Command77("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) => {
|
|
9110
|
+
const projectPath = path60.resolve(opts.path);
|
|
8761
9111
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
8762
9112
|
if (!result.ok) {
|
|
8763
9113
|
logger.error(result.error.message);
|
|
@@ -8771,10 +9121,10 @@ function createLearnCommand() {
|
|
|
8771
9121
|
|
|
8772
9122
|
// src/commands/state/streams.ts
|
|
8773
9123
|
init_dist();
|
|
8774
|
-
import { Command as
|
|
8775
|
-
import * as
|
|
9124
|
+
import { Command as Command78 } from "commander";
|
|
9125
|
+
import * as path61 from "path";
|
|
8776
9126
|
async function runListStreams(opts, globalOpts) {
|
|
8777
|
-
const projectPath =
|
|
9127
|
+
const projectPath = path61.resolve(opts.path);
|
|
8778
9128
|
const indexResult = await loadStreamIndex(projectPath);
|
|
8779
9129
|
const result = await listStreams(projectPath);
|
|
8780
9130
|
if (!result.ok) {
|
|
@@ -8795,7 +9145,7 @@ async function runListStreams(opts, globalOpts) {
|
|
|
8795
9145
|
process.exit(ExitCode.SUCCESS);
|
|
8796
9146
|
}
|
|
8797
9147
|
async function runCreateStream(name, opts) {
|
|
8798
|
-
const result = await createStream(
|
|
9148
|
+
const result = await createStream(path61.resolve(opts.path), name, opts.branch);
|
|
8799
9149
|
if (!result.ok) {
|
|
8800
9150
|
logger.error(result.error.message);
|
|
8801
9151
|
process.exit(ExitCode.ERROR);
|
|
@@ -8804,7 +9154,7 @@ async function runCreateStream(name, opts) {
|
|
|
8804
9154
|
process.exit(ExitCode.SUCCESS);
|
|
8805
9155
|
}
|
|
8806
9156
|
async function runArchiveStream(name, opts) {
|
|
8807
|
-
const result = await archiveStream(
|
|
9157
|
+
const result = await archiveStream(path61.resolve(opts.path), name);
|
|
8808
9158
|
if (!result.ok) {
|
|
8809
9159
|
logger.error(result.error.message);
|
|
8810
9160
|
process.exit(ExitCode.ERROR);
|
|
@@ -8813,7 +9163,7 @@ async function runArchiveStream(name, opts) {
|
|
|
8813
9163
|
process.exit(ExitCode.SUCCESS);
|
|
8814
9164
|
}
|
|
8815
9165
|
async function runActivateStream(name, opts) {
|
|
8816
|
-
const result = await setActiveStream(
|
|
9166
|
+
const result = await setActiveStream(path61.resolve(opts.path), name);
|
|
8817
9167
|
if (!result.ok) {
|
|
8818
9168
|
logger.error(result.error.message);
|
|
8819
9169
|
process.exit(ExitCode.ERROR);
|
|
@@ -8822,7 +9172,7 @@ async function runActivateStream(name, opts) {
|
|
|
8822
9172
|
process.exit(ExitCode.SUCCESS);
|
|
8823
9173
|
}
|
|
8824
9174
|
function createStreamsCommand() {
|
|
8825
|
-
const command = new
|
|
9175
|
+
const command = new Command78("streams").description("Manage state streams");
|
|
8826
9176
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => runListStreams(opts, cmd.optsWithGlobals()));
|
|
8827
9177
|
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) => runCreateStream(name, opts));
|
|
8828
9178
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => runArchiveStream(name, opts));
|
|
@@ -8832,7 +9182,7 @@ function createStreamsCommand() {
|
|
|
8832
9182
|
|
|
8833
9183
|
// src/commands/state/index.ts
|
|
8834
9184
|
function createStateCommand() {
|
|
8835
|
-
const command = new
|
|
9185
|
+
const command = new Command79("state").description("Project state management commands");
|
|
8836
9186
|
command.addCommand(createShowCommand());
|
|
8837
9187
|
command.addCommand(createResetCommand());
|
|
8838
9188
|
command.addCommand(createLearnCommand());
|
|
@@ -8841,9 +9191,9 @@ function createStateCommand() {
|
|
|
8841
9191
|
}
|
|
8842
9192
|
|
|
8843
9193
|
// src/commands/sync-analyses.ts
|
|
8844
|
-
import * as
|
|
8845
|
-
import * as
|
|
8846
|
-
import { Command as
|
|
9194
|
+
import * as fs41 from "fs";
|
|
9195
|
+
import * as path62 from "path";
|
|
9196
|
+
import { Command as Command80 } from "commander";
|
|
8847
9197
|
init_dist();
|
|
8848
9198
|
function isValidRecord(record) {
|
|
8849
9199
|
return typeof record.issueId === "string" && !!record.issueId && typeof record.identifier === "string" && !!record.identifier && typeof record.analyzedAt === "string" && !/[/\\]/.test(record.issueId);
|
|
@@ -8869,7 +9219,7 @@ function extractAnalysisFromComments(comments) {
|
|
|
8869
9219
|
return null;
|
|
8870
9220
|
}
|
|
8871
9221
|
function bootstrapTrackerCommand2(opts, verb) {
|
|
8872
|
-
const projectPath =
|
|
9222
|
+
const projectPath = path62.resolve(opts.dir);
|
|
8873
9223
|
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
8874
9224
|
if (!trackerConfig) {
|
|
8875
9225
|
logger.error(`No tracker config found in harness.config.json. Cannot ${verb}.`);
|
|
@@ -8881,8 +9231,8 @@ function bootstrapTrackerCommand2(opts, verb) {
|
|
|
8881
9231
|
);
|
|
8882
9232
|
return null;
|
|
8883
9233
|
}
|
|
8884
|
-
const projectEnvPath =
|
|
8885
|
-
if (
|
|
9234
|
+
const projectEnvPath = path62.join(projectPath, ".env");
|
|
9235
|
+
if (fs41.existsSync(projectEnvPath) && !process.env.GITHUB_TOKEN) {
|
|
8886
9236
|
__require("dotenv").config({ path: projectEnvPath });
|
|
8887
9237
|
}
|
|
8888
9238
|
const token = process.env.GITHUB_TOKEN;
|
|
@@ -8892,14 +9242,33 @@ function bootstrapTrackerCommand2(opts, verb) {
|
|
|
8892
9242
|
}
|
|
8893
9243
|
return { token, projectPath, trackerConfig };
|
|
8894
9244
|
}
|
|
8895
|
-
function loadRoadmapFeatures(projectPath) {
|
|
8896
|
-
const
|
|
8897
|
-
if (
|
|
9245
|
+
async function loadRoadmapFeatures(projectPath) {
|
|
9246
|
+
const mode = loadProjectRoadmapMode(projectPath);
|
|
9247
|
+
if (mode === "file-less") {
|
|
9248
|
+
const trackerCfg = loadTrackerClientConfigFromProject(projectPath);
|
|
9249
|
+
if (!trackerCfg.ok) {
|
|
9250
|
+
logger.error(`File-less mode: ${trackerCfg.error.message}`);
|
|
9251
|
+
return null;
|
|
9252
|
+
}
|
|
9253
|
+
const clientR = createTrackerClient(trackerCfg.value);
|
|
9254
|
+
if (!clientR.ok) {
|
|
9255
|
+
logger.error(`File-less mode: ${clientR.error.message}`);
|
|
9256
|
+
return null;
|
|
9257
|
+
}
|
|
9258
|
+
const r = await clientR.value.fetchAll();
|
|
9259
|
+
if (!r.ok) {
|
|
9260
|
+
logger.error(`File-less mode: failed to fetch features from tracker: ${r.error.message}`);
|
|
9261
|
+
return null;
|
|
9262
|
+
}
|
|
9263
|
+
return r.value.features.filter((f) => !!f.externalId).map((f) => ({ name: f.name, externalId: f.externalId }));
|
|
9264
|
+
}
|
|
9265
|
+
const roadmapFile = path62.join(projectPath, "docs", "roadmap.md");
|
|
9266
|
+
if (!fs41.existsSync(roadmapFile)) {
|
|
8898
9267
|
logger.error("No docs/roadmap.md found. Cannot discover features with externalIds.");
|
|
8899
9268
|
return null;
|
|
8900
9269
|
}
|
|
8901
|
-
const { parseRoadmap } = (init_dist(), __toCommonJS(dist_exports));
|
|
8902
|
-
const roadmapParsed =
|
|
9270
|
+
const { parseRoadmap: parseRoadmap2 } = (init_dist(), __toCommonJS(dist_exports));
|
|
9271
|
+
const roadmapParsed = parseRoadmap2(fs41.readFileSync(roadmapFile, "utf-8"));
|
|
8903
9272
|
if (!roadmapParsed.ok) {
|
|
8904
9273
|
logger.error("Failed to parse docs/roadmap.md");
|
|
8905
9274
|
return null;
|
|
@@ -8943,16 +9312,16 @@ async function runSyncAnalyses(opts) {
|
|
|
8943
9312
|
const bootstrap = bootstrapTrackerCommand2(opts, "sync");
|
|
8944
9313
|
if (!bootstrap) return process.exit(1);
|
|
8945
9314
|
const { token, projectPath, trackerConfig } = bootstrap;
|
|
8946
|
-
const features = loadRoadmapFeatures(projectPath);
|
|
9315
|
+
const features = await loadRoadmapFeatures(projectPath);
|
|
8947
9316
|
if (!features) return process.exit(1);
|
|
8948
9317
|
if (features.length === 0) {
|
|
8949
9318
|
logger.info("No roadmap features have externalIds. Nothing to sync.");
|
|
8950
9319
|
return;
|
|
8951
9320
|
}
|
|
8952
9321
|
const { AnalysisArchive, loadPublishedIndex: loadPublishedIndex2, savePublishedIndex: savePublishedIndex2 } = await import("@harness-engineering/orchestrator");
|
|
8953
|
-
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-
|
|
9322
|
+
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-I4LWG3IN.js");
|
|
8954
9323
|
const adapter = new GitHubIssuesSyncAdapter2({ token, config: trackerConfig });
|
|
8955
|
-
const archive = new AnalysisArchive(
|
|
9324
|
+
const archive = new AnalysisArchive(path62.join(projectPath, ".harness", "analyses"));
|
|
8956
9325
|
const publishedIndex = loadPublishedIndex2(projectPath);
|
|
8957
9326
|
const { syncedCount, skippedCount, warnCount } = await syncFeatureAnalyses(
|
|
8958
9327
|
features,
|
|
@@ -8972,7 +9341,7 @@ async function runSyncAnalyses(opts) {
|
|
|
8972
9341
|
}
|
|
8973
9342
|
}
|
|
8974
9343
|
function createSyncAnalysesCommand() {
|
|
8975
|
-
const command = new
|
|
9344
|
+
const command = new Command80("sync-analyses").description(
|
|
8976
9345
|
"Pull published intelligence analyses from the external issue tracker into the local .harness/analyses/ directory"
|
|
8977
9346
|
).option("-d, --dir <path>", "Workspace directory", process.cwd()).action(async (opts) => {
|
|
8978
9347
|
try {
|
|
@@ -8985,9 +9354,48 @@ function createSyncAnalysesCommand() {
|
|
|
8985
9354
|
return command;
|
|
8986
9355
|
}
|
|
8987
9356
|
|
|
9357
|
+
// src/commands/sync-main.ts
|
|
9358
|
+
import { Command as Command81 } from "commander";
|
|
9359
|
+
import * as path63 from "path";
|
|
9360
|
+
import {
|
|
9361
|
+
syncMain as defaultSyncMain
|
|
9362
|
+
} from "@harness-engineering/orchestrator";
|
|
9363
|
+
async function runSyncMain(opts) {
|
|
9364
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
9365
|
+
const fn = opts.syncMainFn ?? ((root) => defaultSyncMain(root));
|
|
9366
|
+
const result = await fn(cwd);
|
|
9367
|
+
if (opts.json) {
|
|
9368
|
+
process.stdout.write(`${JSON.stringify(result)}
|
|
9369
|
+
`);
|
|
9370
|
+
} else {
|
|
9371
|
+
process.stdout.write(`${formatHuman(result)}
|
|
9372
|
+
`);
|
|
9373
|
+
}
|
|
9374
|
+
return result.status === "error" ? ExitCode.ERROR : ExitCode.SUCCESS;
|
|
9375
|
+
}
|
|
9376
|
+
function formatHuman(r) {
|
|
9377
|
+
switch (r.status) {
|
|
9378
|
+
case "updated":
|
|
9379
|
+
return `updated ${r.defaultBranch}: ${r.from.slice(0, 7)} -> ${r.to.slice(0, 7)}`;
|
|
9380
|
+
case "no-op":
|
|
9381
|
+
return `up-to-date: ${r.defaultBranch}`;
|
|
9382
|
+
case "skipped":
|
|
9383
|
+
return `skipped (${r.reason}): ${r.detail}`;
|
|
9384
|
+
case "error":
|
|
9385
|
+
return `error: ${r.message}`;
|
|
9386
|
+
}
|
|
9387
|
+
}
|
|
9388
|
+
function createSyncMainCommand() {
|
|
9389
|
+
return new Command81("sync-main").description("Fast-forward the local default branch from origin (no-op on conflict)").option("--json", "Emit a SyncMainResult JSON object", false).option("--path <path>", "Project root path", ".").action(async (opts) => {
|
|
9390
|
+
const cwd = path63.resolve(opts.path ?? ".");
|
|
9391
|
+
const exitCode = await runSyncMain({ cwd, json: Boolean(opts.json) });
|
|
9392
|
+
process.exit(exitCode);
|
|
9393
|
+
});
|
|
9394
|
+
}
|
|
9395
|
+
|
|
8988
9396
|
// src/commands/taint.ts
|
|
8989
9397
|
init_dist();
|
|
8990
|
-
import { Command as
|
|
9398
|
+
import { Command as Command82 } from "commander";
|
|
8991
9399
|
function getProjectRoot() {
|
|
8992
9400
|
return process.cwd();
|
|
8993
9401
|
}
|
|
@@ -9059,28 +9467,28 @@ function registerStatusCommand(taint) {
|
|
|
9059
9467
|
});
|
|
9060
9468
|
}
|
|
9061
9469
|
function createTaintCommand() {
|
|
9062
|
-
const taint = new
|
|
9470
|
+
const taint = new Command82("taint").description("Manage sentinel session taint state");
|
|
9063
9471
|
registerClearCommand(taint);
|
|
9064
9472
|
registerStatusCommand(taint);
|
|
9065
9473
|
return taint;
|
|
9066
9474
|
}
|
|
9067
9475
|
|
|
9068
9476
|
// src/commands/telemetry/index.ts
|
|
9069
|
-
import { Command as
|
|
9477
|
+
import { Command as Command86 } from "commander";
|
|
9070
9478
|
|
|
9071
9479
|
// src/commands/telemetry/identify.ts
|
|
9072
9480
|
init_dist();
|
|
9073
|
-
import { Command as
|
|
9074
|
-
import * as
|
|
9075
|
-
import * as
|
|
9481
|
+
import { Command as Command83 } from "commander";
|
|
9482
|
+
import * as fs42 from "fs";
|
|
9483
|
+
import * as path64 from "path";
|
|
9076
9484
|
function telemetryFilePath(cwd) {
|
|
9077
|
-
return
|
|
9485
|
+
return path64.join(cwd, ".harness", "telemetry.json");
|
|
9078
9486
|
}
|
|
9079
9487
|
function writeTelemetryFile(cwd, data) {
|
|
9080
9488
|
const filePath = telemetryFilePath(cwd);
|
|
9081
|
-
const dir =
|
|
9082
|
-
|
|
9083
|
-
|
|
9489
|
+
const dir = path64.dirname(filePath);
|
|
9490
|
+
fs42.mkdirSync(dir, { recursive: true });
|
|
9491
|
+
fs42.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
9084
9492
|
}
|
|
9085
9493
|
var IDENTITY_FIELDS = ["project", "team", "alias"];
|
|
9086
9494
|
function applyIdentityFields(opts, identity) {
|
|
@@ -9094,7 +9502,7 @@ function printIdentity(identity) {
|
|
|
9094
9502
|
}
|
|
9095
9503
|
}
|
|
9096
9504
|
function createIdentifyCommand() {
|
|
9097
|
-
const cmd = new
|
|
9505
|
+
const cmd = new Command83("identify").description("Set or clear telemetry identity fields in .harness/telemetry.json").option("--project <name>", "Project name").option("--team <name>", "Team name").option("--alias <name>", "User alias").option("--clear", "Remove all identity fields").action((opts) => {
|
|
9098
9506
|
const cwd = process.cwd();
|
|
9099
9507
|
if (opts.clear) {
|
|
9100
9508
|
writeTelemetryFile(cwd, { identity: {} });
|
|
@@ -9119,7 +9527,7 @@ function createIdentifyCommand() {
|
|
|
9119
9527
|
|
|
9120
9528
|
// src/commands/telemetry/status.ts
|
|
9121
9529
|
init_dist();
|
|
9122
|
-
import { Command as
|
|
9530
|
+
import { Command as Command84 } from "commander";
|
|
9123
9531
|
function gatherEnvOverrides() {
|
|
9124
9532
|
const overrides = {};
|
|
9125
9533
|
if (process.env.DO_NOT_TRACK) overrides.DO_NOT_TRACK = process.env.DO_NOT_TRACK;
|
|
@@ -9161,7 +9569,7 @@ function printHumanStatus(result) {
|
|
|
9161
9569
|
printEnvOverrides(result.envOverrides);
|
|
9162
9570
|
}
|
|
9163
9571
|
function createStatusCommand() {
|
|
9164
|
-
const cmd = new
|
|
9572
|
+
const cmd = new Command84("status").description("Show current telemetry consent state, install ID, and identity").option("--json", "Output as JSON").action((opts) => {
|
|
9165
9573
|
const cwd = process.cwd();
|
|
9166
9574
|
const envOverrides = gatherEnvOverrides();
|
|
9167
9575
|
const consent = resolveConsent(cwd, void 0);
|
|
@@ -9194,20 +9602,20 @@ function createStatusCommand() {
|
|
|
9194
9602
|
|
|
9195
9603
|
// src/commands/telemetry/test.ts
|
|
9196
9604
|
init_dist();
|
|
9197
|
-
import { Command as
|
|
9605
|
+
import { Command as Command85 } from "commander";
|
|
9198
9606
|
|
|
9199
9607
|
// src/bin/command-telemetry.ts
|
|
9200
|
-
import { existsSync as
|
|
9201
|
-
import { join as
|
|
9608
|
+
import { existsSync as existsSync39, readFileSync as readFileSync29, mkdirSync as mkdirSync19, appendFileSync, writeFileSync as writeFileSync23 } from "fs";
|
|
9609
|
+
import { join as join51, parse as parsePath, resolve as resolve37 } from "path";
|
|
9202
9610
|
import { spawn as spawn2 } from "child_process";
|
|
9203
9611
|
var POSTHOG_API_KEY = "phc_wNTdCMcfJXZPgdNeDociZW6vwoGGo4nb7vqEfWThFfsG";
|
|
9204
9612
|
var EXCLUDED_COMMANDS = /* @__PURE__ */ new Set(["help", "completion"]);
|
|
9205
9613
|
function findProjectRoot(cwd) {
|
|
9206
|
-
let dir =
|
|
9614
|
+
let dir = resolve37(cwd);
|
|
9207
9615
|
const { root } = parsePath(dir);
|
|
9208
9616
|
while (dir !== root) {
|
|
9209
|
-
if (
|
|
9210
|
-
dir =
|
|
9617
|
+
if (existsSync39(join51(dir, "harness.config.json"))) return dir;
|
|
9618
|
+
dir = resolve37(dir, "..");
|
|
9211
9619
|
}
|
|
9212
9620
|
return cwd;
|
|
9213
9621
|
}
|
|
@@ -9244,8 +9652,8 @@ function resolveCommandName(cmd) {
|
|
|
9244
9652
|
}
|
|
9245
9653
|
function writeCommandRecordSync(cwd, command, duration, outcome) {
|
|
9246
9654
|
try {
|
|
9247
|
-
const metricsDir =
|
|
9248
|
-
|
|
9655
|
+
const metricsDir = join51(cwd, ".harness", "metrics");
|
|
9656
|
+
mkdirSync19(metricsDir, { recursive: true });
|
|
9249
9657
|
const record = {
|
|
9250
9658
|
skill: command,
|
|
9251
9659
|
session: `cli-${Date.now()}`,
|
|
@@ -9254,16 +9662,16 @@ function writeCommandRecordSync(cwd, command, duration, outcome) {
|
|
|
9254
9662
|
outcome,
|
|
9255
9663
|
phasesReached: []
|
|
9256
9664
|
};
|
|
9257
|
-
const adoptionFile =
|
|
9665
|
+
const adoptionFile = join51(metricsDir, "adoption.jsonl");
|
|
9258
9666
|
appendFileSync(adoptionFile, JSON.stringify(record) + "\n");
|
|
9259
9667
|
} catch {
|
|
9260
9668
|
}
|
|
9261
9669
|
}
|
|
9262
9670
|
function isTelemetryDisabledByConfig(cwd) {
|
|
9263
|
-
const configPath =
|
|
9264
|
-
if (!
|
|
9671
|
+
const configPath = join51(cwd, "harness.config.json");
|
|
9672
|
+
if (!existsSync39(configPath)) return false;
|
|
9265
9673
|
try {
|
|
9266
|
-
const config = JSON.parse(
|
|
9674
|
+
const config = JSON.parse(readFileSync29(configPath, "utf-8"));
|
|
9267
9675
|
return config?.telemetry?.enabled === false;
|
|
9268
9676
|
} catch {
|
|
9269
9677
|
return false;
|
|
@@ -9275,11 +9683,11 @@ function isTelemetryOptedOut(cwd) {
|
|
|
9275
9683
|
}
|
|
9276
9684
|
function flushTelemetryBackground(cwd) {
|
|
9277
9685
|
try {
|
|
9278
|
-
const adoptionFile =
|
|
9279
|
-
if (!
|
|
9686
|
+
const adoptionFile = join51(cwd, ".harness", "metrics", "adoption.jsonl");
|
|
9687
|
+
if (!existsSync39(adoptionFile)) return;
|
|
9280
9688
|
if (isTelemetryOptedOut(cwd)) return;
|
|
9281
|
-
const reporterPath =
|
|
9282
|
-
if (!
|
|
9689
|
+
const reporterPath = join51(cwd, ".harness", "hooks", "telemetry-reporter.js");
|
|
9690
|
+
if (!existsSync39(reporterPath)) return;
|
|
9283
9691
|
const child = spawn2(process.execPath, [reporterPath], {
|
|
9284
9692
|
cwd,
|
|
9285
9693
|
stdio: ["pipe", "ignore", "ignore"],
|
|
@@ -9316,7 +9724,7 @@ function reportTestSuccess(status, distinctId, identity) {
|
|
|
9316
9724
|
if (identity.team) logger.info(` team: ${identity.team}`);
|
|
9317
9725
|
}
|
|
9318
9726
|
function createTestCommand() {
|
|
9319
|
-
return new
|
|
9727
|
+
return new Command85("test").description("Send a test event to PostHog and verify connectivity").action(async () => {
|
|
9320
9728
|
const cwd = process.cwd();
|
|
9321
9729
|
const consent = resolveConsent(cwd, void 0);
|
|
9322
9730
|
if (!consent.allowed) {
|
|
@@ -9353,7 +9761,7 @@ function createTestCommand() {
|
|
|
9353
9761
|
|
|
9354
9762
|
// src/commands/telemetry/index.ts
|
|
9355
9763
|
function createTelemetryCommand() {
|
|
9356
|
-
const command = new
|
|
9764
|
+
const command = new Command86("telemetry").description("Telemetry identity and status management");
|
|
9357
9765
|
command.addCommand(createIdentifyCommand());
|
|
9358
9766
|
command.addCommand(createStatusCommand());
|
|
9359
9767
|
command.addCommand(createTestCommand());
|
|
@@ -9361,8 +9769,8 @@ function createTelemetryCommand() {
|
|
|
9361
9769
|
}
|
|
9362
9770
|
|
|
9363
9771
|
// src/commands/traceability.ts
|
|
9364
|
-
import { Command as
|
|
9365
|
-
import
|
|
9772
|
+
import { Command as Command87 } from "commander";
|
|
9773
|
+
import chalk14 from "chalk";
|
|
9366
9774
|
function confidenceLabel(maxConfidence) {
|
|
9367
9775
|
if (maxConfidence >= 0.8) return "explicit";
|
|
9368
9776
|
if (maxConfidence > 0) return "inferred";
|
|
@@ -9371,13 +9779,13 @@ function confidenceLabel(maxConfidence) {
|
|
|
9371
9779
|
function statusIcon(status) {
|
|
9372
9780
|
switch (status) {
|
|
9373
9781
|
case "full":
|
|
9374
|
-
return
|
|
9782
|
+
return chalk14.green("\u2713 full");
|
|
9375
9783
|
case "code-only":
|
|
9376
|
-
return
|
|
9784
|
+
return chalk14.yellow("\u25D0 code-only");
|
|
9377
9785
|
case "test-only":
|
|
9378
|
-
return
|
|
9786
|
+
return chalk14.yellow("\u25D0 test-only");
|
|
9379
9787
|
case "none":
|
|
9380
|
-
return
|
|
9788
|
+
return chalk14.red("\u2717 none");
|
|
9381
9789
|
default:
|
|
9382
9790
|
return status;
|
|
9383
9791
|
}
|
|
@@ -9424,14 +9832,14 @@ function handleEmptyResults(mode) {
|
|
|
9424
9832
|
}
|
|
9425
9833
|
function printResultTable(result, mode) {
|
|
9426
9834
|
const specLabel = result.specPath || result.featureName || "Unknown";
|
|
9427
|
-
console.log(` ${
|
|
9835
|
+
console.log(` ${chalk14.cyan(specLabel)} ${chalk14.dim(`(${result.summary.total} requirements)`)}`);
|
|
9428
9836
|
console.log("");
|
|
9429
9837
|
const numWidth = 4;
|
|
9430
9838
|
const nameWidth = mode === OutputMode.VERBOSE ? 44 : 36;
|
|
9431
9839
|
const codeWidth = 6;
|
|
9432
9840
|
const testWidth = 7;
|
|
9433
9841
|
const confWidth = 12;
|
|
9434
|
-
const header =
|
|
9842
|
+
const header = chalk14.dim(
|
|
9435
9843
|
` ${pad("#", numWidth)}${pad("Requirement", nameWidth)}${pad("Code", codeWidth)}${pad("Tests", testWidth)}${pad("Confidence", confWidth)}Status`
|
|
9436
9844
|
);
|
|
9437
9845
|
console.log(header);
|
|
@@ -9460,10 +9868,10 @@ function printRequirementRows(requirements, mode, cols) {
|
|
|
9460
9868
|
);
|
|
9461
9869
|
if (mode === OutputMode.VERBOSE) {
|
|
9462
9870
|
for (const f of req.codeFiles) {
|
|
9463
|
-
console.log(
|
|
9871
|
+
console.log(chalk14.dim(` code: ${f.path} (${f.method})`));
|
|
9464
9872
|
}
|
|
9465
9873
|
for (const f of req.testFiles) {
|
|
9466
|
-
console.log(
|
|
9874
|
+
console.log(chalk14.dim(` test: ${f.path} (${f.method})`));
|
|
9467
9875
|
}
|
|
9468
9876
|
}
|
|
9469
9877
|
}
|
|
@@ -9474,7 +9882,7 @@ function printCoverageSummary(summary) {
|
|
|
9474
9882
|
const codePct = s.total > 0 ? Math.round(s.withCode / s.total * 100) : 0;
|
|
9475
9883
|
const testPct = s.total > 0 ? Math.round(s.withTests / s.total * 100) : 0;
|
|
9476
9884
|
console.log(
|
|
9477
|
-
` ${
|
|
9885
|
+
` ${chalk14.bold("Coverage:")} ${fullyPct}% fully traced (${s.fullyTraced}/${s.total}), ${codePct}% with code (${s.withCode}/${s.total}), ${testPct}% with tests (${s.withTests}/${s.total})`
|
|
9478
9886
|
);
|
|
9479
9887
|
}
|
|
9480
9888
|
async function runTraceability(options) {
|
|
@@ -9490,14 +9898,14 @@ async function runTraceability(options) {
|
|
|
9490
9898
|
console.log(JSON.stringify(results, null, 2));
|
|
9491
9899
|
} else {
|
|
9492
9900
|
console.log("");
|
|
9493
|
-
console.log(
|
|
9901
|
+
console.log(chalk14.bold("Spec-to-Implementation Traceability"));
|
|
9494
9902
|
console.log("");
|
|
9495
9903
|
for (const result of results) printResultTable(result, mode);
|
|
9496
9904
|
}
|
|
9497
9905
|
process.exit(ExitCode.SUCCESS);
|
|
9498
9906
|
}
|
|
9499
9907
|
function createTraceabilityCommand() {
|
|
9500
|
-
return new
|
|
9908
|
+
return new Command87("traceability").description("Show spec-to-implementation traceability from the knowledge graph").option("--spec <path>", "Filter by spec file path").option("--feature <name>", "Filter by feature name").action(async (opts, cmd) => {
|
|
9501
9909
|
const globalOpts = cmd.optsWithGlobals();
|
|
9502
9910
|
await runTraceability({
|
|
9503
9911
|
spec: opts.spec,
|
|
@@ -9510,15 +9918,15 @@ function createTraceabilityCommand() {
|
|
|
9510
9918
|
}
|
|
9511
9919
|
|
|
9512
9920
|
// src/commands/uninstall.ts
|
|
9513
|
-
import * as
|
|
9514
|
-
import { Command as
|
|
9921
|
+
import * as path65 from "path";
|
|
9922
|
+
import { Command as Command88 } from "commander";
|
|
9515
9923
|
async function runUninstall(skillName, options) {
|
|
9516
9924
|
const packageName = resolvePackageName(skillName);
|
|
9517
9925
|
const shortName = extractSkillName(packageName);
|
|
9518
9926
|
const globalDir = resolveGlobalSkillsDir();
|
|
9519
|
-
const skillsDir =
|
|
9520
|
-
const communityBase =
|
|
9521
|
-
const lockfilePath =
|
|
9927
|
+
const skillsDir = path65.dirname(globalDir);
|
|
9928
|
+
const communityBase = path65.join(skillsDir, "community");
|
|
9929
|
+
const lockfilePath = path65.join(communityBase, "skills-lock.json");
|
|
9522
9930
|
const lockfile = readLockfile2(lockfilePath);
|
|
9523
9931
|
const entry = lockfile.skills[packageName];
|
|
9524
9932
|
if (!entry) {
|
|
@@ -9548,7 +9956,7 @@ async function runUninstall(skillName, options) {
|
|
|
9548
9956
|
return result;
|
|
9549
9957
|
}
|
|
9550
9958
|
function createUninstallCommand() {
|
|
9551
|
-
const cmd = new
|
|
9959
|
+
const cmd = new Command88("uninstall");
|
|
9552
9960
|
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) => {
|
|
9553
9961
|
try {
|
|
9554
9962
|
const result = await runUninstall(skill, opts);
|
|
@@ -9568,9 +9976,9 @@ function createUninstallCommand() {
|
|
|
9568
9976
|
|
|
9569
9977
|
// src/commands/uninstall-constraints.ts
|
|
9570
9978
|
init_dist();
|
|
9571
|
-
import * as
|
|
9572
|
-
import * as
|
|
9573
|
-
import { Command as
|
|
9979
|
+
import * as fs43 from "fs/promises";
|
|
9980
|
+
import * as path66 from "path";
|
|
9981
|
+
import { Command as Command89 } from "commander";
|
|
9574
9982
|
async function runUninstallConstraints(options) {
|
|
9575
9983
|
const { packageName, configPath, lockfilePath } = options;
|
|
9576
9984
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -9590,7 +9998,7 @@ async function runUninstallConstraints(options) {
|
|
|
9590
9998
|
}
|
|
9591
9999
|
let localConfig;
|
|
9592
10000
|
try {
|
|
9593
|
-
const raw = await
|
|
10001
|
+
const raw = await fs43.readFile(configPath, "utf-8");
|
|
9594
10002
|
localConfig = JSON.parse(raw);
|
|
9595
10003
|
} catch (err) {
|
|
9596
10004
|
return {
|
|
@@ -9627,11 +10035,11 @@ async function runUninstallConstraints(options) {
|
|
|
9627
10035
|
};
|
|
9628
10036
|
}
|
|
9629
10037
|
function createUninstallConstraintsCommand() {
|
|
9630
|
-
const cmd = new
|
|
10038
|
+
const cmd = new Command89("uninstall-constraints");
|
|
9631
10039
|
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) => {
|
|
9632
10040
|
let configPath;
|
|
9633
10041
|
if (opts.config) {
|
|
9634
|
-
configPath =
|
|
10042
|
+
configPath = path66.resolve(opts.config);
|
|
9635
10043
|
} else {
|
|
9636
10044
|
const found = findConfigFile();
|
|
9637
10045
|
if (!found.ok) {
|
|
@@ -9640,8 +10048,8 @@ function createUninstallConstraintsCommand() {
|
|
|
9640
10048
|
}
|
|
9641
10049
|
configPath = found.value;
|
|
9642
10050
|
}
|
|
9643
|
-
const projectRoot =
|
|
9644
|
-
const lockfilePath =
|
|
10051
|
+
const projectRoot = path66.dirname(configPath);
|
|
10052
|
+
const lockfilePath = path66.join(projectRoot, ".harness", "constraints.lock.json");
|
|
9645
10053
|
const result = await runUninstallConstraints({
|
|
9646
10054
|
packageName: name,
|
|
9647
10055
|
configPath,
|
|
@@ -9666,13 +10074,13 @@ function createUninstallConstraintsCommand() {
|
|
|
9666
10074
|
}
|
|
9667
10075
|
|
|
9668
10076
|
// src/commands/update.ts
|
|
9669
|
-
import { Command as
|
|
10077
|
+
import { Command as Command90 } from "commander";
|
|
9670
10078
|
import { execFile, execFileSync as execFileSync7 } from "child_process";
|
|
9671
|
-
import { realpathSync, existsSync as
|
|
9672
|
-
import { join as
|
|
10079
|
+
import { realpathSync, existsSync as existsSync40, readFileSync as readFileSync30 } from "fs";
|
|
10080
|
+
import { join as join54 } from "path";
|
|
9673
10081
|
import { promisify } from "util";
|
|
9674
10082
|
import readline4 from "readline";
|
|
9675
|
-
import
|
|
10083
|
+
import chalk15 from "chalk";
|
|
9676
10084
|
function detectPackageManager() {
|
|
9677
10085
|
try {
|
|
9678
10086
|
const argv1 = process.argv[1];
|
|
@@ -9735,10 +10143,10 @@ function prompt3(question) {
|
|
|
9735
10143
|
input: process.stdin,
|
|
9736
10144
|
output: process.stdout
|
|
9737
10145
|
});
|
|
9738
|
-
return new Promise((
|
|
10146
|
+
return new Promise((resolve40) => {
|
|
9739
10147
|
rl.question(question, (answer) => {
|
|
9740
10148
|
rl.close();
|
|
9741
|
-
|
|
10149
|
+
resolve40(answer.trim().toLowerCase());
|
|
9742
10150
|
});
|
|
9743
10151
|
});
|
|
9744
10152
|
}
|
|
@@ -9753,12 +10161,12 @@ async function ensureTelemetryIfNeeded() {
|
|
|
9753
10161
|
}
|
|
9754
10162
|
function refreshHooks() {
|
|
9755
10163
|
const cwd = process.cwd();
|
|
9756
|
-
const configPath =
|
|
9757
|
-
if (!
|
|
10164
|
+
const configPath = join54(cwd, "harness.config.json");
|
|
10165
|
+
if (!existsSync40(configPath)) return;
|
|
9758
10166
|
let profile = "standard";
|
|
9759
|
-
const profilePath =
|
|
10167
|
+
const profilePath = join54(cwd, ".harness", "hooks", "profile.json");
|
|
9760
10168
|
try {
|
|
9761
|
-
const data = JSON.parse(
|
|
10169
|
+
const data = JSON.parse(readFileSync30(profilePath, "utf-8"));
|
|
9762
10170
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
9763
10171
|
profile = data.profile;
|
|
9764
10172
|
}
|
|
@@ -9778,7 +10186,7 @@ function runLocalGraphScan() {
|
|
|
9778
10186
|
execFileSync7("harness", ["graph", "scan", "."], { stdio: "inherit" });
|
|
9779
10187
|
} catch {
|
|
9780
10188
|
logger.warn("Graph scan failed. Run manually:");
|
|
9781
|
-
console.log(` ${
|
|
10189
|
+
console.log(` ${chalk15.cyan("harness graph scan .")}`);
|
|
9782
10190
|
}
|
|
9783
10191
|
}
|
|
9784
10192
|
async function offerRegeneration() {
|
|
@@ -9795,7 +10203,7 @@ async function offerRegeneration() {
|
|
|
9795
10203
|
});
|
|
9796
10204
|
} catch {
|
|
9797
10205
|
logger.warn("Generation failed. Run manually:");
|
|
9798
|
-
console.log(` ${
|
|
10206
|
+
console.log(` ${chalk15.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
9799
10207
|
}
|
|
9800
10208
|
if (!isGlobal) {
|
|
9801
10209
|
runLocalGraphScan();
|
|
@@ -9858,8 +10266,8 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
9858
10266
|
console.log("");
|
|
9859
10267
|
for (const { pkg, current, latest } of outdated) {
|
|
9860
10268
|
const shortName = pkg.replace("@harness-engineering/", "");
|
|
9861
|
-
const currentStr = current ?
|
|
9862
|
-
logger.info(`${shortName}: ${currentStr} \u2192 ${
|
|
10269
|
+
const currentStr = current ? chalk15.dim(`v${current}`) : chalk15.dim("not installed");
|
|
10270
|
+
logger.info(`${shortName}: ${currentStr} \u2192 ${chalk15.green(`v${latest}`)}`);
|
|
9863
10271
|
}
|
|
9864
10272
|
console.log("");
|
|
9865
10273
|
}
|
|
@@ -9875,7 +10283,7 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
9875
10283
|
} catch {
|
|
9876
10284
|
console.log("");
|
|
9877
10285
|
logger.error("Update failed. You can try manually:");
|
|
9878
|
-
console.log(` ${
|
|
10286
|
+
console.log(` ${chalk15.cyan(installCmd)}`);
|
|
9879
10287
|
process.exit(ExitCode.ERROR);
|
|
9880
10288
|
}
|
|
9881
10289
|
refreshHooks();
|
|
@@ -9884,7 +10292,7 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
9884
10292
|
process.exit(ExitCode.SUCCESS);
|
|
9885
10293
|
}
|
|
9886
10294
|
function createUpdateCommand() {
|
|
9887
|
-
return new
|
|
10295
|
+
return new Command90("update").description("Update all @harness-engineering packages to the latest version").option("--version <semver>", "Pin @harness-engineering/cli to a specific version").option("--force", "Force update even if versions match").option(
|
|
9888
10296
|
"--regenerate",
|
|
9889
10297
|
"Only regenerate slash commands and agent definitions (skip package updates)"
|
|
9890
10298
|
).action(async (opts, cmd) => {
|
|
@@ -9894,9 +10302,9 @@ function createUpdateCommand() {
|
|
|
9894
10302
|
}
|
|
9895
10303
|
|
|
9896
10304
|
// src/commands/usage.ts
|
|
9897
|
-
import { Command as
|
|
10305
|
+
import { Command as Command91 } from "commander";
|
|
9898
10306
|
async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
9899
|
-
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-
|
|
10307
|
+
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-I4LWG3IN.js");
|
|
9900
10308
|
const records = readCostRecords(cwd);
|
|
9901
10309
|
if (includeClaudeSessions) {
|
|
9902
10310
|
const ccRecords = parseCCRecords();
|
|
@@ -9989,7 +10397,7 @@ function registerDailyCommand(usage) {
|
|
|
9989
10397
|
else logger.info("No usage data found. Run some harness sessions first.");
|
|
9990
10398
|
return;
|
|
9991
10399
|
}
|
|
9992
|
-
const { aggregateByDay } = await import("./dist-
|
|
10400
|
+
const { aggregateByDay } = await import("./dist-I4LWG3IN.js");
|
|
9993
10401
|
const limited = aggregateByDay(records).slice(0, days);
|
|
9994
10402
|
if (globalOpts.json) {
|
|
9995
10403
|
console.log(
|
|
@@ -10018,7 +10426,7 @@ function registerSessionsCommand(usage) {
|
|
|
10018
10426
|
}
|
|
10019
10427
|
return;
|
|
10020
10428
|
}
|
|
10021
|
-
const { aggregateBySession } = await import("./dist-
|
|
10429
|
+
const { aggregateBySession } = await import("./dist-I4LWG3IN.js");
|
|
10022
10430
|
const sessionData = aggregateBySession(records);
|
|
10023
10431
|
const limited = sessionData.slice(0, limit);
|
|
10024
10432
|
if (globalOpts.json) {
|
|
@@ -10098,7 +10506,7 @@ function registerSessionCommand(usage) {
|
|
|
10098
10506
|
cwd,
|
|
10099
10507
|
globalOpts.includeClaudeSessions
|
|
10100
10508
|
);
|
|
10101
|
-
const { aggregateBySession } = await import("./dist-
|
|
10509
|
+
const { aggregateBySession } = await import("./dist-I4LWG3IN.js");
|
|
10102
10510
|
const sessionData = aggregateBySession(records);
|
|
10103
10511
|
const match = sessionData.find((s) => s.sessionId === id);
|
|
10104
10512
|
if (!match) {
|
|
@@ -10133,7 +10541,7 @@ function registerLatestCommand(usage) {
|
|
|
10133
10541
|
}
|
|
10134
10542
|
return;
|
|
10135
10543
|
}
|
|
10136
|
-
const { aggregateBySession } = await import("./dist-
|
|
10544
|
+
const { aggregateBySession } = await import("./dist-I4LWG3IN.js");
|
|
10137
10545
|
const sessionData = aggregateBySession(records);
|
|
10138
10546
|
const latest = sessionData[0];
|
|
10139
10547
|
if (!latest) {
|
|
@@ -10159,7 +10567,7 @@ function registerLatestCommand(usage) {
|
|
|
10159
10567
|
});
|
|
10160
10568
|
}
|
|
10161
10569
|
function createUsageCommand() {
|
|
10162
|
-
const usage = new
|
|
10570
|
+
const usage = new Command91("usage").description("Token usage and cost tracking");
|
|
10163
10571
|
usage.option(
|
|
10164
10572
|
"--include-claude-sessions",
|
|
10165
10573
|
"Include Claude Code session data from ~/.claude/projects/"
|
|
@@ -10174,15 +10582,15 @@ function createUsageCommand() {
|
|
|
10174
10582
|
// src/commands/validate.ts
|
|
10175
10583
|
init_dist();
|
|
10176
10584
|
init_dist();
|
|
10177
|
-
import { Command as
|
|
10178
|
-
import * as
|
|
10585
|
+
import { Command as Command92 } from "commander";
|
|
10586
|
+
import * as path67 from "path";
|
|
10179
10587
|
async function runValidate(options) {
|
|
10180
10588
|
const configResult = resolveConfig(options.configPath);
|
|
10181
10589
|
if (!configResult.ok) {
|
|
10182
10590
|
return configResult;
|
|
10183
10591
|
}
|
|
10184
10592
|
const config = configResult.value;
|
|
10185
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
10593
|
+
const cwd = options.cwd ?? (options.configPath ? path67.dirname(path67.resolve(options.configPath)) : process.cwd());
|
|
10186
10594
|
const result = {
|
|
10187
10595
|
valid: true,
|
|
10188
10596
|
checks: {
|
|
@@ -10192,7 +10600,7 @@ async function runValidate(options) {
|
|
|
10192
10600
|
},
|
|
10193
10601
|
issues: []
|
|
10194
10602
|
};
|
|
10195
|
-
const agentsMapPath =
|
|
10603
|
+
const agentsMapPath = path67.resolve(cwd, config.agentsMapPath);
|
|
10196
10604
|
const agentsResult = await validateAgentsMap(agentsMapPath);
|
|
10197
10605
|
if (agentsResult.ok) {
|
|
10198
10606
|
result.checks.agentsMap = true;
|
|
@@ -10256,6 +10664,23 @@ async function runValidate(options) {
|
|
|
10256
10664
|
result.issues.push({ check: "solutionsDir", file: issue.file, message: issue.message });
|
|
10257
10665
|
}
|
|
10258
10666
|
}
|
|
10667
|
+
const roadmapModeResult = validateRoadmapMode(config, cwd);
|
|
10668
|
+
if (roadmapModeResult.ok) {
|
|
10669
|
+
result.checks.roadmapMode = true;
|
|
10670
|
+
} else {
|
|
10671
|
+
result.valid = false;
|
|
10672
|
+
result.checks.roadmapMode = false;
|
|
10673
|
+
result.issues.push({
|
|
10674
|
+
check: "roadmapMode",
|
|
10675
|
+
file: "harness.config.json",
|
|
10676
|
+
ruleId: roadmapModeResult.error.code,
|
|
10677
|
+
severity: "error",
|
|
10678
|
+
message: roadmapModeResult.error.message,
|
|
10679
|
+
...roadmapModeResult.error.suggestions?.[0] !== void 0 && {
|
|
10680
|
+
suggestion: roadmapModeResult.error.suggestions[0]
|
|
10681
|
+
}
|
|
10682
|
+
});
|
|
10683
|
+
}
|
|
10259
10684
|
if (options.agentConfigs) {
|
|
10260
10685
|
const agentCfg = await validateAgentConfigs(cwd, {
|
|
10261
10686
|
strict: options.strict === true,
|
|
@@ -10285,11 +10710,11 @@ function resolveValidateMode(globalOpts) {
|
|
|
10285
10710
|
return OutputMode.TEXT;
|
|
10286
10711
|
}
|
|
10287
10712
|
async function printCrossCheckWarnings(mode) {
|
|
10288
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
10713
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-XRSSAXJP.js");
|
|
10289
10714
|
const cwd = process.cwd();
|
|
10290
10715
|
const crossResult = await runCrossCheck2({
|
|
10291
|
-
specsDir:
|
|
10292
|
-
plansDir:
|
|
10716
|
+
specsDir: path67.join(cwd, "docs", "specs"),
|
|
10717
|
+
plansDir: path67.join(cwd, "docs", "plans"),
|
|
10293
10718
|
projectPath: cwd
|
|
10294
10719
|
});
|
|
10295
10720
|
if (!crossResult.ok || crossResult.value.warnings === 0) return;
|
|
@@ -10301,7 +10726,7 @@ async function printCrossCheckWarnings(mode) {
|
|
|
10301
10726
|
${crossResult.value.warnings} warnings`);
|
|
10302
10727
|
}
|
|
10303
10728
|
function createValidateCommand3() {
|
|
10304
|
-
const command = new
|
|
10729
|
+
const command = new Command92("validate").description("Run all validation checks").option("--cross-check", "Run cross-artifact consistency validation").option(
|
|
10305
10730
|
"--agent-configs",
|
|
10306
10731
|
"Validate agent configs (CLAUDE.md, hooks, skills) via agnix or built-in fallback rules"
|
|
10307
10732
|
).option("--strict", "Treat warnings as errors (applies to --agent-configs)").option("--agnix-bin <path>", "Override the agnix binary path discovered on PATH").action(async (opts, cmd) => runValidateAction(opts, cmd.optsWithGlobals()));
|
|
@@ -10394,6 +10819,7 @@ var commandCreators = [
|
|
|
10394
10819
|
createPulseCommand,
|
|
10395
10820
|
createQueryCommand,
|
|
10396
10821
|
createRecommendCommand,
|
|
10822
|
+
createRoadmapCommand,
|
|
10397
10823
|
createScanCommand,
|
|
10398
10824
|
createScanConfigCommand,
|
|
10399
10825
|
createSetupCommand,
|
|
@@ -10403,6 +10829,7 @@ var commandCreators = [
|
|
|
10403
10829
|
createSnapshotCommand,
|
|
10404
10830
|
createStateCommand,
|
|
10405
10831
|
createSyncAnalysesCommand,
|
|
10832
|
+
createSyncMainCommand,
|
|
10406
10833
|
createTaintCommand,
|
|
10407
10834
|
createTelemetryCommand,
|
|
10408
10835
|
createTraceabilityCommand,
|
|
@@ -10415,7 +10842,7 @@ var commandCreators = [
|
|
|
10415
10842
|
|
|
10416
10843
|
// src/index.ts
|
|
10417
10844
|
function createProgram() {
|
|
10418
|
-
const program = new
|
|
10845
|
+
const program = new Command93();
|
|
10419
10846
|
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");
|
|
10420
10847
|
for (const creator of commandCreators) {
|
|
10421
10848
|
program.addCommand(creator());
|