@harness-engineering/cli 3.0.1 → 4.0.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/personas/harness-pm.yaml +33 -0
- package/dist/agents/skills/claude-code/acceptance-eval/SKILL.md +92 -0
- package/dist/agents/skills/claude-code/acceptance-eval/skill.yaml +54 -0
- package/dist/agents/skills/claude-code/harness-brainstorming/SKILL.md +46 -38
- package/dist/agents/skills/claude-code/harness-brainstorming/skill.yaml +1 -0
- package/dist/agents/skills/claude-code/harness-code-review/SKILL.md +10 -11
- package/dist/agents/skills/claude-code/harness-execution/SKILL.md +59 -26
- package/dist/agents/skills/claude-code/harness-integration/SKILL.md +21 -19
- package/dist/agents/skills/claude-code/harness-maintenance-pipeline/SKILL.md +173 -0
- package/dist/agents/skills/claude-code/harness-maintenance-pipeline/skill.yaml +43 -0
- package/dist/agents/skills/claude-code/harness-onboarding/SKILL.md +2 -0
- package/dist/agents/skills/claude-code/harness-planning/SKILL.md +17 -40
- package/dist/agents/skills/claude-code/harness-pulse/SKILL.md +1 -1
- package/dist/agents/skills/claude-code/harness-roadmap/SKILL.md +17 -0
- package/dist/agents/skills/claude-code/harness-roadmap-pilot/SKILL.md +40 -43
- package/dist/agents/skills/claude-code/harness-verification/SKILL.md +9 -12
- package/dist/agents/skills/claude-code/initialize-harness-project/SKILL.md +112 -126
- package/dist/agents/skills/claude-code/outcome-eval/SKILL.md +1 -0
- package/dist/agents/skills/codex/acceptance-eval/SKILL.md +92 -0
- package/dist/agents/skills/codex/acceptance-eval/skill.yaml +54 -0
- package/dist/agents/skills/codex/harness-brainstorming/SKILL.md +46 -38
- package/dist/agents/skills/codex/harness-brainstorming/skill.yaml +1 -0
- package/dist/agents/skills/codex/harness-code-review/SKILL.md +10 -11
- package/dist/agents/skills/codex/harness-execution/SKILL.md +59 -26
- package/dist/agents/skills/codex/harness-integration/SKILL.md +21 -19
- package/dist/agents/skills/codex/harness-maintenance-pipeline/SKILL.md +173 -0
- package/dist/agents/skills/codex/harness-maintenance-pipeline/skill.yaml +43 -0
- package/dist/agents/skills/codex/harness-onboarding/SKILL.md +2 -0
- package/dist/agents/skills/codex/harness-planning/SKILL.md +17 -40
- package/dist/agents/skills/codex/harness-pulse/SKILL.md +1 -1
- package/dist/agents/skills/codex/harness-roadmap/SKILL.md +17 -0
- package/dist/agents/skills/codex/harness-roadmap-pilot/SKILL.md +40 -43
- package/dist/agents/skills/codex/harness-verification/SKILL.md +9 -12
- package/dist/agents/skills/codex/initialize-harness-project/SKILL.md +112 -126
- package/dist/agents/skills/codex/outcome-eval/SKILL.md +1 -0
- package/dist/agents/skills/cursor/acceptance-eval/SKILL.md +92 -0
- package/dist/agents/skills/cursor/acceptance-eval/skill.yaml +54 -0
- package/dist/agents/skills/cursor/harness-brainstorming/SKILL.md +46 -38
- package/dist/agents/skills/cursor/harness-brainstorming/skill.yaml +1 -0
- package/dist/agents/skills/cursor/harness-code-review/SKILL.md +10 -11
- package/dist/agents/skills/cursor/harness-execution/SKILL.md +59 -26
- package/dist/agents/skills/cursor/harness-integration/SKILL.md +21 -19
- package/dist/agents/skills/cursor/harness-maintenance-pipeline/SKILL.md +173 -0
- package/dist/agents/skills/cursor/harness-maintenance-pipeline/skill.yaml +43 -0
- package/dist/agents/skills/cursor/harness-onboarding/SKILL.md +2 -0
- package/dist/agents/skills/cursor/harness-planning/SKILL.md +17 -40
- package/dist/agents/skills/cursor/harness-pulse/SKILL.md +1 -1
- package/dist/agents/skills/cursor/harness-roadmap/SKILL.md +17 -0
- package/dist/agents/skills/cursor/harness-roadmap-pilot/SKILL.md +40 -43
- package/dist/agents/skills/cursor/harness-verification/SKILL.md +9 -12
- package/dist/agents/skills/cursor/initialize-harness-project/SKILL.md +112 -126
- package/dist/agents/skills/cursor/outcome-eval/SKILL.md +1 -0
- package/dist/agents/skills/gemini-cli/acceptance-eval/SKILL.md +92 -0
- package/dist/agents/skills/gemini-cli/acceptance-eval/skill.yaml +54 -0
- package/dist/agents/skills/gemini-cli/harness-brainstorming/SKILL.md +46 -38
- package/dist/agents/skills/gemini-cli/harness-brainstorming/skill.yaml +1 -0
- package/dist/agents/skills/gemini-cli/harness-code-review/SKILL.md +10 -11
- package/dist/agents/skills/gemini-cli/harness-execution/SKILL.md +59 -26
- package/dist/agents/skills/gemini-cli/harness-integration/SKILL.md +21 -19
- package/dist/agents/skills/gemini-cli/harness-maintenance-pipeline/SKILL.md +173 -0
- package/dist/agents/skills/gemini-cli/harness-maintenance-pipeline/skill.yaml +43 -0
- package/dist/agents/skills/gemini-cli/harness-onboarding/SKILL.md +2 -0
- package/dist/agents/skills/gemini-cli/harness-planning/SKILL.md +17 -40
- package/dist/agents/skills/gemini-cli/harness-pulse/SKILL.md +1 -1
- package/dist/agents/skills/gemini-cli/harness-roadmap/SKILL.md +17 -0
- package/dist/agents/skills/gemini-cli/harness-roadmap-pilot/SKILL.md +40 -43
- package/dist/agents/skills/gemini-cli/harness-verification/SKILL.md +9 -12
- package/dist/agents/skills/gemini-cli/initialize-harness-project/SKILL.md +112 -126
- package/dist/agents/skills/gemini-cli/outcome-eval/SKILL.md +1 -0
- package/dist/agents/skills/tests/interaction-channel.test.ts +64 -0
- package/dist/{agents-md-XOJE5ETA.js → agents-md-7OSNWU52.js} +2 -2
- package/dist/{architecture-JEZPJ725.js → architecture-2XYGJGT3.js} +3 -3
- package/dist/{assess-project-RHXOQNMM.js → assess-project-3KPP2D6M.js} +1 -1
- package/dist/bin/harness-mcp.js +16 -15
- package/dist/bin/harness.js +27 -25
- package/dist/{check-phase-gate-GNDOMS35.js → check-phase-gate-46U5AAXE.js} +4 -3
- package/dist/{chunk-MGUPSE6D.js → chunk-363V6GPR.js} +5 -5
- package/dist/{chunk-HEIMJJI4.js → chunk-6NCREWWM.js} +2 -2
- package/dist/{chunk-XFU6SB2Q.js → chunk-6YXCZV33.js} +2171 -924
- package/dist/{chunk-HWTUUQOW.js → chunk-AK67S3O4.js} +3 -3
- package/dist/{chunk-BYVT5LVO.js → chunk-BV45354Q.js} +3 -3
- package/dist/{chunk-WWEVS7RO.js → chunk-ERILXQBP.js} +1181 -486
- package/dist/{chunk-UX3PQKVZ.js → chunk-FZ4Q73HA.js} +1 -1
- package/dist/{chunk-Z4AWEIBY.js → chunk-GXCPDAS5.js} +3 -3
- package/dist/{chunk-XX4OLNWQ.js → chunk-HMW3C5DP.js} +2 -2
- package/dist/{chunk-PC2R5RUJ.js → chunk-HXMAIVV2.js} +1 -1
- package/dist/{chunk-QBC7DI4Q.js → chunk-IYWT3JJW.js} +1 -1
- package/dist/{chunk-N24HCQA4.js → chunk-KLPX55TI.js} +3015 -1138
- package/dist/{chunk-527G27VI.js → chunk-MSBJ445U.js} +4 -4
- package/dist/{chunk-NWLGL3IR.js → chunk-MYVTYFQD.js} +2 -2
- package/dist/{chunk-KAJOS3BA.js → chunk-PHX45JYN.js} +5 -9
- package/dist/{chunk-6WQCLCBO.js → chunk-S2RQPPL6.js} +1 -1
- package/dist/chunk-SPE7P4GL.js +61 -0
- package/dist/{chunk-YQSYCBAH.js → chunk-TTWNGOBT.js} +6 -6
- package/dist/{chunk-6B6UN6SG.js → chunk-UTJJJDNN.js} +11 -1
- package/dist/{chunk-WWACIIBA.js → chunk-V3ZQUCRH.js} +1 -1
- package/dist/{chunk-GSP2XIVS.js → chunk-VTU3QOJT.js} +48 -4
- package/dist/{chunk-3ISHDWO7.js → chunk-VTUINXWR.js} +1 -1
- package/dist/{chunk-27AJKSQY.js → chunk-WBVCN35T.js} +1 -1
- package/dist/{chunk-ONCPJGHY.js → chunk-WL3O2PKJ.js} +6 -6
- package/dist/{chunk-GGKRA7A7.js → chunk-XFHZ7GCI.js} +1 -1
- package/dist/chunk-Y6SUQC32.js +9 -0
- package/dist/{chunk-DE5U6KOL.js → chunk-ZEYAOYJL.js} +1 -1
- package/dist/{ci-workflow-ORZ4F45F.js → ci-workflow-XJN4UO3Q.js} +2 -2
- package/dist/{dist-LHINSVK4.js → dist-IPVFYGY2.js} +85 -15
- package/dist/{docs-AU3DXFFO.js → docs-RSFZTNPG.js} +3 -3
- package/dist/{engine-6GPWOYQH.js → engine-DJIHY4JT.js} +2 -2
- package/dist/{entropy-VLKDRMRT.js → entropy-EAEKJKRM.js} +2 -2
- package/dist/{feedback-YXYY44ZC.js → feedback-JT5X3DBC.js} +1 -1
- package/dist/{generate-agent-definitions-XIHQPKG3.js → generate-agent-definitions-CC6KED3E.js} +4 -4
- package/dist/hooks/adoption-tracker.js +6 -5
- package/dist/hooks/format-check.js +202 -0
- package/dist/hooks/protect-config.js +15 -5
- package/dist/hooks/quality-warner.js +32 -0
- package/dist/hooks/strict-quality-gate.js +48 -0
- package/dist/{index-builder-3AOQ3ZII.js → index-builder-52IJJHJC.js} +2 -2
- package/dist/index.d.ts +8 -8
- package/dist/index.js +27 -25
- package/dist/{loader-YAN56MT3.js → loader-UWNVTYBY.js} +2 -2
- package/dist/{mcp-RK3SFVYB.js → mcp-TCLNK7HG.js} +18 -17
- package/dist/{performance-64DPTLLQ.js → performance-R6O3N3UB.js} +3 -3
- package/dist/{review-pipeline-NENG6LW7.js → review-pipeline-MOOWXWSQ.js} +2 -2
- package/dist/{runtime-FFIIRT2T.js → runtime-RIWQUKBQ.js} +2 -2
- package/dist/{security-3PIBN35B.js → security-U76C54W6.js} +1 -1
- package/dist/{skill-executor-MOCUIAYS.js → skill-executor-BGUCAVBU.js} +2 -2
- package/dist/state-events-F44CUUZG.js +25 -0
- package/dist/{validate-7IAF2ES3.js → validate-IUAV7MOU.js} +3 -3
- package/dist/{validate-cross-check-QPSAOZKD.js → validate-cross-check-M2ZM6RFY.js} +2 -2
- package/package.json +7 -7
- package/dist/hooks/quality-gate.js +0 -131
|
@@ -1,22 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
analyzeDiffDefinition,
|
|
3
|
+
createSelfReviewDefinition,
|
|
4
|
+
handleAnalyzeDiff,
|
|
5
|
+
handleCreateSelfReview,
|
|
6
|
+
handleRequestPeerReview,
|
|
7
|
+
requestPeerReviewDefinition
|
|
8
|
+
} from "./chunk-BV45354Q.js";
|
|
1
9
|
import {
|
|
2
10
|
getSecurityTrendsDefinition,
|
|
3
11
|
handleGetSecurityTrends,
|
|
4
12
|
handleRunSecurityScan,
|
|
5
13
|
runSecurityScanDefinition
|
|
6
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-6NCREWWM.js";
|
|
7
15
|
import {
|
|
8
16
|
handleRunCodeReview,
|
|
9
17
|
runCodeReviewDefinition,
|
|
10
18
|
sortFindingsBySeverity
|
|
11
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-HMW3C5DP.js";
|
|
12
20
|
import {
|
|
13
21
|
assessProjectDefinition,
|
|
14
22
|
handleAssessProject
|
|
15
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-TTWNGOBT.js";
|
|
24
|
+
import {
|
|
25
|
+
checkDocsDefinition,
|
|
26
|
+
handleCheckDocs
|
|
27
|
+
} from "./chunk-363V6GPR.js";
|
|
16
28
|
import {
|
|
17
29
|
detectEntropyDefinition,
|
|
18
30
|
handleDetectEntropy
|
|
19
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-GXCPDAS5.js";
|
|
20
32
|
import {
|
|
21
33
|
checkPerformanceDefinition,
|
|
22
34
|
getCriticalPathsDefinition,
|
|
@@ -26,18 +38,10 @@ import {
|
|
|
26
38
|
handleGetPerfBaselines,
|
|
27
39
|
handleUpdatePerfBaselines,
|
|
28
40
|
updatePerfBaselinesDefinition
|
|
29
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-WL3O2PKJ.js";
|
|
30
42
|
import {
|
|
31
43
|
getBusinessKnowledgeResource
|
|
32
44
|
} from "./chunk-IOW3MW2K.js";
|
|
33
|
-
import {
|
|
34
|
-
analyzeDiffDefinition,
|
|
35
|
-
createSelfReviewDefinition,
|
|
36
|
-
handleAnalyzeDiff,
|
|
37
|
-
handleCreateSelfReview,
|
|
38
|
-
handleRequestPeerReview,
|
|
39
|
-
requestPeerReviewDefinition
|
|
40
|
-
} from "./chunk-BYVT5LVO.js";
|
|
41
45
|
import {
|
|
42
46
|
GENERATED_HEADER_CLAUDE,
|
|
43
47
|
GENERATED_HEADER_CODEX,
|
|
@@ -53,27 +57,23 @@ import {
|
|
|
53
57
|
import {
|
|
54
58
|
handleValidateProject,
|
|
55
59
|
validateToolDefinition
|
|
56
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-AK67S3O4.js";
|
|
57
61
|
import {
|
|
58
62
|
checkDependenciesDefinition,
|
|
59
63
|
handleCheckDependencies
|
|
60
|
-
} from "./chunk-
|
|
61
|
-
import {
|
|
62
|
-
resolveProjectConfig
|
|
63
|
-
} from "./chunk-DE5U6KOL.js";
|
|
64
|
-
import {
|
|
65
|
-
checkDocsDefinition,
|
|
66
|
-
handleCheckDocs
|
|
67
|
-
} from "./chunk-MGUPSE6D.js";
|
|
64
|
+
} from "./chunk-MSBJ445U.js";
|
|
68
65
|
import {
|
|
69
66
|
resultToMcpResponse
|
|
70
67
|
} from "./chunk-RC3OI5NK.js";
|
|
68
|
+
import {
|
|
69
|
+
resolveProjectConfig
|
|
70
|
+
} from "./chunk-ZEYAOYJL.js";
|
|
71
71
|
import {
|
|
72
72
|
sanitizePath
|
|
73
73
|
} from "./chunk-W6Y7ZW3Y.js";
|
|
74
74
|
import {
|
|
75
75
|
loadOrRebuildIndex
|
|
76
|
-
} from "./chunk-
|
|
76
|
+
} from "./chunk-WBVCN35T.js";
|
|
77
77
|
import {
|
|
78
78
|
resolveCommunitySkillsDir,
|
|
79
79
|
resolveGlobalCommunitySkillsDir,
|
|
@@ -82,15 +82,18 @@ import {
|
|
|
82
82
|
resolveProjectSkillsDir,
|
|
83
83
|
resolveSkillsDir,
|
|
84
84
|
resolveTemplatesDir
|
|
85
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-UTJJJDNN.js";
|
|
86
86
|
import {
|
|
87
87
|
SkillMetadataSchema
|
|
88
88
|
} from "./chunk-HIK6OEUF.js";
|
|
89
|
+
import {
|
|
90
|
+
findFiles
|
|
91
|
+
} from "./chunk-Y6SUQC32.js";
|
|
89
92
|
import {
|
|
90
93
|
findConfigFile,
|
|
91
94
|
loadConfig,
|
|
92
95
|
resolveConfig
|
|
93
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-XFHZ7GCI.js";
|
|
94
97
|
import {
|
|
95
98
|
logger
|
|
96
99
|
} from "./chunk-EBJQ6N4M.js";
|
|
@@ -104,8 +107,10 @@ import {
|
|
|
104
107
|
ConflictError,
|
|
105
108
|
DEFAULT_TOKEN_BUDGET,
|
|
106
109
|
DESTRUCTIVE_BASH,
|
|
110
|
+
SIGNAL_CATEGORY_MAP,
|
|
107
111
|
StructuralStrategy,
|
|
108
112
|
TruncationStrategy,
|
|
113
|
+
applyRoadmapDiff,
|
|
109
114
|
checkTaint,
|
|
110
115
|
computeLoadPlan,
|
|
111
116
|
createTrackerClient,
|
|
@@ -118,10 +123,13 @@ import {
|
|
|
118
123
|
loadTrackerClientConfigFromProject,
|
|
119
124
|
loadTrackerSyncConfig,
|
|
120
125
|
paginate,
|
|
126
|
+
reconcilePassed,
|
|
127
|
+
resolveRoadmapStore,
|
|
128
|
+
roadmapSourceExists,
|
|
121
129
|
scanForInjection,
|
|
122
130
|
serializeEnvelope,
|
|
123
131
|
writeTaint
|
|
124
|
-
} from "./chunk-
|
|
132
|
+
} from "./chunk-KLPX55TI.js";
|
|
125
133
|
import {
|
|
126
134
|
Err,
|
|
127
135
|
INDEXED_FILE_KINDS,
|
|
@@ -317,7 +325,6 @@ release-readiness.json
|
|
|
317
325
|
skills-index.json
|
|
318
326
|
stack-profile.json
|
|
319
327
|
metrics/
|
|
320
|
-
events.jsonl
|
|
321
328
|
.install-id
|
|
322
329
|
telemetry.json
|
|
323
330
|
.telemetry-notice-shown
|
|
@@ -738,7 +745,7 @@ async function handleInsightsSummary(input) {
|
|
|
738
745
|
const skip = Array.isArray(input.skip) ? input.skip.filter(
|
|
739
746
|
(k) => typeof k === "string" && INSIGHTS_KEYS.includes(k)
|
|
740
747
|
) : void 0;
|
|
741
|
-
const { composeInsights } = await import("./dist-
|
|
748
|
+
const { composeInsights } = await import("./dist-IPVFYGY2.js");
|
|
742
749
|
const report = await composeInsights(projectPath, {
|
|
743
750
|
...skip && skip.length > 0 && { skip }
|
|
744
751
|
});
|
|
@@ -914,7 +921,7 @@ ${skippedMsg}`
|
|
|
914
921
|
async function handleInitProject(input) {
|
|
915
922
|
const i = input;
|
|
916
923
|
try {
|
|
917
|
-
const { TemplateEngine } = await import("./engine-
|
|
924
|
+
const { TemplateEngine } = await import("./engine-DJIHY4JT.js");
|
|
918
925
|
const engine = new TemplateEngine(resolveTemplatesDir());
|
|
919
926
|
const safePath = sanitizePath(i.path);
|
|
920
927
|
const detected = tryDetectFramework(engine, safePath, i);
|
|
@@ -936,7 +943,7 @@ var listPersonasDefinition = {
|
|
|
936
943
|
inputSchema: { type: "object", properties: {} }
|
|
937
944
|
};
|
|
938
945
|
async function handleListPersonas() {
|
|
939
|
-
const { listPersonas } = await import("./loader-
|
|
946
|
+
const { listPersonas } = await import("./loader-UWNVTYBY.js");
|
|
940
947
|
const result = listPersonas(resolvePersonasDir());
|
|
941
948
|
return resultToMcpResponse(result);
|
|
942
949
|
}
|
|
@@ -960,10 +967,10 @@ async function handleGeneratePersonaArtifacts(input) {
|
|
|
960
967
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.name)) {
|
|
961
968
|
return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.name}`)));
|
|
962
969
|
}
|
|
963
|
-
const { loadPersona } = await import("./loader-
|
|
964
|
-
const { generateRuntime } = await import("./runtime-
|
|
965
|
-
const { generateAgentsMd } = await import("./agents-md-
|
|
966
|
-
const { generateCIWorkflow } = await import("./ci-workflow-
|
|
970
|
+
const { loadPersona } = await import("./loader-UWNVTYBY.js");
|
|
971
|
+
const { generateRuntime } = await import("./runtime-RIWQUKBQ.js");
|
|
972
|
+
const { generateAgentsMd } = await import("./agents-md-7OSNWU52.js");
|
|
973
|
+
const { generateCIWorkflow } = await import("./ci-workflow-XJN4UO3Q.js");
|
|
967
974
|
const personasDir = resolvePersonasDir();
|
|
968
975
|
const filePath = path4.join(personasDir, `${input.name}.yaml`);
|
|
969
976
|
if (!filePath.startsWith(personasDir)) {
|
|
@@ -1018,9 +1025,9 @@ async function handleRunPersona(input) {
|
|
|
1018
1025
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(input.persona)) {
|
|
1019
1026
|
return resultToMcpResponse(Err(new Error(`Invalid persona name: ${input.persona}`)));
|
|
1020
1027
|
}
|
|
1021
|
-
const { loadPersona } = await import("./loader-
|
|
1028
|
+
const { loadPersona } = await import("./loader-UWNVTYBY.js");
|
|
1022
1029
|
const { runPersona } = await import("./runner-AAEF2TXY.js");
|
|
1023
|
-
const { executeSkill } = await import("./skill-executor-
|
|
1030
|
+
const { executeSkill } = await import("./skill-executor-BGUCAVBU.js");
|
|
1024
1031
|
const personasDir = resolvePersonasDir();
|
|
1025
1032
|
const filePath = path4.join(personasDir, `${input.persona}.yaml`);
|
|
1026
1033
|
if (!filePath.startsWith(personasDir)) {
|
|
@@ -1460,11 +1467,16 @@ function loadOrGenerateProfile(projectRoot) {
|
|
|
1460
1467
|
return profile;
|
|
1461
1468
|
}
|
|
1462
1469
|
|
|
1463
|
-
// src/mcp/tools/
|
|
1470
|
+
// src/mcp/tools/skill-telemetry.ts
|
|
1471
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1472
|
+
import { join as join4 } from "path";
|
|
1473
|
+
var SKILL_EVENTS_FILE = join4(".harness", "metrics", "skill-events.jsonl");
|
|
1464
1474
|
async function emitSkillEvent(projectPath, event) {
|
|
1465
1475
|
try {
|
|
1466
|
-
const
|
|
1467
|
-
|
|
1476
|
+
const metricsDir = join4(projectPath, ".harness", "metrics");
|
|
1477
|
+
mkdirSync2(metricsDir, { recursive: true });
|
|
1478
|
+
const record = { ...event, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1479
|
+
appendFileSync2(join4(metricsDir, "skill-events.jsonl"), JSON.stringify(record) + "\n");
|
|
1468
1480
|
} catch {
|
|
1469
1481
|
}
|
|
1470
1482
|
}
|
|
@@ -1536,9 +1548,10 @@ async function handleRunSkill(input) {
|
|
|
1536
1548
|
}
|
|
1537
1549
|
if (input.path) {
|
|
1538
1550
|
const projectPath = sanitizePath(input.path);
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
1551
|
+
const { readHarnessState, isEmptyHarnessState } = await import("./state-events-F44CUUZG.js");
|
|
1552
|
+
const stateResult = await readHarnessState(projectPath);
|
|
1553
|
+
if (stateResult.ok && !isEmptyHarnessState(stateResult.value)) {
|
|
1554
|
+
const stateContent = JSON.stringify(stateResult.value, null, 2);
|
|
1542
1555
|
content += `
|
|
1543
1556
|
|
|
1544
1557
|
---
|
|
@@ -1733,25 +1746,25 @@ import * as fs10 from "fs";
|
|
|
1733
1746
|
import * as path11 from "path";
|
|
1734
1747
|
async function autoSyncRoadmap(projectPath) {
|
|
1735
1748
|
try {
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
const
|
|
1739
|
-
const
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
const
|
|
1743
|
-
const syncResult = syncRoadmap({ projectPath, roadmap });
|
|
1749
|
+
if (!roadmapSourceExists(projectPath)) return;
|
|
1750
|
+
const { syncRoadmap, applySyncChanges } = await import("./dist-IPVFYGY2.js");
|
|
1751
|
+
const store = resolveRoadmapStore({ projectRoot: projectPath });
|
|
1752
|
+
const loaded = await store.load();
|
|
1753
|
+
if (!loaded.ok) return;
|
|
1754
|
+
const roadmap = loaded.value;
|
|
1755
|
+
const before = structuredClone(roadmap);
|
|
1756
|
+
const syncResult = await syncRoadmap({ projectPath, roadmap });
|
|
1744
1757
|
if (!syncResult.ok || syncResult.value.length === 0) {
|
|
1745
|
-
await triggerExternalSync(projectPath
|
|
1758
|
+
await triggerExternalSync(projectPath);
|
|
1746
1759
|
return;
|
|
1747
1760
|
}
|
|
1748
1761
|
applySyncChanges(roadmap, syncResult.value);
|
|
1749
|
-
|
|
1750
|
-
await triggerExternalSync(projectPath
|
|
1762
|
+
await applyRoadmapDiff(store, before, roadmap);
|
|
1763
|
+
await triggerExternalSync(projectPath);
|
|
1751
1764
|
} catch {
|
|
1752
1765
|
}
|
|
1753
1766
|
}
|
|
1754
|
-
async function triggerExternalSync(projectPath
|
|
1767
|
+
async function triggerExternalSync(projectPath) {
|
|
1755
1768
|
try {
|
|
1756
1769
|
const trackerConfig = loadTrackerSyncConfig(projectPath);
|
|
1757
1770
|
if (!trackerConfig) return;
|
|
@@ -1765,12 +1778,12 @@ async function triggerExternalSync(projectPath, roadmapFile) {
|
|
|
1765
1778
|
console.warn("[roadmap-sync] GITHUB_TOKEN not found \u2014 external sync skipped");
|
|
1766
1779
|
return;
|
|
1767
1780
|
}
|
|
1768
|
-
const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-
|
|
1781
|
+
const { fullSync, GitHubIssuesSyncAdapter } = await import("./dist-IPVFYGY2.js");
|
|
1769
1782
|
const adapter = new GitHubIssuesSyncAdapter({
|
|
1770
1783
|
token,
|
|
1771
1784
|
config: trackerConfig
|
|
1772
1785
|
});
|
|
1773
|
-
const result = await fullSync(
|
|
1786
|
+
const result = await fullSync(projectPath, adapter, trackerConfig);
|
|
1774
1787
|
if (result.errors.length > 0) {
|
|
1775
1788
|
for (const err2 of result.errors) {
|
|
1776
1789
|
console.error(
|
|
@@ -1817,7 +1830,8 @@ var manageStateDefinition = {
|
|
|
1817
1830
|
"task-start",
|
|
1818
1831
|
"task-complete",
|
|
1819
1832
|
"phase-start",
|
|
1820
|
-
"phase-complete"
|
|
1833
|
+
"phase-complete",
|
|
1834
|
+
"task-transition"
|
|
1821
1835
|
],
|
|
1822
1836
|
description: "Action to perform"
|
|
1823
1837
|
},
|
|
@@ -1856,18 +1870,42 @@ var manageStateDefinition = {
|
|
|
1856
1870
|
type: "string",
|
|
1857
1871
|
enum: ["active", "resolved", "superseded"],
|
|
1858
1872
|
description: "New status for the entry: active, resolved, or superseded (required for update_entry_status)"
|
|
1859
|
-
}
|
|
1873
|
+
},
|
|
1874
|
+
taskId: { type: "string", description: "Task id (required for task-transition)" },
|
|
1875
|
+
toLane: {
|
|
1876
|
+
type: "string",
|
|
1877
|
+
enum: ["planned", "claimed", "in_progress", "in_review", "done", "blocked", "canceled"],
|
|
1878
|
+
description: "Target lane (required for task-transition)"
|
|
1879
|
+
},
|
|
1880
|
+
dependsOn: {
|
|
1881
|
+
type: "array",
|
|
1882
|
+
items: { type: "string" },
|
|
1883
|
+
description: "Dependency task ids; when set, the task is registered before transitioning"
|
|
1884
|
+
},
|
|
1885
|
+
evidence: {
|
|
1886
|
+
type: "array",
|
|
1887
|
+
items: { type: "string" },
|
|
1888
|
+
description: "PR/commit/test refs (required to enter done)"
|
|
1889
|
+
},
|
|
1890
|
+
force: {
|
|
1891
|
+
type: "boolean",
|
|
1892
|
+
description: "Force an off-table transition (requires actor+reason)"
|
|
1893
|
+
},
|
|
1894
|
+
actor: { type: "string", description: "Actor for a forced transition" },
|
|
1895
|
+
reason: { type: "string", description: "Reason for a forced transition" }
|
|
1860
1896
|
},
|
|
1861
1897
|
required: ["path", "action"]
|
|
1862
1898
|
}
|
|
1863
1899
|
};
|
|
1864
1900
|
async function handleShow(projectPath, input) {
|
|
1865
|
-
const {
|
|
1866
|
-
return resultToMcpResponse(
|
|
1901
|
+
const { readHarnessState } = await import("./state-events-F44CUUZG.js");
|
|
1902
|
+
return resultToMcpResponse(
|
|
1903
|
+
await readHarnessState(projectPath, { stream: input.stream, session: input.session })
|
|
1904
|
+
);
|
|
1867
1905
|
}
|
|
1868
1906
|
async function handleLearn(projectPath, input) {
|
|
1869
1907
|
if (!input.learning) return mcpError("Error: learning is required for learn action");
|
|
1870
|
-
const { appendLearning } = await import("./dist-
|
|
1908
|
+
const { appendLearning } = await import("./dist-IPVFYGY2.js");
|
|
1871
1909
|
const result = await appendLearning(
|
|
1872
1910
|
projectPath,
|
|
1873
1911
|
input.learning,
|
|
@@ -1882,7 +1920,7 @@ async function handleLearn(projectPath, input) {
|
|
|
1882
1920
|
async function handleFailure(projectPath, input) {
|
|
1883
1921
|
if (!input.description) return mcpError("Error: description is required for failure action");
|
|
1884
1922
|
if (!input.failureType) return mcpError("Error: failureType is required for failure action");
|
|
1885
|
-
const { appendFailure } = await import("./dist-
|
|
1923
|
+
const { appendFailure } = await import("./dist-IPVFYGY2.js");
|
|
1886
1924
|
const result = await appendFailure(
|
|
1887
1925
|
projectPath,
|
|
1888
1926
|
input.description,
|
|
@@ -1901,19 +1939,22 @@ async function handleFailure(projectPath, input) {
|
|
|
1901
1939
|
return resultToMcpResponse(Ok({ recorded: true }));
|
|
1902
1940
|
}
|
|
1903
1941
|
async function handleArchive(projectPath, input) {
|
|
1904
|
-
const { archiveFailures } = await import("./dist-
|
|
1942
|
+
const { archiveFailures } = await import("./dist-IPVFYGY2.js");
|
|
1905
1943
|
const result = await archiveFailures(projectPath, input.stream, input.session);
|
|
1906
1944
|
if (!result.ok) return resultToMcpResponse(result);
|
|
1907
1945
|
return resultToMcpResponse(Ok({ archived: true }));
|
|
1908
1946
|
}
|
|
1909
1947
|
async function handleReset(projectPath, input) {
|
|
1910
|
-
const {
|
|
1911
|
-
const result = await
|
|
1948
|
+
const { eventSourcing } = await import("./dist-IPVFYGY2.js");
|
|
1949
|
+
const result = await eventSourcing.resetEventLog(projectPath, {
|
|
1950
|
+
stream: input.stream,
|
|
1951
|
+
session: input.session
|
|
1952
|
+
});
|
|
1912
1953
|
if (!result.ok) return resultToMcpResponse(result);
|
|
1913
1954
|
return resultToMcpResponse(Ok({ reset: true }));
|
|
1914
1955
|
}
|
|
1915
1956
|
async function handleGate(projectPath, input) {
|
|
1916
|
-
const { runMechanicalGate } = await import("./dist-
|
|
1957
|
+
const { runMechanicalGate } = await import("./dist-IPVFYGY2.js");
|
|
1917
1958
|
const result = await runMechanicalGate(projectPath);
|
|
1918
1959
|
if (result.ok) {
|
|
1919
1960
|
await emitSkillEvent(projectPath, {
|
|
@@ -1933,7 +1974,7 @@ async function handleGate(projectPath, input) {
|
|
|
1933
1974
|
}
|
|
1934
1975
|
async function handleSaveHandoff(projectPath, input) {
|
|
1935
1976
|
if (!input.handoff) return mcpError("Error: handoff is required for save-handoff action");
|
|
1936
|
-
const { saveHandoff } = await import("./dist-
|
|
1977
|
+
const { saveHandoff } = await import("./dist-IPVFYGY2.js");
|
|
1937
1978
|
const result = await saveHandoff(
|
|
1938
1979
|
projectPath,
|
|
1939
1980
|
input.handoff,
|
|
@@ -1955,7 +1996,7 @@ async function handleSaveHandoff(projectPath, input) {
|
|
|
1955
1996
|
return resultToMcpResponse(Ok({ saved: true }));
|
|
1956
1997
|
}
|
|
1957
1998
|
async function handleLoadHandoff(projectPath, input) {
|
|
1958
|
-
const { loadHandoff } = await import("./dist-
|
|
1999
|
+
const { loadHandoff } = await import("./dist-IPVFYGY2.js");
|
|
1959
2000
|
return resultToMcpResponse(await loadHandoff(projectPath, input.stream, input.session));
|
|
1960
2001
|
}
|
|
1961
2002
|
async function handleAppendEntry(projectPath, input) {
|
|
@@ -1963,7 +2004,7 @@ async function handleAppendEntry(projectPath, input) {
|
|
|
1963
2004
|
if (!input.authorSkill) return mcpError("Error: authorSkill is required for append_entry action");
|
|
1964
2005
|
if (!input.content) return mcpError("Error: content is required for append_entry action");
|
|
1965
2006
|
if (input.session) {
|
|
1966
|
-
const { appendSessionEntry } = await import("./dist-
|
|
2007
|
+
const { appendSessionEntry } = await import("./dist-IPVFYGY2.js");
|
|
1967
2008
|
const result = await appendSessionEntry(
|
|
1968
2009
|
projectPath,
|
|
1969
2010
|
input.session,
|
|
@@ -1976,16 +2017,17 @@ async function handleAppendEntry(projectPath, input) {
|
|
|
1976
2017
|
if (input.section !== "decisions") {
|
|
1977
2018
|
return mcpError("Error: session is required for non-decisions sections");
|
|
1978
2019
|
}
|
|
1979
|
-
const {
|
|
1980
|
-
const
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2020
|
+
const { randomUUID: randomUUID10 } = await import("crypto");
|
|
2021
|
+
const { emitCoreEvent } = await import("./state-events-F44CUUZG.js");
|
|
2022
|
+
const r = await emitCoreEvent(
|
|
2023
|
+
projectPath,
|
|
2024
|
+
{
|
|
2025
|
+
type: "decision_recorded",
|
|
2026
|
+
payload: { id: randomUUID10(), text: input.content, context: input.authorSkill }
|
|
2027
|
+
},
|
|
2028
|
+
{ stream: input.stream }
|
|
2029
|
+
);
|
|
2030
|
+
if (!r.ok) return resultToMcpResponse(r);
|
|
1989
2031
|
return resultToMcpResponse(Ok({ appended: true, target: "global-state" }));
|
|
1990
2032
|
}
|
|
1991
2033
|
async function handleUpdateEntryStatus(projectPath, input) {
|
|
@@ -1994,7 +2036,7 @@ async function handleUpdateEntryStatus(projectPath, input) {
|
|
|
1994
2036
|
if (!input.entryId) return mcpError("Error: entryId is required for update_entry_status action");
|
|
1995
2037
|
if (!input.newStatus)
|
|
1996
2038
|
return mcpError("Error: newStatus is required for update_entry_status action");
|
|
1997
|
-
const { updateSessionEntryStatus } = await import("./dist-
|
|
2039
|
+
const { updateSessionEntryStatus } = await import("./dist-IPVFYGY2.js");
|
|
1998
2040
|
const result = await updateSessionEntryStatus(
|
|
1999
2041
|
projectPath,
|
|
2000
2042
|
input.session,
|
|
@@ -2002,12 +2044,21 @@ async function handleUpdateEntryStatus(projectPath, input) {
|
|
|
2002
2044
|
input.entryId,
|
|
2003
2045
|
input.newStatus
|
|
2004
2046
|
);
|
|
2047
|
+
if (result.ok && input.section === "decisions" && input.newStatus === "resolved") {
|
|
2048
|
+
try {
|
|
2049
|
+
const { emitApprovalResolved } = await import("./state-events-F44CUUZG.js");
|
|
2050
|
+
await emitApprovalResolved(projectPath, input.entryId, input.newStatus, {
|
|
2051
|
+
stream: input.stream
|
|
2052
|
+
});
|
|
2053
|
+
} catch {
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2005
2056
|
return resultToMcpResponse(result);
|
|
2006
2057
|
}
|
|
2007
2058
|
async function handleReadSection(projectPath, input) {
|
|
2008
2059
|
if (!input.session) return mcpError("Error: session is required for read_section action");
|
|
2009
2060
|
if (!input.section) return mcpError("Error: section is required for read_section action");
|
|
2010
|
-
const { readSessionSection } = await import("./dist-
|
|
2061
|
+
const { readSessionSection } = await import("./dist-IPVFYGY2.js");
|
|
2011
2062
|
const result = await readSessionSection(
|
|
2012
2063
|
projectPath,
|
|
2013
2064
|
input.session,
|
|
@@ -2017,13 +2068,13 @@ async function handleReadSection(projectPath, input) {
|
|
|
2017
2068
|
}
|
|
2018
2069
|
async function handleReadSections(projectPath, input) {
|
|
2019
2070
|
if (!input.session) return mcpError("Error: session is required for read_sections action");
|
|
2020
|
-
const { readSessionSections } = await import("./dist-
|
|
2071
|
+
const { readSessionSections } = await import("./dist-IPVFYGY2.js");
|
|
2021
2072
|
const result = await readSessionSections(projectPath, input.session);
|
|
2022
2073
|
return resultToMcpResponse(result);
|
|
2023
2074
|
}
|
|
2024
2075
|
async function handleArchiveSession(projectPath, input) {
|
|
2025
2076
|
if (!input.session) return mcpError("Error: session is required for archive_session action");
|
|
2026
|
-
const { archiveSession } = await import("./dist-
|
|
2077
|
+
const { archiveSession } = await import("./dist-IPVFYGY2.js");
|
|
2027
2078
|
const { buildArchiveHooks } = await import("@harness-engineering/orchestrator");
|
|
2028
2079
|
const hooks = buildArchiveHooks({ projectPath });
|
|
2029
2080
|
const result = await archiveSession(projectPath, input.session, { hooks });
|
|
@@ -2063,6 +2114,30 @@ async function handlePhaseComplete(projectPath, input) {
|
|
|
2063
2114
|
}
|
|
2064
2115
|
return resultToMcpResponse(Ok({ synced: true, trigger: "phase-complete" }));
|
|
2065
2116
|
}
|
|
2117
|
+
async function handleTaskTransition(projectPath, input) {
|
|
2118
|
+
if (!input.taskId) return mcpError("Error: taskId is required for task-transition action");
|
|
2119
|
+
if (!input.toLane) return mcpError("Error: toLane is required for task-transition action");
|
|
2120
|
+
const { eventSourcing } = await import("./dist-IPVFYGY2.js");
|
|
2121
|
+
const scope = { stream: input.stream, session: input.session };
|
|
2122
|
+
if (input.dependsOn) {
|
|
2123
|
+
const reg = await eventSourcing.registerTask(projectPath, input.taskId, input.dependsOn, scope);
|
|
2124
|
+
if (!reg.ok) return resultToMcpResponse(reg);
|
|
2125
|
+
}
|
|
2126
|
+
const result = await eventSourcing.transitionLane(
|
|
2127
|
+
projectPath,
|
|
2128
|
+
input.taskId,
|
|
2129
|
+
input.toLane,
|
|
2130
|
+
{
|
|
2131
|
+
...input.evidence !== void 0 ? { evidence: input.evidence } : {},
|
|
2132
|
+
...input.force !== void 0 ? { force: input.force } : {},
|
|
2133
|
+
...input.actor !== void 0 ? { actor: input.actor } : {},
|
|
2134
|
+
...input.reason !== void 0 ? { reason: input.reason } : {}
|
|
2135
|
+
},
|
|
2136
|
+
scope
|
|
2137
|
+
);
|
|
2138
|
+
if (!result.ok) return resultToMcpResponse(result);
|
|
2139
|
+
return resultToMcpResponse(Ok({ taskId: input.taskId, lane: input.toLane }));
|
|
2140
|
+
}
|
|
2066
2141
|
var ACTION_HANDLERS = {
|
|
2067
2142
|
show: handleShow,
|
|
2068
2143
|
learn: handleLearn,
|
|
@@ -2080,7 +2155,8 @@ var ACTION_HANDLERS = {
|
|
|
2080
2155
|
"task-start": handleTaskStart,
|
|
2081
2156
|
"task-complete": handleTaskComplete,
|
|
2082
2157
|
"phase-start": handlePhaseStart,
|
|
2083
|
-
"phase-complete": handlePhaseComplete
|
|
2158
|
+
"phase-complete": handlePhaseComplete,
|
|
2159
|
+
"task-transition": handleTaskTransition
|
|
2084
2160
|
};
|
|
2085
2161
|
async function handleManageState(input) {
|
|
2086
2162
|
try {
|
|
@@ -2105,7 +2181,7 @@ var listStreamsDefinition = {
|
|
|
2105
2181
|
};
|
|
2106
2182
|
async function handleListStreams(input) {
|
|
2107
2183
|
try {
|
|
2108
|
-
const { listStreams, loadStreamIndex } = await import("./dist-
|
|
2184
|
+
const { listStreams, loadStreamIndex } = await import("./dist-IPVFYGY2.js");
|
|
2109
2185
|
const projectPath = sanitizePath(input.path);
|
|
2110
2186
|
const indexResult = await loadStreamIndex(projectPath);
|
|
2111
2187
|
const streamsResult = await listStreams(projectPath);
|
|
@@ -2143,7 +2219,7 @@ var checkPhaseGateDefinition = {
|
|
|
2143
2219
|
};
|
|
2144
2220
|
async function handleCheckPhaseGate(input) {
|
|
2145
2221
|
try {
|
|
2146
|
-
const { runCheckPhaseGate } = await import("./check-phase-gate-
|
|
2222
|
+
const { runCheckPhaseGate } = await import("./check-phase-gate-46U5AAXE.js");
|
|
2147
2223
|
const result = await runCheckPhaseGate({ cwd: sanitizePath(input.path) });
|
|
2148
2224
|
if (result.ok) {
|
|
2149
2225
|
return { content: [{ type: "text", text: JSON.stringify(result.value) }] };
|
|
@@ -2199,7 +2275,7 @@ async function handleValidateCrossCheck(input) {
|
|
|
2199
2275
|
};
|
|
2200
2276
|
}
|
|
2201
2277
|
try {
|
|
2202
|
-
const { runCrossCheck } = await import("./validate-cross-check-
|
|
2278
|
+
const { runCrossCheck } = await import("./validate-cross-check-M2ZM6RFY.js");
|
|
2203
2279
|
const specsDir = path12.resolve(projectPath, input.specsDir ?? "docs/specs");
|
|
2204
2280
|
if (!specsDir.startsWith(projectPath)) {
|
|
2205
2281
|
return {
|
|
@@ -2708,11 +2784,11 @@ function resolveOutputDir(platform, opts) {
|
|
|
2708
2784
|
}
|
|
2709
2785
|
async function confirmDeletion(files) {
|
|
2710
2786
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
2711
|
-
return new Promise((
|
|
2787
|
+
return new Promise((resolve11) => {
|
|
2712
2788
|
rl.question(`
|
|
2713
2789
|
Remove ${files.length} orphaned command(s)? (y/N) `, (answer) => {
|
|
2714
2790
|
rl.close();
|
|
2715
|
-
|
|
2791
|
+
resolve11(answer.toLowerCase() === "y");
|
|
2716
2792
|
});
|
|
2717
2793
|
});
|
|
2718
2794
|
}
|
|
@@ -3022,9 +3098,10 @@ async function handleGenerateSlashCommands(input) {
|
|
|
3022
3098
|
// src/mcp/resources/state.ts
|
|
3023
3099
|
async function getStateResource(projectRoot) {
|
|
3024
3100
|
try {
|
|
3025
|
-
const {
|
|
3101
|
+
const { migrateToStreams } = await import("./dist-IPVFYGY2.js");
|
|
3102
|
+
const { readHarnessState } = await import("./state-events-F44CUUZG.js");
|
|
3026
3103
|
await migrateToStreams(projectRoot);
|
|
3027
|
-
const result = await
|
|
3104
|
+
const result = await readHarnessState(projectRoot);
|
|
3028
3105
|
if (result.ok) {
|
|
3029
3106
|
return JSON.stringify(result.value, null, 2);
|
|
3030
3107
|
}
|
|
@@ -3633,8 +3710,8 @@ async function handleIngestSource(input) {
|
|
|
3633
3710
|
const projectPath = sanitizePath(input.path);
|
|
3634
3711
|
const graphDir = path16.join(projectPath, ".harness", "graph");
|
|
3635
3712
|
const { GraphStore, CodeIngestor, TopologicalLinker, KnowledgeIngestor, GitIngestor } = await import("./dist-OWHMNL4W.js");
|
|
3636
|
-
const
|
|
3637
|
-
await
|
|
3713
|
+
const fs53 = await import("fs/promises");
|
|
3714
|
+
await fs53.mkdir(graphDir, { recursive: true });
|
|
3638
3715
|
const store = new GraphStore();
|
|
3639
3716
|
await store.load(graphDir);
|
|
3640
3717
|
const results = [];
|
|
@@ -4042,7 +4119,7 @@ var generateAgentDefinitionsDefinition = {
|
|
|
4042
4119
|
}
|
|
4043
4120
|
};
|
|
4044
4121
|
async function handleGenerateAgentDefinitions(input) {
|
|
4045
|
-
const { generateAgentDefinitions } = await import("./generate-agent-definitions-
|
|
4122
|
+
const { generateAgentDefinitions } = await import("./generate-agent-definitions-CC6KED3E.js");
|
|
4046
4123
|
const platforms = input.platform === "all" || !input.platform ? ["claude-code", "gemini-cli"] : [input.platform];
|
|
4047
4124
|
const results = generateAgentDefinitions({
|
|
4048
4125
|
platforms: [...platforms],
|
|
@@ -4078,7 +4155,7 @@ async function handleManageRoadmapFileLess(input, client) {
|
|
|
4078
4155
|
case "sync":
|
|
4079
4156
|
return handleSync();
|
|
4080
4157
|
case "groom":
|
|
4081
|
-
return err("Error: groom is only supported in file-based roadmap mode
|
|
4158
|
+
return err("Error: groom is only supported in file-based roadmap mode.");
|
|
4082
4159
|
default:
|
|
4083
4160
|
return err(`Error: unknown action`);
|
|
4084
4161
|
}
|
|
@@ -4278,7 +4355,7 @@ function renderRoadmap(features) {
|
|
|
4278
4355
|
// src/mcp/tools/roadmap.ts
|
|
4279
4356
|
var manageRoadmapDefinition = {
|
|
4280
4357
|
name: "manage_roadmap",
|
|
4281
|
-
description: 'Manage the project roadmap: show, add, update, remove, promote, sync, groom features, or query by filter. Reads and writes
|
|
4358
|
+
description: 'Manage the project roadmap: show, add, update, remove, promote, sync, groom features, or query by filter. Reads and writes the project roadmap (sharded or single-file). The "promote" action transitions an existing row toward planned (backlog\u2192planned) and links its spec atomically \u2014 creating a new planned row under the "Intake" lane if the feature does not exist \u2014 returning a structured RoadmapPromoteResult envelope. The "groom" action tidies the roadmap: it demotes unactionable planned rows (no spec & no plan) to backlog and moves completed features into docs/roadmap-archive.md, returning the list of changes.',
|
|
4282
4359
|
inputSchema: {
|
|
4283
4360
|
type: "object",
|
|
4284
4361
|
properties: {
|
|
@@ -4339,22 +4416,12 @@ var manageRoadmapDefinition = {
|
|
|
4339
4416
|
required: ["path", "action"]
|
|
4340
4417
|
}
|
|
4341
4418
|
};
|
|
4342
|
-
function
|
|
4343
|
-
return
|
|
4419
|
+
async function loadRoadmap(projectRoot) {
|
|
4420
|
+
return resolveRoadmapStore({ projectRoot }).load();
|
|
4344
4421
|
}
|
|
4345
|
-
function
|
|
4346
|
-
const
|
|
4347
|
-
|
|
4348
|
-
return fs15.readFileSync(filePath, "utf-8");
|
|
4349
|
-
} catch {
|
|
4350
|
-
return null;
|
|
4351
|
-
}
|
|
4352
|
-
}
|
|
4353
|
-
function writeRoadmapFile(projectRoot, content) {
|
|
4354
|
-
const filePath = roadmapPath(projectRoot);
|
|
4355
|
-
const dir = path18.dirname(filePath);
|
|
4356
|
-
fs15.mkdirSync(dir, { recursive: true });
|
|
4357
|
-
fs15.writeFileSync(filePath, content, "utf-8");
|
|
4422
|
+
async function persistRoadmap(projectRoot, before, after) {
|
|
4423
|
+
const store = resolveRoadmapStore({ projectRoot });
|
|
4424
|
+
return applyRoadmapDiff(store, before, after);
|
|
4358
4425
|
}
|
|
4359
4426
|
function archiveFilePath(projectRoot) {
|
|
4360
4427
|
return path18.join(projectRoot, "docs", "roadmap-archive.md");
|
|
@@ -4393,17 +4460,16 @@ function roadmapNotFoundError() {
|
|
|
4393
4460
|
content: [
|
|
4394
4461
|
{
|
|
4395
4462
|
type: "text",
|
|
4396
|
-
text: "Error:
|
|
4463
|
+
text: "Error: roadmap not found. Create a roadmap first."
|
|
4397
4464
|
}
|
|
4398
4465
|
],
|
|
4399
4466
|
isError: true
|
|
4400
4467
|
};
|
|
4401
4468
|
}
|
|
4402
|
-
function handleShow3(projectPath, input, deps) {
|
|
4403
|
-
const {
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
const result = parseRoadmap(raw);
|
|
4469
|
+
async function handleShow3(projectPath, input, deps) {
|
|
4470
|
+
const { Ok: Ok2 } = deps;
|
|
4471
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4472
|
+
const result = await loadRoadmap(projectPath);
|
|
4407
4473
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4408
4474
|
let roadmap = result.value;
|
|
4409
4475
|
if (input.milestone) {
|
|
@@ -4459,15 +4525,15 @@ function buildFeatureFromInput(input) {
|
|
|
4459
4525
|
updatedAt: null
|
|
4460
4526
|
};
|
|
4461
4527
|
}
|
|
4462
|
-
function handleAdd2(projectPath, input, deps) {
|
|
4463
|
-
const {
|
|
4528
|
+
async function handleAdd2(projectPath, input, deps) {
|
|
4529
|
+
const { Ok: Ok2 } = deps;
|
|
4464
4530
|
const validationError = validateAddFields(input);
|
|
4465
4531
|
if (validationError) return validationError;
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
const result = parseRoadmap(raw);
|
|
4532
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4533
|
+
const result = await loadRoadmap(projectPath);
|
|
4469
4534
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4470
4535
|
const roadmap = result.value;
|
|
4536
|
+
const before = structuredClone(roadmap);
|
|
4471
4537
|
const milestone = roadmap.milestones.find(
|
|
4472
4538
|
(m) => m.name.toLowerCase() === input.milestone.toLowerCase()
|
|
4473
4539
|
);
|
|
@@ -4479,33 +4545,54 @@ function handleAdd2(projectPath, input, deps) {
|
|
|
4479
4545
|
}
|
|
4480
4546
|
milestone.features.push(buildFeatureFromInput(input));
|
|
4481
4547
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4482
|
-
|
|
4548
|
+
const persisted = await persistRoadmap(projectPath, before, roadmap);
|
|
4549
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4483
4550
|
return resultToMcpResponse(Ok2(roadmap));
|
|
4484
4551
|
}
|
|
4485
|
-
function
|
|
4486
|
-
const {
|
|
4552
|
+
function claimRefusedResponse(featureName, roadmap, currentAssignee) {
|
|
4553
|
+
const message = `Claim refused: "${featureName}" is already in-progress under ${currentAssignee ?? "another owner"} (first-claim-wins). Release it first to hand off.`;
|
|
4554
|
+
return {
|
|
4555
|
+
content: [
|
|
4556
|
+
{
|
|
4557
|
+
type: "text",
|
|
4558
|
+
text: JSON.stringify({ ...roadmap, claimed: false, message })
|
|
4559
|
+
}
|
|
4560
|
+
],
|
|
4561
|
+
isError: true
|
|
4562
|
+
};
|
|
4563
|
+
}
|
|
4564
|
+
async function handleUpdate2(projectPath, input, deps) {
|
|
4565
|
+
const { syncRoadmap, applySyncChanges, Ok: Ok2 } = deps;
|
|
4487
4566
|
if (!input.feature) {
|
|
4488
4567
|
return {
|
|
4489
4568
|
content: [{ type: "text", text: "Error: feature is required for update action" }],
|
|
4490
4569
|
isError: true
|
|
4491
4570
|
};
|
|
4492
4571
|
}
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
const result = parseRoadmap(raw);
|
|
4572
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4573
|
+
const result = await loadRoadmap(projectPath);
|
|
4496
4574
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4497
4575
|
const roadmap = result.value;
|
|
4576
|
+
const before = structuredClone(roadmap);
|
|
4498
4577
|
let found = false;
|
|
4499
4578
|
for (const m of roadmap.milestones) {
|
|
4500
4579
|
const feature = m.features.find((f) => f.name.toLowerCase() === input.feature.toLowerCase());
|
|
4501
4580
|
if (feature) {
|
|
4502
|
-
|
|
4581
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4582
|
+
if (input.status) deps.setStatus(roadmap, feature, input.status, date);
|
|
4503
4583
|
if (input.summary !== void 0) feature.summary = input.summary;
|
|
4504
4584
|
if (input.spec !== void 0) feature.spec = input.spec || null;
|
|
4505
4585
|
if (input.plans !== void 0) feature.plans = input.plans;
|
|
4506
4586
|
if (input.blocked_by !== void 0) feature.blockedBy = input.blocked_by;
|
|
4507
4587
|
if (input.assignee !== void 0) {
|
|
4508
|
-
|
|
4588
|
+
if (input.assignee) {
|
|
4589
|
+
deps.claim(roadmap, feature, input.assignee, date);
|
|
4590
|
+
if (feature.assignee !== input.assignee) {
|
|
4591
|
+
return claimRefusedResponse(input.feature, roadmap, feature.assignee);
|
|
4592
|
+
}
|
|
4593
|
+
} else {
|
|
4594
|
+
deps.release(roadmap, feature, date);
|
|
4595
|
+
}
|
|
4509
4596
|
}
|
|
4510
4597
|
found = true;
|
|
4511
4598
|
break;
|
|
@@ -4517,27 +4604,31 @@ function handleUpdate2(projectPath, input, deps) {
|
|
|
4517
4604
|
isError: true
|
|
4518
4605
|
};
|
|
4519
4606
|
}
|
|
4520
|
-
const cascade = syncRoadmap({ projectPath, roadmap });
|
|
4521
|
-
if (cascade.ok
|
|
4522
|
-
|
|
4607
|
+
const cascade = await syncRoadmap({ projectPath, roadmap });
|
|
4608
|
+
if (cascade.ok) {
|
|
4609
|
+
const unblocking = cascade.value.filter((change) => change.to !== "blocked");
|
|
4610
|
+
if (unblocking.length > 0) {
|
|
4611
|
+
applySyncChanges(roadmap, unblocking);
|
|
4612
|
+
}
|
|
4523
4613
|
}
|
|
4524
4614
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4525
|
-
|
|
4615
|
+
const persisted = await persistRoadmap(projectPath, before, roadmap);
|
|
4616
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4526
4617
|
return resultToMcpResponse(Ok2(roadmap));
|
|
4527
4618
|
}
|
|
4528
|
-
function handleRemove2(projectPath, input, deps) {
|
|
4529
|
-
const {
|
|
4619
|
+
async function handleRemove2(projectPath, input, deps) {
|
|
4620
|
+
const { Ok: Ok2 } = deps;
|
|
4530
4621
|
if (!input.feature) {
|
|
4531
4622
|
return {
|
|
4532
4623
|
content: [{ type: "text", text: "Error: feature is required for remove action" }],
|
|
4533
4624
|
isError: true
|
|
4534
4625
|
};
|
|
4535
4626
|
}
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
const result = parseRoadmap(raw);
|
|
4627
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4628
|
+
const result = await loadRoadmap(projectPath);
|
|
4539
4629
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4540
4630
|
const roadmap = result.value;
|
|
4631
|
+
const before = structuredClone(roadmap);
|
|
4541
4632
|
let found = false;
|
|
4542
4633
|
for (const m of roadmap.milestones) {
|
|
4543
4634
|
const idx = m.features.findIndex((f) => f.name.toLowerCase() === input.feature.toLowerCase());
|
|
@@ -4554,7 +4645,8 @@ function handleRemove2(projectPath, input, deps) {
|
|
|
4554
4645
|
};
|
|
4555
4646
|
}
|
|
4556
4647
|
roadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4557
|
-
|
|
4648
|
+
const persisted = await persistRoadmap(projectPath, before, roadmap);
|
|
4649
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4558
4650
|
return resultToMcpResponse(Ok2(roadmap));
|
|
4559
4651
|
}
|
|
4560
4652
|
function promoteEnvelopeResponse(envelope2) {
|
|
@@ -4563,8 +4655,8 @@ function promoteEnvelopeResponse(envelope2) {
|
|
|
4563
4655
|
isError: envelope2.ok === false
|
|
4564
4656
|
};
|
|
4565
4657
|
}
|
|
4566
|
-
function handlePromote2(projectPath, input, deps) {
|
|
4567
|
-
const {
|
|
4658
|
+
async function handlePromote2(projectPath, input, deps) {
|
|
4659
|
+
const { promoteFeature } = deps;
|
|
4568
4660
|
if (!input.feature) {
|
|
4569
4661
|
return {
|
|
4570
4662
|
content: [{ type: "text", text: "Error: feature is required for promote action" }],
|
|
@@ -4577,9 +4669,8 @@ function handlePromote2(projectPath, input, deps) {
|
|
|
4577
4669
|
isError: true
|
|
4578
4670
|
};
|
|
4579
4671
|
}
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
const result = parseRoadmap(raw);
|
|
4672
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4673
|
+
const result = await loadRoadmap(projectPath);
|
|
4583
4674
|
if (!result.ok) {
|
|
4584
4675
|
return promoteEnvelopeResponse({
|
|
4585
4676
|
ok: false,
|
|
@@ -4588,6 +4679,7 @@ function handlePromote2(projectPath, input, deps) {
|
|
|
4588
4679
|
detail: result.error.message
|
|
4589
4680
|
});
|
|
4590
4681
|
}
|
|
4682
|
+
const before = structuredClone(result.value);
|
|
4591
4683
|
const { result: envelope2, nextRoadmap } = promoteFeature(result.value, {
|
|
4592
4684
|
feature: input.feature,
|
|
4593
4685
|
spec: input.spec,
|
|
@@ -4595,30 +4687,28 @@ function handlePromote2(projectPath, input, deps) {
|
|
|
4595
4687
|
});
|
|
4596
4688
|
if (envelope2.ok && envelope2.transitioned !== "noop") {
|
|
4597
4689
|
nextRoadmap.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
} catch (error) {
|
|
4690
|
+
const persisted = await persistRoadmap(projectPath, before, nextRoadmap);
|
|
4691
|
+
if (!persisted.ok) {
|
|
4601
4692
|
return promoteEnvelopeResponse({
|
|
4602
4693
|
ok: false,
|
|
4603
4694
|
reason: "write-failed",
|
|
4604
4695
|
feature: input.feature,
|
|
4605
|
-
detail: error
|
|
4696
|
+
detail: persisted.error.message
|
|
4606
4697
|
});
|
|
4607
4698
|
}
|
|
4608
4699
|
}
|
|
4609
4700
|
return promoteEnvelopeResponse(envelope2);
|
|
4610
4701
|
}
|
|
4611
|
-
function handleQuery2(projectPath, input, deps) {
|
|
4612
|
-
const {
|
|
4702
|
+
async function handleQuery2(projectPath, input, deps) {
|
|
4703
|
+
const { Ok: Ok2 } = deps;
|
|
4613
4704
|
if (!input.filter) {
|
|
4614
4705
|
return {
|
|
4615
4706
|
content: [{ type: "text", text: "Error: filter is required for query action" }],
|
|
4616
4707
|
isError: true
|
|
4617
4708
|
};
|
|
4618
4709
|
}
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
const result = parseRoadmap(raw);
|
|
4710
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4711
|
+
const result = await loadRoadmap(projectPath);
|
|
4622
4712
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4623
4713
|
const roadmap = result.value;
|
|
4624
4714
|
const allFeatures = roadmap.milestones.flatMap(
|
|
@@ -4634,14 +4724,14 @@ function handleQuery2(projectPath, input, deps) {
|
|
|
4634
4724
|
}
|
|
4635
4725
|
return resultToMcpResponse(Ok2(filtered));
|
|
4636
4726
|
}
|
|
4637
|
-
function handleSync2(projectPath, input, deps) {
|
|
4638
|
-
const {
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
const result = parseRoadmap(raw);
|
|
4727
|
+
async function handleSync2(projectPath, input, deps) {
|
|
4728
|
+
const { syncRoadmap, Ok: Ok2 } = deps;
|
|
4729
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4730
|
+
const result = await loadRoadmap(projectPath);
|
|
4642
4731
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4643
4732
|
const roadmap = result.value;
|
|
4644
|
-
const
|
|
4733
|
+
const before = structuredClone(roadmap);
|
|
4734
|
+
const syncResult = await syncRoadmap({
|
|
4645
4735
|
projectPath,
|
|
4646
4736
|
roadmap,
|
|
4647
4737
|
forceSync: input.force_sync ?? false
|
|
@@ -4653,17 +4743,18 @@ function handleSync2(projectPath, input, deps) {
|
|
|
4653
4743
|
}
|
|
4654
4744
|
if (input.apply) {
|
|
4655
4745
|
deps.applySyncChanges(roadmap, changes);
|
|
4656
|
-
|
|
4746
|
+
const persisted = await persistRoadmap(projectPath, before, roadmap);
|
|
4747
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4657
4748
|
return resultToMcpResponse(Ok2({ changes, applied: true, roadmap }));
|
|
4658
4749
|
}
|
|
4659
4750
|
return resultToMcpResponse(Ok2({ changes, applied: false }));
|
|
4660
4751
|
}
|
|
4661
|
-
function handleGroom(projectPath, _input, deps) {
|
|
4662
|
-
const {
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
const result = parseRoadmap(raw);
|
|
4752
|
+
async function handleGroom(projectPath, _input, deps) {
|
|
4753
|
+
const { groomRoadmap, Ok: Ok2 } = deps;
|
|
4754
|
+
if (!roadmapSourceExists(projectPath)) return roadmapNotFoundError();
|
|
4755
|
+
const result = await loadRoadmap(projectPath);
|
|
4666
4756
|
if (!result.ok) return resultToMcpResponse(result);
|
|
4757
|
+
const before = structuredClone(result.value);
|
|
4667
4758
|
const { roadmap: groomed, archived, changes } = groomRoadmap(result.value);
|
|
4668
4759
|
if (changes.length === 0) {
|
|
4669
4760
|
return resultToMcpResponse(
|
|
@@ -4672,7 +4763,8 @@ function handleGroom(projectPath, _input, deps) {
|
|
|
4672
4763
|
}
|
|
4673
4764
|
appendToArchive(projectPath, archived, groomed.frontmatter.project, deps);
|
|
4674
4765
|
groomed.frontmatter.lastManualEdit = (/* @__PURE__ */ new Date()).toISOString();
|
|
4675
|
-
|
|
4766
|
+
const persisted = await persistRoadmap(projectPath, before, groomed);
|
|
4767
|
+
if (!persisted.ok) return resultToMcpResponse(persisted);
|
|
4676
4768
|
const demoted = changes.filter((c) => c.kind === "demoted").length;
|
|
4677
4769
|
return resultToMcpResponse(
|
|
4678
4770
|
Ok2({
|
|
@@ -4684,7 +4776,7 @@ function handleGroom(projectPath, _input, deps) {
|
|
|
4684
4776
|
);
|
|
4685
4777
|
}
|
|
4686
4778
|
var readOnlyActions = /* @__PURE__ */ new Set(["show", "query"]);
|
|
4687
|
-
function dispatchAction(action, projectPath, input, deps) {
|
|
4779
|
+
async function dispatchAction(action, projectPath, input, deps) {
|
|
4688
4780
|
switch (action) {
|
|
4689
4781
|
case "show":
|
|
4690
4782
|
return handleShow3(projectPath, input, deps);
|
|
@@ -4739,9 +4831,12 @@ async function handleManageRoadmap(input) {
|
|
|
4739
4831
|
syncRoadmap,
|
|
4740
4832
|
applySyncChanges,
|
|
4741
4833
|
assignFeature,
|
|
4834
|
+
setStatus,
|
|
4835
|
+
claim,
|
|
4836
|
+
release,
|
|
4742
4837
|
promoteFeature,
|
|
4743
4838
|
groomRoadmap
|
|
4744
|
-
} = await import("./dist-
|
|
4839
|
+
} = await import("./dist-IPVFYGY2.js");
|
|
4745
4840
|
const { Ok: Ok2 } = await import("./dist-ICW4QUEW.js");
|
|
4746
4841
|
const projectPath = projectPathPre;
|
|
4747
4842
|
const deps = {
|
|
@@ -4750,13 +4845,16 @@ async function handleManageRoadmap(input) {
|
|
|
4750
4845
|
syncRoadmap,
|
|
4751
4846
|
applySyncChanges,
|
|
4752
4847
|
assignFeature,
|
|
4848
|
+
setStatus,
|
|
4849
|
+
claim,
|
|
4850
|
+
release,
|
|
4753
4851
|
promoteFeature,
|
|
4754
4852
|
groomRoadmap,
|
|
4755
4853
|
Ok: Ok2
|
|
4756
4854
|
};
|
|
4757
|
-
const response = dispatchAction(input.action, projectPath, input, deps);
|
|
4855
|
+
const response = await dispatchAction(input.action, projectPath, input, deps);
|
|
4758
4856
|
if (shouldTriggerExternalSync(input, response)) {
|
|
4759
|
-
await triggerExternalSync(projectPath
|
|
4857
|
+
await triggerExternalSync(projectPath).catch(() => {
|
|
4760
4858
|
});
|
|
4761
4859
|
}
|
|
4762
4860
|
return response;
|
|
@@ -5182,7 +5280,7 @@ async function handleTransition(validInput, projectPath, id) {
|
|
|
5182
5280
|
const transition = transitionResult.data;
|
|
5183
5281
|
const prompt = renderTransition(transition);
|
|
5184
5282
|
try {
|
|
5185
|
-
const { saveHandoff } = await import("./dist-
|
|
5283
|
+
const { saveHandoff } = await import("./dist-IPVFYGY2.js");
|
|
5186
5284
|
await saveHandoff(
|
|
5187
5285
|
projectPath,
|
|
5188
5286
|
{
|
|
@@ -5263,17 +5361,17 @@ async function handleEmitInteraction(input) {
|
|
|
5263
5361
|
}
|
|
5264
5362
|
async function recordInteraction(projectPath, id, type, decision, stream) {
|
|
5265
5363
|
try {
|
|
5266
|
-
const {
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5276
|
-
}
|
|
5364
|
+
const { emitCoreEvent, emitUserInputCaptured, emitApprovalRequested } = await import("./state-events-F44CUUZG.js");
|
|
5365
|
+
await emitCoreEvent(
|
|
5366
|
+
projectPath,
|
|
5367
|
+
{
|
|
5368
|
+
type: "decision_recorded",
|
|
5369
|
+
payload: { id, text: `[${type}:${id}] ${decision}`, context: "pending user response" }
|
|
5370
|
+
},
|
|
5371
|
+
{ stream }
|
|
5372
|
+
);
|
|
5373
|
+
await emitUserInputCaptured(projectPath, decision, id, { stream });
|
|
5374
|
+
await emitApprovalRequested(projectPath, id, type, decision, { stream });
|
|
5277
5375
|
} catch {
|
|
5278
5376
|
}
|
|
5279
5377
|
}
|
|
@@ -5424,10 +5522,10 @@ async function handleGatherContext(input) {
|
|
|
5424
5522
|
input.include ?? ["state", "learnings", "handoff", "graph", "validation", "businessKnowledge"]
|
|
5425
5523
|
);
|
|
5426
5524
|
const errors = [];
|
|
5427
|
-
const statePromise = includeSet.has("state") ? import("./
|
|
5428
|
-
(
|
|
5525
|
+
const statePromise = includeSet.has("state") ? import("./state-events-F44CUUZG.js").then(
|
|
5526
|
+
(m) => m.readHarnessState(projectPath, { session: input.session })
|
|
5429
5527
|
) : Promise.resolve(null);
|
|
5430
|
-
const learningsPromise = includeSet.has("learnings") ? import("./dist-
|
|
5528
|
+
const learningsPromise = includeSet.has("learnings") ? import("./dist-IPVFYGY2.js").then(
|
|
5431
5529
|
(core) => core.loadBudgetedLearnings(projectPath, {
|
|
5432
5530
|
intent: input.intent,
|
|
5433
5531
|
tokenBudget: input.learningsBudget ?? 1e3,
|
|
@@ -5436,7 +5534,7 @@ async function handleGatherContext(input) {
|
|
|
5436
5534
|
...input.depth !== void 0 && { depth: input.depth }
|
|
5437
5535
|
})
|
|
5438
5536
|
) : Promise.resolve(null);
|
|
5439
|
-
const handoffPromise = includeSet.has("handoff") ? import("./dist-
|
|
5537
|
+
const handoffPromise = includeSet.has("handoff") ? import("./dist-IPVFYGY2.js").then(
|
|
5440
5538
|
(core) => core.loadHandoff(projectPath, void 0, input.session)
|
|
5441
5539
|
) : Promise.resolve(null);
|
|
5442
5540
|
const graphPromise = includeSet.has("graph") ? (async () => {
|
|
@@ -5480,19 +5578,15 @@ async function handleGatherContext(input) {
|
|
|
5480
5578
|
context: contextBlocks
|
|
5481
5579
|
};
|
|
5482
5580
|
})() : Promise.resolve(null);
|
|
5483
|
-
const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-
|
|
5581
|
+
const sessionsPromise = includeSet.has("sessions") && input.session ? import("./dist-IPVFYGY2.js").then(
|
|
5484
5582
|
(core) => core.readSessionSections(projectPath, input.session)
|
|
5485
5583
|
) : Promise.resolve(null);
|
|
5486
5584
|
const shouldIncludeEvents = input.includeEvents !== void 0 ? input.includeEvents : includeSet.has("events") || !!input.session && !input.include;
|
|
5487
|
-
const eventsPromise = shouldIncludeEvents ? import("./
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
});
|
|
5491
|
-
if (!result.ok) return null;
|
|
5492
|
-
return core.formatEventTimeline(result.value);
|
|
5493
|
-
}) : Promise.resolve(null);
|
|
5585
|
+
const eventsPromise = shouldIncludeEvents ? import("./state-events-F44CUUZG.js").then(
|
|
5586
|
+
(m) => m.readAuditTimeline(projectPath, { session: input.session })
|
|
5587
|
+
) : Promise.resolve(null);
|
|
5494
5588
|
const validationPromise = includeSet.has("validation") ? (async () => {
|
|
5495
|
-
const { handleValidateProject: handleValidateProject2 } = await import("./validate-
|
|
5589
|
+
const { handleValidateProject: handleValidateProject2 } = await import("./validate-IUAV7MOU.js");
|
|
5496
5590
|
const result = await handleValidateProject2({ path: projectPath });
|
|
5497
5591
|
const first = result.content[0];
|
|
5498
5592
|
return first ? JSON.parse(first.text) : null;
|
|
@@ -5595,7 +5689,7 @@ async function handleGatherContext(input) {
|
|
|
5595
5689
|
};
|
|
5596
5690
|
if (input.session) {
|
|
5597
5691
|
try {
|
|
5598
|
-
const core = await import("./dist-
|
|
5692
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
5599
5693
|
core.updateSessionIndex(
|
|
5600
5694
|
projectPath,
|
|
5601
5695
|
input.session,
|
|
@@ -5793,7 +5887,7 @@ async function handleReviewChanges(input) {
|
|
|
5793
5887
|
}
|
|
5794
5888
|
}
|
|
5795
5889
|
async function runQuickReview(projectPath, diff, diffLines, downgraded, offset, limit) {
|
|
5796
|
-
const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-
|
|
5890
|
+
const { handleAnalyzeDiff: handleAnalyzeDiff2 } = await import("./feedback-JT5X3DBC.js");
|
|
5797
5891
|
const result = await handleAnalyzeDiff2({ diff, path: projectPath });
|
|
5798
5892
|
const firstContent = result.content[0];
|
|
5799
5893
|
if (!firstContent) throw new Error("Empty analyze_diff response");
|
|
@@ -5828,7 +5922,7 @@ function extractFileCount(diffParsed) {
|
|
|
5828
5922
|
return files?.length ?? 0;
|
|
5829
5923
|
}
|
|
5830
5924
|
async function runStandardReview(projectPath, diff, diffLines, downgraded, offset, limit) {
|
|
5831
|
-
const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-
|
|
5925
|
+
const { handleAnalyzeDiff: handleAnalyzeDiff2, handleCreateSelfReview: handleCreateSelfReview2 } = await import("./feedback-JT5X3DBC.js");
|
|
5832
5926
|
const [diffResult, reviewResult] = await Promise.all([
|
|
5833
5927
|
handleAnalyzeDiff2({ diff, path: projectPath }),
|
|
5834
5928
|
handleCreateSelfReview2({ path: projectPath, diff })
|
|
@@ -5863,7 +5957,7 @@ async function runStandardReview(projectPath, diff, diffLines, downgraded, offse
|
|
|
5863
5957
|
};
|
|
5864
5958
|
}
|
|
5865
5959
|
async function runDeepReview(projectPath, diff, diffLines, _downgraded, offset, limit) {
|
|
5866
|
-
const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-
|
|
5960
|
+
const { handleRunCodeReview: handleRunCodeReview2 } = await import("./review-pipeline-MOOWXWSQ.js");
|
|
5867
5961
|
const result = await handleRunCodeReview2({
|
|
5868
5962
|
path: projectPath,
|
|
5869
5963
|
diff,
|
|
@@ -6158,7 +6252,7 @@ async function handleDetectStaleConstraints(input) {
|
|
|
6158
6252
|
]
|
|
6159
6253
|
};
|
|
6160
6254
|
}
|
|
6161
|
-
const { detectStaleConstraints } = await import("./dist-
|
|
6255
|
+
const { detectStaleConstraints } = await import("./dist-IPVFYGY2.js");
|
|
6162
6256
|
const result = detectStaleConstraints(
|
|
6163
6257
|
store,
|
|
6164
6258
|
windowDays,
|
|
@@ -6189,6 +6283,7 @@ async function handleDetectStaleConstraints(input) {
|
|
|
6189
6283
|
init_dist2();
|
|
6190
6284
|
|
|
6191
6285
|
// src/skill/health-snapshot.ts
|
|
6286
|
+
init_dist2();
|
|
6192
6287
|
import { execSync } from "child_process";
|
|
6193
6288
|
import * as fs16 from "fs";
|
|
6194
6289
|
import * as path19 from "path";
|
|
@@ -6288,10 +6383,10 @@ function countCriticalFindings(securityData) {
|
|
|
6288
6383
|
return findings.filter((f) => f.severity === "error").length;
|
|
6289
6384
|
}
|
|
6290
6385
|
async function runHealthChecks(projectPath) {
|
|
6291
|
-
const { handleAssessProject: handleAssessProject2 } = await import("./assess-project-
|
|
6292
|
-
const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-
|
|
6293
|
-
const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-
|
|
6294
|
-
const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-
|
|
6386
|
+
const { handleAssessProject: handleAssessProject2 } = await import("./assess-project-3KPP2D6M.js");
|
|
6387
|
+
const { handleCheckDependencies: handleCheckDependencies2 } = await import("./architecture-2XYGJGT3.js");
|
|
6388
|
+
const { handleDetectEntropy: handleDetectEntropy2 } = await import("./entropy-EAEKJKRM.js");
|
|
6389
|
+
const { handleRunSecurityScan: handleRunSecurityScan2 } = await import("./security-U76C54W6.js");
|
|
6295
6390
|
const [assessResult, depsResult, entropyResult, securityResult] = await Promise.all([
|
|
6296
6391
|
handleAssessProject2({
|
|
6297
6392
|
path: projectPath,
|
|
@@ -6395,11 +6490,12 @@ async function captureHealthSnapshot(projectPath) {
|
|
|
6395
6490
|
runGraphMetrics(projectPath)
|
|
6396
6491
|
]);
|
|
6397
6492
|
const signals = deriveSignals(checks, metrics);
|
|
6493
|
+
const reconciledChecks = reconcilePassed(checks, signals);
|
|
6398
6494
|
const snapshot = {
|
|
6399
6495
|
capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6400
6496
|
gitHead,
|
|
6401
6497
|
projectPath,
|
|
6402
|
-
checks,
|
|
6498
|
+
checks: reconciledChecks,
|
|
6403
6499
|
metrics,
|
|
6404
6500
|
signals
|
|
6405
6501
|
};
|
|
@@ -6767,17 +6863,7 @@ function recommend(snapshot, skills, options = {}) {
|
|
|
6767
6863
|
}
|
|
6768
6864
|
|
|
6769
6865
|
// src/skill/dispatch-engine.ts
|
|
6770
|
-
var SIGNAL_CATEGORIES =
|
|
6771
|
-
"circular-deps": "structure",
|
|
6772
|
-
"layer-violations": "structure",
|
|
6773
|
-
"high-coupling": "structure",
|
|
6774
|
-
"dead-code": "quality",
|
|
6775
|
-
drift: "quality",
|
|
6776
|
-
"doc-gaps": "quality",
|
|
6777
|
-
"security-findings": "security",
|
|
6778
|
-
"perf-regression": "performance",
|
|
6779
|
-
"low-coverage": "coverage"
|
|
6780
|
-
};
|
|
6866
|
+
var SIGNAL_CATEGORIES = SIGNAL_CATEGORY_MAP;
|
|
6781
6867
|
function getSignalCategory(signal) {
|
|
6782
6868
|
return SIGNAL_CATEGORIES[signal] ?? null;
|
|
6783
6869
|
}
|
|
@@ -7047,7 +7133,7 @@ async function handleDispatchSkills(input) {
|
|
|
7047
7133
|
let skillTriggers;
|
|
7048
7134
|
if (trigger) {
|
|
7049
7135
|
try {
|
|
7050
|
-
const { loadOrRebuildIndex: loadOrRebuildIndex2 } = await import("./index-builder-
|
|
7136
|
+
const { loadOrRebuildIndex: loadOrRebuildIndex2 } = await import("./index-builder-52IJJHJC.js");
|
|
7051
7137
|
const index = loadOrRebuildIndex2("claude-code", projectRoot);
|
|
7052
7138
|
skillTriggers = /* @__PURE__ */ new Map();
|
|
7053
7139
|
for (const [name, entry] of Object.entries(index.skills)) {
|
|
@@ -7134,7 +7220,7 @@ async function handleGetDecayTrends(input) {
|
|
|
7134
7220
|
};
|
|
7135
7221
|
}
|
|
7136
7222
|
try {
|
|
7137
|
-
const core = await import("./dist-
|
|
7223
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
7138
7224
|
const { TimelineManager } = core;
|
|
7139
7225
|
const manager = new TimelineManager(projectPath);
|
|
7140
7226
|
const timeline = manager.load();
|
|
@@ -7269,7 +7355,7 @@ async function handleCodeOutline(input) {
|
|
|
7269
7355
|
};
|
|
7270
7356
|
}
|
|
7271
7357
|
try {
|
|
7272
|
-
const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-
|
|
7358
|
+
const { getOutline, formatOutline, EXTENSION_MAP } = await import("./dist-IPVFYGY2.js");
|
|
7273
7359
|
const { stat } = await import("fs/promises");
|
|
7274
7360
|
const stats = await stat(targetPath).catch(() => null);
|
|
7275
7361
|
if (stats?.isFile()) {
|
|
@@ -7288,7 +7374,7 @@ async function handleCodeOutline(input) {
|
|
|
7288
7374
|
})
|
|
7289
7375
|
);
|
|
7290
7376
|
fileStats.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
7291
|
-
const { paginate: paginate2 } = await import("./dist-
|
|
7377
|
+
const { paginate: paginate2 } = await import("./dist-IPVFYGY2.js");
|
|
7292
7378
|
const paged = paginate2(fileStats, input.offset ?? 0, input.limit ?? 30);
|
|
7293
7379
|
const results = [];
|
|
7294
7380
|
for (const entry of paged.items) {
|
|
@@ -7361,7 +7447,7 @@ async function handleCodeSearch(input) {
|
|
|
7361
7447
|
};
|
|
7362
7448
|
}
|
|
7363
7449
|
try {
|
|
7364
|
-
const { searchSymbols } = await import("./dist-
|
|
7450
|
+
const { searchSymbols } = await import("./dist-IPVFYGY2.js");
|
|
7365
7451
|
const result = await searchSymbols(input.query, directory, input.glob);
|
|
7366
7452
|
const lines = [`Search: "${result.query}" \u2014 ${result.matches.length} matches`];
|
|
7367
7453
|
for (const match of result.matches) {
|
|
@@ -7430,7 +7516,7 @@ async function handleCodeUnfold(input) {
|
|
|
7430
7516
|
}
|
|
7431
7517
|
try {
|
|
7432
7518
|
if (input.symbol) {
|
|
7433
|
-
const { unfoldSymbol } = await import("./dist-
|
|
7519
|
+
const { unfoldSymbol } = await import("./dist-IPVFYGY2.js");
|
|
7434
7520
|
const result = await unfoldSymbol(filePath, input.symbol);
|
|
7435
7521
|
const header = result.warning ? `${result.file}:${result.startLine}-${result.endLine} ${result.warning}
|
|
7436
7522
|
` : `${result.file}:${result.startLine}-${result.endLine}
|
|
@@ -7438,7 +7524,7 @@ async function handleCodeUnfold(input) {
|
|
|
7438
7524
|
return { content: [{ type: "text", text: header + result.content }] };
|
|
7439
7525
|
}
|
|
7440
7526
|
if (input.startLine != null && input.endLine != null) {
|
|
7441
|
-
const { unfoldRange } = await import("./dist-
|
|
7527
|
+
const { unfoldRange } = await import("./dist-IPVFYGY2.js");
|
|
7442
7528
|
const result = await unfoldRange(filePath, input.startLine, input.endLine);
|
|
7443
7529
|
const header = `${result.file}:${result.startLine}-${result.endLine}
|
|
7444
7530
|
`;
|
|
@@ -7623,7 +7709,7 @@ async function handlePredictFailures(input) {
|
|
|
7623
7709
|
};
|
|
7624
7710
|
}
|
|
7625
7711
|
try {
|
|
7626
|
-
const core = await import("./dist-
|
|
7712
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
7627
7713
|
const { TimelineManager, PredictionEngine, SpecImpactEstimator } = core;
|
|
7628
7714
|
const manager = new TimelineManager(projectPath);
|
|
7629
7715
|
const includeRoadmap = input.includeRoadmap !== false;
|
|
@@ -7634,7 +7720,7 @@ async function handlePredictFailures(input) {
|
|
|
7634
7720
|
};
|
|
7635
7721
|
if (input.horizon !== void 0) opts.horizon = input.horizon;
|
|
7636
7722
|
if (input.category) opts.categories = [input.category];
|
|
7637
|
-
const result = engine.predict(opts);
|
|
7723
|
+
const result = await engine.predict(opts);
|
|
7638
7724
|
return {
|
|
7639
7725
|
content: [
|
|
7640
7726
|
{
|
|
@@ -8732,7 +8818,7 @@ async function handleDetectConstraintEmergence(input) {
|
|
|
8732
8818
|
}
|
|
8733
8819
|
const nodePath = await import("path");
|
|
8734
8820
|
const historyPath = nodePath.resolve(projectPath, ".harness/arch/violation-history.json");
|
|
8735
|
-
const { ViolationHistoryManager, detectEmergentConstraints } = await import("./dist-
|
|
8821
|
+
const { ViolationHistoryManager, detectEmergentConstraints } = await import("./dist-IPVFYGY2.js");
|
|
8736
8822
|
const manager = new ViolationHistoryManager(historyPath);
|
|
8737
8823
|
const history = manager.load();
|
|
8738
8824
|
const options = { windowWeeks, minOccurrences };
|
|
@@ -8820,7 +8906,7 @@ async function handleRunCIChecks(input) {
|
|
|
8820
8906
|
};
|
|
8821
8907
|
}
|
|
8822
8908
|
try {
|
|
8823
|
-
const { runCIChecks } = await import("./dist-
|
|
8909
|
+
const { runCIChecks } = await import("./dist-IPVFYGY2.js");
|
|
8824
8910
|
const skip = input.checks ? VALID_CHECKS.filter((c) => !input.checks.includes(c)) : [];
|
|
8825
8911
|
const result = await runCIChecks({
|
|
8826
8912
|
projectRoot: projectPath,
|
|
@@ -8882,7 +8968,7 @@ async function handleGenerateBlueprint(input) {
|
|
|
8882
8968
|
};
|
|
8883
8969
|
}
|
|
8884
8970
|
try {
|
|
8885
|
-
const { ProjectScanner } = await import("./dist-
|
|
8971
|
+
const { ProjectScanner } = await import("./dist-IPVFYGY2.js");
|
|
8886
8972
|
const scanner = new ProjectScanner(projectPath);
|
|
8887
8973
|
const data = await scanner.scan();
|
|
8888
8974
|
return {
|
|
@@ -9081,7 +9167,7 @@ async function handleEmitSkillProposal(input) {
|
|
|
9081
9167
|
let createProposal;
|
|
9082
9168
|
let ProposalConflictError;
|
|
9083
9169
|
try {
|
|
9084
|
-
const core = await import("./dist-
|
|
9170
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
9085
9171
|
createProposal = core.createProposal;
|
|
9086
9172
|
ProposalConflictError = core.ProposalConflictError;
|
|
9087
9173
|
} catch (err2) {
|
|
@@ -9124,8 +9210,8 @@ async function handleEmitSkillProposal(input) {
|
|
|
9124
9210
|
}
|
|
9125
9211
|
|
|
9126
9212
|
// src/mcp/tools/audit-anatomy.ts
|
|
9127
|
-
import * as
|
|
9128
|
-
import * as
|
|
9213
|
+
import * as fs22 from "fs";
|
|
9214
|
+
import * as path24 from "path";
|
|
9129
9215
|
|
|
9130
9216
|
// src/audit/component-anatomy/parsers/ast.ts
|
|
9131
9217
|
import * as fs18 from "fs";
|
|
@@ -9234,6 +9320,10 @@ function findTypeDeclaration(sourceFile, typeName) {
|
|
|
9234
9320
|
return null;
|
|
9235
9321
|
}
|
|
9236
9322
|
|
|
9323
|
+
// src/audit/component-anatomy/resolvers/component-type.ts
|
|
9324
|
+
import * as fs20 from "fs";
|
|
9325
|
+
import * as path23 from "path";
|
|
9326
|
+
|
|
9237
9327
|
// src/audit/component-anatomy/catalog/conventions/button.ts
|
|
9238
9328
|
var buttonConvention = {
|
|
9239
9329
|
componentType: "Button",
|
|
@@ -9817,6 +9907,65 @@ function lookupConvention(componentType) {
|
|
|
9817
9907
|
return conventionByType.get(componentType) ?? null;
|
|
9818
9908
|
}
|
|
9819
9909
|
|
|
9910
|
+
// src/audit/component-anatomy/parsers/jsdoc.ts
|
|
9911
|
+
function extractLeadingJsDoc(source) {
|
|
9912
|
+
const re = /^\s*(?:#![^\n]*\n)?\s*(?:['"]use (?:client|server)['"];?\s*)?\s*\/\*\*([\s\S]*?)\*\//;
|
|
9913
|
+
const match = re.exec(source);
|
|
9914
|
+
if (!match) return null;
|
|
9915
|
+
return match[1].split("\n").map((line) => line.replace(/^\s*\*?\s?/, "")).join("\n").trim();
|
|
9916
|
+
}
|
|
9917
|
+
function readJsDocTag(jsdoc, tag) {
|
|
9918
|
+
const escaped = tag.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9919
|
+
const re = new RegExp(`@${escaped}\\b[ \\t]*(.*)$`, "gm");
|
|
9920
|
+
const values = [];
|
|
9921
|
+
for (const m of jsdoc.matchAll(re)) {
|
|
9922
|
+
values.push((m[1] ?? "").trim());
|
|
9923
|
+
}
|
|
9924
|
+
return values;
|
|
9925
|
+
}
|
|
9926
|
+
function readJsDocTagValue(jsdoc, tag) {
|
|
9927
|
+
const [first] = readJsDocTag(jsdoc, tag);
|
|
9928
|
+
return first ? first : null;
|
|
9929
|
+
}
|
|
9930
|
+
|
|
9931
|
+
// src/audit/component-anatomy/parsers/design-registry.ts
|
|
9932
|
+
import * as fs19 from "fs";
|
|
9933
|
+
import * as path22 from "path";
|
|
9934
|
+
function parseComponentRegistry(designMd) {
|
|
9935
|
+
const lines = designMd.split("\n");
|
|
9936
|
+
const headingIdx = lines.findIndex((l) => /^#{1,6}\s+Component Registry\b/i.test(l));
|
|
9937
|
+
if (headingIdx === -1) return [];
|
|
9938
|
+
const entries = [];
|
|
9939
|
+
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
9940
|
+
const line = lines[i];
|
|
9941
|
+
if (/^#{1,6}\s+/.test(line)) break;
|
|
9942
|
+
const trimmed = line.trim();
|
|
9943
|
+
if (!trimmed.startsWith("|")) continue;
|
|
9944
|
+
const cells = trimmed.slice(1, trimmed.endsWith("|") ? -1 : void 0).split("|").map((c) => c.trim());
|
|
9945
|
+
const [type, file] = cells;
|
|
9946
|
+
if (!type || !file) continue;
|
|
9947
|
+
if (/^type$/i.test(type) || /^:?-{2,}:?$/.test(type)) continue;
|
|
9948
|
+
entries.push({ type, file });
|
|
9949
|
+
}
|
|
9950
|
+
return entries;
|
|
9951
|
+
}
|
|
9952
|
+
function findDesignMd(fromPath) {
|
|
9953
|
+
let dir = path22.dirname(path22.resolve(fromPath));
|
|
9954
|
+
for (; ; ) {
|
|
9955
|
+
let names;
|
|
9956
|
+
try {
|
|
9957
|
+
names = fs19.readdirSync(dir);
|
|
9958
|
+
} catch {
|
|
9959
|
+
names = [];
|
|
9960
|
+
}
|
|
9961
|
+
const hit = names.find((n) => n.toLowerCase() === "design.md");
|
|
9962
|
+
if (hit) return path22.join(dir, hit);
|
|
9963
|
+
const parent = path22.dirname(dir);
|
|
9964
|
+
if (parent === dir) return null;
|
|
9965
|
+
dir = parent;
|
|
9966
|
+
}
|
|
9967
|
+
}
|
|
9968
|
+
|
|
9820
9969
|
// src/audit/component-anatomy/resolvers/component-type.ts
|
|
9821
9970
|
var componentTypeSet = new Set(getCatalogTypes());
|
|
9822
9971
|
function resolveComponentType(filePath, fileContents) {
|
|
@@ -9826,11 +9975,31 @@ function resolveComponentType(filePath, fileContents) {
|
|
|
9826
9975
|
if (registryType !== null) return registryType;
|
|
9827
9976
|
return resolveFromExportName(fileContents);
|
|
9828
9977
|
}
|
|
9829
|
-
function resolveFromJSDoc(_filePath,
|
|
9830
|
-
|
|
9831
|
-
|
|
9832
|
-
|
|
9833
|
-
|
|
9978
|
+
function resolveFromJSDoc(_filePath, fileContents) {
|
|
9979
|
+
const jsdoc = extractLeadingJsDoc(fileContents);
|
|
9980
|
+
if (jsdoc === null) return null;
|
|
9981
|
+
return readJsDocTagValue(jsdoc, "component-type");
|
|
9982
|
+
}
|
|
9983
|
+
var registryCache = /* @__PURE__ */ new Map();
|
|
9984
|
+
function resolveFromDesignRegistry(filePath) {
|
|
9985
|
+
const designPath = findDesignMd(filePath);
|
|
9986
|
+
if (designPath === null) return null;
|
|
9987
|
+
let byFile = registryCache.get(designPath);
|
|
9988
|
+
if (!byFile) {
|
|
9989
|
+
byFile = /* @__PURE__ */ new Map();
|
|
9990
|
+
let contents;
|
|
9991
|
+
try {
|
|
9992
|
+
contents = fs20.readFileSync(designPath, "utf8");
|
|
9993
|
+
} catch {
|
|
9994
|
+
contents = "";
|
|
9995
|
+
}
|
|
9996
|
+
const designDir = path23.dirname(designPath);
|
|
9997
|
+
for (const entry of parseComponentRegistry(contents)) {
|
|
9998
|
+
byFile.set(path23.resolve(designDir, entry.file), entry.type);
|
|
9999
|
+
}
|
|
10000
|
+
registryCache.set(designPath, byFile);
|
|
10001
|
+
}
|
|
10002
|
+
return byFile.get(path23.resolve(filePath)) ?? null;
|
|
9834
10003
|
}
|
|
9835
10004
|
function resolveFromExportName(fileContents) {
|
|
9836
10005
|
const patterns = [
|
|
@@ -9849,20 +10018,142 @@ function resolveFromExportName(fileContents) {
|
|
|
9849
10018
|
return null;
|
|
9850
10019
|
}
|
|
9851
10020
|
|
|
10021
|
+
// src/audit/component-anatomy/resolvers/source-of-truth.ts
|
|
10022
|
+
import * as fs21 from "fs";
|
|
10023
|
+
|
|
10024
|
+
// src/audit/component-anatomy/parsers/anatomy-tags.ts
|
|
10025
|
+
var JSDOC_SOURCE_REF = "design-component-anatomy/jsdoc";
|
|
10026
|
+
function fixHint(name, axis) {
|
|
10027
|
+
return `Add the \`${name}\` ${axis} to this component, or drop the \`@anatomy-${axis} ${name}\` tag if it no longer applies.`;
|
|
10028
|
+
}
|
|
10029
|
+
function parseFlaggedPart(value, axis) {
|
|
10030
|
+
const tokens = value.split(/\s+/).filter(Boolean);
|
|
10031
|
+
const name = tokens[0];
|
|
10032
|
+
if (!name) return null;
|
|
10033
|
+
const flags = new Set(tokens.slice(1).map((t) => t.toLowerCase()));
|
|
10034
|
+
return {
|
|
10035
|
+
name,
|
|
10036
|
+
required: flags.has("required"),
|
|
10037
|
+
...flags.has("exclusive") ? { exclusive: true } : {},
|
|
10038
|
+
fixHint: fixHint(name, axis)
|
|
10039
|
+
};
|
|
10040
|
+
}
|
|
10041
|
+
function parsePipeList(value, axis) {
|
|
10042
|
+
return value.split("|").map((n) => n.trim()).filter(Boolean).map((name) => ({ name, required: false, fixHint: fixHint(name, axis) }));
|
|
10043
|
+
}
|
|
10044
|
+
function buildAnatomyRuleFromJsDoc(jsdoc, componentType) {
|
|
10045
|
+
const slots = readJsDocTag(jsdoc, "anatomy-slot").map((v) => parseFlaggedPart(v, "slot")).filter((p) => p !== null);
|
|
10046
|
+
const states = readJsDocTag(jsdoc, "anatomy-state").map((v) => parseFlaggedPart(v, "state")).filter((p) => p !== null);
|
|
10047
|
+
const variants = readJsDocTag(jsdoc, "anatomy-variant").flatMap(
|
|
10048
|
+
(v) => parsePipeList(v, "variant")
|
|
10049
|
+
);
|
|
10050
|
+
const sizes = readJsDocTag(jsdoc, "anatomy-size").flatMap((v) => parsePipeList(v, "size"));
|
|
10051
|
+
if (slots.length === 0 && states.length === 0 && variants.length === 0 && sizes.length === 0) {
|
|
10052
|
+
return null;
|
|
10053
|
+
}
|
|
10054
|
+
return { componentType, slots, states, variants, sizes, source: { ref: JSDOC_SOURCE_REF } };
|
|
10055
|
+
}
|
|
10056
|
+
|
|
10057
|
+
// src/audit/component-anatomy/parsers/design-overrides.ts
|
|
10058
|
+
var SINGULAR = { slots: "slot", states: "state", variants: "variant", sizes: "size" };
|
|
10059
|
+
var SOURCE_REF = "design-component-anatomy/design-md";
|
|
10060
|
+
function partFrom(raw, axis) {
|
|
10061
|
+
const m = /^([A-Za-z0-9][A-Za-z0-9-]*)\s*(?:\(([^)]*)\))?/.exec(raw.trim());
|
|
10062
|
+
const name = m?.[1];
|
|
10063
|
+
if (!name) return null;
|
|
10064
|
+
const flags = (m?.[2] ?? "").toLowerCase();
|
|
10065
|
+
return {
|
|
10066
|
+
name,
|
|
10067
|
+
required: /\brequired\b/.test(flags),
|
|
10068
|
+
.../\bexclusive\b/.test(flags) ? { exclusive: true } : {},
|
|
10069
|
+
fixHint: `Add the \`${name}\` ${SINGULAR[axis]} to this component (declared in the DESIGN.md anatomy override).`
|
|
10070
|
+
};
|
|
10071
|
+
}
|
|
10072
|
+
function parseAnatomyOverrides(designMd) {
|
|
10073
|
+
const lines = designMd.split("\n");
|
|
10074
|
+
const out = /* @__PURE__ */ new Map();
|
|
10075
|
+
let i = lines.findIndex((l) => /^#{1,6}\s+Component Anatomy Overrides\b/i.test(l));
|
|
10076
|
+
if (i === -1) return out;
|
|
10077
|
+
let current = null;
|
|
10078
|
+
let currentAxis = null;
|
|
10079
|
+
const flush = () => {
|
|
10080
|
+
if (!current) return;
|
|
10081
|
+
out.set(current.type, {
|
|
10082
|
+
componentType: current.type,
|
|
10083
|
+
slots: current.axes.slots,
|
|
10084
|
+
states: current.axes.states,
|
|
10085
|
+
variants: current.axes.variants,
|
|
10086
|
+
sizes: current.axes.sizes,
|
|
10087
|
+
source: { ref: SOURCE_REF }
|
|
10088
|
+
});
|
|
10089
|
+
};
|
|
10090
|
+
for (i += 1; i < lines.length; i++) {
|
|
10091
|
+
const line = lines[i];
|
|
10092
|
+
if (/^#{1,2}\s/.test(line)) break;
|
|
10093
|
+
const compHeading = /^#{3,6}\s+(.+?)\s*$/.exec(line);
|
|
10094
|
+
if (compHeading) {
|
|
10095
|
+
flush();
|
|
10096
|
+
current = {
|
|
10097
|
+
type: compHeading[1].trim(),
|
|
10098
|
+
axes: { slots: [], states: [], variants: [], sizes: [] }
|
|
10099
|
+
};
|
|
10100
|
+
currentAxis = null;
|
|
10101
|
+
continue;
|
|
10102
|
+
}
|
|
10103
|
+
if (!current) continue;
|
|
10104
|
+
const axisMatch = /^\s*(slots|states|variants|sizes)\s*:\s*(.*)$/i.exec(line);
|
|
10105
|
+
if (axisMatch) {
|
|
10106
|
+
currentAxis = axisMatch[1].toLowerCase();
|
|
10107
|
+
const inline = axisMatch[2].trim();
|
|
10108
|
+
if (inline) {
|
|
10109
|
+
for (const tok of inline.split(",")) {
|
|
10110
|
+
const part = partFrom(tok, currentAxis);
|
|
10111
|
+
if (part) current.axes[currentAxis].push(part);
|
|
10112
|
+
}
|
|
10113
|
+
currentAxis = null;
|
|
10114
|
+
}
|
|
10115
|
+
continue;
|
|
10116
|
+
}
|
|
10117
|
+
const listItem = /^\s*[-*]\s+(.*)$/.exec(line);
|
|
10118
|
+
if (listItem && currentAxis) {
|
|
10119
|
+
const part = partFrom(listItem[1], currentAxis);
|
|
10120
|
+
if (part) current.axes[currentAxis].push(part);
|
|
10121
|
+
}
|
|
10122
|
+
}
|
|
10123
|
+
flush();
|
|
10124
|
+
return out;
|
|
10125
|
+
}
|
|
10126
|
+
|
|
9852
10127
|
// src/audit/component-anatomy/resolvers/source-of-truth.ts
|
|
9853
10128
|
function resolveAnatomyRules(filePath, fileContents, componentType) {
|
|
9854
10129
|
if (componentType === null) return null;
|
|
9855
|
-
const jsdocRule = resolveFromJSDoc2(
|
|
10130
|
+
const jsdocRule = resolveFromJSDoc2(fileContents, componentType);
|
|
9856
10131
|
if (jsdocRule !== null) return jsdocRule;
|
|
9857
10132
|
const overrideRule = resolveFromDesignOverrides(filePath, componentType);
|
|
9858
10133
|
if (overrideRule !== null) return overrideRule;
|
|
9859
10134
|
return lookupConvention(componentType);
|
|
9860
10135
|
}
|
|
9861
|
-
function resolveFromJSDoc2(
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
|
|
9865
|
-
|
|
10136
|
+
function resolveFromJSDoc2(fileContents, componentType) {
|
|
10137
|
+
const jsdoc = extractLeadingJsDoc(fileContents);
|
|
10138
|
+
if (jsdoc === null) return null;
|
|
10139
|
+
return buildAnatomyRuleFromJsDoc(jsdoc, componentType);
|
|
10140
|
+
}
|
|
10141
|
+
var overridesCache = /* @__PURE__ */ new Map();
|
|
10142
|
+
function resolveFromDesignOverrides(filePath, componentType) {
|
|
10143
|
+
const designPath = findDesignMd(filePath);
|
|
10144
|
+
if (designPath === null) return null;
|
|
10145
|
+
let byType = overridesCache.get(designPath);
|
|
10146
|
+
if (!byType) {
|
|
10147
|
+
let contents;
|
|
10148
|
+
try {
|
|
10149
|
+
contents = fs21.readFileSync(designPath, "utf8");
|
|
10150
|
+
} catch {
|
|
10151
|
+
contents = "";
|
|
10152
|
+
}
|
|
10153
|
+
byType = parseAnatomyOverrides(contents);
|
|
10154
|
+
overridesCache.set(designPath, byType);
|
|
10155
|
+
}
|
|
10156
|
+
return byType.get(componentType) ?? null;
|
|
9866
10157
|
}
|
|
9867
10158
|
|
|
9868
10159
|
// src/audit/component-anatomy/findings/severity.ts
|
|
@@ -9981,10 +10272,197 @@ function runConventionRule(rule, parsed, options) {
|
|
|
9981
10272
|
return findings;
|
|
9982
10273
|
}
|
|
9983
10274
|
|
|
10275
|
+
// src/audit/component-anatomy/catalog/patterns/index.ts
|
|
10276
|
+
function lineAt(contents, index) {
|
|
10277
|
+
let line = 1;
|
|
10278
|
+
for (let i = 0; i < index && i < contents.length; i++) {
|
|
10279
|
+
if (contents[i] === "\n") line += 1;
|
|
10280
|
+
}
|
|
10281
|
+
return line;
|
|
10282
|
+
}
|
|
10283
|
+
var anyMatch = (contents, res) => res.some((re) => re.test(contents));
|
|
10284
|
+
function firstTrigger(contents, triggers) {
|
|
10285
|
+
let best = -1;
|
|
10286
|
+
for (const re of triggers) {
|
|
10287
|
+
const m = re.exec(contents);
|
|
10288
|
+
if (m && (best === -1 || m.index < best)) best = m.index;
|
|
10289
|
+
}
|
|
10290
|
+
return best;
|
|
10291
|
+
}
|
|
10292
|
+
function presencePattern(spec) {
|
|
10293
|
+
return {
|
|
10294
|
+
code: spec.code,
|
|
10295
|
+
id: spec.id,
|
|
10296
|
+
source: `design-component-anatomy/pattern-${spec.id}`,
|
|
10297
|
+
detect(file, contents, componentType) {
|
|
10298
|
+
const index = firstTrigger(contents, spec.triggers);
|
|
10299
|
+
if (index === -1) return [];
|
|
10300
|
+
if (anyMatch(contents, spec.mitigations)) return [];
|
|
10301
|
+
const line = lineAt(contents, index);
|
|
10302
|
+
const snippet = (contents.split("\n")[line - 1] ?? "").trim();
|
|
10303
|
+
return [
|
|
10304
|
+
{
|
|
10305
|
+
code: spec.code,
|
|
10306
|
+
severity: "warn",
|
|
10307
|
+
file,
|
|
10308
|
+
line,
|
|
10309
|
+
componentType,
|
|
10310
|
+
message: spec.message,
|
|
10311
|
+
evidence: { snippet },
|
|
10312
|
+
rule: { id: spec.code, source: `design-component-anatomy/pattern-${spec.id}` },
|
|
10313
|
+
fix: { kind: "manual", description: spec.fix }
|
|
10314
|
+
}
|
|
10315
|
+
];
|
|
10316
|
+
}
|
|
10317
|
+
};
|
|
10318
|
+
}
|
|
10319
|
+
var SPECS = [
|
|
10320
|
+
{
|
|
10321
|
+
code: "ANAT-P001",
|
|
10322
|
+
id: "map-without-empty",
|
|
10323
|
+
triggers: [/\.map\s*\(/],
|
|
10324
|
+
mitigations: [
|
|
10325
|
+
/\.length\s*===\s*0/,
|
|
10326
|
+
/\.length\s*<\s*1/,
|
|
10327
|
+
/\.length\s*>\s*0/,
|
|
10328
|
+
/\.length\s*\?/,
|
|
10329
|
+
/\blength\s*&&/,
|
|
10330
|
+
/\?\.length\b/,
|
|
10331
|
+
/\bisEmpty\b/,
|
|
10332
|
+
/\bEmptyState\b/,
|
|
10333
|
+
/\bno\s+(results|items|data|entries)\b/i
|
|
10334
|
+
],
|
|
10335
|
+
message: "List rendered with `.map(...)` but no empty state was found. An empty list renders as a blank region \u2014 add a length-zero branch (e.g. an EmptyState).",
|
|
10336
|
+
fix: "Guard the list: `items.length === 0 ? <EmptyState/> : items.map(...)`."
|
|
10337
|
+
},
|
|
10338
|
+
{
|
|
10339
|
+
code: "ANAT-P002",
|
|
10340
|
+
id: "fetch-without-loading",
|
|
10341
|
+
triggers: [/\bfetch\s*\(/, /\buse(Query|SWR|Swr|Mutation)\b/, /\baxios\s*[.(]/, /\bawait\s+/],
|
|
10342
|
+
mitigations: [
|
|
10343
|
+
/\bis?Loading\b/i,
|
|
10344
|
+
/\bisPending\b/i,
|
|
10345
|
+
/\bpending\b/i,
|
|
10346
|
+
/\bSkeleton\b/,
|
|
10347
|
+
/\bSpinner\b/,
|
|
10348
|
+
/<Suspense\b/,
|
|
10349
|
+
/\bplaceholder\b/i
|
|
10350
|
+
],
|
|
10351
|
+
message: "Async data loading found but no loading state. The surface renders empty until data resolves \u2014 add a loading boundary (skeleton, spinner, or Suspense).",
|
|
10352
|
+
fix: "Render a loading affordance while the request is in flight (e.g. `if (isLoading) return <Skeleton/>`)."
|
|
10353
|
+
},
|
|
10354
|
+
{
|
|
10355
|
+
code: "ANAT-P003",
|
|
10356
|
+
id: "fetch-without-error",
|
|
10357
|
+
triggers: [/\bfetch\s*\(/, /\buse(Query|SWR|Swr|Mutation)\b/, /\baxios\s*[.(]/, /\bawait\s+/],
|
|
10358
|
+
mitigations: [
|
|
10359
|
+
/\bis?Error\b/i,
|
|
10360
|
+
/\berror\b/i,
|
|
10361
|
+
/\bcatch\s*\(/,
|
|
10362
|
+
/\.catch\s*\(/,
|
|
10363
|
+
/\bErrorState\b/,
|
|
10364
|
+
/\bErrorBoundary\b/,
|
|
10365
|
+
/role=["']alert["']/
|
|
10366
|
+
],
|
|
10367
|
+
message: "Async data loading found but no error state. A failed request renders nothing or throws \u2014 handle and display the error case.",
|
|
10368
|
+
fix: "Add an error branch (e.g. `if (isError) return <ErrorState/>`) or a try/catch around the request."
|
|
10369
|
+
},
|
|
10370
|
+
{
|
|
10371
|
+
code: "ANAT-P004",
|
|
10372
|
+
id: "conditional-render-without-fallback",
|
|
10373
|
+
triggers: [/\{\s*[\w.]*(data|items|list|results|rows|user|profile)[\w.]*\s*&&/i],
|
|
10374
|
+
mitigations: [/\?\s*\(?</, /\?\?/, /\bEmptyState\b/, /:\s*null/, /:\s*</, /\.length\s*===\s*0/],
|
|
10375
|
+
message: "Data-driven `{value && <\u2026>}` render with no fallback. When `value` is falsy the region collapses silently \u2014 provide an explicit empty/else branch.",
|
|
10376
|
+
fix: "Use a ternary with a fallback: `value ? <View/> : <EmptyState/>`."
|
|
10377
|
+
},
|
|
10378
|
+
{
|
|
10379
|
+
code: "ANAT-P005",
|
|
10380
|
+
id: "form-without-submit-feedback",
|
|
10381
|
+
triggers: [/<form\b/i, /\bonSubmit\b/, /\bhandleSubmit\b/],
|
|
10382
|
+
mitigations: [
|
|
10383
|
+
/\bisSubmitting\b/i,
|
|
10384
|
+
/\bpending\b/i,
|
|
10385
|
+
/\bsuccess\b/i,
|
|
10386
|
+
/\berror\b/i,
|
|
10387
|
+
/\bdisabled\b/,
|
|
10388
|
+
/role=["']alert["']/,
|
|
10389
|
+
/\btoast\b/i
|
|
10390
|
+
],
|
|
10391
|
+
message: "Form submission found but no submit feedback (pending / success / error). The user gets no signal that submit worked or failed.",
|
|
10392
|
+
fix: "Disable the submit control while pending and surface a success/error state after submission."
|
|
10393
|
+
},
|
|
10394
|
+
{
|
|
10395
|
+
code: "ANAT-P006",
|
|
10396
|
+
id: "modal-without-dismiss",
|
|
10397
|
+
triggers: [/<(Modal|Dialog|Drawer|Sheet|Popover)\b/],
|
|
10398
|
+
mitigations: [/\bonClose\b/, /\bonDismiss\b/, /\bonOpenChange\b/, /\bclose\b/i, /\bonCancel\b/],
|
|
10399
|
+
message: "Modal/Dialog/Drawer used with no dismiss affordance (`onClose` / `onDismiss` / `onOpenChange`). A modal the user cannot close is a trap.",
|
|
10400
|
+
fix: "Wire a dismiss handler (`onClose` / `onOpenChange`) and a visible close control."
|
|
10401
|
+
},
|
|
10402
|
+
{
|
|
10403
|
+
code: "ANAT-P007",
|
|
10404
|
+
id: "async-action-without-pending",
|
|
10405
|
+
triggers: [
|
|
10406
|
+
/on[A-Z]\w*=\{?\s*async\b/,
|
|
10407
|
+
/\bhandle\w*\s*=\s*async\b/,
|
|
10408
|
+
/async\s+function\s+handle/i
|
|
10409
|
+
],
|
|
10410
|
+
mitigations: [
|
|
10411
|
+
/\bdisabled\b/,
|
|
10412
|
+
/\bis?Pending\b/i,
|
|
10413
|
+
/\bis?Loading\b/i,
|
|
10414
|
+
/\bis?Submitting\b/i,
|
|
10415
|
+
/\bSpinner\b/
|
|
10416
|
+
],
|
|
10417
|
+
message: "Async action handler with no pending state. Without disabling/feedback the user can double-submit and gets no progress signal.",
|
|
10418
|
+
fix: "Track a pending flag and disable the control (and show a spinner) while the action runs."
|
|
10419
|
+
},
|
|
10420
|
+
{
|
|
10421
|
+
code: "ANAT-P008",
|
|
10422
|
+
id: "list-without-key",
|
|
10423
|
+
triggers: [/\.map\s*\(\s*\(?[^)]*\)?\s*=>\s*</],
|
|
10424
|
+
mitigations: [/\bkey=/],
|
|
10425
|
+
message: "Elements rendered from `.map(...)` with no `key` prop. Missing keys break reconciliation (lost state, mis-ordered DOM).",
|
|
10426
|
+
fix: "Add a stable `key` to the top-level mapped element: `items.map((i) => <Row key={i.id} />)`."
|
|
10427
|
+
},
|
|
10428
|
+
{
|
|
10429
|
+
code: "ANAT-P009",
|
|
10430
|
+
id: "router-without-not-found",
|
|
10431
|
+
triggers: [/<Routes\b/, /<Switch\b/, /\bcreateBrowserRouter\b/],
|
|
10432
|
+
mitigations: [
|
|
10433
|
+
/path=["']\*["']/,
|
|
10434
|
+
/path=["']\/\*["']/,
|
|
10435
|
+
/\bNotFound\b/,
|
|
10436
|
+
/\bcatchAll\b/i,
|
|
10437
|
+
/\b404\b/
|
|
10438
|
+
],
|
|
10439
|
+
message: "Route table with no catch-all / not-found route. Unmatched URLs render a blank page.",
|
|
10440
|
+
fix: 'Add a catch-all route (`<Route path="*" element={<NotFound/>} />`).'
|
|
10441
|
+
},
|
|
10442
|
+
{
|
|
10443
|
+
code: "ANAT-P010",
|
|
10444
|
+
id: "destructive-action-without-confirm",
|
|
10445
|
+
triggers: [
|
|
10446
|
+
/\b(onDelete|handleDelete|handleRemove|onRemove)\b/,
|
|
10447
|
+
/\b(delete|remove|destroy)\w*\s*\(/i
|
|
10448
|
+
],
|
|
10449
|
+
mitigations: [
|
|
10450
|
+
/\bconfirm\s*\(/i,
|
|
10451
|
+
/\bAlertDialog\b/,
|
|
10452
|
+
/\bConfirm(ation)?(Dialog|Modal)?\b/,
|
|
10453
|
+
/\bareYouSure\b/i,
|
|
10454
|
+
/\bwindow\.confirm\b/
|
|
10455
|
+
],
|
|
10456
|
+
message: "Destructive action (delete/remove) with no confirmation step. A single click irreversibly destroys data.",
|
|
10457
|
+
fix: "Gate the action behind a confirmation (e.g. an AlertDialog) before performing it."
|
|
10458
|
+
}
|
|
10459
|
+
];
|
|
10460
|
+
var PATTERN_CHECKS = SPECS.map(presencePattern);
|
|
10461
|
+
|
|
9984
10462
|
// src/mcp/tools/audit-anatomy.ts
|
|
9985
10463
|
var auditAnatomyDefinition = {
|
|
9986
10464
|
name: "audit_anatomy",
|
|
9987
|
-
description: "Audit components for anatomy completeness. Emits ANAT-D* findings for component definitions missing required slots/states (e.g., Button missing `content`). In v1 vertical slice runs the
|
|
10465
|
+
description: "Audit components for anatomy completeness. Emits ANAT-D* findings for component definitions missing required slots/states (e.g., Button missing `content`). In v1 vertical slice runs the component conventions, plus 10 ANAT-P* composition patterns (missing empty/loading/error states, modal dismiss, submit feedback, list keys, route 404, destructive-action confirm, \u2026) in full mode.",
|
|
9988
10466
|
inputSchema: {
|
|
9989
10467
|
type: "object",
|
|
9990
10468
|
properties: {
|
|
@@ -9992,7 +10470,7 @@ var auditAnatomyDefinition = {
|
|
|
9992
10470
|
mode: {
|
|
9993
10471
|
type: "string",
|
|
9994
10472
|
enum: ["fast", "full"],
|
|
9995
|
-
description: "fast = conventions only (cheap AST scan)
|
|
10473
|
+
description: "fast = conventions only (cheap AST scan); full additionally runs the ANAT-P* composition patterns."
|
|
9996
10474
|
},
|
|
9997
10475
|
files: {
|
|
9998
10476
|
type: "array",
|
|
@@ -10020,31 +10498,38 @@ async function runAudit(input) {
|
|
|
10020
10498
|
const projectRoot = sanitizePath(input.path);
|
|
10021
10499
|
const candidateFiles = input.files ?? [];
|
|
10022
10500
|
const conventionsApplied = /* @__PURE__ */ new Set();
|
|
10501
|
+
const patternsApplied = /* @__PURE__ */ new Set();
|
|
10023
10502
|
let totalFiles = 0;
|
|
10024
10503
|
for (const candidatePath of candidateFiles) {
|
|
10025
|
-
const absolute =
|
|
10504
|
+
const absolute = path24.isAbsolute(candidatePath) ? candidatePath : path24.join(projectRoot, candidatePath);
|
|
10026
10505
|
let contents;
|
|
10027
10506
|
try {
|
|
10028
|
-
contents =
|
|
10507
|
+
contents = fs22.readFileSync(absolute, "utf8");
|
|
10029
10508
|
} catch {
|
|
10030
10509
|
continue;
|
|
10031
10510
|
}
|
|
10032
10511
|
totalFiles += 1;
|
|
10512
|
+
const fileRelative = path24.relative(projectRoot, absolute).replaceAll("\\", "/") || absolute;
|
|
10033
10513
|
const componentType = resolveComponentType(absolute, contents);
|
|
10514
|
+
if (mode2 === "full") {
|
|
10515
|
+
for (const pattern of PATTERN_CHECKS) {
|
|
10516
|
+
const patternFindings = pattern.detect(fileRelative, contents, componentType);
|
|
10517
|
+
if (patternFindings.length > 0) patternsApplied.add(pattern.id);
|
|
10518
|
+
findings.push(...patternFindings);
|
|
10519
|
+
}
|
|
10520
|
+
}
|
|
10034
10521
|
if (componentType === null) continue;
|
|
10035
10522
|
const rule = resolveAnatomyRules(absolute, contents, componentType);
|
|
10036
10523
|
if (rule === null) continue;
|
|
10037
10524
|
const parsed = parseComponentDefinitionFromSource(absolute, contents);
|
|
10038
10525
|
if (parsed === null) continue;
|
|
10039
10526
|
conventionsApplied.add(rule.componentType);
|
|
10040
|
-
const fileRelative = path22.relative(projectRoot, absolute).replaceAll("\\", "/") || absolute;
|
|
10041
10527
|
const runnerOptions = { filePath: fileRelative };
|
|
10042
10528
|
if (input.designStrictness !== void 0) {
|
|
10043
10529
|
runnerOptions.strictness = input.designStrictness;
|
|
10044
10530
|
}
|
|
10045
10531
|
const fileFindings = runConventionRule(rule, parsed, runnerOptions);
|
|
10046
10532
|
findings.push(...fileFindings);
|
|
10047
|
-
void mode2;
|
|
10048
10533
|
}
|
|
10049
10534
|
const summary = buildSummary(findings, totalFiles, Date.now() - start);
|
|
10050
10535
|
return {
|
|
@@ -10052,7 +10537,7 @@ async function runAudit(input) {
|
|
|
10052
10537
|
summary,
|
|
10053
10538
|
catalog: {
|
|
10054
10539
|
conventionsApplied: [...conventionsApplied].sort(),
|
|
10055
|
-
patternsApplied: []
|
|
10540
|
+
patternsApplied: [...patternsApplied].sort()
|
|
10056
10541
|
},
|
|
10057
10542
|
meta: { mode: mode2, deferredToA11y: 0 }
|
|
10058
10543
|
};
|
|
@@ -10084,10 +10569,11 @@ async function handleAuditAnatomy(input) {
|
|
|
10084
10569
|
// src/mcp/tools/design-craft.ts
|
|
10085
10570
|
init_dist2();
|
|
10086
10571
|
import * as crypto2 from "crypto";
|
|
10572
|
+
import { execSync as execSync3 } from "child_process";
|
|
10087
10573
|
|
|
10088
10574
|
// src/design-craft/phases/critique.ts
|
|
10089
|
-
import * as
|
|
10090
|
-
import * as
|
|
10575
|
+
import * as fs23 from "fs";
|
|
10576
|
+
import * as path25 from "path";
|
|
10091
10577
|
|
|
10092
10578
|
// src/shared/craft/findings/derived.ts
|
|
10093
10579
|
var TIER_BAND = {
|
|
@@ -10169,8 +10655,8 @@ function parseFindingResponse(raw) {
|
|
|
10169
10655
|
}
|
|
10170
10656
|
function readSource(target) {
|
|
10171
10657
|
if (typeof target.source === "string") return target.source;
|
|
10172
|
-
const resolved =
|
|
10173
|
-
return
|
|
10658
|
+
const resolved = path25.isAbsolute(target.file) ? target.file : path25.resolve(process.cwd(), target.file);
|
|
10659
|
+
return fs23.readFileSync(resolved, "utf8");
|
|
10174
10660
|
}
|
|
10175
10661
|
function formatPrompt(template, targetId, source) {
|
|
10176
10662
|
return template.replace(/\{target\}/g, targetId).replace(/\{source\}/g, source);
|
|
@@ -10232,10 +10718,37 @@ async function runCritique(args) {
|
|
|
10232
10718
|
}
|
|
10233
10719
|
return findings;
|
|
10234
10720
|
}
|
|
10721
|
+
function readImage(imagePath) {
|
|
10722
|
+
const resolved = path25.isAbsolute(imagePath) ? imagePath : path25.resolve(process.cwd(), imagePath);
|
|
10723
|
+
const imageBuffer = fs23.readFileSync(resolved);
|
|
10724
|
+
const ext = path25.extname(resolved).toLowerCase();
|
|
10725
|
+
const mediaType = ext === ".jpg" || ext === ".jpeg" ? "image/jpeg" : ext === ".webp" ? "image/webp" : "image/png";
|
|
10726
|
+
return { imageBuffer, mediaType };
|
|
10727
|
+
}
|
|
10728
|
+
async function runVisionCritique(args) {
|
|
10729
|
+
const findings = [];
|
|
10730
|
+
for (const target of args.targets) {
|
|
10731
|
+
const image = readImage(target.image);
|
|
10732
|
+
const targetId = target.component ?? target.file;
|
|
10733
|
+
for (const rubric of args.rubrics) {
|
|
10734
|
+
const prompt = formatPrompt(
|
|
10735
|
+
rubric.prompt,
|
|
10736
|
+
targetId,
|
|
10737
|
+
"(the rendered component is attached as an image \u2014 judge the visual result, not source code)"
|
|
10738
|
+
);
|
|
10739
|
+
const raw = await args.provider.callVision(prompt, image);
|
|
10740
|
+
const parsed = parseFindingResponse(raw);
|
|
10741
|
+
findings.push(
|
|
10742
|
+
parsed ? buildFinding(target, rubric, parsed) : buildSentinelFinding(target, rubric, raw)
|
|
10743
|
+
);
|
|
10744
|
+
}
|
|
10745
|
+
}
|
|
10746
|
+
return findings;
|
|
10747
|
+
}
|
|
10235
10748
|
|
|
10236
10749
|
// src/design-craft/phases/polish.ts
|
|
10237
|
-
import * as
|
|
10238
|
-
import * as
|
|
10750
|
+
import * as fs24 from "fs";
|
|
10751
|
+
import * as path26 from "path";
|
|
10239
10752
|
var TIER_VALUES2 = ["foundational", "polish", "aspirational"];
|
|
10240
10753
|
var IMPACT_VALUES2 = ["small", "medium", "large"];
|
|
10241
10754
|
var CONFIDENCE_VALUES2 = ["high", "medium", "low"];
|
|
@@ -10296,8 +10809,8 @@ function parsePolishResponse(raw) {
|
|
|
10296
10809
|
}
|
|
10297
10810
|
function readSource2(target) {
|
|
10298
10811
|
if (typeof target.source === "string") return target.source;
|
|
10299
|
-
const resolved =
|
|
10300
|
-
return
|
|
10812
|
+
const resolved = path26.isAbsolute(target.file) ? target.file : path26.resolve(process.cwd(), target.file);
|
|
10813
|
+
return fs24.readFileSync(resolved, "utf8");
|
|
10301
10814
|
}
|
|
10302
10815
|
function patternIsPlausible(source, pattern) {
|
|
10303
10816
|
if (pattern.applicableTo.length === 0) return true;
|
|
@@ -10380,8 +10893,8 @@ async function runPolish(args) {
|
|
|
10380
10893
|
}
|
|
10381
10894
|
|
|
10382
10895
|
// src/design-craft/phases/benchmark.ts
|
|
10383
|
-
import * as
|
|
10384
|
-
import * as
|
|
10896
|
+
import * as fs25 from "fs";
|
|
10897
|
+
import * as path27 from "path";
|
|
10385
10898
|
var CONFIDENCE_VALUES3 = ["high", "medium", "low"];
|
|
10386
10899
|
function isConfidence3(v) {
|
|
10387
10900
|
return typeof v === "string" && CONFIDENCE_VALUES3.includes(v);
|
|
@@ -10447,8 +10960,8 @@ function parseBenchmarkResponse(raw) {
|
|
|
10447
10960
|
}
|
|
10448
10961
|
function readSource3(target) {
|
|
10449
10962
|
if (typeof target.source === "string") return target.source;
|
|
10450
|
-
const resolved =
|
|
10451
|
-
return
|
|
10963
|
+
const resolved = path27.isAbsolute(target.file) ? target.file : path27.resolve(process.cwd(), target.file);
|
|
10964
|
+
return fs25.readFileSync(resolved, "utf8");
|
|
10452
10965
|
}
|
|
10453
10966
|
var CONFIDENCE_RANK = {
|
|
10454
10967
|
high: 3,
|
|
@@ -12549,17 +13062,17 @@ import {
|
|
|
12549
13062
|
} from "@harness-engineering/intelligence";
|
|
12550
13063
|
|
|
12551
13064
|
// src/shared/craft/llm/orchestrator-md.ts
|
|
12552
|
-
import * as
|
|
12553
|
-
import * as
|
|
13065
|
+
import * as fs26 from "fs";
|
|
13066
|
+
import * as path28 from "path";
|
|
12554
13067
|
import { parse as parse4 } from "yaml";
|
|
12555
13068
|
var FILENAME = "harness.orchestrator.md";
|
|
12556
13069
|
function findOrchestratorMd(startDir) {
|
|
12557
|
-
let cur =
|
|
12558
|
-
const root =
|
|
13070
|
+
let cur = path28.resolve(startDir);
|
|
13071
|
+
const root = path28.parse(cur).root;
|
|
12559
13072
|
while (cur !== root) {
|
|
12560
|
-
const candidate =
|
|
12561
|
-
if (
|
|
12562
|
-
cur =
|
|
13073
|
+
const candidate = path28.join(cur, FILENAME);
|
|
13074
|
+
if (fs26.existsSync(candidate)) return candidate;
|
|
13075
|
+
cur = path28.dirname(cur);
|
|
12563
13076
|
}
|
|
12564
13077
|
return null;
|
|
12565
13078
|
}
|
|
@@ -12568,7 +13081,7 @@ function readBackendsFromOrchestratorMd(startDir) {
|
|
|
12568
13081
|
if (file === null) return null;
|
|
12569
13082
|
let raw;
|
|
12570
13083
|
try {
|
|
12571
|
-
raw =
|
|
13084
|
+
raw = fs26.readFileSync(file, "utf-8");
|
|
12572
13085
|
} catch {
|
|
12573
13086
|
return null;
|
|
12574
13087
|
}
|
|
@@ -12839,15 +13352,15 @@ function stringField(obj, key) {
|
|
|
12839
13352
|
}
|
|
12840
13353
|
|
|
12841
13354
|
// src/design-craft/measurement/usage.ts
|
|
12842
|
-
import * as
|
|
12843
|
-
import * as
|
|
12844
|
-
var DEFAULT_RELATIVE_PATH =
|
|
13355
|
+
import * as fs27 from "fs";
|
|
13356
|
+
import * as path29 from "path";
|
|
13357
|
+
var DEFAULT_RELATIVE_PATH = path29.join(".harness", "design-craft", "usage.json");
|
|
12845
13358
|
function emptyCounters() {
|
|
12846
13359
|
return { rubrics: {}, patterns: {}, exemplars: {} };
|
|
12847
13360
|
}
|
|
12848
13361
|
function resolveStorePath(projectRoot) {
|
|
12849
13362
|
const root = projectRoot ?? process.cwd();
|
|
12850
|
-
return
|
|
13363
|
+
return path29.resolve(root, DEFAULT_RELATIVE_PATH);
|
|
12851
13364
|
}
|
|
12852
13365
|
function isCounters(value) {
|
|
12853
13366
|
if (value === null || typeof value !== "object") return false;
|
|
@@ -12856,7 +13369,7 @@ function isCounters(value) {
|
|
|
12856
13369
|
}
|
|
12857
13370
|
function readCountersFromDisk(filePath) {
|
|
12858
13371
|
try {
|
|
12859
|
-
const raw =
|
|
13372
|
+
const raw = fs27.readFileSync(filePath, "utf8");
|
|
12860
13373
|
const parsed = JSON.parse(raw);
|
|
12861
13374
|
if (!isCounters(parsed)) return emptyCounters();
|
|
12862
13375
|
return {
|
|
@@ -12869,8 +13382,8 @@ function readCountersFromDisk(filePath) {
|
|
|
12869
13382
|
}
|
|
12870
13383
|
}
|
|
12871
13384
|
function writeCountersToDisk(filePath, counters) {
|
|
12872
|
-
|
|
12873
|
-
|
|
13385
|
+
fs27.mkdirSync(path29.dirname(filePath), { recursive: true });
|
|
13386
|
+
fs27.writeFileSync(filePath, JSON.stringify(counters, null, 2), "utf8");
|
|
12874
13387
|
}
|
|
12875
13388
|
function bumpCounter(family, id, projectRoot, count = 1) {
|
|
12876
13389
|
if (id.length === 0 || count <= 0) return;
|
|
@@ -12891,13 +13404,13 @@ function recordCite(exemplarId, projectRoot) {
|
|
|
12891
13404
|
}
|
|
12892
13405
|
|
|
12893
13406
|
// src/design-craft/measurement/signal.ts
|
|
12894
|
-
import * as
|
|
12895
|
-
import * as
|
|
13407
|
+
import * as fs28 from "fs";
|
|
13408
|
+
import * as path30 from "path";
|
|
12896
13409
|
import * as crypto from "crypto";
|
|
12897
|
-
var EVENTS_RELATIVE_PATH =
|
|
12898
|
-
var PROPOSALS_RELATIVE_DIR =
|
|
13410
|
+
var EVENTS_RELATIVE_PATH = path30.join(".harness", "design-craft", "signal-events.jsonl");
|
|
13411
|
+
var PROPOSALS_RELATIVE_DIR = path30.join(".harness", "design-craft", "proposals");
|
|
12899
13412
|
function resolveEventsPath(storeRoot) {
|
|
12900
|
-
return
|
|
13413
|
+
return path30.resolve(storeRoot ?? process.cwd(), EVENTS_RELATIVE_PATH);
|
|
12901
13414
|
}
|
|
12902
13415
|
function fingerprintFinding(finding) {
|
|
12903
13416
|
const raw = `${finding.code}|${finding.tier}|${finding.cite.rubricOrPatternId}`;
|
|
@@ -12918,8 +13431,8 @@ function recordSignalEvent(finding, projectRoot, storeRoot) {
|
|
|
12918
13431
|
};
|
|
12919
13432
|
try {
|
|
12920
13433
|
const file = resolveEventsPath(storeRoot);
|
|
12921
|
-
|
|
12922
|
-
|
|
13434
|
+
fs28.mkdirSync(path30.dirname(file), { recursive: true });
|
|
13435
|
+
fs28.appendFileSync(file, JSON.stringify(event) + "\n", "utf8");
|
|
12923
13436
|
} catch {
|
|
12924
13437
|
}
|
|
12925
13438
|
}
|
|
@@ -12936,7 +13449,7 @@ var designCraftToolDefinition = {
|
|
|
12936
13449
|
mode: {
|
|
12937
13450
|
type: "string",
|
|
12938
13451
|
enum: ["fast", "deep"],
|
|
12939
|
-
description: "fast (code-only LLM critique) or deep (
|
|
13452
|
+
description: "fast (code-only LLM critique) or deep (vision critique of rendered screenshots \u2014 requires `captures`)."
|
|
12940
13453
|
},
|
|
12941
13454
|
phases: {
|
|
12942
13455
|
type: "array",
|
|
@@ -12951,7 +13464,11 @@ var designCraftToolDefinition = {
|
|
|
12951
13464
|
autoCapture: {
|
|
12952
13465
|
type: "string",
|
|
12953
13466
|
enum: ["prompt", "auto", "skip"],
|
|
12954
|
-
description:
|
|
13467
|
+
description: 'Deep-mode capture behavior when no `captures` are supplied. "skip" never runs the capture command; "prompt"/"auto" run `captureCommand` when one is configured.'
|
|
13468
|
+
},
|
|
13469
|
+
captureCommand: {
|
|
13470
|
+
type: "string",
|
|
13471
|
+
description: "Deep-mode render+screenshot command. Receives the candidate files via the HARNESS_DESIGN_CRAFT_FILES env var (JSON array) and must print a JSON array of { file, image, component? } to stdout. Used to obtain captures without a built-in browser."
|
|
12955
13472
|
},
|
|
12956
13473
|
designStrictness: {
|
|
12957
13474
|
type: "string",
|
|
@@ -12970,6 +13487,19 @@ var designCraftToolDefinition = {
|
|
|
12970
13487
|
},
|
|
12971
13488
|
required: ["file", "component"]
|
|
12972
13489
|
}
|
|
13490
|
+
},
|
|
13491
|
+
captures: {
|
|
13492
|
+
type: "array",
|
|
13493
|
+
description: 'Deep-mode (vision) captures: rendered component screenshots. Required when mode="deep" and the critique phase runs. Each entry: { file, image, component? }, where `image` is a path to a PNG/JPEG/WebP screenshot (the CLI does not render components itself).',
|
|
13494
|
+
items: {
|
|
13495
|
+
type: "object",
|
|
13496
|
+
properties: {
|
|
13497
|
+
file: { type: "string" },
|
|
13498
|
+
image: { type: "string" },
|
|
13499
|
+
component: { type: "string" }
|
|
13500
|
+
},
|
|
13501
|
+
required: ["file", "image"]
|
|
13502
|
+
}
|
|
12973
13503
|
}
|
|
12974
13504
|
},
|
|
12975
13505
|
required: ["path"]
|
|
@@ -13010,16 +13540,63 @@ function summarizeLlmCalls(provider) {
|
|
|
13010
13540
|
async function runDesignCraft(input) {
|
|
13011
13541
|
return runPipeline(input);
|
|
13012
13542
|
}
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13543
|
+
function runCaptureCommand(command, files, exec) {
|
|
13544
|
+
let stdout;
|
|
13545
|
+
try {
|
|
13546
|
+
stdout = exec ? exec(command, files) : execSync3(command, {
|
|
13547
|
+
encoding: "utf-8",
|
|
13548
|
+
env: { ...process.env, HARNESS_DESIGN_CRAFT_FILES: JSON.stringify(files) },
|
|
13549
|
+
maxBuffer: 16 * 1024 * 1024
|
|
13550
|
+
});
|
|
13551
|
+
} catch (err2) {
|
|
13016
13552
|
return Err({
|
|
13017
|
-
message:
|
|
13553
|
+
message: `design-craft capture command failed: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
13018
13554
|
});
|
|
13019
13555
|
}
|
|
13556
|
+
let parsed;
|
|
13557
|
+
try {
|
|
13558
|
+
parsed = JSON.parse(stdout);
|
|
13559
|
+
} catch {
|
|
13560
|
+
return Err({
|
|
13561
|
+
message: 'design-craft capture command did not emit valid JSON. Expected `[{ "file": "...", "image": "..." }]` on stdout.'
|
|
13562
|
+
});
|
|
13563
|
+
}
|
|
13564
|
+
if (!Array.isArray(parsed)) {
|
|
13565
|
+
return Err({
|
|
13566
|
+
message: "design-craft capture command output must be a JSON array of { file, image }."
|
|
13567
|
+
});
|
|
13568
|
+
}
|
|
13569
|
+
const captures = [];
|
|
13570
|
+
for (const item of parsed) {
|
|
13571
|
+
if (item !== null && typeof item === "object" && typeof item.file === "string" && typeof item.image === "string") {
|
|
13572
|
+
const entry = item;
|
|
13573
|
+
const capture = { file: entry.file, image: entry.image };
|
|
13574
|
+
if (typeof entry.component === "string") capture.component = entry.component;
|
|
13575
|
+
captures.push(capture);
|
|
13576
|
+
}
|
|
13577
|
+
}
|
|
13578
|
+
if (captures.length === 0) {
|
|
13579
|
+
return Err({
|
|
13580
|
+
message: "design-craft capture command produced no valid { file, image } entries."
|
|
13581
|
+
});
|
|
13582
|
+
}
|
|
13583
|
+
return Ok(captures);
|
|
13584
|
+
}
|
|
13585
|
+
async function runPipeline(input) {
|
|
13586
|
+
const mode2 = input.mode ?? "fast";
|
|
13020
13587
|
const phases = selectPhases(input.phases);
|
|
13021
13588
|
const autoCapture = input.autoCapture ?? "prompt";
|
|
13022
|
-
|
|
13589
|
+
let captures = input.captures ?? [];
|
|
13590
|
+
const needsCaptures = mode2 === "deep" && phases.includes("critique") && captures.length === 0;
|
|
13591
|
+
if (needsCaptures && input.captureCommand && autoCapture !== "skip") {
|
|
13592
|
+
const captured = runCaptureCommand(input.captureCommand, input.files ?? [], input.__runCapture);
|
|
13593
|
+
if (!captured.ok) return captured;
|
|
13594
|
+
captures = captured.value;
|
|
13595
|
+
}
|
|
13596
|
+
if (mode2 === "deep" && phases.includes("critique") && captures.length === 0) {
|
|
13597
|
+
return Err({
|
|
13598
|
+
message: 'design-craft deep mode critiques rendered screenshots \u2014 supply `captures` ([{ file, image }]) or configure `captureCommand` to render them (the CLI does not render components itself). Or use mode: "fast".'
|
|
13599
|
+
});
|
|
13023
13600
|
}
|
|
13024
13601
|
const provider = input.__testProvider ?? getProvider();
|
|
13025
13602
|
const critiqueTargets = buildTargetsFromFiles(input.files);
|
|
@@ -13031,12 +13608,14 @@ async function runPipeline(input) {
|
|
|
13031
13608
|
const findings = [];
|
|
13032
13609
|
const scores = [];
|
|
13033
13610
|
let rubricsApplied = [];
|
|
13034
|
-
if (phases.includes("critique")
|
|
13611
|
+
if (phases.includes("critique")) {
|
|
13035
13612
|
const rubrics = [...SEED_RUBRICS];
|
|
13036
|
-
|
|
13037
|
-
|
|
13613
|
+
const critiqueFindings = mode2 === "deep" ? await runVisionCritique({ targets: captures, rubrics, provider }) : critiqueTargets.length > 0 ? await runCritique({ targets: critiqueTargets, rubrics, provider }) : [];
|
|
13614
|
+
if (critiqueFindings.length > 0 || mode2 === "deep") {
|
|
13615
|
+
rubricsApplied = rubrics.map((r) => r.id);
|
|
13616
|
+
}
|
|
13038
13617
|
findings.push(...critiqueFindings);
|
|
13039
|
-
if (recordMeasurement) {
|
|
13618
|
+
if (recordMeasurement && critiqueFindings.length > 0) {
|
|
13040
13619
|
for (const rubric of rubrics) recordTrigger(rubric.id, measurementRoot);
|
|
13041
13620
|
for (const f of critiqueFindings) recordSignalEvent(f, measurementRoot, measurementRoot);
|
|
13042
13621
|
}
|
|
@@ -13109,18 +13688,18 @@ async function handleDesignCraft(input) {
|
|
|
13109
13688
|
}
|
|
13110
13689
|
|
|
13111
13690
|
// src/drift/index.ts
|
|
13112
|
-
import * as
|
|
13113
|
-
import * as
|
|
13691
|
+
import * as fs31 from "fs";
|
|
13692
|
+
import * as path33 from "path";
|
|
13114
13693
|
|
|
13115
13694
|
// src/drift/resolvers/tokens.ts
|
|
13116
|
-
import * as
|
|
13117
|
-
import * as
|
|
13695
|
+
import * as fs29 from "fs";
|
|
13696
|
+
import * as path31 from "path";
|
|
13118
13697
|
function loadTokenSet(projectRoot) {
|
|
13119
|
-
const tokenPath =
|
|
13120
|
-
if (!
|
|
13698
|
+
const tokenPath = path31.join(projectRoot, "design-system", "tokens.json");
|
|
13699
|
+
if (!fs29.existsSync(tokenPath)) return null;
|
|
13121
13700
|
let raw;
|
|
13122
13701
|
try {
|
|
13123
|
-
raw =
|
|
13702
|
+
raw = fs29.readFileSync(tokenPath, "utf-8");
|
|
13124
13703
|
} catch {
|
|
13125
13704
|
return null;
|
|
13126
13705
|
}
|
|
@@ -13144,11 +13723,11 @@ function extractTokens(root) {
|
|
|
13144
13723
|
return set;
|
|
13145
13724
|
}
|
|
13146
13725
|
function loadTokenPathIndex(projectRoot) {
|
|
13147
|
-
const tokenPath =
|
|
13148
|
-
if (!
|
|
13726
|
+
const tokenPath = path31.join(projectRoot, "design-system", "tokens.json");
|
|
13727
|
+
if (!fs29.existsSync(tokenPath)) return null;
|
|
13149
13728
|
let raw;
|
|
13150
13729
|
try {
|
|
13151
|
-
raw =
|
|
13730
|
+
raw = fs29.readFileSync(tokenPath, "utf-8");
|
|
13152
13731
|
} catch {
|
|
13153
13732
|
return null;
|
|
13154
13733
|
}
|
|
@@ -13198,9 +13777,9 @@ function walkForPaths(node, breadcrumb, idx) {
|
|
|
13198
13777
|
}
|
|
13199
13778
|
}
|
|
13200
13779
|
}
|
|
13201
|
-
function pushPath(map, key,
|
|
13780
|
+
function pushPath(map, key, path56) {
|
|
13202
13781
|
const list = map.get(key) ?? [];
|
|
13203
|
-
list.push(
|
|
13782
|
+
list.push(path56);
|
|
13204
13783
|
map.set(key, list);
|
|
13205
13784
|
}
|
|
13206
13785
|
function walk(node, breadcrumb, set) {
|
|
@@ -13247,8 +13826,8 @@ function parsePxValue(s) {
|
|
|
13247
13826
|
}
|
|
13248
13827
|
|
|
13249
13828
|
// src/drift/resolvers/component-registry.ts
|
|
13250
|
-
import * as
|
|
13251
|
-
import * as
|
|
13829
|
+
import * as fs30 from "fs";
|
|
13830
|
+
import * as path32 from "path";
|
|
13252
13831
|
var HTML_PRIMITIVE_MAP = {
|
|
13253
13832
|
Button: "button",
|
|
13254
13833
|
Input: "input",
|
|
@@ -13257,11 +13836,11 @@ var HTML_PRIMITIVE_MAP = {
|
|
|
13257
13836
|
Anchor: "a"
|
|
13258
13837
|
};
|
|
13259
13838
|
function loadComponentRegistry(projectRoot) {
|
|
13260
|
-
const designMdPath =
|
|
13261
|
-
if (!
|
|
13839
|
+
const designMdPath = path32.join(projectRoot, "design-system", "DESIGN.md");
|
|
13840
|
+
if (!fs30.existsSync(designMdPath)) return null;
|
|
13262
13841
|
let raw;
|
|
13263
13842
|
try {
|
|
13264
|
-
raw =
|
|
13843
|
+
raw = fs30.readFileSync(designMdPath, "utf-8");
|
|
13265
13844
|
} catch {
|
|
13266
13845
|
return null;
|
|
13267
13846
|
}
|
|
@@ -13598,7 +14177,7 @@ async function runDetectDrift(input) {
|
|
|
13598
14177
|
for (const file of filesToScan) {
|
|
13599
14178
|
let source;
|
|
13600
14179
|
try {
|
|
13601
|
-
source =
|
|
14180
|
+
source = fs31.readFileSync(file, "utf-8");
|
|
13602
14181
|
} catch {
|
|
13603
14182
|
continue;
|
|
13604
14183
|
}
|
|
@@ -13633,7 +14212,7 @@ async function runDetectDrift(input) {
|
|
|
13633
14212
|
}
|
|
13634
14213
|
async function collectFiles(projectRoot, explicitFiles) {
|
|
13635
14214
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
13636
|
-
return explicitFiles.map((f) =>
|
|
14215
|
+
return explicitFiles.map((f) => path33.isAbsolute(f) ? f : path33.join(projectRoot, f));
|
|
13637
14216
|
}
|
|
13638
14217
|
const out = [];
|
|
13639
14218
|
walk2(projectRoot, out, 0);
|
|
@@ -13643,7 +14222,7 @@ function walk2(dir, out, depth) {
|
|
|
13643
14222
|
if (depth > 8) return;
|
|
13644
14223
|
let entries;
|
|
13645
14224
|
try {
|
|
13646
|
-
entries =
|
|
14225
|
+
entries = fs31.readdirSync(dir, { withFileTypes: true });
|
|
13647
14226
|
} catch {
|
|
13648
14227
|
return;
|
|
13649
14228
|
}
|
|
@@ -13651,7 +14230,7 @@ function walk2(dir, out, depth) {
|
|
|
13651
14230
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
13652
14231
|
continue;
|
|
13653
14232
|
}
|
|
13654
|
-
const full =
|
|
14233
|
+
const full = path33.join(dir, entry.name);
|
|
13655
14234
|
if (entry.isDirectory()) {
|
|
13656
14235
|
walk2(full, out, depth + 1);
|
|
13657
14236
|
} else if (entry.isFile() && DEFAULT_GLOB_EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -13719,8 +14298,8 @@ async function handleDetectDrift(input) {
|
|
|
13719
14298
|
}
|
|
13720
14299
|
|
|
13721
14300
|
// src/align/index.ts
|
|
13722
|
-
import * as
|
|
13723
|
-
import * as
|
|
14301
|
+
import * as fs33 from "fs";
|
|
14302
|
+
import * as path36 from "path";
|
|
13724
14303
|
|
|
13725
14304
|
// src/align/classifier/token-import.ts
|
|
13726
14305
|
var NAMED_IMPORT = /^\s*import\s*\{\s*tokens(?:\s+as\s+\w+)?\s*\}\s*from\s+['"][^'"]+['"];?\s*$/m;
|
|
@@ -13860,9 +14439,9 @@ function sourceLine(source, line) {
|
|
|
13860
14439
|
}
|
|
13861
14440
|
|
|
13862
14441
|
// src/align/codemods/common.ts
|
|
13863
|
-
import * as
|
|
14442
|
+
import * as path34 from "path";
|
|
13864
14443
|
function renderTokenReference(file, tokenPath) {
|
|
13865
|
-
const ext =
|
|
14444
|
+
const ext = path34.extname(file).toLowerCase();
|
|
13866
14445
|
if (ext === ".css" || ext === ".scss") {
|
|
13867
14446
|
return `var(--${tokenPath.replace(/\./g, "-")})`;
|
|
13868
14447
|
}
|
|
@@ -14014,8 +14593,8 @@ function emitPrimitiveSuggestion(finding) {
|
|
|
14014
14593
|
}
|
|
14015
14594
|
|
|
14016
14595
|
// src/align/revert/state.ts
|
|
14017
|
-
import * as
|
|
14018
|
-
import * as
|
|
14596
|
+
import * as fs32 from "fs";
|
|
14597
|
+
import * as path35 from "path";
|
|
14019
14598
|
import { createHash as createHash2 } from "crypto";
|
|
14020
14599
|
var LAST_BATCH_PATH = ".harness/align/last-batch.json";
|
|
14021
14600
|
function hashContent(content) {
|
|
@@ -14047,15 +14626,15 @@ function saveLastBatch(projectRoot, outcomes, mode2, fileReader) {
|
|
|
14047
14626
|
mode: mode2,
|
|
14048
14627
|
entries
|
|
14049
14628
|
};
|
|
14050
|
-
const full =
|
|
14051
|
-
|
|
14052
|
-
|
|
14629
|
+
const full = path35.join(projectRoot, LAST_BATCH_PATH);
|
|
14630
|
+
fs32.mkdirSync(path35.dirname(full), { recursive: true });
|
|
14631
|
+
fs32.writeFileSync(full, JSON.stringify(batch, null, 2) + "\n", "utf-8");
|
|
14053
14632
|
}
|
|
14054
14633
|
function loadLastBatch(projectRoot) {
|
|
14055
|
-
const full =
|
|
14056
|
-
if (!
|
|
14634
|
+
const full = path35.join(projectRoot, LAST_BATCH_PATH);
|
|
14635
|
+
if (!fs32.existsSync(full)) return null;
|
|
14057
14636
|
try {
|
|
14058
|
-
const raw =
|
|
14637
|
+
const raw = fs32.readFileSync(full, "utf-8");
|
|
14059
14638
|
const parsed = JSON.parse(raw);
|
|
14060
14639
|
if (parsed.version !== 1 || !Array.isArray(parsed.entries)) return null;
|
|
14061
14640
|
return parsed;
|
|
@@ -14152,7 +14731,7 @@ function persistRunArtifacts(args) {
|
|
|
14152
14731
|
saveLastBatch(args.projectRoot, args.outcomes, args.mode, (file) => {
|
|
14153
14732
|
const cached = args.sourceCache.get(file);
|
|
14154
14733
|
if (cached !== void 0) return cached;
|
|
14155
|
-
return
|
|
14734
|
+
return fs33.readFileSync(file, "utf-8");
|
|
14156
14735
|
});
|
|
14157
14736
|
}
|
|
14158
14737
|
async function runRevert(projectRoot, mode2, dryRun, startedAt) {
|
|
@@ -14218,7 +14797,7 @@ function readForRevert(file, sourceCache) {
|
|
|
14218
14797
|
const cached = sourceCache.get(file);
|
|
14219
14798
|
if (cached !== void 0) return { kind: "ok", text: cached };
|
|
14220
14799
|
try {
|
|
14221
|
-
return { kind: "ok", text:
|
|
14800
|
+
return { kind: "ok", text: fs33.readFileSync(file, "utf-8") };
|
|
14222
14801
|
} catch (err2) {
|
|
14223
14802
|
return {
|
|
14224
14803
|
kind: "failed",
|
|
@@ -14228,7 +14807,7 @@ function readForRevert(file, sourceCache) {
|
|
|
14228
14807
|
}
|
|
14229
14808
|
function tryWrite(file, content) {
|
|
14230
14809
|
try {
|
|
14231
|
-
|
|
14810
|
+
fs33.writeFileSync(file, content, "utf-8");
|
|
14232
14811
|
return null;
|
|
14233
14812
|
} catch (err2) {
|
|
14234
14813
|
return `cannot write source file: ${err2 instanceof Error ? err2.message : String(err2)}`;
|
|
@@ -14236,10 +14815,10 @@ function tryWrite(file, content) {
|
|
|
14236
14815
|
}
|
|
14237
14816
|
async function loadFindings(projectRoot, input, mode2) {
|
|
14238
14817
|
if (mode2 === "pipeline") {
|
|
14239
|
-
const handoffPath =
|
|
14240
|
-
if (!
|
|
14818
|
+
const handoffPath = path36.join(projectRoot, HANDOFF_PATH);
|
|
14819
|
+
if (!fs33.existsSync(handoffPath)) return [];
|
|
14241
14820
|
try {
|
|
14242
|
-
const raw =
|
|
14821
|
+
const raw = fs33.readFileSync(handoffPath, "utf-8");
|
|
14243
14822
|
const parsed = JSON.parse(raw);
|
|
14244
14823
|
return parsed.pipeline?.driftFindings ?? [];
|
|
14245
14824
|
} catch {
|
|
@@ -14264,7 +14843,7 @@ async function processFinding(finding, tokenPaths, sourceCache, dryRun) {
|
|
|
14264
14843
|
}
|
|
14265
14844
|
let source;
|
|
14266
14845
|
try {
|
|
14267
|
-
source = sourceCache.get(finding.file) ??
|
|
14846
|
+
source = sourceCache.get(finding.file) ?? fs33.readFileSync(finding.file, "utf-8");
|
|
14268
14847
|
sourceCache.set(finding.file, source);
|
|
14269
14848
|
} catch (err2) {
|
|
14270
14849
|
return {
|
|
@@ -14292,7 +14871,7 @@ async function processFinding(finding, tokenPaths, sourceCache, dryRun) {
|
|
|
14292
14871
|
}
|
|
14293
14872
|
if (!dryRun) {
|
|
14294
14873
|
try {
|
|
14295
|
-
|
|
14874
|
+
fs33.writeFileSync(finding.file, result.newSource, "utf-8");
|
|
14296
14875
|
sourceCache.set(finding.file, result.newSource);
|
|
14297
14876
|
} catch (err2) {
|
|
14298
14877
|
return {
|
|
@@ -14354,19 +14933,19 @@ function aggregateOutput(outcomes, meta) {
|
|
|
14354
14933
|
};
|
|
14355
14934
|
}
|
|
14356
14935
|
function writePipelineFixesApplied(projectRoot, outcomes) {
|
|
14357
|
-
const handoffPath =
|
|
14936
|
+
const handoffPath = path36.join(projectRoot, HANDOFF_PATH);
|
|
14358
14937
|
let handoff = {};
|
|
14359
|
-
if (
|
|
14938
|
+
if (fs33.existsSync(handoffPath)) {
|
|
14360
14939
|
try {
|
|
14361
|
-
handoff = JSON.parse(
|
|
14940
|
+
handoff = JSON.parse(fs33.readFileSync(handoffPath, "utf-8"));
|
|
14362
14941
|
} catch {
|
|
14363
14942
|
handoff = {};
|
|
14364
14943
|
}
|
|
14365
14944
|
}
|
|
14366
14945
|
handoff.pipeline = handoff.pipeline ?? {};
|
|
14367
14946
|
handoff.pipeline.fixesApplied = outcomes;
|
|
14368
|
-
|
|
14369
|
-
|
|
14947
|
+
fs33.mkdirSync(path36.dirname(handoffPath), { recursive: true });
|
|
14948
|
+
fs33.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + "\n", "utf-8");
|
|
14370
14949
|
}
|
|
14371
14950
|
function findingKey(finding) {
|
|
14372
14951
|
return `${finding.code}@${finding.file}:${finding.line ?? "?"}`;
|
|
@@ -14442,18 +15021,18 @@ async function handleAlignDesignSystem(input) {
|
|
|
14442
15021
|
}
|
|
14443
15022
|
|
|
14444
15023
|
// src/brand/index.ts
|
|
14445
|
-
import * as
|
|
14446
|
-
import * as
|
|
15024
|
+
import * as fs36 from "fs";
|
|
15025
|
+
import * as path39 from "path";
|
|
14447
15026
|
|
|
14448
15027
|
// src/brand/resolvers/design-md-brand.ts
|
|
14449
|
-
import * as
|
|
14450
|
-
import * as
|
|
15028
|
+
import * as fs34 from "fs";
|
|
15029
|
+
import * as path37 from "path";
|
|
14451
15030
|
function loadBrandRules(projectRoot) {
|
|
14452
|
-
const designMdPath =
|
|
14453
|
-
if (!
|
|
15031
|
+
const designMdPath = path37.join(projectRoot, "design-system", "DESIGN.md");
|
|
15032
|
+
if (!fs34.existsSync(designMdPath)) return null;
|
|
14454
15033
|
let raw;
|
|
14455
15034
|
try {
|
|
14456
|
-
raw =
|
|
15035
|
+
raw = fs34.readFileSync(designMdPath, "utf-8");
|
|
14457
15036
|
} catch {
|
|
14458
15037
|
return null;
|
|
14459
15038
|
}
|
|
@@ -14564,14 +15143,14 @@ function parseAssets(block) {
|
|
|
14564
15143
|
}
|
|
14565
15144
|
|
|
14566
15145
|
// src/brand/resolvers/token-extensions.ts
|
|
14567
|
-
import * as
|
|
14568
|
-
import * as
|
|
15146
|
+
import * as fs35 from "fs";
|
|
15147
|
+
import * as path38 from "path";
|
|
14569
15148
|
function loadBrandTokenIndex(projectRoot) {
|
|
14570
|
-
const tokenPath =
|
|
14571
|
-
if (!
|
|
15149
|
+
const tokenPath = path38.join(projectRoot, "design-system", "tokens.json");
|
|
15150
|
+
if (!fs35.existsSync(tokenPath)) return null;
|
|
14572
15151
|
let raw;
|
|
14573
15152
|
try {
|
|
14574
|
-
raw =
|
|
15153
|
+
raw = fs35.readFileSync(tokenPath, "utf-8");
|
|
14575
15154
|
} catch {
|
|
14576
15155
|
return null;
|
|
14577
15156
|
}
|
|
@@ -14820,7 +15399,7 @@ async function runAuditBrand(input) {
|
|
|
14820
15399
|
for (const file of filesToScan) {
|
|
14821
15400
|
let source;
|
|
14822
15401
|
try {
|
|
14823
|
-
source =
|
|
15402
|
+
source = fs36.readFileSync(file, "utf-8");
|
|
14824
15403
|
} catch {
|
|
14825
15404
|
continue;
|
|
14826
15405
|
}
|
|
@@ -14862,7 +15441,7 @@ async function runAuditBrand(input) {
|
|
|
14862
15441
|
}
|
|
14863
15442
|
function collectFiles2(projectRoot, explicitFiles) {
|
|
14864
15443
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
14865
|
-
return explicitFiles.map((f) =>
|
|
15444
|
+
return explicitFiles.map((f) => path39.isAbsolute(f) ? f : path39.join(projectRoot, f));
|
|
14866
15445
|
}
|
|
14867
15446
|
const out = [];
|
|
14868
15447
|
walk4(projectRoot, out, 0);
|
|
@@ -14872,7 +15451,7 @@ function walk4(dir, out, depth) {
|
|
|
14872
15451
|
if (depth > 8) return;
|
|
14873
15452
|
let entries;
|
|
14874
15453
|
try {
|
|
14875
|
-
entries =
|
|
15454
|
+
entries = fs36.readdirSync(dir, { withFileTypes: true });
|
|
14876
15455
|
} catch {
|
|
14877
15456
|
return;
|
|
14878
15457
|
}
|
|
@@ -14880,7 +15459,7 @@ function walk4(dir, out, depth) {
|
|
|
14880
15459
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
14881
15460
|
continue;
|
|
14882
15461
|
}
|
|
14883
|
-
const full =
|
|
15462
|
+
const full = path39.join(dir, entry.name);
|
|
14884
15463
|
if (entry.isDirectory()) {
|
|
14885
15464
|
walk4(full, out, depth + 1);
|
|
14886
15465
|
} else if (entry.isFile() && DEFAULT_GLOB_EXTENSIONS2.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -14998,15 +15577,15 @@ var VerifierRegistry = class {
|
|
|
14998
15577
|
};
|
|
14999
15578
|
|
|
15000
15579
|
// src/design-pipeline/phases/freshen.ts
|
|
15001
|
-
import * as
|
|
15002
|
-
import * as
|
|
15580
|
+
import * as fs37 from "fs";
|
|
15581
|
+
import * as path40 from "path";
|
|
15003
15582
|
function runFreshen(input) {
|
|
15004
15583
|
const { projectRoot, context } = input;
|
|
15005
|
-
context.graphAvailable =
|
|
15006
|
-
const designMdPath =
|
|
15007
|
-
const tokensJsonPath =
|
|
15008
|
-
context.inputs.designMdExists =
|
|
15009
|
-
context.inputs.tokensJsonExists =
|
|
15584
|
+
context.graphAvailable = fs37.existsSync(path40.join(projectRoot, ".harness", "graph"));
|
|
15585
|
+
const designMdPath = path40.join(projectRoot, "design-system", "DESIGN.md");
|
|
15586
|
+
const tokensJsonPath = path40.join(projectRoot, "design-system", "tokens.json");
|
|
15587
|
+
context.inputs.designMdExists = fs37.existsSync(designMdPath);
|
|
15588
|
+
context.inputs.tokensJsonExists = fs37.existsSync(tokensJsonPath);
|
|
15010
15589
|
if (context.inputs.designMdExists) {
|
|
15011
15590
|
const content = readFileSafe(designMdPath);
|
|
15012
15591
|
context.inputs.componentRegistryExists = content !== null && /^##\s+component\s+registry\b/im.test(content);
|
|
@@ -15015,7 +15594,7 @@ function runFreshen(input) {
|
|
|
15015
15594
|
}
|
|
15016
15595
|
function readFileSafe(p) {
|
|
15017
15596
|
try {
|
|
15018
|
-
return
|
|
15597
|
+
return fs37.readFileSync(p, "utf-8");
|
|
15019
15598
|
} catch {
|
|
15020
15599
|
return null;
|
|
15021
15600
|
}
|
|
@@ -15042,8 +15621,8 @@ async function runDetect(input) {
|
|
|
15042
15621
|
}
|
|
15043
15622
|
|
|
15044
15623
|
// src/design-pipeline/phases/fix.ts
|
|
15045
|
-
import * as
|
|
15046
|
-
import * as
|
|
15624
|
+
import * as fs38 from "fs";
|
|
15625
|
+
import * as path41 from "path";
|
|
15047
15626
|
var MAX_ITERATIONS = 5;
|
|
15048
15627
|
async function runFix(input) {
|
|
15049
15628
|
const { projectRoot, context, mode: mode2, files, designStrictness } = input;
|
|
@@ -15092,11 +15671,11 @@ async function safelyRedetect(projectRoot, mode2, files, designStrictness) {
|
|
|
15092
15671
|
}
|
|
15093
15672
|
}
|
|
15094
15673
|
function writePipelineHandoff(projectRoot, context) {
|
|
15095
|
-
const handoffPath =
|
|
15674
|
+
const handoffPath = path41.join(projectRoot, ".harness", "handoff.json");
|
|
15096
15675
|
let handoff = {};
|
|
15097
|
-
if (
|
|
15676
|
+
if (fs38.existsSync(handoffPath)) {
|
|
15098
15677
|
try {
|
|
15099
|
-
handoff = JSON.parse(
|
|
15678
|
+
handoff = JSON.parse(fs38.readFileSync(handoffPath, "utf-8"));
|
|
15100
15679
|
} catch {
|
|
15101
15680
|
handoff = {};
|
|
15102
15681
|
}
|
|
@@ -15104,8 +15683,8 @@ function writePipelineHandoff(projectRoot, context) {
|
|
|
15104
15683
|
handoff.pipeline = {
|
|
15105
15684
|
driftFindings: context.driftFindings
|
|
15106
15685
|
};
|
|
15107
|
-
|
|
15108
|
-
|
|
15686
|
+
fs38.mkdirSync(path41.dirname(handoffPath), { recursive: true });
|
|
15687
|
+
fs38.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + "\n");
|
|
15109
15688
|
}
|
|
15110
15689
|
|
|
15111
15690
|
// src/design-pipeline/phases/audit.ts
|
|
@@ -15135,8 +15714,8 @@ async function runAudit2(input) {
|
|
|
15135
15714
|
}
|
|
15136
15715
|
|
|
15137
15716
|
// src/design-pipeline/phases/fill.ts
|
|
15138
|
-
import * as
|
|
15139
|
-
import * as
|
|
15717
|
+
import * as fs39 from "fs";
|
|
15718
|
+
import * as path42 from "path";
|
|
15140
15719
|
var DESIGN_MD_STUB = `# Design System
|
|
15141
15720
|
|
|
15142
15721
|
<!-- TODO: author this file. See harness-design skill for guidance. -->
|
|
@@ -15196,11 +15775,11 @@ async function runFill(input) {
|
|
|
15196
15775
|
}
|
|
15197
15776
|
function bootstrapMissingInputs(input) {
|
|
15198
15777
|
const { projectRoot, context } = input;
|
|
15199
|
-
const designMdPath =
|
|
15200
|
-
const tokensJsonPath =
|
|
15201
|
-
if (!
|
|
15202
|
-
|
|
15203
|
-
|
|
15778
|
+
const designMdPath = path42.join(projectRoot, "design-system", "DESIGN.md");
|
|
15779
|
+
const tokensJsonPath = path42.join(projectRoot, "design-system", "tokens.json");
|
|
15780
|
+
if (!fs39.existsSync(designMdPath)) {
|
|
15781
|
+
fs39.mkdirSync(path42.dirname(designMdPath), { recursive: true });
|
|
15782
|
+
fs39.writeFileSync(designMdPath, DESIGN_MD_STUB);
|
|
15204
15783
|
context.bootstrapped.designMd = true;
|
|
15205
15784
|
context.inputs.designMdExists = true;
|
|
15206
15785
|
context.inputs.componentRegistryExists = true;
|
|
@@ -15220,16 +15799,16 @@ function bootstrapMissingInputs(input) {
|
|
|
15220
15799
|
}
|
|
15221
15800
|
}
|
|
15222
15801
|
}
|
|
15223
|
-
if (!
|
|
15224
|
-
|
|
15225
|
-
|
|
15802
|
+
if (!fs39.existsSync(tokensJsonPath)) {
|
|
15803
|
+
fs39.mkdirSync(path42.dirname(tokensJsonPath), { recursive: true });
|
|
15804
|
+
fs39.writeFileSync(tokensJsonPath, TOKENS_JSON_STUB);
|
|
15226
15805
|
context.bootstrapped.tokensJson = true;
|
|
15227
15806
|
context.inputs.tokensJsonExists = true;
|
|
15228
15807
|
}
|
|
15229
15808
|
}
|
|
15230
15809
|
function readFileSafe2(p) {
|
|
15231
15810
|
try {
|
|
15232
|
-
return
|
|
15811
|
+
return fs39.readFileSync(p, "utf-8");
|
|
15233
15812
|
} catch {
|
|
15234
15813
|
return null;
|
|
15235
15814
|
}
|
|
@@ -15264,10 +15843,10 @@ async function invokeCraftPolish(input) {
|
|
|
15264
15843
|
function appendToFile(p, content) {
|
|
15265
15844
|
let existing = "";
|
|
15266
15845
|
try {
|
|
15267
|
-
existing =
|
|
15846
|
+
existing = fs39.readFileSync(p, "utf-8");
|
|
15268
15847
|
} catch {
|
|
15269
15848
|
}
|
|
15270
|
-
|
|
15849
|
+
fs39.writeFileSync(p, existing + content);
|
|
15271
15850
|
}
|
|
15272
15851
|
|
|
15273
15852
|
// src/design-pipeline/phases/report.ts
|
|
@@ -15416,8 +15995,8 @@ async function handleDesignPipeline(input) {
|
|
|
15416
15995
|
}
|
|
15417
15996
|
|
|
15418
15997
|
// src/naming-craft/index.ts
|
|
15419
|
-
import * as
|
|
15420
|
-
import * as
|
|
15998
|
+
import * as fs41 from "fs";
|
|
15999
|
+
import * as path44 from "path";
|
|
15421
16000
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
15422
16001
|
|
|
15423
16002
|
// src/naming-craft/extract/identifiers.ts
|
|
@@ -15741,27 +16320,27 @@ function isConfidence4(v) {
|
|
|
15741
16320
|
}
|
|
15742
16321
|
|
|
15743
16322
|
// src/shared/craft/runs/store.ts
|
|
15744
|
-
import * as
|
|
15745
|
-
import * as
|
|
16323
|
+
import * as fs40 from "fs";
|
|
16324
|
+
import * as path43 from "path";
|
|
15746
16325
|
var RUN_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
15747
16326
|
function runsDir(projectRoot) {
|
|
15748
|
-
return
|
|
16327
|
+
return path43.join(projectRoot, ".harness", "craft", "runs");
|
|
15749
16328
|
}
|
|
15750
16329
|
function runPath(projectRoot, runId) {
|
|
15751
|
-
return
|
|
16330
|
+
return path43.join(runsDir(projectRoot), `${runId}.json`);
|
|
15752
16331
|
}
|
|
15753
16332
|
function saveRunState(projectRoot, state) {
|
|
15754
16333
|
const dir = runsDir(projectRoot);
|
|
15755
|
-
|
|
16334
|
+
fs40.mkdirSync(dir, { recursive: true });
|
|
15756
16335
|
const file = runPath(projectRoot, state.runId);
|
|
15757
|
-
|
|
16336
|
+
fs40.writeFileSync(file, JSON.stringify(state, null, 2));
|
|
15758
16337
|
return { runFile: file };
|
|
15759
16338
|
}
|
|
15760
16339
|
function loadRunState(projectRoot, runId) {
|
|
15761
16340
|
const file = runPath(projectRoot, runId);
|
|
15762
|
-
if (!
|
|
16341
|
+
if (!fs40.existsSync(file)) return null;
|
|
15763
16342
|
try {
|
|
15764
|
-
const raw =
|
|
16343
|
+
const raw = fs40.readFileSync(file, "utf-8");
|
|
15765
16344
|
return JSON.parse(raw);
|
|
15766
16345
|
} catch {
|
|
15767
16346
|
return null;
|
|
@@ -15770,7 +16349,7 @@ function loadRunState(projectRoot, runId) {
|
|
|
15770
16349
|
function deleteRunState(projectRoot, runId) {
|
|
15771
16350
|
const file = runPath(projectRoot, runId);
|
|
15772
16351
|
try {
|
|
15773
|
-
|
|
16352
|
+
fs40.unlinkSync(file);
|
|
15774
16353
|
} catch {
|
|
15775
16354
|
}
|
|
15776
16355
|
}
|
|
@@ -15778,16 +16357,16 @@ function pruneOldRuns(projectRoot, now = Date.now()) {
|
|
|
15778
16357
|
const dir = runsDir(projectRoot);
|
|
15779
16358
|
let entries;
|
|
15780
16359
|
try {
|
|
15781
|
-
entries =
|
|
16360
|
+
entries = fs40.readdirSync(dir, { withFileTypes: true });
|
|
15782
16361
|
} catch {
|
|
15783
16362
|
return;
|
|
15784
16363
|
}
|
|
15785
16364
|
for (const entry of entries) {
|
|
15786
16365
|
if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
|
|
15787
|
-
const full =
|
|
16366
|
+
const full = path43.join(dir, entry.name);
|
|
15788
16367
|
try {
|
|
15789
|
-
const stat =
|
|
15790
|
-
if (now - stat.mtimeMs > RUN_TTL_MS)
|
|
16368
|
+
const stat = fs40.statSync(full);
|
|
16369
|
+
if (now - stat.mtimeMs > RUN_TTL_MS) fs40.unlinkSync(full);
|
|
15791
16370
|
} catch {
|
|
15792
16371
|
}
|
|
15793
16372
|
}
|
|
@@ -15807,7 +16386,7 @@ function gatherProjectContext(input) {
|
|
|
15807
16386
|
for (const file of files) {
|
|
15808
16387
|
let source;
|
|
15809
16388
|
try {
|
|
15810
|
-
source =
|
|
16389
|
+
source = fs41.readFileSync(file, "utf-8");
|
|
15811
16390
|
} catch {
|
|
15812
16391
|
continue;
|
|
15813
16392
|
}
|
|
@@ -15964,7 +16543,7 @@ async function finalizeNamingCraft(input) {
|
|
|
15964
16543
|
}
|
|
15965
16544
|
function collectFiles3(projectRoot, explicitFiles) {
|
|
15966
16545
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
15967
|
-
return explicitFiles.map((f) =>
|
|
16546
|
+
return explicitFiles.map((f) => path44.isAbsolute(f) ? f : path44.join(projectRoot, f));
|
|
15968
16547
|
}
|
|
15969
16548
|
const out = [];
|
|
15970
16549
|
walk5(projectRoot, out, 0);
|
|
@@ -15974,7 +16553,7 @@ function walk5(dir, out, depth) {
|
|
|
15974
16553
|
if (depth > 8) return;
|
|
15975
16554
|
let entries;
|
|
15976
16555
|
try {
|
|
15977
|
-
entries =
|
|
16556
|
+
entries = fs41.readdirSync(dir, { withFileTypes: true });
|
|
15978
16557
|
} catch {
|
|
15979
16558
|
return;
|
|
15980
16559
|
}
|
|
@@ -15982,7 +16561,7 @@ function walk5(dir, out, depth) {
|
|
|
15982
16561
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
15983
16562
|
continue;
|
|
15984
16563
|
}
|
|
15985
|
-
const full =
|
|
16564
|
+
const full = path44.join(dir, entry.name);
|
|
15986
16565
|
if (entry.isDirectory()) walk5(full, out, depth + 1);
|
|
15987
16566
|
else if (entry.isFile() && EXTENSIONS.some((ext) => entry.name.endsWith(ext))) out.push(full);
|
|
15988
16567
|
}
|
|
@@ -16166,32 +16745,32 @@ async function handleNamingCraftFinalize(input) {
|
|
|
16166
16745
|
}
|
|
16167
16746
|
|
|
16168
16747
|
// src/spec-craft/index.ts
|
|
16169
|
-
import * as
|
|
16748
|
+
import * as fs43 from "fs";
|
|
16170
16749
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
16171
16750
|
|
|
16172
16751
|
// src/spec-craft/extract/discover.ts
|
|
16173
|
-
import * as
|
|
16174
|
-
import * as
|
|
16752
|
+
import * as fs42 from "fs";
|
|
16753
|
+
import * as path45 from "path";
|
|
16175
16754
|
function discoverSpecs(projectRoot, kindsFilter) {
|
|
16176
16755
|
const wantProposal = kindsFilter === void 0 || kindsFilter.includes("proposal");
|
|
16177
16756
|
const wantADR = kindsFilter === void 0 || kindsFilter.includes("adr");
|
|
16178
16757
|
const out = [];
|
|
16179
16758
|
if (wantProposal) {
|
|
16180
|
-
const changesDir =
|
|
16181
|
-
if (
|
|
16182
|
-
for (const entry of
|
|
16759
|
+
const changesDir = path45.join(projectRoot, "docs", "changes");
|
|
16760
|
+
if (fs42.existsSync(changesDir)) {
|
|
16761
|
+
for (const entry of fs42.readdirSync(changesDir, { withFileTypes: true })) {
|
|
16183
16762
|
if (!entry.isDirectory()) continue;
|
|
16184
16763
|
if (entry.name.startsWith(".")) continue;
|
|
16185
|
-
const topicDir =
|
|
16186
|
-
const direct =
|
|
16187
|
-
if (
|
|
16764
|
+
const topicDir = path45.join(changesDir, entry.name);
|
|
16765
|
+
const direct = path45.join(topicDir, "proposal.md");
|
|
16766
|
+
if (fs42.existsSync(direct)) {
|
|
16188
16767
|
out.push({ file: direct, kind: "proposal" });
|
|
16189
16768
|
}
|
|
16190
|
-
for (const sub of
|
|
16769
|
+
for (const sub of fs42.readdirSync(topicDir, { withFileTypes: true })) {
|
|
16191
16770
|
if (!sub.isDirectory()) continue;
|
|
16192
16771
|
if (sub.name.startsWith(".")) continue;
|
|
16193
|
-
const subProposal =
|
|
16194
|
-
if (
|
|
16772
|
+
const subProposal = path45.join(topicDir, sub.name, "proposal.md");
|
|
16773
|
+
if (fs42.existsSync(subProposal)) {
|
|
16195
16774
|
out.push({ file: subProposal, kind: "proposal" });
|
|
16196
16775
|
}
|
|
16197
16776
|
}
|
|
@@ -16199,13 +16778,13 @@ function discoverSpecs(projectRoot, kindsFilter) {
|
|
|
16199
16778
|
}
|
|
16200
16779
|
}
|
|
16201
16780
|
if (wantADR) {
|
|
16202
|
-
const decisionsDir =
|
|
16203
|
-
if (
|
|
16204
|
-
for (const entry of
|
|
16781
|
+
const decisionsDir = path45.join(projectRoot, "docs", "knowledge", "decisions");
|
|
16782
|
+
if (fs42.existsSync(decisionsDir)) {
|
|
16783
|
+
for (const entry of fs42.readdirSync(decisionsDir, { withFileTypes: true })) {
|
|
16205
16784
|
if (!entry.isFile()) continue;
|
|
16206
16785
|
if (!entry.name.endsWith(".md")) continue;
|
|
16207
16786
|
if (entry.name.startsWith(".") || entry.name.toUpperCase() === "README.MD") continue;
|
|
16208
|
-
out.push({ file:
|
|
16787
|
+
out.push({ file: path45.join(decisionsDir, entry.name), kind: "adr" });
|
|
16209
16788
|
}
|
|
16210
16789
|
}
|
|
16211
16790
|
}
|
|
@@ -16453,7 +17032,7 @@ async function runSpecCraft(input) {
|
|
|
16453
17032
|
for (const spec of specs) {
|
|
16454
17033
|
let source;
|
|
16455
17034
|
try {
|
|
16456
|
-
source =
|
|
17035
|
+
source = fs43.readFileSync(spec.file, "utf-8");
|
|
16457
17036
|
} catch {
|
|
16458
17037
|
continue;
|
|
16459
17038
|
}
|
|
@@ -16573,13 +17152,13 @@ async function handleSpecCraft(input) {
|
|
|
16573
17152
|
}
|
|
16574
17153
|
|
|
16575
17154
|
// src/copy-craft/index.ts
|
|
16576
|
-
import * as
|
|
16577
|
-
import * as
|
|
17155
|
+
import * as fs45 from "fs";
|
|
17156
|
+
import * as path48 from "path";
|
|
16578
17157
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
16579
17158
|
|
|
16580
17159
|
// src/copy-craft/extract/source.ts
|
|
16581
17160
|
import ts5 from "typescript";
|
|
16582
|
-
import * as
|
|
17161
|
+
import * as path46 from "path";
|
|
16583
17162
|
var LOG_LEVELS = ["log", "info", "warn", "warning", "error", "debug", "trace", "fatal"];
|
|
16584
17163
|
function extractFromSource(input) {
|
|
16585
17164
|
if (!/\.(?:ts|tsx|js|jsx)$/i.test(input.file)) return [];
|
|
@@ -16719,7 +17298,7 @@ function isLikelyLogger(name) {
|
|
|
16719
17298
|
}
|
|
16720
17299
|
function fileMatchesCliPaths(file, cliPaths) {
|
|
16721
17300
|
const paths = cliPaths ?? defaultCliOutputPaths();
|
|
16722
|
-
const normalized = file.split(
|
|
17301
|
+
const normalized = file.split(path46.sep).join("/");
|
|
16723
17302
|
return paths.some((p) => normalized.includes(p));
|
|
16724
17303
|
}
|
|
16725
17304
|
function defaultCliOutputPaths() {
|
|
@@ -16739,8 +17318,8 @@ function cleanCommentText(raw) {
|
|
|
16739
17318
|
|
|
16740
17319
|
// src/copy-craft/extract/commits.ts
|
|
16741
17320
|
import { spawnSync } from "child_process";
|
|
16742
|
-
import * as
|
|
16743
|
-
import * as
|
|
17321
|
+
import * as fs44 from "fs";
|
|
17322
|
+
import * as path47 from "path";
|
|
16744
17323
|
var GIT_TIMEOUT_MS = 1e4;
|
|
16745
17324
|
function extractCommits(input) {
|
|
16746
17325
|
const { projectRoot, since = "1 month ago", limit = 100 } = input;
|
|
@@ -16779,8 +17358,8 @@ function extractCommits(input) {
|
|
|
16779
17358
|
function isGitRepo(projectRoot) {
|
|
16780
17359
|
let dir = projectRoot;
|
|
16781
17360
|
for (let i = 0; i < 10; i++) {
|
|
16782
|
-
if (
|
|
16783
|
-
const parent =
|
|
17361
|
+
if (fs44.existsSync(path47.join(dir, ".git"))) return true;
|
|
17362
|
+
const parent = path47.dirname(dir);
|
|
16784
17363
|
if (parent === dir) break;
|
|
16785
17364
|
dir = parent;
|
|
16786
17365
|
}
|
|
@@ -17086,7 +17665,7 @@ async function runCopyCraft(input) {
|
|
|
17086
17665
|
for (const file of files) {
|
|
17087
17666
|
let source;
|
|
17088
17667
|
try {
|
|
17089
|
-
source =
|
|
17668
|
+
source = fs45.readFileSync(file, "utf-8");
|
|
17090
17669
|
} catch {
|
|
17091
17670
|
continue;
|
|
17092
17671
|
}
|
|
@@ -17170,7 +17749,7 @@ async function runCopyCraft(input) {
|
|
|
17170
17749
|
}
|
|
17171
17750
|
function collectSourceFiles(projectRoot, explicitFiles) {
|
|
17172
17751
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
17173
|
-
return explicitFiles.map((f) =>
|
|
17752
|
+
return explicitFiles.map((f) => path48.isAbsolute(f) ? f : path48.join(projectRoot, f));
|
|
17174
17753
|
}
|
|
17175
17754
|
const out = [];
|
|
17176
17755
|
walk6(projectRoot, out, 0);
|
|
@@ -17180,7 +17759,7 @@ function walk6(dir, out, depth) {
|
|
|
17180
17759
|
if (depth > 8) return;
|
|
17181
17760
|
let entries;
|
|
17182
17761
|
try {
|
|
17183
|
-
entries =
|
|
17762
|
+
entries = fs45.readdirSync(dir, { withFileTypes: true });
|
|
17184
17763
|
} catch {
|
|
17185
17764
|
return;
|
|
17186
17765
|
}
|
|
@@ -17188,7 +17767,7 @@ function walk6(dir, out, depth) {
|
|
|
17188
17767
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
17189
17768
|
continue;
|
|
17190
17769
|
}
|
|
17191
|
-
const full =
|
|
17770
|
+
const full = path48.join(dir, entry.name);
|
|
17192
17771
|
if (entry.isDirectory()) walk6(full, out, depth + 1);
|
|
17193
17772
|
else if (entry.isFile() && SOURCE_EXTENSIONS.some((ext) => entry.name.endsWith(ext)))
|
|
17194
17773
|
out.push(full);
|
|
@@ -17258,8 +17837,8 @@ async function handleCopyCraft(input) {
|
|
|
17258
17837
|
}
|
|
17259
17838
|
|
|
17260
17839
|
// src/test-craft/index.ts
|
|
17261
|
-
import * as
|
|
17262
|
-
import * as
|
|
17840
|
+
import * as fs47 from "fs";
|
|
17841
|
+
import * as path50 from "path";
|
|
17263
17842
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
17264
17843
|
|
|
17265
17844
|
// src/test-craft/extract/framework.ts
|
|
@@ -17365,25 +17944,25 @@ function extractCallbackText(node, fullSource) {
|
|
|
17365
17944
|
}
|
|
17366
17945
|
|
|
17367
17946
|
// src/test-craft/extract/source-pair.ts
|
|
17368
|
-
import * as
|
|
17369
|
-
import * as
|
|
17947
|
+
import * as fs46 from "fs";
|
|
17948
|
+
import * as path49 from "path";
|
|
17370
17949
|
var MAX_SOURCE_CHARS = 2e3;
|
|
17371
17950
|
function resolveSourceFile(testFile) {
|
|
17372
|
-
const dir =
|
|
17951
|
+
const dir = path49.dirname(testFile);
|
|
17373
17952
|
const ext = /\.tsx$/.test(testFile) ? ".tsx" : ".ts";
|
|
17374
|
-
const base =
|
|
17953
|
+
const base = path49.basename(testFile).replace(/\.(?:test|spec)\.(?:tsx?|jsx?)$/, "");
|
|
17375
17954
|
const candidates = [
|
|
17376
17955
|
// Sibling (most common in co-located setups)
|
|
17377
|
-
|
|
17956
|
+
path49.join(dir, base + ext),
|
|
17378
17957
|
// tests/ peer to src/
|
|
17379
|
-
|
|
17958
|
+
path49.join(dir, "..", "src", base + ext),
|
|
17380
17959
|
// tests/ one level deeper than src/
|
|
17381
|
-
|
|
17960
|
+
path49.join(dir, "..", "..", "src", base + ext)
|
|
17382
17961
|
];
|
|
17383
17962
|
for (const candidate of candidates) {
|
|
17384
|
-
if (
|
|
17963
|
+
if (fs46.existsSync(candidate)) {
|
|
17385
17964
|
try {
|
|
17386
|
-
const raw =
|
|
17965
|
+
const raw = fs46.readFileSync(candidate, "utf-8");
|
|
17387
17966
|
const content = raw.length > MAX_SOURCE_CHARS ? raw.slice(0, MAX_SOURCE_CHARS) + "\n[\u2026truncated for prompt cost\u2026]" : raw;
|
|
17388
17967
|
return { file: candidate, content };
|
|
17389
17968
|
} catch {
|
|
@@ -17619,7 +18198,7 @@ async function runTestCraft(input) {
|
|
|
17619
18198
|
for (const file of files) {
|
|
17620
18199
|
let source;
|
|
17621
18200
|
try {
|
|
17622
|
-
source =
|
|
18201
|
+
source = fs47.readFileSync(file, "utf-8");
|
|
17623
18202
|
} catch {
|
|
17624
18203
|
continue;
|
|
17625
18204
|
}
|
|
@@ -17684,7 +18263,7 @@ async function runTestCraft(input) {
|
|
|
17684
18263
|
}
|
|
17685
18264
|
function collectTestFiles(projectRoot, explicitFiles) {
|
|
17686
18265
|
if (explicitFiles !== void 0 && explicitFiles.length > 0) {
|
|
17687
|
-
return explicitFiles.map((f) =>
|
|
18266
|
+
return explicitFiles.map((f) => path50.isAbsolute(f) ? f : path50.join(projectRoot, f));
|
|
17688
18267
|
}
|
|
17689
18268
|
const out = [];
|
|
17690
18269
|
walk7(projectRoot, out, 0);
|
|
@@ -17694,7 +18273,7 @@ function walk7(dir, out, depth) {
|
|
|
17694
18273
|
if (depth > 10) return;
|
|
17695
18274
|
let entries;
|
|
17696
18275
|
try {
|
|
17697
|
-
entries =
|
|
18276
|
+
entries = fs47.readdirSync(dir, { withFileTypes: true });
|
|
17698
18277
|
} catch {
|
|
17699
18278
|
return;
|
|
17700
18279
|
}
|
|
@@ -17702,7 +18281,7 @@ function walk7(dir, out, depth) {
|
|
|
17702
18281
|
if (entry.name.startsWith(".") || entry.name === "node_modules" || entry.name === "dist" || entry.name === "build" || entry.name === "coverage") {
|
|
17703
18282
|
continue;
|
|
17704
18283
|
}
|
|
17705
|
-
const full =
|
|
18284
|
+
const full = path50.join(dir, entry.name);
|
|
17706
18285
|
if (entry.isDirectory()) walk7(full, out, depth + 1);
|
|
17707
18286
|
else if (entry.isFile() && TEST_FILE_EXTS.some((ext) => entry.name.endsWith(ext))) {
|
|
17708
18287
|
out.push(full);
|
|
@@ -17775,18 +18354,18 @@ async function handleTestCraft(input) {
|
|
|
17775
18354
|
}
|
|
17776
18355
|
|
|
17777
18356
|
// src/knowledge-craft/index.ts
|
|
17778
|
-
import * as
|
|
17779
|
-
import * as
|
|
18357
|
+
import * as fs49 from "fs";
|
|
18358
|
+
import * as path52 from "path";
|
|
17780
18359
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
17781
18360
|
|
|
17782
18361
|
// src/knowledge-craft/extract/discover.ts
|
|
17783
|
-
import * as
|
|
17784
|
-
import * as
|
|
18362
|
+
import * as fs48 from "fs";
|
|
18363
|
+
import * as path51 from "path";
|
|
17785
18364
|
var KNOWLEDGE_ROOT = "docs/knowledge";
|
|
17786
18365
|
var DEFAULT_EXCLUDED_DIRS = ["decisions"];
|
|
17787
18366
|
function discoverKnowledgeEntries(projectRoot, extraExcludeDirs) {
|
|
17788
|
-
const root =
|
|
17789
|
-
if (!
|
|
18367
|
+
const root = path51.join(projectRoot, KNOWLEDGE_ROOT);
|
|
18368
|
+
if (!fs48.existsSync(root)) return [];
|
|
17790
18369
|
const exclude = /* @__PURE__ */ new Set([...DEFAULT_EXCLUDED_DIRS, ...extraExcludeDirs ?? []]);
|
|
17791
18370
|
const out = [];
|
|
17792
18371
|
walk8(root, root, out, exclude);
|
|
@@ -17795,13 +18374,13 @@ function discoverKnowledgeEntries(projectRoot, extraExcludeDirs) {
|
|
|
17795
18374
|
function walk8(dir, root, out, exclude) {
|
|
17796
18375
|
let entries;
|
|
17797
18376
|
try {
|
|
17798
|
-
entries =
|
|
18377
|
+
entries = fs48.readdirSync(dir, { withFileTypes: true });
|
|
17799
18378
|
} catch {
|
|
17800
18379
|
return;
|
|
17801
18380
|
}
|
|
17802
18381
|
for (const entry of entries) {
|
|
17803
18382
|
if (entry.name.startsWith(".")) continue;
|
|
17804
|
-
const full =
|
|
18383
|
+
const full = path51.join(dir, entry.name);
|
|
17805
18384
|
if (entry.isDirectory()) {
|
|
17806
18385
|
if (exclude.has(entry.name)) continue;
|
|
17807
18386
|
walk8(full, root, out, exclude);
|
|
@@ -17810,7 +18389,7 @@ function walk8(dir, root, out, exclude) {
|
|
|
17810
18389
|
if (!entry.isFile()) continue;
|
|
17811
18390
|
if (!entry.name.endsWith(".md")) continue;
|
|
17812
18391
|
if (entry.name.toLowerCase() === "readme.md") continue;
|
|
17813
|
-
const rel =
|
|
18392
|
+
const rel = path51.relative(root, full).replaceAll("\\", "/");
|
|
17814
18393
|
out.push({ file: full, relative: rel });
|
|
17815
18394
|
}
|
|
17816
18395
|
}
|
|
@@ -17990,7 +18569,7 @@ async function runKnowledgeCraft(input) {
|
|
|
17990
18569
|
for (const entry of entries) {
|
|
17991
18570
|
let content;
|
|
17992
18571
|
try {
|
|
17993
|
-
content =
|
|
18572
|
+
content = fs49.readFileSync(entry.file, "utf-8");
|
|
17994
18573
|
} catch {
|
|
17995
18574
|
filesSkipped++;
|
|
17996
18575
|
continue;
|
|
@@ -18031,10 +18610,10 @@ async function runKnowledgeCraft(input) {
|
|
|
18031
18610
|
}
|
|
18032
18611
|
function collectEntries(projectRoot, input) {
|
|
18033
18612
|
if (input.files !== void 0 && input.files.length > 0) {
|
|
18034
|
-
const root =
|
|
18613
|
+
const root = path52.join(projectRoot, KNOWLEDGE_ROOT);
|
|
18035
18614
|
return input.files.map((f) => ({
|
|
18036
18615
|
file: f,
|
|
18037
|
-
relative:
|
|
18616
|
+
relative: path52.relative(root, f).replaceAll("\\", "/")
|
|
18038
18617
|
}));
|
|
18039
18618
|
}
|
|
18040
18619
|
return discoverKnowledgeEntries(projectRoot, input.excludeDirs);
|
|
@@ -18096,12 +18675,12 @@ async function handleKnowledgeCraft(input) {
|
|
|
18096
18675
|
}
|
|
18097
18676
|
|
|
18098
18677
|
// src/security-craft/index.ts
|
|
18099
|
-
import * as
|
|
18678
|
+
import * as fs51 from "fs";
|
|
18100
18679
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
18101
18680
|
|
|
18102
18681
|
// src/security-craft/extract/discover.ts
|
|
18103
|
-
import * as
|
|
18104
|
-
import * as
|
|
18682
|
+
import * as fs50 from "fs";
|
|
18683
|
+
import * as path53 from "path";
|
|
18105
18684
|
var SOURCE_EXTENSIONS2 = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
18106
18685
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
18107
18686
|
"node_modules",
|
|
@@ -18121,12 +18700,12 @@ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([
|
|
|
18121
18700
|
]);
|
|
18122
18701
|
var TEST_FILE_PATTERN = /\.(test|spec)\.(ts|tsx|js|jsx|mjs|cjs)$/i;
|
|
18123
18702
|
function discoverSourceFiles(projectRoot, packagesFilter) {
|
|
18124
|
-
const packagesDir =
|
|
18125
|
-
if (!
|
|
18703
|
+
const packagesDir = path53.join(projectRoot, "packages");
|
|
18704
|
+
if (!fs50.existsSync(packagesDir)) return [];
|
|
18126
18705
|
const out = [];
|
|
18127
18706
|
let entries;
|
|
18128
18707
|
try {
|
|
18129
|
-
entries =
|
|
18708
|
+
entries = fs50.readdirSync(packagesDir, { withFileTypes: true });
|
|
18130
18709
|
} catch {
|
|
18131
18710
|
return [];
|
|
18132
18711
|
}
|
|
@@ -18134,8 +18713,8 @@ function discoverSourceFiles(projectRoot, packagesFilter) {
|
|
|
18134
18713
|
if (!entry.isDirectory()) continue;
|
|
18135
18714
|
if (entry.name.startsWith(".")) continue;
|
|
18136
18715
|
if (packagesFilter !== void 0 && !packagesFilter.includes(entry.name)) continue;
|
|
18137
|
-
const srcDir =
|
|
18138
|
-
if (!
|
|
18716
|
+
const srcDir = path53.join(packagesDir, entry.name, "src");
|
|
18717
|
+
if (!fs50.existsSync(srcDir)) continue;
|
|
18139
18718
|
walk9(srcDir, out);
|
|
18140
18719
|
}
|
|
18141
18720
|
return out;
|
|
@@ -18143,13 +18722,13 @@ function discoverSourceFiles(projectRoot, packagesFilter) {
|
|
|
18143
18722
|
function walk9(dir, out) {
|
|
18144
18723
|
let entries;
|
|
18145
18724
|
try {
|
|
18146
|
-
entries =
|
|
18725
|
+
entries = fs50.readdirSync(dir, { withFileTypes: true });
|
|
18147
18726
|
} catch {
|
|
18148
18727
|
return;
|
|
18149
18728
|
}
|
|
18150
18729
|
for (const entry of entries) {
|
|
18151
18730
|
if (entry.name.startsWith(".")) continue;
|
|
18152
|
-
const full =
|
|
18731
|
+
const full = path53.join(dir, entry.name);
|
|
18153
18732
|
if (entry.isDirectory()) {
|
|
18154
18733
|
if (EXCLUDED_DIRS.has(entry.name)) continue;
|
|
18155
18734
|
walk9(full, out);
|
|
@@ -18157,7 +18736,7 @@ function walk9(dir, out) {
|
|
|
18157
18736
|
}
|
|
18158
18737
|
if (!entry.isFile()) continue;
|
|
18159
18738
|
if (TEST_FILE_PATTERN.test(entry.name)) continue;
|
|
18160
|
-
const ext =
|
|
18739
|
+
const ext = path53.extname(entry.name);
|
|
18161
18740
|
if (!SOURCE_EXTENSIONS2.has(ext)) continue;
|
|
18162
18741
|
out.push(full);
|
|
18163
18742
|
}
|
|
@@ -18654,7 +19233,7 @@ async function runSecurityCraft(input) {
|
|
|
18654
19233
|
for (const file of candidateFiles) {
|
|
18655
19234
|
let source;
|
|
18656
19235
|
try {
|
|
18657
|
-
source =
|
|
19236
|
+
source = fs51.readFileSync(file, "utf-8");
|
|
18658
19237
|
} catch {
|
|
18659
19238
|
continue;
|
|
18660
19239
|
}
|
|
@@ -18862,9 +19441,123 @@ function errorResponse(message) {
|
|
|
18862
19441
|
};
|
|
18863
19442
|
}
|
|
18864
19443
|
|
|
19444
|
+
// src/mcp/tools/acceptance-eval.ts
|
|
19445
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
19446
|
+
var acceptanceEvalDefinition = {
|
|
19447
|
+
name: "acceptance_eval",
|
|
19448
|
+
description: "Pre-execution LLM-judgment: does a spec carry measurable, testable, complete acceptance criteria? The upstream twin of outcome_eval. Reads the spec's success/acceptance section, emits a confidence-rated AcceptanceVerdict (MEASURABLE | NOT_MEASURABLE | INCONCLUSIVE) with criteriaFindings (a, advisory), coverageFindings (b, advisory) and a rationale. Authority is DERIVED in TypeScript, never trusted from the LLM: a high-confidence NOT_MEASURABLE is blocking; every other verdict is advisory. testGlobs/testContent are optional evidence for (b) \u2014 omitting them degrades coverage findings to advisory-empty but never affects the measurability gate.",
|
|
19449
|
+
inputSchema: {
|
|
19450
|
+
type: "object",
|
|
19451
|
+
properties: {
|
|
19452
|
+
specPath: {
|
|
19453
|
+
type: "string",
|
|
19454
|
+
description: "Absolute or repo-relative path to the spec markdown to judge"
|
|
19455
|
+
},
|
|
19456
|
+
testGlobs: {
|
|
19457
|
+
type: "array",
|
|
19458
|
+
items: { type: "string" },
|
|
19459
|
+
description: "Optional globs locating test files; their contents supply the (b) coverage evidence. Ignored when testContent is provided. Absolute globs are recommended; relative globs resolve against the MCP server cwd."
|
|
19460
|
+
},
|
|
19461
|
+
testContent: {
|
|
19462
|
+
type: "string",
|
|
19463
|
+
description: "Optional pre-collected test snippets (the (b) evidence). Takes precedence over testGlobs."
|
|
19464
|
+
},
|
|
19465
|
+
model: {
|
|
19466
|
+
type: "string",
|
|
19467
|
+
description: "Optional model override for the acceptance-eval LLM call"
|
|
19468
|
+
}
|
|
19469
|
+
},
|
|
19470
|
+
required: ["specPath"]
|
|
19471
|
+
}
|
|
19472
|
+
};
|
|
19473
|
+
async function resolveAnalysisProvider2(model) {
|
|
19474
|
+
try {
|
|
19475
|
+
const intelligence = await import("@harness-engineering/intelligence");
|
|
19476
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
19477
|
+
if (!apiKey) return null;
|
|
19478
|
+
const Provider = intelligence.AnthropicAnalysisProvider;
|
|
19479
|
+
if (typeof Provider !== "function") return null;
|
|
19480
|
+
return new Provider(model !== void 0 ? { apiKey, defaultModel: model } : { apiKey });
|
|
19481
|
+
} catch {
|
|
19482
|
+
return null;
|
|
19483
|
+
}
|
|
19484
|
+
}
|
|
19485
|
+
function validateInput2(input) {
|
|
19486
|
+
if (typeof input?.specPath !== "string" || input.specPath.length === 0) {
|
|
19487
|
+
return "acceptance_eval: `specPath` is required";
|
|
19488
|
+
}
|
|
19489
|
+
return null;
|
|
19490
|
+
}
|
|
19491
|
+
async function resolveTestContent(input) {
|
|
19492
|
+
if (typeof input.testContent === "string" && input.testContent.length > 0) {
|
|
19493
|
+
return input.testContent;
|
|
19494
|
+
}
|
|
19495
|
+
if (!Array.isArray(input.testGlobs) || input.testGlobs.length === 0) {
|
|
19496
|
+
return void 0;
|
|
19497
|
+
}
|
|
19498
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19499
|
+
const parts = [];
|
|
19500
|
+
for (const pattern of input.testGlobs) {
|
|
19501
|
+
const normalized = pattern.replace(/\\/g, "/");
|
|
19502
|
+
let files;
|
|
19503
|
+
try {
|
|
19504
|
+
files = await findFiles(normalized);
|
|
19505
|
+
} catch {
|
|
19506
|
+
continue;
|
|
19507
|
+
}
|
|
19508
|
+
for (const file of files) {
|
|
19509
|
+
if (seen.has(file)) continue;
|
|
19510
|
+
seen.add(file);
|
|
19511
|
+
try {
|
|
19512
|
+
parts.push(`// ${file}
|
|
19513
|
+
${await readFile2(file, "utf8")}`);
|
|
19514
|
+
} catch {
|
|
19515
|
+
}
|
|
19516
|
+
}
|
|
19517
|
+
}
|
|
19518
|
+
return parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
19519
|
+
}
|
|
19520
|
+
async function handleAcceptanceEval(input) {
|
|
19521
|
+
const validationError = validateInput2(input);
|
|
19522
|
+
if (validationError !== null) return errorResponse2(validationError);
|
|
19523
|
+
try {
|
|
19524
|
+
const { AcceptanceEvaluator } = await import("@harness-engineering/intelligence");
|
|
19525
|
+
const provider = await resolveAnalysisProvider2(input.model);
|
|
19526
|
+
const evaluator = new AcceptanceEvaluator(
|
|
19527
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
19528
|
+
provider ?? unconfiguredProvider2(),
|
|
19529
|
+
input.model !== void 0 ? { model: input.model } : {}
|
|
19530
|
+
);
|
|
19531
|
+
const testContent = await resolveTestContent(input);
|
|
19532
|
+
const verdict = await evaluator.evaluate({
|
|
19533
|
+
specPath: input.specPath,
|
|
19534
|
+
...testContent !== void 0 && { testContent }
|
|
19535
|
+
});
|
|
19536
|
+
return { content: [{ type: "text", text: JSON.stringify(verdict, null, 2) }] };
|
|
19537
|
+
} catch (err2) {
|
|
19538
|
+
const message = err2 instanceof Error ? err2.message : String(err2);
|
|
19539
|
+
return errorResponse2(`acceptance_eval failed: ${message}`);
|
|
19540
|
+
}
|
|
19541
|
+
}
|
|
19542
|
+
function unconfiguredProvider2() {
|
|
19543
|
+
return {
|
|
19544
|
+
analyze: () => Promise.reject(
|
|
19545
|
+
new Error(
|
|
19546
|
+
"No analysis provider configured (set ANTHROPIC_API_KEY). Degrading to an inconclusive, advisory verdict."
|
|
19547
|
+
)
|
|
19548
|
+
)
|
|
19549
|
+
};
|
|
19550
|
+
}
|
|
19551
|
+
function errorResponse2(message) {
|
|
19552
|
+
return {
|
|
19553
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
19554
|
+
isError: true
|
|
19555
|
+
};
|
|
19556
|
+
}
|
|
19557
|
+
|
|
18865
19558
|
// src/mcp/tools/strategy.ts
|
|
18866
|
-
import * as
|
|
18867
|
-
import * as
|
|
19559
|
+
import * as fs52 from "fs/promises";
|
|
19560
|
+
import * as path54 from "path";
|
|
18868
19561
|
var STRATEGY_FILENAME = "STRATEGY.md";
|
|
18869
19562
|
function mcpJson(payload, isError = false) {
|
|
18870
19563
|
return { content: [{ type: "text", text: JSON.stringify(payload) }], isError };
|
|
@@ -18890,7 +19583,7 @@ async function handleValidateStrategy(input) {
|
|
|
18890
19583
|
} catch (error) {
|
|
18891
19584
|
return mcpError2(error instanceof Error ? error.message : String(error));
|
|
18892
19585
|
}
|
|
18893
|
-
const { validateStrategy } = await import("./dist-
|
|
19586
|
+
const { validateStrategy } = await import("./dist-IPVFYGY2.js");
|
|
18894
19587
|
const result = await validateStrategy(projectPath);
|
|
18895
19588
|
if (!result.ok) {
|
|
18896
19589
|
return mcpJson({ present: true, valid: false, error: result.error.message });
|
|
@@ -18915,7 +19608,7 @@ async function handleReadStrategy(input) {
|
|
|
18915
19608
|
} catch (error) {
|
|
18916
19609
|
return mcpError2(error instanceof Error ? error.message : String(error));
|
|
18917
19610
|
}
|
|
18918
|
-
const core = await import("./dist-
|
|
19611
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
18919
19612
|
const validation = await core.validateStrategy(projectPath);
|
|
18920
19613
|
if (!validation.ok) {
|
|
18921
19614
|
return mcpJson({ present: true, valid: false, error: validation.error.message });
|
|
@@ -18923,10 +19616,10 @@ async function handleReadStrategy(input) {
|
|
|
18923
19616
|
if (!validation.value.present) {
|
|
18924
19617
|
return mcpJson({ present: false, valid: true });
|
|
18925
19618
|
}
|
|
18926
|
-
const strategyPath =
|
|
19619
|
+
const strategyPath = path54.join(projectPath, STRATEGY_FILENAME);
|
|
18927
19620
|
let raw;
|
|
18928
19621
|
try {
|
|
18929
|
-
raw = await
|
|
19622
|
+
raw = await fs52.readFile(strategyPath, "utf-8");
|
|
18930
19623
|
} catch (error) {
|
|
18931
19624
|
return mcpError2(error instanceof Error ? error.message : String(error));
|
|
18932
19625
|
}
|
|
@@ -18967,7 +19660,7 @@ async function handleWriteStrategy(input) {
|
|
|
18967
19660
|
} catch (error) {
|
|
18968
19661
|
return mcpError2(error instanceof Error ? error.message : String(error));
|
|
18969
19662
|
}
|
|
18970
|
-
const core = await import("./dist-
|
|
19663
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
18971
19664
|
try {
|
|
18972
19665
|
core.writeStrategyDoc(input.doc, {
|
|
18973
19666
|
cwd: projectPath,
|
|
@@ -18982,11 +19675,11 @@ async function handleWriteStrategy(input) {
|
|
|
18982
19675
|
true
|
|
18983
19676
|
);
|
|
18984
19677
|
}
|
|
18985
|
-
return mcpJson({ written: true, path:
|
|
19678
|
+
return mcpJson({ written: true, path: path54.join(projectPath, STRATEGY_FILENAME) });
|
|
18986
19679
|
}
|
|
18987
19680
|
|
|
18988
19681
|
// src/mcp/tools/pulse.ts
|
|
18989
|
-
import * as
|
|
19682
|
+
import * as path55 from "path";
|
|
18990
19683
|
function mcpJson2(payload, isError = false) {
|
|
18991
19684
|
return { content: [{ type: "text", text: JSON.stringify(payload) }], isError };
|
|
18992
19685
|
}
|
|
@@ -19011,7 +19704,7 @@ async function handleSeedPulseFromStrategy(input) {
|
|
|
19011
19704
|
} catch (error) {
|
|
19012
19705
|
return mcpError3(error instanceof Error ? error.message : String(error));
|
|
19013
19706
|
}
|
|
19014
|
-
const core = await import("./dist-
|
|
19707
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
19015
19708
|
const seed = core.seedFromStrategy({ cwd: projectPath });
|
|
19016
19709
|
return mcpJson2(seed);
|
|
19017
19710
|
}
|
|
@@ -19045,8 +19738,8 @@ async function handleWritePulseConfig(input) {
|
|
|
19045
19738
|
} catch (error) {
|
|
19046
19739
|
return mcpError3(error instanceof Error ? error.message : String(error));
|
|
19047
19740
|
}
|
|
19048
|
-
const resolvedConfigPath = input.configPath !== void 0 ?
|
|
19049
|
-
const core = await import("./dist-
|
|
19741
|
+
const resolvedConfigPath = input.configPath !== void 0 ? path55.isAbsolute(input.configPath) ? input.configPath : path55.join(projectPath, input.configPath) : path55.join(projectPath, "harness.config.json");
|
|
19742
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
19050
19743
|
try {
|
|
19051
19744
|
core.writePulseConfig(input.config, {
|
|
19052
19745
|
configPath: resolvedConfigPath,
|
|
@@ -19095,7 +19788,7 @@ async function handleAcquireCompoundLock(input) {
|
|
|
19095
19788
|
} catch (error) {
|
|
19096
19789
|
return mcpError4(error instanceof Error ? error.message : String(error));
|
|
19097
19790
|
}
|
|
19098
|
-
const core = await import("./dist-
|
|
19791
|
+
const core = await import("./dist-IPVFYGY2.js");
|
|
19099
19792
|
try {
|
|
19100
19793
|
const handle = core.acquireCompoundLock(
|
|
19101
19794
|
input.category,
|
|
@@ -19256,6 +19949,7 @@ var TOOL_DEFINITIONS = [
|
|
|
19256
19949
|
knowledgeCraftDefinition,
|
|
19257
19950
|
securityCraftDefinition,
|
|
19258
19951
|
outcomeEvalDefinition,
|
|
19952
|
+
acceptanceEvalDefinition,
|
|
19259
19953
|
validateStrategyDefinition,
|
|
19260
19954
|
readStrategyDefinition,
|
|
19261
19955
|
writeStrategyDefinition,
|
|
@@ -19349,6 +20043,7 @@ var TOOL_HANDLERS = {
|
|
|
19349
20043
|
knowledge_craft: handleKnowledgeCraft,
|
|
19350
20044
|
security_craft: handleSecurityCraft,
|
|
19351
20045
|
outcome_eval: handleOutcomeEval,
|
|
20046
|
+
acceptance_eval: handleAcceptanceEval,
|
|
19352
20047
|
validate_strategy: handleValidateStrategy,
|
|
19353
20048
|
read_strategy: handleReadStrategy,
|
|
19354
20049
|
write_strategy: handleWriteStrategy,
|
|
@@ -19457,7 +20152,7 @@ async function appendUpdateNotification(result, resolvedRoot) {
|
|
|
19457
20152
|
shouldRunCheck,
|
|
19458
20153
|
readCheckState,
|
|
19459
20154
|
spawnBackgroundCheck
|
|
19460
|
-
} = await import("./dist-
|
|
20155
|
+
} = await import("./dist-IPVFYGY2.js");
|
|
19461
20156
|
const { CLI_VERSION } = await import("./version-CUI434OP.js");
|
|
19462
20157
|
const configInterval = readConfigInterval(resolvedRoot);
|
|
19463
20158
|
const DEFAULT_INTERVAL = 864e5;
|