@harness-engineering/cli 1.27.0 → 1.28.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-architecture-advisor/SKILL.md +17 -5
- package/dist/agents/skills/claude-code/harness-architecture-advisor/skill.yaml +1 -1
- package/dist/agents/skills/claude-code/harness-autopilot/SKILL.md +4 -4
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-design-system/SKILL.md +6 -1
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +4 -4
- package/dist/agents/skills/claude-code/harness-i18n-process/SKILL.md +2 -2
- package/dist/agents/skills/claude-code/harness-integration/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-knowledge-pipeline/SKILL.md +1 -0
- package/dist/agents/skills/claude-code/harness-onboarding/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +3 -3
- package/dist/agents/skills/claude-code/harness-roadmap/SKILL.md +4 -3
- package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +150 -10
- package/dist/agents/skills/claude-code/initialize-harness-project/skill.yaml +2 -1
- package/dist/agents/skills/codex/harness-architecture-advisor/SKILL.md +17 -5
- package/dist/agents/skills/codex/harness-architecture-advisor/skill.yaml +1 -1
- package/dist/agents/skills/codex/harness-autopilot/SKILL.md +4 -4
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +1 -1
- package/dist/agents/skills/codex/harness-design-system/SKILL.md +6 -1
- package/dist/agents/skills/codex/harness-execution/SKILL.md +4 -4
- package/dist/agents/skills/codex/harness-i18n-process/SKILL.md +2 -2
- package/dist/agents/skills/codex/harness-integration/SKILL.md +1 -1
- package/dist/agents/skills/codex/harness-knowledge-pipeline/SKILL.md +1 -0
- package/dist/agents/skills/codex/harness-onboarding/SKILL.md +1 -1
- package/dist/agents/skills/codex/harness-planning/SKILL.md +3 -3
- package/dist/agents/skills/codex/harness-roadmap/SKILL.md +4 -3
- package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +150 -10
- package/dist/agents/skills/codex/initialize-harness-project/skill.yaml +2 -1
- package/dist/agents/skills/cursor/harness-architecture-advisor/SKILL.md +17 -5
- package/dist/agents/skills/cursor/harness-architecture-advisor/skill.yaml +1 -1
- package/dist/agents/skills/cursor/harness-autopilot/SKILL.md +4 -4
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +1 -1
- package/dist/agents/skills/cursor/harness-design-system/SKILL.md +6 -1
- package/dist/agents/skills/cursor/harness-execution/SKILL.md +4 -4
- package/dist/agents/skills/cursor/harness-i18n-process/SKILL.md +2 -2
- package/dist/agents/skills/cursor/harness-integration/SKILL.md +1 -1
- package/dist/agents/skills/cursor/harness-knowledge-pipeline/SKILL.md +1 -0
- package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +1 -1
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +3 -3
- package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +4 -3
- package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +150 -10
- package/dist/agents/skills/cursor/initialize-harness-project/skill.yaml +2 -1
- package/dist/agents/skills/gemini-cli/harness-architecture-advisor/SKILL.md +17 -5
- package/dist/agents/skills/gemini-cli/harness-architecture-advisor/skill.yaml +1 -1
- package/dist/agents/skills/gemini-cli/harness-autopilot/SKILL.md +4 -4
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-design-system/SKILL.md +6 -1
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +4 -4
- package/dist/agents/skills/gemini-cli/harness-i18n-process/SKILL.md +2 -2
- package/dist/agents/skills/gemini-cli/harness-integration/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-knowledge-pipeline/SKILL.md +1 -0
- package/dist/agents/skills/gemini-cli/harness-onboarding/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +3 -3
- package/dist/agents/skills/gemini-cli/harness-roadmap/SKILL.md +4 -3
- package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +150 -10
- package/dist/agents/skills/gemini-cli/initialize-harness-project/skill.yaml +2 -1
- package/dist/{agents-md-2RN666DE.js → agents-md-76IEMV6O.js} +3 -3
- package/dist/{architecture-ZR33YKJB.js → architecture-5GVJRRUD.js} +4 -4
- package/dist/{assess-project-LLWWEQEV.js → assess-project-WDUFL7SO.js} +1 -1
- package/dist/bin/harness-mcp.js +15 -15
- package/dist/bin/harness.js +25 -25
- package/dist/{check-phase-gate-ZVG56NT2.js → check-phase-gate-QR3ONJYP.js} +4 -4
- package/dist/{chunk-FUS4OOGC.js → chunk-3WLXR23L.js} +1 -1
- package/dist/{chunk-IBTHD2UA.js → chunk-6JBJ5FSW.js} +2 -1
- package/dist/{chunk-C7RPQWBG.js → chunk-77OMOFZL.js} +1 -1
- package/dist/{chunk-YZH4I4JO.js → chunk-AJG2LCMA.js} +1 -1
- package/dist/{chunk-3AJW7VQ3.js → chunk-CS7K6ZQ3.js} +2 -2
- package/dist/{chunk-SDDQPMZK.js → chunk-D25P6763.js} +1 -1
- package/dist/{chunk-QTXW5TIS.js → chunk-DHGL3SF2.js} +10 -1
- package/dist/{chunk-CJYNZMOP.js → chunk-E7QD4YHA.js} +8 -8
- package/dist/{chunk-BO4AJZRE.js → chunk-H43UQY6B.js} +1 -1
- package/dist/{chunk-SDPHFDQS.js → chunk-ILVO6LZY.js} +1 -1
- package/dist/{chunk-BZZNYHRY.js → chunk-K5ZOBBQB.js} +9 -9
- package/dist/{chunk-A4ETL34H.js → chunk-KLE6FXWM.js} +23 -1
- package/dist/{chunk-F3Y64VNF.js → chunk-L4RURCSF.js} +6 -6
- package/dist/{chunk-EGRQHLDM.js → chunk-LINAFNIK.js} +5 -5
- package/dist/{chunk-TCMVZ3B7.js → chunk-OPOSSZJS.js} +98 -94
- package/dist/{chunk-L3Y7L4EG.js → chunk-OQS7KURX.js} +1 -1
- package/dist/{chunk-T7UZOJF4.js → chunk-QZ46CI6E.js} +9 -9
- package/dist/{chunk-MGPVA2EM.js → chunk-TFNQ72RF.js} +5 -5
- package/dist/{chunk-3XMOWLK3.js → chunk-USG7DV5N.js} +2 -2
- package/dist/{chunk-776JFSPK.js → chunk-VITLE3PS.js} +1 -1
- package/dist/{chunk-2FOLD76X.js → chunk-WPSM7WT6.js} +2 -2
- package/dist/{chunk-GAIOIINV.js → chunk-X7XQ3ELM.js} +108 -36
- package/dist/{chunk-EICASBYP.js → chunk-YZZVOT7I.js} +1 -1
- package/dist/{chunk-SA57FVGX.js → chunk-Z63IDQ7Y.js} +879 -416
- package/dist/{chunk-Q5DKIL46.js → chunk-Z7ZF2XDL.js} +3 -3
- package/dist/{ci-workflow-JFOL4YWW.js → ci-workflow-GYQXE5X4.js} +3 -3
- package/dist/{dist-CPKL5Y2R.js → dist-46VOJIG4.js} +2 -2
- package/dist/{dist-BZS2XRLG.js → dist-BP6E6QGG.js} +7 -1
- package/dist/{docs-FUTRJQHA.js → docs-CVQMSMQC.js} +4 -4
- package/dist/{engine-FB3HCNVT.js → engine-23PJMRHA.js} +3 -3
- package/dist/{entropy-FIEE4YDA.js → entropy-K2YTZFCP.js} +3 -3
- package/dist/{feedback-7EC4T6XG.js → feedback-6HP42GTK.js} +1 -1
- package/dist/{generate-agent-definitions-FOSFADNR.js → generate-agent-definitions-6RJKL6VW.js} +4 -4
- package/dist/{graph-loader-QEI7SYOQ.js → graph-loader-BI3OFFFS.js} +1 -1
- package/dist/index.d.ts +44 -1
- package/dist/index.js +25 -25
- package/dist/{loader-HXUOBTYA.js → loader-U2WEC3PZ.js} +3 -3
- package/dist/{mcp-ICXMTAD3.js → mcp-N6XVWPXJ.js} +15 -15
- package/dist/{performance-TYXBCTR7.js → performance-T3XAESWR.js} +4 -4
- package/dist/{review-pipeline-B4WPU6BQ.js → review-pipeline-HTC5HA5X.js} +3 -3
- package/dist/{runtime-HQQNOARD.js → runtime-UFXMNN3Q.js} +3 -3
- package/dist/{scan-TX55R6QS.js → scan-XRGMTO6K.js} +1 -1
- package/dist/{security-25VFLIAO.js → security-ROK5IFDA.js} +1 -1
- package/dist/{validate-EAVGKZQB.js → validate-IJHHQHT5.js} +4 -4
- package/dist/{validate-cross-check-SPL3H4O5.js → validate-cross-check-LUQYIA46.js} +3 -3
- package/package.json +6 -6
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
generateRuntime
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-YZZVOT7I.js";
|
|
4
4
|
import {
|
|
5
5
|
generateAgentsMd
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-77OMOFZL.js";
|
|
7
7
|
import {
|
|
8
8
|
generateCIWorkflow
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-3WLXR23L.js";
|
|
10
10
|
import {
|
|
11
11
|
TemplateEngine
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-OQS7KURX.js";
|
|
13
13
|
import {
|
|
14
14
|
generate,
|
|
15
15
|
validate
|
|
@@ -28,21 +28,21 @@ import {
|
|
|
28
28
|
OutputMode,
|
|
29
29
|
createCheckPhaseGateCommand,
|
|
30
30
|
findFiles
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-USG7DV5N.js";
|
|
32
32
|
import {
|
|
33
33
|
createGenerateAgentDefinitionsCommand,
|
|
34
34
|
generateAgentDefinitions
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-D25P6763.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-H43UQY6B.js";
|
|
43
43
|
import {
|
|
44
44
|
createScanCommand
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-DHGL3SF2.js";
|
|
46
46
|
import {
|
|
47
47
|
appendFrameworkAgents,
|
|
48
48
|
captureHealthSnapshot,
|
|
@@ -56,17 +56,17 @@ import {
|
|
|
56
56
|
loadCachedSnapshot,
|
|
57
57
|
persistToolingConfig,
|
|
58
58
|
recommend
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-OPOSSZJS.js";
|
|
60
60
|
import {
|
|
61
61
|
loadGraphStore
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-VITLE3PS.js";
|
|
63
63
|
import {
|
|
64
64
|
loadOrRebuildIndex
|
|
65
65
|
} from "./chunk-27AJKSQY.js";
|
|
66
66
|
import {
|
|
67
67
|
findConfigFile,
|
|
68
68
|
resolveConfig
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-KLE6FXWM.js";
|
|
70
70
|
import {
|
|
71
71
|
VALID_PLATFORMS
|
|
72
72
|
} from "./chunk-3ISINLYT.js";
|
|
@@ -151,7 +151,7 @@ import {
|
|
|
151
151
|
validateKnowledgeMap,
|
|
152
152
|
writeConfig,
|
|
153
153
|
writeLockfile
|
|
154
|
-
} from "./chunk-
|
|
154
|
+
} from "./chunk-6JBJ5FSW.js";
|
|
155
155
|
import {
|
|
156
156
|
Err,
|
|
157
157
|
Ok
|
|
@@ -176,7 +176,7 @@ import {
|
|
|
176
176
|
} from "./chunk-KFQGP6VL.js";
|
|
177
177
|
|
|
178
178
|
// src/index.ts
|
|
179
|
-
import { Command as
|
|
179
|
+
import { Command as Command86 } from "commander";
|
|
180
180
|
|
|
181
181
|
// src/commands/add.ts
|
|
182
182
|
init_dist();
|
|
@@ -365,7 +365,7 @@ function registerSkillsCommand(adoption) {
|
|
|
365
365
|
const globalOpts = cmd.optsWithGlobals();
|
|
366
366
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
367
367
|
const cwd = process.cwd();
|
|
368
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
368
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-46VOJIG4.js");
|
|
369
369
|
const records = readAdoptionRecords(cwd);
|
|
370
370
|
if (records.length === 0) {
|
|
371
371
|
if (globalOpts.json) {
|
|
@@ -401,7 +401,7 @@ function registerRecentCommand(adoption) {
|
|
|
401
401
|
const globalOpts = cmd.optsWithGlobals();
|
|
402
402
|
const limit = Math.max(parseInt(opts.limit, 10) || 20, 1);
|
|
403
403
|
const cwd = process.cwd();
|
|
404
|
-
const { readAdoptionRecords } = await import("./dist-
|
|
404
|
+
const { readAdoptionRecords } = await import("./dist-46VOJIG4.js");
|
|
405
405
|
const records = readAdoptionRecords(cwd);
|
|
406
406
|
if (records.length === 0) {
|
|
407
407
|
if (globalOpts.json) {
|
|
@@ -479,7 +479,7 @@ function registerSkillCommand(adoption) {
|
|
|
479
479
|
adoption.command("skill <name>").description("Show detail for a specific skill").action(async (name, _opts, cmd) => {
|
|
480
480
|
const globalOpts = cmd.optsWithGlobals();
|
|
481
481
|
const cwd = process.cwd();
|
|
482
|
-
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-
|
|
482
|
+
const { readAdoptionRecords, aggregateBySkill } = await import("./dist-46VOJIG4.js");
|
|
483
483
|
const records = readAdoptionRecords(cwd);
|
|
484
484
|
const skillRecords = records.filter((r) => r.skill === name);
|
|
485
485
|
if (skillRecords.length === 0) {
|
|
@@ -1424,10 +1424,10 @@ async function runCheckSecurity(cwd, options) {
|
|
|
1424
1424
|
const projectRoot = path8.resolve(cwd);
|
|
1425
1425
|
let configData = {};
|
|
1426
1426
|
try {
|
|
1427
|
-
const
|
|
1427
|
+
const fs42 = await import("fs");
|
|
1428
1428
|
const configPath = path8.join(projectRoot, "harness.config.json");
|
|
1429
|
-
if (
|
|
1430
|
-
const raw =
|
|
1429
|
+
if (fs42.existsSync(configPath)) {
|
|
1430
|
+
const raw = fs42.readFileSync(configPath, "utf-8");
|
|
1431
1431
|
const parsed = JSON.parse(raw);
|
|
1432
1432
|
configData = parsed.security ?? {};
|
|
1433
1433
|
}
|
|
@@ -2768,16 +2768,16 @@ import { Command as Command25 } from "commander";
|
|
|
2768
2768
|
// src/commands/graph/status.ts
|
|
2769
2769
|
import * as path15 from "path";
|
|
2770
2770
|
async function runGraphStatus(projectPath) {
|
|
2771
|
-
const { GraphStore } = await import("./dist-
|
|
2771
|
+
const { GraphStore } = await import("./dist-BP6E6QGG.js");
|
|
2772
2772
|
const graphDir = path15.join(projectPath, ".harness", "graph");
|
|
2773
2773
|
const store = new GraphStore();
|
|
2774
2774
|
const loaded = await store.load(graphDir);
|
|
2775
2775
|
if (!loaded) return { status: "no_graph", message: "No graph found. Run `harness scan` first." };
|
|
2776
|
-
const
|
|
2776
|
+
const fs42 = await import("fs/promises");
|
|
2777
2777
|
const metaPath = path15.join(graphDir, "metadata.json");
|
|
2778
2778
|
let lastScan = "unknown";
|
|
2779
2779
|
try {
|
|
2780
|
-
const meta = JSON.parse(await
|
|
2780
|
+
const meta = JSON.parse(await fs42.readFile(metaPath, "utf-8"));
|
|
2781
2781
|
lastScan = meta.lastScanTimestamp;
|
|
2782
2782
|
} catch {
|
|
2783
2783
|
}
|
|
@@ -2789,7 +2789,7 @@ async function runGraphStatus(projectPath) {
|
|
|
2789
2789
|
let connectorSyncStatus = {};
|
|
2790
2790
|
try {
|
|
2791
2791
|
const syncMetaPath = path15.join(graphDir, "sync-metadata.json");
|
|
2792
|
-
const syncMeta = JSON.parse(await
|
|
2792
|
+
const syncMeta = JSON.parse(await fs42.readFile(syncMetaPath, "utf-8"));
|
|
2793
2793
|
for (const [name, data] of Object.entries(syncMeta.connectors ?? {})) {
|
|
2794
2794
|
connectorSyncStatus[name] = data.lastSyncTimestamp;
|
|
2795
2795
|
}
|
|
@@ -2808,7 +2808,7 @@ async function runGraphStatus(projectPath) {
|
|
|
2808
2808
|
// src/commands/graph/export.ts
|
|
2809
2809
|
import * as path16 from "path";
|
|
2810
2810
|
async function runGraphExport(projectPath, format) {
|
|
2811
|
-
const { GraphStore } = await import("./dist-
|
|
2811
|
+
const { GraphStore } = await import("./dist-BP6E6QGG.js");
|
|
2812
2812
|
const graphDir = path16.join(projectPath, ".harness", "graph");
|
|
2813
2813
|
const store = new GraphStore();
|
|
2814
2814
|
const loaded = await store.load(graphDir);
|
|
@@ -2844,7 +2844,7 @@ import * as path19 from "path";
|
|
|
2844
2844
|
import { Command as Command23 } from "commander";
|
|
2845
2845
|
import * as path17 from "path";
|
|
2846
2846
|
async function runQuery(projectPath, rootNodeId, opts) {
|
|
2847
|
-
const { GraphStore, ContextQL } = await import("./dist-
|
|
2847
|
+
const { GraphStore, ContextQL } = await import("./dist-BP6E6QGG.js");
|
|
2848
2848
|
const store = new GraphStore();
|
|
2849
2849
|
const graphDir = path17.join(projectPath, ".harness", "graph");
|
|
2850
2850
|
const loaded = await store.load(graphDir);
|
|
@@ -2897,9 +2897,9 @@ import { Command as Command24 } from "commander";
|
|
|
2897
2897
|
import * as path18 from "path";
|
|
2898
2898
|
async function loadConnectorConfig(projectPath, source) {
|
|
2899
2899
|
try {
|
|
2900
|
-
const
|
|
2900
|
+
const fs42 = await import("fs/promises");
|
|
2901
2901
|
const configPath = path18.join(projectPath, "harness.config.json");
|
|
2902
|
-
const config = JSON.parse(await
|
|
2902
|
+
const config = JSON.parse(await fs42.readFile(configPath, "utf-8"));
|
|
2903
2903
|
const connector = config.graph?.connectors?.[source];
|
|
2904
2904
|
return connector ?? {};
|
|
2905
2905
|
} catch {
|
|
@@ -2933,6 +2933,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2933
2933
|
TopologicalLinker,
|
|
2934
2934
|
KnowledgeIngestor,
|
|
2935
2935
|
GitIngestor,
|
|
2936
|
+
RequirementIngestor,
|
|
2936
2937
|
SyncManager,
|
|
2937
2938
|
JiraConnector,
|
|
2938
2939
|
SlackConnector,
|
|
@@ -2940,7 +2941,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2940
2941
|
ConfluenceConnector,
|
|
2941
2942
|
FigmaConnector,
|
|
2942
2943
|
MiroConnector
|
|
2943
|
-
} = await import("./dist-
|
|
2944
|
+
} = await import("./dist-BP6E6QGG.js");
|
|
2944
2945
|
const graphDir = path18.join(projectPath, ".harness", "graph");
|
|
2945
2946
|
const store = new GraphStore();
|
|
2946
2947
|
await store.load(graphDir);
|
|
@@ -2949,8 +2950,11 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2949
2950
|
const codeResult = await new CodeIngestor(store).ingest(projectPath);
|
|
2950
2951
|
new TopologicalLinker(store).link();
|
|
2951
2952
|
const knowledgeResult = await new KnowledgeIngestor(store).ingestAll(projectPath);
|
|
2953
|
+
const reqResult = await new RequirementIngestor(store).ingestSpecs(
|
|
2954
|
+
path18.join(projectPath, "docs", "changes")
|
|
2955
|
+
);
|
|
2952
2956
|
const gitResult = await new GitIngestor(store).ingest(projectPath);
|
|
2953
|
-
const { createExtractionRunner } = await import("./dist-
|
|
2957
|
+
const { createExtractionRunner } = await import("./dist-BP6E6QGG.js");
|
|
2954
2958
|
const extractedDir = path18.join(projectPath, ".harness", "knowledge", "extracted");
|
|
2955
2959
|
const signalsResult = await createExtractionRunner().run(projectPath, store, extractedDir);
|
|
2956
2960
|
const syncManager = new SyncManager(store, graphDir);
|
|
@@ -2971,6 +2975,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2971
2975
|
const merged = mergeResults(
|
|
2972
2976
|
codeResult,
|
|
2973
2977
|
knowledgeResult,
|
|
2978
|
+
reqResult,
|
|
2974
2979
|
gitResult,
|
|
2975
2980
|
signalsResult,
|
|
2976
2981
|
connectorResult
|
|
@@ -2989,8 +2994,13 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2989
2994
|
case "git":
|
|
2990
2995
|
result = await new GitIngestor(store).ingest(projectPath);
|
|
2991
2996
|
break;
|
|
2997
|
+
case "requirements":
|
|
2998
|
+
result = await new RequirementIngestor(store).ingestSpecs(
|
|
2999
|
+
path18.join(projectPath, "docs", "changes")
|
|
3000
|
+
);
|
|
3001
|
+
break;
|
|
2992
3002
|
case "business-signals": {
|
|
2993
|
-
const { createExtractionRunner } = await import("./dist-
|
|
3003
|
+
const { createExtractionRunner } = await import("./dist-BP6E6QGG.js");
|
|
2994
3004
|
const extractedDir = path18.join(projectPath, ".harness", "knowledge", "extracted");
|
|
2995
3005
|
result = await createExtractionRunner().run(projectPath, store, extractedDir);
|
|
2996
3006
|
break;
|
|
@@ -2999,7 +3009,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
2999
3009
|
const knownConnectors = ["jira", "slack", "ci", "confluence", "figma", "miro"];
|
|
3000
3010
|
if (!knownConnectors.includes(source)) {
|
|
3001
3011
|
throw new Error(
|
|
3002
|
-
`Unknown source: ${source}. Available: code, knowledge, git, business-signals, jira, slack, ci, confluence, figma, miro`
|
|
3012
|
+
`Unknown source: ${source}. Available: code, knowledge, git, requirements, business-signals, jira, slack, ci, confluence, figma, miro`
|
|
3003
3013
|
);
|
|
3004
3014
|
}
|
|
3005
3015
|
if (!SyncManager) {
|
|
@@ -3029,7 +3039,7 @@ async function runIngest(projectPath, source, opts) {
|
|
|
3029
3039
|
function createIngestCommand() {
|
|
3030
3040
|
return new Command24("ingest").description("Ingest data into the knowledge graph").option(
|
|
3031
3041
|
"--source <name>",
|
|
3032
|
-
"Source to ingest (code, knowledge, git, business-signals, jira, slack, ci, confluence, figma, miro)"
|
|
3042
|
+
"Source to ingest (code, knowledge, git, requirements, business-signals, jira, slack, ci, confluence, figma, miro)"
|
|
3033
3043
|
).option("--all", "Run all sources (code, knowledge, git, and configured connectors)").option("--full", "Force full re-ingestion").action(async (opts, cmd) => {
|
|
3034
3044
|
if (!opts.source && !opts.all) {
|
|
3035
3045
|
console.error("Error: --source or --all is required");
|
|
@@ -5289,21 +5299,28 @@ function createKnowledgePipelineCommand() {
|
|
|
5289
5299
|
const projectDir = process.cwd();
|
|
5290
5300
|
try {
|
|
5291
5301
|
const graphDir = path38.join(projectDir, ".harness", "graph");
|
|
5292
|
-
const
|
|
5293
|
-
await
|
|
5294
|
-
const { GraphStore, KnowledgePipelineRunner } = await import("./dist-
|
|
5302
|
+
const fs42 = await import("fs/promises");
|
|
5303
|
+
await fs42.mkdir(graphDir, { recursive: true });
|
|
5304
|
+
const { GraphStore, KnowledgePipelineRunner } = await import("./dist-BP6E6QGG.js");
|
|
5295
5305
|
const store = new GraphStore();
|
|
5296
5306
|
try {
|
|
5297
5307
|
await store.load(graphDir);
|
|
5298
5308
|
} catch {
|
|
5299
5309
|
}
|
|
5310
|
+
const cfgResult = resolveConfig();
|
|
5311
|
+
const cfgKnowledge = cfgResult.ok ? cfgResult.value.knowledge : void 0;
|
|
5312
|
+
const inferenceOptions = cfgKnowledge && ((cfgKnowledge.domainPatterns?.length ?? 0) > 0 || (cfgKnowledge.domainBlocklist?.length ?? 0) > 0) ? {
|
|
5313
|
+
...(cfgKnowledge.domainPatterns?.length ?? 0) > 0 ? { extraPatterns: cfgKnowledge.domainPatterns } : {},
|
|
5314
|
+
...(cfgKnowledge.domainBlocklist?.length ?? 0) > 0 ? { extraBlocklist: cfgKnowledge.domainBlocklist } : {}
|
|
5315
|
+
} : void 0;
|
|
5300
5316
|
const pipelineOpts = {
|
|
5301
5317
|
projectDir,
|
|
5302
5318
|
fix: Boolean(opts.fix),
|
|
5303
5319
|
ci: Boolean(opts.ci),
|
|
5304
5320
|
...opts.domain ? { domain: opts.domain } : {},
|
|
5305
5321
|
graphDir,
|
|
5306
|
-
analyzeImages: Boolean(opts.analyzeImages)
|
|
5322
|
+
analyzeImages: Boolean(opts.analyzeImages),
|
|
5323
|
+
...inferenceOptions ? { inferenceOptions } : {}
|
|
5307
5324
|
};
|
|
5308
5325
|
if (opts.imagePaths) {
|
|
5309
5326
|
pipelineOpts.imagePaths = opts.imagePaths.split(",").map((p) => p.trim());
|
|
@@ -5626,7 +5643,7 @@ function createMcpCommand() {
|
|
|
5626
5643
|
parseBudget
|
|
5627
5644
|
).action(async (opts) => {
|
|
5628
5645
|
const [{ startServer: startServer2, getToolDefinitions: getToolDefinitions2 }, { selectTier }] = await Promise.all([
|
|
5629
|
-
import("./mcp-
|
|
5646
|
+
import("./mcp-N6XVWPXJ.js"),
|
|
5630
5647
|
import("./tool-tiers-7QGZ3FKY.js")
|
|
5631
5648
|
]);
|
|
5632
5649
|
if (opts.tools && opts.tools.length > 0) {
|
|
@@ -5646,14 +5663,439 @@ function createMcpCommand() {
|
|
|
5646
5663
|
});
|
|
5647
5664
|
}
|
|
5648
5665
|
|
|
5649
|
-
// src/commands/
|
|
5666
|
+
// src/commands/migrate.ts
|
|
5667
|
+
init_dist();
|
|
5650
5668
|
import { Command as Command48 } from "commander";
|
|
5669
|
+
import * as fs24 from "fs";
|
|
5651
5670
|
import * as path40 from "path";
|
|
5671
|
+
import * as readline from "readline";
|
|
5672
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
5673
|
+
import chalk7 from "chalk";
|
|
5674
|
+
function isGitRepo(cwd) {
|
|
5675
|
+
try {
|
|
5676
|
+
execFileSync4("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe" });
|
|
5677
|
+
return true;
|
|
5678
|
+
} catch {
|
|
5679
|
+
return false;
|
|
5680
|
+
}
|
|
5681
|
+
}
|
|
5682
|
+
function readJson2(p) {
|
|
5683
|
+
try {
|
|
5684
|
+
return JSON.parse(fs24.readFileSync(p, "utf8"));
|
|
5685
|
+
} catch {
|
|
5686
|
+
return null;
|
|
5687
|
+
}
|
|
5688
|
+
}
|
|
5689
|
+
function listDirs(p) {
|
|
5690
|
+
if (!fs24.existsSync(p)) return [];
|
|
5691
|
+
return fs24.readdirSync(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
5692
|
+
}
|
|
5693
|
+
function listFiles(p, predicate) {
|
|
5694
|
+
if (!fs24.existsSync(p)) return [];
|
|
5695
|
+
return fs24.readdirSync(p, { withFileTypes: true }).filter((e) => e.isFile() && predicate(e.name)).map((e) => e.name);
|
|
5696
|
+
}
|
|
5697
|
+
function walk(dir, predicate) {
|
|
5698
|
+
if (!fs24.existsSync(dir)) return [];
|
|
5699
|
+
const out = [];
|
|
5700
|
+
for (const entry of fs24.readdirSync(dir, { withFileTypes: true })) {
|
|
5701
|
+
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
|
|
5702
|
+
const full = path40.join(dir, entry.name);
|
|
5703
|
+
if (entry.isDirectory()) out.push(...walk(full, predicate));
|
|
5704
|
+
else if (predicate(full)) out.push(full);
|
|
5705
|
+
}
|
|
5706
|
+
return out;
|
|
5707
|
+
}
|
|
5708
|
+
function buildAutopilotMap(cwd) {
|
|
5709
|
+
const map = /* @__PURE__ */ new Map();
|
|
5710
|
+
const sessionsDir = path40.join(cwd, ".harness", "sessions");
|
|
5711
|
+
if (!fs24.existsSync(sessionsDir)) return map;
|
|
5712
|
+
for (const session of listDirs(sessionsDir)) {
|
|
5713
|
+
if (!session.startsWith("changes--")) continue;
|
|
5714
|
+
const statePath = path40.join(sessionsDir, session, "autopilot-state.json");
|
|
5715
|
+
const state = readJson2(statePath);
|
|
5716
|
+
if (!state) continue;
|
|
5717
|
+
const specMatch = (state.specPath ?? "").match(/^docs\/changes\/([^/]+)\/proposal\.md$/);
|
|
5718
|
+
if (!specMatch || !specMatch[1]) continue;
|
|
5719
|
+
const topic = specMatch[1];
|
|
5720
|
+
for (const phase of state.phases ?? []) {
|
|
5721
|
+
if (phase.planPath) map.set(phase.planPath, topic);
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
return map;
|
|
5725
|
+
}
|
|
5726
|
+
function extractTopicFromHeader(planAbs, proposalTopics) {
|
|
5727
|
+
let text;
|
|
5728
|
+
try {
|
|
5729
|
+
text = fs24.readFileSync(planAbs, "utf8").slice(0, 4e3);
|
|
5730
|
+
} catch {
|
|
5731
|
+
return null;
|
|
5732
|
+
}
|
|
5733
|
+
const patterns = [
|
|
5734
|
+
/docs\/changes\/([a-z0-9-]+)\/proposal\.md/i,
|
|
5735
|
+
/\*\*Spec[^:]*:\*\*\s*\[?[^\]]*?docs\/changes\/([a-z0-9-]+)/i
|
|
5736
|
+
];
|
|
5737
|
+
for (const re of patterns) {
|
|
5738
|
+
const m = text.match(re);
|
|
5739
|
+
if (m && m[1] && proposalTopics.has(m[1])) return m[1];
|
|
5740
|
+
}
|
|
5741
|
+
return null;
|
|
5742
|
+
}
|
|
5743
|
+
function extractTopicFromFilename(filename, proposalTopics) {
|
|
5744
|
+
const stripped = filename.replace(/\.md$/, "").replace(/^\d{4}-\d{2}-\d{2}-/, "").replace(/-plan$/, "").replace(/-phase\d+(-plan)?$/, "");
|
|
5745
|
+
if (proposalTopics.has(stripped)) return stripped;
|
|
5746
|
+
let best = null;
|
|
5747
|
+
for (const topic of proposalTopics) {
|
|
5748
|
+
const matches = stripped === topic || stripped.startsWith(`${topic}-`);
|
|
5749
|
+
if (matches && (!best || topic.length > best.length)) best = topic;
|
|
5750
|
+
}
|
|
5751
|
+
return best;
|
|
5752
|
+
}
|
|
5753
|
+
async function buildPlan(opts) {
|
|
5754
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
5755
|
+
const configPath = path40.join(cwd, "harness.config.json");
|
|
5756
|
+
const configResult = fs24.existsSync(configPath) ? resolveConfig(configPath) : null;
|
|
5757
|
+
let docsDir;
|
|
5758
|
+
if (configResult?.ok) {
|
|
5759
|
+
docsDir = configResult.value.docsDir;
|
|
5760
|
+
} else if (configResult && !configResult.ok) {
|
|
5761
|
+
docsDir = "./docs";
|
|
5762
|
+
logger.warn(
|
|
5763
|
+
` harness.config.json failed to load (${configResult.error.message}); defaulting docsDir to ./docs`
|
|
5764
|
+
);
|
|
5765
|
+
} else {
|
|
5766
|
+
docsDir = "./docs";
|
|
5767
|
+
}
|
|
5768
|
+
const docsAbs = path40.resolve(cwd, docsDir);
|
|
5769
|
+
const relRaw = path40.relative(cwd, docsAbs).replaceAll("\\", "/");
|
|
5770
|
+
const docsRel = relRaw === "" ? "docs" : relRaw;
|
|
5771
|
+
const plan = {
|
|
5772
|
+
cwd,
|
|
5773
|
+
isGitRepo: isGitRepo(cwd),
|
|
5774
|
+
docsDir: docsRel,
|
|
5775
|
+
adrMoves: [],
|
|
5776
|
+
planMoves: [],
|
|
5777
|
+
orphanPlans: [],
|
|
5778
|
+
proposalTopics: /* @__PURE__ */ new Set()
|
|
5779
|
+
};
|
|
5780
|
+
const legacyAdrDir = path40.join(cwd, ".harness", "architecture");
|
|
5781
|
+
if (fs24.existsSync(legacyAdrDir)) {
|
|
5782
|
+
for (const topic of listDirs(legacyAdrDir)) {
|
|
5783
|
+
const topicSrc = path40.join(legacyAdrDir, topic);
|
|
5784
|
+
for (const file of listFiles(topicSrc, (n) => n.endsWith(".md"))) {
|
|
5785
|
+
const src = `.harness/architecture/${topic}/${file}`;
|
|
5786
|
+
const dest = `${docsRel}/architecture/${topic}/${file}`;
|
|
5787
|
+
plan.adrMoves.push({ src, dest });
|
|
5788
|
+
}
|
|
5789
|
+
}
|
|
5790
|
+
}
|
|
5791
|
+
const legacyPlanDir = path40.join(cwd, "docs", "plans");
|
|
5792
|
+
const changesDir = path40.join(cwd, "docs", "changes");
|
|
5793
|
+
for (const topic of listDirs(changesDir)) {
|
|
5794
|
+
if (fs24.existsSync(path40.join(changesDir, topic, "proposal.md"))) {
|
|
5795
|
+
plan.proposalTopics.add(topic);
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5798
|
+
if (fs24.existsSync(legacyPlanDir)) {
|
|
5799
|
+
const autopilotMap = buildAutopilotMap(cwd);
|
|
5800
|
+
for (const file of listFiles(legacyPlanDir, (n) => n.endsWith(".md") && n !== "index.md")) {
|
|
5801
|
+
const planRel = `docs/plans/${file}`;
|
|
5802
|
+
const planAbs = path40.join(legacyPlanDir, file);
|
|
5803
|
+
const isVerification = file.includes("VERIFICATION");
|
|
5804
|
+
const subdir = isVerification ? "verifications" : "plans";
|
|
5805
|
+
let topic = null;
|
|
5806
|
+
let via = null;
|
|
5807
|
+
const fromAutopilot = autopilotMap.get(planRel);
|
|
5808
|
+
if (fromAutopilot) {
|
|
5809
|
+
topic = fromAutopilot;
|
|
5810
|
+
via = "autopilot";
|
|
5811
|
+
} else {
|
|
5812
|
+
const fromHeader = extractTopicFromHeader(planAbs, plan.proposalTopics);
|
|
5813
|
+
if (fromHeader) {
|
|
5814
|
+
topic = fromHeader;
|
|
5815
|
+
via = "header";
|
|
5816
|
+
} else {
|
|
5817
|
+
const fromFilename = extractTopicFromFilename(file, plan.proposalTopics);
|
|
5818
|
+
if (fromFilename) {
|
|
5819
|
+
topic = fromFilename;
|
|
5820
|
+
via = "filename";
|
|
5821
|
+
}
|
|
5822
|
+
}
|
|
5823
|
+
}
|
|
5824
|
+
if (topic && via) {
|
|
5825
|
+
plan.planMoves.push({
|
|
5826
|
+
src: planRel,
|
|
5827
|
+
dest: `docs/changes/${topic}/${subdir}/${file}`,
|
|
5828
|
+
topic,
|
|
5829
|
+
via,
|
|
5830
|
+
subdir
|
|
5831
|
+
});
|
|
5832
|
+
} else {
|
|
5833
|
+
plan.orphanPlans.push(planRel);
|
|
5834
|
+
}
|
|
5835
|
+
}
|
|
5836
|
+
}
|
|
5837
|
+
return plan;
|
|
5838
|
+
}
|
|
5839
|
+
function gitMv(cwd, src, dest, useGit) {
|
|
5840
|
+
const destAbs = path40.join(cwd, dest);
|
|
5841
|
+
fs24.mkdirSync(path40.dirname(destAbs), { recursive: true });
|
|
5842
|
+
if (useGit) {
|
|
5843
|
+
try {
|
|
5844
|
+
execFileSync4("git", ["mv", src, dest], { cwd, stdio: "pipe" });
|
|
5845
|
+
return;
|
|
5846
|
+
} catch {
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
fs24.renameSync(path40.join(cwd, src), destAbs);
|
|
5850
|
+
}
|
|
5851
|
+
function executeMigration(plan) {
|
|
5852
|
+
let moved = 0;
|
|
5853
|
+
let failed = 0;
|
|
5854
|
+
const allMoves = [
|
|
5855
|
+
...plan.adrMoves.map((m) => ({ src: m.src, dest: m.dest })),
|
|
5856
|
+
...plan.planMoves.map((m) => ({ src: m.src, dest: m.dest }))
|
|
5857
|
+
];
|
|
5858
|
+
for (const { src, dest } of allMoves) {
|
|
5859
|
+
try {
|
|
5860
|
+
gitMv(plan.cwd, src, dest, plan.isGitRepo);
|
|
5861
|
+
moved++;
|
|
5862
|
+
} catch (err) {
|
|
5863
|
+
failed++;
|
|
5864
|
+
logger.error(` ${src} -> ${dest}: ${err.message}`);
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
return { moved, failed };
|
|
5868
|
+
}
|
|
5869
|
+
function buildPathLookup(plan) {
|
|
5870
|
+
const lookup = /* @__PURE__ */ new Map();
|
|
5871
|
+
for (const m of plan.adrMoves) lookup.set(m.src, m.dest);
|
|
5872
|
+
for (const m of plan.planMoves) lookup.set(m.src, m.dest);
|
|
5873
|
+
return lookup;
|
|
5874
|
+
}
|
|
5875
|
+
function updateReferences(plan) {
|
|
5876
|
+
const lookup = buildPathLookup(plan);
|
|
5877
|
+
if (lookup.size === 0) return { files: 0, replacements: 0 };
|
|
5878
|
+
let files = 0;
|
|
5879
|
+
let replacements = 0;
|
|
5880
|
+
const targets = [
|
|
5881
|
+
...walk(path40.join(plan.cwd, "docs"), (p) => p.endsWith(".md")),
|
|
5882
|
+
...walk(path40.join(plan.cwd, ".harness", "sessions"), (p) => p.endsWith(".json"))
|
|
5883
|
+
];
|
|
5884
|
+
for (const target of targets) {
|
|
5885
|
+
let text;
|
|
5886
|
+
try {
|
|
5887
|
+
text = fs24.readFileSync(target, "utf8");
|
|
5888
|
+
} catch {
|
|
5889
|
+
continue;
|
|
5890
|
+
}
|
|
5891
|
+
let result = text;
|
|
5892
|
+
let count = 0;
|
|
5893
|
+
for (const [oldPath, newPath] of lookup) {
|
|
5894
|
+
if (!result.includes(oldPath)) continue;
|
|
5895
|
+
const occurrences = result.split(oldPath).length - 1;
|
|
5896
|
+
result = result.split(oldPath).join(newPath);
|
|
5897
|
+
count += occurrences;
|
|
5898
|
+
}
|
|
5899
|
+
if (count > 0 && result !== text) {
|
|
5900
|
+
fs24.writeFileSync(target, result);
|
|
5901
|
+
files++;
|
|
5902
|
+
replacements += count;
|
|
5903
|
+
}
|
|
5904
|
+
}
|
|
5905
|
+
return { files, replacements };
|
|
5906
|
+
}
|
|
5907
|
+
async function prompt(question, def = "n") {
|
|
5908
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
5909
|
+
const answer = await new Promise((resolve37) => rl.question(question, resolve37));
|
|
5910
|
+
rl.close();
|
|
5911
|
+
return (answer.trim() || def).toLowerCase();
|
|
5912
|
+
}
|
|
5913
|
+
async function resolveOrphans(plan, opts) {
|
|
5914
|
+
if (plan.orphanPlans.length === 0) return Ok({ bucketed: false });
|
|
5915
|
+
const strategy = opts.orphanStrategy ?? "ask";
|
|
5916
|
+
if (opts.dryRun && strategy === "ask") {
|
|
5917
|
+
logger.info(
|
|
5918
|
+
` Dry run + --orphan-strategy=ask: not prompting; ${plan.orphanPlans.length} orphan(s) would be asked about interactively.`
|
|
5919
|
+
);
|
|
5920
|
+
return Ok({ bucketed: false });
|
|
5921
|
+
}
|
|
5922
|
+
if (strategy === "skip" || strategy === "ask" && !process.stdin.isTTY) {
|
|
5923
|
+
logger.warn(
|
|
5924
|
+
` Leaving ${plan.orphanPlans.length} orphan plan(s) in docs/plans/ (no proposal match)`
|
|
5925
|
+
);
|
|
5926
|
+
return Ok({ bucketed: false });
|
|
5927
|
+
}
|
|
5928
|
+
if (strategy === "bucket") {
|
|
5929
|
+
const topic = opts.orphanTopic;
|
|
5930
|
+
if (!topic) {
|
|
5931
|
+
return Err(
|
|
5932
|
+
new CLIError(
|
|
5933
|
+
"--orphan-strategy=bucket requires --orphan-topic <name>",
|
|
5934
|
+
ExitCode.VALIDATION_FAILED
|
|
5935
|
+
)
|
|
5936
|
+
);
|
|
5937
|
+
}
|
|
5938
|
+
bucketOrphans(plan, topic);
|
|
5939
|
+
return Ok({ bucketed: true });
|
|
5940
|
+
}
|
|
5941
|
+
console.log("");
|
|
5942
|
+
logger.warn(`Found ${plan.orphanPlans.length} plan(s) with no matching proposal:`);
|
|
5943
|
+
for (const p of plan.orphanPlans.slice(0, 10)) console.log(` - ${p}`);
|
|
5944
|
+
if (plan.orphanPlans.length > 10) console.log(` ... and ${plan.orphanPlans.length - 10} more`);
|
|
5945
|
+
console.log("");
|
|
5946
|
+
console.log(" [s] skip \u2014 leave them in docs/plans/");
|
|
5947
|
+
console.log(" [b] bucket \u2014 move all into a single stub topic");
|
|
5948
|
+
const answer = await prompt("Choose [s/b]: ", "s");
|
|
5949
|
+
if (answer === "b") {
|
|
5950
|
+
const topicAnswer = await prompt("Stub topic name (e.g., legacy-plans): ", "legacy-plans");
|
|
5951
|
+
bucketOrphans(plan, topicAnswer);
|
|
5952
|
+
return Ok({ bucketed: true });
|
|
5953
|
+
}
|
|
5954
|
+
logger.info(` Leaving ${plan.orphanPlans.length} orphan plan(s) in docs/plans/`);
|
|
5955
|
+
return Ok({ bucketed: false });
|
|
5956
|
+
}
|
|
5957
|
+
function bucketOrphans(plan, topic) {
|
|
5958
|
+
for (const src of plan.orphanPlans) {
|
|
5959
|
+
const file = path40.basename(src);
|
|
5960
|
+
const isVerification = file.includes("VERIFICATION");
|
|
5961
|
+
const subdir = isVerification ? "verifications" : "plans";
|
|
5962
|
+
plan.planMoves.push({
|
|
5963
|
+
src,
|
|
5964
|
+
dest: `docs/changes/${topic}/${subdir}/${file}`,
|
|
5965
|
+
topic,
|
|
5966
|
+
via: "orphan-bucket",
|
|
5967
|
+
subdir
|
|
5968
|
+
});
|
|
5969
|
+
}
|
|
5970
|
+
plan.orphanPlans = [];
|
|
5971
|
+
}
|
|
5972
|
+
function summarize(plan) {
|
|
5973
|
+
console.log("");
|
|
5974
|
+
console.log(chalk7.bold(" Migration plan"));
|
|
5975
|
+
console.log("");
|
|
5976
|
+
if (plan.adrMoves.length === 0 && plan.planMoves.length === 0 && plan.orphanPlans.length === 0) {
|
|
5977
|
+
logger.success(" Nothing to migrate. Layout is already up to date.");
|
|
5978
|
+
return;
|
|
5979
|
+
}
|
|
5980
|
+
if (plan.adrMoves.length > 0) {
|
|
5981
|
+
logger.info(
|
|
5982
|
+
` ADR moves: ${plan.adrMoves.length} file(s) \u2014 .harness/architecture/ \u2192 ${plan.docsDir}/architecture/`
|
|
5983
|
+
);
|
|
5984
|
+
}
|
|
5985
|
+
if (plan.planMoves.length > 0) {
|
|
5986
|
+
const byVia = plan.planMoves.reduce((acc, m) => {
|
|
5987
|
+
acc[m.via] = (acc[m.via] ?? 0) + 1;
|
|
5988
|
+
return acc;
|
|
5989
|
+
}, {});
|
|
5990
|
+
logger.info(
|
|
5991
|
+
` Plan moves: ${plan.planMoves.length} file(s) \u2014 docs/plans/ \u2192 docs/changes/<topic>/{plans,verifications}/`
|
|
5992
|
+
);
|
|
5993
|
+
for (const [via, n] of Object.entries(byVia)) console.log(chalk7.dim(` via ${via}: ${n}`));
|
|
5994
|
+
}
|
|
5995
|
+
if (plan.orphanPlans.length > 0) {
|
|
5996
|
+
logger.warn(` Orphan plans: ${plan.orphanPlans.length} (no proposal match \u2014 needs decision)`);
|
|
5997
|
+
}
|
|
5998
|
+
console.log("");
|
|
5999
|
+
}
|
|
6000
|
+
async function detectLegacyArtifacts(cwd) {
|
|
6001
|
+
const adrLegacyDir = path40.join(cwd, ".harness", "architecture");
|
|
6002
|
+
const adrLegacy = fs24.existsSync(adrLegacyDir) && listDirs(adrLegacyDir).length > 0;
|
|
6003
|
+
const planLegacyDir = path40.join(cwd, "docs", "plans");
|
|
6004
|
+
const planLegacy = fs24.existsSync(planLegacyDir) && listFiles(planLegacyDir, (n) => n.endsWith(".md") && n !== "index.md").length > 0;
|
|
6005
|
+
return { adrLegacy, planLegacy };
|
|
6006
|
+
}
|
|
6007
|
+
async function runMigrate(opts) {
|
|
6008
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
6009
|
+
const plan = await buildPlan({ ...opts, cwd });
|
|
6010
|
+
summarize(plan);
|
|
6011
|
+
const empty = {
|
|
6012
|
+
movesPlanned: 0,
|
|
6013
|
+
movesApplied: 0,
|
|
6014
|
+
movesFailed: 0,
|
|
6015
|
+
orphansRemaining: 0,
|
|
6016
|
+
references: { files: 0, replacements: 0 },
|
|
6017
|
+
dryRun: opts.dryRun ?? false,
|
|
6018
|
+
cancelled: false
|
|
6019
|
+
};
|
|
6020
|
+
if (plan.adrMoves.length === 0 && plan.planMoves.length === 0 && plan.orphanPlans.length === 0) {
|
|
6021
|
+
return Ok(empty);
|
|
6022
|
+
}
|
|
6023
|
+
const orphanResult = await resolveOrphans(plan, opts);
|
|
6024
|
+
if (!orphanResult.ok) return orphanResult;
|
|
6025
|
+
if (orphanResult.value.bucketed) summarize(plan);
|
|
6026
|
+
const movesPlanned = plan.adrMoves.length + plan.planMoves.length;
|
|
6027
|
+
if (opts.dryRun) {
|
|
6028
|
+
logger.info(" Dry run \u2014 no files moved.");
|
|
6029
|
+
return Ok({
|
|
6030
|
+
...empty,
|
|
6031
|
+
movesPlanned,
|
|
6032
|
+
orphansRemaining: plan.orphanPlans.length
|
|
6033
|
+
});
|
|
6034
|
+
}
|
|
6035
|
+
if (!opts.yes && process.stdin.isTTY) {
|
|
6036
|
+
const answer = await prompt("Apply this migration? [y/N] ", "n");
|
|
6037
|
+
if (answer !== "y" && answer !== "yes") {
|
|
6038
|
+
logger.info(" Cancelled.");
|
|
6039
|
+
return Ok({ ...empty, movesPlanned, cancelled: true });
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
const { moved, failed } = executeMigration(plan);
|
|
6043
|
+
console.log("");
|
|
6044
|
+
if (failed > 0) logger.error(` Moved ${moved} file(s); ${failed} failed.`);
|
|
6045
|
+
else logger.success(` Moved ${moved} file(s).`);
|
|
6046
|
+
let references = { files: 0, replacements: 0 };
|
|
6047
|
+
if (!opts.skipReferences) {
|
|
6048
|
+
references = updateReferences(plan);
|
|
6049
|
+
if (references.replacements > 0) {
|
|
6050
|
+
logger.success(
|
|
6051
|
+
` Updated ${references.replacements} reference(s) across ${references.files} file(s).`
|
|
6052
|
+
);
|
|
6053
|
+
} else {
|
|
6054
|
+
logger.info(" No reference updates needed.");
|
|
6055
|
+
}
|
|
6056
|
+
}
|
|
6057
|
+
console.log("");
|
|
6058
|
+
logger.info(" Next: review changes with `git status` / `git diff`, then commit.");
|
|
6059
|
+
return Ok({
|
|
6060
|
+
movesPlanned,
|
|
6061
|
+
movesApplied: moved,
|
|
6062
|
+
movesFailed: failed,
|
|
6063
|
+
orphansRemaining: plan.orphanPlans.length,
|
|
6064
|
+
references,
|
|
6065
|
+
dryRun: false,
|
|
6066
|
+
cancelled: false
|
|
6067
|
+
});
|
|
6068
|
+
}
|
|
6069
|
+
function createMigrateCommand() {
|
|
6070
|
+
return new Command48("migrate").description("Migrate legacy harness artifact locations to current layout").option("--dry-run", "Show the migration plan without moving files", false).option("--yes", "Skip confirmation prompt", false).option(
|
|
6071
|
+
"--skip-references",
|
|
6072
|
+
"Do not update path references in docs/sessions after moves",
|
|
6073
|
+
false
|
|
6074
|
+
).option("--orphan-strategy <strategy>", "How to handle orphan plans (ask|skip|bucket)", "ask").option("--orphan-topic <name>", "Stub topic name when --orphan-strategy=bucket").action(async (options) => {
|
|
6075
|
+
const result = await runMigrate({
|
|
6076
|
+
cwd: process.cwd(),
|
|
6077
|
+
dryRun: options.dryRun,
|
|
6078
|
+
yes: options.yes,
|
|
6079
|
+
skipReferences: options.skipReferences,
|
|
6080
|
+
orphanStrategy: options.orphanStrategy,
|
|
6081
|
+
orphanTopic: options.orphanTopic
|
|
6082
|
+
});
|
|
6083
|
+
if (!result.ok) {
|
|
6084
|
+
logger.error(result.error.message);
|
|
6085
|
+
process.exit(result.error.exitCode);
|
|
6086
|
+
}
|
|
6087
|
+
process.exit(result.value.movesFailed === 0 ? ExitCode.SUCCESS : ExitCode.ERROR);
|
|
6088
|
+
});
|
|
6089
|
+
}
|
|
6090
|
+
|
|
6091
|
+
// src/commands/orchestrator.ts
|
|
6092
|
+
import { Command as Command49 } from "commander";
|
|
6093
|
+
import * as path41 from "path";
|
|
5652
6094
|
import { Orchestrator, WorkflowLoader, launchTUI } from "@harness-engineering/orchestrator";
|
|
5653
6095
|
function createOrchestratorCommand() {
|
|
5654
|
-
const orchestrator = new
|
|
6096
|
+
const orchestrator = new Command49("orchestrator");
|
|
5655
6097
|
orchestrator.command("run").description("Run the orchestrator daemon").option("-w, --workflow <path>", "Path to harness.orchestrator.md", "harness.orchestrator.md").option("--headless", "Run without TUI (server-only mode for use with web dashboard)").action(async (opts) => {
|
|
5656
|
-
const workflowPath =
|
|
6098
|
+
const workflowPath = path41.resolve(process.cwd(), opts.workflow);
|
|
5657
6099
|
const loader = new WorkflowLoader();
|
|
5658
6100
|
const result = await loader.loadWorkflow(workflowPath);
|
|
5659
6101
|
if (!result.ok) {
|
|
@@ -5686,13 +6128,13 @@ function createOrchestratorCommand() {
|
|
|
5686
6128
|
|
|
5687
6129
|
// src/commands/perf.ts
|
|
5688
6130
|
init_dist();
|
|
5689
|
-
import { Command as
|
|
5690
|
-
import * as
|
|
6131
|
+
import { Command as Command50 } from "commander";
|
|
6132
|
+
import * as path42 from "path";
|
|
5691
6133
|
function registerBenchCommand(perf) {
|
|
5692
6134
|
perf.command("bench [glob]").description("Run benchmarks via vitest bench").action(async (glob2, _opts, cmd) => {
|
|
5693
6135
|
const globalOpts = cmd.optsWithGlobals();
|
|
5694
6136
|
const cwd = process.cwd();
|
|
5695
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
6137
|
+
const { BenchmarkRunner } = await import("./dist-46VOJIG4.js");
|
|
5696
6138
|
const runner = new BenchmarkRunner();
|
|
5697
6139
|
const benchFiles = runner.discover(cwd, glob2);
|
|
5698
6140
|
if (benchFiles.length === 0) {
|
|
@@ -5756,7 +6198,7 @@ async function runBaselinesShow(globalOpts) {
|
|
|
5756
6198
|
}
|
|
5757
6199
|
async function runBaselinesUpdate(globalOpts) {
|
|
5758
6200
|
const cwd = process.cwd();
|
|
5759
|
-
const { BenchmarkRunner } = await import("./dist-
|
|
6201
|
+
const { BenchmarkRunner } = await import("./dist-46VOJIG4.js");
|
|
5760
6202
|
const runner = new BenchmarkRunner();
|
|
5761
6203
|
const manager = new BaselineManager(cwd);
|
|
5762
6204
|
logger.info("Running benchmarks to update baselines...");
|
|
@@ -5797,9 +6239,9 @@ function registerReportCommand(perf) {
|
|
|
5797
6239
|
perf.command("report").description("Full performance report with metrics, trends, and hotspots").action(async (_opts, cmd) => {
|
|
5798
6240
|
const globalOpts = cmd.optsWithGlobals();
|
|
5799
6241
|
const cwd = process.cwd();
|
|
5800
|
-
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-
|
|
6242
|
+
const { EntropyAnalyzer: EntropyAnalyzer2 } = await import("./dist-46VOJIG4.js");
|
|
5801
6243
|
const analyzer = new EntropyAnalyzer2({
|
|
5802
|
-
rootDir:
|
|
6244
|
+
rootDir: path42.resolve(cwd),
|
|
5803
6245
|
analyze: { complexity: true, coupling: true }
|
|
5804
6246
|
});
|
|
5805
6247
|
const result = await analyzer.analyze();
|
|
@@ -5858,7 +6300,7 @@ function registerCriticalPathsCommand(perf) {
|
|
|
5858
6300
|
});
|
|
5859
6301
|
}
|
|
5860
6302
|
function createPerfCommand() {
|
|
5861
|
-
const perf = new
|
|
6303
|
+
const perf = new Command50("perf").description("Performance benchmark and baseline management");
|
|
5862
6304
|
registerBenchCommand(perf);
|
|
5863
6305
|
registerBaselinesCommands(perf);
|
|
5864
6306
|
registerReportCommand(perf);
|
|
@@ -5867,10 +6309,10 @@ function createPerfCommand() {
|
|
|
5867
6309
|
}
|
|
5868
6310
|
|
|
5869
6311
|
// src/commands/persona/index.ts
|
|
5870
|
-
import { Command as
|
|
6312
|
+
import { Command as Command53 } from "commander";
|
|
5871
6313
|
|
|
5872
6314
|
// src/commands/persona/list.ts
|
|
5873
|
-
import { Command as
|
|
6315
|
+
import { Command as Command51 } from "commander";
|
|
5874
6316
|
function printPersonaList(personas) {
|
|
5875
6317
|
if (personas.length === 0) {
|
|
5876
6318
|
logger.info("No personas found.");
|
|
@@ -5884,7 +6326,7 @@ function printPersonaList(personas) {
|
|
|
5884
6326
|
}
|
|
5885
6327
|
}
|
|
5886
6328
|
function createListCommand2() {
|
|
5887
|
-
return new
|
|
6329
|
+
return new Command51("list").description("List available agent personas").action(async (_opts, cmd) => {
|
|
5888
6330
|
const globalOpts = cmd.optsWithGlobals();
|
|
5889
6331
|
const personasDir = resolvePersonasDir();
|
|
5890
6332
|
const result = listPersonas(personasDir);
|
|
@@ -5904,51 +6346,51 @@ function createListCommand2() {
|
|
|
5904
6346
|
}
|
|
5905
6347
|
|
|
5906
6348
|
// src/commands/persona/generate.ts
|
|
5907
|
-
import { Command as
|
|
5908
|
-
import * as
|
|
5909
|
-
import * as
|
|
6349
|
+
import { Command as Command52 } from "commander";
|
|
6350
|
+
import * as fs25 from "fs";
|
|
6351
|
+
import * as path43 from "path";
|
|
5910
6352
|
function generatePersonaArtifacts(persona, outputDir, only) {
|
|
5911
6353
|
const slug = toKebabCase(persona.name);
|
|
5912
6354
|
const generated = [];
|
|
5913
6355
|
if (!only || only === "runtime") {
|
|
5914
6356
|
const result = generateRuntime(persona);
|
|
5915
6357
|
if (result.ok) {
|
|
5916
|
-
const outPath =
|
|
5917
|
-
|
|
5918
|
-
|
|
6358
|
+
const outPath = path43.join(outputDir, `${slug}.runtime.json`);
|
|
6359
|
+
fs25.mkdirSync(path43.dirname(outPath), { recursive: true });
|
|
6360
|
+
fs25.writeFileSync(outPath, result.value);
|
|
5919
6361
|
generated.push(outPath);
|
|
5920
6362
|
}
|
|
5921
6363
|
}
|
|
5922
6364
|
if (!only || only === "agents-md") {
|
|
5923
6365
|
const result = generateAgentsMd(persona);
|
|
5924
6366
|
if (result.ok) {
|
|
5925
|
-
const outPath =
|
|
5926
|
-
|
|
6367
|
+
const outPath = path43.join(outputDir, `${slug}.agents.md`);
|
|
6368
|
+
fs25.writeFileSync(outPath, result.value);
|
|
5927
6369
|
generated.push(outPath);
|
|
5928
6370
|
}
|
|
5929
6371
|
}
|
|
5930
6372
|
if (!only || only === "ci") {
|
|
5931
6373
|
const result = generateCIWorkflow(persona, "github");
|
|
5932
6374
|
if (result.ok) {
|
|
5933
|
-
const outPath =
|
|
5934
|
-
|
|
5935
|
-
|
|
6375
|
+
const outPath = path43.join(outputDir, ".github", "workflows", `${slug}.yml`);
|
|
6376
|
+
fs25.mkdirSync(path43.dirname(outPath), { recursive: true });
|
|
6377
|
+
fs25.writeFileSync(outPath, result.value);
|
|
5936
6378
|
generated.push(outPath);
|
|
5937
6379
|
}
|
|
5938
6380
|
}
|
|
5939
6381
|
return generated;
|
|
5940
6382
|
}
|
|
5941
6383
|
function createGenerateCommand3() {
|
|
5942
|
-
return new
|
|
6384
|
+
return new Command52("generate").description("Generate artifacts from a persona config").argument("<name>", "Persona name (e.g., architecture-enforcer)").option("--output-dir <dir>", "Output directory", ".").option("--only <type>", "Generate only: ci, agents-md, runtime").action(async (name, opts, cmd) => {
|
|
5943
6385
|
const globalOpts = cmd.optsWithGlobals();
|
|
5944
|
-
const personaResult = loadPersona(
|
|
6386
|
+
const personaResult = loadPersona(path43.join(resolvePersonasDir(), `${name}.yaml`));
|
|
5945
6387
|
if (!personaResult.ok) {
|
|
5946
6388
|
logger.error(personaResult.error.message);
|
|
5947
6389
|
process.exit(ExitCode.ERROR);
|
|
5948
6390
|
}
|
|
5949
6391
|
const generated = generatePersonaArtifacts(
|
|
5950
6392
|
personaResult.value,
|
|
5951
|
-
|
|
6393
|
+
path43.resolve(opts.outputDir),
|
|
5952
6394
|
opts.only
|
|
5953
6395
|
);
|
|
5954
6396
|
if (!globalOpts.quiet) {
|
|
@@ -5961,7 +6403,7 @@ function createGenerateCommand3() {
|
|
|
5961
6403
|
|
|
5962
6404
|
// src/commands/persona/index.ts
|
|
5963
6405
|
function createPersonaCommand() {
|
|
5964
|
-
const command = new
|
|
6406
|
+
const command = new Command53("persona").description("Agent persona management commands");
|
|
5965
6407
|
command.addCommand(createListCommand2());
|
|
5966
6408
|
command.addCommand(createGenerateCommand3());
|
|
5967
6409
|
return command;
|
|
@@ -5969,8 +6411,8 @@ function createPersonaCommand() {
|
|
|
5969
6411
|
|
|
5970
6412
|
// src/commands/predict.ts
|
|
5971
6413
|
init_dist();
|
|
5972
|
-
import { Command as
|
|
5973
|
-
import
|
|
6414
|
+
import { Command as Command54 } from "commander";
|
|
6415
|
+
import chalk8 from "chalk";
|
|
5974
6416
|
var CATEGORY_ORDER = [
|
|
5975
6417
|
"circular-deps",
|
|
5976
6418
|
"layer-violations",
|
|
@@ -5991,11 +6433,11 @@ function crossingLabel(weeks) {
|
|
|
5991
6433
|
function severityPrefix(severity) {
|
|
5992
6434
|
switch (severity) {
|
|
5993
6435
|
case "critical":
|
|
5994
|
-
return
|
|
6436
|
+
return chalk8.red("[critical]");
|
|
5995
6437
|
case "warning":
|
|
5996
|
-
return
|
|
6438
|
+
return chalk8.yellow("[warning]");
|
|
5997
6439
|
case "info":
|
|
5998
|
-
return
|
|
6440
|
+
return chalk8.blue("[info]");
|
|
5999
6441
|
}
|
|
6000
6442
|
}
|
|
6001
6443
|
function printCategoryTable(result) {
|
|
@@ -6075,7 +6517,7 @@ function handlePredictError(err, mode) {
|
|
|
6075
6517
|
process.exit(ExitCode.ERROR);
|
|
6076
6518
|
}
|
|
6077
6519
|
function createPredictCommand() {
|
|
6078
|
-
const command = new
|
|
6520
|
+
const command = new Command54("predict").description("Predict which architectural constraints will break and when").option("--category <name>", "Filter to a single metric category").option("--no-roadmap", "Baseline only \u2014 skip roadmap spec impact").option("--horizon <weeks>", "Forecast horizon in weeks (default: 12)", "12").action(async (opts, cmd) => {
|
|
6079
6521
|
const globalOpts = cmd.optsWithGlobals();
|
|
6080
6522
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
6081
6523
|
try {
|
|
@@ -6098,9 +6540,9 @@ function createPredictCommand() {
|
|
|
6098
6540
|
}
|
|
6099
6541
|
|
|
6100
6542
|
// src/commands/publish-analyses.ts
|
|
6101
|
-
import * as
|
|
6102
|
-
import * as
|
|
6103
|
-
import { Command as
|
|
6543
|
+
import * as fs26 from "fs";
|
|
6544
|
+
import * as path44 from "path";
|
|
6545
|
+
import { Command as Command55 } from "commander";
|
|
6104
6546
|
init_dist();
|
|
6105
6547
|
import {
|
|
6106
6548
|
renderAnalysisComment,
|
|
@@ -6108,7 +6550,7 @@ import {
|
|
|
6108
6550
|
savePublishedIndex
|
|
6109
6551
|
} from "@harness-engineering/orchestrator";
|
|
6110
6552
|
function bootstrapTrackerCommand(opts, verb) {
|
|
6111
|
-
const projectPath =
|
|
6553
|
+
const projectPath = path44.resolve(opts.dir);
|
|
6112
6554
|
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
6113
6555
|
if (!trackerConfig) {
|
|
6114
6556
|
logger.error(`No tracker config found in harness.config.json. Cannot ${verb}.`);
|
|
@@ -6120,8 +6562,8 @@ function bootstrapTrackerCommand(opts, verb) {
|
|
|
6120
6562
|
);
|
|
6121
6563
|
return null;
|
|
6122
6564
|
}
|
|
6123
|
-
const projectEnvPath =
|
|
6124
|
-
if (
|
|
6565
|
+
const projectEnvPath = path44.join(projectPath, ".env");
|
|
6566
|
+
if (fs26.existsSync(projectEnvPath) && !process.env.GITHUB_TOKEN) {
|
|
6125
6567
|
__require("dotenv").config({ path: projectEnvPath });
|
|
6126
6568
|
}
|
|
6127
6569
|
const token = process.env.GITHUB_TOKEN;
|
|
@@ -6132,13 +6574,13 @@ function bootstrapTrackerCommand(opts, verb) {
|
|
|
6132
6574
|
return { token, projectPath, trackerConfig };
|
|
6133
6575
|
}
|
|
6134
6576
|
function buildNameToExternalIdMap(projectPath) {
|
|
6135
|
-
const roadmapFile =
|
|
6136
|
-
if (!
|
|
6577
|
+
const roadmapFile = path44.join(projectPath, "docs", "roadmap.md");
|
|
6578
|
+
if (!fs26.existsSync(roadmapFile)) {
|
|
6137
6579
|
logger.error("No docs/roadmap.md found. Cannot map issue hashes to external IDs.");
|
|
6138
6580
|
return null;
|
|
6139
6581
|
}
|
|
6140
6582
|
const { parseRoadmap } = (init_dist(), __toCommonJS(dist_exports));
|
|
6141
|
-
const roadmapParsed = parseRoadmap(
|
|
6583
|
+
const roadmapParsed = parseRoadmap(fs26.readFileSync(roadmapFile, "utf-8"));
|
|
6142
6584
|
if (!roadmapParsed.ok) {
|
|
6143
6585
|
logger.error("Failed to parse docs/roadmap.md");
|
|
6144
6586
|
return null;
|
|
@@ -6188,8 +6630,8 @@ async function runPublishAnalyses(opts) {
|
|
|
6188
6630
|
const nameToExternalId = buildNameToExternalIdMap(projectPath);
|
|
6189
6631
|
if (!nameToExternalId) return process.exit(1);
|
|
6190
6632
|
const { AnalysisArchive } = await import("@harness-engineering/orchestrator");
|
|
6191
|
-
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-
|
|
6192
|
-
const archive = new AnalysisArchive(
|
|
6633
|
+
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-46VOJIG4.js");
|
|
6634
|
+
const archive = new AnalysisArchive(path44.join(projectPath, ".harness", "analyses"));
|
|
6193
6635
|
const analyses = await archive.list();
|
|
6194
6636
|
if (analyses.length === 0) {
|
|
6195
6637
|
logger.info("No analyses found to publish.");
|
|
@@ -6211,7 +6653,7 @@ async function runPublishAnalyses(opts) {
|
|
|
6211
6653
|
}
|
|
6212
6654
|
}
|
|
6213
6655
|
function createPublishAnalysesCommand() {
|
|
6214
|
-
const command = new
|
|
6656
|
+
const command = new Command55("publish-analyses").description(
|
|
6215
6657
|
"Publishes locally generated intelligence analyses to the external issue tracker (e.g., GitHub)"
|
|
6216
6658
|
).option("-d, --dir <path>", "Workspace directory", process.cwd()).action(async (opts) => {
|
|
6217
6659
|
try {
|
|
@@ -6227,8 +6669,8 @@ function createPublishAnalysesCommand() {
|
|
|
6227
6669
|
}
|
|
6228
6670
|
|
|
6229
6671
|
// src/commands/recommend.ts
|
|
6230
|
-
import { Command as
|
|
6231
|
-
import
|
|
6672
|
+
import { Command as Command56 } from "commander";
|
|
6673
|
+
import chalk9 from "chalk";
|
|
6232
6674
|
async function resolveSnapshot(cwd, noCache) {
|
|
6233
6675
|
if (!noCache) {
|
|
6234
6676
|
const cached = loadCachedSnapshot(cwd);
|
|
@@ -6262,12 +6704,12 @@ async function runRecommend(options) {
|
|
|
6262
6704
|
function formatRecommendation(rec) {
|
|
6263
6705
|
const lines = [];
|
|
6264
6706
|
if (rec.urgency === "critical") {
|
|
6265
|
-
lines.push(` ${
|
|
6707
|
+
lines.push(` ${chalk9.red("[CRITICAL]")} ${rec.sequence}. ${rec.skillName}`);
|
|
6266
6708
|
} else {
|
|
6267
6709
|
lines.push(` ${rec.sequence}. ${rec.skillName} (${rec.score.toFixed(2)})`);
|
|
6268
6710
|
}
|
|
6269
6711
|
for (const reason of rec.reasons) {
|
|
6270
|
-
lines.push(` ${
|
|
6712
|
+
lines.push(` ${chalk9.dim("\u2192")} ${reason}`);
|
|
6271
6713
|
}
|
|
6272
6714
|
return lines.join("\n");
|
|
6273
6715
|
}
|
|
@@ -6291,7 +6733,7 @@ function printRecommendations(result) {
|
|
|
6291
6733
|
console.log("");
|
|
6292
6734
|
}
|
|
6293
6735
|
function createRecommendCommand() {
|
|
6294
|
-
const command = new
|
|
6736
|
+
const command = new Command56("recommend").description("Recommend skills based on codebase health analysis").option("--no-cache", "Force fresh health snapshot").option("--top <n>", "Max recommendations (default 5)", "5").action(async (opts, cmd) => {
|
|
6295
6737
|
const globalOpts = cmd.optsWithGlobals();
|
|
6296
6738
|
const mode = globalOpts.json ? OutputMode.JSON : OutputMode.TEXT;
|
|
6297
6739
|
try {
|
|
@@ -6328,9 +6770,9 @@ function createRecommendCommand() {
|
|
|
6328
6770
|
|
|
6329
6771
|
// src/commands/scan-config.ts
|
|
6330
6772
|
init_dist();
|
|
6331
|
-
import { Command as
|
|
6332
|
-
import { existsSync as
|
|
6333
|
-
import { join as
|
|
6773
|
+
import { Command as Command57 } from "commander";
|
|
6774
|
+
import { existsSync as existsSync23, readFileSync as readFileSync15, writeFileSync as writeFileSync13 } from "fs";
|
|
6775
|
+
import { join as join32, relative as relative3 } from "path";
|
|
6334
6776
|
var CONFIG_FILES = ["CLAUDE.md", "AGENTS.md", ".gemini/settings.json", "skill.yaml"];
|
|
6335
6777
|
function stripHighSeverityPatterns(content, injectionFindings) {
|
|
6336
6778
|
const highLines = /* @__PURE__ */ new Set();
|
|
@@ -6356,17 +6798,17 @@ function applyFix(filePath, targetDir, content, injectionFindings) {
|
|
|
6356
6798
|
if (!hasHighSeverity) return;
|
|
6357
6799
|
const { cleaned, linesStripped } = stripHighSeverityPatterns(content, injectionFindings);
|
|
6358
6800
|
if (linesStripped > 0) {
|
|
6359
|
-
|
|
6801
|
+
writeFileSync13(filePath, cleaned);
|
|
6360
6802
|
logger.info(
|
|
6361
|
-
`scan-config --fix: stripped ${linesStripped} high-severity line(s) from ${
|
|
6803
|
+
`scan-config --fix: stripped ${linesStripped} high-severity line(s) from ${relative3(targetDir, filePath).replaceAll("\\", "/")}`
|
|
6362
6804
|
);
|
|
6363
6805
|
}
|
|
6364
6806
|
}
|
|
6365
6807
|
function scanSingleFile(filePath, targetDir, scanner, options) {
|
|
6366
|
-
if (!
|
|
6808
|
+
if (!existsSync23(filePath)) return null;
|
|
6367
6809
|
let content;
|
|
6368
6810
|
try {
|
|
6369
|
-
content =
|
|
6811
|
+
content = readFileSync15(filePath, "utf8");
|
|
6370
6812
|
} catch {
|
|
6371
6813
|
return null;
|
|
6372
6814
|
}
|
|
@@ -6378,7 +6820,7 @@ function scanSingleFile(filePath, targetDir, scanner, options) {
|
|
|
6378
6820
|
applyFix(filePath, targetDir, content, injectionFindings);
|
|
6379
6821
|
}
|
|
6380
6822
|
return {
|
|
6381
|
-
file:
|
|
6823
|
+
file: relative3(targetDir, filePath).replaceAll("\\", "/"),
|
|
6382
6824
|
findings,
|
|
6383
6825
|
overallSeverity: computeOverallSeverity(findings)
|
|
6384
6826
|
};
|
|
@@ -6387,7 +6829,7 @@ async function runScanConfig(targetDir, options) {
|
|
|
6387
6829
|
const scanner = new SecurityScanner(parseSecurityConfig({}));
|
|
6388
6830
|
const results = [];
|
|
6389
6831
|
for (const configFile of CONFIG_FILES) {
|
|
6390
|
-
const result = scanSingleFile(
|
|
6832
|
+
const result = scanSingleFile(join32(targetDir, configFile), targetDir, scanner, options);
|
|
6391
6833
|
if (result) results.push(result);
|
|
6392
6834
|
}
|
|
6393
6835
|
return { exitCode: computeScanExitCode(results), results };
|
|
@@ -6417,7 +6859,7 @@ function formatTextOutput(result) {
|
|
|
6417
6859
|
}
|
|
6418
6860
|
}
|
|
6419
6861
|
function createScanConfigCommand() {
|
|
6420
|
-
const command = new
|
|
6862
|
+
const command = new Command57("scan-config").description(
|
|
6421
6863
|
"Scan CLAUDE.md, AGENTS.md, .gemini/settings.json, and skill.yaml for prompt injection patterns"
|
|
6422
6864
|
).option("--path <dir>", "Target directory to scan (default: cwd)").option("--fix", "Strip high-severity patterns from files in-place").action(async (opts, cmd) => {
|
|
6423
6865
|
const globalOpts = cmd.optsWithGlobals();
|
|
@@ -6434,24 +6876,24 @@ function createScanConfigCommand() {
|
|
|
6434
6876
|
}
|
|
6435
6877
|
|
|
6436
6878
|
// src/commands/setup.ts
|
|
6437
|
-
import { Command as
|
|
6438
|
-
import * as
|
|
6879
|
+
import { Command as Command58 } from "commander";
|
|
6880
|
+
import * as fs29 from "fs";
|
|
6439
6881
|
import * as os7 from "os";
|
|
6440
|
-
import * as
|
|
6441
|
-
import
|
|
6882
|
+
import * as path47 from "path";
|
|
6883
|
+
import chalk11 from "chalk";
|
|
6442
6884
|
|
|
6443
6885
|
// src/utils/first-run.ts
|
|
6444
|
-
import * as
|
|
6886
|
+
import * as fs27 from "fs";
|
|
6445
6887
|
import * as os6 from "os";
|
|
6446
|
-
import * as
|
|
6447
|
-
var HARNESS_DIR =
|
|
6448
|
-
var MARKER_FILE =
|
|
6888
|
+
import * as path45 from "path";
|
|
6889
|
+
var HARNESS_DIR = path45.join(os6.homedir(), ".harness");
|
|
6890
|
+
var MARKER_FILE = path45.join(HARNESS_DIR, ".setup-complete");
|
|
6449
6891
|
function isFirstRun() {
|
|
6450
|
-
return !
|
|
6892
|
+
return !fs27.existsSync(MARKER_FILE);
|
|
6451
6893
|
}
|
|
6452
6894
|
function markSetupComplete() {
|
|
6453
|
-
|
|
6454
|
-
|
|
6895
|
+
fs27.mkdirSync(HARNESS_DIR, { recursive: true });
|
|
6896
|
+
fs27.writeFileSync(MARKER_FILE, "", "utf-8");
|
|
6455
6897
|
}
|
|
6456
6898
|
function printFirstRunWelcome() {
|
|
6457
6899
|
try {
|
|
@@ -6464,31 +6906,31 @@ function printFirstRunWelcome() {
|
|
|
6464
6906
|
}
|
|
6465
6907
|
|
|
6466
6908
|
// src/commands/telemetry-wizard.ts
|
|
6467
|
-
import * as
|
|
6468
|
-
import * as
|
|
6469
|
-
import
|
|
6470
|
-
import
|
|
6471
|
-
function
|
|
6472
|
-
const rl =
|
|
6909
|
+
import * as fs28 from "fs";
|
|
6910
|
+
import * as path46 from "path";
|
|
6911
|
+
import readline2 from "readline";
|
|
6912
|
+
import chalk10 from "chalk";
|
|
6913
|
+
function prompt2(question) {
|
|
6914
|
+
const rl = readline2.createInterface({
|
|
6473
6915
|
input: process.stdin,
|
|
6474
6916
|
output: process.stdout
|
|
6475
6917
|
});
|
|
6476
|
-
return new Promise((
|
|
6918
|
+
return new Promise((resolve37) => {
|
|
6477
6919
|
rl.question(question, (answer) => {
|
|
6478
6920
|
rl.close();
|
|
6479
|
-
|
|
6921
|
+
resolve37(answer.trim().toLowerCase());
|
|
6480
6922
|
});
|
|
6481
6923
|
});
|
|
6482
6924
|
}
|
|
6483
6925
|
function promptRaw(question) {
|
|
6484
|
-
const rl =
|
|
6926
|
+
const rl = readline2.createInterface({
|
|
6485
6927
|
input: process.stdin,
|
|
6486
6928
|
output: process.stdout
|
|
6487
6929
|
});
|
|
6488
|
-
return new Promise((
|
|
6930
|
+
return new Promise((resolve37) => {
|
|
6489
6931
|
rl.question(question, (answer) => {
|
|
6490
6932
|
rl.close();
|
|
6491
|
-
|
|
6933
|
+
resolve37(answer.trim());
|
|
6492
6934
|
});
|
|
6493
6935
|
});
|
|
6494
6936
|
}
|
|
@@ -6496,21 +6938,21 @@ function isNo(answer) {
|
|
|
6496
6938
|
return answer === "n" || answer === "no";
|
|
6497
6939
|
}
|
|
6498
6940
|
function isTelemetryConfigured(cwd) {
|
|
6499
|
-
const configPath =
|
|
6941
|
+
const configPath = path46.join(cwd, "harness.config.json");
|
|
6500
6942
|
try {
|
|
6501
|
-
const raw = JSON.parse(
|
|
6943
|
+
const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
|
|
6502
6944
|
return raw.telemetry !== void 0 && raw.adoption !== void 0;
|
|
6503
6945
|
} catch {
|
|
6504
6946
|
return false;
|
|
6505
6947
|
}
|
|
6506
6948
|
}
|
|
6507
6949
|
function writeTelemetryConfig(cwd, result) {
|
|
6508
|
-
const configPath =
|
|
6950
|
+
const configPath = path46.join(cwd, "harness.config.json");
|
|
6509
6951
|
try {
|
|
6510
|
-
const raw = JSON.parse(
|
|
6952
|
+
const raw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
|
|
6511
6953
|
raw.telemetry = { enabled: result.telemetryEnabled };
|
|
6512
6954
|
raw.adoption = { enabled: result.adoptionEnabled };
|
|
6513
|
-
|
|
6955
|
+
fs28.writeFileSync(configPath, JSON.stringify(raw, null, 2) + "\n");
|
|
6514
6956
|
} catch (err) {
|
|
6515
6957
|
console.warn(
|
|
6516
6958
|
`\u26A0 Could not update ${configPath}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -6518,18 +6960,18 @@ function writeTelemetryConfig(cwd, result) {
|
|
|
6518
6960
|
}
|
|
6519
6961
|
const hasIdentity = result.identity.project || result.identity.team || result.identity.alias;
|
|
6520
6962
|
if (hasIdentity) {
|
|
6521
|
-
const harnessDir =
|
|
6522
|
-
|
|
6523
|
-
const telemetryFile =
|
|
6524
|
-
|
|
6963
|
+
const harnessDir = path46.join(cwd, ".harness");
|
|
6964
|
+
fs28.mkdirSync(harnessDir, { recursive: true });
|
|
6965
|
+
const telemetryFile = path46.join(harnessDir, "telemetry.json");
|
|
6966
|
+
fs28.writeFileSync(telemetryFile, JSON.stringify({ identity: result.identity }, null, 2) + "\n");
|
|
6525
6967
|
}
|
|
6526
6968
|
}
|
|
6527
6969
|
async function promptIdentity() {
|
|
6528
6970
|
console.log(
|
|
6529
|
-
` ${
|
|
6971
|
+
` ${chalk10.cyan("Identity")} (optional) \u2014 tag telemetry with project/team/alias for filtering.`
|
|
6530
6972
|
);
|
|
6531
6973
|
console.log(" Leave blank to keep anonymous.");
|
|
6532
|
-
const answer = await
|
|
6974
|
+
const answer = await prompt2(" Set identity fields? (y/N) ");
|
|
6533
6975
|
const identity = {};
|
|
6534
6976
|
if (answer === "y" || answer === "yes") {
|
|
6535
6977
|
const projectName = await promptRaw(" Project name: ");
|
|
@@ -6544,31 +6986,31 @@ async function promptIdentity() {
|
|
|
6544
6986
|
async function runTelemetryWizard() {
|
|
6545
6987
|
if (!process.stdin.isTTY) return null;
|
|
6546
6988
|
console.log("");
|
|
6547
|
-
console.log(` ${
|
|
6989
|
+
console.log(` ${chalk10.bold("Telemetry Configuration")}`);
|
|
6548
6990
|
console.log("");
|
|
6549
6991
|
console.log(" Harness can collect anonymous usage data to help improve the tool.");
|
|
6550
6992
|
console.log(" You can change these settings anytime in harness.config.json.");
|
|
6551
6993
|
console.log("");
|
|
6552
6994
|
console.log(
|
|
6553
|
-
` ${
|
|
6995
|
+
` ${chalk10.cyan("Anonymous telemetry")} \u2014 error rates, command usage, performance metrics.`
|
|
6554
6996
|
);
|
|
6555
6997
|
console.log(" No code, file contents, or personally identifiable info is sent.");
|
|
6556
|
-
const telemetryAnswer = await
|
|
6998
|
+
const telemetryAnswer = await prompt2(" Enable anonymous telemetry? (Y/n) ");
|
|
6557
6999
|
const telemetryEnabled = !isNo(telemetryAnswer);
|
|
6558
7000
|
console.log("");
|
|
6559
7001
|
console.log(
|
|
6560
|
-
` ${
|
|
7002
|
+
` ${chalk10.cyan("Adoption tracking")} \u2014 which skills are used, how often, and success rates.`
|
|
6561
7003
|
);
|
|
6562
7004
|
console.log(" Stored locally in .harness/adoption/. Never sent externally.");
|
|
6563
|
-
const adoptionAnswer = await
|
|
7005
|
+
const adoptionAnswer = await prompt2(" Enable local adoption tracking? (Y/n) ");
|
|
6564
7006
|
const adoptionEnabled = !isNo(adoptionAnswer);
|
|
6565
7007
|
console.log("");
|
|
6566
7008
|
const identity = await promptIdentity();
|
|
6567
7009
|
return { telemetryEnabled, adoptionEnabled, identity };
|
|
6568
7010
|
}
|
|
6569
7011
|
async function ensureTelemetryConfigured(cwd) {
|
|
6570
|
-
const configPath =
|
|
6571
|
-
if (!
|
|
7012
|
+
const configPath = path46.join(cwd, "harness.config.json");
|
|
7013
|
+
if (!fs28.existsSync(configPath)) {
|
|
6572
7014
|
return { status: "warn", message: "Not a harness project \u2014 skipped telemetry configuration" };
|
|
6573
7015
|
}
|
|
6574
7016
|
if (isTelemetryConfigured(cwd)) {
|
|
@@ -6614,7 +7056,7 @@ function runSlashCommandGeneration() {
|
|
|
6614
7056
|
}
|
|
6615
7057
|
}
|
|
6616
7058
|
function detectClient(dirName) {
|
|
6617
|
-
return
|
|
7059
|
+
return fs29.existsSync(path47.join(os7.homedir(), dirName));
|
|
6618
7060
|
}
|
|
6619
7061
|
async function runMcpSetup(cwd) {
|
|
6620
7062
|
const results = [];
|
|
@@ -6651,12 +7093,12 @@ async function runMcpSetup(cwd) {
|
|
|
6651
7093
|
return results;
|
|
6652
7094
|
}
|
|
6653
7095
|
function formatStep(result) {
|
|
6654
|
-
const icon = result.status === "pass" ?
|
|
7096
|
+
const icon = result.status === "pass" ? chalk11.green("\u2713") : result.status === "warn" ? chalk11.yellow("\u26A0") : chalk11.red("\u2717");
|
|
6655
7097
|
return ` ${icon} ${result.message}`;
|
|
6656
7098
|
}
|
|
6657
7099
|
function configureTier0Integrations(cwd) {
|
|
6658
7100
|
try {
|
|
6659
|
-
const mcpPath =
|
|
7101
|
+
const mcpPath = path47.join(cwd, ".mcp.json");
|
|
6660
7102
|
const config = readMcpConfig(mcpPath);
|
|
6661
7103
|
const tier0 = INTEGRATION_REGISTRY.filter((i) => i.tier === 0);
|
|
6662
7104
|
const added = [];
|
|
@@ -6665,9 +7107,9 @@ function configureTier0Integrations(cwd) {
|
|
|
6665
7107
|
writeMcpEntry(mcpPath, integration.name, integration.mcpConfig);
|
|
6666
7108
|
added.push(integration.displayName);
|
|
6667
7109
|
}
|
|
6668
|
-
const geminiDir =
|
|
6669
|
-
if (
|
|
6670
|
-
const geminiPath =
|
|
7110
|
+
const geminiDir = path47.join(cwd, ".gemini");
|
|
7111
|
+
if (fs29.existsSync(geminiDir)) {
|
|
7112
|
+
const geminiPath = path47.join(geminiDir, "settings.json");
|
|
6671
7113
|
const geminiConfig = readMcpConfig(geminiPath);
|
|
6672
7114
|
for (const integration of tier0) {
|
|
6673
7115
|
if (!geminiConfig.mcpServers[integration.name]) {
|
|
@@ -6688,14 +7130,14 @@ function configureTier0Integrations(cwd) {
|
|
|
6688
7130
|
}
|
|
6689
7131
|
}
|
|
6690
7132
|
function ensureHooks(cwd) {
|
|
6691
|
-
const configPath =
|
|
6692
|
-
if (!
|
|
7133
|
+
const configPath = path47.join(cwd, "harness.config.json");
|
|
7134
|
+
if (!fs29.existsSync(configPath)) {
|
|
6693
7135
|
return { status: "warn", message: "Not a harness project \u2014 skipped hook installation" };
|
|
6694
7136
|
}
|
|
6695
7137
|
let profile = "standard";
|
|
6696
|
-
const profilePath =
|
|
7138
|
+
const profilePath = path47.join(cwd, ".harness", "hooks", "profile.json");
|
|
6697
7139
|
try {
|
|
6698
|
-
const data = JSON.parse(
|
|
7140
|
+
const data = JSON.parse(fs29.readFileSync(profilePath, "utf-8"));
|
|
6699
7141
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
6700
7142
|
profile = data.profile;
|
|
6701
7143
|
}
|
|
@@ -6712,9 +7154,22 @@ function ensureHooks(cwd) {
|
|
|
6712
7154
|
return { status: "warn", message: `Hook installation skipped \u2014 ${msg}` };
|
|
6713
7155
|
}
|
|
6714
7156
|
}
|
|
7157
|
+
async function detectLegacyLayout(cwd) {
|
|
7158
|
+
const { adrLegacy, planLegacy } = await detectLegacyArtifacts(cwd);
|
|
7159
|
+
if (!adrLegacy && !planLegacy) {
|
|
7160
|
+
return { status: "pass", message: "Layout up to date" };
|
|
7161
|
+
}
|
|
7162
|
+
const parts = [];
|
|
7163
|
+
if (adrLegacy) parts.push(".harness/architecture/");
|
|
7164
|
+
if (planLegacy) parts.push("docs/plans/");
|
|
7165
|
+
return {
|
|
7166
|
+
status: "warn",
|
|
7167
|
+
message: `Legacy paths detected (${parts.join(", ")}) \u2014 run \`harness migrate\` to upgrade`
|
|
7168
|
+
};
|
|
7169
|
+
}
|
|
6715
7170
|
async function runInitialGraphScan(cwd) {
|
|
6716
7171
|
try {
|
|
6717
|
-
const { runScan: runScan2 } = await import("./scan-
|
|
7172
|
+
const { runScan: runScan2 } = await import("./scan-XRGMTO6K.js");
|
|
6718
7173
|
const result = await runScan2(cwd);
|
|
6719
7174
|
return {
|
|
6720
7175
|
status: "pass",
|
|
@@ -6744,6 +7199,8 @@ async function runSetup(cwd) {
|
|
|
6744
7199
|
steps.push(telemetryResult);
|
|
6745
7200
|
const graphResult = await runInitialGraphScan(cwd);
|
|
6746
7201
|
steps.push(graphResult);
|
|
7202
|
+
const legacyResult = await detectLegacyLayout(cwd);
|
|
7203
|
+
steps.push(legacyResult);
|
|
6747
7204
|
const success = steps.every((s) => s.status !== "fail");
|
|
6748
7205
|
if (success) {
|
|
6749
7206
|
markSetupComplete();
|
|
@@ -6751,10 +7208,10 @@ async function runSetup(cwd) {
|
|
|
6751
7208
|
return { steps, success };
|
|
6752
7209
|
}
|
|
6753
7210
|
function createSetupCommand() {
|
|
6754
|
-
return new
|
|
7211
|
+
return new Command58("setup").description("Configure harness environment: slash commands, MCP, and more").action(async () => {
|
|
6755
7212
|
const cwd = process.cwd();
|
|
6756
7213
|
console.log("");
|
|
6757
|
-
console.log(` ${
|
|
7214
|
+
console.log(` ${chalk11.bold("harness setup")}`);
|
|
6758
7215
|
console.log("");
|
|
6759
7216
|
const { steps, success } = await runSetup(cwd);
|
|
6760
7217
|
for (const step of steps) {
|
|
@@ -6774,15 +7231,15 @@ function createSetupCommand() {
|
|
|
6774
7231
|
|
|
6775
7232
|
// src/commands/share.ts
|
|
6776
7233
|
init_dist();
|
|
6777
|
-
import { Command as
|
|
6778
|
-
import * as
|
|
6779
|
-
import * as
|
|
7234
|
+
import { Command as Command59 } from "commander";
|
|
7235
|
+
import * as fs30 from "fs";
|
|
7236
|
+
import * as path48 from "path";
|
|
6780
7237
|
import { parse as parseYaml } from "yaml";
|
|
6781
7238
|
var MANIFEST_FILENAME = "constraints.yaml";
|
|
6782
7239
|
async function runShareAction(projectPath, options) {
|
|
6783
|
-
const rootDir =
|
|
6784
|
-
const manifestPath =
|
|
6785
|
-
if (!
|
|
7240
|
+
const rootDir = path48.resolve(projectPath);
|
|
7241
|
+
const manifestPath = path48.join(rootDir, MANIFEST_FILENAME);
|
|
7242
|
+
if (!fs30.existsSync(manifestPath)) {
|
|
6786
7243
|
logger.error(
|
|
6787
7244
|
`No ${MANIFEST_FILENAME} found at ${manifestPath}.
|
|
6788
7245
|
Create a constraints.yaml in your project root to define what to share.`
|
|
@@ -6791,7 +7248,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6791
7248
|
}
|
|
6792
7249
|
let parsed;
|
|
6793
7250
|
try {
|
|
6794
|
-
const raw =
|
|
7251
|
+
const raw = fs30.readFileSync(manifestPath, "utf-8");
|
|
6795
7252
|
parsed = parseYaml(raw);
|
|
6796
7253
|
} catch (err) {
|
|
6797
7254
|
logger.error(
|
|
@@ -6805,7 +7262,7 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6805
7262
|
process.exit(1);
|
|
6806
7263
|
}
|
|
6807
7264
|
const manifest = manifestResult.value;
|
|
6808
|
-
const configResult = resolveConfig(
|
|
7265
|
+
const configResult = resolveConfig(path48.join(rootDir, "harness.config.json"));
|
|
6809
7266
|
if (!configResult.ok) {
|
|
6810
7267
|
logger.error(configResult.error.message);
|
|
6811
7268
|
process.exit(1);
|
|
@@ -6823,8 +7280,8 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6823
7280
|
);
|
|
6824
7281
|
process.exit(1);
|
|
6825
7282
|
}
|
|
6826
|
-
const outputDir =
|
|
6827
|
-
const outputPath =
|
|
7283
|
+
const outputDir = path48.resolve(options.output);
|
|
7284
|
+
const outputPath = path48.join(outputDir, `${manifest.name}.harness-constraints.json`);
|
|
6828
7285
|
const writeResult = await writeConfig(outputPath, bundle);
|
|
6829
7286
|
if (!writeResult.ok) {
|
|
6830
7287
|
logger.error(`Failed to write bundle: ${writeResult.error.message}`);
|
|
@@ -6833,28 +7290,28 @@ Create a constraints.yaml in your project root to define what to share.`
|
|
|
6833
7290
|
logger.success(`Bundle written to ${outputPath}`);
|
|
6834
7291
|
}
|
|
6835
7292
|
function createShareCommand() {
|
|
6836
|
-
return new
|
|
7293
|
+
return new Command59("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) => {
|
|
6837
7294
|
await runShareAction(projectPath, options);
|
|
6838
7295
|
});
|
|
6839
7296
|
}
|
|
6840
7297
|
|
|
6841
7298
|
// src/commands/skill/index.ts
|
|
6842
|
-
import { Command as
|
|
7299
|
+
import { Command as Command67 } from "commander";
|
|
6843
7300
|
|
|
6844
7301
|
// src/commands/skill/list.ts
|
|
6845
|
-
import { Command as
|
|
6846
|
-
import * as
|
|
6847
|
-
import * as
|
|
7302
|
+
import { Command as Command60 } from "commander";
|
|
7303
|
+
import * as fs31 from "fs";
|
|
7304
|
+
import * as path49 from "path";
|
|
6848
7305
|
import { parse } from "yaml";
|
|
6849
7306
|
function scanDirectory(dirPath, source) {
|
|
6850
|
-
if (!
|
|
6851
|
-
const entries =
|
|
7307
|
+
if (!fs31.existsSync(dirPath)) return [];
|
|
7308
|
+
const entries = fs31.readdirSync(dirPath, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
6852
7309
|
const skills = [];
|
|
6853
7310
|
for (const name of entries) {
|
|
6854
|
-
const yamlPath =
|
|
6855
|
-
if (!
|
|
7311
|
+
const yamlPath = path49.join(dirPath, name, "skill.yaml");
|
|
7312
|
+
if (!fs31.existsSync(yamlPath)) continue;
|
|
6856
7313
|
try {
|
|
6857
|
-
const raw =
|
|
7314
|
+
const raw = fs31.readFileSync(yamlPath, "utf-8");
|
|
6858
7315
|
const parsed = parse(raw);
|
|
6859
7316
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
6860
7317
|
if (result.success) {
|
|
@@ -6872,10 +7329,10 @@ function scanDirectory(dirPath, source) {
|
|
|
6872
7329
|
}
|
|
6873
7330
|
function collectCommunitySkills(seen, allSkills) {
|
|
6874
7331
|
const globalDir = resolveGlobalSkillsDir();
|
|
6875
|
-
const skillsDir =
|
|
6876
|
-
const communityBase =
|
|
6877
|
-
const communityPlatformDir =
|
|
6878
|
-
const lockfilePath =
|
|
7332
|
+
const skillsDir = path49.dirname(globalDir);
|
|
7333
|
+
const communityBase = path49.join(skillsDir, "community");
|
|
7334
|
+
const communityPlatformDir = path49.join(communityBase, "claude-code");
|
|
7335
|
+
const lockfilePath = path49.join(communityBase, "skills-lock.json");
|
|
6879
7336
|
const lockfile = readLockfile2(lockfilePath);
|
|
6880
7337
|
const communitySkills = scanDirectory(communityPlatformDir, "community");
|
|
6881
7338
|
for (const skill of communitySkills) {
|
|
@@ -6945,7 +7402,7 @@ function printSkillsVerbose(skills) {
|
|
|
6945
7402
|
for (const s of skills) printSkillEntry(s);
|
|
6946
7403
|
}
|
|
6947
7404
|
function createListCommand3() {
|
|
6948
|
-
return new
|
|
7405
|
+
return new Command60("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) => {
|
|
6949
7406
|
const globalOpts = cmd.optsWithGlobals();
|
|
6950
7407
|
const skills = collectSkills({ filter: resolveFilter(opts) });
|
|
6951
7408
|
if (globalOpts.json) {
|
|
@@ -6960,13 +7417,13 @@ function createListCommand3() {
|
|
|
6960
7417
|
}
|
|
6961
7418
|
|
|
6962
7419
|
// src/commands/skill/run.ts
|
|
6963
|
-
import { Command as
|
|
6964
|
-
import * as
|
|
6965
|
-
import * as
|
|
7420
|
+
import { Command as Command61 } from "commander";
|
|
7421
|
+
import * as fs32 from "fs";
|
|
7422
|
+
import * as path50 from "path";
|
|
6966
7423
|
import { parse as parse2 } from "yaml";
|
|
6967
7424
|
|
|
6968
7425
|
// src/skill/complexity.ts
|
|
6969
|
-
import { execFileSync as
|
|
7426
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
6970
7427
|
function evaluateSignals(signals) {
|
|
6971
7428
|
if (signals.fileCount >= 3) return "thorough";
|
|
6972
7429
|
if (signals.newDir) return "thorough";
|
|
@@ -6978,17 +7435,17 @@ function evaluateSignals(signals) {
|
|
|
6978
7435
|
}
|
|
6979
7436
|
function detectComplexity(projectPath) {
|
|
6980
7437
|
try {
|
|
6981
|
-
const base =
|
|
7438
|
+
const base = execFileSync5("git", ["merge-base", "HEAD", "main"], {
|
|
6982
7439
|
cwd: projectPath,
|
|
6983
7440
|
encoding: "utf-8",
|
|
6984
7441
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6985
7442
|
}).trim();
|
|
6986
|
-
const diffFiles =
|
|
7443
|
+
const diffFiles = execFileSync5("git", ["diff", "--name-only", base], {
|
|
6987
7444
|
cwd: projectPath,
|
|
6988
7445
|
encoding: "utf-8",
|
|
6989
7446
|
stdio: ["pipe", "pipe", "pipe"]
|
|
6990
7447
|
}).trim().split("\n").filter(Boolean);
|
|
6991
|
-
const diffStat =
|
|
7448
|
+
const diffStat = execFileSync5("git", ["diff", "--stat", base], {
|
|
6992
7449
|
cwd: projectPath,
|
|
6993
7450
|
encoding: "utf-8",
|
|
6994
7451
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -7058,10 +7515,10 @@ ${options.principles}`);
|
|
|
7058
7515
|
|
|
7059
7516
|
// src/commands/skill/run.ts
|
|
7060
7517
|
function loadSkillMetadata(skillDir) {
|
|
7061
|
-
const yamlPath =
|
|
7062
|
-
if (!
|
|
7518
|
+
const yamlPath = path50.join(skillDir, "skill.yaml");
|
|
7519
|
+
if (!fs32.existsSync(yamlPath)) return null;
|
|
7063
7520
|
try {
|
|
7064
|
-
const result = SkillMetadataSchema.safeParse(parse2(
|
|
7521
|
+
const result = SkillMetadataSchema.safeParse(parse2(fs32.readFileSync(yamlPath, "utf-8")));
|
|
7065
7522
|
return result.success ? result.data : null;
|
|
7066
7523
|
} catch {
|
|
7067
7524
|
return null;
|
|
@@ -7073,22 +7530,22 @@ function resolveComplexity(metadata, requested, projectPath) {
|
|
|
7073
7530
|
return requested;
|
|
7074
7531
|
}
|
|
7075
7532
|
function loadPrinciples(projectPath) {
|
|
7076
|
-
const principlesPath =
|
|
7077
|
-
return
|
|
7533
|
+
const principlesPath = path50.join(projectPath, "docs", "principles.md");
|
|
7534
|
+
return fs32.existsSync(principlesPath) ? fs32.readFileSync(principlesPath, "utf-8") : void 0;
|
|
7078
7535
|
}
|
|
7079
7536
|
function readMostRecentFileInDir(dirPath) {
|
|
7080
|
-
const files =
|
|
7081
|
-
if (files.length > 0) return
|
|
7537
|
+
const files = fs32.readdirSync(dirPath).map((f) => ({ name: f, mtime: fs32.statSync(path50.join(dirPath, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
|
|
7538
|
+
if (files.length > 0) return fs32.readFileSync(path50.join(dirPath, files[0].name), "utf-8");
|
|
7082
7539
|
return void 0;
|
|
7083
7540
|
}
|
|
7084
7541
|
function loadPriorState(metadata, projectPath) {
|
|
7085
7542
|
if (!metadata?.state.persistent || metadata.state.files.length === 0) return void 0;
|
|
7086
7543
|
for (const stateFilePath of metadata.state.files) {
|
|
7087
|
-
const fullPath =
|
|
7088
|
-
if (!
|
|
7089
|
-
const stat =
|
|
7544
|
+
const fullPath = path50.join(projectPath, stateFilePath);
|
|
7545
|
+
if (!fs32.existsSync(fullPath)) continue;
|
|
7546
|
+
const stat = fs32.statSync(fullPath);
|
|
7090
7547
|
if (stat.isDirectory()) return readMostRecentFileInDir(fullPath);
|
|
7091
|
-
return
|
|
7548
|
+
return fs32.readFileSync(fullPath, "utf-8");
|
|
7092
7549
|
}
|
|
7093
7550
|
return void 0;
|
|
7094
7551
|
}
|
|
@@ -7108,9 +7565,9 @@ function resolvePhaseState(metadata, projectPath, phase) {
|
|
|
7108
7565
|
}
|
|
7109
7566
|
function appendProjectState(content, metadata, projectPath, hasPathOpt) {
|
|
7110
7567
|
if (!metadata?.state.persistent || !hasPathOpt) return content;
|
|
7111
|
-
const stateFile =
|
|
7112
|
-
if (!
|
|
7113
|
-
const stateContent =
|
|
7568
|
+
const stateFile = path50.join(projectPath, ".harness", "state.json");
|
|
7569
|
+
if (!fs32.existsSync(stateFile)) return content;
|
|
7570
|
+
const stateContent = fs32.readFileSync(stateFile, "utf-8");
|
|
7114
7571
|
return content + `
|
|
7115
7572
|
|
|
7116
7573
|
---
|
|
@@ -7122,14 +7579,14 @@ ${stateContent}
|
|
|
7122
7579
|
}
|
|
7123
7580
|
async function runSkill(name, opts) {
|
|
7124
7581
|
const skillsDir = resolveSkillsDir();
|
|
7125
|
-
const skillDir =
|
|
7126
|
-
if (!
|
|
7582
|
+
const skillDir = path50.join(skillsDir, name);
|
|
7583
|
+
if (!fs32.existsSync(skillDir)) {
|
|
7127
7584
|
logger.error(`Skill not found: ${name}`);
|
|
7128
7585
|
process.exit(ExitCode.ERROR);
|
|
7129
7586
|
return;
|
|
7130
7587
|
}
|
|
7131
7588
|
const metadata = loadSkillMetadata(skillDir);
|
|
7132
|
-
const projectPath = opts.path ?
|
|
7589
|
+
const projectPath = opts.path ? path50.resolve(opts.path) : process.cwd();
|
|
7133
7590
|
const complexity = resolveComplexity(
|
|
7134
7591
|
metadata,
|
|
7135
7592
|
opts.complexity ?? "standard",
|
|
@@ -7156,14 +7613,14 @@ async function runSkill(name, opts) {
|
|
|
7156
7613
|
...stateWarning !== void 0 && { stateWarning },
|
|
7157
7614
|
...opts.party !== void 0 && { party: opts.party }
|
|
7158
7615
|
});
|
|
7159
|
-
const skillMdPath =
|
|
7160
|
-
if (!
|
|
7616
|
+
const skillMdPath = path50.join(skillDir, "SKILL.md");
|
|
7617
|
+
if (!fs32.existsSync(skillMdPath)) {
|
|
7161
7618
|
logger.error(`SKILL.md not found for skill: ${name}`);
|
|
7162
7619
|
process.exit(ExitCode.ERROR);
|
|
7163
7620
|
return;
|
|
7164
7621
|
}
|
|
7165
7622
|
const content = appendProjectState(
|
|
7166
|
-
|
|
7623
|
+
fs32.readFileSync(skillMdPath, "utf-8"),
|
|
7167
7624
|
metadata,
|
|
7168
7625
|
projectPath,
|
|
7169
7626
|
!!opts.path
|
|
@@ -7172,13 +7629,13 @@ async function runSkill(name, opts) {
|
|
|
7172
7629
|
process.exit(ExitCode.SUCCESS);
|
|
7173
7630
|
}
|
|
7174
7631
|
function createRunCommand2() {
|
|
7175
|
-
return new
|
|
7632
|
+
return new Command61("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));
|
|
7176
7633
|
}
|
|
7177
7634
|
|
|
7178
7635
|
// src/commands/skill/validate.ts
|
|
7179
|
-
import { Command as
|
|
7180
|
-
import * as
|
|
7181
|
-
import * as
|
|
7636
|
+
import { Command as Command62 } from "commander";
|
|
7637
|
+
import * as fs33 from "fs";
|
|
7638
|
+
import * as path51 from "path";
|
|
7182
7639
|
import { parse as parse3 } from "yaml";
|
|
7183
7640
|
var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
7184
7641
|
"## When to Use",
|
|
@@ -7190,11 +7647,11 @@ var BEHAVIORAL_REQUIRED_SECTIONS = [
|
|
|
7190
7647
|
];
|
|
7191
7648
|
var KNOWLEDGE_REQUIRED_SECTIONS = ["## Instructions"];
|
|
7192
7649
|
function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
7193
|
-
if (!
|
|
7650
|
+
if (!fs33.existsSync(skillMdPath)) {
|
|
7194
7651
|
errors.push(`${name}: missing SKILL.md`);
|
|
7195
7652
|
return;
|
|
7196
7653
|
}
|
|
7197
|
-
const mdContent =
|
|
7654
|
+
const mdContent = fs33.readFileSync(skillMdPath, "utf-8");
|
|
7198
7655
|
if (!mdContent.trim().startsWith("# ")) {
|
|
7199
7656
|
errors.push(`${name}/SKILL.md: must start with an h1 heading`);
|
|
7200
7657
|
}
|
|
@@ -7219,20 +7676,20 @@ function validateSkillMd(name, skillMdPath, skillType, errors) {
|
|
|
7219
7676
|
}
|
|
7220
7677
|
}
|
|
7221
7678
|
function validateSkillEntry(name, skillsDir, errors) {
|
|
7222
|
-
const skillDir =
|
|
7223
|
-
const yamlPath =
|
|
7224
|
-
if (!
|
|
7679
|
+
const skillDir = path51.join(skillsDir, name);
|
|
7680
|
+
const yamlPath = path51.join(skillDir, "skill.yaml");
|
|
7681
|
+
if (!fs33.existsSync(yamlPath)) {
|
|
7225
7682
|
errors.push(`${name}: missing skill.yaml`);
|
|
7226
7683
|
return false;
|
|
7227
7684
|
}
|
|
7228
7685
|
try {
|
|
7229
|
-
const raw =
|
|
7686
|
+
const raw = fs33.readFileSync(yamlPath, "utf-8");
|
|
7230
7687
|
const result = SkillMetadataSchema.safeParse(parse3(raw));
|
|
7231
7688
|
if (!result.success) {
|
|
7232
7689
|
errors.push(`${name}/skill.yaml: ${result.error.message}`);
|
|
7233
7690
|
return false;
|
|
7234
7691
|
}
|
|
7235
|
-
validateSkillMd(name,
|
|
7692
|
+
validateSkillMd(name, path51.join(skillDir, "SKILL.md"), result.data.type, errors);
|
|
7236
7693
|
return true;
|
|
7237
7694
|
} catch (e) {
|
|
7238
7695
|
errors.push(`${name}: parse error \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -7240,15 +7697,15 @@ function validateSkillEntry(name, skillsDir, errors) {
|
|
|
7240
7697
|
}
|
|
7241
7698
|
}
|
|
7242
7699
|
function createValidateCommand2() {
|
|
7243
|
-
return new
|
|
7700
|
+
return new Command62("validate").description("Validate all skill.yaml files and SKILL.md structure").action(async (_opts, cmd) => {
|
|
7244
7701
|
const globalOpts = cmd.optsWithGlobals();
|
|
7245
7702
|
const skillsDir = resolveSkillsDir();
|
|
7246
|
-
if (!
|
|
7703
|
+
if (!fs33.existsSync(skillsDir)) {
|
|
7247
7704
|
logger.info("No skills directory found.");
|
|
7248
7705
|
process.exit(ExitCode.SUCCESS);
|
|
7249
7706
|
return;
|
|
7250
7707
|
}
|
|
7251
|
-
const entries =
|
|
7708
|
+
const entries = fs33.readdirSync(skillsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
|
|
7252
7709
|
const errors = [];
|
|
7253
7710
|
let validated = 0;
|
|
7254
7711
|
for (const name of entries) {
|
|
@@ -7270,24 +7727,24 @@ function createValidateCommand2() {
|
|
|
7270
7727
|
}
|
|
7271
7728
|
|
|
7272
7729
|
// src/commands/skill/info.ts
|
|
7273
|
-
import { Command as
|
|
7274
|
-
import * as
|
|
7275
|
-
import * as
|
|
7730
|
+
import { Command as Command63 } from "commander";
|
|
7731
|
+
import * as fs34 from "fs";
|
|
7732
|
+
import * as path52 from "path";
|
|
7276
7733
|
import { parse as parse4 } from "yaml";
|
|
7277
7734
|
function loadSkillMetadata2(name) {
|
|
7278
7735
|
const skillsDir = resolveSkillsDir();
|
|
7279
|
-
const skillDir =
|
|
7280
|
-
if (!
|
|
7736
|
+
const skillDir = path52.join(skillsDir, name);
|
|
7737
|
+
if (!fs34.existsSync(skillDir)) {
|
|
7281
7738
|
logger.error(`Skill not found: ${name}`);
|
|
7282
7739
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
7283
7740
|
}
|
|
7284
|
-
const yamlPath =
|
|
7285
|
-
if (!
|
|
7741
|
+
const yamlPath = path52.join(skillDir, "skill.yaml");
|
|
7742
|
+
if (!fs34.existsSync(yamlPath)) {
|
|
7286
7743
|
logger.error(`skill.yaml not found for skill: ${name}`);
|
|
7287
7744
|
return { ok: false, exitCode: ExitCode.ERROR };
|
|
7288
7745
|
}
|
|
7289
7746
|
try {
|
|
7290
|
-
const raw =
|
|
7747
|
+
const raw = fs34.readFileSync(yamlPath, "utf-8");
|
|
7291
7748
|
const parsed = parse4(raw);
|
|
7292
7749
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
7293
7750
|
if (!result.success) {
|
|
@@ -7316,7 +7773,7 @@ function printSkillInfo(skill) {
|
|
|
7316
7773
|
console.log(`Persistent: ${skill.state.persistent}`);
|
|
7317
7774
|
}
|
|
7318
7775
|
function createInfoCommand() {
|
|
7319
|
-
return new
|
|
7776
|
+
return new Command63("info").description("Show metadata for a skill").argument("<name>", "Skill name (e.g., harness-tdd)").action(async (name, _opts, cmd) => {
|
|
7320
7777
|
const globalOpts = cmd.optsWithGlobals();
|
|
7321
7778
|
const loaded = loadSkillMetadata2(name);
|
|
7322
7779
|
if (!loaded.ok) {
|
|
@@ -7333,7 +7790,7 @@ function createInfoCommand() {
|
|
|
7333
7790
|
}
|
|
7334
7791
|
|
|
7335
7792
|
// src/commands/skill/search.ts
|
|
7336
|
-
import { Command as
|
|
7793
|
+
import { Command as Command64 } from "commander";
|
|
7337
7794
|
async function runSearch(query, opts) {
|
|
7338
7795
|
const results = await searchNpmRegistry(query, opts.registry);
|
|
7339
7796
|
return results.filter((r) => {
|
|
@@ -7378,15 +7835,15 @@ async function runSearchAction(query, opts, globalOpts) {
|
|
|
7378
7835
|
}
|
|
7379
7836
|
}
|
|
7380
7837
|
function createSearchCommand() {
|
|
7381
|
-
return new
|
|
7838
|
+
return new Command64("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) => {
|
|
7382
7839
|
await runSearchAction(query, opts, cmd.optsWithGlobals());
|
|
7383
7840
|
});
|
|
7384
7841
|
}
|
|
7385
7842
|
|
|
7386
7843
|
// src/commands/skill/create.ts
|
|
7387
|
-
import { Command as
|
|
7388
|
-
import * as
|
|
7389
|
-
import * as
|
|
7844
|
+
import { Command as Command65 } from "commander";
|
|
7845
|
+
import * as path53 from "path";
|
|
7846
|
+
import * as fs35 from "fs";
|
|
7390
7847
|
import YAML from "yaml";
|
|
7391
7848
|
var KEBAB_CASE_RE = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
7392
7849
|
function buildReadme(name, description) {
|
|
@@ -7470,22 +7927,22 @@ function runCreate(name, opts) {
|
|
|
7470
7927
|
if (!KEBAB_CASE_RE.test(name)) {
|
|
7471
7928
|
throw new Error(`Invalid skill name "${name}". Must be kebab-case (e.g., my-skill).`);
|
|
7472
7929
|
}
|
|
7473
|
-
const baseDir = opts.outputDir ??
|
|
7474
|
-
const skillDir =
|
|
7475
|
-
if (
|
|
7930
|
+
const baseDir = opts.outputDir ?? path53.join(process.cwd(), "agents", "skills", "claude-code");
|
|
7931
|
+
const skillDir = path53.join(baseDir, name);
|
|
7932
|
+
if (fs35.existsSync(skillDir)) {
|
|
7476
7933
|
throw new Error(`Skill directory already exists: ${skillDir}`);
|
|
7477
7934
|
}
|
|
7478
|
-
|
|
7935
|
+
fs35.mkdirSync(skillDir, { recursive: true });
|
|
7479
7936
|
const description = opts.description || `A community skill: ${name}`;
|
|
7480
7937
|
const skillYaml = buildSkillYaml(name, opts);
|
|
7481
|
-
const skillYamlPath =
|
|
7482
|
-
|
|
7938
|
+
const skillYamlPath = path53.join(skillDir, "skill.yaml");
|
|
7939
|
+
fs35.writeFileSync(skillYamlPath, YAML.stringify(skillYaml));
|
|
7483
7940
|
const skillMd = buildSkillMd(name, description);
|
|
7484
|
-
const skillMdPath =
|
|
7485
|
-
|
|
7941
|
+
const skillMdPath = path53.join(skillDir, "SKILL.md");
|
|
7942
|
+
fs35.writeFileSync(skillMdPath, skillMd);
|
|
7486
7943
|
const readme = buildReadme(name, description);
|
|
7487
|
-
const readmePath =
|
|
7488
|
-
|
|
7944
|
+
const readmePath = path53.join(skillDir, "README.md");
|
|
7945
|
+
fs35.writeFileSync(readmePath, readme);
|
|
7489
7946
|
return {
|
|
7490
7947
|
name,
|
|
7491
7948
|
directory: skillDir,
|
|
@@ -7499,12 +7956,12 @@ function printCreateResult(name, result) {
|
|
|
7499
7956
|
}
|
|
7500
7957
|
logger.info(`
|
|
7501
7958
|
Next steps:`);
|
|
7502
|
-
logger.info(` 1. Edit ${
|
|
7959
|
+
logger.info(` 1. Edit ${path53.join(result.directory, "SKILL.md")} with your skill content`);
|
|
7503
7960
|
logger.info(` 2. Run: harness skill validate ${name}`);
|
|
7504
7961
|
logger.info(` 3. Run: harness skills publish`);
|
|
7505
7962
|
}
|
|
7506
7963
|
function createCreateCommand() {
|
|
7507
|
-
return new
|
|
7964
|
+
return new Command65("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) => {
|
|
7508
7965
|
const globalOpts = cmd.optsWithGlobals();
|
|
7509
7966
|
try {
|
|
7510
7967
|
const result = runCreate(name, {
|
|
@@ -7527,26 +7984,26 @@ function createCreateCommand() {
|
|
|
7527
7984
|
}
|
|
7528
7985
|
|
|
7529
7986
|
// src/commands/skill/publish.ts
|
|
7530
|
-
import { Command as
|
|
7531
|
-
import * as
|
|
7532
|
-
import * as
|
|
7533
|
-
import { execFileSync as
|
|
7987
|
+
import { Command as Command66 } from "commander";
|
|
7988
|
+
import * as fs37 from "fs";
|
|
7989
|
+
import * as path55 from "path";
|
|
7990
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
7534
7991
|
|
|
7535
7992
|
// src/registry/validator.ts
|
|
7536
|
-
import * as
|
|
7537
|
-
import * as
|
|
7993
|
+
import * as fs36 from "fs";
|
|
7994
|
+
import * as path54 from "path";
|
|
7538
7995
|
import { parse as parse5 } from "yaml";
|
|
7539
7996
|
import semver4 from "semver";
|
|
7540
7997
|
async function validateForPublish(skillDir, registryUrl) {
|
|
7541
7998
|
const errors = [];
|
|
7542
|
-
const skillYamlPath =
|
|
7543
|
-
if (!
|
|
7999
|
+
const skillYamlPath = path54.join(skillDir, "skill.yaml");
|
|
8000
|
+
if (!fs36.existsSync(skillYamlPath)) {
|
|
7544
8001
|
errors.push("skill.yaml not found. Create one with: harness skill create <name>");
|
|
7545
8002
|
return { valid: false, errors };
|
|
7546
8003
|
}
|
|
7547
8004
|
let skillMeta;
|
|
7548
8005
|
try {
|
|
7549
|
-
const raw =
|
|
8006
|
+
const raw = fs36.readFileSync(skillYamlPath, "utf-8");
|
|
7550
8007
|
const parsed = parse5(raw);
|
|
7551
8008
|
const result = SkillMetadataSchema.safeParse(parsed);
|
|
7552
8009
|
if (!result.success) {
|
|
@@ -7568,11 +8025,11 @@ async function validateForPublish(skillDir, registryUrl) {
|
|
|
7568
8025
|
if (!skillMeta.triggers || skillMeta.triggers.length === 0) {
|
|
7569
8026
|
errors.push("At least one trigger is required. Add triggers to skill.yaml.");
|
|
7570
8027
|
}
|
|
7571
|
-
const skillMdPath =
|
|
7572
|
-
if (!
|
|
8028
|
+
const skillMdPath = path54.join(skillDir, "SKILL.md");
|
|
8029
|
+
if (!fs36.existsSync(skillMdPath)) {
|
|
7573
8030
|
errors.push("SKILL.md not found. Create it with content describing your skill.");
|
|
7574
8031
|
} else {
|
|
7575
|
-
const content =
|
|
8032
|
+
const content = fs36.readFileSync(skillMdPath, "utf-8");
|
|
7576
8033
|
if (!content.includes("## When to Use")) {
|
|
7577
8034
|
errors.push('SKILL.md must contain a "## When to Use" section.');
|
|
7578
8035
|
}
|
|
@@ -7652,11 +8109,11 @@ ${errorList}`);
|
|
|
7652
8109
|
}
|
|
7653
8110
|
const meta = validation.skillMeta;
|
|
7654
8111
|
const pkg = derivePackageJson(meta);
|
|
7655
|
-
const pkgPath =
|
|
7656
|
-
|
|
7657
|
-
const readmePath =
|
|
7658
|
-
if (!
|
|
7659
|
-
const skillMdContent =
|
|
8112
|
+
const pkgPath = path55.join(skillDir, "package.json");
|
|
8113
|
+
fs37.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
8114
|
+
const readmePath = path55.join(skillDir, "README.md");
|
|
8115
|
+
if (!fs37.existsSync(readmePath)) {
|
|
8116
|
+
const skillMdContent = fs37.readFileSync(path55.join(skillDir, "SKILL.md"), "utf-8");
|
|
7660
8117
|
const readme = `# ${pkg.name}
|
|
7661
8118
|
|
|
7662
8119
|
${meta.description}
|
|
@@ -7670,7 +8127,7 @@ harness install ${meta.name}
|
|
|
7670
8127
|
---
|
|
7671
8128
|
|
|
7672
8129
|
${skillMdContent}`;
|
|
7673
|
-
|
|
8130
|
+
fs37.writeFileSync(readmePath, readme);
|
|
7674
8131
|
}
|
|
7675
8132
|
if (opts.dryRun) {
|
|
7676
8133
|
return {
|
|
@@ -7684,7 +8141,7 @@ ${skillMdContent}`;
|
|
|
7684
8141
|
if (opts.registry) {
|
|
7685
8142
|
publishArgs.push("--registry", opts.registry);
|
|
7686
8143
|
}
|
|
7687
|
-
|
|
8144
|
+
execFileSync6("npm", publishArgs, {
|
|
7688
8145
|
cwd: skillDir,
|
|
7689
8146
|
stdio: "pipe",
|
|
7690
8147
|
timeout: 6e4
|
|
@@ -7696,7 +8153,7 @@ ${skillMdContent}`;
|
|
|
7696
8153
|
};
|
|
7697
8154
|
}
|
|
7698
8155
|
function createPublishCommand() {
|
|
7699
|
-
return new
|
|
8156
|
+
return new Command66("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) => {
|
|
7700
8157
|
const globalOpts = cmd.optsWithGlobals();
|
|
7701
8158
|
const skillDir = opts.dir || process.cwd();
|
|
7702
8159
|
try {
|
|
@@ -7721,7 +8178,7 @@ function createPublishCommand() {
|
|
|
7721
8178
|
|
|
7722
8179
|
// src/commands/skill/index.ts
|
|
7723
8180
|
function createSkillCommand() {
|
|
7724
|
-
const command = new
|
|
8181
|
+
const command = new Command67("skill").description("Skill management commands");
|
|
7725
8182
|
command.addCommand(createListCommand3());
|
|
7726
8183
|
command.addCommand(createRunCommand2());
|
|
7727
8184
|
command.addCommand(createValidateCommand2());
|
|
@@ -7734,9 +8191,9 @@ function createSkillCommand() {
|
|
|
7734
8191
|
|
|
7735
8192
|
// src/commands/snapshot.ts
|
|
7736
8193
|
init_dist();
|
|
7737
|
-
import { Command as
|
|
8194
|
+
import { Command as Command68 } from "commander";
|
|
7738
8195
|
import { execSync as execSync5 } from "child_process";
|
|
7739
|
-
import
|
|
8196
|
+
import chalk12 from "chalk";
|
|
7740
8197
|
function getCommitHash3(cwd) {
|
|
7741
8198
|
try {
|
|
7742
8199
|
return execSync5("git rev-parse --short HEAD", { cwd, encoding: "utf-8" }).toString().trim();
|
|
@@ -7761,9 +8218,9 @@ function formatDelta(delta) {
|
|
|
7761
8218
|
function directionSymbol(direction) {
|
|
7762
8219
|
switch (direction) {
|
|
7763
8220
|
case "improving":
|
|
7764
|
-
return
|
|
8221
|
+
return chalk12.green("improving");
|
|
7765
8222
|
case "declining":
|
|
7766
|
-
return
|
|
8223
|
+
return chalk12.red("declining");
|
|
7767
8224
|
case "stable":
|
|
7768
8225
|
return "=";
|
|
7769
8226
|
}
|
|
@@ -7916,7 +8373,7 @@ function registerListCommand(parent) {
|
|
|
7916
8373
|
});
|
|
7917
8374
|
}
|
|
7918
8375
|
function createSnapshotCommand() {
|
|
7919
|
-
const command = new
|
|
8376
|
+
const command = new Command68("snapshot").description("Architecture timeline snapshot commands");
|
|
7920
8377
|
registerCaptureCommand(command);
|
|
7921
8378
|
registerTrendsCommand(command);
|
|
7922
8379
|
registerListCommand(command);
|
|
@@ -7924,12 +8381,12 @@ function createSnapshotCommand() {
|
|
|
7924
8381
|
}
|
|
7925
8382
|
|
|
7926
8383
|
// src/commands/state/index.ts
|
|
7927
|
-
import { Command as
|
|
8384
|
+
import { Command as Command73 } from "commander";
|
|
7928
8385
|
|
|
7929
8386
|
// src/commands/state/show.ts
|
|
7930
8387
|
init_dist();
|
|
7931
|
-
import { Command as
|
|
7932
|
-
import * as
|
|
8388
|
+
import { Command as Command69 } from "commander";
|
|
8389
|
+
import * as path56 from "path";
|
|
7933
8390
|
function printStateText(state, stream) {
|
|
7934
8391
|
if (stream) console.log(`Stream: ${stream}`);
|
|
7935
8392
|
console.log(`Schema Version: ${state.schemaVersion}`);
|
|
@@ -7953,9 +8410,9 @@ function printStateProgress(progress) {
|
|
|
7953
8410
|
}
|
|
7954
8411
|
}
|
|
7955
8412
|
function createShowCommand() {
|
|
7956
|
-
return new
|
|
8413
|
+
return new Command69("show").description("Show current project state").option("--path <path>", "Project root path", ".").option("--stream <name>", "Target a specific stream").action(async (opts, cmd) => {
|
|
7957
8414
|
const globalOpts = cmd.optsWithGlobals();
|
|
7958
|
-
const result = await loadState(
|
|
8415
|
+
const result = await loadState(path56.resolve(opts.path), opts.stream);
|
|
7959
8416
|
if (!result.ok) {
|
|
7960
8417
|
logger.error(result.error.message);
|
|
7961
8418
|
process.exit(ExitCode.ERROR);
|
|
@@ -7971,10 +8428,10 @@ function createShowCommand() {
|
|
|
7971
8428
|
|
|
7972
8429
|
// src/commands/state/reset.ts
|
|
7973
8430
|
init_dist();
|
|
7974
|
-
import { Command as
|
|
7975
|
-
import * as
|
|
7976
|
-
import * as
|
|
7977
|
-
import * as
|
|
8431
|
+
import { Command as Command70 } from "commander";
|
|
8432
|
+
import * as fs38 from "fs";
|
|
8433
|
+
import * as path57 from "path";
|
|
8434
|
+
import * as readline3 from "readline";
|
|
7978
8435
|
async function resolveStatePath(projectPath, stream) {
|
|
7979
8436
|
if (stream) {
|
|
7980
8437
|
const streamResult = await resolveStreamPath(projectPath, { stream });
|
|
@@ -7982,22 +8439,22 @@ async function resolveStatePath(projectPath, stream) {
|
|
|
7982
8439
|
logger.error(streamResult.error.message);
|
|
7983
8440
|
process.exit(ExitCode.ERROR);
|
|
7984
8441
|
}
|
|
7985
|
-
return
|
|
8442
|
+
return path57.join(streamResult.value, "state.json");
|
|
7986
8443
|
}
|
|
7987
|
-
return
|
|
8444
|
+
return path57.join(projectPath, ".harness", "state.json");
|
|
7988
8445
|
}
|
|
7989
8446
|
async function confirmReset() {
|
|
7990
|
-
const rl =
|
|
7991
|
-
const answer = await new Promise((
|
|
7992
|
-
rl.question("Reset project state? This cannot be undone. [y/N] ",
|
|
8447
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
8448
|
+
const answer = await new Promise((resolve37) => {
|
|
8449
|
+
rl.question("Reset project state? This cannot be undone. [y/N] ", resolve37);
|
|
7993
8450
|
});
|
|
7994
8451
|
rl.close();
|
|
7995
8452
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
7996
8453
|
}
|
|
7997
8454
|
async function runReset(opts) {
|
|
7998
|
-
const projectPath =
|
|
8455
|
+
const projectPath = path57.resolve(opts.path);
|
|
7999
8456
|
const statePath = await resolveStatePath(projectPath, opts.stream);
|
|
8000
|
-
if (!
|
|
8457
|
+
if (!fs38.existsSync(statePath)) {
|
|
8001
8458
|
logger.info("No state file found. Nothing to reset.");
|
|
8002
8459
|
process.exit(ExitCode.SUCCESS);
|
|
8003
8460
|
}
|
|
@@ -8006,7 +8463,7 @@ async function runReset(opts) {
|
|
|
8006
8463
|
process.exit(ExitCode.SUCCESS);
|
|
8007
8464
|
}
|
|
8008
8465
|
try {
|
|
8009
|
-
|
|
8466
|
+
fs38.unlinkSync(statePath);
|
|
8010
8467
|
logger.success("Project state reset.");
|
|
8011
8468
|
} catch (e) {
|
|
8012
8469
|
logger.error(`Failed to reset state: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -8015,16 +8472,16 @@ async function runReset(opts) {
|
|
|
8015
8472
|
process.exit(ExitCode.SUCCESS);
|
|
8016
8473
|
}
|
|
8017
8474
|
function createResetCommand() {
|
|
8018
|
-
return new
|
|
8475
|
+
return new Command70("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));
|
|
8019
8476
|
}
|
|
8020
8477
|
|
|
8021
8478
|
// src/commands/state/learn.ts
|
|
8022
8479
|
init_dist();
|
|
8023
|
-
import { Command as
|
|
8024
|
-
import * as
|
|
8480
|
+
import { Command as Command71 } from "commander";
|
|
8481
|
+
import * as path58 from "path";
|
|
8025
8482
|
function createLearnCommand() {
|
|
8026
|
-
return new
|
|
8027
|
-
const projectPath =
|
|
8483
|
+
return new Command71("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) => {
|
|
8484
|
+
const projectPath = path58.resolve(opts.path);
|
|
8028
8485
|
const result = await appendLearning(projectPath, message, void 0, void 0, opts.stream);
|
|
8029
8486
|
if (!result.ok) {
|
|
8030
8487
|
logger.error(result.error.message);
|
|
@@ -8038,10 +8495,10 @@ function createLearnCommand() {
|
|
|
8038
8495
|
|
|
8039
8496
|
// src/commands/state/streams.ts
|
|
8040
8497
|
init_dist();
|
|
8041
|
-
import { Command as
|
|
8042
|
-
import * as
|
|
8498
|
+
import { Command as Command72 } from "commander";
|
|
8499
|
+
import * as path59 from "path";
|
|
8043
8500
|
async function runListStreams(opts, globalOpts) {
|
|
8044
|
-
const projectPath =
|
|
8501
|
+
const projectPath = path59.resolve(opts.path);
|
|
8045
8502
|
const indexResult = await loadStreamIndex(projectPath);
|
|
8046
8503
|
const result = await listStreams(projectPath);
|
|
8047
8504
|
if (!result.ok) {
|
|
@@ -8062,7 +8519,7 @@ async function runListStreams(opts, globalOpts) {
|
|
|
8062
8519
|
process.exit(ExitCode.SUCCESS);
|
|
8063
8520
|
}
|
|
8064
8521
|
async function runCreateStream(name, opts) {
|
|
8065
|
-
const result = await createStream(
|
|
8522
|
+
const result = await createStream(path59.resolve(opts.path), name, opts.branch);
|
|
8066
8523
|
if (!result.ok) {
|
|
8067
8524
|
logger.error(result.error.message);
|
|
8068
8525
|
process.exit(ExitCode.ERROR);
|
|
@@ -8071,7 +8528,7 @@ async function runCreateStream(name, opts) {
|
|
|
8071
8528
|
process.exit(ExitCode.SUCCESS);
|
|
8072
8529
|
}
|
|
8073
8530
|
async function runArchiveStream(name, opts) {
|
|
8074
|
-
const result = await archiveStream(
|
|
8531
|
+
const result = await archiveStream(path59.resolve(opts.path), name);
|
|
8075
8532
|
if (!result.ok) {
|
|
8076
8533
|
logger.error(result.error.message);
|
|
8077
8534
|
process.exit(ExitCode.ERROR);
|
|
@@ -8080,7 +8537,7 @@ async function runArchiveStream(name, opts) {
|
|
|
8080
8537
|
process.exit(ExitCode.SUCCESS);
|
|
8081
8538
|
}
|
|
8082
8539
|
async function runActivateStream(name, opts) {
|
|
8083
|
-
const result = await setActiveStream(
|
|
8540
|
+
const result = await setActiveStream(path59.resolve(opts.path), name);
|
|
8084
8541
|
if (!result.ok) {
|
|
8085
8542
|
logger.error(result.error.message);
|
|
8086
8543
|
process.exit(ExitCode.ERROR);
|
|
@@ -8089,7 +8546,7 @@ async function runActivateStream(name, opts) {
|
|
|
8089
8546
|
process.exit(ExitCode.SUCCESS);
|
|
8090
8547
|
}
|
|
8091
8548
|
function createStreamsCommand() {
|
|
8092
|
-
const command = new
|
|
8549
|
+
const command = new Command72("streams").description("Manage state streams");
|
|
8093
8550
|
command.command("list").description("List all known streams").option("--path <path>", "Project root path", ".").action(async (opts, cmd) => runListStreams(opts, cmd.optsWithGlobals()));
|
|
8094
8551
|
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));
|
|
8095
8552
|
command.command("archive <name>").description("Archive a stream").option("--path <path>", "Project root path", ".").action(async (name, opts) => runArchiveStream(name, opts));
|
|
@@ -8099,7 +8556,7 @@ function createStreamsCommand() {
|
|
|
8099
8556
|
|
|
8100
8557
|
// src/commands/state/index.ts
|
|
8101
8558
|
function createStateCommand() {
|
|
8102
|
-
const command = new
|
|
8559
|
+
const command = new Command73("state").description("Project state management commands");
|
|
8103
8560
|
command.addCommand(createShowCommand());
|
|
8104
8561
|
command.addCommand(createResetCommand());
|
|
8105
8562
|
command.addCommand(createLearnCommand());
|
|
@@ -8108,9 +8565,9 @@ function createStateCommand() {
|
|
|
8108
8565
|
}
|
|
8109
8566
|
|
|
8110
8567
|
// src/commands/sync-analyses.ts
|
|
8111
|
-
import * as
|
|
8112
|
-
import * as
|
|
8113
|
-
import { Command as
|
|
8568
|
+
import * as fs39 from "fs";
|
|
8569
|
+
import * as path60 from "path";
|
|
8570
|
+
import { Command as Command74 } from "commander";
|
|
8114
8571
|
init_dist();
|
|
8115
8572
|
function isValidRecord(record) {
|
|
8116
8573
|
return typeof record.issueId === "string" && !!record.issueId && typeof record.identifier === "string" && !!record.identifier && typeof record.analyzedAt === "string" && !/[/\\]/.test(record.issueId);
|
|
@@ -8136,7 +8593,7 @@ function extractAnalysisFromComments(comments) {
|
|
|
8136
8593
|
return null;
|
|
8137
8594
|
}
|
|
8138
8595
|
function bootstrapTrackerCommand2(opts, verb) {
|
|
8139
|
-
const projectPath =
|
|
8596
|
+
const projectPath = path60.resolve(opts.dir);
|
|
8140
8597
|
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
8141
8598
|
if (!trackerConfig) {
|
|
8142
8599
|
logger.error(`No tracker config found in harness.config.json. Cannot ${verb}.`);
|
|
@@ -8148,8 +8605,8 @@ function bootstrapTrackerCommand2(opts, verb) {
|
|
|
8148
8605
|
);
|
|
8149
8606
|
return null;
|
|
8150
8607
|
}
|
|
8151
|
-
const projectEnvPath =
|
|
8152
|
-
if (
|
|
8608
|
+
const projectEnvPath = path60.join(projectPath, ".env");
|
|
8609
|
+
if (fs39.existsSync(projectEnvPath) && !process.env.GITHUB_TOKEN) {
|
|
8153
8610
|
__require("dotenv").config({ path: projectEnvPath });
|
|
8154
8611
|
}
|
|
8155
8612
|
const token = process.env.GITHUB_TOKEN;
|
|
@@ -8160,13 +8617,13 @@ function bootstrapTrackerCommand2(opts, verb) {
|
|
|
8160
8617
|
return { token, projectPath, trackerConfig };
|
|
8161
8618
|
}
|
|
8162
8619
|
function loadRoadmapFeatures(projectPath) {
|
|
8163
|
-
const roadmapFile =
|
|
8164
|
-
if (!
|
|
8620
|
+
const roadmapFile = path60.join(projectPath, "docs", "roadmap.md");
|
|
8621
|
+
if (!fs39.existsSync(roadmapFile)) {
|
|
8165
8622
|
logger.error("No docs/roadmap.md found. Cannot discover features with externalIds.");
|
|
8166
8623
|
return null;
|
|
8167
8624
|
}
|
|
8168
8625
|
const { parseRoadmap } = (init_dist(), __toCommonJS(dist_exports));
|
|
8169
|
-
const roadmapParsed = parseRoadmap(
|
|
8626
|
+
const roadmapParsed = parseRoadmap(fs39.readFileSync(roadmapFile, "utf-8"));
|
|
8170
8627
|
if (!roadmapParsed.ok) {
|
|
8171
8628
|
logger.error("Failed to parse docs/roadmap.md");
|
|
8172
8629
|
return null;
|
|
@@ -8217,9 +8674,9 @@ async function runSyncAnalyses(opts) {
|
|
|
8217
8674
|
return;
|
|
8218
8675
|
}
|
|
8219
8676
|
const { AnalysisArchive, loadPublishedIndex: loadPublishedIndex2, savePublishedIndex: savePublishedIndex2 } = await import("@harness-engineering/orchestrator");
|
|
8220
|
-
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-
|
|
8677
|
+
const { GitHubIssuesSyncAdapter: GitHubIssuesSyncAdapter2 } = await import("./dist-46VOJIG4.js");
|
|
8221
8678
|
const adapter = new GitHubIssuesSyncAdapter2({ token, config: trackerConfig });
|
|
8222
|
-
const archive = new AnalysisArchive(
|
|
8679
|
+
const archive = new AnalysisArchive(path60.join(projectPath, ".harness", "analyses"));
|
|
8223
8680
|
const publishedIndex = loadPublishedIndex2(projectPath);
|
|
8224
8681
|
const { syncedCount, skippedCount, warnCount } = await syncFeatureAnalyses(
|
|
8225
8682
|
features,
|
|
@@ -8239,7 +8696,7 @@ async function runSyncAnalyses(opts) {
|
|
|
8239
8696
|
}
|
|
8240
8697
|
}
|
|
8241
8698
|
function createSyncAnalysesCommand() {
|
|
8242
|
-
const command = new
|
|
8699
|
+
const command = new Command74("sync-analyses").description(
|
|
8243
8700
|
"Pull published intelligence analyses from the external issue tracker into the local .harness/analyses/ directory"
|
|
8244
8701
|
).option("-d, --dir <path>", "Workspace directory", process.cwd()).action(async (opts) => {
|
|
8245
8702
|
try {
|
|
@@ -8254,7 +8711,7 @@ function createSyncAnalysesCommand() {
|
|
|
8254
8711
|
|
|
8255
8712
|
// src/commands/taint.ts
|
|
8256
8713
|
init_dist();
|
|
8257
|
-
import { Command as
|
|
8714
|
+
import { Command as Command75 } from "commander";
|
|
8258
8715
|
function getProjectRoot() {
|
|
8259
8716
|
return process.cwd();
|
|
8260
8717
|
}
|
|
@@ -8326,28 +8783,28 @@ function registerStatusCommand(taint) {
|
|
|
8326
8783
|
});
|
|
8327
8784
|
}
|
|
8328
8785
|
function createTaintCommand() {
|
|
8329
|
-
const taint = new
|
|
8786
|
+
const taint = new Command75("taint").description("Manage sentinel session taint state");
|
|
8330
8787
|
registerClearCommand(taint);
|
|
8331
8788
|
registerStatusCommand(taint);
|
|
8332
8789
|
return taint;
|
|
8333
8790
|
}
|
|
8334
8791
|
|
|
8335
8792
|
// src/commands/telemetry/index.ts
|
|
8336
|
-
import { Command as
|
|
8793
|
+
import { Command as Command79 } from "commander";
|
|
8337
8794
|
|
|
8338
8795
|
// src/commands/telemetry/identify.ts
|
|
8339
8796
|
init_dist();
|
|
8340
|
-
import { Command as
|
|
8341
|
-
import * as
|
|
8342
|
-
import * as
|
|
8797
|
+
import { Command as Command76 } from "commander";
|
|
8798
|
+
import * as fs40 from "fs";
|
|
8799
|
+
import * as path61 from "path";
|
|
8343
8800
|
function telemetryFilePath(cwd) {
|
|
8344
|
-
return
|
|
8801
|
+
return path61.join(cwd, ".harness", "telemetry.json");
|
|
8345
8802
|
}
|
|
8346
8803
|
function writeTelemetryFile(cwd, data) {
|
|
8347
8804
|
const filePath = telemetryFilePath(cwd);
|
|
8348
|
-
const dir =
|
|
8349
|
-
|
|
8350
|
-
|
|
8805
|
+
const dir = path61.dirname(filePath);
|
|
8806
|
+
fs40.mkdirSync(dir, { recursive: true });
|
|
8807
|
+
fs40.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
8351
8808
|
}
|
|
8352
8809
|
var IDENTITY_FIELDS = ["project", "team", "alias"];
|
|
8353
8810
|
function applyIdentityFields(opts, identity) {
|
|
@@ -8361,7 +8818,7 @@ function printIdentity(identity) {
|
|
|
8361
8818
|
}
|
|
8362
8819
|
}
|
|
8363
8820
|
function createIdentifyCommand() {
|
|
8364
|
-
const cmd = new
|
|
8821
|
+
const cmd = new Command76("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) => {
|
|
8365
8822
|
const cwd = process.cwd();
|
|
8366
8823
|
if (opts.clear) {
|
|
8367
8824
|
writeTelemetryFile(cwd, { identity: {} });
|
|
@@ -8386,7 +8843,7 @@ function createIdentifyCommand() {
|
|
|
8386
8843
|
|
|
8387
8844
|
// src/commands/telemetry/status.ts
|
|
8388
8845
|
init_dist();
|
|
8389
|
-
import { Command as
|
|
8846
|
+
import { Command as Command77 } from "commander";
|
|
8390
8847
|
function gatherEnvOverrides() {
|
|
8391
8848
|
const overrides = {};
|
|
8392
8849
|
if (process.env.DO_NOT_TRACK) overrides.DO_NOT_TRACK = process.env.DO_NOT_TRACK;
|
|
@@ -8428,7 +8885,7 @@ function printHumanStatus(result) {
|
|
|
8428
8885
|
printEnvOverrides(result.envOverrides);
|
|
8429
8886
|
}
|
|
8430
8887
|
function createStatusCommand() {
|
|
8431
|
-
const cmd = new
|
|
8888
|
+
const cmd = new Command77("status").description("Show current telemetry consent state, install ID, and identity").option("--json", "Output as JSON").action((opts) => {
|
|
8432
8889
|
const cwd = process.cwd();
|
|
8433
8890
|
const envOverrides = gatherEnvOverrides();
|
|
8434
8891
|
const consent = resolveConsent(cwd, void 0);
|
|
@@ -8461,20 +8918,20 @@ function createStatusCommand() {
|
|
|
8461
8918
|
|
|
8462
8919
|
// src/commands/telemetry/test.ts
|
|
8463
8920
|
init_dist();
|
|
8464
|
-
import { Command as
|
|
8921
|
+
import { Command as Command78 } from "commander";
|
|
8465
8922
|
|
|
8466
8923
|
// src/bin/command-telemetry.ts
|
|
8467
|
-
import { existsSync as
|
|
8468
|
-
import { join as
|
|
8924
|
+
import { existsSync as existsSync37, readFileSync as readFileSync26, mkdirSync as mkdirSync16, appendFileSync, writeFileSync as writeFileSync19 } from "fs";
|
|
8925
|
+
import { join as join47, parse as parsePath, resolve as resolve34 } from "path";
|
|
8469
8926
|
import { spawn as spawn2 } from "child_process";
|
|
8470
8927
|
var POSTHOG_API_KEY = "phc_wNTdCMcfJXZPgdNeDociZW6vwoGGo4nb7vqEfWThFfsG";
|
|
8471
8928
|
var EXCLUDED_COMMANDS = /* @__PURE__ */ new Set(["help", "completion"]);
|
|
8472
8929
|
function findProjectRoot(cwd) {
|
|
8473
|
-
let dir =
|
|
8930
|
+
let dir = resolve34(cwd);
|
|
8474
8931
|
const { root } = parsePath(dir);
|
|
8475
8932
|
while (dir !== root) {
|
|
8476
|
-
if (
|
|
8477
|
-
dir =
|
|
8933
|
+
if (existsSync37(join47(dir, "harness.config.json"))) return dir;
|
|
8934
|
+
dir = resolve34(dir, "..");
|
|
8478
8935
|
}
|
|
8479
8936
|
return cwd;
|
|
8480
8937
|
}
|
|
@@ -8511,8 +8968,8 @@ function resolveCommandName(cmd) {
|
|
|
8511
8968
|
}
|
|
8512
8969
|
function writeCommandRecordSync(cwd, command, duration, outcome) {
|
|
8513
8970
|
try {
|
|
8514
|
-
const metricsDir =
|
|
8515
|
-
|
|
8971
|
+
const metricsDir = join47(cwd, ".harness", "metrics");
|
|
8972
|
+
mkdirSync16(metricsDir, { recursive: true });
|
|
8516
8973
|
const record = {
|
|
8517
8974
|
skill: command,
|
|
8518
8975
|
session: `cli-${Date.now()}`,
|
|
@@ -8521,16 +8978,16 @@ function writeCommandRecordSync(cwd, command, duration, outcome) {
|
|
|
8521
8978
|
outcome,
|
|
8522
8979
|
phasesReached: []
|
|
8523
8980
|
};
|
|
8524
|
-
const adoptionFile =
|
|
8981
|
+
const adoptionFile = join47(metricsDir, "adoption.jsonl");
|
|
8525
8982
|
appendFileSync(adoptionFile, JSON.stringify(record) + "\n");
|
|
8526
8983
|
} catch {
|
|
8527
8984
|
}
|
|
8528
8985
|
}
|
|
8529
8986
|
function isTelemetryDisabledByConfig(cwd) {
|
|
8530
|
-
const configPath =
|
|
8531
|
-
if (!
|
|
8987
|
+
const configPath = join47(cwd, "harness.config.json");
|
|
8988
|
+
if (!existsSync37(configPath)) return false;
|
|
8532
8989
|
try {
|
|
8533
|
-
const config = JSON.parse(
|
|
8990
|
+
const config = JSON.parse(readFileSync26(configPath, "utf-8"));
|
|
8534
8991
|
return config?.telemetry?.enabled === false;
|
|
8535
8992
|
} catch {
|
|
8536
8993
|
return false;
|
|
@@ -8542,11 +8999,11 @@ function isTelemetryOptedOut(cwd) {
|
|
|
8542
8999
|
}
|
|
8543
9000
|
function flushTelemetryBackground(cwd) {
|
|
8544
9001
|
try {
|
|
8545
|
-
const adoptionFile =
|
|
8546
|
-
if (!
|
|
9002
|
+
const adoptionFile = join47(cwd, ".harness", "metrics", "adoption.jsonl");
|
|
9003
|
+
if (!existsSync37(adoptionFile)) return;
|
|
8547
9004
|
if (isTelemetryOptedOut(cwd)) return;
|
|
8548
|
-
const reporterPath =
|
|
8549
|
-
if (!
|
|
9005
|
+
const reporterPath = join47(cwd, ".harness", "hooks", "telemetry-reporter.js");
|
|
9006
|
+
if (!existsSync37(reporterPath)) return;
|
|
8550
9007
|
const child = spawn2(process.execPath, [reporterPath], {
|
|
8551
9008
|
cwd,
|
|
8552
9009
|
stdio: ["pipe", "ignore", "ignore"],
|
|
@@ -8583,7 +9040,7 @@ function reportTestSuccess(status, distinctId, identity) {
|
|
|
8583
9040
|
if (identity.team) logger.info(` team: ${identity.team}`);
|
|
8584
9041
|
}
|
|
8585
9042
|
function createTestCommand() {
|
|
8586
|
-
return new
|
|
9043
|
+
return new Command78("test").description("Send a test event to PostHog and verify connectivity").action(async () => {
|
|
8587
9044
|
const cwd = process.cwd();
|
|
8588
9045
|
const consent = resolveConsent(cwd, void 0);
|
|
8589
9046
|
if (!consent.allowed) {
|
|
@@ -8620,7 +9077,7 @@ function createTestCommand() {
|
|
|
8620
9077
|
|
|
8621
9078
|
// src/commands/telemetry/index.ts
|
|
8622
9079
|
function createTelemetryCommand() {
|
|
8623
|
-
const command = new
|
|
9080
|
+
const command = new Command79("telemetry").description("Telemetry identity and status management");
|
|
8624
9081
|
command.addCommand(createIdentifyCommand());
|
|
8625
9082
|
command.addCommand(createStatusCommand());
|
|
8626
9083
|
command.addCommand(createTestCommand());
|
|
@@ -8628,8 +9085,8 @@ function createTelemetryCommand() {
|
|
|
8628
9085
|
}
|
|
8629
9086
|
|
|
8630
9087
|
// src/commands/traceability.ts
|
|
8631
|
-
import { Command as
|
|
8632
|
-
import
|
|
9088
|
+
import { Command as Command80 } from "commander";
|
|
9089
|
+
import chalk13 from "chalk";
|
|
8633
9090
|
function confidenceLabel(maxConfidence) {
|
|
8634
9091
|
if (maxConfidence >= 0.8) return "explicit";
|
|
8635
9092
|
if (maxConfidence > 0) return "inferred";
|
|
@@ -8638,13 +9095,13 @@ function confidenceLabel(maxConfidence) {
|
|
|
8638
9095
|
function statusIcon(status) {
|
|
8639
9096
|
switch (status) {
|
|
8640
9097
|
case "full":
|
|
8641
|
-
return
|
|
9098
|
+
return chalk13.green("\u2713 full");
|
|
8642
9099
|
case "code-only":
|
|
8643
|
-
return
|
|
9100
|
+
return chalk13.yellow("\u25D0 code-only");
|
|
8644
9101
|
case "test-only":
|
|
8645
|
-
return
|
|
9102
|
+
return chalk13.yellow("\u25D0 test-only");
|
|
8646
9103
|
case "none":
|
|
8647
|
-
return
|
|
9104
|
+
return chalk13.red("\u2717 none");
|
|
8648
9105
|
default:
|
|
8649
9106
|
return status;
|
|
8650
9107
|
}
|
|
@@ -8678,22 +9135,27 @@ function handleNoStore(mode) {
|
|
|
8678
9135
|
}
|
|
8679
9136
|
function handleEmptyResults(mode) {
|
|
8680
9137
|
if (mode === OutputMode.JSON) {
|
|
8681
|
-
console.log(
|
|
9138
|
+
console.log(
|
|
9139
|
+
JSON.stringify({
|
|
9140
|
+
results: [],
|
|
9141
|
+
message: "No requirements found in graph. Run `harness scan` to ingest spec requirements."
|
|
9142
|
+
})
|
|
9143
|
+
);
|
|
8682
9144
|
} else if (mode !== OutputMode.QUIET) {
|
|
8683
|
-
logger.info("No requirements found in graph. Run `harness scan` to
|
|
9145
|
+
logger.info("No requirements found in graph. Run `harness scan` to ingest spec requirements.");
|
|
8684
9146
|
}
|
|
8685
9147
|
process.exit(ExitCode.SUCCESS);
|
|
8686
9148
|
}
|
|
8687
9149
|
function printResultTable(result, mode) {
|
|
8688
9150
|
const specLabel = result.specPath || result.featureName || "Unknown";
|
|
8689
|
-
console.log(` ${
|
|
9151
|
+
console.log(` ${chalk13.cyan(specLabel)} ${chalk13.dim(`(${result.summary.total} requirements)`)}`);
|
|
8690
9152
|
console.log("");
|
|
8691
9153
|
const numWidth = 4;
|
|
8692
9154
|
const nameWidth = mode === OutputMode.VERBOSE ? 44 : 36;
|
|
8693
9155
|
const codeWidth = 6;
|
|
8694
9156
|
const testWidth = 7;
|
|
8695
9157
|
const confWidth = 12;
|
|
8696
|
-
const header =
|
|
9158
|
+
const header = chalk13.dim(
|
|
8697
9159
|
` ${pad("#", numWidth)}${pad("Requirement", nameWidth)}${pad("Code", codeWidth)}${pad("Tests", testWidth)}${pad("Confidence", confWidth)}Status`
|
|
8698
9160
|
);
|
|
8699
9161
|
console.log(header);
|
|
@@ -8722,10 +9184,10 @@ function printRequirementRows(requirements, mode, cols) {
|
|
|
8722
9184
|
);
|
|
8723
9185
|
if (mode === OutputMode.VERBOSE) {
|
|
8724
9186
|
for (const f of req.codeFiles) {
|
|
8725
|
-
console.log(
|
|
9187
|
+
console.log(chalk13.dim(` code: ${f.path} (${f.method})`));
|
|
8726
9188
|
}
|
|
8727
9189
|
for (const f of req.testFiles) {
|
|
8728
|
-
console.log(
|
|
9190
|
+
console.log(chalk13.dim(` test: ${f.path} (${f.method})`));
|
|
8729
9191
|
}
|
|
8730
9192
|
}
|
|
8731
9193
|
}
|
|
@@ -8736,7 +9198,7 @@ function printCoverageSummary(summary) {
|
|
|
8736
9198
|
const codePct = s.total > 0 ? Math.round(s.withCode / s.total * 100) : 0;
|
|
8737
9199
|
const testPct = s.total > 0 ? Math.round(s.withTests / s.total * 100) : 0;
|
|
8738
9200
|
console.log(
|
|
8739
|
-
` ${
|
|
9201
|
+
` ${chalk13.bold("Coverage:")} ${fullyPct}% fully traced (${s.fullyTraced}/${s.total}), ${codePct}% with code (${s.withCode}/${s.total}), ${testPct}% with tests (${s.withTests}/${s.total})`
|
|
8740
9202
|
);
|
|
8741
9203
|
}
|
|
8742
9204
|
async function runTraceability(options) {
|
|
@@ -8744,7 +9206,7 @@ async function runTraceability(options) {
|
|
|
8744
9206
|
const projectPath = process.cwd();
|
|
8745
9207
|
const store = await loadGraphStore(projectPath);
|
|
8746
9208
|
if (!store) handleNoStore(mode);
|
|
8747
|
-
const graphModule = await import("./dist-
|
|
9209
|
+
const graphModule = await import("./dist-BP6E6QGG.js");
|
|
8748
9210
|
const filterOptions = buildFilterOptions(options);
|
|
8749
9211
|
const results = graphModule.queryTraceability(store, filterOptions);
|
|
8750
9212
|
if (results.length === 0) handleEmptyResults(mode);
|
|
@@ -8752,14 +9214,14 @@ async function runTraceability(options) {
|
|
|
8752
9214
|
console.log(JSON.stringify(results, null, 2));
|
|
8753
9215
|
} else {
|
|
8754
9216
|
console.log("");
|
|
8755
|
-
console.log(
|
|
9217
|
+
console.log(chalk13.bold("Spec-to-Implementation Traceability"));
|
|
8756
9218
|
console.log("");
|
|
8757
9219
|
for (const result of results) printResultTable(result, mode);
|
|
8758
9220
|
}
|
|
8759
9221
|
process.exit(ExitCode.SUCCESS);
|
|
8760
9222
|
}
|
|
8761
9223
|
function createTraceabilityCommand() {
|
|
8762
|
-
return new
|
|
9224
|
+
return new Command80("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) => {
|
|
8763
9225
|
const globalOpts = cmd.optsWithGlobals();
|
|
8764
9226
|
await runTraceability({
|
|
8765
9227
|
spec: opts.spec,
|
|
@@ -8772,15 +9234,15 @@ function createTraceabilityCommand() {
|
|
|
8772
9234
|
}
|
|
8773
9235
|
|
|
8774
9236
|
// src/commands/uninstall.ts
|
|
8775
|
-
import * as
|
|
8776
|
-
import { Command as
|
|
9237
|
+
import * as path62 from "path";
|
|
9238
|
+
import { Command as Command81 } from "commander";
|
|
8777
9239
|
async function runUninstall(skillName, options) {
|
|
8778
9240
|
const packageName = resolvePackageName(skillName);
|
|
8779
9241
|
const shortName = extractSkillName(packageName);
|
|
8780
9242
|
const globalDir = resolveGlobalSkillsDir();
|
|
8781
|
-
const skillsDir =
|
|
8782
|
-
const communityBase =
|
|
8783
|
-
const lockfilePath =
|
|
9243
|
+
const skillsDir = path62.dirname(globalDir);
|
|
9244
|
+
const communityBase = path62.join(skillsDir, "community");
|
|
9245
|
+
const lockfilePath = path62.join(communityBase, "skills-lock.json");
|
|
8784
9246
|
const lockfile = readLockfile2(lockfilePath);
|
|
8785
9247
|
const entry = lockfile.skills[packageName];
|
|
8786
9248
|
if (!entry) {
|
|
@@ -8810,7 +9272,7 @@ async function runUninstall(skillName, options) {
|
|
|
8810
9272
|
return result;
|
|
8811
9273
|
}
|
|
8812
9274
|
function createUninstallCommand() {
|
|
8813
|
-
const cmd = new
|
|
9275
|
+
const cmd = new Command81("uninstall");
|
|
8814
9276
|
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) => {
|
|
8815
9277
|
try {
|
|
8816
9278
|
const result = await runUninstall(skill, opts);
|
|
@@ -8830,9 +9292,9 @@ function createUninstallCommand() {
|
|
|
8830
9292
|
|
|
8831
9293
|
// src/commands/uninstall-constraints.ts
|
|
8832
9294
|
init_dist();
|
|
8833
|
-
import * as
|
|
8834
|
-
import * as
|
|
8835
|
-
import { Command as
|
|
9295
|
+
import * as fs41 from "fs/promises";
|
|
9296
|
+
import * as path63 from "path";
|
|
9297
|
+
import { Command as Command82 } from "commander";
|
|
8836
9298
|
async function runUninstallConstraints(options) {
|
|
8837
9299
|
const { packageName, configPath, lockfilePath } = options;
|
|
8838
9300
|
const lockfileResult = await readLockfile(lockfilePath);
|
|
@@ -8852,7 +9314,7 @@ async function runUninstallConstraints(options) {
|
|
|
8852
9314
|
}
|
|
8853
9315
|
let localConfig;
|
|
8854
9316
|
try {
|
|
8855
|
-
const raw = await
|
|
9317
|
+
const raw = await fs41.readFile(configPath, "utf-8");
|
|
8856
9318
|
localConfig = JSON.parse(raw);
|
|
8857
9319
|
} catch (err) {
|
|
8858
9320
|
return {
|
|
@@ -8889,11 +9351,11 @@ async function runUninstallConstraints(options) {
|
|
|
8889
9351
|
};
|
|
8890
9352
|
}
|
|
8891
9353
|
function createUninstallConstraintsCommand() {
|
|
8892
|
-
const cmd = new
|
|
9354
|
+
const cmd = new Command82("uninstall-constraints");
|
|
8893
9355
|
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) => {
|
|
8894
9356
|
let configPath;
|
|
8895
9357
|
if (opts.config) {
|
|
8896
|
-
configPath =
|
|
9358
|
+
configPath = path63.resolve(opts.config);
|
|
8897
9359
|
} else {
|
|
8898
9360
|
const found = findConfigFile();
|
|
8899
9361
|
if (!found.ok) {
|
|
@@ -8902,8 +9364,8 @@ function createUninstallConstraintsCommand() {
|
|
|
8902
9364
|
}
|
|
8903
9365
|
configPath = found.value;
|
|
8904
9366
|
}
|
|
8905
|
-
const projectRoot =
|
|
8906
|
-
const lockfilePath =
|
|
9367
|
+
const projectRoot = path63.dirname(configPath);
|
|
9368
|
+
const lockfilePath = path63.join(projectRoot, ".harness", "constraints.lock.json");
|
|
8907
9369
|
const result = await runUninstallConstraints({
|
|
8908
9370
|
packageName: name,
|
|
8909
9371
|
configPath,
|
|
@@ -8928,13 +9390,13 @@ function createUninstallConstraintsCommand() {
|
|
|
8928
9390
|
}
|
|
8929
9391
|
|
|
8930
9392
|
// src/commands/update.ts
|
|
8931
|
-
import { Command as
|
|
8932
|
-
import { execFile, execFileSync as
|
|
8933
|
-
import { realpathSync, existsSync as
|
|
8934
|
-
import { join as
|
|
9393
|
+
import { Command as Command83 } from "commander";
|
|
9394
|
+
import { execFile, execFileSync as execFileSync7 } from "child_process";
|
|
9395
|
+
import { realpathSync, existsSync as existsSync38, readFileSync as readFileSync27 } from "fs";
|
|
9396
|
+
import { join as join50 } from "path";
|
|
8935
9397
|
import { promisify } from "util";
|
|
8936
|
-
import
|
|
8937
|
-
import
|
|
9398
|
+
import readline4 from "readline";
|
|
9399
|
+
import chalk14 from "chalk";
|
|
8938
9400
|
function detectPackageManager() {
|
|
8939
9401
|
try {
|
|
8940
9402
|
const argv1 = process.argv[1];
|
|
@@ -8963,7 +9425,7 @@ async function getLatestVersionAsync(pkg) {
|
|
|
8963
9425
|
function getInstalledVersions(pm, packages) {
|
|
8964
9426
|
const versions = {};
|
|
8965
9427
|
try {
|
|
8966
|
-
const output =
|
|
9428
|
+
const output = execFileSync7(pm, ["list", "-g", "--json"], {
|
|
8967
9429
|
encoding: "utf-8",
|
|
8968
9430
|
timeout: 15e3
|
|
8969
9431
|
});
|
|
@@ -8981,7 +9443,7 @@ function getInstalledVersions(pm, packages) {
|
|
|
8981
9443
|
}
|
|
8982
9444
|
function getInstalledPackages(pm) {
|
|
8983
9445
|
try {
|
|
8984
|
-
const output =
|
|
9446
|
+
const output = execFileSync7(pm, ["list", "-g", "--json"], {
|
|
8985
9447
|
encoding: "utf-8",
|
|
8986
9448
|
timeout: 15e3
|
|
8987
9449
|
});
|
|
@@ -8992,15 +9454,15 @@ function getInstalledPackages(pm) {
|
|
|
8992
9454
|
return ["@harness-engineering/cli", "@harness-engineering/core"];
|
|
8993
9455
|
}
|
|
8994
9456
|
}
|
|
8995
|
-
function
|
|
8996
|
-
const rl =
|
|
9457
|
+
function prompt3(question) {
|
|
9458
|
+
const rl = readline4.createInterface({
|
|
8997
9459
|
input: process.stdin,
|
|
8998
9460
|
output: process.stdout
|
|
8999
9461
|
});
|
|
9000
|
-
return new Promise((
|
|
9462
|
+
return new Promise((resolve37) => {
|
|
9001
9463
|
rl.question(question, (answer) => {
|
|
9002
9464
|
rl.close();
|
|
9003
|
-
|
|
9465
|
+
resolve37(answer.trim().toLowerCase());
|
|
9004
9466
|
});
|
|
9005
9467
|
});
|
|
9006
9468
|
}
|
|
@@ -9015,12 +9477,12 @@ async function ensureTelemetryIfNeeded() {
|
|
|
9015
9477
|
}
|
|
9016
9478
|
function refreshHooks() {
|
|
9017
9479
|
const cwd = process.cwd();
|
|
9018
|
-
const configPath =
|
|
9019
|
-
if (!
|
|
9480
|
+
const configPath = join50(cwd, "harness.config.json");
|
|
9481
|
+
if (!existsSync38(configPath)) return;
|
|
9020
9482
|
let profile = "standard";
|
|
9021
|
-
const profilePath =
|
|
9483
|
+
const profilePath = join50(cwd, ".harness", "hooks", "profile.json");
|
|
9022
9484
|
try {
|
|
9023
|
-
const data = JSON.parse(
|
|
9485
|
+
const data = JSON.parse(readFileSync27(profilePath, "utf-8"));
|
|
9024
9486
|
if (data.profile && ["minimal", "standard", "strict"].includes(data.profile)) {
|
|
9025
9487
|
profile = data.profile;
|
|
9026
9488
|
}
|
|
@@ -9037,27 +9499,27 @@ function refreshHooks() {
|
|
|
9037
9499
|
function runLocalGraphScan() {
|
|
9038
9500
|
try {
|
|
9039
9501
|
logger.info("Scanning codebase to rebuild knowledge graph...");
|
|
9040
|
-
|
|
9502
|
+
execFileSync7("harness", ["graph", "scan", "."], { stdio: "inherit" });
|
|
9041
9503
|
} catch {
|
|
9042
9504
|
logger.warn("Graph scan failed. Run manually:");
|
|
9043
|
-
console.log(` ${
|
|
9505
|
+
console.log(` ${chalk14.cyan("harness graph scan .")}`);
|
|
9044
9506
|
}
|
|
9045
9507
|
}
|
|
9046
9508
|
async function offerRegeneration() {
|
|
9047
9509
|
console.log("");
|
|
9048
|
-
const regenAnswer = await
|
|
9510
|
+
const regenAnswer = await prompt3(
|
|
9049
9511
|
"Regenerate slash commands, agent definitions, and knowledge graph? (Y/n) "
|
|
9050
9512
|
);
|
|
9051
9513
|
if (regenAnswer === "n" || regenAnswer === "no") return;
|
|
9052
|
-
const scopeAnswer = await
|
|
9514
|
+
const scopeAnswer = await prompt3("Generate for (G)lobal or (l)ocal project? (G/l) ");
|
|
9053
9515
|
const isGlobal = scopeAnswer !== "l" && scopeAnswer !== "local";
|
|
9054
9516
|
try {
|
|
9055
|
-
|
|
9517
|
+
execFileSync7("harness", ["generate", ...isGlobal ? ["--global"] : []], {
|
|
9056
9518
|
stdio: "inherit"
|
|
9057
9519
|
});
|
|
9058
9520
|
} catch {
|
|
9059
9521
|
logger.warn("Generation failed. Run manually:");
|
|
9060
|
-
console.log(` ${
|
|
9522
|
+
console.log(` ${chalk14.cyan(`harness generate${isGlobal ? " --global" : ""}`)}`);
|
|
9061
9523
|
}
|
|
9062
9524
|
if (!isGlobal) {
|
|
9063
9525
|
runLocalGraphScan();
|
|
@@ -9120,8 +9582,8 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
9120
9582
|
console.log("");
|
|
9121
9583
|
for (const { pkg, current, latest } of outdated) {
|
|
9122
9584
|
const shortName = pkg.replace("@harness-engineering/", "");
|
|
9123
|
-
const currentStr = current ?
|
|
9124
|
-
logger.info(`${shortName}: ${currentStr} \u2192 ${
|
|
9585
|
+
const currentStr = current ? chalk14.dim(`v${current}`) : chalk14.dim("not installed");
|
|
9586
|
+
logger.info(`${shortName}: ${currentStr} \u2192 ${chalk14.green(`v${latest}`)}`);
|
|
9125
9587
|
}
|
|
9126
9588
|
console.log("");
|
|
9127
9589
|
}
|
|
@@ -9131,13 +9593,13 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
9131
9593
|
}
|
|
9132
9594
|
try {
|
|
9133
9595
|
logger.info("Updating packages...");
|
|
9134
|
-
|
|
9596
|
+
execFileSync7(pm, ["install", "-g", ...installPkgs], { stdio: "inherit", timeout: 12e4 });
|
|
9135
9597
|
console.log("");
|
|
9136
9598
|
logger.success("Update complete");
|
|
9137
9599
|
} catch {
|
|
9138
9600
|
console.log("");
|
|
9139
9601
|
logger.error("Update failed. You can try manually:");
|
|
9140
|
-
console.log(` ${
|
|
9602
|
+
console.log(` ${chalk14.cyan(installCmd)}`);
|
|
9141
9603
|
process.exit(ExitCode.ERROR);
|
|
9142
9604
|
}
|
|
9143
9605
|
refreshHooks();
|
|
@@ -9146,7 +9608,7 @@ async function runUpdateAction(opts, globalOpts) {
|
|
|
9146
9608
|
process.exit(ExitCode.SUCCESS);
|
|
9147
9609
|
}
|
|
9148
9610
|
function createUpdateCommand() {
|
|
9149
|
-
return new
|
|
9611
|
+
return new Command83("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(
|
|
9150
9612
|
"--regenerate",
|
|
9151
9613
|
"Only regenerate slash commands and agent definitions (skip package updates)"
|
|
9152
9614
|
).action(async (opts, cmd) => {
|
|
@@ -9156,9 +9618,9 @@ function createUpdateCommand() {
|
|
|
9156
9618
|
}
|
|
9157
9619
|
|
|
9158
9620
|
// src/commands/usage.ts
|
|
9159
|
-
import { Command as
|
|
9621
|
+
import { Command as Command84 } from "commander";
|
|
9160
9622
|
async function loadAndPriceRecords(cwd, includeClaudeSessions = false) {
|
|
9161
|
-
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-
|
|
9623
|
+
const { readCostRecords, loadPricingData, calculateCost, parseCCRecords } = await import("./dist-46VOJIG4.js");
|
|
9162
9624
|
const records = readCostRecords(cwd);
|
|
9163
9625
|
if (includeClaudeSessions) {
|
|
9164
9626
|
const ccRecords = parseCCRecords();
|
|
@@ -9251,7 +9713,7 @@ function registerDailyCommand(usage) {
|
|
|
9251
9713
|
else logger.info("No usage data found. Run some harness sessions first.");
|
|
9252
9714
|
return;
|
|
9253
9715
|
}
|
|
9254
|
-
const { aggregateByDay } = await import("./dist-
|
|
9716
|
+
const { aggregateByDay } = await import("./dist-46VOJIG4.js");
|
|
9255
9717
|
const limited = aggregateByDay(records).slice(0, days);
|
|
9256
9718
|
if (globalOpts.json) {
|
|
9257
9719
|
console.log(
|
|
@@ -9280,7 +9742,7 @@ function registerSessionsCommand(usage) {
|
|
|
9280
9742
|
}
|
|
9281
9743
|
return;
|
|
9282
9744
|
}
|
|
9283
|
-
const { aggregateBySession } = await import("./dist-
|
|
9745
|
+
const { aggregateBySession } = await import("./dist-46VOJIG4.js");
|
|
9284
9746
|
const sessionData = aggregateBySession(records);
|
|
9285
9747
|
const limited = sessionData.slice(0, limit);
|
|
9286
9748
|
if (globalOpts.json) {
|
|
@@ -9360,7 +9822,7 @@ function registerSessionCommand(usage) {
|
|
|
9360
9822
|
cwd,
|
|
9361
9823
|
globalOpts.includeClaudeSessions
|
|
9362
9824
|
);
|
|
9363
|
-
const { aggregateBySession } = await import("./dist-
|
|
9825
|
+
const { aggregateBySession } = await import("./dist-46VOJIG4.js");
|
|
9364
9826
|
const sessionData = aggregateBySession(records);
|
|
9365
9827
|
const match = sessionData.find((s) => s.sessionId === id);
|
|
9366
9828
|
if (!match) {
|
|
@@ -9395,7 +9857,7 @@ function registerLatestCommand(usage) {
|
|
|
9395
9857
|
}
|
|
9396
9858
|
return;
|
|
9397
9859
|
}
|
|
9398
|
-
const { aggregateBySession } = await import("./dist-
|
|
9860
|
+
const { aggregateBySession } = await import("./dist-46VOJIG4.js");
|
|
9399
9861
|
const sessionData = aggregateBySession(records);
|
|
9400
9862
|
const latest = sessionData[0];
|
|
9401
9863
|
if (!latest) {
|
|
@@ -9421,7 +9883,7 @@ function registerLatestCommand(usage) {
|
|
|
9421
9883
|
});
|
|
9422
9884
|
}
|
|
9423
9885
|
function createUsageCommand() {
|
|
9424
|
-
const usage = new
|
|
9886
|
+
const usage = new Command84("usage").description("Token usage and cost tracking");
|
|
9425
9887
|
usage.option(
|
|
9426
9888
|
"--include-claude-sessions",
|
|
9427
9889
|
"Include Claude Code session data from ~/.claude/projects/"
|
|
@@ -9436,15 +9898,15 @@ function createUsageCommand() {
|
|
|
9436
9898
|
// src/commands/validate.ts
|
|
9437
9899
|
init_dist();
|
|
9438
9900
|
init_dist();
|
|
9439
|
-
import { Command as
|
|
9440
|
-
import * as
|
|
9901
|
+
import { Command as Command85 } from "commander";
|
|
9902
|
+
import * as path64 from "path";
|
|
9441
9903
|
async function runValidate(options) {
|
|
9442
9904
|
const configResult = resolveConfig(options.configPath);
|
|
9443
9905
|
if (!configResult.ok) {
|
|
9444
9906
|
return configResult;
|
|
9445
9907
|
}
|
|
9446
9908
|
const config = configResult.value;
|
|
9447
|
-
const cwd = options.cwd ?? (options.configPath ?
|
|
9909
|
+
const cwd = options.cwd ?? (options.configPath ? path64.dirname(path64.resolve(options.configPath)) : process.cwd());
|
|
9448
9910
|
const result = {
|
|
9449
9911
|
valid: true,
|
|
9450
9912
|
checks: {
|
|
@@ -9454,7 +9916,7 @@ async function runValidate(options) {
|
|
|
9454
9916
|
},
|
|
9455
9917
|
issues: []
|
|
9456
9918
|
};
|
|
9457
|
-
const agentsMapPath =
|
|
9919
|
+
const agentsMapPath = path64.resolve(cwd, config.agentsMapPath);
|
|
9458
9920
|
const agentsResult = await validateAgentsMap(agentsMapPath);
|
|
9459
9921
|
if (agentsResult.ok) {
|
|
9460
9922
|
result.checks.agentsMap = true;
|
|
@@ -9519,11 +9981,11 @@ function resolveValidateMode(globalOpts) {
|
|
|
9519
9981
|
return OutputMode.TEXT;
|
|
9520
9982
|
}
|
|
9521
9983
|
async function printCrossCheckWarnings(mode) {
|
|
9522
|
-
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-
|
|
9984
|
+
const { runCrossCheck: runCrossCheck2 } = await import("./validate-cross-check-LUQYIA46.js");
|
|
9523
9985
|
const cwd = process.cwd();
|
|
9524
9986
|
const crossResult = await runCrossCheck2({
|
|
9525
|
-
specsDir:
|
|
9526
|
-
plansDir:
|
|
9987
|
+
specsDir: path64.join(cwd, "docs", "specs"),
|
|
9988
|
+
plansDir: path64.join(cwd, "docs", "plans"),
|
|
9527
9989
|
projectPath: cwd
|
|
9528
9990
|
});
|
|
9529
9991
|
if (!crossResult.ok || crossResult.value.warnings === 0) return;
|
|
@@ -9535,7 +9997,7 @@ async function printCrossCheckWarnings(mode) {
|
|
|
9535
9997
|
${crossResult.value.warnings} warnings`);
|
|
9536
9998
|
}
|
|
9537
9999
|
function createValidateCommand3() {
|
|
9538
|
-
const command = new
|
|
10000
|
+
const command = new Command85("validate").description("Run all validation checks").option("--cross-check", "Run cross-artifact consistency validation").option(
|
|
9539
10001
|
"--agent-configs",
|
|
9540
10002
|
"Validate agent configs (CLAUDE.md, hooks, skills) via agnix or built-in fallback rules"
|
|
9541
10003
|
).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()));
|
|
@@ -9585,8 +10047,8 @@ Agent configs checked via ${engineLabel}${note}`);
|
|
|
9585
10047
|
// src/commands/_registry.ts
|
|
9586
10048
|
var commandCreators = [
|
|
9587
10049
|
createAddCommand,
|
|
9588
|
-
createAdviseSkillsCommand,
|
|
9589
10050
|
createAdoptionCommand,
|
|
10051
|
+
createAdviseSkillsCommand,
|
|
9590
10052
|
createAgentCommand,
|
|
9591
10053
|
createAuditProtectedCommand,
|
|
9592
10054
|
createBlueprintCommand,
|
|
@@ -9618,6 +10080,7 @@ var commandCreators = [
|
|
|
9618
10080
|
createLearningsCommand,
|
|
9619
10081
|
createLinterCommand,
|
|
9620
10082
|
createMcpCommand,
|
|
10083
|
+
createMigrateCommand,
|
|
9621
10084
|
createOrchestratorCommand,
|
|
9622
10085
|
createPerfCommand,
|
|
9623
10086
|
createPersonaCommand,
|
|
@@ -9646,7 +10109,7 @@ var commandCreators = [
|
|
|
9646
10109
|
|
|
9647
10110
|
// src/index.ts
|
|
9648
10111
|
function createProgram() {
|
|
9649
|
-
const program = new
|
|
10112
|
+
const program = new Command86();
|
|
9650
10113
|
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");
|
|
9651
10114
|
for (const creator of commandCreators) {
|
|
9652
10115
|
program.addCommand(creator());
|